Merge "Use language tags to store Configuration's locale list."
diff --git a/Android.bp b/Android.bp
index 121decb..ee3cb8f 100644
--- a/Android.bp
+++ b/Android.bp
@@ -234,6 +234,11 @@
"wifi/java",
],
},
+
+ required: [
+ // TODO: remove gps_debug when the build system propagates "required" properly.
+ "gps_debug.conf",
+ ],
}
// Collection of classes that are generated from non-Java files that are not listed in
@@ -307,11 +312,6 @@
static_libs: ["framework-internal-utils"],
- required: [
- // TODO: remove gps_debug when the build system propagates "required" properly.
- "gps_debug.conf",
- ],
-
dxflags: [
"--core-library",
"--multi-dex",
diff --git a/api/current.txt b/api/current.txt
index 26c90df..e3fde23 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -16906,9 +16906,10 @@
method @NonNull public abstract android.hardware.camera2.CaptureRequest.Builder createReprocessCaptureRequest(@NonNull android.hardware.camera2.TotalCaptureResult) throws android.hardware.camera2.CameraAccessException;
method public abstract void createReprocessableCaptureSession(@NonNull android.hardware.camera2.params.InputConfiguration, @NonNull java.util.List<android.view.Surface>, @NonNull android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException;
method public abstract void createReprocessableCaptureSessionByConfigurations(@NonNull android.hardware.camera2.params.InputConfiguration, @NonNull java.util.List<android.hardware.camera2.params.OutputConfiguration>, @NonNull android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException;
+ method public int getCameraAudioRestriction() throws android.hardware.camera2.CameraAccessException;
method @NonNull public abstract String getId();
method public boolean isSessionConfigurationSupported(@NonNull android.hardware.camera2.params.SessionConfiguration) throws android.hardware.camera2.CameraAccessException;
- method public int setCameraAudioRestriction(int) throws android.hardware.camera2.CameraAccessException;
+ method public void setCameraAudioRestriction(int) throws android.hardware.camera2.CameraAccessException;
field public static final int AUDIO_RESTRICTION_NONE = 0; // 0x0
field public static final int AUDIO_RESTRICTION_VIBRATION = 1; // 0x1
field public static final int AUDIO_RESTRICTION_VIBRATION_SOUND = 3; // 0x3
diff --git a/cmds/app_process/Android.bp b/cmds/app_process/Android.bp
index f925023..8be95e4 100644
--- a/cmds/app_process/Android.bp
+++ b/cmds/app_process/Android.bp
@@ -22,7 +22,6 @@
"libcutils",
"libdl",
"libhidlbase",
- "libhwbinder",
"liblog",
"libnativeloader",
"libutils",
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 15c6425..287a289 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -146,6 +146,7 @@
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -3646,6 +3647,22 @@
return false;
}
+ private static final class RequestFinishCallback extends IRequestFinishCallback.Stub {
+ private final WeakReference<Activity> mActivityRef;
+
+ RequestFinishCallback(WeakReference<Activity> activityRef) {
+ mActivityRef = activityRef;
+ }
+
+ @Override
+ public void requestFinish() {
+ Activity activity = mActivityRef.get();
+ if (activity != null) {
+ activity.mHandler.post(activity::finishAfterTransition);
+ }
+ }
+ }
+
/**
* Called when the activity has detected the user's press of the back
* key. The default implementation simply finishes the current activity,
@@ -3671,11 +3688,7 @@
// while at the root of the task. This call allows ActivityTaskManager
// to intercept or defer finishing.
ActivityTaskManager.getService().onBackPressedOnTaskRoot(mToken,
- new IRequestFinishCallback.Stub() {
- public void requestFinish() {
- mHandler.post(() -> finishAfterTransition());
- }
- });
+ new RequestFinishCallback(new WeakReference<>(this)));
} catch (RemoteException e) {
finishAfterTransition();
}
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 1f283ea..bb87d96 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -5845,39 +5845,42 @@
ActivityThread.currentOpPackageName())) {
// This app is noting an app-op for itself. Deliver immediately.
sNotedAppOpsCollector.onSelfNoted(new SyncNotedAppOp(code));
- } else if (binderUid != null && binderUid == uid) {
- // We are inside of a two-way binder call. Delivered to caller via
- // {@link #prefixParcelWithAppOpsIfNeeded}
- long[] appOpsNotedInThisBinderTransaction;
- appOpsNotedInThisBinderTransaction = sAppOpsNotedInThisBinderTransaction.get();
- if (appOpsNotedInThisBinderTransaction == null) {
- appOpsNotedInThisBinderTransaction = new long[2];
- sAppOpsNotedInThisBinderTransaction.set(appOpsNotedInThisBinderTransaction);
- }
+ return;
+ }
+ }
- if (code < 64) {
- appOpsNotedInThisBinderTransaction[0] |= 1L << code;
- } else {
- appOpsNotedInThisBinderTransaction[1] |= 1L << (code - 64);
- }
+ if (binderUid != null && binderUid == uid) {
+ // If this is inside of a two-way binder call: Delivered to caller via
+ // {@link #prefixParcelWithAppOpsIfNeeded}
+ long[] appOpsNotedInThisBinderTransaction;
+
+ appOpsNotedInThisBinderTransaction = sAppOpsNotedInThisBinderTransaction.get();
+ if (appOpsNotedInThisBinderTransaction == null) {
+ appOpsNotedInThisBinderTransaction = new long[2];
+ sAppOpsNotedInThisBinderTransaction.set(appOpsNotedInThisBinderTransaction);
+ }
+
+ if (code < 64) {
+ appOpsNotedInThisBinderTransaction[0] |= 1L << code;
} else {
- // We cannot deliver the note synchronous. Hence send it to the system server to
- // notify the noted process.
- if (message == null) {
- // Default message is a stack trace
- message = getFormattedStackTrace();
- }
+ appOpsNotedInThisBinderTransaction[1] |= 1L << (code - 64);
+ }
+ } else {
+ // Cannot deliver the note synchronous: Hence send it to the system server to
+ // notify the noted process.
+ if (message == null) {
+ // Default message is a stack trace
+ message = getFormattedStackTrace();
+ }
- long token = Binder.clearCallingIdentity();
- try {
- mService.noteAsyncOp(mContext.getOpPackageName(), uid, packageName, code,
- message);
- } catch (RemoteException e) {
- e.rethrowFromSystemServer();
- } finally {
- Binder.restoreCallingIdentity(token);
- }
+ long token = Binder.clearCallingIdentity();
+ try {
+ mService.noteAsyncOp(mContext.getOpPackageName(), uid, packageName, code, message);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ } finally {
+ Binder.restoreCallingIdentity(token);
}
}
}
diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java
index b8ab114..8e8a81d 100644
--- a/core/java/android/hardware/Camera.java
+++ b/core/java/android/hardware/Camera.java
@@ -2169,11 +2169,18 @@
}
/**
- * Setting camera audio restriction mode.
+ * Set camera audio restriction mode.
*
* @hide
*/
- public native final int setAudioRestriction(int mode);
+ public native final void setAudioRestriction(int mode);
+
+ /**
+ * Get currently applied camera audio restriction mode.
+ *
+ * @hide
+ */
+ public native final int getAudioRestriction();
/**
* Image size (width and height dimensions).
diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java
index ed2d0c9..0714884 100644
--- a/core/java/android/hardware/camera2/CameraDevice.java
+++ b/core/java/android/hardware/camera2/CameraDevice.java
@@ -1251,25 +1251,43 @@
* are setting different modes, the system will pick a the mode that's union of
* all modes set by CameraDevice.</p>
*
- * <p>The mute settings will be automatically removed when the CameraDevice is closed or
- * the application is disconnected from the camera.</p>
+ * <p>The mute settings from this CameraDevice will be automatically removed when the
+ * CameraDevice is closed or the application is disconnected from the camera.</p>
*
* @param mode An enumeration selecting the audio restriction mode for this camera device.
*
- * @return The system-wide mute mode setting resulting from this call
- *
* @throws IllegalArgumentException if the mode is not supported
*
* @throws CameraAccessException if the camera device is no longer connected or has
* encountered a fatal error
* @throws IllegalStateException if the camera device has been closed
+ *
+ * @see #getCameraAudioRestriction
*/
- public @CAMERA_AUDIO_RESTRICTION int setCameraAudioRestriction(
+ public void setCameraAudioRestriction(
@CAMERA_AUDIO_RESTRICTION int mode) throws CameraAccessException {
throw new UnsupportedOperationException("Subclasses must override this method");
}
/**
+ * Get currently applied global camera audio restriction mode.
+ *
+ * <p>Application can use this method to retrieve the system-wide camera audio restriction
+ * settings described in {@link #setCameraAudioRestriction}.</p>
+ *
+ * @return The system-wide mute mode setting resulting from this call
+ *
+ * @throws CameraAccessException if the camera device is no longer connected or has
+ * encountered a fatal error
+ * @throws IllegalStateException if the camera device has been closed
+ *
+ * @see #setCameraAudioRestriction
+ */
+ public @CAMERA_AUDIO_RESTRICTION int getCameraAudioRestriction() throws CameraAccessException {
+ throw new UnsupportedOperationException("Subclasses must override this method");
+ }
+
+ /**
* To be inherited by android.hardware.camera2.* code only.
* @hide
*/
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
index 1f385dd..4494d27 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
@@ -2570,11 +2570,19 @@
}
@Override
- public @CAMERA_AUDIO_RESTRICTION int setCameraAudioRestriction(
+ public void setCameraAudioRestriction(
@CAMERA_AUDIO_RESTRICTION int mode) throws CameraAccessException {
synchronized(mInterfaceLock) {
checkIfCameraClosedOrInError();
- return mRemoteDevice.setCameraAudioRestriction(mode);
+ mRemoteDevice.setCameraAudioRestriction(mode);
+ }
+ }
+
+ @Override
+ public @CAMERA_AUDIO_RESTRICTION int getCameraAudioRestriction() throws CameraAccessException {
+ synchronized(mInterfaceLock) {
+ checkIfCameraClosedOrInError();
+ return mRemoteDevice.getGlobalAudioRestriction();
}
}
}
diff --git a/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java b/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java
index ff2819b..3660f29 100644
--- a/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java
+++ b/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java
@@ -258,9 +258,18 @@
}
}
- public int setCameraAudioRestriction(int mode) throws CameraAccessException {
+ public void setCameraAudioRestriction(int mode) throws CameraAccessException {
try {
- return mRemoteDevice.setCameraAudioRestriction(mode);
+ mRemoteDevice.setCameraAudioRestriction(mode);
+ } catch (Throwable t) {
+ CameraManager.throwAsPublicException(t);
+ throw new UnsupportedOperationException("Unexpected exception", t);
+ }
+ }
+
+ public int getGlobalAudioRestriction() throws CameraAccessException {
+ try {
+ return mRemoteDevice.getGlobalAudioRestriction();
} catch (Throwable t) {
CameraManager.throwAsPublicException(t);
throw new UnsupportedOperationException("Unexpected exception", t);
diff --git a/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java b/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java
index 0fa17c1..5d1435a 100644
--- a/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java
+++ b/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java
@@ -767,14 +767,25 @@
}
@Override
- public int setCameraAudioRestriction(int mode) {
+ public void setCameraAudioRestriction(int mode) {
if (mLegacyDevice.isClosed()) {
String err = "Cannot set camera audio restriction, device has been closed.";
Log.e(TAG, err);
throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err);
}
- return mLegacyDevice.setAudioRestriction(mode);
+ mLegacyDevice.setAudioRestriction(mode);
+ }
+
+ @Override
+ public int getGlobalAudioRestriction() {
+ if (mLegacyDevice.isClosed()) {
+ String err = "Cannot set camera audio restriction, device has been closed.";
+ Log.e(TAG, err);
+ throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err);
+ }
+
+ return mLegacyDevice.getAudioRestriction();
}
@Override
diff --git a/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java b/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java
index c0d8baa..fbc9ac32 100644
--- a/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java
+++ b/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java
@@ -550,8 +550,12 @@
return lastFrame;
}
- public int setAudioRestriction(int mode) {
- return mRequestThreadManager.setAudioRestriction(mode);
+ public void setAudioRestriction(int mode) {
+ mRequestThreadManager.setAudioRestriction(mode);
+ }
+
+ public int getAudioRestriction() {
+ return mRequestThreadManager.getAudioRestriction();
}
/**
diff --git a/core/java/android/hardware/camera2/legacy/RequestThreadManager.java b/core/java/android/hardware/camera2/legacy/RequestThreadManager.java
index a514ee1..32411fb 100644
--- a/core/java/android/hardware/camera2/legacy/RequestThreadManager.java
+++ b/core/java/android/hardware/camera2/legacy/RequestThreadManager.java
@@ -1105,9 +1105,16 @@
condition.block();
}
- public int setAudioRestriction(int mode) {
+ public void setAudioRestriction(int mode) {
if (mCamera != null) {
- return mCamera.setAudioRestriction(mode);
+ mCamera.setAudioRestriction(mode);
+ }
+ throw new IllegalStateException("Camera has been released!");
+ }
+
+ public int getAudioRestriction() {
+ if (mCamera != null) {
+ return mCamera.getAudioRestriction();
}
throw new IllegalStateException("Camera has been released!");
}
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index b5d4945..31995f7 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -460,7 +460,7 @@
ZygoteHooks.gcAndFinalize();
}
- private static boolean profileSystemServer() {
+ private static boolean shouldProfileSystemServer() {
boolean defaultValue = SystemProperties.getBoolean("dalvik.vm.profilesystemserver",
/*default=*/ false);
// Can't use DeviceConfig since it's not initialized at this point.
@@ -492,7 +492,7 @@
}
// Capturing profiles is only supported for debug or eng builds since selinux normally
// prevents it.
- if (profileSystemServer() && (Build.IS_USERDEBUG || Build.IS_ENG)) {
+ if (shouldProfileSystemServer() && (Build.IS_USERDEBUG || Build.IS_ENG)) {
try {
Log.d(TAG, "Preparing system server profile");
prepareSystemServerProfile(systemServerClasspath);
@@ -765,8 +765,7 @@
Zygote.applyDebuggerSystemProperty(parsedArgs);
Zygote.applyInvokeWithSystemProperty(parsedArgs);
- if (profileSystemServer()) {
-
+ if (shouldProfileSystemServer()) {
parsedArgs.mRuntimeFlags |= Zygote.PROFILE_SYSTEM_SERVER;
}
diff --git a/core/jni/android_hardware_Camera.cpp b/core/jni/android_hardware_Camera.cpp
index f87163b..bc69735 100644
--- a/core/jni/android_hardware_Camera.cpp
+++ b/core/jni/android_hardware_Camera.cpp
@@ -1023,22 +1023,38 @@
}
}
-static int32_t android_hardware_Camera_setAudioRestriction(
+static void android_hardware_Camera_setAudioRestriction(
JNIEnv *env, jobject thiz, jint mode)
{
ALOGV("setAudioRestriction");
sp<Camera> camera = get_native_camera(env, thiz, NULL);
if (camera == 0) {
jniThrowRuntimeException(env, "camera has been disconnected");
- return -1;
+ return;
}
int32_t ret = camera->setAudioRestriction(mode);
if (ret < 0) {
jniThrowRuntimeException(env, "Illegal argument or low-level eror");
+ return;
+ }
+}
+
+static int32_t android_hardware_Camera_getAudioRestriction(
+ JNIEnv *env, jobject thiz)
+{
+ ALOGV("getAudioRestriction");
+ sp<Camera> camera = get_native_camera(env, thiz, NULL);
+ if (camera == 0) {
+ jniThrowRuntimeException(env, "camera has been disconnected");
return -1;
}
+ int32_t ret = camera->getGlobalAudioRestriction();
+ if (ret < 0) {
+ jniThrowRuntimeException(env, "Illegal argument or low-level eror");
+ return -1;
+ }
return ret;
}
@@ -1127,8 +1143,11 @@
"(I)V",
(void *)android_hardware_Camera_enableFocusMoveCallback},
{ "setAudioRestriction",
- "(I)I",
+ "(I)V",
(void *)android_hardware_Camera_setAudioRestriction},
+ { "getAudioRestriction",
+ "()I",
+ (void *)android_hardware_Camera_getAudioRestriction},
};
struct field {
diff --git a/core/jni/android_os_Debug.cpp b/core/jni/android_os_Debug.cpp
index 13e1dfa..cf8df28 100644
--- a/core/jni/android_os_Debug.cpp
+++ b/core/jni/android_os_Debug.cpp
@@ -564,11 +564,13 @@
// Read system memory info including ZRAM. The values are stored in the vector
// in the same order as MEMINFO_* enum
- std::vector<uint64_t> mem(MEMINFO_COUNT);
- std::vector<std::string> tags(::android::meminfo::SysMemInfo::kDefaultSysMemInfoTags);
+ std::vector<std::string_view> tags(
+ ::android::meminfo::SysMemInfo::kDefaultSysMemInfoTags.begin(),
+ ::android::meminfo::SysMemInfo::kDefaultSysMemInfoTags.end());
tags.insert(tags.begin() + MEMINFO_ZRAM_TOTAL, "Zram:");
+ std::vector<uint64_t> mem(tags.size());
::android::meminfo::SysMemInfo smi;
- if (!smi.ReadMemInfo(tags, &mem)) {
+ if (!smi.ReadMemInfo(tags.size(), tags.data(), mem.data())) {
jniThrowRuntimeException(env, "SysMemInfo read failed");
return;
}
diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp
index 4cb2d15..8aa6f86 100644
--- a/core/jni/android_util_Process.cpp
+++ b/core/jni/android_util_Process.cpp
@@ -31,6 +31,7 @@
#include <android-base/unique_fd.h>
#include <algorithm>
+#include <array>
#include <limits>
#include <memory>
#include <string>
@@ -630,14 +631,16 @@
static jlong android_os_Process_getFreeMemory(JNIEnv* env, jobject clazz)
{
- static const std::vector<std::string> memFreeTags = {
+ std::array<std::string_view, 2> memFreeTags = {
::android::meminfo::SysMemInfo::kMemFree,
::android::meminfo::SysMemInfo::kMemCached,
};
std::vector<uint64_t> mem(memFreeTags.size());
::android::meminfo::SysMemInfo smi;
- if (!smi.ReadMemInfo(memFreeTags, &mem)) {
+ if (!smi.ReadMemInfo(memFreeTags.size(),
+ memFreeTags.data(),
+ mem.data())) {
jniThrowRuntimeException(env, "SysMemInfo read failed to get Free Memory");
return -1L;
}
diff --git a/core/proto/Android.bp b/core/proto/Android.bp
index 3b891d6..e199dab 100644
--- a/core/proto/Android.bp
+++ b/core/proto/Android.bp
@@ -28,3 +28,13 @@
"android/bluetooth/smp/enums.proto",
],
}
+
+java_library_host {
+ name: "windowmanager-log-proto",
+ srcs: [
+ "android/server/windowmanagerlog.proto"
+ ],
+ proto: {
+ type: "full",
+ },
+}
diff --git a/core/proto/android/server/windowmanagerlog.proto b/core/proto/android/server/windowmanagerlog.proto
new file mode 100644
index 0000000..5bee1bd
--- /dev/null
+++ b/core/proto/android/server/windowmanagerlog.proto
@@ -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.
+ */
+
+syntax = "proto2";
+
+package com.android.server.wm;
+
+option java_multiple_files = true;
+
+/* represents a single log entry */
+message ProtoLogMessage {
+ /* log statement identifier, created from message string and log level. */
+ optional fixed32 message_hash = 1;
+ /* log time, relative to the elapsed system time clock. */
+ optional fixed64 elapsed_realtime_nanos = 2;
+ /* string parameters passed to the log call. */
+ repeated string str_params = 3;
+ /* integer parameters passed to the log call. */
+ repeated sint64 sint64_params = 4 [packed=true];
+ /* floating point parameters passed to the log call. */
+ repeated double double_params = 5 [packed=true];
+ /* boolean parameters passed to the log call. */
+ repeated bool boolean_params = 6 [packed=true];
+}
+
+/* represents a log file containing window manager log entries.
+ Encoded, it should start with 0x9 0x57 0x49 0x4e 0x44 0x4f 0x4c 0x4f 0x47 (.WINDOLOG), such
+ that they can be easily identified. */
+message WindowManagerLogFileProto {
+ /* constant; MAGIC_NUMBER = (long) MAGIC_NUMBER_H << 32 | MagicNumber.MAGIC_NUMBER_L
+ (this is needed because enums have to be 32 bits and there's no nice way to put 64bit
+ constants into .proto files. */
+ enum MagicNumber {
+ INVALID = 0;
+ MAGIC_NUMBER_L = 0x444e4957; /* WIND (little-endian ASCII) */
+ MAGIC_NUMBER_H = 0x474f4c4f; /* OLOG (little-endian ASCII) */
+ }
+
+ /* the magic number header */
+ optional fixed64 magic_number = 1;
+ /* log proto version. */
+ optional string version = 2;
+ /* offset between real-time clock and elapsed system time clock in miliseconds.
+ Calculated as: (System.currentTimeMillis() - (SystemClock.elapsedRealtimeNanos() / 1000000) */
+ optional fixed64 realTimeToElapsedTimeOffsetMillis = 3;
+ /* log entries */
+ repeated ProtoLogMessage log = 4;
+}
diff --git a/core/proto/android/stats/docsui/docsui_enums.proto b/core/proto/android/stats/docsui/docsui_enums.proto
index 655b5e3..f648912 100644
--- a/core/proto/android/stats/docsui/docsui_enums.proto
+++ b/core/proto/android/stats/docsui/docsui_enums.proto
@@ -184,6 +184,8 @@
TYPE_CHIP_DOCS = 4;
TYPE_SEARCH_HISTORY = 5;
TYPE_SEARCH_STRING = 6;
+ TYPE_CHIP_LARGE_FILES = 7;
+ TYPE_CHIP_FROM_THIS_WEEK = 8;
}
enum SearchMode {
diff --git a/core/res/res/values-en-rCA/strings.xml b/core/res/res/values-en-rCA/strings.xml
index 797d75f..839aaac 100644
--- a/core/res/res/values-en-rCA/strings.xml
+++ b/core/res/res/values-en-rCA/strings.xml
@@ -95,6 +95,7 @@
<string name="notification_channel_voice_mail" msgid="3954099424160511919">"Voicemail messages"</string>
<string name="notification_channel_wfc" msgid="2130802501654254801">"Wi-Fi Calling"</string>
<string name="notification_channel_sim" msgid="4052095493875188564">"SIM status"</string>
+ <string name="notification_channel_sim_high_prio" msgid="1787666807724243207">"High priority SIM status"</string>
<string name="peerTtyModeFull" msgid="6165351790010341421">"Peer requested TTY Mode FULL"</string>
<string name="peerTtyModeHco" msgid="5728602160669216784">"Peer requested TTY Mode HCO"</string>
<string name="peerTtyModeVco" msgid="1742404978686538049">"Peer requested TTY Mode VCO"</string>
@@ -1256,10 +1257,10 @@
<string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Tap to see all networks"</string>
<string name="wifi_available_action_connect" msgid="2635699628459488788">"Connect"</string>
<string name="wifi_available_action_all_networks" msgid="4368435796357931006">"All networks"</string>
- <string name="wifi_suggestion_title" msgid="9099832833531486167">"Connect to Wi‑Fi networks?"</string>
- <string name="wifi_suggestion_content" msgid="5883181205841582873">"Suggested by <xliff:g id="NAME">%s</xliff:g>"</string>
- <string name="wifi_suggestion_action_allow_app" msgid="3689946344485394085">"Yes"</string>
- <string name="wifi_suggestion_action_disallow_app" msgid="7977918905605931385">"No"</string>
+ <string name="wifi_suggestion_title" msgid="6396033039578436801">"Allow suggested Wi‑Fi networks?"</string>
+ <string name="wifi_suggestion_content" msgid="5603992011371520746">"<xliff:g id="NAME">%s</xliff:g> suggested networks. Device may connect automatically."</string>
+ <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"Allow"</string>
+ <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"No, thanks"</string>
<string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi‑Fi will turn on automatically"</string>
<string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"When you\'re near a high‑quality saved network"</string>
<string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Don\'t turn back on"</string>
diff --git a/core/res/res/values-en-rXC/strings.xml b/core/res/res/values-en-rXC/strings.xml
index 7ea7650..7961a86 100644
--- a/core/res/res/values-en-rXC/strings.xml
+++ b/core/res/res/values-en-rXC/strings.xml
@@ -95,6 +95,7 @@
<string name="notification_channel_voice_mail" msgid="3954099424160511919">"Voicemail messages"</string>
<string name="notification_channel_wfc" msgid="2130802501654254801">"Wi-Fi calling"</string>
<string name="notification_channel_sim" msgid="4052095493875188564">"SIM status"</string>
+ <string name="notification_channel_sim_high_prio" msgid="1787666807724243207">"High priority SIM status"</string>
<string name="peerTtyModeFull" msgid="6165351790010341421">"Peer requested TTY Mode FULL"</string>
<string name="peerTtyModeHco" msgid="5728602160669216784">"Peer requested TTY Mode HCO"</string>
<string name="peerTtyModeVco" msgid="1742404978686538049">"Peer requested TTY Mode VCO"</string>
@@ -1256,10 +1257,10 @@
<string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Tap to see all networks"</string>
<string name="wifi_available_action_connect" msgid="2635699628459488788">"Connect"</string>
<string name="wifi_available_action_all_networks" msgid="4368435796357931006">"All networks"</string>
- <string name="wifi_suggestion_title" msgid="9099832833531486167">"Connect to Wi‑Fi networks?"</string>
- <string name="wifi_suggestion_content" msgid="5883181205841582873">"Suggested by <xliff:g id="NAME">%s</xliff:g>"</string>
- <string name="wifi_suggestion_action_allow_app" msgid="3689946344485394085">"Yes"</string>
- <string name="wifi_suggestion_action_disallow_app" msgid="7977918905605931385">"No"</string>
+ <string name="wifi_suggestion_title" msgid="6396033039578436801">"Allow suggested Wi‑Fi networks?"</string>
+ <string name="wifi_suggestion_content" msgid="5603992011371520746">"<xliff:g id="NAME">%s</xliff:g> suggested networks. Device may connect automatically."</string>
+ <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"Allow"</string>
+ <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"No thanks"</string>
<string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi‑Fi will turn on automatically"</string>
<string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"When you\'re near a high quality saved network"</string>
<string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Don\'t turn back on"</string>
diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml
index 1346b86..0229cf5 100644
--- a/core/res/res/values-es-rUS/strings.xml
+++ b/core/res/res/values-es-rUS/strings.xml
@@ -30,7 +30,7 @@
<string name="untitled" msgid="4638956954852782576">"<Sin título>"</string>
<string name="emptyPhoneNumber" msgid="7694063042079676517">"(No hay número de teléfono)"</string>
<string name="unknownName" msgid="6867811765370350269">"Desconocido"</string>
- <string name="defaultVoiceMailAlphaTag" msgid="2660020990097733077">"Correo de voz"</string>
+ <string name="defaultVoiceMailAlphaTag" msgid="2660020990097733077">"Buzón de voz"</string>
<string name="defaultMsisdnAlphaTag" msgid="2850889754919584674">"MSISDN1"</string>
<string name="mmiError" msgid="5154499457739052907">"Problema de conexión o código incorrecto de MMI."</string>
<string name="mmiFdnError" msgid="5224398216385316471">"La operación está limitada a números de marcación fija."</string>
@@ -944,7 +944,7 @@
<string name="permdesc_writeHistoryBookmarks" product="default" msgid="8497389531014185509">"Permite que la aplicación modifique el historial o los favoritos del navegador almacenados en el dispositivo. La aplicación puede utilizar este permiso para borrar o modificar los datos del navegador. Nota: Este permiso no puede ser utilizado por navegadores externos ni otras aplicaciones que tengan funciones de navegación por Internet."</string>
<string name="permlab_setAlarm" msgid="1379294556362091814">"programar una alarma"</string>
<string name="permdesc_setAlarm" msgid="316392039157473848">"Permite que la aplicación establezca una alarma en una aplicación de alarma instalada. Es posible que algunas aplicaciones de alarma no incluyan esta función."</string>
- <string name="permlab_addVoicemail" msgid="5525660026090959044">"agregar correo de voz"</string>
+ <string name="permlab_addVoicemail" msgid="5525660026090959044">"agregar buzón de voz"</string>
<string name="permdesc_addVoicemail" msgid="6604508651428252437">"Permite que la aplicación agregue mensajes a la bandeja de entrada de tu buzón de voz."</string>
<string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"Modificar los permisos de ubicación geográfica del navegador"</string>
<string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"Permite que la aplicación modifique los permisos de ubicación geográfica del navegador. Las aplicaciones maliciosas pueden utilizar esto para permitir el envío de información de ubicación a sitios web arbitrarios."</string>
diff --git a/core/res/res/values-sl/strings.xml b/core/res/res/values-sl/strings.xml
index 7c6c159..b75644b 100644
--- a/core/res/res/values-sl/strings.xml
+++ b/core/res/res/values-sl/strings.xml
@@ -2053,7 +2053,7 @@
<string name="shortcut_restore_signature_mismatch" msgid="2406209324521327518">"Bližnjice ni bilo mogoče obnoviti zaradi neujemanja podpisa aplikacije"</string>
<string name="shortcut_restore_unknown_issue" msgid="8703738064603262597">"Bližnjice ni bilo mogoče obnoviti"</string>
<string name="shortcut_disabled_reason_unknown" msgid="5276016910284687075">"Bližnjica je onemogočena"</string>
- <string name="harmful_app_warning_uninstall" msgid="4837672735619532931">"ODSTRANI"</string>
+ <string name="harmful_app_warning_uninstall" msgid="4837672735619532931">"ODMESTI"</string>
<string name="harmful_app_warning_open_anyway" msgid="596432803680914321">"VSEENO ODPRI"</string>
<string name="harmful_app_warning_title" msgid="8982527462829423432">"Zaznana je bila škodljiva aplikacija"</string>
<string name="slices_permission_request" msgid="8484943441501672932">"Aplikacija <xliff:g id="APP_0">%1$s</xliff:g> želi prikazati izreze aplikacije <xliff:g id="APP_2">%2$s</xliff:g>"</string>
diff --git a/core/res/res/values-ta/strings.xml b/core/res/res/values-ta/strings.xml
index 726e223..7028672 100644
--- a/core/res/res/values-ta/strings.xml
+++ b/core/res/res/values-ta/strings.xml
@@ -365,9 +365,9 @@
<string name="permlab_enableCarMode" msgid="5684504058192921098">"கார் பயன்முறையை இயக்குதல்"</string>
<string name="permdesc_enableCarMode" msgid="4853187425751419467">"கார் முறையை இயக்க, ஆப்ஸை அனுமதிக்கிறது."</string>
<string name="permlab_killBackgroundProcesses" msgid="3914026687420177202">"பிற பயன்பாடுகளை மூடுதல்"</string>
- <string name="permdesc_killBackgroundProcesses" msgid="4593353235959733119">"பிற பயன்பாடுகளின் பின்புலச் செயல்முறைகளை நிறுத்த ஆப்ஸை அனுமதிக்கிறது. இதனால் பிற பயன்பாடுகள் இயங்குவதை நிறுத்தலாம்."</string>
- <string name="permlab_systemAlertWindow" msgid="7238805243128138690">"இந்த ஆப்ஸ் பிற பயன்பாடுகளின் மேலே தோன்றலாம்"</string>
- <string name="permdesc_systemAlertWindow" msgid="2393776099672266188">"இந்த ஆப்ஸ் பிற பயன்பாடுகளின் மேலே அல்லது திரையின் பிற பகுதிகளில் தோன்றலாம். இது வழக்கமான ஆப்ஸ் உபயோகத்தில் குறுக்கிட்டு, பிற பயன்பாடுகள் தோன்றும் விதத்தை மாற்றக்கூடும்."</string>
+ <string name="permdesc_killBackgroundProcesses" msgid="4593353235959733119">"பிற ஆப்ஸின் பின்புலச் செயல்முறைகளை நிறுத்த ஆப்ஸை அனுமதிக்கிறது. இதனால் பிற பயன்பாடுகள் இயங்குவதை நிறுத்தலாம்."</string>
+ <string name="permlab_systemAlertWindow" msgid="7238805243128138690">"இந்த ஆப்ஸ் பிற ஆப்ஸின் மேலே தோன்றலாம்"</string>
+ <string name="permdesc_systemAlertWindow" msgid="2393776099672266188">"இந்த ஆப்ஸ் பிற ஆப்ஸின் மேலே அல்லது திரையின் பிற பகுதிகளில் தோன்றலாம். இது வழக்கமான ஆப்ஸ் உபயோகத்தில் குறுக்கிட்டு, பிற பயன்பாடுகள் தோன்றும் விதத்தை மாற்றக்கூடும்."</string>
<string name="permlab_runInBackground" msgid="7365290743781858803">"பின்னணியில் இயக்கு"</string>
<string name="permdesc_runInBackground" msgid="7370142232209999824">"இந்த ஆப்ஸ், பின்னணியில் இயங்கலாம். இதனால் பேட்டரி விரைவாகத் தீர்ந்துவிடக்கூடும்."</string>
<string name="permlab_useDataInBackground" msgid="8694951340794341809">"பின்னணியில் தரவைப் பயன்படுத்து"</string>
@@ -1380,7 +1380,7 @@
<string name="fast_scroll_alphabet" msgid="5433275485499039199">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
<string name="fast_scroll_numeric_alphabet" msgid="4030170524595123610">" 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
<string name="alert_windows_notification_channel_group_name" msgid="1463953341148606396">"பிற ஆப்ஸின் மேலே காட்டு"</string>
- <string name="alert_windows_notification_channel_name" msgid="3116610965549449803">"<xliff:g id="NAME">%s</xliff:g> பிற பயன்பாடுகளின் மீது தோன்றுகிறது"</string>
+ <string name="alert_windows_notification_channel_name" msgid="3116610965549449803">"<xliff:g id="NAME">%s</xliff:g> பிற ஆப்ஸின் மீது தோன்றுகிறது"</string>
<string name="alert_windows_notification_title" msgid="3697657294867638947">"<xliff:g id="NAME">%s</xliff:g> பிற ஆப்ஸின் மீது தோன்றுகிறது"</string>
<string name="alert_windows_notification_message" msgid="8917232109522912560">"<xliff:g id="NAME">%s</xliff:g> இந்த அம்சத்தைப் பயன்படுத்த வேண்டாம் என நினைத்தால், அமைப்புகளைத் திறந்து அதை முடக்க, தட்டவும்."</string>
<string name="alert_windows_notification_turn_off_action" msgid="2902891971380544651">"ஆஃப் செய்"</string>
@@ -2004,7 +2004,7 @@
<string name="standby_warning_message" product="default" msgid="5222741828239073484">"இந்தச் சாதனம் விரைவில் ஆஃப் ஆகலாம். இதைத் தொடர்ந்து ஆனில் வைக்கத் தட்டவும்."</string>
<string name="notification_appops_camera_active" msgid="5050283058419699771">"கேமரா"</string>
<string name="notification_appops_microphone_active" msgid="4335305527588191730">"மைக்ரோஃபோன்"</string>
- <string name="notification_appops_overlay_active" msgid="633813008357934729">"உங்கள் திரையில் உள்ள பிற பயன்பாடுகளின் மேல் காட்டுகிறது"</string>
+ <string name="notification_appops_overlay_active" msgid="633813008357934729">"உங்கள் திரையில் உள்ள பிற ஆப்ஸின் மேல் காட்டுகிறது"</string>
<string name="dynamic_mode_notification_channel_name" msgid="2348803891571320452">"வழக்கமான பேட்டரி சேமிப்பானுக்கான விவர அறிவிப்பு"</string>
<string name="dynamic_mode_notification_title" msgid="508815255807182035">"வழக்கமாகச் சார்ஜ் செய்வதற்கு முன்பே பேட்டரி தீர்ந்துபோகக்கூடும்"</string>
<string name="dynamic_mode_notification_summary" msgid="2541166298550402690">"பேட்டரி நிலையை நீட்டிக்க பேட்டரி சேமிப்பான் இயக்கப்பட்டுள்ளது"</string>
diff --git a/core/res/res/values-th/strings.xml b/core/res/res/values-th/strings.xml
index d170932..3472cb2 100644
--- a/core/res/res/values-th/strings.xml
+++ b/core/res/res/values-th/strings.xml
@@ -299,7 +299,7 @@
<string name="permgrouplab_microphone" msgid="171539900250043464">"ไมโครโฟน"</string>
<string name="permgroupdesc_microphone" msgid="4988812113943554584">"บันทึกเสียง"</string>
<string name="permgrouprequest_microphone" msgid="9167492350681916038">"อนุญาตให้ <b><xliff:g id="APP_NAME">%1$s</xliff:g></b> บันทึกเสียงไหม"</string>
- <string name="permgrouplab_activityRecognition" msgid="1565108047054378642">"กิจกรรมการเคลื่อนไหวร่างกาย"</string>
+ <string name="permgrouplab_activityRecognition" msgid="1565108047054378642">"การเคลื่อนไหวร่างกาย"</string>
<string name="permgroupdesc_activityRecognition" msgid="6949472038320473478">"เข้าถึงกิจกรรมการเคลื่อนไหวร่างกายของคุณ"</string>
<string name="permgrouprequest_activityRecognition" msgid="7626438016904799383">"อนุญาตให้ <b><xliff:g id="APP_NAME">%1$s</xliff:g></b> เข้าถึงกิจกรรมการเคลื่อนไหวร่างกายของคุณไหม"</string>
<string name="permgrouplab_camera" msgid="4820372495894586615">"กล้องถ่ายรูป"</string>
diff --git a/media/jni/Android.bp b/media/jni/Android.bp
index c49b1e4..84fe27d 100644
--- a/media/jni/Android.bp
+++ b/media/jni/Android.bp
@@ -52,7 +52,6 @@
"libandroidfw",
"libhidlallocatorutils",
"libhidlbase",
- "libhidltransport",
"android.hardware.cas@1.0",
"android.hardware.cas.native@1.0",
"android.hidl.memory@1.0",
@@ -147,7 +146,6 @@
"android.hidl.memory@1.0",
"libhidlbase",
"libhidlmemory",
- "libhidltransport",
"libbinderthreadstate",
// MediaPlayer2 implementation
diff --git a/packages/BackupEncryption/Android.bp b/packages/BackupEncryption/Android.bp
index 50dbcdb..9bcd677 100644
--- a/packages/BackupEncryption/Android.bp
+++ b/packages/BackupEncryption/Android.bp
@@ -17,8 +17,15 @@
android_app {
name: "BackupEncryption",
srcs: ["src/**/*.java"],
+ libs: ["backup-encryption-protos"],
optimize: { enabled: false },
platform_apis: true,
certificate: "platform",
privileged: true,
-}
\ No newline at end of file
+}
+
+java_library {
+ name: "backup-encryption-protos",
+ proto: { type: "nano" },
+ srcs: ["proto/**/*.proto"],
+}
diff --git a/packages/BackupEncryption/proto/wrapped_key.proto b/packages/BackupEncryption/proto/wrapped_key.proto
new file mode 100644
index 0000000..817b7b4
--- /dev/null
+++ b/packages/BackupEncryption/proto/wrapped_key.proto
@@ -0,0 +1,52 @@
+syntax = "proto2";
+
+package android_backup_crypto;
+
+option java_package = "com.android.server.backup.encryption.protos";
+option java_outer_classname = "WrappedKeyProto";
+
+// Metadata associated with a tertiary key.
+message KeyMetadata {
+ // Type of Cipher algorithm the key is used for.
+ enum Type {
+ UNKNOWN = 0;
+ // No padding. Uses 12-byte nonce. Tag length 16 bytes.
+ AES_256_GCM = 1;
+ }
+
+ // What kind of Cipher algorithm the key is used for. We assume at the moment
+ // that this will always be AES_256_GCM and throw if this is not the case.
+ // Provided here for forwards compatibility in case at some point we need to
+ // change Cipher algorithm.
+ optional Type type = 1;
+}
+
+// An encrypted tertiary key.
+message WrappedKey {
+ // The Cipher with which the key was encrypted.
+ enum WrapAlgorithm {
+ UNKNOWN = 0;
+ // No padding. Uses 16-byte nonce (see nonce field). Tag length 16 bytes.
+ // The nonce is 16-bytes as this is wrapped with a key in AndroidKeyStore.
+ // AndroidKeyStore requires that it generates the IV, and it generates a
+ // 16-byte IV for you. You CANNOT provide your own IV.
+ AES_256_GCM = 1;
+ }
+
+ // Cipher algorithm used to wrap the key. We assume at the moment that this
+ // is always AES_256_GC and throw if this is not the case. Provided here for
+ // forwards compatibility if at some point we need to change Cipher algorithm.
+ optional WrapAlgorithm wrap_algorithm = 1;
+
+ // The nonce used to initialize the Cipher in AES/256/GCM mode.
+ optional bytes nonce = 2;
+
+ // The encrypted bytes of the key material.
+ optional bytes key = 3;
+
+ // Associated key metadata.
+ optional KeyMetadata metadata = 4;
+
+ // Deprecated field; Do not use
+ reserved 5;
+}
diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/keys/KeyWrapUtils.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/keys/KeyWrapUtils.java
new file mode 100644
index 0000000..a043c1f
--- /dev/null
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/keys/KeyWrapUtils.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.encryption.keys;
+
+import com.android.server.backup.encryption.protos.nano.WrappedKeyProto;
+
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.util.Locale;
+
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.GCMParameterSpec;
+
+/** Utility functions for wrapping and unwrapping tertiary keys. */
+public class KeyWrapUtils {
+ private static final String AES_GCM_MODE = "AES/GCM/NoPadding";
+ private static final int GCM_TAG_LENGTH_BYTES = 16;
+ private static final int BITS_PER_BYTE = 8;
+ private static final int GCM_TAG_LENGTH_BITS = GCM_TAG_LENGTH_BYTES * BITS_PER_BYTE;
+ private static final String KEY_ALGORITHM = "AES";
+
+ /**
+ * Uses the secondary key to unwrap the wrapped tertiary key.
+ *
+ * @param secondaryKey The secondary key used to wrap the tertiary key.
+ * @param wrappedKey The wrapped tertiary key.
+ * @return The unwrapped tertiary key.
+ * @throws InvalidKeyException if the provided secondary key cannot unwrap the tertiary key.
+ */
+ public static SecretKey unwrap(SecretKey secondaryKey, WrappedKeyProto.WrappedKey wrappedKey)
+ throws InvalidKeyException, NoSuchAlgorithmException,
+ InvalidAlgorithmParameterException, NoSuchPaddingException {
+ if (wrappedKey.wrapAlgorithm != WrappedKeyProto.WrappedKey.AES_256_GCM) {
+ throw new InvalidKeyException(
+ String.format(
+ Locale.US,
+ "Could not unwrap key wrapped with %s algorithm",
+ wrappedKey.wrapAlgorithm));
+ }
+
+ if (wrappedKey.metadata == null) {
+ throw new InvalidKeyException("Metadata missing from wrapped tertiary key.");
+ }
+
+ if (wrappedKey.metadata.type != WrappedKeyProto.KeyMetadata.AES_256_GCM) {
+ throw new InvalidKeyException(
+ String.format(
+ Locale.US,
+ "Wrapped key was unexpected %s algorithm. Only support"
+ + " AES/GCM/NoPadding.",
+ wrappedKey.metadata.type));
+ }
+
+ Cipher cipher = getCipher();
+
+ cipher.init(
+ Cipher.UNWRAP_MODE,
+ secondaryKey,
+ new GCMParameterSpec(GCM_TAG_LENGTH_BITS, wrappedKey.nonce));
+
+ return (SecretKey) cipher.unwrap(wrappedKey.key, KEY_ALGORITHM, Cipher.SECRET_KEY);
+ }
+
+ /**
+ * Wraps the tertiary key with the secondary key.
+ *
+ * @param secondaryKey The secondary key to use for wrapping.
+ * @param tertiaryKey The key to wrap.
+ * @return The wrapped key.
+ * @throws InvalidKeyException if the key is not good for wrapping.
+ * @throws IllegalBlockSizeException if there is an issue wrapping.
+ */
+ public static WrappedKeyProto.WrappedKey wrap(SecretKey secondaryKey, SecretKey tertiaryKey)
+ throws InvalidKeyException, IllegalBlockSizeException, NoSuchAlgorithmException,
+ NoSuchPaddingException {
+ Cipher cipher = getCipher();
+ cipher.init(Cipher.WRAP_MODE, secondaryKey);
+
+ WrappedKeyProto.WrappedKey wrappedKey = new WrappedKeyProto.WrappedKey();
+ wrappedKey.key = cipher.wrap(tertiaryKey);
+ wrappedKey.nonce = cipher.getIV();
+ wrappedKey.wrapAlgorithm = WrappedKeyProto.WrappedKey.AES_256_GCM;
+ wrappedKey.metadata = new WrappedKeyProto.KeyMetadata();
+ wrappedKey.metadata.type = WrappedKeyProto.KeyMetadata.AES_256_GCM;
+ return wrappedKey;
+ }
+
+ /**
+ * Rewraps a tertiary key with a new secondary key.
+ *
+ * @param oldSecondaryKey The old secondary key, used to unwrap the tertiary key.
+ * @param newSecondaryKey The new secondary key, used to rewrap the tertiary key.
+ * @param tertiaryKey The tertiary key, wrapped by {@code oldSecondaryKey}.
+ * @return The tertiary key, wrapped by {@code newSecondaryKey}.
+ * @throws InvalidKeyException if the key is not good for wrapping or unwrapping.
+ * @throws IllegalBlockSizeException if there is an issue wrapping.
+ */
+ public static WrappedKeyProto.WrappedKey rewrap(
+ SecretKey oldSecondaryKey,
+ SecretKey newSecondaryKey,
+ WrappedKeyProto.WrappedKey tertiaryKey)
+ throws InvalidKeyException, IllegalBlockSizeException,
+ InvalidAlgorithmParameterException, NoSuchAlgorithmException,
+ NoSuchPaddingException {
+ return wrap(newSecondaryKey, unwrap(oldSecondaryKey, tertiaryKey));
+ }
+
+ private static Cipher getCipher() throws NoSuchPaddingException, NoSuchAlgorithmException {
+ return Cipher.getInstance(AES_GCM_MODE);
+ }
+
+ // Statics only
+ private KeyWrapUtils() {}
+}
diff --git a/packages/BackupEncryption/test/robolectric/Android.bp b/packages/BackupEncryption/test/robolectric/Android.bp
index 6d1abbb..3376ec9 100644
--- a/packages/BackupEncryption/test/robolectric/Android.bp
+++ b/packages/BackupEncryption/test/robolectric/Android.bp
@@ -20,6 +20,7 @@
],
java_resource_dirs: ["config"],
libs: [
+ "backup-encryption-protos",
"platform-test-annotations",
"testng",
],
diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/keys/KeyWrapUtilsTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/keys/KeyWrapUtilsTest.java
new file mode 100644
index 0000000..b607404
--- /dev/null
+++ b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/keys/KeyWrapUtilsTest.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.encryption.keys;
+
+import static com.android.server.backup.testing.CryptoTestUtils.generateAesKey;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.platform.test.annotations.Presubmit;
+
+import com.android.server.backup.encryption.protos.nano.WrappedKeyProto;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+import java.security.InvalidKeyException;
+
+import javax.crypto.SecretKey;
+
+/** Key wrapping tests */
+@RunWith(RobolectricTestRunner.class)
+@Presubmit
+public class KeyWrapUtilsTest {
+ private static final int KEY_SIZE_BITS = 256;
+ private static final int BITS_PER_BYTE = 8;
+ private static final int GCM_NONCE_LENGTH_BYTES = 16;
+ private static final int GCM_TAG_LENGTH_BYTES = 16;
+
+ /** Test a wrapped key has metadata */
+ @Test
+ public void wrap_addsMetadata() throws Exception {
+ WrappedKeyProto.WrappedKey wrappedKey =
+ KeyWrapUtils.wrap(
+ /*secondaryKey=*/ generateAesKey(), /*tertiaryKey=*/ generateAesKey());
+ assertThat(wrappedKey.metadata).isNotNull();
+ assertThat(wrappedKey.metadata.type).isEqualTo(WrappedKeyProto.KeyMetadata.AES_256_GCM);
+ }
+
+ /** Test a wrapped key has an algorithm specified */
+ @Test
+ public void wrap_addsWrapAlgorithm() throws Exception {
+ WrappedKeyProto.WrappedKey wrappedKey =
+ KeyWrapUtils.wrap(
+ /*secondaryKey=*/ generateAesKey(), /*tertiaryKey=*/ generateAesKey());
+ assertThat(wrappedKey.wrapAlgorithm).isEqualTo(WrappedKeyProto.WrappedKey.AES_256_GCM);
+ }
+
+ /** Test a wrapped key haas an nonce of the right length */
+ @Test
+ public void wrap_addsNonceOfAppropriateLength() throws Exception {
+ WrappedKeyProto.WrappedKey wrappedKey =
+ KeyWrapUtils.wrap(
+ /*secondaryKey=*/ generateAesKey(), /*tertiaryKey=*/ generateAesKey());
+ assertThat(wrappedKey.nonce).hasLength(GCM_NONCE_LENGTH_BYTES);
+ }
+
+ /** Test a wrapped key has a key of the right length */
+ @Test
+ public void wrap_addsTagOfAppropriateLength() throws Exception {
+ WrappedKeyProto.WrappedKey wrappedKey =
+ KeyWrapUtils.wrap(
+ /*secondaryKey=*/ generateAesKey(), /*tertiaryKey=*/ generateAesKey());
+ assertThat(wrappedKey.key).hasLength(KEY_SIZE_BITS / BITS_PER_BYTE + GCM_TAG_LENGTH_BYTES);
+ }
+
+ /** Ensure a key can be wrapped and unwrapped again */
+ @Test
+ public void unwrap_unwrapsEncryptedKey() throws Exception {
+ SecretKey secondaryKey = generateAesKey();
+ SecretKey tertiaryKey = generateAesKey();
+ WrappedKeyProto.WrappedKey wrappedKey = KeyWrapUtils.wrap(secondaryKey, tertiaryKey);
+ SecretKey unwrappedKey = KeyWrapUtils.unwrap(secondaryKey, wrappedKey);
+ assertThat(unwrappedKey).isEqualTo(tertiaryKey);
+ }
+
+ /** Ensure the unwrap method rejects keys with bad algorithms */
+ @Test(expected = InvalidKeyException.class)
+ public void unwrap_throwsForBadWrapAlgorithm() throws Exception {
+ SecretKey secondaryKey = generateAesKey();
+ WrappedKeyProto.WrappedKey wrappedKey = KeyWrapUtils.wrap(secondaryKey, generateAesKey());
+ wrappedKey.wrapAlgorithm = WrappedKeyProto.WrappedKey.UNKNOWN;
+
+ KeyWrapUtils.unwrap(secondaryKey, wrappedKey);
+ }
+
+ /** Ensure the unwrap method rejects metadata indicating the encryption type is unknown */
+ @Test(expected = InvalidKeyException.class)
+ public void unwrap_throwsForBadKeyAlgorithm() throws Exception {
+ SecretKey secondaryKey = generateAesKey();
+ WrappedKeyProto.WrappedKey wrappedKey = KeyWrapUtils.wrap(secondaryKey, generateAesKey());
+ wrappedKey.metadata.type = WrappedKeyProto.KeyMetadata.UNKNOWN;
+
+ KeyWrapUtils.unwrap(secondaryKey, wrappedKey);
+ }
+
+ /** Ensure the unwrap method rejects wrapped keys missing the metadata */
+ @Test(expected = InvalidKeyException.class)
+ public void unwrap_throwsForMissingMetadata() throws Exception {
+ SecretKey secondaryKey = generateAesKey();
+ WrappedKeyProto.WrappedKey wrappedKey = KeyWrapUtils.wrap(secondaryKey, generateAesKey());
+ wrappedKey.metadata = null;
+
+ KeyWrapUtils.unwrap(secondaryKey, wrappedKey);
+ }
+
+ /** Ensure unwrap rejects invalid secondary keys */
+ @Test(expected = InvalidKeyException.class)
+ public void unwrap_throwsForBadSecondaryKey() throws Exception {
+ WrappedKeyProto.WrappedKey wrappedKey =
+ KeyWrapUtils.wrap(
+ /*secondaryKey=*/ generateAesKey(), /*tertiaryKey=*/ generateAesKey());
+
+ KeyWrapUtils.unwrap(generateAesKey(), wrappedKey);
+ }
+
+ /** Ensure rewrap can rewrap keys */
+ @Test
+ public void rewrap_canBeUnwrappedWithNewSecondaryKey() throws Exception {
+ SecretKey tertiaryKey = generateAesKey();
+ SecretKey oldSecondaryKey = generateAesKey();
+ SecretKey newSecondaryKey = generateAesKey();
+ WrappedKeyProto.WrappedKey wrappedWithOld = KeyWrapUtils.wrap(oldSecondaryKey, tertiaryKey);
+
+ WrappedKeyProto.WrappedKey wrappedWithNew =
+ KeyWrapUtils.rewrap(oldSecondaryKey, newSecondaryKey, wrappedWithOld);
+
+ assertThat(KeyWrapUtils.unwrap(newSecondaryKey, wrappedWithNew)).isEqualTo(tertiaryKey);
+ }
+
+ /** Ensure rewrap doesn't create something decryptable by an old key */
+ @Test(expected = InvalidKeyException.class)
+ public void rewrap_cannotBeUnwrappedWithOldSecondaryKey() throws Exception {
+ SecretKey tertiaryKey = generateAesKey();
+ SecretKey oldSecondaryKey = generateAesKey();
+ SecretKey newSecondaryKey = generateAesKey();
+ WrappedKeyProto.WrappedKey wrappedWithOld = KeyWrapUtils.wrap(oldSecondaryKey, tertiaryKey);
+
+ WrappedKeyProto.WrappedKey wrappedWithNew =
+ KeyWrapUtils.rewrap(oldSecondaryKey, newSecondaryKey, wrappedWithOld);
+
+ KeyWrapUtils.unwrap(oldSecondaryKey, wrappedWithNew);
+ }
+}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
index 55ff591..a2fa461 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
@@ -135,6 +135,7 @@
mEnableOk = true;
mOk.setEnabled(true);
+ mOk.setFilterTouchesWhenObscured(true);
}
/**
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index 84f5a04..98db7c8 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -27,6 +27,7 @@
import android.os.ParcelUuid;
import android.os.SystemClock;
import android.text.TextUtils;
+import android.util.EventLog;
import android.util.Log;
import androidx.annotation.VisibleForTesting;
@@ -799,10 +800,9 @@
== BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE ||
mDevice.getBluetoothClass().getDeviceClass()
== BluetoothClass.Device.AUDIO_VIDEO_WEARABLE_HEADSET) {
- mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_ALLOWED);
- } else {
- mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_REJECTED);
+ EventLog.writeEvent(0x534e4554, "138529441", -1, "");
}
+ mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_REJECTED);
}
}
}
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
index 9be636d..4a0ed6f 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
@@ -148,5 +148,6 @@
VALIDATORS.put(Global.POWER_BUTTON_LONG_PRESS, new InclusiveIntegerRangeValidator(0, 5));
VALIDATORS.put(
Global.POWER_BUTTON_VERY_LONG_PRESS, new InclusiveIntegerRangeValidator(0, 1));
+ VALIDATORS.put(Global.NOTIFICATION_BUBBLES, BOOLEAN_VALIDATOR);
}
}
diff --git a/packages/Shell/res/values-zh-rTW/strings.xml b/packages/Shell/res/values-zh-rTW/strings.xml
index fc6397d..96671c8 100644
--- a/packages/Shell/res/values-zh-rTW/strings.xml
+++ b/packages/Shell/res/values-zh-rTW/strings.xml
@@ -25,9 +25,9 @@
<string name="bugreport_finished_text" product="watch" msgid="1223616207145252689">"系統即將在手機上顯示錯誤報告"</string>
<string name="bugreport_finished_text" product="tv" msgid="5758325479058638893">"選取即可分享錯誤報告"</string>
<string name="bugreport_finished_text" product="default" msgid="8353769438382138847">"輕觸即可分享錯誤報告"</string>
- <string name="bugreport_finished_pending_screenshot_text" product="tv" msgid="2343263822812016950">"選取即可分享不包含螢幕擷取畫面的錯誤報告;你也可以等候螢幕畫面擷取完畢"</string>
- <string name="bugreport_finished_pending_screenshot_text" product="watch" msgid="1474435374470177193">"輕觸即可分享無螢幕擷圖的錯誤報告;你也可以等候螢幕畫面擷取完畢"</string>
- <string name="bugreport_finished_pending_screenshot_text" product="default" msgid="1474435374470177193">"輕觸即可分享無螢幕擷圖的錯誤報告;你也可以等候螢幕畫面擷取完畢"</string>
+ <string name="bugreport_finished_pending_screenshot_text" product="tv" msgid="2343263822812016950">"選取即可分享不包含螢幕截圖的錯誤報告;你也可以等候螢幕畫面擷取完畢"</string>
+ <string name="bugreport_finished_pending_screenshot_text" product="watch" msgid="1474435374470177193">"輕觸即可分享無螢幕截圖的錯誤報告;你也可以等候螢幕畫面擷取完畢"</string>
+ <string name="bugreport_finished_pending_screenshot_text" product="default" msgid="1474435374470177193">"輕觸即可分享無螢幕截圖的錯誤報告;你也可以等候螢幕畫面擷取完畢"</string>
<string name="bugreport_confirm" msgid="5917407234515812495">"錯誤報告的資料來自系統的各種記錄檔,當中可能包含敏感資料 (例如應用程式使用情形和位置資料)。請務必只與你信任的使用者和應用程式分享錯誤報告。"</string>
<string name="bugreport_confirm_dont_repeat" msgid="6179945398364357318">"不要再顯示"</string>
<string name="bugreport_storage_title" msgid="5332488144740527109">"錯誤報告"</string>
@@ -35,9 +35,9 @@
<string name="bugreport_add_details_to_zip_failed" msgid="1302931926486712371">"無法在 ZIP 檔案中加入錯誤報告"</string>
<string name="bugreport_unnamed" msgid="2800582406842092709">"未命名"</string>
<string name="bugreport_info_action" msgid="2158204228510576227">"詳細資料"</string>
- <string name="bugreport_screenshot_action" msgid="8677781721940614995">"螢幕擷取畫面"</string>
- <string name="bugreport_screenshot_taken" msgid="5684211273096253120">"已成功拍攝螢幕擷取畫面。"</string>
- <string name="bugreport_screenshot_failed" msgid="5853049140806834601">"無法拍攝螢幕擷取畫面。"</string>
+ <string name="bugreport_screenshot_action" msgid="8677781721940614995">"螢幕截圖"</string>
+ <string name="bugreport_screenshot_taken" msgid="5684211273096253120">"已成功拍攝螢幕截圖。"</string>
+ <string name="bugreport_screenshot_failed" msgid="5853049140806834601">"無法拍攝螢幕截圖。"</string>
<string name="bugreport_info_dialog_title" msgid="1355948594292983332">"錯誤報告 <xliff:g id="ID">#%d</xliff:g> 的詳細資料"</string>
<string name="bugreport_info_name" msgid="4414036021935139527">"檔案名稱"</string>
<string name="bugreport_info_title" msgid="2306030793918239804">"錯誤標題"</string>
diff --git a/packages/SystemUI/res-keyguard/values-cs/strings.xml b/packages/SystemUI/res-keyguard/values-cs/strings.xml
index 0d88a20..3adc279 100644
--- a/packages/SystemUI/res-keyguard/values-cs/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-cs/strings.xml
@@ -41,7 +41,7 @@
<string name="keyguard_low_battery" msgid="9218432555787624490">"Připojte dobíjecí zařízení."</string>
<string name="keyguard_instructions_when_pattern_disabled" msgid="8566679946700751371">"Klávesy odemknete stisknutím tlačítka nabídky."</string>
<string name="keyguard_network_locked_message" msgid="6743537524631420759">"Síť je blokována"</string>
- <string name="keyguard_missing_sim_message_short" msgid="6327533369959764518">"Není vložena SIM karta"</string>
+ <string name="keyguard_missing_sim_message_short" msgid="6327533369959764518">"Chybí SIM karta"</string>
<string name="keyguard_missing_sim_message" product="tablet" msgid="4550152848200783542">"V tabletu není SIM karta."</string>
<string name="keyguard_missing_sim_message" product="default" msgid="6585414237800161146">"V telefonu není SIM karta."</string>
<string name="keyguard_missing_sim_instructions" msgid="7350295932015220392">"Vložte SIM kartu."</string>
diff --git a/packages/SystemUI/res-keyguard/values-fa/strings.xml b/packages/SystemUI/res-keyguard/values-fa/strings.xml
index 22c4c48..9c9de22 100644
--- a/packages/SystemUI/res-keyguard/values-fa/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-fa/strings.xml
@@ -37,7 +37,7 @@
<string name="keyguard_plugged_in_wireless" msgid="8404159927155454732">"<xliff:g id="PERCENTAGE">%s</xliff:g> • درحال شارژ بیسیم"</string>
<string name="keyguard_plugged_in" msgid="3161102098900158923">"<xliff:g id="PERCENTAGE">%s</xliff:g> • درحال شارژ شدن"</string>
<string name="keyguard_plugged_in_charging_fast" msgid="3684592786276709342">"<xliff:g id="PERCENTAGE">%s</xliff:g> • درحال شارژ سریع"</string>
- <string name="keyguard_plugged_in_charging_slowly" msgid="509533586841478405">"<xliff:g id="PERCENTAGE">%s</xliff:g> • درحال شارژ آهسته"</string>
+ <string name="keyguard_plugged_in_charging_slowly" msgid="509533586841478405">"<xliff:g id="PERCENTAGE">%s</xliff:g> • آهستهآهسته شارژ میشود"</string>
<string name="keyguard_low_battery" msgid="9218432555787624490">"شارژر را وصل کنید."</string>
<string name="keyguard_instructions_when_pattern_disabled" msgid="8566679946700751371">"برای باز کردن قفل روی «منو» فشار دهید."</string>
<string name="keyguard_network_locked_message" msgid="6743537524631420759">"شبکه قفل شد"</string>
diff --git a/packages/SystemUI/res/values-be/strings.xml b/packages/SystemUI/res/values-be/strings.xml
index dcd31bd..3674564 100644
--- a/packages/SystemUI/res/values-be/strings.xml
+++ b/packages/SystemUI/res/values-be/strings.xml
@@ -329,7 +329,7 @@
<string name="quick_settings_location_off_label" msgid="7464544086507331459">"Вызначэнне месцазнаходжання адключана"</string>
<string name="quick_settings_media_device_label" msgid="1302906836372603762">"Мультымедыйная прылада"</string>
<string name="quick_settings_rssi_label" msgid="7725671335550695589">"RSSI"</string>
- <string name="quick_settings_rssi_emergency_only" msgid="2713774041672886750">"Толькі экстраныя выклікі"</string>
+ <string name="quick_settings_rssi_emergency_only" msgid="2713774041672886750">"Толькі экстранныя выклікі"</string>
<string name="quick_settings_settings_label" msgid="5326556592578065401">"Налады"</string>
<string name="quick_settings_time_label" msgid="4635969182239736408">"Час"</string>
<string name="quick_settings_user_label" msgid="5238995632130897840">"Я"</string>
@@ -423,7 +423,7 @@
<string name="keyguard_indication_charging_time_wireless" msgid="6959284458466962592">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Ідзе бесправадная зарадка (да поўнага зараду засталося <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>)"</string>
<string name="keyguard_indication_charging_time" msgid="2056340799276374421">"Ідзе зарадка (<xliff:g id="PERCENTAGE">%2$s</xliff:g>, яшчэ <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>)"</string>
<string name="keyguard_indication_charging_time_fast" msgid="7767562163577492332">"Ідзе хуткая зарадка (<xliff:g id="PERCENTAGE">%2$s</xliff:g>, яшчэ <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>)"</string>
- <string name="keyguard_indication_charging_time_slowly" msgid="3769655133567307069">"Ідзе павольная зарадка (<xliff:g id="PERCENTAGE">%2$s</xliff:g>, яшчэ <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>)"</string>
+ <string name="keyguard_indication_charging_time_slowly" msgid="3769655133567307069">"Ідзе павольная зарадка (<xliff:g id="PERCENTAGE">%2$s</xliff:g>, <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> да канца)"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="7305948938141024937">"Перайсці да іншага карыстальніка"</string>
<string name="accessibility_multi_user_switch_switcher_with_current" msgid="8434880595284601601">"Перайсці да іншага карыстальніка, бягучы карыстальнік <xliff:g id="CURRENT_USER_NAME">%s</xliff:g>"</string>
<string name="accessibility_multi_user_switch_inactive" msgid="1424081831468083402">"Бягучы карыстальнік <xliff:g id="CURRENT_USER_NAME">%s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-bn/strings.xml b/packages/SystemUI/res/values-bn/strings.xml
index 5141b57..6b7561b 100644
--- a/packages/SystemUI/res/values-bn/strings.xml
+++ b/packages/SystemUI/res/values-bn/strings.xml
@@ -232,9 +232,9 @@
<string name="accessibility_quick_settings_airplane_changed_on" msgid="8983005603505087728">"বিমান মোড চালু হয়েছে।"</string>
<string name="accessibility_quick_settings_dnd_none_on" msgid="2960643943620637020">"সম্পূর্ণ নীরব"</string>
<string name="accessibility_quick_settings_dnd_alarms_on" msgid="3357131899365865386">"শুধুমাত্র অ্যালার্ম"</string>
- <string name="accessibility_quick_settings_dnd" msgid="5555155552520665891">"বিরক্ত করবেন না।"</string>
- <string name="accessibility_quick_settings_dnd_changed_off" msgid="2757071272328547807">"\'বিরক্ত করবেন না\' বন্ধ আছে।"</string>
- <string name="accessibility_quick_settings_dnd_changed_on" msgid="6808220653747701059">"\'বিরক্ত করবেন না\' চালু করা হয়েছে।"</string>
+ <string name="accessibility_quick_settings_dnd" msgid="5555155552520665891">"বিরক্ত করবে না।"</string>
+ <string name="accessibility_quick_settings_dnd_changed_off" msgid="2757071272328547807">"\'বিরক্ত করবে না\' বন্ধ আছে।"</string>
+ <string name="accessibility_quick_settings_dnd_changed_on" msgid="6808220653747701059">"\'বিরক্ত করবে না\' চালু করা হয়েছে।"</string>
<string name="accessibility_quick_settings_bluetooth" msgid="6341675755803320038">"ব্লুটুথ"</string>
<string name="accessibility_quick_settings_bluetooth_off" msgid="2133631372372064339">"ব্লুটুথ বন্ধ আছে।"</string>
<string name="accessibility_quick_settings_bluetooth_on" msgid="7681999166216621838">"ব্লুটুথ চালু আছে।"</string>
@@ -299,7 +299,7 @@
<string name="start_dreams" msgid="5640361424498338327">"স্ক্রিন সেভার"</string>
<string name="ethernet_label" msgid="7967563676324087464">"ইথারনেট"</string>
<string name="quick_settings_header_onboarding_text" msgid="8030309023792936283">"আরও বিকল্পের জন্য আইকনগুলি টাচ করে ধরে থাকুন"</string>
- <string name="quick_settings_dnd_label" msgid="7112342227663678739">"বিরক্ত করবেন না"</string>
+ <string name="quick_settings_dnd_label" msgid="7112342227663678739">"বিরক্ত করবে না"</string>
<string name="quick_settings_dnd_priority_label" msgid="483232950670692036">"শুধুমাত্র অগ্রাধিকার"</string>
<string name="quick_settings_dnd_alarms_label" msgid="2559229444312445858">"শুধুমাত্র অ্যালার্মগুলি"</string>
<string name="quick_settings_dnd_none_label" msgid="5025477807123029478">"একদম নিরব"</string>
@@ -461,7 +461,7 @@
<string name="manage_notifications_text" msgid="2386728145475108753">"পরিচালনা করুন"</string>
<string name="notification_section_header_gentle" msgid="4372438504154095677">"নীরব বিজ্ঞপ্তি"</string>
<string name="accessibility_notification_section_header_gentle_clear_all" msgid="4286716295850400959">"সব নীরব বিজ্ঞপ্তি মুছুন"</string>
- <string name="dnd_suppressing_shade_text" msgid="1904574852846769301">"\'বিরক্ত করবেন না\' দিয়ে বিজ্ঞপ্তি পজ করা হয়েছে"</string>
+ <string name="dnd_suppressing_shade_text" msgid="1904574852846769301">"\'বিরক্ত করবে না\' দিয়ে বিজ্ঞপ্তি পজ করা হয়েছে"</string>
<string name="media_projection_action_text" msgid="8470872969457985954">"এখন শুরু করুন"</string>
<string name="empty_shade_text" msgid="708135716272867002">"কোনো বিজ্ঞপ্তি নেই"</string>
<string name="profile_owned_footer" msgid="8021888108553696069">"প্রোফাইল পর্যবেক্ষণ করা হতে পারে"</string>
@@ -737,9 +737,9 @@
<string name="keyboard_shortcut_group_applications_youtube" msgid="6555453761294723317">"YouTube"</string>
<string name="keyboard_shortcut_group_applications_calendar" msgid="9043614299194991263">"ক্যালেন্ডার"</string>
<string name="tuner_full_zen_title" msgid="4540823317772234308">"ভলিউম নিয়ন্ত্রণ সহ দেখান"</string>
- <string name="volume_and_do_not_disturb" msgid="1750270820297253561">"বিরক্ত করবেন না"</string>
+ <string name="volume_and_do_not_disturb" msgid="1750270820297253561">"বিরক্ত করবে না"</string>
<string name="volume_dnd_silent" msgid="4363882330723050727">"ভলিউম বোতামের শর্টকাট"</string>
- <string name="volume_up_silent" msgid="7545869833038212815">"ভলিউম বাড়িয়ে \'বিরক্ত করবেন না\' মোড থেকে বেরিয়ে আসুন"</string>
+ <string name="volume_up_silent" msgid="7545869833038212815">"ভলিউম বাড়িয়ে \'বিরক্ত করবে না\' মোড থেকে বেরিয়ে আসুন"</string>
<string name="battery" msgid="7498329822413202973">"ব্যাটারি"</string>
<string name="clock" msgid="7416090374234785905">"ঘড়ি"</string>
<string name="headset" msgid="4534219457597457353">"হেডসেট"</string>
@@ -883,10 +883,10 @@
<string name="mobile_carrier_text_format" msgid="3241721038678469804">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g>, <xliff:g id="MOBILE_DATA_TYPE">%2$s</xliff:g>"</string>
<string name="wifi_is_off" msgid="1838559392210456893">"ওয়াই ফাই বন্ধ আছে"</string>
<string name="bt_is_off" msgid="2640685272289706392">"ব্লুটুথ বন্ধ আছে"</string>
- <string name="dnd_is_off" msgid="6167780215212497572">"বিরক্ত করবেন না বিকল্পটি বন্ধ আছে"</string>
- <string name="qs_dnd_prompt_auto_rule" msgid="862559028345233052">"বিরক্ত করবেন না বিকল্পটি একটি স্বয়ংক্রিয় নিয়ম <xliff:g id="ID_1">%s</xliff:g> এর দ্বারা চালু করা হয়েছে।"</string>
- <string name="qs_dnd_prompt_app" msgid="7978037419334156034">"বিরক্ত করবেন না বিকল্পটি একটি অ্যাপ <xliff:g id="ID_1">%s</xliff:g> এর দ্বারা চালু করা হয়েছে।"</string>
- <string name="qs_dnd_prompt_auto_rule_app" msgid="2599343675391111951">"বিরক্ত করবেন না বিকল্পটি একটি স্বয়ংক্রিয় নিয়ম বা অ্যাপের দ্বারা চালু করা হয়েছে।"</string>
+ <string name="dnd_is_off" msgid="6167780215212497572">"বিরক্ত করবে না বিকল্পটি বন্ধ আছে"</string>
+ <string name="qs_dnd_prompt_auto_rule" msgid="862559028345233052">"বিরক্ত করবে না বিকল্পটি একটি স্বয়ংক্রিয় নিয়ম <xliff:g id="ID_1">%s</xliff:g> এর দ্বারা চালু করা হয়েছে।"</string>
+ <string name="qs_dnd_prompt_app" msgid="7978037419334156034">"বিরক্ত করবে না বিকল্পটি একটি অ্যাপ <xliff:g id="ID_1">%s</xliff:g> এর দ্বারা চালু করা হয়েছে।"</string>
+ <string name="qs_dnd_prompt_auto_rule_app" msgid="2599343675391111951">"বিরক্ত করবে না বিকল্পটি একটি স্বয়ংক্রিয় নিয়ম বা অ্যাপের দ্বারা চালু করা হয়েছে।"</string>
<string name="qs_dnd_until" msgid="3469471136280079874">"<xliff:g id="ID_1">%s</xliff:g> পর্যন্ত"</string>
<string name="qs_dnd_keep" msgid="1825009164681928736">"রাখুন"</string>
<string name="qs_dnd_replace" msgid="8019520786644276623">"বদলে দিন"</string>
diff --git a/packages/SystemUI/res/values-cs/strings.xml b/packages/SystemUI/res/values-cs/strings.xml
index abea52a..87aaa94 100644
--- a/packages/SystemUI/res/values-cs/strings.xml
+++ b/packages/SystemUI/res/values-cs/strings.xml
@@ -194,7 +194,7 @@
<string name="accessibility_bluetooth_tether" msgid="4102784498140271969">"Sdílené připojení přes Bluetooth."</string>
<string name="accessibility_airplane_mode" msgid="834748999790763092">"Režim Letadlo."</string>
<string name="accessibility_vpn_on" msgid="5993385083262856059">"VPN je zapnuto."</string>
- <string name="accessibility_no_sims" msgid="3957997018324995781">"Není vložena SIM karta"</string>
+ <string name="accessibility_no_sims" msgid="3957997018324995781">"Chybí SIM karta"</string>
<string name="carrier_network_change_mode" msgid="8149202439957837762">"Probíhá změna sítě operátora"</string>
<string name="accessibility_battery_details" msgid="7645516654955025422">"Otevřít podrobnosti o baterii"</string>
<string name="accessibility_battery_level" msgid="7451474187113371965">"Stav baterie: <xliff:g id="NUMBER">%d</xliff:g> procent."</string>
diff --git a/packages/SystemUI/res/values-da/strings.xml b/packages/SystemUI/res/values-da/strings.xml
index 6a97ee6..57c934e 100644
--- a/packages/SystemUI/res/values-da/strings.xml
+++ b/packages/SystemUI/res/values-da/strings.xml
@@ -215,7 +215,7 @@
<skip />
<string name="accessibility_notification_dismissed" msgid="854211387186306927">"Notifikationen er annulleret."</string>
<string name="accessibility_desc_notification_shade" msgid="4690274844447504208">"Notifikationspanel."</string>
- <string name="accessibility_desc_quick_settings" msgid="6186378411582437046">"Hurtige indstillinger."</string>
+ <string name="accessibility_desc_quick_settings" msgid="6186378411582437046">"Kvikmenu."</string>
<string name="accessibility_desc_lock_screen" msgid="5625143713611759164">"Låseskærm."</string>
<string name="accessibility_desc_settings" msgid="3417884241751434521">"Indstillinger"</string>
<string name="accessibility_desc_recent_apps" msgid="4876900986661819788">"Oversigt."</string>
@@ -588,7 +588,7 @@
<string name="system_ui_tuner" msgid="708224127392452018">"System UI Tuner"</string>
<string name="show_battery_percentage" msgid="5444136600512968798">"Vis procent for det indbyggede batteri"</string>
<string name="show_battery_percentage_summary" msgid="3215025775576786037">"Vis procenttallet for batteriniveauet i ikonet for statusbjælken, når der ikke oplades"</string>
- <string name="quick_settings" msgid="10042998191725428">"Hurtige indstillinger"</string>
+ <string name="quick_settings" msgid="10042998191725428">"Kvikmenu"</string>
<string name="status_bar" msgid="4877645476959324760">"Statusbjælke"</string>
<string name="overview" msgid="4018602013895926956">"Oversigt"</string>
<string name="demo_mode" msgid="2532177350215638026">"Demotilstand for systemets brugerflade"</string>
@@ -604,7 +604,7 @@
<string name="zen_alarm_warning" msgid="444533119582244293">"Du vil ikke kunne høre din næste alarm <xliff:g id="WHEN">%1$s</xliff:g>"</string>
<string name="alarm_template" msgid="3980063409350522735">"kl. <xliff:g id="WHEN">%1$s</xliff:g>"</string>
<string name="alarm_template_far" msgid="4242179982586714810">"på <xliff:g id="WHEN">%1$s</xliff:g>"</string>
- <string name="accessibility_quick_settings_detail" msgid="2579369091672902101">"Hurtige indstillinger <xliff:g id="TITLE">%s</xliff:g>."</string>
+ <string name="accessibility_quick_settings_detail" msgid="2579369091672902101">"Kvikmenu <xliff:g id="TITLE">%s</xliff:g>."</string>
<string name="accessibility_status_bar_hotspot" msgid="4099381329956402865">"Hotspot"</string>
<string name="accessibility_managed_profile" msgid="6613641363112584120">"Arbejdsprofil"</string>
<string name="tuner_warning_title" msgid="7094689930793031682">"Sjovt for nogle, men ikke for alle"</string>
@@ -617,8 +617,8 @@
<string name="activity_not_found" msgid="348423244327799974">"Applikationen er ikke installeret på din enhed."</string>
<string name="clock_seconds" msgid="7689554147579179507">"Vis sekunder"</string>
<string name="clock_seconds_desc" msgid="6282693067130470675">"Vis sekunder i statuslinjen. Dette kan påvirke batteriets levetid."</string>
- <string name="qs_rearrange" msgid="8060918697551068765">"Omarranger Hurtige indstillinger"</string>
- <string name="show_brightness" msgid="6613930842805942519">"Vis lysstyrke i Hurtige indstillinger"</string>
+ <string name="qs_rearrange" msgid="8060918697551068765">"Omarranger Kvikmenu"</string>
+ <string name="show_brightness" msgid="6613930842805942519">"Vis lysstyrke i Kvikmenu"</string>
<string name="experimental" msgid="6198182315536726162">"Eksperimentel"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"Vil du slå Bluetooth til?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"Bluetooth skal være slået til, før du kan knytte dit tastatur til din tablet."</string>
@@ -813,15 +813,15 @@
<string name="accessibility_qs_edit_remove_tile" msgid="7484493384665907197">"Fjern <xliff:g id="TILE_NAME">%1$s</xliff:g>"</string>
<string name="accessibility_qs_edit_tile_add" msgid="3520406665865985109">"Føj <xliff:g id="TILE_NAME">%1$s</xliff:g> til position <xliff:g id="POSITION">%2$d</xliff:g>"</string>
<string name="accessibility_qs_edit_tile_move" msgid="3108103090006972938">"Flyt <xliff:g id="TILE_NAME">%1$s</xliff:g> til position <xliff:g id="POSITION">%2$d</xliff:g>"</string>
- <string name="accessibility_desc_quick_settings_edit" msgid="8073587401747016103">"Redigeringsværktøj for Hurtige indstillinger."</string>
+ <string name="accessibility_desc_quick_settings_edit" msgid="8073587401747016103">"Redigeringsværktøj til Kvikmenu."</string>
<string name="accessibility_desc_notification_icon" msgid="8352414185263916335">"<xliff:g id="ID_1">%1$s</xliff:g>-notifikation: <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="dock_forced_resizable" msgid="5914261505436217520">"Appen fungerer muligvis ikke i opdelt skærm."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="3871617304250207291">"Appen understøtter ikke opdelt skærm."</string>
<string name="forced_resizable_secondary_display" msgid="4230857851756391925">"Appen fungerer muligvis ikke på sekundære skærme."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="7793821742158306742">"Appen kan ikke åbnes på sekundære skærme."</string>
<string name="accessibility_quick_settings_settings" msgid="6132460890024942157">"Åbn Indstillinger."</string>
- <string name="accessibility_quick_settings_expand" msgid="2375165227880477530">"Åbn Hurtige indstillinger."</string>
- <string name="accessibility_quick_settings_collapse" msgid="1792625797142648105">"Luk Hurtige indstillinger."</string>
+ <string name="accessibility_quick_settings_expand" msgid="2375165227880477530">"Åbn Kvikmenu."</string>
+ <string name="accessibility_quick_settings_collapse" msgid="1792625797142648105">"Luk Kvikmenu."</string>
<string name="accessibility_quick_settings_alarm_set" msgid="1863000242431528676">"Alarmen er indstillet."</string>
<string name="accessibility_quick_settings_user" msgid="1567445362870421770">"Logget ind som <xliff:g id="ID_1">%s</xliff:g>"</string>
<string name="data_connection_no_internet" msgid="4503302451650972989">"Intet internet"</string>
diff --git a/packages/SystemUI/res/values-en-rCA/strings.xml b/packages/SystemUI/res/values-en-rCA/strings.xml
index 9903cc0..938cb0d 100644
--- a/packages/SystemUI/res/values-en-rCA/strings.xml
+++ b/packages/SystemUI/res/values-en-rCA/strings.xml
@@ -401,6 +401,7 @@
<string name="speed_bump_explanation" msgid="1288875699658819755">"Less urgent notifications below"</string>
<string name="notification_tap_again" msgid="7590196980943943842">"Tap again to open"</string>
<string name="keyguard_unlock" msgid="6035822649218712063">"Swipe up to open"</string>
+ <string name="keyguard_retry" msgid="5221600879614948709">"Swipe up to try again"</string>
<string name="do_disclosure_generic" msgid="5615898451805157556">"This device is managed by your organisation"</string>
<string name="do_disclosure_with_name" msgid="5640615509915445501">"This device is managed by <xliff:g id="ORGANIZATION_NAME">%s</xliff:g>"</string>
<string name="phone_hint" msgid="4872890986869209950">"Swipe from icon for phone"</string>
@@ -544,6 +545,7 @@
<string name="screen_pinning_description_recents_invisible_accessible" msgid="6134833683151189507">"This keeps it in view until you unpin. Touch & hold Home to unpin."</string>
<string name="screen_pinning_toast" msgid="2266705122951934150">"To unpin this screen, touch & hold Back and Overview buttons"</string>
<string name="screen_pinning_toast_recents_invisible" msgid="8252402309499161281">"To unpin this screen, touch & hold Back and Home buttons"</string>
+ <string name="screen_pinning_toast_gesture_nav" msgid="5070548776081664958">"To unpin this screen, swipe up & hold"</string>
<string name="screen_pinning_positive" msgid="3783985798366751226">"Got it"</string>
<string name="screen_pinning_negative" msgid="3741602308343880268">"No, thanks"</string>
<string name="screen_pinning_start" msgid="1022122128489278317">"Screen pinned"</string>
diff --git a/packages/SystemUI/res/values-en-rXC/strings.xml b/packages/SystemUI/res/values-en-rXC/strings.xml
index d8c862a..139215aa 100644
--- a/packages/SystemUI/res/values-en-rXC/strings.xml
+++ b/packages/SystemUI/res/values-en-rXC/strings.xml
@@ -401,6 +401,7 @@
<string name="speed_bump_explanation" msgid="1288875699658819755">"Less urgent notifications below"</string>
<string name="notification_tap_again" msgid="7590196980943943842">"Tap again to open"</string>
<string name="keyguard_unlock" msgid="6035822649218712063">"Swipe up to open"</string>
+ <string name="keyguard_retry" msgid="5221600879614948709">"Swipe up to try again"</string>
<string name="do_disclosure_generic" msgid="5615898451805157556">"This device is managed by your organization"</string>
<string name="do_disclosure_with_name" msgid="5640615509915445501">"This device is managed by <xliff:g id="ORGANIZATION_NAME">%s</xliff:g>"</string>
<string name="phone_hint" msgid="4872890986869209950">"Swipe from icon for phone"</string>
@@ -544,6 +545,7 @@
<string name="screen_pinning_description_recents_invisible_accessible" msgid="6134833683151189507">"This keeps it in view until you unpin. Touch & hold Home to unpin."</string>
<string name="screen_pinning_toast" msgid="2266705122951934150">"To unpin this screen, touch & hold Back and Overview buttons"</string>
<string name="screen_pinning_toast_recents_invisible" msgid="8252402309499161281">"To unpin this screen, touch & hold Back and Home buttons"</string>
+ <string name="screen_pinning_toast_gesture_nav" msgid="5070548776081664958">"To unpin this screen, swipe up & hold"</string>
<string name="screen_pinning_positive" msgid="3783985798366751226">"Got it"</string>
<string name="screen_pinning_negative" msgid="3741602308343880268">"No thanks"</string>
<string name="screen_pinning_start" msgid="1022122128489278317">"Screen pinned"</string>
diff --git a/packages/SystemUI/res/values-fr-rCA/strings.xml b/packages/SystemUI/res/values-fr-rCA/strings.xml
index 137b780..e8e4232 100644
--- a/packages/SystemUI/res/values-fr-rCA/strings.xml
+++ b/packages/SystemUI/res/values-fr-rCA/strings.xml
@@ -417,7 +417,7 @@
<string name="keyguard_indication_charging_time_wireless" msgid="6959284458466962592">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • En recharge sans fil (<xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> jusqu\'à la recharge complète)"</string>
<string name="keyguard_indication_charging_time" msgid="2056340799276374421">"En recharge : <xliff:g id="PERCENTAGE">%2$s</xliff:g> (<xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> jusqu\'à charge complète)"</string>
<string name="keyguard_indication_charging_time_fast" msgid="7767562163577492332">"En recharge rapide : <xliff:g id="PERCENTAGE">%2$s</xliff:g> (<xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> jusqu\'à ch. comp.)"</string>
- <string name="keyguard_indication_charging_time_slowly" msgid="3769655133567307069">"En recharge lente : <xliff:g id="PERCENTAGE">%2$s</xliff:g> (<xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> jusqu\'à ch. comp.)"</string>
+ <string name="keyguard_indication_charging_time_slowly" msgid="3769655133567307069">"Recharge lente : <xliff:g id="PERCENTAGE">%2$s</xliff:g> (à 100 %% dans <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>)"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="7305948938141024937">"Changer d\'utilisateur"</string>
<string name="accessibility_multi_user_switch_switcher_with_current" msgid="8434880595284601601">"Changer d\'utilisateur (utilisateur actuel <xliff:g id="CURRENT_USER_NAME">%s</xliff:g>)"</string>
<string name="accessibility_multi_user_switch_inactive" msgid="1424081831468083402">"Utilisateur actuel : <xliff:g id="CURRENT_USER_NAME">%s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-gl/strings.xml b/packages/SystemUI/res/values-gl/strings.xml
index 15d9c2b..5480eab8 100644
--- a/packages/SystemUI/res/values-gl/strings.xml
+++ b/packages/SystemUI/res/values-gl/strings.xml
@@ -599,7 +599,7 @@
<string name="status_bar_work" msgid="6022553324802866373">"Perfil de traballo"</string>
<string name="status_bar_airplane" msgid="7057575501472249002">"Modo avión"</string>
<string name="add_tile" msgid="2995389510240786221">"Engade un atallo"</string>
- <string name="broadcast_tile" msgid="3894036511763289383">"Atallo de emisión"</string>
+ <string name="broadcast_tile" msgid="3894036511763289383">"Atallo de difusión"</string>
<string name="zen_alarm_warning_indef" msgid="3482966345578319605">"Non escoitarás a alarma seguinte <xliff:g id="WHEN">%1$s</xliff:g> a menos que desactives esta opción antes desa hora"</string>
<string name="zen_alarm_warning" msgid="444533119582244293">"Non escoitarás a alarma seguinte <xliff:g id="WHEN">%1$s</xliff:g>"</string>
<string name="alarm_template" msgid="3980063409350522735">"ás <xliff:g id="WHEN">%1$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-gu/strings.xml b/packages/SystemUI/res/values-gu/strings.xml
index f90f494..1a15cf7 100644
--- a/packages/SystemUI/res/values-gu/strings.xml
+++ b/packages/SystemUI/res/values-gu/strings.xml
@@ -314,7 +314,7 @@
<string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="4930931771490695395">"શ્રવણ યંત્રો"</string>
<string name="quick_settings_bluetooth_secondary_label_transient" msgid="4551281899312150640">"ચાલુ કરી રહ્યાં છીએ…"</string>
<string name="quick_settings_brightness_label" msgid="6968372297018755815">"તેજ"</string>
- <string name="quick_settings_rotation_unlocked_label" msgid="7305323031808150099">"આપમેળે ફેરવો"</string>
+ <string name="quick_settings_rotation_unlocked_label" msgid="7305323031808150099">"ઑટો રોટેટ"</string>
<string name="accessibility_quick_settings_rotation" msgid="4231661040698488779">"સ્ક્રીનને આપમેળે ફેરવો"</string>
<string name="accessibility_quick_settings_rotation_value" msgid="8187398200140760213">"<xliff:g id="ID_1">%s</xliff:g> મોડ"</string>
<string name="quick_settings_rotation_locked_label" msgid="6359205706154282377">"પરિભ્રમણ લૉક થયું"</string>
diff --git a/packages/SystemUI/res/values-hu/strings.xml b/packages/SystemUI/res/values-hu/strings.xml
index 5f47e20..3e3696b 100644
--- a/packages/SystemUI/res/values-hu/strings.xml
+++ b/packages/SystemUI/res/values-hu/strings.xml
@@ -647,7 +647,7 @@
<string name="inline_minimize_button" msgid="966233327974702195">"Kis méret"</string>
<string name="inline_silent_button_silent" msgid="5315879183296940969">"Néma"</string>
<string name="inline_silent_button_stay_silent" msgid="6308371431217601009">"Néma megjelenítés"</string>
- <string name="inline_silent_button_alert" msgid="6008435419895088034">"Értesítések"</string>
+ <string name="inline_silent_button_alert" msgid="6008435419895088034">"Figyelemfelkeltő"</string>
<string name="inline_silent_button_keep_alerting" msgid="327696842264359693">"Értesítések folytatása"</string>
<string name="inline_turn_off_notifications" msgid="8635596135532202355">"Az értesítések kikapcsolása"</string>
<string name="inline_keep_showing_app" msgid="1723113469580031041">"Továbbra is megjelenjenek az alkalmazás értesítései?"</string>
diff --git a/packages/SystemUI/res/values-or/strings.xml b/packages/SystemUI/res/values-or/strings.xml
index 3760007..e9b150c 100644
--- a/packages/SystemUI/res/values-or/strings.xml
+++ b/packages/SystemUI/res/values-or/strings.xml
@@ -250,9 +250,9 @@
<string name="accessibility_quick_settings_close" msgid="3115847794692516306">"ପ୍ୟାନେଲ୍ ବନ୍ଦ କରନ୍ତୁ।"</string>
<string name="accessibility_quick_settings_more_time" msgid="3659274935356197708">"ଅଧିକ ସମୟ।"</string>
<string name="accessibility_quick_settings_less_time" msgid="2404728746293515623">"କମ୍ ସମୟ।"</string>
- <string name="accessibility_quick_settings_flashlight_off" msgid="4936432000069786988">"ଫ୍ଲାଶ୍ଲାଇଟ୍ ଅଫ୍ ଅଛି।"</string>
+ <string name="accessibility_quick_settings_flashlight_off" msgid="4936432000069786988">"ଫ୍ଲାସ୍ଲାଇଟ୍ ବନ୍ଦ ଅଛି।"</string>
<string name="accessibility_quick_settings_flashlight_unavailable" msgid="8012811023312280810">"ଟର୍ଚ୍ଚ ଲାଇଟ୍ ଅନୁପଲବ୍ଧ।"</string>
- <string name="accessibility_quick_settings_flashlight_on" msgid="2003479320007841077">"ଫ୍ଲାଶ୍ଲାଇଟ୍ ଅନ୍ ଅଛି।"</string>
+ <string name="accessibility_quick_settings_flashlight_on" msgid="2003479320007841077">"ଫ୍ଲାସ୍ଲାଇଟ୍ ଚାଲୁଅଛି।"</string>
<string name="accessibility_quick_settings_flashlight_changed_off" msgid="3303701786768224304">"ଟର୍ଚ୍ଚ ଲାଇଟ୍ ବନ୍ଦ ଅଛି।"</string>
<string name="accessibility_quick_settings_flashlight_changed_on" msgid="6531793301533894686">"ଟର୍ଚ୍ଚ ଲାଇଟ୍ ଅନ୍ ଅଛି।"</string>
<string name="accessibility_quick_settings_color_inversion_changed_off" msgid="4406577213290173911">"ରଙ୍ଗ ବିପରୀତିକରଣକୁ ବନ୍ଦ କରିଦିଆଗଲା।"</string>
@@ -362,7 +362,7 @@
<item quantity="one">%d ଡିଭାଇସ୍</item>
</plurals>
<string name="quick_settings_notifications_label" msgid="4818156442169154523">"ବିଜ୍ଞପ୍ତି"</string>
- <string name="quick_settings_flashlight_label" msgid="2133093497691661546">"ଫ୍ଲାଶ୍ଲାଇଟ"</string>
+ <string name="quick_settings_flashlight_label" msgid="2133093497691661546">"ଫ୍ଲାସ୍ଲାଇଟ୍"</string>
<string name="quick_settings_cellular_detail_title" msgid="3661194685666477347">"ମୋବାଇଲ୍ ଡାଟା"</string>
<string name="quick_settings_cellular_detail_data_usage" msgid="1964260360259312002">"ଡାଟାର ବ୍ୟବହାର"</string>
<string name="quick_settings_cellular_detail_remaining_data" msgid="722715415543541249">"ଅବଶିଷ୍ଟ ଡାଟା"</string>
diff --git a/packages/SystemUI/res/values-pl/strings.xml b/packages/SystemUI/res/values-pl/strings.xml
index 93a2c12..4be1a0f 100644
--- a/packages/SystemUI/res/values-pl/strings.xml
+++ b/packages/SystemUI/res/values-pl/strings.xml
@@ -465,10 +465,10 @@
<string name="media_projection_dialog_service_text" msgid="3075544489835858258">"Podczas nagrywania lub przesyłania usługa udostępniająca tę funkcję może rejestrować wszelkie informacje poufne wyświetlane na ekranie lub odtwarzane na urządzeniu takie jak dźwięki czy podawane hasła, informacje o płatnościach, zdjęcia i wiadomości."</string>
<string name="media_projection_dialog_title" msgid="8124184308671641248">"Ujawnianie poufnych informacji podczas przesyłania/nagrywania"</string>
<string name="media_projection_remember_text" msgid="3103510882172746752">"Nie pokazuj ponownie"</string>
- <string name="clear_all_notifications_text" msgid="814192889771462828">"Ukryj wszystkie"</string>
+ <string name="clear_all_notifications_text" msgid="814192889771462828">"Usuń wszystkie"</string>
<string name="manage_notifications_text" msgid="2386728145475108753">"Zarządzaj"</string>
- <string name="notification_section_header_gentle" msgid="4372438504154095677">"Powiadomienia ciche"</string>
- <string name="accessibility_notification_section_header_gentle_clear_all" msgid="4286716295850400959">"Wyczyść wszystkie ciche powiadomienia"</string>
+ <string name="notification_section_header_gentle" msgid="4372438504154095677">"Ciche powiadomienia"</string>
+ <string name="accessibility_notification_section_header_gentle_clear_all" msgid="4286716295850400959">"Usuń wszystkie ciche powiadomienia"</string>
<string name="dnd_suppressing_shade_text" msgid="1904574852846769301">"Powiadomienia wstrzymane przez tryb Nie przeszkadzać"</string>
<string name="media_projection_action_text" msgid="8470872969457985954">"Rozpocznij teraz"</string>
<string name="empty_shade_text" msgid="708135716272867002">"Brak powiadomień"</string>
diff --git a/packages/SystemUI/res/values-ta/strings.xml b/packages/SystemUI/res/values-ta/strings.xml
index d56ccef..2b12149 100644
--- a/packages/SystemUI/res/values-ta/strings.xml
+++ b/packages/SystemUI/res/values-ta/strings.xml
@@ -845,7 +845,7 @@
<string name="pip_skip_to_prev" msgid="1955311326688637914">"முந்தையதற்குச் செல்"</string>
<string name="thermal_shutdown_title" msgid="4458304833443861111">"வெப்பத்தினால் ஃபோன் ஆஃப் செய்யப்பட்டது"</string>
<string name="thermal_shutdown_message" msgid="9006456746902370523">"இப்போது உங்கள் ஃபோன் இயல்புநிலையில் இயங்குகிறது"</string>
- <string name="thermal_shutdown_dialog_message" msgid="566347880005304139">"உங்கள் ஃபோன் அதிகமாகச் சூடானதால், அதன் சூட்டைக் குறைக்க, ஆஃப் செய்யப்பட்டது. இப்போது உங்கள் ஃபோன் இயல்புநிலையில் இயங்குகிறது.\n\nபின்வருவனவற்றைச் செய்தால், ஃபோன் சூடாகலாம்:\n • அதிகளவு தரவைப் பயன்படுத்தும் பயன்பாடுகளை (எ.கா: கேமிங், வீடியோ (அ) வழிகாட்டுதல் பயன்பாடுகள்) பயன்படுத்துவது\n • பெரிய கோப்புகளைப் பதிவிறக்குவது/பதிவேற்றுவது\n • அதிக வெப்பநிலையில் ஃபோனைப் பயன்படுத்துவது"</string>
+ <string name="thermal_shutdown_dialog_message" msgid="566347880005304139">"உங்கள் ஃபோன் அதிகமாகச் சூடானதால், அதன் சூட்டைக் குறைக்க, ஆஃப் செய்யப்பட்டது. இப்போது உங்கள் ஃபோன் இயல்புநிலையில் இயங்குகிறது.\n\nபின்வருவனவற்றைச் செய்தால், ஃபோன் சூடாகலாம்:\n • அதிகளவு தரவைப் பயன்படுத்தும் ஆப்ஸை (எ.கா: கேமிங், வீடியோ (அ) வழிகாட்டுதல் பயன்பாடுகள்) பயன்படுத்துவது\n • பெரிய கோப்புகளைப் பதிவிறக்குவது/பதிவேற்றுவது\n • அதிக வெப்பநிலையில் ஃபோனைப் பயன்படுத்துவது"</string>
<string name="high_temp_title" msgid="4589508026407318374">"மொபைல் சூடாகிறது"</string>
<string name="high_temp_notif_message" msgid="5642466103153429279">"மொபைலின் வெப்ப அளவு குறையும் போது, சில அம்சங்களைப் பயன்படுத்த முடியாது"</string>
<string name="high_temp_dialog_message" msgid="6840700639374113553">"உங்கள் மொபைலின் வெப்ப அளவு தானாகவே குறையும். தொடர்ந்து நீங்கள் மொபைலைப் பயன்படுத்தலாம், ஆனால் அதன் வேகம் குறைவாக இருக்கக்கூடும்.\n\nமொபைலின் வெப்ப அளவு குறைந்தவுடன், அது இயல்பு நிலையில் இயங்கும்."</string>
diff --git a/packages/SystemUI/res/values-vi/strings.xml b/packages/SystemUI/res/values-vi/strings.xml
index 6f38b18..d3caa2b 100644
--- a/packages/SystemUI/res/values-vi/strings.xml
+++ b/packages/SystemUI/res/values-vi/strings.xml
@@ -55,11 +55,11 @@
<string name="label_view" msgid="6304565553218192990">"Xem"</string>
<string name="always_use_device" msgid="4015357883336738417">"Luôn mở <xliff:g id="APPLICATION">%1$s</xliff:g> khi kết nối <xliff:g id="USB_DEVICE">%2$s</xliff:g>"</string>
<string name="always_use_accessory" msgid="3257892669444535154">"Luôn mở <xliff:g id="APPLICATION">%1$s</xliff:g> khi kết nối <xliff:g id="USB_ACCESSORY">%2$s</xliff:g>"</string>
- <string name="usb_debugging_title" msgid="4513918393387141949">"Cho phép gỡ lỗi USB?"</string>
+ <string name="usb_debugging_title" msgid="4513918393387141949">"Cho phép gỡ lỗi qua USB?"</string>
<string name="usb_debugging_message" msgid="2220143855912376496">"Tệp tham chiếu khóa RSA của máy tính là:\n<xliff:g id="FINGERPRINT">%1$s</xliff:g>"</string>
<string name="usb_debugging_always" msgid="303335496705863070">"Luôn cho phép từ máy tính này"</string>
<string name="usb_debugging_allow" msgid="2272145052073254852">"Cho phép"</string>
- <string name="usb_debugging_secondary_user_title" msgid="6353808721761220421">"Tính năng gỡ lỗi USB không được phép"</string>
+ <string name="usb_debugging_secondary_user_title" msgid="6353808721761220421">"Không cho phép chế độ gỡ lỗi qua USB"</string>
<string name="usb_debugging_secondary_user_message" msgid="6067122453571699801">"Người dùng hiện đã đăng nhập vào thiết bị này không thể bật tính năng gỡ lỗi USB. Để sử dụng tính năng này, hãy chuyển sang người dùng chính."</string>
<string name="usb_contaminant_title" msgid="206854874263058490">"Đã tắt cổng USB"</string>
<string name="usb_contaminant_message" msgid="7379089091591609111">"Để bảo vệ thiết bị của bạn khỏi chất lỏng hay mảnh vỡ, cổng USB sẽ tắt và không phát hiện được bất kỳ phụ kiện nào.\n\nBạn sẽ nhận được thông báo khi có thể sử dụng lại cổng USB."</string>
diff --git a/packages/SystemUI/res/values-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml
index 03e31cd..457206d 100644
--- a/packages/SystemUI/res/values-zh-rTW/strings.xml
+++ b/packages/SystemUI/res/values-zh-rTW/strings.xml
@@ -264,8 +264,8 @@
<string name="accessibility_quick_settings_work_mode_on" msgid="7650588553988014341">"工作模式已開啟。"</string>
<string name="accessibility_quick_settings_work_mode_changed_off" msgid="5605534876107300711">"工作模式已關閉。"</string>
<string name="accessibility_quick_settings_work_mode_changed_on" msgid="249840330756998612">"工作模式已開啟。"</string>
- <string name="accessibility_quick_settings_data_saver_changed_off" msgid="650231949881093289">"Data Saver 已關閉。"</string>
- <string name="accessibility_quick_settings_data_saver_changed_on" msgid="4218725402373934151">"Data Saver 已開啟。"</string>
+ <string name="accessibility_quick_settings_data_saver_changed_off" msgid="650231949881093289">"數據節省模式已關閉。"</string>
+ <string name="accessibility_quick_settings_data_saver_changed_on" msgid="4218725402373934151">"數據節省模式已開啟。"</string>
<string name="accessibility_quick_settings_sensor_privacy_changed_off" msgid="5152819588955163090">"已關閉感應器隱私設定。"</string>
<string name="accessibility_quick_settings_sensor_privacy_changed_on" msgid="529705259565826355">"已開啟感應器隱私設定。"</string>
<string name="accessibility_brightness" msgid="8003681285547803095">"螢幕亮度"</string>
@@ -459,8 +459,8 @@
<string name="media_projection_remember_text" msgid="3103510882172746752">"不要再顯示"</string>
<string name="clear_all_notifications_text" msgid="814192889771462828">"全部清除"</string>
<string name="manage_notifications_text" msgid="2386728145475108753">"管理"</string>
- <string name="notification_section_header_gentle" msgid="4372438504154095677">"無聲通知"</string>
- <string name="accessibility_notification_section_header_gentle_clear_all" msgid="4286716295850400959">"清除所有無聲通知"</string>
+ <string name="notification_section_header_gentle" msgid="4372438504154095677">"靜音通知"</string>
+ <string name="accessibility_notification_section_header_gentle_clear_all" msgid="4286716295850400959">"清除所有靜音通知"</string>
<string name="dnd_suppressing_shade_text" msgid="1904574852846769301">"「零打擾」模式已將通知設為暫停"</string>
<string name="media_projection_action_text" msgid="8470872969457985954">"立即開始"</string>
<string name="empty_shade_text" msgid="708135716272867002">"沒有通知"</string>
@@ -645,7 +645,7 @@
<string name="inline_block_button" msgid="8735843688021655065">"封鎖"</string>
<string name="inline_keep_button" msgid="6665940297019018232">"繼續顯示"</string>
<string name="inline_minimize_button" msgid="966233327974702195">"最小化"</string>
- <string name="inline_silent_button_silent" msgid="5315879183296940969">"無聲"</string>
+ <string name="inline_silent_button_silent" msgid="5315879183296940969">"靜音"</string>
<string name="inline_silent_button_stay_silent" msgid="6308371431217601009">"繼續顯示通知但不發出音效"</string>
<string name="inline_silent_button_alert" msgid="6008435419895088034">"快訊"</string>
<string name="inline_silent_button_keep_alerting" msgid="327696842264359693">"繼續顯示通知"</string>
@@ -747,8 +747,8 @@
<string name="accessibility_status_bar_headphones" msgid="9156307120060559989">"已與耳機連線"</string>
<string name="accessibility_status_bar_headset" msgid="8666419213072449202">"已與耳機連線"</string>
<string name="data_saver" msgid="5037565123367048522">"數據節省模式"</string>
- <string name="accessibility_data_saver_on" msgid="8454111686783887148">"Data Saver 已開啟"</string>
- <string name="accessibility_data_saver_off" msgid="8841582529453005337">"Data Saver 已關閉"</string>
+ <string name="accessibility_data_saver_on" msgid="8454111686783887148">"數據節省模式已開啟"</string>
+ <string name="accessibility_data_saver_off" msgid="8841582529453005337">"數據節省模式已關閉"</string>
<string name="switch_bar_on" msgid="1142437840752794229">"開啟"</string>
<string name="switch_bar_off" msgid="8803270596930432874">"關閉"</string>
<string name="nav_bar" msgid="1993221402773877607">"導覽列"</string>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index e4a8aab..720074b 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -217,8 +217,6 @@
}
}
- private static KeyguardUpdateMonitor sInstance;
-
private final Context mContext;
private final boolean mIsPrimaryUser;
HashMap<Integer, SimData> mSimDatas = new HashMap<Integer, SimData>();
@@ -1389,15 +1387,6 @@
Trace.endSection();
}
-
- /** Provides access to the static instance. */
- public static KeyguardUpdateMonitor getInstance(Context context) {
- if (sInstance == null) {
- sInstance = new KeyguardUpdateMonitor(context, Looper.getMainLooper());
- }
- return sInstance;
- }
-
protected void handleStartedGoingToSleep(int arg1) {
checkIsHandlerThread();
mLockIconPressed = false;
@@ -1830,7 +1819,10 @@
return shouldListen;
}
- private boolean shouldListenForFace() {
+ /**
+ * If face auth is allows to scan on this exact moment.
+ */
+ public boolean shouldListenForFace() {
final boolean awakeKeyguard = mKeyguardIsVisible && mDeviceInteractive && !mGoingToSleep;
final int user = getCurrentUser();
final int strongAuth = mStrongAuthTracker.getStrongAuthForUser(user);
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactory.java
index 0f99674..e8b5285 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactory.java
@@ -21,6 +21,8 @@
import android.content.ContentProvider;
import android.content.Context;
import android.content.Intent;
+import android.os.Process;
+import android.util.Log;
import androidx.annotation.NonNull;
import androidx.core.app.AppComponentFactory;
@@ -38,6 +40,7 @@
*/
public class SystemUIAppComponentFactory extends AppComponentFactory {
+ private static final String TAG = "AppComponentFactory";
@Inject
public ContextComponentHelper mComponentHelper;
@@ -57,6 +60,9 @@
SystemUIFactory.createFromConfig(context);
SystemUIFactory.getInstance().getRootComponent().inject(
SystemUIAppComponentFactory.this);
+ Log.d(TAG, "Initialized during Application creation in Process "
+ + Process.myPid() + ", Thread " + Process.myTid());
+ Log.d(TAG, "mComponentHelper: " + mComponentHelper);
}
);
}
@@ -77,6 +83,8 @@
SystemUIFactory.createFromConfig(context);
SystemUIFactory.getInstance().getRootComponent().inject(
contentProvider);
+ Log.d(TAG, "Initialized during ContentProvider creation in Process "
+ + Process.myPid() + ", Thread " + Process.myTid());
}
);
}
@@ -89,6 +97,12 @@
public Service instantiateServiceCompat(
@NonNull ClassLoader cl, @NonNull String className, Intent intent)
throws InstantiationException, IllegalAccessException, ClassNotFoundException {
+ if (mComponentHelper == null) {
+ // Everything is about to crash if this is true, but that is inevitable. We either crash
+ // here or crash lower in the stack. Better to crash early!
+ Log.wtf(TAG, "Uninitialized mComponentHelper in Process" + Process.myPid() + ", Thread "
+ + Process.myTid());
+ }
Service service = mComponentHelper.resolveService(className);
if (service != null) {
return service;
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
index 00bfb3f..bab64db 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
@@ -417,6 +417,9 @@
mDozeSensors.dump(pw);
}
+ /**
+ * @see DozeSensors.ProxSensor
+ */
private abstract class ProximityCheck implements SensorEventListener, Runnable {
private static final int TIMEOUT_DELAY_MS = 500;
@@ -428,6 +431,7 @@
private boolean mRegistered;
private boolean mFinished;
private float mMaxRange;
+ private boolean mUsingBrightnessSensor;
protected abstract void onProximityResult(int result);
@@ -435,6 +439,7 @@
Preconditions.checkState(!mFinished && !mRegistered);
Sensor sensor = DozeSensors.findSensorWithType(mSensorManager,
mContext.getString(R.string.doze_brightness_sensor_type));
+ mUsingBrightnessSensor = sensor != null;
if (sensor == null) {
sensor = mSensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY);
}
@@ -453,6 +458,9 @@
mRegistered = true;
}
+ /**
+ * @see DozeSensors.ProxSensor#onSensorChanged(SensorEvent)
+ */
@Override
public void onSensorChanged(SensorEvent event) {
if (event.values.length == 0) {
@@ -462,7 +470,14 @@
if (DozeMachine.DEBUG) {
Log.d(TAG, "ProxCheck: Event: value=" + event.values[0] + " max=" + mMaxRange);
}
- final boolean isNear = event.values[0] < mMaxRange;
+ final boolean isNear;
+ if (mUsingBrightnessSensor) {
+ // The custom brightness sensor is gated by the proximity sensor and will
+ // return 0 whenever prox is covered.
+ isNear = event.values[0] == 0;
+ } else {
+ isNear = event.values[0] < mMaxRange;
+ }
finishWithResult(isNear ? RESULT_NEAR : RESULT_FAR);
}
}
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 f91b03e..a79ecd9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -39,6 +39,7 @@
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.Region;
+import android.hardware.biometrics.BiometricSourceType;
import android.os.PowerManager;
import android.os.SystemClock;
import android.provider.DeviceConfig;
@@ -62,6 +63,7 @@
import com.android.keyguard.KeyguardClockSwitch;
import com.android.keyguard.KeyguardStatusView;
import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.systemui.DejankUtils;
import com.android.systemui.Dependency;
import com.android.systemui.Interpolators;
@@ -172,6 +174,25 @@
R.id.keyguard_hun_animator_start_tag);
private static final AnimationProperties KEYGUARD_HUN_PROPERTIES =
new AnimationProperties().setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
+ @VisibleForTesting
+ final KeyguardUpdateMonitorCallback mKeyguardUpdateCallback =
+ new KeyguardUpdateMonitorCallback() {
+ @Override
+ public void onBiometricRunningStateChanged(boolean running,
+ BiometricSourceType biometricSourceType) {
+ boolean keyguardOrShadeLocked = mBarState == StatusBarState.KEYGUARD
+ || mBarState == StatusBarState.SHADE_LOCKED;
+ if (!running && mFirstBypassAttempt && keyguardOrShadeLocked && !mDozing) {
+ mFirstBypassAttempt = false;
+ animateKeyguardStatusBarIn(StackStateAnimator.ANIMATION_DURATION_STANDARD);
+ }
+ }
+
+ @Override
+ public void onFinishedGoingToSleep(int why) {
+ mFirstBypassAttempt = mKeyguardBypassController.getBypassEnabled();
+ }
+ };
private final InjectionInflationController mInjectionInflationController;
private final PowerManager mPowerManager;
@@ -392,6 +413,7 @@
private boolean mShowingKeyguardHeadsUp;
private boolean mAllowExpandForSmallExpansion;
private Runnable mExpandAfterLayoutRunnable;
+ private boolean mFirstBypassAttempt;
private PluginManager mPluginManager;
private FrameLayout mPluginFrame;
@@ -427,6 +449,7 @@
mThemeResId = context.getThemeResId();
mKeyguardBypassController = bypassController;
mUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
+ mFirstBypassAttempt = mKeyguardBypassController.getBypassEnabled();
dynamicPrivacyController.addListener(this);
mBottomAreaShadeAlphaAnimator = ValueAnimator.ofFloat(1f, 0);
@@ -519,6 +542,7 @@
Dependency.get(StatusBarStateController.class).addCallback(this);
Dependency.get(ZenModeController.class).addCallback(this);
Dependency.get(ConfigurationController.class).addCallback(this);
+ mUpdateMonitor.registerCallback(mKeyguardUpdateCallback);
// Theme might have changed between inflating this view and attaching it to the window, so
// force a call to onThemeChanged
onThemeChanged();
@@ -531,6 +555,7 @@
Dependency.get(StatusBarStateController.class).removeCallback(this);
Dependency.get(ZenModeController.class).removeCallback(this);
Dependency.get(ConfigurationController.class).removeCallback(this);
+ mUpdateMonitor.removeCallback(mKeyguardUpdateCallback);
}
@Override
@@ -2366,7 +2391,9 @@
* mKeyguardStatusBarAnimateAlpha;
newAlpha *= 1.0f - mKeyguardHeadsUpShowingAmount;
mKeyguardStatusBar.setAlpha(newAlpha);
- mKeyguardStatusBar.setVisibility(newAlpha != 0f && !mDozing ? VISIBLE : INVISIBLE);
+ boolean hideForBypass = mFirstBypassAttempt && mUpdateMonitor.shouldListenForFace();
+ mKeyguardStatusBar.setVisibility(newAlpha != 0f && !mDozing && !hideForBypass
+ ? VISIBLE : INVISIBLE);
}
private void updateKeyguardBottomAreaAlpha() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
index 86ab3a7..2f24494 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
@@ -21,19 +21,23 @@
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.StatusBarManager;
+import android.hardware.biometrics.BiometricSourceType;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.MotionEvent;
+import android.view.View;
import android.view.ViewGroup;
import androidx.test.filters.SmallTest;
import com.android.keyguard.KeyguardStatusView;
+import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.SystemUIFactory;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.FalsingManager;
@@ -51,7 +55,6 @@
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.ZenModeController;
-import com.android.systemui.tuner.TunerService;
import com.android.systemui.util.InjectionInflationController;
import org.junit.Before;
@@ -101,7 +104,11 @@
@Mock
private KeyguardAffordanceHelper mAffordanceHelper;
@Mock
+ private KeyguardUpdateMonitor mUpdateMonitor;
+ @Mock
private FalsingManager mFalsingManager;
+ @Mock
+ private KeyguardBypassController mKeyguardBypassController;
private NotificationPanelView mNotificationPanelView;
@Before
@@ -112,22 +119,21 @@
when(mHeadsUpCallback.getContext()).thenReturn(mContext);
mDependency.injectTestDependency(StatusBarStateController.class,
mStatusBarStateController);
+ mDependency.injectTestDependency(KeyguardUpdateMonitor.class, mUpdateMonitor);
mDependency.injectMockDependency(ShadeController.class);
mDependency.injectMockDependency(NotificationLockscreenUserManager.class);
mDependency.injectMockDependency(ConfigurationController.class);
mDependency.injectMockDependency(ZenModeController.class);
- KeyguardBypassController bypassController = new KeyguardBypassController(mContext,
- mock(TunerService.class), mStatusBarStateController,
- mock(NotificationLockscreenUserManager.class));
NotificationWakeUpCoordinator coordinator =
new NotificationWakeUpCoordinator(mContext,
mock(HeadsUpManagerPhone.class),
new StatusBarStateControllerImpl(),
- bypassController);
+ mKeyguardBypassController);
PulseExpansionHandler expansionHandler = new PulseExpansionHandler(mContext, coordinator,
- bypassController, mHeadsUpManager, mock(NotificationRoundnessManager.class));
+ mKeyguardBypassController, mHeadsUpManager,
+ mock(NotificationRoundnessManager.class));
mNotificationPanelView = new TestableNotificationPanelView(coordinator, expansionHandler,
- bypassController);
+ mKeyguardBypassController);
mNotificationPanelView.setHeadsUpManager(mHeadsUpManager);
mNotificationPanelView.setBar(mPanelBar);
@@ -187,6 +193,20 @@
assertThat(mNotificationPanelView.isTrackingBlocked()).isFalse();
}
+ @Test
+ public void testKeyguardStatusBarVisibility_hiddenForBypass() {
+ when(mUpdateMonitor.shouldListenForFace()).thenReturn(true);
+ mNotificationPanelView.mKeyguardUpdateCallback.onBiometricRunningStateChanged(true,
+ BiometricSourceType.FACE);
+ verify(mKeyguardStatusBar, never()).setVisibility(View.VISIBLE);
+
+ when(mKeyguardBypassController.getBypassEnabled()).thenReturn(true);
+ mNotificationPanelView.mKeyguardUpdateCallback.onFinishedGoingToSleep(0);
+ mNotificationPanelView.mKeyguardUpdateCallback.onBiometricRunningStateChanged(true,
+ BiometricSourceType.FACE);
+ verify(mKeyguardStatusBar, never()).setVisibility(View.VISIBLE);
+ }
+
private class TestableNotificationPanelView extends NotificationPanelView {
TestableNotificationPanelView(NotificationWakeUpCoordinator coordinator,
PulseExpansionHandler expansionHandler,
diff --git a/packages/overlays/DisplayCutoutEmulationCornerOverlay/res/values-ru/strings.xml b/packages/overlays/DisplayCutoutEmulationCornerOverlay/res/values-ru/strings.xml
index dc77981..0ff85fe 100644
--- a/packages/overlays/DisplayCutoutEmulationCornerOverlay/res/values-ru/strings.xml
+++ b/packages/overlays/DisplayCutoutEmulationCornerOverlay/res/values-ru/strings.xml
@@ -17,5 +17,5 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="1677693377327336341">"Сделать вырез в углу"</string>
+ <string name="display_cutout_emulation_overlay" msgid="1677693377327336341">"В правом углу"</string>
</resources>
diff --git a/packages/overlays/DisplayCutoutEmulationDoubleOverlay/res/values-ru/strings.xml b/packages/overlays/DisplayCutoutEmulationDoubleOverlay/res/values-ru/strings.xml
index a02eaf7..2493da3 100644
--- a/packages/overlays/DisplayCutoutEmulationDoubleOverlay/res/values-ru/strings.xml
+++ b/packages/overlays/DisplayCutoutEmulationDoubleOverlay/res/values-ru/strings.xml
@@ -17,5 +17,5 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="5323179900047630217">"Увеличить вырез вдвое"</string>
+ <string name="display_cutout_emulation_overlay" msgid="5323179900047630217">"Сверху и снизу"</string>
</resources>
diff --git a/packages/overlays/DisplayCutoutEmulationTallOverlay/res/values-ru/strings.xml b/packages/overlays/DisplayCutoutEmulationTallOverlay/res/values-ru/strings.xml
index 1d1656d..89ac1c3 100644
--- a/packages/overlays/DisplayCutoutEmulationTallOverlay/res/values-ru/strings.xml
+++ b/packages/overlays/DisplayCutoutEmulationTallOverlay/res/values-ru/strings.xml
@@ -17,5 +17,5 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="6424539415439220018">"Сделать вырез выше"</string>
+ <string name="display_cutout_emulation_overlay" msgid="6424539415439220018">"Сверху"</string>
</resources>
diff --git a/services/core/java/com/android/server/PackageWatchdog.java b/services/core/java/com/android/server/PackageWatchdog.java
index 5672a13..3fbb21e 100644
--- a/services/core/java/com/android/server/PackageWatchdog.java
+++ b/services/core/java/com/android/server/PackageWatchdog.java
@@ -865,8 +865,8 @@
MonitoredPackage p = it.next();
int oldState = p.getHealthCheckStateLocked();
int newState = p.handleElapsedTimeLocked(elapsedMs);
- if (oldState != MonitoredPackage.STATE_FAILED
- && newState == MonitoredPackage.STATE_FAILED) {
+ if (oldState != HealthCheckState.FAILED
+ && newState == HealthCheckState.FAILED) {
Slog.i(TAG, "Package " + p.mName + " failed health check");
failedPackages.add(p);
}
@@ -941,6 +941,23 @@
}
}
+ @Retention(SOURCE)
+ @IntDef(value = {
+ HealthCheckState.ACTIVE,
+ HealthCheckState.INACTIVE,
+ HealthCheckState.PASSED,
+ HealthCheckState.FAILED})
+ public @interface HealthCheckState {
+ // The package has not passed health check but has requested a health check
+ int ACTIVE = 0;
+ // The package has not passed health check and has not requested a health check
+ int INACTIVE = 1;
+ // The package has passed health check
+ int PASSED = 2;
+ // The package has failed health check
+ int FAILED = 3;
+ }
+
/**
* Represents a package and its health check state along with the time
* it should be monitored for.
@@ -949,23 +966,12 @@
* instances of this class.
*/
class MonitoredPackage {
- // Health check states
- // TODO(b/120598832): Prefix with HEALTH_CHECK
- // mName has not passed health check but has requested a health check
- public static final int STATE_ACTIVE = 0;
- // mName has not passed health check and has not requested a health check
- public static final int STATE_INACTIVE = 1;
- // mName has passed health check
- public static final int STATE_PASSED = 2;
- // mName has failed health check
- public static final int STATE_FAILED = 3;
-
//TODO(b/120598832): VersionedPackage?
private final String mName;
// One of STATE_[ACTIVE|INACTIVE|PASSED|FAILED]. Updated on construction and after
// methods that could change the health check state: handleElapsedTimeLocked and
// tryPassHealthCheckLocked
- private int mHealthCheckState = STATE_INACTIVE;
+ private int mHealthCheckState = HealthCheckState.INACTIVE;
// Whether an explicit health check has passed.
// This value in addition with mHealthCheckDurationMs determines the health check state
// of the package, see #getHealthCheckStateLocked
@@ -1052,7 +1058,7 @@
+ ". Using total duration " + mDurationMs + "ms instead");
initialHealthCheckDurationMs = mDurationMs;
}
- if (mHealthCheckState == STATE_INACTIVE) {
+ if (mHealthCheckState == HealthCheckState.INACTIVE) {
// Transitions to ACTIVE
mHealthCheckDurationMs = initialHealthCheckDurationMs;
}
@@ -1072,7 +1078,7 @@
}
// Transitions to FAILED if now <= 0 and health check not passed
mDurationMs -= elapsedMs;
- if (mHealthCheckState == STATE_ACTIVE) {
+ if (mHealthCheckState == HealthCheckState.ACTIVE) {
// We only update health check durations if we have #setHealthCheckActiveLocked
// This ensures we don't leave the INACTIVE state for an unexpected elapsed time
// Transitions to FAILED if now <= 0 and health check not passed
@@ -1082,14 +1088,15 @@
}
/**
- * Marks the health check as passed and transitions to {@link #STATE_PASSED}
- * if not yet {@link #STATE_FAILED}.
+ * Marks the health check as passed and transitions to {@link HealthCheckState.PASSED}
+ * if not yet {@link HealthCheckState.FAILED}.
*
- * @return the new health check state
+ * @return the new {@link HealthCheckState health check state}
*/
@GuardedBy("mLock")
+ @HealthCheckState
public int tryPassHealthCheckLocked() {
- if (mHealthCheckState != STATE_FAILED) {
+ if (mHealthCheckState != HealthCheckState.FAILED) {
// FAILED is a final state so only pass if we haven't failed
// Transition to PASSED
mHasPassedHealthCheck = true;
@@ -1102,12 +1109,11 @@
return mName;
}
- //TODO(b/120598832): IntDef
/**
- * Returns the current health check state, any of {@link #STATE_ACTIVE},
- * {@link #STATE_INACTIVE} or {@link #STATE_PASSED}
+ * Returns the current {@link HealthCheckState health check state}.
*/
@GuardedBy("mLock")
+ @HealthCheckState
public int getHealthCheckStateLocked() {
return mHealthCheckState;
}
@@ -1140,28 +1146,30 @@
*/
@GuardedBy("mLock")
public boolean isPendingHealthChecksLocked() {
- return mHealthCheckState == STATE_ACTIVE || mHealthCheckState == STATE_INACTIVE;
+ return mHealthCheckState == HealthCheckState.ACTIVE
+ || mHealthCheckState == HealthCheckState.INACTIVE;
}
/**
* Updates the health check state based on {@link #mHasPassedHealthCheck}
* and {@link #mHealthCheckDurationMs}.
*
- * @return the new health check state
+ * @return the new {@link HealthCheckState health check state}
*/
@GuardedBy("mLock")
+ @HealthCheckState
private int updateHealthCheckStateLocked() {
int oldState = mHealthCheckState;
if (mHasPassedHealthCheck) {
// Set final state first to avoid ambiguity
- mHealthCheckState = STATE_PASSED;
+ mHealthCheckState = HealthCheckState.PASSED;
} else if (mHealthCheckDurationMs <= 0 || mDurationMs <= 0) {
// Set final state first to avoid ambiguity
- mHealthCheckState = STATE_FAILED;
+ mHealthCheckState = HealthCheckState.FAILED;
} else if (mHealthCheckDurationMs == Long.MAX_VALUE) {
- mHealthCheckState = STATE_INACTIVE;
+ mHealthCheckState = HealthCheckState.INACTIVE;
} else {
- mHealthCheckState = STATE_ACTIVE;
+ mHealthCheckState = HealthCheckState.ACTIVE;
}
Slog.i(TAG, "Updated health check state for package " + mName + ": "
+ toString(oldState) + " -> " + toString(mHealthCheckState));
@@ -1169,15 +1177,15 @@
}
/** Returns a {@link String} representation of the current health check state. */
- private String toString(int state) {
+ private String toString(@HealthCheckState int state) {
switch (state) {
- case STATE_ACTIVE:
+ case HealthCheckState.ACTIVE:
return "ACTIVE";
- case STATE_INACTIVE:
+ case HealthCheckState.INACTIVE:
return "INACTIVE";
- case STATE_PASSED:
+ case HealthCheckState.PASSED:
return "PASSED";
- case STATE_FAILED:
+ case HealthCheckState.FAILED:
return "FAILED";
default:
return "UNKNOWN";
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index f7e825e..eb2723a 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -79,7 +79,6 @@
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
-import java.util.stream.Collectors;
/**
* Since phone process can be restarted, this class provides a centralized place
@@ -849,10 +848,7 @@
}
}
if ((events & PhoneStateListener
- .LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE) != 0
- && TelephonyPermissions.checkReadPhoneStateOnAnyActiveSub(
- r.context, r.callerPid, r.callerUid, r.callingPackage,
- "listen_active_data_subid_change")) {
+ .LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE) != 0) {
try {
r.callback.onActiveDataSubIdChanged(mActiveDataSubId);
} catch (RemoteException ex) {
@@ -1829,23 +1825,11 @@
log("notifyActiveDataSubIdChanged: activeDataSubId=" + activeDataSubId);
}
- // Create a copy to prevent the IPC call while checking carrier privilege under the lock.
- List<Record> copiedRecords;
- synchronized (mRecords) {
- copiedRecords = new ArrayList<>(mRecords);
- }
mActiveDataSubId = activeDataSubId;
-
- // Filter the record that does not listen to this change or does not have the permission.
- copiedRecords = copiedRecords.stream().filter(r -> r.matchPhoneStateListenerEvent(
- PhoneStateListener.LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE)
- && TelephonyPermissions.checkReadPhoneStateOnAnyActiveSub(
- mContext, r.callerPid, r.callerUid, r.callingPackage,
- "notifyActiveDataSubIdChanged")).collect(Collectors.toCollection(ArrayList::new));
-
synchronized (mRecords) {
- for (Record r : copiedRecords) {
- if (mRecords.contains(r)) {
+ for (Record r : mRecords) {
+ if (r.matchPhoneStateListenerEvent(
+ PhoneStateListener.LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE)) {
try {
r.callback.onActiveDataSubIdChanged(activeDataSubId);
} catch (RemoteException ex) {
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index e53cbc1..7f69a68 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -65,7 +65,6 @@
import android.content.pm.UserInfo;
import android.database.ContentObserver;
import android.hardware.camera2.CameraDevice.CAMERA_AUDIO_RESTRICTION;
-import android.media.AudioAttributes;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Binder;
@@ -361,7 +360,7 @@
public int startNesting;
public ArrayMap<String, Ops> pkgOps;
- public SparseIntArray opModes;
+ private SparseIntArray opModes;
// true indicates there is an interested observer, false there isn't but it has such an op
public SparseBooleanArray foregroundOps;
@@ -383,6 +382,64 @@
&& (pendingState == UID_STATE_CACHED));
}
+ public int getOpModeCount() {
+ return opModes != null ? opModes.size() : 0;
+ }
+
+ public int getOpCodeAt(int index) {
+ return opModes.keyAt(index);
+ }
+
+ public boolean hasOpMode(int code) {
+ return opModes != null && opModes.indexOfKey(code) >= 0;
+ }
+
+ public int getOpMode(int code) {
+ return opModes.get(code);
+ }
+
+ public boolean putOpMode(int code, int mode) {
+ if (mode == AppOpsManager.opToDefaultMode(code)) {
+ return removeOpMode(code);
+ }
+ if (opModes == null) {
+ opModes = new SparseIntArray();
+ }
+ int index = opModes.indexOfKey(code);
+ if (index < 0) {
+ opModes.put(code, mode);
+ return true;
+ }
+ if (opModes.valueAt(index) == mode) {
+ return false;
+ }
+ opModes.setValueAt(index, mode);
+ return true;
+ }
+
+ public boolean removeOpMode(int code) {
+ if (opModes == null) {
+ return false;
+ }
+ int index = opModes.indexOfKey(code);
+ if (index < 0) {
+ return false;
+ }
+ opModes.removeAt(index);
+ if (opModes.size() == 0) {
+ opModes = null;
+ }
+ return true;
+ }
+
+ @Nullable
+ public SparseIntArray cloneOpModes() {
+ if (opModes == null) {
+ return null;
+ }
+ return opModes.clone();
+ }
+
int evalMode(int op, int mode) {
if (mode == AppOpsManager.MODE_FOREGROUND) {
return state <= AppOpsManager.resolveFirstUnrestrictedUidState(op)
@@ -410,14 +467,13 @@
public void evalForegroundOps(SparseArray<ArraySet<ModeCallback>> watchers) {
SparseBooleanArray which = null;
hasForegroundWatchers = false;
- if (opModes != null) {
- for (int i = opModes.size() - 1; i >= 0; i--) {
- if (opModes.valueAt(i) == AppOpsManager.MODE_FOREGROUND) {
- if (which == null) {
- which = new SparseBooleanArray();
- }
- evalForegroundWatchers(opModes.keyAt(i), watchers, which);
+ for (int i = getOpModeCount() - 1; i >= 0; i--) {
+ int code = getOpCodeAt(i);
+ if (getOpMode(code) == AppOpsManager.MODE_FOREGROUND) {
+ if (which == null) {
+ which = new SparseBooleanArray();
}
+ evalForegroundWatchers(code, watchers, which);
}
}
if (pkgOps != null) {
@@ -1056,24 +1112,28 @@
return resOps;
}
- private ArrayList<AppOpsManager.OpEntry> collectOps(SparseIntArray uidOps, int[] ops) {
- if (uidOps == null) {
+ @Nullable
+ private ArrayList<AppOpsManager.OpEntry> collectOps(@NonNull UidState uidState,
+ @Nullable int[] ops) {
+ int opModeCount = uidState.getOpModeCount();
+ if (opModeCount == 0) {
return null;
}
ArrayList<AppOpsManager.OpEntry> resOps = null;
if (ops == null) {
resOps = new ArrayList<>();
- for (int j=0; j<uidOps.size(); j++) {
- resOps.add(new OpEntry(uidOps.keyAt(j), uidOps.valueAt(j)));
+ for (int i = 0; i < opModeCount; i++) {
+ int code = uidState.getOpCodeAt(i);
+ resOps.add(new OpEntry(code, uidState.getOpMode(code)));
}
} else {
- for (int j=0; j<ops.length; j++) {
- int index = uidOps.indexOfKey(ops[j]);
- if (index >= 0) {
+ for (int i = 0; i < ops.length; i++) {
+ int code = ops[i];
+ if (uidState.hasOpMode(code)) {
if (resOps == null) {
resOps = new ArrayList<>();
}
- resOps.add(new OpEntry(uidOps.keyAt(j), uidOps.valueAt(j)));
+ resOps.add(new OpEntry(code, uidState.getOpMode(code)));
}
}
}
@@ -1219,11 +1279,11 @@
if (uidState == null) {
return null;
}
- ArrayList<AppOpsManager.OpEntry> resOps = collectOps(uidState.opModes, ops);
+ ArrayList<AppOpsManager.OpEntry> resOps = collectOps(uidState, ops);
if (resOps == null) {
return null;
}
- ArrayList<AppOpsManager.PackageOps> res = new ArrayList<AppOpsManager.PackageOps>();
+ ArrayList<AppOpsManager.PackageOps> res = new ArrayList<>();
AppOpsManager.PackageOps resPackage = new AppOpsManager.PackageOps(
null, uidState.uid, resOps);
res.add(resPackage);
@@ -1291,29 +1351,14 @@
return;
}
uidState = new UidState(uid);
- uidState.opModes = new SparseIntArray();
- uidState.opModes.put(code, mode);
+ uidState.putOpMode(code, mode);
mUidStates.put(uid, uidState);
scheduleWriteLocked();
- } else if (uidState.opModes == null) {
- if (mode != defaultMode) {
- uidState.opModes = new SparseIntArray();
- uidState.opModes.put(code, mode);
+ } else {
+ boolean changed = uidState.putOpMode(code, mode);
+ if (changed) {
scheduleWriteLocked();
}
- } else {
- if (uidState.opModes.indexOfKey(code) >= 0 && uidState.opModes.get(code) == mode) {
- return;
- }
- if (mode == defaultMode) {
- uidState.opModes.delete(code);
- if (uidState.opModes.size() <= 0) {
- uidState.opModes = null;
- }
- } else {
- uidState.opModes.put(code, mode);
- }
- scheduleWriteLocked();
}
uidState.evalForegroundOps(mOpModeWatchers);
}
@@ -1552,16 +1597,13 @@
for (int i = mUidStates.size() - 1; i >= 0; i--) {
UidState uidState = mUidStates.valueAt(i);
- SparseIntArray opModes = uidState.opModes;
- if (opModes != null && (uidState.uid == reqUid || reqUid == -1)) {
- final int uidOpCount = opModes.size();
- for (int j = uidOpCount - 1; j >= 0; j--) {
- final int code = opModes.keyAt(j);
+ if (uidState.uid == reqUid || reqUid == -1) {
+ for (int opModeIndex = uidState.getOpModeCount() - 1; opModeIndex >= 0;
+ opModeIndex--) {
+ final int code = uidState.getOpCodeAt(opModeIndex);
+
if (AppOpsManager.opAllowsReset(code)) {
- opModes.removeAt(j);
- if (opModes.size() <= 0) {
- uidState.opModes = null;
- }
+ uidState.removeOpMode(code);
for (String packageName : getPackagesForUid(uidState.uid)) {
callbacks = addCallbacks(callbacks, code, uidState.uid, packageName,
mOpModeWatchers.get(code));
@@ -1811,9 +1853,8 @@
}
code = AppOpsManager.opToSwitch(code);
UidState uidState = getUidStateLocked(uid, false);
- if (uidState != null && uidState.opModes != null
- && uidState.opModes.indexOfKey(code) >= 0) {
- final int rawMode = uidState.opModes.get(code);
+ if (uidState != null && uidState.hasOpMode(code)) {
+ final int rawMode = uidState.getOpMode(code);
return raw ? rawMode : uidState.evalMode(code, rawMode);
}
Op op = getOpLocked(code, uid, packageName, false, false);
@@ -1982,8 +2023,8 @@
final int switchCode = AppOpsManager.opToSwitch(code);
// If there is a non-default per UID policy (we set UID op mode only if
// non-default) it takes over, otherwise use the per package policy.
- if (uidState.opModes != null && uidState.opModes.indexOfKey(switchCode) >= 0) {
- final int uidMode = uidState.evalMode(code, uidState.opModes.get(switchCode));
+ if (uidState.hasOpMode(switchCode)) {
+ final int uidMode = uidState.evalMode(code, uidState.getOpMode(switchCode));
if (uidMode != AppOpsManager.MODE_ALLOWED) {
if (DEBUG) Slog.d(TAG, "noteOperation: uid reject #" + uidMode + " for code "
+ switchCode + " (" + code + ") uid " + uid + " package "
@@ -2284,8 +2325,8 @@
// If there is a non-default per UID policy (we set UID op mode only if
// non-default) it takes over, otherwise use the per package policy.
final int opCode = op.op;
- if (uidState.opModes != null && uidState.opModes.indexOfKey(switchCode) >= 0) {
- final int uidMode = uidState.evalMode(code, uidState.opModes.get(switchCode));
+ if (uidState.hasOpMode(switchCode)) {
+ final int uidMode = uidState.evalMode(code, uidState.getOpMode(switchCode));
if (uidMode != AppOpsManager.MODE_ALLOWED
&& (!startIfModeDefault || uidMode != AppOpsManager.MODE_DEFAULT)) {
if (DEBUG) Slog.d(TAG, "noteOperation: uid reject #" + uidMode + " for code "
@@ -2604,9 +2645,8 @@
|| !callback.isWatchingUid(uidState.uid)) {
continue;
}
- boolean doAllPackages = uidState.opModes != null
- && uidState.opModes.indexOfKey(code) >= 0
- && uidState.opModes.get(code) == AppOpsManager.MODE_FOREGROUND;
+ boolean doAllPackages = uidState.hasOpMode(code)
+ && uidState.getOpMode(code) == AppOpsManager.MODE_FOREGROUND;
if (uidState.pkgOps != null) {
for (int pkgi = uidState.pkgOps.size() - 1; pkgi >= 0; pkgi--) {
final Op op = uidState.pkgOps.valueAt(pkgi).get(code);
@@ -2927,12 +2967,9 @@
if (uidState == null) {
continue;
}
- if (uidState.opModes != null) {
- final int idx = uidState.opModes.indexOfKey(AppOpsManager.OP_RUN_IN_BACKGROUND);
- if (idx >= 0) {
- uidState.opModes.put(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND,
- uidState.opModes.valueAt(idx));
- }
+ if (uidState.hasOpMode(AppOpsManager.OP_RUN_IN_BACKGROUND)) {
+ uidState.putOpMode(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, uidState.getOpMode(
+ AppOpsManager.OP_RUN_IN_BACKGROUND));
}
if (uidState.pkgOps == null) {
continue;
@@ -2988,10 +3025,7 @@
final int code = Integer.parseInt(parser.getAttributeValue(null, "n"));
final int mode = Integer.parseInt(parser.getAttributeValue(null, "m"));
UidState uidState = getUidStateLocked(uid, true);
- if (uidState.opModes == null) {
- uidState.opModes = new SparseIntArray();
- }
- uidState.opModes.put(code, mode);
+ uidState.putOpMode(code, mode);
} else {
Slog.w(TAG, "Unknown element under <uid-ops>: "
+ parser.getName());
@@ -3143,47 +3177,38 @@
out.startTag(null, "app-ops");
out.attribute(null, "v", String.valueOf(CURRENT_VERSION));
- SparseArray<SparseIntArray> uidStatesClone;
+ final SparseArray<SparseIntArray> uidOpModes = new SparseArray<>();
synchronized (this) {
- uidStatesClone = new SparseArray<>(mUidStates.size());
-
- final int uidStateCount = mUidStates.size();
- for (int uidStateNum = 0; uidStateNum < uidStateCount; uidStateNum++) {
- UidState uidState = mUidStates.valueAt(uidStateNum);
- int uid = mUidStates.keyAt(uidStateNum);
-
- SparseIntArray opModes = uidState.opModes;
- if (opModes != null && opModes.size() > 0) {
- uidStatesClone.put(uid, new SparseIntArray(opModes.size()));
-
- final int opCount = opModes.size();
- for (int opCountNum = 0; opCountNum < opCount; opCountNum++) {
- uidStatesClone.get(uid).put(
- opModes.keyAt(opCountNum),
- opModes.valueAt(opCountNum));
- }
+ final int uidStatesSize = mUidStates.size();
+ for (int i = 0; i < uidStatesSize; i++) {
+ final SparseIntArray opModes = mUidStates.valueAt(i).cloneOpModes();
+ if (opModes != null) {
+ final int uid = mUidStates.keyAt(i);
+ uidOpModes.put(uid, opModes);
}
}
}
- final int uidStateCount = uidStatesClone.size();
- for (int uidStateNum = 0; uidStateNum < uidStateCount; uidStateNum++) {
- SparseIntArray opModes = uidStatesClone.valueAt(uidStateNum);
- if (opModes != null && opModes.size() > 0) {
- out.startTag(null, "uid");
- out.attribute(null, "n",
- Integer.toString(uidStatesClone.keyAt(uidStateNum)));
- final int opCount = opModes.size();
- for (int opCountNum = 0; opCountNum < opCount; opCountNum++) {
- final int op = opModes.keyAt(opCountNum);
- final int mode = opModes.valueAt(opCountNum);
- out.startTag(null, "op");
- out.attribute(null, "n", Integer.toString(op));
- out.attribute(null, "m", Integer.toString(mode));
- out.endTag(null, "op");
- }
- out.endTag(null, "uid");
+ final int uidOpModesSize = uidOpModes.size();
+ for (int uidOpModesIndex = 0; uidOpModesIndex < uidOpModesSize; uidOpModesIndex++) {
+ final int uid = uidOpModes.keyAt(uidOpModesIndex);
+ final SparseIntArray opModes = uidOpModes.valueAt(uidOpModesIndex);
+
+ out.startTag(null, "uid");
+ out.attribute(null, "n", Integer.toString(uid));
+
+ final int opModesSize = opModes.size();
+ for (int opModesIndex = 0; opModesIndex < opModesSize; opModesIndex++) {
+ final int code = opModes.keyAt(opModesIndex);
+ final int mode = opModes.valueAt(opModesIndex);
+
+ out.startTag(null, "op");
+ out.attribute(null, "n", Integer.toString(code));
+ out.attribute(null, "m", Integer.toString(mode));
+ out.endTag(null, "op");
}
+
+ out.endTag(null, "uid");
}
if (allOps != null) {
@@ -4128,21 +4153,22 @@
}
for (int i=0; i<mUidStates.size(); i++) {
UidState uidState = mUidStates.valueAt(i);
- final SparseIntArray opModes = uidState.opModes;
final ArrayMap<String, Ops> pkgOps = uidState.pkgOps;
if (dumpWatchers || dumpHistory) {
continue;
}
if (dumpOp >= 0 || dumpPackage != null || dumpMode >= 0) {
- boolean hasOp = dumpOp < 0 || (uidState.opModes != null
- && uidState.opModes.indexOfKey(dumpOp) >= 0);
+ boolean hasOp = dumpOp < 0 || uidState.hasOpMode(dumpOp);
boolean hasPackage = dumpPackage == null;
boolean hasMode = dumpMode < 0;
- if (!hasMode && opModes != null) {
- for (int opi = 0; !hasMode && opi < opModes.size(); opi++) {
- if (opModes.valueAt(opi) == dumpMode) {
+ if (!hasMode) {
+ int opModeCount = uidState.getOpModeCount();
+ for (int opModeIndex = 0; opModeIndex < opModeCount; opModeIndex++) {
+ int code = uidState.getOpCodeAt(opModeIndex);
+ if (uidState.getOpMode(code) == dumpMode) {
hasMode = true;
+ break;
}
}
}
@@ -4209,20 +4235,18 @@
}
needSep = true;
- if (opModes != null) {
- final int opModeCount = opModes.size();
- for (int j = 0; j < opModeCount; j++) {
- final int code = opModes.keyAt(j);
- final int mode = opModes.valueAt(j);
- if (dumpOp >= 0 && dumpOp != code) {
- continue;
- }
- if (dumpMode >= 0 && dumpMode != mode) {
- continue;
- }
- pw.print(" "); pw.print(AppOpsManager.opToName(code));
- pw.print(": mode="); pw.println(AppOpsManager.modeToName(mode));
+ final int opModeCount = uidState.getOpModeCount();
+ for (int opModeIndex = 0; opModeIndex < opModeCount; opModeIndex++) {
+ final int code = uidState.getOpCodeAt(opModeIndex);
+ final int mode = uidState.getOpMode(code);
+ if (dumpOp >= 0 && dumpOp != code) {
+ continue;
}
+ if (dumpMode >= 0 && dumpMode != mode) {
+ continue;
+ }
+ pw.print(" "); pw.print(AppOpsManager.opToName(code));
+ pw.print(": mode="); pw.println(AppOpsManager.modeToName(mode));
}
if (pkgOps == null) {
diff --git a/services/core/java/com/android/server/content/ContentService.java b/services/core/java/com/android/server/content/ContentService.java
index 36e872a..4a62bc5 100644
--- a/services/core/java/com/android/server/content/ContentService.java
+++ b/services/core/java/com/android/server/content/ContentService.java
@@ -55,6 +55,7 @@
import android.os.ShellCallback;
import android.os.UserHandle;
import android.text.TextUtils;
+import android.text.format.DateUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
@@ -64,6 +65,8 @@
import android.util.SparseIntArray;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.BackgroundThread;
import com.android.internal.os.BinderDeathDispatcher;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.DumpUtils;
@@ -89,6 +92,14 @@
/** Do a WTF if a single observer is registered more than this times. */
private static final int TOO_MANY_OBSERVERS_THRESHOLD = 1000;
+ /**
+ * Delay to apply to content change notifications dispatched to apps running
+ * in the background. This is used to help prevent stampeding when the user
+ * is performing CPU/RAM intensive foreground tasks, such as when capturing
+ * burst photos.
+ */
+ private static final long BACKGROUND_OBSERVER_DELAY = 10 * DateUtils.SECOND_IN_MILLIS;
+
public static class Lifecycle extends SystemService {
private ContentService mService;
@@ -426,28 +437,15 @@
flags, userHandle, calls);
}
final int numCalls = calls.size();
- for (int i=0; i<numCalls; i++) {
- ObserverCall oc = calls.get(i);
- try {
- oc.mObserver.onChange(oc.mSelfChange, uri, userHandle);
- if (DEBUG) Slog.d(TAG, "Notified " + oc.mObserver + " of " + "update at "
- + uri);
- } catch (RemoteException ex) {
- synchronized (mRootNode) {
- Log.w(TAG, "Found dead observer, removing");
- IBinder binder = oc.mObserver.asBinder();
- final ArrayList<ObserverNode.ObserverEntry> list
- = oc.mNode.mObservers;
- int numList = list.size();
- for (int j=0; j<numList; j++) {
- ObserverNode.ObserverEntry oe = list.get(j);
- if (oe.observer.asBinder() == binder) {
- list.remove(j);
- j--;
- numList--;
- }
- }
- }
+ for (int i = 0; i < numCalls; i++) {
+ // Immediately dispatch notifications to foreground apps that
+ // are important to the user; all other background observers are
+ // delayed to avoid stampeding
+ final ObserverCall oc = calls.get(i);
+ if (oc.mProcState <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) {
+ oc.run();
+ } else {
+ BackgroundThread.getHandler().postDelayed(oc, BACKGROUND_OBSERVER_DELAY);
}
}
if ((flags&ContentResolver.NOTIFY_SYNC_TO_NETWORK) != 0) {
@@ -486,23 +484,33 @@
UserHandle.getCallingUserId(), Build.VERSION_CODES.CUR_DEVELOPMENT, callingPackage);
}
- /**
- * Hide this class since it is not part of api,
- * but current unittest framework requires it to be public
- * @hide
- *
- */
- public static final class ObserverCall {
- final ObserverNode mNode;
+ /** {@hide} */
+ @VisibleForTesting
+ public static final class ObserverCall implements Runnable {
final IContentObserver mObserver;
final boolean mSelfChange;
- final int mObserverUserId;
+ final Uri mUri;
+ final int mUserId;
+ final int mProcState;
- ObserverCall(ObserverNode node, IContentObserver observer, boolean selfChange, int observerUserId) {
- mNode = node;
+ ObserverCall(IContentObserver observer, boolean selfChange, Uri uri, int userId,
+ int procState) {
mObserver = observer;
mSelfChange = selfChange;
- mObserverUserId = observerUserId;
+ mUri = uri;
+ mUserId = userId;
+ mProcState = procState;
+ }
+
+ @Override
+ public void run() {
+ try {
+ mObserver.onChange(mSelfChange, mUri, mUserId);
+ if (DEBUG) Slog.d(TAG, "Notified " + mObserver + " of update at " + mUri);
+ } catch (RemoteException ignored) {
+ // We already have a death observer that will clean up if the
+ // remote process dies
+ }
}
}
@@ -1345,11 +1353,8 @@
return ContentResolver.SYNC_EXEMPTION_NONE;
}
- /**
- * Hide this class since it is not part of api,
- * but current unittest framework requires it to be public
- * @hide
- */
+ /** {@hide} */
+ @VisibleForTesting
public static final class ObserverNode {
private class ObserverEntry implements IBinder.DeathRecipient {
public final IContentObserver observer;
@@ -1546,7 +1551,7 @@
return false;
}
- private void collectMyObserversLocked(boolean leaf, IContentObserver observer,
+ private void collectMyObserversLocked(Uri uri, boolean leaf, IContentObserver observer,
boolean observerWantsSelfNotifications, int flags,
int targetUserHandle, ArrayList<ObserverCall> calls) {
int N = mObservers.size();
@@ -1588,8 +1593,10 @@
if (DEBUG) Slog.d(TAG, "Reporting to " + entry.observer + ": leaf=" + leaf
+ " flags=" + Integer.toHexString(flags)
+ " desc=" + entry.notifyForDescendants);
- calls.add(new ObserverCall(this, entry.observer, selfChange,
- UserHandle.getUserId(entry.uid)));
+ final int procState = LocalServices.getService(ActivityManagerInternal.class)
+ .getUidProcessState(entry.uid);
+ calls.add(new ObserverCall(entry.observer, selfChange, uri,
+ targetUserHandle, procState));
}
}
}
@@ -1605,14 +1612,14 @@
if (index >= segmentCount) {
// This is the leaf node, notify all observers
if (DEBUG) Slog.d(TAG, "Collecting leaf observers @ #" + index + ", node " + mName);
- collectMyObserversLocked(true, observer, observerWantsSelfNotifications,
+ collectMyObserversLocked(uri, true, observer, observerWantsSelfNotifications,
flags, targetUserHandle, calls);
} else if (index < segmentCount){
segment = getUriSegment(uri, index);
if (DEBUG) Slog.d(TAG, "Collecting non-leaf observers @ #" + index + " / "
+ segment);
// Notify any observers at this level who are interested in descendants
- collectMyObserversLocked(false, observer, observerWantsSelfNotifications,
+ collectMyObserversLocked(uri, false, observer, observerWantsSelfNotifications,
flags, targetUserHandle, calls);
}
diff --git a/services/core/java/com/android/server/rollback/Rollback.java b/services/core/java/com/android/server/rollback/Rollback.java
index 0d5746b..6769fe0 100644
--- a/services/core/java/com/android/server/rollback/Rollback.java
+++ b/services/core/java/com/android/server/rollback/Rollback.java
@@ -65,7 +65,7 @@
/**
* The directory where the rollback data is stored.
*/
- public final File backupDir;
+ private final File mBackupDir;
/**
* The time when the upgrade occurred, for purposes of expiring
@@ -74,24 +74,24 @@
* The timestamp is not applicable for all rollback states, but we make
* sure to keep it non-null to avoid potential errors there.
*/
- public @NonNull Instant timestamp;
+ private @NonNull Instant mTimestamp;
/**
* The session ID for the staged session if this rollback data represents a staged session,
* {@code -1} otherwise.
*/
- public final int stagedSessionId;
+ private final int mStagedSessionId;
/**
* The current state of the rollback.
* ENABLING, AVAILABLE, or COMMITTED.
*/
- public @RollbackState int state;
+ private @RollbackState int mState;
/**
* The id of the post-reboot apk session for a staged install, if any.
*/
- public int apkSessionId = -1;
+ private int mApkSessionId = -1;
/**
* True if we are expecting the package manager to call restoreUserData
@@ -99,7 +99,7 @@
* has not yet been fully applied.
*/
// NOTE: All accesses to this field are from the RollbackManager handler thread.
- public boolean restoreUserDataInProgress = false;
+ private boolean mRestoreUserDataInProgress = false;
/**
* Constructs a new, empty Rollback instance.
@@ -114,10 +114,10 @@
/* isStaged */ stagedSessionId != -1,
/* causePackages */ new ArrayList<>(),
/* committedSessionId */ -1);
- this.backupDir = backupDir;
- this.stagedSessionId = stagedSessionId;
- this.state = ROLLBACK_STATE_ENABLING;
- this.timestamp = Instant.now();
+ mBackupDir = backupDir;
+ mStagedSessionId = stagedSessionId;
+ mState = ROLLBACK_STATE_ENABLING;
+ mTimestamp = Instant.now();
}
/**
@@ -126,21 +126,115 @@
Rollback(RollbackInfo info, File backupDir, Instant timestamp, int stagedSessionId,
@RollbackState int state, int apkSessionId, boolean restoreUserDataInProgress) {
this.info = info;
- this.backupDir = backupDir;
- this.timestamp = timestamp;
- this.stagedSessionId = stagedSessionId;
- this.state = state;
- this.apkSessionId = apkSessionId;
- this.restoreUserDataInProgress = restoreUserDataInProgress;
+ mBackupDir = backupDir;
+ mTimestamp = timestamp;
+ mStagedSessionId = stagedSessionId;
+ mState = state;
+ mApkSessionId = apkSessionId;
+ mRestoreUserDataInProgress = restoreUserDataInProgress;
}
/**
* Whether the rollback is for rollback of a staged install.
*/
- public boolean isStaged() {
+ boolean isStaged() {
return info.isStaged();
}
+ /**
+ * Returns the directory in which rollback data should be stored.
+ */
+ File getBackupDir() {
+ return mBackupDir;
+ }
+
+ /**
+ * Returns the time when the upgrade occurred, for purposes of expiring rollback data.
+ */
+ Instant getTimestamp() {
+ return mTimestamp;
+ }
+
+ /**
+ * Sets the time at which upgrade occurred.
+ */
+ void setTimestamp(Instant timestamp) {
+ mTimestamp = timestamp;
+ }
+
+ /**
+ * Returns the session ID for the staged session if this rollback data represents a staged
+ * session, or {@code -1} otherwise.
+ */
+ int getStagedSessionId() {
+ return mStagedSessionId;
+ }
+
+ /**
+ * Returns true if the rollback is in the ENABLING state.
+ */
+ boolean isEnabling() {
+ return mState == ROLLBACK_STATE_ENABLING;
+ }
+
+ /**
+ * Returns true if the rollback is in the AVAILABLE state.
+ */
+ boolean isAvailable() {
+ return mState == ROLLBACK_STATE_AVAILABLE;
+ }
+
+ /**
+ * Returns true if the rollback is in the COMMITTED state.
+ */
+ boolean isCommitted() {
+ return mState == ROLLBACK_STATE_COMMITTED;
+ }
+
+ /**
+ * Sets the state of the rollback to AVAILABLE.
+ */
+ void setAvailable() {
+ mState = ROLLBACK_STATE_AVAILABLE;
+ }
+
+ /**
+ * Sets the state of the rollback to COMMITTED.
+ */
+ void setCommitted() {
+ mState = ROLLBACK_STATE_COMMITTED;
+ }
+
+ /**
+ * Returns the id of the post-reboot apk session for a staged install, if any.
+ */
+ int getApkSessionId() {
+ return mApkSessionId;
+ }
+
+ /**
+ * Sets the id of the post-reboot apk session for a staged install.
+ */
+ void setApkSessionId(int apkSessionId) {
+ mApkSessionId = apkSessionId;
+ }
+
+ /**
+ * Returns true if we are expecting the package manager to call restoreUserData for this
+ * rollback because it has just been committed but the rollback has not yet been fully applied.
+ */
+ boolean isRestoreUserDataInProgress() {
+ return mRestoreUserDataInProgress;
+ }
+
+ /**
+ * Sets whether we are expecting the package manager to call restoreUserData for this
+ * rollback because it has just been committed but the rollback has not yet been fully applied.
+ */
+ void setRestoreUserDataInProgress(boolean restoreUserDataInProgress) {
+ mRestoreUserDataInProgress = restoreUserDataInProgress;
+ }
+
static String rollbackStateToString(@RollbackState int state) {
switch (state) {
case Rollback.ROLLBACK_STATE_ENABLING: return "enabling";
@@ -160,7 +254,7 @@
throw new ParseException("Invalid rollback state: " + state, 0);
}
- public String getStateAsString() {
- return rollbackStateToString(state);
+ String getStateAsString() {
+ return rollbackStateToString(mState);
}
}
diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
index 3147bc6..96d284b 100644
--- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
+++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
@@ -282,7 +282,7 @@
List<RollbackInfo> rollbacks = new ArrayList<>();
for (int i = 0; i < mRollbacks.size(); ++i) {
Rollback rollback = mRollbacks.get(i);
- if (rollback.state == Rollback.ROLLBACK_STATE_AVAILABLE) {
+ if (rollback.isAvailable()) {
rollbacks.add(rollback.info);
}
}
@@ -298,7 +298,7 @@
List<RollbackInfo> rollbacks = new ArrayList<>();
for (int i = 0; i < mRollbacks.size(); ++i) {
Rollback rollback = mRollbacks.get(i);
- if (rollback.state == Rollback.ROLLBACK_STATE_COMMITTED) {
+ if (rollback.isCommitted()) {
rollbacks.add(rollback.info);
}
}
@@ -332,7 +332,7 @@
Iterator<Rollback> iter = mRollbacks.iterator();
while (iter.hasNext()) {
Rollback rollback = iter.next();
- rollback.timestamp = rollback.timestamp.plusMillis(timeDifference);
+ rollback.setTimestamp(rollback.getTimestamp().plusMillis(timeDifference));
saveRollback(rollback);
}
}
@@ -358,7 +358,7 @@
Slog.i(TAG, "Initiating rollback");
Rollback rollback = getRollbackForId(rollbackId);
- if (rollback == null || rollback.state != Rollback.ROLLBACK_STATE_AVAILABLE) {
+ if (rollback == null || !rollback.isAvailable()) {
sendFailure(statusReceiver, RollbackManager.STATUS_FAILURE_ROLLBACK_UNAVAILABLE,
"Rollback unavailable");
return;
@@ -454,8 +454,8 @@
// TODO: Could this cause a rollback to be
// resurrected if it should otherwise have
// expired by now?
- rollback.state = Rollback.ROLLBACK_STATE_AVAILABLE;
- rollback.restoreUserDataInProgress = false;
+ rollback.setAvailable();
+ rollback.setRestoreUserDataInProgress(false);
}
sendFailure(statusReceiver, RollbackManager.STATUS_FAILURE_INSTALL,
"Rollback downgrade install failed: "
@@ -468,7 +468,7 @@
if (!rollback.isStaged()) {
// All calls to restoreUserData should have
// completed by now for a non-staged install.
- rollback.restoreUserDataInProgress = false;
+ rollback.setRestoreUserDataInProgress(false);
}
rollback.info.setCommittedSessionId(parentSessionId);
@@ -490,8 +490,8 @@
);
synchronized (mLock) {
- rollback.state = Rollback.ROLLBACK_STATE_COMMITTED;
- rollback.restoreUserDataInProgress = true;
+ rollback.setCommitted();
+ rollback.setRestoreUserDataInProgress(true);
}
parentSession.commit(receiver.getIntentSender());
} catch (IOException e) {
@@ -618,9 +618,9 @@
synchronized (mLock) {
for (Rollback rollback : mRollbacks) {
if (rollback.isStaged()) {
- if (rollback.state == Rollback.ROLLBACK_STATE_ENABLING) {
+ if (rollback.isEnabling()) {
enabling.add(rollback);
- } else if (rollback.restoreUserDataInProgress) {
+ } else if (rollback.isRestoreUserDataInProgress()) {
restoreInProgress.add(rollback);
}
@@ -635,8 +635,8 @@
for (Rollback rollback : enabling) {
PackageInstaller installer = mContext.getPackageManager().getPackageInstaller();
- PackageInstaller.SessionInfo session = installer.getSessionInfo(
- rollback.stagedSessionId);
+ PackageInstaller.SessionInfo session =
+ installer.getSessionInfo(rollback.getStagedSessionId());
if (session == null || session.isStagedSessionFailed()) {
// TODO: Do we need to remove this from
// mRollbacks, or is it okay to leave as
@@ -650,13 +650,13 @@
for (Rollback rollback : restoreInProgress) {
PackageInstaller installer = mContext.getPackageManager().getPackageInstaller();
- PackageInstaller.SessionInfo session = installer.getSessionInfo(
- rollback.stagedSessionId);
+ PackageInstaller.SessionInfo session =
+ installer.getSessionInfo(rollback.getStagedSessionId());
// TODO: What if session is null?
if (session != null) {
if (session.isStagedSessionApplied() || session.isStagedSessionFailed()) {
synchronized (mLock) {
- rollback.restoreUserDataInProgress = false;
+ rollback.setRestoreUserDataInProgress(false);
}
saveRollback(rollback);
}
@@ -694,8 +694,7 @@
while (iter.hasNext()) {
Rollback rollback = iter.next();
// TODO: Should we remove rollbacks in the ENABLING state here?
- if (rollback.state == Rollback.ROLLBACK_STATE_AVAILABLE
- || rollback.state == Rollback.ROLLBACK_STATE_ENABLING) {
+ if (rollback.isEnabling() || rollback.isAvailable()) {
for (PackageRollbackInfo info : rollback.info.getPackages()) {
if (info.getPackageName().equals(packageName)
&& !packageVersionsEqual(
@@ -761,15 +760,16 @@
Iterator<Rollback> iter = mRollbacks.iterator();
while (iter.hasNext()) {
Rollback rollback = iter.next();
- if (rollback.state != Rollback.ROLLBACK_STATE_AVAILABLE) {
+ if (!rollback.isAvailable()) {
continue;
}
if (!now.isBefore(
- rollback.timestamp.plusMillis(mRollbackLifetimeDurationInMillis))) {
+ rollback.getTimestamp()
+ .plusMillis(mRollbackLifetimeDurationInMillis))) {
iter.remove();
deleteRollback(rollback);
- } else if (oldest == null || oldest.isAfter(rollback.timestamp)) {
- oldest = rollback.timestamp;
+ } else if (oldest == null || oldest.isAfter(rollback.getTimestamp())) {
+ oldest = rollback.getTimestamp();
}
}
}
@@ -877,7 +877,7 @@
synchronized (mLock) {
for (int i = 0; i < mRollbacks.size(); ++i) {
Rollback rollback = mRollbacks.get(i);
- if (rollback.apkSessionId == parentSession.getSessionId()) {
+ if (rollback.getApkSessionId() == parentSession.getSessionId()) {
// This is the apk session for a staged session with rollback enabled. We do not
// need to create a new rollback for this session.
return true;
@@ -1020,7 +1020,7 @@
// staged installs
for (int i = 0; i < mRollbacks.size(); i++) {
Rollback rollback = mRollbacks.get(i);
- if (rollback.state != Rollback.ROLLBACK_STATE_ENABLING) {
+ if (!rollback.isEnabling()) {
continue;
}
@@ -1053,7 +1053,7 @@
synchronized (mLock) {
for (int i = 0; i < mRollbacks.size(); ++i) {
Rollback candidate = mRollbacks.get(i);
- if (candidate.restoreUserDataInProgress) {
+ if (candidate.isRestoreUserDataInProgress()) {
info = getPackageRollbackInfo(candidate, packageName);
if (info != null) {
rollback = candidate;
@@ -1146,8 +1146,8 @@
synchronized (mLock) {
for (int i = 0; i < mRollbacks.size(); ++i) {
Rollback candidate = mRollbacks.get(i);
- if (candidate.stagedSessionId == originalSessionId) {
- candidate.apkSessionId = apkSessionId;
+ if (candidate.getStagedSessionId() == originalSessionId) {
+ candidate.setApkSessionId(apkSessionId);
rollback = candidate;
break;
}
@@ -1333,8 +1333,8 @@
// to a new package being installed. Won't this revive an expired
// rollback? Consider adding a ROLLBACK_STATE_EXPIRED to address this.
synchronized (mLock) {
- rollback.state = Rollback.ROLLBACK_STATE_AVAILABLE;
- rollback.timestamp = Instant.now();
+ rollback.setAvailable();
+ rollback.setTimestamp(Instant.now());
}
saveRollback(rollback);
@@ -1434,9 +1434,9 @@
ipw.println(info.getRollbackId() + ":");
ipw.increaseIndent();
ipw.println("-state: " + rollback.getStateAsString());
- ipw.println("-timestamp: " + rollback.timestamp);
- if (rollback.stagedSessionId != -1) {
- ipw.println("-stagedSessionId: " + rollback.stagedSessionId);
+ ipw.println("-timestamp: " + rollback.getTimestamp());
+ if (rollback.getStagedSessionId() != -1) {
+ ipw.println("-stagedSessionId: " + rollback.getStagedSessionId());
}
ipw.println("-packages:");
ipw.increaseIndent();
@@ -1446,7 +1446,7 @@
+ " -> " + pkg.getVersionRolledBackTo().getLongVersionCode());
}
ipw.decreaseIndent();
- if (rollback.state == Rollback.ROLLBACK_STATE_COMMITTED) {
+ if (rollback.isCommitted()) {
ipw.println("-causePackages:");
ipw.increaseIndent();
for (VersionedPackage cPkg : info.getCausePackages()) {
diff --git a/services/core/java/com/android/server/rollback/RollbackStore.java b/services/core/java/com/android/server/rollback/RollbackStore.java
index b2448f6..772c53f 100644
--- a/services/core/java/com/android/server/rollback/RollbackStore.java
+++ b/services/core/java/com/android/server/rollback/RollbackStore.java
@@ -17,7 +17,6 @@
package com.android.server.rollback;
import static com.android.server.rollback.Rollback.rollbackStateFromString;
-import static com.android.server.rollback.Rollback.rollbackStateToString;
import android.annotation.NonNull;
import android.content.pm.VersionedPackage;
@@ -216,7 +215,7 @@
static void backupPackageCodePath(Rollback rollback, String packageName, String codePath)
throws IOException {
File sourceFile = new File(codePath);
- File targetDir = new File(rollback.backupDir, packageName);
+ File targetDir = new File(rollback.getBackupDir(), packageName);
targetDir.mkdirs();
File targetFile = new File(targetDir, sourceFile.getName());
@@ -229,7 +228,7 @@
* Includes the base apk and any splits. Returns null if none found.
*/
static File[] getPackageCodePaths(Rollback rollback, String packageName) {
- File targetDir = new File(rollback.backupDir, packageName);
+ File targetDir = new File(rollback.getBackupDir(), packageName);
File[] files = targetDir.listFiles();
if (files == null || files.length == 0) {
return null;
@@ -243,7 +242,7 @@
*/
static void deletePackageCodePaths(Rollback rollback) {
for (PackageRollbackInfo info : rollback.info.getPackages()) {
- File targetDir = new File(rollback.backupDir, info.getPackageName());
+ File targetDir = new File(rollback.getBackupDir(), info.getPackageName());
removeFile(targetDir);
}
}
@@ -255,13 +254,13 @@
try {
JSONObject dataJson = new JSONObject();
dataJson.put("info", rollbackInfoToJson(rollback.info));
- dataJson.put("timestamp", rollback.timestamp.toString());
- dataJson.put("stagedSessionId", rollback.stagedSessionId);
- dataJson.put("state", rollbackStateToString(rollback.state));
- dataJson.put("apkSessionId", rollback.apkSessionId);
- dataJson.put("restoreUserDataInProgress", rollback.restoreUserDataInProgress);
+ dataJson.put("timestamp", rollback.getTimestamp().toString());
+ dataJson.put("stagedSessionId", rollback.getStagedSessionId());
+ dataJson.put("state", rollback.getStateAsString());
+ dataJson.put("apkSessionId", rollback.getApkSessionId());
+ dataJson.put("restoreUserDataInProgress", rollback.isRestoreUserDataInProgress());
- PrintWriter pw = new PrintWriter(new File(rollback.backupDir, "rollback.json"));
+ PrintWriter pw = new PrintWriter(new File(rollback.getBackupDir(), "rollback.json"));
pw.println(dataJson.toString());
pw.close();
} catch (JSONException e) {
@@ -273,7 +272,7 @@
* Removes all persistent storage associated with the given rollback.
*/
void deleteRollback(Rollback rollback) {
- removeFile(rollback.backupDir);
+ removeFile(rollback.getBackupDir());
}
/**
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index 7274d17..97b2047 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -109,8 +109,6 @@
"libGLESv2",
"libnetutils",
"libhidlbase",
- "libhidltransport",
- "libhwbinder",
"libutils",
"libhwui",
"libbpf_android",
diff --git a/services/tests/servicestests/src/com/android/server/appop/AppOpsUpgradeTest.java b/services/tests/servicestests/src/com/android/server/appop/AppOpsUpgradeTest.java
index 66d2bab..70650de 100644
--- a/services/tests/servicestests/src/com/android/server/appop/AppOpsUpgradeTest.java
+++ b/services/tests/servicestests/src/com/android/server/appop/AppOpsUpgradeTest.java
@@ -89,13 +89,11 @@
final int defaultModeOp2 = AppOpsManager.opToDefaultMode(op2);
for(int i = 0; i < uidStates.size(); i++) {
final AppOpsService.UidState uidState = uidStates.valueAt(i);
- if (uidState.opModes != null) {
- final int uidMode1 = uidState.opModes.get(op1, defaultModeOp1);
- final int uidMode2 = uidState.opModes.get(op2, defaultModeOp2);
- assertEquals(uidMode1, uidMode2);
- if (uidMode1 != defaultModeOp1) {
- numberOfNonDefaultOps++;
- }
+ final int uidMode1 = uidState.hasOpMode(op1) ? uidState.getOpMode(op1) : defaultModeOp1;
+ final int uidMode2 = uidState.hasOpMode(op2) ? uidState.getOpMode(op2) : defaultModeOp2;
+ assertEquals(uidMode1, uidMode2);
+ if (uidMode1 != defaultModeOp1) {
+ numberOfNonDefaultOps++;
}
if (uidState.pkgOps == null) {
continue;
diff --git a/services/tests/servicestests/src/com/android/server/rollback/RollbackUnitTest.java b/services/tests/servicestests/src/com/android/server/rollback/RollbackUnitTest.java
new file mode 100644
index 0000000..d27f1c7
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/rollback/RollbackUnitTest.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.rollback;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.File;
+
+@RunWith(JUnit4.class)
+public class RollbackUnitTest {
+
+ @Test
+ public void newEmptyStagedRollbackDefaults() {
+ int rollbackId = 123;
+ int sessionId = 567;
+ File file = new File("/test/testing");
+
+ Rollback rollback = new Rollback(rollbackId, file, sessionId);
+
+ assertThat(rollback.isEnabling()).isTrue();
+ assertThat(rollback.getBackupDir().getAbsolutePath()).isEqualTo("/test/testing");
+ assertThat(rollback.isStaged()).isTrue();
+ assertThat(rollback.getStagedSessionId()).isEqualTo(567);
+ }
+
+ @Test
+ public void newEmptyNonStagedRollbackDefaults() {
+ int rollbackId = 123;
+ File file = new File("/test/testing");
+
+ Rollback rollback = new Rollback(rollbackId, file, -1);
+
+ assertThat(rollback.isEnabling()).isTrue();
+ assertThat(rollback.getBackupDir().getAbsolutePath()).isEqualTo("/test/testing");
+ assertThat(rollback.isStaged()).isFalse();
+ }
+
+ @Test
+ public void rollbackStateChanges() {
+ Rollback rollback = new Rollback(123, new File("/test/testing"), -1);
+
+ assertThat(rollback.isEnabling()).isTrue();
+ assertThat(rollback.isAvailable()).isFalse();
+ assertThat(rollback.isCommitted()).isFalse();
+
+ rollback.setAvailable();
+
+ assertThat(rollback.isEnabling()).isFalse();
+ assertThat(rollback.isAvailable()).isTrue();
+ assertThat(rollback.isCommitted()).isFalse();
+
+ rollback.setCommitted();
+
+ assertThat(rollback.isEnabling()).isFalse();
+ assertThat(rollback.isAvailable()).isFalse();
+ assertThat(rollback.isCommitted()).isTrue();
+ }
+
+}
diff --git a/startop/apps/test/Android.bp b/startop/apps/test/Android.bp
index a4906d7..13b7b5c 100644
--- a/startop/apps/test/Android.bp
+++ b/startop/apps/test/Android.bp
@@ -21,6 +21,7 @@
"src/LayoutInflationActivity.java",
"src/ComplexLayoutInflationActivity.java",
"src/FrameLayoutInflationActivity.java",
+ "src/SystemServerBenchmarkActivity.java",
"src/TextViewInflationActivity.java",
],
platform_apis: true,
diff --git a/startop/apps/test/AndroidManifest.xml b/startop/apps/test/AndroidManifest.xml
index 467d8f7..15785d4 100644
--- a/startop/apps/test/AndroidManifest.xml
+++ b/startop/apps/test/AndroidManifest.xml
@@ -71,6 +71,19 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
+
+ <activity
+ android:label="SystemServer Benchmark"
+ android:name=".SystemServerBenchmarkActivity"
+ android:exported="true" >
+
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+
</application>
</manifest>
diff --git a/startop/apps/test/res/layout/system_server_benchmark_page.xml b/startop/apps/test/res/layout/system_server_benchmark_page.xml
new file mode 100644
index 0000000..337fe65
--- /dev/null
+++ b/startop/apps/test/res/layout/system_server_benchmark_page.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" >
+
+ <GridLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:columnCount="3"
+ android:id="@+id/benchmark_list">
+
+ <TextView android:text="Benchmark"/>
+ <TextView android:text="Mean (ms)"/>
+ <TextView android:text="Stdev (ms)"/>
+
+ </GridLayout>
+</ScrollView>
diff --git a/startop/apps/test/src/SystemServerBenchmarkActivity.java b/startop/apps/test/src/SystemServerBenchmarkActivity.java
new file mode 100644
index 0000000..59a30a5
--- /dev/null
+++ b/startop/apps/test/src/SystemServerBenchmarkActivity.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.startop.test;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.Trace;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.GridLayout;
+import android.widget.TextView;
+
+import java.util.Arrays;
+
+class Benchmark {
+ public static final int NUM_ITERATIONS = 1000;
+
+ public Benchmark(ViewGroup parent, CharSequence name, Runnable thunk) {
+ Context context = parent.getContext();
+ Button button = new Button(context);
+ TextView mean = new TextView(context);
+ TextView stdev = new TextView(context);
+
+ button.setText(name);
+ mean.setText("");
+ stdev.setText("");
+
+ button.setOnClickListener((_button) -> {
+ mean.setText("Running...");
+ stdev.setText("");
+
+ new AsyncTask() {
+ double resultMean = 0;
+ double resultStdev = 0;
+
+ @Override
+ protected Object doInBackground(Object... _args) {
+ long[] results = new long[NUM_ITERATIONS];
+
+ // Run benchmark
+ for (int i = 0; i < results.length; i++) {
+ results[i] = -System.nanoTime();
+ thunk.run();
+ results[i] += System.nanoTime();
+ }
+
+ // Compute mean
+ long sum = Arrays.stream(results).sum();
+ resultMean = (double) sum / results.length;
+
+ // Compute standard deviation
+ double variance = 0;
+ for (long i : results) {
+ double t = (double) i - resultMean;
+ variance += t * t;
+ }
+ variance /= results.length - 1;
+
+ resultStdev = Math.sqrt(variance);
+
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(Object _result) {
+ mean.setText(String.format("%.3f", resultMean / 1e6));
+ stdev.setText(String.format("%.3f", resultStdev / 1e6));
+ }
+ }.execute(new Object());
+ });
+
+ parent.addView(button);
+ parent.addView(mean);
+ parent.addView(stdev);
+ }
+}
+
+public class SystemServerBenchmarkActivity extends Activity {
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.system_server_benchmark_page);
+
+ GridLayout benchmarkList = findViewById(R.id.benchmark_list);
+
+ new Benchmark(benchmarkList, "Empty", () -> {
+ });
+
+ PackageManager pm = getPackageManager();
+ new Benchmark(benchmarkList, "getInstalledApplications", () -> {
+ pm.getInstalledApplications(PackageManager.MATCH_SYSTEM_ONLY);
+ });
+ }
+}
diff --git a/startop/scripts/app_startup/app_startup_runner.py b/startop/scripts/app_startup/app_startup_runner.py
index eb582f9..fa1c4e6 100755
--- a/startop/scripts/app_startup/app_startup_runner.py
+++ b/startop/scripts/app_startup/app_startup_runner.py
@@ -41,11 +41,12 @@
sys.path.append(os.path.dirname(DIR))
import lib.cmd_utils as cmd_utils
import lib.print_utils as print_utils
-import iorap.compiler as compiler
from app_startup.run_app_with_prefetch import PrefetchAppRunner
import app_startup.lib.args_utils as args_utils
from app_startup.lib.data_frame import DataFrame
from app_startup.lib.perfetto_trace_collector import PerfettoTraceCollector
+from iorap.compiler import CompilerType
+import iorap.compiler as compiler
# The following command line options participate in the combinatorial generation.
# All other arguments have a global effect.
@@ -58,8 +59,6 @@
CollectorPackageInfo = NamedTuple('CollectorPackageInfo',
[('package', str), ('compiler_filter', str)])
-_COMPILER_SCRIPT = os.path.join(os.path.dirname(os.path.dirname(
- os.path.realpath(__file__))), 'iorap/compiler.py')
# by 2; systrace starts up slowly.
_UNLOCK_SCREEN_SCRIPT = os.path.join(
@@ -135,6 +134,10 @@
action='append',
help='The trace duration (milliseconds) in '
'compilation')
+ optional_named.add_argument('--compiler-type', dest='compiler_type',
+ type=CompilerType, choices=list(CompilerType),
+ default=CompilerType.DEVICE,
+ help='The type of compiler.')
return parser.parse_args(argv)
@@ -211,26 +214,26 @@
return DataFrame(d)
-def compile_perfetto_trace(inodes_path: str,
+def build_ri_compiler_argv(inodes_path: str,
perfetto_trace_file: str,
- trace_duration: Optional[timedelta]) -> TextIO:
- compiler_trace_file = tempfile.NamedTemporaryFile()
- argv = [_COMPILER_SCRIPT, '-i', inodes_path, '--perfetto-trace',
- perfetto_trace_file, '-o', compiler_trace_file.name]
+ trace_duration: Optional[timedelta]
+ ) -> str:
+ argv = ['-i', inodes_path, '--perfetto-trace',
+ perfetto_trace_file]
if trace_duration is not None:
argv += ['--duration', str(int(trace_duration.total_seconds()
- * PerfettoTraceCollector.MS_PER_SEC))]
+ * PerfettoTraceCollector.MS_PER_SEC))]
print_utils.debug_print(argv)
- compiler.main(argv)
- return compiler_trace_file
+ return argv
def execute_run_using_perfetto_trace(collector_info,
run_combos: Iterable[RunCommandArgs],
simulate: bool,
inodes_path: str,
- timeout: int) -> DataFrame:
+ timeout: int,
+ compiler_type: CompilerType) -> DataFrame:
""" Executes run based on perfetto trace. """
passed, perfetto_trace_file = run_perfetto_collector(collector_info,
timeout,
@@ -244,9 +247,15 @@
if simulate:
compiler_trace_file = tempfile.NamedTemporaryFile()
else:
- compiler_trace_file = compile_perfetto_trace(inodes_path,
- perfetto_trace_file.name,
- combos.trace_duration)
+ ri_compiler_argv = build_ri_compiler_argv(inodes_path,
+ perfetto_trace_file.name,
+ combos.trace_duration)
+ compiler_trace_file = compiler.compile(compiler_type,
+ inodes_path,
+ ri_compiler_argv,
+ combos.package,
+ combos.activity)
+
with compiler_trace_file:
combos = combos._replace(input=compiler_trace_file.name)
print_utils.debug_print(combos)
@@ -261,7 +270,8 @@
grouped_run_combos: Iterable[Tuple[CollectorPackageInfo, Iterable[RunCommandArgs]]],
simulate: bool,
inodes_path: str,
- timeout: int):
+ timeout: int,
+ compiler_type: CompilerType):
# nothing will work if the screen isn't unlocked first.
cmd_utils.execute_arbitrary_command([_UNLOCK_SCREEN_SCRIPT],
timeout,
@@ -273,7 +283,8 @@
run_combos,
simulate,
inodes_path,
- timeout)
+ timeout,
+ compiler_type)
def gather_results(commands: Iterable[Tuple[DataFrame]],
key_list: List[str], value_list: List[Tuple[str, ...]]):
@@ -361,7 +372,8 @@
exec = execute_run_combos(grouped_combos(),
opts.simulate,
opts.inodes,
- opts.timeout)
+ opts.timeout,
+ opts.compiler_type)
results = gather_results(exec, _COMBINATORIAL_OPTIONS, combos())
diff --git a/startop/scripts/app_startup/app_startup_runner_test.py b/startop/scripts/app_startup/app_startup_runner_test.py
index 42ea5f0..382f6f3 100755
--- a/startop/scripts/app_startup/app_startup_runner_test.py
+++ b/startop/scripts/app_startup/app_startup_runner_test.py
@@ -92,7 +92,7 @@
"""
d = {'compiler_filters': None, 'simulate': False, 'debug': False,
'output': None, 'timeout': 10, 'loop_count': 1, 'inodes': None,
- 'trace_duration': None}
+ 'trace_duration': None, 'compiler_type': asr.CompilerType.HOST}
d.update(kwargs)
return d
diff --git a/startop/scripts/iorap/compiler.py b/startop/scripts/iorap/compiler.py
old mode 100755
new mode 100644
index 17b58c1..1426d34
--- a/startop/scripts/iorap/compiler.py
+++ b/startop/scripts/iorap/compiler.py
@@ -1,323 +1,73 @@
#!/usr/bin/env python3
-
#
-# Copyright (C) 2019 The Android Open Source Project
+# Copyright 2019, The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
-# http://www.apache.org/licenses/LICENSE-2.0
+# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
-#
-#
-# Dependencies:
-#
-# $> sudo apt-get install python3-pip
-# $> pip3 install --user protobuf sqlalchemy sqlite3
-#
-
-import optparse
+import importlib
import os
-import re
import sys
import tempfile
-from pathlib import Path
-from datetime import timedelta
-from typing import Iterable, Optional, List
+from enum import Enum
+from typing import TextIO, List
+# local import
DIR = os.path.abspath(os.path.dirname(__file__))
sys.path.append(os.path.dirname(DIR))
-from iorap.generated.TraceFile_pb2 import *
-from iorap.lib.inode2filename import Inode2Filename
+import lib.print_utils as print_utils
-parent_dir_name = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
-sys.path.append(parent_dir_name)
-from trace_analyzer.lib.trace2db import Trace2Db, MmFilemapAddToPageCache, \
- RawFtraceEntry
-import lib.cmd_utils as cmd_utils
+# Type of compiler.
+class CompilerType(Enum):
+ HOST = 1 # iorap.cmd.compiler on host
+ DEVICE = 2 # adb shell iorap.cmd.compiler
+ RI = 3 # compiler.py
-_PAGE_SIZE = 4096 # adb shell getconf PAGESIZE ## size of a memory page in bytes.
-ANDROID_BUILD_TOP = Path(parent_dir_name).parents[3]
-TRACECONV_BIN = ANDROID_BUILD_TOP.joinpath(
- 'external/perfetto/tools/traceconv')
+def compile_perfetto_trace_ri(
+ argv: List[str],
+ compiler) -> TextIO:
+ print_utils.debug_print('Compile using RI compiler.')
+ compiler_trace_file = tempfile.NamedTemporaryFile()
+ argv.extend(['-o', compiler_trace_file.name])
+ print_utils.debug_print(argv)
+ compiler.main([''] + argv)
+ return compiler_trace_file
-class PageRun:
- """
- Intermediate representation for a run of one or more pages.
- """
- def __init__(self, device_number: int, inode: int, offset: int, length: int):
- self.device_number = device_number
- self.inode = inode
- self.offset = offset
- self.length = length
+def compile_perfetto_trace_device(inodes_path: str,
+ package: str,
+ activity: str,
+ compiler) -> TextIO:
+ print_utils.debug_print('Compile using on-device compiler.')
+ compiler_trace_file = tempfile.NamedTemporaryFile()
+ compiler.main(inodes_path, package, activity, compiler_trace_file.name)
+ return compiler_trace_file
- def __str__(self):
- return "PageRun(device_number=%d, inode=%d, offset=%d, length=%d)" \
- %(self.device_number, self.inode, self.offset, self.length)
+def compile(compiler_type: CompilerType,
+ inodes_path: str,
+ ri_compiler_argv,
+ package: str,
+ activity: str) -> TextIO:
+ if compiler_type == CompilerType.RI:
+ compiler = importlib.import_module('iorap.compiler_ri')
+ compiler_trace_file = compile_perfetto_trace_ri(ri_compiler_argv,
+ compiler)
+ return compiler_trace_file
+ if compiler_type == CompilerType.DEVICE:
+ compiler = importlib.import_module('iorap.compiler_device')
+ compiler_trace_file = compile_perfetto_trace_device(inodes_path,
+ package,
+ activity,
+ compiler)
+ return compiler_trace_file
-def debug_print(msg):
- #print(msg)
- pass
-
-UNDER_LAUNCH = False
-
-def page_cache_entries_to_runs(page_cache_entries: Iterable[MmFilemapAddToPageCache]):
- global _PAGE_SIZE
-
- runs = [
- PageRun(device_number=pg_entry.dev, inode=pg_entry.ino, offset=pg_entry.ofs,
- length=_PAGE_SIZE)
- for pg_entry in page_cache_entries
- ]
-
- for r in runs:
- debug_print(r)
-
- print("Stats: Page runs totaling byte length: %d" %(len(runs) * _PAGE_SIZE))
-
- return runs
-
-def optimize_page_runs(page_runs):
- new_entries = []
- last_entry = None
- for pg_entry in page_runs:
- if last_entry:
- if pg_entry.device_number == last_entry.device_number and pg_entry.inode == last_entry.inode:
- # we are dealing with a run for the same exact file as a previous run.
- if pg_entry.offset == last_entry.offset + last_entry.length:
- # trivially contiguous entries. merge them together.
- last_entry.length += pg_entry.length
- continue
- # Default: Add the run without merging it to a previous run.
- last_entry = pg_entry
- new_entries.append(pg_entry)
- return new_entries
-
-def is_filename_matching_filter(file_name, filters=[]):
- """
- Blacklist-style regular expression filters.
-
- :return: True iff file_name has an RE match in one of the filters.
- """
- for filt in filters:
- res = re.search(filt, file_name)
- if res:
- return True
-
- return False
-
-def build_protobuf(page_runs, inode2filename, filters=[]):
- trace_file = TraceFile()
- trace_file_index = trace_file.index
-
- file_id_counter = 0
- file_id_map = {} # filename -> id
-
- stats_length_total = 0
- filename_stats = {} # filename -> total size
-
- skipped_inode_map = {}
- filtered_entry_map = {} # filename -> count
-
- for pg_entry in page_runs:
- fn = inode2filename.resolve(pg_entry.device_number, pg_entry.inode)
- if not fn:
- skipped_inode_map[pg_entry.inode] = skipped_inode_map.get(pg_entry.inode, 0) + 1
- continue
-
- filename = fn
-
- if filters and not is_filename_matching_filter(filename, filters):
- filtered_entry_map[filename] = filtered_entry_map.get(filename, 0) + 1
- continue
-
- file_id = file_id_map.get(filename)
- if not file_id:
- file_id = file_id_counter
- file_id_map[filename] = file_id_counter
- file_id_counter = file_id_counter + 1
-
- file_index_entry = trace_file_index.entries.add()
- file_index_entry.id = file_id
- file_index_entry.file_name = filename
-
- # already in the file index, add the file entry.
- file_entry = trace_file.list.entries.add()
- file_entry.index_id = file_id
- file_entry.file_length = pg_entry.length
- stats_length_total += file_entry.file_length
- file_entry.file_offset = pg_entry.offset
-
- filename_stats[filename] = filename_stats.get(filename, 0) + file_entry.file_length
-
- for inode, count in skipped_inode_map.items():
- print("WARNING: Skip inode %s because it's not in inode map (%d entries)" %(inode, count))
-
- print("Stats: Sum of lengths %d" %(stats_length_total))
-
- if filters:
- print("Filter: %d total files removed." %(len(filtered_entry_map)))
-
- for fn, count in filtered_entry_map.items():
- print("Filter: File '%s' removed '%d' entries." %(fn, count))
-
- for filename, file_size in filename_stats.items():
- print("%s,%s" %(filename, file_size))
-
- return trace_file
-
-def calc_trace_end_time(trace2db: Trace2Db,
- trace_duration: Optional[timedelta]) -> float:
- """
- Calculates the end time based on the trace duration.
- The start time is the first receiving mm file map event.
- The end time is the start time plus the trace duration.
- All of them are in milliseconds.
- """
- # If the duration is not set, assume all time is acceptable.
- if trace_duration is None:
- # float('inf')
- return RawFtraceEntry.__table__.c.timestamp.type.python_type('inf')
-
- first_event = trace2db.session.query(MmFilemapAddToPageCache).join(
- MmFilemapAddToPageCache.raw_ftrace_entry).order_by(
- RawFtraceEntry.timestamp).first()
-
- # total_seconds() will return a float number.
- return first_event.raw_ftrace_entry.timestamp + trace_duration.total_seconds()
-
-def query_add_to_page_cache(trace2db: Trace2Db, trace_duration: Optional[timedelta]):
- end_time = calc_trace_end_time(trace2db, trace_duration)
- # SELECT * FROM tbl ORDER BY id;
- return trace2db.session.query(MmFilemapAddToPageCache).join(
- MmFilemapAddToPageCache.raw_ftrace_entry).filter(
- RawFtraceEntry.timestamp <= end_time).order_by(
- MmFilemapAddToPageCache.id).all()
-
-def transform_perfetto_trace_to_systrace(path_to_perfetto_trace: str,
- path_to_tmp_systrace: str) -> None:
- """ Transforms the systrace file from perfetto trace. """
- cmd_utils.run_command_nofail([str(TRACECONV_BIN),
- 'systrace',
- path_to_perfetto_trace,
- path_to_tmp_systrace])
-
-
-def run(sql_db_path:str,
- trace_file:str,
- trace_duration:Optional[timedelta],
- output_file:str,
- inode_table:str,
- filter:List[str]) -> int:
- trace2db = Trace2Db(sql_db_path)
- # Speed optimization: Skip any entries that aren't mm_filemap_add_to_pagecache.
- trace2db.set_raw_ftrace_entry_filter(\
- lambda entry: entry['function'] == 'mm_filemap_add_to_page_cache')
- # TODO: parse multiple trace files here.
- parse_count = trace2db.parse_file_into_db(trace_file)
-
- mm_filemap_add_to_page_cache_rows = query_add_to_page_cache(trace2db,
- trace_duration)
- print("DONE. Parsed %d entries into sql db." %(len(mm_filemap_add_to_page_cache_rows)))
-
- page_runs = page_cache_entries_to_runs(mm_filemap_add_to_page_cache_rows)
- print("DONE. Converted %d entries" %(len(page_runs)))
-
- # TODO: flags to select optimizations.
- optimized_page_runs = optimize_page_runs(page_runs)
- print("DONE. Optimized down to %d entries" %(len(optimized_page_runs)))
-
- print("Build protobuf...")
- trace_file = build_protobuf(optimized_page_runs, inode_table, filter)
-
- print("Write protobuf to file...")
- output_file = open(output_file, 'wb')
- output_file.write(trace_file.SerializeToString())
- output_file.close()
-
- print("DONE")
-
- # TODO: Silent running mode [no output except on error] for build runs.
-
- return 0
-
-def main(argv):
- parser = optparse.OptionParser(usage="Usage: %prog [options]", description="Compile systrace file into TraceFile.pb")
- parser.add_option('-i', dest='inode_data_file', metavar='FILE',
- help='Read cached inode data from a file saved earlier with pagecache.py -d')
- parser.add_option('-t', dest='trace_file', metavar='FILE',
- help='Path to systrace file (trace.html) that will be parsed')
- parser.add_option('--perfetto-trace', dest='perfetto_trace_file',
- metavar='FILE',
- help='Path to perfetto trace that will be parsed')
-
- parser.add_option('--db', dest='sql_db', metavar='FILE',
- help='Path to intermediate sqlite3 database [default: in-memory].')
-
- parser.add_option('-f', dest='filter', action="append", default=[],
- help="Add file filter. All file entries not matching one of the filters are discarded.")
-
- parser.add_option('-l', dest='launch_lock', action="store_true", default=False,
- help="Exclude all events not inside launch_lock")
-
- parser.add_option('-o', dest='output_file', metavar='FILE',
- help='Output protobuf file')
-
- parser.add_option('--duration', dest='trace_duration', action="store",
- type=int, help='The duration of trace in milliseconds.')
-
- options, categories = parser.parse_args(argv[1:])
-
- # TODO: OptionParser should have some flags to make these mandatory.
- if not options.inode_data_file:
- parser.error("-i is required")
- if not options.trace_file and not options.perfetto_trace_file:
- parser.error("one of -t or --perfetto-trace is required")
- if options.trace_file and options.perfetto_trace_file:
- parser.error("please enter either -t or --perfetto-trace, not both")
- if not options.output_file:
- parser.error("-o is required")
-
- if options.launch_lock:
- print("INFO: Launch lock flag (-l) enabled; filtering all events not inside launch_lock.")
-
- inode_table = Inode2Filename.new_from_filename(options.inode_data_file)
-
- sql_db_path = ":memory:"
- if options.sql_db:
- sql_db_path = options.sql_db
-
- trace_duration = timedelta(milliseconds=options.trace_duration) if \
- options.trace_duration is not None else None
-
- # if the input is systrace
- if options.trace_file:
- return run(sql_db_path,
- options.trace_file,
- trace_duration,
- options.output_file,
- inode_table,
- options.filter)
-
- # if the input is perfetto trace
- # TODO python 3.7 switch to using nullcontext
- with tempfile.NamedTemporaryFile() as trace_file:
- transform_perfetto_trace_to_systrace(options.perfetto_trace_file,
- trace_file.name)
- return run(sql_db_path,
- trace_file.name,
- trace_duration,
- options.output_file,
- inode_table,
- options.filter)
-
-if __name__ == '__main__':
- print(sys.argv)
- sys.exit(main(sys.argv))
+ # Should not arrive here.
+ raise ValueError('Unknown compiler type')
diff --git a/startop/scripts/iorap/compiler_device.py b/startop/scripts/iorap/compiler_device.py
new file mode 100644
index 0000000..d941cd9
--- /dev/null
+++ b/startop/scripts/iorap/compiler_device.py
@@ -0,0 +1,68 @@
+#!/usr/bin/env python3
+#
+# Copyright 2019, The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT 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 argparse
+import os
+import sys
+from typing import List
+
+DIR = os.path.abspath(os.path.dirname(__file__))
+sys.path.append(os.path.dirname(DIR)) # framework/base/startop/script
+import lib.print_utils as print_utils
+import iorap.lib.iorapd_utils as iorapd_utils
+from app_startup.lib.app_runner import AppRunner
+
+IORAP_COMMON_BASH_SCRIPT = os.path.join(DIR, 'common')
+
+def parse_options(argv: List[str] = None):
+ """Parses command line arguments and returns an argparse Namespace object."""
+ parser = argparse.ArgumentParser(description="Compile perfetto trace file")
+ required_named = parser.add_argument_group('required named arguments')
+
+ required_named.add_argument('-i', dest='inodes', metavar='FILE',
+ help='Read cached inode data from a file saved '
+ 'earlier with pagecache.py -d')
+ required_named.add_argument('-p', dest='package',
+ help='Package of the app to be compiled')
+
+ optional_named = parser.add_argument_group('optional named arguments')
+ optional_named.add_argument('-o', dest='output',
+ help='The compiled trace is stored into the output file')
+ optional_named.add_argument('-a', dest='activity',
+ help='Activity of the app to be compiled')
+ optional_named.add_argument('-d', dest='debug', action='store_true'
+ , help='Activity of the app to be compiled')
+
+ return parser.parse_args(argv)
+
+def main(inodes, package, activity, output, **kwargs) -> int:
+ """Entries of the program."""
+ if not activity:
+ activity = AppRunner.get_activity(package)
+
+ passed = iorapd_utils.compile_perfetto_trace_on_device(package, activity,
+ inodes)
+ if passed and output:
+ iorapd_utils.get_iorapd_compiler_trace(package, activity, output)
+
+ return 0
+
+if __name__ == '__main__':
+ opts = parse_options()
+ if opts.debug:
+ print_utils.DEBUG = opts.debug
+ print_utils.debug_print(opts)
+ sys.exit(main(**(vars(opts))))
diff --git a/startop/scripts/iorap/compiler_ri.py b/startop/scripts/iorap/compiler_ri.py
new file mode 100755
index 0000000..17b58c1
--- /dev/null
+++ b/startop/scripts/iorap/compiler_ri.py
@@ -0,0 +1,323 @@
+#!/usr/bin/env python3
+
+#
+# Copyright (C) 2019 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+#
+# Dependencies:
+#
+# $> sudo apt-get install python3-pip
+# $> pip3 install --user protobuf sqlalchemy sqlite3
+#
+
+import optparse
+import os
+import re
+import sys
+import tempfile
+from pathlib import Path
+from datetime import timedelta
+from typing import Iterable, Optional, List
+
+DIR = os.path.abspath(os.path.dirname(__file__))
+sys.path.append(os.path.dirname(DIR))
+from iorap.generated.TraceFile_pb2 import *
+from iorap.lib.inode2filename import Inode2Filename
+
+parent_dir_name = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
+sys.path.append(parent_dir_name)
+from trace_analyzer.lib.trace2db import Trace2Db, MmFilemapAddToPageCache, \
+ RawFtraceEntry
+import lib.cmd_utils as cmd_utils
+
+_PAGE_SIZE = 4096 # adb shell getconf PAGESIZE ## size of a memory page in bytes.
+ANDROID_BUILD_TOP = Path(parent_dir_name).parents[3]
+TRACECONV_BIN = ANDROID_BUILD_TOP.joinpath(
+ 'external/perfetto/tools/traceconv')
+
+class PageRun:
+ """
+ Intermediate representation for a run of one or more pages.
+ """
+ def __init__(self, device_number: int, inode: int, offset: int, length: int):
+ self.device_number = device_number
+ self.inode = inode
+ self.offset = offset
+ self.length = length
+
+ def __str__(self):
+ return "PageRun(device_number=%d, inode=%d, offset=%d, length=%d)" \
+ %(self.device_number, self.inode, self.offset, self.length)
+
+def debug_print(msg):
+ #print(msg)
+ pass
+
+UNDER_LAUNCH = False
+
+def page_cache_entries_to_runs(page_cache_entries: Iterable[MmFilemapAddToPageCache]):
+ global _PAGE_SIZE
+
+ runs = [
+ PageRun(device_number=pg_entry.dev, inode=pg_entry.ino, offset=pg_entry.ofs,
+ length=_PAGE_SIZE)
+ for pg_entry in page_cache_entries
+ ]
+
+ for r in runs:
+ debug_print(r)
+
+ print("Stats: Page runs totaling byte length: %d" %(len(runs) * _PAGE_SIZE))
+
+ return runs
+
+def optimize_page_runs(page_runs):
+ new_entries = []
+ last_entry = None
+ for pg_entry in page_runs:
+ if last_entry:
+ if pg_entry.device_number == last_entry.device_number and pg_entry.inode == last_entry.inode:
+ # we are dealing with a run for the same exact file as a previous run.
+ if pg_entry.offset == last_entry.offset + last_entry.length:
+ # trivially contiguous entries. merge them together.
+ last_entry.length += pg_entry.length
+ continue
+ # Default: Add the run without merging it to a previous run.
+ last_entry = pg_entry
+ new_entries.append(pg_entry)
+ return new_entries
+
+def is_filename_matching_filter(file_name, filters=[]):
+ """
+ Blacklist-style regular expression filters.
+
+ :return: True iff file_name has an RE match in one of the filters.
+ """
+ for filt in filters:
+ res = re.search(filt, file_name)
+ if res:
+ return True
+
+ return False
+
+def build_protobuf(page_runs, inode2filename, filters=[]):
+ trace_file = TraceFile()
+ trace_file_index = trace_file.index
+
+ file_id_counter = 0
+ file_id_map = {} # filename -> id
+
+ stats_length_total = 0
+ filename_stats = {} # filename -> total size
+
+ skipped_inode_map = {}
+ filtered_entry_map = {} # filename -> count
+
+ for pg_entry in page_runs:
+ fn = inode2filename.resolve(pg_entry.device_number, pg_entry.inode)
+ if not fn:
+ skipped_inode_map[pg_entry.inode] = skipped_inode_map.get(pg_entry.inode, 0) + 1
+ continue
+
+ filename = fn
+
+ if filters and not is_filename_matching_filter(filename, filters):
+ filtered_entry_map[filename] = filtered_entry_map.get(filename, 0) + 1
+ continue
+
+ file_id = file_id_map.get(filename)
+ if not file_id:
+ file_id = file_id_counter
+ file_id_map[filename] = file_id_counter
+ file_id_counter = file_id_counter + 1
+
+ file_index_entry = trace_file_index.entries.add()
+ file_index_entry.id = file_id
+ file_index_entry.file_name = filename
+
+ # already in the file index, add the file entry.
+ file_entry = trace_file.list.entries.add()
+ file_entry.index_id = file_id
+ file_entry.file_length = pg_entry.length
+ stats_length_total += file_entry.file_length
+ file_entry.file_offset = pg_entry.offset
+
+ filename_stats[filename] = filename_stats.get(filename, 0) + file_entry.file_length
+
+ for inode, count in skipped_inode_map.items():
+ print("WARNING: Skip inode %s because it's not in inode map (%d entries)" %(inode, count))
+
+ print("Stats: Sum of lengths %d" %(stats_length_total))
+
+ if filters:
+ print("Filter: %d total files removed." %(len(filtered_entry_map)))
+
+ for fn, count in filtered_entry_map.items():
+ print("Filter: File '%s' removed '%d' entries." %(fn, count))
+
+ for filename, file_size in filename_stats.items():
+ print("%s,%s" %(filename, file_size))
+
+ return trace_file
+
+def calc_trace_end_time(trace2db: Trace2Db,
+ trace_duration: Optional[timedelta]) -> float:
+ """
+ Calculates the end time based on the trace duration.
+ The start time is the first receiving mm file map event.
+ The end time is the start time plus the trace duration.
+ All of them are in milliseconds.
+ """
+ # If the duration is not set, assume all time is acceptable.
+ if trace_duration is None:
+ # float('inf')
+ return RawFtraceEntry.__table__.c.timestamp.type.python_type('inf')
+
+ first_event = trace2db.session.query(MmFilemapAddToPageCache).join(
+ MmFilemapAddToPageCache.raw_ftrace_entry).order_by(
+ RawFtraceEntry.timestamp).first()
+
+ # total_seconds() will return a float number.
+ return first_event.raw_ftrace_entry.timestamp + trace_duration.total_seconds()
+
+def query_add_to_page_cache(trace2db: Trace2Db, trace_duration: Optional[timedelta]):
+ end_time = calc_trace_end_time(trace2db, trace_duration)
+ # SELECT * FROM tbl ORDER BY id;
+ return trace2db.session.query(MmFilemapAddToPageCache).join(
+ MmFilemapAddToPageCache.raw_ftrace_entry).filter(
+ RawFtraceEntry.timestamp <= end_time).order_by(
+ MmFilemapAddToPageCache.id).all()
+
+def transform_perfetto_trace_to_systrace(path_to_perfetto_trace: str,
+ path_to_tmp_systrace: str) -> None:
+ """ Transforms the systrace file from perfetto trace. """
+ cmd_utils.run_command_nofail([str(TRACECONV_BIN),
+ 'systrace',
+ path_to_perfetto_trace,
+ path_to_tmp_systrace])
+
+
+def run(sql_db_path:str,
+ trace_file:str,
+ trace_duration:Optional[timedelta],
+ output_file:str,
+ inode_table:str,
+ filter:List[str]) -> int:
+ trace2db = Trace2Db(sql_db_path)
+ # Speed optimization: Skip any entries that aren't mm_filemap_add_to_pagecache.
+ trace2db.set_raw_ftrace_entry_filter(\
+ lambda entry: entry['function'] == 'mm_filemap_add_to_page_cache')
+ # TODO: parse multiple trace files here.
+ parse_count = trace2db.parse_file_into_db(trace_file)
+
+ mm_filemap_add_to_page_cache_rows = query_add_to_page_cache(trace2db,
+ trace_duration)
+ print("DONE. Parsed %d entries into sql db." %(len(mm_filemap_add_to_page_cache_rows)))
+
+ page_runs = page_cache_entries_to_runs(mm_filemap_add_to_page_cache_rows)
+ print("DONE. Converted %d entries" %(len(page_runs)))
+
+ # TODO: flags to select optimizations.
+ optimized_page_runs = optimize_page_runs(page_runs)
+ print("DONE. Optimized down to %d entries" %(len(optimized_page_runs)))
+
+ print("Build protobuf...")
+ trace_file = build_protobuf(optimized_page_runs, inode_table, filter)
+
+ print("Write protobuf to file...")
+ output_file = open(output_file, 'wb')
+ output_file.write(trace_file.SerializeToString())
+ output_file.close()
+
+ print("DONE")
+
+ # TODO: Silent running mode [no output except on error] for build runs.
+
+ return 0
+
+def main(argv):
+ parser = optparse.OptionParser(usage="Usage: %prog [options]", description="Compile systrace file into TraceFile.pb")
+ parser.add_option('-i', dest='inode_data_file', metavar='FILE',
+ help='Read cached inode data from a file saved earlier with pagecache.py -d')
+ parser.add_option('-t', dest='trace_file', metavar='FILE',
+ help='Path to systrace file (trace.html) that will be parsed')
+ parser.add_option('--perfetto-trace', dest='perfetto_trace_file',
+ metavar='FILE',
+ help='Path to perfetto trace that will be parsed')
+
+ parser.add_option('--db', dest='sql_db', metavar='FILE',
+ help='Path to intermediate sqlite3 database [default: in-memory].')
+
+ parser.add_option('-f', dest='filter', action="append", default=[],
+ help="Add file filter. All file entries not matching one of the filters are discarded.")
+
+ parser.add_option('-l', dest='launch_lock', action="store_true", default=False,
+ help="Exclude all events not inside launch_lock")
+
+ parser.add_option('-o', dest='output_file', metavar='FILE',
+ help='Output protobuf file')
+
+ parser.add_option('--duration', dest='trace_duration', action="store",
+ type=int, help='The duration of trace in milliseconds.')
+
+ options, categories = parser.parse_args(argv[1:])
+
+ # TODO: OptionParser should have some flags to make these mandatory.
+ if not options.inode_data_file:
+ parser.error("-i is required")
+ if not options.trace_file and not options.perfetto_trace_file:
+ parser.error("one of -t or --perfetto-trace is required")
+ if options.trace_file and options.perfetto_trace_file:
+ parser.error("please enter either -t or --perfetto-trace, not both")
+ if not options.output_file:
+ parser.error("-o is required")
+
+ if options.launch_lock:
+ print("INFO: Launch lock flag (-l) enabled; filtering all events not inside launch_lock.")
+
+ inode_table = Inode2Filename.new_from_filename(options.inode_data_file)
+
+ sql_db_path = ":memory:"
+ if options.sql_db:
+ sql_db_path = options.sql_db
+
+ trace_duration = timedelta(milliseconds=options.trace_duration) if \
+ options.trace_duration is not None else None
+
+ # if the input is systrace
+ if options.trace_file:
+ return run(sql_db_path,
+ options.trace_file,
+ trace_duration,
+ options.output_file,
+ inode_table,
+ options.filter)
+
+ # if the input is perfetto trace
+ # TODO python 3.7 switch to using nullcontext
+ with tempfile.NamedTemporaryFile() as trace_file:
+ transform_perfetto_trace_to_systrace(options.perfetto_trace_file,
+ trace_file.name)
+ return run(sql_db_path,
+ trace_file.name,
+ trace_duration,
+ options.output_file,
+ inode_table,
+ options.filter)
+
+if __name__ == '__main__':
+ print(sys.argv)
+ sys.exit(main(sys.argv))
diff --git a/startop/scripts/iorap/compiler_test.py b/startop/scripts/iorap/compiler_test.py
index 1a9f059..d1f11c5 100644
--- a/startop/scripts/iorap/compiler_test.py
+++ b/startop/scripts/iorap/compiler_test.py
@@ -30,7 +30,7 @@
"""
import os
-import compiler
+import compiler_host as compiler
DIR = os.path.abspath(os.path.dirname(__file__))
TEXTCACHE = os.path.join(DIR, 'test_fixtures/compiler/common_textcache')
diff --git a/startop/scripts/iorap/lib/iorapd_utils.py b/startop/scripts/iorap/lib/iorapd_utils.py
index 0d62180..f6f21fd 100644
--- a/startop/scripts/iorap/lib/iorapd_utils.py
+++ b/startop/scripts/iorap/lib/iorapd_utils.py
@@ -18,10 +18,9 @@
import os
import sys
-from pathlib import Path
-# up to two level, like '../../'
-sys.path.append(Path(os.path.abspath(__file__)).parents[2])
+# up to two level
+sys.path.append(os.path.join(os.path.abspath(__file__),'../..'))
import lib.cmd_utils as cmd_utils
IORAPID_LIB_DIR = os.path.abspath(os.path.dirname(__file__))
@@ -39,6 +38,22 @@
# Match logic of 'AppComponentName' in iorap::compiler C++ code.
return '{}/{}%2F{}.{}'.format(IORAPD_DATA_PATH, package, activity, suffix)
+def compile_perfetto_trace_on_device(package: str, activity: str,
+ inodes: str) -> bool:
+ """Compiles the perfetto trace using on-device compiler."""
+ passed, _ = cmd_utils.run_shell_func(IORAP_COMMON_BASH_SCRIPT,
+ 'iorapd_compiler_for_app_trace',
+ [package, activity, inodes])
+ return passed
+
+def get_iorapd_compiler_trace(package: str, activity: str, dest: str) -> str:
+ """Gets compiler trace to dest file."""
+ src = _iorapd_path_to_data_file(package, activity, 'compiled_trace.pb')
+ passed, _ = cmd_utils.run_shell_command('adb pull "{}" "{}"'.format(src, dest))
+ if not passed:
+ return False
+ return True
+
def iorapd_compiler_install_trace_file(package: str, activity: str,
input_file: str) -> bool:
"""Installs a compiled trace file.
diff --git a/telephony/java/android/telephony/PhoneStateListener.java b/telephony/java/android/telephony/PhoneStateListener.java
index 271195b..6a3c06e 100644
--- a/telephony/java/android/telephony/PhoneStateListener.java
+++ b/telephony/java/android/telephony/PhoneStateListener.java
@@ -33,13 +33,13 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.IPhoneStateListener;
+import dalvik.system.VMRuntime;
+
import java.lang.ref.WeakReference;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executor;
-import dalvik.system.VMRuntime;
-
/**
* A listener class for monitoring changes in specific telephony states
* on the device, including service state, signal strength, message
@@ -301,11 +301,6 @@
* it could be the current active opportunistic subscription in use, or the
* subscription user selected as default data subscription in DSDS mode.
*
- * Requires Permission: No permission is required to listen, but notification requires
- * {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE} or the calling
- * app has carrier privileges (see {@link TelephonyManager#hasCarrierPrivileges})
- * on any active subscription.
- *
* @see #onActiveDataSubscriptionIdChanged
*/
public static final int LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE = 0x00400000;
diff --git a/tests/BootImageProfileTest/Android.bp b/tests/BootImageProfileTest/Android.bp
new file mode 100644
index 0000000..1b097a8
--- /dev/null
+++ b/tests/BootImageProfileTest/Android.bp
@@ -0,0 +1,20 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+java_test_host {
+ name: "BootImageProfileTest",
+ srcs: ["src/**/*.java"],
+ libs: ["tradefed"],
+ test_suites: ["general-tests"],
+}
diff --git a/tests/BootImageProfileTest/AndroidTest.xml b/tests/BootImageProfileTest/AndroidTest.xml
new file mode 100644
index 0000000..c132007
--- /dev/null
+++ b/tests/BootImageProfileTest/AndroidTest.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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 BootImageProfileTest">
+ <!-- do not use DeviceSetup#set-property because it reboots the device b/136200738.
+ furthermore the changes in /data/local.prop don't actually seem to get picked up.
+ -->
+ <target_preparer
+ class="com.android.tradefed.targetprep.DeviceSetup">
+ <!-- we need this magic flag, otherwise it always reboots and breaks the selinux -->
+ <option name="force-skip-system-props" value="true" />
+
+ <option name="run-command" value="setprop dalvik.vm.profilesystemserver true" />
+ <option name="run-command" value="setprop dalvik.vm.profilebootclasspath true" />
+
+ <!-- Profiling does not pick up the above changes we restart the shell -->
+ <option name="run-command" value="stop" />
+ <option name="run-command" value="start" />
+
+ <!-- give it some time to restart the shell; otherwise the first unit test might fail -->
+ <option name="run-command" value="sleep 2" />
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.HostTest" >
+ <option name="class" value="com.android.bootimageprofile.BootImageProfileTest" />
+ </test>
+</configuration>
diff --git a/tests/BootImageProfileTest/TEST_MAPPING b/tests/BootImageProfileTest/TEST_MAPPING
new file mode 100644
index 0000000..1b569f9
--- /dev/null
+++ b/tests/BootImageProfileTest/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "presubmit": [
+ {
+ "name": "BootImageProfileTest"
+ }
+ ]
+}
diff --git a/tests/BootImageProfileTest/src/com/android/bootimageprofile/BootImageProfileTest.java b/tests/BootImageProfileTest/src/com/android/bootimageprofile/BootImageProfileTest.java
new file mode 100644
index 0000000..17986a3
--- /dev/null
+++ b/tests/BootImageProfileTest/src/com/android/bootimageprofile/BootImageProfileTest.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bootimageprofile;
+
+import static org.junit.Assert.assertTrue;
+
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.IDeviceTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class BootImageProfileTest implements IDeviceTest {
+ private ITestDevice mTestDevice;
+ private static final String SYSTEM_SERVER_PROFILE =
+ "/data/misc/profiles/cur/0/android/primary.prof";
+
+ @Override
+ public void setDevice(ITestDevice testDevice) {
+ mTestDevice = testDevice;
+ }
+
+ @Override
+ public ITestDevice getDevice() {
+ return mTestDevice;
+ }
+
+ /**
+ * Test that the boot image profile properties are set.
+ */
+ @Test
+ public void testProperties() throws Exception {
+ String res = mTestDevice.getProperty("dalvik.vm.profilebootclasspath");
+ assertTrue("profile boot class path not enabled", res != null && res.equals("true"));
+ res = mTestDevice.getProperty("dalvik.vm.profilesystemserver");
+ assertTrue("profile system server not enabled", res != null && res.equals("true"));
+ }
+
+ private void forceSaveProfile(String pkg) throws Exception {
+ String pid = mTestDevice.executeShellCommand("pidof " + pkg).trim();
+ assertTrue("Invalid pid " + pid, pid.length() > 0);
+ String res = mTestDevice.executeShellCommand("kill -s SIGUSR1 " + pid).trim();
+ assertTrue("kill SIGUSR1: " + res, res.length() == 0);
+ }
+
+ @Test
+ public void testSystemServerProfile() throws Exception {
+ // Trunacte the profile before force it to be saved to prevent previous profiles
+ // causing the test to pass.
+ String res;
+ res = mTestDevice.executeShellCommand("truncate -s 0 " + SYSTEM_SERVER_PROFILE).trim();
+ assertTrue(res, res.length() == 0);
+ // Force save profiles in case the system just started.
+ Thread.sleep(1000);
+ forceSaveProfile("system_server");
+ Thread.sleep(2000);
+ // Validate that the profile is non empty.
+ res = mTestDevice.executeShellCommand("profman --dump-only --profile-file="
+ + SYSTEM_SERVER_PROFILE);
+ boolean sawFramework = false;
+ boolean sawServices = false;
+ for (String line : res.split("\n")) {
+ if (line.contains("framework.jar")) {
+ sawFramework = true;
+ } else if (line.contains("services.jar")) {
+ sawServices = true;
+ }
+ }
+ assertTrue("Did not see framework.jar in " + res, sawFramework);
+ assertTrue("Did not see services.jar in " + res, sawServices);
+ }
+}
diff --git a/tests/PackageWatchdog/Android.bp b/tests/PackageWatchdog/Android.bp
index 88d92c4..0b75039 100644
--- a/tests/PackageWatchdog/Android.bp
+++ b/tests/PackageWatchdog/Android.bp
@@ -23,6 +23,7 @@
"androidx.test.rules",
"services.core",
"services.net",
+ "truth-prebuilt",
],
libs: ["android.test.runner"],
jni_libs: [
diff --git a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
index 2d867f9..9a60330 100644
--- a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
+++ b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
@@ -18,9 +18,8 @@
import static android.service.watchdog.ExplicitHealthCheckService.PackageConfig;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
@@ -43,6 +42,7 @@
import androidx.test.InstrumentationRegistry;
+import com.android.server.PackageWatchdog.HealthCheckState;
import com.android.server.PackageWatchdog.MonitoredPackage;
import com.android.server.PackageWatchdog.PackageHealthObserver;
import com.android.server.PackageWatchdog.PackageHealthObserverImpact;
@@ -64,7 +64,6 @@
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
-// TODO: Use Truth in tests.
/**
* Test PackageWatchdog.
*/
@@ -118,13 +117,12 @@
TestObserver observer = new TestObserver(OBSERVER_NAME_1);
watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION);
- raiseFatalFailure(watchdog, Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
- mTestLooper.dispatchAll();
+ raiseFatalFailureAndDispatch(watchdog,
+ Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
// The failed packages should be the same as the registered ones to ensure registration is
// done successfully
- assertEquals(1, observer.mHealthCheckFailedPackages.size());
- assertTrue(observer.mHealthCheckFailedPackages.contains(APP_A));
+ assertThat(observer.mHealthCheckFailedPackages).containsExactly(APP_A);
}
@Test
@@ -135,17 +133,14 @@
watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
watchdog.startObservingHealth(observer2, Arrays.asList(APP_A, APP_B), SHORT_DURATION);
- raiseFatalFailure(watchdog, Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE),
- new VersionedPackage(APP_B, VERSION_CODE)));
- mTestLooper.dispatchAll();
+ raiseFatalFailureAndDispatch(watchdog,
+ Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE),
+ new VersionedPackage(APP_B, VERSION_CODE)));
// The failed packages should be the same as the registered ones to ensure registration is
// done successfully
- assertEquals(1, observer1.mHealthCheckFailedPackages.size());
- assertEquals(2, observer2.mHealthCheckFailedPackages.size());
- assertTrue(observer1.mHealthCheckFailedPackages.contains(APP_A));
- assertTrue(observer2.mHealthCheckFailedPackages.contains(APP_A));
- assertTrue(observer2.mHealthCheckFailedPackages.contains(APP_B));
+ assertThat(observer1.mHealthCheckFailedPackages).containsExactly(APP_A);
+ assertThat(observer2.mHealthCheckFailedPackages).containsExactly(APP_A, APP_B);
}
@Test
@@ -155,11 +150,11 @@
watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION);
watchdog.unregisterHealthObserver(observer);
- raiseFatalFailure(watchdog, Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
- mTestLooper.dispatchAll();
+ raiseFatalFailureAndDispatch(watchdog,
+ Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
// We should have no failed packages to ensure unregistration is done successfully
- assertEquals(0, observer.mHealthCheckFailedPackages.size());
+ assertThat(observer.mHealthCheckFailedPackages).isEmpty();
}
@Test
@@ -171,13 +166,13 @@
watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
watchdog.startObservingHealth(observer2, Arrays.asList(APP_A), SHORT_DURATION);
watchdog.unregisterHealthObserver(observer2);
- raiseFatalFailure(watchdog, Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
- mTestLooper.dispatchAll();
+ raiseFatalFailureAndDispatch(watchdog,
+ Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
// observer1 should receive failed packages as intended.
- assertEquals(1, observer1.mHealthCheckFailedPackages.size());
+ assertThat(observer1.mHealthCheckFailedPackages).containsExactly(APP_A);
// observer2 should have no failed packages to ensure unregistration is done successfully
- assertEquals(0, observer2.mHealthCheckFailedPackages.size());
+ assertThat(observer2.mHealthCheckFailedPackages).isEmpty();
}
@Test
@@ -187,11 +182,11 @@
watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION);
moveTimeForwardAndDispatch(SHORT_DURATION);
- raiseFatalFailure(watchdog, Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
- mTestLooper.dispatchAll();
+ raiseFatalFailureAndDispatch(watchdog,
+ Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
// We should have no failed packages for the fatal failure is raised after expiration
- assertEquals(0, observer.mHealthCheckFailedPackages.size());
+ assertThat(observer.mHealthCheckFailedPackages).isEmpty();
}
@Test
@@ -203,13 +198,13 @@
watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
watchdog.startObservingHealth(observer2, Arrays.asList(APP_A), LONG_DURATION);
moveTimeForwardAndDispatch(SHORT_DURATION);
- raiseFatalFailure(watchdog, Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
- mTestLooper.dispatchAll();
+ raiseFatalFailureAndDispatch(watchdog,
+ Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
// We should have no failed packages for the fatal failure is raised after expiration
- assertEquals(0, observer1.mHealthCheckFailedPackages.size());
+ assertThat(observer1.mHealthCheckFailedPackages).isEmpty();
// We should have failed packages since observer2 hasn't expired
- assertEquals(1, observer2.mHealthCheckFailedPackages.size());
+ assertThat(observer2.mHealthCheckFailedPackages).containsExactly(APP_A);
}
/** Observing already observed package extends the observation time. */
@@ -230,12 +225,11 @@
// Then advance time such that it should have expired were it not for the second observation
moveTimeForwardAndDispatch((SHORT_DURATION / 2) + 1);
- raiseFatalFailure(watchdog, Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
- mTestLooper.dispatchAll();
+ raiseFatalFailureAndDispatch(watchdog,
+ Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
// Verify that we receive failed packages as expected for APP_A not expired
- assertEquals(1, observer.mHealthCheckFailedPackages.size());
- assertTrue(observer.mHealthCheckFailedPackages.contains(APP_A));
+ assertThat(observer.mHealthCheckFailedPackages).containsExactly(APP_A);
}
/**
@@ -256,17 +250,14 @@
// Then resume observer1 and observer2
watchdog2.registerHealthObserver(observer1);
watchdog2.registerHealthObserver(observer2);
- raiseFatalFailure(watchdog2, Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE),
- new VersionedPackage(APP_B, VERSION_CODE)));
- mTestLooper.dispatchAll();
+ raiseFatalFailureAndDispatch(watchdog2,
+ Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE),
+ new VersionedPackage(APP_B, VERSION_CODE)));
// We should receive failed packages as expected to ensure observers are persisted and
// resumed correctly
- assertEquals(1, observer1.mHealthCheckFailedPackages.size());
- assertEquals(2, observer2.mHealthCheckFailedPackages.size());
- assertTrue(observer1.mHealthCheckFailedPackages.contains(APP_A));
- assertTrue(observer1.mHealthCheckFailedPackages.contains(APP_A));
- assertTrue(observer2.mHealthCheckFailedPackages.contains(APP_B));
+ assertThat(observer1.mHealthCheckFailedPackages).containsExactly(APP_A);
+ assertThat(observer2.mHealthCheckFailedPackages).containsExactly(APP_A, APP_B);
}
/**
@@ -290,8 +281,8 @@
mTestLooper.dispatchAll();
// Verify that observers are not notified
- assertEquals(0, observer1.mMitigatedPackages.size());
- assertEquals(0, observer2.mMitigatedPackages.size());
+ assertThat(observer1.mHealthCheckFailedPackages).isEmpty();
+ assertThat(observer2.mHealthCheckFailedPackages).isEmpty();
}
/**
@@ -309,14 +300,12 @@
watchdog.startObservingHealth(observer1, Arrays.asList(APP_B), SHORT_DURATION);
// Then fail APP_C (not observed) above the threshold
- raiseFatalFailure(watchdog, Arrays.asList(new VersionedPackage(APP_C, VERSION_CODE)));
-
- // Run handler so package failures are dispatched to observers
- mTestLooper.dispatchAll();
+ raiseFatalFailureAndDispatch(watchdog,
+ Arrays.asList(new VersionedPackage(APP_C, VERSION_CODE)));
// Verify that observers are not notified
- assertEquals(0, observer1.mMitigatedPackages.size());
- assertEquals(0, observer2.mMitigatedPackages.size());
+ assertThat(observer1.mHealthCheckFailedPackages).isEmpty();
+ assertThat(observer2.mHealthCheckFailedPackages).isEmpty();
}
/**
@@ -341,14 +330,11 @@
watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION);
// Then fail APP_A (different version) above the threshold
- raiseFatalFailure(watchdog,
+ raiseFatalFailureAndDispatch(watchdog,
Arrays.asList(new VersionedPackage(APP_A, differentVersionCode)));
- // Run handler so package failures are dispatched to observers
- mTestLooper.dispatchAll();
-
// Verify that observers are not notified
- assertEquals(0, observer.mMitigatedPackages.size());
+ assertThat(observer.mHealthCheckFailedPackages).isEmpty();
}
@@ -378,13 +364,11 @@
SHORT_DURATION);
// Then fail all apps above the threshold
- raiseFatalFailure(watchdog, Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE),
- new VersionedPackage(APP_B, VERSION_CODE),
- new VersionedPackage(APP_C, VERSION_CODE),
- new VersionedPackage(APP_D, VERSION_CODE)));
-
- // Run handler so package failures are dispatched to observers
- mTestLooper.dispatchAll();
+ raiseFatalFailureAndDispatch(watchdog,
+ Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE),
+ new VersionedPackage(APP_B, VERSION_CODE),
+ new VersionedPackage(APP_C, VERSION_CODE),
+ new VersionedPackage(APP_D, VERSION_CODE)));
// Verify least impact observers are notifed of package failures
List<String> observerNonePackages = observerNone.mMitigatedPackages;
@@ -393,16 +377,13 @@
List<String> observerLowPackages = observerLow.mMitigatedPackages;
// APP_D failure observed by only observerNone is not caught cos its impact is none
- assertEquals(0, observerNonePackages.size());
+ assertThat(observerNonePackages).isEmpty();
// APP_C failure is caught by observerHigh cos it's the lowest impact observer
- assertEquals(1, observerHighPackages.size());
- assertEquals(APP_C, observerHighPackages.get(0));
+ assertThat(observerHighPackages).containsExactly(APP_C);
// APP_B failure is caught by observerMid cos it's the lowest impact observer
- assertEquals(1, observerMidPackages.size());
- assertEquals(APP_B, observerMidPackages.get(0));
+ assertThat(observerMidPackages).containsExactly(APP_B);
// APP_A failure is caught by observerLow cos it's the lowest impact observer
- assertEquals(1, observerLowPackages.size());
- assertEquals(APP_A, observerLowPackages.get(0));
+ assertThat(observerLowPackages).containsExactly(APP_A);
}
/**
@@ -429,14 +410,12 @@
watchdog.startObservingHealth(observerSecond, Arrays.asList(APP_A), LONG_DURATION);
// Then fail APP_A above the threshold
- raiseFatalFailure(watchdog, Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
- // Run handler so package failures are dispatched to observers
- mTestLooper.dispatchAll();
+ raiseFatalFailureAndDispatch(watchdog,
+ Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
// Verify only observerFirst is notifed
- assertEquals(1, observerFirst.mMitigatedPackages.size());
- assertEquals(APP_A, observerFirst.mMitigatedPackages.get(0));
- assertEquals(0, observerSecond.mMitigatedPackages.size());
+ assertThat(observerFirst.mMitigatedPackages).containsExactly(APP_A);
+ assertThat(observerSecond.mMitigatedPackages).isEmpty();
// After observerFirst handles failure, next action it has is high impact
observerFirst.mImpact = PackageHealthObserverImpact.USER_IMPACT_HIGH;
@@ -444,14 +423,12 @@
observerSecond.mMitigatedPackages.clear();
// Then fail APP_A again above the threshold
- raiseFatalFailure(watchdog, Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
- // Run handler so package failures are dispatched to observers
- mTestLooper.dispatchAll();
+ raiseFatalFailureAndDispatch(watchdog,
+ Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
// Verify only observerSecond is notifed cos it has least impact
- assertEquals(1, observerSecond.mMitigatedPackages.size());
- assertEquals(APP_A, observerSecond.mMitigatedPackages.get(0));
- assertEquals(0, observerFirst.mMitigatedPackages.size());
+ assertThat(observerSecond.mMitigatedPackages).containsExactly(APP_A);
+ assertThat(observerFirst.mMitigatedPackages).isEmpty();
// After observerSecond handles failure, it has no further actions
observerSecond.mImpact = PackageHealthObserverImpact.USER_IMPACT_NONE;
@@ -459,14 +436,12 @@
observerSecond.mMitigatedPackages.clear();
// Then fail APP_A again above the threshold
- raiseFatalFailure(watchdog, Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
- // Run handler so package failures are dispatched to observers
- mTestLooper.dispatchAll();
+ raiseFatalFailureAndDispatch(watchdog,
+ Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
// Verify only observerFirst is notifed cos it has the only action
- assertEquals(1, observerFirst.mMitigatedPackages.size());
- assertEquals(APP_A, observerFirst.mMitigatedPackages.get(0));
- assertEquals(0, observerSecond.mMitigatedPackages.size());
+ assertThat(observerFirst.mMitigatedPackages).containsExactly(APP_A);
+ assertThat(observerSecond.mMitigatedPackages).isEmpty();
// After observerFirst handles failure, it too has no further actions
observerFirst.mImpact = PackageHealthObserverImpact.USER_IMPACT_NONE;
@@ -474,13 +449,12 @@
observerSecond.mMitigatedPackages.clear();
// Then fail APP_A again above the threshold
- raiseFatalFailure(watchdog, Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
- // Run handler so package failures are dispatched to observers
- mTestLooper.dispatchAll();
+ raiseFatalFailureAndDispatch(watchdog,
+ Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
// Verify no observer is notified cos no actions left
- assertEquals(0, observerFirst.mMitigatedPackages.size());
- assertEquals(0, observerSecond.mMitigatedPackages.size());
+ assertThat(observerFirst.mMitigatedPackages).isEmpty();
+ assertThat(observerSecond.mMitigatedPackages).isEmpty();
}
/**
@@ -499,15 +473,12 @@
watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
// Then fail APP_A above the threshold
- raiseFatalFailure(watchdog, Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
-
- // Run handler so package failures are dispatched to observers
- mTestLooper.dispatchAll();
+ raiseFatalFailureAndDispatch(watchdog,
+ Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
// Verify only one observer is notifed
- assertEquals(1, observer1.mMitigatedPackages.size());
- assertEquals(APP_A, observer1.mMitigatedPackages.get(0));
- assertEquals(0, observer2.mMitigatedPackages.size());
+ assertThat(observer1.mMitigatedPackages).containsExactly(APP_A);
+ assertThat(observer2.mMitigatedPackages).isEmpty();
}
/**
@@ -536,9 +507,7 @@
// Verify we requested health checks for APP_A and APP_B
List<String> requestedPackages = controller.getRequestedPackages();
- assertEquals(2, requestedPackages.size());
- assertEquals(APP_A, requestedPackages.get(0));
- assertEquals(APP_B, requestedPackages.get(1));
+ assertThat(requestedPackages).containsExactly(APP_A, APP_B);
// Then health check passed for APP_A (observer1 is aware)
controller.setPackagePassed(APP_A);
@@ -553,18 +522,16 @@
moveTimeForwardAndDispatch(SHORT_DURATION);
// Verify we cancelled all requests on expiry
- assertEquals(0, controller.getRequestedPackages().size());
+ assertThat(controller.getRequestedPackages()).isEmpty();
// Verify observer1 is not notified
- assertEquals(0, observer1.mMitigatedPackages.size());
+ assertThat(observer1.mMitigatedPackages).isEmpty();
// Verify observer2 is notifed because health checks for APP_B never passed
- assertEquals(1, observer2.mMitigatedPackages.size());
- assertEquals(APP_B, observer2.mMitigatedPackages.get(0));
+ assertThat(observer2.mMitigatedPackages).containsExactly(APP_B);
// Verify observer3 is notifed because health checks for APP_A did not pass before expiry
- assertEquals(1, observer3.mMitigatedPackages.size());
- assertEquals(APP_A, observer3.mMitigatedPackages.get(0));
+ assertThat(observer3.mMitigatedPackages).containsExactly(APP_A);
}
/**
@@ -591,9 +558,7 @@
// Verify we requested health checks for APP_A and APP_B
List<String> requestedPackages = controller.getRequestedPackages();
- assertEquals(2, requestedPackages.size());
- assertEquals(APP_A, requestedPackages.get(0));
- assertEquals(APP_B, requestedPackages.get(1));
+ assertThat(requestedPackages).containsExactly(APP_A, APP_B);
// Disable explicit health checks (marks APP_A and APP_B as passed)
setExplicitHealthCheckEnabled(false);
@@ -602,13 +567,13 @@
mTestLooper.dispatchAll();
// Verify all checks are cancelled
- assertEquals(0, controller.getRequestedPackages().size());
+ assertThat(controller.getRequestedPackages()).isEmpty();
// Then expire APP_A
moveTimeForwardAndDispatch(SHORT_DURATION);
// Verify APP_A is not failed (APP_B) is not expired yet
- assertEquals(0, observer.mMitigatedPackages.size());
+ assertThat(observer.mMitigatedPackages).isEmpty();
// Re-enable explicit health checks
setExplicitHealthCheckEnabled(true);
@@ -617,7 +582,7 @@
mTestLooper.dispatchAll();
// Verify no requests are made cos APP_A is expired and APP_B was marked as passed
- assertEquals(0, controller.getRequestedPackages().size());
+ assertThat(controller.getRequestedPackages()).isEmpty();
// Then set new supported packages
controller.setSupportedPackages(Arrays.asList(APP_C));
@@ -629,15 +594,13 @@
// Verify requests are only made for APP_C
requestedPackages = controller.getRequestedPackages();
- assertEquals(1, requestedPackages.size());
- assertEquals(APP_C, requestedPackages.get(0));
+ assertThat(requestedPackages).containsExactly(APP_C);
// Then expire APP_A and APP_C
moveTimeForwardAndDispatch(SHORT_DURATION);
// Verify only APP_C is failed because explicit health checks was not supported for APP_A
- assertEquals(1, observer.mMitigatedPackages.size());
- assertEquals(APP_C, observer.mMitigatedPackages.get(0));
+ assertThat(observer.mMitigatedPackages).containsExactly(APP_C);
}
/**
@@ -661,8 +624,7 @@
moveTimeForwardAndDispatch(SHORT_DURATION);
// Verify that health check is failed
- assertEquals(1, observer.mMitigatedPackages.size());
- assertEquals(APP_A, observer.mMitigatedPackages.get(0));
+ assertThat(observer.mMitigatedPackages).containsExactly(APP_A);
// Then clear failed packages and start observing a random package so requests are synced
// and PackageWatchdog#onSupportedPackages is called and APP_A has a chance to fail again
@@ -671,7 +633,7 @@
watchdog.startObservingHealth(observer, Arrays.asList(APP_B), LONG_DURATION);
// Verify that health check failure is not notified again
- assertTrue(observer.mMitigatedPackages.isEmpty());
+ assertThat(observer.mMitigatedPackages).isEmpty();
}
/** Tests {@link MonitoredPackage} health check state transitions. */
@@ -687,36 +649,38 @@
// Verify transition: inactive -> active -> passed
// Verify initially inactive
- assertEquals(MonitoredPackage.STATE_INACTIVE, m1.getHealthCheckStateLocked());
+ assertThat(m1.getHealthCheckStateLocked()).isEqualTo(HealthCheckState.INACTIVE);
// Verify still inactive, until we #setHealthCheckActiveLocked
- assertEquals(MonitoredPackage.STATE_INACTIVE, m1.handleElapsedTimeLocked(SHORT_DURATION));
+ assertThat(m1.handleElapsedTimeLocked(SHORT_DURATION)).isEqualTo(HealthCheckState.INACTIVE);
// Verify now active
- assertEquals(MonitoredPackage.STATE_ACTIVE, m1.setHealthCheckActiveLocked(SHORT_DURATION));
+ assertThat(m1.setHealthCheckActiveLocked(SHORT_DURATION)).isEqualTo(
+ HealthCheckState.ACTIVE);
// Verify now passed
- assertEquals(MonitoredPackage.STATE_PASSED, m1.tryPassHealthCheckLocked());
+ assertThat(m1.tryPassHealthCheckLocked()).isEqualTo(HealthCheckState.PASSED);
// Verify transition: inactive -> active -> failed
// Verify initially inactive
- assertEquals(MonitoredPackage.STATE_INACTIVE, m2.getHealthCheckStateLocked());
+ assertThat(m2.getHealthCheckStateLocked()).isEqualTo(HealthCheckState.INACTIVE);
// Verify now active
- assertEquals(MonitoredPackage.STATE_ACTIVE, m2.setHealthCheckActiveLocked(SHORT_DURATION));
+ assertThat(m2.setHealthCheckActiveLocked(SHORT_DURATION)).isEqualTo(
+ HealthCheckState.ACTIVE);
// Verify now failed
- assertEquals(MonitoredPackage.STATE_FAILED, m2.handleElapsedTimeLocked(SHORT_DURATION));
+ assertThat(m2.handleElapsedTimeLocked(SHORT_DURATION)).isEqualTo(HealthCheckState.FAILED);
// Verify transition: inactive -> failed
// Verify initially inactive
- assertEquals(MonitoredPackage.STATE_INACTIVE, m3.getHealthCheckStateLocked());
+ assertThat(m3.getHealthCheckStateLocked()).isEqualTo(HealthCheckState.INACTIVE);
// Verify now failed because package expired
- assertEquals(MonitoredPackage.STATE_FAILED, m3.handleElapsedTimeLocked(LONG_DURATION));
+ assertThat(m3.handleElapsedTimeLocked(LONG_DURATION)).isEqualTo(HealthCheckState.FAILED);
// Verify remains failed even when asked to pass
- assertEquals(MonitoredPackage.STATE_FAILED, m3.tryPassHealthCheckLocked());
+ assertThat(m3.tryPassHealthCheckLocked()).isEqualTo(HealthCheckState.FAILED);
// Verify transition: passed
- assertEquals(MonitoredPackage.STATE_PASSED, m4.getHealthCheckStateLocked());
+ assertThat(m4.getHealthCheckStateLocked()).isEqualTo(HealthCheckState.PASSED);
// Verify remains passed even if health check fails
- assertEquals(MonitoredPackage.STATE_PASSED, m4.handleElapsedTimeLocked(SHORT_DURATION));
+ assertThat(m4.handleElapsedTimeLocked(SHORT_DURATION)).isEqualTo(HealthCheckState.PASSED);
// Verify remains passed even if package expires
- assertEquals(MonitoredPackage.STATE_PASSED, m4.handleElapsedTimeLocked(LONG_DURATION));
+ assertThat(m4.handleElapsedTimeLocked(LONG_DURATION)).isEqualTo(HealthCheckState.PASSED);
}
@Test
@@ -735,8 +699,7 @@
mTestLooper.dispatchAll();
// Verify the NetworkStack observer is notified
- assertEquals(1, observer.mMitigatedPackages.size());
- assertEquals(APP_A, observer.mMitigatedPackages.get(0));
+ assertThat(observer.mMitigatedPackages).containsExactly(APP_A);
}
private void adoptShellPermissions(String... permissions) {
@@ -772,10 +735,12 @@
}
/** Trigger package failures above the threshold. */
- private void raiseFatalFailure(PackageWatchdog watchdog, List<VersionedPackage> packages) {
+ private void raiseFatalFailureAndDispatch(PackageWatchdog watchdog,
+ List<VersionedPackage> packages) {
for (int i = 0; i < watchdog.getTriggerFailureCount(); i++) {
watchdog.onPackageFailure(packages);
}
+ mTestLooper.dispatchAll();
}
private PackageWatchdog createWatchdog() {
@@ -790,13 +755,13 @@
new PackageWatchdog(mSpyContext, policyFile, handler, handler, controller,
mConnectivityModuleConnector, mTestClock);
// Verify controller is not automatically started
- assertFalse(controller.mIsEnabled);
+ assertThat(controller.mIsEnabled).isFalse();
if (withPackagesReady) {
// Only capture the NetworkStack callback for the latest registered watchdog
reset(mConnectivityModuleConnector);
watchdog.onPackagesReady();
// Verify controller by default is started when packages are ready
- assertTrue(controller.mIsEnabled);
+ assertThat(controller.mIsEnabled).isTrue();
verify(mConnectivityModuleConnector).registerHealthListener(
mConnectivityModuleCallbackCaptor.capture());
diff --git a/tests/RollbackTest/Android.bp b/tests/RollbackTest/Android.bp
index 2bd5931..231d045b 100644
--- a/tests/RollbackTest/Android.bp
+++ b/tests/RollbackTest/Android.bp
@@ -31,9 +31,9 @@
}
java_test_host {
- name: "SecondaryUserRollbackTest",
- srcs: ["SecondaryUserRollbackTest/src/**/*.java"],
+ name: "MultiUserRollbackTest",
+ srcs: ["MultiUserRollbackTest/src/**/*.java"],
libs: ["tradefed"],
test_suites: ["general-tests"],
- test_config: "SecondaryUserRollbackTest.xml",
+ test_config: "MultiUserRollbackTest.xml",
}
diff --git a/tests/RollbackTest/SecondaryUserRollbackTest.xml b/tests/RollbackTest/MultiUserRollbackTest.xml
similarity index 67%
rename from tests/RollbackTest/SecondaryUserRollbackTest.xml
rename to tests/RollbackTest/MultiUserRollbackTest.xml
index 6b3f05c..41cec46 100644
--- a/tests/RollbackTest/SecondaryUserRollbackTest.xml
+++ b/tests/RollbackTest/MultiUserRollbackTest.xml
@@ -13,17 +13,12 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<configuration description="Runs the rollback test from a secondary user">
- <option name="test-suite-tag" value="SecondaryUserRollbackTest" />
- <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
- <option name="cleanup-apks" value="true" />
- <option name="test-file-name" value="RollbackTest.apk" />
- </target_preparer>
+<configuration description="Runs rollback tests for multiple users">
+ <option name="test-suite-tag" value="MultiUserRollbackTest" />
<target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
<option name="run-command" value="pm uninstall com.android.cts.install.lib.testapp.A" />
- <option name="run-command" value="pm uninstall com.android.cts.install.lib.testapp.B" />
</target_preparer>
<test class="com.android.tradefed.testtype.HostTest" >
- <option name="class" value="com.android.tests.rollback.host.SecondaryUserRollbackTest" />
+ <option name="class" value="com.android.tests.rollback.host.MultiUserRollbackTest" />
</test>
</configuration>
diff --git a/tests/RollbackTest/MultiUserRollbackTest/src/com/android/tests/rollback/host/MultiUserRollbackTest.java b/tests/RollbackTest/MultiUserRollbackTest/src/com/android/tests/rollback/host/MultiUserRollbackTest.java
new file mode 100644
index 0000000..52f6eba
--- /dev/null
+++ b/tests/RollbackTest/MultiUserRollbackTest/src/com/android/tests/rollback/host/MultiUserRollbackTest.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tests.rollback.host;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Runs rollback tests for multiple users.
+ */
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class MultiUserRollbackTest extends BaseHostJUnit4Test {
+ // The user that was running originally when the test starts.
+ private int mOriginalUserId;
+ private int mSecondaryUserId = -1;
+ private static final long SWITCH_USER_COMPLETED_NUMBER_OF_POLLS = 60;
+ private static final long SWITCH_USER_COMPLETED_POLL_INTERVAL_IN_MILLIS = 1000;
+
+
+ @After
+ public void tearDown() throws Exception {
+ getDevice().switchUser(mOriginalUserId);
+ getDevice().executeShellCommand("pm uninstall com.android.cts.install.lib.testapp.A");
+ removeSecondaryUserIfNecessary();
+ }
+
+ @Before
+ public void setup() throws Exception {
+ mOriginalUserId = getDevice().getCurrentUser();
+ installPackageAsUser("RollbackTest.apk", true, mOriginalUserId);
+ createAndSwitchToSecondaryUserIfNecessary();
+ installPackageAsUser("RollbackTest.apk", true, mSecondaryUserId);
+ }
+
+ @Test
+ public void testBasicForSecondaryUser() throws Exception {
+ runPhaseForUsers("testBasic", mSecondaryUserId);
+ }
+
+ @Test
+ public void testMultipleUsers() throws Exception {
+ runPhaseForUsers("testMultipleUsersInstallV1", mOriginalUserId, mSecondaryUserId);
+ runPhaseForUsers("testMultipleUsersUpgradeToV2", mOriginalUserId);
+ runPhaseForUsers("testMultipleUsersUpdateUserData", mOriginalUserId, mSecondaryUserId);
+ switchToUser(mOriginalUserId);
+ getDevice().executeShellCommand("pm rollback-app com.android.cts.install.lib.testapp.A");
+ runPhaseForUsers("testMultipleUsersVerifyUserdataRollback", mOriginalUserId,
+ mSecondaryUserId);
+ }
+
+ /**
+ * Run the phase for the given user ids, in the order they are given.
+ */
+ private void runPhaseForUsers(String phase, int... userIds) throws Exception {
+ for (int userId: userIds) {
+ switchToUser(userId);
+ assertTrue(runDeviceTests("com.android.tests.rollback",
+ "com.android.tests.rollback.MultiUserRollbackTest",
+ phase));
+ }
+ }
+
+ private void removeSecondaryUserIfNecessary() throws Exception {
+ if (mSecondaryUserId != -1) {
+ getDevice().removeUser(mSecondaryUserId);
+ mSecondaryUserId = -1;
+ }
+ }
+
+ private void createAndSwitchToSecondaryUserIfNecessary() throws Exception {
+ if (mSecondaryUserId == -1) {
+ mOriginalUserId = getDevice().getCurrentUser();
+ mSecondaryUserId = getDevice().createUser("MultiUserRollbackTest_User"
+ + System.currentTimeMillis());
+ switchToUser(mSecondaryUserId);
+ }
+ }
+
+ private void switchToUser(int userId) throws Exception {
+ if (getDevice().getCurrentUser() == userId) {
+ return;
+ }
+
+ assertTrue(getDevice().switchUser(userId));
+ for (int i = 0; i < SWITCH_USER_COMPLETED_NUMBER_OF_POLLS; ++i) {
+ String userState = getDevice().executeShellCommand("am get-started-user-state "
+ + userId);
+ if (userState.contains("RUNNING_UNLOCKED")) {
+ return;
+ }
+ Thread.sleep(SWITCH_USER_COMPLETED_POLL_INTERVAL_IN_MILLIS);
+ }
+ fail("User switch to user " + userId + " timed out");
+ }
+}
diff --git a/tests/RollbackTest/RollbackTest.xml b/tests/RollbackTest/RollbackTest.xml
index 70cd867..a14b01c 100644
--- a/tests/RollbackTest/RollbackTest.xml
+++ b/tests/RollbackTest/RollbackTest.xml
@@ -22,8 +22,9 @@
<option name="package" value="com.android.tests.rollback" />
<option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
- <!-- Exclude the StagedRollbackTest tests, which needs to be specially
- driven from the StagedRollbackTest host test -->
+ <!-- Exclude the StagedRollbackTest and MultiUserRollbackTest tests, which need to be
+ specially driven from the StagedRollbackTest and MultiUserRollbackTest host test -->
<option name="exclude-filter" value="com.android.tests.rollback.StagedRollbackTest" />
+ <option name="exclude-filter" value="com.android.tests.rollback.MultiUserRollbackTest" />
</test>
</configuration>
diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/MultiUserRollbackTest.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/MultiUserRollbackTest.java
new file mode 100644
index 0000000..0ffe041
--- /dev/null
+++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/MultiUserRollbackTest.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tests.rollback;
+
+import static com.android.cts.rollback.lib.RollbackInfoSubject.assertThat;
+import static com.android.cts.rollback.lib.RollbackUtils.getUniqueRollbackInfoForPackage;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.Manifest;
+import android.content.rollback.RollbackInfo;
+import android.content.rollback.RollbackManager;
+
+import com.android.cts.install.lib.Install;
+import com.android.cts.install.lib.InstallUtils;
+import com.android.cts.install.lib.TestApp;
+import com.android.cts.rollback.lib.Rollback;
+import com.android.cts.rollback.lib.RollbackUtils;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+
+@RunWith(JUnit4.class)
+public class MultiUserRollbackTest {
+
+ @Before
+ public void adoptShellPermissions() {
+ InstallUtils.adoptShellPermissionIdentity(
+ Manifest.permission.INSTALL_PACKAGES,
+ Manifest.permission.DELETE_PACKAGES,
+ Manifest.permission.TEST_MANAGE_ROLLBACKS,
+ Manifest.permission.MANAGE_ROLLBACKS);
+ }
+
+ @After
+ public void dropShellPermissions() {
+ InstallUtils.dropShellPermissionIdentity();
+ }
+
+ @Test
+ public void testBasic() throws Exception {
+ new RollbackTest().testBasic();
+ }
+
+ /**
+ * Install version 1 of the test app. This method is run for both users.
+ */
+ @Test
+ public void testMultipleUsersInstallV1() throws Exception {
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(-1);
+ Install.single(TestApp.A1).commit();
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
+ InstallUtils.processUserData(TestApp.A);
+ }
+
+ /**
+ * Upgrade the test app to version 2. This method should only run once as the system user,
+ * and will update the app for both users.
+ */
+ @Test
+ public void testMultipleUsersUpgradeToV2() throws Exception {
+ RollbackManager rm = RollbackUtils.getRollbackManager();
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
+ Install.single(TestApp.A2).setEnableRollback().commit();
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
+ RollbackInfo rollback = getUniqueRollbackInfoForPackage(
+ rm.getAvailableRollbacks(), TestApp.A);
+ assertThat(rollback).isNotNull();
+ assertThat(rollback).packagesContainsExactly(
+ Rollback.from(TestApp.A2).to(TestApp.A1));
+ }
+
+ /**
+ * This method is run for both users. Assert that the test app has upgraded for both users, and
+ * update their userdata to reflect this new version.
+ */
+ @Test
+ public void testMultipleUsersUpdateUserData() {
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
+ InstallUtils.processUserData(TestApp.A);
+ }
+
+ /**
+ * The system will have rolled back the test app at this stage. Verify that the rollback has
+ * taken place, and that the userdata has been correctly rolled back. This method is run for
+ * both users.
+ */
+ @Test
+ public void testMultipleUsersVerifyUserdataRollback() {
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
+ InstallUtils.processUserData(TestApp.A);
+ }
+}
diff --git a/tests/RollbackTest/SecondaryUserRollbackTest/src/com/android/tests/rollback/host/SecondaryUserRollbackTest.java b/tests/RollbackTest/SecondaryUserRollbackTest/src/com/android/tests/rollback/host/SecondaryUserRollbackTest.java
deleted file mode 100644
index 11a0fbb..0000000
--- a/tests/RollbackTest/SecondaryUserRollbackTest/src/com/android/tests/rollback/host/SecondaryUserRollbackTest.java
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tests.rollback.host;
-
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
-import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/**
- * Runs rollback tests from a secondary user.
- */
-@RunWith(DeviceJUnit4ClassRunner.class)
-public class SecondaryUserRollbackTest extends BaseHostJUnit4Test {
- private static final int SYSTEM_USER_ID = 0;
- // The user that was running originally when the test starts.
- private int mOriginalUser = SYSTEM_USER_ID;
- private int mSecondaryUserId = -1;
- private static final long SWITCH_USER_COMPLETED_NUMBER_OF_POLLS = 60;
- private static final long SWITCH_USER_COMPLETED_POLL_INTERVAL_IN_MILLIS = 1000;
-
-
- @After
- public void tearDown() throws Exception {
- getDevice().switchUser(mOriginalUser);
- getDevice().executeShellCommand("pm uninstall com.android.cts.install.lib.testapp.A");
- getDevice().executeShellCommand("pm uninstall com.android.cts.install.lib.testapp.B");
- removeSecondaryUserIfNecessary();
- }
-
- @Before
- public void setup() throws Exception {
- createAndSwitchToSecondaryUserIfNecessary();
- installPackageAsUser("RollbackTest.apk", true, mSecondaryUserId, "--user current");
- }
-
- @Test
- public void testBasic() throws Exception {
- assertTrue(runDeviceTests("com.android.tests.rollback",
- "com.android.tests.rollback.RollbackTest",
- "testBasic"));
- }
-
- private void removeSecondaryUserIfNecessary() throws Exception {
- if (mSecondaryUserId != -1) {
- getDevice().removeUser(mSecondaryUserId);
- mSecondaryUserId = -1;
- }
- }
-
- private void createAndSwitchToSecondaryUserIfNecessary() throws Exception {
- if (mSecondaryUserId == -1) {
- mOriginalUser = getDevice().getCurrentUser();
- mSecondaryUserId = getDevice().createUser("SecondaryUserRollbackTest_User");
- assertTrue(getDevice().switchUser(mSecondaryUserId));
- // give time for user to be switched
- waitForSwitchUserCompleted(mSecondaryUserId);
- }
- }
-
- private void waitForSwitchUserCompleted(int userId) throws Exception {
- for (int i = 0; i < SWITCH_USER_COMPLETED_NUMBER_OF_POLLS; ++i) {
- String logs = getDevice().executeAdbCommand("logcat", "-v", "brief", "-d",
- "ActivityManager:D");
- if (logs.contains("Posting BOOT_COMPLETED user #" + userId)) {
- return;
- }
- Thread.sleep(SWITCH_USER_COMPLETED_POLL_INTERVAL_IN_MILLIS);
- }
- fail("User switch to user " + userId + " timed out");
- }
-}
diff --git a/tests/RollbackTest/TEST_MAPPING b/tests/RollbackTest/TEST_MAPPING
index 7ae03e6..fefde5b 100644
--- a/tests/RollbackTest/TEST_MAPPING
+++ b/tests/RollbackTest/TEST_MAPPING
@@ -7,7 +7,7 @@
"name": "StagedRollbackTest"
},
{
- "name": "SecondaryUserRollbackTest"
+ "name": "MultiUserRollbackTest"
}
]
}
diff --git a/tests/net/Android.bp b/tests/net/Android.bp
index 502aa97..e91abb6 100644
--- a/tests/net/Android.bp
+++ b/tests/net/Android.bp
@@ -20,8 +20,6 @@
"libdl_android",
"libhidl-gen-utils",
"libhidlbase",
- "libhidltransport",
- "libhwbinder",
"libjsoncpp",
"liblog",
"liblzma",
diff --git a/tools/protologtool/Android.bp b/tools/protologtool/Android.bp
new file mode 100644
index 0000000..a86c226
--- /dev/null
+++ b/tools/protologtool/Android.bp
@@ -0,0 +1,28 @@
+java_binary_host {
+ name: "protologtool",
+ manifest: "manifest.txt",
+ srcs: [
+ "src/**/*.kt",
+ ],
+ static_libs: [
+ "javaparser",
+ "windowmanager-log-proto",
+ "jsonlib",
+ ],
+}
+
+java_test_host {
+ name: "protologtool-tests",
+ test_suites: ["general-tests"],
+ srcs: [
+ "src/**/*.kt",
+ "tests/**/*.kt",
+ ],
+ static_libs: [
+ "javaparser",
+ "windowmanager-log-proto",
+ "jsonlib",
+ "junit",
+ "mockito",
+ ],
+}
diff --git a/tools/protologtool/README.md b/tools/protologtool/README.md
new file mode 100644
index 0000000..3439357
--- /dev/null
+++ b/tools/protologtool/README.md
@@ -0,0 +1,106 @@
+# ProtoLogTool
+
+Code transformation tool and viewer for ProtoLog.
+
+## What does it do?
+
+ProtoLogTool incorporates three different modes of operation:
+
+### Code transformation
+
+Command: `process <protolog class path> <protolog implementation class path>
+ <protolog groups class path> <config.jar> [<input.java>] <output.srcjar>`
+
+In this mode ProtoLogTool transforms every ProtoLog logging call in form of:
+```java
+ProtoLog.x(ProtoLogGroup.GROUP_NAME, "Format string %d %s", value1, value2);
+```
+into:
+```java
+if (GROUP_NAME.isLogToAny()) {
+ ProtoLogImpl.x(ProtoLogGroup.GROUP_NAME, 123456, "Format string %d %s or null", value1, value2);
+}
+```
+where `ProtoLog`, `ProtoLogImpl` and `ProtoLogGroup` are the classes provided as arguments
+ (can be imported, static imported or full path, wildcard imports are not allowed) and, `x` is the
+ logging method. The transformation is done on the source level. A hash is generated from the format
+ string and log level and inserted after the `ProtoLogGroup` argument. The format string is replaced
+ by `null` if `ProtoLogGroup.GROUP_NAME.isLogToLogcat()` returns false. If `ProtoLogGroup.GROUP_NAME.isEnabled()`
+ returns false the log statement is removed entirely from the resultant code.
+
+Input is provided as a list of java source file names. Transformed source is saved to a single
+source jar file. The ProtoLogGroup class with all dependencies should be provided as a compiled
+jar file (config.jar).
+
+### Viewer config generation
+
+Command: `viewerconf <protolog class path> <protolog implementation class path
+<protolog groups class path> <config.jar> [<input.java>] <output.json>`
+
+This command is similar in it's syntax to the previous one, only instead of creating a processed source jar
+it writes a viewer configuration file with following schema:
+```json
+{
+ "version": "1.0.0",
+ "messages": {
+ "123456": {
+ "message": "Format string %d %s",
+ "level": "ERROR",
+ "group": "GROUP_NAME"
+ },
+ },
+ "groups": {
+ "GROUP_NAME": {
+ "tag": "TestLog"
+ }
+ }
+}
+
+```
+
+### Binary log viewing
+
+Command: `read <viewer.json> <wm_log.pb>`
+
+Reads the binary ProtoLog log file and outputs a human-readable LogCat-like text log.
+
+## What is ProtoLog?
+
+ProtoLog is a logging system created for the WindowManager project. It allows both binary and text logging
+and is tunable in runtime. It consists of 3 different submodules:
+* logging system built-in the Android app,
+* log viewer for reading binary logs,
+* a code processing tool.
+
+ProtoLog is designed to reduce both application size (and by that memory usage) and amount of resources needed
+for logging. This is achieved by replacing log message strings with their hashes and only loading to memory/writing
+full log messages when necessary.
+
+### Text logging
+
+For text-based logs Android LogCat is used as a backend. Message strings are loaded from a viewer config
+located on the device when needed.
+
+### Binary logging
+
+Binary logs are saved as Protocol Buffers file. They can be read using the ProtoLog tool or specialised
+viewer like Winscope.
+
+## How to use ProtoLog?
+
+### Adding a new logging group or log statement
+
+To add a new ProtoLogGroup simple create a new enum ProtoLogGroup member with desired parameters.
+
+To add a new logging statement just add a new call to ProtoLog.x where x is a log level.
+
+After doing any changes to logging groups or statements you should run `make update-protolog` to update
+viewer configuration saved in the code repository.
+
+## How to change settings on device in runtime?
+Use the `adb shell su root cmd window logging` command. To get help just type
+`adb shell su root cmd window logging help`.
+
+
+
+
diff --git a/tools/protologtool/TEST_MAPPING b/tools/protologtool/TEST_MAPPING
new file mode 100644
index 0000000..52b12dc
--- /dev/null
+++ b/tools/protologtool/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "presubmit": [
+ {
+ "name": "protologtool-tests"
+ }
+ ]
+}
diff --git a/tools/protologtool/manifest.txt b/tools/protologtool/manifest.txt
new file mode 100644
index 0000000..f5e53c4
--- /dev/null
+++ b/tools/protologtool/manifest.txt
@@ -0,0 +1 @@
+Main-class: com.android.protologtool.ProtoLogTool
diff --git a/tools/protologtool/src/com/android/protologtool/CodeUtils.kt b/tools/protologtool/src/com/android/protologtool/CodeUtils.kt
new file mode 100644
index 0000000..facca62
--- /dev/null
+++ b/tools/protologtool/src/com/android/protologtool/CodeUtils.kt
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.protologtool
+
+import com.github.javaparser.StaticJavaParser
+import com.github.javaparser.ast.CompilationUnit
+import com.github.javaparser.ast.ImportDeclaration
+import com.github.javaparser.ast.NodeList
+import com.github.javaparser.ast.expr.BinaryExpr
+import com.github.javaparser.ast.expr.Expression
+import com.github.javaparser.ast.expr.MethodCallExpr
+import com.github.javaparser.ast.expr.SimpleName
+import com.github.javaparser.ast.expr.StringLiteralExpr
+import com.github.javaparser.ast.expr.TypeExpr
+import com.github.javaparser.ast.type.PrimitiveType
+import com.github.javaparser.ast.type.Type
+
+object CodeUtils {
+ /**
+ * Returns a stable hash of a string.
+ * We reimplement String::hashCode() for readability reasons.
+ */
+ fun hash(str: String, level: LogLevel): Int {
+ return (level.name + str).map { c -> c.toInt() }.reduce { h, c -> h * 31 + c }
+ }
+
+ fun isWildcardStaticImported(code: CompilationUnit, className: String): Boolean {
+ return code.findAll(ImportDeclaration::class.java)
+ .any { im -> im.isStatic && im.isAsterisk && im.name.toString() == className }
+ }
+
+ fun isClassImportedOrSamePackage(code: CompilationUnit, className: String): Boolean {
+ val packageName = className.substringBeforeLast('.')
+ return code.packageDeclaration.isPresent &&
+ code.packageDeclaration.get().nameAsString == packageName ||
+ code.findAll(ImportDeclaration::class.java)
+ .any { im ->
+ !im.isStatic &&
+ ((!im.isAsterisk && im.name.toString() == className) ||
+ (im.isAsterisk && im.name.toString() == packageName))
+ }
+ }
+
+ fun staticallyImportedMethods(code: CompilationUnit, className: String): Set<String> {
+ return code.findAll(ImportDeclaration::class.java)
+ .filter { im ->
+ im.isStatic &&
+ im.name.toString().substringBeforeLast('.') == className
+ }
+ .map { im -> im.name.toString().substringAfterLast('.') }.toSet()
+ }
+
+ fun concatMultilineString(expr: Expression): String {
+ return when (expr) {
+ is StringLiteralExpr -> expr.asString()
+ is BinaryExpr -> when {
+ expr.operator == BinaryExpr.Operator.PLUS ->
+ concatMultilineString(expr.left) + concatMultilineString(expr.right)
+ else -> throw InvalidProtoLogCallException(
+ "messageString must be a string literal " +
+ "or concatenation of string literals.", expr)
+ }
+ else -> throw InvalidProtoLogCallException("messageString must be a string literal " +
+ "or concatenation of string literals.", expr)
+ }
+ }
+
+ enum class LogDataTypes(
+ val type: Type,
+ val toType: (Expression) -> Expression = { expr -> expr }
+ ) {
+ // When adding new LogDataType make sure to update {@code logDataTypesToBitMask} accordingly
+ STRING(StaticJavaParser.parseClassOrInterfaceType("String"),
+ { expr ->
+ MethodCallExpr(TypeExpr(StaticJavaParser.parseClassOrInterfaceType("String")),
+ SimpleName("valueOf"), NodeList(expr))
+ }),
+ LONG(PrimitiveType.longType()),
+ DOUBLE(PrimitiveType.doubleType()),
+ BOOLEAN(PrimitiveType.booleanType());
+ }
+
+ fun parseFormatString(messageString: String): List<LogDataTypes> {
+ val types = mutableListOf<LogDataTypes>()
+ var i = 0
+ while (i < messageString.length) {
+ if (messageString[i] == '%') {
+ if (i + 1 >= messageString.length) {
+ throw InvalidFormatStringException("Invalid format string in config")
+ }
+ when (messageString[i + 1]) {
+ 'b' -> types.add(CodeUtils.LogDataTypes.BOOLEAN)
+ 'd', 'o', 'x' -> types.add(CodeUtils.LogDataTypes.LONG)
+ 'f', 'e', 'g' -> types.add(CodeUtils.LogDataTypes.DOUBLE)
+ 's' -> types.add(CodeUtils.LogDataTypes.STRING)
+ '%' -> {
+ }
+ else -> throw InvalidFormatStringException("Invalid format string field" +
+ " %${messageString[i + 1]}")
+ }
+ i += 2
+ } else {
+ i += 1
+ }
+ }
+ return types
+ }
+
+ fun logDataTypesToBitMask(types: List<LogDataTypes>): Int {
+ if (types.size > 16) {
+ throw InvalidFormatStringException("Too many log call parameters " +
+ "- max 16 parameters supported")
+ }
+ var mask = 0
+ types.forEachIndexed { idx, type ->
+ val x = LogDataTypes.values().indexOf(type)
+ mask = mask or (x shl (idx * 2))
+ }
+ return mask
+ }
+}
diff --git a/tools/protologtool/src/com/android/protologtool/CommandOptions.kt b/tools/protologtool/src/com/android/protologtool/CommandOptions.kt
new file mode 100644
index 0000000..df49e15
--- /dev/null
+++ b/tools/protologtool/src/com/android/protologtool/CommandOptions.kt
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.protologtool
+
+import java.util.regex.Pattern
+
+class CommandOptions(args: Array<String>) {
+ companion object {
+ const val TRANSFORM_CALLS_CMD = "transform-protolog-calls"
+ const val GENERATE_CONFIG_CMD = "generate-viewer-config"
+ const val READ_LOG_CMD = "read-log"
+ private val commands = setOf(TRANSFORM_CALLS_CMD, GENERATE_CONFIG_CMD, READ_LOG_CMD)
+
+ private const val PROTOLOG_CLASS_PARAM = "--protolog-class"
+ private const val PROTOLOGIMPL_CLASS_PARAM = "--protolog-impl-class"
+ private const val PROTOLOGGROUP_CLASS_PARAM = "--loggroups-class"
+ private const val PROTOLOGGROUP_JAR_PARAM = "--loggroups-jar"
+ private const val VIEWER_CONFIG_JSON_PARAM = "--viewer-conf"
+ private const val OUTPUT_SOURCE_JAR_PARAM = "--output-srcjar"
+ private val parameters = setOf(PROTOLOG_CLASS_PARAM, PROTOLOGIMPL_CLASS_PARAM,
+ PROTOLOGGROUP_CLASS_PARAM, PROTOLOGGROUP_JAR_PARAM, VIEWER_CONFIG_JSON_PARAM,
+ OUTPUT_SOURCE_JAR_PARAM)
+
+ val USAGE = """
+ Usage: ${Constants.NAME} <command> [<args>]
+ Available commands:
+
+ $TRANSFORM_CALLS_CMD $PROTOLOG_CLASS_PARAM <class name> $PROTOLOGIMPL_CLASS_PARAM
+ <class name> $PROTOLOGGROUP_CLASS_PARAM <class name> $PROTOLOGGROUP_JAR_PARAM
+ <config.jar> $OUTPUT_SOURCE_JAR_PARAM <output.srcjar> [<input.java>]
+ - processes java files replacing stub calls with logging code.
+
+ $GENERATE_CONFIG_CMD $PROTOLOG_CLASS_PARAM <class name> $PROTOLOGGROUP_CLASS_PARAM
+ <class name> $PROTOLOGGROUP_JAR_PARAM <config.jar> $VIEWER_CONFIG_JSON_PARAM
+ <viewer.json> [<input.java>]
+ - creates viewer config file from given java files.
+
+ $READ_LOG_CMD $VIEWER_CONFIG_JSON_PARAM <viewer.json> <wm_log.pb>
+ - translates a binary log to a readable format.
+ """.trimIndent()
+
+ private fun validateClassName(name: String): String {
+ if (!Pattern.matches("^([a-z]+[A-Za-z0-9]*\\.)+([A-Za-z0-9]+)$", name)) {
+ throw InvalidCommandException("Invalid class name $name")
+ }
+ return name
+ }
+
+ private fun getParam(paramName: String, params: Map<String, String>): String {
+ if (!params.containsKey(paramName)) {
+ throw InvalidCommandException("Param $paramName required")
+ }
+ return params.getValue(paramName)
+ }
+
+ private fun validateNotSpecified(paramName: String, params: Map<String, String>): String {
+ if (params.containsKey(paramName)) {
+ throw InvalidCommandException("Unsupported param $paramName")
+ }
+ return ""
+ }
+
+ private fun validateJarName(name: String): String {
+ if (!name.endsWith(".jar")) {
+ throw InvalidCommandException("Jar file required, got $name instead")
+ }
+ return name
+ }
+
+ private fun validateSrcJarName(name: String): String {
+ if (!name.endsWith(".srcjar")) {
+ throw InvalidCommandException("Source jar file required, got $name instead")
+ }
+ return name
+ }
+
+ private fun validateJSONName(name: String): String {
+ if (!name.endsWith(".json")) {
+ throw InvalidCommandException("Json file required, got $name instead")
+ }
+ return name
+ }
+
+ private fun validateJavaInputList(list: List<String>): List<String> {
+ if (list.isEmpty()) {
+ throw InvalidCommandException("No java source input files")
+ }
+ list.forEach { name ->
+ if (!name.endsWith(".java")) {
+ throw InvalidCommandException("Not a java source file $name")
+ }
+ }
+ return list
+ }
+
+ private fun validateLogInputList(list: List<String>): String {
+ if (list.isEmpty()) {
+ throw InvalidCommandException("No log input file")
+ }
+ if (list.size > 1) {
+ throw InvalidCommandException("Only one log input file allowed")
+ }
+ return list[0]
+ }
+ }
+
+ val protoLogClassNameArg: String
+ val protoLogGroupsClassNameArg: String
+ val protoLogImplClassNameArg: String
+ val protoLogGroupsJarArg: String
+ val viewerConfigJsonArg: String
+ val outputSourceJarArg: String
+ val logProtofileArg: String
+ val javaSourceArgs: List<String>
+ val command: String
+
+ init {
+ if (args.isEmpty()) {
+ throw InvalidCommandException("No command specified.")
+ }
+ command = args[0]
+ if (command !in commands) {
+ throw InvalidCommandException("Unknown command.")
+ }
+
+ val params: MutableMap<String, String> = mutableMapOf()
+ val inputFiles: MutableList<String> = mutableListOf()
+
+ var idx = 1
+ while (idx < args.size) {
+ if (args[idx].startsWith("--")) {
+ if (idx + 1 >= args.size) {
+ throw InvalidCommandException("No value for ${args[idx]}")
+ }
+ if (args[idx] !in parameters) {
+ throw InvalidCommandException("Unknown parameter ${args[idx]}")
+ }
+ if (args[idx + 1].startsWith("--")) {
+ throw InvalidCommandException("No value for ${args[idx]}")
+ }
+ if (params.containsKey(args[idx])) {
+ throw InvalidCommandException("Duplicated parameter ${args[idx]}")
+ }
+ params[args[idx]] = args[idx + 1]
+ idx += 2
+ } else {
+ inputFiles.add(args[idx])
+ idx += 1
+ }
+ }
+
+ when (command) {
+ TRANSFORM_CALLS_CMD -> {
+ protoLogClassNameArg = validateClassName(getParam(PROTOLOG_CLASS_PARAM, params))
+ protoLogGroupsClassNameArg = validateClassName(getParam(PROTOLOGGROUP_CLASS_PARAM,
+ params))
+ protoLogImplClassNameArg = validateClassName(getParam(PROTOLOGIMPL_CLASS_PARAM,
+ params))
+ protoLogGroupsJarArg = validateJarName(getParam(PROTOLOGGROUP_JAR_PARAM, params))
+ viewerConfigJsonArg = validateNotSpecified(VIEWER_CONFIG_JSON_PARAM, params)
+ outputSourceJarArg = validateSrcJarName(getParam(OUTPUT_SOURCE_JAR_PARAM, params))
+ javaSourceArgs = validateJavaInputList(inputFiles)
+ logProtofileArg = ""
+ }
+ GENERATE_CONFIG_CMD -> {
+ protoLogClassNameArg = validateClassName(getParam(PROTOLOG_CLASS_PARAM, params))
+ protoLogGroupsClassNameArg = validateClassName(getParam(PROTOLOGGROUP_CLASS_PARAM,
+ params))
+ protoLogImplClassNameArg = validateNotSpecified(PROTOLOGIMPL_CLASS_PARAM, params)
+ protoLogGroupsJarArg = validateJarName(getParam(PROTOLOGGROUP_JAR_PARAM, params))
+ viewerConfigJsonArg = validateJSONName(getParam(VIEWER_CONFIG_JSON_PARAM, params))
+ outputSourceJarArg = validateNotSpecified(OUTPUT_SOURCE_JAR_PARAM, params)
+ javaSourceArgs = validateJavaInputList(inputFiles)
+ logProtofileArg = ""
+ }
+ READ_LOG_CMD -> {
+ protoLogClassNameArg = validateNotSpecified(PROTOLOG_CLASS_PARAM, params)
+ protoLogGroupsClassNameArg = validateNotSpecified(PROTOLOGGROUP_CLASS_PARAM, params)
+ protoLogImplClassNameArg = validateNotSpecified(PROTOLOGIMPL_CLASS_PARAM, params)
+ protoLogGroupsJarArg = validateNotSpecified(PROTOLOGGROUP_JAR_PARAM, params)
+ viewerConfigJsonArg = validateJSONName(getParam(VIEWER_CONFIG_JSON_PARAM, params))
+ outputSourceJarArg = validateNotSpecified(OUTPUT_SOURCE_JAR_PARAM, params)
+ javaSourceArgs = listOf()
+ logProtofileArg = validateLogInputList(inputFiles)
+ }
+ else -> {
+ throw InvalidCommandException("Unknown command.")
+ }
+ }
+ }
+}
diff --git a/tools/protologtool/src/com/android/protologtool/Constants.kt b/tools/protologtool/src/com/android/protologtool/Constants.kt
new file mode 100644
index 0000000..2ccfc4d
--- /dev/null
+++ b/tools/protologtool/src/com/android/protologtool/Constants.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.protologtool
+
+object Constants {
+ const val NAME = "protologtool"
+ const val VERSION = "1.0.0"
+ const val IS_ENABLED_METHOD = "isEnabled"
+ const val IS_LOG_TO_LOGCAT_METHOD = "isLogToLogcat"
+ const val IS_LOG_TO_ANY_METHOD = "isLogToAny"
+ const val GET_TAG_METHOD = "getTag"
+ const val ENUM_VALUES_METHOD = "values"
+}
diff --git a/tools/protologtool/src/com/android/protologtool/LogGroup.kt b/tools/protologtool/src/com/android/protologtool/LogGroup.kt
new file mode 100644
index 0000000..42a37a2
--- /dev/null
+++ b/tools/protologtool/src/com/android/protologtool/LogGroup.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.protologtool
+
+data class LogGroup(
+ val name: String,
+ val enabled: Boolean,
+ val textEnabled: Boolean,
+ val tag: String
+)
diff --git a/tools/protologtool/src/com/android/protologtool/LogLevel.kt b/tools/protologtool/src/com/android/protologtool/LogLevel.kt
new file mode 100644
index 0000000..dc29557
--- /dev/null
+++ b/tools/protologtool/src/com/android/protologtool/LogLevel.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.protologtool
+
+import com.github.javaparser.ast.Node
+
+enum class LogLevel {
+ DEBUG, VERBOSE, INFO, WARN, ERROR, WTF;
+
+ companion object {
+ fun getLevelForMethodName(name: String, node: Node): LogLevel {
+ return when (name) {
+ "d" -> DEBUG
+ "v" -> VERBOSE
+ "i" -> INFO
+ "w" -> WARN
+ "e" -> ERROR
+ "wtf" -> WTF
+ else -> throw InvalidProtoLogCallException("Unknown log level $name", node)
+ }
+ }
+ }
+}
diff --git a/tools/protologtool/src/com/android/protologtool/LogParser.kt b/tools/protologtool/src/com/android/protologtool/LogParser.kt
new file mode 100644
index 0000000..4d0eb0e
--- /dev/null
+++ b/tools/protologtool/src/com/android/protologtool/LogParser.kt
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.protologtool
+
+import com.android.json.stream.JsonReader
+import com.android.server.wm.ProtoLogMessage
+import com.android.server.wm.WindowManagerLogFileProto
+import java.io.BufferedReader
+import java.io.InputStream
+import java.io.InputStreamReader
+import java.io.PrintStream
+import java.lang.Exception
+import java.text.SimpleDateFormat
+import java.util.Date
+import java.util.Locale
+
+/**
+ * Implements a simple parser/viewer for binary ProtoLog logs.
+ * A binary log is translated into Android "LogCat"-like text log.
+ */
+class LogParser(private val configParser: ViewerConfigParser) {
+ companion object {
+ private val dateFormat = SimpleDateFormat("MM-dd HH:mm:ss.SSS", Locale.US)
+ private val magicNumber =
+ WindowManagerLogFileProto.MagicNumber.MAGIC_NUMBER_H.number.toLong() shl 32 or
+ WindowManagerLogFileProto.MagicNumber.MAGIC_NUMBER_L.number.toLong()
+ }
+
+ private fun printTime(time: Long, offset: Long, ps: PrintStream) {
+ ps.print(dateFormat.format(Date(time / 1000000 + offset)) + " ")
+ }
+
+ private fun printFormatted(
+ protoLogMessage: ProtoLogMessage,
+ configEntry: ViewerConfigParser.ConfigEntry,
+ ps: PrintStream
+ ) {
+ val strParmIt = protoLogMessage.strParamsList.iterator()
+ val longParamsIt = protoLogMessage.sint64ParamsList.iterator()
+ val doubleParamsIt = protoLogMessage.doubleParamsList.iterator()
+ val boolParamsIt = protoLogMessage.booleanParamsList.iterator()
+ val args = mutableListOf<Any>()
+ val format = configEntry.messageString
+ val argTypes = CodeUtils.parseFormatString(format)
+ try {
+ argTypes.forEach {
+ when (it) {
+ CodeUtils.LogDataTypes.BOOLEAN -> args.add(boolParamsIt.next())
+ CodeUtils.LogDataTypes.LONG -> args.add(longParamsIt.next())
+ CodeUtils.LogDataTypes.DOUBLE -> args.add(doubleParamsIt.next())
+ CodeUtils.LogDataTypes.STRING -> args.add(strParmIt.next())
+ }
+ }
+ } catch (ex: NoSuchElementException) {
+ throw InvalidFormatStringException("Invalid format string in config", ex)
+ }
+ if (strParmIt.hasNext() || longParamsIt.hasNext() ||
+ doubleParamsIt.hasNext() || boolParamsIt.hasNext()) {
+ throw RuntimeException("Invalid format string in config - no enough matchers")
+ }
+ val formatted = format.format(*(args.toTypedArray()))
+ ps.print("${configEntry.level} ${configEntry.tag}: $formatted\n")
+ }
+
+ private fun printUnformatted(protoLogMessage: ProtoLogMessage, ps: PrintStream, tag: String) {
+ ps.println("$tag: ${protoLogMessage.messageHash} - ${protoLogMessage.strParamsList}" +
+ " ${protoLogMessage.sint64ParamsList} ${protoLogMessage.doubleParamsList}" +
+ " ${protoLogMessage.booleanParamsList}")
+ }
+
+ fun parse(protoLogInput: InputStream, jsonConfigInput: InputStream, ps: PrintStream) {
+ val jsonReader = JsonReader(BufferedReader(InputStreamReader(jsonConfigInput)))
+ val config = configParser.parseConfig(jsonReader)
+ val protoLog = WindowManagerLogFileProto.parseFrom(protoLogInput)
+
+ if (protoLog.magicNumber != magicNumber) {
+ throw InvalidInputException("ProtoLog file magic number is invalid.")
+ }
+ if (protoLog.version != Constants.VERSION) {
+ throw InvalidInputException("ProtoLog file version not supported by this tool," +
+ " log version ${protoLog.version}, viewer version ${Constants.VERSION}")
+ }
+
+ protoLog.logList.forEach { log ->
+ printTime(log.elapsedRealtimeNanos, protoLog.realTimeToElapsedTimeOffsetMillis, ps)
+ if (log.messageHash !in config) {
+ printUnformatted(log, ps, "UNKNOWN")
+ } else {
+ val conf = config.getValue(log.messageHash)
+ try {
+ printFormatted(log, conf, ps)
+ } catch (ex: Exception) {
+ printUnformatted(log, ps, "INVALID")
+ }
+ }
+ }
+ }
+}
diff --git a/tools/protologtool/src/com/android/protologtool/ProtoLogCallProcessor.kt b/tools/protologtool/src/com/android/protologtool/ProtoLogCallProcessor.kt
new file mode 100644
index 0000000..29d8ae5
--- /dev/null
+++ b/tools/protologtool/src/com/android/protologtool/ProtoLogCallProcessor.kt
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.protologtool
+
+import com.github.javaparser.ast.CompilationUnit
+import com.github.javaparser.ast.expr.Expression
+import com.github.javaparser.ast.expr.FieldAccessExpr
+import com.github.javaparser.ast.expr.MethodCallExpr
+import com.github.javaparser.ast.expr.NameExpr
+
+/**
+ * Helper class for visiting all ProtoLog calls.
+ * For every valid call in the given {@code CompilationUnit} a {@code ProtoLogCallVisitor} callback
+ * is executed.
+ */
+open class ProtoLogCallProcessor(
+ private val protoLogClassName: String,
+ private val protoLogGroupClassName: String,
+ private val groupMap: Map<String, LogGroup>
+) {
+ private val protoLogSimpleClassName = protoLogClassName.substringAfterLast('.')
+ private val protoLogGroupSimpleClassName = protoLogGroupClassName.substringAfterLast('.')
+
+ private fun getLogGroupName(
+ expr: Expression,
+ isClassImported: Boolean,
+ staticImports: Set<String>
+ ): String {
+ return when (expr) {
+ is NameExpr -> when {
+ expr.nameAsString in staticImports -> expr.nameAsString
+ else ->
+ throw InvalidProtoLogCallException("Unknown/not imported ProtoLogGroup", expr)
+ }
+ is FieldAccessExpr -> when {
+ expr.scope.toString() == protoLogGroupClassName
+ || isClassImported &&
+ expr.scope.toString() == protoLogGroupSimpleClassName -> expr.nameAsString
+ else ->
+ throw InvalidProtoLogCallException("Unknown/not imported ProtoLogGroup", expr)
+ }
+ else -> throw InvalidProtoLogCallException("Invalid group argument " +
+ "- must be ProtoLogGroup enum member reference", expr)
+ }
+ }
+
+ private fun isProtoCall(
+ call: MethodCallExpr,
+ isLogClassImported: Boolean,
+ staticLogImports: Collection<String>
+ ): Boolean {
+ return call.scope.isPresent && call.scope.get().toString() == protoLogClassName ||
+ isLogClassImported && call.scope.isPresent &&
+ call.scope.get().toString() == protoLogSimpleClassName ||
+ !call.scope.isPresent && staticLogImports.contains(call.name.toString())
+ }
+
+ open fun process(code: CompilationUnit, callVisitor: ProtoLogCallVisitor?): CompilationUnit {
+ if (CodeUtils.isWildcardStaticImported(code, protoLogClassName) ||
+ CodeUtils.isWildcardStaticImported(code, protoLogGroupClassName)) {
+ throw IllegalImportException("Wildcard static imports of $protoLogClassName " +
+ "and $protoLogGroupClassName methods are not supported.")
+ }
+
+ val isLogClassImported = CodeUtils.isClassImportedOrSamePackage(code, protoLogClassName)
+ val staticLogImports = CodeUtils.staticallyImportedMethods(code, protoLogClassName)
+ val isGroupClassImported = CodeUtils.isClassImportedOrSamePackage(code,
+ protoLogGroupClassName)
+ val staticGroupImports = CodeUtils.staticallyImportedMethods(code, protoLogGroupClassName)
+
+ code.findAll(MethodCallExpr::class.java)
+ .filter { call ->
+ isProtoCall(call, isLogClassImported, staticLogImports)
+ }.forEach { call ->
+ if (call.arguments.size < 2) {
+ throw InvalidProtoLogCallException("Method signature does not match " +
+ "any ProtoLog method.", call)
+ }
+
+ val messageString = CodeUtils.concatMultilineString(call.getArgument(1))
+ val groupNameArg = call.getArgument(0)
+ val groupName =
+ getLogGroupName(groupNameArg, isGroupClassImported, staticGroupImports)
+ if (groupName !in groupMap) {
+ throw InvalidProtoLogCallException("Unknown group argument " +
+ "- not a ProtoLogGroup enum member", call)
+ }
+
+ callVisitor?.processCall(call, messageString, LogLevel.getLevelForMethodName(
+ call.name.toString(), call), groupMap.getValue(groupName))
+ }
+ return code
+ }
+}
diff --git a/tools/protologtool/src/com/android/protologtool/ProtoLogCallVisitor.kt b/tools/protologtool/src/com/android/protologtool/ProtoLogCallVisitor.kt
new file mode 100644
index 0000000..42a75f8
--- /dev/null
+++ b/tools/protologtool/src/com/android/protologtool/ProtoLogCallVisitor.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.protologtool
+
+import com.github.javaparser.ast.expr.MethodCallExpr
+
+interface ProtoLogCallVisitor {
+ fun processCall(call: MethodCallExpr, messageString: String, level: LogLevel, group: LogGroup)
+}
diff --git a/tools/protologtool/src/com/android/protologtool/ProtoLogGroupReader.kt b/tools/protologtool/src/com/android/protologtool/ProtoLogGroupReader.kt
new file mode 100644
index 0000000..664c8a6
--- /dev/null
+++ b/tools/protologtool/src/com/android/protologtool/ProtoLogGroupReader.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.protologtool
+
+import com.android.protologtool.Constants.ENUM_VALUES_METHOD
+import com.android.protologtool.Constants.GET_TAG_METHOD
+import com.android.protologtool.Constants.IS_ENABLED_METHOD
+import com.android.protologtool.Constants.IS_LOG_TO_LOGCAT_METHOD
+import java.io.File
+import java.lang.RuntimeException
+import java.net.URLClassLoader
+
+class ProtoLogGroupReader {
+ private fun getClassloaderForJar(jarPath: String): ClassLoader {
+ val jarFile = File(jarPath)
+ val url = jarFile.toURI().toURL()
+ return URLClassLoader(arrayOf(url), ProtoLogGroupReader::class.java.classLoader)
+ }
+
+ private fun getEnumValues(clazz: Class<*>): List<Enum<*>> {
+ val valuesMethod = clazz.getMethod(ENUM_VALUES_METHOD)
+ @Suppress("UNCHECKED_CAST")
+ return (valuesMethod.invoke(null) as Array<Enum<*>>).toList()
+ }
+
+ private fun getLogGroupFromEnumValue(group: Any, clazz: Class<*>): LogGroup {
+ val enabled = clazz.getMethod(IS_ENABLED_METHOD).invoke(group) as Boolean
+ val textEnabled = clazz.getMethod(IS_LOG_TO_LOGCAT_METHOD).invoke(group) as Boolean
+ val tag = clazz.getMethod(GET_TAG_METHOD).invoke(group) as String
+ val name = (group as Enum<*>).name
+ return LogGroup(name, enabled, textEnabled, tag)
+ }
+
+ fun loadFromJar(jarPath: String, className: String): Map<String, LogGroup> {
+ try {
+ val classLoader = getClassloaderForJar(jarPath)
+ val clazz = classLoader.loadClass(className)
+ val values = getEnumValues(clazz)
+ return values.map { group ->
+ group.name to getLogGroupFromEnumValue(group, clazz)
+ }.toMap()
+ } catch (ex: ReflectiveOperationException) {
+ throw RuntimeException("Unable to load ProtoLogGroup enum class", ex)
+ }
+ }
+}
diff --git a/tools/protologtool/src/com/android/protologtool/ProtoLogTool.kt b/tools/protologtool/src/com/android/protologtool/ProtoLogTool.kt
new file mode 100644
index 0000000..485a047
--- /dev/null
+++ b/tools/protologtool/src/com/android/protologtool/ProtoLogTool.kt
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.protologtool
+
+import com.android.protologtool.CommandOptions.Companion.USAGE
+import com.github.javaparser.StaticJavaParser
+import java.io.File
+import java.io.FileInputStream
+import java.io.FileOutputStream
+import java.util.jar.JarOutputStream
+import java.util.zip.ZipEntry
+import kotlin.system.exitProcess
+
+object ProtoLogTool {
+ private fun showHelpAndExit() {
+ println(USAGE)
+ exitProcess(-1)
+ }
+
+ private fun processClasses(command: CommandOptions) {
+ val groups = ProtoLogGroupReader()
+ .loadFromJar(command.protoLogGroupsJarArg, command.protoLogGroupsClassNameArg)
+ val out = FileOutputStream(command.outputSourceJarArg)
+ val outJar = JarOutputStream(out)
+ val processor = ProtoLogCallProcessor(command.protoLogClassNameArg,
+ command.protoLogGroupsClassNameArg, groups)
+ val transformer = SourceTransformer(command.protoLogImplClassNameArg, processor)
+
+ command.javaSourceArgs.forEach { path ->
+ val file = File(path)
+ val code = StaticJavaParser.parse(file)
+ val outSrc = transformer.processClass(code)
+ val pack = if (code.packageDeclaration.isPresent) code.packageDeclaration
+ .get().nameAsString else ""
+ val newPath = pack.replace('.', '/') + '/' + file.name
+ outJar.putNextEntry(ZipEntry(newPath))
+ outJar.write(outSrc.toByteArray())
+ outJar.closeEntry()
+ }
+
+ outJar.close()
+ out.close()
+ }
+
+ private fun viewerConf(command: CommandOptions) {
+ val groups = ProtoLogGroupReader()
+ .loadFromJar(command.protoLogGroupsJarArg, command.protoLogGroupsClassNameArg)
+ val processor = ProtoLogCallProcessor(command.protoLogClassNameArg,
+ command.protoLogGroupsClassNameArg, groups)
+ val builder = ViewerConfigBuilder(processor)
+ command.javaSourceArgs.forEach { path ->
+ val file = File(path)
+ builder.processClass(StaticJavaParser.parse(file))
+ }
+ val out = FileOutputStream(command.viewerConfigJsonArg)
+ out.write(builder.build().toByteArray())
+ out.close()
+ }
+
+ fun read(command: CommandOptions) {
+ LogParser(ViewerConfigParser())
+ .parse(FileInputStream(command.logProtofileArg),
+ FileInputStream(command.viewerConfigJsonArg), System.out)
+ }
+
+ @JvmStatic
+ fun main(args: Array<String>) {
+ try {
+ val command = CommandOptions(args)
+ when (command.command) {
+ CommandOptions.TRANSFORM_CALLS_CMD -> processClasses(command)
+ CommandOptions.GENERATE_CONFIG_CMD -> viewerConf(command)
+ CommandOptions.READ_LOG_CMD -> read(command)
+ }
+ } catch (ex: InvalidCommandException) {
+ println(ex.message)
+ showHelpAndExit()
+ }
+ }
+}
diff --git a/tools/protologtool/src/com/android/protologtool/SourceTransformer.kt b/tools/protologtool/src/com/android/protologtool/SourceTransformer.kt
new file mode 100644
index 0000000..319a8170
--- /dev/null
+++ b/tools/protologtool/src/com/android/protologtool/SourceTransformer.kt
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.protologtool
+
+import com.android.protologtool.Constants.IS_LOG_TO_ANY_METHOD
+import com.github.javaparser.StaticJavaParser
+import com.github.javaparser.ast.CompilationUnit
+import com.github.javaparser.ast.NodeList
+import com.github.javaparser.ast.body.VariableDeclarator
+import com.github.javaparser.ast.expr.BooleanLiteralExpr
+import com.github.javaparser.ast.expr.CastExpr
+import com.github.javaparser.ast.expr.FieldAccessExpr
+import com.github.javaparser.ast.expr.IntegerLiteralExpr
+import com.github.javaparser.ast.expr.MethodCallExpr
+import com.github.javaparser.ast.expr.NameExpr
+import com.github.javaparser.ast.expr.NullLiteralExpr
+import com.github.javaparser.ast.expr.SimpleName
+import com.github.javaparser.ast.expr.VariableDeclarationExpr
+import com.github.javaparser.ast.stmt.BlockStmt
+import com.github.javaparser.ast.stmt.ExpressionStmt
+import com.github.javaparser.ast.stmt.IfStmt
+import com.github.javaparser.ast.type.ArrayType
+import com.github.javaparser.printer.PrettyPrinter
+import com.github.javaparser.printer.PrettyPrinterConfiguration
+import com.github.javaparser.printer.lexicalpreservation.LexicalPreservingPrinter
+
+class SourceTransformer(
+ protoLogImplClassName: String,
+ private val protoLogCallProcessor: ProtoLogCallProcessor
+) : ProtoLogCallVisitor {
+ override fun processCall(
+ call: MethodCallExpr,
+ messageString: String,
+ level: LogLevel,
+ group: LogGroup
+ ) {
+ // Input format: ProtoLog.e(GROUP, "msg %d", arg)
+ if (!call.parentNode.isPresent) {
+ // Should never happen
+ throw RuntimeException("Unable to process log call $call " +
+ "- no parent node in AST")
+ }
+ if (call.parentNode.get() !is ExpressionStmt) {
+ // Should never happen
+ throw RuntimeException("Unable to process log call $call " +
+ "- parent node in AST is not an ExpressionStmt")
+ }
+ val parentStmt = call.parentNode.get() as ExpressionStmt
+ if (!parentStmt.parentNode.isPresent) {
+ // Should never happen
+ throw RuntimeException("Unable to process log call $call " +
+ "- no grandparent node in AST")
+ }
+ val ifStmt: IfStmt
+ if (group.enabled) {
+ val hash = CodeUtils.hash(messageString, level)
+ val newCall = call.clone()
+ if (!group.textEnabled) {
+ // Remove message string if text logging is not enabled by default.
+ // Out: ProtoLog.e(GROUP, null, arg)
+ newCall.arguments[1].replace(NameExpr("null"))
+ }
+ // Insert message string hash as a second argument.
+ // Out: ProtoLog.e(GROUP, 1234, null, arg)
+ newCall.arguments.add(1, IntegerLiteralExpr(hash))
+ val argTypes = CodeUtils.parseFormatString(messageString)
+ val typeMask = CodeUtils.logDataTypesToBitMask(argTypes)
+ // Insert bitmap representing which Number parameters are to be considered as
+ // floating point numbers.
+ // Out: ProtoLog.e(GROUP, 1234, 0, null, arg)
+ newCall.arguments.add(2, IntegerLiteralExpr(typeMask))
+ // Replace call to a stub method with an actual implementation.
+ // Out: com.android.server.wm.ProtoLogImpl.e(GROUP, 1234, null, arg)
+ newCall.setScope(protoLogImplClassNode)
+ // Create a call to GROUP.isLogAny()
+ // Out: GROUP.isLogAny()
+ val isLogAnyExpr = MethodCallExpr(newCall.arguments[0].clone(),
+ SimpleName(IS_LOG_TO_ANY_METHOD))
+ if (argTypes.size != call.arguments.size - 2) {
+ throw InvalidProtoLogCallException(
+ "Number of arguments does not mach format string", call)
+ }
+ val blockStmt = BlockStmt()
+ if (argTypes.isNotEmpty()) {
+ // Assign every argument to a variable to check its type in compile time
+ // (this is assignment is optimized-out by dex tool, there is no runtime impact)/
+ // Out: long protoLogParam0 = arg
+ argTypes.forEachIndexed { idx, type ->
+ val varName = "protoLogParam$idx"
+ val declaration = VariableDeclarator(type.type, varName,
+ type.toType(newCall.arguments[idx + 4].clone()))
+ blockStmt.addStatement(ExpressionStmt(VariableDeclarationExpr(declaration)))
+ newCall.setArgument(idx + 4, NameExpr(SimpleName(varName)))
+ }
+ } else {
+ // Assign (Object[])null as the vararg parameter to prevent allocating an empty
+ // object array.
+ val nullArray = CastExpr(ArrayType(objectType), NullLiteralExpr())
+ newCall.addArgument(nullArray)
+ }
+ blockStmt.addStatement(ExpressionStmt(newCall))
+ // Create an IF-statement with the previously created condition.
+ // Out: if (GROUP.isLogAny()) {
+ // long protoLogParam0 = arg;
+ // com.android.server.wm.ProtoLogImpl.e(GROUP, 1234, 0, null, protoLogParam0);
+ // }
+ ifStmt = IfStmt(isLogAnyExpr, blockStmt, null)
+ } else {
+ // Surround with if (false).
+ val newCall = parentStmt.clone()
+ ifStmt = IfStmt(BooleanLiteralExpr(false), BlockStmt(NodeList(newCall)), null)
+ newCall.setBlockComment(" ${group.name} is disabled ")
+ }
+ // Inline the new statement.
+ val printedIfStmt = inlinePrinter.print(ifStmt)
+ // Append blank lines to preserve line numbering in file (to allow debugging)
+ val newLines = LexicalPreservingPrinter.print(parentStmt).count { c -> c == '\n' }
+ val newStmt = printedIfStmt.substringBeforeLast('}') + ("\n".repeat(newLines)) + '}'
+ val inlinedIfStmt = StaticJavaParser.parseStatement(newStmt)
+ LexicalPreservingPrinter.setup(inlinedIfStmt)
+ // Replace the original call.
+ if (!parentStmt.replace(inlinedIfStmt)) {
+ // Should never happen
+ throw RuntimeException("Unable to process log call $call " +
+ "- unable to replace the call.")
+ }
+ }
+
+ private val inlinePrinter: PrettyPrinter
+ private val objectType = StaticJavaParser.parseClassOrInterfaceType("Object")
+
+ init {
+ val config = PrettyPrinterConfiguration()
+ config.endOfLineCharacter = " "
+ config.indentSize = 0
+ config.tabWidth = 1
+ inlinePrinter = PrettyPrinter(config)
+ }
+
+ private val protoLogImplClassNode =
+ StaticJavaParser.parseExpression<FieldAccessExpr>(protoLogImplClassName)
+
+ fun processClass(compilationUnit: CompilationUnit): String {
+ LexicalPreservingPrinter.setup(compilationUnit)
+ protoLogCallProcessor.process(compilationUnit, this)
+ return LexicalPreservingPrinter.print(compilationUnit)
+ }
+}
diff --git a/tools/protologtool/src/com/android/protologtool/ViewerConfigBuilder.kt b/tools/protologtool/src/com/android/protologtool/ViewerConfigBuilder.kt
new file mode 100644
index 0000000..8ce9a49
--- /dev/null
+++ b/tools/protologtool/src/com/android/protologtool/ViewerConfigBuilder.kt
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.protologtool
+
+import com.android.json.stream.JsonWriter
+import com.github.javaparser.ast.CompilationUnit
+import com.android.protologtool.Constants.VERSION
+import com.github.javaparser.ast.expr.MethodCallExpr
+import java.io.StringWriter
+
+class ViewerConfigBuilder(
+ private val protoLogCallVisitor: ProtoLogCallProcessor
+) : ProtoLogCallVisitor {
+ override fun processCall(
+ call: MethodCallExpr,
+ messageString: String,
+ level: LogLevel,
+ group: LogGroup
+ ) {
+ if (group.enabled) {
+ val key = CodeUtils.hash(messageString, level)
+ if (statements.containsKey(key)) {
+ if (statements[key] != Triple(messageString, level, group)) {
+ throw HashCollisionException(
+ "Please modify the log message \"$messageString\" " +
+ "or \"${statements[key]}\" - their hashes are equal.")
+ }
+ } else {
+ groups.add(group)
+ statements[key] = Triple(messageString, level, group)
+ }
+ }
+ }
+
+ private val statements: MutableMap<Int, Triple<String, LogLevel, LogGroup>> = mutableMapOf()
+ private val groups: MutableSet<LogGroup> = mutableSetOf()
+
+ fun processClass(unit: CompilationUnit) {
+ protoLogCallVisitor.process(unit, this)
+ }
+
+ fun build(): String {
+ val stringWriter = StringWriter()
+ val writer = JsonWriter(stringWriter)
+ writer.setIndent(" ")
+ writer.beginObject()
+ writer.name("version")
+ writer.value(VERSION)
+ writer.name("messages")
+ writer.beginObject()
+ statements.toSortedMap().forEach { (key, value) ->
+ writer.name(key.toString())
+ writer.beginObject()
+ writer.name("message")
+ writer.value(value.first)
+ writer.name("level")
+ writer.value(value.second.name)
+ writer.name("group")
+ writer.value(value.third.name)
+ writer.endObject()
+ }
+ writer.endObject()
+ writer.name("groups")
+ writer.beginObject()
+ groups.toSortedSet(Comparator { o1, o2 -> o1.name.compareTo(o2.name) }).forEach { group ->
+ writer.name(group.name)
+ writer.beginObject()
+ writer.name("tag")
+ writer.value(group.tag)
+ writer.endObject()
+ }
+ writer.endObject()
+ writer.endObject()
+ stringWriter.buffer.append('\n')
+ return stringWriter.toString()
+ }
+}
diff --git a/tools/protologtool/src/com/android/protologtool/ViewerConfigParser.kt b/tools/protologtool/src/com/android/protologtool/ViewerConfigParser.kt
new file mode 100644
index 0000000..69cf92d
--- /dev/null
+++ b/tools/protologtool/src/com/android/protologtool/ViewerConfigParser.kt
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.protologtool
+
+import com.android.json.stream.JsonReader
+
+open class ViewerConfigParser {
+ data class MessageEntry(
+ val messageString: String,
+ val level: String,
+ val groupName: String
+ )
+
+ fun parseMessage(jsonReader: JsonReader): MessageEntry {
+ jsonReader.beginObject()
+ var message: String? = null
+ var level: String? = null
+ var groupName: String? = null
+ while (jsonReader.hasNext()) {
+ val key = jsonReader.nextName()
+ when (key) {
+ "message" -> message = jsonReader.nextString()
+ "level" -> level = jsonReader.nextString()
+ "group" -> groupName = jsonReader.nextString()
+ else -> jsonReader.skipValue()
+ }
+ }
+ jsonReader.endObject()
+ if (message.isNullOrBlank() || level.isNullOrBlank() || groupName.isNullOrBlank()) {
+ throw InvalidViewerConfigException("Invalid message entry in viewer config")
+ }
+ return MessageEntry(message, level, groupName)
+ }
+
+ data class GroupEntry(val tag: String)
+
+ fun parseGroup(jsonReader: JsonReader): GroupEntry {
+ jsonReader.beginObject()
+ var tag: String? = null
+ while (jsonReader.hasNext()) {
+ val key = jsonReader.nextName()
+ when (key) {
+ "tag" -> tag = jsonReader.nextString()
+ else -> jsonReader.skipValue()
+ }
+ }
+ jsonReader.endObject()
+ if (tag.isNullOrBlank()) {
+ throw InvalidViewerConfigException("Invalid group entry in viewer config")
+ }
+ return GroupEntry(tag)
+ }
+
+ fun parseMessages(jsonReader: JsonReader): Map<Int, MessageEntry> {
+ val config: MutableMap<Int, MessageEntry> = mutableMapOf()
+ jsonReader.beginObject()
+ while (jsonReader.hasNext()) {
+ val key = jsonReader.nextName()
+ val hash = key.toIntOrNull()
+ ?: throw InvalidViewerConfigException("Invalid key in messages viewer config")
+ config[hash] = parseMessage(jsonReader)
+ }
+ jsonReader.endObject()
+ return config
+ }
+
+ fun parseGroups(jsonReader: JsonReader): Map<String, GroupEntry> {
+ val config: MutableMap<String, GroupEntry> = mutableMapOf()
+ jsonReader.beginObject()
+ while (jsonReader.hasNext()) {
+ val key = jsonReader.nextName()
+ config[key] = parseGroup(jsonReader)
+ }
+ jsonReader.endObject()
+ return config
+ }
+
+ data class ConfigEntry(val messageString: String, val level: String, val tag: String)
+
+ open fun parseConfig(jsonReader: JsonReader): Map<Int, ConfigEntry> {
+ var messages: Map<Int, MessageEntry>? = null
+ var groups: Map<String, GroupEntry>? = null
+ var version: String? = null
+
+ jsonReader.beginObject()
+ while (jsonReader.hasNext()) {
+ val key = jsonReader.nextName()
+ when (key) {
+ "messages" -> messages = parseMessages(jsonReader)
+ "groups" -> groups = parseGroups(jsonReader)
+ "version" -> version = jsonReader.nextString()
+
+ else -> jsonReader.skipValue()
+ }
+ }
+ jsonReader.endObject()
+ if (messages == null || groups == null || version == null) {
+ throw InvalidViewerConfigException("Invalid config - definitions missing")
+ }
+ if (version != Constants.VERSION) {
+ throw InvalidViewerConfigException("Viewer config version not supported by this tool," +
+ " config version $version, viewer version ${Constants.VERSION}")
+ }
+ return messages.map { msg ->
+ msg.key to ConfigEntry(
+ msg.value.messageString, msg.value.level, groups[msg.value.groupName]?.tag
+ ?: throw InvalidViewerConfigException(
+ "Group definition missing for ${msg.value.groupName}"))
+ }.toMap()
+ }
+}
diff --git a/tools/protologtool/src/com/android/protologtool/exceptions.kt b/tools/protologtool/src/com/android/protologtool/exceptions.kt
new file mode 100644
index 0000000..2199785
--- /dev/null
+++ b/tools/protologtool/src/com/android/protologtool/exceptions.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.protologtool
+
+import com.github.javaparser.ast.Node
+import java.lang.Exception
+import java.lang.RuntimeException
+
+class HashCollisionException(message: String) : RuntimeException(message)
+
+class IllegalImportException(message: String) : Exception(message)
+
+class InvalidProtoLogCallException(message: String, node: Node)
+ : RuntimeException("$message\nAt: $node")
+
+class InvalidViewerConfigException : Exception {
+ constructor(message: String) : super(message)
+
+ constructor(message: String, ex: Exception) : super(message, ex)
+}
+
+class InvalidFormatStringException : Exception {
+ constructor(message: String) : super(message)
+
+ constructor(message: String, ex: Exception) : super(message, ex)
+}
+
+class InvalidInputException(message: String) : Exception(message)
+
+class InvalidCommandException(message: String) : Exception(message)
diff --git a/tools/protologtool/tests/com/android/protologtool/CodeUtilsTest.kt b/tools/protologtool/tests/com/android/protologtool/CodeUtilsTest.kt
new file mode 100644
index 0000000..82daa73
--- /dev/null
+++ b/tools/protologtool/tests/com/android/protologtool/CodeUtilsTest.kt
@@ -0,0 +1,206 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.protologtool
+
+import com.github.javaparser.StaticJavaParser
+import com.github.javaparser.ast.expr.BinaryExpr
+import com.github.javaparser.ast.expr.StringLiteralExpr
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Test
+
+class CodeUtilsTest {
+ @Test
+ fun hash() {
+ assertEquals(-1704685243, CodeUtils.hash("test", LogLevel.DEBUG))
+ }
+
+ @Test
+ fun hash_changeLevel() {
+ assertEquals(-1176900998, CodeUtils.hash("test", LogLevel.ERROR))
+ }
+
+ @Test
+ fun hash_changeMessage() {
+ assertEquals(-1305634931, CodeUtils.hash("test2", LogLevel.DEBUG))
+ }
+
+ @Test
+ fun isWildcardStaticImported_true() {
+ val code = """package org.example.test;
+ import static org.example.Test.*;
+ """
+ assertTrue(CodeUtils.isWildcardStaticImported(
+ StaticJavaParser.parse(code), "org.example.Test"))
+ }
+
+ @Test
+ fun isWildcardStaticImported_notStatic() {
+ val code = """package org.example.test;
+ import org.example.Test.*;
+ """
+ assertFalse(CodeUtils.isWildcardStaticImported(
+ StaticJavaParser.parse(code), "org.example.Test"))
+ }
+
+ @Test
+ fun isWildcardStaticImported_differentClass() {
+ val code = """package org.example.test;
+ import static org.example.Test2.*;
+ """
+ assertFalse(CodeUtils.isWildcardStaticImported(
+ StaticJavaParser.parse(code), "org.example.Test"))
+ }
+
+ @Test
+ fun isWildcardStaticImported_notWildcard() {
+ val code = """package org.example.test;
+ import org.example.Test.test;
+ """
+ assertFalse(CodeUtils.isWildcardStaticImported(
+ StaticJavaParser.parse(code), "org.example.Test"))
+ }
+
+ @Test
+ fun isClassImportedOrSamePackage_imported() {
+ val code = """package org.example.test;
+ import org.example.Test;
+ """
+ assertTrue(CodeUtils.isClassImportedOrSamePackage(
+ StaticJavaParser.parse(code), "org.example.Test"))
+ }
+
+ @Test
+ fun isClassImportedOrSamePackage_samePackage() {
+ val code = """package org.example.test;
+ """
+ assertTrue(CodeUtils.isClassImportedOrSamePackage(
+ StaticJavaParser.parse(code), "org.example.test.Test"))
+ }
+
+ @Test
+ fun isClassImportedOrSamePackage_false() {
+ val code = """package org.example.test;
+ import org.example.Test;
+ """
+ assertFalse(CodeUtils.isClassImportedOrSamePackage(
+ StaticJavaParser.parse(code), "org.example.Test2"))
+ }
+
+ @Test
+ fun staticallyImportedMethods_ab() {
+ val code = """
+ import static org.example.Test.a;
+ import static org.example.Test.b;
+ """
+ val imported = CodeUtils.staticallyImportedMethods(StaticJavaParser.parse(code),
+ "org.example.Test")
+ assertTrue(imported.containsAll(listOf("a", "b")))
+ assertEquals(2, imported.size)
+ }
+
+ @Test
+ fun staticallyImportedMethods_differentClass() {
+ val code = """
+ import static org.example.Test.a;
+ import static org.example.Test2.b;
+ """
+ val imported = CodeUtils.staticallyImportedMethods(StaticJavaParser.parse(code),
+ "org.example.Test")
+ assertTrue(imported.containsAll(listOf("a")))
+ assertEquals(1, imported.size)
+ }
+
+ @Test
+ fun staticallyImportedMethods_notStatic() {
+ val code = """
+ import static org.example.Test.a;
+ import org.example.Test.b;
+ """
+ val imported = CodeUtils.staticallyImportedMethods(StaticJavaParser.parse(code),
+ "org.example.Test")
+ assertTrue(imported.containsAll(listOf("a")))
+ assertEquals(1, imported.size)
+ }
+
+ @Test
+ fun concatMultilineString_single() {
+ val str = StringLiteralExpr("test")
+ val out = CodeUtils.concatMultilineString(str)
+ assertEquals("test", out)
+ }
+
+ @Test
+ fun concatMultilineString_double() {
+ val str = """
+ "test" + "abc"
+ """
+ val code = StaticJavaParser.parseExpression<BinaryExpr>(str)
+ val out = CodeUtils.concatMultilineString(code)
+ assertEquals("testabc", out)
+ }
+
+ @Test
+ fun concatMultilineString_multiple() {
+ val str = """
+ "test" + "abc" + "1234" + "test"
+ """
+ val code = StaticJavaParser.parseExpression<BinaryExpr>(str)
+ val out = CodeUtils.concatMultilineString(code)
+ assertEquals("testabc1234test", out)
+ }
+
+ @Test
+ fun parseFormatString() {
+ val str = "%b %d %o %x %f %e %g %s %%"
+ val out = CodeUtils.parseFormatString(str)
+ assertEquals(listOf(
+ CodeUtils.LogDataTypes.BOOLEAN,
+ CodeUtils.LogDataTypes.LONG,
+ CodeUtils.LogDataTypes.LONG,
+ CodeUtils.LogDataTypes.LONG,
+ CodeUtils.LogDataTypes.DOUBLE,
+ CodeUtils.LogDataTypes.DOUBLE,
+ CodeUtils.LogDataTypes.DOUBLE,
+ CodeUtils.LogDataTypes.STRING
+ ), out)
+ }
+
+ @Test(expected = InvalidFormatStringException::class)
+ fun parseFormatString_invalid() {
+ val str = "%q"
+ CodeUtils.parseFormatString(str)
+ }
+
+ @Test
+ fun logDataTypesToBitMask() {
+ val types = listOf(CodeUtils.LogDataTypes.STRING, CodeUtils.LogDataTypes.DOUBLE,
+ CodeUtils.LogDataTypes.LONG, CodeUtils.LogDataTypes.BOOLEAN)
+ val mask = CodeUtils.logDataTypesToBitMask(types)
+ assertEquals(0b11011000, mask)
+ }
+
+ @Test(expected = InvalidFormatStringException::class)
+ fun logDataTypesToBitMask_toManyParams() {
+ val types = mutableListOf<CodeUtils.LogDataTypes>()
+ for (i in 0..16) {
+ types.add(CodeUtils.LogDataTypes.STRING)
+ }
+ CodeUtils.logDataTypesToBitMask(types)
+ }
+}
diff --git a/tools/protologtool/tests/com/android/protologtool/CommandOptionsTest.kt b/tools/protologtool/tests/com/android/protologtool/CommandOptionsTest.kt
new file mode 100644
index 0000000..c1cd473
--- /dev/null
+++ b/tools/protologtool/tests/com/android/protologtool/CommandOptionsTest.kt
@@ -0,0 +1,250 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.protologtool
+
+import org.junit.Assert.assertEquals
+import org.junit.Test
+
+class CommandOptionsTest {
+ companion object {
+ val TEST_JAVA_SRC = listOf(
+ "frameworks/base/services/core/java/com/android/server/wm/" +
+ "AccessibilityController.java",
+ "frameworks/base/services/core/java/com/android/server/wm/ActivityDisplay.java",
+ "frameworks/base/services/core/java/com/android/server/wm/" +
+ "ActivityMetricsLaunchObserver.java"
+ )
+ private const val TEST_PROTOLOG_CLASS = "com.android.server.wm.ProtoLog"
+ private const val TEST_PROTOLOGIMPL_CLASS = "com.android.server.wm.ProtoLogImpl"
+ private const val TEST_PROTOLOGGROUP_CLASS = "com.android.server.wm.ProtoLogGroup"
+ private const val TEST_PROTOLOGGROUP_JAR = "out/soong/.intermediates/frameworks/base/" +
+ "services/core/services.core.wm.protologgroups/android_common/javac/" +
+ "services.core.wm.protologgroups.jar"
+ private const val TEST_SRC_JAR = "out/soong/.temp/sbox175955373/" +
+ "services.core.wm.protolog.srcjar"
+ private const val TEST_VIEWER_JSON = "out/soong/.temp/sbox175955373/" +
+ "services.core.wm.protolog.json"
+ private const val TEST_LOG = "./test_log.pb"
+ }
+
+ @Test(expected = InvalidCommandException::class)
+ fun noCommand() {
+ CommandOptions(arrayOf())
+ }
+
+ @Test(expected = InvalidCommandException::class)
+ fun invalidCommand() {
+ val testLine = "invalid"
+ CommandOptions(testLine.split(' ').toTypedArray())
+ }
+
+ @Test
+ fun transformClasses() {
+ val testLine = "transform-protolog-calls --protolog-class $TEST_PROTOLOG_CLASS " +
+ "--protolog-impl-class $TEST_PROTOLOGIMPL_CLASS " +
+ "--loggroups-class $TEST_PROTOLOGGROUP_CLASS " +
+ "--loggroups-jar $TEST_PROTOLOGGROUP_JAR " +
+ "--output-srcjar $TEST_SRC_JAR ${TEST_JAVA_SRC.joinToString(" ")}"
+ val cmd = CommandOptions(testLine.split(' ').toTypedArray())
+ assertEquals(CommandOptions.TRANSFORM_CALLS_CMD, cmd.command)
+ assertEquals(TEST_PROTOLOG_CLASS, cmd.protoLogClassNameArg)
+ assertEquals(TEST_PROTOLOGIMPL_CLASS, cmd.protoLogImplClassNameArg)
+ assertEquals(TEST_PROTOLOGGROUP_CLASS, cmd.protoLogGroupsClassNameArg)
+ assertEquals(TEST_PROTOLOGGROUP_JAR, cmd.protoLogGroupsJarArg)
+ assertEquals(TEST_SRC_JAR, cmd.outputSourceJarArg)
+ assertEquals(TEST_JAVA_SRC, cmd.javaSourceArgs)
+ }
+
+ @Test(expected = InvalidCommandException::class)
+ fun transformClasses_noProtoLogClass() {
+ val testLine = "transform-protolog-calls " +
+ "--protolog-impl-class $TEST_PROTOLOGIMPL_CLASS " +
+ "--loggroups-class $TEST_PROTOLOGGROUP_CLASS " +
+ "--loggroups-jar $TEST_PROTOLOGGROUP_JAR " +
+ "--output-srcjar $TEST_SRC_JAR ${TEST_JAVA_SRC.joinToString(" ")}"
+ CommandOptions(testLine.split(' ').toTypedArray())
+ }
+
+ @Test(expected = InvalidCommandException::class)
+ fun transformClasses_noProtoLogImplClass() {
+ val testLine = "transform-protolog-calls --protolog-class $TEST_PROTOLOG_CLASS " +
+ "--loggroups-class $TEST_PROTOLOGGROUP_CLASS " +
+ "--loggroups-jar $TEST_PROTOLOGGROUP_JAR " +
+ "--output-srcjar $TEST_SRC_JAR ${TEST_JAVA_SRC.joinToString(" ")}"
+ CommandOptions(testLine.split(' ').toTypedArray())
+ }
+
+ @Test(expected = InvalidCommandException::class)
+ fun transformClasses_noProtoLogGroupClass() {
+ val testLine = "transform-protolog-calls --protolog-class $TEST_PROTOLOG_CLASS " +
+ "--protolog-impl-class $TEST_PROTOLOGIMPL_CLASS " +
+ "--loggroups-jar $TEST_PROTOLOGGROUP_JAR " +
+ "--output-srcjar $TEST_SRC_JAR ${TEST_JAVA_SRC.joinToString(" ")}"
+ CommandOptions(testLine.split(' ').toTypedArray())
+ }
+
+ @Test(expected = InvalidCommandException::class)
+ fun transformClasses_noProtoLogGroupJar() {
+ val testLine = "transform-protolog-calls --protolog-class $TEST_PROTOLOG_CLASS " +
+ "--protolog-impl-class $TEST_PROTOLOGIMPL_CLASS " +
+ "--loggroups-class $TEST_PROTOLOGGROUP_CLASS " +
+ "--output-srcjar $TEST_SRC_JAR ${TEST_JAVA_SRC.joinToString(" ")}"
+ CommandOptions(testLine.split(' ').toTypedArray())
+ }
+
+ @Test(expected = InvalidCommandException::class)
+ fun transformClasses_noOutJar() {
+ val testLine = "transform-protolog-calls --protolog-class $TEST_PROTOLOG_CLASS " +
+ "--protolog-impl-class $TEST_PROTOLOGIMPL_CLASS " +
+ "--loggroups-class $TEST_PROTOLOGGROUP_CLASS " +
+ "--loggroups-jar $TEST_PROTOLOGGROUP_JAR " +
+ TEST_JAVA_SRC.joinToString(" ")
+ CommandOptions(testLine.split(' ').toTypedArray())
+ }
+
+ @Test(expected = InvalidCommandException::class)
+ fun transformClasses_noJavaInput() {
+ val testLine = "transform-protolog-calls --protolog-class $TEST_PROTOLOG_CLASS " +
+ "--protolog-impl-class $TEST_PROTOLOGIMPL_CLASS " +
+ "--loggroups-class $TEST_PROTOLOGGROUP_CLASS " +
+ "--loggroups-jar $TEST_PROTOLOGGROUP_JAR " +
+ "--output-srcjar $TEST_SRC_JAR"
+ CommandOptions(testLine.split(' ').toTypedArray())
+ }
+
+ @Test(expected = InvalidCommandException::class)
+ fun transformClasses_invalidProtoLogClass() {
+ val testLine = "transform-protolog-calls --protolog-class invalid " +
+ "--protolog-impl-class $TEST_PROTOLOGIMPL_CLASS " +
+ "--loggroups-class $TEST_PROTOLOGGROUP_CLASS " +
+ "--loggroups-jar $TEST_PROTOLOGGROUP_JAR " +
+ "--output-srcjar $TEST_SRC_JAR ${TEST_JAVA_SRC.joinToString(" ")}"
+ CommandOptions(testLine.split(' ').toTypedArray())
+ }
+
+ @Test(expected = InvalidCommandException::class)
+ fun transformClasses_invalidProtoLogImplClass() {
+ val testLine = "transform-protolog-calls --protolog-class $TEST_PROTOLOG_CLASS " +
+ "--protolog-impl-class invalid " +
+ "--loggroups-class $TEST_PROTOLOGGROUP_CLASS " +
+ "--loggroups-jar $TEST_PROTOLOGGROUP_JAR " +
+ "--output-srcjar $TEST_SRC_JAR ${TEST_JAVA_SRC.joinToString(" ")}"
+ CommandOptions(testLine.split(' ').toTypedArray())
+ }
+
+ @Test(expected = InvalidCommandException::class)
+ fun transformClasses_invalidProtoLogGroupClass() {
+ val testLine = "transform-protolog-calls --protolog-class $TEST_PROTOLOG_CLASS " +
+ "--protolog-impl-class $TEST_PROTOLOGIMPL_CLASS " +
+ "--loggroups-class invalid " +
+ "--loggroups-jar $TEST_PROTOLOGGROUP_JAR " +
+ "--output-srcjar $TEST_SRC_JAR ${TEST_JAVA_SRC.joinToString(" ")}"
+ CommandOptions(testLine.split(' ').toTypedArray())
+ }
+
+ @Test(expected = InvalidCommandException::class)
+ fun transformClasses_invalidProtoLogGroupJar() {
+ val testLine = "transform-protolog-calls --protolog-class $TEST_PROTOLOG_CLASS " +
+ "--protolog-impl-class $TEST_PROTOLOGIMPL_CLASS " +
+ "--loggroups-class $TEST_PROTOLOGGROUP_CLASS " +
+ "--loggroups-jar invalid.txt " +
+ "--output-srcjar $TEST_SRC_JAR ${TEST_JAVA_SRC.joinToString(" ")}"
+ CommandOptions(testLine.split(' ').toTypedArray())
+ }
+
+ @Test(expected = InvalidCommandException::class)
+ fun transformClasses_invalidOutJar() {
+ val testLine = "transform-protolog-calls --protolog-class $TEST_PROTOLOG_CLASS " +
+ "--protolog-impl-class $TEST_PROTOLOGIMPL_CLASS " +
+ "--loggroups-class $TEST_PROTOLOGGROUP_CLASS " +
+ "--loggroups-jar $TEST_PROTOLOGGROUP_JAR " +
+ "--output-srcjar invalid.db ${TEST_JAVA_SRC.joinToString(" ")}"
+ CommandOptions(testLine.split(' ').toTypedArray())
+ }
+
+ @Test(expected = InvalidCommandException::class)
+ fun transformClasses_invalidJavaInput() {
+ val testLine = "transform-protolog-calls --protolog-class $TEST_PROTOLOG_CLASS " +
+ "--protolog-impl-class $TEST_PROTOLOGIMPL_CLASS " +
+ "--loggroups-class $TEST_PROTOLOGGROUP_CLASS " +
+ "--loggroups-jar $TEST_PROTOLOGGROUP_JAR " +
+ "--output-srcjar $TEST_SRC_JAR invalid.py"
+ CommandOptions(testLine.split(' ').toTypedArray())
+ }
+
+ @Test(expected = InvalidCommandException::class)
+ fun transformClasses_unknownParam() {
+ val testLine = "transform-protolog-calls --protolog-class $TEST_PROTOLOG_CLASS " +
+ "--unknown test --protolog-impl-class $TEST_PROTOLOGIMPL_CLASS " +
+ "--loggroups-class $TEST_PROTOLOGGROUP_CLASS " +
+ "--loggroups-jar $TEST_PROTOLOGGROUP_JAR " +
+ "--output-srcjar $TEST_SRC_JAR ${TEST_JAVA_SRC.joinToString(" ")}"
+ CommandOptions(testLine.split(' ').toTypedArray())
+ }
+
+ @Test(expected = InvalidCommandException::class)
+ fun transformClasses_noValue() {
+ val testLine = "transform-protolog-calls --protolog-class $TEST_PROTOLOG_CLASS " +
+ "--protolog-impl-class " +
+ "--loggroups-class $TEST_PROTOLOGGROUP_CLASS " +
+ "--loggroups-jar $TEST_PROTOLOGGROUP_JAR " +
+ "--output-srcjar $TEST_SRC_JAR ${TEST_JAVA_SRC.joinToString(" ")}"
+ CommandOptions(testLine.split(' ').toTypedArray())
+ }
+
+ @Test
+ fun generateConfig() {
+ val testLine = "generate-viewer-config --protolog-class $TEST_PROTOLOG_CLASS " +
+ "--loggroups-class $TEST_PROTOLOGGROUP_CLASS " +
+ "--loggroups-jar $TEST_PROTOLOGGROUP_JAR " +
+ "--viewer-conf $TEST_VIEWER_JSON ${TEST_JAVA_SRC.joinToString(" ")}"
+ val cmd = CommandOptions(testLine.split(' ').toTypedArray())
+ assertEquals(CommandOptions.GENERATE_CONFIG_CMD, cmd.command)
+ assertEquals(TEST_PROTOLOG_CLASS, cmd.protoLogClassNameArg)
+ assertEquals(TEST_PROTOLOGGROUP_CLASS, cmd.protoLogGroupsClassNameArg)
+ assertEquals(TEST_PROTOLOGGROUP_JAR, cmd.protoLogGroupsJarArg)
+ assertEquals(TEST_VIEWER_JSON, cmd.viewerConfigJsonArg)
+ assertEquals(TEST_JAVA_SRC, cmd.javaSourceArgs)
+ }
+
+ @Test(expected = InvalidCommandException::class)
+ fun generateConfig_noViewerConfig() {
+ val testLine = "generate-viewer-config --protolog-class $TEST_PROTOLOG_CLASS " +
+ "--loggroups-class $TEST_PROTOLOGGROUP_CLASS " +
+ "--loggroups-jar $TEST_PROTOLOGGROUP_JAR " +
+ TEST_JAVA_SRC.joinToString(" ")
+ CommandOptions(testLine.split(' ').toTypedArray())
+ }
+
+ @Test(expected = InvalidCommandException::class)
+ fun generateConfig_invalidViewerConfig() {
+ val testLine = "generate-viewer-config --protolog-class $TEST_PROTOLOG_CLASS " +
+ "--loggroups-class $TEST_PROTOLOGGROUP_CLASS " +
+ "--loggroups-jar $TEST_PROTOLOGGROUP_JAR " +
+ "--viewer-conf invalid.yaml ${TEST_JAVA_SRC.joinToString(" ")}"
+ CommandOptions(testLine.split(' ').toTypedArray())
+ }
+
+ @Test
+ fun readLog() {
+ val testLine = "read-log --viewer-conf $TEST_VIEWER_JSON $TEST_LOG"
+ val cmd = CommandOptions(testLine.split(' ').toTypedArray())
+ assertEquals(CommandOptions.READ_LOG_CMD, cmd.command)
+ assertEquals(TEST_VIEWER_JSON, cmd.viewerConfigJsonArg)
+ assertEquals(TEST_LOG, cmd.logProtofileArg)
+ }
+}
diff --git a/tools/protologtool/tests/com/android/protologtool/LogParserTest.kt b/tools/protologtool/tests/com/android/protologtool/LogParserTest.kt
new file mode 100644
index 0000000..7106ea6
--- /dev/null
+++ b/tools/protologtool/tests/com/android/protologtool/LogParserTest.kt
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.protologtool
+
+import com.android.json.stream.JsonReader
+import com.android.server.wm.ProtoLogMessage
+import com.android.server.wm.WindowManagerLogFileProto
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mockito
+import org.mockito.Mockito.mock
+import java.io.ByteArrayOutputStream
+import java.io.InputStream
+import java.io.OutputStream
+import java.io.PrintStream
+import java.text.SimpleDateFormat
+import java.util.Date
+import java.util.Locale
+
+class LogParserTest {
+ private val configParser: ViewerConfigParser = mock(ViewerConfigParser::class.java)
+ private val parser = LogParser(configParser)
+ private var config: MutableMap<Int, ViewerConfigParser.ConfigEntry> = mutableMapOf()
+ private var outStream: OutputStream = ByteArrayOutputStream()
+ private var printStream: PrintStream = PrintStream(outStream)
+ private val dateFormat = SimpleDateFormat("MM-dd HH:mm:ss.SSS", Locale.US)
+
+ @Before
+ fun init() {
+ Mockito.`when`(configParser.parseConfig(any(JsonReader::class.java))).thenReturn(config)
+ }
+
+ private fun <T> any(type: Class<T>): T = Mockito.any<T>(type)
+
+ private fun getConfigDummyStream(): InputStream {
+ return "".byteInputStream()
+ }
+
+ private fun buildProtoInput(logBuilder: WindowManagerLogFileProto.Builder): InputStream {
+ logBuilder.setVersion(Constants.VERSION)
+ logBuilder.magicNumber =
+ WindowManagerLogFileProto.MagicNumber.MAGIC_NUMBER_H.number.toLong() shl 32 or
+ WindowManagerLogFileProto.MagicNumber.MAGIC_NUMBER_L.number.toLong()
+ return logBuilder.build().toByteArray().inputStream()
+ }
+
+ private fun testDate(timeMS: Long): String {
+ return dateFormat.format(Date(timeMS))
+ }
+
+ @Test
+ fun parse() {
+ config[70933285] = ViewerConfigParser.ConfigEntry("Test completed successfully: %b",
+ "ERROR", "WindowManager")
+
+ val logBuilder = WindowManagerLogFileProto.newBuilder()
+ val logMessageBuilder = ProtoLogMessage.newBuilder()
+ logMessageBuilder
+ .setMessageHash(70933285)
+ .setElapsedRealtimeNanos(0)
+ .addBooleanParams(true)
+ logBuilder.addLog(logMessageBuilder.build())
+
+ parser.parse(buildProtoInput(logBuilder), getConfigDummyStream(), printStream)
+
+ assertEquals("${testDate(0)} ERROR WindowManager: Test completed successfully: true\n",
+ outStream.toString())
+ }
+
+ @Test
+ fun parse_formatting() {
+ config[123] = ViewerConfigParser.ConfigEntry("Test completed successfully: %b %d %% %o" +
+ " %x %e %g %s %f", "ERROR", "WindowManager")
+
+ val logBuilder = WindowManagerLogFileProto.newBuilder()
+ val logMessageBuilder = ProtoLogMessage.newBuilder()
+ logMessageBuilder
+ .setMessageHash(123)
+ .setElapsedRealtimeNanos(0)
+ .addBooleanParams(true)
+ .addAllSint64Params(listOf(1000, 20000, 300000))
+ .addAllDoubleParams(listOf(0.1, 0.00001, 1000.1))
+ .addStrParams("test")
+ logBuilder.addLog(logMessageBuilder.build())
+
+ parser.parse(buildProtoInput(logBuilder), getConfigDummyStream(), printStream)
+
+ assertEquals("${testDate(0)} ERROR WindowManager: Test completed successfully: " +
+ "true 1000 % 47040 493e0 1.000000e-01 1.00000e-05 test 1000.100000\n",
+ outStream.toString())
+ }
+
+ @Test
+ fun parse_invalidParamsTooMany() {
+ config[123] = ViewerConfigParser.ConfigEntry("Test completed successfully: %b %d %% %o",
+ "ERROR", "WindowManager")
+
+ val logBuilder = WindowManagerLogFileProto.newBuilder()
+ val logMessageBuilder = ProtoLogMessage.newBuilder()
+ logMessageBuilder
+ .setMessageHash(123)
+ .setElapsedRealtimeNanos(0)
+ .addBooleanParams(true)
+ .addAllSint64Params(listOf(1000, 20000, 300000))
+ .addAllDoubleParams(listOf(0.1, 0.00001, 1000.1))
+ .addStrParams("test")
+ logBuilder.addLog(logMessageBuilder.build())
+
+ parser.parse(buildProtoInput(logBuilder), getConfigDummyStream(), printStream)
+
+ assertEquals("${testDate(0)} INVALID: 123 - [test] [1000, 20000, 300000] " +
+ "[0.1, 1.0E-5, 1000.1] [true]\n", outStream.toString())
+ }
+
+ @Test
+ fun parse_invalidParamsNotEnough() {
+ config[123] = ViewerConfigParser.ConfigEntry("Test completed successfully: %b %d %% %o" +
+ " %x %e %g %s %f", "ERROR", "WindowManager")
+
+ val logBuilder = WindowManagerLogFileProto.newBuilder()
+ val logMessageBuilder = ProtoLogMessage.newBuilder()
+ logMessageBuilder
+ .setMessageHash(123)
+ .setElapsedRealtimeNanos(0)
+ .addBooleanParams(true)
+ .addStrParams("test")
+ logBuilder.addLog(logMessageBuilder.build())
+
+ parser.parse(buildProtoInput(logBuilder), getConfigDummyStream(), printStream)
+
+ assertEquals("${testDate(0)} INVALID: 123 - [test] [] [] [true]\n",
+ outStream.toString())
+ }
+
+ @Test(expected = InvalidInputException::class)
+ fun parse_invalidMagicNumber() {
+ val logBuilder = WindowManagerLogFileProto.newBuilder()
+ logBuilder.setVersion(Constants.VERSION)
+ logBuilder.magicNumber = 0
+ val stream = logBuilder.build().toByteArray().inputStream()
+
+ parser.parse(stream, getConfigDummyStream(), printStream)
+ }
+
+ @Test(expected = InvalidInputException::class)
+ fun parse_invalidVersion() {
+ val logBuilder = WindowManagerLogFileProto.newBuilder()
+ logBuilder.setVersion("invalid")
+ logBuilder.magicNumber =
+ WindowManagerLogFileProto.MagicNumber.MAGIC_NUMBER_H.number.toLong() shl 32 or
+ WindowManagerLogFileProto.MagicNumber.MAGIC_NUMBER_L.number.toLong()
+ val stream = logBuilder.build().toByteArray().inputStream()
+
+ parser.parse(stream, getConfigDummyStream(), printStream)
+ }
+
+ @Test
+ fun parse_noConfig() {
+ val logBuilder = WindowManagerLogFileProto.newBuilder()
+ val logMessageBuilder = ProtoLogMessage.newBuilder()
+ logMessageBuilder
+ .setMessageHash(70933285)
+ .setElapsedRealtimeNanos(0)
+ .addBooleanParams(true)
+ logBuilder.addLog(logMessageBuilder.build())
+
+ parser.parse(buildProtoInput(logBuilder), getConfigDummyStream(), printStream)
+
+ assertEquals("${testDate(0)} UNKNOWN: 70933285 - [] [] [] [true]\n",
+ outStream.toString())
+ }
+}
diff --git a/tools/protologtool/tests/com/android/protologtool/ProtoLogCallProcessorTest.kt b/tools/protologtool/tests/com/android/protologtool/ProtoLogCallProcessorTest.kt
new file mode 100644
index 0000000..dcb1f7f
--- /dev/null
+++ b/tools/protologtool/tests/com/android/protologtool/ProtoLogCallProcessorTest.kt
@@ -0,0 +1,226 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.protologtool
+
+import com.github.javaparser.StaticJavaParser
+import com.github.javaparser.ast.expr.MethodCallExpr
+import org.junit.Assert.assertEquals
+import org.junit.Test
+
+class ProtoLogCallProcessorTest {
+ private data class LogCall(
+ val call: MethodCallExpr,
+ val messageString: String,
+ val level: LogLevel,
+ val group: LogGroup
+ )
+
+ private val groupMap: MutableMap<String, LogGroup> = mutableMapOf()
+ private val calls: MutableList<LogCall> = mutableListOf()
+ private val visitor = ProtoLogCallProcessor("org.example.ProtoLog", "org.example.ProtoLogGroup",
+ groupMap)
+ private val processor = object : ProtoLogCallVisitor {
+ override fun processCall(
+ call: MethodCallExpr,
+ messageString: String,
+ level: LogLevel,
+ group: LogGroup
+ ) {
+ calls.add(LogCall(call, messageString, level, group))
+ }
+ }
+
+ private fun checkCalls() {
+ assertEquals(1, calls.size)
+ val c = calls[0]
+ assertEquals("test %b", c.messageString)
+ assertEquals(groupMap["TEST"], c.group)
+ assertEquals(LogLevel.DEBUG, c.level)
+ }
+
+ @Test
+ fun process_samePackage() {
+ val code = """
+ package org.example;
+
+ class Test {
+ void test() {
+ ProtoLog.d(ProtoLogGroup.TEST, "test %b", true);
+ ProtoLog.e(ProtoLogGroup.ERROR, "error %d", 1);
+ }
+ }
+ """
+ groupMap["TEST"] = LogGroup("TEST", true, false, "WindowManager")
+ groupMap["ERROR"] = LogGroup("ERROR", true, true, "WindowManagerERROR")
+ visitor.process(StaticJavaParser.parse(code), processor)
+ assertEquals(2, calls.size)
+ var c = calls[0]
+ assertEquals("test %b", c.messageString)
+ assertEquals(groupMap["TEST"], c.group)
+ assertEquals(LogLevel.DEBUG, c.level)
+ c = calls[1]
+ assertEquals("error %d", c.messageString)
+ assertEquals(groupMap["ERROR"], c.group)
+ assertEquals(LogLevel.ERROR, c.level)
+ }
+
+ @Test
+ fun process_imported() {
+ val code = """
+ package org.example2;
+
+ import org.example.ProtoLog;
+ import org.example.ProtoLogGroup;
+
+ class Test {
+ void test() {
+ ProtoLog.d(ProtoLogGroup.TEST, "test %b", true);
+ }
+ }
+ """
+ groupMap["TEST"] = LogGroup("TEST", true, true, "WindowManager")
+ visitor.process(StaticJavaParser.parse(code), processor)
+ checkCalls()
+ }
+
+ @Test
+ fun process_importedStatic() {
+ val code = """
+ package org.example2;
+
+ import static org.example.ProtoLog.d;
+ import static org.example.ProtoLogGroup.TEST;
+
+ class Test {
+ void test() {
+ d(TEST, "test %b", true);
+ }
+ }
+ """
+ groupMap["TEST"] = LogGroup("TEST", true, true, "WindowManager")
+ visitor.process(StaticJavaParser.parse(code), processor)
+ checkCalls()
+ }
+
+ @Test(expected = InvalidProtoLogCallException::class)
+ fun process_groupNotImported() {
+ val code = """
+ package org.example2;
+
+ import org.example.ProtoLog;
+
+ class Test {
+ void test() {
+ ProtoLog.d(ProtoLogGroup.TEST, "test %b", true);
+ }
+ }
+ """
+ groupMap["TEST"] = LogGroup("TEST", true, true, "WindowManager")
+ visitor.process(StaticJavaParser.parse(code), processor)
+ }
+
+ @Test
+ fun process_protoLogNotImported() {
+ val code = """
+ package org.example2;
+
+ import org.example.ProtoLogGroup;
+
+ class Test {
+ void test() {
+ ProtoLog.d(ProtoLogGroup.TEST, "test %b", true);
+ }
+ }
+ """
+ groupMap["TEST"] = LogGroup("TEST", true, true, "WindowManager")
+ visitor.process(StaticJavaParser.parse(code), processor)
+ assertEquals(0, calls.size)
+ }
+
+ @Test(expected = InvalidProtoLogCallException::class)
+ fun process_unknownGroup() {
+ val code = """
+ package org.example;
+
+ class Test {
+ void test() {
+ ProtoLog.d(ProtoLogGroup.TEST, "test %b", true);
+ }
+ }
+ """
+ visitor.process(StaticJavaParser.parse(code), processor)
+ }
+
+ @Test(expected = InvalidProtoLogCallException::class)
+ fun process_staticGroup() {
+ val code = """
+ package org.example;
+
+ class Test {
+ void test() {
+ ProtoLog.d(TEST, "test %b", true);
+ }
+ }
+ """
+ visitor.process(StaticJavaParser.parse(code), processor)
+ }
+
+ @Test(expected = InvalidProtoLogCallException::class)
+ fun process_badGroup() {
+ val code = """
+ package org.example;
+
+ class Test {
+ void test() {
+ ProtoLog.d(0, "test %b", true);
+ }
+ }
+ """
+ visitor.process(StaticJavaParser.parse(code), processor)
+ }
+
+ @Test(expected = InvalidProtoLogCallException::class)
+ fun process_invalidSignature() {
+ val code = """
+ package org.example;
+
+ class Test {
+ void test() {
+ ProtoLog.d("test");
+ }
+ }
+ """
+ visitor.process(StaticJavaParser.parse(code), processor)
+ }
+
+ @Test
+ fun process_disabled() {
+ // Disabled groups are also processed.
+ val code = """
+ package org.example;
+
+ class Test {
+ void test() {
+ ProtoLog.d(ProtoLogGroup.TEST, "test %b", true);
+ }
+ }
+ """
+ groupMap["TEST"] = LogGroup("TEST", false, true, "WindowManager")
+ visitor.process(StaticJavaParser.parse(code), processor)
+ checkCalls()
+ }
+}
diff --git a/tools/protologtool/tests/com/android/protologtool/SourceTransformerTest.kt b/tools/protologtool/tests/com/android/protologtool/SourceTransformerTest.kt
new file mode 100644
index 0000000..7b8dd9a
--- /dev/null
+++ b/tools/protologtool/tests/com/android/protologtool/SourceTransformerTest.kt
@@ -0,0 +1,373 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.protologtool
+
+import com.github.javaparser.StaticJavaParser
+import com.github.javaparser.ast.CompilationUnit
+import com.github.javaparser.ast.expr.MethodCallExpr
+import com.github.javaparser.ast.stmt.IfStmt
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Test
+import org.mockito.Mockito
+
+class SourceTransformerTest {
+ companion object {
+ private const val PROTO_LOG_IMPL_PATH = "org.example.ProtoLogImpl"
+ private val TEST_CODE = """
+ package org.example;
+
+ class Test {
+ void test() {
+ ProtoLog.w(TEST_GROUP, "test %d %f", 100, 0.1);
+ }
+ }
+ """.trimIndent()
+
+ private val TEST_CODE_MULTILINE = """
+ package org.example;
+
+ class Test {
+ void test() {
+ ProtoLog.w(TEST_GROUP, "test %d %f " +
+ "abc %s\n test", 100,
+ 0.1, "test");
+ }
+ }
+ """.trimIndent()
+
+ private val TEST_CODE_NO_PARAMS = """
+ package org.example;
+
+ class Test {
+ void test() {
+ ProtoLog.w(TEST_GROUP, "test");
+ }
+ }
+ """.trimIndent()
+
+ /* ktlint-disable max-line-length */
+ private val TRANSFORMED_CODE_TEXT_ENABLED = """
+ package org.example;
+
+ class Test {
+ void test() {
+ if (TEST_GROUP.isLogToAny()) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, 835524026, 9, "test %d %f", protoLogParam0, protoLogParam1); }
+ }
+ }
+ """.trimIndent()
+
+ private val TRANSFORMED_CODE_MULTILINE_TEXT_ENABLED = """
+ package org.example;
+
+ class Test {
+ void test() {
+ if (TEST_GROUP.isLogToAny()) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; String protoLogParam2 = String.valueOf("test"); org.example.ProtoLogImpl.w(TEST_GROUP, -986393606, 9, "test %d %f " + "abc %s\n test", protoLogParam0, protoLogParam1, protoLogParam2);
+
+ }
+ }
+ }
+ """.trimIndent()
+
+ private val TRANSFORMED_CODE_NO_PARAMS = """
+ package org.example;
+
+ class Test {
+ void test() {
+ if (TEST_GROUP.isLogToAny()) { org.example.ProtoLogImpl.w(TEST_GROUP, 1282022424, 0, "test", (Object[]) null); }
+ }
+ }
+ """.trimIndent()
+
+ private val TRANSFORMED_CODE_TEXT_DISABLED = """
+ package org.example;
+
+ class Test {
+ void test() {
+ if (TEST_GROUP.isLogToAny()) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, 835524026, 9, null, protoLogParam0, protoLogParam1); }
+ }
+ }
+ """.trimIndent()
+
+ private val TRANSFORMED_CODE_MULTILINE_TEXT_DISABLED = """
+ package org.example;
+
+ class Test {
+ void test() {
+ if (TEST_GROUP.isLogToAny()) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; String protoLogParam2 = String.valueOf("test"); org.example.ProtoLogImpl.w(TEST_GROUP, -986393606, 9, null, protoLogParam0, protoLogParam1, protoLogParam2);
+
+ }
+ }
+ }
+ """.trimIndent()
+
+ private val TRANSFORMED_CODE_DISABLED = """
+ package org.example;
+
+ class Test {
+ void test() {
+ if (false) { /* TEST_GROUP is disabled */ ProtoLog.w(TEST_GROUP, "test %d %f", 100, 0.1); }
+ }
+ }
+ """.trimIndent()
+
+ private val TRANSFORMED_CODE_MULTILINE_DISABLED = """
+ package org.example;
+
+ class Test {
+ void test() {
+ if (false) { /* TEST_GROUP is disabled */ ProtoLog.w(TEST_GROUP, "test %d %f " + "abc %s\n test", 100, 0.1, "test");
+
+ }
+ }
+ }
+ """.trimIndent()
+ /* ktlint-enable max-line-length */
+ }
+
+ private val processor: ProtoLogCallProcessor = Mockito.mock(ProtoLogCallProcessor::class.java)
+ private val sourceJarWriter = SourceTransformer("org.example.ProtoLogImpl", processor)
+
+ private fun <T> any(type: Class<T>): T = Mockito.any<T>(type)
+
+ @Test
+ fun processClass_textEnabled() {
+ val code = StaticJavaParser.parse(TEST_CODE)
+
+ Mockito.`when`(processor.process(any(CompilationUnit::class.java),
+ any(ProtoLogCallVisitor::class.java))).thenAnswer { invocation ->
+ val visitor = invocation.arguments[1] as ProtoLogCallVisitor
+
+ visitor.processCall(code.findAll(MethodCallExpr::class.java)[0], "test %d %f",
+ LogLevel.WARN, LogGroup("TEST_GROUP", true, true, "WM_TEST"))
+
+ invocation.arguments[0] as CompilationUnit
+ }
+
+ val out = sourceJarWriter.processClass(code)
+
+ val ifStmts = code.findAll(IfStmt::class.java)
+ assertEquals(1, ifStmts.size)
+ val ifStmt = ifStmts[0]
+ assertEquals("TEST_GROUP.${Constants.IS_LOG_TO_ANY_METHOD}()",
+ ifStmt.condition.toString())
+ assertFalse(ifStmt.elseStmt.isPresent)
+ assertEquals(3, ifStmt.thenStmt.childNodes.size)
+ val methodCall = ifStmt.thenStmt.findAll(MethodCallExpr::class.java)[0] as MethodCallExpr
+ assertEquals(PROTO_LOG_IMPL_PATH, methodCall.scope.get().toString())
+ assertEquals("w", methodCall.name.asString())
+ assertEquals(6, methodCall.arguments.size)
+ assertEquals("TEST_GROUP", methodCall.arguments[0].toString())
+ assertEquals("835524026", methodCall.arguments[1].toString())
+ assertEquals(0b1001.toString(), methodCall.arguments[2].toString())
+ assertEquals("\"test %d %f\"", methodCall.arguments[3].toString())
+ assertEquals("protoLogParam0", methodCall.arguments[4].toString())
+ assertEquals("protoLogParam1", methodCall.arguments[5].toString())
+ assertEquals(TRANSFORMED_CODE_TEXT_ENABLED, out)
+ }
+
+ @Test
+ fun processClass_textEnabledMultiline() {
+ val code = StaticJavaParser.parse(TEST_CODE_MULTILINE)
+
+ Mockito.`when`(processor.process(any(CompilationUnit::class.java),
+ any(ProtoLogCallVisitor::class.java))).thenAnswer { invocation ->
+ val visitor = invocation.arguments[1] as ProtoLogCallVisitor
+
+ visitor.processCall(code.findAll(MethodCallExpr::class.java)[0],
+ "test %d %f abc %s\n test", LogLevel.WARN, LogGroup("TEST_GROUP",
+ true, true, "WM_TEST"))
+
+ invocation.arguments[0] as CompilationUnit
+ }
+
+ val out = sourceJarWriter.processClass(code)
+
+ val ifStmts = code.findAll(IfStmt::class.java)
+ assertEquals(1, ifStmts.size)
+ val ifStmt = ifStmts[0]
+ assertEquals("TEST_GROUP.${Constants.IS_LOG_TO_ANY_METHOD}()",
+ ifStmt.condition.toString())
+ assertFalse(ifStmt.elseStmt.isPresent)
+ assertEquals(4, ifStmt.thenStmt.childNodes.size)
+ val methodCall = ifStmt.thenStmt.findAll(MethodCallExpr::class.java)[1] as MethodCallExpr
+ assertEquals(PROTO_LOG_IMPL_PATH, methodCall.scope.get().toString())
+ assertEquals("w", methodCall.name.asString())
+ assertEquals(7, methodCall.arguments.size)
+ assertEquals("TEST_GROUP", methodCall.arguments[0].toString())
+ assertEquals("-986393606", methodCall.arguments[1].toString())
+ assertEquals(0b001001.toString(), methodCall.arguments[2].toString())
+ assertEquals("protoLogParam0", methodCall.arguments[4].toString())
+ assertEquals("protoLogParam1", methodCall.arguments[5].toString())
+ assertEquals("protoLogParam2", methodCall.arguments[6].toString())
+ assertEquals(TRANSFORMED_CODE_MULTILINE_TEXT_ENABLED, out)
+ }
+
+ @Test
+ fun processClass_noParams() {
+ val code = StaticJavaParser.parse(TEST_CODE_NO_PARAMS)
+
+ Mockito.`when`(processor.process(any(CompilationUnit::class.java),
+ any(ProtoLogCallVisitor::class.java))).thenAnswer { invocation ->
+ val visitor = invocation.arguments[1] as ProtoLogCallVisitor
+
+ visitor.processCall(code.findAll(MethodCallExpr::class.java)[0], "test",
+ LogLevel.WARN, LogGroup("TEST_GROUP", true, true, "WM_TEST"))
+
+ invocation.arguments[0] as CompilationUnit
+ }
+
+ val out = sourceJarWriter.processClass(code)
+
+ val ifStmts = code.findAll(IfStmt::class.java)
+ assertEquals(1, ifStmts.size)
+ val ifStmt = ifStmts[0]
+ assertEquals("TEST_GROUP.${Constants.IS_LOG_TO_ANY_METHOD}()",
+ ifStmt.condition.toString())
+ assertFalse(ifStmt.elseStmt.isPresent)
+ assertEquals(1, ifStmt.thenStmt.childNodes.size)
+ val methodCall = ifStmt.thenStmt.findAll(MethodCallExpr::class.java)[0] as MethodCallExpr
+ assertEquals(PROTO_LOG_IMPL_PATH, methodCall.scope.get().toString())
+ assertEquals("w", methodCall.name.asString())
+ assertEquals(5, methodCall.arguments.size)
+ assertEquals("TEST_GROUP", methodCall.arguments[0].toString())
+ assertEquals("1282022424", methodCall.arguments[1].toString())
+ assertEquals(0.toString(), methodCall.arguments[2].toString())
+ assertEquals(TRANSFORMED_CODE_NO_PARAMS, out)
+ }
+
+ @Test
+ fun processClass_textDisabled() {
+ val code = StaticJavaParser.parse(TEST_CODE)
+
+ Mockito.`when`(processor.process(any(CompilationUnit::class.java),
+ any(ProtoLogCallVisitor::class.java))).thenAnswer { invocation ->
+ val visitor = invocation.arguments[1] as ProtoLogCallVisitor
+
+ visitor.processCall(code.findAll(MethodCallExpr::class.java)[0], "test %d %f",
+ LogLevel.WARN, LogGroup("TEST_GROUP", true, false, "WM_TEST"))
+
+ invocation.arguments[0] as CompilationUnit
+ }
+
+ val out = sourceJarWriter.processClass(code)
+
+ val ifStmts = code.findAll(IfStmt::class.java)
+ assertEquals(1, ifStmts.size)
+ val ifStmt = ifStmts[0]
+ assertEquals("TEST_GROUP.${Constants.IS_LOG_TO_ANY_METHOD}()",
+ ifStmt.condition.toString())
+ assertFalse(ifStmt.elseStmt.isPresent)
+ assertEquals(3, ifStmt.thenStmt.childNodes.size)
+ val methodCall = ifStmt.thenStmt.findAll(MethodCallExpr::class.java)[0] as MethodCallExpr
+ assertEquals(PROTO_LOG_IMPL_PATH, methodCall.scope.get().toString())
+ assertEquals("w", methodCall.name.asString())
+ assertEquals(6, methodCall.arguments.size)
+ assertEquals("TEST_GROUP", methodCall.arguments[0].toString())
+ assertEquals("835524026", methodCall.arguments[1].toString())
+ assertEquals(0b1001.toString(), methodCall.arguments[2].toString())
+ assertEquals("null", methodCall.arguments[3].toString())
+ assertEquals("protoLogParam0", methodCall.arguments[4].toString())
+ assertEquals("protoLogParam1", methodCall.arguments[5].toString())
+ assertEquals(TRANSFORMED_CODE_TEXT_DISABLED, out)
+ }
+
+ @Test
+ fun processClass_textDisabledMultiline() {
+ val code = StaticJavaParser.parse(TEST_CODE_MULTILINE)
+
+ Mockito.`when`(processor.process(any(CompilationUnit::class.java),
+ any(ProtoLogCallVisitor::class.java))).thenAnswer { invocation ->
+ val visitor = invocation.arguments[1] as ProtoLogCallVisitor
+
+ visitor.processCall(code.findAll(MethodCallExpr::class.java)[0],
+ "test %d %f abc %s\n test", LogLevel.WARN, LogGroup("TEST_GROUP",
+ true, false, "WM_TEST"))
+
+ invocation.arguments[0] as CompilationUnit
+ }
+
+ val out = sourceJarWriter.processClass(code)
+
+ val ifStmts = code.findAll(IfStmt::class.java)
+ assertEquals(1, ifStmts.size)
+ val ifStmt = ifStmts[0]
+ assertEquals("TEST_GROUP.${Constants.IS_LOG_TO_ANY_METHOD}()",
+ ifStmt.condition.toString())
+ assertFalse(ifStmt.elseStmt.isPresent)
+ assertEquals(4, ifStmt.thenStmt.childNodes.size)
+ val methodCall = ifStmt.thenStmt.findAll(MethodCallExpr::class.java)[1] as MethodCallExpr
+ assertEquals(PROTO_LOG_IMPL_PATH, methodCall.scope.get().toString())
+ assertEquals("w", methodCall.name.asString())
+ assertEquals(7, methodCall.arguments.size)
+ assertEquals("TEST_GROUP", methodCall.arguments[0].toString())
+ assertEquals("-986393606", methodCall.arguments[1].toString())
+ assertEquals(0b001001.toString(), methodCall.arguments[2].toString())
+ assertEquals("null", methodCall.arguments[3].toString())
+ assertEquals("protoLogParam0", methodCall.arguments[4].toString())
+ assertEquals("protoLogParam1", methodCall.arguments[5].toString())
+ assertEquals("protoLogParam2", methodCall.arguments[6].toString())
+ assertEquals(TRANSFORMED_CODE_MULTILINE_TEXT_DISABLED, out)
+ }
+
+ @Test
+ fun processClass_disabled() {
+ val code = StaticJavaParser.parse(TEST_CODE)
+
+ Mockito.`when`(processor.process(any(CompilationUnit::class.java),
+ any(ProtoLogCallVisitor::class.java))).thenAnswer { invocation ->
+ val visitor = invocation.arguments[1] as ProtoLogCallVisitor
+
+ visitor.processCall(code.findAll(MethodCallExpr::class.java)[0], "test %d %f",
+ LogLevel.WARN, LogGroup("TEST_GROUP", false, true, "WM_TEST"))
+
+ invocation.arguments[0] as CompilationUnit
+ }
+
+ val out = sourceJarWriter.processClass(code)
+
+ val ifStmts = code.findAll(IfStmt::class.java)
+ assertEquals(1, ifStmts.size)
+ val ifStmt = ifStmts[0]
+ assertEquals("false", ifStmt.condition.toString())
+ assertEquals(TRANSFORMED_CODE_DISABLED, out)
+ }
+
+ @Test
+ fun processClass_disabledMultiline() {
+ val code = StaticJavaParser.parse(TEST_CODE_MULTILINE)
+
+ Mockito.`when`(processor.process(any(CompilationUnit::class.java),
+ any(ProtoLogCallVisitor::class.java))).thenAnswer { invocation ->
+ val visitor = invocation.arguments[1] as ProtoLogCallVisitor
+
+ visitor.processCall(code.findAll(MethodCallExpr::class.java)[0],
+ "test %d %f abc %s\n test", LogLevel.WARN, LogGroup("TEST_GROUP",
+ false, true, "WM_TEST"))
+
+ invocation.arguments[0] as CompilationUnit
+ }
+
+ val out = sourceJarWriter.processClass(code)
+
+ val ifStmts = code.findAll(IfStmt::class.java)
+ assertEquals(1, ifStmts.size)
+ val ifStmt = ifStmts[0]
+ assertEquals("false", ifStmt.condition.toString())
+ assertEquals(TRANSFORMED_CODE_MULTILINE_DISABLED, out)
+ }
+}
diff --git a/tools/protologtool/tests/com/android/protologtool/ViewerConfigBuilderTest.kt b/tools/protologtool/tests/com/android/protologtool/ViewerConfigBuilderTest.kt
new file mode 100644
index 0000000..53d2e8b
--- /dev/null
+++ b/tools/protologtool/tests/com/android/protologtool/ViewerConfigBuilderTest.kt
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.protologtool
+
+import com.android.json.stream.JsonReader
+import com.github.javaparser.ast.CompilationUnit
+import com.github.javaparser.ast.expr.MethodCallExpr
+import org.junit.Assert.assertEquals
+import org.junit.Test
+import org.mockito.Mockito
+import java.io.StringReader
+
+class ViewerConfigBuilderTest {
+ companion object {
+ private val TAG1 = "WM_TEST"
+ private val TAG2 = "WM_DEBUG"
+ private val TEST1 = ViewerConfigParser.ConfigEntry("test1", LogLevel.INFO.name, TAG1)
+ private val TEST2 = ViewerConfigParser.ConfigEntry("test2", LogLevel.DEBUG.name, TAG2)
+ private val TEST3 = ViewerConfigParser.ConfigEntry("test3", LogLevel.ERROR.name, TAG2)
+ }
+
+ private val processor: ProtoLogCallProcessor = Mockito.mock(ProtoLogCallProcessor::class.java)
+ private val configBuilder = ViewerConfigBuilder(processor)
+ private val dummyCompilationUnit = CompilationUnit()
+
+ private fun <T> any(type: Class<T>): T = Mockito.any<T>(type)
+
+ private fun parseConfig(json: String): Map<Int, ViewerConfigParser.ConfigEntry> {
+ return ViewerConfigParser().parseConfig(JsonReader(StringReader(json)))
+ }
+
+ @Test
+ fun processClass() {
+ Mockito.`when`(processor.process(any(CompilationUnit::class.java),
+ any(ProtoLogCallVisitor::class.java))).thenAnswer { invocation ->
+ val visitor = invocation.arguments[1] as ProtoLogCallVisitor
+
+ visitor.processCall(MethodCallExpr(), TEST1.messageString, LogLevel.INFO,
+ LogGroup("TEST_GROUP", true, true, TAG1))
+ visitor.processCall(MethodCallExpr(), TEST2.messageString, LogLevel.DEBUG,
+ LogGroup("DEBUG_GROUP", true, true, TAG2))
+ visitor.processCall(MethodCallExpr(), TEST3.messageString, LogLevel.ERROR,
+ LogGroup("DEBUG_GROUP", true, true, TAG2))
+
+ invocation.arguments[0] as CompilationUnit
+ }
+
+ configBuilder.processClass(dummyCompilationUnit)
+
+ val parsedConfig = parseConfig(configBuilder.build())
+ assertEquals(3, parsedConfig.size)
+ assertEquals(TEST1, parsedConfig[CodeUtils.hash(TEST1.messageString,
+ LogLevel.INFO)])
+ assertEquals(TEST2, parsedConfig[CodeUtils.hash(TEST2.messageString,
+ LogLevel.DEBUG)])
+ assertEquals(TEST3, parsedConfig[CodeUtils.hash(TEST3.messageString,
+ LogLevel.ERROR)])
+ }
+
+ @Test
+ fun processClass_nonUnique() {
+ Mockito.`when`(processor.process(any(CompilationUnit::class.java),
+ any(ProtoLogCallVisitor::class.java))).thenAnswer { invocation ->
+ val visitor = invocation.arguments[1] as ProtoLogCallVisitor
+
+ visitor.processCall(MethodCallExpr(), TEST1.messageString, LogLevel.INFO,
+ LogGroup("TEST_GROUP", true, true, TAG1))
+ visitor.processCall(MethodCallExpr(), TEST1.messageString, LogLevel.INFO,
+ LogGroup("TEST_GROUP", true, true, TAG1))
+ visitor.processCall(MethodCallExpr(), TEST1.messageString, LogLevel.INFO,
+ LogGroup("TEST_GROUP", true, true, TAG1))
+
+ invocation.arguments[0] as CompilationUnit
+ }
+
+ configBuilder.processClass(dummyCompilationUnit)
+
+ val parsedConfig = parseConfig(configBuilder.build())
+ assertEquals(1, parsedConfig.size)
+ assertEquals(TEST1, parsedConfig[CodeUtils.hash(TEST1.messageString, LogLevel.INFO)])
+ }
+
+ @Test
+ fun processClass_disabled() {
+ Mockito.`when`(processor.process(any(CompilationUnit::class.java),
+ any(ProtoLogCallVisitor::class.java))).thenAnswer { invocation ->
+ val visitor = invocation.arguments[1] as ProtoLogCallVisitor
+
+ visitor.processCall(MethodCallExpr(), TEST1.messageString, LogLevel.INFO,
+ LogGroup("TEST_GROUP", true, true, TAG1))
+ visitor.processCall(MethodCallExpr(), TEST2.messageString, LogLevel.DEBUG,
+ LogGroup("DEBUG_GROUP", false, true, TAG2))
+ visitor.processCall(MethodCallExpr(), TEST3.messageString, LogLevel.ERROR,
+ LogGroup("DEBUG_GROUP", true, false, TAG2))
+
+ invocation.arguments[0] as CompilationUnit
+ }
+
+ configBuilder.processClass(dummyCompilationUnit)
+
+ val parsedConfig = parseConfig(configBuilder.build())
+ assertEquals(2, parsedConfig.size)
+ assertEquals(TEST1, parsedConfig[CodeUtils.hash(TEST1.messageString, LogLevel.INFO)])
+ assertEquals(TEST3, parsedConfig[CodeUtils.hash(TEST3.messageString, LogLevel.ERROR)])
+ }
+}
diff --git a/tools/protologtool/tests/com/android/protologtool/ViewerConfigParserTest.kt b/tools/protologtool/tests/com/android/protologtool/ViewerConfigParserTest.kt
new file mode 100644
index 0000000..c0cea73
--- /dev/null
+++ b/tools/protologtool/tests/com/android/protologtool/ViewerConfigParserTest.kt
@@ -0,0 +1,327 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.protologtool
+
+import com.android.json.stream.JsonReader
+import org.junit.Test
+import java.io.StringReader
+import org.junit.Assert.assertEquals
+
+class ViewerConfigParserTest {
+ private val parser = ViewerConfigParser()
+
+ private fun getJSONReader(str: String): JsonReader {
+ return JsonReader(StringReader(str))
+ }
+
+ @Test
+ fun parseMessage() {
+ val json = """
+ {
+ "message": "Test completed successfully: %b",
+ "level": "ERROR",
+ "group": "GENERIC_WM"
+ }
+ """
+ val msg = parser.parseMessage(getJSONReader(json))
+ assertEquals("Test completed successfully: %b", msg.messageString)
+ assertEquals("ERROR", msg.level)
+ assertEquals("GENERIC_WM", msg.groupName)
+ }
+
+ @Test
+ fun parseMessage_reorder() {
+ val json = """
+ {
+ "group": "GENERIC_WM",
+ "level": "ERROR",
+ "message": "Test completed successfully: %b"
+ }
+ """
+ val msg = parser.parseMessage(getJSONReader(json))
+ assertEquals("Test completed successfully: %b", msg.messageString)
+ assertEquals("ERROR", msg.level)
+ assertEquals("GENERIC_WM", msg.groupName)
+ }
+
+ @Test
+ fun parseMessage_unknownEntry() {
+ val json = """
+ {
+ "unknown": "unknown entries should not block parsing",
+ "message": "Test completed successfully: %b",
+ "level": "ERROR",
+ "group": "GENERIC_WM"
+ }
+ """
+ val msg = parser.parseMessage(getJSONReader(json))
+ assertEquals("Test completed successfully: %b", msg.messageString)
+ assertEquals("ERROR", msg.level)
+ assertEquals("GENERIC_WM", msg.groupName)
+ }
+
+ @Test(expected = InvalidViewerConfigException::class)
+ fun parseMessage_noMessage() {
+ val json = """
+ {
+ "level": "ERROR",
+ "group": "GENERIC_WM"
+ }
+ """
+ parser.parseMessage(getJSONReader(json))
+ }
+
+ @Test(expected = InvalidViewerConfigException::class)
+ fun parseMessage_noLevel() {
+ val json = """
+ {
+ "message": "Test completed successfully: %b",
+ "group": "GENERIC_WM"
+ }
+ """
+ parser.parseMessage(getJSONReader(json))
+ }
+
+ @Test(expected = InvalidViewerConfigException::class)
+ fun parseMessage_noGroup() {
+ val json = """
+ {
+ "message": "Test completed successfully: %b",
+ "level": "ERROR"
+ }
+ """
+ parser.parseMessage(getJSONReader(json))
+ }
+
+ @Test
+ fun parseGroup() {
+ val json = """
+ {
+ "tag": "WindowManager"
+ }
+ """
+ val group = parser.parseGroup(getJSONReader(json))
+ assertEquals("WindowManager", group.tag)
+ }
+
+ @Test
+ fun parseGroup_unknownEntry() {
+ val json = """
+ {
+ "unknown": "unknown entries should not block parsing",
+ "tag": "WindowManager"
+ }
+ """
+ val group = parser.parseGroup(getJSONReader(json))
+ assertEquals("WindowManager", group.tag)
+ }
+
+ @Test(expected = InvalidViewerConfigException::class)
+ fun parseGroup_noTag() {
+ val json = """
+ {
+ }
+ """
+ parser.parseGroup(getJSONReader(json))
+ }
+
+ @Test
+ fun parseMessages() {
+ val json = """
+ {
+ "70933285": {
+ "message": "Test completed successfully: %b",
+ "level": "ERROR",
+ "group": "GENERIC_WM"
+ },
+ "1792430067": {
+ "message": "Attempted to add window to a display that does not exist: %d. Aborting.",
+ "level": "WARN",
+ "group": "ERROR_WM"
+ }
+ }
+ """
+ val messages = parser.parseMessages(getJSONReader(json))
+ assertEquals(2, messages.size)
+ val msg1 =
+ ViewerConfigParser.MessageEntry("Test completed successfully: %b",
+ "ERROR", "GENERIC_WM")
+ val msg2 =
+ ViewerConfigParser.MessageEntry("Attempted to add window to a display that " +
+ "does not exist: %d. Aborting.", "WARN", "ERROR_WM")
+
+ assertEquals(msg1, messages[70933285])
+ assertEquals(msg2, messages[1792430067])
+ }
+
+ @Test(expected = InvalidViewerConfigException::class)
+ fun parseMessages_invalidHash() {
+ val json = """
+ {
+ "invalid": {
+ "message": "Test completed successfully: %b",
+ "level": "ERROR",
+ "group": "GENERIC_WM"
+ }
+ }
+ """
+ parser.parseMessages(getJSONReader(json))
+ }
+
+ @Test
+ fun parseGroups() {
+ val json = """
+ {
+ "GENERIC_WM": {
+ "tag": "WindowManager"
+ },
+ "ERROR_WM": {
+ "tag": "WindowManagerError"
+ }
+ }
+ """
+ val groups = parser.parseGroups(getJSONReader(json))
+ assertEquals(2, groups.size)
+ val grp1 = ViewerConfigParser.GroupEntry("WindowManager")
+ val grp2 = ViewerConfigParser.GroupEntry("WindowManagerError")
+ assertEquals(grp1, groups["GENERIC_WM"])
+ assertEquals(grp2, groups["ERROR_WM"])
+ }
+
+ @Test
+ fun parseConfig() {
+ val json = """
+ {
+ "version": "${Constants.VERSION}",
+ "messages": {
+ "70933285": {
+ "message": "Test completed successfully: %b",
+ "level": "ERROR",
+ "group": "GENERIC_WM"
+ }
+ },
+ "groups": {
+ "GENERIC_WM": {
+ "tag": "WindowManager"
+ }
+ }
+ }
+ """
+ val config = parser.parseConfig(getJSONReader(json))
+ assertEquals(1, config.size)
+ val cfg1 = ViewerConfigParser.ConfigEntry("Test completed successfully: %b",
+ "ERROR", "WindowManager")
+ assertEquals(cfg1, config[70933285])
+ }
+
+ @Test(expected = InvalidViewerConfigException::class)
+ fun parseConfig_invalidVersion() {
+ val json = """
+ {
+ "version": "invalid",
+ "messages": {
+ "70933285": {
+ "message": "Test completed successfully: %b",
+ "level": "ERROR",
+ "group": "GENERIC_WM"
+ }
+ },
+ "groups": {
+ "GENERIC_WM": {
+ "tag": "WindowManager"
+ }
+ }
+ }
+ """
+ parser.parseConfig(getJSONReader(json))
+ }
+
+ @Test(expected = InvalidViewerConfigException::class)
+ fun parseConfig_noVersion() {
+ val json = """
+ {
+ "messages": {
+ "70933285": {
+ "message": "Test completed successfully: %b",
+ "level": "ERROR",
+ "group": "GENERIC_WM"
+ }
+ },
+ "groups": {
+ "GENERIC_WM": {
+ "tag": "WindowManager"
+ }
+ }
+ }
+ """
+ parser.parseConfig(getJSONReader(json))
+ }
+
+ @Test(expected = InvalidViewerConfigException::class)
+ fun parseConfig_noMessages() {
+ val json = """
+ {
+ "version": "${Constants.VERSION}",
+ "groups": {
+ "GENERIC_WM": {
+ "tag": "WindowManager"
+ }
+ }
+ }
+ """
+ parser.parseConfig(getJSONReader(json))
+ }
+
+ @Test(expected = InvalidViewerConfigException::class)
+ fun parseConfig_noGroups() {
+ val json = """
+ {
+ "version": "${Constants.VERSION}",
+ "messages": {
+ "70933285": {
+ "message": "Test completed successfully: %b",
+ "level": "ERROR",
+ "group": "GENERIC_WM"
+ }
+ }
+ }
+ """
+ parser.parseConfig(getJSONReader(json))
+ }
+
+ @Test(expected = InvalidViewerConfigException::class)
+ fun parseConfig_missingGroup() {
+ val json = """
+ {
+ "version": "${Constants.VERSION}",
+ "messages": {
+ "70933285": {
+ "message": "Test completed successfully: %b",
+ "level": "ERROR",
+ "group": "GENERIC_WM"
+ }
+ },
+ "groups": {
+ "ERROR_WM": {
+ "tag": "WindowManager"
+ }
+ }
+ }
+ """
+ val config = parser.parseConfig(getJSONReader(json))
+ }
+}