Merge "Track brightness changes in nits rather than backlight values."
diff --git a/Android.bp b/Android.bp
index 2dcbc92..69ee848 100644
--- a/Android.bp
+++ b/Android.bp
@@ -256,6 +256,7 @@
"core/java/android/service/euicc/IGetEidCallback.aidl",
"core/java/android/service/euicc/IGetEuiccInfoCallback.aidl",
"core/java/android/service/euicc/IGetEuiccProfileInfoListCallback.aidl",
+ "core/java/android/service/euicc/IGetOtaStatusCallback.aidl",
"core/java/android/service/euicc/IRetainSubscriptionsForFactoryResetCallback.aidl",
"core/java/android/service/euicc/ISwitchToSubscriptionCallback.aidl",
"core/java/android/service/euicc/IUpdateSubscriptionNicknameCallback.aidl",
@@ -721,11 +722,13 @@
],
srcs: [
+ "core/proto/android/os/batterytype.proto",
"core/proto/android/os/cpufreq.proto",
"core/proto/android/os/cpuinfo.proto",
"core/proto/android/os/kernelwake.proto",
"core/proto/android/os/pagetypeinfo.proto",
"core/proto/android/os/procrank.proto",
+ "core/proto/android/os/ps.proto",
"core/proto/android/os/system_properties.proto",
],
diff --git a/Android.mk b/Android.mk
index 1f37326..0e363d2 100644
--- a/Android.mk
+++ b/Android.mk
@@ -40,6 +40,10 @@
frameworks/base/telephony/java/android/telephony/mbms/StreamingServiceInfo.aidl \
frameworks/base/telephony/java/android/telephony/ServiceState.aidl \
frameworks/base/telephony/java/android/telephony/SubscriptionInfo.aidl \
+ frameworks/base/telephony/java/android/telephony/CellIdentityCdma.aidl \
+ frameworks/base/telephony/java/android/telephony/CellIdentityGsm.aidl \
+ frameworks/base/telephony/java/android/telephony/CellIdentityLte.aidl \
+ frameworks/base/telephony/java/android/telephony/CellIdentityWcdma.aidl \
frameworks/base/telephony/java/android/telephony/CellInfo.aidl \
frameworks/base/telephony/java/android/telephony/SignalStrength.aidl \
frameworks/base/telephony/java/android/telephony/IccOpenLogicalChannelResponse.aidl \
@@ -248,12 +252,26 @@
system/netd/server/binder/android/net/UidRange.aidl \
frameworks/base/telephony/java/android/telephony/PcoData.aidl \
+aidl_parcelables :=
+define stubs-to-aidl-parcelables
+ gen := $(TARGET_OUT_COMMON_INTERMEDIATES)/$1.aidl
+ aidl_parcelables += $$(gen)
+ $$(gen): $(call java-lib-header-files,$1) | $(HOST_OUT_EXECUTABLES)/sdkparcelables
+ @echo Extract SDK parcelables: $$@
+ rm -f $$@
+ $(HOST_OUT_EXECUTABLES)/sdkparcelables $$< $$@
+endef
+
+$(foreach stubs,android_stubs_current android_test_stubs_current android_system_stubs_current,\
+ $(eval $(call stubs-to-aidl-parcelables,$(stubs))))
+
gen := $(TARGET_OUT_COMMON_INTERMEDIATES)/framework.aidl
-$(gen): PRIVATE_SRC_FILES := $(aidl_files)
-ALL_SDK_FILES += $(gen)
-$(gen): $(aidl_files) | $(AIDL)
- @echo Aidl Preprocess: $@
- $(hide) $(AIDL) --preprocess $@ $(PRIVATE_SRC_FILES)
+.KATI_RESTAT: $(gen)
+$(gen): $(aidl_parcelables)
+ @echo Combining SDK parcelables: $@
+ rm -f $@.tmp
+ cat $^ | sort -u > $@.tmp
+ $(call commit-change-for-toc,$@)
# the documentation
# ============================================================
@@ -550,8 +568,6 @@
include $(BUILD_DROIDDOC)
-# $(gen), i.e. framework.aidl, is also needed while building against the current stub.
-$(full_target): $(gen)
$(INTERNAL_PLATFORM_API_FILE): $(full_target)
$(call dist-for-goals,sdk,$(INTERNAL_PLATFORM_API_FILE))
@@ -587,8 +603,6 @@
include $(BUILD_DROIDDOC)
-# $(gen), i.e. framework.aidl, is also needed while building against the current stub.
-$(full_target): $(gen)
$(INTERNAL_PLATFORM_SYSTEM_API_FILE): $(full_target)
$(call dist-for-goals,sdk,$(INTERNAL_PLATFORM_SYSTEM_API_FILE))
@@ -625,8 +639,6 @@
include $(BUILD_DROIDDOC)
-# $(gen), i.e. framework.aidl, is also needed while building against the current stub.
-$(full_target): $(gen)
$(INTERNAL_PLATFORM_TEST_API_FILE): $(full_target)
$(call dist-for-goals,sdk,$(INTERNAL_PLATFORM_TEST_API_FILE))
@@ -656,9 +668,6 @@
include $(BUILD_DROIDDOC)
-# $(gen), i.e. framework.aidl, is also needed while building against the current stub.
-$(full_target): $(gen)
-
# Run this for checkbuild
checkbuild: doc-comment-check-docs
# Check comment when you are updating the API
diff --git a/api/current.txt b/api/current.txt
index 8375b4d..def2b7f 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -6338,7 +6338,7 @@
method public android.os.UserHandle createAndManageUser(android.content.ComponentName, java.lang.String, android.content.ComponentName, android.os.PersistableBundle, int);
method public void enableSystemApp(android.content.ComponentName, java.lang.String);
method public int enableSystemApp(android.content.ComponentName, android.content.Intent);
- method public android.security.AttestedKeyPair generateKeyPair(android.content.ComponentName, java.lang.String, android.security.keystore.KeyGenParameterSpec);
+ method public android.security.AttestedKeyPair generateKeyPair(android.content.ComponentName, java.lang.String, android.security.keystore.KeyGenParameterSpec, int);
method public java.lang.String[] getAccountTypesWithManagementDisabled();
method public java.util.List<android.content.ComponentName> getActiveAdmins();
method public java.util.Set<java.lang.String> getAffiliationIds(android.content.ComponentName);
@@ -6572,6 +6572,10 @@
field public static final int FLAG_EVICT_CREDENTIAL_ENCRYPTION_KEY = 1; // 0x1
field public static final int FLAG_MANAGED_CAN_ACCESS_PARENT = 2; // 0x2
field public static final int FLAG_PARENT_CAN_ACCESS_MANAGED = 1; // 0x1
+ field public static final int ID_TYPE_BASE_INFO = 1; // 0x1
+ field public static final int ID_TYPE_IMEI = 4; // 0x4
+ field public static final int ID_TYPE_MEID = 8; // 0x8
+ field public static final int ID_TYPE_SERIAL = 2; // 0x2
field public static final int KEYGUARD_DISABLE_FEATURES_ALL = 2147483647; // 0x7fffffff
field public static final int KEYGUARD_DISABLE_FEATURES_NONE = 0; // 0x0
field public static final int KEYGUARD_DISABLE_FINGERPRINT = 32; // 0x20
@@ -15488,6 +15492,7 @@
method public <T> T get(android.hardware.camera2.CameraCharacteristics.Key<T>);
method public java.util.List<android.hardware.camera2.CaptureRequest.Key<?>> getAvailableCaptureRequestKeys();
method public java.util.List<android.hardware.camera2.CaptureResult.Key<?>> getAvailableCaptureResultKeys();
+ method public java.util.List<android.hardware.camera2.CaptureRequest.Key<?>> getAvailableSessionKeys();
method public java.util.List<android.hardware.camera2.CameraCharacteristics.Key<?>> getKeys();
field public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> COLOR_CORRECTION_AVAILABLE_ABERRATION_MODES;
field public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> CONTROL_AE_AVAILABLE_ANTIBANDING_MODES;
@@ -15586,6 +15591,7 @@
method public abstract void close();
method public abstract android.hardware.camera2.CaptureRequest.Builder createCaptureRequest(int) throws android.hardware.camera2.CameraAccessException;
method public abstract void createCaptureSession(java.util.List<android.view.Surface>, android.hardware.camera2.CameraCaptureSession.StateCallback, android.os.Handler) throws android.hardware.camera2.CameraAccessException;
+ method public void createCaptureSession(android.hardware.camera2.params.SessionConfiguration) throws android.hardware.camera2.CameraAccessException;
method public abstract void createCaptureSessionByOutputConfigurations(java.util.List<android.hardware.camera2.params.OutputConfiguration>, android.hardware.camera2.CameraCaptureSession.StateCallback, android.os.Handler) throws android.hardware.camera2.CameraAccessException;
method public abstract void createConstrainedHighSpeedCaptureSession(java.util.List<android.view.Surface>, android.hardware.camera2.CameraCaptureSession.StateCallback, android.os.Handler) throws android.hardware.camera2.CameraAccessException;
method public abstract android.hardware.camera2.CaptureRequest.Builder createReprocessCaptureRequest(android.hardware.camera2.TotalCaptureResult) throws android.hardware.camera2.CameraAccessException;
@@ -16129,6 +16135,20 @@
field public static final int RED = 0; // 0x0
}
+ public final class SessionConfiguration {
+ ctor public SessionConfiguration(int, java.util.List<android.hardware.camera2.params.OutputConfiguration>, android.hardware.camera2.CameraCaptureSession.StateCallback, android.os.Handler);
+ method public android.os.Handler getHandler();
+ method public android.hardware.camera2.params.InputConfiguration getInputConfiguration();
+ method public java.util.List<android.hardware.camera2.params.OutputConfiguration> getOutputConfigurations();
+ method public android.hardware.camera2.CaptureRequest getSessionParameters();
+ method public int getSessionType();
+ method public android.hardware.camera2.CameraCaptureSession.StateCallback getStateCallback();
+ method public void setInputConfiguration(android.hardware.camera2.params.InputConfiguration);
+ method public void setSessionParameters(android.hardware.camera2.CaptureRequest);
+ field public static final int SESSION_HIGH_SPEED = 1; // 0x1
+ field public static final int SESSION_REGULAR = 0; // 0x0
+ }
+
public final class StreamConfigurationMap {
method public android.util.Size[] getHighResolutionOutputSizes(int);
method public android.util.Range<java.lang.Integer>[] getHighSpeedVideoFpsRanges();
@@ -21702,6 +21722,7 @@
field public static final java.lang.String ACTION_AUDIO_BECOMING_NOISY = "android.media.AUDIO_BECOMING_NOISY";
field public static final java.lang.String ACTION_HDMI_AUDIO_PLUG = "android.media.action.HDMI_AUDIO_PLUG";
field public static final java.lang.String ACTION_HEADSET_PLUG = "android.intent.action.HEADSET_PLUG";
+ field public static final java.lang.String ACTION_MICROPHONE_MUTE_CHANGED = "android.media.action.MICROPHONE_MUTE_CHANGED";
field public static final deprecated java.lang.String ACTION_SCO_AUDIO_STATE_CHANGED = "android.media.SCO_AUDIO_STATE_CHANGED";
field public static final java.lang.String ACTION_SCO_AUDIO_STATE_UPDATED = "android.media.ACTION_SCO_AUDIO_STATE_UPDATED";
field public static final int ADJUST_LOWER = -1; // 0xffffffff
@@ -31553,6 +31574,7 @@
method public final boolean postAtTime(java.lang.Runnable, long);
method public final boolean postAtTime(java.lang.Runnable, java.lang.Object, long);
method public final boolean postDelayed(java.lang.Runnable, long);
+ method public final boolean postDelayed(java.lang.Runnable, java.lang.Object, long);
method public final void removeCallbacks(java.lang.Runnable);
method public final void removeCallbacks(java.lang.Runnable, java.lang.Object);
method public final void removeCallbacksAndMessages(java.lang.Object);
@@ -44264,6 +44286,7 @@
method public int indexOfValue(boolean);
method public int keyAt(int);
method public void put(int, boolean);
+ method public void removeAt(int);
method public int size();
method public boolean valueAt(int);
}
@@ -47758,6 +47781,7 @@
method public java.lang.CharSequence getPackageName();
method public android.view.accessibility.AccessibilityRecord getRecord(int);
method public int getRecordCount();
+ method public int getWindowChanges();
method public void initFromParcel(android.os.Parcel);
method public static android.view.accessibility.AccessibilityEvent obtain(int);
method public static android.view.accessibility.AccessibilityEvent obtain(android.view.accessibility.AccessibilityEvent);
@@ -47802,6 +47826,17 @@
field public static final int TYPE_WINDOWS_CHANGED = 4194304; // 0x400000
field public static final int TYPE_WINDOW_CONTENT_CHANGED = 2048; // 0x800
field public static final int TYPE_WINDOW_STATE_CHANGED = 32; // 0x20
+ field public static final int WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED = 128; // 0x80
+ field public static final int WINDOWS_CHANGE_ACTIVE = 32; // 0x20
+ field public static final int WINDOWS_CHANGE_ADDED = 1; // 0x1
+ field public static final int WINDOWS_CHANGE_BOUNDS = 8; // 0x8
+ field public static final int WINDOWS_CHANGE_CHILDREN = 512; // 0x200
+ field public static final int WINDOWS_CHANGE_FOCUSED = 64; // 0x40
+ field public static final int WINDOWS_CHANGE_LAYER = 16; // 0x10
+ field public static final int WINDOWS_CHANGE_PARENT = 256; // 0x100
+ field public static final int WINDOWS_CHANGE_PIP = 1024; // 0x400
+ field public static final int WINDOWS_CHANGE_REMOVED = 2; // 0x2
+ field public static final int WINDOWS_CHANGE_TITLE = 4; // 0x4
}
public abstract interface AccessibilityEventSource {
diff --git a/api/system-current.txt b/api/system-current.txt
index 3e78167..596474c 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -1340,9 +1340,30 @@
package android.hardware.location {
+ public class ContextHubClient implements java.io.Closeable {
+ method public void close();
+ method public android.hardware.location.ContextHubInfo getAttachedHub();
+ method public int sendMessageToNanoApp(android.hardware.location.NanoAppMessage);
+ }
+
+ public class ContextHubClientCallback {
+ ctor public ContextHubClientCallback();
+ method public void onHubReset(android.hardware.location.ContextHubClient);
+ method public void onMessageFromNanoApp(android.hardware.location.ContextHubClient, android.hardware.location.NanoAppMessage);
+ method public void onNanoAppAborted(android.hardware.location.ContextHubClient, long, int);
+ method public void onNanoAppDisabled(android.hardware.location.ContextHubClient, long);
+ method public void onNanoAppEnabled(android.hardware.location.ContextHubClient, long);
+ method public void onNanoAppLoaded(android.hardware.location.ContextHubClient, long);
+ method public void onNanoAppUnloaded(android.hardware.location.ContextHubClient, long);
+ }
+
public class ContextHubInfo implements android.os.Parcelable {
ctor public ContextHubInfo();
method public int describeContents();
+ method public byte getChreApiMajorVersion();
+ method public byte getChreApiMinorVersion();
+ method public short getChrePatchVersion();
+ method public long getChrePlatformId();
method public int getId();
method public int getMaxPacketLengthBytes();
method public android.hardware.location.MemoryRegion[] getMemoryRegions();
@@ -1362,19 +1383,27 @@
}
public final class ContextHubManager {
- method public int[] findNanoAppOnHub(int, android.hardware.location.NanoAppFilter);
- method public int[] getContextHubHandles();
- method public android.hardware.location.ContextHubInfo getContextHubInfo(int);
- method public android.hardware.location.NanoAppInstanceInfo getNanoAppInstanceInfo(int);
- method public int loadNanoApp(int, android.hardware.location.NanoApp);
- method public int registerCallback(android.hardware.location.ContextHubManager.Callback);
- method public int registerCallback(android.hardware.location.ContextHubManager.Callback, android.os.Handler);
- method public int sendMessage(int, int, android.hardware.location.ContextHubMessage);
- method public int unloadNanoApp(int);
- method public int unregisterCallback(android.hardware.location.ContextHubManager.Callback);
+ method public android.hardware.location.ContextHubClient createClient(android.hardware.location.ContextHubInfo, android.hardware.location.ContextHubClientCallback, java.util.concurrent.Executor);
+ method public android.hardware.location.ContextHubClient createClient(android.hardware.location.ContextHubInfo, android.hardware.location.ContextHubClientCallback);
+ method public android.hardware.location.ContextHubTransaction<java.lang.Void> disableNanoApp(android.hardware.location.ContextHubInfo, long);
+ method public android.hardware.location.ContextHubTransaction<java.lang.Void> enableNanoApp(android.hardware.location.ContextHubInfo, long);
+ method public deprecated int[] findNanoAppOnHub(int, android.hardware.location.NanoAppFilter);
+ method public deprecated int[] getContextHubHandles();
+ method public deprecated android.hardware.location.ContextHubInfo getContextHubInfo(int);
+ method public java.util.List<android.hardware.location.ContextHubInfo> getContextHubs();
+ method public deprecated android.hardware.location.NanoAppInstanceInfo getNanoAppInstanceInfo(int);
+ method public deprecated int loadNanoApp(int, android.hardware.location.NanoApp);
+ method public android.hardware.location.ContextHubTransaction<java.lang.Void> loadNanoApp(android.hardware.location.ContextHubInfo, android.hardware.location.NanoAppBinary);
+ method public android.hardware.location.ContextHubTransaction<java.util.List<android.hardware.location.NanoAppState>> queryNanoApps(android.hardware.location.ContextHubInfo);
+ method public deprecated int registerCallback(android.hardware.location.ContextHubManager.Callback);
+ method public deprecated int registerCallback(android.hardware.location.ContextHubManager.Callback, android.os.Handler);
+ method public deprecated int sendMessage(int, int, android.hardware.location.ContextHubMessage);
+ method public deprecated int unloadNanoApp(int);
+ method public android.hardware.location.ContextHubTransaction<java.lang.Void> unloadNanoApp(android.hardware.location.ContextHubInfo, long);
+ method public deprecated int unregisterCallback(android.hardware.location.ContextHubManager.Callback);
}
- public static abstract class ContextHubManager.Callback {
+ public static abstract deprecated class ContextHubManager.Callback {
ctor protected ContextHubManager.Callback();
method public abstract void onMessageReceipt(int, int, android.hardware.location.ContextHubMessage);
}
@@ -1392,6 +1421,37 @@
field public static final android.os.Parcelable.Creator<android.hardware.location.ContextHubMessage> CREATOR;
}
+ public class ContextHubTransaction<T> {
+ method public int getType();
+ method public void setOnCompleteListener(android.hardware.location.ContextHubTransaction.OnCompleteListener<T>, java.util.concurrent.Executor);
+ method public void setOnCompleteListener(android.hardware.location.ContextHubTransaction.OnCompleteListener<T>);
+ method public static java.lang.String typeToString(int, boolean);
+ method public android.hardware.location.ContextHubTransaction.Response<T> waitForResponse(long, java.util.concurrent.TimeUnit) throws java.lang.InterruptedException, java.util.concurrent.TimeoutException;
+ field public static final int RESULT_FAILED_AT_HUB = 5; // 0x5
+ field public static final int RESULT_FAILED_BAD_PARAMS = 2; // 0x2
+ field public static final int RESULT_FAILED_BUSY = 4; // 0x4
+ field public static final int RESULT_FAILED_HAL_UNAVAILABLE = 8; // 0x8
+ field public static final int RESULT_FAILED_SERVICE_INTERNAL_FAILURE = 7; // 0x7
+ field public static final int RESULT_FAILED_TIMEOUT = 6; // 0x6
+ field public static final int RESULT_FAILED_UNINITIALIZED = 3; // 0x3
+ field public static final int RESULT_FAILED_UNKNOWN = 1; // 0x1
+ field public static final int RESULT_SUCCESS = 0; // 0x0
+ field public static final int TYPE_DISABLE_NANOAPP = 3; // 0x3
+ field public static final int TYPE_ENABLE_NANOAPP = 2; // 0x2
+ field public static final int TYPE_LOAD_NANOAPP = 0; // 0x0
+ field public static final int TYPE_QUERY_NANOAPPS = 4; // 0x4
+ field public static final int TYPE_UNLOAD_NANOAPP = 1; // 0x1
+ }
+
+ public static abstract interface ContextHubTransaction.OnCompleteListener<L> {
+ method public abstract void onComplete(android.hardware.location.ContextHubTransaction<L>, android.hardware.location.ContextHubTransaction.Response<L>);
+ }
+
+ public static class ContextHubTransaction.Response<R> {
+ method public R getContents();
+ method public int getResult();
+ }
+
public final class GeofenceHardware {
method public boolean addGeofence(int, int, android.hardware.location.GeofenceHardwareRequest, android.hardware.location.GeofenceHardwareCallback);
method public int[] getMonitoringTypes();
@@ -1508,6 +1568,25 @@
field public static final android.os.Parcelable.Creator<android.hardware.location.NanoApp> CREATOR;
}
+ public final class NanoAppBinary implements android.os.Parcelable {
+ ctor public NanoAppBinary(byte[]);
+ method public int describeContents();
+ method public byte[] getBinary();
+ method public byte[] getBinaryNoHeader();
+ method public int getFlags();
+ method public int getHeaderVersion();
+ method public long getHwHubType();
+ method public long getNanoAppId();
+ method public int getNanoAppVersion();
+ method public byte getTargetChreApiMajorVersion();
+ method public byte getTargetChreApiMinorVersion();
+ method public boolean hasValidHeader();
+ method public boolean isEncrypted();
+ method public boolean isSigned();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.hardware.location.NanoAppBinary> CREATOR;
+ }
+
public class NanoAppFilter {
ctor public NanoAppFilter(long, int, int, long);
method public int describeContents();
@@ -1541,6 +1620,28 @@
field public static final android.os.Parcelable.Creator<android.hardware.location.NanoAppInstanceInfo> CREATOR;
}
+ public final class NanoAppMessage implements android.os.Parcelable {
+ method public static android.hardware.location.NanoAppMessage createMessageFromNanoApp(long, int, byte[], boolean);
+ method public static android.hardware.location.NanoAppMessage createMessageToNanoApp(long, int, byte[]);
+ method public int describeContents();
+ method public byte[] getMessageBody();
+ method public int getMessageType();
+ method public long getNanoAppId();
+ method public boolean isBroadcastMessage();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.hardware.location.NanoAppMessage> CREATOR;
+ }
+
+ public final class NanoAppState implements android.os.Parcelable {
+ ctor public NanoAppState(long, int, boolean);
+ method public int describeContents();
+ method public long getNanoAppId();
+ method public long getNanoAppVersion();
+ method public boolean isEnabled();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.hardware.location.NanoAppState> CREATOR;
+ }
+
}
package android.hardware.radio {
@@ -4368,10 +4469,10 @@
}
public final class StatsManager {
- method public boolean addConfiguration(java.lang.String, byte[], java.lang.String, java.lang.String);
- method public byte[] getData(java.lang.String);
+ method public boolean addConfiguration(long, byte[], java.lang.String, java.lang.String);
+ method public byte[] getData(long);
method public byte[] getMetadata();
- method public boolean removeConfiguration(java.lang.String);
+ method public boolean removeConfiguration(long);
}
}
diff --git a/cmds/content/src/com/android/commands/content/Content.java b/cmds/content/src/com/android/commands/content/Content.java
index 716bc5f..f75678b 100644
--- a/cmds/content/src/com/android/commands/content/Content.java
+++ b/cmds/content/src/com/android/commands/content/Content.java
@@ -32,12 +32,10 @@
import android.os.UserHandle;
import android.text.TextUtils;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
+import libcore.io.Streams;
-import libcore.io.IoUtils;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
/**
* This class is a command line utility for manipulating content. A client
@@ -122,13 +120,14 @@
+ "\n"
+ "usage: adb shell content read --uri <URI> [--user <USER_ID>]\n"
+ " Example:\n"
- + " # cat default ringtone to a file, then pull to host\n"
- + " adb shell 'content read --uri content://settings/system/ringtone >"
- + " /mnt/sdcard/tmp.ogg' && adb pull /mnt/sdcard/tmp.ogg\n"
+ + " adb shell 'content read --uri content://settings/system/ringtone_cache' > host.ogg\n"
+ + "\n"
+ + "usage: adb shell content write --uri <URI> [--user <USER_ID>]\n"
+ + " Example:\n"
+ + " adb shell 'content write --uri content://settings/system/ringtone_cache' < host.ogg\n"
+ "\n"
+ "usage: adb shell content gettype --uri <URI> [--user <USER_ID>]\n"
+ " Example:\n"
- + " # Show the mime-type of the URI\n"
+ " adb shell content gettype --uri content://media/internal/audio/media/\n"
+ "\n";
@@ -139,6 +138,7 @@
private static final String ARGUMENT_QUERY = "query";
private static final String ARGUMENT_CALL = "call";
private static final String ARGUMENT_READ = "read";
+ private static final String ARGUMENT_WRITE = "write";
private static final String ARGUMENT_GET_TYPE = "gettype";
private static final String ARGUMENT_WHERE = "--where";
private static final String ARGUMENT_BIND = "--bind";
@@ -179,6 +179,8 @@
return parseCallCommand();
} else if (ARGUMENT_READ.equals(operation)) {
return parseReadCommand();
+ } else if (ARGUMENT_WRITE.equals(operation)) {
+ return parseWriteCommand();
} else if (ARGUMENT_GET_TYPE.equals(operation)) {
return parseGetTypeCommand();
} else {
@@ -339,6 +341,25 @@
return new ReadCommand(uri, userId);
}
+ private WriteCommand parseWriteCommand() {
+ Uri uri = null;
+ int userId = UserHandle.USER_SYSTEM;
+ for (String argument; (argument = mTokenizer.nextArg())!= null;) {
+ if (ARGUMENT_URI.equals(argument)) {
+ uri = Uri.parse(argumentValueRequired(argument));
+ } else if (ARGUMENT_USER.equals(argument)) {
+ userId = Integer.parseInt(argumentValueRequired(argument));
+ } else {
+ throw new IllegalArgumentException("Unsupported argument: " + argument);
+ }
+ }
+ if (uri == null) {
+ throw new IllegalArgumentException("Content provider URI not specified."
+ + " Did you specify --uri argument?");
+ }
+ return new WriteCommand(uri, userId);
+ }
+
public QueryCommand parseQueryCommand() {
Uri uri = null;
int userId = UserHandle.USER_SYSTEM;
@@ -561,20 +582,21 @@
@Override
public void onExecute(IContentProvider provider) throws Exception {
- final ParcelFileDescriptor fd = provider.openFile(null, mUri, "r", null, null);
- copy(new FileInputStream(fd.getFileDescriptor()), System.out);
+ try (ParcelFileDescriptor fd = provider.openFile(null, mUri, "r", null, null)) {
+ Streams.copy(new FileInputStream(fd.getFileDescriptor()), System.out);
+ }
+ }
+ }
+
+ private static class WriteCommand extends Command {
+ public WriteCommand(Uri uri, int userId) {
+ super(uri, userId);
}
- private static void copy(InputStream is, OutputStream os) throws IOException {
- final byte[] buffer = new byte[8 * 1024];
- int read;
- try {
- while ((read = is.read(buffer)) > -1) {
- os.write(buffer, 0, read);
- }
- } finally {
- IoUtils.closeQuietly(is);
- IoUtils.closeQuietly(os);
+ @Override
+ public void onExecute(IContentProvider provider) throws Exception {
+ try (ParcelFileDescriptor fd = provider.openFile(null, mUri, "w", null, null)) {
+ Streams.copy(System.in, new FileOutputStream(fd.getFileDescriptor()));
}
}
}
diff --git a/cmds/incident_helper/src/ih_util.cpp b/cmds/incident_helper/src/ih_util.cpp
index 4bf956a..e23e80a 100644
--- a/cmds/incident_helper/src/ih_util.cpp
+++ b/cmds/incident_helper/src/ih_util.cpp
@@ -52,6 +52,12 @@
return toLowerStr(trimDefault(s));
}
+static inline bool isNumber(const std::string& s) {
+ std::string::const_iterator it = s.begin();
+ while (it != s.end() && std::isdigit(*it)) ++it;
+ return !s.empty() && it == s.end();
+}
+
// This is similiar to Split in android-base/file.h, but it won't add empty string
static void split(const std::string& line, std::vector<std::string>& words,
const trans_func& func, const std::string& delimiters) {
@@ -86,24 +92,80 @@
return record;
}
+bool getColumnIndices(std::vector<int>& indices, const char** headerNames, const std::string& line) {
+ indices.clear();
+
+ size_t lastIndex = 0;
+ int i = 0;
+ while (headerNames[i] != NULL) {
+ string s = headerNames[i];
+ lastIndex = line.find(s, lastIndex);
+ if (lastIndex == string::npos) {
+ fprintf(stderr, "Bad Task Header: %s\n", line.c_str());
+ return false;
+ }
+ lastIndex += s.length();
+ indices.push_back(lastIndex);
+ i++;
+ }
+
+ return true;
+}
+
record_t parseRecordByColumns(const std::string& line, const std::vector<int>& indices, const std::string& delimiters) {
record_t record;
int lastIndex = 0;
+ int lastBeginning = 0;
int lineSize = (int)line.size();
for (std::vector<int>::const_iterator it = indices.begin(); it != indices.end(); ++it) {
int idx = *it;
- if (lastIndex > idx || idx > lineSize) {
- record.clear(); // The indices is wrong, return empty;
+ if (idx <= lastIndex) {
+ // We saved up until lastIndex last time, so we should start at
+ // lastIndex + 1 this time.
+ idx = lastIndex + 1;
+ }
+ if (idx > lineSize) {
+ if (lastIndex < idx && lastIndex < lineSize) {
+ // There's a little bit more for us to save, which we'll do
+ // outside of the loop.
+ break;
+ }
+ // If we're past the end of the line AND we've already saved everything up to the end.
+ fprintf(stderr, "index wrong: lastIndex: %d, idx: %d, lineSize: %d\n", lastIndex, idx, lineSize);
+ record.clear(); // The indices are wrong, return empty.
return record;
}
while (idx < lineSize && delimiters.find(line[idx++]) == std::string::npos);
record.push_back(trimDefault(line.substr(lastIndex, idx - lastIndex)));
+ lastBeginning = lastIndex;
lastIndex = idx;
}
- record.push_back(trimDefault(line.substr(lastIndex, lineSize - lastIndex)));
+ if (lineSize - lastIndex > 0) {
+ int beginning = lastIndex;
+ if (record.size() == indices.size()) {
+ // We've already encountered all of the columns...put whatever is
+ // left in the last column.
+ record.pop_back();
+ beginning = lastBeginning;
+ }
+ record.push_back(trimDefault(line.substr(beginning, lineSize - beginning)));
+ }
return record;
}
+void printRecord(const record_t& record) {
+ fprintf(stderr, "Record: { ");
+ if (record.size() == 0) {
+ fprintf(stderr, "}\n");
+ return;
+ }
+ for(size_t i = 0; i < record.size(); ++i) {
+ if(i != 0) fprintf(stderr, "\", ");
+ fprintf(stderr, "\"%s", record[i].c_str());
+ }
+ fprintf(stderr, "\" }\n");
+}
+
bool stripPrefix(std::string* line, const char* key, bool endAtDelimiter) {
const auto head = line->find_first_not_of(DEFAULT_WHITESPACE);
if (head == std::string::npos) return false;
@@ -210,7 +272,10 @@
void
Table::addEnumTypeMap(const char* field, const char* enumNames[], const int enumValues[], const int enumSize)
{
- if (mFields.find(field) == mFields.end()) return;
+ if (mFields.find(field) == mFields.end()) {
+ fprintf(stderr, "Field '%s' not found", string(field).c_str());
+ return;
+ }
map<std::string, int> enu;
for (int i = 0; i < enumSize; i++) {
@@ -268,6 +333,8 @@
}
} else if (mEnumValuesByName.find(value) != mEnumValuesByName.end()) {
proto->write(found, mEnumValuesByName[value]);
+ } else if (isNumber(value)) {
+ proto->write(found, toInt(value));
} else {
return false;
}
diff --git a/cmds/incident_helper/src/ih_util.h b/cmds/incident_helper/src/ih_util.h
index 58ef290..b063b2f 100644
--- a/cmds/incident_helper/src/ih_util.h
+++ b/cmds/incident_helper/src/ih_util.h
@@ -56,12 +56,23 @@
record_t parseRecord(const std::string& line, const std::string& delimiters = DEFAULT_WHITESPACE);
/**
+ * Gets the list of end indices of each word in the line and places it in the given vector,
+ * clearing out the vector beforehand. These indices can be used with parseRecordByColumns.
+ * Will return false if there was a problem getting the indices. headerNames
+ * must be NULL terminated.
+ */
+bool getColumnIndices(std::vector<int>& indices, const char* headerNames[], const std::string& line);
+
+/**
* When a text-format table aligns by its vertical position, it is not possible to split them by purely delimiters.
* This function allows to parse record by its header's column position' indices, must in ascending order.
* At the same time, it still looks at the char at index, if it doesn't belong to delimiters, moves forward to find the delimiters.
*/
record_t parseRecordByColumns(const std::string& line, const std::vector<int>& indices, const std::string& delimiters = DEFAULT_WHITESPACE);
+/** Prints record_t to stderr */
+void printRecord(const record_t& record);
+
/**
* When the line starts/ends with the given key, the function returns true
* as well as the line argument is changed to the rest trimmed part of the original.
diff --git a/cmds/incident_helper/src/main.cpp b/cmds/incident_helper/src/main.cpp
index c8a0883..8c6cd78 100644
--- a/cmds/incident_helper/src/main.cpp
+++ b/cmds/incident_helper/src/main.cpp
@@ -16,11 +16,13 @@
#define LOG_TAG "incident_helper"
+#include "parsers/BatteryTypeParser.h"
#include "parsers/CpuFreqParser.h"
#include "parsers/CpuInfoParser.h"
#include "parsers/KernelWakesParser.h"
#include "parsers/PageTypeInfoParser.h"
#include "parsers/ProcrankParser.h"
+#include "parsers/PsParser.h"
#include "parsers/SystemPropertiesParser.h"
#include <android-base/file.h>
@@ -63,6 +65,10 @@
return new CpuInfoParser();
case 2004:
return new CpuFreqParser();
+ case 2005:
+ return new PsParser();
+ case 2006:
+ return new BatteryTypeParser();
default:
return NULL;
}
diff --git a/cmds/incident_helper/src/parsers/BatteryTypeParser.cpp b/cmds/incident_helper/src/parsers/BatteryTypeParser.cpp
new file mode 100644
index 0000000..ced6cf8
--- /dev/null
+++ b/cmds/incident_helper/src/parsers/BatteryTypeParser.cpp
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#define LOG_TAG "incident_helper"
+
+#include <android/util/ProtoOutputStream.h>
+
+#include "frameworks/base/core/proto/android/os/batterytype.proto.h"
+#include "ih_util.h"
+#include "BatteryTypeParser.h"
+
+using namespace android::os;
+
+status_t
+BatteryTypeParser::Parse(const int in, const int out) const
+{
+ Reader reader(in);
+ string line;
+ bool readLine = false;
+
+ ProtoOutputStream proto;
+
+ // parse line by line
+ while (reader.readLine(&line)) {
+ if (line.empty()) continue;
+
+ if (readLine) {
+ fprintf(stderr, "Multiple lines in file. Unsure what to do.\n");
+ break;
+ }
+
+ proto.write(BatteryTypeProto::TYPE, line);
+
+ readLine = true;
+ }
+
+ if (!reader.ok(&line)) {
+ fprintf(stderr, "Bad read from fd %d: %s\n", in, line.c_str());
+ return -1;
+ }
+
+ if (!proto.flush(out)) {
+ fprintf(stderr, "[%s]Error writing proto back\n", this->name.string());
+ return -1;
+ }
+ fprintf(stderr, "[%s]Proto size: %zu bytes\n", this->name.string(), proto.size());
+ return NO_ERROR;
+}
diff --git a/cmds/incident_helper/src/parsers/BatteryTypeParser.h b/cmds/incident_helper/src/parsers/BatteryTypeParser.h
new file mode 100644
index 0000000..ac0c098
--- /dev/null
+++ b/cmds/incident_helper/src/parsers/BatteryTypeParser.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef BATTERY_TYPE_PARSER_H
+#define BATTERY_TYPE_PARSER_H
+
+#include "TextParserBase.h"
+
+using namespace android;
+
+/**
+ * Battery type parser, parses text in file
+ * /sys/class/power_supply/bms/battery_type.
+ */
+class BatteryTypeParser : public TextParserBase {
+public:
+ BatteryTypeParser() : TextParserBase(String8("BatteryTypeParser")) {};
+ ~BatteryTypeParser() {};
+
+ virtual status_t Parse(const int in, const int out) const;
+};
+
+#endif // BATTERY_TYPE_PARSER_H
diff --git a/cmds/incident_helper/src/parsers/CpuInfoParser.cpp b/cmds/incident_helper/src/parsers/CpuInfoParser.cpp
index 3faca00..d73de54 100644
--- a/cmds/incident_helper/src/parsers/CpuInfoParser.cpp
+++ b/cmds/incident_helper/src/parsers/CpuInfoParser.cpp
@@ -49,6 +49,7 @@
vector<int> columnIndices; // task table can't be split by purely delimiter, needs column positions.
record_t record;
int nline = 0;
+ int diff = 0;
bool nextToSwap = false;
bool nextToUsage = false;
@@ -107,18 +108,10 @@
header = parseHeader(line, "[ %]");
nextToUsage = false;
- // NAME is not in the list since the last split index is default to the end of line.
- const char* headerNames[11] = { "PID", "TID", "USER", "PR", "NI", "CPU", "S", "VIRT", "RES", "PCY", "CMD" };
- size_t lastIndex = 0;
- for (int i = 0; i < 11; i++) {
- string s = headerNames[i];
- lastIndex = line.find(s, lastIndex);
- if (lastIndex == string::npos) {
- fprintf(stderr, "Bad Task Header: %s\n", line.c_str());
- return -1;
- }
- lastIndex += s.length();
- columnIndices.push_back(lastIndex);
+ // NAME is not in the list since we need to modify the end of the CMD index.
+ const char* headerNames[] = { "PID", "TID", "USER", "PR", "NI", "CPU", "S", "VIRT", "RES", "PCY", "CMD", NULL };
+ if (!getColumnIndices(columnIndices, headerNames, line)) {
+ return -1;
}
// Need to remove the end index of CMD and use the start index of NAME because CMD values contain spaces.
// for example: ... CMD NAME
@@ -128,12 +121,20 @@
int endCMD = columnIndices.back();
columnIndices.pop_back();
columnIndices.push_back(line.find("NAME", endCMD) - 1);
+ // Add NAME index to complete the column list.
+ columnIndices.push_back(columnIndices.back() + 4);
continue;
}
record = parseRecordByColumns(line, columnIndices);
- if (record.size() != header.size()) {
- fprintf(stderr, "[%s]Line %d has missing fields:\n%s\n", this->name.string(), nline, line.c_str());
+ diff = record.size() - header.size();
+ if (diff < 0) {
+ fprintf(stderr, "[%s]Line %d has %d missing fields\n%s\n", this->name.string(), nline, -diff, line.c_str());
+ printRecord(record);
+ continue;
+ } else if (diff > 0) {
+ fprintf(stderr, "[%s]Line %d has %d extra fields\n%s\n", this->name.string(), nline, diff, line.c_str());
+ printRecord(record);
continue;
}
diff --git a/cmds/incident_helper/src/parsers/KernelWakesParser.cpp b/cmds/incident_helper/src/parsers/KernelWakesParser.cpp
index ada4a5d..cae51ab 100644
--- a/cmds/incident_helper/src/parsers/KernelWakesParser.cpp
+++ b/cmds/incident_helper/src/parsers/KernelWakesParser.cpp
@@ -47,10 +47,14 @@
// parse for each record, the line delimiter is \t only!
record = parseRecord(line, TAB_DELIMITER);
- if (record.size() != header.size()) {
+ if (record.size() < header.size()) {
// TODO: log this to incident report!
fprintf(stderr, "[%s]Line %d has missing fields\n%s\n", this->name.string(), nline, line.c_str());
continue;
+ } else if (record.size() > header.size()) {
+ // TODO: log this to incident report!
+ fprintf(stderr, "[%s]Line %d has extra fields\n%s\n", this->name.string(), nline, line.c_str());
+ continue;
}
long long token = proto.start(KernelWakeSources::WAKEUP_SOURCES);
diff --git a/cmds/incident_helper/src/parsers/PsParser.cpp b/cmds/incident_helper/src/parsers/PsParser.cpp
new file mode 100644
index 0000000..e9014ca
--- /dev/null
+++ b/cmds/incident_helper/src/parsers/PsParser.cpp
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#define LOG_TAG "incident_helper"
+
+#include <android/util/ProtoOutputStream.h>
+
+#include "frameworks/base/core/proto/android/os/ps.proto.h"
+#include "ih_util.h"
+#include "PsParser.h"
+
+using namespace android::os;
+
+status_t PsParser::Parse(const int in, const int out) const {
+ Reader reader(in);
+ string line;
+ header_t header; // the header of /d/wakeup_sources
+ vector<int> columnIndices; // task table can't be split by purely delimiter, needs column positions.
+ record_t record; // retain each record
+ int nline = 0;
+ int diff = 0;
+
+ ProtoOutputStream proto;
+ Table table(PsDumpProto::Process::_FIELD_NAMES, PsDumpProto::Process::_FIELD_IDS, PsDumpProto::Process::_FIELD_COUNT);
+ const char* pcyNames[] = { "fg", "bg", "ta" };
+ const int pcyValues[] = {PsDumpProto::Process::POLICY_FG, PsDumpProto::Process::POLICY_BG, PsDumpProto::Process::POLICY_TA};
+ table.addEnumTypeMap("pcy", pcyNames, pcyValues, 3);
+ const char* sNames[] = { "D", "R", "S", "T", "t", "X", "Z" };
+ const int sValues[] = {PsDumpProto::Process::STATE_D, PsDumpProto::Process::STATE_R, PsDumpProto::Process::STATE_S, PsDumpProto::Process::STATE_T, PsDumpProto::Process::STATE_TRACING, PsDumpProto::Process::STATE_X, PsDumpProto::Process::STATE_Z};
+ table.addEnumTypeMap("s", sNames, sValues, 7);
+
+ // Parse line by line
+ while (reader.readLine(&line)) {
+ if (line.empty()) continue;
+
+ if (nline++ == 0) {
+ header = parseHeader(line, DEFAULT_WHITESPACE);
+
+ const char* headerNames[] = { "LABEL", "USER", "PID", "TID", "PPID", "VSZ", "RSS", "WCHAN", "ADDR", "S", "PRI", "NI", "RTPRIO", "SCH", "PCY", "TIME", "CMD", NULL };
+ if (!getColumnIndices(columnIndices, headerNames, line)) {
+ return -1;
+ }
+
+ continue;
+ }
+
+ record = parseRecordByColumns(line, columnIndices);
+
+ diff = record.size() - header.size();
+ if (diff < 0) {
+ // TODO: log this to incident report!
+ fprintf(stderr, "[%s]Line %d has %d missing fields\n%s\n", this->name.string(), nline, -diff, line.c_str());
+ printRecord(record);
+ continue;
+ } else if (diff > 0) {
+ // TODO: log this to incident report!
+ fprintf(stderr, "[%s]Line %d has %d extra fields\n%s\n", this->name.string(), nline, diff, line.c_str());
+ printRecord(record);
+ continue;
+ }
+
+ long long token = proto.start(PsDumpProto::PROCESSES);
+ for (int i=0; i<(int)record.size(); i++) {
+ if (!table.insertField(&proto, header[i], record[i])) {
+ fprintf(stderr, "[%s]Line %d has bad value %s of %s\n",
+ this->name.string(), nline, header[i].c_str(), record[i].c_str());
+ }
+ }
+ proto.end(token);
+ }
+
+ if (!reader.ok(&line)) {
+ fprintf(stderr, "Bad read from fd %d: %s\n", in, line.c_str());
+ return -1;
+ }
+
+ if (!proto.flush(out)) {
+ fprintf(stderr, "[%s]Error writing proto back\n", this->name.string());
+ return -1;
+ }
+ fprintf(stderr, "[%s]Proto size: %zu bytes\n", this->name.string(), proto.size());
+ return NO_ERROR;
+}
diff --git a/cmds/incident_helper/src/parsers/PsParser.h b/cmds/incident_helper/src/parsers/PsParser.h
new file mode 100644
index 0000000..9488e40
--- /dev/null
+++ b/cmds/incident_helper/src/parsers/PsParser.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef PS_PARSER_H
+#define PS_PARSER_H
+
+#include "TextParserBase.h"
+
+/**
+ * PS parser, parses output of 'ps' command to protobuf.
+ */
+class PsParser : public TextParserBase {
+public:
+ PsParser() : TextParserBase(String8("Ps")) {};
+ ~PsParser() {};
+
+ virtual status_t Parse(const int in, const int out) const;
+};
+
+#endif // PS_PARSER_H
diff --git a/cmds/incident_helper/testdata/batterytype.txt b/cmds/incident_helper/testdata/batterytype.txt
new file mode 100644
index 0000000..c763d36
--- /dev/null
+++ b/cmds/incident_helper/testdata/batterytype.txt
@@ -0,0 +1 @@
+random_battery_type_string
diff --git a/cmds/incident_helper/testdata/ps.txt b/cmds/incident_helper/testdata/ps.txt
new file mode 100644
index 0000000..72dafc2c4
--- /dev/null
+++ b/cmds/incident_helper/testdata/ps.txt
@@ -0,0 +1,9 @@
+LABEL USER PID TID PPID VSZ RSS WCHAN ADDR S PRI NI RTPRIO SCH PCY TIME CMD
+u:r:init:s0 root 1 1 0 15816 2636 SyS_epoll_wait 0 S 19 0 - 0 fg 00:00:01 init
+u:r:kernel:s0 root 2 2 0 0 0 kthreadd 0 S 19 0 - 0 fg 00:00:00 kthreadd
+u:r:surfaceflinger:s0 system 499 534 1 73940 22024 futex_wait_queue_me 0 S 42 -9 2 1 fg 00:00:00 EventThread
+u:r:hal_gnss_default:s0 gps 670 2004 1 43064 7272 poll_schedule_timeout 0 S 19 0 - 0 fg 00:00:00 Loc_hal_worker
+u:r:platform_app:s0:c512,c768 u0_a48 1660 1976 806 4468612 138328 binder_thread_read 0 S 35 -16 - 0 ta 00:00:00 HwBinder:1660_1
+u:r:perfd:s0 root 1939 1946 1 18132 2088 __skb_recv_datagram 7b9782fd14 S 19 0 - 0 00:00:00 perfd
+u:r:perfd:s0 root 1939 1955 1 18132 2088 do_sigtimedwait 7b9782ff6c S 19 0 - 0 00:00:00 POSIX timer 0
+u:r:shell:s0 shell 2645 2645 802 11664 2972 0 7f67a2f8b4 R 19 0 - 0 fg 00:00:00 ps
diff --git a/cmds/incident_helper/tests/BatteryTypeParser_test.cpp b/cmds/incident_helper/tests/BatteryTypeParser_test.cpp
new file mode 100644
index 0000000..7fbe22d
--- /dev/null
+++ b/cmds/incident_helper/tests/BatteryTypeParser_test.cpp
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "BatteryTypeParser.h"
+
+#include "frameworks/base/core/proto/android/os/batterytype.pb.h"
+
+#include <android-base/file.h>
+#include <android-base/test_utils.h>
+#include <gmock/gmock.h>
+#include <google/protobuf/message_lite.h>
+#include <gtest/gtest.h>
+#include <string.h>
+#include <fcntl.h>
+
+using namespace android::base;
+using namespace android::os;
+using namespace std;
+using ::testing::StrEq;
+using ::testing::Test;
+using ::testing::internal::CaptureStderr;
+using ::testing::internal::CaptureStdout;
+using ::testing::internal::GetCapturedStderr;
+using ::testing::internal::GetCapturedStdout;
+
+class BatteryTypeParserTest : public Test {
+public:
+ virtual void SetUp() override {
+ ASSERT_TRUE(tf.fd != -1);
+ }
+
+protected:
+ TemporaryFile tf;
+
+ const string kTestPath = GetExecutableDirectory();
+ const string kTestDataPath = kTestPath + "/testdata/";
+};
+
+TEST_F(BatteryTypeParserTest, Success) {
+ const string testFile = kTestDataPath + "batterytype.txt";
+ BatteryTypeParser parser;
+ BatteryTypeProto expected;
+
+ expected.set_type("random_battery_type_string");
+
+ int fd = open(testFile.c_str(), O_RDONLY);
+ ASSERT_TRUE(fd != -1);
+
+ CaptureStdout();
+ ASSERT_EQ(NO_ERROR, parser.Parse(fd, STDOUT_FILENO));
+ EXPECT_EQ(GetCapturedStdout(), expected.SerializeAsString());
+ close(fd);
+}
diff --git a/cmds/incident_helper/tests/PsParser_test.cpp b/cmds/incident_helper/tests/PsParser_test.cpp
new file mode 100644
index 0000000..1f03a7f
--- /dev/null
+++ b/cmds/incident_helper/tests/PsParser_test.cpp
@@ -0,0 +1,300 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "PsParser.h"
+
+#include "frameworks/base/core/proto/android/os/ps.pb.h"
+
+#include <android-base/file.h>
+#include <android-base/test_utils.h>
+#include <gmock/gmock.h>
+#include <google/protobuf/message_lite.h>
+#include <gtest/gtest.h>
+#include <string.h>
+#include <fcntl.h>
+
+using namespace android::base;
+using namespace android::os;
+using namespace std;
+using ::testing::StrEq;
+using ::testing::Test;
+using ::testing::internal::CaptureStderr;
+using ::testing::internal::CaptureStdout;
+using ::testing::internal::GetCapturedStderr;
+using ::testing::internal::GetCapturedStdout;
+
+class PsParserTest : public Test {
+public:
+ virtual void SetUp() override {
+ ASSERT_TRUE(tf.fd != -1);
+ }
+
+protected:
+ TemporaryFile tf;
+
+ const string kTestPath = GetExecutableDirectory();
+ const string kTestDataPath = kTestPath + "/testdata/";
+};
+
+TEST_F(PsParserTest, Normal) {
+ const string testFile = kTestDataPath + "ps.txt";
+ PsParser parser;
+ PsDumpProto expected;
+ PsDumpProto got;
+
+ PsDumpProto::Process* record1 = expected.add_processes();
+ record1->set_label("u:r:init:s0");
+ record1->set_user("root");
+ record1->set_pid(1);
+ record1->set_tid(1);
+ record1->set_ppid(0);
+ record1->set_vsz(15816);
+ record1->set_rss(2636);
+ record1->set_wchan("SyS_epoll_wait");
+ record1->set_addr("0");
+ record1->set_s(PsDumpProto_Process_ProcessStateCode_STATE_S);
+ record1->set_pri(19);
+ record1->set_ni(0);
+ record1->set_rtprio("-");
+ record1->set_sch(PsDumpProto_Process_SchedulingPolicy_SCH_NORMAL);
+ record1->set_pcy(PsDumpProto::Process::POLICY_FG);
+ record1->set_time("00:00:01");
+ record1->set_cmd("init");
+
+ PsDumpProto::Process* record2 = expected.add_processes();
+ record2->set_label("u:r:kernel:s0");
+ record2->set_user("root");
+ record2->set_pid(2);
+ record2->set_tid(2);
+ record2->set_ppid(0);
+ record2->set_vsz(0);
+ record2->set_rss(0);
+ record2->set_wchan("kthreadd");
+ record2->set_addr("0");
+ record2->set_s(PsDumpProto_Process_ProcessStateCode_STATE_S);
+ record2->set_pri(19);
+ record2->set_ni(0);
+ record2->set_rtprio("-");
+ record2->set_sch(PsDumpProto_Process_SchedulingPolicy_SCH_NORMAL);
+ record2->set_pcy(PsDumpProto::Process::POLICY_FG);
+ record2->set_time("00:00:00");
+ record2->set_cmd("kthreadd");
+
+ PsDumpProto::Process* record3 = expected.add_processes();
+ record3->set_label("u:r:surfaceflinger:s0");
+ record3->set_user("system");
+ record3->set_pid(499);
+ record3->set_tid(534);
+ record3->set_ppid(1);
+ record3->set_vsz(73940);
+ record3->set_rss(22024);
+ record3->set_wchan("futex_wait_queue_me");
+ record3->set_addr("0");
+ record3->set_s(PsDumpProto_Process_ProcessStateCode_STATE_S);
+ record3->set_pri(42);
+ record3->set_ni(-9);
+ record3->set_rtprio("2");
+ record3->set_sch(PsDumpProto_Process_SchedulingPolicy_SCH_FIFO);
+ record3->set_pcy(PsDumpProto::Process::POLICY_FG);
+ record3->set_time("00:00:00");
+ record3->set_cmd("EventThread");
+
+ PsDumpProto::Process* record4 = expected.add_processes();
+ record4->set_label("u:r:hal_gnss_default:s0");
+ record4->set_user("gps");
+ record4->set_pid(670);
+ record4->set_tid(2004);
+ record4->set_ppid(1);
+ record4->set_vsz(43064);
+ record4->set_rss(7272);
+ record4->set_wchan("poll_schedule_timeout");
+ record4->set_addr("0");
+ record4->set_s(PsDumpProto_Process_ProcessStateCode_STATE_S);
+ record4->set_pri(19);
+ record4->set_ni(0);
+ record4->set_rtprio("-");
+ record4->set_sch(PsDumpProto_Process_SchedulingPolicy_SCH_NORMAL);
+ record4->set_pcy(PsDumpProto::Process::POLICY_FG);
+ record4->set_time("00:00:00");
+ record4->set_cmd("Loc_hal_worker");
+
+ PsDumpProto::Process* record5 = expected.add_processes();
+ record5->set_label("u:r:platform_app:s0:c512,c768");
+ record5->set_user("u0_a48");
+ record5->set_pid(1660);
+ record5->set_tid(1976);
+ record5->set_ppid(806);
+ record5->set_vsz(4468612);
+ record5->set_rss(138328);
+ record5->set_wchan("binder_thread_read");
+ record5->set_addr("0");
+ record5->set_s(PsDumpProto_Process_ProcessStateCode_STATE_S);
+ record5->set_pri(35);
+ record5->set_ni(-16);
+ record5->set_rtprio("-");
+ record5->set_sch(PsDumpProto_Process_SchedulingPolicy_SCH_NORMAL);
+ record5->set_pcy(PsDumpProto::Process::POLICY_TA);
+ record5->set_time("00:00:00");
+ record5->set_cmd("HwBinder:1660_1");
+
+ PsDumpProto::Process* record6 = expected.add_processes();
+ record6->set_label("u:r:perfd:s0");
+ record6->set_user("root");
+ record6->set_pid(1939);
+ record6->set_tid(1946);
+ record6->set_ppid(1);
+ record6->set_vsz(18132);
+ record6->set_rss(2088);
+ record6->set_wchan("__skb_recv_datagram");
+ record6->set_addr("7b9782fd14");
+ record6->set_s(PsDumpProto_Process_ProcessStateCode_STATE_S);
+ record6->set_pri(19);
+ record6->set_ni(0);
+ record6->set_rtprio("-");
+ record6->set_sch(PsDumpProto_Process_SchedulingPolicy_SCH_NORMAL);
+ record6->set_pcy(PsDumpProto::Process::POLICY_UNKNOWN);
+ record6->set_time("00:00:00");
+ record6->set_cmd("perfd");
+
+ PsDumpProto::Process* record7 = expected.add_processes();
+ record7->set_label("u:r:perfd:s0");
+ record7->set_user("root");
+ record7->set_pid(1939);
+ record7->set_tid(1955);
+ record7->set_ppid(1);
+ record7->set_vsz(18132);
+ record7->set_rss(2088);
+ record7->set_wchan("do_sigtimedwait");
+ record7->set_addr("7b9782ff6c");
+ record7->set_s(PsDumpProto_Process_ProcessStateCode_STATE_S);
+ record7->set_pri(19);
+ record7->set_ni(0);
+ record7->set_rtprio("-");
+ record7->set_sch(PsDumpProto_Process_SchedulingPolicy_SCH_NORMAL);
+ record7->set_pcy(PsDumpProto::Process::POLICY_UNKNOWN);
+ record7->set_time("00:00:00");
+ record7->set_cmd("POSIX timer 0");
+
+ PsDumpProto::Process* record8 = expected.add_processes();
+ record8->set_label("u:r:shell:s0");
+ record8->set_user("shell");
+ record8->set_pid(2645);
+ record8->set_tid(2645);
+ record8->set_ppid(802);
+ record8->set_vsz(11664);
+ record8->set_rss(2972);
+ record8->set_wchan("0");
+ record8->set_addr("7f67a2f8b4");
+ record8->set_s(PsDumpProto_Process_ProcessStateCode_STATE_R);
+ record8->set_pri(19);
+ record8->set_ni(0);
+ record8->set_rtprio("-");
+ record8->set_sch(PsDumpProto_Process_SchedulingPolicy_SCH_NORMAL);
+ record8->set_pcy(PsDumpProto::Process::POLICY_FG);
+ record8->set_time("00:00:00");
+ record8->set_cmd("ps");
+
+ int fd = open(testFile.c_str(), O_RDONLY);
+ ASSERT_TRUE(fd != -1);
+
+ CaptureStdout();
+ ASSERT_EQ(NO_ERROR, parser.Parse(fd, STDOUT_FILENO));
+ got.ParseFromString(GetCapturedStdout());
+ bool matches = true;
+
+ if (got.processes_size() != expected.processes_size()) {
+ fprintf(stderr, "Got %d processes, want %d\n", got.processes_size(), expected.processes_size());
+ matches = false;
+ } else {
+ int n = got.processes_size();
+ for (int i = 0; i < n; i++) {
+ PsDumpProto::Process g = got.processes(i);
+ PsDumpProto::Process e = expected.processes(i);
+
+ if (g.label() != e.label()) {
+ fprintf(stderr, "prcs[%d]: Invalid label. Got %s, want %s\n", i, g.label().c_str(), e.label().c_str());
+ matches = false;
+ }
+ if (g.user() != e.user()) {
+ fprintf(stderr, "prcs[%d]: Invalid user. Got %s, want %s\n", i, g.user().c_str(), e.user().c_str());
+ matches = false;
+ }
+ if (g.pid() != e.pid()) {
+ fprintf(stderr, "prcs[%d]: Invalid pid. Got %d, want %d\n", i, g.pid(), e.pid());
+ matches = false;
+ }
+ if (g.tid() != e.tid()) {
+ fprintf(stderr, "prcs[%d]: Invalid tid. Got %d, want %d\n", i, g.tid(), e.tid());
+ matches = false;
+ }
+ if (g.ppid() != e.ppid()) {
+ fprintf(stderr, "prcs[%d]: Invalid ppid. Got %d, want %d\n", i, g.ppid(), e.ppid());
+ matches = false;
+ }
+ if (g.vsz() != e.vsz()) {
+ fprintf(stderr, "prcs[%d]: Invalid vsz. Got %d, want %d\n", i, g.vsz(), e.vsz());
+ matches = false;
+ }
+ if (g.rss() != e.rss()) {
+ fprintf(stderr, "prcs[%d]: Invalid rss. Got %d, want %d\n", i, g.rss(), e.rss());
+ matches = false;
+ }
+ if (g.wchan() != e.wchan()) {
+ fprintf(stderr, "prcs[%d]: Invalid wchan. Got %s, want %s\n", i, g.wchan().c_str(), e.wchan().c_str());
+ matches = false;
+ }
+ if (g.addr() != e.addr()) {
+ fprintf(stderr, "prcs[%d]: Invalid addr. Got %s, want %s\n", i, g.addr().c_str(), e.addr().c_str());
+ matches = false;
+ }
+ if (g.s() != e.s()) {
+ fprintf(stderr, "prcs[%d]: Invalid s. Got %u, want %u\n", i, g.s(), e.s());
+ matches = false;
+ }
+ if (g.pri() != e.pri()) {
+ fprintf(stderr, "prcs[%d]: Invalid pri. Got %d, want %d\n", i, g.pri(), e.pri());
+ matches = false;
+ }
+ if (g.ni() != e.ni()) {
+ fprintf(stderr, "prcs[%d]: Invalid ni. Got %d, want %d\n", i, g.ni(), e.ni());
+ matches = false;
+ }
+ if (g.rtprio() != e.rtprio()) {
+ fprintf(stderr, "prcs[%d]: Invalid rtprio. Got %s, want %s\n", i, g.rtprio().c_str(), e.rtprio().c_str());
+ matches = false;
+ }
+ if (g.sch() != e.sch()) {
+ fprintf(stderr, "prcs[%d]: Invalid sch. Got %u, want %u\n", i, g.sch(), e.sch());
+ matches = false;
+ }
+ if (g.pcy() != e.pcy()) {
+ fprintf(stderr, "prcs[%d]: Invalid pcy. Got %u, want %u\n", i, g.pcy(), e.pcy());
+ matches = false;
+ }
+ if (g.time() != e.time()) {
+ fprintf(stderr, "prcs[%d]: Invalid time. Got %s, want %s\n", i, g.time().c_str(), e.time().c_str());
+ matches = false;
+ }
+ if (g.cmd() != e.cmd()) {
+ fprintf(stderr, "prcs[%d]: Invalid cmd. Got %s, want %s\n", i, g.cmd().c_str(), e.cmd().c_str());
+ matches = false;
+ }
+ }
+ }
+
+ EXPECT_TRUE(matches);
+ close(fd);
+}
diff --git a/cmds/incident_helper/tests/ih_util_test.cpp b/cmds/incident_helper/tests/ih_util_test.cpp
index 5740b33..7b8cf52 100644
--- a/cmds/incident_helper/tests/ih_util_test.cpp
+++ b/cmds/incident_helper/tests/ih_util_test.cpp
@@ -71,11 +71,29 @@
EXPECT_EQ(expected, result);
result = parseRecordByColumns("abc \t2345 6789 ", indices);
- expected = { "abc", "2345", "6789" };
+ expected = { "abc", "2345 6789" };
EXPECT_EQ(expected, result);
- result = parseRecordByColumns("abc \t23456789 bob", indices);
- expected = { "abc", "23456789", "bob" };
+ std::string extraColumn1 = "abc \t23456789 bob";
+ std::string emptyMidColm = "abc \t bob";
+ std::string longFirstClm = "abcdefgt\t6789 bob";
+ std::string lngFrstEmpty = "abcdefgt\t bob";
+
+ result = parseRecordByColumns(extraColumn1, indices);
+ expected = { "abc", "23456789 bob" };
+ EXPECT_EQ(expected, result);
+
+ // 2nd column should be treated as an empty entry.
+ result = parseRecordByColumns(emptyMidColm, indices);
+ expected = { "abc", "bob" };
+ EXPECT_EQ(expected, result);
+
+ result = parseRecordByColumns(longFirstClm, indices);
+ expected = { "abcdefgt", "6789 bob" };
+ EXPECT_EQ(expected, result);
+
+ result = parseRecordByColumns(lngFrstEmpty, indices);
+ expected = { "abcdefgt", "bob" };
EXPECT_EQ(expected, result);
}
diff --git a/cmds/incidentd/Android.mk b/cmds/incidentd/Android.mk
index fb8ef63..11d3e49 100644
--- a/cmds/incidentd/Android.mk
+++ b/cmds/incidentd/Android.mk
@@ -72,9 +72,7 @@
gen_src_dir:=
GEN:=
-ifeq ($(BUILD_WITH_INCIDENTD_RC), true)
LOCAL_INIT_RC := incidentd.rc
-endif
include $(BUILD_EXECUTABLE)
diff --git a/cmds/incidentd/src/Section.cpp b/cmds/incidentd/src/Section.cpp
index 1bf795b..22053ef 100644
--- a/cmds/incidentd/src/Section.cpp
+++ b/cmds/incidentd/src/Section.cpp
@@ -521,7 +521,7 @@
ALOGW("CommandSection '%s' failed to set up stdout: %s", this->name.string(), strerror(errno));
_exit(EXIT_FAILURE);
}
- execv(this->mCommand[0], (char *const *) this->mCommand);
+ execvp(this->mCommand[0], (char *const *) this->mCommand);
int err = errno; // record command error code
ALOGW("CommandSection '%s' failed in executing command: %s", this->name.string(), strerror(errno));
_exit(err); // exit with command error code
diff --git a/cmds/statsd/Android.mk b/cmds/statsd/Android.mk
index f98ee3d..a365f54 100644
--- a/cmds/statsd/Android.mk
+++ b/cmds/statsd/Android.mk
@@ -20,8 +20,12 @@
src/stats_log.proto \
src/statsd_config.proto \
src/atoms.proto \
+ src/field_util.cpp \
+ src/stats_log_util.cpp \
+ src/dimension.cpp \
src/anomaly/AnomalyMonitor.cpp \
src/anomaly/AnomalyTracker.cpp \
+ src/anomaly/DurationAnomalyTracker.cpp \
src/condition/CombinationConditionTracker.cpp \
src/condition/condition_util.cpp \
src/condition/SimpleConditionTracker.cpp \
@@ -162,6 +166,7 @@
tests/indexed_priority_queue_test.cpp \
tests/LogEntryMatcher_test.cpp \
tests/LogReader_test.cpp \
+ tests/LogEvent_test.cpp \
tests/MetricsManager_test.cpp \
tests/StatsLogProcessor_test.cpp \
tests/UidMap_test.cpp \
@@ -175,7 +180,10 @@
tests/metrics/ValueMetricProducer_test.cpp \
tests/metrics/GaugeMetricProducer_test.cpp \
tests/guardrail/StatsdStats_test.cpp \
- tests/metrics/metrics_test_helper.cpp
+ tests/metrics/metrics_test_helper.cpp \
+ tests/statsd_test_util.cpp \
+ tests/e2e/WakelockDuration_e2e_test.cpp \
+ tests/e2e/MetricConditionLink_e2e_test.cpp
LOCAL_STATIC_LIBRARIES := \
$(statsd_common_static_libraries) \
@@ -197,4 +205,4 @@
##############################
-include $(call all-makefiles-under,$(LOCAL_PATH))
+include $(call all-makefiles-under,$(LOCAL_PATH))
\ No newline at end of file
diff --git a/cmds/statsd/src/HashableDimensionKey.cpp b/cmds/statsd/src/HashableDimensionKey.cpp
index 0b6f8f2..288ebe9 100644
--- a/cmds/statsd/src/HashableDimensionKey.cpp
+++ b/cmds/statsd/src/HashableDimensionKey.cpp
@@ -13,92 +13,108 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
#include "HashableDimensionKey.h"
+#include "dimension.h"
namespace android {
namespace os {
namespace statsd {
+android::hash_t hashDimensionsValue(int64_t seed, const DimensionsValue& value) {
+ android::hash_t hash = seed;
+ hash = android::JenkinsHashMix(hash, android::hash_type(value.field()));
+
+ hash = android::JenkinsHashMix(hash, android::hash_type((int)value.value_case()));
+ switch (value.value_case()) {
+ case DimensionsValue::ValueCase::kValueStr:
+ hash = android::JenkinsHashMix(
+ hash,
+ static_cast<uint32_t>(std::hash<std::string>()(value.value_str())));
+ break;
+ case DimensionsValue::ValueCase::kValueInt:
+ hash = android::JenkinsHashMix(hash, android::hash_type(value.value_int()));
+ break;
+ case DimensionsValue::ValueCase::kValueLong:
+ hash = android::JenkinsHashMix(
+ hash, android::hash_type(static_cast<int64_t>(value.value_long())));
+ break;
+ case DimensionsValue::ValueCase::kValueBool:
+ hash = android::JenkinsHashMix(hash, android::hash_type(value.value_bool()));
+ break;
+ case DimensionsValue::ValueCase::kValueFloat: {
+ float floatVal = value.value_float();
+ hash = android::JenkinsHashMixBytes(hash, (uint8_t*)&floatVal, sizeof(float));
+ break;
+ }
+ case DimensionsValue::ValueCase::kValueTuple: {
+ hash = android::JenkinsHashMix(hash, android::hash_type(
+ value.value_tuple().dimensions_value_size()));
+ for (int i = 0; i < value.value_tuple().dimensions_value_size(); ++i) {
+ hash = android::JenkinsHashMix(
+ hash,
+ hashDimensionsValue(value.value_tuple().dimensions_value(i)));
+ }
+ break;
+ }
+ case DimensionsValue::ValueCase::VALUE_NOT_SET:
+ break;
+ }
+ return JenkinsHashWhiten(hash);
+}
+
+android::hash_t hashDimensionsValue(const DimensionsValue& value) {
+ return hashDimensionsValue(0, value);
+}
+
using std::string;
+
string HashableDimensionKey::toString() const {
string flattened;
- for (const auto& pair : mKeyValuePairs) {
- flattened += std::to_string(pair.key());
- flattened += ":";
- switch (pair.value_case()) {
- case KeyValuePair::ValueCase::kValueStr:
- flattened += pair.value_str();
- break;
- case KeyValuePair::ValueCase::kValueInt:
- flattened += std::to_string(pair.value_int());
- break;
- case KeyValuePair::ValueCase::kValueLong:
- flattened += std::to_string(pair.value_long());
- break;
- case KeyValuePair::ValueCase::kValueBool:
- flattened += std::to_string(pair.value_bool());
- break;
- case KeyValuePair::ValueCase::kValueFloat:
- flattened += std::to_string(pair.value_float());
- break;
- default:
- break;
- }
- flattened += "|";
- }
+ DimensionsValueToString(getDimensionsValue(), &flattened);
return flattened;
}
-bool HashableDimensionKey::operator==(const HashableDimensionKey& that) const {
- const auto& keyValue2 = that.getKeyValuePairs();
- if (mKeyValuePairs.size() != keyValue2.size()) {
+bool compareDimensionsValue(const DimensionsValue& s1, const DimensionsValue& s2) {
+ if (s1.field() != s2.field()) {
return false;
}
-
- for (size_t i = 0; i < keyValue2.size(); i++) {
- const auto& kv1 = mKeyValuePairs[i];
- const auto& kv2 = keyValue2[i];
- if (kv1.key() != kv2.key()) {
- return false;
- }
-
- if (kv1.value_case() != kv2.value_case()) {
- return false;
- }
-
- switch (kv1.value_case()) {
- case KeyValuePair::ValueCase::kValueStr:
- if (kv1.value_str() != kv2.value_str()) {
- return false;
- }
- break;
- case KeyValuePair::ValueCase::kValueInt:
- if (kv1.value_int() != kv2.value_int()) {
- return false;
- }
- break;
- case KeyValuePair::ValueCase::kValueLong:
- if (kv1.value_long() != kv2.value_long()) {
- return false;
- }
- break;
- case KeyValuePair::ValueCase::kValueBool:
- if (kv1.value_bool() != kv2.value_bool()) {
- return false;
- }
- break;
- case KeyValuePair::ValueCase::kValueFloat: {
- if (kv1.value_float() != kv2.value_float()) {
- return false;
- }
- break;
- }
- case KeyValuePair::ValueCase::VALUE_NOT_SET:
- break;
- }
+ if (s1.value_case() != s1.value_case()) {
+ return false;
}
- return true;
+ switch (s1.value_case()) {
+ case DimensionsValue::ValueCase::kValueStr:
+ return (s1.value_str() == s2.value_str());
+ case DimensionsValue::ValueCase::kValueInt:
+ return s1.value_int() == s2.value_int();
+ case DimensionsValue::ValueCase::kValueLong:
+ return s1.value_long() == s2.value_long();
+ case DimensionsValue::ValueCase::kValueBool:
+ return s1.value_bool() == s2.value_bool();
+ case DimensionsValue::ValueCase::kValueFloat:
+ return s1.value_float() == s2.value_float();
+ case DimensionsValue::ValueCase::kValueTuple:
+ {
+ if (s1.value_tuple().dimensions_value_size() !=
+ s2.value_tuple().dimensions_value_size()) {
+ return false;
+ }
+ bool allMatched = true;
+ for (int i = 0; allMatched && i < s1.value_tuple().dimensions_value_size(); ++i) {
+ allMatched &= compareDimensionsValue(s1.value_tuple().dimensions_value(i),
+ s2.value_tuple().dimensions_value(i));
+ }
+ return allMatched;
+ }
+ case DimensionsValue::ValueCase::VALUE_NOT_SET:
+ default:
+ return true;
+ }
+}
+
+bool HashableDimensionKey::operator==(const HashableDimensionKey& that) const {
+ return compareDimensionsValue(getDimensionsValue(), that.getDimensionsValue());
};
bool HashableDimensionKey::operator<(const HashableDimensionKey& that) const {
diff --git a/cmds/statsd/src/HashableDimensionKey.h b/cmds/statsd/src/HashableDimensionKey.h
index 85215552..85c317f 100644
--- a/cmds/statsd/src/HashableDimensionKey.h
+++ b/cmds/statsd/src/HashableDimensionKey.h
@@ -25,20 +25,20 @@
class HashableDimensionKey {
public:
- explicit HashableDimensionKey(const std::vector<KeyValuePair>& keyValuePairs)
- : mKeyValuePairs(keyValuePairs){};
+ explicit HashableDimensionKey(const DimensionsValue& dimensionsValue)
+ : mDimensionsValue(dimensionsValue){};
HashableDimensionKey(){};
HashableDimensionKey(const HashableDimensionKey& that)
- : mKeyValuePairs(that.getKeyValuePairs()){};
+ : mDimensionsValue(that.getDimensionsValue()){};
HashableDimensionKey& operator=(const HashableDimensionKey& from) = default;
std::string toString() const;
- inline const std::vector<KeyValuePair>& getKeyValuePairs() const {
- return mKeyValuePairs;
+ inline const DimensionsValue& getDimensionsValue() const {
+ return mDimensionsValue;
}
bool operator==(const HashableDimensionKey& that) const;
@@ -50,9 +50,12 @@
}
private:
- std::vector<KeyValuePair> mKeyValuePairs;
+ DimensionsValue mDimensionsValue;
};
+android::hash_t hashDimensionsValue(int64_t seed, const DimensionsValue& value);
+android::hash_t hashDimensionsValue(const DimensionsValue& value);
+
} // namespace statsd
} // namespace os
} // namespace android
@@ -60,42 +63,11 @@
namespace std {
using android::os::statsd::HashableDimensionKey;
-using android::os::statsd::KeyValuePair;
template <>
struct hash<HashableDimensionKey> {
std::size_t operator()(const HashableDimensionKey& key) const {
- android::hash_t hash = 0;
- for (const auto& pair : key.getKeyValuePairs()) {
- hash = android::JenkinsHashMix(hash, android::hash_type(pair.key()));
- hash = android::JenkinsHashMix(
- hash, android::hash_type(static_cast<int32_t>(pair.value_case())));
- switch (pair.value_case()) {
- case KeyValuePair::ValueCase::kValueStr:
- hash = android::JenkinsHashMix(
- hash,
- static_cast<uint32_t>(std::hash<std::string>()(pair.value_str())));
- break;
- case KeyValuePair::ValueCase::kValueInt:
- hash = android::JenkinsHashMix(hash, android::hash_type(pair.value_int()));
- break;
- case KeyValuePair::ValueCase::kValueLong:
- hash = android::JenkinsHashMix(
- hash, android::hash_type(static_cast<int64_t>(pair.value_long())));
- break;
- case KeyValuePair::ValueCase::kValueBool:
- hash = android::JenkinsHashMix(hash, android::hash_type(pair.value_bool()));
- break;
- case KeyValuePair::ValueCase::kValueFloat: {
- float floatVal = pair.value_float();
- hash = android::JenkinsHashMixBytes(hash, (uint8_t*)&floatVal, sizeof(float));
- break;
- }
- case KeyValuePair::ValueCase::VALUE_NOT_SET:
- break;
- }
- }
- return hash;
+ return hashDimensionsValue(key.getDimensionsValue());
}
};
diff --git a/cmds/statsd/src/StatsLogProcessor.cpp b/cmds/statsd/src/StatsLogProcessor.cpp
index 0c078d5..9678014 100644
--- a/cmds/statsd/src/StatsLogProcessor.cpp
+++ b/cmds/statsd/src/StatsLogProcessor.cpp
@@ -54,7 +54,7 @@
const int FIELD_ID_REPORTS = 2;
// for ConfigKey
const int FIELD_ID_UID = 1;
-const int FIELD_ID_NAME = 2;
+const int FIELD_ID_ID = 2;
// for ConfigMetricsReport
const int FIELD_ID_METRICS = 1;
const int FIELD_ID_UID_MAP = 2;
@@ -63,11 +63,12 @@
StatsLogProcessor::StatsLogProcessor(const sp<UidMap>& uidMap,
const sp<AnomalyMonitor>& anomalyMonitor,
+ const long timeBaseSec,
const std::function<void(const ConfigKey&)>& sendBroadcast)
: mUidMap(uidMap),
mAnomalyMonitor(anomalyMonitor),
mSendBroadcast(sendBroadcast),
- mTimeBaseSec(time(nullptr)) {
+ mTimeBaseSec(timeBaseSec) {
// On each initialization of StatsLogProcessor, check stats-data directory to see if there is
// any left over data to be read.
StorageManager::sendBroadcast(STATS_DATA_DIR, mSendBroadcast);
@@ -81,6 +82,9 @@
void StatsLogProcessor::onAnomalyAlarmFired(
const uint64_t timestampNs,
unordered_set<sp<const AnomalyAlarm>, SpHash<AnomalyAlarm>> anomalySet) {
+ // TODO: This is a thread-safety issue. mMetricsManagers could change under our feet.
+ // TODO: Solution? Lock everything! :(
+ // TODO: Question: Can we replace the other lock (broadcast), or do we need to supplement it?
for (const auto& itr : mMetricsManagers) {
itr.second->onAnomalyAlarmFired(timestampNs, anomalySet);
}
@@ -94,7 +98,6 @@
pair.second->onLogEvent(msg);
flushIfNecessary(msg.GetTimestampNs(), pair.first, *(pair.second));
}
-
// Hard-coded logic to update the isolated uid's in the uid-map.
// The field numbers need to be currently updated by hand with atoms.proto
if (msg.GetTagId() == android::util::ISOLATED_UID_CHANGED) {
@@ -114,9 +117,7 @@
void StatsLogProcessor::OnConfigUpdated(const ConfigKey& key, const StatsdConfig& config) {
ALOGD("Updated configuration for key %s", key.ToString().c_str());
-
sp<MetricsManager> newMetricsManager = new MetricsManager(key, config, mTimeBaseSec, mUidMap);
-
auto it = mMetricsManagers.find(key);
if (it == mMetricsManagers.end() && mMetricsManagers.size() > StatsdStats::kMaxConfigCount) {
ALOGE("Can't accept more configs!");
@@ -149,6 +150,19 @@
return it->second->byteSize();
}
+void StatsLogProcessor::onDumpReport(const ConfigKey& key, const uint64_t& dumpTimeStampNs, ConfigMetricsReportList* report) {
+ auto it = mMetricsManagers.find(key);
+ if (it == mMetricsManagers.end()) {
+ ALOGW("Config source %s does not exist", key.ToString().c_str());
+ return;
+ }
+ report->mutable_config_key()->set_uid(key.GetUid());
+ report->mutable_config_key()->set_id(key.GetId());
+ ConfigMetricsReport* configMetricsReport = report->add_reports();
+ it->second->onDumpReport(dumpTimeStampNs, configMetricsReport);
+ // TODO: dump uid mapping.
+}
+
void StatsLogProcessor::onDumpReport(const ConfigKey& key, vector<uint8_t>* outData) {
auto it = mMetricsManagers.find(key);
if (it == mMetricsManagers.end()) {
@@ -167,7 +181,7 @@
// Start of ConfigKey.
long long configKeyToken = proto.start(FIELD_TYPE_MESSAGE | FIELD_ID_CONFIG_KEY);
proto.write(FIELD_TYPE_INT32 | FIELD_ID_UID, key.GetUid());
- proto.write(FIELD_TYPE_STRING | FIELD_ID_NAME, key.GetName());
+ proto.write(FIELD_TYPE_INT64 | FIELD_ID_ID, (long long)key.GetId());
proto.end(configKeyToken);
// End of ConfigKey.
@@ -264,8 +278,8 @@
vector<uint8_t> data;
onDumpReport(key, &data);
// TODO: Add a guardrail to prevent accumulation of file on disk.
- string file_name = StringPrintf("%s/%d-%s-%ld", STATS_DATA_DIR, key.GetUid(),
- key.GetName().c_str(), time(nullptr));
+ string file_name = StringPrintf("%s/%d-%lld-%ld", STATS_DATA_DIR, key.GetUid(),
+ (long long)key.GetId(), time(nullptr));
StorageManager::writeFile(file_name.c_str(), &data[0], data.size());
}
}
diff --git a/cmds/statsd/src/StatsLogProcessor.h b/cmds/statsd/src/StatsLogProcessor.h
index 1e5c426..f62fc4e 100644
--- a/cmds/statsd/src/StatsLogProcessor.h
+++ b/cmds/statsd/src/StatsLogProcessor.h
@@ -13,9 +13,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-#ifndef STATS_LOG_PROCESSOR_H
-#define STATS_LOG_PROCESSOR_H
+#pragma once
+
+#include <gtest/gtest_prod.h>
#include "config/ConfigListener.h"
#include "logd/LogReader.h"
#include "metrics/MetricsManager.h"
@@ -33,6 +34,7 @@
class StatsLogProcessor : public ConfigListener {
public:
StatsLogProcessor(const sp<UidMap>& uidMap, const sp<AnomalyMonitor>& anomalyMonitor,
+ const long timeBaseSec,
const std::function<void(const ConfigKey&)>& sendBroadcast);
virtual ~StatsLogProcessor();
@@ -44,6 +46,7 @@
size_t GetMetricsSize(const ConfigKey& key) const;
void onDumpReport(const ConfigKey& key, vector<uint8_t>* outData);
+ void onDumpReport(const ConfigKey& key, const uint64_t& dumpTimeStampNs, ConfigMetricsReportList* report);
/* Tells MetricsManager that the alarms in anomalySet have fired. Modifies anomalySet. */
void onAnomalyAlarmFired(
@@ -81,10 +84,12 @@
FRIEND_TEST(StatsLogProcessorTest, TestRateLimitByteSize);
FRIEND_TEST(StatsLogProcessorTest, TestRateLimitBroadcast);
FRIEND_TEST(StatsLogProcessorTest, TestDropWhenByteSizeTooLarge);
+ FRIEND_TEST(StatsLogProcessorTest, TestDropWhenByteSizeTooLarge);
+ FRIEND_TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensions);
+ FRIEND_TEST(MetricConditionLinkE2eTest, TestMultiplePredicatesAndLinks);
+
};
} // namespace statsd
} // namespace os
} // namespace android
-
-#endif // STATS_LOG_PROCESSOR_H
diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp
index dab3880..45f1ea1 100644
--- a/cmds/statsd/src/StatsService.cpp
+++ b/cmds/statsd/src/StatsService.cpp
@@ -75,7 +75,7 @@
{
mUidMap = new UidMap();
mConfigManager = new ConfigManager();
- mProcessor = new StatsLogProcessor(mUidMap, mAnomalyMonitor, [this](const ConfigKey& key) {
+ mProcessor = new StatsLogProcessor(mUidMap, mAnomalyMonitor, time(nullptr), [this](const ConfigKey& key) {
sp<IStatsCompanionService> sc = getStatsCompanionService();
auto receiver = mConfigManager->GetConfigReceiver(key);
if (sc == nullptr) {
@@ -335,7 +335,7 @@
print_cmd_help(out);
return UNKNOWN_ERROR;
}
- auto receiver = mConfigManager->GetConfigReceiver(ConfigKey(uid, name));
+ auto receiver = mConfigManager->GetConfigReceiver(ConfigKey(uid, StrToInt64(name)));
sp<IStatsCompanionService> sc = getStatsCompanionService();
if (sc != nullptr) {
sc->sendBroadcast(String16(receiver.first.c_str()), String16(receiver.second.c_str()));
@@ -404,13 +404,13 @@
}
// Add / update the config.
- mConfigManager->UpdateConfig(ConfigKey(uid, name), config);
+ mConfigManager->UpdateConfig(ConfigKey(uid, StrToInt64(name)), config);
} else {
if (argCount == 2) {
cmd_remove_all_configs(out);
} else {
// Remove the config.
- mConfigManager->RemoveConfig(ConfigKey(uid, name));
+ mConfigManager->RemoveConfig(ConfigKey(uid, StrToInt64(name)));
}
}
@@ -459,7 +459,7 @@
}
if (good) {
vector<uint8_t> data;
- mProcessor->onDumpReport(ConfigKey(uid, name), &data);
+ mProcessor->onDumpReport(ConfigKey(uid, StrToInt64(name)), &data);
// TODO: print the returned StatsLogReport to file instead of printing to logcat.
if (proto) {
for (size_t i = 0; i < data.size(); i ++) {
@@ -699,12 +699,11 @@
mProcessor->OnLogEvent(event);
}
-Status StatsService::getData(const String16& key, vector<uint8_t>* output) {
+Status StatsService::getData(int64_t key, vector<uint8_t>* output) {
IPCThreadState* ipc = IPCThreadState::self();
VLOG("StatsService::getData with Pid %i, Uid %i", ipc->getCallingPid(), ipc->getCallingUid());
if (checkCallingPermission(String16(kPermissionDump))) {
- string keyStr = string(String8(key).string());
- ConfigKey configKey(ipc->getCallingUid(), keyStr);
+ ConfigKey configKey(ipc->getCallingUid(), key);
mProcessor->onDumpReport(configKey, output);
return Status::ok();
} else {
@@ -724,16 +723,18 @@
}
}
-Status StatsService::addConfiguration(const String16& key,
+Status StatsService::addConfiguration(int64_t key,
const vector <uint8_t>& config,
const String16& package, const String16& cls,
bool* success) {
IPCThreadState* ipc = IPCThreadState::self();
if (checkCallingPermission(String16(kPermissionDump))) {
- string keyString = string(String8(key).string());
- ConfigKey configKey(ipc->getCallingUid(), keyString);
+ ConfigKey configKey(ipc->getCallingUid(), key);
StatsdConfig cfg;
- cfg.ParseFromArray(&config[0], config.size());
+ if (!cfg.ParseFromArray(&config[0], config.size())) {
+ *success = false;
+ return Status::ok();
+ }
mConfigManager->UpdateConfig(configKey, cfg);
mConfigManager->SetConfigReceiver(configKey, string(String8(package).string()),
string(String8(cls).string()));
@@ -745,11 +746,10 @@
}
}
-Status StatsService::removeConfiguration(const String16& key, bool* success) {
+Status StatsService::removeConfiguration(int64_t key, bool* success) {
IPCThreadState* ipc = IPCThreadState::self();
if (checkCallingPermission(String16(kPermissionDump))) {
- string keyStr = string(String8(key).string());
- mConfigManager->RemoveConfig(ConfigKey(ipc->getCallingUid(), keyStr));
+ mConfigManager->RemoveConfig(ConfigKey(ipc->getCallingUid(), key));
*success = true;
return Status::ok();
} else {
diff --git a/cmds/statsd/src/StatsService.h b/cmds/statsd/src/StatsService.h
index 08fcdac..c0424f3 100644
--- a/cmds/statsd/src/StatsService.h
+++ b/cmds/statsd/src/StatsService.h
@@ -77,25 +77,27 @@
/**
* Binder call for clients to request data for this configuration key.
*/
- virtual Status getData(const String16& key, vector<uint8_t>* output) override;
+ virtual Status getData(int64_t key, vector<uint8_t>* output) override;
+
/**
* Binder call for clients to get metadata across all configs in statsd.
*/
virtual Status getMetadata(vector<uint8_t>* output) override;
+
/**
* Binder call to let clients send a configuration and indicate they're interested when they
* should requestData for this configuration.
*/
- virtual Status addConfiguration(const String16& key, const vector <uint8_t>& config,
- const String16& package, const String16& cls, bool* success)
+ virtual Status addConfiguration(int64_t key, const vector <uint8_t>& config,
+ const String16& package, const String16& cls, bool* success)
override;
/**
* Binder call to allow clients to remove the specified configuration.
*/
- virtual Status removeConfiguration(const String16& key, bool* success) override;
+ virtual Status removeConfiguration(int64_t key, bool* success) override;
// TODO: public for testing since statsd doesn't run when system starts. Change to private
// later.
diff --git a/cmds/statsd/src/anomaly/AnomalyTracker.cpp b/cmds/statsd/src/anomaly/AnomalyTracker.cpp
index 162a34b..05c68e1 100644
--- a/cmds/statsd/src/anomaly/AnomalyTracker.cpp
+++ b/cmds/statsd/src/anomaly/AnomalyTracker.cpp
@@ -30,17 +30,15 @@
namespace os {
namespace statsd {
-// TODO: Separate DurationAnomalyTracker as a separate subclass and let each MetricProducer
-// decide and let which one it wants.
// TODO: Get rid of bucketNumbers, and return to the original circular array method.
AnomalyTracker::AnomalyTracker(const Alert& alert, const ConfigKey& configKey)
: mAlert(alert),
mConfigKey(configKey),
- mNumOfPastBuckets(mAlert.number_of_buckets() - 1) {
+ mNumOfPastBuckets(mAlert.num_buckets() - 1) {
VLOG("AnomalyTracker() called");
- if (mAlert.number_of_buckets() <= 0) {
+ if (mAlert.num_buckets() <= 0) {
ALOGE("Cannot create AnomalyTracker with %lld buckets",
- (long long)mAlert.number_of_buckets());
+ (long long)mAlert.num_buckets());
return;
}
if (!mAlert.has_trigger_if_sum_gt()) {
@@ -52,7 +50,6 @@
AnomalyTracker::~AnomalyTracker() {
VLOG("~AnomalyTracker() called");
- stopAllAlarms();
}
void AnomalyTracker::resetStorage() {
@@ -61,8 +58,6 @@
// Excludes the current bucket.
mPastBuckets.resize(mNumOfPastBuckets);
mSumOverPastBuckets.clear();
-
- if (!mAlarms.empty()) VLOG("AnomalyTracker.resetStorage() called but mAlarms is NOT empty!");
}
size_t AnomalyTracker::index(int64_t bucketNum) const {
@@ -205,43 +200,26 @@
return;
}
// TODO(guardrail): Consider guarding against too short refractory periods.
- mLastAlarmTimestampNs = timestampNs;
-
+ mLastAnomalyTimestampNs = timestampNs;
// TODO: If we had access to the bucket_size_millis, consider calling resetStorage()
// if (mAlert.refractory_period_secs() > mNumOfPastBuckets * bucketSizeNs) { resetStorage(); }
- if (mAlert.has_incidentd_details()) {
- if (mAlert.has_name()) {
- ALOGW("An anomaly (%s) has occurred! Informing incidentd.",
- mAlert.name().c_str());
+ if (!mSubscriptions.empty()) {
+ if (mAlert.has_id()) {
+ ALOGI("An anomaly (%llu) has occurred! Informing subscribers.",mAlert.id());
+ informSubscribers();
} else {
- // TODO: Can construct a name based on the criteria (and/or relay the criteria).
- ALOGW("An anomaly (nameless) has occurred! Informing incidentd.");
+ ALOGI("An anomaly (with no id) has occurred! Not informing any subscribers.");
}
- informIncidentd();
} else {
- ALOGW("An anomaly has occurred! (But informing incidentd not requested.)");
+ ALOGI("An anomaly has occurred! (But no subscriber for that alert.)");
}
- StatsdStats::getInstance().noteAnomalyDeclared(mConfigKey, mAlert.name());
+ StatsdStats::getInstance().noteAnomalyDeclared(mConfigKey, mAlert.id());
android::util::stats_write(android::util::ANOMALY_DETECTED, mConfigKey.GetUid(),
- mConfigKey.GetName().c_str(), mAlert.name().c_str());
-}
-
-void AnomalyTracker::declareAnomalyIfAlarmExpired(const HashableDimensionKey& dimensionKey,
- const uint64_t& timestampNs) {
- auto itr = mAlarms.find(dimensionKey);
- if (itr == mAlarms.end()) {
- return;
- }
-
- if (itr->second != nullptr &&
- static_cast<uint32_t>(timestampNs / NS_PER_SEC) >= itr->second->timestampSec) {
- declareAnomaly(timestampNs);
- stopAlarm(dimensionKey);
- }
+ mConfigKey.GetId(), mAlert.id());
}
void AnomalyTracker::detectAndDeclareAnomaly(const uint64_t& timestampNs,
@@ -261,91 +239,51 @@
}
}
-void AnomalyTracker::startAlarm(const HashableDimensionKey& dimensionKey,
- const uint64_t& timestampNs) {
- uint32_t timestampSec = static_cast<uint32_t>(timestampNs / NS_PER_SEC);
- if (isInRefractoryPeriod(timestampNs)) {
- VLOG("Skipping setting anomaly alarm since it'd fall in the refractory period");
+bool AnomalyTracker::isInRefractoryPeriod(const uint64_t& timestampNs) const {
+ return mLastAnomalyTimestampNs >= 0 &&
+ timestampNs - mLastAnomalyTimestampNs <= mAlert.refractory_period_secs() * NS_PER_SEC;
+}
+
+void AnomalyTracker::informSubscribers() {
+ VLOG("informSubscribers called.");
+ if (mSubscriptions.empty()) {
+ ALOGE("Attempt to call with no subscribers.");
return;
}
- sp<const AnomalyAlarm> alarm = new AnomalyAlarm{timestampSec};
- mAlarms.insert({dimensionKey, alarm});
- if (mAnomalyMonitor != nullptr) {
- mAnomalyMonitor->add(alarm);
- }
-}
-
-void AnomalyTracker::stopAlarm(const HashableDimensionKey& dimensionKey) {
- auto itr = mAlarms.find(dimensionKey);
- if (itr != mAlarms.end()) {
- mAlarms.erase(dimensionKey);
- if (mAnomalyMonitor != nullptr) {
- mAnomalyMonitor->remove(itr->second);
+ std::set<int> incidentdSections;
+ for (const Subscription& subscription : mSubscriptions) {
+ switch (subscription.subscriber_information_case()) {
+ case Subscription::SubscriberInformationCase::kIncidentdDetails:
+ for (int i = 0; i < subscription.incidentd_details().section_size(); i++) {
+ incidentdSections.insert(subscription.incidentd_details().section(i));
+ }
+ break;
+ case Subscription::SubscriberInformationCase::kPerfettoDetails:
+ ALOGW("Perfetto reports not implemented.");
+ break;
+ default:
+ break;
}
}
-}
-
-void AnomalyTracker::stopAllAlarms() {
- std::set<HashableDimensionKey> keys;
- for (auto itr = mAlarms.begin(); itr != mAlarms.end(); ++itr) {
- keys.insert(itr->first);
- }
- for (auto key : keys) {
- stopAlarm(key);
- }
-}
-
-bool AnomalyTracker::isInRefractoryPeriod(const uint64_t& timestampNs) {
- return mLastAlarmTimestampNs >= 0 &&
- timestampNs - mLastAlarmTimestampNs <= mAlert.refractory_period_secs() * NS_PER_SEC;
-}
-
-void AnomalyTracker::informAlarmsFired(const uint64_t& timestampNs,
- unordered_set<sp<const AnomalyAlarm>, SpHash<AnomalyAlarm>>& firedAlarms) {
-
- if (firedAlarms.empty() || mAlarms.empty()) return;
- // Find the intersection of firedAlarms and mAlarms.
- // The for loop is inefficient, since it loops over all keys, but that's okay since it is very
- // seldomly called. The alternative would be having AnomalyAlarms store information about the
- // AnomalyTracker and key, but that's a lot of data overhead to speed up something that is
- // rarely ever called.
- unordered_map<HashableDimensionKey, sp<const AnomalyAlarm>> matchedAlarms;
- for (const auto& kv : mAlarms) {
- if (firedAlarms.count(kv.second) > 0) {
- matchedAlarms.insert({kv.first, kv.second});
+ if (!incidentdSections.empty()) {
+ sp<IIncidentManager> service = interface_cast<IIncidentManager>(
+ defaultServiceManager()->getService(android::String16("incident")));
+ if (service != NULL) {
+ IncidentReportArgs incidentReport;
+ for (const auto section : incidentdSections) {
+ incidentReport.addSection(section);
+ }
+ int64_t alertId = mAlert.id();
+ std::vector<uint8_t> header;
+ uint8_t* src = static_cast<uint8_t*>(static_cast<void*>(&alertId));
+ header.insert(header.end(), src, src + sizeof(int64_t));
+ incidentReport.addHeader(header);
+ service->reportIncident(incidentReport);
+ } else {
+ ALOGW("Couldn't get the incident service.");
}
}
-
- // Now declare each of these alarms to have fired.
- for (const auto& kv : matchedAlarms) {
- declareAnomaly(timestampNs /* TODO: , kv.first */);
- mAlarms.erase(kv.first);
- firedAlarms.erase(kv.second); // No one else can also own it, so we're done with it.
- }
-}
-
-void AnomalyTracker::informIncidentd() {
- VLOG("informIncidentd called.");
- if (!mAlert.has_incidentd_details()) {
- ALOGE("Attempted to call incidentd without any incidentd_details.");
- return;
- }
- sp<IIncidentManager> service = interface_cast<IIncidentManager>(
- defaultServiceManager()->getService(android::String16("incident")));
- if (service == NULL) {
- ALOGW("Couldn't get the incident service.");
- return;
- }
-
- IncidentReportArgs incidentReport;
- const Alert::IncidentdDetails& details = mAlert.incidentd_details();
- for (int i = 0; i < details.section_size(); i++) {
- incidentReport.addSection(details.section(i));
- }
- // TODO: Pass in mAlert.name() into the addHeader?
-
- service->reportIncident(incidentReport);
}
} // namespace statsd
diff --git a/cmds/statsd/src/anomaly/AnomalyTracker.h b/cmds/statsd/src/anomaly/AnomalyTracker.h
index 874add2..2d5ab86 100644
--- a/cmds/statsd/src/anomaly/AnomalyTracker.h
+++ b/cmds/statsd/src/anomaly/AnomalyTracker.h
@@ -40,6 +40,11 @@
virtual ~AnomalyTracker();
+ // Add subscriptions that depend on this alert.
+ void addSubscription(const Subscription& subscription) {
+ mSubscriptions.push_back(subscription);
+ }
+
// Adds a bucket.
// Bucket index starts from 0.
void addPastBucket(std::shared_ptr<DimToValMap> bucketValues, const int64_t& bucketNum);
@@ -61,23 +66,11 @@
const HashableDimensionKey& key,
const int64_t& currentBucketValue);
- // Starts the alarm at the given timestamp.
- void startAlarm(const HashableDimensionKey& dimensionKey, const uint64_t& eventTime);
- // Stops the alarm.
- void stopAlarm(const HashableDimensionKey& dimensionKey);
-
- // Stop all the alarms owned by this tracker.
- void stopAllAlarms();
-
- // Init the anmaly monitor which is shared across anomaly trackers.
- inline void setAnomalyMonitor(const sp<AnomalyMonitor>& anomalyMonitor) {
- mAnomalyMonitor = anomalyMonitor;
+ // Init the AnomalyMonitor which is shared across anomaly trackers.
+ virtual void setAnomalyMonitor(const sp<AnomalyMonitor>& anomalyMonitor) {
+ return; // Base AnomalyTracker class has no need for the AnomalyMonitor.
}
- // Declares the anomaly when the alarm expired given the current timestamp.
- void declareAnomalyIfAlarmExpired(const HashableDimensionKey& dimensionKey,
- const uint64_t& timestampNs);
-
// Helper function to return the sum value of past buckets at given dimension.
int64_t getSumOverPastBuckets(const HashableDimensionKey& key) const;
@@ -89,9 +82,9 @@
return mAlert.trigger_if_sum_gt();
}
- // Helper function to return the last alarm timestamp.
- inline int64_t getLastAlarmTimestampNs() const {
- return mLastAlarmTimestampNs;
+ // Helper function to return the timestamp of the last detected anomaly.
+ inline int64_t getLastAnomalyTimestampNs() const {
+ return mLastAnomalyTimestampNs;
}
inline int getNumOfPastBuckets() const {
@@ -100,18 +93,18 @@
// Declares an anomaly for each alarm in firedAlarms that belongs to this AnomalyTracker,
// and removes it from firedAlarms. Does NOT remove the alarm from the AnomalyMonitor.
- // TODO: This will actually be called from a different thread, so make it thread-safe!
- // TODO: Consider having AnomalyMonitor have a reference to each relevant MetricProducer
- // instead of calling it from a chain starting at StatsLogProcessor.
- void informAlarmsFired(const uint64_t& timestampNs,
- unordered_set<sp<const AnomalyAlarm>, SpHash<AnomalyAlarm>>& firedAlarms);
+ virtual void informAlarmsFired(const uint64_t& timestampNs,
+ unordered_set<sp<const AnomalyAlarm>, SpHash<AnomalyAlarm>>& firedAlarms) {
+ return; // The base AnomalyTracker class doesn't have alarms.
+ }
protected:
- void flushPastBuckets(const int64_t& currBucketNum);
-
// statsd_config.proto Alert message that defines this tracker.
const Alert mAlert;
+ // The subscriptions that depend on this alert.
+ std::vector<Subscription> mSubscriptions;
+
// A reference to the Alert's config key.
const ConfigKey& mConfigKey;
@@ -119,14 +112,7 @@
// for the anomaly detection (since the current bucket is not in the past).
int mNumOfPastBuckets;
- // The alarms owned by this tracker. The alarm monitor also shares the alarm pointers when they
- // are still active.
- std::unordered_map<HashableDimensionKey, sp<const AnomalyAlarm>> mAlarms;
-
- // Anomaly alarm monitor.
- sp<AnomalyMonitor> mAnomalyMonitor;
-
- // The exisiting bucket list.
+ // The existing bucket list.
std::vector<shared_ptr<DimToValMap>> mPastBuckets;
// Sum over all existing buckets cached in mPastBuckets.
@@ -136,7 +122,9 @@
int64_t mMostRecentBucketNum = -1;
// The timestamp when the last anomaly was declared.
- int64_t mLastAlarmTimestampNs = -1;
+ int64_t mLastAnomalyTimestampNs = -1;
+
+ void flushPastBuckets(const int64_t& currBucketNum);
// Add the information in the given bucket to mSumOverPastBuckets.
void addBucketToSum(const shared_ptr<DimToValMap>& bucket);
@@ -145,26 +133,21 @@
// and remove any items with value 0.
void subtractBucketFromSum(const shared_ptr<DimToValMap>& bucket);
- bool isInRefractoryPeriod(const uint64_t& timestampNs);
+ bool isInRefractoryPeriod(const uint64_t& timestampNs) const;
// Calculates the corresponding bucket index within the circular array.
size_t index(int64_t bucketNum) const;
// Resets all bucket data. For use when all the data gets stale.
- void resetStorage();
+ virtual void resetStorage();
- // Informs the incident service that an anomaly has occurred.
- void informIncidentd();
+ // Informs the subscribers that an anomaly has occurred.
+ void informSubscribers();
FRIEND_TEST(AnomalyTrackerTest, TestConsecutiveBuckets);
FRIEND_TEST(AnomalyTrackerTest, TestSparseBuckets);
FRIEND_TEST(GaugeMetricProducerTest, TestAnomalyDetection);
FRIEND_TEST(CountMetricProducerTest, TestAnomalyDetection);
- FRIEND_TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp);
- FRIEND_TEST(OringDurationTrackerTest, TestAnomalyDetection);
- FRIEND_TEST(MaxDurationTrackerTest, TestAnomalyDetection);
- FRIEND_TEST(MaxDurationTrackerTest, TestAnomalyDetection);
- FRIEND_TEST(OringDurationTrackerTest, TestAnomalyDetection);
};
} // namespace statsd
diff --git a/cmds/statsd/src/anomaly/DurationAnomalyTracker.cpp b/cmds/statsd/src/anomaly/DurationAnomalyTracker.cpp
new file mode 100644
index 0000000..d30810f
--- /dev/null
+++ b/cmds/statsd/src/anomaly/DurationAnomalyTracker.cpp
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define DEBUG true // STOPSHIP if true
+#include "Log.h"
+
+#include "DurationAnomalyTracker.h"
+#include "guardrail/StatsdStats.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+DurationAnomalyTracker::DurationAnomalyTracker(const Alert& alert, const ConfigKey& configKey)
+ : AnomalyTracker(alert, configKey) {
+}
+
+DurationAnomalyTracker::~DurationAnomalyTracker() {
+ stopAllAlarms();
+}
+
+void DurationAnomalyTracker::resetStorage() {
+ AnomalyTracker::resetStorage();
+ if (!mAlarms.empty()) VLOG("AnomalyTracker.resetStorage() called but mAlarms is NOT empty!");
+}
+
+void DurationAnomalyTracker::declareAnomalyIfAlarmExpired(const HashableDimensionKey& dimensionKey,
+ const uint64_t& timestampNs) {
+ auto itr = mAlarms.find(dimensionKey);
+ if (itr == mAlarms.end()) {
+ return;
+ }
+
+ if (itr->second != nullptr &&
+ static_cast<uint32_t>(timestampNs / NS_PER_SEC) >= itr->second->timestampSec) {
+ declareAnomaly(timestampNs);
+ stopAlarm(dimensionKey);
+ }
+}
+
+void DurationAnomalyTracker::startAlarm(const HashableDimensionKey& dimensionKey,
+ const uint64_t& timestampNs) {
+
+ uint32_t timestampSec = static_cast<uint32_t>(timestampNs / NS_PER_SEC);
+ if (isInRefractoryPeriod(timestampNs)) {
+ VLOG("Skipping setting anomaly alarm since it'd fall in the refractory period");
+ return;
+ }
+ sp<const AnomalyAlarm> alarm = new AnomalyAlarm{timestampSec};
+ mAlarms.insert({dimensionKey, alarm});
+ if (mAnomalyMonitor != nullptr) {
+ mAnomalyMonitor->add(alarm);
+ }
+}
+
+void DurationAnomalyTracker::stopAlarm(const HashableDimensionKey& dimensionKey) {
+ auto itr = mAlarms.find(dimensionKey);
+ if (itr != mAlarms.end()) {
+ mAlarms.erase(dimensionKey);
+ if (mAnomalyMonitor != nullptr) {
+ mAnomalyMonitor->remove(itr->second);
+ }
+ }
+}
+
+void DurationAnomalyTracker::stopAllAlarms() {
+ std::set<HashableDimensionKey> keys;
+ for (auto itr = mAlarms.begin(); itr != mAlarms.end(); ++itr) {
+ keys.insert(itr->first);
+ }
+ for (auto key : keys) {
+ stopAlarm(key);
+ }
+}
+
+void DurationAnomalyTracker::informAlarmsFired(const uint64_t& timestampNs,
+ unordered_set<sp<const AnomalyAlarm>, SpHash<AnomalyAlarm>>& firedAlarms) {
+
+ if (firedAlarms.empty() || mAlarms.empty()) return;
+ // Find the intersection of firedAlarms and mAlarms.
+ // The for loop is inefficient, since it loops over all keys, but that's okay since it is very
+ // seldomly called. The alternative would be having AnomalyAlarms store information about the
+ // DurationAnomalyTracker and key, but that's a lot of data overhead to speed up something that is
+ // rarely ever called.
+ unordered_map<HashableDimensionKey, sp<const AnomalyAlarm>> matchedAlarms;
+ for (const auto& kv : mAlarms) {
+ if (firedAlarms.count(kv.second) > 0) {
+ matchedAlarms.insert({kv.first, kv.second});
+ }
+ }
+
+ // Now declare each of these alarms to have fired.
+ for (const auto& kv : matchedAlarms) {
+ declareAnomaly(timestampNs /* TODO: , kv.first */);
+ mAlarms.erase(kv.first);
+ firedAlarms.erase(kv.second); // No one else can also own it, so we're done with it.
+ }
+}
+
+} // namespace statsd
+} // namespace os
+} // namespace android
diff --git a/cmds/statsd/src/anomaly/DurationAnomalyTracker.h b/cmds/statsd/src/anomaly/DurationAnomalyTracker.h
new file mode 100644
index 0000000..182ce3b
--- /dev/null
+++ b/cmds/statsd/src/anomaly/DurationAnomalyTracker.h
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include "AnomalyMonitor.h"
+#include "AnomalyTracker.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+using std::unordered_map;
+
+class DurationAnomalyTracker : public virtual AnomalyTracker {
+public:
+ DurationAnomalyTracker(const Alert& alert, const ConfigKey& configKey);
+
+ virtual ~DurationAnomalyTracker();
+
+ // Starts the alarm at the given timestamp.
+ void startAlarm(const HashableDimensionKey& dimensionKey, const uint64_t& eventTime);
+
+ // Stops the alarm.
+ void stopAlarm(const HashableDimensionKey& dimensionKey);
+
+ // Stop all the alarms owned by this tracker.
+ void stopAllAlarms();
+
+ // Init the AnomalyMonitor which is shared across anomaly trackers.
+ void setAnomalyMonitor(const sp<AnomalyMonitor>& anomalyMonitor) override {
+ mAnomalyMonitor = anomalyMonitor;
+ }
+
+ // Declares the anomaly when the alarm expired given the current timestamp.
+ void declareAnomalyIfAlarmExpired(const HashableDimensionKey& dimensionKey,
+ const uint64_t& timestampNs);
+
+ // Declares an anomaly for each alarm in firedAlarms that belongs to this DurationAnomalyTracker
+ // and removes it from firedAlarms. Does NOT remove the alarm from the AnomalyMonitor.
+ // TODO: This will actually be called from a different thread, so make it thread-safe!
+ // This means that almost every function in DurationAnomalyTracker needs to be locked.
+ // But this should be done at the level of StatsLogProcessor, which needs to lock
+ // mMetricsMangers anyway.
+ void informAlarmsFired(const uint64_t& timestampNs,
+ unordered_set<sp<const AnomalyAlarm>, SpHash<AnomalyAlarm>>& firedAlarms) override;
+
+protected:
+ // The alarms owned by this tracker. The alarm monitor also shares the alarm pointers when they
+ // are still active.
+ std::unordered_map<HashableDimensionKey, sp<const AnomalyAlarm>> mAlarms;
+
+ // Anomaly alarm monitor.
+ sp<AnomalyMonitor> mAnomalyMonitor;
+
+ // Resets all bucket data. For use when all the data gets stale.
+ void resetStorage() override;
+
+ FRIEND_TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp);
+ FRIEND_TEST(OringDurationTrackerTest, TestAnomalyDetection);
+ FRIEND_TEST(MaxDurationTrackerTest, TestAnomalyDetection);
+ FRIEND_TEST(MaxDurationTrackerTest, TestAnomalyDetection);
+ FRIEND_TEST(OringDurationTrackerTest, TestAnomalyDetection);
+};
+
+} // namespace statsd
+} // namespace os
+} // namespace android
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index 1c6d9b0..221a554 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -104,24 +104,18 @@
}
/**
- * An attribution represents an application or module that is part of process where a particular bit
- * of work is done.
+ * This proto represents a node of an attribution chain.
+ * Note: All attribution chains are represented as a repeated field of type
+ * AttributionNode. It is understood that in such arrays, the order is that
+ * of calls, that is [A, B, C] if A calls B that calls C.
*/
-message Attribution {
- // The uid for an application or module.
+message AttributionNode {
+ // The uid for a given element in the attribution chain.
optional int32 uid = 1;
- // The string tag for the attribution node.
- optional string tag = 2;
-}
-/**
- * An attribution chain represents the chained attributions of applications or modules that
- * resulted in a particular bit of work being done.
- * The ordering of the attributions is that of calls, that is uid = [A, B, C] if A calls B that
- * calls C.
- */
-message AttributionChain {
- repeated Attribution attribution = 1;
+ // The (optional) string tag for an element in the attribution chain. If the
+ // element has no tag, it is encoded as an empty string.
+ optional string tag = 2;
}
/*
@@ -151,7 +145,7 @@
*/
message AttributionChainDummyAtom {
- optional AttributionChain attribution_chain = 1;
+ repeated AttributionNode attribution_node = 1;
optional int32 value = 2;
}
@@ -421,8 +415,7 @@
* TODO
*/
message WakelockStateChanged {
- // TODO: Add attribution instead of uid.
- optional int32 uid = 1;
+ repeated AttributionNode attribution_node = 1;
// Type of wakelock.
enum Type {
@@ -780,13 +773,14 @@
* frameworks/base/services/core/java/com/android/server/am/ActivityRecord.java
*/
message ActivityForegroundStateChanged {
+ optional int32 uid = 1;
+ optional string pkg_name = 2;
+ optional string class_name = 3;
+
enum Activity {
MOVE_TO_BACKGROUND = 0;
MOVE_TO_FOREGROUND = 1;
}
- optional int32 uid = 1;
- optional string pkg_name = 2;
- optional string class_name = 3;
optional Activity activity = 4;
}
@@ -851,11 +845,11 @@
// Uid that owns the config whose anomaly detection alert fired.
optional int32 config_uid = 1;
- // Name of the config whose anomaly detection alert fired.
- optional string config_name = 2;
+ // Id of the config whose anomaly detection alert fired.
+ optional int64 config_id = 2;
- // Name of the alert (i.e. name of the anomaly that was detected).
- optional string alert_name = 3;
+ // Id of the alert (i.e. name of the anomaly that was detected).
+ optional int64 alert_id = 3;
}
/**
diff --git a/cmds/statsd/src/condition/CombinationConditionTracker.cpp b/cmds/statsd/src/condition/CombinationConditionTracker.cpp
index bb4b817..afa26f6 100644
--- a/cmds/statsd/src/condition/CombinationConditionTracker.cpp
+++ b/cmds/statsd/src/condition/CombinationConditionTracker.cpp
@@ -30,20 +30,20 @@
using std::unordered_map;
using std::vector;
-CombinationConditionTracker::CombinationConditionTracker(const string& name, const int index)
- : ConditionTracker(name, index) {
- VLOG("creating CombinationConditionTracker %s", mName.c_str());
+CombinationConditionTracker::CombinationConditionTracker(const int64_t& id, const int index)
+ : ConditionTracker(id, index) {
+ VLOG("creating CombinationConditionTracker %lld", (long long)mConditionId);
}
CombinationConditionTracker::~CombinationConditionTracker() {
- VLOG("~CombinationConditionTracker() %s", mName.c_str());
+ VLOG("~CombinationConditionTracker() %lld", (long long)mConditionId);
}
bool CombinationConditionTracker::init(const vector<Predicate>& allConditionConfig,
const vector<sp<ConditionTracker>>& allConditionTrackers,
- const unordered_map<string, int>& conditionNameIndexMap,
+ const unordered_map<int64_t, int>& conditionIdIndexMap,
vector<bool>& stack) {
- VLOG("Combination predicate init() %s", mName.c_str());
+ VLOG("Combination predicate init() %lld", (long long)mConditionId);
if (mInitialized) {
return true;
}
@@ -62,11 +62,11 @@
return false;
}
- for (string child : combinationCondition.predicate()) {
- auto it = conditionNameIndexMap.find(child);
+ for (auto child : combinationCondition.predicate()) {
+ auto it = conditionIdIndexMap.find(child);
- if (it == conditionNameIndexMap.end()) {
- ALOGW("Predicate %s not found in the config", child.c_str());
+ if (it == conditionIdIndexMap.end()) {
+ ALOGW("Predicate %lld not found in the config", (long long)child);
return false;
}
@@ -79,13 +79,13 @@
}
bool initChildSucceeded = childTracker->init(allConditionConfig, allConditionTrackers,
- conditionNameIndexMap, stack);
+ conditionIdIndexMap, stack);
if (!initChildSucceeded) {
- ALOGW("Child initialization failed %s ", child.c_str());
+ ALOGW("Child initialization failed %lld ", (long long)child);
return false;
} else {
- ALOGW("Child initialization success %s ", child.c_str());
+ ALOGW("Child initialization success %lld ", (long long)child);
}
mChildren.push_back(childIndex);
@@ -103,7 +103,7 @@
}
void CombinationConditionTracker::isConditionMet(
- const map<string, HashableDimensionKey>& conditionParameters,
+ const ConditionKey& conditionParameters,
const vector<sp<ConditionTracker>>& allConditions,
vector<ConditionState>& conditionCache) const {
for (const int childIndex : mChildren) {
@@ -154,8 +154,8 @@
}
}
nonSlicedConditionCache[mIndex] = ConditionState::kUnknown;
- ALOGD("CombinationPredicate %s sliced may changed? %d", mName.c_str(),
- conditionChangedCache[mIndex] == true);
+ ALOGD("CombinationPredicate %lld sliced may changed? %d", (long long)mConditionId,
+ conditionChangedCache[mIndex] == true);
}
}
diff --git a/cmds/statsd/src/condition/CombinationConditionTracker.h b/cmds/statsd/src/condition/CombinationConditionTracker.h
index 9336914..dfd3837 100644
--- a/cmds/statsd/src/condition/CombinationConditionTracker.h
+++ b/cmds/statsd/src/condition/CombinationConditionTracker.h
@@ -26,13 +26,13 @@
class CombinationConditionTracker : public virtual ConditionTracker {
public:
- CombinationConditionTracker(const std::string& name, const int index);
+ CombinationConditionTracker(const int64_t& id, const int index);
~CombinationConditionTracker();
bool init(const std::vector<Predicate>& allConditionConfig,
const std::vector<sp<ConditionTracker>>& allConditionTrackers,
- const std::unordered_map<std::string, int>& conditionNameIndexMap,
+ const std::unordered_map<int64_t, int>& conditionIdIndexMap,
std::vector<bool>& stack) override;
void evaluateCondition(const LogEvent& event,
@@ -41,7 +41,7 @@
std::vector<ConditionState>& conditionCache,
std::vector<bool>& changedCache) override;
- void isConditionMet(const std::map<std::string, HashableDimensionKey>& conditionParameters,
+ void isConditionMet(const ConditionKey& conditionParameters,
const std::vector<sp<ConditionTracker>>& allConditions,
std::vector<ConditionState>& conditionCache) const override;
diff --git a/cmds/statsd/src/condition/ConditionTracker.h b/cmds/statsd/src/condition/ConditionTracker.h
index 6f66ad6..773860f 100644
--- a/cmds/statsd/src/condition/ConditionTracker.h
+++ b/cmds/statsd/src/condition/ConditionTracker.h
@@ -32,8 +32,8 @@
class ConditionTracker : public virtual RefBase {
public:
- ConditionTracker(const std::string& name, const int index)
- : mName(name),
+ ConditionTracker(const int64_t& id, const int index)
+ : mConditionId(id),
mIndex(index),
mInitialized(false),
mTrackerIndex(),
@@ -42,17 +42,19 @@
virtual ~ConditionTracker(){};
+ inline const int64_t& getId() { return mConditionId; }
+
// Initialize this ConditionTracker. This initialization is done recursively (DFS). It can also
// be done in the constructor, but we do it separately because (1) easy to return a bool to
// indicate whether the initialization is successful. (2) makes unit test easier.
// allConditionConfig: the list of all Predicate config from statsd_config.
// allConditionTrackers: the list of all ConditionTrackers (this is needed because we may also
// need to call init() on children conditions)
- // conditionNameIndexMap: the mapping from condition name to its index.
+ // conditionIdIndexMap: the mapping from condition id to its index.
// stack: a bit map to keep track which nodes have been visited on the stack in the recursion.
virtual bool init(const std::vector<Predicate>& allConditionConfig,
const std::vector<sp<ConditionTracker>>& allConditionTrackers,
- const std::unordered_map<std::string, int>& conditionNameIndexMap,
+ const std::unordered_map<int64_t, int>& conditionIdIndexMap,
std::vector<bool>& stack) = 0;
// evaluate current condition given the new event.
@@ -83,7 +85,7 @@
// done recursively
// [conditionCache]: the cache holding the condition evaluation values.
virtual void isConditionMet(
- const std::map<std::string, HashableDimensionKey>& conditionParameters,
+ const ConditionKey& conditionParameters,
const std::vector<sp<ConditionTracker>>& allConditions,
std::vector<ConditionState>& conditionCache) const = 0;
@@ -97,9 +99,7 @@
}
protected:
- // We don't really need the string name, but having a name here makes log messages
- // easy to debug.
- const std::string mName;
+ const int64_t mConditionId;
// the index of this condition in the manager's condition list.
const int mIndex;
diff --git a/cmds/statsd/src/condition/ConditionWizard.cpp b/cmds/statsd/src/condition/ConditionWizard.cpp
index 411f7e5..d99c2cc 100644
--- a/cmds/statsd/src/condition/ConditionWizard.cpp
+++ b/cmds/statsd/src/condition/ConditionWizard.cpp
@@ -24,7 +24,7 @@
using std::vector;
ConditionState ConditionWizard::query(const int index,
- const map<string, HashableDimensionKey>& parameters) {
+ const ConditionKey& parameters) {
vector<ConditionState> cache(mAllConditions.size(), ConditionState::kNotEvaluated);
mAllConditions[index]->isConditionMet(parameters, mAllConditions, cache);
diff --git a/cmds/statsd/src/condition/ConditionWizard.h b/cmds/statsd/src/condition/ConditionWizard.h
index 30a3684..4ff5c07 100644
--- a/cmds/statsd/src/condition/ConditionWizard.h
+++ b/cmds/statsd/src/condition/ConditionWizard.h
@@ -19,6 +19,7 @@
#include "ConditionTracker.h"
#include "condition_util.h"
+#include "stats_util.h"
namespace android {
namespace os {
@@ -40,7 +41,7 @@
// the conditionParameters contains the parameters for it's children SimpleConditionTrackers.
virtual ConditionState query(
const int conditionIndex,
- const std::map<std::string, HashableDimensionKey>& conditionParameters);
+ const ConditionKey& conditionParameters);
private:
std::vector<sp<ConditionTracker>> mAllConditions;
diff --git a/cmds/statsd/src/condition/SimpleConditionTracker.cpp b/cmds/statsd/src/condition/SimpleConditionTracker.cpp
index a63bc04..2525721 100644
--- a/cmds/statsd/src/condition/SimpleConditionTracker.cpp
+++ b/cmds/statsd/src/condition/SimpleConditionTracker.cpp
@@ -33,17 +33,17 @@
using std::vector;
SimpleConditionTracker::SimpleConditionTracker(
- const ConfigKey& key, const string& name, const int index,
+ const ConfigKey& key, const int64_t& id, const int index,
const SimplePredicate& simplePredicate,
- const unordered_map<string, int>& trackerNameIndexMap)
- : ConditionTracker(name, index), mConfigKey(key) {
- VLOG("creating SimpleConditionTracker %s", mName.c_str());
+ const unordered_map<int64_t, int>& trackerNameIndexMap)
+ : ConditionTracker(id, index), mConfigKey(key) {
+ VLOG("creating SimpleConditionTracker %lld", (long long)mConditionId);
mCountNesting = simplePredicate.count_nesting();
if (simplePredicate.has_start()) {
auto pair = trackerNameIndexMap.find(simplePredicate.start());
if (pair == trackerNameIndexMap.end()) {
- ALOGW("Start matcher %s not found in the config", simplePredicate.start().c_str());
+ ALOGW("Start matcher %lld not found in the config", (long long)simplePredicate.start());
return;
}
mStartLogMatcherIndex = pair->second;
@@ -55,7 +55,7 @@
if (simplePredicate.has_stop()) {
auto pair = trackerNameIndexMap.find(simplePredicate.stop());
if (pair == trackerNameIndexMap.end()) {
- ALOGW("Stop matcher %s not found in the config", simplePredicate.stop().c_str());
+ ALOGW("Stop matcher %lld not found in the config", (long long)simplePredicate.stop());
return;
}
mStopLogMatcherIndex = pair->second;
@@ -67,7 +67,7 @@
if (simplePredicate.has_stop_all()) {
auto pair = trackerNameIndexMap.find(simplePredicate.stop_all());
if (pair == trackerNameIndexMap.end()) {
- ALOGW("Stop all matcher %s not found in the config", simplePredicate.stop().c_str());
+ ALOGW("Stop all matcher %lld found in the config", (long long)simplePredicate.stop_all());
return;
}
mStopAllLogMatcherIndex = pair->second;
@@ -76,10 +76,9 @@
mStopAllLogMatcherIndex = -1;
}
- mOutputDimension.insert(mOutputDimension.begin(), simplePredicate.dimension().begin(),
- simplePredicate.dimension().end());
+ mOutputDimensions = simplePredicate.dimensions();
- if (mOutputDimension.size() > 0) {
+ if (mOutputDimensions.child_size() > 0) {
mSliced = true;
}
@@ -100,15 +99,15 @@
bool SimpleConditionTracker::init(const vector<Predicate>& allConditionConfig,
const vector<sp<ConditionTracker>>& allConditionTrackers,
- const unordered_map<string, int>& conditionNameIndexMap,
+ const unordered_map<int64_t, int>& conditionIdIndexMap,
vector<bool>& stack) {
// SimpleConditionTracker does not have dependency on other conditions, thus we just return
// if the initialization was successful.
return mInitialized;
}
-void print(map<HashableDimensionKey, int>& conditions, const string& name) {
- VLOG("%s DUMP:", name.c_str());
+void print(map<HashableDimensionKey, int>& conditions, const int64_t& id) {
+ VLOG("%lld DUMP:", (long long)id);
for (const auto& pair : conditions) {
VLOG("\t%s : %d", pair.first.c_str(), pair.second);
}
@@ -136,10 +135,11 @@
// 1. Report the tuple count if the tuple count > soft limit
if (mSlicedConditionState.size() > StatsdStats::kDimensionKeySizeSoftLimit - 1) {
size_t newTupleCount = mSlicedConditionState.size() + 1;
- StatsdStats::getInstance().noteConditionDimensionSize(mConfigKey, mName, newTupleCount);
+ StatsdStats::getInstance().noteConditionDimensionSize(mConfigKey, mConditionId, newTupleCount);
// 2. Don't add more tuples, we are above the allowed threshold. Drop the data.
if (newTupleCount > StatsdStats::kDimensionKeySizeHardLimit) {
- ALOGE("Predicate %s dropping data for dimension key %s", mName.c_str(), newKey.c_str());
+ ALOGE("Predicate %lld dropping data for dimension key %s",
+ (long long)mConditionId, newKey.c_str());
return true;
}
}
@@ -150,6 +150,14 @@
bool matchStart,
std::vector<ConditionState>& conditionCache,
std::vector<bool>& conditionChangedCache) {
+ if ((int)conditionChangedCache.size() <= mIndex) {
+ ALOGE("handleConditionEvent: param conditionChangedCache not initialized.");
+ return;
+ }
+ if ((int)conditionCache.size() <= mIndex) {
+ ALOGE("handleConditionEvent: param conditionCache not initialized.");
+ return;
+ }
bool changed = false;
auto outputIt = mSlicedConditionState.find(outputKey);
ConditionState newCondition;
@@ -215,13 +223,13 @@
// dump all dimensions for debugging
if (DEBUG) {
- print(mSlicedConditionState, mName);
+ print(mSlicedConditionState, mConditionId);
}
conditionChangedCache[mIndex] = changed;
conditionCache[mIndex] = newCondition;
- VLOG("SimplePredicate %s nonSlicedChange? %d", mName.c_str(),
+ VLOG("SimplePredicate %lld nonSlicedChange? %d", (long long)mConditionId,
conditionChangedCache[mIndex] == true);
}
@@ -232,7 +240,8 @@
vector<bool>& conditionChangedCache) {
if (conditionCache[mIndex] != ConditionState::kNotEvaluated) {
// it has been evaluated.
- VLOG("Yes, already evaluated, %s %d", mName.c_str(), conditionCache[mIndex]);
+ VLOG("Yes, already evaluated, %lld %d",
+ (long long)mConditionId, conditionCache[mIndex]);
return;
}
@@ -278,37 +287,65 @@
return;
}
- // outputKey is the output key values. e.g, uid:1234
- const HashableDimensionKey outputKey(getDimensionKey(event, mOutputDimension));
- handleConditionEvent(outputKey, matchedState == 1, conditionCache, conditionChangedCache);
+ // outputKey is the output values. e.g, uid:1234
+ const std::vector<DimensionsValue> outputValues = getDimensionKeys(event, mOutputDimensions);
+ if (outputValues.size() == 0) {
+ // The original implementation would generate an empty string dimension hash when condition
+ // is not sliced.
+ handleConditionEvent(
+ DEFAULT_DIMENSION_KEY, matchedState == 1, conditionCache, conditionChangedCache);
+ } else if (outputValues.size() == 1) {
+ handleConditionEvent(HashableDimensionKey(outputValues[0]), matchedState == 1,
+ conditionCache, conditionChangedCache);
+ } else {
+ // If this event has multiple nodes in the attribution chain, this log event probably will
+ // generate multiple dimensions. If so, we will find if the condition changes for any
+ // dimension and ask the corresponding metric producer to verify whether the actual sliced
+ // condition has changed or not.
+ // A high level assumption is that a predicate is either sliced or unsliced. We will never
+ // have both sliced and unsliced version of a predicate.
+ for (const DimensionsValue& outputValue : outputValues) {
+ vector<ConditionState> dimensionalConditionCache(conditionCache.size(),
+ ConditionState::kNotEvaluated);
+ vector<bool> dimensionalConditionChangedCache(conditionChangedCache.size(), false);
+
+ handleConditionEvent(HashableDimensionKey(outputValue), matchedState == 1,
+ dimensionalConditionCache, dimensionalConditionChangedCache);
+
+ OrConditionState(dimensionalConditionCache, &conditionCache);
+ OrBooleanVector(dimensionalConditionChangedCache, &conditionChangedCache);
+ }
+ }
}
void SimpleConditionTracker::isConditionMet(
- const map<string, HashableDimensionKey>& conditionParameters,
+ const ConditionKey& conditionParameters,
const vector<sp<ConditionTracker>>& allConditions,
vector<ConditionState>& conditionCache) const {
- const auto pair = conditionParameters.find(mName);
- HashableDimensionKey key =
- (pair == conditionParameters.end()) ? DEFAULT_DIMENSION_KEY : pair->second;
+ const auto pair = conditionParameters.find(mConditionId);
- if (pair == conditionParameters.end() && mOutputDimension.size() > 0) {
- ALOGE("Predicate %s output has dimension, but it's not specified in the query!",
- mName.c_str());
+ if (pair == conditionParameters.end() && mOutputDimensions.child_size() > 0) {
+ ALOGE("Predicate %lld output has dimension, but it's not specified in the query!",
+ (long long)mConditionId);
conditionCache[mIndex] = mInitialValue;
return;
}
+ std::vector<HashableDimensionKey> defaultKeys = {DEFAULT_DIMENSION_KEY};
+ const std::vector<HashableDimensionKey> &keys =
+ (pair == conditionParameters.end()) ? defaultKeys : pair->second;
- VLOG("simplePredicate %s query key: %s", mName.c_str(), key.c_str());
-
- auto startedCountIt = mSlicedConditionState.find(key);
- if (startedCountIt == mSlicedConditionState.end()) {
- conditionCache[mIndex] = mInitialValue;
- } else {
- conditionCache[mIndex] =
- startedCountIt->second > 0 ? ConditionState::kTrue : ConditionState::kFalse;
+ ConditionState conditionState = ConditionState::kNotEvaluated;
+ for (const auto& key : keys) {
+ auto startedCountIt = mSlicedConditionState.find(key);
+ if (startedCountIt != mSlicedConditionState.end()) {
+ conditionState = conditionState |
+ (startedCountIt->second > 0 ? ConditionState::kTrue : ConditionState::kFalse);
+ } else {
+ conditionState = conditionState | mInitialValue;
+ }
}
-
- VLOG("Predicate %s return %d", mName.c_str(), conditionCache[mIndex]);
+ conditionCache[mIndex] = conditionState;
+ VLOG("Predicate %lld return %d", (long long)mConditionId, conditionCache[mIndex]);
}
} // namespace statsd
diff --git a/cmds/statsd/src/condition/SimpleConditionTracker.h b/cmds/statsd/src/condition/SimpleConditionTracker.h
index 644d84c..815b445 100644
--- a/cmds/statsd/src/condition/SimpleConditionTracker.h
+++ b/cmds/statsd/src/condition/SimpleConditionTracker.h
@@ -29,15 +29,15 @@
class SimpleConditionTracker : public virtual ConditionTracker {
public:
- SimpleConditionTracker(const ConfigKey& key, const std::string& name, const int index,
+ SimpleConditionTracker(const ConfigKey& key, const int64_t& id, const int index,
const SimplePredicate& simplePredicate,
- const std::unordered_map<std::string, int>& trackerNameIndexMap);
+ const std::unordered_map<int64_t, int>& trackerNameIndexMap);
~SimpleConditionTracker();
bool init(const std::vector<Predicate>& allConditionConfig,
const std::vector<sp<ConditionTracker>>& allConditionTrackers,
- const std::unordered_map<std::string, int>& conditionNameIndexMap,
+ const std::unordered_map<int64_t, int>& conditionIdIndexMap,
std::vector<bool>& stack) override;
void evaluateCondition(const LogEvent& event,
@@ -46,7 +46,7 @@
std::vector<ConditionState>& conditionCache,
std::vector<bool>& changedCache) override;
- void isConditionMet(const std::map<std::string, HashableDimensionKey>& conditionParameters,
+ void isConditionMet(const ConditionKey& conditionParameters,
const std::vector<sp<ConditionTracker>>& allConditions,
std::vector<ConditionState>& conditionCache) const override;
@@ -66,7 +66,7 @@
ConditionState mInitialValue;
- std::vector<KeyMatcher> mOutputDimension;
+ FieldMatcher mOutputDimensions;
std::map<HashableDimensionKey, int> mSlicedConditionState;
diff --git a/cmds/statsd/src/condition/condition_util.cpp b/cmds/statsd/src/condition/condition_util.cpp
index 53ef9d5..a5aee73 100644
--- a/cmds/statsd/src/condition/condition_util.cpp
+++ b/cmds/statsd/src/condition/condition_util.cpp
@@ -27,6 +27,7 @@
#include "ConditionTracker.h"
#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
#include "stats_util.h"
+#include "dimension.h"
namespace android {
namespace os {
@@ -93,23 +94,106 @@
return newCondition;
}
-HashableDimensionKey getDimensionKeyForCondition(const LogEvent& event,
- const MetricConditionLink& link) {
- vector<KeyMatcher> eventKey;
- eventKey.reserve(link.key_in_what().size());
+ConditionState operator|(ConditionState l, ConditionState r) {
+ return l >= r ? l : r;
+}
- for (const auto& key : link.key_in_what()) {
- eventKey.push_back(key);
+void OrConditionState(const std::vector<ConditionState>& ref, vector<ConditionState> * ored) {
+ if (ref.size() != ored->size()) {
+ return;
+ }
+ for (size_t i = 0; i < ored->size(); ++i) {
+ ored->at(i) = ored->at(i) | ref.at(i);
+ }
+}
+
+void OrBooleanVector(const std::vector<bool>& ref, vector<bool> * ored) {
+ if (ref.size() != ored->size()) {
+ return;
+ }
+ for (size_t i = 0; i < ored->size(); ++i) {
+ ored->at(i) = ored->at(i) | ref.at(i);
+ }
+}
+
+void getFieldsFromFieldMatcher(const FieldMatcher& matcher, const Field& parentField,
+ std::vector<Field> *allFields) {
+ Field newParent = parentField;
+ Field* leaf = getSingleLeaf(&newParent);
+ leaf->set_field(matcher.field());
+ if (matcher.child_size() == 0) {
+ allFields->push_back(newParent);
+ return;
+ }
+ for (int i = 0; i < matcher.child_size(); ++i) {
+ leaf->add_child();
+ getFieldsFromFieldMatcher(matcher.child(i), newParent, allFields);
+ }
+}
+
+void getFieldsFromFieldMatcher(const FieldMatcher& matcher, std::vector<Field> *allFields) {
+ Field parentField;
+ getFieldsFromFieldMatcher(matcher, parentField, allFields);
+}
+
+void flattenValueLeaves(const DimensionsValue& value,
+ std::vector<DimensionsValue> *allLaves) {
+ switch (value.value_case()) {
+ case DimensionsValue::ValueCase::kValueStr:
+ case DimensionsValue::ValueCase::kValueInt:
+ case DimensionsValue::ValueCase::kValueLong:
+ case DimensionsValue::ValueCase::kValueBool:
+ case DimensionsValue::ValueCase::kValueFloat:
+ case DimensionsValue::ValueCase::VALUE_NOT_SET:
+ allLaves->push_back(value);
+ break;
+ case DimensionsValue::ValueCase::kValueTuple:
+ for (int i = 0; i < value.value_tuple().dimensions_value_size(); ++i) {
+ flattenValueLeaves(value.value_tuple().dimensions_value(i), allLaves);
+ }
+ break;
+ }
+}
+
+std::vector<HashableDimensionKey> getDimensionKeysForCondition(
+ const LogEvent& event, const MetricConditionLink& link) {
+ std::vector<Field> whatFields;
+ getFieldsFromFieldMatcher(link.dimensions_in_what(), &whatFields);
+ std::vector<Field> conditionFields;
+ getFieldsFromFieldMatcher(link.dimensions_in_condition(), &conditionFields);
+
+ std::vector<HashableDimensionKey> hashableDimensionKeys;
+
+ // TODO(yanglu): here we could simplify the logic to get the leaf value node in what and
+ // directly construct the full condition value tree.
+ std::vector<DimensionsValue> whatValues = getDimensionKeys(event, link.dimensions_in_what());
+
+ for (size_t i = 0; i < whatValues.size(); ++i) {
+ std::vector<DimensionsValue> whatLeaves;
+ flattenValueLeaves(whatValues[i], &whatLeaves);
+ if (whatLeaves.size() != whatFields.size() ||
+ whatLeaves.size() != conditionFields.size()) {
+ ALOGE("Dimensions between what and condition not equal.");
+ return hashableDimensionKeys;
+ }
+ FieldValueMap conditionValueMap;
+ for (size_t j = 0; j < whatLeaves.size(); ++j) {
+ if (!setFieldInLeafValueProto(conditionFields[j], &whatLeaves[j])) {
+ ALOGE("Not able to reset the field for condition leaf value.");
+ return hashableDimensionKeys;
+ }
+ conditionValueMap.insert(std::make_pair(conditionFields[j], whatLeaves[j]));
+ }
+ std::vector<DimensionsValue> conditionValues;
+ findDimensionsValues(conditionValueMap, link.dimensions_in_condition(), &conditionValues);
+ if (conditionValues.size() != 1) {
+ ALOGE("Not able to find unambiguous field value in condition atom.");
+ continue;
+ }
+ hashableDimensionKeys.push_back(HashableDimensionKey(conditionValues[0]));
}
- vector<KeyValuePair> dimensionKey = getDimensionKey(event, eventKey);
-
- for (int i = 0; i < link.key_in_what_size(); i++) {
- auto& kv = dimensionKey[i];
- kv.set_key(link.key_in_condition(i).key());
- }
-
- return HashableDimensionKey(dimensionKey);
+ return hashableDimensionKeys;
}
} // namespace statsd
diff --git a/cmds/statsd/src/condition/condition_util.h b/cmds/statsd/src/condition/condition_util.h
index 934c207..598027b 100644
--- a/cmds/statsd/src/condition/condition_util.h
+++ b/cmds/statsd/src/condition/condition_util.h
@@ -32,12 +32,16 @@
kTrue = 1,
};
+ConditionState operator|(ConditionState l, ConditionState r);
+void OrConditionState(const std::vector<ConditionState>& ref, vector<ConditionState> * ored);
+void OrBooleanVector(const std::vector<bool>& ref, vector<bool> * ored);
+
ConditionState evaluateCombinationCondition(const std::vector<int>& children,
const LogicalOperation& operation,
const std::vector<ConditionState>& conditionCache);
-HashableDimensionKey getDimensionKeyForCondition(const LogEvent& event,
- const MetricConditionLink& link);
+std::vector<HashableDimensionKey> getDimensionKeysForCondition(
+ const LogEvent& event, const MetricConditionLink& link);
} // namespace statsd
} // namespace os
} // namespace android
diff --git a/cmds/statsd/src/config/ConfigKey.cpp b/cmds/statsd/src/config/ConfigKey.cpp
index a365dc0..d791f86 100644
--- a/cmds/statsd/src/config/ConfigKey.cpp
+++ b/cmds/statsd/src/config/ConfigKey.cpp
@@ -27,10 +27,10 @@
ConfigKey::ConfigKey() {
}
-ConfigKey::ConfigKey(const ConfigKey& that) : mName(that.mName), mUid(that.mUid) {
+ConfigKey::ConfigKey(const ConfigKey& that) : mId(that.mId), mUid(that.mUid) {
}
-ConfigKey::ConfigKey(int uid, const string& name) : mName(name), mUid(uid) {
+ConfigKey::ConfigKey(int uid, const int64_t& id) : mId(id), mUid(uid) {
}
ConfigKey::~ConfigKey() {
@@ -38,10 +38,21 @@
string ConfigKey::ToString() const {
ostringstream out;
- out << '(' << mUid << ',' << mName << ')';
+ out << '(' << mUid << ',' << mId << ')';
return out.str();
}
+
+int64_t StrToInt64(const string& str) {
+ char* endp;
+ int64_t value;
+ value = strtoll(str.c_str(), &endp, 0);
+ if (endp == str.c_str() || *endp != '\0') {
+ value = 0;
+ }
+ return value;
+}
+
} // namespace statsd
} // namespace os
} // namespace android
diff --git a/cmds/statsd/src/config/ConfigKey.h b/cmds/statsd/src/config/ConfigKey.h
index 3489c43..3ad0eed 100644
--- a/cmds/statsd/src/config/ConfigKey.h
+++ b/cmds/statsd/src/config/ConfigKey.h
@@ -37,14 +37,14 @@
public:
ConfigKey();
explicit ConfigKey(const ConfigKey& that);
- ConfigKey(int uid, const string& name);
+ ConfigKey(int uid, const int64_t& id);
~ConfigKey();
inline int GetUid() const {
return mUid;
}
- inline const string& GetName() const {
- return mName;
+ inline const int64_t& GetId() const {
+ return mId;
}
inline bool operator<(const ConfigKey& that) const {
@@ -54,17 +54,17 @@
if (mUid > that.mUid) {
return false;
}
- return mName < that.mName;
+ return mId < that.mId;
};
inline bool operator==(const ConfigKey& that) const {
- return mUid == that.mUid && mName == that.mName;
+ return mUid == that.mUid && mId == that.mId;
};
string ToString() const;
private:
- string mName;
+ int64_t mId;
int mUid;
};
@@ -72,6 +72,8 @@
return os << config.ToString();
}
+int64_t StrToInt64(const string& str);
+
} // namespace statsd
} // namespace os
} // namespace android
@@ -87,7 +89,7 @@
template <>
struct hash<ConfigKey> {
std::size_t operator()(const ConfigKey& key) const {
- return (7 * key.GetUid()) ^ ((hash<string>()(key.GetName())));
+ return (7 * key.GetUid()) ^ ((hash<long long>()(key.GetId())));
}
};
diff --git a/cmds/statsd/src/config/ConfigManager.cpp b/cmds/statsd/src/config/ConfigManager.cpp
index cb3f3d6..de75c71 100644
--- a/cmds/statsd/src/config/ConfigManager.cpp
+++ b/cmds/statsd/src/config/ConfigManager.cpp
@@ -103,7 +103,7 @@
}
void ConfigManager::remove_saved_configs(const ConfigKey& key) {
- string prefix = StringPrintf("%d-%s", key.GetUid(), key.GetName().c_str());
+ string prefix = StringPrintf("%d-%lld", key.GetUid(), (long long)key.GetId());
StorageManager::deletePrefixedFiles(STATS_SERVICE_DIR, prefix.c_str());
}
@@ -173,7 +173,7 @@
fprintf(out, "CONFIGURATIONS (%d)\n", (int)mConfigs.size());
fprintf(out, " uid name\n");
for (const auto& key : mConfigs) {
- fprintf(out, " %6d %s\n", key.GetUid(), key.GetName().c_str());
+ fprintf(out, " %6d %lld\n", key.GetUid(), (long long)key.GetId());
auto receiverIt = mConfigReceivers.find(key);
if (receiverIt != mConfigReceivers.end()) {
fprintf(out, " -> received by %s, %s\n", receiverIt->second.first.c_str(),
@@ -189,8 +189,8 @@
remove_saved_configs(key);
// Then we save the latest config.
- string file_name = StringPrintf("%s/%d-%s-%ld", STATS_SERVICE_DIR, key.GetUid(),
- key.GetName().c_str(), time(nullptr));
+ string file_name = StringPrintf("%s/%d-%lld-%ld", STATS_SERVICE_DIR, key.GetUid(),
+ (long long)key.GetId(), time(nullptr));
const int numBytes = config.ByteSize();
vector<uint8_t> buffer(numBytes);
config.SerializeToArray(&buffer[0], numBytes);
@@ -200,7 +200,7 @@
StatsdConfig build_fake_config() {
// HACK: Hard code a test metric for counting screen on events...
StatsdConfig config;
- config.set_name("CONFIG_12345");
+ config.set_id(12345);
int WAKE_LOCK_TAG_ID = 1111; // put a fake id here to make testing easier.
int WAKE_LOCK_UID_KEY_ID = 1;
@@ -209,7 +209,7 @@
int WAKE_LOCK_ACQUIRE_VALUE = 1;
int WAKE_LOCK_RELEASE_VALUE = 0;
- int APP_USAGE_ID = 12345;
+ int APP_USAGE_TAG_ID = 12345;
int APP_USAGE_UID_KEY_ID = 1;
int APP_USAGE_STATE_KEY = 2;
int APP_USAGE_FOREGROUND = 1;
@@ -232,14 +232,14 @@
// Count Screen ON events.
CountMetric* metric = config.add_count_metric();
- metric->set_name("METRIC_1");
- metric->set_what("SCREEN_TURNED_ON");
+ metric->set_id(1); // METRIC_1
+ metric->set_what(102); // "SCREEN_TURNED_ON"
metric->mutable_bucket()->set_bucket_size_millis(30 * 1000L);
// Anomaly threshold for screen-on count.
// TODO(b/70627390): Uncomment once the bug is fixed.
/*Alert* alert = config.add_alert();
- alert->set_name("ALERT_1");
+ alert->set_id("ALERT_1");
alert->set_metric_name("METRIC_1");
alert->set_number_of_buckets(6);
alert->set_trigger_if_sum_gt(10);
@@ -256,17 +256,18 @@
// Count process state changes, slice by uid.
metric = config.add_count_metric();
- metric->set_name("METRIC_2");
- metric->set_what("PROCESS_STATE_CHANGE");
+ metric->set_id(2); // "METRIC_2"
+ metric->set_what(104);
metric->mutable_bucket()->set_bucket_size_millis(30 * 1000L);
- KeyMatcher* keyMatcher = metric->add_dimension();
- keyMatcher->set_key(UID_PROCESS_STATE_UID_KEY);
+ FieldMatcher* dimensions = metric->mutable_dimensions();
+ dimensions->set_field(UID_PROCESS_STATE_TAG_ID);
+ dimensions->add_child()->set_field(UID_PROCESS_STATE_UID_KEY);
// Anomaly threshold for background count.
// TODO(b/70627390): Uncomment once the bug is fixed.
/*
alert = config.add_alert();
- alert->set_name("ALERT_2");
+ alert->set_id("ALERT_2");
alert->set_metric_name("METRIC_2");
alert->set_number_of_buckets(4);
alert->set_trigger_if_sum_gt(30);
@@ -277,79 +278,95 @@
// Count process state changes, slice by uid, while SCREEN_IS_OFF
metric = config.add_count_metric();
- metric->set_name("METRIC_3");
- metric->set_what("PROCESS_STATE_CHANGE");
+ metric->set_id(3);
+ metric->set_what(104);
metric->mutable_bucket()->set_bucket_size_millis(30 * 1000L);
- keyMatcher = metric->add_dimension();
- keyMatcher->set_key(UID_PROCESS_STATE_UID_KEY);
- metric->set_condition("SCREEN_IS_OFF");
+
+ dimensions = metric->mutable_dimensions();
+ dimensions->set_field(UID_PROCESS_STATE_TAG_ID);
+ dimensions->add_child()->set_field(UID_PROCESS_STATE_UID_KEY);
+ metric->set_condition(202);
// Count wake lock, slice by uid, while SCREEN_IS_ON and app in background
metric = config.add_count_metric();
- metric->set_name("METRIC_4");
- metric->set_what("APP_GET_WL");
+ metric->set_id(4);
+ metric->set_what(107);
metric->mutable_bucket()->set_bucket_size_millis(30 * 1000L);
- keyMatcher = metric->add_dimension();
- keyMatcher->set_key(WAKE_LOCK_UID_KEY_ID);
- metric->set_condition("APP_IS_BACKGROUND_AND_SCREEN_ON");
+ dimensions = metric->mutable_dimensions();
+ dimensions->set_field(WAKE_LOCK_TAG_ID);
+ dimensions->add_child()->set_field(WAKE_LOCK_UID_KEY_ID);
+
+
+ metric->set_condition(204);
MetricConditionLink* link = metric->add_links();
- link->set_condition("APP_IS_BACKGROUND");
- link->add_key_in_what()->set_key(WAKE_LOCK_UID_KEY_ID);
- link->add_key_in_condition()->set_key(APP_USAGE_UID_KEY_ID);
+ link->set_condition(203);
+ link->mutable_dimensions_in_what()->set_field(WAKE_LOCK_TAG_ID);
+ link->mutable_dimensions_in_what()->add_child()->set_field(WAKE_LOCK_UID_KEY_ID);
+ link->mutable_dimensions_in_condition()->set_field(APP_USAGE_TAG_ID);
+ link->mutable_dimensions_in_condition()->add_child()->set_field(APP_USAGE_UID_KEY_ID);
// Duration of an app holding any wl, while screen on and app in background, slice by uid
DurationMetric* durationMetric = config.add_duration_metric();
- durationMetric->set_name("METRIC_5");
+ durationMetric->set_id(5);
durationMetric->mutable_bucket()->set_bucket_size_millis(30 * 1000L);
durationMetric->set_aggregation_type(DurationMetric_AggregationType_SUM);
- keyMatcher = durationMetric->add_dimension();
- keyMatcher->set_key(WAKE_LOCK_UID_KEY_ID);
- durationMetric->set_what("WL_HELD_PER_APP_PER_NAME");
- durationMetric->set_condition("APP_IS_BACKGROUND_AND_SCREEN_ON");
+ dimensions = durationMetric->mutable_dimensions();
+ dimensions->set_field(WAKE_LOCK_TAG_ID);
+ dimensions->add_child()->set_field(WAKE_LOCK_UID_KEY_ID);
+ durationMetric->set_what(205);
+ durationMetric->set_condition(204);
link = durationMetric->add_links();
- link->set_condition("APP_IS_BACKGROUND");
- link->add_key_in_what()->set_key(WAKE_LOCK_UID_KEY_ID);
- link->add_key_in_condition()->set_key(APP_USAGE_UID_KEY_ID);
+ link->set_condition(203);
+ link->mutable_dimensions_in_what()->set_field(WAKE_LOCK_TAG_ID);
+ link->mutable_dimensions_in_what()->add_child()->set_field(WAKE_LOCK_UID_KEY_ID);
+ link->mutable_dimensions_in_condition()->set_field(APP_USAGE_TAG_ID);
+ link->mutable_dimensions_in_condition()->add_child()->set_field(APP_USAGE_UID_KEY_ID);
// max Duration of an app holding any wl, while screen on and app in background, slice by uid
durationMetric = config.add_duration_metric();
- durationMetric->set_name("METRIC_6");
+ durationMetric->set_id(6);
durationMetric->mutable_bucket()->set_bucket_size_millis(30 * 1000L);
durationMetric->set_aggregation_type(DurationMetric_AggregationType_MAX_SPARSE);
- keyMatcher = durationMetric->add_dimension();
- keyMatcher->set_key(WAKE_LOCK_UID_KEY_ID);
- durationMetric->set_what("WL_HELD_PER_APP_PER_NAME");
- durationMetric->set_condition("APP_IS_BACKGROUND_AND_SCREEN_ON");
+ dimensions = durationMetric->mutable_dimensions();
+ dimensions->set_field(WAKE_LOCK_TAG_ID);
+ dimensions->add_child()->set_field(WAKE_LOCK_UID_KEY_ID);
+ durationMetric->set_what(205);
+ durationMetric->set_condition(204);
link = durationMetric->add_links();
- link->set_condition("APP_IS_BACKGROUND");
- link->add_key_in_what()->set_key(WAKE_LOCK_UID_KEY_ID);
- link->add_key_in_condition()->set_key(APP_USAGE_UID_KEY_ID);
+ link->set_condition(203);
+ link->mutable_dimensions_in_what()->set_field(WAKE_LOCK_TAG_ID);
+ link->mutable_dimensions_in_what()->add_child()->set_field(WAKE_LOCK_UID_KEY_ID);
+ link->mutable_dimensions_in_condition()->set_field(APP_USAGE_TAG_ID);
+ link->mutable_dimensions_in_condition()->add_child()->set_field(APP_USAGE_UID_KEY_ID);
// Duration of an app holding any wl, while screen on and app in background
durationMetric = config.add_duration_metric();
- durationMetric->set_name("METRIC_7");
+ durationMetric->set_id(7);
durationMetric->mutable_bucket()->set_bucket_size_millis(30 * 1000L);
durationMetric->set_aggregation_type(DurationMetric_AggregationType_MAX_SPARSE);
- durationMetric->set_what("WL_HELD_PER_APP_PER_NAME");
- durationMetric->set_condition("APP_IS_BACKGROUND_AND_SCREEN_ON");
+ durationMetric->set_what(205);
+ durationMetric->set_condition(204);
link = durationMetric->add_links();
- link->set_condition("APP_IS_BACKGROUND");
- link->add_key_in_what()->set_key(WAKE_LOCK_UID_KEY_ID);
- link->add_key_in_condition()->set_key(APP_USAGE_UID_KEY_ID);
+ link->set_condition(203);
+ link->mutable_dimensions_in_what()->set_field(WAKE_LOCK_TAG_ID);
+ link->mutable_dimensions_in_what()->add_child()->set_field(WAKE_LOCK_UID_KEY_ID);
+ link->mutable_dimensions_in_condition()->set_field(APP_USAGE_TAG_ID);
+ link->mutable_dimensions_in_condition()->add_child()->set_field(APP_USAGE_UID_KEY_ID);
+
// Duration of screen on time.
durationMetric = config.add_duration_metric();
- durationMetric->set_name("METRIC_8");
+ durationMetric->set_id(8);
durationMetric->mutable_bucket()->set_bucket_size_millis(10 * 1000L);
durationMetric->set_aggregation_type(DurationMetric_AggregationType_SUM);
- durationMetric->set_what("SCREEN_IS_ON");
+ durationMetric->set_what(201);
// Anomaly threshold for background count.
// TODO(b/70627390): Uncomment once the bug is fixed.
/*
alert = config.add_alert();
- alert->set_name("ALERT_8");
- alert->set_metric_name("METRIC_8");
+ alert->set_id(308);
+ alert->set_metric_id(8);
alert->set_number_of_buckets(4);
alert->set_trigger_if_sum_gt(2000000000); // 2 seconds
alert->set_refractory_period_secs(120);
@@ -358,142 +375,148 @@
// Value metric to count KERNEL_WAKELOCK when screen turned on
ValueMetric* valueMetric = config.add_value_metric();
- valueMetric->set_name("METRIC_6");
- valueMetric->set_what("KERNEL_WAKELOCK");
- valueMetric->set_value_field(KERNEL_WAKELOCK_COUNT_KEY);
- valueMetric->set_condition("SCREEN_IS_ON");
- keyMatcher = valueMetric->add_dimension();
- keyMatcher->set_key(KERNEL_WAKELOCK_NAME_KEY);
+ valueMetric->set_id(11);
+ valueMetric->set_what(109);
+ valueMetric->mutable_value_field()->set_field(KERNEL_WAKELOCK_TAG_ID);
+ valueMetric->mutable_value_field()->add_child()->set_field(KERNEL_WAKELOCK_COUNT_KEY);
+ valueMetric->set_condition(201);
+ dimensions = valueMetric->mutable_dimensions();
+ dimensions->set_field(KERNEL_WAKELOCK_TAG_ID);
+ dimensions->add_child()->set_field(KERNEL_WAKELOCK_NAME_KEY);
// This is for testing easier. We should never set bucket size this small.
valueMetric->mutable_bucket()->set_bucket_size_millis(60 * 1000L);
// Add an EventMetric to log process state change events.
EventMetric* eventMetric = config.add_event_metric();
- eventMetric->set_name("METRIC_9");
- eventMetric->set_what("SCREEN_TURNED_ON");
+ eventMetric->set_id(9);
+ eventMetric->set_what(102); // "SCREEN_TURNED_ON"
// Add an GaugeMetric.
GaugeMetric* gaugeMetric = config.add_gauge_metric();
- gaugeMetric->set_name("METRIC_10");
- gaugeMetric->set_what("DEVICE_TEMPERATURE");
- gaugeMetric->mutable_gauge_fields()->add_field_num(DEVICE_TEMPERATURE_KEY);
+ gaugeMetric->set_id(10);
+ gaugeMetric->set_what(101);
+ auto gaugeFieldMatcher = gaugeMetric->mutable_gauge_fields_filter()->mutable_fields();
+ gaugeFieldMatcher->set_field(DEVICE_TEMPERATURE_TAG_ID);
+ gaugeFieldMatcher->add_child()->set_field(DEVICE_TEMPERATURE_KEY);
gaugeMetric->mutable_bucket()->set_bucket_size_millis(60 * 1000L);
- // Event matchers............
+ // Event matchers.
AtomMatcher* temperatureAtomMatcher = config.add_atom_matcher();
- temperatureAtomMatcher->set_name("DEVICE_TEMPERATURE");
- temperatureAtomMatcher->mutable_simple_atom_matcher()->set_tag(
+ temperatureAtomMatcher->set_id(101); // "DEVICE_TEMPERATURE"
+ temperatureAtomMatcher->mutable_simple_atom_matcher()->set_atom_id(
DEVICE_TEMPERATURE_TAG_ID);
AtomMatcher* eventMatcher = config.add_atom_matcher();
- eventMatcher->set_name("SCREEN_TURNED_ON");
+ eventMatcher->set_id(102); // "SCREEN_TURNED_ON"
SimpleAtomMatcher* simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher();
- simpleAtomMatcher->set_tag(SCREEN_EVENT_TAG_ID);
- KeyValueMatcher* keyValueMatcher = simpleAtomMatcher->add_key_value_matcher();
- keyValueMatcher->mutable_key_matcher()->set_key(SCREEN_EVENT_STATE_KEY);
- keyValueMatcher->set_eq_int(SCREEN_EVENT_ON_VALUE);
+ simpleAtomMatcher->set_atom_id(SCREEN_EVENT_TAG_ID);
+ FieldValueMatcher* fieldValueMatcher = simpleAtomMatcher->add_field_value_matcher();
+ fieldValueMatcher->set_field(SCREEN_EVENT_STATE_KEY);
+ fieldValueMatcher->set_eq_int(SCREEN_EVENT_ON_VALUE);
eventMatcher = config.add_atom_matcher();
- eventMatcher->set_name("SCREEN_TURNED_OFF");
+ eventMatcher->set_id(103); // "SCREEN_TURNED_OFF"
simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher();
- simpleAtomMatcher->set_tag(SCREEN_EVENT_TAG_ID);
- keyValueMatcher = simpleAtomMatcher->add_key_value_matcher();
- keyValueMatcher->mutable_key_matcher()->set_key(SCREEN_EVENT_STATE_KEY);
- keyValueMatcher->set_eq_int(SCREEN_EVENT_OFF_VALUE);
+ simpleAtomMatcher->set_atom_id(SCREEN_EVENT_TAG_ID);
+ fieldValueMatcher = simpleAtomMatcher->add_field_value_matcher();
+ fieldValueMatcher->set_field(SCREEN_EVENT_STATE_KEY);
+ fieldValueMatcher->set_eq_int(SCREEN_EVENT_OFF_VALUE);
eventMatcher = config.add_atom_matcher();
- eventMatcher->set_name("PROCESS_STATE_CHANGE");
+ eventMatcher->set_id(104); // "PROCESS_STATE_CHANGE"
simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher();
- simpleAtomMatcher->set_tag(UID_PROCESS_STATE_TAG_ID);
+ simpleAtomMatcher->set_atom_id(UID_PROCESS_STATE_TAG_ID);
eventMatcher = config.add_atom_matcher();
- eventMatcher->set_name("APP_GOES_BACKGROUND");
+ eventMatcher->set_id(105); // "APP_GOES_BACKGROUND"
simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher();
- simpleAtomMatcher->set_tag(APP_USAGE_ID);
- keyValueMatcher = simpleAtomMatcher->add_key_value_matcher();
- keyValueMatcher->mutable_key_matcher()->set_key(APP_USAGE_STATE_KEY);
- keyValueMatcher->set_eq_int(APP_USAGE_BACKGROUND);
+ simpleAtomMatcher->set_atom_id(APP_USAGE_TAG_ID);
+ fieldValueMatcher = simpleAtomMatcher->add_field_value_matcher();
+ fieldValueMatcher->set_field(APP_USAGE_STATE_KEY);
+ fieldValueMatcher->set_eq_int(APP_USAGE_BACKGROUND);
eventMatcher = config.add_atom_matcher();
- eventMatcher->set_name("APP_GOES_FOREGROUND");
+ eventMatcher->set_id(106); // "APP_GOES_FOREGROUND"
simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher();
- simpleAtomMatcher->set_tag(APP_USAGE_ID);
- keyValueMatcher = simpleAtomMatcher->add_key_value_matcher();
- keyValueMatcher->mutable_key_matcher()->set_key(APP_USAGE_STATE_KEY);
- keyValueMatcher->set_eq_int(APP_USAGE_FOREGROUND);
+ simpleAtomMatcher->set_atom_id(APP_USAGE_TAG_ID);
+ fieldValueMatcher = simpleAtomMatcher->add_field_value_matcher();
+ fieldValueMatcher->set_field(APP_USAGE_STATE_KEY);
+ fieldValueMatcher->set_eq_int(APP_USAGE_FOREGROUND);
eventMatcher = config.add_atom_matcher();
- eventMatcher->set_name("APP_GET_WL");
+ eventMatcher->set_id(107); // "APP_GET_WL"
simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher();
- simpleAtomMatcher->set_tag(WAKE_LOCK_TAG_ID);
- keyValueMatcher = simpleAtomMatcher->add_key_value_matcher();
- keyValueMatcher->mutable_key_matcher()->set_key(WAKE_LOCK_STATE_KEY);
- keyValueMatcher->set_eq_int(WAKE_LOCK_ACQUIRE_VALUE);
+ simpleAtomMatcher->set_atom_id(WAKE_LOCK_TAG_ID);
+ fieldValueMatcher = simpleAtomMatcher->add_field_value_matcher();
+ fieldValueMatcher->set_field(WAKE_LOCK_STATE_KEY);
+ fieldValueMatcher->set_eq_int(WAKE_LOCK_ACQUIRE_VALUE);
eventMatcher = config.add_atom_matcher();
- eventMatcher->set_name("APP_RELEASE_WL");
+ eventMatcher->set_id(108); //"APP_RELEASE_WL"
simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher();
- simpleAtomMatcher->set_tag(WAKE_LOCK_TAG_ID);
- keyValueMatcher = simpleAtomMatcher->add_key_value_matcher();
- keyValueMatcher->mutable_key_matcher()->set_key(WAKE_LOCK_STATE_KEY);
- keyValueMatcher->set_eq_int(WAKE_LOCK_RELEASE_VALUE);
+ simpleAtomMatcher->set_atom_id(WAKE_LOCK_TAG_ID);
+ fieldValueMatcher = simpleAtomMatcher->add_field_value_matcher();
+ fieldValueMatcher->set_field(WAKE_LOCK_STATE_KEY);
+ fieldValueMatcher->set_eq_int(WAKE_LOCK_RELEASE_VALUE);
// pulled events
eventMatcher = config.add_atom_matcher();
- eventMatcher->set_name("KERNEL_WAKELOCK");
+ eventMatcher->set_id(109); // "KERNEL_WAKELOCK"
simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher();
- simpleAtomMatcher->set_tag(KERNEL_WAKELOCK_TAG_ID);
+ simpleAtomMatcher->set_atom_id(KERNEL_WAKELOCK_TAG_ID);
// Predicates.............
Predicate* predicate = config.add_predicate();
- predicate->set_name("SCREEN_IS_ON");
+ predicate->set_id(201); // "SCREEN_IS_ON"
SimplePredicate* simplePredicate = predicate->mutable_simple_predicate();
- simplePredicate->set_start("SCREEN_TURNED_ON");
- simplePredicate->set_stop("SCREEN_TURNED_OFF");
+ simplePredicate->set_start(102); // "SCREEN_TURNED_ON"
+ simplePredicate->set_stop(103);
simplePredicate->set_count_nesting(false);
predicate = config.add_predicate();
- predicate->set_name("SCREEN_IS_OFF");
+ predicate->set_id(202); // "SCREEN_IS_OFF"
simplePredicate = predicate->mutable_simple_predicate();
- simplePredicate->set_start("SCREEN_TURNED_OFF");
- simplePredicate->set_stop("SCREEN_TURNED_ON");
+ simplePredicate->set_start(103);
+ simplePredicate->set_stop(102); // "SCREEN_TURNED_ON"
simplePredicate->set_count_nesting(false);
predicate = config.add_predicate();
- predicate->set_name("APP_IS_BACKGROUND");
+ predicate->set_id(203); // "APP_IS_BACKGROUND"
simplePredicate = predicate->mutable_simple_predicate();
- simplePredicate->set_start("APP_GOES_BACKGROUND");
- simplePredicate->set_stop("APP_GOES_FOREGROUND");
- KeyMatcher* predicate_dimension1 = simplePredicate->add_dimension();
- predicate_dimension1->set_key(APP_USAGE_UID_KEY_ID);
+ simplePredicate->set_start(105);
+ simplePredicate->set_stop(106);
+ FieldMatcher* predicate_dimension1 = simplePredicate->mutable_dimensions();
+ predicate_dimension1->set_field(APP_USAGE_TAG_ID);
+ predicate_dimension1->add_child()->set_field(APP_USAGE_UID_KEY_ID);
simplePredicate->set_count_nesting(false);
predicate = config.add_predicate();
- predicate->set_name("APP_IS_BACKGROUND_AND_SCREEN_ON");
+ predicate->set_id(204); // "APP_IS_BACKGROUND_AND_SCREEN_ON"
Predicate_Combination* combination_predicate = predicate->mutable_combination();
combination_predicate->set_operation(LogicalOperation::AND);
- combination_predicate->add_predicate("APP_IS_BACKGROUND");
- combination_predicate->add_predicate("SCREEN_IS_ON");
+ combination_predicate->add_predicate(203);
+ combination_predicate->add_predicate(201);
predicate = config.add_predicate();
- predicate->set_name("WL_HELD_PER_APP_PER_NAME");
+ predicate->set_id(205); // "WL_HELD_PER_APP_PER_NAME"
simplePredicate = predicate->mutable_simple_predicate();
- simplePredicate->set_start("APP_GET_WL");
- simplePredicate->set_stop("APP_RELEASE_WL");
- KeyMatcher* predicate_dimension = simplePredicate->add_dimension();
- predicate_dimension->set_key(WAKE_LOCK_UID_KEY_ID);
- predicate_dimension = simplePredicate->add_dimension();
- predicate_dimension->set_key(WAKE_LOCK_NAME_KEY);
+ simplePredicate->set_start(107);
+ simplePredicate->set_stop(108);
+ FieldMatcher* predicate_dimension = simplePredicate->mutable_dimensions();
+ predicate_dimension1->set_field(WAKE_LOCK_TAG_ID);
+ predicate_dimension->add_child()->set_field(WAKE_LOCK_UID_KEY_ID);
+ predicate_dimension->add_child()->set_field(WAKE_LOCK_NAME_KEY);
simplePredicate->set_count_nesting(true);
predicate = config.add_predicate();
- predicate->set_name("WL_HELD_PER_APP");
+ predicate->set_id(206); // "WL_HELD_PER_APP"
simplePredicate = predicate->mutable_simple_predicate();
- simplePredicate->set_start("APP_GET_WL");
- simplePredicate->set_stop("APP_RELEASE_WL");
+ simplePredicate->set_start(107);
+ simplePredicate->set_stop(108);
simplePredicate->set_initial_value(SimplePredicate_InitialValue_FALSE);
- predicate_dimension = simplePredicate->add_dimension();
- predicate_dimension->set_key(WAKE_LOCK_UID_KEY_ID);
+ predicate_dimension = simplePredicate->mutable_dimensions();
+ predicate_dimension->set_field(WAKE_LOCK_TAG_ID);
+ predicate_dimension->add_child()->set_field(WAKE_LOCK_UID_KEY_ID);
simplePredicate->set_count_nesting(true);
return config;
diff --git a/cmds/statsd/src/dimension.cpp b/cmds/statsd/src/dimension.cpp
new file mode 100644
index 0000000..8c0ae97
--- /dev/null
+++ b/cmds/statsd/src/dimension.cpp
@@ -0,0 +1,373 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "Log.h"
+
+#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
+#include "dimension.h"
+#include "field_util.h"
+
+
+namespace android {
+namespace os {
+namespace statsd {
+
+const DimensionsValue* getSingleLeafValue(const DimensionsValue* value) {
+ if (value->value_case() == DimensionsValue::ValueCase::kValueTuple) {
+ return getSingleLeafValue(&value->value_tuple().dimensions_value(0));
+ } else {
+ return value;
+ }
+}
+
+DimensionsValue getSingleLeafValue(const DimensionsValue& value) {
+ const DimensionsValue* leafValue = getSingleLeafValue(&value);
+ return *leafValue;
+}
+
+void appendLeafNodeToParent(const Field& field,
+ const DimensionsValue& value,
+ DimensionsValue* parentValue) {
+ if (field.child_size() <= 0) {
+ *parentValue = value;
+ parentValue->set_field(field.field());
+ return;
+ }
+ parentValue->set_field(field.field());
+ int idx = -1;
+ for (int i = 0; i < parentValue->mutable_value_tuple()->dimensions_value_size(); ++i) {
+ if (parentValue->mutable_value_tuple()->dimensions_value(i).field() ==
+ field.child(0).field()) {
+ idx = i;
+ }
+ }
+ if (idx < 0) {
+ parentValue->mutable_value_tuple()->add_dimensions_value();
+ idx = parentValue->mutable_value_tuple()->dimensions_value_size() - 1;
+ }
+ appendLeafNodeToParent(
+ field.child(0), value,
+ parentValue->mutable_value_tuple()->mutable_dimensions_value(idx));
+}
+
+void addNodeToRootDimensionsValues(const Field& field,
+ const DimensionsValue& node,
+ std::vector<DimensionsValue>* rootValues) {
+ if (rootValues == nullptr) {
+ return;
+ }
+ if (rootValues->empty()) {
+ DimensionsValue rootValue;
+ appendLeafNodeToParent(field, node, &rootValue);
+ rootValues->push_back(rootValue);
+ } else {
+ for (size_t i = 0; i < rootValues->size(); ++i) {
+ appendLeafNodeToParent(field, node, &rootValues->at(i));
+ }
+ }
+}
+
+namespace {
+
+void findDimensionsValues(
+ const FieldValueMap& fieldValueMap,
+ const FieldMatcher& matcher,
+ const Field& field,
+ std::vector<DimensionsValue>* rootDimensionsValues);
+
+void findNonRepeatedDimensionsValues(
+ const FieldValueMap& fieldValueMap,
+ const FieldMatcher& matcher,
+ const Field& field,
+ std::vector<DimensionsValue>* rootValues) {
+ if (matcher.child_size() > 0) {
+ for (const auto& childMatcher : matcher.child()) {
+ Field childField = field;
+ appendLeaf(&childField, childMatcher.field());
+ findDimensionsValues(fieldValueMap, childMatcher, childField, rootValues);
+ }
+ } else {
+ auto ret = fieldValueMap.equal_range(field);
+ int found = 0;
+ for (auto it = ret.first; it != ret.second; ++it) {
+ found++;
+ }
+ // Not found.
+ if (found <= 0) {
+ return;
+ }
+ if (found > 1) {
+ ALOGE("Found multiple values for optional field.");
+ return;
+ }
+ addNodeToRootDimensionsValues(field, ret.first->second, rootValues);
+ }
+}
+
+void findRepeatedDimensionsValues(const FieldValueMap& fieldValueMap,
+ const FieldMatcher& matcher,
+ const Field& field,
+ std::vector<DimensionsValue>* rootValues) {
+ if (matcher.position() == Position::FIRST) {
+ Field first_field = field;
+ setPositionForLeaf(&first_field, 0);
+ findNonRepeatedDimensionsValues(fieldValueMap, matcher, first_field, rootValues);
+ } else {
+ auto itLower = fieldValueMap.lower_bound(field);
+ if (itLower == fieldValueMap.end()) {
+ return;
+ }
+ Field next_field = field;
+ getNextField(&next_field);
+ auto itUpper = fieldValueMap.lower_bound(next_field);
+
+ switch (matcher.position()) {
+ case Position::LAST:
+ {
+ itUpper--;
+ if (itUpper != fieldValueMap.end()) {
+ Field last_field = field;
+ int last_index = getPositionByReferenceField(field, itUpper->first);
+ if (last_index < 0) {
+ return;
+ }
+ setPositionForLeaf(&last_field, last_index);
+ findNonRepeatedDimensionsValues(
+ fieldValueMap, matcher, last_field, rootValues);
+ }
+ }
+ break;
+ case Position::ANY:
+ {
+ std::set<int> indexes;
+ for (auto it = itLower; it != itUpper; ++it) {
+ int index = getPositionByReferenceField(field, it->first);
+ if (index >= 0) {
+ indexes.insert(index);
+ }
+ }
+ if (!indexes.empty()) {
+ Field any_field = field;
+ std::vector<DimensionsValue> allValues;
+ for (const int index : indexes) {
+ setPositionForLeaf(&any_field, index);
+ std::vector<DimensionsValue> newValues = *rootValues;
+ findNonRepeatedDimensionsValues(
+ fieldValueMap, matcher, any_field, &newValues);
+ allValues.insert(allValues.end(), newValues.begin(), newValues.end());
+ }
+ rootValues->clear();
+ rootValues->insert(rootValues->end(), allValues.begin(), allValues.end());
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+void findDimensionsValues(
+ const FieldValueMap& fieldValueMap,
+ const FieldMatcher& matcher,
+ const Field& field,
+ std::vector<DimensionsValue>* rootDimensionsValues) {
+ if (!matcher.has_position()) {
+ findNonRepeatedDimensionsValues(fieldValueMap, matcher, field, rootDimensionsValues);
+ } else {
+ findRepeatedDimensionsValues(fieldValueMap, matcher, field, rootDimensionsValues);
+ }
+}
+
+} // namespace
+
+void findDimensionsValues(
+ const FieldValueMap& fieldValueMap,
+ const FieldMatcher& matcher,
+ std::vector<DimensionsValue>* rootDimensionsValues) {
+ findDimensionsValues(fieldValueMap, matcher,
+ buildSimpleAtomField(matcher.field()), rootDimensionsValues);
+}
+
+FieldMatcher buildSimpleAtomFieldMatcher(const int tagId) {
+ FieldMatcher matcher;
+ matcher.set_field(tagId);
+ return matcher;
+}
+
+FieldMatcher buildSimpleAtomFieldMatcher(const int tagId, const int atomFieldNum) {
+ FieldMatcher matcher;
+ matcher.set_field(tagId);
+ matcher.add_child()->set_field(atomFieldNum);
+ return matcher;
+}
+
+constexpr int ATTRIBUTION_FIELD_NUM_IN_ATOM_PROTO = 1;
+constexpr int UID_FIELD_NUM_IN_ATTRIBUTION_NODE_PROTO = 1;
+constexpr int TAG_FIELD_NUM_IN_ATTRIBUTION_NODE_PROTO = 2;
+
+void buildAttributionUidFieldMatcher(const int tagId, const Position position,
+ FieldMatcher *matcher) {
+ matcher->set_field(tagId);
+ matcher->add_child()->set_field(ATTRIBUTION_FIELD_NUM_IN_ATOM_PROTO);
+ FieldMatcher* child = matcher->mutable_child(0)->add_child();
+ child->set_field(UID_FIELD_NUM_IN_ATTRIBUTION_NODE_PROTO);
+}
+
+void buildAttributionTagFieldMatcher(const int tagId, const Position position,
+ FieldMatcher *matcher) {
+ matcher->set_field(tagId);
+ FieldMatcher* child = matcher->add_child();
+ child->set_field(ATTRIBUTION_FIELD_NUM_IN_ATOM_PROTO);
+ child->set_position(position);
+ child = child->add_child();
+ child->set_field(TAG_FIELD_NUM_IN_ATTRIBUTION_NODE_PROTO);
+}
+
+void buildAttributionFieldMatcher(const int tagId, const Position position,
+ FieldMatcher *matcher) {
+ matcher->set_field(tagId);
+ FieldMatcher* child = matcher->add_child();
+ child->set_field(ATTRIBUTION_FIELD_NUM_IN_ATOM_PROTO);
+ child->set_position(position);
+ child = child->add_child();
+ child->set_field(UID_FIELD_NUM_IN_ATTRIBUTION_NODE_PROTO);
+ child->set_field(TAG_FIELD_NUM_IN_ATTRIBUTION_NODE_PROTO);
+}
+
+void DimensionsValueToString(const DimensionsValue& value, std::string *flattened) {
+ *flattened += std::to_string(value.field());
+ *flattened += ":";
+ switch (value.value_case()) {
+ case DimensionsValue::ValueCase::kValueStr:
+ *flattened += value.value_str();
+ break;
+ case DimensionsValue::ValueCase::kValueInt:
+ *flattened += std::to_string(value.value_int());
+ break;
+ case DimensionsValue::ValueCase::kValueLong:
+ *flattened += std::to_string(value.value_long());
+ break;
+ case DimensionsValue::ValueCase::kValueBool:
+ *flattened += std::to_string(value.value_bool());
+ break;
+ case DimensionsValue::ValueCase::kValueFloat:
+ *flattened += std::to_string(value.value_float());
+ break;
+ case DimensionsValue::ValueCase::kValueTuple:
+ {
+ *flattened += "{";
+ for (int i = 0; i < value.value_tuple().dimensions_value_size(); ++i) {
+ DimensionsValueToString(value.value_tuple().dimensions_value(i), flattened);
+ *flattened += "|";
+ }
+ *flattened += "}";
+ }
+ break;
+ case DimensionsValue::ValueCase::VALUE_NOT_SET:
+ break;
+ }
+}
+
+void getDimensionsValueLeafNodes(
+ const DimensionsValue& value, std::vector<DimensionsValue> *leafNodes) {
+ switch (value.value_case()) {
+ case DimensionsValue::ValueCase::kValueStr:
+ case DimensionsValue::ValueCase::kValueInt:
+ case DimensionsValue::ValueCase::kValueLong:
+ case DimensionsValue::ValueCase::kValueBool:
+ case DimensionsValue::ValueCase::kValueFloat:
+ leafNodes->push_back(value);
+ break;
+ case DimensionsValue::ValueCase::kValueTuple:
+ for (int i = 0; i < value.value_tuple().dimensions_value_size(); ++i) {
+ getDimensionsValueLeafNodes(value.value_tuple().dimensions_value(i), leafNodes);
+ }
+ break;
+ case DimensionsValue::ValueCase::VALUE_NOT_SET:
+ break;
+ default:
+ break;
+ }
+}
+
+std::string DimensionsValueToString(const DimensionsValue& value) {
+ std::string flatten;
+ DimensionsValueToString(value, &flatten);
+ return flatten;
+}
+
+bool IsSubDimension(const DimensionsValue& dimension, const DimensionsValue& sub) {
+ if (dimension.field() != sub.field()) {
+ return false;
+ }
+ if (dimension.value_case() != sub.value_case()) {
+ return false;
+ }
+ switch (dimension.value_case()) {
+ case DimensionsValue::ValueCase::kValueStr:
+ return dimension.value_str() == sub.value_str();
+ case DimensionsValue::ValueCase::kValueInt:
+ return dimension.value_int() == sub.value_int();
+ case DimensionsValue::ValueCase::kValueLong:
+ return dimension.value_long() == sub.value_long();
+ case DimensionsValue::ValueCase::kValueBool:
+ return dimension.value_bool() == sub.value_bool();
+ case DimensionsValue::ValueCase::kValueFloat:
+ return dimension.value_float() == sub.value_float();
+ case DimensionsValue::ValueCase::kValueTuple: {
+ if (dimension.value_tuple().dimensions_value_size() < sub.value_tuple().dimensions_value_size()) {
+ return false;
+ }
+ bool allSub = true;
+ for (int i = 0; i < sub.value_tuple().dimensions_value_size(); ++i) {
+ bool isSub = false;
+ for (int j = 0; !isSub && j < dimension.value_tuple().dimensions_value_size(); ++j) {
+ isSub |= IsSubDimension(dimension.value_tuple().dimensions_value(j),
+ sub.value_tuple().dimensions_value(i));
+ }
+ allSub &= isSub;
+ }
+ return allSub;
+ }
+ break;
+ case DimensionsValue::ValueCase::VALUE_NOT_SET:
+ return false;
+ default:
+ return false;
+ }
+}
+
+long getLongFromDimenValue(const DimensionsValue& dimensionValue) {
+ switch (dimensionValue.value_case()) {
+ case DimensionsValue::ValueCase::kValueInt:
+ return dimensionValue.value_int();
+ case DimensionsValue::ValueCase::kValueLong:
+ return dimensionValue.value_long();
+ case DimensionsValue::ValueCase::kValueBool:
+ return dimensionValue.value_bool() ? 1 : 0;
+ case DimensionsValue::ValueCase::kValueFloat:
+ return (int64_t)dimensionValue.value_float();
+ case DimensionsValue::ValueCase::kValueTuple:
+ case DimensionsValue::ValueCase::kValueStr:
+ case DimensionsValue::ValueCase::VALUE_NOT_SET:
+ return 0;
+ }
+}
+
+} // namespace statsd
+} // namespace os
+} // namespace android
diff --git a/cmds/statsd/src/dimension.h b/cmds/statsd/src/dimension.h
new file mode 100644
index 0000000..5bb64a9
--- /dev/null
+++ b/cmds/statsd/src/dimension.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <log/logprint.h>
+#include <set>
+#include <vector>
+#include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
+#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
+#include "field_util.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+
+// Returns the leaf node from the DimensionsValue proto. It assume that the input has only one
+// leaf node at most.
+const DimensionsValue* getSingleLeafValue(const DimensionsValue* value);
+DimensionsValue getSingleLeafValue(const DimensionsValue& value);
+
+// Constructs the DimensionsValue protos from the FieldMatcher. Each DimensionsValue proto
+// represents a tree. When the input proto has repeated fields and the input "dimensions" wants
+// "ANY" locations, it will return multiple trees.
+void findDimensionsValues(
+ const FieldValueMap& fieldValueMap,
+ const FieldMatcher& matcher,
+ std::vector<DimensionsValue>* rootDimensionsValues);
+
+// Utils to build FieldMatcher proto for simple one-depth atoms.
+FieldMatcher buildSimpleAtomFieldMatcher(const int tagId, const int atomFieldNum);
+FieldMatcher buildSimpleAtomFieldMatcher(const int tagId);
+
+// Utils to build FieldMatcher proto for attribution nodes.
+FieldMatcher buildAttributionUidFieldMatcher(const int tagId, const Position position);
+FieldMatcher buildAttributionTagFieldMatcher(const int tagId, const Position position);
+FieldMatcher buildAttributionFieldMatcher(const int tagId, const Position position);
+
+// Utils to print pretty string for DimensionsValue proto.
+std::string DimensionsValueToString(const DimensionsValue& value);
+void DimensionsValueToString(const DimensionsValue& value, std::string *flattened);
+
+bool IsSubDimension(const DimensionsValue& dimension, const DimensionsValue& sub);
+
+// Helper function to get long value from the DimensionsValue proto.
+long getLongFromDimenValue(const DimensionsValue& dimensionValue);
+} // namespace statsd
+} // namespace os
+} // namespace android
diff --git a/cmds/statsd/src/field_util.cpp b/cmds/statsd/src/field_util.cpp
new file mode 100644
index 0000000..d10e167
--- /dev/null
+++ b/cmds/statsd/src/field_util.cpp
@@ -0,0 +1,318 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "Log.h"
+#include "field_util.h"
+
+#include <set>
+#include <vector>
+
+namespace android {
+namespace os {
+namespace statsd {
+
+// This function is to compare two Field trees where each node has at most one child.
+bool CompareField(const Field& a, const Field& b) {
+ if (a.field() < b.field()) {
+ return true;
+ }
+ if (a.field() > b.field()) {
+ return false;
+ }
+ if (a.position_index() < b.position_index()) {
+ return true;
+ }
+ if (a.position_index() > b.position_index()) {
+ return false;
+ }
+ if (a.child_size() < b.child_size()) {
+ return true;
+ }
+ if (a.child_size() > b.child_size()) {
+ return false;
+ }
+ if (a.child_size() == 0 && b.child_size() == 0) {
+ return false;
+ }
+ return CompareField(a.child(0), b.child(0));
+}
+
+const Field* getSingleLeaf(const Field* field) {
+ if (field->child_size() <= 0) {
+ return field;
+ } else {
+ return getSingleLeaf(&field->child(0));
+ }
+}
+
+Field* getSingleLeaf(Field* field) {
+ if (field->child_size() <= 0) {
+ return field;
+ } else {
+ return getSingleLeaf(field->mutable_child(0));
+ }
+}
+
+void FieldToString(const Field& field, std::string *flattened) {
+ *flattened += std::to_string(field.field());
+ if (field.has_position_index()) {
+ *flattened += "[";
+ *flattened += std::to_string(field.position_index());
+ *flattened += "]";
+ }
+ if (field.child_size() <= 0) {
+ return;
+ }
+ *flattened += ".";
+ *flattened += "{";
+ for (int i = 0 ; i < field.child_size(); ++i) {
+ *flattened += FieldToString(field.child(i));
+ }
+ *flattened += "},";
+}
+
+std::string FieldToString(const Field& field) {
+ std::string flatten;
+ FieldToString(field, &flatten);
+ return flatten;
+}
+
+bool setFieldInLeafValueProto(const Field &field, DimensionsValue* leafValue) {
+ if (field.child_size() <= 0) {
+ leafValue->set_field(field.field());
+ return true;
+ } else if (field.child_size() == 1) {
+ return setFieldInLeafValueProto(field.child(0), leafValue);
+ } else {
+ ALOGE("Not able to set the 'field' in leaf value for multiple children.");
+ return false;
+ }
+}
+
+Field buildAtomField(const int tagId, const Field &atomField) {
+ Field field;
+ *field.add_child() = atomField;
+ field.set_field(tagId);
+ return field;
+}
+
+Field buildSimpleAtomField(const int tagId, const int atomFieldNum) {
+ Field field;
+ field.set_field(tagId);
+ field.add_child()->set_field(atomFieldNum);
+ return field;
+}
+
+Field buildSimpleAtomField(const int tagId) {
+ Field field;
+ field.set_field(tagId);
+ return field;
+}
+
+void appendLeaf(Field *parent, int node_field_num) {
+ if (!parent->has_field()) {
+ parent->set_field(node_field_num);
+ } else if (parent->child_size() <= 0) {
+ parent->add_child()->set_field(node_field_num);
+ } else {
+ appendLeaf(parent->mutable_child(0), node_field_num);
+ }
+}
+
+void appendLeaf(Field *parent, int node_field_num, int position) {
+ if (!parent->has_field()) {
+ parent->set_field(node_field_num);
+ parent->set_position_index(position);
+ } else if (parent->child_size() <= 0) {
+ auto child = parent->add_child();
+ child->set_field(node_field_num);
+ child->set_position_index(position);
+ } else {
+ appendLeaf(parent->mutable_child(0), node_field_num, position);
+ }
+}
+
+
+void getNextField(Field* field) {
+ if (field->child_size() <= 0) {
+ field->set_field(field->field() + 1);
+ return;
+ }
+ if (field->child_size() != 1) {
+ return;
+ }
+ getNextField(field->mutable_child(0));
+}
+
+void increasePosition(Field *field) {
+ if (!field->has_position_index()) {
+ field->set_position_index(0);
+ } else {
+ field->set_position_index(field->position_index() + 1);
+ }
+}
+
+int getPositionByReferenceField(const Field& ref, const Field& field_with_index) {
+ if (ref.child_size() <= 0) {
+ return field_with_index.position_index();
+ }
+ if (ref.child_size() != 1 ||
+ field_with_index.child_size() != 1) {
+ return -1;
+ }
+ return getPositionByReferenceField(ref.child(0), field_with_index.child(0));
+}
+
+void setPositionForLeaf(Field *field, int index) {
+ if (field->child_size() <= 0) {
+ field->set_position_index(index);
+ } else {
+ setPositionForLeaf(field->mutable_child(0), index);
+ }
+}
+
+void findFields(
+ const FieldValueMap& fieldValueMap,
+ const FieldMatcher& matcher,
+ const Field& field,
+ std::vector<Field>* rootFields);
+
+void findNonRepeatedFields(
+ const FieldValueMap& fieldValueMap,
+ const FieldMatcher& matcher,
+ const Field& field,
+ std::vector<Field>* rootFields) {
+ if (matcher.child_size() > 0) {
+ for (const auto& childMatcher : matcher.child()) {
+ Field childField = field;
+ appendLeaf(&childField, childMatcher.field());
+ findFields(fieldValueMap, childMatcher, childField, rootFields);
+ }
+ } else {
+ auto ret = fieldValueMap.equal_range(field);
+ int found = 0;
+ for (auto it = ret.first; it != ret.second; ++it) {
+ found++;
+ }
+ // Not found.
+ if (found <= 0) {
+ return;
+ }
+ if (found > 1) {
+ ALOGE("Found multiple values for optional field.");
+ return;
+ }
+ rootFields->push_back(ret.first->first);
+ }
+}
+
+void findRepeatedFields(const FieldValueMap& fieldValueMap, const FieldMatcher& matcher,
+ const Field& field, std::vector<Field>* rootFields) {
+ if (matcher.position() == Position::FIRST) {
+ Field first_field = field;
+ setPositionForLeaf(&first_field, 0);
+ findNonRepeatedFields(fieldValueMap, matcher, first_field, rootFields);
+ } else {
+ auto itLower = fieldValueMap.lower_bound(field);
+ if (itLower == fieldValueMap.end()) {
+ return;
+ }
+ Field next_field = field;
+ getNextField(&next_field);
+ auto itUpper = fieldValueMap.lower_bound(next_field);
+
+ switch (matcher.position()) {
+ case Position::LAST:
+ {
+ itUpper--;
+ if (itUpper != fieldValueMap.end()) {
+ Field last_field = field;
+ int last_index = getPositionByReferenceField(field, itUpper->first);
+ if (last_index < 0) {
+ return;
+ }
+ setPositionForLeaf(&last_field, last_index);
+ findNonRepeatedFields(
+ fieldValueMap, matcher, last_field, rootFields);
+ }
+ }
+ break;
+ case Position::ANY:
+ {
+ std::set<int> indexes;
+ for (auto it = itLower; it != itUpper; ++it) {
+ int index = getPositionByReferenceField(field, it->first);
+ if (index >= 0) {
+ indexes.insert(index);
+ }
+ }
+ if (!indexes.empty()) {
+ Field any_field = field;
+ for (const int index : indexes) {
+ setPositionForLeaf(&any_field, index);
+ findNonRepeatedFields(
+ fieldValueMap, matcher, any_field, rootFields);
+ }
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+void findFields(
+ const FieldValueMap& fieldValueMap,
+ const FieldMatcher& matcher,
+ const Field& field,
+ std::vector<Field>* rootFields) {
+ if (!matcher.has_position()) {
+ findNonRepeatedFields(fieldValueMap, matcher, field, rootFields);
+ } else {
+ findRepeatedFields(fieldValueMap, matcher, field, rootFields);
+ }
+}
+
+void filterFields(const FieldMatcher& matcher, FieldValueMap* fieldValueMap) {
+ std::vector<Field> rootFields;
+ findFields(*fieldValueMap, matcher, buildSimpleAtomField(matcher.field()), &rootFields);
+ std::set<Field, FieldCmp> rootFieldSet(rootFields.begin(), rootFields.end());
+ auto it = fieldValueMap->begin();
+ while (it != fieldValueMap->end()) {
+ if (rootFieldSet.find(it->first) == rootFieldSet.end()) {
+ it = fieldValueMap->erase(it);
+ } else {
+ it++;
+ }
+ }
+}
+
+bool hasLeafNode(const FieldMatcher& matcher) {
+ if (!matcher.has_field()) {
+ return false;
+ }
+ for (int i = 0; i < matcher.child_size(); ++i) {
+ if (hasLeafNode(matcher.child(i))) {
+ return true;
+ }
+ }
+ return true;
+}
+
+} // namespace statsd
+} // namespace os
+} // namespace android
diff --git a/cmds/statsd/src/field_util.h b/cmds/statsd/src/field_util.h
new file mode 100644
index 0000000..5907e17
--- /dev/null
+++ b/cmds/statsd/src/field_util.h
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
+#include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
+
+#include <unordered_map>
+
+namespace android {
+namespace os {
+namespace statsd {
+
+// Function to sort the Field protos.
+bool CompareField(const Field& a, const Field& b);
+struct FieldCmp {
+ bool operator()(const Field& a, const Field& b) const {
+ return CompareField(a, b);
+ }
+};
+
+// Flattened dimensions value map. To save space, usually the key contains the tree structure info
+// and value field is only leaf node.
+typedef std::map<Field, DimensionsValue, FieldCmp> FieldValueMap;
+
+// Util function to print the Field proto.
+std::string FieldToString(const Field& field);
+
+// Util function to find the leaf node from the input Field proto and set it in the corresponding
+// value proto.
+bool setFieldInLeafValueProto(const Field &field, DimensionsValue* leafValue);
+
+// Returns the leaf node from the Field proto. It assume that the input has only one
+// leaf node at most.
+const Field* getSingleLeaf(const Field* field);
+Field* getSingleLeaf(Field* field);
+
+// Append a node to the current leaf. It assumes that the input "parent" has one leaf node at most.
+void appendLeaf(Field *parent, int node_field_num);
+void appendLeaf(Field *parent, int node_field_num, int position);
+
+// Given the field sorting logic, this function is to increase the "field" at the leaf node.
+void getNextField(Field* field);
+
+// Increase the position index for the node. If the "position_index" is not set, set it as 0.
+void increasePosition(Field *field);
+
+// Finds the leaf node and set the index there.
+void setPositionForLeaf(Field *field, int index);
+
+// Returns true if the matcher has specified at least one leaf node.
+bool hasLeafNode(const FieldMatcher& matcher);
+
+// The two input Field proto are describing the same tree structure. Both contain one leaf node at
+// most. This is find the position index info for the leaf node at "reference" stored in the
+// "field_with_index" tree.
+int getPositionByReferenceField(const Field& reference, const Field& field_with_index);
+
+// Utils to build the Field proto for simple atom fields.
+Field buildAtomField(const int tagId, const Field &atomField);
+Field buildSimpleAtomField(const int tagId, const int atomFieldNum);
+Field buildSimpleAtomField(const int tagId);
+
+// Find out all the fields specified by the matcher.
+void findFields(
+ const FieldValueMap& fieldValueMap,
+ const FieldMatcher& matcher,
+ std::vector<Field>* rootFields);
+
+// Filter out the fields not in the field matcher.
+void filterFields(const FieldMatcher& matcher, FieldValueMap* fieldValueMap);
+
+} // namespace statsd
+} // namespace os
+} // namespace android
diff --git a/cmds/statsd/src/guardrail/StatsdStats.cpp b/cmds/statsd/src/guardrail/StatsdStats.cpp
index bf277f0..33927aa 100644
--- a/cmds/statsd/src/guardrail/StatsdStats.cpp
+++ b/cmds/statsd/src/guardrail/StatsdStats.cpp
@@ -80,7 +80,7 @@
StatsdStatsReport_ConfigStats configStats;
configStats.set_uid(key.GetUid());
- configStats.set_name(key.GetName());
+ configStats.set_id(key.GetId());
configStats.set_creation_time_sec(nowTimeSec);
configStats.set_metric_count(metricsCount);
configStats.set_condition_count(conditionsCount);
@@ -196,34 +196,34 @@
mUidMapStats.set_bytes_used(bytes);
}
-void StatsdStats::noteConditionDimensionSize(const ConfigKey& key, const string& name, int size) {
+void StatsdStats::noteConditionDimensionSize(const ConfigKey& key, const int64_t& id, int size) {
lock_guard<std::mutex> lock(mLock);
// if name doesn't exist before, it will create the key with count 0.
auto& conditionSizeMap = mConditionStats[key];
- if (size > conditionSizeMap[name]) {
- conditionSizeMap[name] = size;
+ if (size > conditionSizeMap[id]) {
+ conditionSizeMap[id] = size;
}
}
-void StatsdStats::noteMetricDimensionSize(const ConfigKey& key, const string& name, int size) {
+void StatsdStats::noteMetricDimensionSize(const ConfigKey& key, const int64_t& id, int size) {
lock_guard<std::mutex> lock(mLock);
// if name doesn't exist before, it will create the key with count 0.
auto& metricsDimensionMap = mMetricsStats[key];
- if (size > metricsDimensionMap[name]) {
- metricsDimensionMap[name] = size;
+ if (size > metricsDimensionMap[id]) {
+ metricsDimensionMap[id] = size;
}
}
-void StatsdStats::noteMatcherMatched(const ConfigKey& key, const string& name) {
+void StatsdStats::noteMatcherMatched(const ConfigKey& key, const int64_t& id) {
lock_guard<std::mutex> lock(mLock);
auto& matcherStats = mMatcherStats[key];
- matcherStats[name]++;
+ matcherStats[id]++;
}
-void StatsdStats::noteAnomalyDeclared(const ConfigKey& key, const string& name) {
+void StatsdStats::noteAnomalyDeclared(const ConfigKey& key, const int64_t& id) {
lock_guard<std::mutex> lock(mLock);
auto& alertStats = mAlertStats[key];
- alertStats[name]++;
+ alertStats[id]++;
}
void StatsdStats::noteRegisteredAnomalyAlarmChanged() {
@@ -279,9 +279,10 @@
const auto& matcherStats = mMatcherStats[key];
for (const auto& stats : matcherStats) {
auto output = configStats.add_matcher_stats();
- output->set_name(stats.first);
+ output->set_id(stats.first);
output->set_matched_times(stats.second);
- VLOG("matcher %s matched %d times", stats.first.c_str(), stats.second);
+ VLOG("matcher %lld matched %d times",
+ (long long)stats.first, stats.second);
}
}
// Add condition stats
@@ -289,9 +290,10 @@
const auto& conditionStats = mConditionStats[key];
for (const auto& stats : conditionStats) {
auto output = configStats.add_condition_stats();
- output->set_name(stats.first);
+ output->set_id(stats.first);
output->set_max_tuple_counts(stats.second);
- VLOG("condition %s max output tuple size %d", stats.first.c_str(), stats.second);
+ VLOG("condition %lld max output tuple size %d",
+ (long long)stats.first, stats.second);
}
}
// Add metrics stats
@@ -299,9 +301,10 @@
const auto& conditionStats = mMetricsStats[key];
for (const auto& stats : conditionStats) {
auto output = configStats.add_metric_stats();
- output->set_name(stats.first);
+ output->set_id(stats.first);
output->set_max_tuple_counts(stats.second);
- VLOG("metrics %s max output tuple size %d", stats.first.c_str(), stats.second);
+ VLOG("metrics %lld max output tuple size %d",
+ (long long)stats.first, stats.second);
}
}
// Add anomaly detection alert stats
@@ -309,9 +312,9 @@
const auto& alertStats = mAlertStats[key];
for (const auto& stats : alertStats) {
auto output = configStats.add_alert_stats();
- output->set_name(stats.first);
+ output->set_id(stats.first);
output->set_alerted_times(stats.second);
- VLOG("alert %s declared %d times", stats.first.c_str(), stats.second);
+ VLOG("alert %lld declared %d times", (long long)stats.first, stats.second);
}
}
}
@@ -343,9 +346,9 @@
// in production.
if (DEBUG) {
VLOG("*****ICEBOX*****");
- VLOG("Config {%d-%s}: creation=%d, deletion=%d, #metric=%d, #condition=%d, "
+ VLOG("Config {%d-%lld}: creation=%d, deletion=%d, #metric=%d, #condition=%d, "
"#matcher=%d, #alert=%d, #valid=%d",
- configStats.uid(), configStats.name().c_str(), configStats.creation_time_sec(),
+ configStats.uid(), (long long)configStats.id(), configStats.creation_time_sec(),
configStats.deletion_time_sec(), configStats.metric_count(),
configStats.condition_count(), configStats.matcher_count(),
configStats.alert_count(), configStats.is_valid());
@@ -364,9 +367,9 @@
auto& configStats = pair.second;
if (DEBUG) {
VLOG("********Active Configs***********");
- VLOG("Config {%d-%s}: creation=%d, deletion=%d, #metric=%d, #condition=%d, "
+ VLOG("Config {%d-%lld}: creation=%d, deletion=%d, #metric=%d, #condition=%d, "
"#matcher=%d, #alert=%d, #valid=%d",
- configStats.uid(), configStats.name().c_str(), configStats.creation_time_sec(),
+ configStats.uid(), (long long)configStats.id(), configStats.creation_time_sec(),
configStats.deletion_time_sec(), configStats.metric_count(),
configStats.condition_count(), configStats.matcher_count(),
configStats.alert_count(), configStats.is_valid());
diff --git a/cmds/statsd/src/guardrail/StatsdStats.h b/cmds/statsd/src/guardrail/StatsdStats.h
index cb868e1f..45aa192 100644
--- a/cmds/statsd/src/guardrail/StatsdStats.h
+++ b/cmds/statsd/src/guardrail/StatsdStats.h
@@ -99,10 +99,10 @@
* count > kDimensionKeySizeSoftLimit.
*
* [key]: The config key that this condition belongs to.
- * [name]: The name of the condition.
+ * [id]: The id of the condition.
* [size]: The output tuple size.
*/
- void noteConditionDimensionSize(const ConfigKey& key, const std::string& name, int size);
+ void noteConditionDimensionSize(const ConfigKey& key, const int64_t& id, int size);
/**
* Report the size of output tuple of a metric.
@@ -111,26 +111,26 @@
* count > kDimensionKeySizeSoftLimit.
*
* [key]: The config key that this metric belongs to.
- * [name]: The name of the metric.
+ * [id]: The id of the metric.
* [size]: The output tuple size.
*/
- void noteMetricDimensionSize(const ConfigKey& key, const std::string& name, int size);
+ void noteMetricDimensionSize(const ConfigKey& key, const int64_t& id, int size);
/**
* Report a matcher has been matched.
*
* [key]: The config key that this matcher belongs to.
- * [name]: The name of the matcher.
+ * [id]: The id of the matcher.
*/
- void noteMatcherMatched(const ConfigKey& key, const std::string& name);
+ void noteMatcherMatched(const ConfigKey& key, const int64_t& id);
/**
* Report that an anomaly detection alert has been declared.
*
* [key]: The config key that this alert belongs to.
- * [name]: The name of the alert.
+ * [id]: The id of the alert.
*/
- void noteAnomalyDeclared(const ConfigKey& key, const std::string& name);
+ void noteAnomalyDeclared(const ConfigKey& key, const int64_t& id);
/**
* Report an atom event has been logged.
@@ -187,12 +187,12 @@
// Stores the number of output tuple of condition trackers when it's bigger than
// kDimensionKeySizeSoftLimit. When you see the number is kDimensionKeySizeHardLimit +1,
// it means some data has been dropped.
- std::map<const ConfigKey, std::map<const std::string, int>> mConditionStats;
+ std::map<const ConfigKey, std::map<const int64_t, int>> mConditionStats;
// Stores the number of output tuple of metric producers when it's bigger than
// kDimensionKeySizeSoftLimit. When you see the number is kDimensionKeySizeHardLimit +1,
// it means some data has been dropped.
- std::map<const ConfigKey, std::map<const std::string, int>> mMetricsStats;
+ std::map<const ConfigKey, std::map<const int64_t, int>> mMetricsStats;
// Stores the number of times a pushed atom is logged.
// The size of the vector is the largest pushed atom id in atoms.proto + 1. Atoms
@@ -206,10 +206,10 @@
// Stores the number of times an anomaly detection alert has been declared
// (per config, per alert name).
- std::map<const ConfigKey, std::map<const std::string, int>> mAlertStats;
+ std::map<const ConfigKey, std::map<const int64_t, int>> mAlertStats;
// Stores how many times a matcher have been matched.
- std::map<const ConfigKey, std::map<const std::string, int>> mMatcherStats;
+ std::map<const ConfigKey, std::map<const int64_t, int>> mMatcherStats;
void noteConfigRemovedInternalLocked(const ConfigKey& key);
diff --git a/cmds/statsd/src/logd/LogEvent.cpp b/cmds/statsd/src/logd/LogEvent.cpp
index d660b5f..49a6e33 100644
--- a/cmds/statsd/src/logd/LogEvent.cpp
+++ b/cmds/statsd/src/logd/LogEvent.cpp
@@ -17,8 +17,14 @@
#define DEBUG true // STOPSHIP if true
#include "logd/LogEvent.h"
+#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
+
+#include <set>
#include <sstream>
-#include "stats_util.h"
+
+#include "field_util.h"
+#include "dimension.h"
+#include "stats_log_util.h"
namespace android {
namespace os {
@@ -30,16 +36,20 @@
using android::util::ProtoOutputStream;
LogEvent::LogEvent(log_msg& msg) {
- mContext =
+ android_log_context context =
create_android_log_parser(msg.msg() + sizeof(uint32_t), msg.len() - sizeof(uint32_t));
mTimestampNs = msg.entry_v1.sec * NS_PER_SEC + msg.entry_v1.nsec;
mLogUid = msg.entry_v4.uid;
- init(mContext);
+ init(context);
+ if (context) {
+ android_log_destroy(&context);
+ }
}
LogEvent::LogEvent(int32_t tagId, uint64_t timestampNs) {
mTimestampNs = timestampNs;
mTagId = tagId;
+ mLogUid = 0;
mContext = create_android_logger(1937006964); // the event tag shared by all stats logs
if (mContext) {
android_log_write_int32(mContext, tagId);
@@ -53,6 +63,14 @@
// turns to reader mode
mContext = create_android_log_parser(buffer, len);
init(mContext);
+ // destroy the context to save memory.
+ android_log_destroy(&mContext);
+ }
+}
+
+LogEvent::~LogEvent() {
+ if (mContext) {
+ android_log_destroy(&mContext);
}
}
@@ -98,19 +116,72 @@
return false;
}
-LogEvent::~LogEvent() {
+bool LogEvent::write(const std::vector<AttributionNode>& nodes) {
if (mContext) {
- android_log_destroy(&mContext);
+ if (android_log_write_list_begin(mContext) < 0) {
+ return false;
+ }
+ for (size_t i = 0; i < nodes.size(); ++i) {
+ if (!write(nodes[i])) {
+ return false;
+ }
+ }
+ if (android_log_write_list_end(mContext) < 0) {
+ return false;
+ }
+ return true;
+ }
+ return false;
+}
+
+bool LogEvent::write(const AttributionNode& node) {
+ if (mContext) {
+ if (android_log_write_list_begin(mContext) < 0) {
+ return false;
+ }
+ if (android_log_write_int32(mContext, node.uid()) < 0) {
+ return false;
+ }
+ if (android_log_write_string8(mContext, node.tag().c_str()) < 0) {
+ return false;
+ }
+ if (android_log_write_int32(mContext, node.uid()) < 0) {
+ return false;
+ }
+ if (android_log_write_list_end(mContext) < 0) {
+ return false;
+ }
+ return true;
+ }
+ return false;
+}
+
+namespace {
+
+void increaseField(Field *field, bool is_child) {
+ if (is_child) {
+ if (field->child_size() <= 0) {
+ field->add_child();
+ }
+ } else {
+ field->clear_child();
+ }
+ Field* curr = is_child ? field->mutable_child(0) : field;
+ if (!curr->has_field()) {
+ curr->set_field(1);
+ } else {
+ curr->set_field(curr->field() + 1);
}
}
+} // namespace
+
/**
* The elements of each log event are stored as a vector of android_log_list_elements.
* The goal is to do as little preprocessing as possible, because we read a tiny fraction
* of the elements that are written to the log.
*/
void LogEvent::init(android_log_context context) {
- mElements.clear();
android_log_list_element elem;
// TODO: The log is actually structured inside one list. This is convenient
// because we'll be able to use it to put the attribution (WorkSource) block first
@@ -118,24 +189,79 @@
// list-related log elements and the order we get there is our index-keyed data
// structure.
int i = 0;
+
+ int seenListStart = 0;
+
+ Field field;
do {
elem = android_log_read_next(context);
switch ((int)elem.type) {
case EVENT_TYPE_INT:
- // elem at [0] is EVENT_TYPE_LIST, [1] is the tag id. If we add WorkSource, it would
- // be the list starting at [2].
+ // elem at [0] is EVENT_TYPE_LIST, [1] is the tag id.
if (i == 1) {
mTagId = elem.data.int32;
- break;
+ } else {
+ increaseField(&field, seenListStart > 0/* is_child */);
+ DimensionsValue dimensionsValue;
+ dimensionsValue.set_value_int(elem.data.int32);
+ setFieldInLeafValueProto(field, &dimensionsValue);
+ mFieldValueMap.insert(
+ std::make_pair(buildAtomField(mTagId, field), dimensionsValue));
}
+ break;
case EVENT_TYPE_FLOAT:
+ {
+ increaseField(&field, seenListStart > 0/* is_child */);
+ DimensionsValue dimensionsValue;
+ dimensionsValue.set_value_float(elem.data.float32);
+ setFieldInLeafValueProto(field, &dimensionsValue);
+ mFieldValueMap.insert(
+ std::make_pair(buildAtomField(mTagId, field), dimensionsValue));
+ }
+ break;
case EVENT_TYPE_STRING:
+ {
+ increaseField(&field, seenListStart > 0/* is_child */);
+ DimensionsValue dimensionsValue;
+ dimensionsValue.set_value_str(string(elem.data.string, elem.len).c_str());
+ setFieldInLeafValueProto(field, &dimensionsValue);
+ mFieldValueMap.insert(
+ std::make_pair(buildAtomField(mTagId, field), dimensionsValue));
+ }
+ break;
case EVENT_TYPE_LONG:
- mElements.push_back(elem);
+ {
+ increaseField(&field, seenListStart > 0 /* is_child */);
+ DimensionsValue dimensionsValue;
+ dimensionsValue.set_value_long(elem.data.int64);
+ setFieldInLeafValueProto(field, &dimensionsValue);
+ mFieldValueMap.insert(
+ std::make_pair(buildAtomField(mTagId, field), dimensionsValue));
+ }
break;
case EVENT_TYPE_LIST:
+ if (i >= 1) {
+ if (seenListStart > 0) {
+ increasePosition(&field);
+ } else {
+ increaseField(&field, false /* is_child */);
+ }
+ seenListStart++;
+ if (seenListStart >= 3) {
+ ALOGE("Depth > 2. Not supported!");
+ return;
+ }
+ }
break;
case EVENT_TYPE_LIST_STOP:
+ seenListStart--;
+ if (seenListStart == 0) {
+ field.clear_position_index();
+ } else {
+ if (field.child_size() > 0) {
+ field.mutable_child(0)->clear_field();
+ }
+ }
break;
case EVENT_TYPE_UNKNOWN:
break;
@@ -147,142 +273,145 @@
}
int64_t LogEvent::GetLong(size_t key, status_t* err) const {
- if (key < 1 || (key - 1) >= mElements.size()) {
+ DimensionsValue value;
+ if (!GetSimpleAtomDimensionsValueProto(key, &value)) {
*err = BAD_INDEX;
return 0;
}
- key--;
- const android_log_list_element& elem = mElements[key];
- if (elem.type == EVENT_TYPE_INT) {
- return elem.data.int32;
- } else if (elem.type == EVENT_TYPE_LONG) {
- return elem.data.int64;
- } else if (elem.type == EVENT_TYPE_FLOAT) {
- return (int64_t)elem.data.float32;
- } else {
- *err = BAD_TYPE;
- return 0;
+ const DimensionsValue* leafValue = getSingleLeafValue(&value);
+ switch (leafValue->value_case()) {
+ case DimensionsValue::ValueCase::kValueInt:
+ return (int64_t)leafValue->value_int();
+ case DimensionsValue::ValueCase::kValueLong:
+ return leafValue->value_long();
+ case DimensionsValue::ValueCase::kValueBool:
+ return leafValue->value_bool() ? 1 : 0;
+ case DimensionsValue::ValueCase::kValueFloat:
+ return (int64_t)leafValue->value_float();
+ case DimensionsValue::ValueCase::kValueTuple:
+ case DimensionsValue::ValueCase::kValueStr:
+ case DimensionsValue::ValueCase::VALUE_NOT_SET: {
+ *err = BAD_TYPE;
+ return 0;
+ }
}
}
const char* LogEvent::GetString(size_t key, status_t* err) const {
- if (key < 1 || (key - 1) >= mElements.size()) {
+ DimensionsValue value;
+ if (!GetSimpleAtomDimensionsValueProto(key, &value)) {
*err = BAD_INDEX;
- return NULL;
+ return 0;
}
- key--;
- const android_log_list_element& elem = mElements[key];
- if (elem.type != EVENT_TYPE_STRING) {
- *err = BAD_TYPE;
- return NULL;
+ const DimensionsValue* leafValue = getSingleLeafValue(&value);
+ switch (leafValue->value_case()) {
+ case DimensionsValue::ValueCase::kValueStr:
+ return leafValue->value_str().c_str();
+ case DimensionsValue::ValueCase::kValueInt:
+ case DimensionsValue::ValueCase::kValueLong:
+ case DimensionsValue::ValueCase::kValueBool:
+ case DimensionsValue::ValueCase::kValueFloat:
+ case DimensionsValue::ValueCase::kValueTuple:
+ case DimensionsValue::ValueCase::VALUE_NOT_SET: {
+ *err = BAD_TYPE;
+ return 0;
+ }
}
- // Need to add the '/0' at the end by specifying the length of the string.
- return string(elem.data.string, elem.len).c_str();
}
bool LogEvent::GetBool(size_t key, status_t* err) const {
- if (key < 1 || (key - 1) >= mElements.size()) {
+ DimensionsValue value;
+ if (!GetSimpleAtomDimensionsValueProto(key, &value)) {
*err = BAD_INDEX;
return 0;
}
- key--;
- const android_log_list_element& elem = mElements[key];
- if (elem.type == EVENT_TYPE_INT) {
- return elem.data.int32 != 0;
- } else if (elem.type == EVENT_TYPE_LONG) {
- return elem.data.int64 != 0;
- } else if (elem.type == EVENT_TYPE_FLOAT) {
- return elem.data.float32 != 0;
- } else {
- *err = BAD_TYPE;
- return 0;
+ const DimensionsValue* leafValue = getSingleLeafValue(&value);
+ switch (leafValue->value_case()) {
+ case DimensionsValue::ValueCase::kValueInt:
+ return leafValue->value_int() != 0;
+ case DimensionsValue::ValueCase::kValueLong:
+ return leafValue->value_long() != 0;
+ case DimensionsValue::ValueCase::kValueBool:
+ return leafValue->value_bool();
+ case DimensionsValue::ValueCase::kValueFloat:
+ return leafValue->value_float() != 0;
+ case DimensionsValue::ValueCase::kValueTuple:
+ case DimensionsValue::ValueCase::kValueStr:
+ case DimensionsValue::ValueCase::VALUE_NOT_SET: {
+ *err = BAD_TYPE;
+ return 0;
+ }
}
}
float LogEvent::GetFloat(size_t key, status_t* err) const {
- if (key < 1 || (key - 1) >= mElements.size()) {
+ DimensionsValue value;
+ if (!GetSimpleAtomDimensionsValueProto(key, &value)) {
*err = BAD_INDEX;
return 0;
}
- key--;
- const android_log_list_element& elem = mElements[key];
- if (elem.type == EVENT_TYPE_INT) {
- return (float)elem.data.int32;
- } else if (elem.type == EVENT_TYPE_LONG) {
- return (float)elem.data.int64;
- } else if (elem.type == EVENT_TYPE_FLOAT) {
- return elem.data.float32;
- } else {
- *err = BAD_TYPE;
- return 0;
+ const DimensionsValue* leafValue = getSingleLeafValue(&value);
+ switch (leafValue->value_case()) {
+ case DimensionsValue::ValueCase::kValueInt:
+ return (float)leafValue->value_int();
+ case DimensionsValue::ValueCase::kValueLong:
+ return (float)leafValue->value_long();
+ case DimensionsValue::ValueCase::kValueBool:
+ return leafValue->value_bool() ? 1.0f : 0.0f;
+ case DimensionsValue::ValueCase::kValueFloat:
+ return leafValue->value_float();
+ case DimensionsValue::ValueCase::kValueTuple:
+ case DimensionsValue::ValueCase::kValueStr:
+ case DimensionsValue::ValueCase::VALUE_NOT_SET: {
+ *err = BAD_TYPE;
+ return 0;
+ }
}
}
-KeyValuePair LogEvent::GetKeyValueProto(size_t key) const {
- KeyValuePair pair;
- pair.set_key(key);
- // If the value is not valid, return the KeyValuePair without assigning the value.
- // Caller can detect the error by checking the enum for "one of" proto type.
- if (key < 1 || (key - 1) >= mElements.size()) {
- return pair;
- }
- key--;
+void LogEvent::GetAtomDimensionsValueProtos(const FieldMatcher& matcher,
+ std::vector<DimensionsValue> *dimensionsValues) const {
+ findDimensionsValues(mFieldValueMap, matcher, dimensionsValues);
+}
- const android_log_list_element& elem = mElements[key];
- if (elem.type == EVENT_TYPE_INT) {
- pair.set_value_int(elem.data.int32);
- } else if (elem.type == EVENT_TYPE_LONG) {
- pair.set_value_long(elem.data.int64);
- } else if (elem.type == EVENT_TYPE_STRING) {
- pair.set_value_str(elem.data.string);
- } else if (elem.type == EVENT_TYPE_FLOAT) {
- pair.set_value_float(elem.data.float32);
+bool LogEvent::GetAtomDimensionsValueProto(const FieldMatcher& matcher,
+ DimensionsValue* dimensionsValue) const {
+ std::vector<DimensionsValue> rootDimensionsValues;
+ findDimensionsValues(mFieldValueMap, matcher, &rootDimensionsValues);
+ if (rootDimensionsValues.size() != 1) {
+ return false;
}
- return pair;
+ *dimensionsValue = rootDimensionsValues.front();
+ return true;
+}
+
+bool LogEvent::GetSimpleAtomDimensionsValueProto(size_t atomField,
+ DimensionsValue* dimensionsValue) const {
+ return GetAtomDimensionsValueProto(
+ buildSimpleAtomFieldMatcher(mTagId, atomField), dimensionsValue);
+}
+
+DimensionsValue LogEvent::GetSimpleAtomDimensionsValueProto(size_t atomField) const {
+ DimensionsValue dimensionsValue;
+ GetSimpleAtomDimensionsValueProto(atomField, &dimensionsValue);
+ return dimensionsValue;
}
string LogEvent::ToString() const {
ostringstream result;
result << "{ " << mTimestampNs << " (" << mTagId << ")";
- const size_t N = mElements.size();
- for (size_t i=0; i<N; i++) {
- result << " ";
- result << (i + 1);
+ for (const auto& itr : mFieldValueMap) {
+ result << FieldToString(itr.first);
result << "->";
- const android_log_list_element& elem = mElements[i];
- if (elem.type == EVENT_TYPE_INT) {
- result << elem.data.int32;
- } else if (elem.type == EVENT_TYPE_LONG) {
- result << elem.data.int64;
- } else if (elem.type == EVENT_TYPE_FLOAT) {
- result << elem.data.float32;
- } else if (elem.type == EVENT_TYPE_STRING) {
- // Need to add the '/0' at the end by specifying the length of the string.
- result << string(elem.data.string, elem.len).c_str();
- }
+ result << DimensionsValueToString(itr.second);
+ result << " ";
}
result << " }";
return result.str();
}
-void LogEvent::ToProto(ProtoOutputStream& proto) const {
- long long atomToken = proto.start(FIELD_TYPE_MESSAGE | mTagId);
- const size_t N = mElements.size();
- for (size_t i=0; i<N; i++) {
- const int key = i + 1;
-
- const android_log_list_element& elem = mElements[i];
- if (elem.type == EVENT_TYPE_INT) {
- proto.write(FIELD_TYPE_INT32 | key, elem.data.int32);
- } else if (elem.type == EVENT_TYPE_LONG) {
- proto.write(FIELD_TYPE_INT64 | key, (long long)elem.data.int64);
- } else if (elem.type == EVENT_TYPE_FLOAT) {
- proto.write(FIELD_TYPE_FLOAT | key, elem.data.float32);
- } else if (elem.type == EVENT_TYPE_STRING) {
- proto.write(FIELD_TYPE_STRING | key, elem.data.string);
- }
- }
- proto.end(atomToken);
+void LogEvent::ToProto(ProtoOutputStream& protoOutput) const {
+ writeFieldValueTreeToStream(getFieldValueMap(), &protoOutput);
}
} // namespace statsd
diff --git a/cmds/statsd/src/logd/LogEvent.h b/cmds/statsd/src/logd/LogEvent.h
index d3f38de..8f3dedf 100644
--- a/cmds/statsd/src/logd/LogEvent.h
+++ b/cmds/statsd/src/logd/LogEvent.h
@@ -16,6 +16,7 @@
#pragma once
+#include "field_util.h"
#include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
#include <android/util/ProtoOutputStream.h>
@@ -23,9 +24,11 @@
#include <log/log_read.h>
#include <private/android_logger.h>
#include <utils/Errors.h>
+#include <utils/JenkinsHash.h>
#include <memory>
#include <string>
+#include <map>
#include <vector>
namespace android {
@@ -77,6 +80,20 @@
bool GetBool(size_t key, status_t* err) const;
float GetFloat(size_t key, status_t* err) const;
+ /*
+ * Get DimensionsValue proto objects from FieldMatcher.
+ */
+ void GetAtomDimensionsValueProtos(
+ const FieldMatcher& matcher, std::vector<DimensionsValue> *dimensionsValues) const;
+ bool GetAtomDimensionsValueProto(
+ const FieldMatcher& matcher, DimensionsValue* dimensionsValue) const;
+
+ /*
+ * Get a DimensionsValue proto objects from Field.
+ */
+ bool GetSimpleAtomDimensionsValueProto(size_t field, DimensionsValue* dimensionsValue) const;
+ DimensionsValue GetSimpleAtomDimensionsValueProto(size_t atomField) const;
+
/**
* Write test data to the LogEvent. This can only be used when the LogEvent is constructed
* using LogEvent(tagId, timestampNs). You need to call init() before you can read from it.
@@ -87,6 +104,8 @@
bool write(int64_t value);
bool write(const string& value);
bool write(float value);
+ bool write(const std::vector<AttributionNode>& nodes);
+ bool write(const AttributionNode& node);
/**
* Return a string representation of this event.
@@ -98,11 +117,6 @@
*/
void ToProto(android::util::ProtoOutputStream& out) const;
- /*
- * Get a KeyValuePair proto object.
- */
- KeyValuePair GetKeyValueProto(size_t key) const;
-
/**
* Used with the constructor where tag is passed in. Converts the log_event_list to read mode
* and prepares the list for reading.
@@ -114,10 +128,12 @@
*/
void setTimestampNs(uint64_t timestampNs) {mTimestampNs = timestampNs;}
- int size() const {
- return mElements.size();
+ inline int size() const {
+ return mFieldValueMap.size();
}
+ inline const FieldValueMap& getFieldValueMap() const { return mFieldValueMap; }
+
private:
/**
* Don't copy, it's slower. If we really need this we can add it but let's try to
@@ -130,8 +146,11 @@
*/
void init(android_log_context context);
- vector<android_log_list_element> mElements;
+ FieldValueMap mFieldValueMap;
+ // This field is used when statsD wants to create log event object and write fields to it. After
+ // calling init() function, this object would be destroyed to save memory usage.
+ // When the log event is created from log msg, this field is never initiated.
android_log_context mContext;
uint64_t mTimestampNs;
diff --git a/cmds/statsd/src/matchers/CombinationLogMatchingTracker.cpp b/cmds/statsd/src/matchers/CombinationLogMatchingTracker.cpp
index 51a38b6..15c067e 100644
--- a/cmds/statsd/src/matchers/CombinationLogMatchingTracker.cpp
+++ b/cmds/statsd/src/matchers/CombinationLogMatchingTracker.cpp
@@ -29,8 +29,8 @@
using std::unordered_map;
using std::vector;
-CombinationLogMatchingTracker::CombinationLogMatchingTracker(const string& name, const int index)
- : LogMatchingTracker(name, index) {
+CombinationLogMatchingTracker::CombinationLogMatchingTracker(const int64_t& id, const int index)
+ : LogMatchingTracker(id, index) {
}
CombinationLogMatchingTracker::~CombinationLogMatchingTracker() {
@@ -38,7 +38,7 @@
bool CombinationLogMatchingTracker::init(const vector<AtomMatcher>& allLogMatchers,
const vector<sp<LogMatchingTracker>>& allTrackers,
- const unordered_map<string, int>& matcherMap,
+ const unordered_map<int64_t, int>& matcherMap,
vector<bool>& stack) {
if (mInitialized) {
return true;
@@ -60,10 +60,10 @@
return false;
}
- for (const string& child : matcher.matcher()) {
+ for (const auto& child : matcher.matcher()) {
auto pair = matcherMap.find(child);
if (pair == matcherMap.end()) {
- ALOGW("Matcher %s not found in the config", child.c_str());
+ ALOGW("Matcher %lld not found in the config", (long long)child);
return false;
}
@@ -76,14 +76,14 @@
}
if (!allTrackers[childIndex]->init(allLogMatchers, allTrackers, matcherMap, stack)) {
- ALOGW("child matcher init failed %s", child.c_str());
+ ALOGW("child matcher init failed %lld", (long long)child);
return false;
}
mChildren.push_back(childIndex);
- const set<int>& childTagIds = allTrackers[childIndex]->getTagIds();
- mTagIds.insert(childTagIds.begin(), childTagIds.end());
+ const set<int>& childTagIds = allTrackers[childIndex]->getAtomIds();
+ mAtomIds.insert(childTagIds.begin(), childTagIds.end());
}
mInitialized = true;
@@ -100,7 +100,7 @@
return;
}
- if (mTagIds.find(event.GetTagId()) == mTagIds.end()) {
+ if (mAtomIds.find(event.GetTagId()) == mAtomIds.end()) {
matcherResults[mIndex] = MatchingState::kNotMatched;
return;
}
diff --git a/cmds/statsd/src/matchers/CombinationLogMatchingTracker.h b/cmds/statsd/src/matchers/CombinationLogMatchingTracker.h
index 81f6e80..2a3f08d 100644
--- a/cmds/statsd/src/matchers/CombinationLogMatchingTracker.h
+++ b/cmds/statsd/src/matchers/CombinationLogMatchingTracker.h
@@ -31,11 +31,11 @@
// Represents a AtomMatcher_Combination in the StatsdConfig.
class CombinationLogMatchingTracker : public virtual LogMatchingTracker {
public:
- CombinationLogMatchingTracker(const std::string& name, const int index);
+ CombinationLogMatchingTracker(const int64_t& id, const int index);
bool init(const std::vector<AtomMatcher>& allLogMatchers,
const std::vector<sp<LogMatchingTracker>>& allTrackers,
- const std::unordered_map<std::string, int>& matcherMap,
+ const std::unordered_map<int64_t, int>& matcherMap,
std::vector<bool>& stack);
~CombinationLogMatchingTracker();
diff --git a/cmds/statsd/src/matchers/LogMatchingTracker.h b/cmds/statsd/src/matchers/LogMatchingTracker.h
index 8162c44..4f30a04 100644
--- a/cmds/statsd/src/matchers/LogMatchingTracker.h
+++ b/cmds/statsd/src/matchers/LogMatchingTracker.h
@@ -33,8 +33,8 @@
class LogMatchingTracker : public virtual RefBase {
public:
- LogMatchingTracker(const std::string& name, const int index)
- : mName(name), mIndex(index), mInitialized(false){};
+ LogMatchingTracker(const int64_t& id, const int index)
+ : mId(id), mIndex(index), mInitialized(false){};
virtual ~LogMatchingTracker(){};
@@ -48,7 +48,7 @@
// circle dependency.
virtual bool init(const std::vector<AtomMatcher>& allLogMatchers,
const std::vector<sp<LogMatchingTracker>>& allTrackers,
- const std::unordered_map<std::string, int>& matcherMap,
+ const std::unordered_map<int64_t, int>& matcherMap,
std::vector<bool>& stack) = 0;
// Called when a log event comes.
@@ -65,17 +65,17 @@
// Get the tagIds that this matcher cares about. The combined collection is stored
// in MetricMananger, so that we can pass any LogEvents that are not interest of us. It uses
// some memory but hopefully it can save us much CPU time when there is flood of events.
- virtual const std::set<int>& getTagIds() const {
- return mTagIds;
+ virtual const std::set<int>& getAtomIds() const {
+ return mAtomIds;
}
- const std::string& getName() const {
- return mName;
+ const int64_t& getId() const {
+ return mId;
}
protected:
// Name of this matching. We don't really need the name, but it makes log message easy to debug.
- const std::string mName;
+ const int64_t mId;
// Index of this LogMatchingTracker in MetricsManager's container.
const int mIndex;
@@ -88,7 +88,7 @@
// useful when we have a complex CombinationLogMatcherTracker.
// TODO: Consider use an array instead of stl set. In reality, the number of the tag ids a
// LogMatchingTracker cares is only a few.
- std::set<int> mTagIds;
+ std::set<int> mAtomIds;
};
} // namespace statsd
diff --git a/cmds/statsd/src/matchers/SimpleLogMatchingTracker.cpp b/cmds/statsd/src/matchers/SimpleLogMatchingTracker.cpp
index ac217ab..31b3db5 100644
--- a/cmds/statsd/src/matchers/SimpleLogMatchingTracker.cpp
+++ b/cmds/statsd/src/matchers/SimpleLogMatchingTracker.cpp
@@ -29,13 +29,14 @@
using std::vector;
-SimpleLogMatchingTracker::SimpleLogMatchingTracker(const string& name, const int index,
- const SimpleAtomMatcher& matcher)
- : LogMatchingTracker(name, index), mMatcher(matcher) {
- if (!matcher.has_tag()) {
+SimpleLogMatchingTracker::SimpleLogMatchingTracker(const int64_t& id, const int index,
+ const SimpleAtomMatcher& matcher,
+ const UidMap& uidMap)
+ : LogMatchingTracker(id, index), mMatcher(matcher), mUidMap(uidMap) {
+ if (!matcher.has_atom_id()) {
mInitialized = false;
} else {
- mTagIds.insert(matcher.tag());
+ mAtomIds.insert(matcher.atom_id());
mInitialized = true;
}
}
@@ -45,7 +46,7 @@
bool SimpleLogMatchingTracker::init(const vector<AtomMatcher>& allLogMatchers,
const vector<sp<LogMatchingTracker>>& allTrackers,
- const unordered_map<string, int>& matcherMap,
+ const unordered_map<int64_t, int>& matcherMap,
vector<bool>& stack) {
// no need to do anything.
return mInitialized;
@@ -55,18 +56,18 @@
const vector<sp<LogMatchingTracker>>& allTrackers,
vector<MatchingState>& matcherResults) {
if (matcherResults[mIndex] != MatchingState::kNotComputed) {
- VLOG("Matcher %s already evaluated ", mName.c_str());
+ VLOG("Matcher %lld already evaluated ", (long long)mId);
return;
}
- if (mTagIds.find(event.GetTagId()) == mTagIds.end()) {
+ if (mAtomIds.find(event.GetTagId()) == mAtomIds.end()) {
matcherResults[mIndex] = MatchingState::kNotMatched;
return;
}
- bool matched = matchesSimple(mMatcher, event);
+ bool matched = matchesSimple(mUidMap, mMatcher, event);
matcherResults[mIndex] = matched ? MatchingState::kMatched : MatchingState::kNotMatched;
- VLOG("Stats SimpleLogMatcher %s matched? %d", mName.c_str(), matched);
+ VLOG("Stats SimpleLogMatcher %lld matched? %d", (long long)mId, matched);
}
} // namespace statsd
diff --git a/cmds/statsd/src/matchers/SimpleLogMatchingTracker.h b/cmds/statsd/src/matchers/SimpleLogMatchingTracker.h
index 2c188c1..28b339c 100644
--- a/cmds/statsd/src/matchers/SimpleLogMatchingTracker.h
+++ b/cmds/statsd/src/matchers/SimpleLogMatchingTracker.h
@@ -24,6 +24,7 @@
#include <vector>
#include "LogMatchingTracker.h"
#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
+#include "packages/UidMap.h"
namespace android {
namespace os {
@@ -31,14 +32,15 @@
class SimpleLogMatchingTracker : public virtual LogMatchingTracker {
public:
- SimpleLogMatchingTracker(const std::string& name, const int index,
- const SimpleAtomMatcher& matcher);
+ SimpleLogMatchingTracker(const int64_t& id, const int index,
+ const SimpleAtomMatcher& matcher,
+ const UidMap& uidMap);
~SimpleLogMatchingTracker();
bool init(const std::vector<AtomMatcher>& allLogMatchers,
const std::vector<sp<LogMatchingTracker>>& allTrackers,
- const std::unordered_map<std::string, int>& matcherMap,
+ const std::unordered_map<int64_t, int>& matcherMap,
std::vector<bool>& stack) override;
void onLogEvent(const LogEvent& event,
@@ -47,6 +49,7 @@
private:
const SimpleAtomMatcher mMatcher;
+ const UidMap& mUidMap;
};
} // namespace statsd
diff --git a/cmds/statsd/src/matchers/matcher_util.cpp b/cmds/statsd/src/matchers/matcher_util.cpp
index 9e88e5d0..46d9b92 100644
--- a/cmds/statsd/src/matchers/matcher_util.cpp
+++ b/cmds/statsd/src/matchers/matcher_util.cpp
@@ -19,7 +19,9 @@
#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
#include "matchers/LogMatchingTracker.h"
#include "matchers/matcher_util.h"
+#include "dimension.h"
#include "stats_util.h"
+#include "field_util.h"
#include <log/event_tag_map.h>
#include <log/log_event_list.h>
@@ -91,127 +93,173 @@
return matched;
}
-bool matchesSimple(const SimpleAtomMatcher& simpleMatcher, const LogEvent& event) {
- const int tagId = event.GetTagId();
+bool IsAttributionUidField(const Field& field) {
+ return field.child_size() == 1 && field.child(0).field() == 1
+ && field.child(0).child_size() == 1 && field.child(0).child(0).field() == 1;
+}
- if (simpleMatcher.tag() != tagId) {
- return false;
- }
- // now see if this event is interesting to us -- matches ALL the matchers
- // defined in the metrics.
- bool allMatched = true;
- for (int j = 0; allMatched && j < simpleMatcher.key_value_matcher_size(); j++) {
- auto cur = simpleMatcher.key_value_matcher(j);
-
- // TODO: Check if this key is a magic key (eg package name).
- // TODO: Maybe make packages a different type in the config?
- int key = cur.key_matcher().key();
-
- const KeyValueMatcher::ValueMatcherCase matcherCase = cur.value_matcher_case();
- if (matcherCase == KeyValueMatcher::ValueMatcherCase::kEqString) {
- // String fields
- status_t err = NO_ERROR;
- const char* val = event.GetString(key, &err);
- if (err == NO_ERROR && val != NULL) {
- if (!(cur.eq_string() == val)) {
- allMatched = false;
- break;
- }
- } else {
- allMatched = false;
- break;
- }
- } else if (matcherCase == KeyValueMatcher::ValueMatcherCase::kEqInt ||
- matcherCase == KeyValueMatcher::ValueMatcherCase::kLtInt ||
- matcherCase == KeyValueMatcher::ValueMatcherCase::kGtInt ||
- matcherCase == KeyValueMatcher::ValueMatcherCase::kLteInt ||
- matcherCase == KeyValueMatcher::ValueMatcherCase::kGteInt) {
- // Integer fields
- status_t err = NO_ERROR;
- int64_t val = event.GetLong(key, &err);
- if (err == NO_ERROR) {
- if (matcherCase == KeyValueMatcher::ValueMatcherCase::kEqInt) {
- if (!(val == cur.eq_int())) {
- allMatched = false;
- break;
- }
- } else if (matcherCase == KeyValueMatcher::ValueMatcherCase::kLtInt) {
- if (!(val < cur.lt_int())) {
- allMatched = false;
- break;
- }
- } else if (matcherCase == KeyValueMatcher::ValueMatcherCase::kGtInt) {
- if (!(val > cur.gt_int())) {
- allMatched = false;
- break;
- }
- } else if (matcherCase == KeyValueMatcher::ValueMatcherCase::kLteInt) {
- if (!(val <= cur.lte_int())) {
- allMatched = false;
- break;
- }
- } else if (matcherCase == KeyValueMatcher::ValueMatcherCase::kGteInt) {
- if (!(val >= cur.gte_int())) {
- allMatched = false;
- break;
- }
- }
- } else {
- allMatched = false;
- break;
- }
- } else if (matcherCase == KeyValueMatcher::ValueMatcherCase::kEqBool) {
- // Boolean fields
- status_t err = NO_ERROR;
- bool val = event.GetBool(key, &err);
- if (err == NO_ERROR) {
- if (!(cur.eq_bool() == val)) {
- allMatched = false;
- break;
- }
- } else {
- allMatched = false;
- break;
- }
- } else if (matcherCase == KeyValueMatcher::ValueMatcherCase::kLtFloat ||
- matcherCase == KeyValueMatcher::ValueMatcherCase::kGtFloat) {
- // Float fields
- status_t err = NO_ERROR;
- float val = event.GetFloat(key, &err);
- if (err == NO_ERROR) {
- if (matcherCase == KeyValueMatcher::ValueMatcherCase::kLtFloat) {
- if (!(val < cur.lt_float())) {
- allMatched = false;
- break;
- }
- } else if (matcherCase == KeyValueMatcher::ValueMatcherCase::kGtFloat) {
- if (!(val > cur.gt_float())) {
- allMatched = false;
- break;
- }
- }
- } else {
- allMatched = false;
- break;
- }
- } else {
- // If value matcher is not present, assume that we match.
+bool matchesNonRepeatedField(
+ const UidMap& uidMap,
+ const FieldValueMap& fieldMap,
+ const FieldValueMatcher&matcher,
+ const Field& field) {
+ if (matcher.value_matcher_case() ==
+ FieldValueMatcher::ValueMatcherCase::VALUE_MATCHER_NOT_SET) {
+ return !fieldMap.empty() && fieldMap.begin()->first.field() == matcher.field();
+ } else if (matcher.value_matcher_case() == FieldValueMatcher::ValueMatcherCase::kMatchesTuple) {
+ bool allMatched = true;
+ for (int i = 0; allMatched && i < matcher.matches_tuple().field_value_matcher_size(); ++i) {
+ const auto& childMatcher = matcher.matches_tuple().field_value_matcher(i);
+ Field childField = field;
+ appendLeaf(&childField, childMatcher.field());
+ allMatched &= matchFieldSimple(uidMap, fieldMap, childMatcher, childField);
}
+ return allMatched;
+ } else {
+ auto ret = fieldMap.equal_range(field);
+ int found = 0;
+ for (auto it = ret.first; it != ret.second; ++it) {
+ found++;
+ }
+ // Not found.
+ if (found <= 0) {
+ return false;
+ }
+ if (found > 1) {
+ ALOGE("Found multiple values for optional field.");
+ return false;
+ }
+ bool matched = false;
+ switch (matcher.value_matcher_case()) {
+ case FieldValueMatcher::ValueMatcherCase::kEqBool:
+ // Logd does not support bool, it is int instead.
+ matched = ((ret.first->second.value_int() > 0) == matcher.eq_bool());
+ break;
+ case FieldValueMatcher::ValueMatcherCase::kEqString:
+ {
+ if (IsAttributionUidField(field)) {
+ const int uid = ret.first->second.value_int();
+ std::set<string> packageNames =
+ uidMap.getAppNamesFromUid(uid, true /* normalize*/);
+ matched = packageNames.find(matcher.eq_string()) != packageNames.end();
+ } else {
+ matched = (ret.first->second.value_str() == matcher.eq_string());
+ }
+ }
+ break;
+ case FieldValueMatcher::ValueMatcherCase::kEqInt:
+ matched = (ret.first->second.value_int() == matcher.eq_int());
+ break;
+ case FieldValueMatcher::ValueMatcherCase::kLtInt:
+ matched = (ret.first->second.value_int() < matcher.lt_int());
+ break;
+ case FieldValueMatcher::ValueMatcherCase::kGtInt:
+ matched = (ret.first->second.value_int() > matcher.gt_int());
+ break;
+ case FieldValueMatcher::ValueMatcherCase::kLtFloat:
+ matched = (ret.first->second.value_float() < matcher.lt_float());
+ break;
+ case FieldValueMatcher::ValueMatcherCase::kGtFloat:
+ matched = (ret.first->second.value_float() > matcher.gt_float());
+ break;
+ case FieldValueMatcher::ValueMatcherCase::kLteInt:
+ matched = (ret.first->second.value_int() <= matcher.lte_int());
+ break;
+ case FieldValueMatcher::ValueMatcherCase::kGteInt:
+ matched = (ret.first->second.value_int() >= matcher.gte_int());
+ break;
+ default:
+ break;
+ }
+ return matched;
}
- return allMatched;
}
-vector<KeyValuePair> getDimensionKey(const LogEvent& event,
- const std::vector<KeyMatcher>& dimensions) {
- vector<KeyValuePair> key;
- key.reserve(dimensions.size());
- for (const KeyMatcher& dimension : dimensions) {
- KeyValuePair k = event.GetKeyValueProto(dimension.key());
- key.push_back(k);
+bool matchesRepeatedField(const UidMap& uidMap, const FieldValueMap& fieldMap,
+ const FieldValueMatcher&matcher, const Field& field) {
+ if (matcher.position() == Position::FIRST) {
+ Field first_field = field;
+ setPositionForLeaf(&first_field, 0);
+ return matchesNonRepeatedField(uidMap, fieldMap, matcher, first_field);
+ } else {
+ auto itLower = fieldMap.lower_bound(field);
+ if (itLower == fieldMap.end()) {
+ return false;
+ }
+ Field next_field = field;
+ getNextField(&next_field);
+ auto itUpper = fieldMap.lower_bound(next_field);
+ switch (matcher.position()) {
+ case Position::LAST:
+ {
+ itUpper--;
+ if (itUpper == fieldMap.end()) {
+ return false;
+ } else {
+ Field last_field = field;
+ int last_index = getPositionByReferenceField(field, itUpper->first);
+ if (last_index < 0) {
+ return false;
+ }
+ setPositionForLeaf(&last_field, last_index);
+ return matchesNonRepeatedField(uidMap, fieldMap, matcher, last_field);
+ }
+ }
+ break;
+ case Position::ANY:
+ {
+ std::set<int> indexes;
+ for (auto it = itLower; it != itUpper; ++it) {
+ int index = getPositionByReferenceField(field, it->first);
+ if (index >= 0) {
+ indexes.insert(index);
+ }
+ }
+ bool matched = false;
+ for (const int index : indexes) {
+ Field any_field = field;
+ setPositionForLeaf(&any_field, index);
+ matched |= matchesNonRepeatedField(uidMap, fieldMap, matcher, any_field);
+ }
+ return matched;
+ }
+ default:
+ return false;
+ }
}
- return key;
+
}
+bool matchFieldSimple(const UidMap& uidMap, const FieldValueMap& fieldMap,
+ const FieldValueMatcher&matcher, const Field& field) {
+ if (!matcher.has_position()) {
+ return matchesNonRepeatedField(uidMap, fieldMap, matcher, field);
+ } else {
+ return matchesRepeatedField(uidMap, fieldMap, matcher, field);
+ }
+}
+
+bool matchesSimple(const UidMap& uidMap, const SimpleAtomMatcher& simpleMatcher,
+ const LogEvent& event) {
+ if (simpleMatcher.field_value_matcher_size() <= 0) {
+ return event.GetTagId() == simpleMatcher.atom_id();
+ }
+ Field root_field;
+ root_field.set_field(simpleMatcher.atom_id());
+ FieldValueMatcher root_field_matcher;
+ root_field_matcher.set_field(simpleMatcher.atom_id());
+ for (int i = 0; i < simpleMatcher.field_value_matcher_size(); i++) {
+ *root_field_matcher.mutable_matches_tuple()->add_field_value_matcher() =
+ simpleMatcher.field_value_matcher(i);
+ }
+ return matchFieldSimple(uidMap, event.getFieldValueMap(), root_field_matcher, root_field);
+}
+
+vector<DimensionsValue> getDimensionKeys(const LogEvent& event, const FieldMatcher& matcher) {
+ vector<DimensionsValue> values;
+ findDimensionsValues(event.getFieldValueMap(), matcher, &values);
+ return values;
+}
} // namespace statsd
} // namespace os
} // namespace android
diff --git a/cmds/statsd/src/matchers/matcher_util.h b/cmds/statsd/src/matchers/matcher_util.h
index f54ab36..704cb4c 100644
--- a/cmds/statsd/src/matchers/matcher_util.h
+++ b/cmds/statsd/src/matchers/matcher_util.h
@@ -14,8 +14,7 @@
* limitations under the License.
*/
-#ifndef MATCHER_UTIL_H
-#define MATCHER_UTIL_H
+#pragma once
#include "logd/LogEvent.h"
@@ -28,6 +27,7 @@
#include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
#include "stats_util.h"
+#include "packages/UidMap.h"
namespace android {
namespace os {
@@ -42,12 +42,14 @@
bool combinationMatch(const std::vector<int>& children, const LogicalOperation& operation,
const std::vector<MatchingState>& matcherResults);
-bool matchesSimple(const SimpleAtomMatcher& simpleMatcher, const LogEvent& wrapper);
+bool matchFieldSimple(const UidMap& uidMap, const FieldValueMap& dimensionsMap,
+ const FieldValueMatcher& matcher, const Field& field);
-std::vector<KeyValuePair> getDimensionKey(const LogEvent& event,
- const std::vector<KeyMatcher>& dimensions);
+bool matchesSimple(const UidMap& uidMap,
+ const SimpleAtomMatcher& simpleMatcher, const LogEvent& wrapper);
+
+std::vector<DimensionsValue> getDimensionKeys(const LogEvent& event, const FieldMatcher& matcher);
} // namespace statsd
} // namespace os
} // namespace android
-#endif // MATCHER_UTIL_H
diff --git a/cmds/statsd/src/metrics/CountMetricProducer.cpp b/cmds/statsd/src/metrics/CountMetricProducer.cpp
index 9031ed0..a24364d 100644
--- a/cmds/statsd/src/metrics/CountMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/CountMetricProducer.cpp
@@ -20,6 +20,7 @@
#include "CountMetricProducer.h"
#include "guardrail/StatsdStats.h"
#include "stats_util.h"
+#include "stats_log_util.h"
#include <limits.h>
#include <stdlib.h>
@@ -42,7 +43,7 @@
namespace statsd {
// for StatsLogReport
-const int FIELD_ID_NAME = 1;
+const int FIELD_ID_ID = 1;
const int FIELD_ID_START_REPORT_NANOS = 2;
const int FIELD_ID_END_REPORT_NANOS = 3;
const int FIELD_ID_COUNT_METRICS = 5;
@@ -51,12 +52,6 @@
// for CountMetricData
const int FIELD_ID_DIMENSION = 1;
const int FIELD_ID_BUCKET_INFO = 2;
-// for KeyValuePair
-const int FIELD_ID_KEY = 1;
-const int FIELD_ID_VALUE_STR = 2;
-const int FIELD_ID_VALUE_INT = 3;
-const int FIELD_ID_VALUE_BOOL = 4;
-const int FIELD_ID_VALUE_FLOAT = 5;
// for CountBucketInfo
const int FIELD_ID_START_BUCKET_NANOS = 1;
const int FIELD_ID_END_BUCKET_NANOS = 2;
@@ -66,7 +61,7 @@
const int conditionIndex,
const sp<ConditionWizard>& wizard,
const uint64_t startTimeNs)
- : MetricProducer(metric.name(), key, startTimeNs, conditionIndex, wizard) {
+ : MetricProducer(metric.id(), key, startTimeNs, conditionIndex, wizard) {
// TODO: evaluate initial conditions. and set mConditionMet.
if (metric.has_bucket() && metric.bucket().has_bucket_size_millis()) {
mBucketSizeNs = metric.bucket().bucket_size_millis() * 1000 * 1000;
@@ -75,7 +70,7 @@
}
// TODO: use UidMap if uid->pkg_name is required
- mDimension.insert(mDimension.begin(), metric.dimension().begin(), metric.dimension().end());
+ mDimensions = metric.dimensions();
if (metric.links().size() > 0) {
mConditionLinks.insert(mConditionLinks.begin(), metric.links().begin(),
@@ -83,7 +78,7 @@
mConditionSliced = true;
}
- VLOG("metric %s created. bucket size %lld start_time: %lld", metric.name().c_str(),
+ VLOG("metric %lld created. bucket size %lld start_time: %lld", (long long)metric.id(),
(long long)mBucketSizeNs, (long long)mStartTimeNs);
}
@@ -92,43 +87,49 @@
}
void CountMetricProducer::onSlicedConditionMayChangeLocked(const uint64_t eventTime) {
- VLOG("Metric %s onSlicedConditionMayChange", mName.c_str());
+ VLOG("Metric %lld onSlicedConditionMayChange", (long long)mMetricId);
+}
+
+void CountMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, StatsLogReport* report) {
+ flushIfNeededLocked(dumpTimeNs);
+ report->set_metric_id(mMetricId);
+ report->set_start_report_nanos(mStartTimeNs);
+
+ auto count_metrics = report->mutable_count_metrics();
+ for (const auto& counter : mPastBuckets) {
+ CountMetricData* metricData = count_metrics->add_data();
+ *metricData->mutable_dimension() = counter.first.getDimensionsValue();
+ for (const auto& bucket : counter.second) {
+ CountBucketInfo* bucketInfo = metricData->add_bucket_info();
+ bucketInfo->set_start_bucket_nanos(bucket.mBucketStartNs);
+ bucketInfo->set_end_bucket_nanos(bucket.mBucketEndNs);
+ bucketInfo->set_count(bucket.mCount);
+ }
+ }
}
void CountMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs,
ProtoOutputStream* protoOutput) {
flushIfNeededLocked(dumpTimeNs);
- protoOutput->write(FIELD_TYPE_STRING | FIELD_ID_NAME, mName);
+ protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_ID, (long long)mMetricId);
protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_START_REPORT_NANOS, (long long)mStartTimeNs);
long long protoToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_ID_COUNT_METRICS);
- VLOG("metric %s dump report now...", mName.c_str());
+ VLOG("metric %lld dump report now...",(long long)mMetricId);
for (const auto& counter : mPastBuckets) {
const HashableDimensionKey& hashableKey = counter.first;
- const vector<KeyValuePair>& kvs = hashableKey.getKeyValuePairs();
VLOG(" dimension key %s", hashableKey.c_str());
long long wrapperToken =
protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DATA);
- // First fill dimension (KeyValuePairs).
- for (const auto& kv : kvs) {
- long long dimensionToken = protoOutput->start(
- FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DIMENSION);
- protoOutput->write(FIELD_TYPE_INT32 | FIELD_ID_KEY, kv.key());
- if (kv.has_value_str()) {
- protoOutput->write(FIELD_TYPE_STRING | FIELD_ID_VALUE_STR, kv.value_str());
- } else if (kv.has_value_int()) {
- protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_VALUE_INT, kv.value_int());
- } else if (kv.has_value_bool()) {
- protoOutput->write(FIELD_TYPE_BOOL | FIELD_ID_VALUE_BOOL, kv.value_bool());
- } else if (kv.has_value_float()) {
- protoOutput->write(FIELD_TYPE_FLOAT | FIELD_ID_VALUE_FLOAT, kv.value_float());
- }
- protoOutput->end(dimensionToken);
- }
+ // First fill dimension.
+ long long dimensionToken = protoOutput->start(
+ FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DIMENSION);
+ writeDimensionsValueProtoToStream(hashableKey.getDimensionsValue(), protoOutput);
+ protoOutput->end(dimensionToken);
// Then fill bucket_info (CountBucketInfo).
for (const auto& bucket : counter.second) {
@@ -157,7 +158,7 @@
void CountMetricProducer::onConditionChangedLocked(const bool conditionMet,
const uint64_t eventTime) {
- VLOG("Metric %s onConditionChanged", mName.c_str());
+ VLOG("Metric %lld onConditionChanged", (long long)mMetricId);
mCondition = conditionMet;
}
@@ -169,11 +170,11 @@
// 1. Report the tuple count if the tuple count > soft limit
if (mCurrentSlicedCounter->size() > StatsdStats::kDimensionKeySizeSoftLimit - 1) {
size_t newTupleCount = mCurrentSlicedCounter->size() + 1;
- StatsdStats::getInstance().noteMetricDimensionSize(mConfigKey, mName, newTupleCount);
+ StatsdStats::getInstance().noteMetricDimensionSize(mConfigKey, mMetricId, newTupleCount);
// 2. Don't add more tuples, we are above the allowed threshold. Drop the data.
if (newTupleCount > StatsdStats::kDimensionKeySizeHardLimit) {
- ALOGE("CountMetric %s dropping data for dimension key %s", mName.c_str(),
- newKey.c_str());
+ ALOGE("CountMetric %lld dropping data for dimension key %s",
+ (long long)mMetricId, newKey.c_str());
return true;
}
}
@@ -183,7 +184,7 @@
void CountMetricProducer::onMatchedLogEventInternalLocked(
const size_t matcherIndex, const HashableDimensionKey& eventKey,
- const map<string, HashableDimensionKey>& conditionKey, bool condition,
+ const ConditionKey& conditionKey, bool condition,
const LogEvent& event) {
uint64_t eventTimeNs = event.GetTimestampNs();
@@ -214,7 +215,7 @@
mCurrentSlicedCounter->find(eventKey)->second);
}
- VLOG("metric %s %s->%lld", mName.c_str(), eventKey.c_str(),
+ VLOG("metric %lld %s->%lld", (long long)mMetricId, eventKey.c_str(),
(long long)(*mCurrentSlicedCounter)[eventKey]);
}
@@ -233,8 +234,8 @@
info.mCount = counter.second;
auto& bucketList = mPastBuckets[counter.first];
bucketList.push_back(info);
- VLOG("metric %s, dump key value: %s -> %lld", mName.c_str(), counter.first.c_str(),
- (long long)counter.second);
+ VLOG("metric %lld, dump key value: %s -> %lld",
+ (long long)mMetricId, counter.first.c_str(), (long long)counter.second);
}
for (auto& tracker : mAnomalyTrackers) {
@@ -246,7 +247,7 @@
uint64_t numBucketsForward = (eventTimeNs - mCurrentBucketStartTimeNs) / mBucketSizeNs;
mCurrentBucketStartTimeNs = mCurrentBucketStartTimeNs + numBucketsForward * mBucketSizeNs;
mCurrentBucketNum += numBucketsForward;
- VLOG("metric %s: new bucket start time: %lld", mName.c_str(),
+ VLOG("metric %lld: new bucket start time: %lld", (long long)mMetricId,
(long long)mCurrentBucketStartTimeNs);
}
diff --git a/cmds/statsd/src/metrics/CountMetricProducer.h b/cmds/statsd/src/metrics/CountMetricProducer.h
index e32fc06..6087ae5 100644
--- a/cmds/statsd/src/metrics/CountMetricProducer.h
+++ b/cmds/statsd/src/metrics/CountMetricProducer.h
@@ -51,12 +51,13 @@
protected:
void onMatchedLogEventInternalLocked(
const size_t matcherIndex, const HashableDimensionKey& eventKey,
- const std::map<std::string, HashableDimensionKey>& conditionKey, bool condition,
+ const ConditionKey& conditionKey, bool condition,
const LogEvent& event) override;
private:
void onDumpReportLocked(const uint64_t dumpTimeNs,
android::util::ProtoOutputStream* protoOutput) override;
+ void onDumpReportLocked(const uint64_t dumpTimeNs, StatsLogReport* report) override;
// Internal interface to handle condition change.
void onConditionChangedLocked(const bool conditionMet, const uint64_t eventTime) override;
diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.cpp b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
index 1c8f422..0117b6d 100644
--- a/cmds/statsd/src/metrics/DurationMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
@@ -20,6 +20,7 @@
#include "DurationMetricProducer.h"
#include "guardrail/StatsdStats.h"
#include "stats_util.h"
+#include "stats_log_util.h"
#include <limits.h>
#include <stdlib.h>
@@ -41,7 +42,7 @@
namespace statsd {
// for StatsLogReport
-const int FIELD_ID_NAME = 1;
+const int FIELD_ID_ID = 1;
const int FIELD_ID_START_REPORT_NANOS = 2;
const int FIELD_ID_END_REPORT_NANOS = 3;
const int FIELD_ID_DURATION_METRICS = 6;
@@ -50,12 +51,6 @@
// for DurationMetricData
const int FIELD_ID_DIMENSION = 1;
const int FIELD_ID_BUCKET_INFO = 2;
-// for KeyValuePair
-const int FIELD_ID_KEY = 1;
-const int FIELD_ID_VALUE_STR = 2;
-const int FIELD_ID_VALUE_INT = 3;
-const int FIELD_ID_VALUE_BOOL = 4;
-const int FIELD_ID_VALUE_FLOAT = 5;
// for DurationBucketInfo
const int FIELD_ID_START_BUCKET_NANOS = 1;
const int FIELD_ID_END_BUCKET_NANOS = 2;
@@ -66,15 +61,15 @@
const size_t stopIndex, const size_t stopAllIndex,
const bool nesting,
const sp<ConditionWizard>& wizard,
- const vector<KeyMatcher>& internalDimension,
+ const FieldMatcher& internalDimensions,
const uint64_t startTimeNs)
- : MetricProducer(metric.name(), key, startTimeNs, conditionIndex, wizard),
+ : MetricProducer(metric.id(), key, startTimeNs, conditionIndex, wizard),
mAggregationType(metric.aggregation_type()),
mStartIndex(startIndex),
mStopIndex(stopIndex),
mStopAllIndex(stopAllIndex),
mNested(nesting),
- mInternalDimension(internalDimension) {
+ mInternalDimensions(internalDimensions) {
// TODO: The following boiler plate code appears in all MetricProducers, but we can't abstract
// them in the base class, because the proto generated CountMetric, and DurationMetric are
// not related. Maybe we should add a template in the future??
@@ -85,7 +80,7 @@
}
// TODO: use UidMap if uid->pkg_name is required
- mDimension.insert(mDimension.begin(), metric.dimension().begin(), metric.dimension().end());
+ mDimensions = metric.dimensions();
if (metric.links().size() > 0) {
mConditionLinks.insert(mConditionLinks.begin(), metric.links().begin(),
@@ -93,7 +88,7 @@
mConditionSliced = true;
}
- VLOG("metric %s created. bucket size %lld start_time: %lld", metric.name().c_str(),
+ VLOG("metric %lld created. bucket size %lld start_time: %lld", (long long)metric.id(),
(long long)mBucketSizeNs, (long long)mStartTimeNs);
}
@@ -101,15 +96,19 @@
VLOG("~DurationMetric() called");
}
-sp<AnomalyTracker> DurationMetricProducer::createAnomalyTracker(const Alert &alert) {
- if (alert.trigger_if_sum_gt() > alert.number_of_buckets() * mBucketSizeNs) {
- ALOGW("invalid alert: threshold (%lld) > possible recordable value (%d x %lld)",
- alert.trigger_if_sum_gt(), alert.number_of_buckets(),
+sp<AnomalyTracker> DurationMetricProducer::addAnomalyTracker(const Alert &alert) {
+ std::lock_guard<std::mutex> lock(mMutex);
+ if (alert.trigger_if_sum_gt() > alert.num_buckets() * mBucketSizeNs) {
+ ALOGW("invalid alert: threshold (%f) > possible recordable value (%d x %lld)",
+ alert.trigger_if_sum_gt(), alert.num_buckets(),
(long long)mBucketSizeNs);
return nullptr;
}
- // TODO: return a DurationAnomalyTracker (which should sublclass AnomalyTracker)
- return new AnomalyTracker(alert, mConfigKey);
+ sp<DurationAnomalyTracker> anomalyTracker = new DurationAnomalyTracker(alert, mConfigKey);
+ if (anomalyTracker != nullptr) {
+ mAnomalyTrackers.push_back(anomalyTracker);
+ }
+ return anomalyTracker;
}
unique_ptr<DurationTracker> DurationMetricProducer::createDurationTracker(
@@ -117,17 +116,17 @@
switch (mAggregationType) {
case DurationMetric_AggregationType_SUM:
return make_unique<OringDurationTracker>(
- mConfigKey, mName, eventKey, mWizard, mConditionTrackerIndex, mNested,
+ mConfigKey, mMetricId, eventKey, mWizard, mConditionTrackerIndex, mNested,
mCurrentBucketStartTimeNs, mBucketSizeNs, mAnomalyTrackers);
case DurationMetric_AggregationType_MAX_SPARSE:
return make_unique<MaxDurationTracker>(
- mConfigKey, mName, eventKey, mWizard, mConditionTrackerIndex, mNested,
+ mConfigKey, mMetricId, eventKey, mWizard, mConditionTrackerIndex, mNested,
mCurrentBucketStartTimeNs, mBucketSizeNs, mAnomalyTrackers);
}
}
void DurationMetricProducer::onSlicedConditionMayChangeLocked(const uint64_t eventTime) {
- VLOG("Metric %s onSlicedConditionMayChange", mName.c_str());
+ VLOG("Metric %lld onSlicedConditionMayChange", (long long)mMetricId);
flushIfNeededLocked(eventTime);
// Now for each of the on-going event, check if the condition has changed for them.
for (auto& pair : mCurrentSlicedDuration) {
@@ -137,7 +136,7 @@
void DurationMetricProducer::onConditionChangedLocked(const bool conditionMet,
const uint64_t eventTime) {
- VLOG("Metric %s onConditionChanged", mName.c_str());
+ VLOG("Metric %lld onConditionChanged", (long long)mMetricId);
mCondition = conditionMet;
flushIfNeededLocked(eventTime);
// TODO: need to populate the condition change time from the event which triggers the condition
@@ -147,40 +146,46 @@
}
}
+void DurationMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, StatsLogReport* report) {
+ flushIfNeededLocked(dumpTimeNs);
+ report->set_metric_id(mMetricId);
+ report->set_start_report_nanos(mStartTimeNs);
+
+ auto duration_metrics = report->mutable_duration_metrics();
+ for (const auto& pair : mPastBuckets) {
+ DurationMetricData* metricData = duration_metrics->add_data();
+ *metricData->mutable_dimension() = pair.first.getDimensionsValue();
+ for (const auto& bucket : pair.second) {
+ auto bucketInfo = metricData->add_bucket_info();
+ bucketInfo->set_start_bucket_nanos(bucket.mBucketStartNs);
+ bucketInfo->set_end_bucket_nanos(bucket.mBucketEndNs);
+ bucketInfo->set_duration_nanos(bucket.mDuration);
+ }
+ }
+}
+
void DurationMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs,
ProtoOutputStream* protoOutput) {
flushIfNeededLocked(dumpTimeNs);
- protoOutput->write(FIELD_TYPE_STRING | FIELD_ID_NAME, mName);
+ protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_ID, (long long)mMetricId);
protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_START_REPORT_NANOS, (long long)mStartTimeNs);
long long protoToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_ID_DURATION_METRICS);
- VLOG("metric %s dump report now...", mName.c_str());
+ VLOG("metric %lld dump report now...", (long long)mMetricId);
for (const auto& pair : mPastBuckets) {
const HashableDimensionKey& hashableKey = pair.first;
- const vector<KeyValuePair>& kvs = hashableKey.getKeyValuePairs();
VLOG(" dimension key %s", hashableKey.c_str());
long long wrapperToken =
protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DATA);
- // First fill dimension (KeyValuePairs).
- for (const auto& kv : kvs) {
- long long dimensionToken = protoOutput->start(
- FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DIMENSION);
- protoOutput->write(FIELD_TYPE_INT32 | FIELD_ID_KEY, kv.key());
- if (kv.has_value_str()) {
- protoOutput->write(FIELD_TYPE_STRING | FIELD_ID_VALUE_STR, kv.value_str());
- } else if (kv.has_value_int()) {
- protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_VALUE_INT, kv.value_int());
- } else if (kv.has_value_bool()) {
- protoOutput->write(FIELD_TYPE_BOOL | FIELD_ID_VALUE_BOOL, kv.value_bool());
- } else if (kv.has_value_float()) {
- protoOutput->write(FIELD_TYPE_FLOAT | FIELD_ID_VALUE_FLOAT, kv.value_float());
- }
- protoOutput->end(dimensionToken);
- }
+ // First fill dimension.
+ long long dimensionToken = protoOutput->start(
+ FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DIMENSION);
+ writeDimensionsValueProtoToStream(hashableKey.getDimensionsValue(), protoOutput);
+ protoOutput->end(dimensionToken);
// Then fill bucket_info (DurationBucketInfo).
for (const auto& bucket : pair.second) {
@@ -232,11 +237,11 @@
// 1. Report the tuple count if the tuple count > soft limit
if (mCurrentSlicedDuration.size() > StatsdStats::kDimensionKeySizeSoftLimit - 1) {
size_t newTupleCount = mCurrentSlicedDuration.size() + 1;
- StatsdStats::getInstance().noteMetricDimensionSize(mConfigKey, mName, newTupleCount);
+ StatsdStats::getInstance().noteMetricDimensionSize(mConfigKey, mMetricId, newTupleCount);
// 2. Don't add more tuples, we are above the allowed threshold. Drop the data.
if (newTupleCount > StatsdStats::kDimensionKeySizeHardLimit) {
- ALOGE("DurationMetric %s dropping data for dimension key %s", mName.c_str(),
- newKey.c_str());
+ ALOGE("DurationMetric %lld dropping data for dimension key %s",
+ (long long)mMetricId, newKey.c_str());
return true;
}
}
@@ -245,7 +250,7 @@
void DurationMetricProducer::onMatchedLogEventInternalLocked(
const size_t matcherIndex, const HashableDimensionKey& eventKey,
- const map<string, HashableDimensionKey>& conditionKeys, bool condition,
+ const ConditionKey& conditionKeys, bool condition,
const LogEvent& event) {
flushIfNeededLocked(event.GetTimestampNs());
@@ -256,7 +261,6 @@
return;
}
- HashableDimensionKey atomKey(getDimensionKey(event, mInternalDimension));
if (mCurrentSlicedDuration.find(eventKey) == mCurrentSlicedDuration.end()) {
if (hitGuardRailLocked(eventKey)) {
@@ -267,11 +271,25 @@
auto it = mCurrentSlicedDuration.find(eventKey);
- if (matcherIndex == mStartIndex) {
- it->second->noteStart(atomKey, condition, event.GetTimestampNs(), conditionKeys);
- } else if (matcherIndex == mStopIndex) {
- it->second->noteStop(atomKey, event.GetTimestampNs(), false);
+ std::vector<DimensionsValue> values = getDimensionKeys(event, mInternalDimensions);
+ if (values.empty()) {
+ if (matcherIndex == mStartIndex) {
+ it->second->noteStart(DEFAULT_DIMENSION_KEY, condition,
+ event.GetTimestampNs(), conditionKeys);
+ } else if (matcherIndex == mStopIndex) {
+ it->second->noteStop(DEFAULT_DIMENSION_KEY, event.GetTimestampNs(), false);
+ }
+ } else {
+ for (const DimensionsValue& value : values) {
+ if (matcherIndex == mStartIndex) {
+ it->second->noteStart(HashableDimensionKey(value), condition,
+ event.GetTimestampNs(), conditionKeys);
+ } else if (matcherIndex == mStopIndex) {
+ it->second->noteStop(HashableDimensionKey(value), event.GetTimestampNs(), false);
+ }
+ }
}
+
}
size_t DurationMetricProducer::byteSizeLocked() const {
diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.h b/cmds/statsd/src/metrics/DurationMetricProducer.h
index 7044b4b..e06b9a1 100644
--- a/cmds/statsd/src/metrics/DurationMetricProducer.h
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.h
@@ -20,6 +20,7 @@
#include <unordered_map>
#include <android/util/ProtoOutputStream.h>
+#include "../anomaly/DurationAnomalyTracker.h"
#include "../condition/ConditionTracker.h"
#include "../matchers/matcher_util.h"
#include "MetricProducer.h"
@@ -41,21 +42,22 @@
const int conditionIndex, const size_t startIndex,
const size_t stopIndex, const size_t stopAllIndex, const bool nesting,
const sp<ConditionWizard>& wizard,
- const vector<KeyMatcher>& internalDimension, const uint64_t startTimeNs);
+ const FieldMatcher& internalDimensions, const uint64_t startTimeNs);
virtual ~DurationMetricProducer();
- virtual sp<AnomalyTracker> createAnomalyTracker(const Alert &alert) override;
+ sp<AnomalyTracker> addAnomalyTracker(const Alert &alert) override;
protected:
void onMatchedLogEventInternalLocked(
const size_t matcherIndex, const HashableDimensionKey& eventKey,
- const std::map<std::string, HashableDimensionKey>& conditionKeys, bool condition,
+ const ConditionKey& conditionKeys, bool condition,
const LogEvent& event) override;
private:
void onDumpReportLocked(const uint64_t dumpTimeNs,
android::util::ProtoOutputStream* protoOutput) override;
+ void onDumpReportLocked(const uint64_t dumpTimeNs, StatsLogReport* report) override;
// Internal interface to handle condition change.
void onConditionChangedLocked(const bool conditionMet, const uint64_t eventTime) override;
@@ -84,7 +86,7 @@
const bool mNested;
// The dimension from the atom predicate. e.g., uid, wakelock name.
- const vector<KeyMatcher> mInternalDimension;
+ const FieldMatcher mInternalDimensions;
// Save the past buckets and we can clear when the StatsLogReport is dumped.
// TODO: Add a lock to mPastBuckets.
@@ -98,6 +100,9 @@
std::unique_ptr<DurationTracker> createDurationTracker(
const HashableDimensionKey& eventKey) const;
+ // This hides the base class's std::vector<sp<AnomalyTracker>> mAnomalyTrackers
+ std::vector<sp<DurationAnomalyTracker>> mAnomalyTrackers;
+
// Util function to check whether the specified dimension hits the guardrail.
bool hitGuardRailLocked(const HashableDimensionKey& newKey);
@@ -105,6 +110,7 @@
FRIEND_TEST(DurationMetricTrackerTest, TestNoCondition);
FRIEND_TEST(DurationMetricTrackerTest, TestNonSlicedCondition);
+ FRIEND_TEST(WakelockDurationE2eTest, TestAggregatedPredicates);
};
} // namespace statsd
diff --git a/cmds/statsd/src/metrics/EventMetricProducer.cpp b/cmds/statsd/src/metrics/EventMetricProducer.cpp
index 6a072b0..821d8ea4 100644
--- a/cmds/statsd/src/metrics/EventMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/EventMetricProducer.cpp
@@ -41,7 +41,7 @@
namespace statsd {
// for StatsLogReport
-const int FIELD_ID_NAME = 1;
+const int FIELD_ID_ID = 1;
const int FIELD_ID_START_REPORT_NANOS = 2;
const int FIELD_ID_END_REPORT_NANOS = 3;
const int FIELD_ID_EVENT_METRICS = 4;
@@ -55,7 +55,7 @@
const int conditionIndex,
const sp<ConditionWizard>& wizard,
const uint64_t startTimeNs)
- : MetricProducer(metric.name(), key, startTimeNs, conditionIndex, wizard) {
+ : MetricProducer(metric.id(), key, startTimeNs, conditionIndex, wizard) {
if (metric.links().size() > 0) {
mConditionLinks.insert(mConditionLinks.begin(), metric.links().begin(),
metric.links().end());
@@ -64,7 +64,7 @@
startNewProtoOutputStreamLocked();
- VLOG("metric %s created. bucket size %lld start_time: %lld", metric.name().c_str(),
+ VLOG("metric %lld created. bucket size %lld start_time: %lld", (long long)metric.id(),
(long long)mBucketSizeNs, (long long)mStartTimeNs);
}
@@ -96,14 +96,19 @@
return buffer;
}
+void EventMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, StatsLogReport* report) {
+
+}
+
void EventMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs,
ProtoOutputStream* protoOutput) {
- protoOutput->write(FIELD_TYPE_STRING | FIELD_ID_NAME, mName);
+ protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_ID, (long long)mMetricId);
protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_START_REPORT_NANOS, (long long)mStartTimeNs);
protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_END_REPORT_NANOS, (long long)dumpTimeNs);
size_t bufferSize = mProto->size();
- VLOG("metric %s dump report now... proto size: %zu ", mName.c_str(), bufferSize);
+ VLOG("metric %lld dump report now... proto size: %zu ",
+ (long long)mMetricId, bufferSize);
std::unique_ptr<std::vector<uint8_t>> buffer = serializeProtoLocked(*mProto);
protoOutput->write(FIELD_TYPE_MESSAGE | FIELD_ID_EVENT_METRICS,
@@ -115,13 +120,13 @@
void EventMetricProducer::onConditionChangedLocked(const bool conditionMet,
const uint64_t eventTime) {
- VLOG("Metric %s onConditionChanged", mName.c_str());
+ VLOG("Metric %lld onConditionChanged", (long long)mMetricId);
mCondition = conditionMet;
}
void EventMetricProducer::onMatchedLogEventInternalLocked(
const size_t matcherIndex, const HashableDimensionKey& eventKey,
- const std::map<std::string, HashableDimensionKey>& conditionKey, bool condition,
+ const ConditionKey& conditionKey, bool condition,
const LogEvent& event) {
if (!condition) {
return;
diff --git a/cmds/statsd/src/metrics/EventMetricProducer.h b/cmds/statsd/src/metrics/EventMetricProducer.h
index 6120ad8..a57b07d 100644
--- a/cmds/statsd/src/metrics/EventMetricProducer.h
+++ b/cmds/statsd/src/metrics/EventMetricProducer.h
@@ -46,11 +46,12 @@
private:
void onMatchedLogEventInternalLocked(
const size_t matcherIndex, const HashableDimensionKey& eventKey,
- const std::map<std::string, HashableDimensionKey>& conditionKey, bool condition,
+ const ConditionKey& conditionKey, bool condition,
const LogEvent& event) override;
void onDumpReportLocked(const uint64_t dumpTimeNs,
android::util::ProtoOutputStream* protoOutput) override;
+ void onDumpReportLocked(const uint64_t dumpTimeNs, StatsLogReport* report) override;
// Internal interface to handle condition change.
void onConditionChangedLocked(const bool conditionMet, const uint64_t eventTime) override;
diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
index 47cca0e..eaf1de2 100644
--- a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
@@ -19,6 +19,8 @@
#include "GaugeMetricProducer.h"
#include "guardrail/StatsdStats.h"
+#include "dimension.h"
+#include "stats_log_util.h"
#include <cutils/log.h>
@@ -42,7 +44,7 @@
namespace statsd {
// for StatsLogReport
-const int FIELD_ID_NAME = 1;
+const int FIELD_ID_ID = 1;
const int FIELD_ID_START_REPORT_NANOS = 2;
const int FIELD_ID_END_REPORT_NANOS = 3;
const int FIELD_ID_GAUGE_METRICS = 8;
@@ -51,12 +53,6 @@
// for GaugeMetricData
const int FIELD_ID_DIMENSION = 1;
const int FIELD_ID_BUCKET_INFO = 2;
-// for KeyValuePair
-const int FIELD_ID_KEY = 1;
-const int FIELD_ID_VALUE_STR = 2;
-const int FIELD_ID_VALUE_INT = 3;
-const int FIELD_ID_VALUE_BOOL = 4;
-const int FIELD_ID_VALUE_FLOAT = 5;
// for GaugeBucketInfo
const int FIELD_ID_START_BUCKET_NANOS = 1;
const int FIELD_ID_END_BUCKET_NANOS = 2;
@@ -67,22 +63,22 @@
const sp<ConditionWizard>& wizard, const int atomTagId,
const int pullTagId, const uint64_t startTimeNs,
shared_ptr<StatsPullerManager> statsPullerManager)
- : MetricProducer(metric.name(), key, startTimeNs, conditionIndex, wizard),
+ : MetricProducer(metric.id(), key, startTimeNs, conditionIndex, wizard),
mStatsPullerManager(statsPullerManager),
mPullTagId(pullTagId),
mAtomTagId(atomTagId) {
+ mCurrentSlicedBucket = std::make_shared<DimToGaugeFieldsMap>();
+ mCurrentSlicedBucketForAnomaly = std::make_shared<DimToValMap>();
if (metric.has_bucket() && metric.bucket().has_bucket_size_millis()) {
mBucketSizeNs = metric.bucket().bucket_size_millis() * 1000 * 1000;
} else {
mBucketSizeNs = kDefaultGaugemBucketSizeNs;
}
- for (int i = 0; i < metric.gauge_fields().field_num_size(); i++) {
- mGaugeFields.push_back(metric.gauge_fields().field_num(i));
- }
+ mFieldFilter = metric.gauge_fields_filter();
// TODO: use UidMap if uid->pkg_name is required
- mDimension.insert(mDimension.begin(), metric.dimension().begin(), metric.dimension().end());
+ mDimensions = metric.dimensions();
if (metric.links().size() > 0) {
mConditionLinks.insert(mConditionLinks.begin(), metric.links().begin(),
@@ -93,10 +89,10 @@
// Kicks off the puller immediately.
if (mPullTagId != -1) {
mStatsPullerManager->RegisterReceiver(mPullTagId, this,
- metric.bucket().bucket_size_millis());
+ metric.bucket().bucket_size_millis());
}
- VLOG("metric %s created. bucket size %lld start_time: %lld", metric.name().c_str(),
+ VLOG("metric %lld created. bucket size %lld start_time: %lld", (long long)metric.id(),
(long long)mBucketSizeNs, (long long)mStartTimeNs);
}
@@ -116,40 +112,32 @@
}
}
+void GaugeMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, StatsLogReport* report) {
+ flushIfNeededLocked(dumpTimeNs);
+}
+
void GaugeMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs,
ProtoOutputStream* protoOutput) {
- VLOG("gauge metric %s dump report now...", mName.c_str());
+ VLOG("gauge metric %lld report now...", (long long)mMetricId);
flushIfNeededLocked(dumpTimeNs);
- protoOutput->write(FIELD_TYPE_STRING | FIELD_ID_NAME, mName);
+ protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_ID, (long long)mMetricId);
protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_START_REPORT_NANOS, (long long)mStartTimeNs);
long long protoToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_ID_GAUGE_METRICS);
for (const auto& pair : mPastBuckets) {
const HashableDimensionKey& hashableKey = pair.first;
- const vector<KeyValuePair>& kvs = hashableKey.getKeyValuePairs();
VLOG(" dimension key %s", hashableKey.c_str());
long long wrapperToken =
protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DATA);
- // First fill dimension (KeyValuePairs).
- for (const auto& kv : kvs) {
- long long dimensionToken = protoOutput->start(
- FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DIMENSION);
- protoOutput->write(FIELD_TYPE_INT32 | FIELD_ID_KEY, kv.key());
- if (kv.has_value_str()) {
- protoOutput->write(FIELD_TYPE_STRING | FIELD_ID_VALUE_STR, kv.value_str());
- } else if (kv.has_value_int()) {
- protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_VALUE_INT, kv.value_int());
- } else if (kv.has_value_bool()) {
- protoOutput->write(FIELD_TYPE_BOOL | FIELD_ID_VALUE_BOOL, kv.value_bool());
- } else if (kv.has_value_float()) {
- protoOutput->write(FIELD_TYPE_FLOAT | FIELD_ID_VALUE_FLOAT, kv.value_float());
- }
- protoOutput->end(dimensionToken);
- }
+ // First fill dimension.
+ long long dimensionToken = protoOutput->start(
+ FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DIMENSION);
+ writeDimensionsValueProtoToStream(hashableKey.getDimensionsValue(), protoOutput);
+ protoOutput->end(dimensionToken);
// Then fill bucket_info (GaugeBucketInfo).
for (const auto& bucket : pair.second) {
@@ -160,25 +148,11 @@
protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_END_BUCKET_NANOS,
(long long)bucket.mBucketEndNs);
long long atomToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_ID_ATOM);
- long long eventToken = protoOutput->start(FIELD_TYPE_MESSAGE | mAtomTagId);
- for (const auto& pair : bucket.mEvent->kv) {
- if (pair.has_value_int()) {
- protoOutput->write(FIELD_TYPE_INT32 | pair.key(), pair.value_int());
- } else if (pair.has_value_long()) {
- protoOutput->write(FIELD_TYPE_INT64 | pair.key(), pair.value_long());
- } else if (pair.has_value_str()) {
- protoOutput->write(FIELD_TYPE_STRING | pair.key(), pair.value_str());
- } else if (pair.has_value_long()) {
- protoOutput->write(FIELD_TYPE_FLOAT | pair.key(), pair.value_float());
- } else if (pair.has_value_bool()) {
- protoOutput->write(FIELD_TYPE_BOOL | pair.key(), pair.value_bool());
- }
- }
- protoOutput->end(eventToken);
+ writeFieldValueTreeToStream(*bucket.mGaugeFields, protoOutput);
protoOutput->end(atomToken);
protoOutput->end(bucketInfoToken);
- VLOG("\t bucket [%lld - %lld] content: %s", (long long)bucket.mBucketStartNs,
- (long long)bucket.mBucketEndNs, bucket.mEvent->ToString().c_str());
+ VLOG("\t bucket [%lld - %lld] includes %d gauge fields.", (long long)bucket.mBucketStartNs,
+ (long long)bucket.mBucketEndNs, (int)bucket.mGaugeFields->size());
}
protoOutput->end(wrapperToken);
}
@@ -192,7 +166,7 @@
void GaugeMetricProducer::onConditionChangedLocked(const bool conditionMet,
const uint64_t eventTime) {
- VLOG("Metric %s onConditionChanged", mName.c_str());
+ VLOG("Metric %lld onConditionChanged", (long long)mMetricId);
flushIfNeededLocked(eventTime);
mCondition = conditionMet;
@@ -220,21 +194,16 @@
}
void GaugeMetricProducer::onSlicedConditionMayChangeLocked(const uint64_t eventTime) {
- VLOG("Metric %s onSlicedConditionMayChange", mName.c_str());
+ VLOG("Metric %lld onSlicedConditionMayChange", (long long)mMetricId);
}
-shared_ptr<EventKV> GaugeMetricProducer::getGauge(const LogEvent& event) {
- shared_ptr<EventKV> ret = make_shared<EventKV>();
- if (mGaugeFields.size() == 0) {
- for (int i = 1; i <= event.size(); i++) {
- ret->kv.push_back(event.GetKeyValueProto(i));
- }
- } else {
- for (int i = 0; i < (int)mGaugeFields.size(); i++) {
- ret->kv.push_back(event.GetKeyValueProto(mGaugeFields[i]));
- }
+std::shared_ptr<FieldValueMap> GaugeMetricProducer::getGaugeFields(const LogEvent& event) {
+ std::shared_ptr<FieldValueMap> gaugeFields =
+ std::make_shared<FieldValueMap>(event.getFieldValueMap());
+ if (!mFieldFilter.include_all()) {
+ filterFields(mFieldFilter.fields(), gaugeFields.get());
}
- return ret;
+ return gaugeFields;
}
void GaugeMetricProducer::onDataPulled(const std::vector<std::shared_ptr<LogEvent>>& allData) {
@@ -254,11 +223,11 @@
// 1. Report the tuple count if the tuple count > soft limit
if (mCurrentSlicedBucket->size() > StatsdStats::kDimensionKeySizeSoftLimit - 1) {
size_t newTupleCount = mCurrentSlicedBucket->size() + 1;
- StatsdStats::getInstance().noteMetricDimensionSize(mConfigKey, mName, newTupleCount);
+ StatsdStats::getInstance().noteMetricDimensionSize(mConfigKey, mMetricId, newTupleCount);
// 2. Don't add more tuples, we are above the allowed threshold. Drop the data.
if (newTupleCount > StatsdStats::kDimensionKeySizeHardLimit) {
- ALOGE("GaugeMetric %s dropping data for dimension key %s", mName.c_str(),
- newKey.c_str());
+ ALOGE("GaugeMetric %lld dropping data for dimension key %s",
+ (long long)mMetricId, newKey.c_str());
return true;
}
}
@@ -268,7 +237,7 @@
void GaugeMetricProducer::onMatchedLogEventInternalLocked(
const size_t matcherIndex, const HashableDimensionKey& eventKey,
- const map<string, HashableDimensionKey>& conditionKey, bool condition,
+ const ConditionKey& conditionKey, bool condition,
const LogEvent& event) {
if (condition == false) {
return;
@@ -285,21 +254,21 @@
if (mCurrentSlicedBucket->find(eventKey) != mCurrentSlicedBucket->end()) {
return;
}
- shared_ptr<EventKV> gauge = getGauge(event);
+ std::shared_ptr<FieldValueMap> gaugeFields = getGaugeFields(event);
if (hitGuardRailLocked(eventKey)) {
return;
}
- (*mCurrentSlicedBucket)[eventKey] = gauge;
+ (*mCurrentSlicedBucket)[eventKey] = gaugeFields;
// Anomaly detection on gauge metric only works when there is one numeric
// field specified.
if (mAnomalyTrackers.size() > 0) {
- if (gauge->kv.size() == 1) {
- KeyValuePair pair = gauge->kv[0];
+ if (gaugeFields->size() == 1) {
+ const DimensionsValue& dimensionsValue = gaugeFields->begin()->second;
long gaugeVal = 0;
- if (pair.has_value_int()) {
- gaugeVal = (long)pair.value_int();
- } else if (pair.has_value_long()) {
- gaugeVal = pair.value_long();
+ if (dimensionsValue.has_value_int()) {
+ gaugeVal = (long)dimensionsValue.value_int();
+ } else if (dimensionsValue.has_value_long()) {
+ gaugeVal = dimensionsValue.value_long();
}
for (auto& tracker : mAnomalyTrackers) {
tracker->detectAndDeclareAnomaly(eventTimeNs, mCurrentBucketNum, eventKey,
@@ -313,12 +282,12 @@
mCurrentSlicedBucketForAnomaly->clear();
status_t err = NO_ERROR;
for (const auto& slice : *mCurrentSlicedBucket) {
- KeyValuePair pair = slice.second->kv[0];
+ const DimensionsValue& dimensionsValue = slice.second->begin()->second;
long gaugeVal = 0;
- if (pair.has_value_int()) {
- gaugeVal = (long)pair.value_int();
- } else if (pair.has_value_long()) {
- gaugeVal = pair.value_long();
+ if (dimensionsValue.has_value_int()) {
+ gaugeVal = (long)dimensionsValue.value_int();
+ } else if (dimensionsValue.has_value_long()) {
+ gaugeVal = dimensionsValue.value_long();
}
(*mCurrentSlicedBucketForAnomaly)[slice.first] = gaugeVal;
}
@@ -342,11 +311,11 @@
info.mBucketNum = mCurrentBucketNum;
for (const auto& slice : *mCurrentSlicedBucket) {
- info.mEvent = slice.second;
+ info.mGaugeFields = slice.second;
auto& bucketList = mPastBuckets[slice.first];
bucketList.push_back(info);
- VLOG("gauge metric %s, dump key value: %s -> %s", mName.c_str(),
- slice.first.c_str(), slice.second->ToString().c_str());
+ VLOG("gauge metric %lld, dump key value: %s",
+ (long long)mMetricId, slice.first.c_str());
}
// Reset counters
@@ -357,13 +326,13 @@
}
}
- mCurrentSlicedBucket = std::make_shared<DimToEventKVMap>();
+ mCurrentSlicedBucket = std::make_shared<DimToGaugeFieldsMap>();
// Adjusts the bucket start time
int64_t numBucketsForward = (eventTimeNs - mCurrentBucketStartTimeNs) / mBucketSizeNs;
mCurrentBucketStartTimeNs = mCurrentBucketStartTimeNs + numBucketsForward * mBucketSizeNs;
mCurrentBucketNum += numBucketsForward;
- VLOG("metric %s: new bucket start time: %lld", mName.c_str(),
+ VLOG("metric %lld: new bucket start time: %lld", (long long)mMetricId,
(long long)mCurrentBucketStartTimeNs);
}
diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.h b/cmds/statsd/src/metrics/GaugeMetricProducer.h
index 19d51e8..2a6401d 100644
--- a/cmds/statsd/src/metrics/GaugeMetricProducer.h
+++ b/cmds/statsd/src/metrics/GaugeMetricProducer.h
@@ -35,10 +35,13 @@
struct GaugeBucket {
int64_t mBucketStartNs;
int64_t mBucketEndNs;
- std::shared_ptr<EventKV> mEvent;
+ std::shared_ptr<FieldValueMap> mGaugeFields;
uint64_t mBucketNum;
};
+typedef std::unordered_map<HashableDimensionKey, std::shared_ptr<FieldValueMap>>
+ DimToGaugeFieldsMap;
+
// This gauge metric producer first register the puller to automatically pull the gauge at the
// beginning of each bucket. If the condition is met, insert it to the bucket info. Otherwise
// proactively pull the gauge when the condition is changed to be true. Therefore, the gauge metric
@@ -57,12 +60,13 @@
protected:
void onMatchedLogEventInternalLocked(
const size_t matcherIndex, const HashableDimensionKey& eventKey,
- const std::map<std::string, HashableDimensionKey>& conditionKey, bool condition,
+ const ConditionKey& conditionKey, bool condition,
const LogEvent& event) override;
private:
void onDumpReportLocked(const uint64_t dumpTimeNs,
android::util::ProtoOutputStream* protoOutput) override;
+ void onDumpReportLocked(const uint64_t dumpTimeNs, StatsLogReport* report) override;
// for testing
GaugeMetricProducer(const ConfigKey& key, const GaugeMetric& gaugeMetric,
@@ -94,10 +98,10 @@
std::unordered_map<HashableDimensionKey, std::vector<GaugeBucket>> mPastBuckets;
// The current bucket.
- std::shared_ptr<DimToEventKVMap> mCurrentSlicedBucket = std::make_shared<DimToEventKVMap>();
+ std::shared_ptr<DimToGaugeFieldsMap> mCurrentSlicedBucket;
// The current bucket for anomaly detection.
- std::shared_ptr<DimToValMap> mCurrentSlicedBucketForAnomaly = std::make_shared<DimToValMap>();
+ std::shared_ptr<DimToValMap> mCurrentSlicedBucketForAnomaly;
// Translate Atom based bucket to single numeric value bucket for anomaly
void updateCurrentSlicedBucketForAnomaly();
@@ -105,10 +109,10 @@
int mAtomTagId;
// Whitelist of fields to report. Empty means all are reported.
- std::vector<int> mGaugeFields;
+ FieldFilter mFieldFilter;
// apply a whitelist on the original input
- std::shared_ptr<EventKV> getGauge(const LogEvent& event);
+ std::shared_ptr<FieldValueMap> getGaugeFields(const LogEvent& event);
// Util function to check whether the specified dimension hits the guardrail.
bool hitGuardRailLocked(const HashableDimensionKey& newKey);
diff --git a/cmds/statsd/src/metrics/MetricProducer.cpp b/cmds/statsd/src/metrics/MetricProducer.cpp
index 5286908..d620a7e 100644
--- a/cmds/statsd/src/metrics/MetricProducer.cpp
+++ b/cmds/statsd/src/metrics/MetricProducer.cpp
@@ -28,24 +28,14 @@
return;
}
- HashableDimensionKey eventKey;
-
- if (mDimension.size() > 0) {
- vector<KeyValuePair> key = getDimensionKey(event, mDimension);
- eventKey = HashableDimensionKey(key);
- } else {
- eventKey = DEFAULT_DIMENSION_KEY;
- }
-
bool condition;
-
- map<string, HashableDimensionKey> conditionKeys;
+ ConditionKey conditionKey;
if (mConditionSliced) {
for (const auto& link : mConditionLinks) {
- HashableDimensionKey conditionKey = getDimensionKeyForCondition(event, link);
- conditionKeys[link.condition()] = conditionKey;
+ conditionKey.insert(std::make_pair(link.condition(),
+ getDimensionKeysForCondition(event, link)));
}
- if (mWizard->query(mConditionTrackerIndex, conditionKeys) != ConditionState::kTrue) {
+ if (mWizard->query(mConditionTrackerIndex, conditionKey) != ConditionState::kTrue) {
condition = false;
} else {
condition = true;
@@ -53,7 +43,17 @@
} else {
condition = mCondition;
}
- onMatchedLogEventInternalLocked(matcherIndex, eventKey, conditionKeys, condition, event);
+
+ if (mDimensions.child_size() > 0) {
+ vector<DimensionsValue> dimensionValues = getDimensionKeys(event, mDimensions);
+ for (const DimensionsValue& dimensionValue : dimensionValues) {
+ onMatchedLogEventInternalLocked(
+ matcherIndex, HashableDimensionKey(dimensionValue), conditionKey, condition, event);
+ }
+ } else {
+ onMatchedLogEventInternalLocked(
+ matcherIndex, DEFAULT_DIMENSION_KEY, conditionKey, condition, event);
+ }
}
} // namespace statsd
diff --git a/cmds/statsd/src/metrics/MetricProducer.h b/cmds/statsd/src/metrics/MetricProducer.h
index 85ef4ad..3779c44 100644
--- a/cmds/statsd/src/metrics/MetricProducer.h
+++ b/cmds/statsd/src/metrics/MetricProducer.h
@@ -39,9 +39,9 @@
// be a no-op.
class MetricProducer : public virtual PackageInfoListener {
public:
- MetricProducer(const std::string& name, const ConfigKey& key, const int64_t startTimeNs,
+ MetricProducer(const int64_t& metricId, const ConfigKey& key, const int64_t startTimeNs,
const int conditionIndex, const sp<ConditionWizard>& wizard)
- : mName(name),
+ : mMetricId(metricId),
mConfigKey(key),
mStartTimeNs(startTimeNs),
mCurrentBucketStartTimeNs(startTimeNs),
@@ -50,6 +50,7 @@
mConditionSliced(false),
mWizard(wizard),
mConditionTrackerIndex(conditionIndex){};
+
virtual ~MetricProducer(){};
void notifyAppUpgrade(const string& apk, const int uid, const int64_t version) override{
@@ -90,6 +91,10 @@
std::lock_guard<std::mutex> lock(mMutex);
return onDumpReportLocked(dumpTimeNs, protoOutput);
}
+ void onDumpReport(const uint64_t dumpTimeNs, StatsLogReport* report) {
+ std::lock_guard<std::mutex> lock(mMutex);
+ return onDumpReportLocked(dumpTimeNs, report);
+ }
// Returns the memory in bytes currently used to store this metric's data. Does not change
// state.
@@ -98,13 +103,13 @@
return byteSizeLocked();
}
- virtual sp<AnomalyTracker> createAnomalyTracker(const Alert &alert) {
- return new AnomalyTracker(alert, mConfigKey);
- }
-
- void addAnomalyTracker(sp<AnomalyTracker> tracker) {
+ virtual sp<AnomalyTracker> addAnomalyTracker(const Alert &alert) {
std::lock_guard<std::mutex> lock(mMutex);
- mAnomalyTrackers.push_back(tracker);
+ sp<AnomalyTracker> anomalyTracker = new AnomalyTracker(alert, mConfigKey);
+ if (anomalyTracker != nullptr) {
+ mAnomalyTrackers.push_back(anomalyTracker);
+ }
+ return anomalyTracker;
}
int64_t getBuckeSizeInNs() const {
@@ -112,14 +117,19 @@
return mBucketSizeNs;
}
+ inline const int64_t& getMetricId() {
+ return mMetricId;
+ }
+
protected:
virtual void onConditionChangedLocked(const bool condition, const uint64_t eventTime) = 0;
virtual void onSlicedConditionMayChangeLocked(const uint64_t eventTime) = 0;
virtual void onDumpReportLocked(const uint64_t dumpTimeNs,
android::util::ProtoOutputStream* protoOutput) = 0;
+ virtual void onDumpReportLocked(const uint64_t dumpTimeNs, StatsLogReport* report) = 0;
virtual size_t byteSizeLocked() const = 0;
- const std::string mName;
+ const int64_t mMetricId;
const ConfigKey mConfigKey;
@@ -140,7 +150,7 @@
int mConditionTrackerIndex;
- std::vector<KeyMatcher> mDimension; // The dimension defined in statsd_config
+ FieldMatcher mDimensions; // The dimension defined in statsd_config
std::vector<MetricConditionLink> mConditionLinks;
@@ -163,7 +173,7 @@
*/
virtual void onMatchedLogEventInternalLocked(
const size_t matcherIndex, const HashableDimensionKey& eventKey,
- const std::map<std::string, HashableDimensionKey>& conditionKey, bool condition,
+ const ConditionKey& conditionKey, bool condition,
const LogEvent& event) = 0;
// Consume the parsed stats log entry that already matched the "what" of the metric.
diff --git a/cmds/statsd/src/metrics/MetricsManager.cpp b/cmds/statsd/src/metrics/MetricsManager.cpp
index 231bd8e..7f0239f 100644
--- a/cmds/statsd/src/metrics/MetricsManager.cpp
+++ b/cmds/statsd/src/metrics/MetricsManager.cpp
@@ -49,9 +49,9 @@
const long timeBaseSec, sp<UidMap> uidMap)
: mConfigKey(key), mUidMap(uidMap) {
mConfigValid =
- initStatsdConfig(key, config, timeBaseSec, mTagIds, mAllAtomMatchers, mAllConditionTrackers,
+ initStatsdConfig(key, config, *uidMap, timeBaseSec, mTagIds, mAllAtomMatchers, mAllConditionTrackers,
mAllMetricProducers, mAllAnomalyTrackers, mConditionToMetricMap,
- mTrackerToMetricMap, mTrackerToConditionMap);
+ mTrackerToMetricMap, mTrackerToConditionMap, mNoReportMetricIds);
if (!config.has_log_source()) {
// TODO(b/70794411): uncomment the following line and remove the hard coded log source
@@ -142,15 +142,25 @@
initLogSourceWhiteList();
}
+void MetricsManager::onDumpReport(const uint64_t& dumpTimeStampNs, ConfigMetricsReport* report) {
+ for (const auto& producer : mAllMetricProducers) {
+ if (mNoReportMetricIds.find(producer->getMetricId()) == mNoReportMetricIds.end()) {
+ producer->onDumpReport(dumpTimeStampNs, report->add_metrics());
+ }
+ }
+}
+
void MetricsManager::onDumpReport(ProtoOutputStream* protoOutput) {
VLOG("=========================Metric Reports Start==========================");
uint64_t dumpTimeStampNs = time(nullptr) * NS_PER_SEC;
// one StatsLogReport per MetricProduer
- for (auto& metric : mAllMetricProducers) {
- long long token =
- protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_METRICS);
- metric->onDumpReport(dumpTimeStampNs, protoOutput);
- protoOutput->end(token);
+ for (const auto& producer : mAllMetricProducers) {
+ if (mNoReportMetricIds.find(producer->getMetricId()) == mNoReportMetricIds.end()) {
+ long long token =
+ protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_METRICS);
+ producer->onDumpReport(dumpTimeStampNs, protoOutput);
+ protoOutput->end(token);
+ }
}
VLOG("=========================Metric Reports End==========================");
}
@@ -167,6 +177,22 @@
VLOG("log source %d not on the whitelist", event.GetUid());
return;
}
+ } else { // Check that app hook fields are valid.
+ // TODO: Find a way to make these checks easier to maintain if the app hooks get changed.
+
+ // Label is 2nd from last field and must be from [0, 15].
+ status_t err = NO_ERROR;
+ long label = event.GetLong(event.size()-1, &err);
+ if (err != NO_ERROR || label < 0 || label > 15) {
+ VLOG("App hook does not have valid label %ld", label);
+ return;
+ }
+ // The state must be from 0,3. This part of code must be manually updated.
+ long apphookState = event.GetLong(event.size(), &err);
+ if (err != NO_ERROR || apphookState < 0 || apphookState > 3) {
+ VLOG("App hook does not have valid state %ld", apphookState);
+ return;
+ }
}
int tagId = event.GetTagId();
@@ -234,7 +260,7 @@
for (size_t i = 0; i < mAllAtomMatchers.size(); i++) {
if (matcherCache[i] == MatchingState::kMatched) {
StatsdStats::getInstance().noteMatcherMatched(mConfigKey,
- mAllAtomMatchers[i]->getName());
+ mAllAtomMatchers[i]->getId());
auto pair = mTrackerToMetricMap.find(i);
if (pair != mTrackerToMetricMap.end()) {
auto& metricList = pair->second;
diff --git a/cmds/statsd/src/metrics/MetricsManager.h b/cmds/statsd/src/metrics/MetricsManager.h
index 8faa75d..8175cd2 100644
--- a/cmds/statsd/src/metrics/MetricsManager.h
+++ b/cmds/statsd/src/metrics/MetricsManager.h
@@ -45,8 +45,9 @@
void onLogEvent(const LogEvent& event);
- void onAnomalyAlarmFired(const uint64_t timestampNs,
- unordered_set<sp<const AnomalyAlarm>, SpHash<AnomalyAlarm>>& anomalySet);
+ void onAnomalyAlarmFired(
+ const uint64_t timestampNs,
+ unordered_set<sp<const AnomalyAlarm>, SpHash<AnomalyAlarm>>& anomalySet);
void setAnomalyMonitor(const sp<AnomalyMonitor>& anomalyMonitor);
@@ -58,6 +59,7 @@
// Config source owner can call onDumpReport() to get all the metrics collected.
virtual void onDumpReport(android::util::ProtoOutputStream* protoOutput);
+ virtual void onDumpReport(const uint64_t& dumpTimeStampNs, ConfigMetricsReport* report);
// Computes the total byte size of all metrics managed by a single config source.
// Does not change the state.
@@ -128,6 +130,12 @@
std::unordered_map<int, std::vector<int>> mConditionToMetricMap;
void initLogSourceWhiteList();
+
+ // The metrics that don't need to be uploaded or even reported.
+ std::set<int64_t> mNoReportMetricIds;
+
+ FRIEND_TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensions);
+ FRIEND_TEST(MetricConditionLinkE2eTest, TestMultiplePredicatesAndLinks);
};
} // namespace statsd
diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.cpp b/cmds/statsd/src/metrics/ValueMetricProducer.cpp
index 40aed7b..5f7d761 100644
--- a/cmds/statsd/src/metrics/ValueMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/ValueMetricProducer.cpp
@@ -17,8 +17,10 @@
#define DEBUG false // STOPSHIP if true
#include "Log.h"
+#include "dimension.h"
#include "ValueMetricProducer.h"
#include "guardrail/StatsdStats.h"
+#include "stats_log_util.h"
#include <cutils/log.h>
#include <limits.h>
@@ -45,7 +47,7 @@
namespace statsd {
// for StatsLogReport
-const int FIELD_ID_NAME = 1;
+const int FIELD_ID_ID = 1;
const int FIELD_ID_START_REPORT_NANOS = 2;
const int FIELD_ID_END_REPORT_NANOS = 3;
const int FIELD_ID_VALUE_METRICS = 7;
@@ -54,12 +56,6 @@
// for ValueMetricData
const int FIELD_ID_DIMENSION = 1;
const int FIELD_ID_BUCKET_INFO = 2;
-// for KeyValuePair
-const int FIELD_ID_KEY = 1;
-const int FIELD_ID_VALUE_STR = 2;
-const int FIELD_ID_VALUE_INT = 3;
-const int FIELD_ID_VALUE_BOOL = 4;
-const int FIELD_ID_VALUE_FLOAT = 5;
// for ValueBucketInfo
const int FIELD_ID_START_BUCKET_NANOS = 1;
const int FIELD_ID_END_BUCKET_NANOS = 2;
@@ -73,7 +69,7 @@
const sp<ConditionWizard>& wizard, const int pullTagId,
const uint64_t startTimeNs,
shared_ptr<StatsPullerManager> statsPullerManager)
- : MetricProducer(metric.name(), key, startTimeNs, conditionIndex, wizard),
+ : MetricProducer(metric.id(), key, startTimeNs, conditionIndex, wizard),
mValueField(metric.value_field()),
mStatsPullerManager(statsPullerManager),
mPullTagId(pullTagId) {
@@ -84,7 +80,7 @@
mBucketSizeNs = kDefaultBucketSizeMillis * 1000 * 1000;
}
- mDimension.insert(mDimension.begin(), metric.dimension().begin(), metric.dimension().end());
+ mDimensions = metric.dimensions();
if (metric.links().size() > 0) {
mConditionLinks.insert(mConditionLinks.begin(), metric.links().begin(),
@@ -97,8 +93,8 @@
mStatsPullerManager->RegisterReceiver(mPullTagId, this,
metric.bucket().bucket_size_millis());
}
- VLOG("value metric %s created. bucket size %lld start_time: %lld", metric.name().c_str(),
- (long long)mBucketSizeNs, (long long)mStartTimeNs);
+ VLOG("value metric %lld created. bucket size %lld start_time: %lld",
+ (long long)metric.id(), (long long)mBucketSizeNs, (long long)mStartTimeNs);
}
// for testing
@@ -118,40 +114,45 @@
}
void ValueMetricProducer::onSlicedConditionMayChangeLocked(const uint64_t eventTime) {
- VLOG("Metric %s onSlicedConditionMayChange", mName.c_str());
+ VLOG("Metric %lld onSlicedConditionMayChange", (long long)mMetricId);
+}
+
+void ValueMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, StatsLogReport* report) {
+ flushIfNeededLocked(dumpTimeNs);
+ report->set_metric_id(mMetricId);
+ report->set_start_report_nanos(mStartTimeNs);
+ auto value_metrics = report->mutable_value_metrics();
+ for (const auto& pair : mPastBuckets) {
+ ValueMetricData* metricData = value_metrics->add_data();
+ *metricData->mutable_dimension() = pair.first.getDimensionsValue();
+ for (const auto& bucket : pair.second) {
+ ValueBucketInfo* bucketInfo = metricData->add_bucket_info();
+ bucketInfo->set_start_bucket_nanos(bucket.mBucketStartNs);
+ bucketInfo->set_end_bucket_nanos(bucket.mBucketEndNs);
+ bucketInfo->set_value(bucket.mValue);
+ }
+ }
}
void ValueMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs,
ProtoOutputStream* protoOutput) {
- VLOG("metric %s dump report now...", mName.c_str());
+ VLOG("metric %lld dump report now...", (long long)mMetricId);
flushIfNeededLocked(dumpTimeNs);
- protoOutput->write(FIELD_TYPE_STRING | FIELD_ID_NAME, mName);
+ protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_ID, (long long)mMetricId);
protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_START_REPORT_NANOS, (long long)mStartTimeNs);
long long protoToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_ID_VALUE_METRICS);
for (const auto& pair : mPastBuckets) {
const HashableDimensionKey& hashableKey = pair.first;
VLOG(" dimension key %s", hashableKey.c_str());
- const vector<KeyValuePair>& kvs = hashableKey.getKeyValuePairs();
long long wrapperToken =
protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DATA);
- // First fill dimension (KeyValuePairs).
- for (const auto& kv : kvs) {
- long long dimensionToken = protoOutput->start(
- FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DIMENSION);
- protoOutput->write(FIELD_TYPE_INT32 | FIELD_ID_KEY, kv.key());
- if (kv.has_value_str()) {
- protoOutput->write(FIELD_TYPE_STRING | FIELD_ID_VALUE_STR, kv.value_str());
- } else if (kv.has_value_int()) {
- protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_VALUE_INT, kv.value_int());
- } else if (kv.has_value_bool()) {
- protoOutput->write(FIELD_TYPE_BOOL | FIELD_ID_VALUE_BOOL, kv.value_bool());
- } else if (kv.has_value_float()) {
- protoOutput->write(FIELD_TYPE_FLOAT | FIELD_ID_VALUE_FLOAT, kv.value_float());
- }
- protoOutput->end(dimensionToken);
- }
+ // First fill dimension.
+ long long dimensionToken = protoOutput->start(
+ FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DIMENSION);
+ writeDimensionsValueProtoToStream(hashableKey.getDimensionsValue(), protoOutput);
+ protoOutput->end(dimensionToken);
// Then fill bucket_info (ValueBucketInfo).
for (const auto& bucket : pair.second) {
@@ -171,7 +172,7 @@
protoOutput->end(protoToken);
protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_END_REPORT_NANOS, (long long)dumpTimeNs);
- VLOG("metric %s dump report now...", mName.c_str());
+ VLOG("metric %lld dump report now...", (long long)mMetricId);
mPastBuckets.clear();
mStartTimeNs = mCurrentBucketStartTimeNs;
// TODO: Clear mDimensionKeyMap once the report is dumped.
@@ -218,7 +219,8 @@
// For scheduled pulled data, the effective event time is snap to the nearest
// bucket boundary to make bucket finalize.
uint64_t realEventTime = allData.at(0)->GetTimestampNs();
- uint64_t eventTime = mStartTimeNs + ((realEventTime - mStartTimeNs)/mBucketSizeNs) * mBucketSizeNs;
+ uint64_t eventTime = mStartTimeNs +
+ ((realEventTime - mStartTimeNs)/mBucketSizeNs) * mBucketSizeNs;
mCondition = false;
for (const auto& data : allData) {
@@ -242,11 +244,11 @@
}
if (mCurrentSlicedBucket.size() > StatsdStats::kDimensionKeySizeSoftLimit - 1) {
size_t newTupleCount = mCurrentSlicedBucket.size() + 1;
- StatsdStats::getInstance().noteMetricDimensionSize(mConfigKey, mName, newTupleCount);
+ StatsdStats::getInstance().noteMetricDimensionSize(mConfigKey, mMetricId, newTupleCount);
// 2. Don't add more tuples, we are above the allowed threshold. Drop the data.
if (newTupleCount > StatsdStats::kDimensionKeySizeHardLimit) {
- ALOGE("ValueMetric %s dropping data for dimension key %s", mName.c_str(),
- newKey.c_str());
+ ALOGE("ValueMetric %lld dropping data for dimension key %s",
+ (long long)mMetricId, newKey.c_str());
return true;
}
}
@@ -256,7 +258,7 @@
void ValueMetricProducer::onMatchedLogEventInternalLocked(
const size_t matcherIndex, const HashableDimensionKey& eventKey,
- const map<string, HashableDimensionKey>& conditionKey, bool condition,
+ const ConditionKey& conditionKey, bool condition,
const LogEvent& event) {
uint64_t eventTimeNs = event.GetTimestampNs();
if (eventTimeNs < mCurrentBucketStartTimeNs) {
@@ -272,7 +274,11 @@
}
Interval& interval = mCurrentSlicedBucket[eventKey];
- long value = get_value(event);
+ std::shared_ptr<FieldValueMap> valueFieldMap = getValueFields(event);
+ if (valueFieldMap->empty() || valueFieldMap->size() > 1) {
+ return;
+ }
+ const long value = getLongFromDimenValue(valueFieldMap->begin()->second);
if (mPullTagId != -1) { // for pulled events
if (mCondition == true) {
@@ -296,15 +302,11 @@
}
}
-long ValueMetricProducer::get_value(const LogEvent& event) {
- status_t err = NO_ERROR;
- long val = event.GetLong(mValueField, &err);
- if (err == NO_ERROR) {
- return val;
- } else {
- VLOG("Can't find value in message. %s", event.ToString().c_str());
- return 0;
- }
+std::shared_ptr<FieldValueMap> ValueMetricProducer::getValueFields(const LogEvent& event) {
+ std::shared_ptr<FieldValueMap> valueFields =
+ std::make_shared<FieldValueMap>(event.getFieldValueMap());
+ filterFields(mValueField, valueFields.get());
+ return valueFields;
}
void ValueMetricProducer::flushIfNeededLocked(const uint64_t& eventTimeNs) {
@@ -346,7 +348,7 @@
if (numBucketsForward > 1) {
VLOG("Skipping forward %lld buckets", (long long)numBucketsForward);
}
- VLOG("metric %s: new bucket start time: %lld", mName.c_str(),
+ VLOG("metric %lld: new bucket start time: %lld", (long long)mMetricId,
(long long)mCurrentBucketStartTimeNs);
}
diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.h b/cmds/statsd/src/metrics/ValueMetricProducer.h
index 2f27e4e..3e7032d 100644
--- a/cmds/statsd/src/metrics/ValueMetricProducer.h
+++ b/cmds/statsd/src/metrics/ValueMetricProducer.h
@@ -50,12 +50,13 @@
protected:
void onMatchedLogEventInternalLocked(
const size_t matcherIndex, const HashableDimensionKey& eventKey,
- const std::map<std::string, HashableDimensionKey>& conditionKey, bool condition,
+ const ConditionKey& conditionKey, bool condition,
const LogEvent& event) override;
private:
void onDumpReportLocked(const uint64_t dumpTimeNs,
android::util::ProtoOutputStream* protoOutput) override;
+ void onDumpReportLocked(const uint64_t dumpTimeNs, StatsLogReport* report) override;
// Internal interface to handle condition change.
void onConditionChangedLocked(const bool conditionMet, const uint64_t eventTime) override;
@@ -69,7 +70,7 @@
// Util function to flush the old packet.
void flushIfNeededLocked(const uint64_t& eventTime);
- const int32_t mValueField;
+ const FieldMatcher mValueField;
std::shared_ptr<StatsPullerManager> mStatsPullerManager;
@@ -102,7 +103,7 @@
// TODO: Add a lock to mPastBuckets.
std::unordered_map<HashableDimensionKey, std::vector<ValueBucket>> mPastBuckets;
- long get_value(const LogEvent& event);
+ std::shared_ptr<FieldValueMap> getValueFields(const LogEvent& event);
// Util function to check whether the specified dimension hits the guardrail.
bool hitGuardRailLocked(const HashableDimensionKey& newKey);
diff --git a/cmds/statsd/src/metrics/duration_helper/DurationTracker.h b/cmds/statsd/src/metrics/duration_helper/DurationTracker.h
index 3c714b3..842581e 100644
--- a/cmds/statsd/src/metrics/duration_helper/DurationTracker.h
+++ b/cmds/statsd/src/metrics/duration_helper/DurationTracker.h
@@ -17,7 +17,7 @@
#ifndef DURATION_TRACKER_H
#define DURATION_TRACKER_H
-#include "anomaly/AnomalyTracker.h"
+#include "anomaly/DurationAnomalyTracker.h"
#include "condition/ConditionWizard.h"
#include "config/ConfigKey.h"
#include "stats_util.h"
@@ -60,12 +60,12 @@
class DurationTracker {
public:
- DurationTracker(const ConfigKey& key, const string& name, const HashableDimensionKey& eventKey,
+ DurationTracker(const ConfigKey& key, const int64_t& id, const HashableDimensionKey& eventKey,
sp<ConditionWizard> wizard, int conditionIndex, bool nesting,
uint64_t currentBucketStartNs, uint64_t bucketSizeNs,
- const std::vector<sp<AnomalyTracker>>& anomalyTrackers)
+ const std::vector<sp<DurationAnomalyTracker>>& anomalyTrackers)
: mConfigKey(key),
- mName(name),
+ mTrackerId(id),
mEventKey(eventKey),
mWizard(wizard),
mConditionTrackerIndex(conditionIndex),
@@ -94,7 +94,7 @@
std::unordered_map<HashableDimensionKey, std::vector<DurationBucket>>* output) = 0;
// Predict the anomaly timestamp given the current status.
- virtual int64_t predictAnomalyTimestampNs(const AnomalyTracker& anomalyTracker,
+ virtual int64_t predictAnomalyTimestampNs(const DurationAnomalyTracker& anomalyTracker,
const uint64_t currentTimestamp) const = 0;
protected:
@@ -145,7 +145,7 @@
// A reference to the DurationMetricProducer's config key.
const ConfigKey& mConfigKey;
- const std::string mName;
+ const int64_t mTrackerId;
HashableDimensionKey mEventKey;
@@ -163,7 +163,7 @@
uint64_t mCurrentBucketNum;
- std::vector<sp<AnomalyTracker>> mAnomalyTrackers;
+ std::vector<sp<DurationAnomalyTracker>> mAnomalyTrackers;
FRIEND_TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp);
FRIEND_TEST(OringDurationTrackerTest, TestAnomalyDetection);
diff --git a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp
index 6050f43..94f98ad 100644
--- a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp
+++ b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp
@@ -24,12 +24,12 @@
namespace os {
namespace statsd {
-MaxDurationTracker::MaxDurationTracker(const ConfigKey& key, const string& name,
+MaxDurationTracker::MaxDurationTracker(const ConfigKey& key, const int64_t& id,
const HashableDimensionKey& eventKey,
sp<ConditionWizard> wizard, int conditionIndex, bool nesting,
uint64_t currentBucketStartNs, uint64_t bucketSizeNs,
- const std::vector<sp<AnomalyTracker>>& anomalyTrackers)
- : DurationTracker(key, name, eventKey, wizard, conditionIndex, nesting, currentBucketStartNs,
+ const vector<sp<DurationAnomalyTracker>>& anomalyTrackers)
+ : DurationTracker(key, id, eventKey, wizard, conditionIndex, nesting, currentBucketStartNs,
bucketSizeNs, anomalyTrackers) {
}
@@ -42,12 +42,13 @@
// 1. Report the tuple count if the tuple count > soft limit
if (mInfos.size() > StatsdStats::kDimensionKeySizeSoftLimit - 1) {
size_t newTupleCount = mInfos.size() + 1;
- StatsdStats::getInstance().noteMetricDimensionSize(mConfigKey, mName + mEventKey.toString(),
- newTupleCount);
+ StatsdStats::getInstance().noteMetricDimensionSize(
+ mConfigKey, hashDimensionsValue(mTrackerId, mEventKey.getDimensionsValue()),
+ newTupleCount);
// 2. Don't add more tuples, we are above the allowed threshold. Drop the data.
if (newTupleCount > StatsdStats::kDimensionKeySizeHardLimit) {
- ALOGE("MaxDurTracker %s dropping data for dimension key %s", mName.c_str(),
- newKey.c_str());
+ ALOGE("MaxDurTracker %lld dropping data for dimension key %s",
+ (long long)mTrackerId, newKey.c_str());
return true;
}
}
@@ -281,7 +282,7 @@
}
}
-int64_t MaxDurationTracker::predictAnomalyTimestampNs(const AnomalyTracker& anomalyTracker,
+int64_t MaxDurationTracker::predictAnomalyTimestampNs(const DurationAnomalyTracker& anomalyTracker,
const uint64_t currentTimestamp) const {
ALOGE("Max duration producer does not support anomaly timestamp prediction!!!");
return currentTimestamp;
diff --git a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h
index 10eddb8..68c48cb1 100644
--- a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h
+++ b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h
@@ -28,11 +28,11 @@
// they stop or bucket expires.
class MaxDurationTracker : public DurationTracker {
public:
- MaxDurationTracker(const ConfigKey& key, const string& name,
+ MaxDurationTracker(const ConfigKey& key, const int64_t& id,
const HashableDimensionKey& eventKey, sp<ConditionWizard> wizard,
int conditionIndex, bool nesting, uint64_t currentBucketStartNs,
uint64_t bucketSizeNs,
- const std::vector<sp<AnomalyTracker>>& anomalyTrackers);
+ const std::vector<sp<DurationAnomalyTracker>>& anomalyTrackers);
void noteStart(const HashableDimensionKey& key, bool condition, const uint64_t eventTime,
const ConditionKey& conditionKey) override;
void noteStop(const HashableDimensionKey& key, const uint64_t eventTime,
@@ -46,7 +46,7 @@
void onSlicedConditionMayChange(const uint64_t timestamp) override;
void onConditionChanged(bool condition, const uint64_t timestamp) override;
- int64_t predictAnomalyTimestampNs(const AnomalyTracker& anomalyTracker,
+ int64_t predictAnomalyTimestampNs(const DurationAnomalyTracker& anomalyTracker,
const uint64_t currentTimestamp) const override;
private:
diff --git a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp
index 5c43096..c77d0b7 100644
--- a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp
+++ b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp
@@ -24,13 +24,12 @@
using std::pair;
-OringDurationTracker::OringDurationTracker(const ConfigKey& key, const string& name,
- const HashableDimensionKey& eventKey,
- sp<ConditionWizard> wizard, int conditionIndex,
- bool nesting, uint64_t currentBucketStartNs,
- uint64_t bucketSizeNs,
- const std::vector<sp<AnomalyTracker>>& anomalyTrackers)
- : DurationTracker(key, name, eventKey, wizard, conditionIndex, nesting, currentBucketStartNs,
+OringDurationTracker::OringDurationTracker(
+ const ConfigKey& key, const int64_t& id, const HashableDimensionKey& eventKey,
+ sp<ConditionWizard> wizard, int conditionIndex, bool nesting, uint64_t currentBucketStartNs,
+ uint64_t bucketSizeNs, const vector<sp<DurationAnomalyTracker>>& anomalyTrackers)
+
+ : DurationTracker(key, id, eventKey, wizard, conditionIndex, nesting, currentBucketStartNs,
bucketSizeNs, anomalyTrackers),
mStarted(),
mPaused() {
@@ -45,12 +44,13 @@
}
if (mConditionKeyMap.size() > StatsdStats::kDimensionKeySizeSoftLimit - 1) {
size_t newTupleCount = mConditionKeyMap.size() + 1;
- StatsdStats::getInstance().noteMetricDimensionSize(mConfigKey, mName + mEventKey.toString(),
- newTupleCount);
+ StatsdStats::getInstance().noteMetricDimensionSize(
+ mConfigKey, hashDimensionsValue(mTrackerId, mEventKey.getDimensionsValue()),
+ newTupleCount);
// 2. Don't add more tuples, we are above the allowed threshold. Drop the data.
if (newTupleCount > StatsdStats::kDimensionKeySizeHardLimit) {
- ALOGE("OringDurTracker %s dropping data for dimension key %s", mName.c_str(),
- newKey.c_str());
+ ALOGE("OringDurTracker %lld dropping data for dimension key %s",
+ (long long)mTrackerId, newKey.c_str());
return true;
}
}
@@ -264,8 +264,8 @@
}
}
-int64_t OringDurationTracker::predictAnomalyTimestampNs(const AnomalyTracker& anomalyTracker,
- const uint64_t eventTimestampNs) const {
+int64_t OringDurationTracker::predictAnomalyTimestampNs(
+ const DurationAnomalyTracker& anomalyTracker, const uint64_t eventTimestampNs) const {
// TODO: Unit-test this and see if it can be done more efficiently (e.g. use int32).
// All variables below represent durations (not timestamps).
diff --git a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h
index b7d3cba..7fe649c 100644
--- a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h
+++ b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h
@@ -27,11 +27,11 @@
// Tracks the "Or'd" duration -- if 2 durations are overlapping, they won't be double counted.
class OringDurationTracker : public DurationTracker {
public:
- OringDurationTracker(const ConfigKey& key, const string& name,
+ OringDurationTracker(const ConfigKey& key, const int64_t& id,
const HashableDimensionKey& eventKey, sp<ConditionWizard> wizard,
int conditionIndex, bool nesting, uint64_t currentBucketStartNs,
uint64_t bucketSizeNs,
- const std::vector<sp<AnomalyTracker>>& anomalyTrackers);
+ const std::vector<sp<DurationAnomalyTracker>>& anomalyTrackers);
void noteStart(const HashableDimensionKey& key, bool condition, const uint64_t eventTime,
const ConditionKey& conditionKey) override;
@@ -46,7 +46,7 @@
uint64_t timestampNs,
std::unordered_map<HashableDimensionKey, std::vector<DurationBucket>>* output) override;
- int64_t predictAnomalyTimestampNs(const AnomalyTracker& anomalyTracker,
+ int64_t predictAnomalyTimestampNs(const DurationAnomalyTracker& anomalyTracker,
const uint64_t currentTimestamp) const override;
private:
diff --git a/cmds/statsd/src/metrics/metrics_manager_util.cpp b/cmds/statsd/src/metrics/metrics_manager_util.cpp
index 5d0e97e..89d54c7b 100644
--- a/cmds/statsd/src/metrics/metrics_manager_util.cpp
+++ b/cmds/statsd/src/metrics/metrics_manager_util.cpp
@@ -38,21 +38,21 @@
namespace os {
namespace statsd {
-bool handleMetricWithLogTrackers(const string what, const int metricIndex,
+bool handleMetricWithLogTrackers(const int64_t what, const int metricIndex,
const bool usedForDimension,
const vector<sp<LogMatchingTracker>>& allAtomMatchers,
- const unordered_map<string, int>& logTrackerMap,
+ const unordered_map<int64_t, int>& logTrackerMap,
unordered_map<int, std::vector<int>>& trackerToMetricMap,
int& logTrackerIndex) {
auto logTrackerIt = logTrackerMap.find(what);
if (logTrackerIt == logTrackerMap.end()) {
- ALOGW("cannot find the AtomMatcher \"%s\" in config", what.c_str());
+ ALOGW("cannot find the AtomMatcher \"%lld\" in config", (long long)what);
return false;
}
- if (usedForDimension && allAtomMatchers[logTrackerIt->second]->getTagIds().size() > 1) {
- ALOGE("AtomMatcher \"%s\" has more than one tag ids. When a metric has dimension, "
+ if (usedForDimension && allAtomMatchers[logTrackerIt->second]->getAtomIds().size() > 1) {
+ ALOGE("AtomMatcher \"%lld\" has more than one tag ids. When a metric has dimension, "
"the \"what\" can only about one atom type.",
- what.c_str());
+ (long long)what);
return false;
}
logTrackerIndex = logTrackerIt->second;
@@ -62,22 +62,22 @@
}
bool handleMetricWithConditions(
- const string condition, const int metricIndex,
- const unordered_map<string, int>& conditionTrackerMap,
+ const int64_t condition, const int metricIndex,
+ const unordered_map<int64_t, int>& conditionTrackerMap,
const ::google::protobuf::RepeatedPtrField<::android::os::statsd::MetricConditionLink>&
links,
vector<sp<ConditionTracker>>& allConditionTrackers, int& conditionIndex,
unordered_map<int, std::vector<int>>& conditionToMetricMap) {
auto condition_it = conditionTrackerMap.find(condition);
if (condition_it == conditionTrackerMap.end()) {
- ALOGW("cannot find Predicate \"%s\" in the config", condition.c_str());
+ ALOGW("cannot find Predicate \"%lld\" in the config", (long long)condition);
return false;
}
for (const auto& link : links) {
auto it = conditionTrackerMap.find(link.condition());
if (it == conditionTrackerMap.end()) {
- ALOGW("cannot find Predicate \"%s\" in the config", link.condition().c_str());
+ ALOGW("cannot find Predicate \"%lld\" in the config", (long long)link.condition());
return false;
}
allConditionTrackers[condition_it->second]->setSliced(true);
@@ -92,7 +92,8 @@
return true;
}
-bool initLogTrackers(const StatsdConfig& config, unordered_map<string, int>& logTrackerMap,
+bool initLogTrackers(const StatsdConfig& config, const UidMap& uidMap,
+ unordered_map<int64_t, int>& logTrackerMap,
vector<sp<LogMatchingTracker>>& allAtomMatchers, set<int>& allTagIds) {
vector<AtomMatcher> matcherConfigs;
const int atomMatcherCount = config.atom_matcher_size();
@@ -106,22 +107,22 @@
switch (logMatcher.contents_case()) {
case AtomMatcher::ContentsCase::kSimpleAtomMatcher:
allAtomMatchers.push_back(new SimpleLogMatchingTracker(
- logMatcher.name(), index, logMatcher.simple_atom_matcher()));
+ logMatcher.id(), index, logMatcher.simple_atom_matcher(), uidMap));
break;
case AtomMatcher::ContentsCase::kCombination:
allAtomMatchers.push_back(
- new CombinationLogMatchingTracker(logMatcher.name(), index));
+ new CombinationLogMatchingTracker(logMatcher.id(), index));
break;
default:
- ALOGE("Matcher \"%s\" malformed", logMatcher.name().c_str());
+ ALOGE("Matcher \"%lld\" malformed", (long long)logMatcher.id());
return false;
// continue;
}
- if (logTrackerMap.find(logMatcher.name()) != logTrackerMap.end()) {
+ if (logTrackerMap.find(logMatcher.id()) != logTrackerMap.end()) {
ALOGE("Duplicate AtomMatcher found!");
return false;
}
- logTrackerMap[logMatcher.name()] = index;
+ logTrackerMap[logMatcher.id()] = index;
matcherConfigs.push_back(logMatcher);
}
@@ -131,15 +132,15 @@
return false;
}
// Collect all the tag ids that are interesting. TagIds exist in leaf nodes only.
- const set<int>& tagIds = matcher->getTagIds();
+ const set<int>& tagIds = matcher->getAtomIds();
allTagIds.insert(tagIds.begin(), tagIds.end());
}
return true;
}
bool initConditions(const ConfigKey& key, const StatsdConfig& config,
- const unordered_map<string, int>& logTrackerMap,
- unordered_map<string, int>& conditionTrackerMap,
+ const unordered_map<int64_t, int>& logTrackerMap,
+ unordered_map<int64_t, int>& conditionTrackerMap,
vector<sp<ConditionTracker>>& allConditionTrackers,
unordered_map<int, std::vector<int>>& trackerToConditionMap) {
vector<Predicate> conditionConfigs;
@@ -153,23 +154,23 @@
switch (condition.contents_case()) {
case Predicate::ContentsCase::kSimplePredicate: {
allConditionTrackers.push_back(new SimpleConditionTracker(
- key, condition.name(), index, condition.simple_predicate(), logTrackerMap));
+ key, condition.id(), index, condition.simple_predicate(), logTrackerMap));
break;
}
case Predicate::ContentsCase::kCombination: {
allConditionTrackers.push_back(
- new CombinationConditionTracker(condition.name(), index));
+ new CombinationConditionTracker(condition.id(), index));
break;
}
default:
- ALOGE("Predicate \"%s\" malformed", condition.name().c_str());
+ ALOGE("Predicate \"%lld\" malformed", (long long)condition.id());
return false;
}
- if (conditionTrackerMap.find(condition.name()) != conditionTrackerMap.end()) {
+ if (conditionTrackerMap.find(condition.id()) != conditionTrackerMap.end()) {
ALOGE("Duplicate Predicate found!");
return false;
}
- conditionTrackerMap[condition.name()] = index;
+ conditionTrackerMap[condition.id()] = index;
conditionConfigs.push_back(condition);
}
@@ -189,14 +190,15 @@
}
bool initMetrics(const ConfigKey& key, const StatsdConfig& config, const long timeBaseSec,
- const unordered_map<string, int>& logTrackerMap,
- const unordered_map<string, int>& conditionTrackerMap,
+ const unordered_map<int64_t, int>& logTrackerMap,
+ const unordered_map<int64_t, int>& conditionTrackerMap,
const vector<sp<LogMatchingTracker>>& allAtomMatchers,
vector<sp<ConditionTracker>>& allConditionTrackers,
vector<sp<MetricProducer>>& allMetricProducers,
unordered_map<int, std::vector<int>>& conditionToMetricMap,
unordered_map<int, std::vector<int>>& trackerToMetricMap,
- unordered_map<string, int>& metricMap) {
+ unordered_map<int64_t, int>& metricMap,
+ std::set<int64_t> &noReportMetricIds) {
sp<ConditionWizard> wizard = new ConditionWizard(allConditionTrackers);
const int allMetricsCount = config.count_metric_size() + config.duration_metric_size() +
config.event_metric_size() + config.value_metric_size();
@@ -205,24 +207,27 @@
// Align all buckets to same instant in MIN_BUCKET_SIZE_SEC, so that avoid alarm
// clock will not grow very aggressive. New metrics will be delayed up to
// MIN_BUCKET_SIZE_SEC before starting.
- long currentTimeSec = time(nullptr);
- uint64_t startTimeNs = (currentTimeSec - kMinBucketSizeSec -
- (currentTimeSec - timeBaseSec) % kMinBucketSizeSec) *
- NS_PER_SEC;
+ // Why not use timeBaseSec directly?
+// long currentTimeSec = time(nullptr);
+// uint64_t startTimeNs = (currentTimeSec - kMinBucketSizeSec -
+// (currentTimeSec - timeBaseSec) % kMinBucketSizeSec) *
+// NS_PER_SEC;
+
+ uint64_t startTimeNs = timeBaseSec * NS_PER_SEC;
// Build MetricProducers for each metric defined in config.
// build CountMetricProducer
for (int i = 0; i < config.count_metric_size(); i++) {
const CountMetric& metric = config.count_metric(i);
if (!metric.has_what()) {
- ALOGW("cannot find \"what\" in CountMetric \"%s\"", metric.name().c_str());
+ ALOGW("cannot find \"what\" in CountMetric \"%lld\"", (long long)metric.id());
return false;
}
int metricIndex = allMetricProducers.size();
- metricMap.insert({metric.name(), metricIndex});
+ metricMap.insert({metric.id(), metricIndex});
int trackerIndex;
- if (!handleMetricWithLogTrackers(metric.what(), metricIndex, metric.dimension_size() > 0,
+ if (!handleMetricWithLogTrackers(metric.what(), metricIndex, metric.has_dimensions(),
allAtomMatchers, logTrackerMap, trackerToMetricMap,
trackerIndex)) {
return false;
@@ -252,7 +257,7 @@
for (int i = 0; i < config.duration_metric_size(); i++) {
int metricIndex = allMetricProducers.size();
const DurationMetric& metric = config.duration_metric(i);
- metricMap.insert({metric.name(), metricIndex});
+ metricMap.insert({metric.id(), metricIndex});
auto what_it = conditionTrackerMap.find(metric.what());
if (what_it == conditionTrackerMap.end()) {
@@ -274,7 +279,7 @@
int trackerIndices[3] = {-1, -1, -1};
if (!simplePredicate.has_start() ||
!handleMetricWithLogTrackers(simplePredicate.start(), metricIndex,
- metric.dimension_size() > 0, allAtomMatchers,
+ metric.has_dimensions(), allAtomMatchers,
logTrackerMap, trackerToMetricMap, trackerIndices[0])) {
ALOGE("Duration metrics must specify a valid the start event matcher");
return false;
@@ -282,21 +287,19 @@
if (simplePredicate.has_stop() &&
!handleMetricWithLogTrackers(simplePredicate.stop(), metricIndex,
- metric.dimension_size() > 0, allAtomMatchers,
+ metric.has_dimensions(), allAtomMatchers,
logTrackerMap, trackerToMetricMap, trackerIndices[1])) {
return false;
}
if (simplePredicate.has_stop_all() &&
!handleMetricWithLogTrackers(simplePredicate.stop_all(), metricIndex,
- metric.dimension_size() > 0, allAtomMatchers,
+ metric.has_dimensions(), allAtomMatchers,
logTrackerMap, trackerToMetricMap, trackerIndices[2])) {
return false;
}
- vector<KeyMatcher> internalDimension;
- internalDimension.insert(internalDimension.begin(), simplePredicate.dimension().begin(),
- simplePredicate.dimension().end());
+ FieldMatcher internalDimensions = simplePredicate.dimensions();
int conditionIndex = -1;
@@ -316,7 +319,7 @@
sp<MetricProducer> durationMetric = new DurationMetricProducer(
key, metric, conditionIndex, trackerIndices[0], trackerIndices[1],
- trackerIndices[2], nesting, wizard, internalDimension, startTimeNs);
+ trackerIndices[2], nesting, wizard, internalDimensions, startTimeNs);
allMetricProducers.push_back(durationMetric);
}
@@ -325,8 +328,8 @@
for (int i = 0; i < config.event_metric_size(); i++) {
int metricIndex = allMetricProducers.size();
const EventMetric& metric = config.event_metric(i);
- metricMap.insert({metric.name(), metricIndex});
- if (!metric.has_name() || !metric.has_what()) {
+ metricMap.insert({metric.id(), metricIndex});
+ if (!metric.has_id() || !metric.has_what()) {
ALOGW("cannot find the metric name or what in config");
return false;
}
@@ -361,14 +364,14 @@
for (int i = 0; i < config.value_metric_size(); i++) {
const ValueMetric& metric = config.value_metric(i);
if (!metric.has_what()) {
- ALOGW("cannot find \"what\" in ValueMetric \"%s\"", metric.name().c_str());
+ ALOGW("cannot find \"what\" in ValueMetric \"%lld\"", (long long)metric.id());
return false;
}
int metricIndex = allMetricProducers.size();
- metricMap.insert({metric.name(), metricIndex});
+ metricMap.insert({metric.id(), metricIndex});
int trackerIndex;
- if (!handleMetricWithLogTrackers(metric.what(), metricIndex, metric.dimension_size() > 0,
+ if (!handleMetricWithLogTrackers(metric.what(), metricIndex, metric.has_dimensions(),
allAtomMatchers, logTrackerMap, trackerToMetricMap,
trackerIndex)) {
return false;
@@ -376,10 +379,10 @@
sp<LogMatchingTracker> atomMatcher = allAtomMatchers.at(trackerIndex);
// If it is pulled atom, it should be simple matcher with one tagId.
- if (atomMatcher->getTagIds().size() != 1) {
+ if (atomMatcher->getAtomIds().size() != 1) {
return false;
}
- int atomTagId = *(atomMatcher->getTagIds().begin());
+ int atomTagId = *(atomMatcher->getAtomIds().begin());
int pullTagId = statsPullerManager.PullerForMatcherExists(atomTagId) ? atomTagId : -1;
int conditionIndex = -1;
@@ -406,27 +409,27 @@
for (int i = 0; i < config.gauge_metric_size(); i++) {
const GaugeMetric& metric = config.gauge_metric(i);
if (!metric.has_what()) {
- ALOGW("cannot find \"what\" in GaugeMetric \"%s\"", metric.name().c_str());
+ ALOGW("cannot find \"what\" in GaugeMetric \"%lld\"", (long long)metric.id());
return false;
}
- if ((!metric.gauge_fields().has_include_all() ||
- (metric.gauge_fields().include_all() == false)) &&
- metric.gauge_fields().field_num_size() == 0) {
- ALOGW("Incorrect field filter setting in GaugeMetric %s", metric.name().c_str());
+ if ((!metric.gauge_fields_filter().has_include_all() ||
+ (metric.gauge_fields_filter().include_all() == false)) &&
+ !hasLeafNode(metric.gauge_fields_filter().fields())) {
+ ALOGW("Incorrect field filter setting in GaugeMetric %lld", (long long)metric.id());
return false;
}
- if ((metric.gauge_fields().has_include_all() &&
- metric.gauge_fields().include_all() == true) &&
- metric.gauge_fields().field_num_size() > 0) {
- ALOGW("Incorrect field filter setting in GaugeMetric %s", metric.name().c_str());
+ if ((metric.gauge_fields_filter().has_include_all() &&
+ metric.gauge_fields_filter().include_all() == true) &&
+ hasLeafNode(metric.gauge_fields_filter().fields())) {
+ ALOGW("Incorrect field filter setting in GaugeMetric %lld", (long long)metric.id());
return false;
}
int metricIndex = allMetricProducers.size();
- metricMap.insert({metric.name(), metricIndex});
+ metricMap.insert({metric.id(), metricIndex});
int trackerIndex;
- if (!handleMetricWithLogTrackers(metric.what(), metricIndex, metric.dimension_size() > 0,
+ if (!handleMetricWithLogTrackers(metric.what(), metricIndex, metric.has_dimensions(),
allAtomMatchers, logTrackerMap, trackerToMetricMap,
trackerIndex)) {
return false;
@@ -434,10 +437,10 @@
sp<LogMatchingTracker> atomMatcher = allAtomMatchers.at(trackerIndex);
// If it is pulled atom, it should be simple matcher with one tagId.
- if (atomMatcher->getTagIds().size() != 1) {
+ if (atomMatcher->getAtomIds().size() != 1) {
return false;
}
- int atomTagId = *(atomMatcher->getTagIds().begin());
+ int atomTagId = *(atomMatcher->getAtomIds().begin());
int pullTagId = statsPullerManager.PullerForMatcherExists(atomTagId) ? atomTagId : -1;
int conditionIndex = -1;
@@ -459,49 +462,79 @@
key, metric, conditionIndex, wizard, pullTagId, atomTagId, startTimeNs);
allMetricProducers.push_back(gaugeProducer);
}
- return true;
-}
-
-bool initAlerts(const StatsdConfig& config, const unordered_map<string, int>& metricProducerMap,
- vector<sp<MetricProducer>>& allMetricProducers,
- vector<sp<AnomalyTracker>>& allAnomalyTrackers) {
- for (int i = 0; i < config.alert_size(); i++) {
- const Alert& alert = config.alert(i);
- const auto& itr = metricProducerMap.find(alert.metric_name());
- if (itr == metricProducerMap.end()) {
- ALOGW("alert \"%s\" has unknown metric name: \"%s\"", alert.name().c_str(),
- alert.metric_name().c_str());
+ for (int i = 0; i < config.no_report_metric_size(); ++i) {
+ const auto no_report_metric = config.no_report_metric(i);
+ if (metricMap.find(no_report_metric) == metricMap.end()) {
+ ALOGW("no_report_metric %lld not exist", no_report_metric);
return false;
}
- if (alert.trigger_if_sum_gt() < 0 || alert.number_of_buckets() <= 0) {
- ALOGW("invalid alert: threshold=%lld num_buckets= %d",
- alert.trigger_if_sum_gt(), alert.number_of_buckets());
- return false;
- }
- const int metricIndex = itr->second;
- sp<MetricProducer> metric = allMetricProducers[metricIndex];
- sp<AnomalyTracker> anomalyTracker = metric->createAnomalyTracker(alert);
- if (anomalyTracker != nullptr) {
- metric->addAnomalyTracker(anomalyTracker);
- allAnomalyTrackers.push_back(anomalyTracker);
- }
+ noReportMetricIds.insert(no_report_metric);
}
return true;
}
-bool initStatsdConfig(const ConfigKey& key, const StatsdConfig& config, const long timeBaseSec, set<int>& allTagIds,
+bool initAlerts(const StatsdConfig& config,
+ const unordered_map<int64_t, int>& metricProducerMap,
+ vector<sp<MetricProducer>>& allMetricProducers,
+ vector<sp<AnomalyTracker>>& allAnomalyTrackers) {
+ unordered_map<int64_t, int> anomalyTrackerMap;
+ for (int i = 0; i < config.alert_size(); i++) {
+ const Alert& alert = config.alert(i);
+ const auto& itr = metricProducerMap.find(alert.metric_id());
+ if (itr == metricProducerMap.end()) {
+ ALOGW("alert \"%lld\" has unknown metric id: \"%lld\"", (long long)alert.id(),
+ (long long)alert.metric_id());
+ return false;
+ }
+ if (alert.trigger_if_sum_gt() < 0 || alert.num_buckets() <= 0) {
+ ALOGW("invalid alert: threshold=%f num_buckets= %d",
+ alert.trigger_if_sum_gt(), alert.num_buckets());
+ return false;
+ }
+ const int metricIndex = itr->second;
+ sp<MetricProducer> metric = allMetricProducers[metricIndex];
+ sp<AnomalyTracker> anomalyTracker = metric->addAnomalyTracker(alert);
+ if (anomalyTracker != nullptr) {
+ anomalyTrackerMap.insert(std::make_pair(alert.id(), allAnomalyTrackers.size()));
+ allAnomalyTrackers.push_back(anomalyTracker);
+ }
+ }
+ for (int i = 0; i < config.subscription_size(); ++i) {
+ const Subscription& subscription = config.subscription(i);
+ if (subscription.subscriber_information_case() ==
+ Subscription::SubscriberInformationCase::SUBSCRIBER_INFORMATION_NOT_SET) {
+ ALOGW("subscription \"%lld\" has no subscriber info.\"",
+ (long long)subscription.id());
+ return false;
+ }
+ const auto& itr = anomalyTrackerMap.find(subscription.rule_id());
+ if (itr == anomalyTrackerMap.end()) {
+ ALOGW("subscription \"%lld\" has unknown rule id: \"%lld\"",
+ (long long)subscription.id(), (long long)subscription.rule_id());
+ return false;
+ }
+ const int anomalyTrackerIndex = itr->second;
+ allAnomalyTrackers[anomalyTrackerIndex]->addSubscription(subscription);
+ }
+ return true;
+}
+
+bool initStatsdConfig(const ConfigKey& key, const StatsdConfig& config,
+ const UidMap& uidMap,
+ const long timeBaseSec, set<int>& allTagIds,
vector<sp<LogMatchingTracker>>& allAtomMatchers,
vector<sp<ConditionTracker>>& allConditionTrackers,
vector<sp<MetricProducer>>& allMetricProducers,
vector<sp<AnomalyTracker>>& allAnomalyTrackers,
unordered_map<int, std::vector<int>>& conditionToMetricMap,
unordered_map<int, std::vector<int>>& trackerToMetricMap,
- unordered_map<int, std::vector<int>>& trackerToConditionMap) {
- unordered_map<string, int> logTrackerMap;
- unordered_map<string, int> conditionTrackerMap;
- unordered_map<string, int> metricProducerMap;
+ unordered_map<int, std::vector<int>>& trackerToConditionMap,
+ std::set<int64_t> &noReportMetricIds) {
+ unordered_map<int64_t, int> logTrackerMap;
+ unordered_map<int64_t, int> conditionTrackerMap;
+ unordered_map<int64_t, int> metricProducerMap;
- if (!initLogTrackers(config, logTrackerMap, allAtomMatchers, allTagIds)) {
+ if (!initLogTrackers(config, uidMap, logTrackerMap, allAtomMatchers, allTagIds)) {
ALOGE("initLogMatchingTrackers failed");
return false;
}
@@ -515,7 +548,7 @@
if (!initMetrics(key, config, timeBaseSec, logTrackerMap, conditionTrackerMap, allAtomMatchers,
allConditionTrackers, allMetricProducers, conditionToMetricMap,
- trackerToMetricMap, metricProducerMap)) {
+ trackerToMetricMap, metricProducerMap, noReportMetricIds)) {
ALOGE("initMetricProducers failed");
return false;
}
diff --git a/cmds/statsd/src/metrics/metrics_manager_util.h b/cmds/statsd/src/metrics/metrics_manager_util.h
index 3337332..4f19ada 100644
--- a/cmds/statsd/src/metrics/metrics_manager_util.h
+++ b/cmds/statsd/src/metrics/metrics_manager_util.h
@@ -43,7 +43,8 @@
// [allAtomMatchers]: should store the sp to all the LogMatchingTracker
// [allTagIds]: contains the set of all interesting tag ids to this config.
bool initLogTrackers(const StatsdConfig& config,
- std::unordered_map<std::string, int>& logTrackerMap,
+ const UidMap& uidMap,
+ std::unordered_map<int64_t, int>& logTrackerMap,
std::vector<sp<LogMatchingTracker>>& allAtomMatchers,
std::set<int>& allTagIds);
@@ -58,8 +59,8 @@
// [trackerToConditionMap]: contain the mapping from index of
// log tracker to condition trackers that use the log tracker
bool initConditions(const ConfigKey& key, const StatsdConfig& config,
- const std::unordered_map<std::string, int>& logTrackerMap,
- std::unordered_map<std::string, int>& conditionTrackerMap,
+ const std::unordered_map<int64_t, int>& logTrackerMap,
+ std::unordered_map<int64_t, int>& conditionTrackerMap,
std::vector<sp<ConditionTracker>>& allConditionTrackers,
std::unordered_map<int, std::vector<int>>& trackerToConditionMap,
std::unordered_map<int, std::vector<MetricConditionLink>>& eventConditionLinks);
@@ -78,25 +79,29 @@
// [trackerToMetricMap]: contains the mapping from log tracker to MetricProducer index.
bool initMetrics(
const ConfigKey& key, const StatsdConfig& config, const long timeBaseSec,
- const std::unordered_map<std::string, int>& logTrackerMap,
- const std::unordered_map<std::string, int>& conditionTrackerMap,
+ const std::unordered_map<int64_t, int>& logTrackerMap,
+ const std::unordered_map<int64_t, int>& conditionTrackerMap,
const std::unordered_map<int, std::vector<MetricConditionLink>>& eventConditionLinks,
const vector<sp<LogMatchingTracker>>& allAtomMatchers,
vector<sp<ConditionTracker>>& allConditionTrackers,
std::vector<sp<MetricProducer>>& allMetricProducers,
std::unordered_map<int, std::vector<int>>& conditionToMetricMap,
- std::unordered_map<int, std::vector<int>>& trackerToMetricMap);
+ std::unordered_map<int, std::vector<int>>& trackerToMetricMap,
+ std::set<int64_t> &noReportMetricIds);
// Initialize MetricsManager from StatsdConfig.
// Parameters are the members of MetricsManager. See MetricsManager for declaration.
-bool initStatsdConfig(const ConfigKey& key, const StatsdConfig& config, const long timeBaseSec, std::set<int>& allTagIds,
+bool initStatsdConfig(const ConfigKey& key, const StatsdConfig& config,
+ const UidMap& uidMap,
+ const long timeBaseSec, std::set<int>& allTagIds,
std::vector<sp<LogMatchingTracker>>& allAtomMatchers,
std::vector<sp<ConditionTracker>>& allConditionTrackers,
std::vector<sp<MetricProducer>>& allMetricProducers,
vector<sp<AnomalyTracker>>& allAnomalyTrackers,
std::unordered_map<int, std::vector<int>>& conditionToMetricMap,
std::unordered_map<int, std::vector<int>>& trackerToMetricMap,
- std::unordered_map<int, std::vector<int>>& trackerToConditionMap);
+ std::unordered_map<int, std::vector<int>>& trackerToConditionMap,
+ std::set<int64_t> &noReportMetricIds);
} // namespace statsd
} // namespace os
diff --git a/cmds/statsd/src/packages/UidMap.cpp b/cmds/statsd/src/packages/UidMap.cpp
index 21a9cf3..416b87b 100644
--- a/cmds/statsd/src/packages/UidMap.cpp
+++ b/cmds/statsd/src/packages/UidMap.cpp
@@ -404,4 +404,4 @@
} // namespace statsd
} // namespace os
-} // namespace android
+} // namespace android
\ No newline at end of file
diff --git a/cmds/statsd/src/packages/UidMap.h b/cmds/statsd/src/packages/UidMap.h
index a9aec94..02dea54 100644
--- a/cmds/statsd/src/packages/UidMap.h
+++ b/cmds/statsd/src/packages/UidMap.h
@@ -168,4 +168,3 @@
} // namespace statsd
} // namespace os
} // namespace android
-
diff --git a/cmds/statsd/src/stats_log.proto b/cmds/statsd/src/stats_log.proto
index 3c85c57..0b369bb 100644
--- a/cmds/statsd/src/stats_log.proto
+++ b/cmds/statsd/src/stats_log.proto
@@ -24,8 +24,14 @@
import "frameworks/base/cmds/statsd/src/atoms.proto";
-message KeyValuePair {
- optional int32 key = 1;
+message Field {
+ optional int32 field = 1;
+ optional int32 position_index = 2 [default = -1];
+ repeated Field child = 3;
+}
+
+message DimensionsValue {
+ optional int32 field = 1;
oneof value {
string value_str = 2;
@@ -33,9 +39,14 @@
int64 value_long = 4;
bool value_bool = 5;
float value_float = 6;
+ DimensionsValueTuple value_tuple = 7;
}
}
+message DimensionsValueTuple {
+ repeated DimensionsValue dimensions_value = 1;
+}
+
message EventMetricData {
optional int64 timestamp_nanos = 1;
@@ -51,7 +62,7 @@
}
message CountMetricData {
- repeated KeyValuePair dimension = 1;
+ optional DimensionsValue dimension = 1;
repeated CountBucketInfo bucket_info = 2;
}
@@ -65,7 +76,7 @@
}
message DurationMetricData {
- repeated KeyValuePair dimension = 1;
+ optional DimensionsValue dimension = 1;
repeated DurationBucketInfo bucket_info = 2;
}
@@ -79,7 +90,7 @@
}
message ValueMetricData {
- repeated KeyValuePair dimension = 1;
+ optional DimensionsValue dimension = 1;
repeated ValueBucketInfo bucket_info = 2;
}
@@ -93,7 +104,7 @@
}
message GaugeMetricData {
- repeated KeyValuePair dimension = 1;
+ optional DimensionsValue dimension = 1;
repeated GaugeBucketInfo bucket_info = 2;
}
@@ -126,7 +137,7 @@
}
message StatsLogReport {
- optional string metric_name = 1;
+ optional int64 metric_id = 1;
optional int64 start_report_nanos = 2;
@@ -167,7 +178,7 @@
message ConfigMetricsReportList {
message ConfigKey {
optional int32 uid = 1;
- optional string name = 2;
+ optional int64 id = 2;
}
optional ConfigKey config_key = 1;
@@ -180,28 +191,28 @@
optional int32 stats_end_time_sec = 2;
message MatcherStats {
- optional string name = 1;
+ optional int64 id = 1;
optional int32 matched_times = 2;
}
message ConditionStats {
- optional string name = 1;
+ optional int64 id = 1;
optional int32 max_tuple_counts = 2;
}
message MetricStats {
- optional string name = 1;
+ optional int64 id = 1;
optional int32 max_tuple_counts = 2;
}
message AlertStats {
- optional string name = 1;
+ optional int64 id = 1;
optional int32 alerted_times = 2;
}
message ConfigStats {
optional int32 uid = 1;
- optional string name = 2;
+ optional int64 id = 2;
optional int32 creation_time_sec = 3;
optional int32 deletion_time_sec = 4;
optional int32 metric_count = 5;
diff --git a/cmds/statsd/src/stats_log_util.cpp b/cmds/statsd/src/stats_log_util.cpp
new file mode 100644
index 0000000..476e117
--- /dev/null
+++ b/cmds/statsd/src/stats_log_util.cpp
@@ -0,0 +1,227 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "stats_log_util.h"
+
+#include <set>
+#include <stack>
+#include <utils/Log.h>
+
+using android::util::FIELD_COUNT_REPEATED;
+using android::util::FIELD_TYPE_BOOL;
+using android::util::FIELD_TYPE_FLOAT;
+using android::util::FIELD_TYPE_INT32;
+using android::util::FIELD_TYPE_INT64;
+using android::util::FIELD_TYPE_MESSAGE;
+using android::util::FIELD_TYPE_STRING;
+using android::util::ProtoOutputStream;
+
+namespace android {
+namespace os {
+namespace statsd {
+
+// for DimensionsValue Proto
+const int DIMENSIONS_VALUE_FIELD = 1;
+const int DIMENSIONS_VALUE_VALUE_STR = 2;
+const int DIMENSIONS_VALUE_VALUE_INT = 3;
+const int DIMENSIONS_VALUE_VALUE_LONG = 4;
+const int DIMENSIONS_VALUE_VALUE_BOOL = 5;
+const int DIMENSIONS_VALUE_VALUE_FLOAT = 6;
+const int DIMENSIONS_VALUE_VALUE_TUPLE = 7;
+
+// for MessageValue Proto
+const int FIELD_ID_FIELD_VALUE_IN_MESSAGE_VALUE_PROTO = 1;
+
+void writeDimensionsValueProtoToStream(const DimensionsValue& dimensionsValue,
+ ProtoOutputStream* protoOutput) {
+ protoOutput->write(FIELD_TYPE_INT32 | DIMENSIONS_VALUE_FIELD, dimensionsValue.field());
+ switch (dimensionsValue.value_case()) {
+ case DimensionsValue::ValueCase::kValueStr:
+ protoOutput->write(FIELD_TYPE_STRING | DIMENSIONS_VALUE_VALUE_STR,
+ dimensionsValue.value_str());
+ break;
+ case DimensionsValue::ValueCase::kValueInt:
+ protoOutput->write(FIELD_TYPE_INT32 | DIMENSIONS_VALUE_VALUE_INT,
+ dimensionsValue.value_int());
+ break;
+ case DimensionsValue::ValueCase::kValueLong:
+ protoOutput->write(FIELD_TYPE_INT64 | DIMENSIONS_VALUE_VALUE_LONG,
+ dimensionsValue.value_long());
+ break;
+ case DimensionsValue::ValueCase::kValueBool:
+ protoOutput->write(FIELD_TYPE_BOOL | DIMENSIONS_VALUE_VALUE_BOOL,
+ dimensionsValue.value_bool());
+ break;
+ case DimensionsValue::ValueCase::kValueFloat:
+ protoOutput->write(FIELD_TYPE_FLOAT | DIMENSIONS_VALUE_VALUE_FLOAT,
+ dimensionsValue.value_float());
+ break;
+ case DimensionsValue::ValueCase::kValueTuple:
+ {
+ long long tupleToken = protoOutput->start(
+ FIELD_TYPE_MESSAGE | DIMENSIONS_VALUE_VALUE_TUPLE);
+ for (int i = 0; i < dimensionsValue.value_tuple().dimensions_value_size(); ++i) {
+ long long token = protoOutput->start(
+ FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED
+ | FIELD_ID_FIELD_VALUE_IN_MESSAGE_VALUE_PROTO);
+ writeDimensionsValueProtoToStream(
+ dimensionsValue.value_tuple().dimensions_value(i), protoOutput);
+ protoOutput->end(token);
+ }
+ protoOutput->end(tupleToken);
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+// for Field Proto
+const int FIELD_FIELD = 1;
+const int FIELD_POSITION_INDEX = 2;
+const int FIELD_CHILD = 3;
+
+void writeFieldProtoToStream(
+ const Field& field, util::ProtoOutputStream* protoOutput) {
+ protoOutput->write(FIELD_TYPE_INT32 | FIELD_FIELD, field.field());
+ if (field.has_position_index()) {
+ protoOutput->write(FIELD_TYPE_INT32 | FIELD_POSITION_INDEX, field.position_index());
+ }
+ for (int i = 0; i < field.child_size(); ++i) {
+ long long childToken = protoOutput->start(
+ FIELD_TYPE_MESSAGE| FIELD_COUNT_REPEATED | FIELD_CHILD);
+ writeFieldProtoToStream(field.child(i), protoOutput);
+ protoOutput->end(childToken);
+ }
+}
+
+namespace {
+
+void addOrUpdateChildrenMap(
+ const Field& root,
+ const Field& node,
+ std::map<Field, std::set<Field, FieldCmp>, FieldCmp> *childrenMap) {
+ Field parentNode = root;
+ if (node.has_position_index()) {
+ appendLeaf(&parentNode, node.field(), node.position_index());
+ } else {
+ appendLeaf(&parentNode, node.field());
+ }
+ if (childrenMap->find(parentNode) == childrenMap->end()) {
+ childrenMap->insert(std::make_pair(parentNode, std::set<Field, FieldCmp>{}));
+ }
+ auto it = childrenMap->find(parentNode);
+ for (int i = 0; i < node.child_size(); ++i) {
+ auto child = node.child(i);
+ Field childNode = parentNode;
+ if (child.has_position_index()) {
+ appendLeaf(&childNode, child.field(), child.position_index());
+ } else {
+ appendLeaf(&childNode, child.field());
+ }
+ it->second.insert(childNode);
+ addOrUpdateChildrenMap(parentNode, child, childrenMap);
+ }
+}
+
+void addOrUpdateChildrenMap(
+ const Field& field,
+ std::map<Field, std::set<Field, FieldCmp>, FieldCmp> *childrenMap) {
+ Field root;
+ addOrUpdateChildrenMap(root, field, childrenMap);
+}
+
+} // namespace
+
+void writeFieldValueTreeToStream(const FieldValueMap &fieldValueMap,
+ util::ProtoOutputStream* protoOutput) {
+ std::map<Field, std::set<Field, FieldCmp>, FieldCmp> childrenMap;
+ // Rebuild the field tree.
+ for (auto it = fieldValueMap.begin(); it != fieldValueMap.end(); ++it) {
+ addOrUpdateChildrenMap(it->first, &childrenMap);
+ }
+ std::stack<std::pair<long long, Field>> tokenStack;
+ // Iterate over the node tree to fill the Atom proto.
+ for (auto it = childrenMap.begin(); it != childrenMap.end(); ++it) {
+ const Field* nodeLeaf = getSingleLeaf(&it->first);
+ const int fieldNum = nodeLeaf->field();
+ while (!tokenStack.empty()) {
+ auto currentMsgNode = tokenStack.top().second;
+ auto currentMsgNodeChildrenIt = childrenMap.find(currentMsgNode);
+ if (currentMsgNodeChildrenIt->second.find(it->first) ==
+ currentMsgNodeChildrenIt->second.end()) {
+ protoOutput->end(tokenStack.top().first);
+ tokenStack.pop();
+ } else {
+ break;
+ }
+ }
+ if (it->second.size() == 0) {
+ auto itValue = fieldValueMap.find(it->first);
+ if (itValue != fieldValueMap.end()) {
+ const DimensionsValue& value = itValue->second;
+ switch (value.value_case()) {
+ case DimensionsValue::ValueCase::kValueStr:
+ protoOutput->write(FIELD_TYPE_STRING | fieldNum,
+ value.value_str());
+ break;
+ case DimensionsValue::ValueCase::kValueInt:
+ protoOutput->write(FIELD_TYPE_INT32 | fieldNum,
+ value.value_int());
+ break;
+ case DimensionsValue::ValueCase::kValueLong:
+ protoOutput->write(FIELD_TYPE_INT64 | fieldNum,
+ value.value_long());
+ break;
+ case DimensionsValue::ValueCase::kValueBool:
+ protoOutput->write(FIELD_TYPE_BOOL | fieldNum,
+ value.value_bool());
+ break;
+ case DimensionsValue::ValueCase::kValueFloat:
+ protoOutput->write(FIELD_TYPE_FLOAT | fieldNum,
+ value.value_float());
+ break;
+ // This would not happen as the node has no child.
+ case DimensionsValue::ValueCase::kValueTuple:
+ break;
+ default:
+ break;
+ }
+ } else {
+ ALOGE("Leaf node value not found. This should never happen.");
+ }
+ } else {
+ long long token;
+ if (nodeLeaf->has_position_index()) {
+ token = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | fieldNum);
+ } else {
+ token = protoOutput->start(FIELD_TYPE_MESSAGE | fieldNum);
+ }
+ tokenStack.push(std::make_pair(token, it->first));
+ }
+ }
+
+ while (!tokenStack.empty()) {
+ protoOutput->end(tokenStack.top().first);
+ tokenStack.pop();
+ }
+
+
+}
+
+} // namespace statsd
+} // namespace os
+} // namespace android
\ No newline at end of file
diff --git a/cmds/statsd/src/stats_log_util.h b/cmds/statsd/src/stats_log_util.h
new file mode 100644
index 0000000..1f81860
--- /dev/null
+++ b/cmds/statsd/src/stats_log_util.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <android/util/ProtoOutputStream.h>
+#include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
+#include "field_util.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+// Helper function to write DimensionsValue proto to ProtoOutputStream.
+void writeDimensionsValueProtoToStream(
+ const DimensionsValue& fieldValue, util::ProtoOutputStream* protoOutput);
+
+// Helper function to write Field proto to ProtoOutputStream.
+void writeFieldProtoToStream(
+ const Field& field, util::ProtoOutputStream* protoOutput);
+
+// Helper function to construct the field value tree and write to ProtoOutputStream
+void writeFieldValueTreeToStream(const FieldValueMap &fieldValueMap,
+ util::ProtoOutputStream* protoOutput);
+
+} // namespace statsd
+} // namespace os
+} // namespace android
\ No newline at end of file
diff --git a/cmds/statsd/src/stats_util.h b/cmds/statsd/src/stats_util.h
index 1cdf031..160b1f4 100644
--- a/cmds/statsd/src/stats_util.h
+++ b/cmds/statsd/src/stats_util.h
@@ -27,45 +27,15 @@
namespace os {
namespace statsd {
-const HashableDimensionKey DEFAULT_DIMENSION_KEY = HashableDimensionKey(vector<KeyValuePair>());
+const HashableDimensionKey DEFAULT_DIMENSION_KEY = HashableDimensionKey();
// Minimum bucket size in seconds
const long kMinBucketSizeSec = 5 * 60;
-typedef std::map<std::string, HashableDimensionKey> ConditionKey;
+typedef std::map<int64_t, std::vector<HashableDimensionKey>> ConditionKey;
typedef std::unordered_map<HashableDimensionKey, int64_t> DimToValMap;
-/*
- * In memory rep for LogEvent. Uses much less memory than LogEvent
- */
-typedef struct EventKV {
- std::vector<KeyValuePair> kv;
- string ToString() const {
- std::ostringstream result;
- result << "{ ";
- const size_t N = kv.size();
- for (size_t i = 0; i < N; i++) {
- result << " ";
- result << (i + 1);
- result << "->";
- const auto& pair = kv[i];
- if (pair.has_value_int()) {
- result << pair.value_int();
- } else if (pair.has_value_long()) {
- result << pair.value_long();
- } else if (pair.has_value_float()) {
- result << pair.value_float();
- } else if (pair.has_value_str()) {
- result << pair.value_str().c_str();
- }
- }
- result << " }";
- return result.str();
- }
-} EventKV;
-
-typedef std::unordered_map<HashableDimensionKey, std::shared_ptr<EventKV>> DimToEventKVMap;
} // namespace statsd
} // namespace os
} // namespace android
diff --git a/cmds/statsd/src/statsd_config.proto b/cmds/statsd/src/statsd_config.proto
index 4729f6a..a5057da 100644
--- a/cmds/statsd/src/statsd_config.proto
+++ b/cmds/statsd/src/statsd_config.proto
@@ -22,30 +22,52 @@
option java_package = "com.android.internal.os";
option java_outer_classname = "StatsdConfigProto";
-message KeyMatcher {
- optional int32 key = 1;
-
- optional bool as_package_name = 2 [default = false];
+enum Position {
+ POSITION_UNKNOWN = 0;
+ FIRST = 1;
+ LAST = 2;
+ ANY = 3;
}
-message KeyValueMatcher {
- optional KeyMatcher key_matcher = 1;
+message FieldMatcher {
+ optional int32 field = 1;
+
+ optional Position position = 2;
+
+ repeated FieldMatcher child = 3;
+}
+
+message FieldValueMatcher {
+ // Field id, as specified in the atom proto message.
+ optional int32 field = 1;
+
+ // For repeated fields, specifies the position in the array.
+ // FIRST and LAST mean that if the values are found at the first
+ // or last position, it's a match. ANY means that if the values are found
+ // anywhere in the array, then it's a match.
+ optional Position position = 2;
oneof value_matcher {
- bool eq_bool = 2;
- string eq_string = 3;
- int32 eq_int = 4;
+ bool eq_bool = 3;
+ string eq_string = 4;
+ int32 eq_int = 5;
- int64 lt_int = 5;
- int64 gt_int = 6;
- float lt_float = 7;
- float gt_float = 8;
+ int64 lt_int = 6;
+ int64 gt_int = 7;
+ float lt_float = 8;
+ float gt_float = 9;
- int64 lte_int = 9;
- int64 gte_int = 10;
+ int64 lte_int = 10;
+ int64 gte_int = 11;
+
+ MessageMatcher matches_tuple = 12;
}
}
+message MessageMatcher {
+ repeated FieldValueMatcher field_value_matcher = 1;
+}
+
enum LogicalOperation {
LOGICAL_OPERATION_UNSPECIFIED = 0;
AND = 1;
@@ -56,18 +78,18 @@
}
message SimpleAtomMatcher {
- optional int32 tag = 1;
+ optional int32 atom_id = 1;
- repeated KeyValueMatcher key_value_matcher = 2;
+ repeated FieldValueMatcher field_value_matcher = 2;
}
message AtomMatcher {
- optional string name = 1;
+ optional int64 id = 1;
message Combination {
optional LogicalOperation operation = 1;
- repeated string matcher = 2;
+ repeated int64 matcher = 2;
}
oneof contents {
SimpleAtomMatcher simple_atom_matcher = 2;
@@ -76,13 +98,13 @@
}
message SimplePredicate {
- optional string start = 1;
+ optional int64 start = 1;
- optional string stop = 2;
+ optional int64 stop = 2;
optional bool count_nesting = 3 [default = true];
- optional string stop_all = 4;
+ optional int64 stop_all = 4;
enum InitialValue {
UNKNOWN = 0;
@@ -90,16 +112,16 @@
}
optional InitialValue initial_value = 5 [default = FALSE];
- repeated KeyMatcher dimension = 6;
+ optional FieldMatcher dimensions = 6;
}
message Predicate {
- optional string name = 1;
+ optional int64 id = 1;
message Combination {
optional LogicalOperation operation = 1;
- repeated string predicate = 2;
+ repeated int64 predicate = 2;
}
oneof contents {
@@ -113,36 +135,36 @@
}
message MetricConditionLink {
- optional string condition = 1;
+ optional int64 condition = 1;
- repeated KeyMatcher key_in_what = 2;
+ optional FieldMatcher dimensions_in_what = 2;
- repeated KeyMatcher key_in_condition = 3;
+ optional FieldMatcher dimensions_in_condition = 3;
}
message FieldFilter {
- optional bool include_all = 1;
- repeated int32 field_num = 2;
+ optional bool include_all = 1 [default = false];
+ optional FieldMatcher fields = 2;
}
message EventMetric {
- optional string name = 1;
+ optional int64 id = 1;
- optional string what = 2;
+ optional int64 what = 2;
- optional string condition = 3;
+ optional int64 condition = 3;
repeated MetricConditionLink links = 4;
}
message CountMetric {
- optional string name = 1;
+ optional int64 id = 1;
- optional string what = 2;
+ optional int64 what = 2;
- optional string condition = 3;
+ optional int64 condition = 3;
- repeated KeyMatcher dimension = 4;
+ optional FieldMatcher dimensions = 4;
optional Bucket bucket = 5;
@@ -150,11 +172,11 @@
}
message DurationMetric {
- optional string name = 1;
+ optional int64 id = 1;
- optional string what = 2;
+ optional int64 what = 2;
- optional string condition = 3;
+ optional int64 condition = 3;
repeated MetricConditionLink links = 4;
@@ -165,21 +187,21 @@
}
optional AggregationType aggregation_type = 5 [default = SUM];
- repeated KeyMatcher dimension = 6;
+ optional FieldMatcher dimensions = 6;
optional Bucket bucket = 7;
}
message GaugeMetric {
- optional string name = 1;
+ optional int64 id = 1;
- optional string what = 2;
+ optional int64 what = 2;
- optional FieldFilter gauge_fields = 3;
+ optional FieldFilter gauge_fields_filter = 3;
- optional string condition = 4;
+ optional int64 condition = 4;
- repeated KeyMatcher dimension = 5;
+ optional FieldMatcher dimensions = 5;
optional Bucket bucket = 6;
@@ -187,15 +209,15 @@
}
message ValueMetric {
- optional string name = 1;
+ optional int64 id = 1;
- optional string what = 2;
+ optional int64 what = 2;
- optional int32 value_field = 3;
+ optional FieldMatcher value_field = 3;
- optional string condition = 4;
+ optional int64 condition = 4;
- repeated KeyMatcher dimension = 5;
+ optional FieldMatcher dimensions = 5;
optional Bucket bucket = 6;
@@ -206,20 +228,15 @@
}
message Alert {
- optional string name = 1;
+ optional int64 id = 1;
- optional string metric_name = 2;
+ optional int64 metric_id = 2;
- message IncidentdDetails {
- repeated int32 section = 1;
- }
- optional IncidentdDetails incidentd_details = 3;
+ optional int32 num_buckets = 3;
- optional int32 number_of_buckets = 4;
+ optional int32 refractory_period_secs = 4;
- optional int32 refractory_period_secs = 5;
-
- optional int64 trigger_if_sum_gt = 6;
+ optional double trigger_if_sum_gt = 5;
}
message AllowedLogSource {
@@ -227,8 +244,40 @@
repeated string package = 2;
}
+message Alarm {
+ optional int64 id = 1;
+ optional int64 offset_millis = 2;
+ optional int64 period_millis = 3;
+}
+
+message IncidentdDetails {
+ repeated int32 section = 1;
+}
+
+message PerfettoDetails {
+ optional int32 perfetto_stuff = 1;
+}
+
+message Subscription {
+ optional int64 id = 1;
+
+ enum RuleType {
+ RULE_TYPE_UNSPECIFIED = 0;
+ ALARM = 1;
+ ALERT = 2;
+ }
+ optional RuleType rule_type = 2;
+
+ optional int64 rule_id = 3;
+
+ oneof subscriber_information {
+ IncidentdDetails incidentd_details = 4;
+ PerfettoDetails perfetto_details = 5;
+ }
+}
+
message StatsdConfig {
- optional string name = 1;
+ optional int64 id = 1;
repeated EventMetric event_metric = 2;
@@ -246,5 +295,11 @@
repeated Alert alert = 9;
- optional AllowedLogSource log_source = 10;
+ repeated Alarm alarm = 10;
+
+ repeated Subscription subscription = 11;
+
+ optional AllowedLogSource log_source = 12;
+
+ repeated int64 no_report_metric = 13;
}
diff --git a/cmds/statsd/src/storage/StorageManager.cpp b/cmds/statsd/src/storage/StorageManager.cpp
index 9919abf..c542db2 100644
--- a/cmds/statsd/src/storage/StorageManager.cpp
+++ b/cmds/statsd/src/storage/StorageManager.cpp
@@ -124,7 +124,7 @@
}
if (index < 2) continue;
- sendBroadcast(ConfigKey(uid, configName));
+ sendBroadcast(ConfigKey(uid, StrToInt64(configName)));
}
}
@@ -198,6 +198,7 @@
index++;
}
if (index < 2) continue;
+
string file_name = StringPrintf("%s/%s", STATS_SERVICE_DIR, name);
VLOG("full file %s", file_name.c_str());
int fd = open(file_name.c_str(), O_RDONLY | O_CLOEXEC);
@@ -206,7 +207,7 @@
if (android::base::ReadFdToString(fd, &content)) {
StatsdConfig config;
if (config.ParseFromString(content)) {
- configsMap[ConfigKey(uid, configName)] = config;
+ configsMap[ConfigKey(uid, StrToInt64(configName))] = config;
VLOG("map key uid=%d|name=%s", uid, name);
}
}
diff --git a/cmds/statsd/tests/ConfigManager_test.cpp b/cmds/statsd/tests/ConfigManager_test.cpp
index 3d923e2..3eac5d2 100644
--- a/cmds/statsd/tests/ConfigManager_test.cpp
+++ b/cmds/statsd/tests/ConfigManager_test.cpp
@@ -14,6 +14,7 @@
#include "src/config/ConfigManager.h"
#include "src/metrics/MetricsManager.h"
+#include "statsd_test_util.h"
#include <gmock/gmock.h>
#include <gtest/gtest.h>
@@ -31,7 +32,7 @@
namespace statsd {
static ostream& operator<<(ostream& os, const StatsdConfig& config) {
- return os << "StatsdConfig{name=" << config.name().c_str() << "}";
+ return os << "StatsdConfig{id=" << config.id() << "}";
}
} // namespace statsd
@@ -50,19 +51,21 @@
/**
* Validate that the ConfigKey is the one we wanted.
*/
-MATCHER_P2(ConfigKeyEq, uid, name, "") {
- return arg.GetUid() == uid && arg.GetName() == name;
+MATCHER_P2(ConfigKeyEq, uid, id, "") {
+ return arg.GetUid() == uid && (long long)arg.GetId() == (long long)id;
}
/**
* Validate that the StatsdConfig is the one we wanted.
*/
-MATCHER_P(StatsdConfigEq, name, "") {
- return arg.name() == name;
+MATCHER_P(StatsdConfigEq, id, 0) {
+ return (long long)arg.id() == (long long)id;
}
+const int64_t testConfigId = 12345;
+
TEST(ConfigManagerTest, TestFakeConfig) {
- auto metricsManager = std::make_unique<MetricsManager>(ConfigKey(0, "test"),
+ auto metricsManager = std::make_unique<MetricsManager>(ConfigKey(0, testConfigId),
build_fake_config(), 1000, new UidMap());
EXPECT_TRUE(metricsManager->isConfigValid());
}
@@ -77,13 +80,13 @@
manager->AddListener(listener);
StatsdConfig config91;
- config91.set_name("91");
+ config91.set_id(91);
StatsdConfig config92;
- config92.set_name("92");
+ config92.set_id(92);
StatsdConfig config93;
- config93.set_name("93");
+ config93.set_id(93);
StatsdConfig config94;
- config94.set_name("94");
+ config94.set_id(94);
{
InSequence s;
@@ -91,42 +94,46 @@
manager->Startup();
// Add another one
- EXPECT_CALL(*(listener.get()), OnConfigUpdated(ConfigKeyEq(1, "zzz"), StatsdConfigEq("91")))
+ EXPECT_CALL(*(listener.get()), OnConfigUpdated(ConfigKeyEq(1, StringToId("zzz")),
+ StatsdConfigEq(91)))
.RetiresOnSaturation();
- manager->UpdateConfig(ConfigKey(1, "zzz"), config91);
+ manager->UpdateConfig(ConfigKey(1, StringToId("zzz")), config91);
// Update It
- EXPECT_CALL(*(listener.get()), OnConfigUpdated(ConfigKeyEq(1, "zzz"), StatsdConfigEq("92")))
+ EXPECT_CALL(*(listener.get()), OnConfigUpdated(ConfigKeyEq(1, StringToId("zzz")),
+ StatsdConfigEq(92)))
.RetiresOnSaturation();
- manager->UpdateConfig(ConfigKey(1, "zzz"), config92);
+ manager->UpdateConfig(ConfigKey(1, StringToId("zzz")), config92);
// Add one with the same uid but a different name
- EXPECT_CALL(*(listener.get()), OnConfigUpdated(ConfigKeyEq(1, "yyy"), StatsdConfigEq("93")))
+ EXPECT_CALL(*(listener.get()), OnConfigUpdated(ConfigKeyEq(1, StringToId("yyy")),
+ StatsdConfigEq(93)))
.RetiresOnSaturation();
- manager->UpdateConfig(ConfigKey(1, "yyy"), config93);
+ manager->UpdateConfig(ConfigKey(1, StringToId("yyy")), config93);
// Add one with the same name but a different uid
- EXPECT_CALL(*(listener.get()), OnConfigUpdated(ConfigKeyEq(2, "zzz"), StatsdConfigEq("94")))
+ EXPECT_CALL(*(listener.get()), OnConfigUpdated(ConfigKeyEq(2, StringToId("zzz")),
+ StatsdConfigEq(94)))
.RetiresOnSaturation();
- manager->UpdateConfig(ConfigKey(2, "zzz"), config94);
+ manager->UpdateConfig(ConfigKey(2, StringToId("zzz")), config94);
// Remove (1,yyy)
- EXPECT_CALL(*(listener.get()), OnConfigRemoved(ConfigKeyEq(1, "yyy")))
+ EXPECT_CALL(*(listener.get()), OnConfigRemoved(ConfigKeyEq(1, StringToId("yyy"))))
.RetiresOnSaturation();
- manager->RemoveConfig(ConfigKey(1, "yyy"));
+ manager->RemoveConfig(ConfigKey(1, StringToId("yyy")));
// Remove (2,zzz)
- EXPECT_CALL(*(listener.get()), OnConfigRemoved(ConfigKeyEq(2, "zzz")))
+ EXPECT_CALL(*(listener.get()), OnConfigRemoved(ConfigKeyEq(2, StringToId("zzz"))))
.RetiresOnSaturation();
- manager->RemoveConfig(ConfigKey(2, "zzz"));
+ manager->RemoveConfig(ConfigKey(2, StringToId("zzz")));
// Remove (1,zzz)
- EXPECT_CALL(*(listener.get()), OnConfigRemoved(ConfigKeyEq(1, "zzz")))
+ EXPECT_CALL(*(listener.get()), OnConfigRemoved(ConfigKeyEq(1, StringToId("zzz"))))
.RetiresOnSaturation();
- manager->RemoveConfig(ConfigKey(1, "zzz"));
+ manager->RemoveConfig(ConfigKey(1, StringToId("zzz")));
// Remove (2,zzz) again and we shouldn't get the callback
- manager->RemoveConfig(ConfigKey(2, "zzz"));
+ manager->RemoveConfig(ConfigKey(2, StringToId("zzz")));
}
}
@@ -142,16 +149,16 @@
StatsdConfig config;
EXPECT_CALL(*(listener.get()), OnConfigUpdated(_, _)).Times(5);
- EXPECT_CALL(*(listener.get()), OnConfigRemoved(ConfigKeyEq(2, "xxx")));
- EXPECT_CALL(*(listener.get()), OnConfigRemoved(ConfigKeyEq(2, "yyy")));
- EXPECT_CALL(*(listener.get()), OnConfigRemoved(ConfigKeyEq(2, "zzz")));
+ EXPECT_CALL(*(listener.get()), OnConfigRemoved(ConfigKeyEq(2, StringToId("xxx"))));
+ EXPECT_CALL(*(listener.get()), OnConfigRemoved(ConfigKeyEq(2, StringToId("yyy"))));
+ EXPECT_CALL(*(listener.get()), OnConfigRemoved(ConfigKeyEq(2, StringToId("zzz"))));
manager->Startup();
- manager->UpdateConfig(ConfigKey(1, "aaa"), config);
- manager->UpdateConfig(ConfigKey(2, "xxx"), config);
- manager->UpdateConfig(ConfigKey(2, "yyy"), config);
- manager->UpdateConfig(ConfigKey(2, "zzz"), config);
- manager->UpdateConfig(ConfigKey(3, "bbb"), config);
+ manager->UpdateConfig(ConfigKey(1, StringToId("aaa")), config);
+ manager->UpdateConfig(ConfigKey(2, StringToId("xxx")), config);
+ manager->UpdateConfig(ConfigKey(2, StringToId("yyy")), config);
+ manager->UpdateConfig(ConfigKey(2, StringToId("zzz")), config);
+ manager->UpdateConfig(ConfigKey(3, StringToId("bbb")), config);
manager->RemoveConfigs(2);
}
diff --git a/cmds/statsd/tests/LogEntryMatcher_test.cpp b/cmds/statsd/tests/LogEntryMatcher_test.cpp
index 1ec9155..111b4ba 100644
--- a/cmds/statsd/tests/LogEntryMatcher_test.cpp
+++ b/cmds/statsd/tests/LogEntryMatcher_test.cpp
@@ -32,65 +32,313 @@
const int FIELD_ID_2 = 2;
const int FIELD_ID_3 = 2;
+const int ATTRIBUTION_UID_FIELD_ID = 1;
+const int ATTRIBUTION_TAG_FIELD_ID = 2;
+
// Private API from liblog.
extern "C" void android_log_rewind(android_log_context ctx);
#ifdef __ANDROID__
TEST(AtomMatcherTest, TestSimpleMatcher) {
+ UidMap uidMap;
+
// Set up the matcher
AtomMatcher matcher;
auto simpleMatcher = matcher.mutable_simple_atom_matcher();
- simpleMatcher->set_tag(TAG_ID);
+ simpleMatcher->set_atom_id(TAG_ID);
LogEvent event(TAG_ID, 0);
+ EXPECT_TRUE(event.write(11));
event.init();
// Test
- EXPECT_TRUE(matchesSimple(*simpleMatcher, event));
+ EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+
+ // Wrong tag id.
+ simpleMatcher->set_atom_id(TAG_ID + 1);
+ EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
}
-TEST(AtomMatcherTest, TestBoolMatcher) {
- // Set up the matcher
- AtomMatcher matcher;
- auto simpleMatcher = matcher.mutable_simple_atom_matcher();
- simpleMatcher->set_tag(TAG_ID);
- auto keyValue1 = simpleMatcher->add_key_value_matcher();
- keyValue1->mutable_key_matcher()->set_key(FIELD_ID_1);
- auto keyValue2 = simpleMatcher->add_key_value_matcher();
- keyValue2->mutable_key_matcher()->set_key(FIELD_ID_2);
+TEST(AtomMatcherTest, TestAttributionMatcher) {
+ UidMap uidMap;
+ AttributionNode attribution_node1;
+ attribution_node1.set_uid(1111);
+ attribution_node1.set_tag("location1");
+
+ AttributionNode attribution_node2;
+ attribution_node2.set_uid(2222);
+ attribution_node2.set_tag("location2");
+
+ AttributionNode attribution_node3;
+ attribution_node3.set_uid(3333);
+ attribution_node3.set_tag("location3");
+ std::vector<AttributionNode> attribution_nodes =
+ { attribution_node1, attribution_node2, attribution_node3 };
// Set up the event
LogEvent event(TAG_ID, 0);
- event.write(true);
- event.write(false);
+ event.write(attribution_nodes);
+ event.write("some value");
+ // Convert to a LogEvent
+ event.init();
+
+ // Set up the matcher
+ AtomMatcher matcher;
+ auto simpleMatcher = matcher.mutable_simple_atom_matcher();
+ simpleMatcher->set_atom_id(TAG_ID);
+
+ // Match first node.
+ auto attributionMatcher = simpleMatcher->add_field_value_matcher();
+ attributionMatcher->set_field(FIELD_ID_1);
+ attributionMatcher->set_position(Position::FIRST);
+ attributionMatcher->mutable_matches_tuple()->add_field_value_matcher()->set_field(
+ ATTRIBUTION_TAG_FIELD_ID);
+ attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string("tag");
+
+ auto fieldMatcher = simpleMatcher->add_field_value_matcher();
+ fieldMatcher->set_field(FIELD_ID_2);
+ fieldMatcher->set_eq_string("some value");
+
+ // Tag not matched.
+ EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+ attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
+ ->set_eq_string("location3");
+ EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+ attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
+ ->set_eq_string("location1");
+ EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+
+ // Match last node.
+ attributionMatcher->set_position(Position::LAST);
+ EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+ attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
+ ->set_eq_string("location3");
+ EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+
+ // Match any node.
+ attributionMatcher->set_position(Position::ANY);
+ EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+ attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
+ ->set_eq_string("location1");
+ EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+ attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
+ ->set_eq_string("location2");
+ EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+ attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
+ ->set_eq_string("location3");
+ EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+ attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
+ ->set_eq_string("location4");
+ EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+
+ // Attribution match but primitive field not match.
+ attributionMatcher->set_position(Position::ANY);
+ attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
+ ->set_eq_string("location2");
+ fieldMatcher->set_eq_string("wrong value");
+ EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+
+ fieldMatcher->set_eq_string("some value");
+
+ // Uid match.
+ attributionMatcher->set_position(Position::ANY);
+ attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_field(
+ ATTRIBUTION_UID_FIELD_ID);
+ attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string("pkg0");
+ EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+
+ uidMap.updateMap({1111, 1111, 2222, 3333, 3333} /* uid list */,
+ {1, 1, 2, 1, 2} /* version list */,
+ {android::String16("pkg0"), android::String16("pkg1"),
+ android::String16("pkg1"), android::String16("Pkg2"),
+ android::String16("PkG3")} /* package name list */);
+
+ EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+ attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
+ ->set_eq_string("pkg3");
+ EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+ attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
+ ->set_eq_string("pkg2");
+ EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+ attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
+ ->set_eq_string("pkg1");
+ EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+ attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
+ ->set_eq_string("pkg0");
+ EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+
+ attributionMatcher->set_position(Position::FIRST);
+ attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
+ ->set_eq_string("pkg0");
+ EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+ attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
+ ->set_eq_string("pkg3");
+ EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+ attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
+ ->set_eq_string("pkg2");
+ EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+ attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
+ ->set_eq_string("pkg1");
+ EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+
+ attributionMatcher->set_position(Position::LAST);
+ attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
+ ->set_eq_string("pkg0");
+ EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+ attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
+ ->set_eq_string("pkg3");
+ EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+ attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
+ ->set_eq_string("pkg2");
+ EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+ attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
+ ->set_eq_string("pkg1");
+ EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+
+ // Uid + tag.
+ attributionMatcher->set_position(Position::ANY);
+ attributionMatcher->mutable_matches_tuple()->add_field_value_matcher()->set_field(
+ ATTRIBUTION_TAG_FIELD_ID);
+ attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
+ ->set_eq_string("pkg0");
+ attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)
+ ->set_eq_string("location1");
+ EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+ attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
+ ->set_eq_string("pkg1");
+ attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)
+ ->set_eq_string("location1");
+ EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+ attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
+ ->set_eq_string("pkg1");
+ attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)
+ ->set_eq_string("location2");
+ EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+ attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
+ ->set_eq_string("pkg2");
+ attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)
+ ->set_eq_string("location3");
+ EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+ attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
+ ->set_eq_string("pkg3");
+ attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)
+ ->set_eq_string("location3");
+ EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+ attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
+ ->set_eq_string("pkg3");
+ attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)
+ ->set_eq_string("location1");
+ EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+
+ attributionMatcher->set_position(Position::FIRST);
+ attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
+ ->set_eq_string("pkg0");
+ attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)
+ ->set_eq_string("location1");
+ EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+ attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
+ ->set_eq_string("pkg1");
+ attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)
+ ->set_eq_string("location1");
+ EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+ attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
+ ->set_eq_string("pkg1");
+ attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)
+ ->set_eq_string("location2");
+ EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+ attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
+ ->set_eq_string("pkg2");
+ attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)
+ ->set_eq_string("location3");
+ EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+ attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
+ ->set_eq_string("pkg3");
+ attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)
+ ->set_eq_string("location3");
+ EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+ attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
+ ->set_eq_string("pkg3");
+ attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)
+ ->set_eq_string("location1");
+ EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+
+ attributionMatcher->set_position(Position::LAST);
+ attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
+ ->set_eq_string("pkg0");
+ attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)
+ ->set_eq_string("location1");
+ EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+ attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
+ ->set_eq_string("pkg1");
+ attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)
+ ->set_eq_string("location1");
+ EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+ attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
+ ->set_eq_string("pkg1");
+ attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)
+ ->set_eq_string("location2");
+ EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+ attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
+ ->set_eq_string("pkg2");
+ attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)
+ ->set_eq_string("location3");
+ EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+ attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
+ ->set_eq_string("pkg3");
+ attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)
+ ->set_eq_string("location3");
+ EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+ attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)
+ ->set_eq_string("pkg3");
+ attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)
+ ->set_eq_string("location1");
+ EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+}
+
+TEST(AtomMatcherTest, TestBoolMatcher) {
+ UidMap uidMap;
+ // Set up the matcher
+ AtomMatcher matcher;
+ auto simpleMatcher = matcher.mutable_simple_atom_matcher();
+ simpleMatcher->set_atom_id(TAG_ID);
+ auto keyValue1 = simpleMatcher->add_field_value_matcher();
+ keyValue1->set_field(FIELD_ID_1);
+ auto keyValue2 = simpleMatcher->add_field_value_matcher();
+ keyValue2->set_field(FIELD_ID_2);
+
+ // Set up the event
+ LogEvent event(TAG_ID, 0);
+ EXPECT_TRUE(event.write(true));
+ EXPECT_TRUE(event.write(false));
// Convert to a LogEvent
event.init();
// Test
keyValue1->set_eq_bool(true);
keyValue2->set_eq_bool(false);
- EXPECT_TRUE(matchesSimple(*simpleMatcher, event));
+ EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
keyValue1->set_eq_bool(false);
keyValue2->set_eq_bool(false);
- EXPECT_FALSE(matchesSimple(*simpleMatcher, event));
+ EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
- keyValue1->set_eq_bool(true);
- keyValue2->set_eq_bool(false);
- EXPECT_TRUE(matchesSimple(*simpleMatcher, event));
+ keyValue1->set_eq_bool(false);
+ keyValue2->set_eq_bool(true);
+ EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
keyValue1->set_eq_bool(true);
keyValue2->set_eq_bool(true);
- EXPECT_FALSE(matchesSimple(*simpleMatcher, event));
+ EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
}
TEST(AtomMatcherTest, TestStringMatcher) {
+ UidMap uidMap;
// Set up the matcher
AtomMatcher matcher;
auto simpleMatcher = matcher.mutable_simple_atom_matcher();
- simpleMatcher->set_tag(TAG_ID);
- auto keyValue = simpleMatcher->add_key_value_matcher();
- keyValue->mutable_key_matcher()->set_key(FIELD_ID_1);
+ simpleMatcher->set_atom_id(TAG_ID);
+ auto keyValue = simpleMatcher->add_field_value_matcher();
+ keyValue->set_field(FIELD_ID_1);
keyValue->set_eq_string("some value");
// Set up the event
@@ -100,18 +348,19 @@
event.init();
// Test
- EXPECT_TRUE(matchesSimple(*simpleMatcher, event));
+ EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
}
TEST(AtomMatcherTest, TestMultiFieldsMatcher) {
+ UidMap uidMap;
// Set up the matcher
AtomMatcher matcher;
auto simpleMatcher = matcher.mutable_simple_atom_matcher();
- simpleMatcher->set_tag(TAG_ID);
- auto keyValue1 = simpleMatcher->add_key_value_matcher();
- keyValue1->mutable_key_matcher()->set_key(FIELD_ID_1);
- auto keyValue2 = simpleMatcher->add_key_value_matcher();
- keyValue2->mutable_key_matcher()->set_key(FIELD_ID_2);
+ simpleMatcher->set_atom_id(TAG_ID);
+ auto keyValue1 = simpleMatcher->add_field_value_matcher();
+ keyValue1->set_field(FIELD_ID_1);
+ auto keyValue2 = simpleMatcher->add_field_value_matcher();
+ keyValue2->set_field(FIELD_ID_2);
// Set up the event
LogEvent event(TAG_ID, 0);
@@ -124,25 +373,26 @@
// Test
keyValue1->set_eq_int(2);
keyValue2->set_eq_int(3);
- EXPECT_TRUE(matchesSimple(*simpleMatcher, event));
+ EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
keyValue1->set_eq_int(2);
keyValue2->set_eq_int(4);
- EXPECT_FALSE(matchesSimple(*simpleMatcher, event));
+ EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
keyValue1->set_eq_int(4);
keyValue2->set_eq_int(3);
- EXPECT_FALSE(matchesSimple(*simpleMatcher, event));
+ EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
}
TEST(AtomMatcherTest, TestIntComparisonMatcher) {
+ UidMap uidMap;
// Set up the matcher
AtomMatcher matcher;
auto simpleMatcher = matcher.mutable_simple_atom_matcher();
- simpleMatcher->set_tag(TAG_ID);
- auto keyValue = simpleMatcher->add_key_value_matcher();
- keyValue->mutable_key_matcher()->set_key(FIELD_ID_1);
+ simpleMatcher->set_atom_id(TAG_ID);
+ auto keyValue = simpleMatcher->add_field_value_matcher();
+ keyValue->set_field(FIELD_ID_1);
// Set up the event
LogEvent event(TAG_ID, 0);
@@ -153,82 +403,83 @@
// eq_int
keyValue->set_eq_int(10);
- EXPECT_FALSE(matchesSimple(*simpleMatcher, event));
+ EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
keyValue->set_eq_int(11);
- EXPECT_TRUE(matchesSimple(*simpleMatcher, event));
+ EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
keyValue->set_eq_int(12);
- EXPECT_FALSE(matchesSimple(*simpleMatcher, event));
+ EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
// lt_int
keyValue->set_lt_int(10);
- EXPECT_FALSE(matchesSimple(*simpleMatcher, event));
+ EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
keyValue->set_lt_int(11);
- EXPECT_FALSE(matchesSimple(*simpleMatcher, event));
+ EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
keyValue->set_lt_int(12);
- EXPECT_TRUE(matchesSimple(*simpleMatcher, event));
+ EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
// lte_int
keyValue->set_lte_int(10);
- EXPECT_FALSE(matchesSimple(*simpleMatcher, event));
+ EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
keyValue->set_lte_int(11);
- EXPECT_TRUE(matchesSimple(*simpleMatcher, event));
+ EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
keyValue->set_lte_int(12);
- EXPECT_TRUE(matchesSimple(*simpleMatcher, event));
+ EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
// gt_int
keyValue->set_gt_int(10);
- EXPECT_TRUE(matchesSimple(*simpleMatcher, event));
+ EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
keyValue->set_gt_int(11);
- EXPECT_FALSE(matchesSimple(*simpleMatcher, event));
+ EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
keyValue->set_gt_int(12);
- EXPECT_FALSE(matchesSimple(*simpleMatcher, event));
+ EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
// gte_int
keyValue->set_gte_int(10);
- EXPECT_TRUE(matchesSimple(*simpleMatcher, event));
+ EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
keyValue->set_gte_int(11);
- EXPECT_TRUE(matchesSimple(*simpleMatcher, event));
+ EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
keyValue->set_gte_int(12);
- EXPECT_FALSE(matchesSimple(*simpleMatcher, event));
+ EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
}
TEST(AtomMatcherTest, TestFloatComparisonMatcher) {
+ UidMap uidMap;
// Set up the matcher
AtomMatcher matcher;
auto simpleMatcher = matcher.mutable_simple_atom_matcher();
- simpleMatcher->set_tag(TAG_ID);
+ simpleMatcher->set_atom_id(TAG_ID);
- auto keyValue = simpleMatcher->add_key_value_matcher();
- keyValue->mutable_key_matcher()->set_key(FIELD_ID_1);
+ auto keyValue = simpleMatcher->add_field_value_matcher();
+ keyValue->set_field(FIELD_ID_1);
LogEvent event1(TAG_ID, 0);
keyValue->set_lt_float(10.0);
event1.write(10.1f);
event1.init();
- EXPECT_FALSE(matchesSimple(*simpleMatcher, event1));
+ EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event1));
LogEvent event2(TAG_ID, 0);
event2.write(9.9f);
event2.init();
- EXPECT_TRUE(matchesSimple(*simpleMatcher, event2));
+ EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event2));
LogEvent event3(TAG_ID, 0);
event3.write(10.1f);
event3.init();
keyValue->set_gt_float(10.0);
- EXPECT_TRUE(matchesSimple(*simpleMatcher, event3));
+ EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event3));
LogEvent event4(TAG_ID, 0);
event4.write(9.9f);
event4.init();
- EXPECT_FALSE(matchesSimple(*simpleMatcher, event4));
+ EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event4));
}
// Helper for the composite matchers.
void addSimpleMatcher(SimpleAtomMatcher* simpleMatcher, int tag, int key, int val) {
- simpleMatcher->set_tag(tag);
- auto keyValue = simpleMatcher->add_key_value_matcher();
- keyValue->mutable_key_matcher()->set_key(key);
+ simpleMatcher->set_atom_id(tag);
+ auto keyValue = simpleMatcher->add_field_value_matcher();
+ keyValue->set_field(key);
keyValue->set_eq_int(val);
}
diff --git a/cmds/statsd/tests/LogEvent_test.cpp b/cmds/statsd/tests/LogEvent_test.cpp
new file mode 100644
index 0000000..fd28460
--- /dev/null
+++ b/cmds/statsd/tests/LogEvent_test.cpp
@@ -0,0 +1,573 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <gtest/gtest.h>
+#include <log/log_event_list.h>
+#include "src/logd/LogEvent.h"
+
+#ifdef __ANDROID__
+
+namespace android {
+namespace os {
+namespace statsd {
+
+TEST(LogEventTest, testEmptyEvent) {
+ const int32_t TAG_ID = 123;
+ LogEvent event(TAG_ID, 0);
+ event.init();
+
+ DimensionsValue dimensionsValue;
+ EXPECT_FALSE(event.GetSimpleAtomDimensionsValueProto(234, &dimensionsValue));
+ FieldMatcher dimensions;
+ dimensions.set_field(event.GetTagId());
+ EXPECT_FALSE(event.GetAtomDimensionsValueProto(dimensions, &dimensionsValue));
+
+ dimensions.add_child()->set_field(3);
+ dimensions.mutable_child(0)->set_position(Position::FIRST);
+ EXPECT_FALSE(event.GetAtomDimensionsValueProto(dimensions, &dimensionsValue));
+
+ dimensions.mutable_child(0)->set_position(Position::ANY);
+ EXPECT_FALSE(event.GetAtomDimensionsValueProto(dimensions, &dimensionsValue));
+
+ dimensions.mutable_child(0)->set_position(Position::LAST);
+ EXPECT_FALSE(event.GetAtomDimensionsValueProto(dimensions, &dimensionsValue));
+}
+
+TEST(LogEventTest, testRepeatedAttributionNode) {
+ const int32_t TAG_ID = 123;
+ LogEvent event(TAG_ID, 0);
+ AttributionNode attribution_node1;
+ attribution_node1.set_uid(1111);
+ attribution_node1.set_tag("locationService");
+
+ AttributionNode attribution_node2;
+ attribution_node2.set_uid(2222);
+ attribution_node2.set_tag("locationService2");
+
+ AttributionNode attribution_node3;
+ attribution_node3.set_uid(3333);
+ attribution_node3.set_tag("locationService3");
+ std::vector<AttributionNode> attribution_nodes =
+ {attribution_node1, attribution_node2, attribution_node3};
+
+ // 1nd field: int32.
+ EXPECT_TRUE(event.write(int32_t(11)));
+ // 2rd field: float.
+ EXPECT_TRUE(event.write(3.45f));
+ // Here it assume that the atom proto contains a repeated AttributionNode field.
+ // 3rd field: attribution node. This is repeated field.
+ EXPECT_TRUE(event.write(attribution_nodes));
+ // 4th field: bool.
+ EXPECT_TRUE(event.write(true));
+ // 5th field: long.
+ EXPECT_TRUE(event.write(uint64_t(1234)));
+
+ event.init();
+
+ DimensionsValue dimensionsValue;
+ // Query single primitive fields.
+ EXPECT_TRUE(event.GetSimpleAtomDimensionsValueProto(1, &dimensionsValue));
+ EXPECT_EQ(dimensionsValue.field(), event.GetTagId());
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value_size(), 1);
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).field(), 1);
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_int(), 11);
+
+ EXPECT_TRUE(event.GetSimpleAtomDimensionsValueProto(2, &dimensionsValue));
+ EXPECT_EQ(dimensionsValue.field(), event.GetTagId());
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value_size(), 1);
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).field(), 2);
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_float(), 3.45f);
+
+ EXPECT_TRUE(event.GetSimpleAtomDimensionsValueProto(4, &dimensionsValue));
+ EXPECT_EQ(dimensionsValue.field(), event.GetTagId());
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value_size(), 1);
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).field(), 4);
+ // The bool value is stored in value_int field as logD does not support bool.
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_int(), true);
+
+ EXPECT_TRUE(event.GetSimpleAtomDimensionsValueProto(5, &dimensionsValue));
+ EXPECT_EQ(dimensionsValue.field(), event.GetTagId());
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value_size(), 1);
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).field(), 5);
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_long(), long(1234));
+
+ // First attribution.
+ FieldMatcher first_uid_dimensions;
+ first_uid_dimensions.set_field(event.GetTagId());
+ first_uid_dimensions.add_child()->set_field(3);
+ first_uid_dimensions.mutable_child(0)->set_position(Position::FIRST);
+ first_uid_dimensions.mutable_child(0)->add_child()->set_field(1);
+ EXPECT_TRUE(event.GetAtomDimensionsValueProto(first_uid_dimensions, &dimensionsValue));
+ EXPECT_EQ(dimensionsValue.field(), event.GetTagId());
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value_size(), 1);
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).field(), 3);
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple()
+ .dimensions_value_size(), 1);
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple()
+ .dimensions_value(0).field(), 1);
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple()
+ .dimensions_value(0).value_int(), 1111);
+
+ FieldMatcher first_tag_dimensions = first_uid_dimensions;
+ first_tag_dimensions.mutable_child(0)->mutable_child(0)->set_field(2);
+ EXPECT_TRUE(event.GetAtomDimensionsValueProto(first_tag_dimensions, &dimensionsValue));
+ EXPECT_EQ(dimensionsValue.field(), event.GetTagId());
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value_size(), 1);
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).field(), 3);
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple()
+ .dimensions_value_size(), 1);
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple()
+ .dimensions_value(0).field(), 2);
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple()
+ .dimensions_value(0).value_str(), "locationService");
+
+ FieldMatcher first_attribution_dimensions = first_uid_dimensions;
+ first_attribution_dimensions.mutable_child(0)->add_child()->set_field(2);
+ EXPECT_TRUE(event.GetAtomDimensionsValueProto(first_attribution_dimensions, &dimensionsValue));
+ EXPECT_EQ(dimensionsValue.field(), event.GetTagId());
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value_size(), 1);
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).field(), 3);
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple()
+ .dimensions_value_size(), 2);
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple()
+ .dimensions_value(0).field(), 1);
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple()
+ .dimensions_value(0).value_int(), 1111);
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple()
+ .dimensions_value(1).field(), 2);
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple()
+ .dimensions_value(1).value_str(), "locationService");
+
+ FieldMatcher last_attribution_dimensions = first_attribution_dimensions;
+ last_attribution_dimensions.mutable_child(0)->set_position(Position::LAST);
+ EXPECT_TRUE(event.GetAtomDimensionsValueProto(last_attribution_dimensions, &dimensionsValue));
+ EXPECT_EQ(dimensionsValue.field(), event.GetTagId());
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value_size(), 1);
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).field(), 3);
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple()
+ .dimensions_value_size(), 2);
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple()
+ .dimensions_value(0).field(), 1);
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple()
+ .dimensions_value(0).value_int(), 3333);
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple()
+ .dimensions_value(1).field(), 2);
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple()
+ .dimensions_value(1).value_str(), "locationService3");
+
+ FieldMatcher any_attribution_dimensions = first_attribution_dimensions;
+ any_attribution_dimensions.mutable_child(0)->set_position(Position::ANY);
+ std::vector<DimensionsValue> dimensionsValues;
+ event.GetAtomDimensionsValueProtos(any_attribution_dimensions, &dimensionsValues);
+ EXPECT_EQ(dimensionsValues.size(), 3u);
+ EXPECT_EQ(dimensionsValues[0].field(), event.GetTagId());
+ EXPECT_EQ(dimensionsValues[0].value_tuple().dimensions_value_size(), 1);
+ EXPECT_EQ(dimensionsValues[0].value_tuple().dimensions_value(0).field(), 3);
+ EXPECT_EQ(dimensionsValues[0].value_tuple().dimensions_value(0).value_tuple()
+ .dimensions_value_size(), 2);
+ EXPECT_EQ(dimensionsValues[0].value_tuple().dimensions_value(0).value_tuple()
+ .dimensions_value(0).field(), 1);
+ EXPECT_EQ(dimensionsValues[0].value_tuple().dimensions_value(0).value_tuple()
+ .dimensions_value(0).value_int(), 1111);
+ EXPECT_EQ(dimensionsValues[0].value_tuple().dimensions_value(0).value_tuple()
+ .dimensions_value(1).field(), 2);
+ EXPECT_EQ(dimensionsValues[0].value_tuple().dimensions_value(0).value_tuple()
+ .dimensions_value(1).value_str(), "locationService");
+ EXPECT_EQ(dimensionsValues[1].field(), event.GetTagId());
+ EXPECT_EQ(dimensionsValues[1].value_tuple().dimensions_value_size(), 1);
+ EXPECT_EQ(dimensionsValues[1].value_tuple().dimensions_value(0).field(), 3);
+ EXPECT_EQ(dimensionsValues[1].value_tuple().dimensions_value(0).value_tuple()
+ .dimensions_value_size(), 2);
+ EXPECT_EQ(dimensionsValues[1].value_tuple().dimensions_value(0).value_tuple()
+ .dimensions_value(0).field(), 1);
+ EXPECT_EQ(dimensionsValues[1].value_tuple().dimensions_value(0).value_tuple()
+ .dimensions_value(0).value_int(), 2222);
+ EXPECT_EQ(dimensionsValues[1].value_tuple().dimensions_value(0).value_tuple()
+ .dimensions_value(1).field(), 2);
+ EXPECT_EQ(dimensionsValues[1].value_tuple().dimensions_value(0).value_tuple()
+ .dimensions_value(1).value_str(), "locationService2");
+ EXPECT_EQ(dimensionsValues[2].field(), event.GetTagId());
+ EXPECT_EQ(dimensionsValues[2].value_tuple().dimensions_value_size(), 1);
+ EXPECT_EQ(dimensionsValues[2].value_tuple().dimensions_value(0).field(), 3);
+ EXPECT_EQ(dimensionsValues[2].value_tuple().dimensions_value(0).value_tuple()
+ .dimensions_value_size(), 2);
+ EXPECT_EQ(dimensionsValues[2].value_tuple().dimensions_value(0).value_tuple()
+ .dimensions_value(0).field(), 1);
+ EXPECT_EQ(dimensionsValues[2].value_tuple().dimensions_value(0).value_tuple()
+ .dimensions_value(0).value_int(), 3333);
+ EXPECT_EQ(dimensionsValues[2].value_tuple().dimensions_value(0).value_tuple()
+ .dimensions_value(1).field(), 2);
+ EXPECT_EQ(dimensionsValues[2].value_tuple().dimensions_value(0).value_tuple()
+ .dimensions_value(1).value_str(), "locationService3");
+
+ FieldMatcher mixed_dimensions = any_attribution_dimensions;
+ mixed_dimensions.add_child()->set_field(1000);
+ mixed_dimensions.add_child()->set_field(6); // missing field.
+ mixed_dimensions.add_child()->set_field(3); // position not set.
+ mixed_dimensions.add_child()->set_field(5);
+ mixed_dimensions.add_child()->set_field(1);
+ dimensionsValues.clear();
+ event.GetAtomDimensionsValueProtos(mixed_dimensions, &dimensionsValues);
+ EXPECT_EQ(dimensionsValues.size(), 3u);
+ EXPECT_EQ(dimensionsValues[0].field(), event.GetTagId());
+ EXPECT_EQ(dimensionsValues[0].value_tuple().dimensions_value_size(), 3);
+ EXPECT_EQ(dimensionsValues[0].value_tuple().dimensions_value(0).field(), 3);
+ EXPECT_EQ(dimensionsValues[0].value_tuple().dimensions_value(0).value_tuple().dimensions_value_size(), 2);
+ EXPECT_EQ(dimensionsValues[0].value_tuple().dimensions_value(0).value_tuple().dimensions_value(0).field(), 1);
+ EXPECT_EQ(dimensionsValues[0].value_tuple().dimensions_value(0).value_tuple().dimensions_value(0).value_int(),
+ 1111);
+ EXPECT_EQ(dimensionsValues[0].value_tuple().dimensions_value(0).value_tuple().dimensions_value(1).field(), 2);
+ EXPECT_EQ(dimensionsValues[0].value_tuple().dimensions_value(0).value_tuple().dimensions_value(1).value_str(),
+ "locationService");
+ EXPECT_EQ(dimensionsValues[0].value_tuple().dimensions_value(1).field(), 5);
+ EXPECT_EQ(dimensionsValues[0].value_tuple().dimensions_value(1).value_long(), long(1234));
+ EXPECT_EQ(dimensionsValues[0].value_tuple().dimensions_value(2).field(), 1);
+ EXPECT_EQ(dimensionsValues[0].value_tuple().dimensions_value(2).value_int(), 11);
+
+ EXPECT_EQ(dimensionsValues[1].field(), event.GetTagId());
+ EXPECT_EQ(dimensionsValues[1].value_tuple().dimensions_value_size(), 3);
+ EXPECT_EQ(dimensionsValues[1].value_tuple().dimensions_value(0).field(), 3);
+ EXPECT_EQ(dimensionsValues[1].value_tuple().dimensions_value(0).value_tuple().dimensions_value_size(), 2);
+ EXPECT_EQ(dimensionsValues[1].value_tuple().dimensions_value(0).value_tuple().dimensions_value(0).field(), 1);
+ EXPECT_EQ(dimensionsValues[1].value_tuple().dimensions_value(0).value_tuple().dimensions_value(0).value_int(),
+ 2222);
+ EXPECT_EQ(dimensionsValues[1].value_tuple().dimensions_value(0).value_tuple().dimensions_value(1).field(), 2);
+ EXPECT_EQ(dimensionsValues[1].value_tuple().dimensions_value(0).value_tuple().dimensions_value(1).value_str(),
+ "locationService2");
+ EXPECT_EQ(dimensionsValues[1].value_tuple().dimensions_value(1).field(), 5);
+ EXPECT_EQ(dimensionsValues[1].value_tuple().dimensions_value(1).value_long(), long(1234));
+ EXPECT_EQ(dimensionsValues[1].value_tuple().dimensions_value(2).field(), 1);
+ EXPECT_EQ(dimensionsValues[1].value_tuple().dimensions_value(2).value_int(), 11);
+
+ EXPECT_EQ(dimensionsValues[2].field(), event.GetTagId());
+ EXPECT_EQ(dimensionsValues[2].value_tuple().dimensions_value_size(), 3);
+ EXPECT_EQ(dimensionsValues[2].value_tuple().dimensions_value(0).field(), 3);
+ EXPECT_EQ(dimensionsValues[2].value_tuple().dimensions_value(0).value_tuple().dimensions_value_size(), 2);
+ EXPECT_EQ(dimensionsValues[2].value_tuple().dimensions_value(0).value_tuple().dimensions_value(0).field(), 1);
+ EXPECT_EQ(dimensionsValues[2].value_tuple().dimensions_value(0).value_tuple().dimensions_value(0).value_int(),
+ 3333);
+ EXPECT_EQ(dimensionsValues[2].value_tuple().dimensions_value(0).value_tuple().dimensions_value(1).field(), 2);
+ EXPECT_EQ(dimensionsValues[2].value_tuple().dimensions_value(0).value_tuple().dimensions_value(1).value_str(),
+ "locationService3");
+ EXPECT_EQ(dimensionsValues[2].value_tuple().dimensions_value(1).field(), 5);
+ EXPECT_EQ(dimensionsValues[2].value_tuple().dimensions_value(1).value_long(), long(1234));
+ EXPECT_EQ(dimensionsValues[2].value_tuple().dimensions_value(2).field(), 1);
+ EXPECT_EQ(dimensionsValues[2].value_tuple().dimensions_value(2).value_int(), 11);
+
+ FieldMatcher wrong_dimensions = mixed_dimensions;
+ // Wrong tagId.
+ wrong_dimensions.set_field(event.GetTagId() + 100);
+ dimensionsValues.clear();
+ event.GetAtomDimensionsValueProtos(wrong_dimensions, &dimensionsValues);
+ EXPECT_TRUE(dimensionsValues.empty());
+}
+
+TEST(LogEventTest, testMessageField) {
+ const int32_t TAG_ID = 123;
+ LogEvent event(TAG_ID, 0);
+ AttributionNode attribution_node1;
+ attribution_node1.set_uid(1111);
+ attribution_node1.set_tag("locationService");
+
+ AttributionNode attribution_node2;
+ attribution_node2.set_uid(2222);
+ attribution_node2.set_tag("locationService2");
+
+ // 1nd field: int32.
+ EXPECT_TRUE(event.write(int32_t(11)));
+ // 2rd field: float.
+ EXPECT_TRUE(event.write(3.45f));
+ // Here it assume that the atom proto contains two optional AttributionNode fields.
+ // 3rd field: attribution node. This is not repeated field.
+ EXPECT_TRUE(event.write(attribution_node1));
+ // 4th field: another attribution field. This is not repeated field.
+ EXPECT_TRUE(event.write(attribution_node2));
+ // 5th field: bool.
+ EXPECT_TRUE(event.write(true));
+ // 6th field: long.
+ EXPECT_TRUE(event.write(uint64_t(1234)));
+
+ event.init();
+
+ FieldMatcher uid_dimensions1;
+ uid_dimensions1.set_field(event.GetTagId());
+ uid_dimensions1.add_child()->set_field(3);
+ uid_dimensions1.mutable_child(0)->add_child()->set_field(1);
+
+ FieldMatcher tag_dimensions1;
+ tag_dimensions1.set_field(event.GetTagId());
+ tag_dimensions1.add_child()->set_field(3);
+ tag_dimensions1.mutable_child(0)->add_child()->set_field(2);
+
+ FieldMatcher attribution_dimensions1;
+ attribution_dimensions1.set_field(event.GetTagId());
+ attribution_dimensions1.add_child()->set_field(3);
+ attribution_dimensions1.mutable_child(0)->add_child()->set_field(1);
+ attribution_dimensions1.mutable_child(0)->add_child()->set_field(2);
+
+ FieldMatcher uid_dimensions2 = uid_dimensions1;
+ uid_dimensions2.mutable_child(0)->set_field(4);
+
+ FieldMatcher tag_dimensions2 = tag_dimensions1;
+ tag_dimensions2.mutable_child(0)->set_field(4);
+
+ FieldMatcher attribution_dimensions2 = attribution_dimensions1;
+ attribution_dimensions2.mutable_child(0)->set_field(4);
+
+ FieldMatcher mixed_dimensions = attribution_dimensions1;
+ mixed_dimensions.add_child()->set_field(4);
+ mixed_dimensions.mutable_child(1)->add_child()->set_field(1);
+ mixed_dimensions.add_child()->set_field(1000);
+ mixed_dimensions.add_child()->set_field(5);
+ mixed_dimensions.add_child()->set_field(1);
+
+ DimensionsValue dimensionsValue;
+
+ // Query single primitive fields.
+ EXPECT_TRUE(event.GetSimpleAtomDimensionsValueProto(1, &dimensionsValue));
+ EXPECT_EQ(dimensionsValue.field(), event.GetTagId());
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value_size(), 1);
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).field(), 1);
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_int(), 11);
+
+ EXPECT_TRUE(event.GetSimpleAtomDimensionsValueProto(2, &dimensionsValue));
+ EXPECT_EQ(dimensionsValue.field(), event.GetTagId());
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value_size(), 1);
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).field(), 2);
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_float(), 3.45f);
+
+ EXPECT_TRUE(event.GetSimpleAtomDimensionsValueProto(5, &dimensionsValue));
+ EXPECT_EQ(dimensionsValue.field(), event.GetTagId());
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value_size(), 1);
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).field(), 5);
+ // The bool value is stored in value_int field as logD does not support bool.
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_int(), true);
+
+ EXPECT_TRUE(event.GetSimpleAtomDimensionsValueProto(6, &dimensionsValue));
+ EXPECT_EQ(dimensionsValue.field(), event.GetTagId());
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value_size(), 1);
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).field(), 6);
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_long(), long(1234));
+
+ // Query atom field 3: attribution node uid field only.
+ EXPECT_TRUE(event.GetAtomDimensionsValueProto(uid_dimensions1, &dimensionsValue));
+ EXPECT_EQ(dimensionsValue.field(), event.GetTagId());
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value_size(), 1);
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).field(), 3);
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple()
+ .dimensions_value_size(), 1);
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple()
+ .dimensions_value(0).field(), 1);
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple()
+ .dimensions_value(0).value_int(), 1111);
+
+ // Query atom field 3: attribution node tag field only.
+ EXPECT_TRUE(event.GetAtomDimensionsValueProto(tag_dimensions1, &dimensionsValue));
+ EXPECT_EQ(dimensionsValue.field(), event.GetTagId());
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value_size(), 1);
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).field(), 3);
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple()
+ .dimensions_value_size(), 1);
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple()
+ .dimensions_value(0).field(), 2);
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple()
+ .dimensions_value(0).value_str(), "locationService");
+
+ // Query atom field 3: attribution node uid + tag fields.
+ EXPECT_TRUE(event.GetAtomDimensionsValueProto(attribution_dimensions1, &dimensionsValue));
+ EXPECT_EQ(dimensionsValue.field(), event.GetTagId());
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value_size(), 1);
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).field(), 3);
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple()
+ .dimensions_value_size(), 2);
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple()
+ .dimensions_value(0).field(), 1);
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple()
+ .dimensions_value(0).value_int(), 1111);
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple()
+ .dimensions_value(1).field(), 2);
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple()
+ .dimensions_value(1).value_str(), "locationService");
+
+ // Query atom field 4: attribution node uid field only.
+ EXPECT_TRUE(event.GetAtomDimensionsValueProto(uid_dimensions2, &dimensionsValue));
+ EXPECT_EQ(dimensionsValue.field(), event.GetTagId());
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value_size(), 1);
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).field(), 4);
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple()
+ .dimensions_value_size(), 1);
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple()
+ .dimensions_value(0).field(), 1);
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple()
+ .dimensions_value(0).value_int(), 2222);
+
+ // Query atom field 4: attribution node tag field only.
+ EXPECT_TRUE(event.GetAtomDimensionsValueProto(tag_dimensions2, &dimensionsValue));
+ EXPECT_EQ(dimensionsValue.field(), event.GetTagId());
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value_size(), 1);
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).field(), 4);
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple()
+ .dimensions_value_size(), 1);
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple()
+ .dimensions_value(0).field(), 2);
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple()
+ .dimensions_value(0).value_str(), "locationService2");
+
+ // Query atom field 4: attribution node uid + tag fields.
+ EXPECT_TRUE(event.GetAtomDimensionsValueProto(attribution_dimensions2, &dimensionsValue));
+ EXPECT_EQ(dimensionsValue.field(), event.GetTagId());
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value_size(), 1);
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).field(), 4);
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple()
+ .dimensions_value_size(), 2);
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple()
+ .dimensions_value(0).field(), 1);
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple()
+ .dimensions_value(0).value_int(), 2222);
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple()
+ .dimensions_value(1).field(), 2);
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple()
+ .dimensions_value(1).value_str(), "locationService2");
+
+ // Query multiple fields:
+ // 1/ Field 3: attribution uid + tag.
+ // 2/ Field 4: attribution uid only.
+ // 3/ Field not exist.
+ // 4/ Primitive fields #5
+ // 5/ Primitive fields #1
+ EXPECT_TRUE(event.GetAtomDimensionsValueProto(mixed_dimensions, &dimensionsValue));
+ EXPECT_EQ(dimensionsValue.field(), event.GetTagId());
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value_size(), 4);
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).field(), 3);
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple()
+ .dimensions_value_size(), 2);
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple()
+ .dimensions_value(0).field(), 1);
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple()
+ .dimensions_value(0).value_int(), 1111);
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple()
+ .dimensions_value(1).field(), 2);
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_tuple()
+ .dimensions_value(1).value_str(), "locationService");
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(1).field(), 4);
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(1).value_tuple()
+ .dimensions_value_size(), 1);
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(1).value_tuple()
+ .dimensions_value(0).field(), 1);
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(1).value_tuple()
+ .dimensions_value(0).value_int(), 2222);
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(2).field(), 5);
+ // The bool value is stored in value_int field as logD does not support bool.
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(2).value_int(), true);
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(3).field(), 1);
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(3).value_int(), 11);
+}
+
+TEST(LogEventTest, testAllPrimitiveFields) {
+ const int32_t TAG_ID = 123;
+ LogEvent event(TAG_ID, 0);
+
+ // 1nd field: int32.
+ EXPECT_TRUE(event.write(int32_t(11)));
+ // 2rd field: float.
+ EXPECT_TRUE(event.write(3.45f));
+ // 3th field: string.
+ EXPECT_TRUE(event.write("test"));
+ // 4th field: bool.
+ EXPECT_TRUE(event.write(true));
+ // 5th field: bool.
+ EXPECT_TRUE(event.write(false));
+ // 6th field: long.
+ EXPECT_TRUE(event.write(uint64_t(1234)));
+
+ event.init();
+
+ DimensionsValue dimensionsValue;
+ EXPECT_TRUE(event.GetSimpleAtomDimensionsValueProto(1, &dimensionsValue));
+ EXPECT_EQ(dimensionsValue.field(), event.GetTagId());
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value_size(), 1);
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).field(), 1);
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_int(), 11);
+
+ EXPECT_TRUE(event.GetSimpleAtomDimensionsValueProto(2, &dimensionsValue));
+ EXPECT_EQ(dimensionsValue.field(), event.GetTagId());
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value_size(), 1);
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).field(), 2);
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_float(), 3.45f);
+
+ EXPECT_TRUE(event.GetSimpleAtomDimensionsValueProto(3, &dimensionsValue));
+ EXPECT_EQ(dimensionsValue.field(), event.GetTagId());
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value_size(), 1);
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).field(), 3);
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_str(), "test");
+
+ EXPECT_TRUE(event.GetSimpleAtomDimensionsValueProto(4, &dimensionsValue));
+ EXPECT_EQ(dimensionsValue.field(), event.GetTagId());
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value_size(), 1);
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).field(), 4);
+ // The bool value is stored in value_int field as logD does not support bool.
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_int(), true);
+
+ EXPECT_TRUE(event.GetSimpleAtomDimensionsValueProto(5, &dimensionsValue));
+ EXPECT_EQ(dimensionsValue.field(), event.GetTagId());
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value_size(), 1);
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).field(), 5);
+ // The bool value is stored in value_int field as logD does not support bool.
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_int(), false);
+
+ EXPECT_TRUE(event.GetSimpleAtomDimensionsValueProto(6, &dimensionsValue));
+ EXPECT_EQ(dimensionsValue.field(), event.GetTagId());
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value_size(), 1);
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).field(), 6);
+ EXPECT_EQ(dimensionsValue.value_tuple().dimensions_value(0).value_long(), long(1234));
+
+ // Field not exist.
+ EXPECT_FALSE(event.GetSimpleAtomDimensionsValueProto(7, &dimensionsValue));
+}
+
+TEST(LogEventTest, testWriteAtomProtoToStream) {
+ AttributionNode attribution_node1;
+ attribution_node1.set_uid(1111);
+ attribution_node1.set_tag("locationService");
+
+ AttributionNode attribution_node2;
+ attribution_node2.set_uid(2222);
+ attribution_node2.set_tag("locationService2");
+
+ AttributionNode attribution_node3;
+ attribution_node3.set_uid(3333);
+ attribution_node3.set_tag("locationService3");
+ std::vector<AttributionNode> attribution_nodes =
+ {attribution_node1, attribution_node2, attribution_node3};
+
+ LogEvent event(1, 0);
+ EXPECT_TRUE(event.write("222"));
+ EXPECT_TRUE(event.write(attribution_nodes));
+ EXPECT_TRUE(event.write(345));
+ EXPECT_TRUE(event.write(attribution_node3));
+ EXPECT_TRUE(event.write("hello"));
+ event.init();
+
+ util::ProtoOutputStream protoOutput;
+ // For now only see whether it will crash.
+ // TODO(yanglu): test parsing from stream.
+ event.ToProto(protoOutput);
+}
+
+} // namespace statsd
+} // namespace os
+} // namespace android
+#else
+GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
\ No newline at end of file
diff --git a/cmds/statsd/tests/MetricsManager_test.cpp b/cmds/statsd/tests/MetricsManager_test.cpp
index 3c8ccab..cb212a7 100644
--- a/cmds/statsd/tests/MetricsManager_test.cpp
+++ b/cmds/statsd/tests/MetricsManager_test.cpp
@@ -21,6 +21,7 @@
#include "src/metrics/MetricProducer.h"
#include "src/metrics/ValueMetricProducer.h"
#include "src/metrics/metrics_manager_util.h"
+#include "statsd_test_util.h"
#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
@@ -40,53 +41,55 @@
// TODO: ADD MORE TEST CASES.
-const ConfigKey kConfigKey(0, "test");
+const ConfigKey kConfigKey(0, 12345);
const long timeBaseSec = 1000;
StatsdConfig buildGoodConfig() {
StatsdConfig config;
- config.set_name("12345");
+ config.set_id(12345);
AtomMatcher* eventMatcher = config.add_atom_matcher();
- eventMatcher->set_name("SCREEN_IS_ON");
+ eventMatcher->set_id(StringToId("SCREEN_IS_ON"));
SimpleAtomMatcher* simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher();
- simpleAtomMatcher->set_tag(2 /*SCREEN_STATE_CHANGE*/);
- simpleAtomMatcher->add_key_value_matcher()->mutable_key_matcher()->set_key(
+ simpleAtomMatcher->set_atom_id(2 /*SCREEN_STATE_CHANGE*/);
+ simpleAtomMatcher->add_field_value_matcher()->set_field(
1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE*/);
- simpleAtomMatcher->mutable_key_value_matcher(0)->set_eq_int(
+ simpleAtomMatcher->mutable_field_value_matcher(0)->set_eq_int(
2 /*SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_ON*/);
eventMatcher = config.add_atom_matcher();
- eventMatcher->set_name("SCREEN_IS_OFF");
+ eventMatcher->set_id(StringToId("SCREEN_IS_OFF"));
simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher();
- simpleAtomMatcher->set_tag(2 /*SCREEN_STATE_CHANGE*/);
- simpleAtomMatcher->add_key_value_matcher()->mutable_key_matcher()->set_key(
+ simpleAtomMatcher->set_atom_id(2 /*SCREEN_STATE_CHANGE*/);
+ simpleAtomMatcher->add_field_value_matcher()->set_field(
1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE*/);
- simpleAtomMatcher->mutable_key_value_matcher(0)->set_eq_int(
+ simpleAtomMatcher->mutable_field_value_matcher(0)->set_eq_int(
1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_OFF*/);
eventMatcher = config.add_atom_matcher();
- eventMatcher->set_name("SCREEN_ON_OR_OFF");
+ eventMatcher->set_id(StringToId("SCREEN_ON_OR_OFF"));
AtomMatcher_Combination* combination = eventMatcher->mutable_combination();
combination->set_operation(LogicalOperation::OR);
- combination->add_matcher("SCREEN_IS_ON");
- combination->add_matcher("SCREEN_IS_OFF");
+ combination->add_matcher(StringToId("SCREEN_IS_ON"));
+ combination->add_matcher(StringToId("SCREEN_IS_OFF"));
CountMetric* metric = config.add_count_metric();
- metric->set_name("3");
- metric->set_what("SCREEN_IS_ON");
+ metric->set_id(3);
+ metric->set_what(StringToId("SCREEN_IS_ON"));
metric->mutable_bucket()->set_bucket_size_millis(30 * 1000L);
- KeyMatcher* keyMatcher = metric->add_dimension();
- keyMatcher->set_key(1);
+ metric->mutable_dimensions()->set_field(2 /*SCREEN_STATE_CHANGE*/);
+ metric->mutable_dimensions()->add_child()->set_field(1);
+
+ config.add_no_report_metric(3);
auto alert = config.add_alert();
- alert->set_name("3");
- alert->set_metric_name("3");
- alert->set_number_of_buckets(10);
+ alert->set_id(3);
+ alert->set_metric_id(3);
+ alert->set_num_buckets(10);
alert->set_refractory_period_secs(100);
alert->set_trigger_if_sum_gt(100);
return config;
@@ -94,48 +97,48 @@
StatsdConfig buildCircleMatchers() {
StatsdConfig config;
- config.set_name("12345");
+ config.set_id(12345);
AtomMatcher* eventMatcher = config.add_atom_matcher();
- eventMatcher->set_name("SCREEN_IS_ON");
+ eventMatcher->set_id(StringToId("SCREEN_IS_ON"));
SimpleAtomMatcher* simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher();
- simpleAtomMatcher->set_tag(2 /*SCREEN_STATE_CHANGE*/);
- simpleAtomMatcher->add_key_value_matcher()->mutable_key_matcher()->set_key(
+ simpleAtomMatcher->set_atom_id(2 /*SCREEN_STATE_CHANGE*/);
+ simpleAtomMatcher->add_field_value_matcher()->set_field(
1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE*/);
- simpleAtomMatcher->mutable_key_value_matcher(0)->set_eq_int(
+ simpleAtomMatcher->mutable_field_value_matcher(0)->set_eq_int(
2 /*SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_ON*/);
eventMatcher = config.add_atom_matcher();
- eventMatcher->set_name("SCREEN_ON_OR_OFF");
+ eventMatcher->set_id(StringToId("SCREEN_ON_OR_OFF"));
AtomMatcher_Combination* combination = eventMatcher->mutable_combination();
combination->set_operation(LogicalOperation::OR);
- combination->add_matcher("SCREEN_IS_ON");
+ combination->add_matcher(StringToId("SCREEN_IS_ON"));
// Circle dependency
- combination->add_matcher("SCREEN_ON_OR_OFF");
+ combination->add_matcher(StringToId("SCREEN_ON_OR_OFF"));
return config;
}
StatsdConfig buildAlertWithUnknownMetric() {
StatsdConfig config;
- config.set_name("12345");
+ config.set_id(12345);
AtomMatcher* eventMatcher = config.add_atom_matcher();
- eventMatcher->set_name("SCREEN_IS_ON");
+ eventMatcher->set_id(StringToId("SCREEN_IS_ON"));
CountMetric* metric = config.add_count_metric();
- metric->set_name("3");
- metric->set_what("SCREEN_IS_ON");
+ metric->set_id(3);
+ metric->set_what(StringToId("SCREEN_IS_ON"));
metric->mutable_bucket()->set_bucket_size_millis(30 * 1000L);
- KeyMatcher* keyMatcher = metric->add_dimension();
- keyMatcher->set_key(1);
+ metric->mutable_dimensions()->set_field(2 /*SCREEN_STATE_CHANGE*/);
+ metric->mutable_dimensions()->add_child()->set_field(1);
auto alert = config.add_alert();
- alert->set_name("3");
- alert->set_metric_name("2");
- alert->set_number_of_buckets(10);
+ alert->set_id(3);
+ alert->set_metric_id(2);
+ alert->set_num_buckets(10);
alert->set_refractory_period_secs(100);
alert->set_trigger_if_sum_gt(100);
return config;
@@ -143,83 +146,83 @@
StatsdConfig buildMissingMatchers() {
StatsdConfig config;
- config.set_name("12345");
+ config.set_id(12345);
AtomMatcher* eventMatcher = config.add_atom_matcher();
- eventMatcher->set_name("SCREEN_IS_ON");
+ eventMatcher->set_id(StringToId("SCREEN_IS_ON"));
SimpleAtomMatcher* simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher();
- simpleAtomMatcher->set_tag(2 /*SCREEN_STATE_CHANGE*/);
- simpleAtomMatcher->add_key_value_matcher()->mutable_key_matcher()->set_key(
+ simpleAtomMatcher->set_atom_id(2 /*SCREEN_STATE_CHANGE*/);
+ simpleAtomMatcher->add_field_value_matcher()->set_field(
1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE*/);
- simpleAtomMatcher->mutable_key_value_matcher(0)->set_eq_int(
+ simpleAtomMatcher->mutable_field_value_matcher(0)->set_eq_int(
2 /*SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_ON*/);
eventMatcher = config.add_atom_matcher();
- eventMatcher->set_name("SCREEN_ON_OR_OFF");
+ eventMatcher->set_id(StringToId("SCREEN_ON_OR_OFF"));
AtomMatcher_Combination* combination = eventMatcher->mutable_combination();
combination->set_operation(LogicalOperation::OR);
- combination->add_matcher("SCREEN_IS_ON");
+ combination->add_matcher(StringToId("SCREEN_IS_ON"));
// undefined matcher
- combination->add_matcher("ABC");
+ combination->add_matcher(StringToId("ABC"));
return config;
}
StatsdConfig buildMissingPredicate() {
StatsdConfig config;
- config.set_name("12345");
+ config.set_id(12345);
CountMetric* metric = config.add_count_metric();
- metric->set_name("3");
- metric->set_what("SCREEN_EVENT");
+ metric->set_id(3);
+ metric->set_what(StringToId("SCREEN_EVENT"));
metric->mutable_bucket()->set_bucket_size_millis(30 * 1000L);
- metric->set_condition("SOME_CONDITION");
+ metric->set_condition(StringToId("SOME_CONDITION"));
AtomMatcher* eventMatcher = config.add_atom_matcher();
- eventMatcher->set_name("SCREEN_EVENT");
+ eventMatcher->set_id(StringToId("SCREEN_EVENT"));
SimpleAtomMatcher* simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher();
- simpleAtomMatcher->set_tag(2);
+ simpleAtomMatcher->set_atom_id(2);
return config;
}
StatsdConfig buildDimensionMetricsWithMultiTags() {
StatsdConfig config;
- config.set_name("12345");
+ config.set_id(12345);
AtomMatcher* eventMatcher = config.add_atom_matcher();
- eventMatcher->set_name("BATTERY_VERY_LOW");
+ eventMatcher->set_id(StringToId("BATTERY_VERY_LOW"));
SimpleAtomMatcher* simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher();
- simpleAtomMatcher->set_tag(2);
+ simpleAtomMatcher->set_atom_id(2);
eventMatcher = config.add_atom_matcher();
- eventMatcher->set_name("BATTERY_VERY_VERY_LOW");
+ eventMatcher->set_id(StringToId("BATTERY_VERY_VERY_LOW"));
simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher();
- simpleAtomMatcher->set_tag(3);
+ simpleAtomMatcher->set_atom_id(3);
eventMatcher = config.add_atom_matcher();
- eventMatcher->set_name("BATTERY_LOW");
+ eventMatcher->set_id(StringToId("BATTERY_LOW"));
AtomMatcher_Combination* combination = eventMatcher->mutable_combination();
combination->set_operation(LogicalOperation::OR);
- combination->add_matcher("BATTERY_VERY_LOW");
- combination->add_matcher("BATTERY_VERY_VERY_LOW");
+ combination->add_matcher(StringToId("BATTERY_VERY_LOW"));
+ combination->add_matcher(StringToId("BATTERY_VERY_VERY_LOW"));
// Count process state changes, slice by uid, while SCREEN_IS_OFF
CountMetric* metric = config.add_count_metric();
- metric->set_name("3");
- metric->set_what("BATTERY_LOW");
+ metric->set_id(3);
+ metric->set_what(StringToId("BATTERY_LOW"));
metric->mutable_bucket()->set_bucket_size_millis(30 * 1000L);
- KeyMatcher* keyMatcher = metric->add_dimension();
- keyMatcher->set_key(1);
+ // This case is interesting. We want to dimension across two atoms.
+ metric->mutable_dimensions()->add_child()->set_field(1);
auto alert = config.add_alert();
- alert->set_name("3");
- alert->set_metric_name("3");
- alert->set_number_of_buckets(10);
+ alert->set_id(103);
+ alert->set_metric_id(3);
+ alert->set_num_buckets(10);
alert->set_refractory_period_secs(100);
alert->set_trigger_if_sum_gt(100);
return config;
@@ -227,46 +230,47 @@
StatsdConfig buildCirclePredicates() {
StatsdConfig config;
- config.set_name("12345");
+ config.set_id(12345);
AtomMatcher* eventMatcher = config.add_atom_matcher();
- eventMatcher->set_name("SCREEN_IS_ON");
+ eventMatcher->set_id(StringToId("SCREEN_IS_ON"));
SimpleAtomMatcher* simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher();
- simpleAtomMatcher->set_tag(2 /*SCREEN_STATE_CHANGE*/);
- simpleAtomMatcher->add_key_value_matcher()->mutable_key_matcher()->set_key(
+ simpleAtomMatcher->set_atom_id(2 /*SCREEN_STATE_CHANGE*/);
+ simpleAtomMatcher->add_field_value_matcher()->set_field(
1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE*/);
- simpleAtomMatcher->mutable_key_value_matcher(0)->set_eq_int(
+ simpleAtomMatcher->mutable_field_value_matcher(0)->set_eq_int(
2 /*SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_ON*/);
eventMatcher = config.add_atom_matcher();
- eventMatcher->set_name("SCREEN_IS_OFF");
+ eventMatcher->set_id(StringToId("SCREEN_IS_OFF"));
simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher();
- simpleAtomMatcher->set_tag(2 /*SCREEN_STATE_CHANGE*/);
- simpleAtomMatcher->add_key_value_matcher()->mutable_key_matcher()->set_key(
+ simpleAtomMatcher->set_atom_id(2 /*SCREEN_STATE_CHANGE*/);
+ simpleAtomMatcher->add_field_value_matcher()->set_field(
1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE*/);
- simpleAtomMatcher->mutable_key_value_matcher(0)->set_eq_int(
+ simpleAtomMatcher->mutable_field_value_matcher(0)->set_eq_int(
1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_OFF*/);
auto condition = config.add_predicate();
- condition->set_name("SCREEN_IS_ON");
+ condition->set_id(StringToId("SCREEN_IS_ON"));
SimplePredicate* simplePredicate = condition->mutable_simple_predicate();
- simplePredicate->set_start("SCREEN_IS_ON");
- simplePredicate->set_stop("SCREEN_IS_OFF");
+ simplePredicate->set_start(StringToId("SCREEN_IS_ON"));
+ simplePredicate->set_stop(StringToId("SCREEN_IS_OFF"));
condition = config.add_predicate();
- condition->set_name("SCREEN_IS_EITHER_ON_OFF");
+ condition->set_id(StringToId("SCREEN_IS_EITHER_ON_OFF"));
Predicate_Combination* combination = condition->mutable_combination();
combination->set_operation(LogicalOperation::OR);
- combination->add_predicate("SCREEN_IS_ON");
- combination->add_predicate("SCREEN_IS_EITHER_ON_OFF");
+ combination->add_predicate(StringToId("SCREEN_IS_ON"));
+ combination->add_predicate(StringToId("SCREEN_IS_EITHER_ON_OFF"));
return config;
}
TEST(MetricsManagerTest, TestGoodConfig) {
+ UidMap uidMap;
StatsdConfig config = buildGoodConfig();
set<int> allTagIds;
vector<sp<LogMatchingTracker>> allAtomMatchers;
@@ -276,15 +280,19 @@
unordered_map<int, std::vector<int>> conditionToMetricMap;
unordered_map<int, std::vector<int>> trackerToMetricMap;
unordered_map<int, std::vector<int>> trackerToConditionMap;
+ std::set<int64_t> noReportMetricIds;
- EXPECT_TRUE(initStatsdConfig(kConfigKey, config, timeBaseSec, allTagIds, allAtomMatchers,
+ EXPECT_TRUE(initStatsdConfig(kConfigKey, config, uidMap, timeBaseSec, allTagIds, allAtomMatchers,
allConditionTrackers, allMetricProducers, allAnomalyTrackers,
- conditionToMetricMap, trackerToMetricMap, trackerToConditionMap));
+ conditionToMetricMap, trackerToMetricMap, trackerToConditionMap,
+ noReportMetricIds));
EXPECT_EQ(1u, allMetricProducers.size());
EXPECT_EQ(1u, allAnomalyTrackers.size());
+ EXPECT_EQ(1u, noReportMetricIds.size());
}
TEST(MetricsManagerTest, TestDimensionMetricsWithMultiTags) {
+ UidMap uidMap;
StatsdConfig config = buildDimensionMetricsWithMultiTags();
set<int> allTagIds;
vector<sp<LogMatchingTracker>> allAtomMatchers;
@@ -294,13 +302,16 @@
unordered_map<int, std::vector<int>> conditionToMetricMap;
unordered_map<int, std::vector<int>> trackerToMetricMap;
unordered_map<int, std::vector<int>> trackerToConditionMap;
+ std::set<int64_t> noReportMetricIds;
- EXPECT_FALSE(initStatsdConfig(kConfigKey, config, timeBaseSec, allTagIds, allAtomMatchers,
+ EXPECT_FALSE(initStatsdConfig(kConfigKey, config, uidMap, timeBaseSec, allTagIds, allAtomMatchers,
allConditionTrackers, allMetricProducers, allAnomalyTrackers,
- conditionToMetricMap, trackerToMetricMap, trackerToConditionMap));
+ conditionToMetricMap, trackerToMetricMap, trackerToConditionMap,
+ noReportMetricIds));
}
TEST(MetricsManagerTest, TestCircleLogMatcherDependency) {
+ UidMap uidMap;
StatsdConfig config = buildCircleMatchers();
set<int> allTagIds;
vector<sp<LogMatchingTracker>> allAtomMatchers;
@@ -310,13 +321,16 @@
unordered_map<int, std::vector<int>> conditionToMetricMap;
unordered_map<int, std::vector<int>> trackerToMetricMap;
unordered_map<int, std::vector<int>> trackerToConditionMap;
+ std::set<int64_t> noReportMetricIds;
- EXPECT_FALSE(initStatsdConfig(kConfigKey, config, timeBaseSec, allTagIds, allAtomMatchers,
+ EXPECT_FALSE(initStatsdConfig(kConfigKey, config, uidMap, timeBaseSec, allTagIds, allAtomMatchers,
allConditionTrackers, allMetricProducers, allAnomalyTrackers,
- conditionToMetricMap, trackerToMetricMap, trackerToConditionMap));
+ conditionToMetricMap, trackerToMetricMap, trackerToConditionMap,
+ noReportMetricIds));
}
TEST(MetricsManagerTest, TestMissingMatchers) {
+ UidMap uidMap;
StatsdConfig config = buildMissingMatchers();
set<int> allTagIds;
vector<sp<LogMatchingTracker>> allAtomMatchers;
@@ -326,12 +340,15 @@
unordered_map<int, std::vector<int>> conditionToMetricMap;
unordered_map<int, std::vector<int>> trackerToMetricMap;
unordered_map<int, std::vector<int>> trackerToConditionMap;
- EXPECT_FALSE(initStatsdConfig(kConfigKey, config, timeBaseSec, allTagIds, allAtomMatchers,
+ std::set<int64_t> noReportMetricIds;
+ EXPECT_FALSE(initStatsdConfig(kConfigKey, config, uidMap, timeBaseSec, allTagIds, allAtomMatchers,
allConditionTrackers, allMetricProducers, allAnomalyTrackers,
- conditionToMetricMap, trackerToMetricMap, trackerToConditionMap));
+ conditionToMetricMap, trackerToMetricMap, trackerToConditionMap,
+ noReportMetricIds));
}
TEST(MetricsManagerTest, TestMissingPredicate) {
+ UidMap uidMap;
StatsdConfig config = buildMissingPredicate();
set<int> allTagIds;
vector<sp<LogMatchingTracker>> allAtomMatchers;
@@ -341,12 +358,15 @@
unordered_map<int, std::vector<int>> conditionToMetricMap;
unordered_map<int, std::vector<int>> trackerToMetricMap;
unordered_map<int, std::vector<int>> trackerToConditionMap;
- EXPECT_FALSE(initStatsdConfig(kConfigKey, config, timeBaseSec, allTagIds, allAtomMatchers,
+ std::set<int64_t> noReportMetricIds;
+ EXPECT_FALSE(initStatsdConfig(kConfigKey, config, uidMap, timeBaseSec, allTagIds, allAtomMatchers,
allConditionTrackers, allMetricProducers, allAnomalyTrackers,
- conditionToMetricMap, trackerToMetricMap, trackerToConditionMap));
+ conditionToMetricMap, trackerToMetricMap, trackerToConditionMap,
+ noReportMetricIds));
}
TEST(MetricsManagerTest, TestCirclePredicateDependency) {
+ UidMap uidMap;
StatsdConfig config = buildCirclePredicates();
set<int> allTagIds;
vector<sp<LogMatchingTracker>> allAtomMatchers;
@@ -356,13 +376,16 @@
unordered_map<int, std::vector<int>> conditionToMetricMap;
unordered_map<int, std::vector<int>> trackerToMetricMap;
unordered_map<int, std::vector<int>> trackerToConditionMap;
+ std::set<int64_t> noReportMetricIds;
- EXPECT_FALSE(initStatsdConfig(kConfigKey, config, timeBaseSec, allTagIds, allAtomMatchers,
+ EXPECT_FALSE(initStatsdConfig(kConfigKey, config, uidMap, timeBaseSec, allTagIds, allAtomMatchers,
allConditionTrackers, allMetricProducers, allAnomalyTrackers,
- conditionToMetricMap, trackerToMetricMap, trackerToConditionMap));
+ conditionToMetricMap, trackerToMetricMap, trackerToConditionMap,
+ noReportMetricIds));
}
TEST(MetricsManagerTest, testAlertWithUnknownMetric) {
+ UidMap uidMap;
StatsdConfig config = buildAlertWithUnknownMetric();
set<int> allTagIds;
vector<sp<LogMatchingTracker>> allAtomMatchers;
@@ -372,10 +395,12 @@
unordered_map<int, std::vector<int>> conditionToMetricMap;
unordered_map<int, std::vector<int>> trackerToMetricMap;
unordered_map<int, std::vector<int>> trackerToConditionMap;
+ std::set<int64_t> noReportMetricIds;
- EXPECT_FALSE(initStatsdConfig(kConfigKey, config, timeBaseSec, allTagIds, allAtomMatchers,
+ EXPECT_FALSE(initStatsdConfig(kConfigKey, config, uidMap, timeBaseSec, allTagIds, allAtomMatchers,
allConditionTrackers, allMetricProducers, allAnomalyTrackers,
- conditionToMetricMap, trackerToMetricMap, trackerToConditionMap));
+ conditionToMetricMap, trackerToMetricMap, trackerToConditionMap,
+ noReportMetricIds));
}
#else
diff --git a/cmds/statsd/tests/StatsLogProcessor_test.cpp b/cmds/statsd/tests/StatsLogProcessor_test.cpp
index 9b96bb7..5d053e2 100644
--- a/cmds/statsd/tests/StatsLogProcessor_test.cpp
+++ b/cmds/statsd/tests/StatsLogProcessor_test.cpp
@@ -41,7 +41,7 @@
*/
class MockMetricsManager : public MetricsManager {
public:
- MockMetricsManager() : MetricsManager(ConfigKey(1, "key"), StatsdConfig(), 1000, new UidMap()) {
+ MockMetricsManager() : MetricsManager(ConfigKey(1, 12345), StatsdConfig(), 1000, new UidMap()) {
}
MOCK_METHOD0(byteSize, size_t());
@@ -52,11 +52,11 @@
sp<UidMap> m = new UidMap();
sp<AnomalyMonitor> anomalyMonitor;
// Construct the processor with a dummy sendBroadcast function that does nothing.
- StatsLogProcessor p(m, anomalyMonitor, [](const ConfigKey& key) {});
+ StatsLogProcessor p(m, anomalyMonitor, 0, [](const ConfigKey& key) {});
MockMetricsManager mockMetricsManager;
- ConfigKey key(100, "key");
+ ConfigKey key(100, 12345);
// Expect only the first flush to trigger a check for byte size since the last two are
// rate-limited.
EXPECT_CALL(mockMetricsManager, byteSize()).Times(1);
@@ -69,12 +69,12 @@
sp<UidMap> m = new UidMap();
sp<AnomalyMonitor> anomalyMonitor;
int broadcastCount = 0;
- StatsLogProcessor p(m, anomalyMonitor,
+ StatsLogProcessor p(m, anomalyMonitor, 0,
[&broadcastCount](const ConfigKey& key) { broadcastCount++; });
MockMetricsManager mockMetricsManager;
- ConfigKey key(100, "key");
+ ConfigKey key(100, 12345);
EXPECT_CALL(mockMetricsManager, byteSize())
.Times(2)
.WillRepeatedly(Return(int(StatsdStats::kMaxMetricsBytesPerConfig * .95)));
@@ -93,12 +93,12 @@
sp<UidMap> m = new UidMap();
sp<AnomalyMonitor> anomalyMonitor;
int broadcastCount = 0;
- StatsLogProcessor p(m, anomalyMonitor,
+ StatsLogProcessor p(m, anomalyMonitor, 0,
[&broadcastCount](const ConfigKey& key) { broadcastCount++; });
MockMetricsManager mockMetricsManager;
- ConfigKey key(100, "key");
+ ConfigKey key(100, 12345);
EXPECT_CALL(mockMetricsManager, byteSize())
.Times(1)
.WillRepeatedly(Return(int(StatsdStats::kMaxMetricsBytesPerConfig * 1.2)));
diff --git a/cmds/statsd/tests/UidMap_test.cpp b/cmds/statsd/tests/UidMap_test.cpp
index 3fa96d3..945af27 100644
--- a/cmds/statsd/tests/UidMap_test.cpp
+++ b/cmds/statsd/tests/UidMap_test.cpp
@@ -18,6 +18,7 @@
#include "guardrail/StatsdStats.h"
#include "logd/LogEvent.h"
#include "statslog.h"
+#include "statsd_test_util.h"
#include <gtest/gtest.h>
@@ -37,7 +38,7 @@
sp<UidMap> m = new UidMap();
sp<AnomalyMonitor> anomalyMonitor;
// Construct the processor with a dummy sendBroadcast function that does nothing.
- StatsLogProcessor p(m, anomalyMonitor, [](const ConfigKey& key) {});
+ StatsLogProcessor p(m, anomalyMonitor, 0, [](const ConfigKey& key) {});
LogEvent addEvent(android::util::ISOLATED_UID_CHANGED, 1);
addEvent.write(100); // parent UID
addEvent.write(101); // isolated UID
@@ -156,8 +157,8 @@
TEST(UidMapTest, TestClearingOutput) {
UidMap m;
- ConfigKey config1(1, "config1");
- ConfigKey config2(1, "config2");
+ ConfigKey config1(1, StringToId("config1"));
+ ConfigKey config2(1, StringToId("config2"));
m.OnConfigUpdated(config1);
@@ -211,7 +212,7 @@
TEST(UidMapTest, TestMemoryComputed) {
UidMap m;
- ConfigKey config1(1, "config1");
+ ConfigKey config1(1, StringToId("config1"));
m.OnConfigUpdated(config1);
size_t startBytes = m.mBytesUsed;
@@ -241,7 +242,7 @@
UidMap m;
string buf;
- ConfigKey config1(1, "config1");
+ ConfigKey config1(1, StringToId("config1"));
m.OnConfigUpdated(config1);
size_t startBytes = m.mBytesUsed;
@@ -273,4 +274,4 @@
} // namespace statsd
} // namespace os
-} // namespace android
+} // namespace android
\ No newline at end of file
diff --git a/cmds/statsd/tests/anomaly/AnomalyTracker_test.cpp b/cmds/statsd/tests/anomaly/AnomalyTracker_test.cpp
index f62171d..5842bc8 100644
--- a/cmds/statsd/tests/anomaly/AnomalyTracker_test.cpp
+++ b/cmds/statsd/tests/anomaly/AnomalyTracker_test.cpp
@@ -31,17 +31,13 @@
namespace os {
namespace statsd {
-const ConfigKey kConfigKey(0, "test");
+const ConfigKey kConfigKey(0, 12345);
HashableDimensionKey getMockDimensionKey(int key, string value) {
- KeyValuePair pair;
- pair.set_key(key);
- pair.set_value_str(value);
-
- vector<KeyValuePair> pairs;
- pairs.push_back(pair);
-
- return HashableDimensionKey(pairs);
+ DimensionsValue dimensionsValue;
+ dimensionsValue.set_field(key);
+ dimensionsValue.set_value_str(value);
+ return HashableDimensionKey(dimensionsValue);
}
void AddValueToBucket(const std::vector<std::pair<HashableDimensionKey, long>>& key_value_pair_list,
@@ -61,7 +57,7 @@
TEST(AnomalyTrackerTest, TestConsecutiveBuckets) {
const int64_t bucketSizeNs = 30 * NS_PER_SEC;
Alert alert;
- alert.set_number_of_buckets(3);
+ alert.set_num_buckets(3);
alert.set_refractory_period_secs(2 * bucketSizeNs / NS_PER_SEC);
alert.set_trigger_if_sum_gt(2);
@@ -89,7 +85,7 @@
EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, -1LL);
EXPECT_FALSE(anomalyTracker.detectAnomaly(0, *bucket0));
anomalyTracker.detectAndDeclareAnomaly(eventTimestamp0, 0, *bucket0);
- EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, -1L);
+ EXPECT_EQ(anomalyTracker.mLastAnomalyTimestampNs, -1L);
// Adds past bucket #0
anomalyTracker.addPastBucket(bucket0, 0);
@@ -100,7 +96,7 @@
EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 0LL);
EXPECT_FALSE(anomalyTracker.detectAnomaly(1, *bucket1));
anomalyTracker.detectAndDeclareAnomaly(eventTimestamp1, 1, *bucket1);
- EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, -1L);
+ EXPECT_EQ(anomalyTracker.mLastAnomalyTimestampNs, -1L);
// Adds past bucket #0 again. The sum does not change.
anomalyTracker.addPastBucket(bucket0, 0);
@@ -111,7 +107,7 @@
EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 0LL);
EXPECT_FALSE(anomalyTracker.detectAnomaly(1, *bucket1));
anomalyTracker.detectAndDeclareAnomaly(eventTimestamp1 + 1, 1, *bucket1);
- EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, -1L);
+ EXPECT_EQ(anomalyTracker.mLastAnomalyTimestampNs, -1L);
// Adds past bucket #1.
anomalyTracker.addPastBucket(bucket1, 1);
@@ -122,7 +118,7 @@
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyC), 1LL);
EXPECT_TRUE(anomalyTracker.detectAnomaly(2, *bucket2));
anomalyTracker.detectAndDeclareAnomaly(eventTimestamp2, 2, *bucket2);
- EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp2);
+ EXPECT_EQ(anomalyTracker.mLastAnomalyTimestampNs, eventTimestamp2);
// Adds past bucket #1 again. Nothing changes.
anomalyTracker.addPastBucket(bucket1, 1);
@@ -133,7 +129,7 @@
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyC), 1LL);
EXPECT_TRUE(anomalyTracker.detectAnomaly(2, *bucket2));
anomalyTracker.detectAndDeclareAnomaly(eventTimestamp2 + 1, 2, *bucket2);
- EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp2);
+ EXPECT_EQ(anomalyTracker.mLastAnomalyTimestampNs, eventTimestamp2);
// Adds past bucket #2.
anomalyTracker.addPastBucket(bucket2, 2);
@@ -144,7 +140,7 @@
EXPECT_TRUE(anomalyTracker.detectAnomaly(3, *bucket3));
anomalyTracker.detectAndDeclareAnomaly(eventTimestamp3, 3, *bucket3);
// Within refractory period.
- EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp2);
+ EXPECT_EQ(anomalyTracker.mLastAnomalyTimestampNs, eventTimestamp2);
// Adds bucket #3.
anomalyTracker.addPastBucket(bucket3, 3L);
@@ -154,7 +150,7 @@
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 1LL);
EXPECT_FALSE(anomalyTracker.detectAnomaly(4, *bucket4));
anomalyTracker.detectAndDeclareAnomaly(eventTimestamp4, 4, *bucket4);
- EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp2);
+ EXPECT_EQ(anomalyTracker.mLastAnomalyTimestampNs, eventTimestamp2);
// Adds bucket #4.
anomalyTracker.addPastBucket(bucket4, 4);
@@ -164,7 +160,7 @@
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 1LL);
EXPECT_TRUE(anomalyTracker.detectAnomaly(5, *bucket5));
anomalyTracker.detectAndDeclareAnomaly(eventTimestamp5, 5, *bucket5);
- EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp5);
+ EXPECT_EQ(anomalyTracker.mLastAnomalyTimestampNs, eventTimestamp5);
// Adds bucket #5.
anomalyTracker.addPastBucket(bucket5, 5);
@@ -175,13 +171,13 @@
EXPECT_TRUE(anomalyTracker.detectAnomaly(6, *bucket6));
// Within refractory period.
anomalyTracker.detectAndDeclareAnomaly(eventTimestamp6, 6, *bucket6);
- EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp5);
+ EXPECT_EQ(anomalyTracker.mLastAnomalyTimestampNs, eventTimestamp5);
}
TEST(AnomalyTrackerTest, TestSparseBuckets) {
const int64_t bucketSizeNs = 30 * NS_PER_SEC;
Alert alert;
- alert.set_number_of_buckets(3);
+ alert.set_num_buckets(3);
alert.set_refractory_period_secs(2 * bucketSizeNs / NS_PER_SEC);
alert.set_trigger_if_sum_gt(2);
@@ -210,7 +206,7 @@
EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL);
EXPECT_FALSE(anomalyTracker.detectAnomaly(9, *bucket9));
anomalyTracker.detectAndDeclareAnomaly(eventTimestamp1, 9, *bucket9);
- EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, -1);
+ EXPECT_EQ(anomalyTracker.mLastAnomalyTimestampNs, -1);
// Add past bucket #9
anomalyTracker.addPastBucket(bucket9, 9);
@@ -224,7 +220,7 @@
EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 15L);
anomalyTracker.detectAndDeclareAnomaly(eventTimestamp2, 16, *bucket16);
EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL);
- EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp2);
+ EXPECT_EQ(anomalyTracker.mLastAnomalyTimestampNs, eventTimestamp2);
EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 15L);
// Add past bucket #16
@@ -237,7 +233,7 @@
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 4LL);
// Within refractory period.
anomalyTracker.detectAndDeclareAnomaly(eventTimestamp3, 18, *bucket18);
- EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp2);
+ EXPECT_EQ(anomalyTracker.mLastAnomalyTimestampNs, eventTimestamp2);
EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 1UL);
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 4LL);
@@ -253,7 +249,7 @@
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 1LL);
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyC), 1LL);
anomalyTracker.detectAndDeclareAnomaly(eventTimestamp4, 20, *bucket20);
- EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp4);
+ EXPECT_EQ(anomalyTracker.mLastAnomalyTimestampNs, eventTimestamp4);
// Add bucket #18 again. Nothing changes.
anomalyTracker.addPastBucket(bucket18, 18);
@@ -267,7 +263,7 @@
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyC), 1LL);
anomalyTracker.detectAndDeclareAnomaly(eventTimestamp4 + 1, 20, *bucket20);
// Within refractory period.
- EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp4);
+ EXPECT_EQ(anomalyTracker.mLastAnomalyTimestampNs, eventTimestamp4);
// Add past bucket #20
anomalyTracker.addPastBucket(bucket20, 20);
@@ -279,7 +275,7 @@
EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 24L);
EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL);
anomalyTracker.detectAndDeclareAnomaly(eventTimestamp5, 25, *bucket25);
- EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp4);
+ EXPECT_EQ(anomalyTracker.mLastAnomalyTimestampNs, eventTimestamp4);
// Add past bucket #25
anomalyTracker.addPastBucket(bucket25, 25);
@@ -291,7 +287,7 @@
EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL);
anomalyTracker.detectAndDeclareAnomaly(eventTimestamp6, 28, *bucket28);
EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL);
- EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp4);
+ EXPECT_EQ(anomalyTracker.mLastAnomalyTimestampNs, eventTimestamp4);
// Updates current bucket #28.
(*bucket28)[keyE] = 5;
@@ -300,7 +296,7 @@
EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL);
anomalyTracker.detectAndDeclareAnomaly(eventTimestamp6 + 7, 28, *bucket28);
EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL);
- EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp6 + 7);
+ EXPECT_EQ(anomalyTracker.mLastAnomalyTimestampNs, eventTimestamp6 + 7);
}
} // namespace statsd
diff --git a/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp b/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp
index eb0fafe..819f2be 100644
--- a/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp
+++ b/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp
@@ -11,12 +11,15 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
+
#include "src/condition/SimpleConditionTracker.h"
+#include "tests/statsd_test_util.h"
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <stdio.h>
#include <vector>
+#include <numeric>
using std::map;
using std::unordered_map;
@@ -28,17 +31,24 @@
namespace os {
namespace statsd {
-const ConfigKey kConfigKey(0, "test");
+const ConfigKey kConfigKey(0, 12345);
+
+const int ATTRIBUTION_NODE_FIELD_ID = 1;
+const int ATTRIBUTION_UID_FIELD_ID = 1;
+const int TAG_ID = 1;
SimplePredicate getWakeLockHeldCondition(bool countNesting, bool defaultFalse,
- bool outputSlicedUid) {
+ bool outputSlicedUid, Position position) {
SimplePredicate simplePredicate;
- simplePredicate.set_start("WAKE_LOCK_ACQUIRE");
- simplePredicate.set_stop("WAKE_LOCK_RELEASE");
- simplePredicate.set_stop_all("RELEASE_ALL");
+ simplePredicate.set_start(StringToId("WAKE_LOCK_ACQUIRE"));
+ simplePredicate.set_stop(StringToId("WAKE_LOCK_RELEASE"));
+ simplePredicate.set_stop_all(StringToId("RELEASE_ALL"));
if (outputSlicedUid) {
- KeyMatcher* keyMatcher = simplePredicate.add_dimension();
- keyMatcher->set_key(1);
+ simplePredicate.mutable_dimensions()->set_field(TAG_ID);
+ simplePredicate.mutable_dimensions()->add_child()->set_field(ATTRIBUTION_NODE_FIELD_ID);
+ simplePredicate.mutable_dimensions()->mutable_child(0)->set_position(position);
+ simplePredicate.mutable_dimensions()->mutable_child(0)->add_child()->set_field(
+ ATTRIBUTION_UID_FIELD_ID);
}
simplePredicate.set_count_nesting(countNesting);
@@ -47,38 +57,70 @@
return simplePredicate;
}
-void makeWakeLockEvent(LogEvent* event, int uid, const string& wl, int acquire) {
- event->write(uid); // uid
+void writeAttributionNodesToEvent(LogEvent* event, const std::vector<int> &uids) {
+ std::vector<AttributionNode> nodes;
+ for (size_t i = 0; i < uids.size(); ++i) {
+ AttributionNode node;
+ node.set_uid(uids[i]);
+ nodes.push_back(node);
+ }
+ event->write(nodes); // attribution chain.
+}
+
+void makeWakeLockEvent(
+ LogEvent* event, const std::vector<int> &uids, const string& wl, int acquire) {
+ writeAttributionNodesToEvent(event, uids);
event->write(wl);
event->write(acquire);
event->init();
}
-map<string, HashableDimensionKey> getWakeLockQueryKey(int key, int uid,
- const string& conditionName) {
- // test query
- KeyValuePair kv1;
- kv1.set_key(key);
- kv1.set_value_int(uid);
- vector<KeyValuePair> kv_list;
- kv_list.push_back(kv1);
- map<string, HashableDimensionKey> queryKey;
- queryKey[conditionName] = HashableDimensionKey(kv_list);
- return queryKey;
+std::map<int64_t, std::vector<HashableDimensionKey>> getWakeLockQueryKey(
+ const Position position,
+ const std::vector<int> &uids, const string& conditionName) {
+ std::map<int64_t, std::vector<HashableDimensionKey>> outputKeyMap;
+ std::vector<int> uid_indexes;
+ switch(position) {
+ case Position::FIRST:
+ uid_indexes.push_back(0);
+ break;
+ case Position::LAST:
+ uid_indexes.push_back(uids.size() - 1);
+ break;
+ case Position::ANY:
+ uid_indexes.resize(uids.size());
+ std::iota(uid_indexes.begin(), uid_indexes.end(), 0);
+ break;
+ default:
+ break;
+ }
+
+ for (const int idx : uid_indexes) {
+ DimensionsValue dimensionsValue;
+ dimensionsValue.set_field(TAG_ID);
+ dimensionsValue.mutable_value_tuple()->add_dimensions_value()->set_field(
+ ATTRIBUTION_NODE_FIELD_ID);
+ dimensionsValue.mutable_value_tuple()->mutable_dimensions_value(0)
+ ->mutable_value_tuple()->add_dimensions_value()->set_field(ATTRIBUTION_NODE_FIELD_ID);
+ dimensionsValue.mutable_value_tuple()->mutable_dimensions_value(0)
+ ->mutable_value_tuple()->mutable_dimensions_value(0)->set_value_int(uids[idx]);
+ outputKeyMap[StringToId(conditionName)].push_back(HashableDimensionKey(dimensionsValue));
+ }
+ return outputKeyMap;
}
TEST(SimpleConditionTrackerTest, TestNonSlicedCondition) {
SimplePredicate simplePredicate;
- simplePredicate.set_start("SCREEN_TURNED_ON");
- simplePredicate.set_stop("SCREEN_TURNED_OFF");
+ simplePredicate.set_start(StringToId("SCREEN_TURNED_ON"));
+ simplePredicate.set_stop(StringToId("SCREEN_TURNED_OFF"));
simplePredicate.set_count_nesting(false);
simplePredicate.set_initial_value(SimplePredicate_InitialValue_UNKNOWN);
- unordered_map<string, int> trackerNameIndexMap;
- trackerNameIndexMap["SCREEN_TURNED_ON"] = 0;
- trackerNameIndexMap["SCREEN_TURNED_OFF"] = 1;
+ unordered_map<int64_t, int> trackerNameIndexMap;
+ trackerNameIndexMap[StringToId("SCREEN_TURNED_ON")] = 0;
+ trackerNameIndexMap[StringToId("SCREEN_TURNED_OFF")] = 1;
- SimpleConditionTracker conditionTracker(kConfigKey, "SCREEN_IS_ON", 0 /*tracker index*/,
+ SimpleConditionTracker conditionTracker(kConfigKey, StringToId("SCREEN_IS_ON"), 0 /*tracker index*/,
simplePredicate, trackerNameIndexMap);
LogEvent event(1 /*tagId*/, 0 /*timestamp*/);
@@ -152,15 +194,15 @@
TEST(SimpleConditionTrackerTest, TestNonSlicedConditionNestCounting) {
SimplePredicate simplePredicate;
- simplePredicate.set_start("SCREEN_TURNED_ON");
- simplePredicate.set_stop("SCREEN_TURNED_OFF");
+ simplePredicate.set_start(StringToId("SCREEN_TURNED_ON"));
+ simplePredicate.set_stop(StringToId("SCREEN_TURNED_OFF"));
simplePredicate.set_count_nesting(true);
- unordered_map<string, int> trackerNameIndexMap;
- trackerNameIndexMap["SCREEN_TURNED_ON"] = 0;
- trackerNameIndexMap["SCREEN_TURNED_OFF"] = 1;
+ unordered_map<int64_t, int> trackerNameIndexMap;
+ trackerNameIndexMap[StringToId("SCREEN_TURNED_ON")] = 0;
+ trackerNameIndexMap[StringToId("SCREEN_TURNED_OFF")] = 1;
- SimpleConditionTracker conditionTracker(kConfigKey, "SCREEN_IS_ON",
+ SimpleConditionTracker conditionTracker(kConfigKey, StringToId("SCREEN_IS_ON"),
0 /*condition tracker index*/, simplePredicate,
trackerNameIndexMap);
@@ -221,108 +263,133 @@
}
TEST(SimpleConditionTrackerTest, TestSlicedCondition) {
- SimplePredicate simplePredicate = getWakeLockHeldCondition(
- true /*nesting*/, true /*default to false*/, true /*output slice by uid*/);
- string conditionName = "WL_HELD_BY_UID2";
+ for (Position position :
+ { Position::ANY, Position::FIRST, Position::LAST}) {
+ SimplePredicate simplePredicate = getWakeLockHeldCondition(
+ true /*nesting*/, true /*default to false*/, true /*output slice by uid*/,
+ position);
+ string conditionName = "WL_HELD_BY_UID2";
- unordered_map<string, int> trackerNameIndexMap;
- trackerNameIndexMap["WAKE_LOCK_ACQUIRE"] = 0;
- trackerNameIndexMap["WAKE_LOCK_RELEASE"] = 1;
- trackerNameIndexMap["RELEASE_ALL"] = 2;
+ unordered_map<int64_t, int> trackerNameIndexMap;
+ trackerNameIndexMap[StringToId("WAKE_LOCK_ACQUIRE")] = 0;
+ trackerNameIndexMap[StringToId("WAKE_LOCK_RELEASE")] = 1;
+ trackerNameIndexMap[StringToId("RELEASE_ALL")] = 2;
- SimpleConditionTracker conditionTracker(kConfigKey, conditionName,
- 0 /*condition tracker index*/, simplePredicate,
- trackerNameIndexMap);
- int uid = 111;
+ SimpleConditionTracker conditionTracker(kConfigKey, StringToId(conditionName),
+ 0 /*condition tracker index*/, simplePredicate,
+ trackerNameIndexMap);
+ std::vector<int> uids = {111, 222, 333};
- LogEvent event(1 /*tagId*/, 0 /*timestamp*/);
- makeWakeLockEvent(&event, uid, "wl1", 1);
+ LogEvent event(1 /*tagId*/, 0 /*timestamp*/);
+ makeWakeLockEvent(&event, uids, "wl1", 1);
- // one matched start
- vector<MatchingState> matcherState;
- matcherState.push_back(MatchingState::kMatched);
- matcherState.push_back(MatchingState::kNotMatched);
- matcherState.push_back(MatchingState::kNotMatched);
- vector<sp<ConditionTracker>> allPredicates;
- vector<ConditionState> conditionCache(1, ConditionState::kNotEvaluated);
- vector<bool> changedCache(1, false);
+ // one matched start
+ vector<MatchingState> matcherState;
+ matcherState.push_back(MatchingState::kMatched);
+ matcherState.push_back(MatchingState::kNotMatched);
+ matcherState.push_back(MatchingState::kNotMatched);
+ vector<sp<ConditionTracker>> allPredicates;
+ vector<ConditionState> conditionCache(1, ConditionState::kNotEvaluated);
+ vector<bool> changedCache(1, false);
- conditionTracker.evaluateCondition(event, matcherState, allPredicates, conditionCache,
- changedCache);
+ conditionTracker.evaluateCondition(event, matcherState, allPredicates, conditionCache,
+ changedCache);
- EXPECT_EQ(1UL, conditionTracker.mSlicedConditionState.size());
- EXPECT_TRUE(changedCache[0]);
+ if (position == Position::FIRST ||
+ position == Position::LAST) {
+ EXPECT_EQ(1UL, conditionTracker.mSlicedConditionState.size());
+ } else {
+ EXPECT_EQ(uids.size(), conditionTracker.mSlicedConditionState.size());
+ }
+ EXPECT_TRUE(changedCache[0]);
- // Now test query
- const auto queryKey = getWakeLockQueryKey(1, uid, conditionName);
- conditionCache[0] = ConditionState::kNotEvaluated;
+ // Now test query
+ const auto queryKey = getWakeLockQueryKey(position, uids, conditionName);
+ conditionCache[0] = ConditionState::kNotEvaluated;
- conditionTracker.isConditionMet(queryKey, allPredicates, conditionCache);
- EXPECT_EQ(ConditionState::kTrue, conditionCache[0]);
+ conditionTracker.isConditionMet(queryKey, allPredicates, conditionCache);
+ EXPECT_EQ(ConditionState::kTrue, conditionCache[0]);
- // another wake lock acquired by this uid
- LogEvent event2(1 /*tagId*/, 0 /*timestamp*/);
- makeWakeLockEvent(&event2, uid, "wl2", 1);
- matcherState.clear();
- matcherState.push_back(MatchingState::kMatched);
- matcherState.push_back(MatchingState::kNotMatched);
- conditionCache[0] = ConditionState::kNotEvaluated;
- changedCache[0] = false;
- conditionTracker.evaluateCondition(event2, matcherState, allPredicates, conditionCache,
- changedCache);
- EXPECT_FALSE(changedCache[0]);
+ // another wake lock acquired by this uid
+ LogEvent event2(1 /*tagId*/, 0 /*timestamp*/);
+ makeWakeLockEvent(&event2, uids, "wl2", 1);
+ matcherState.clear();
+ matcherState.push_back(MatchingState::kMatched);
+ matcherState.push_back(MatchingState::kNotMatched);
+ conditionCache[0] = ConditionState::kNotEvaluated;
+ changedCache[0] = false;
+ conditionTracker.evaluateCondition(event2, matcherState, allPredicates, conditionCache,
+ changedCache);
+ EXPECT_FALSE(changedCache[0]);
+ if (position == Position::FIRST ||
+ position == Position::LAST) {
+ EXPECT_EQ(1UL, conditionTracker.mSlicedConditionState.size());
+ } else {
+ EXPECT_EQ(uids.size(), conditionTracker.mSlicedConditionState.size());
+ }
- // wake lock 1 release
- LogEvent event3(1 /*tagId*/, 0 /*timestamp*/);
- makeWakeLockEvent(&event3, uid, "wl1", 0); // now release it.
- matcherState.clear();
- matcherState.push_back(MatchingState::kNotMatched);
- matcherState.push_back(MatchingState::kMatched);
- conditionCache[0] = ConditionState::kNotEvaluated;
- changedCache[0] = false;
- conditionTracker.evaluateCondition(event3, matcherState, allPredicates, conditionCache,
- changedCache);
- // nothing changes, because wake lock 2 is still held for this uid
- EXPECT_FALSE(changedCache[0]);
+ // wake lock 1 release
+ LogEvent event3(1 /*tagId*/, 0 /*timestamp*/);
+ makeWakeLockEvent(&event3, uids, "wl1", 0); // now release it.
+ matcherState.clear();
+ matcherState.push_back(MatchingState::kNotMatched);
+ matcherState.push_back(MatchingState::kMatched);
+ conditionCache[0] = ConditionState::kNotEvaluated;
+ changedCache[0] = false;
+ conditionTracker.evaluateCondition(event3, matcherState, allPredicates, conditionCache,
+ changedCache);
+ // nothing changes, because wake lock 2 is still held for this uid
+ EXPECT_FALSE(changedCache[0]);
+ if (position == Position::FIRST ||
+ position == Position::LAST) {
+ EXPECT_EQ(1UL, conditionTracker.mSlicedConditionState.size());
+ } else {
+ EXPECT_EQ(uids.size(), conditionTracker.mSlicedConditionState.size());
+ }
- LogEvent event4(1 /*tagId*/, 0 /*timestamp*/);
- makeWakeLockEvent(&event4, uid, "wl2", 0); // now release it.
- matcherState.clear();
- matcherState.push_back(MatchingState::kNotMatched);
- matcherState.push_back(MatchingState::kMatched);
- conditionCache[0] = ConditionState::kNotEvaluated;
- changedCache[0] = false;
- conditionTracker.evaluateCondition(event4, matcherState, allPredicates, conditionCache,
- changedCache);
- EXPECT_EQ(0UL, conditionTracker.mSlicedConditionState.size());
- EXPECT_TRUE(changedCache[0]);
+ LogEvent event4(1 /*tagId*/, 0 /*timestamp*/);
+ makeWakeLockEvent(&event4, uids, "wl2", 0); // now release it.
+ matcherState.clear();
+ matcherState.push_back(MatchingState::kNotMatched);
+ matcherState.push_back(MatchingState::kMatched);
+ conditionCache[0] = ConditionState::kNotEvaluated;
+ changedCache[0] = false;
+ conditionTracker.evaluateCondition(event4, matcherState, allPredicates, conditionCache,
+ changedCache);
+ EXPECT_EQ(0UL, conditionTracker.mSlicedConditionState.size());
+ EXPECT_TRUE(changedCache[0]);
- // query again
- conditionCache[0] = ConditionState::kNotEvaluated;
- conditionTracker.isConditionMet(queryKey, allPredicates, conditionCache);
- EXPECT_EQ(ConditionState::kFalse, conditionCache[0]);
+ // query again
+ conditionCache[0] = ConditionState::kNotEvaluated;
+ conditionTracker.isConditionMet(queryKey, allPredicates, conditionCache);
+ EXPECT_EQ(ConditionState::kFalse, conditionCache[0]);
+
+ }
+
}
TEST(SimpleConditionTrackerTest, TestSlicedWithNoOutputDim) {
SimplePredicate simplePredicate = getWakeLockHeldCondition(
- true /*nesting*/, true /*default to false*/, false /*slice output by uid*/);
+ true /*nesting*/, true /*default to false*/, false /*slice output by uid*/,
+ Position::ANY /* position */);
string conditionName = "WL_HELD";
- unordered_map<string, int> trackerNameIndexMap;
- trackerNameIndexMap["WAKE_LOCK_ACQUIRE"] = 0;
- trackerNameIndexMap["WAKE_LOCK_RELEASE"] = 1;
- trackerNameIndexMap["RELEASE_ALL"] = 2;
+ unordered_map<int64_t, int> trackerNameIndexMap;
+ trackerNameIndexMap[StringToId("WAKE_LOCK_ACQUIRE")] = 0;
+ trackerNameIndexMap[StringToId("WAKE_LOCK_RELEASE")] = 1;
+ trackerNameIndexMap[StringToId("RELEASE_ALL")] = 2;
- SimpleConditionTracker conditionTracker(kConfigKey, conditionName,
+ SimpleConditionTracker conditionTracker(kConfigKey, StringToId(conditionName),
0 /*condition tracker index*/, simplePredicate,
trackerNameIndexMap);
- int uid1 = 111;
+
+ std::vector<int> uid_list1 = {111, 1111, 11111};
string uid1_wl1 = "wl1_1";
- int uid2 = 222;
+ std::vector<int> uid_list2 = {222, 2222, 22222};
string uid2_wl1 = "wl2_1";
LogEvent event(1 /*tagId*/, 0 /*timestamp*/);
- makeWakeLockEvent(&event, uid1, uid1_wl1, 1);
+ makeWakeLockEvent(&event, uid_list1, uid1_wl1, 1);
// one matched start for uid1
vector<MatchingState> matcherState;
@@ -340,7 +407,7 @@
EXPECT_TRUE(changedCache[0]);
// Now test query
- map<string, HashableDimensionKey> queryKey;
+ ConditionKey queryKey;
conditionCache[0] = ConditionState::kNotEvaluated;
conditionTracker.isConditionMet(queryKey, allPredicates, conditionCache);
@@ -348,7 +415,7 @@
// another wake lock acquired by this uid
LogEvent event2(1 /*tagId*/, 0 /*timestamp*/);
- makeWakeLockEvent(&event2, uid2, uid2_wl1, 1);
+ makeWakeLockEvent(&event2, uid_list2, uid2_wl1, 1);
matcherState.clear();
matcherState.push_back(MatchingState::kMatched);
matcherState.push_back(MatchingState::kNotMatched);
@@ -360,7 +427,7 @@
// uid1 wake lock 1 release
LogEvent event3(1 /*tagId*/, 0 /*timestamp*/);
- makeWakeLockEvent(&event3, uid1, uid1_wl1, 0); // now release it.
+ makeWakeLockEvent(&event3, uid_list1, uid1_wl1, 0); // now release it.
matcherState.clear();
matcherState.push_back(MatchingState::kNotMatched);
matcherState.push_back(MatchingState::kMatched);
@@ -372,7 +439,7 @@
EXPECT_FALSE(changedCache[0]);
LogEvent event4(1 /*tagId*/, 0 /*timestamp*/);
- makeWakeLockEvent(&event4, uid2, uid2_wl1, 0); // now release it.
+ makeWakeLockEvent(&event4, uid_list2, uid2_wl1, 0); // now release it.
matcherState.clear();
matcherState.push_back(MatchingState::kNotMatched);
matcherState.push_back(MatchingState::kMatched);
@@ -390,95 +457,111 @@
}
TEST(SimpleConditionTrackerTest, TestStopAll) {
- SimplePredicate simplePredicate = getWakeLockHeldCondition(
- true /*nesting*/, true /*default to false*/, true /*output slice by uid*/);
- string conditionName = "WL_HELD_BY_UID3";
+ for (Position position :
+ {Position::ANY, Position::FIRST, Position::LAST}) {
+ SimplePredicate simplePredicate = getWakeLockHeldCondition(
+ true /*nesting*/, true /*default to false*/, true /*output slice by uid*/,
+ position);
+ string conditionName = "WL_HELD_BY_UID3";
- unordered_map<string, int> trackerNameIndexMap;
- trackerNameIndexMap["WAKE_LOCK_ACQUIRE"] = 0;
- trackerNameIndexMap["WAKE_LOCK_RELEASE"] = 1;
- trackerNameIndexMap["RELEASE_ALL"] = 2;
+ unordered_map<int64_t, int> trackerNameIndexMap;
+ trackerNameIndexMap[StringToId("WAKE_LOCK_ACQUIRE")] = 0;
+ trackerNameIndexMap[StringToId("WAKE_LOCK_RELEASE")] = 1;
+ trackerNameIndexMap[StringToId("RELEASE_ALL")] = 2;
- SimpleConditionTracker conditionTracker(kConfigKey, conditionName,
- 0 /*condition tracker index*/, simplePredicate,
- trackerNameIndexMap);
- int uid1 = 111;
- int uid2 = 222;
+ SimpleConditionTracker conditionTracker(kConfigKey, StringToId(conditionName),
+ 0 /*condition tracker index*/, simplePredicate,
+ trackerNameIndexMap);
- LogEvent event(1 /*tagId*/, 0 /*timestamp*/);
- makeWakeLockEvent(&event, uid1, "wl1", 1);
+ std::vector<int> uid_list1 = {111, 1111, 11111};
+ std::vector<int> uid_list2 = {222, 2222, 22222};
- // one matched start
- vector<MatchingState> matcherState;
- matcherState.push_back(MatchingState::kMatched);
- matcherState.push_back(MatchingState::kNotMatched);
- matcherState.push_back(MatchingState::kNotMatched);
- vector<sp<ConditionTracker>> allPredicates;
- vector<ConditionState> conditionCache(1, ConditionState::kNotEvaluated);
- vector<bool> changedCache(1, false);
+ LogEvent event(1 /*tagId*/, 0 /*timestamp*/);
+ makeWakeLockEvent(&event, uid_list1, "wl1", 1);
- conditionTracker.evaluateCondition(event, matcherState, allPredicates, conditionCache,
- changedCache);
+ // one matched start
+ vector<MatchingState> matcherState;
+ matcherState.push_back(MatchingState::kMatched);
+ matcherState.push_back(MatchingState::kNotMatched);
+ matcherState.push_back(MatchingState::kNotMatched);
+ vector<sp<ConditionTracker>> allPredicates;
+ vector<ConditionState> conditionCache(1, ConditionState::kNotEvaluated);
+ vector<bool> changedCache(1, false);
- EXPECT_EQ(1UL, conditionTracker.mSlicedConditionState.size());
- EXPECT_TRUE(changedCache[0]);
+ conditionTracker.evaluateCondition(event, matcherState, allPredicates, conditionCache,
+ changedCache);
+ if (position == Position::FIRST ||
+ position == Position::LAST) {
+ EXPECT_EQ(1UL, conditionTracker.mSlicedConditionState.size());
+ } else {
+ EXPECT_EQ(uid_list1.size(), conditionTracker.mSlicedConditionState.size());
+ }
+ EXPECT_TRUE(changedCache[0]);
- // Now test query
- const auto queryKey = getWakeLockQueryKey(1, uid1, conditionName);
- conditionCache[0] = ConditionState::kNotEvaluated;
+ // Now test query
+ const auto queryKey = getWakeLockQueryKey(position, uid_list1, conditionName);
+ conditionCache[0] = ConditionState::kNotEvaluated;
- conditionTracker.isConditionMet(queryKey, allPredicates, conditionCache);
- EXPECT_EQ(ConditionState::kTrue, conditionCache[0]);
+ conditionTracker.isConditionMet(queryKey, allPredicates, conditionCache);
+ EXPECT_EQ(ConditionState::kTrue, conditionCache[0]);
- // another wake lock acquired by uid2
- LogEvent event2(1 /*tagId*/, 0 /*timestamp*/);
- makeWakeLockEvent(&event2, uid2, "wl2", 1);
- matcherState.clear();
- matcherState.push_back(MatchingState::kMatched);
- matcherState.push_back(MatchingState::kNotMatched);
- matcherState.push_back(MatchingState::kNotMatched);
- conditionCache[0] = ConditionState::kNotEvaluated;
- changedCache[0] = false;
- conditionTracker.evaluateCondition(event2, matcherState, allPredicates, conditionCache,
- changedCache);
- EXPECT_EQ(2UL, conditionTracker.mSlicedConditionState.size());
- EXPECT_TRUE(changedCache[0]);
+ // another wake lock acquired by uid2
+ LogEvent event2(1 /*tagId*/, 0 /*timestamp*/);
+ makeWakeLockEvent(&event2, uid_list2, "wl2", 1);
+ matcherState.clear();
+ matcherState.push_back(MatchingState::kMatched);
+ matcherState.push_back(MatchingState::kNotMatched);
+ matcherState.push_back(MatchingState::kNotMatched);
+ conditionCache[0] = ConditionState::kNotEvaluated;
+ changedCache[0] = false;
+ conditionTracker.evaluateCondition(event2, matcherState, allPredicates, conditionCache,
+ changedCache);
+ if (position == Position::FIRST ||
+ position == Position::LAST) {
+ EXPECT_EQ(2UL, conditionTracker.mSlicedConditionState.size());
+ } else {
+ EXPECT_EQ(uid_list1.size() + uid_list2.size(),
+ conditionTracker.mSlicedConditionState.size());
+ }
+ EXPECT_TRUE(changedCache[0]);
- // TEST QUERY
- const auto queryKey2 = getWakeLockQueryKey(1, uid2, conditionName);
- conditionCache[0] = ConditionState::kNotEvaluated;
+ // TEST QUERY
+ const auto queryKey2 = getWakeLockQueryKey(position, uid_list2, conditionName);
+ conditionCache[0] = ConditionState::kNotEvaluated;
- conditionTracker.isConditionMet(queryKey, allPredicates, conditionCache);
- EXPECT_EQ(ConditionState::kTrue, conditionCache[0]);
+ conditionTracker.isConditionMet(queryKey, allPredicates, conditionCache);
+ EXPECT_EQ(ConditionState::kTrue, conditionCache[0]);
- // stop all event
- LogEvent event3(2 /*tagId*/, 0 /*timestamp*/);
- matcherState.clear();
- matcherState.push_back(MatchingState::kNotMatched);
- matcherState.push_back(MatchingState::kNotMatched);
- matcherState.push_back(MatchingState::kMatched);
+ // stop all event
+ LogEvent event3(2 /*tagId*/, 0 /*timestamp*/);
+ matcherState.clear();
+ matcherState.push_back(MatchingState::kNotMatched);
+ matcherState.push_back(MatchingState::kNotMatched);
+ matcherState.push_back(MatchingState::kMatched);
- conditionCache[0] = ConditionState::kNotEvaluated;
- changedCache[0] = false;
- conditionTracker.evaluateCondition(event3, matcherState, allPredicates, conditionCache,
- changedCache);
- EXPECT_TRUE(changedCache[0]);
- EXPECT_EQ(0UL, conditionTracker.mSlicedConditionState.size());
+ conditionCache[0] = ConditionState::kNotEvaluated;
+ changedCache[0] = false;
+ conditionTracker.evaluateCondition(event3, matcherState, allPredicates, conditionCache,
+ changedCache);
+ EXPECT_TRUE(changedCache[0]);
+ EXPECT_EQ(0UL, conditionTracker.mSlicedConditionState.size());
- // TEST QUERY
- const auto queryKey3 = getWakeLockQueryKey(1, uid1, conditionName);
- conditionCache[0] = ConditionState::kNotEvaluated;
+ // TEST QUERY
+ const auto queryKey3 = getWakeLockQueryKey(position, uid_list1, conditionName);
+ conditionCache[0] = ConditionState::kNotEvaluated;
- conditionTracker.isConditionMet(queryKey, allPredicates, conditionCache);
- EXPECT_EQ(ConditionState::kFalse, conditionCache[0]);
+ conditionTracker.isConditionMet(queryKey, allPredicates, conditionCache);
+ EXPECT_EQ(ConditionState::kFalse, conditionCache[0]);
- // TEST QUERY
- const auto queryKey4 = getWakeLockQueryKey(1, uid2, conditionName);
- conditionCache[0] = ConditionState::kNotEvaluated;
+ // TEST QUERY
+ const auto queryKey4 = getWakeLockQueryKey(position, uid_list2, conditionName);
+ conditionCache[0] = ConditionState::kNotEvaluated;
- conditionTracker.isConditionMet(queryKey, allPredicates, conditionCache);
- EXPECT_EQ(ConditionState::kFalse, conditionCache[0]);
+ conditionTracker.isConditionMet(queryKey, allPredicates, conditionCache);
+ EXPECT_EQ(ConditionState::kFalse, conditionCache[0]);
+ }
+
}
} // namespace statsd
diff --git a/cmds/statsd/tests/e2e/MetricConditionLink_e2e_test.cpp b/cmds/statsd/tests/e2e/MetricConditionLink_e2e_test.cpp
new file mode 100644
index 0000000..b56b817
--- /dev/null
+++ b/cmds/statsd/tests/e2e/MetricConditionLink_e2e_test.cpp
@@ -0,0 +1,228 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <gtest/gtest.h>
+
+#include "src/StatsLogProcessor.h"
+#include "tests/statsd_test_util.h"
+
+#include <vector>
+
+namespace android {
+namespace os {
+namespace statsd {
+
+#ifdef __ANDROID__
+
+StatsdConfig CreateStatsdConfig() {
+ StatsdConfig config;
+ *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher();
+ *config.add_atom_matcher() = CreateScreenTurnedOffAtomMatcher();
+
+ *config.add_atom_matcher() = CreateSyncStartAtomMatcher();
+ *config.add_atom_matcher() = CreateSyncEndAtomMatcher();
+
+ *config.add_atom_matcher() = CreateMoveToBackgroundAtomMatcher();
+ *config.add_atom_matcher() = CreateMoveToForegroundAtomMatcher();
+
+ auto appCrashMatcher = CreateProcessCrashAtomMatcher();
+ *config.add_atom_matcher() = appCrashMatcher;
+
+ auto screenIsOffPredicate = CreateScreenIsOffPredicate();
+
+ auto isSyncingPredicate = CreateIsSyncingPredicate();
+ *isSyncingPredicate.mutable_simple_predicate()->mutable_dimensions() =
+ CreateDimensions(
+ android::util::SYNC_STATE_CHANGED, {1 /* uid field */});
+
+ auto isInBackgroundPredicate = CreateIsInBackgroundPredicate();
+ *isInBackgroundPredicate.mutable_simple_predicate()->mutable_dimensions() =
+ CreateDimensions(android::util::ACTIVITY_FOREGROUND_STATE_CHANGED, {1 /* uid field */ });
+
+ *config.add_predicate() = screenIsOffPredicate;
+ *config.add_predicate() = isSyncingPredicate;
+ *config.add_predicate() = isInBackgroundPredicate;
+
+ auto combinationPredicate = config.add_predicate();
+ combinationPredicate->set_id(StringToId("combinationPredicate"));
+ combinationPredicate->mutable_combination()->set_operation(LogicalOperation::AND);
+ addPredicateToPredicateCombination(screenIsOffPredicate, combinationPredicate);
+ addPredicateToPredicateCombination(isSyncingPredicate, combinationPredicate);
+ addPredicateToPredicateCombination(isInBackgroundPredicate, combinationPredicate);
+
+ auto countMetric = config.add_count_metric();
+ countMetric->set_id(StringToId("AppCrashes"));
+ countMetric->set_what(appCrashMatcher.id());
+ countMetric->set_condition(combinationPredicate->id());
+ // The metric is dimensioning by uid only.
+ *countMetric->mutable_dimensions() =
+ CreateDimensions(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED, {1});
+ countMetric->mutable_bucket()->set_bucket_size_millis(30 * 1000LL);
+
+ // Links between crash atom and condition of app is in syncing.
+ auto links = countMetric->add_links();
+ links->set_condition(isSyncingPredicate.id());
+ auto dimensionWhat = links->mutable_dimensions_in_what();
+ dimensionWhat->set_field(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED);
+ dimensionWhat->add_child()->set_field(1); // uid field.
+ auto dimensionCondition = links->mutable_dimensions_in_condition();
+ dimensionCondition->set_field(android::util::SYNC_STATE_CHANGED);
+ dimensionCondition->add_child()->set_field(1); // uid field.
+
+ // Links between crash atom and condition of app is in background.
+ links = countMetric->add_links();
+ links->set_condition(isInBackgroundPredicate.id());
+ dimensionWhat = links->mutable_dimensions_in_what();
+ dimensionWhat->set_field(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED);
+ dimensionWhat->add_child()->set_field(1); // uid field.
+ dimensionCondition = links->mutable_dimensions_in_condition();
+ dimensionCondition->set_field(android::util::ACTIVITY_FOREGROUND_STATE_CHANGED);
+ dimensionCondition->add_child()->set_field(1); // uid field.
+ return config;
+}
+
+TEST(MetricConditionLinkE2eTest, TestMultiplePredicatesAndLinks) {
+ auto config = CreateStatsdConfig();
+ uint64_t bucketStartTimeNs = 10000000000;
+ uint64_t bucketSizeNs = config.count_metric(0).bucket().bucket_size_millis() * 1000 * 1000;
+
+ ConfigKey cfgKey;
+ auto processor = CreateStatsLogProcessor(bucketStartTimeNs / NS_PER_SEC, config, cfgKey);
+ EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
+ EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
+
+ int appUid = 123;
+ auto crashEvent1 = CreateAppCrashEvent(appUid, bucketStartTimeNs + 1);
+ auto crashEvent2 = CreateAppCrashEvent(appUid, bucketStartTimeNs + 201);
+ auto crashEvent3= CreateAppCrashEvent(appUid, bucketStartTimeNs + 2 * bucketSizeNs - 101);
+
+ auto crashEvent4 = CreateAppCrashEvent(appUid, bucketStartTimeNs + 51);
+ auto crashEvent5 = CreateAppCrashEvent(appUid, bucketStartTimeNs + bucketSizeNs + 299);
+ auto crashEvent6 = CreateAppCrashEvent(appUid, bucketStartTimeNs + bucketSizeNs + 2001);
+
+ auto crashEvent7 = CreateAppCrashEvent(appUid, bucketStartTimeNs + 16);
+ auto crashEvent8 = CreateAppCrashEvent(appUid, bucketStartTimeNs + bucketSizeNs + 249);
+
+ auto crashEvent9 = CreateAppCrashEvent(appUid, bucketStartTimeNs + bucketSizeNs + 351);
+ auto crashEvent10 = CreateAppCrashEvent(appUid, bucketStartTimeNs + 2 * bucketSizeNs - 2);
+
+ auto screenTurnedOnEvent =
+ CreateScreenStateChangedEvent(ScreenStateChanged::STATE_ON, bucketStartTimeNs + 2);
+ auto screenTurnedOffEvent =
+ CreateScreenStateChangedEvent(ScreenStateChanged::STATE_OFF, bucketStartTimeNs + 200);
+ auto screenTurnedOnEvent2 =
+ CreateScreenStateChangedEvent(ScreenStateChanged::STATE_ON,
+ bucketStartTimeNs + 2 * bucketSizeNs - 100);
+
+ auto syncOnEvent1 =
+ CreateSyncStartEvent(appUid, "ReadEmail", bucketStartTimeNs + 50);
+ auto syncOffEvent1 =
+ CreateSyncEndEvent(appUid, "ReadEmail", bucketStartTimeNs + bucketSizeNs + 300);
+ auto syncOnEvent2 =
+ CreateSyncStartEvent(appUid, "ReadDoc", bucketStartTimeNs + bucketSizeNs + 2000);
+
+ auto moveToBackgroundEvent1 =
+ CreateMoveToBackgroundEvent(appUid, bucketStartTimeNs + 15);
+ auto moveToForegroundEvent1 =
+ CreateMoveToForegroundEvent(appUid, bucketStartTimeNs + bucketSizeNs + 250);
+
+ auto moveToBackgroundEvent2 =
+ CreateMoveToBackgroundEvent(appUid, bucketStartTimeNs + bucketSizeNs + 350);
+ auto moveToForegroundEvent2 =
+ CreateMoveToForegroundEvent(appUid, bucketStartTimeNs + 2 * bucketSizeNs - 1);
+
+ /*
+ bucket #1 bucket #2
+
+
+ | | | | | | | | | | (crashEvents)
+ |-------------------------------------|-----------------------------------|---------
+
+ | | (MoveToBkground)
+
+ | | (MoveToForeground)
+
+ | | (SyncIsOn)
+ | (SyncIsOff)
+ | | (ScreenIsOn)
+ | (ScreenIsOff)
+ */
+ std::vector<std::unique_ptr<LogEvent>> events;
+ events.push_back(std::move(crashEvent1));
+ events.push_back(std::move(crashEvent2));
+ events.push_back(std::move(crashEvent3));
+ events.push_back(std::move(crashEvent4));
+ events.push_back(std::move(crashEvent5));
+ events.push_back(std::move(crashEvent6));
+ events.push_back(std::move(crashEvent7));
+ events.push_back(std::move(crashEvent8));
+ events.push_back(std::move(crashEvent9));
+ events.push_back(std::move(crashEvent10));
+ events.push_back(std::move(screenTurnedOnEvent));
+ events.push_back(std::move(screenTurnedOffEvent));
+ events.push_back(std::move(screenTurnedOnEvent2));
+ events.push_back(std::move(syncOnEvent1));
+ events.push_back(std::move(syncOffEvent1));
+ events.push_back(std::move(syncOnEvent2));
+ events.push_back(std::move(moveToBackgroundEvent1));
+ events.push_back(std::move(moveToForegroundEvent1));
+ events.push_back(std::move(moveToBackgroundEvent2));
+ events.push_back(std::move(moveToForegroundEvent2));
+
+ sortLogEventsByTimestamp(&events);
+
+ for (const auto& event : events) {
+ processor->OnLogEvent(*event);
+ }
+ ConfigMetricsReportList reports;
+ processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs - 1, &reports);
+ EXPECT_EQ(reports.reports_size(), 1);
+ EXPECT_EQ(reports.reports(0).metrics_size(), 1);
+ EXPECT_EQ(reports.reports(0).metrics(0).count_metrics().data_size(), 1);
+ EXPECT_EQ(reports.reports(0).metrics(0).count_metrics().data(0).bucket_info_size(), 1);
+ EXPECT_EQ(reports.reports(0).metrics(0).count_metrics().data(0).bucket_info(0).count(), 1);
+ auto data = reports.reports(0).metrics(0).count_metrics().data(0);
+ // Validate dimension value.
+ EXPECT_EQ(data.dimension().field(),
+ android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED);
+ EXPECT_EQ(data.dimension().value_tuple().dimensions_value_size(), 1);
+ // Uid field.
+ EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0).field(), 1);
+ EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0).value_int(), appUid);
+
+ reports.Clear();
+ processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs + 1, &reports);
+ EXPECT_EQ(reports.reports_size(), 1);
+ EXPECT_EQ(reports.reports(0).metrics_size(), 1);
+ EXPECT_EQ(reports.reports(0).metrics(0).count_metrics().data_size(), 1);
+ EXPECT_EQ(reports.reports(0).metrics(0).count_metrics().data(0).bucket_info_size(), 2);
+ EXPECT_EQ(reports.reports(0).metrics(0).count_metrics().data(0).bucket_info(0).count(), 1);
+ EXPECT_EQ(reports.reports(0).metrics(0).count_metrics().data(0).bucket_info(1).count(), 3);
+ data = reports.reports(0).metrics(0).count_metrics().data(0);
+ // Validate dimension value.
+ EXPECT_EQ(data.dimension().field(),
+ android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED);
+ EXPECT_EQ(data.dimension().value_tuple().dimensions_value_size(), 1);
+ // Uid field.
+ EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0).field(), 1);
+ EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0).value_int(), appUid);
+}
+
+#else
+GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
+
+} // namespace statsd
+} // namespace os
+} // namespace android
\ No newline at end of file
diff --git a/cmds/statsd/tests/e2e/WakelockDuration_e2e_test.cpp b/cmds/statsd/tests/e2e/WakelockDuration_e2e_test.cpp
new file mode 100644
index 0000000..ecdb002
--- /dev/null
+++ b/cmds/statsd/tests/e2e/WakelockDuration_e2e_test.cpp
@@ -0,0 +1,178 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <gtest/gtest.h>
+
+#include "src/StatsLogProcessor.h"
+#include "tests/statsd_test_util.h"
+
+#include <vector>
+
+namespace android {
+namespace os {
+namespace statsd {
+
+#ifdef __ANDROID__
+
+StatsdConfig CreateStatsdConfig(DurationMetric::AggregationType aggregationType) {
+ StatsdConfig config;
+ *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher();
+ *config.add_atom_matcher() = CreateScreenTurnedOffAtomMatcher();
+ *config.add_atom_matcher() = CreateAcquireWakelockAtomMatcher();
+ *config.add_atom_matcher() = CreateReleaseWakelockAtomMatcher();
+
+ auto screenIsOffPredicate = CreateScreenIsOffPredicate();
+ *config.add_predicate() = screenIsOffPredicate;
+
+ auto holdingWakelockPredicate = CreateHoldingWakelockPredicate();
+ // The predicate is dimensioning by any attribution node and both by uid and tag.
+ *holdingWakelockPredicate.mutable_simple_predicate()->mutable_dimensions() =
+ CreateAttributionUidAndTagDimensions(
+ android::util::WAKELOCK_STATE_CHANGED, {Position::FIRST, Position::LAST});
+ *config.add_predicate() = holdingWakelockPredicate;
+
+ auto durationMetric = config.add_duration_metric();
+ durationMetric->set_id(StringToId("WakelockDuration"));
+ durationMetric->set_what(holdingWakelockPredicate.id());
+ durationMetric->set_condition(screenIsOffPredicate.id());
+ durationMetric->set_aggregation_type(aggregationType);
+ // The metric is dimensioning by first attribution node and only by uid.
+ *durationMetric->mutable_dimensions() =
+ CreateAttributionUidDimensions(
+ android::util::WAKELOCK_STATE_CHANGED, {Position::FIRST});
+ durationMetric->mutable_bucket()->set_bucket_size_millis(30 * 1000LL);
+ return config;
+}
+
+TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensions) {
+ ConfigKey cfgKey;
+ for (auto aggregationType : { DurationMetric::SUM, DurationMetric::MAX_SPARSE }) {
+ auto config = CreateStatsdConfig(aggregationType);
+ uint64_t bucketStartTimeNs = 10000000000;
+ uint64_t bucketSizeNs =
+ config.duration_metric(0).bucket().bucket_size_millis() * 1000 * 1000;
+
+ auto processor = CreateStatsLogProcessor(bucketStartTimeNs / NS_PER_SEC, config, cfgKey);
+ EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
+ EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
+
+ auto screenTurnedOnEvent =
+ CreateScreenStateChangedEvent(ScreenStateChanged::STATE_ON, bucketStartTimeNs + 1);
+ auto screenTurnedOffEvent =
+ CreateScreenStateChangedEvent(ScreenStateChanged::STATE_OFF, bucketStartTimeNs + 200);
+ auto screenTurnedOnEvent2 =
+ CreateScreenStateChangedEvent(ScreenStateChanged::STATE_ON,
+ bucketStartTimeNs + bucketSizeNs + 500);
+
+ std::vector<AttributionNode> attributions1 =
+ {CreateAttribution(111, "App1"), CreateAttribution(222, "GMSCoreModule1"),
+ CreateAttribution(222, "GMSCoreModule2")};
+
+ std::vector<AttributionNode> attributions2 =
+ {CreateAttribution(111, "App2"), CreateAttribution(222, "GMSCoreModule1"),
+ CreateAttribution(222, "GMSCoreModule2")};
+
+ auto acquireEvent1 = CreateAcquireWakelockEvent(
+ attributions1, "wl1", bucketStartTimeNs + 2);
+ auto acquireEvent2 = CreateAcquireWakelockEvent(
+ attributions2, "wl2", bucketStartTimeNs + bucketSizeNs - 10);
+
+ auto releaseEvent1 = CreateReleaseWakelockEvent(
+ attributions1, "wl1", bucketStartTimeNs + bucketSizeNs + 2);
+ auto releaseEvent2 = CreateReleaseWakelockEvent(
+ attributions2, "wl2", bucketStartTimeNs + 2 * bucketSizeNs - 15);
+
+ std::vector<std::unique_ptr<LogEvent>> events;
+
+ events.push_back(std::move(screenTurnedOnEvent));
+ events.push_back(std::move(screenTurnedOffEvent));
+ events.push_back(std::move(screenTurnedOnEvent2));
+ events.push_back(std::move(acquireEvent1));
+ events.push_back(std::move(acquireEvent2));
+ events.push_back(std::move(releaseEvent1));
+ events.push_back(std::move(releaseEvent2));
+
+ sortLogEventsByTimestamp(&events);
+
+ for (const auto& event : events) {
+ processor->OnLogEvent(*event);
+ }
+
+ ConfigMetricsReportList reports;
+ processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs - 1, &reports);
+ EXPECT_EQ(reports.reports_size(), 1);
+ EXPECT_EQ(reports.reports(0).metrics_size(), 1);
+ // Only 1 dimension output. The tag dimension in the predicate has been aggregated.
+ EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data_size(), 1);
+
+ auto data = reports.reports(0).metrics(0).duration_metrics().data(0);
+ // Validate dimension value.
+ EXPECT_EQ(data.dimension().field(),
+ android::util::WAKELOCK_STATE_CHANGED);
+ EXPECT_EQ(data.dimension().value_tuple().dimensions_value_size(), 1);
+ // Attribution field.
+ EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0).field(), 1);
+ // Uid only.
+ EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0)
+ .value_tuple().dimensions_value_size(), 1);
+ EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0)
+ .value_tuple().dimensions_value(0).field(), 1);
+ EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0)
+ .value_tuple().dimensions_value(0).value_int(), 111);
+ // Validate bucket info.
+ EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data(0).bucket_info_size(), 1);
+ data = reports.reports(0).metrics(0).duration_metrics().data(0);
+ // The wakelock holding interval starts from the screen off event and to the end of the 1st
+ // bucket.
+ EXPECT_EQ((unsigned long long)data.bucket_info(0).duration_nanos(), bucketSizeNs - 200);
+
+ reports.Clear();
+ processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs + 1, &reports);
+ EXPECT_EQ(reports.reports_size(), 1);
+ EXPECT_EQ(reports.reports(0).metrics_size(), 1);
+ EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data_size(), 1);
+ // Dump the report after the end of 2nd bucket.
+ EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data(0).bucket_info_size(), 2);
+ data = reports.reports(0).metrics(0).duration_metrics().data(0);
+ // Validate dimension value.
+ EXPECT_EQ(data.dimension().field(),
+ android::util::WAKELOCK_STATE_CHANGED);
+ EXPECT_EQ(data.dimension().value_tuple().dimensions_value_size(), 1);
+ // Attribution field.
+ EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0).field(), 1);
+ // Uid only.
+ EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0)
+ .value_tuple().dimensions_value_size(), 1);
+ EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0)
+ .value_tuple().dimensions_value(0).field(), 1);
+ EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0)
+ .value_tuple().dimensions_value(0).value_int(), 111);
+ // Two output buckets.
+ // The wakelock holding interval in the 1st bucket starts from the screen off event and to
+ // the end of the 1st bucket.
+ EXPECT_EQ((unsigned long long)data.bucket_info(0).duration_nanos(),
+ bucketStartTimeNs + bucketSizeNs - (bucketStartTimeNs + 200));
+ // The wakelock holding interval in the 2nd bucket starts at the beginning of the bucket and
+ // ends at the second screen on event.
+ EXPECT_EQ((unsigned long long)data.bucket_info(1).duration_nanos(), 500UL);
+ }
+}
+
+#else
+GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
+
+} // namespace statsd
+} // namespace os
+} // namespace android
\ No newline at end of file
diff --git a/cmds/statsd/tests/guardrail/StatsdStats_test.cpp b/cmds/statsd/tests/guardrail/StatsdStats_test.cpp
index 7658044..a134300 100644
--- a/cmds/statsd/tests/guardrail/StatsdStats_test.cpp
+++ b/cmds/statsd/tests/guardrail/StatsdStats_test.cpp
@@ -14,6 +14,7 @@
#include "src/guardrail/StatsdStats.h"
#include "statslog.h"
+#include "tests/statsd_test_util.h"
#include <gtest/gtest.h>
#include <vector>
@@ -28,8 +29,7 @@
TEST(StatsdStatsTest, TestValidConfigAdd) {
StatsdStats stats;
- string name = "StatsdTest";
- ConfigKey key(0, name);
+ ConfigKey key(0, 12345);
const int metricsCount = 10;
const int conditionsCount = 20;
const int matchersCount = 30;
@@ -45,7 +45,7 @@
EXPECT_EQ(1, report.config_stats_size());
const auto& configReport = report.config_stats(0);
EXPECT_EQ(0, configReport.uid());
- EXPECT_EQ(name, configReport.name());
+ EXPECT_EQ(12345, configReport.id());
EXPECT_EQ(metricsCount, configReport.metric_count());
EXPECT_EQ(conditionsCount, configReport.condition_count());
EXPECT_EQ(matchersCount, configReport.matcher_count());
@@ -56,8 +56,7 @@
TEST(StatsdStatsTest, TestInvalidConfigAdd) {
StatsdStats stats;
- string name = "StatsdTest";
- ConfigKey key(0, name);
+ ConfigKey key(0, 12345);
const int metricsCount = 10;
const int conditionsCount = 20;
const int matchersCount = 30;
@@ -78,8 +77,7 @@
TEST(StatsdStatsTest, TestConfigRemove) {
StatsdStats stats;
- string name = "StatsdTest";
- ConfigKey key(0, name);
+ ConfigKey key(0, 12345);
const int metricsCount = 10;
const int conditionsCount = 20;
const int matchersCount = 30;
@@ -105,22 +103,22 @@
TEST(StatsdStatsTest, TestSubStats) {
StatsdStats stats;
- ConfigKey key(0, "test");
+ ConfigKey key(0, 12345);
stats.noteConfigReceived(key, 2, 3, 4, 5, true);
- stats.noteMatcherMatched(key, "matcher1");
- stats.noteMatcherMatched(key, "matcher1");
- stats.noteMatcherMatched(key, "matcher2");
+ stats.noteMatcherMatched(key, StringToId("matcher1"));
+ stats.noteMatcherMatched(key, StringToId("matcher1"));
+ stats.noteMatcherMatched(key, StringToId("matcher2"));
- stats.noteConditionDimensionSize(key, "condition1", 250);
- stats.noteConditionDimensionSize(key, "condition1", 240);
+ stats.noteConditionDimensionSize(key, StringToId("condition1"), 250);
+ stats.noteConditionDimensionSize(key, StringToId("condition1"), 240);
- stats.noteMetricDimensionSize(key, "metric1", 201);
- stats.noteMetricDimensionSize(key, "metric1", 202);
+ stats.noteMetricDimensionSize(key, StringToId("metric1"), 201);
+ stats.noteMetricDimensionSize(key, StringToId("metric1"), 202);
- stats.noteAnomalyDeclared(key, "alert1");
- stats.noteAnomalyDeclared(key, "alert1");
- stats.noteAnomalyDeclared(key, "alert2");
+ stats.noteAnomalyDeclared(key, StringToId("alert1"));
+ stats.noteAnomalyDeclared(key, StringToId("alert1"));
+ stats.noteAnomalyDeclared(key, StringToId("alert2"));
// broadcast-> 2
stats.noteBroadcastSent(key);
@@ -147,39 +145,39 @@
EXPECT_EQ(2, configReport.matcher_stats_size());
// matcher1 is the first in the list
- if (!configReport.matcher_stats(0).name().compare("matcher1")) {
+ if (configReport.matcher_stats(0).id() == StringToId("matcher1")) {
EXPECT_EQ(2, configReport.matcher_stats(0).matched_times());
EXPECT_EQ(1, configReport.matcher_stats(1).matched_times());
- EXPECT_EQ("matcher2", configReport.matcher_stats(1).name());
+ EXPECT_EQ(StringToId("matcher2"), configReport.matcher_stats(1).id());
} else {
// matcher1 is the second in the list.
EXPECT_EQ(1, configReport.matcher_stats(0).matched_times());
- EXPECT_EQ("matcher2", configReport.matcher_stats(0).name());
+ EXPECT_EQ(StringToId("matcher2"), configReport.matcher_stats(0).id());
EXPECT_EQ(2, configReport.matcher_stats(1).matched_times());
- EXPECT_EQ("matcher1", configReport.matcher_stats(1).name());
+ EXPECT_EQ(StringToId("matcher1"), configReport.matcher_stats(1).id());
}
EXPECT_EQ(2, configReport.alert_stats_size());
- bool alert1first = !configReport.alert_stats(0).name().compare("alert1");
- EXPECT_EQ("alert1", configReport.alert_stats(alert1first ? 0 : 1).name());
+ bool alert1first = configReport.alert_stats(0).id() == StringToId("alert1");
+ EXPECT_EQ(StringToId("alert1"), configReport.alert_stats(alert1first ? 0 : 1).id());
EXPECT_EQ(2, configReport.alert_stats(alert1first ? 0 : 1).alerted_times());
- EXPECT_EQ("alert2", configReport.alert_stats(alert1first ? 1 : 0).name());
+ EXPECT_EQ(StringToId("alert2"), configReport.alert_stats(alert1first ? 1 : 0).id());
EXPECT_EQ(1, configReport.alert_stats(alert1first ? 1 : 0).alerted_times());
EXPECT_EQ(1, configReport.condition_stats_size());
- EXPECT_EQ("condition1", configReport.condition_stats(0).name());
+ EXPECT_EQ(StringToId("condition1"), configReport.condition_stats(0).id());
EXPECT_EQ(250, configReport.condition_stats(0).max_tuple_counts());
EXPECT_EQ(1, configReport.metric_stats_size());
- EXPECT_EQ("metric1", configReport.metric_stats(0).name());
+ EXPECT_EQ(StringToId("metric1"), configReport.metric_stats(0).id());
EXPECT_EQ(202, configReport.metric_stats(0).max_tuple_counts());
// after resetting the stats, some new events come
- stats.noteMatcherMatched(key, "matcher99");
- stats.noteConditionDimensionSize(key, "condition99", 300);
- stats.noteMetricDimensionSize(key, "metric99", 270);
- stats.noteAnomalyDeclared(key, "alert99");
+ stats.noteMatcherMatched(key, StringToId("matcher99"));
+ stats.noteConditionDimensionSize(key, StringToId("condition99"), 300);
+ stats.noteMetricDimensionSize(key, StringToId("metric99tion99"), 270);
+ stats.noteAnomalyDeclared(key, StringToId("alert99"));
// now the config stats should only contain the stats about the new event.
stats.dumpStats(&output, false);
@@ -188,19 +186,19 @@
EXPECT_EQ(1, report.config_stats_size());
const auto& configReport2 = report.config_stats(0);
EXPECT_EQ(1, configReport2.matcher_stats_size());
- EXPECT_EQ("matcher99", configReport2.matcher_stats(0).name());
+ EXPECT_EQ(StringToId("matcher99"), configReport2.matcher_stats(0).id());
EXPECT_EQ(1, configReport2.matcher_stats(0).matched_times());
EXPECT_EQ(1, configReport2.condition_stats_size());
- EXPECT_EQ("condition99", configReport2.condition_stats(0).name());
+ EXPECT_EQ(StringToId("condition99"), configReport2.condition_stats(0).id());
EXPECT_EQ(300, configReport2.condition_stats(0).max_tuple_counts());
EXPECT_EQ(1, configReport2.metric_stats_size());
- EXPECT_EQ("metric99", configReport2.metric_stats(0).name());
+ EXPECT_EQ(StringToId("metric99tion99"), configReport2.metric_stats(0).id());
EXPECT_EQ(270, configReport2.metric_stats(0).max_tuple_counts());
EXPECT_EQ(1, configReport2.alert_stats_size());
- EXPECT_EQ("alert99", configReport2.alert_stats(0).name());
+ EXPECT_EQ(StringToId("alert99"), configReport2.alert_stats(0).id());
EXPECT_EQ(1, configReport2.alert_stats(0).alerted_times());
}
@@ -260,7 +258,7 @@
for (int i = 0; i < StatsdStats::kMaxTimestampCount; i++) {
timestamps.push_back(i);
}
- ConfigKey key(0, "test");
+ ConfigKey key(0, 12345);
stats.noteConfigReceived(key, 2, 3, 4, 5, true);
for (int i = 0; i < StatsdStats::kMaxTimestampCount; i++) {
diff --git a/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp b/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp
index eec94539..4cb242a 100644
--- a/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp
@@ -13,7 +13,9 @@
// limitations under the License.
#include "src/metrics/CountMetricProducer.h"
+#include "src/dimension.h"
#include "metrics_test_helper.h"
+#include "tests/statsd_test_util.h"
#include <gmock/gmock.h>
#include <gtest/gtest.h>
@@ -32,7 +34,7 @@
namespace os {
namespace statsd {
-const ConfigKey kConfigKey(0, "test");
+const ConfigKey kConfigKey(0, 12345);
TEST(CountMetricProducerTest, TestNonDimensionalEvents) {
int64_t bucketStartTimeNs = 10000000000;
@@ -42,7 +44,7 @@
int tagId = 1;
CountMetric metric;
- metric.set_name("1");
+ metric.set_id(1);
metric.mutable_bucket()->set_bucket_size_millis(bucketSizeNs / 1000000);
LogEvent event1(tagId, bucketStartTimeNs + 1);
@@ -99,9 +101,9 @@
int64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
CountMetric metric;
- metric.set_name("1");
+ metric.set_id(1);
metric.mutable_bucket()->set_bucket_size_millis(bucketSizeNs / 1000000);
- metric.set_condition("SCREEN_ON");
+ metric.set_condition(StringToId("SCREEN_ON"));
LogEvent event1(1, bucketStartTimeNs + 1);
LogEvent event2(1, bucketStartTimeNs + 10);
@@ -137,26 +139,31 @@
int64_t bucketStartTimeNs = 10000000000;
int64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
- CountMetric metric;
- metric.set_name("1");
- metric.mutable_bucket()->set_bucket_size_millis(bucketSizeNs / 1000000);
- metric.set_condition("APP_IN_BACKGROUND_PER_UID_AND_SCREEN_ON");
- MetricConditionLink* link = metric.add_links();
- link->set_condition("APP_IN_BACKGROUND_PER_UID");
- link->add_key_in_what()->set_key(1);
- link->add_key_in_condition()->set_key(2);
+ int tagId = 1;
+ int conditionTagId = 2;
- LogEvent event1(1, bucketStartTimeNs + 1);
+ CountMetric metric;
+ metric.set_id(1);
+ metric.mutable_bucket()->set_bucket_size_millis(bucketSizeNs / 1000000);
+ metric.set_condition(StringToId("APP_IN_BACKGROUND_PER_UID_AND_SCREEN_ON"));
+ MetricConditionLink* link = metric.add_links();
+ link->set_condition(StringToId("APP_IN_BACKGROUND_PER_UID"));
+ *link->mutable_dimensions_in_what() = buildSimpleAtomFieldMatcher(tagId, 1);
+ *link->mutable_dimensions_in_condition() = buildSimpleAtomFieldMatcher(conditionTagId, 2);
+
+ LogEvent event1(tagId, bucketStartTimeNs + 1);
event1.write("111"); // uid
event1.init();
ConditionKey key1;
- key1["APP_IN_BACKGROUND_PER_UID"] = getMockedDimensionKey(2, "111");
+ key1[StringToId("APP_IN_BACKGROUND_PER_UID")] =
+ {getMockedDimensionKey(conditionTagId, 2, "111")};
- LogEvent event2(1, bucketStartTimeNs + 10);
+ LogEvent event2(tagId, bucketStartTimeNs + 10);
event2.write("222"); // uid
event2.init();
ConditionKey key2;
- key2["APP_IN_BACKGROUND_PER_UID"] = getMockedDimensionKey(2, "222");
+ key2[StringToId("APP_IN_BACKGROUND_PER_UID")] =
+ {getMockedDimensionKey(conditionTagId, 2, "222")};
sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
EXPECT_CALL(*wizard, query(_, key1)).WillOnce(Return(ConditionState::kFalse));
@@ -185,10 +192,10 @@
TEST(CountMetricProducerTest, TestAnomalyDetection) {
Alert alert;
- alert.set_name("alert");
- alert.set_metric_name("1");
+ alert.set_id(11);
+ alert.set_metric_id(1);
alert.set_trigger_if_sum_gt(2);
- alert.set_number_of_buckets(2);
+ alert.set_num_buckets(2);
alert.set_refractory_period_secs(1);
int64_t bucketStartTimeNs = 10000000000;
@@ -196,16 +203,14 @@
int64_t bucket2StartTimeNs = bucketStartTimeNs + bucketSizeNs;
int64_t bucket3StartTimeNs = bucketStartTimeNs + 2 * bucketSizeNs;
- sp<AnomalyTracker> anomalyTracker = new AnomalyTracker(alert, kConfigKey);
-
CountMetric metric;
- metric.set_name("1");
+ metric.set_id(1);
metric.mutable_bucket()->set_bucket_size_millis(bucketSizeNs / 1000000);
sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
CountMetricProducer countProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard,
bucketStartTimeNs);
- countProducer.addAnomalyTracker(anomalyTracker);
+ sp<AnomalyTracker> anomalyTracker = countProducer.addAnomalyTracker(alert);
int tagId = 1;
LogEvent event1(tagId, bucketStartTimeNs + 1);
@@ -222,13 +227,13 @@
EXPECT_EQ(1UL, countProducer.mCurrentSlicedCounter->size());
EXPECT_EQ(2L, countProducer.mCurrentSlicedCounter->begin()->second);
- EXPECT_EQ(anomalyTracker->getLastAlarmTimestampNs(), -1LL);
+ EXPECT_EQ(anomalyTracker->getLastAnomalyTimestampNs(), -1LL);
// One event in bucket #2. No alarm as bucket #0 is trashed out.
countProducer.onMatchedLogEvent(1 /*log matcher index*/, event3);
EXPECT_EQ(1UL, countProducer.mCurrentSlicedCounter->size());
EXPECT_EQ(1L, countProducer.mCurrentSlicedCounter->begin()->second);
- EXPECT_EQ(anomalyTracker->getLastAlarmTimestampNs(), -1LL);
+ EXPECT_EQ(anomalyTracker->getLastAnomalyTimestampNs(), -1LL);
// Two events in bucket #3.
countProducer.onMatchedLogEvent(1 /*log matcher index*/, event4);
@@ -237,12 +242,12 @@
EXPECT_EQ(1UL, countProducer.mCurrentSlicedCounter->size());
EXPECT_EQ(3L, countProducer.mCurrentSlicedCounter->begin()->second);
// Anomaly at event 6 is within refractory period. The alarm is at event 5 timestamp not event 6
- EXPECT_EQ(anomalyTracker->getLastAlarmTimestampNs(), (long long)event5.GetTimestampNs());
+ EXPECT_EQ(anomalyTracker->getLastAnomalyTimestampNs(), (long long)event5.GetTimestampNs());
countProducer.onMatchedLogEvent(1 /*log matcher index*/, event7);
EXPECT_EQ(1UL, countProducer.mCurrentSlicedCounter->size());
EXPECT_EQ(4L, countProducer.mCurrentSlicedCounter->begin()->second);
- EXPECT_EQ(anomalyTracker->getLastAlarmTimestampNs(), (long long)event7.GetTimestampNs());
+ EXPECT_EQ(anomalyTracker->getLastAnomalyTimestampNs(), (long long)event7.GetTimestampNs());
}
} // namespace statsd
diff --git a/cmds/statsd/tests/metrics/DurationMetricProducer_test.cpp b/cmds/statsd/tests/metrics/DurationMetricProducer_test.cpp
index 58a4ac6..a4213de 100644
--- a/cmds/statsd/tests/metrics/DurationMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/DurationMetricProducer_test.cpp
@@ -36,7 +36,7 @@
namespace os {
namespace statsd {
-const ConfigKey kConfigKey(0, "test");
+const ConfigKey kConfigKey(0, 12345);
TEST(DurationMetricTrackerTest, TestNoCondition) {
sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
@@ -44,7 +44,7 @@
uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
DurationMetric metric;
- metric.set_name("1");
+ metric.set_id(1);
metric.mutable_bucket()->set_bucket_size_millis(bucketSizeNs / 1000000);
metric.set_aggregation_type(DurationMetric_AggregationType_SUM);
@@ -52,9 +52,10 @@
LogEvent event1(tagId, bucketStartTimeNs + 1);
LogEvent event2(tagId, bucketStartTimeNs + bucketSizeNs + 2);
+ FieldMatcher dimensions;
DurationMetricProducer durationProducer(
kConfigKey, metric, -1 /*no condition*/, 1 /* start index */, 2 /* stop index */,
- 3 /* stop_all index */, false /*nesting*/, wizard, {}, bucketStartTimeNs);
+ 3 /* stop_all index */, false /*nesting*/, wizard, dimensions, bucketStartTimeNs);
durationProducer.onMatchedLogEvent(1 /* start index*/, event1);
durationProducer.onMatchedLogEvent(2 /* stop index*/, event2);
@@ -78,7 +79,7 @@
uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
DurationMetric metric;
- metric.set_name("1");
+ metric.set_id(1);
metric.mutable_bucket()->set_bucket_size_millis(bucketSizeNs / 1000000);
metric.set_aggregation_type(DurationMetric_AggregationType_SUM);
@@ -88,9 +89,10 @@
LogEvent event3(tagId, bucketStartTimeNs + bucketSizeNs + 1);
LogEvent event4(tagId, bucketStartTimeNs + bucketSizeNs + 3);
+ FieldMatcher dimensions;
DurationMetricProducer durationProducer(
kConfigKey, metric, 0 /* condition index */, 1 /* start index */, 2 /* stop index */,
- 3 /* stop_all index */, false /*nesting*/, wizard, {}, bucketStartTimeNs);
+ 3 /* stop_all index */, false /*nesting*/, wizard, dimensions, bucketStartTimeNs);
EXPECT_FALSE(durationProducer.mCondition);
EXPECT_FALSE(durationProducer.isConditionSliced());
diff --git a/cmds/statsd/tests/metrics/EventMetricProducer_test.cpp b/cmds/statsd/tests/metrics/EventMetricProducer_test.cpp
index baaac67..7171de9 100644
--- a/cmds/statsd/tests/metrics/EventMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/EventMetricProducer_test.cpp
@@ -13,7 +13,9 @@
// limitations under the License.
#include "src/metrics/EventMetricProducer.h"
+#include "src/dimension.h"
#include "metrics_test_helper.h"
+#include "tests/statsd_test_util.h"
#include <gmock/gmock.h>
#include <gtest/gtest.h>
@@ -32,7 +34,7 @@
namespace os {
namespace statsd {
-const ConfigKey kConfigKey(0, "test");
+const ConfigKey kConfigKey(0, 12345);
TEST(EventMetricProducerTest, TestNoCondition) {
uint64_t bucketStartTimeNs = 10000000000;
@@ -40,7 +42,7 @@
uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
EventMetric metric;
- metric.set_name("1");
+ metric.set_id(1);
LogEvent event1(1 /*tag id*/, bucketStartTimeNs + 1);
LogEvent event2(1 /*tag id*/, bucketStartTimeNs + 2);
@@ -63,8 +65,8 @@
uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
EventMetric metric;
- metric.set_name("1");
- metric.set_condition("SCREEN_ON");
+ metric.set_id(1);
+ metric.set_condition(StringToId("SCREEN_ON"));
LogEvent event1(1, bucketStartTimeNs + 1);
LogEvent event2(1, bucketStartTimeNs + 10);
@@ -88,25 +90,28 @@
uint64_t bucketStartTimeNs = 10000000000;
uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
- EventMetric metric;
- metric.set_name("1");
- metric.set_condition("APP_IN_BACKGROUND_PER_UID_AND_SCREEN_ON");
- MetricConditionLink* link = metric.add_links();
- link->set_condition("APP_IN_BACKGROUND_PER_UID");
- link->add_key_in_what()->set_key(1);
- link->add_key_in_condition()->set_key(2);
+ int tagId = 1;
+ int conditionTagId = 2;
- LogEvent event1(1, bucketStartTimeNs + 1);
- event1.write("111"); // uid
+ EventMetric metric;
+ metric.set_id(1);
+ metric.set_condition(StringToId("APP_IN_BACKGROUND_PER_UID_AND_SCREEN_ON"));
+ MetricConditionLink* link = metric.add_links();
+ link->set_condition(StringToId("APP_IN_BACKGROUND_PER_UID"));
+ *link->mutable_dimensions_in_what() = buildSimpleAtomFieldMatcher(tagId, 1);
+ *link->mutable_dimensions_in_condition() = buildSimpleAtomFieldMatcher(conditionTagId, 2);
+
+ LogEvent event1(tagId, bucketStartTimeNs + 1);
+ EXPECT_TRUE(event1.write("111"));
event1.init();
ConditionKey key1;
- key1["APP_IN_BACKGROUND_PER_UID"] = getMockedDimensionKey(2, "111");
+ key1[StringToId("APP_IN_BACKGROUND_PER_UID")] = {getMockedDimensionKey(conditionTagId, 2, "111")};
- LogEvent event2(1, bucketStartTimeNs + 10);
- event2.write("222"); // uid
+ LogEvent event2(tagId, bucketStartTimeNs + 10);
+ EXPECT_TRUE(event2.write("222"));
event2.init();
ConditionKey key2;
- key2["APP_IN_BACKGROUND_PER_UID"] = getMockedDimensionKey(2, "222");
+ key2[StringToId("APP_IN_BACKGROUND_PER_UID")] = {getMockedDimensionKey(conditionTagId, 2, "222")};
sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
EXPECT_CALL(*wizard, query(_, key1)).WillOnce(Return(ConditionState::kFalse));
diff --git a/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp b/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp
index 5204834..749cf26 100644
--- a/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp
@@ -15,6 +15,7 @@
#include "src/metrics/GaugeMetricProducer.h"
#include "logd/LogEvent.h"
#include "metrics_test_helper.h"
+#include "tests/statsd_test_util.h"
#include <gmock/gmock.h>
#include <gtest/gtest.h>
@@ -34,9 +35,9 @@
namespace os {
namespace statsd {
-const ConfigKey kConfigKey(0, "test");
+const ConfigKey kConfigKey(0, 12345);
const int tagId = 1;
-const string metricName = "test_metric";
+const int64_t metricId = 123;
const int64_t bucketStartTimeNs = 10000000000;
const int64_t bucketSizeNs = 60 * 1000 * 1000 * 1000LL;
const int64_t bucket2StartTimeNs = bucketStartTimeNs + bucketSizeNs;
@@ -45,9 +46,13 @@
TEST(GaugeMetricProducerTest, TestNoCondition) {
GaugeMetric metric;
- metric.set_name(metricName);
+ metric.set_id(metricId);
metric.mutable_bucket()->set_bucket_size_millis(bucketSizeNs / 1000000);
- metric.mutable_gauge_fields()->add_field_num(2);
+ metric.mutable_gauge_fields_filter()->set_include_all(false);
+ auto gaugeFieldMatcher = metric.mutable_gauge_fields_filter()->mutable_fields();
+ gaugeFieldMatcher->set_field(tagId);
+ gaugeFieldMatcher->add_child()->set_field(1);
+ gaugeFieldMatcher->add_child()->set_field(3);
sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
@@ -64,30 +69,41 @@
vector<shared_ptr<LogEvent>> allData;
allData.clear();
shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1);
- event->write(tagId);
+ event->write(10);
+ event->write("some value");
event->write(11);
event->init();
allData.push_back(event);
gaugeProducer.onDataPulled(allData);
EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size());
- EXPECT_EQ(11, gaugeProducer.mCurrentSlicedBucket->begin()->second->kv[0].value_int());
+ auto it = gaugeProducer.mCurrentSlicedBucket->begin()->second->begin();
+ EXPECT_EQ(10, it->second.value_int());
+ it++;
+ EXPECT_EQ(11, it->second.value_int());
EXPECT_EQ(0UL, gaugeProducer.mPastBuckets.size());
allData.clear();
std::shared_ptr<LogEvent> event2 =
std::make_shared<LogEvent>(tagId, bucket3StartTimeNs + 10);
- event2->write(tagId);
+ event2->write(24);
+ event2->write("some value");
event2->write(25);
event2->init();
allData.push_back(event2);
gaugeProducer.onDataPulled(allData);
EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size());
- EXPECT_EQ(25, gaugeProducer.mCurrentSlicedBucket->begin()->second->kv[0].value_int());
+ it = gaugeProducer.mCurrentSlicedBucket->begin()->second->begin();
+ EXPECT_EQ(24, it->second.value_int());
+ it++;
+ EXPECT_EQ(25, it->second.value_int());
// One dimension.
EXPECT_EQ(1UL, gaugeProducer.mPastBuckets.size());
EXPECT_EQ(1UL, gaugeProducer.mPastBuckets.begin()->second.size());
- EXPECT_EQ(11L, gaugeProducer.mPastBuckets.begin()->second.back().mEvent->kv[0].value_int());
+ it = gaugeProducer.mPastBuckets.begin()->second.back().mGaugeFields->begin();
+ EXPECT_EQ(10L, it->second.value_int());
+ it++;
+ EXPECT_EQ(11L, it->second.value_int());
EXPECT_EQ(1UL, gaugeProducer.mPastBuckets.begin()->second.back().mBucketNum);
gaugeProducer.flushIfNeededLocked(bucket4StartTimeNs);
@@ -95,16 +111,21 @@
// One dimension.
EXPECT_EQ(1UL, gaugeProducer.mPastBuckets.size());
EXPECT_EQ(2UL, gaugeProducer.mPastBuckets.begin()->second.size());
- EXPECT_EQ(25L, gaugeProducer.mPastBuckets.begin()->second.back().mEvent->kv[0].value_int());
+ it = gaugeProducer.mPastBuckets.begin()->second.back().mGaugeFields->begin();
+ EXPECT_EQ(24L, it->second.value_int());
+ it++;
+ EXPECT_EQ(25L, it->second.value_int());
EXPECT_EQ(2UL, gaugeProducer.mPastBuckets.begin()->second.back().mBucketNum);
}
TEST(GaugeMetricProducerTest, TestWithCondition) {
GaugeMetric metric;
- metric.set_name(metricName);
+ metric.set_id(metricId);
metric.mutable_bucket()->set_bucket_size_millis(bucketSizeNs / 1000000);
- metric.mutable_gauge_fields()->add_field_num(2);
- metric.set_condition("SCREEN_ON");
+ auto gaugeFieldMatcher = metric.mutable_gauge_fields_filter()->mutable_fields();
+ gaugeFieldMatcher->set_field(tagId);
+ gaugeFieldMatcher->add_child()->set_field(2);
+ metric.set_condition(StringToId("SCREEN_ON"));
sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
@@ -116,7 +137,7 @@
.WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) {
data->clear();
shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 10);
- event->write(tagId);
+ event->write("some value");
event->write(100);
event->init();
data->push_back(event);
@@ -128,28 +149,32 @@
gaugeProducer.onConditionChanged(true, bucketStartTimeNs + 8);
EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size());
- EXPECT_EQ(100, gaugeProducer.mCurrentSlicedBucket->begin()->second->kv[0].value_int());
+ EXPECT_EQ(100,
+ gaugeProducer.mCurrentSlicedBucket->begin()->second->begin()->second.value_int());
EXPECT_EQ(0UL, gaugeProducer.mPastBuckets.size());
vector<shared_ptr<LogEvent>> allData;
allData.clear();
shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1);
- event->write(1);
+ event->write("some value");
event->write(110);
event->init();
allData.push_back(event);
gaugeProducer.onDataPulled(allData);
EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size());
- EXPECT_EQ(110, gaugeProducer.mCurrentSlicedBucket->begin()->second->kv[0].value_int());
+ EXPECT_EQ(110,
+ gaugeProducer.mCurrentSlicedBucket->begin()->second->begin()->second.value_int());
EXPECT_EQ(1UL, gaugeProducer.mPastBuckets.size());
- EXPECT_EQ(100, gaugeProducer.mPastBuckets.begin()->second.back().mEvent->kv[0].value_int());
+ EXPECT_EQ(100, gaugeProducer.mPastBuckets.begin()->second.back()
+ .mGaugeFields->begin()->second.value_int());
gaugeProducer.onConditionChanged(false, bucket2StartTimeNs + 10);
gaugeProducer.flushIfNeededLocked(bucket3StartTimeNs + 10);
EXPECT_EQ(1UL, gaugeProducer.mPastBuckets.size());
EXPECT_EQ(2UL, gaugeProducer.mPastBuckets.begin()->second.size());
- EXPECT_EQ(110L, gaugeProducer.mPastBuckets.begin()->second.back().mEvent->kv[0].value_int());
+ EXPECT_EQ(110L, gaugeProducer.mPastBuckets.begin()->second.back()
+ .mGaugeFields->begin()->second.value_int());
EXPECT_EQ(1UL, gaugeProducer.mPastBuckets.begin()->second.back().mBucketNum);
}
@@ -162,61 +187,66 @@
EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, _)).WillOnce(Return());
GaugeMetric metric;
- metric.set_name(metricName);
+ metric.set_id(metricId);
metric.mutable_bucket()->set_bucket_size_millis(bucketSizeNs / 1000000);
- metric.mutable_gauge_fields()->add_field_num(2);
+ auto gaugeFieldMatcher = metric.mutable_gauge_fields_filter()->mutable_fields();
+ gaugeFieldMatcher->set_field(tagId);
+ gaugeFieldMatcher->add_child()->set_field(2);
GaugeMetricProducer gaugeProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard,
tagId, tagId, bucketStartTimeNs, pullerManager);
Alert alert;
- alert.set_name("alert");
- alert.set_metric_name(metricName);
+ alert.set_id(101);
+ alert.set_metric_id(metricId);
alert.set_trigger_if_sum_gt(25);
- alert.set_number_of_buckets(2);
- sp<AnomalyTracker> anomalyTracker = new AnomalyTracker(alert, kConfigKey);
- gaugeProducer.addAnomalyTracker(anomalyTracker);
+ alert.set_num_buckets(2);
+ sp<AnomalyTracker> anomalyTracker = gaugeProducer.addAnomalyTracker(alert);
- std::shared_ptr<LogEvent> event1 = std::make_shared<LogEvent>(1, bucketStartTimeNs + 1);
- event1->write(1);
+ int tagId = 1;
+ std::shared_ptr<LogEvent> event1 = std::make_shared<LogEvent>(tagId, bucketStartTimeNs + 1);
+ event1->write("some value");
event1->write(13);
event1->init();
gaugeProducer.onDataPulled({event1});
EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size());
- EXPECT_EQ(13L, gaugeProducer.mCurrentSlicedBucket->begin()->second->kv[0].value_int());
- EXPECT_EQ(anomalyTracker->getLastAlarmTimestampNs(), -1LL);
+ EXPECT_EQ(13L,
+ gaugeProducer.mCurrentSlicedBucket->begin()->second->begin()->second.value_int());
+ EXPECT_EQ(anomalyTracker->getLastAnomalyTimestampNs(), -1LL);
std::shared_ptr<LogEvent> event2 =
- std::make_shared<LogEvent>(1, bucketStartTimeNs + bucketSizeNs + 10);
- event2->write(1);
+ std::make_shared<LogEvent>(tagId, bucketStartTimeNs + bucketSizeNs + 10);
+ event2->write("some value");
event2->write(15);
event2->init();
gaugeProducer.onDataPulled({event2});
EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size());
- EXPECT_EQ(15L, gaugeProducer.mCurrentSlicedBucket->begin()->second->kv[0].value_int());
- EXPECT_EQ(anomalyTracker->getLastAlarmTimestampNs(), (long long)event2->GetTimestampNs());
+ EXPECT_EQ(15L,
+ gaugeProducer.mCurrentSlicedBucket->begin()->second->begin()->second.value_int());
+ EXPECT_EQ(anomalyTracker->getLastAnomalyTimestampNs(), (long long)event2->GetTimestampNs());
std::shared_ptr<LogEvent> event3 =
- std::make_shared<LogEvent>(1, bucketStartTimeNs + 2 * bucketSizeNs + 10);
- event3->write(1);
+ std::make_shared<LogEvent>(tagId, bucketStartTimeNs + 2 * bucketSizeNs + 10);
+ event3->write("some value");
event3->write(24);
event3->init();
gaugeProducer.onDataPulled({event3});
EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size());
- EXPECT_EQ(24L, gaugeProducer.mCurrentSlicedBucket->begin()->second->kv[0].value_int());
- EXPECT_EQ(anomalyTracker->getLastAlarmTimestampNs(), (long long)event3->GetTimestampNs());
+ EXPECT_EQ(24L,
+ gaugeProducer.mCurrentSlicedBucket->begin()->second->begin()->second.value_int());
+ EXPECT_EQ(anomalyTracker->getLastAnomalyTimestampNs(), (long long)event3->GetTimestampNs());
// The event4 does not have the gauge field. Thus the current bucket value is 0.
std::shared_ptr<LogEvent> event4 =
- std::make_shared<LogEvent>(1, bucketStartTimeNs + 3 * bucketSizeNs + 10);
- event4->write(1);
+ std::make_shared<LogEvent>(tagId, bucketStartTimeNs + 3 * bucketSizeNs + 10);
+ event4->write("some value");
event4->init();
gaugeProducer.onDataPulled({event4});
EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size());
- EXPECT_EQ(0, gaugeProducer.mCurrentSlicedBucket->begin()->second->kv[0].value_int());
- EXPECT_EQ(anomalyTracker->getLastAlarmTimestampNs(), (long long)event3->GetTimestampNs());
+ EXPECT_TRUE(gaugeProducer.mCurrentSlicedBucket->begin()->second->empty());
+ EXPECT_EQ(anomalyTracker->getLastAnomalyTimestampNs(), (long long)event3->GetTimestampNs());
}
} // namespace statsd
diff --git a/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp b/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp
index 7dac0fb..704a466 100644
--- a/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp
+++ b/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp
@@ -13,8 +13,9 @@
// limitations under the License.
#include "src/metrics/duration_helper/MaxDurationTracker.h"
-#include "metrics_test_helper.h"
#include "src/condition/ConditionWizard.h"
+#include "metrics_test_helper.h"
+#include "tests/statsd_test_util.h"
#include <gmock/gmock.h>
#include <gtest/gtest.h>
@@ -36,24 +37,27 @@
namespace os {
namespace statsd {
-const ConfigKey kConfigKey(0, "test");
+const ConfigKey kConfigKey(0, 12345);
-const HashableDimensionKey eventKey = getMockedDimensionKey(0, "1");
-const HashableDimensionKey conditionKey = getMockedDimensionKey(4, "1");
-const HashableDimensionKey key1 = getMockedDimensionKey(1, "1");
-const HashableDimensionKey key2 = getMockedDimensionKey(1, "2");
+const int TagId = 1;
+
+const HashableDimensionKey eventKey = getMockedDimensionKey(TagId, 0, "1");
+const std::vector<HashableDimensionKey> conditionKey = {getMockedDimensionKey(TagId, 4, "1")};
+const HashableDimensionKey key1 = getMockedDimensionKey(TagId, 1, "1");
+const HashableDimensionKey key2 = getMockedDimensionKey(TagId, 1, "2");
TEST(MaxDurationTrackerTest, TestSimpleMaxDuration) {
sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets;
ConditionKey conditionKey1;
- conditionKey1["condition"] = conditionKey;
+ conditionKey1[StringToId("condition")] = conditionKey;
uint64_t bucketStartTimeNs = 10000000000;
uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
- MaxDurationTracker tracker(kConfigKey, "metric", eventKey, wizard, -1, false, bucketStartTimeNs,
+ int64_t metricId = 1;
+ MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, false, bucketStartTimeNs,
bucketSizeNs, {});
tracker.noteStart(key1, true, bucketStartTimeNs, conditionKey1);
@@ -77,12 +81,13 @@
unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets;
ConditionKey conditionKey1;
- conditionKey1["condition"] = conditionKey;
+ conditionKey1[StringToId("condition")] = conditionKey;
uint64_t bucketStartTimeNs = 10000000000;
uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
- MaxDurationTracker tracker(kConfigKey, "metric", eventKey, wizard, -1, false, bucketStartTimeNs,
+ int64_t metricId = 1;
+ MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, false, bucketStartTimeNs,
bucketSizeNs, {});
tracker.noteStart(key1, true, bucketStartTimeNs + 1, conditionKey1);
@@ -108,12 +113,13 @@
unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets;
ConditionKey conditionKey1;
- conditionKey1["condition"] = conditionKey;
+ conditionKey1[StringToId("condition")] = conditionKey;
uint64_t bucketStartTimeNs = 10000000000;
uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
- MaxDurationTracker tracker(kConfigKey, "metric", eventKey, wizard, -1, false, bucketStartTimeNs,
+ int64_t metricId = 1;
+ MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, false, bucketStartTimeNs,
bucketSizeNs, {});
// The event starts.
@@ -139,12 +145,13 @@
unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets;
ConditionKey conditionKey1;
- conditionKey1["condition"] = conditionKey;
+ conditionKey1[StringToId("condition")] = conditionKey;
uint64_t bucketStartTimeNs = 10000000000;
uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
- MaxDurationTracker tracker(kConfigKey, "metric", eventKey, wizard, -1, true, bucketStartTimeNs,
+ int64_t metricId = 1;
+ MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, true, bucketStartTimeNs,
bucketSizeNs, {});
// 2 starts
@@ -174,8 +181,8 @@
sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
ConditionKey conditionKey1;
- HashableDimensionKey eventKey = getMockedDimensionKey(2, "maps");
- conditionKey1["APP_BACKGROUND"] = conditionKey;
+ HashableDimensionKey eventKey = getMockedDimensionKey(TagId, 2, "maps");
+ conditionKey1[StringToId("APP_BACKGROUND")] = conditionKey;
EXPECT_CALL(*wizard, query(_, conditionKey1)) // #4
.WillOnce(Return(ConditionState::kFalse));
@@ -187,7 +194,8 @@
uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
int64_t durationTimeNs = 2 * 1000;
- MaxDurationTracker tracker(kConfigKey, "metric", eventKey, wizard, 1, false, bucketStartTimeNs,
+ int64_t metricId = 1;
+ MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false, bucketStartTimeNs,
bucketSizeNs, {});
EXPECT_TRUE(tracker.mAnomalyTrackers.empty());
@@ -204,35 +212,36 @@
}
TEST(MaxDurationTrackerTest, TestAnomalyDetection) {
+ int64_t metricId = 1;
Alert alert;
- alert.set_name("alert");
- alert.set_metric_name("metric");
+ alert.set_id(101);
+ alert.set_metric_id(metricId);
alert.set_trigger_if_sum_gt(32 * NS_PER_SEC);
- alert.set_number_of_buckets(2);
+ alert.set_num_buckets(2);
alert.set_refractory_period_secs(1);
unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets;
sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
ConditionKey conditionKey1;
- conditionKey1["APP_BACKGROUND"] = conditionKey;
+ conditionKey1[StringToId("APP_BACKGROUND")] = conditionKey;
uint64_t bucketStartTimeNs = 10 * NS_PER_SEC;
uint64_t eventStartTimeNs = bucketStartTimeNs + NS_PER_SEC + 1;
uint64_t bucketSizeNs = 30 * NS_PER_SEC;
- sp<AnomalyTracker> anomalyTracker = new AnomalyTracker(alert, kConfigKey);
- MaxDurationTracker tracker(kConfigKey, "metric", eventKey, wizard, -1, true, bucketStartTimeNs,
+ sp<DurationAnomalyTracker> anomalyTracker = new DurationAnomalyTracker(alert, kConfigKey);
+ MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, true, bucketStartTimeNs,
bucketSizeNs, {anomalyTracker});
tracker.noteStart(key1, true, eventStartTimeNs, conditionKey1);
tracker.noteStop(key1, eventStartTimeNs + 10, false);
- EXPECT_EQ(anomalyTracker->mLastAlarmTimestampNs, -1);
+ EXPECT_EQ(anomalyTracker->mLastAnomalyTimestampNs, -1);
EXPECT_EQ(10LL, tracker.mDuration);
tracker.noteStart(key2, true, eventStartTimeNs + 20, conditionKey1);
tracker.flushIfNeeded(eventStartTimeNs + 2 * bucketSizeNs + 3 * NS_PER_SEC, &buckets);
tracker.noteStop(key2, eventStartTimeNs + 2 * bucketSizeNs + 3 * NS_PER_SEC, false);
EXPECT_EQ((long long)(4 * NS_PER_SEC + 1LL), tracker.mDuration);
- EXPECT_EQ(anomalyTracker->mLastAlarmTimestampNs,
+ EXPECT_EQ(anomalyTracker->mLastAnomalyTimestampNs,
(long long)(eventStartTimeNs + 2 * bucketSizeNs + 3 * NS_PER_SEC));
}
diff --git a/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp b/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp
index 9ec302f..36cdaae 100644
--- a/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp
+++ b/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp
@@ -13,8 +13,9 @@
// limitations under the License.
#include "src/metrics/duration_helper/OringDurationTracker.h"
-#include "metrics_test_helper.h"
#include "src/condition/ConditionWizard.h"
+#include "metrics_test_helper.h"
+#include "tests/statsd_test_util.h"
#include <gmock/gmock.h>
#include <gtest/gtest.h>
@@ -34,18 +35,20 @@
namespace os {
namespace statsd {
-const ConfigKey kConfigKey(0, "test");
-const HashableDimensionKey eventKey = getMockedDimensionKey(0, "event");
+const ConfigKey kConfigKey(0, 12345);
+const int TagId = 1;
+const int64_t metricId = 123;
+const HashableDimensionKey eventKey = getMockedDimensionKey(TagId, 0, "event");
-const HashableDimensionKey kConditionKey1 = getMockedDimensionKey(1, "maps");
-const HashableDimensionKey kEventKey1 = getMockedDimensionKey(2, "maps");
-const HashableDimensionKey kEventKey2 = getMockedDimensionKey(3, "maps");
+const std::vector<HashableDimensionKey> kConditionKey1 = {getMockedDimensionKey(TagId, 1, "maps")};
+const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps");
+const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps");
TEST(OringDurationTrackerTest, TestDurationOverlap) {
sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
ConditionKey key1;
- key1["APP_BACKGROUND"] = kConditionKey1;
+ key1[StringToId("APP_BACKGROUND")] = kConditionKey1;
unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets;
@@ -54,7 +57,7 @@
uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
uint64_t durationTimeNs = 2 * 1000;
- OringDurationTracker tracker(kConfigKey, "metric", eventKey, wizard, 1, false,
+ OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false,
bucketStartTimeNs, bucketSizeNs, {});
tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1);
@@ -74,7 +77,7 @@
sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
ConditionKey key1;
- key1["APP_BACKGROUND"] = kConditionKey1;
+ key1[StringToId("APP_BACKGROUND")] = kConditionKey1;
unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets;
@@ -82,7 +85,7 @@
uint64_t eventStartTimeNs = bucketStartTimeNs + 1;
uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
- OringDurationTracker tracker(kConfigKey, "metric", eventKey, wizard, 1, true, bucketStartTimeNs,
+ OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true, bucketStartTimeNs,
bucketSizeNs, {});
tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1);
@@ -101,7 +104,7 @@
sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
ConditionKey key1;
- key1["APP_BACKGROUND"] = kConditionKey1;
+ key1[StringToId("APP_BACKGROUND")] = kConditionKey1;
unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets;
@@ -109,7 +112,7 @@
uint64_t eventStartTimeNs = bucketStartTimeNs + 1;
uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
- OringDurationTracker tracker(kConfigKey, "metric", eventKey, wizard, 1, true, bucketStartTimeNs,
+ OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true, bucketStartTimeNs,
bucketSizeNs, {});
tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1);
@@ -127,7 +130,7 @@
sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
ConditionKey key1;
- key1["APP_BACKGROUND"] = kConditionKey1;
+ key1[StringToId("APP_BACKGROUND")] = kConditionKey1;
unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets;
@@ -136,7 +139,7 @@
uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
uint64_t durationTimeNs = 2 * 1000;
- OringDurationTracker tracker(kConfigKey, "metric", eventKey, wizard, 1, true, bucketStartTimeNs,
+ OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true, bucketStartTimeNs,
bucketSizeNs, {});
tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1);
@@ -162,7 +165,7 @@
sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
ConditionKey key1;
- key1["APP_BACKGROUND"] = kConditionKey1;
+ key1[StringToId("APP_BACKGROUND")] = kConditionKey1;
EXPECT_CALL(*wizard, query(_, key1)) // #4
.WillOnce(Return(ConditionState::kFalse));
@@ -174,7 +177,7 @@
uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
uint64_t durationTimeNs = 2 * 1000;
- OringDurationTracker tracker(kConfigKey, "metric", eventKey, wizard, 1, false,
+ OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false,
bucketStartTimeNs, bucketSizeNs, {});
tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1);
@@ -193,7 +196,7 @@
sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
ConditionKey key1;
- key1["APP_BACKGROUND"] = kConditionKey1;
+ key1[StringToId("APP_BACKGROUND")] = kConditionKey1;
EXPECT_CALL(*wizard, query(_, key1))
.Times(2)
@@ -207,7 +210,7 @@
uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
uint64_t durationTimeNs = 2 * 1000;
- OringDurationTracker tracker(kConfigKey, "metric", eventKey, wizard, 1, false,
+ OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false,
bucketStartTimeNs, bucketSizeNs, {});
tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1);
@@ -228,7 +231,7 @@
sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
ConditionKey key1;
- key1["APP_BACKGROUND"] = kConditionKey1;
+ key1[StringToId("APP_BACKGROUND")] = kConditionKey1;
EXPECT_CALL(*wizard, query(_, key1)) // #4
.WillOnce(Return(ConditionState::kFalse));
@@ -239,7 +242,7 @@
uint64_t eventStartTimeNs = bucketStartTimeNs + 1;
uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
- OringDurationTracker tracker(kConfigKey, "metric", eventKey, wizard, 1, true, bucketStartTimeNs,
+ OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true, bucketStartTimeNs,
bucketSizeNs, {});
tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1);
@@ -259,22 +262,22 @@
TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp) {
Alert alert;
- alert.set_name("alert");
- alert.set_metric_name("1");
+ alert.set_id(101);
+ alert.set_metric_id(1);
alert.set_trigger_if_sum_gt(40 * NS_PER_SEC);
- alert.set_number_of_buckets(2);
+ alert.set_num_buckets(2);
alert.set_refractory_period_secs(1);
unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets;
sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
ConditionKey key1;
- key1["APP_BACKGROUND"] = kConditionKey1;
+ key1[StringToId("APP_BACKGROUND")] = kConditionKey1;
uint64_t bucketStartTimeNs = 10 * NS_PER_SEC;
uint64_t eventStartTimeNs = bucketStartTimeNs + NS_PER_SEC + 1;
uint64_t bucketSizeNs = 30 * NS_PER_SEC;
- sp<AnomalyTracker> anomalyTracker = new AnomalyTracker(alert, kConfigKey);
- OringDurationTracker tracker(kConfigKey, "metric", eventKey, wizard, 1, true, bucketStartTimeNs,
+ sp<DurationAnomalyTracker> anomalyTracker = new DurationAnomalyTracker(alert, kConfigKey);
+ OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true, bucketStartTimeNs,
bucketSizeNs, {anomalyTracker});
// Nothing in the past bucket.
@@ -321,27 +324,27 @@
TEST(OringDurationTrackerTest, TestAnomalyDetection) {
Alert alert;
- alert.set_name("alert");
- alert.set_metric_name("1");
+ alert.set_id(101);
+ alert.set_metric_id(1);
alert.set_trigger_if_sum_gt(40 * NS_PER_SEC);
- alert.set_number_of_buckets(2);
+ alert.set_num_buckets(2);
alert.set_refractory_period_secs(1);
unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets;
sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
ConditionKey key1;
- key1["APP_BACKGROUND"] = kConditionKey1;
+ key1[StringToId("APP_BACKGROUND")] = kConditionKey1;
uint64_t bucketStartTimeNs = 10 * NS_PER_SEC;
uint64_t eventStartTimeNs = bucketStartTimeNs + NS_PER_SEC + 1;
uint64_t bucketSizeNs = 30 * NS_PER_SEC;
- sp<AnomalyTracker> anomalyTracker = new AnomalyTracker(alert, kConfigKey);
- OringDurationTracker tracker(kConfigKey, "metric", eventKey, wizard, 1, true /*nesting*/,
+ sp<DurationAnomalyTracker> anomalyTracker = new DurationAnomalyTracker(alert, kConfigKey);
+ OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true /*nesting*/,
bucketStartTimeNs, bucketSizeNs, {anomalyTracker});
tracker.noteStart(DEFAULT_DIMENSION_KEY, true, eventStartTimeNs, key1);
tracker.noteStop(DEFAULT_DIMENSION_KEY, eventStartTimeNs + 10, false);
- EXPECT_EQ(anomalyTracker->mLastAlarmTimestampNs, -1);
+ EXPECT_EQ(anomalyTracker->mLastAnomalyTimestampNs, -1);
EXPECT_TRUE(tracker.mStarted.empty());
EXPECT_EQ(10LL, tracker.mDuration);
@@ -355,7 +358,7 @@
tracker.noteStop(DEFAULT_DIMENSION_KEY, eventStartTimeNs + 2 * bucketSizeNs + 25, false);
EXPECT_EQ(anomalyTracker->getSumOverPastBuckets(eventKey), (long long)(bucketSizeNs));
EXPECT_EQ((long long)(eventStartTimeNs + 2 * bucketSizeNs + 25),
- anomalyTracker->mLastAlarmTimestampNs);
+ anomalyTracker->mLastAnomalyTimestampNs);
}
} // namespace statsd
diff --git a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp
index 6f117d3..15acca4 100644
--- a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp
@@ -14,6 +14,7 @@
#include "src/metrics/ValueMetricProducer.h"
#include "metrics_test_helper.h"
+#include "tests/statsd_test_util.h"
#include <gmock/gmock.h>
#include <gtest/gtest.h>
@@ -34,9 +35,9 @@
namespace os {
namespace statsd {
-const ConfigKey kConfigKey(0, "test");
+const ConfigKey kConfigKey(0, 12345);
const int tagId = 1;
-const string metricName = "test_metric";
+const int64_t metricId = 123;
const int64_t bucketStartTimeNs = 10000000000;
const int64_t bucketSizeNs = 60 * 1000 * 1000 * 1000LL;
const int64_t bucket2StartTimeNs = bucketStartTimeNs + bucketSizeNs;
@@ -48,9 +49,10 @@
*/
TEST(ValueMetricProducerTest, TestNonDimensionalEvents) {
ValueMetric metric;
- metric.set_name(metricName);
+ metric.set_id(metricId);
metric.mutable_bucket()->set_bucket_size_millis(bucketSizeNs / 1000000);
- metric.set_value_field(2);
+ metric.mutable_value_field()->set_field(tagId);
+ metric.mutable_value_field()->add_child()->set_field(2);
sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
// TODO: pending refactor of StatsPullerManager
@@ -124,10 +126,11 @@
*/
TEST(ValueMetricProducerTest, TestEventsWithNonSlicedCondition) {
ValueMetric metric;
- metric.set_name(metricName);
+ metric.set_id(metricId);
metric.mutable_bucket()->set_bucket_size_millis(bucketSizeNs / 1000000);
- metric.set_value_field(2);
- metric.set_condition("SCREEN_ON");
+ metric.mutable_value_field()->set_field(tagId);
+ metric.mutable_value_field()->add_child()->set_field(2);
+ metric.set_condition(StringToId("SCREEN_ON"));
sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
shared_ptr<MockStatsPullerManager> pullerManager =
@@ -200,9 +203,10 @@
TEST(ValueMetricProducerTest, TestPushedEventsWithoutCondition) {
ValueMetric metric;
- metric.set_name(metricName);
+ metric.set_id(metricId);
metric.mutable_bucket()->set_bucket_size_millis(bucketSizeNs / 1000000);
- metric.set_value_field(2);
+ metric.mutable_value_field()->set_field(tagId);
+ metric.mutable_value_field()->add_child()->set_field(2);
sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
shared_ptr<MockStatsPullerManager> pullerManager =
@@ -240,22 +244,22 @@
TEST(ValueMetricProducerTest, TestAnomalyDetection) {
Alert alert;
- alert.set_name("alert");
- alert.set_metric_name(metricName);
+ alert.set_id(101);
+ alert.set_metric_id(metricId);
alert.set_trigger_if_sum_gt(130);
- alert.set_number_of_buckets(2);
+ alert.set_num_buckets(2);
alert.set_refractory_period_secs(3);
- sp<AnomalyTracker> anomalyTracker = new AnomalyTracker(alert, kConfigKey);
ValueMetric metric;
- metric.set_name(metricName);
+ metric.set_id(metricId);
metric.mutable_bucket()->set_bucket_size_millis(bucketSizeNs / 1000000);
- metric.set_value_field(2);
+ metric.mutable_value_field()->set_field(tagId);
+ metric.mutable_value_field()->add_child()->set_field(2);
sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
ValueMetricProducer valueProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard,
-1 /*not pulled*/, bucketStartTimeNs);
- valueProducer.addAnomalyTracker(anomalyTracker);
+ sp<AnomalyTracker> anomalyTracker = valueProducer.addAnomalyTracker(alert);
shared_ptr<LogEvent> event1
@@ -292,23 +296,23 @@
// Two events in bucket #0.
valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event1);
valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event2);
- EXPECT_EQ(anomalyTracker->getLastAlarmTimestampNs(), -1LL); // Value sum == 30 <= 130.
+ EXPECT_EQ(anomalyTracker->getLastAnomalyTimestampNs(), -1LL); // Value sum == 30 <= 130.
// One event in bucket #2. No alarm as bucket #0 is trashed out.
valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event3);
- EXPECT_EQ(anomalyTracker->getLastAlarmTimestampNs(), -1LL); // Value sum == 130 <= 130.
+ EXPECT_EQ(anomalyTracker->getLastAnomalyTimestampNs(), -1LL); // Value sum == 130 <= 130.
// Three events in bucket #3.
valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event4);
// Anomaly at event 4 since Value sum == 131 > 130!
- EXPECT_EQ(anomalyTracker->getLastAlarmTimestampNs(), (long long)event4->GetTimestampNs());
+ EXPECT_EQ(anomalyTracker->getLastAnomalyTimestampNs(), (long long)event4->GetTimestampNs());
valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event5);
// Event 5 is within 3 sec refractory period. Thus last alarm timestamp is still event4.
- EXPECT_EQ(anomalyTracker->getLastAlarmTimestampNs(), (long long)event4->GetTimestampNs());
+ EXPECT_EQ(anomalyTracker->getLastAnomalyTimestampNs(), (long long)event4->GetTimestampNs());
valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event6);
// Anomaly at event 6 since Value sum == 160 > 130 and after refractory period.
- EXPECT_EQ(anomalyTracker->getLastAlarmTimestampNs(), (long long)event6->GetTimestampNs());
+ EXPECT_EQ(anomalyTracker->getLastAnomalyTimestampNs(), (long long)event6->GetTimestampNs());
}
} // namespace statsd
diff --git a/cmds/statsd/tests/metrics/metrics_test_helper.cpp b/cmds/statsd/tests/metrics/metrics_test_helper.cpp
index a0a854a..fc7245c 100644
--- a/cmds/statsd/tests/metrics/metrics_test_helper.cpp
+++ b/cmds/statsd/tests/metrics/metrics_test_helper.cpp
@@ -18,15 +18,12 @@
namespace os {
namespace statsd {
-HashableDimensionKey getMockedDimensionKey(int key, string value) {
- KeyValuePair pair;
- pair.set_key(key);
- pair.set_value_str(value);
-
- vector<KeyValuePair> pairs;
- pairs.push_back(pair);
-
- return HashableDimensionKey(pairs);
+HashableDimensionKey getMockedDimensionKey(int tagId, int key, string value) {
+ DimensionsValue dimensionsValue;
+ dimensionsValue.set_field(tagId);
+ dimensionsValue.mutable_value_tuple()->add_dimensions_value()->set_field(key);
+ dimensionsValue.mutable_value_tuple()->mutable_dimensions_value(0)->set_value_str(value);
+ return HashableDimensionKey(dimensionsValue);
}
} // namespace statsd
diff --git a/cmds/statsd/tests/metrics/metrics_test_helper.h b/cmds/statsd/tests/metrics/metrics_test_helper.h
index 7cb3329..23e86f9 100644
--- a/cmds/statsd/tests/metrics/metrics_test_helper.h
+++ b/cmds/statsd/tests/metrics/metrics_test_helper.h
@@ -28,7 +28,7 @@
MOCK_METHOD2(
query,
ConditionState(const int conditionIndex,
- const std::map<std::string, HashableDimensionKey>& conditionParameters));
+ const ConditionKey& conditionParameters));
};
class MockStatsPullerManager : public StatsPullerManager {
@@ -38,7 +38,7 @@
MOCK_METHOD2(Pull, bool(const int pullCode, vector<std::shared_ptr<LogEvent>>* data));
};
-HashableDimensionKey getMockedDimensionKey(int key, std::string value);
+HashableDimensionKey getMockedDimensionKey(int tagId, int key, std::string value);
} // namespace statsd
} // namespace os
diff --git a/cmds/statsd/tests/statsd_test_util.cpp b/cmds/statsd/tests/statsd_test_util.cpp
new file mode 100644
index 0000000..939dc1f
--- /dev/null
+++ b/cmds/statsd/tests/statsd_test_util.cpp
@@ -0,0 +1,324 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <gtest/gtest.h>
+#include "statsd_test_util.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+AtomMatcher CreateWakelockStateChangedAtomMatcher(const string& name,
+ WakelockStateChanged::State state) {
+ AtomMatcher atom_matcher;
+ atom_matcher.set_id(StringToId(name));
+ auto simple_atom_matcher = atom_matcher.mutable_simple_atom_matcher();
+ simple_atom_matcher->set_atom_id(android::util::WAKELOCK_STATE_CHANGED);
+ auto field_value_matcher = simple_atom_matcher->add_field_value_matcher();
+ field_value_matcher->set_field(4); // State field.
+ field_value_matcher->set_eq_int(state);
+ return atom_matcher;
+}
+
+AtomMatcher CreateAcquireWakelockAtomMatcher() {
+ return CreateWakelockStateChangedAtomMatcher("AcquireWakelock", WakelockStateChanged::ACQUIRE);
+}
+
+AtomMatcher CreateReleaseWakelockAtomMatcher() {
+ return CreateWakelockStateChangedAtomMatcher("ReleaseWakelock", WakelockStateChanged::RELEASE);
+}
+
+AtomMatcher CreateScreenStateChangedAtomMatcher(
+ const string& name, ScreenStateChanged::State state) {
+ AtomMatcher atom_matcher;
+ atom_matcher.set_id(StringToId(name));
+ auto simple_atom_matcher = atom_matcher.mutable_simple_atom_matcher();
+ simple_atom_matcher->set_atom_id(android::util::SCREEN_STATE_CHANGED);
+ auto field_value_matcher = simple_atom_matcher->add_field_value_matcher();
+ field_value_matcher->set_field(1); // State field.
+ field_value_matcher->set_eq_int(state);
+ return atom_matcher;
+}
+
+AtomMatcher CreateScreenTurnedOnAtomMatcher() {
+ return CreateScreenStateChangedAtomMatcher("ScreenTurnedOn", ScreenStateChanged::STATE_ON);
+}
+
+AtomMatcher CreateScreenTurnedOffAtomMatcher() {
+ return CreateScreenStateChangedAtomMatcher("ScreenTurnedOff", ScreenStateChanged::STATE_OFF);
+}
+
+AtomMatcher CreateSyncStateChangedAtomMatcher(
+ const string& name, SyncStateChanged::State state) {
+ AtomMatcher atom_matcher;
+ atom_matcher.set_id(StringToId(name));
+ auto simple_atom_matcher = atom_matcher.mutable_simple_atom_matcher();
+ simple_atom_matcher->set_atom_id(android::util::SYNC_STATE_CHANGED);
+ auto field_value_matcher = simple_atom_matcher->add_field_value_matcher();
+ field_value_matcher->set_field(3); // State field.
+ field_value_matcher->set_eq_int(state);
+ return atom_matcher;
+}
+
+AtomMatcher CreateSyncStartAtomMatcher() {
+ return CreateSyncStateChangedAtomMatcher("SyncStart", SyncStateChanged::ON);
+}
+
+AtomMatcher CreateSyncEndAtomMatcher() {
+ return CreateSyncStateChangedAtomMatcher("SyncEnd", SyncStateChanged::OFF);
+}
+
+AtomMatcher CreateActivityForegroundStateChangedAtomMatcher(
+ const string& name, ActivityForegroundStateChanged::Activity activity) {
+ AtomMatcher atom_matcher;
+ atom_matcher.set_id(StringToId(name));
+ auto simple_atom_matcher = atom_matcher.mutable_simple_atom_matcher();
+ simple_atom_matcher->set_atom_id(android::util::ACTIVITY_FOREGROUND_STATE_CHANGED);
+ auto field_value_matcher = simple_atom_matcher->add_field_value_matcher();
+ field_value_matcher->set_field(4); // Activity field.
+ field_value_matcher->set_eq_int(activity);
+ return atom_matcher;
+}
+
+AtomMatcher CreateMoveToBackgroundAtomMatcher() {
+ return CreateActivityForegroundStateChangedAtomMatcher(
+ "MoveToBackground", ActivityForegroundStateChanged::MOVE_TO_BACKGROUND);
+}
+
+AtomMatcher CreateMoveToForegroundAtomMatcher() {
+ return CreateActivityForegroundStateChangedAtomMatcher(
+ "MoveToForeground", ActivityForegroundStateChanged::MOVE_TO_FOREGROUND);
+}
+
+AtomMatcher CreateProcessLifeCycleStateChangedAtomMatcher(
+ const string& name, ProcessLifeCycleStateChanged::Event event) {
+ AtomMatcher atom_matcher;
+ atom_matcher.set_id(StringToId(name));
+ auto simple_atom_matcher = atom_matcher.mutable_simple_atom_matcher();
+ simple_atom_matcher->set_atom_id(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED);
+ auto field_value_matcher = simple_atom_matcher->add_field_value_matcher();
+ field_value_matcher->set_field(3); // Process state field.
+ field_value_matcher->set_eq_int(event);
+ return atom_matcher;
+}
+
+AtomMatcher CreateProcessCrashAtomMatcher() {
+ return CreateProcessLifeCycleStateChangedAtomMatcher(
+ "ProcessCrashed", ProcessLifeCycleStateChanged::PROCESS_CRASHED);
+}
+
+
+Predicate CreateScreenIsOnPredicate() {
+ Predicate predicate;
+ predicate.set_id(StringToId("ScreenIsOn"));
+ predicate.mutable_simple_predicate()->set_start(StringToId("ScreenTurnedOn"));
+ predicate.mutable_simple_predicate()->set_stop(StringToId("ScreenTurnedOff"));
+ return predicate;
+}
+
+Predicate CreateScreenIsOffPredicate() {
+ Predicate predicate;
+ predicate.set_id(StringToId("ScreenIsOff"));
+ predicate.mutable_simple_predicate()->set_start(StringToId("ScreenTurnedOff"));
+ predicate.mutable_simple_predicate()->set_stop(StringToId("ScreenTurnedOn"));
+ return predicate;
+}
+
+Predicate CreateHoldingWakelockPredicate() {
+ Predicate predicate;
+ predicate.set_id(StringToId("HoldingWakelock"));
+ predicate.mutable_simple_predicate()->set_start(StringToId("AcquireWakelock"));
+ predicate.mutable_simple_predicate()->set_stop(StringToId("ReleaseWakelock"));
+ return predicate;
+}
+
+Predicate CreateIsSyncingPredicate() {
+ Predicate predicate;
+ predicate.set_id(StringToId("IsSyncing"));
+ predicate.mutable_simple_predicate()->set_start(StringToId("SyncStart"));
+ predicate.mutable_simple_predicate()->set_stop(StringToId("SyncEnd"));
+ return predicate;
+}
+
+Predicate CreateIsInBackgroundPredicate() {
+ Predicate predicate;
+ predicate.set_id(StringToId("IsInBackground"));
+ predicate.mutable_simple_predicate()->set_start(StringToId("MoveToBackground"));
+ predicate.mutable_simple_predicate()->set_stop(StringToId("MoveToForeground"));
+ return predicate;
+}
+
+void addPredicateToPredicateCombination(const Predicate& predicate,
+ Predicate* combinationPredicate) {
+ combinationPredicate->mutable_combination()->add_predicate(predicate.id());
+}
+
+FieldMatcher CreateAttributionUidDimensions(const int atomId,
+ const std::vector<Position>& positions) {
+ FieldMatcher dimensions;
+ dimensions.set_field(atomId);
+ for (const auto position : positions) {
+ auto child = dimensions.add_child();
+ child->set_field(1);
+ child->set_position(position);
+ child->add_child()->set_field(1);
+ }
+ return dimensions;
+}
+
+FieldMatcher CreateAttributionUidAndTagDimensions(const int atomId,
+ const std::vector<Position>& positions) {
+ FieldMatcher dimensions;
+ dimensions.set_field(atomId);
+ for (const auto position : positions) {
+ auto child = dimensions.add_child();
+ child->set_field(1);
+ child->set_position(position);
+ child->add_child()->set_field(1);
+ child->add_child()->set_field(2);
+ }
+ return dimensions;
+}
+
+FieldMatcher CreateDimensions(const int atomId, const std::vector<int>& fields) {
+ FieldMatcher dimensions;
+ dimensions.set_field(atomId);
+ for (const int field : fields) {
+ dimensions.add_child()->set_field(field);
+ }
+ return dimensions;
+}
+
+std::unique_ptr<LogEvent> CreateScreenStateChangedEvent(
+ const ScreenStateChanged::State state, uint64_t timestampNs) {
+ auto event = std::make_unique<LogEvent>(android::util::SCREEN_STATE_CHANGED, timestampNs);
+ EXPECT_TRUE(event->write(state));
+ event->init();
+ return event;
+}
+
+std::unique_ptr<LogEvent> CreateWakelockStateChangedEvent(
+ const std::vector<AttributionNode>& attributions, const string& wakelockName,
+ const WakelockStateChanged::State state, uint64_t timestampNs) {
+ auto event = std::make_unique<LogEvent>(android::util::WAKELOCK_STATE_CHANGED, timestampNs);
+ event->write(attributions);
+ event->write(WakelockStateChanged::PARTIAL);
+ event->write(wakelockName);
+ event->write(state);
+ event->init();
+ return event;
+}
+
+std::unique_ptr<LogEvent> CreateAcquireWakelockEvent(
+ const std::vector<AttributionNode>& attributions,
+ const string& wakelockName, uint64_t timestampNs) {
+ return CreateWakelockStateChangedEvent(
+ attributions, wakelockName, WakelockStateChanged::ACQUIRE, timestampNs);
+}
+
+std::unique_ptr<LogEvent> CreateReleaseWakelockEvent(
+ const std::vector<AttributionNode>& attributions,
+ const string& wakelockName, uint64_t timestampNs) {
+ return CreateWakelockStateChangedEvent(
+ attributions, wakelockName, WakelockStateChanged::RELEASE, timestampNs);
+}
+
+std::unique_ptr<LogEvent> CreateActivityForegroundStateChangedEvent(
+ const int uid, const ActivityForegroundStateChanged::Activity activity, uint64_t timestampNs) {
+ auto event = std::make_unique<LogEvent>(
+ android::util::ACTIVITY_FOREGROUND_STATE_CHANGED, timestampNs);
+ event->write(uid);
+ event->write("pkg_name");
+ event->write("class_name");
+ event->write(activity);
+ event->init();
+ return event;
+}
+
+std::unique_ptr<LogEvent> CreateMoveToBackgroundEvent(const int uid, uint64_t timestampNs) {
+ return CreateActivityForegroundStateChangedEvent(
+ uid, ActivityForegroundStateChanged::MOVE_TO_BACKGROUND, timestampNs);
+}
+
+std::unique_ptr<LogEvent> CreateMoveToForegroundEvent(const int uid, uint64_t timestampNs) {
+ return CreateActivityForegroundStateChangedEvent(
+ uid, ActivityForegroundStateChanged::MOVE_TO_FOREGROUND, timestampNs);
+}
+
+std::unique_ptr<LogEvent> CreateSyncStateChangedEvent(
+ const int uid, const string& name, const SyncStateChanged::State state, uint64_t timestampNs) {
+ auto event = std::make_unique<LogEvent>(android::util::SYNC_STATE_CHANGED, timestampNs);
+ event->write(uid);
+ event->write(name);
+ event->write(state);
+ event->init();
+ return event;
+}
+
+std::unique_ptr<LogEvent> CreateSyncStartEvent(
+ const int uid, const string& name, uint64_t timestampNs){
+ return CreateSyncStateChangedEvent(uid, name, SyncStateChanged::ON, timestampNs);
+}
+
+std::unique_ptr<LogEvent> CreateSyncEndEvent(
+ const int uid, const string& name, uint64_t timestampNs) {
+ return CreateSyncStateChangedEvent(uid, name, SyncStateChanged::OFF, timestampNs);
+}
+
+std::unique_ptr<LogEvent> CreateProcessLifeCycleStateChangedEvent(
+ const int uid, const ProcessLifeCycleStateChanged::Event event, uint64_t timestampNs) {
+ auto logEvent = std::make_unique<LogEvent>(
+ android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED, timestampNs);
+ logEvent->write(uid);
+ logEvent->write("");
+ logEvent->write(event);
+ logEvent->init();
+ return logEvent;
+}
+
+std::unique_ptr<LogEvent> CreateAppCrashEvent(const int uid, uint64_t timestampNs) {
+ return CreateProcessLifeCycleStateChangedEvent(
+ uid, ProcessLifeCycleStateChanged::PROCESS_CRASHED, timestampNs);
+}
+
+sp<StatsLogProcessor> CreateStatsLogProcessor(const long timeBaseSec, const StatsdConfig& config,
+ const ConfigKey& key) {
+ sp<UidMap> uidMap = new UidMap();
+ sp<AnomalyMonitor> anomalyMonitor = new AnomalyMonitor(10); // 10 seconds
+ sp<StatsLogProcessor> processor = new StatsLogProcessor(
+ uidMap, anomalyMonitor, timeBaseSec, [](const ConfigKey&){});
+ processor->OnConfigUpdated(key, config);
+ return processor;
+}
+
+AttributionNode CreateAttribution(const int& uid, const string& tag) {
+ AttributionNode attribution;
+ attribution.set_uid(uid);
+ attribution.set_tag(tag);
+ return attribution;
+}
+
+void sortLogEventsByTimestamp(std::vector<std::unique_ptr<LogEvent>> *events) {
+ std::sort(events->begin(), events->end(),
+ [](const std::unique_ptr<LogEvent>& a, const std::unique_ptr<LogEvent>& b) {
+ return a->GetTimestampNs() < b->GetTimestampNs();
+ });
+}
+
+int64_t StringToId(const string& str) {
+ return static_cast<int64_t>(std::hash<std::string>()(str));
+}
+} // namespace statsd
+} // namespace os
+} // namespace android
\ No newline at end of file
diff --git a/cmds/statsd/tests/statsd_test_util.h b/cmds/statsd/tests/statsd_test_util.h
new file mode 100644
index 0000000..5e19da0
--- /dev/null
+++ b/cmds/statsd/tests/statsd_test_util.h
@@ -0,0 +1,128 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#pragma once
+
+#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
+#include "statslog.h"
+#include "src/logd/LogEvent.h"
+#include "src/StatsLogProcessor.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+// Create AtomMatcher proto for acquiring wakelock.
+AtomMatcher CreateAcquireWakelockAtomMatcher();
+
+// Create AtomMatcher proto for releasing wakelock.
+AtomMatcher CreateReleaseWakelockAtomMatcher() ;
+
+// Create AtomMatcher proto for screen turned on.
+AtomMatcher CreateScreenTurnedOnAtomMatcher();
+
+// Create AtomMatcher proto for screen turned off.
+AtomMatcher CreateScreenTurnedOffAtomMatcher();
+
+// Create AtomMatcher proto for app sync turned on.
+AtomMatcher CreateSyncStartAtomMatcher();
+
+// Create AtomMatcher proto for app sync turned off.
+AtomMatcher CreateSyncEndAtomMatcher();
+
+// Create AtomMatcher proto for app sync moves to background.
+AtomMatcher CreateMoveToBackgroundAtomMatcher();
+
+// Create AtomMatcher proto for app sync moves to foreground.
+AtomMatcher CreateMoveToForegroundAtomMatcher();
+
+// Create AtomMatcher proto for process crashes
+AtomMatcher CreateProcessCrashAtomMatcher() ;
+
+// Create Predicate proto for screen is on.
+Predicate CreateScreenIsOnPredicate();
+
+// Create Predicate proto for screen is off.
+Predicate CreateScreenIsOffPredicate();
+
+// Create Predicate proto for holding wakelock.
+Predicate CreateHoldingWakelockPredicate();
+
+// Create a Predicate proto for app syncing.
+Predicate CreateIsSyncingPredicate();
+
+// Create a Predicate proto for app is in background.
+Predicate CreateIsInBackgroundPredicate();
+
+// Add a predicate to the predicate combination.
+void addPredicateToPredicateCombination(const Predicate& predicate, Predicate* combination);
+
+// Create dimensions from primitive fields.
+FieldMatcher CreateDimensions(const int atomId, const std::vector<int>& fields);
+
+// Create dimensions by attribution uid and tag.
+FieldMatcher CreateAttributionUidAndTagDimensions(const int atomId,
+ const std::vector<Position>& positions);
+
+// Create dimensions by attribution uid only.
+FieldMatcher CreateAttributionUidDimensions(const int atomId,
+ const std::vector<Position>& positions);
+
+// Create log event for screen state changed.
+std::unique_ptr<LogEvent> CreateScreenStateChangedEvent(
+ const ScreenStateChanged::State state, uint64_t timestampNs);
+
+// Create log event for app moving to background.
+std::unique_ptr<LogEvent> CreateMoveToBackgroundEvent(const int uid, uint64_t timestampNs);
+
+// Create log event for app moving to foreground.
+std::unique_ptr<LogEvent> CreateMoveToForegroundEvent(const int uid, uint64_t timestampNs);
+
+// Create log event when the app sync starts.
+std::unique_ptr<LogEvent> CreateSyncStartEvent(
+ const int uid, const string& name, uint64_t timestampNs);
+
+// Create log event when the app sync ends.
+std::unique_ptr<LogEvent> CreateSyncEndEvent(
+ const int uid, const string& name, uint64_t timestampNs);
+
+// Create log event when the app sync ends.
+std::unique_ptr<LogEvent> CreateAppCrashEvent(
+ const int uid, uint64_t timestampNs);
+
+// Create log event for acquiring wakelock.
+std::unique_ptr<LogEvent> CreateAcquireWakelockEvent(
+ const std::vector<AttributionNode>& attributions,
+ const string& wakelockName, uint64_t timestampNs);
+
+// Create log event for releasing wakelock.
+std::unique_ptr<LogEvent> CreateReleaseWakelockEvent(
+ const std::vector<AttributionNode>& attributions,
+ const string& wakelockName, uint64_t timestampNs);
+
+// Helper function to create an AttributionNode proto.
+AttributionNode CreateAttribution(const int& uid, const string& tag);
+
+// Create a statsd log event processor upon the start time in seconds, config and key.
+sp<StatsLogProcessor> CreateStatsLogProcessor(const long timeBaseSec, const StatsdConfig& config,
+ const ConfigKey& key);
+
+// Util function to sort the log events by timestamp.
+void sortLogEventsByTimestamp(std::vector<std::unique_ptr<LogEvent>> *events);
+
+int64_t StringToId(const string& str);
+
+} // namespace statsd
+} // namespace os
+} // namespace android
\ No newline at end of file
diff --git a/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/DisplayProtoUtils.java b/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/DisplayProtoUtils.java
index fe3d86d..93dba71 100644
--- a/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/DisplayProtoUtils.java
+++ b/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/DisplayProtoUtils.java
@@ -26,7 +26,7 @@
sb.append("ConfigKey: ");
if (reports.hasConfigKey()) {
com.android.os.StatsLog.ConfigMetricsReportList.ConfigKey key = reports.getConfigKey();
- sb.append("\tuid: ").append(key.getUid()).append(" name: ").append(key.getName())
+ sb.append("\tuid: ").append(key.getUid()).append(" name: ").append(key.getId())
.append("\n");
}
@@ -34,7 +34,7 @@
sb.append("StatsLogReport size: ").append(report.getMetricsCount()).append("\n");
for (StatsLog.StatsLogReport log : report.getMetricsList()) {
sb.append("\n\n");
- sb.append("metric id: ").append(log.getMetricName()).append("\n");
+ sb.append("metric id: ").append(log.getMetricId()).append("\n");
sb.append("start time:").append(getDateStr(log.getStartReportNanos())).append("\n");
sb.append("end time:").append(getDateStr(log.getEndReportNanos())).append("\n");
@@ -71,20 +71,25 @@
return DateFormat.format("dd/MM hh:mm:ss", nanoSec/1000000).toString();
}
- private static void displayDimension(StringBuilder sb, List<StatsLog.KeyValuePair> pairs) {
- for (com.android.os.StatsLog.KeyValuePair kv : pairs) {
- sb.append(kv.getKey()).append(":");
- if (kv.hasValueBool()) {
- sb.append(kv.getValueBool());
- } else if (kv.hasValueFloat()) {
- sb.append(kv.getValueFloat());
- } else if (kv.hasValueInt()) {
- sb.append(kv.getValueInt());
- } else if (kv.hasValueStr()) {
- sb.append(kv.getValueStr());
+ private static void displayDimension(StringBuilder sb, StatsLog.DimensionsValue dimensionValue) {
+ sb.append(dimensionValue.getField()).append(":");
+ if (dimensionValue.hasValueBool()) {
+ sb.append(dimensionValue.getValueBool());
+ } else if (dimensionValue.hasValueFloat()) {
+ sb.append(dimensionValue.getValueFloat());
+ } else if (dimensionValue.hasValueInt()) {
+ sb.append(dimensionValue.getValueInt());
+ } else if (dimensionValue.hasValueStr()) {
+ sb.append(dimensionValue.getValueStr());
+ } else if (dimensionValue.hasValueTuple()) {
+ sb.append("{");
+ for (StatsLog.DimensionsValue child :
+ dimensionValue.getValueTuple().getDimensionsValueList()) {
+ displayDimension(sb, child);
}
- sb.append(" ");
+ sb.append("}");
}
+ sb.append(" ");
}
public static void displayDurationMetricData(StringBuilder sb, StatsLog.StatsLogReport log) {
@@ -93,7 +98,7 @@
sb.append("Dimension size: ").append(durationMetricDataWrapper.getDataCount()).append("\n");
for (StatsLog.DurationMetricData duration : durationMetricDataWrapper.getDataList()) {
sb.append("dimension: ");
- displayDimension(sb, duration.getDimensionList());
+ displayDimension(sb, duration.getDimension());
sb.append("\n");
for (StatsLog.DurationBucketInfo info : duration.getBucketInfoList()) {
@@ -120,7 +125,7 @@
sb.append("Dimension size: ").append(countMetricDataWrapper.getDataCount()).append("\n");
for (StatsLog.CountMetricData count : countMetricDataWrapper.getDataList()) {
sb.append("dimension: ");
- displayDimension(sb, count.getDimensionList());
+ displayDimension(sb, count.getDimension());
sb.append("\n");
for (StatsLog.CountBucketInfo info : count.getBucketInfoList()) {
diff --git a/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/MainActivity.java b/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/MainActivity.java
index 70dd634..119d6f5 100644
--- a/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/MainActivity.java
+++ b/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/MainActivity.java
@@ -34,6 +34,8 @@
public class MainActivity extends Activity {
private final static String TAG = "StatsdDogfood";
+ private final static long CONFIG_ID = 987654321;
+
final int[] mUids = {11111111, 2222222};
StatsManager mStatsManager;
@@ -163,7 +165,7 @@
return;
}
if (mStatsManager != null) {
- byte[] data = mStatsManager.getData("fake");
+ byte[] data = mStatsManager.getData(CONFIG_ID);
if (data != null) {
displayData(data);
} else {
@@ -186,7 +188,7 @@
byte[] config = new byte[inputStream.available()];
inputStream.read(config);
if (mStatsManager != null) {
- if (mStatsManager.addConfiguration("fake",
+ if (mStatsManager.addConfiguration(CONFIG_ID,
config, getPackageName(), MainActivity.this.getClass().getName())) {
Toast.makeText(
MainActivity.this, "Config pushed", Toast.LENGTH_LONG).show();
@@ -252,7 +254,9 @@
Log.d(TAG, "invalid pkg id");
return;
}
- StatsLog.write(StatsLog.WAKELOCK_STATE_CHANGED, mUids[id], 0, name, 1);
+ int[] uids = new int[] {mUids[id]};
+ String[] tags = new String[] {"acquire"};
+ StatsLog.write(StatsLog.WAKELOCK_STATE_CHANGED, uids, tags, 0, name, 1);
StringBuilder sb = new StringBuilder();
sb.append("StagsLog.write(10, ").append(mUids[id]).append(", ").append(0)
.append(", ").append(name).append(", 1);");
@@ -264,7 +268,9 @@
Log.d(TAG, "invalid pkg id");
return;
}
- StatsLog.write(10, mUids[id], 0, name, 0);
+ int[] uids = new int[] {mUids[id]};
+ String[] tags = new String[] {"release"};
+ StatsLog.write(10, uids, tags, 0, name, 0);
StringBuilder sb = new StringBuilder();
sb.append("StagsLog.write(10, ").append(mUids[id]).append(", ").append(0)
.append(", ").append(name).append(", 0);");
diff --git a/cmds/statsd/tools/loadtest/res/layout/activity_loadtest.xml b/cmds/statsd/tools/loadtest/res/layout/activity_loadtest.xml
index 2a254df..f10b69d 100644
--- a/cmds/statsd/tools/loadtest/res/layout/activity_loadtest.xml
+++ b/cmds/statsd/tools/loadtest/res/layout/activity_loadtest.xml
@@ -137,13 +137,54 @@
android:text="@integer/duration_default"
android:textSize="30dp"/>
</LinearLayout>
- <CheckBox
+ <CheckBox
android:id="@+id/placebo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/placebo"
android:checked="false" />
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+ <CheckBox
+ android:id="@+id/include_count"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/count"
+ android:checked="true"/>
+ <CheckBox
+ android:id="@+id/include_duration"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/duration"
+ android:checked="true"/>
+ <CheckBox
+ android:id="@+id/include_event"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/event"
+ android:checked="true"/>
+ </LinearLayout>
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+ <CheckBox
+ android:id="@+id/include_value"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/value"
+ android:checked="true"/>
+ <CheckBox
+ android:id="@+id/include_gauge"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/gauge"
+ android:checked="true"/>
+ </LinearLayout>
+
<Space
android:layout_width="1dp"
android:layout_height="30dp"/>
diff --git a/cmds/statsd/tools/loadtest/res/values/strings.xml b/cmds/statsd/tools/loadtest/res/values/strings.xml
index 522337e..d0f77c6 100644
--- a/cmds/statsd/tools/loadtest/res/values/strings.xml
+++ b/cmds/statsd/tools/loadtest/res/values/strings.xml
@@ -26,5 +26,10 @@
<string name="duration_label">test duration (mins): </string>
<string name="start">  Start  </string>
<string name="stop">  Stop  </string>
+ <string name="count"> count </string>
+ <string name="duration"> duration </string>
+ <string name="event"> event </string>
+ <string name="value"> value </string>
+ <string name="gauge"> gauge </string>
</resources>
diff --git a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/ConfigFactory.java b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/ConfigFactory.java
index 0d890fb..4bd2844 100644
--- a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/ConfigFactory.java
+++ b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/ConfigFactory.java
@@ -27,8 +27,7 @@
import com.android.internal.os.StatsdConfigProto.EventMetric;
import com.android.internal.os.StatsdConfigProto.GaugeMetric;
import com.android.internal.os.StatsdConfigProto.ValueMetric;
-import com.android.internal.os.StatsdConfigProto.KeyMatcher;
-import com.android.internal.os.StatsdConfigProto.KeyValueMatcher;
+import com.android.internal.os.StatsdConfigProto.FieldValueMatcher;
import com.android.internal.os.StatsdConfigProto.AtomMatcher;
import com.android.internal.os.StatsdConfigProto.SimplePredicate;
import com.android.internal.os.StatsdConfigProto.StatsdConfig;
@@ -42,7 +41,7 @@
* Creates StatsdConfig protos for loadtesting.
*/
public class ConfigFactory {
- public static final String CONFIG_NAME = "LOADTEST";
+ public static final long CONFIG_ID = 123456789;
private static final String TAG = "loadtest.ConfigFactory";
@@ -83,34 +82,46 @@
* @param placebo If true, only return an empty config
* @return The serialized config
*/
- public byte[] getConfig(int replication, long bucketMillis, boolean placebo) {
+ public byte[] getConfig(int replication, long bucketMillis, boolean placebo, boolean includeCount,
+ boolean includeDuration, boolean includeEvent, boolean includeValue,
+ boolean includeGauge) {
StatsdConfig.Builder config = StatsdConfig.newBuilder()
- .setName(CONFIG_NAME);
+ .setId(CONFIG_ID);
if (placebo) {
replication = 0; // Config will be empty, aside from a name.
}
int numMetrics = 0;
for (int i = 0; i < replication; i++) {
// metrics
- for (EventMetric metric : mTemplate.getEventMetricList()) {
- addEventMetric(metric, i, config);
- numMetrics++;
+ if (includeEvent) {
+ for (EventMetric metric : mTemplate.getEventMetricList()) {
+ addEventMetric(metric, i, config);
+ numMetrics++;
+ }
}
- for (CountMetric metric : mTemplate.getCountMetricList()) {
- addCountMetric(metric, i, bucketMillis, config);
- numMetrics++;
+ if (includeCount) {
+ for (CountMetric metric : mTemplate.getCountMetricList()) {
+ addCountMetric(metric, i, bucketMillis, config);
+ numMetrics++;
+ }
}
- for (DurationMetric metric : mTemplate.getDurationMetricList()) {
- addDurationMetric(metric, i, bucketMillis, config);
- numMetrics++;
+ if (includeDuration) {
+ for (DurationMetric metric : mTemplate.getDurationMetricList()) {
+ addDurationMetric(metric, i, bucketMillis, config);
+ numMetrics++;
+ }
}
- for (GaugeMetric metric : mTemplate.getGaugeMetricList()) {
- addGaugeMetric(metric, i, bucketMillis, config);
- numMetrics++;
+ if (includeGauge) {
+ for (GaugeMetric metric : mTemplate.getGaugeMetricList()) {
+ addGaugeMetric(metric, i, bucketMillis, config);
+ numMetrics++;
+ }
}
- for (ValueMetric metric : mTemplate.getValueMetricList()) {
- addValueMetric(metric, i, bucketMillis, config);
- numMetrics++;
+ if (includeValue) {
+ for (ValueMetric metric : mTemplate.getValueMetricList()) {
+ addValueMetric(metric, i, bucketMillis, config);
+ numMetrics++;
+ }
}
// predicates
for (Predicate predicate : mTemplate.getPredicateList()) {
@@ -149,7 +160,7 @@
*/
private void addEventMetric(EventMetric template, int suffix, StatsdConfig.Builder config) {
EventMetric.Builder metric = template.toBuilder()
- .setName(template.getName() + suffix)
+ .setId(template.getId() + suffix)
.setWhat(template.getWhat() + suffix);
if (template.hasCondition()) {
metric.setCondition(template.getCondition() + suffix);
@@ -175,7 +186,7 @@
private void addCountMetric(CountMetric template, int suffix, long bucketMillis,
StatsdConfig.Builder config) {
CountMetric.Builder metric = template.toBuilder()
- .setName(template.getName() + suffix)
+ .setId(template.getId() + suffix)
.setWhat(template.getWhat() + suffix);
if (template.hasCondition()) {
metric.setCondition(template.getCondition() + suffix);
@@ -196,7 +207,7 @@
private void addDurationMetric(DurationMetric template, int suffix, long bucketMillis,
StatsdConfig.Builder config) {
DurationMetric.Builder metric = template.toBuilder()
- .setName(template.getName() + suffix)
+ .setId(template.getId() + suffix)
.setWhat(template.getWhat() + suffix);
if (template.hasCondition()) {
metric.setCondition(template.getCondition() + suffix);
@@ -217,7 +228,7 @@
private void addGaugeMetric(GaugeMetric template, int suffix, long bucketMillis,
StatsdConfig.Builder config) {
GaugeMetric.Builder metric = template.toBuilder()
- .setName(template.getName() + suffix)
+ .setId(template.getId() + suffix)
.setWhat(template.getWhat() + suffix);
if (template.hasCondition()) {
metric.setCondition(template.getCondition() + suffix);
@@ -238,7 +249,7 @@
private void addValueMetric(ValueMetric template, int suffix, long bucketMillis,
StatsdConfig.Builder config) {
ValueMetric.Builder metric = template.toBuilder()
- .setName(template.getName() + suffix)
+ .setId(template.getId() + suffix)
.setWhat(template.getWhat() + suffix);
if (template.hasCondition()) {
metric.setCondition(template.getCondition() + suffix);
@@ -258,11 +269,11 @@
*/
private void addPredicate(Predicate template, int suffix, StatsdConfig.Builder config) {
Predicate.Builder predicate = template.toBuilder()
- .setName(template.getName() + suffix);
+ .setId(template.getId() + suffix);
if (template.hasCombination()) {
Predicate.Combination.Builder cb = template.getCombination().toBuilder()
.clearPredicate();
- for (String child : template.getCombination().getPredicateList()) {
+ for (long child : template.getCombination().getPredicateList()) {
cb.addPredicate(child + suffix);
}
predicate.setCombination(cb.build());
@@ -285,11 +296,11 @@
*/
private void addMatcher(AtomMatcher template, int suffix, StatsdConfig.Builder config) {
AtomMatcher.Builder matcher = template.toBuilder()
- .setName(template.getName() + suffix);
+ .setId(template.getId() + suffix);
if (template.hasCombination()) {
AtomMatcher.Combination.Builder cb = template.getCombination().toBuilder()
.clearMatcher();
- for (String child : template.getCombination().getMatcherList()) {
+ for (long child : template.getCombination().getMatcherList()) {
cb.addMatcher(child + suffix);
}
matcher.setCombination(cb);
diff --git a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/DisplayProtoUtils.java b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/DisplayProtoUtils.java
index 735a327..19087d8 100644
--- a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/DisplayProtoUtils.java
+++ b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/DisplayProtoUtils.java
@@ -26,7 +26,7 @@
sb.append("ConfigKey: ");
if (reports.hasConfigKey()) {
com.android.os.StatsLog.ConfigMetricsReportList.ConfigKey key = reports.getConfigKey();
- sb.append("\tuid: ").append(key.getUid()).append(" name: ").append(key.getName())
+ sb.append("\tuid: ").append(key.getUid()).append(" id: ").append(key.getId())
.append("\n");
}
@@ -34,7 +34,7 @@
sb.append("StatsLogReport size: ").append(report.getMetricsCount()).append("\n");
for (StatsLog.StatsLogReport log : report.getMetricsList()) {
sb.append("\n\n");
- sb.append("metric id: ").append(log.getMetricName()).append("\n");
+ sb.append("metric id: ").append(log.getMetricId()).append("\n");
sb.append("start time:").append(getDateStr(log.getStartReportNanos())).append("\n");
sb.append("end time:").append(getDateStr(log.getEndReportNanos())).append("\n");
@@ -71,20 +71,25 @@
return DateFormat.format("dd/MM hh:mm:ss", nanoSec/1000000).toString();
}
- private static void displayDimension(StringBuilder sb, List<StatsLog.KeyValuePair> pairs) {
- for (com.android.os.StatsLog.KeyValuePair kv : pairs) {
- sb.append(kv.getKey()).append(":");
- if (kv.hasValueBool()) {
- sb.append(kv.getValueBool());
- } else if (kv.hasValueFloat()) {
- sb.append(kv.getValueFloat());
- } else if (kv.hasValueInt()) {
- sb.append(kv.getValueInt());
- } else if (kv.hasValueStr()) {
- sb.append(kv.getValueStr());
+ private static void displayDimension(StringBuilder sb, StatsLog.DimensionsValue dimensionValue) {
+ sb.append(dimensionValue.getField()).append(":");
+ if (dimensionValue.hasValueBool()) {
+ sb.append(dimensionValue.getValueBool());
+ } else if (dimensionValue.hasValueFloat()) {
+ sb.append(dimensionValue.getValueFloat());
+ } else if (dimensionValue.hasValueInt()) {
+ sb.append(dimensionValue.getValueInt());
+ } else if (dimensionValue.hasValueStr()) {
+ sb.append(dimensionValue.getValueStr());
+ } else if (dimensionValue.hasValueTuple()) {
+ sb.append("{");
+ for (StatsLog.DimensionsValue child :
+ dimensionValue.getValueTuple().getDimensionsValueList()) {
+ displayDimension(sb, child);
}
- sb.append(" ");
+ sb.append("}");
}
+ sb.append(" ");
}
public static void displayDurationMetricData(StringBuilder sb, StatsLog.StatsLogReport log) {
@@ -93,7 +98,7 @@
sb.append("Dimension size: ").append(durationMetricDataWrapper.getDataCount()).append("\n");
for (StatsLog.DurationMetricData duration : durationMetricDataWrapper.getDataList()) {
sb.append("dimension: ");
- displayDimension(sb, duration.getDimensionList());
+ displayDimension(sb, duration.getDimension());
sb.append("\n");
for (StatsLog.DurationBucketInfo info : duration.getBucketInfoList()) {
@@ -120,7 +125,7 @@
sb.append("Dimension size: ").append(countMetricDataWrapper.getDataCount()).append("\n");
for (StatsLog.CountMetricData count : countMetricDataWrapper.getDataList()) {
sb.append("dimension: ");
- displayDimension(sb, count.getDimensionList());
+ displayDimension(sb, count.getDimension());
sb.append("\n");
for (StatsLog.CountBucketInfo info : count.getBucketInfoList()) {
diff --git a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/LoadtestActivity.java b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/LoadtestActivity.java
index 0a30ff8..86da16c 100644
--- a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/LoadtestActivity.java
+++ b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/LoadtestActivity.java
@@ -110,6 +110,11 @@
private EditText mDurationText;
private TextView mReportText;
private CheckBox mPlaceboCheckBox;
+ private CheckBox mCountMetricCheckBox;
+ private CheckBox mDurationMetricCheckBox;
+ private CheckBox mEventMetricCheckBox;
+ private CheckBox mValueMetricCheckBox;
+ private CheckBox mGaugeMetricCheckBox;
/** When the load test started. */
private long mStartedTimeMillis;
@@ -129,6 +134,31 @@
*/
private boolean mPlacebo;
+ /**
+ * Whether to include CountMetric in the config.
+ */
+ private boolean mIncludeCountMetric;
+
+ /**
+ * Whether to include DurationMetric in the config.
+ */
+ private boolean mIncludeDurationMetric;
+
+ /**
+ * Whether to include EventMetric in the config.
+ */
+ private boolean mIncludeEventMetric;
+
+ /**
+ * Whether to include ValueMetric in the config.
+ */
+ private boolean mIncludeValueMetric;
+
+ /**
+ * Whether to include GaugeMetric in the config.
+ */
+ private boolean mIncludeGaugeMetric;
+
/** The burst size. */
private int mBurst;
@@ -170,6 +200,7 @@
initPeriod();
initDuration();
initPlacebo();
+ initMetricWhitelist();
// Hide the keyboard outside edit texts.
findViewById(R.id.outside).setOnTouchListener(new View.OnTouchListener() {
@@ -263,7 +294,7 @@
return null;
}
if (mStatsManager != null) {
- byte[] data = mStatsManager.getData(ConfigFactory.CONFIG_NAME);
+ byte[] data = mStatsManager.getData(ConfigFactory.CONFIG_ID);
if (data != null) {
ConfigMetricsReportList reports = null;
try {
@@ -329,7 +360,9 @@
getData();
// Create a config and push it to statsd.
- if (!setConfig(mFactory.getConfig(mReplication, mBucketMins * 60 * 1000, mPlacebo))) {
+ if (!setConfig(mFactory.getConfig(mReplication, mBucketMins * 60 * 1000, mPlacebo,
+ mIncludeCountMetric, mIncludeDurationMetric, mIncludeEventMetric,
+ mIncludeValueMetric, mIncludeGaugeMetric))) {
return;
}
@@ -420,7 +453,7 @@
// TODO: Clear all configs instead of specific ones.
if (mStatsManager != null) {
if (mStarted) {
- if (!mStatsManager.removeConfiguration(ConfigFactory.CONFIG_NAME)) {
+ if (!mStatsManager.removeConfiguration(ConfigFactory.CONFIG_ID)) {
Log.d(TAG, "Removed loadtest statsd configs.");
} else {
Log.d(TAG, "Failed to remove loadtest configs.");
@@ -431,7 +464,7 @@
private boolean setConfig(byte[] config) {
if (mStatsManager != null) {
- if (mStatsManager.addConfiguration(ConfigFactory.CONFIG_NAME,
+ if (mStatsManager.addConfiguration(ConfigFactory.CONFIG_ID,
config, getPackageName(), LoadtestActivity.this.getClass().getName())) {
Log.d(TAG, "Config pushed to statsd");
return true;
@@ -548,4 +581,48 @@
}
});
}
+
+ private void initMetricWhitelist() {
+ mCountMetricCheckBox = findViewById(R.id.include_count);
+ mCountMetricCheckBox.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ mIncludeCountMetric = mCountMetricCheckBox.isChecked();
+ }
+ });
+ mDurationMetricCheckBox = findViewById(R.id.include_duration);
+ mDurationMetricCheckBox.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ mIncludeDurationMetric = mDurationMetricCheckBox.isChecked();
+ }
+ });
+ mEventMetricCheckBox = findViewById(R.id.include_event);
+ mEventMetricCheckBox.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ mIncludeEventMetric = mEventMetricCheckBox.isChecked();
+ }
+ });
+ mValueMetricCheckBox = findViewById(R.id.include_value);
+ mValueMetricCheckBox.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ mIncludeValueMetric = mValueMetricCheckBox.isChecked();
+ }
+ });
+ mGaugeMetricCheckBox = findViewById(R.id.include_gauge);
+ mGaugeMetricCheckBox.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ mIncludeGaugeMetric = mGaugeMetricCheckBox.isChecked();
+ }
+ });
+
+ mIncludeCountMetric = mCountMetricCheckBox.isChecked();
+ mIncludeDurationMetric = mDurationMetricCheckBox.isChecked();
+ mIncludeEventMetric = mEventMetricCheckBox.isChecked();
+ mIncludeValueMetric = mValueMetricCheckBox.isChecked();
+ mIncludeGaugeMetric = mGaugeMetricCheckBox.isChecked();
+ }
}
diff --git a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/ValidationRecorder.java b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/ValidationRecorder.java
index 4b614aa..d122654 100644
--- a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/ValidationRecorder.java
+++ b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/ValidationRecorder.java
@@ -59,18 +59,8 @@
Log.d(TAG, "GOT DATA");
for (ConfigMetricsReport report : reports) {
for (StatsLogReport logReport : report.getMetricsList()) {
- if (!logReport.hasMetricName()) {
+ if (!logReport.hasMetricId()) {
Log.e(TAG, "Metric missing name.");
- continue;
- }
- String metricName = logReport.getMetricName();
- if (metricName.startsWith("EVENT_BATTERY_LEVEL_CHANGES_WHILE_SCREEN_IS_ON_")) {
- validateEventBatteryLevelChangesWhileScreenIsOn(logReport);
- continue;
- }
- if (metricName.startsWith("EVENT_BATTERY_LEVEL_CHANGES_")) {
- validateEventBatteryLevelChanges(logReport);
- continue;
}
}
}
@@ -78,7 +68,7 @@
}
private void validateEventBatteryLevelChanges(StatsLogReport logReport) {
- Log.d(TAG, "Validating " + logReport.getMetricName());
+ Log.d(TAG, "Validating " + logReport.getMetricId());
if (logReport.hasEventMetrics()) {
Log.d(TAG, "Num events captured: " + logReport.getEventMetrics().getDataCount());
for (EventMetricData data : logReport.getEventMetrics().getDataList()) {
@@ -90,6 +80,6 @@
}
private void validateEventBatteryLevelChangesWhileScreenIsOn(StatsLogReport logReport) {
- Log.d(TAG, "Validating " + logReport.getMetricName());
+ Log.d(TAG, "Validating " + logReport.getMetricId());
}
}
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 1adae7a..847f91b 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -60,6 +60,7 @@
import android.os.ServiceManager;
import android.os.SystemProperties;
import android.os.UserHandle;
+import android.os.WorkSource;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.DisplayMetrics;
@@ -3911,10 +3912,10 @@
/**
* @hide
*/
- public static void noteWakeupAlarm(PendingIntent ps, int sourceUid, String sourcePkg,
- String tag) {
+ public static void noteWakeupAlarm(PendingIntent ps, WorkSource workSource, int sourceUid,
+ String sourcePkg, String tag) {
try {
- getService().noteWakeupAlarm((ps != null) ? ps.getTarget() : null,
+ getService().noteWakeupAlarm((ps != null) ? ps.getTarget() : null, workSource,
sourceUid, sourcePkg, tag);
} catch (RemoteException ex) {
}
@@ -3923,19 +3924,24 @@
/**
* @hide
*/
- public static void noteAlarmStart(PendingIntent ps, int sourceUid, String tag) {
+ public static void noteAlarmStart(PendingIntent ps, WorkSource workSource, int sourceUid,
+ String tag) {
try {
- getService().noteAlarmStart((ps != null) ? ps.getTarget() : null, sourceUid, tag);
+ getService().noteAlarmStart((ps != null) ? ps.getTarget() : null, workSource,
+ sourceUid, tag);
} catch (RemoteException ex) {
}
}
+
/**
* @hide
*/
- public static void noteAlarmFinish(PendingIntent ps, int sourceUid, String tag) {
+ public static void noteAlarmFinish(PendingIntent ps, WorkSource workSource, int sourceUid,
+ String tag) {
try {
- getService().noteAlarmFinish((ps != null) ? ps.getTarget() : null, sourceUid, tag);
+ getService().noteAlarmFinish((ps != null) ? ps.getTarget() : null, workSource,
+ sourceUid, tag);
} catch (RemoteException ex) {
}
}
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index c09403c..4c558f3 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -75,20 +75,20 @@
*/
static public void noteWakeupAlarm(PendingIntent ps, int sourceUid, String sourcePkg,
String tag) {
- ActivityManager.noteWakeupAlarm(ps, sourceUid, sourcePkg, tag);
+ ActivityManager.noteWakeupAlarm(ps, null, sourceUid, sourcePkg, tag);
}
/**
* @deprecated use ActivityManager.noteAlarmStart instead.
*/
static public void noteAlarmStart(PendingIntent ps, int sourceUid, String tag) {
- ActivityManager.noteAlarmStart(ps, sourceUid, tag);
+ ActivityManager.noteAlarmStart(ps, null, sourceUid, tag);
}
/**
* @deprecated use ActivityManager.noteAlarmFinish instead.
*/
static public void noteAlarmFinish(PendingIntent ps, int sourceUid, String tag) {
- ActivityManager.noteAlarmFinish(ps, sourceUid, tag);
+ ActivityManager.noteAlarmFinish(ps, null, sourceUid, tag);
}
}
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index a4e221a..a9e633f 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -63,6 +63,7 @@
import android.os.ParcelFileDescriptor;
import android.os.PersistableBundle;
import android.os.StrictMode;
+import android.os.WorkSource;
import android.service.voice.IVoiceInteractionSession;
import com.android.internal.app.IVoiceInteractor;
import com.android.internal.os.IResultReceiver;
@@ -86,6 +87,9 @@
// the ones in frameworks/native/libs/binder/include/binder/IActivityManager.h
// =============== Beginning of transactions used on native side as well ======================
ParcelFileDescriptor openContentUri(in String uriString);
+ void registerUidObserver(in IUidObserver observer, int which, int cutpoint,
+ String callingPackage);
+ void unregisterUidObserver(in IUidObserver observer);
// =============== End of transactions used on native side as well ============================
// Special low-level communication with activity manager.
@@ -195,7 +199,7 @@
void enterSafeMode();
boolean startNextMatchingActivity(in IBinder callingActivity,
in Intent intent, in Bundle options);
- void noteWakeupAlarm(in IIntentSender sender, int sourceUid,
+ void noteWakeupAlarm(in IIntentSender sender, in WorkSource workSource, int sourceUid,
in String sourcePkg, in String tag);
void removeContentProvider(in IBinder connection, boolean stable);
void setRequestedOrientation(in IBinder token, int requestedOrientation);
@@ -465,8 +469,8 @@
void dumpHeapFinished(in String path);
void setVoiceKeepAwake(in IVoiceInteractionSession session, boolean keepAwake);
void updateLockTaskPackages(int userId, in String[] packages);
- void noteAlarmStart(in IIntentSender sender, int sourceUid, in String tag);
- void noteAlarmFinish(in IIntentSender sender, int sourceUid, in String tag);
+ void noteAlarmStart(in IIntentSender sender, in WorkSource workSource, int sourceUid, in String tag);
+ void noteAlarmFinish(in IIntentSender sender, in WorkSource workSource, int sourceUid, in String tag);
int getPackageProcessState(in String packageName, in String callingPackage);
oneway void showLockTaskEscapeMessage(in IBinder token);
void updateDeviceOwner(in String packageName);
@@ -478,9 +482,6 @@
*/
void keyguardGoingAway(int flags);
int getUidProcessState(int uid, in String callingPackage);
- void registerUidObserver(in IUidObserver observer, int which, int cutpoint,
- String callingPackage);
- void unregisterUidObserver(in IUidObserver observer);
boolean isAssistDataAllowedOnCurrentActivity();
boolean showAssistFromActivity(in IBinder token, in Bundle args);
boolean isRootVoiceInteraction(in IBinder token);
@@ -626,9 +627,6 @@
/** Cancels the window transitions for the given task. */
void cancelTaskWindowTransition(int taskId);
- /** Cancels the thumbnail transitions for the given task. */
- void cancelTaskThumbnailTransition(int taskId);
-
/**
* @param taskId the id of the task to retrieve the sAutoapshots for
* @param reducedResolution if set, if the snapshot needs to be loaded from disk, this will load
diff --git a/core/java/android/app/IUidObserver.aidl b/core/java/android/app/IUidObserver.aidl
index 01a9cbf..ce88809 100644
--- a/core/java/android/app/IUidObserver.aidl
+++ b/core/java/android/app/IUidObserver.aidl
@@ -18,15 +18,14 @@
/** {@hide} */
oneway interface IUidObserver {
- /**
- * General report of a state change of an uid.
- *
- * @param uid The uid for which the state change is being reported.
- * @param procState The updated process state for the uid.
- * @param procStateSeq The sequence no. associated with process state change of the uid,
- * see UidRecord.procStateSeq for details.
- */
- void onUidStateChanged(int uid, int procState, long procStateSeq);
+ // WARNING: when these transactions are updated, check if they are any callers on the native
+ // side. If so, make sure they are using the correct transaction ids and arguments.
+ // If a transaction which will also be used on the native side is being inserted, add it to
+ // below block of transactions.
+
+ // Since these transactions are also called from native code, these must be kept in sync with
+ // the ones in frameworks/native/include/binder/IActivityManager.h
+ // =============== Beginning of transactions used on native side as well ======================
/**
* Report that there are no longer any processes running for a uid.
@@ -44,6 +43,18 @@
*/
void onUidIdle(int uid, boolean disabled);
+ // =============== End of transactions used on native side as well ============================
+
+ /**
+ * General report of a state change of an uid.
+ *
+ * @param uid The uid for which the state change is being reported.
+ * @param procState The updated process state for the uid.
+ * @param procStateSeq The sequence no. associated with process state change of the uid,
+ * see UidRecord.procStateSeq for details.
+ */
+ void onUidStateChanged(int uid, int procState, long procStateSeq);
+
/**
* Report when the cached state of a uid has changed.
* If true, a uid has become cached -- that is, it has some active processes that are
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 495fd3c..66cf991 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -552,8 +552,16 @@
registerService(Context.WALLPAPER_SERVICE, WallpaperManager.class,
new CachedServiceFetcher<WallpaperManager>() {
@Override
- public WallpaperManager createService(ContextImpl ctx) {
- return new WallpaperManager(ctx.getOuterContext(),
+ public WallpaperManager createService(ContextImpl ctx)
+ throws ServiceNotFoundException {
+ final IBinder b;
+ if (ctx.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.P) {
+ b = ServiceManager.getServiceOrThrow(Context.WALLPAPER_SERVICE);
+ } else {
+ b = ServiceManager.getService(Context.WALLPAPER_SERVICE);
+ }
+ IWallpaperManager service = IWallpaperManager.Stub.asInterface(b);
+ return new WallpaperManager(service, ctx.getOuterContext(),
ctx.mMainThread.getHandler());
}});
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index 3829afb..f21746c 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -286,9 +286,8 @@
private Bitmap mDefaultWallpaper;
private Handler mMainLooperHandler;
- Globals(Looper looper) {
- IBinder b = ServiceManager.getService(Context.WALLPAPER_SERVICE);
- mService = IWallpaperManager.Stub.asInterface(b);
+ Globals(IWallpaperManager service, Looper looper) {
+ mService = service;
mMainLooperHandler = new Handler(looper);
forgetLoadedWallpaper();
}
@@ -497,17 +496,17 @@
private static final Object sSync = new Object[0];
private static Globals sGlobals;
- static void initGlobals(Looper looper) {
+ static void initGlobals(IWallpaperManager service, Looper looper) {
synchronized (sSync) {
if (sGlobals == null) {
- sGlobals = new Globals(looper);
+ sGlobals = new Globals(service, looper);
}
}
}
- /*package*/ WallpaperManager(Context context, Handler handler) {
+ /*package*/ WallpaperManager(IWallpaperManager service, Context context, Handler handler) {
mContext = context;
- initGlobals(context.getMainLooper());
+ initGlobals(service, context.getMainLooper());
}
/**
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 7e80ac7..0b74741 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -1668,6 +1668,46 @@
public static final String ACTION_DEVICE_ADMIN_SERVICE
= "android.app.action.DEVICE_ADMIN_SERVICE";
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = true, prefix = {"ID_TYPE_"}, value = {
+ ID_TYPE_BASE_INFO,
+ ID_TYPE_SERIAL,
+ ID_TYPE_IMEI,
+ ID_TYPE_MEID
+ })
+ public @interface AttestationIdType {}
+
+ /**
+ * Specifies that the device should attest its manufacturer details. For use with
+ * {@link #generateKeyPair}.
+ *
+ * @see #generateKeyPair
+ */
+ public static final int ID_TYPE_BASE_INFO = 1;
+
+ /**
+ * Specifies that the device should attest its serial number. For use with
+ * {@link #generateKeyPair}.
+ *
+ * @see #generateKeyPair
+ */
+ public static final int ID_TYPE_SERIAL = 2;
+
+ /**
+ * Specifies that the device should attest its IMEI. For use with {@link #generateKeyPair}.
+ *
+ * @see #generateKeyPair
+ */
+ public static final int ID_TYPE_IMEI = 4;
+
+ /**
+ * Specifies that the device should attest its MEID. For use with {@link #generateKeyPair}.
+ *
+ * @see #generateKeyPair
+ */
+ public static final int ID_TYPE_MEID = 8;
+
/**
* Return true if the given administrator component is currently active (enabled) in the system.
*
@@ -4106,22 +4146,46 @@
* @param algorithm The key generation algorithm, see {@link java.security.KeyPairGenerator}.
* @param keySpec Specification of the key to generate, see
* {@link java.security.KeyPairGenerator}.
+ * @param idAttestationFlags A bitmask of all the identifiers that should be included in the
+ * attestation record ({@code ID_TYPE_BASE_INFO}, {@code ID_TYPE_SERIAL},
+ * {@code ID_TYPE_IMEI} and {@code ID_TYPE_MEID}), or {@code 0} if no device
+ * identification is required in the attestation record.
+ * Device owner, profile owner and their delegated certificate installer can use
+ * {@link #ID_TYPE_BASE_INFO} to request inclusion of the general device information
+ * including manufacturer, model, brand, device and product in the attestation record.
+ * Only device owner and their delegated certificate installer can use
+ * {@link #ID_TYPE_SERIAL}, {@link #ID_TYPE_IMEI} and {@link #ID_TYPE_MEID} to request
+ * unique device identifiers to be attested.
+ * <p>
+ * If any of {@link #ID_TYPE_SERIAL}, {@link #ID_TYPE_IMEI} and {@link #ID_TYPE_MEID}
+ * is set, it is implicitly assumed that {@link #ID_TYPE_BASE_INFO} is also set.
+ * <p>
+ * If any flag is specified, then an attestation challenge must be included in the
+ * {@code keySpec}.
* @return A non-null {@code AttestedKeyPair} if the key generation succeeded, null otherwise.
* @throws SecurityException if {@code admin} is not {@code null} and not a device or profile
- * owner.
- * @throws IllegalArgumentException if the alias in {@code keySpec} is empty, or if the
+ * owner. If Device ID attestation is requested (using {@link #ID_TYPE_SERIAL},
+ * {@link #ID_TYPE_IMEI} or {@link #ID_TYPE_MEID}), the caller must be the Device Owner
+ * or the Certificate Installer delegate.
+ * @throws IllegalArgumentException if the alias in {@code keySpec} is empty, if the
* algorithm specification in {@code keySpec} is not {@code RSAKeyGenParameterSpec}
- * or {@code ECGenParameterSpec}.
+ * or {@code ECGenParameterSpec}, or if Device ID attestation was requested but the
+ * {@code keySpec} does not contain an attestation challenge.
+ * @see KeyGenParameterSpec.Builder#setAttestationChallenge(byte[])
*/
public AttestedKeyPair generateKeyPair(@Nullable ComponentName admin,
- @NonNull String algorithm, @NonNull KeyGenParameterSpec keySpec) {
+ @NonNull String algorithm, @NonNull KeyGenParameterSpec keySpec,
+ @AttestationIdType int idAttestationFlags) {
throwIfParentInstance("generateKeyPair");
try {
final ParcelableKeyGenParameterSpec parcelableSpec =
new ParcelableKeyGenParameterSpec(keySpec);
KeymasterCertificateChain attestationChain = new KeymasterCertificateChain();
+
+ // Translate ID attestation flags to values used by AttestationUtils
final boolean success = mService.generateKeyPair(
- admin, mContext.getPackageName(), algorithm, parcelableSpec, attestationChain);
+ admin, mContext.getPackageName(), algorithm, parcelableSpec,
+ idAttestationFlags, attestationChain);
if (!success) {
Log.e(TAG, "Error generating key via DevicePolicyManagerService.");
return null;
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 7cf19ee..5916a62 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -173,7 +173,7 @@
boolean removeKeyPair(in ComponentName who, in String callerPackage, String alias);
boolean generateKeyPair(in ComponentName who, in String callerPackage, in String algorithm,
in ParcelableKeyGenParameterSpec keySpec,
- out KeymasterCertificateChain attestationChain);
+ in int idAttestationFlags, out KeymasterCertificateChain attestationChain);
boolean setKeyPairCertificate(in ComponentName who, in String callerPackage, in String alias,
in byte[] certBuffer, in byte[] certChainBuffer, boolean isUserSelectable);
void choosePrivateKeyAlias(int uid, in Uri uri, in String alias, IBinder aliasCallback);
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 137c169..4cedeaa 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -324,6 +324,15 @@
public static final int BIND_ADJUST_WITH_ACTIVITY = 0x0080;
/**
+ * @hide Flag for {@link #bindService}: allows binding to a service provided
+ * by an instant app. Note that the caller may not have access to the instant
+ * app providing the service which is a violation of the instant app sandbox.
+ * This flag is intended ONLY for development/testing and should be used with
+ * great care. Only the system is allowed to use this flag.
+ */
+ public static final int BIND_ALLOW_INSTANT = 0x00400000;
+
+ /**
* @hide Flag for {@link #bindService}: like {@link #BIND_NOT_FOREGROUND}, but puts it
* up in to the important background state (instead of transient).
*/
@@ -3012,7 +3021,8 @@
SYSTEM_HEALTH_SERVICE,
//@hide: INCIDENT_SERVICE,
//@hide: STATS_COMPANION_SERVICE,
- COMPANION_DEVICE_SERVICE
+ COMPANION_DEVICE_SERVICE,
+ CROSS_PROFILE_APPS_SERVICE
})
@Retention(RetentionPolicy.SOURCE)
public @interface ServiceName {}
@@ -3091,6 +3101,14 @@
* service objects between various different contexts (Activities, Applications,
* Services, Providers, etc.)
*
+ * <p>Note: Instant apps, for which {@link PackageManager#isInstantApp()} returns true,
+ * don't have access to the following system services: {@link #DEVICE_POLICY_SERVICE},
+ * {@link #FINGERPRINT_SERVICE}, {@link #SHORTCUT_SERVICE}, {@link #USB_SERVICE},
+ * {@link #WALLPAPER_SERVICE}, {@link #WIFI_P2P_SERVICE}, {@link #WIFI_SERVICE},
+ * {@link #WIFI_AWARE_SERVICE}. For these services this method will return <code>null</code>.
+ * Generally, if you are running as an instant app you should always check whether the result
+ * of this method is null.
+ *
* @param name The name of the desired service.
*
* @return The service or null if the name does not exist.
@@ -3174,6 +3192,14 @@
* Services, Providers, etc.)
* </p>
*
+ * <p>Note: Instant apps, for which {@link PackageManager#isInstantApp()} returns true,
+ * don't have access to the following system services: {@link #DEVICE_POLICY_SERVICE},
+ * {@link #FINGERPRINT_SERVICE}, {@link #SHORTCUT_SERVICE}, {@link #USB_SERVICE},
+ * {@link #WALLPAPER_SERVICE}, {@link #WIFI_P2P_SERVICE}, {@link #WIFI_SERVICE},
+ * {@link #WIFI_AWARE_SERVICE}. For these services this method will return <code>null</code>.
+ * Generally, if you are running as an instant app you should always check whether the result
+ * of this method is null.
+ *
* @param serviceClass The class of the desired service.
* @return The service or null if the class is not a supported system service.
*/
diff --git a/core/java/android/content/pm/PackageList.java b/core/java/android/content/pm/PackageList.java
new file mode 100644
index 0000000..cfd99ab
--- /dev/null
+++ b/core/java/android/content/pm/PackageList.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.pm.PackageManagerInternal.PackageListObserver;
+
+import com.android.server.LocalServices;
+
+import java.util.List;
+
+/**
+ * All of the package name installed on the system.
+ * <p>A self observable list that automatically removes the listener when it goes out of scope.
+ *
+ * @hide Only for use within the system server.
+ */
+public class PackageList implements PackageListObserver, AutoCloseable {
+ private final PackageListObserver mWrappedObserver;
+ private final List<String> mPackageNames;
+
+ /**
+ * Create a new object.
+ * <p>Ownership of the given {@link List} transfers to this object and should not
+ * be modified by the caller.
+ */
+ public PackageList(@NonNull List<String> packageNames, @Nullable PackageListObserver observer) {
+ mPackageNames = packageNames;
+ mWrappedObserver = observer;
+ }
+
+ @Override
+ public void onPackageAdded(String packageName) {
+ if (mWrappedObserver != null) {
+ mWrappedObserver.onPackageAdded(packageName);
+ }
+ }
+
+ @Override
+ public void onPackageRemoved(String packageName) {
+ if (mWrappedObserver != null) {
+ mWrappedObserver.onPackageRemoved(packageName);
+ }
+ }
+
+ @Override
+ public void close() throws Exception {
+ LocalServices.getService(PackageManagerInternal.class).removePackageListObserver(this);
+ }
+
+ /**
+ * Returns the names of packages installed on the system.
+ * <p>The list is a copy-in-time and the actual set of installed packages may differ. Real
+ * time updates to the package list are sent via the {@link PackageListObserver} callback.
+ */
+ public @NonNull List<String> getPackageNames() {
+ return mPackageNames;
+ }
+}
diff --git a/core/java/android/content/pm/PackageManagerInternal.java b/core/java/android/content/pm/PackageManagerInternal.java
index 713cd10..8ee8e10 100644
--- a/core/java/android/content/pm/PackageManagerInternal.java
+++ b/core/java/android/content/pm/PackageManagerInternal.java
@@ -53,6 +53,14 @@
@Retention(RetentionPolicy.SOURCE)
public @interface KnownPackage {}
+ /** Observer called whenever the list of packages changes */
+ public interface PackageListObserver {
+ /** A package was added to the system. */
+ void onPackageAdded(@NonNull String packageName);
+ /** A package was removed from the system. */
+ void onPackageRemoved(@NonNull String packageName);
+ }
+
/**
* Provider for package names.
*/
@@ -435,6 +443,35 @@
public abstract @Nullable PackageParser.Package getPackage(@NonNull String packageName);
/**
+ * Returns a list without a change observer.
+ *
+ * {@see #getPackageList(PackageListObserver)}
+ */
+ public @NonNull PackageList getPackageList() {
+ return getPackageList(null);
+ }
+
+ /**
+ * Returns the list of packages installed at the time of the method call.
+ * <p>The given observer is notified when the list of installed packages
+ * changes [eg. a package was installed or uninstalled]. It will not be
+ * notified if a package is updated.
+ * <p>The package list will not be updated automatically as packages are
+ * installed / uninstalled. Any changes must be handled within the observer.
+ */
+ public abstract @NonNull PackageList getPackageList(@Nullable PackageListObserver observer);
+
+ /**
+ * Removes the observer.
+ * <p>Generally not needed. {@link #getPackageList(PackageListObserver)} will automatically
+ * remove the observer.
+ * <p>Does nothing if the observer isn't currently registered.
+ * <p>Observers are notified asynchronously and it's possible for an observer to be
+ * invoked after its been removed.
+ */
+ public abstract void removePackageListObserver(@NonNull PackageListObserver observer);
+
+ /**
* Returns a package object for the disabled system package name.
*/
public abstract @Nullable PackageParser.Package getDisabledPackage(@NonNull String packageName);
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index 3a3048e..57ab18e 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -21,6 +21,7 @@
import android.hardware.camera2.impl.CameraMetadataNative;
import android.hardware.camera2.impl.PublicKey;
import android.hardware.camera2.impl.SyntheticKey;
+import android.hardware.camera2.params.SessionConfiguration;
import android.hardware.camera2.utils.TypeReference;
import android.util.Rational;
@@ -169,6 +170,7 @@
private final CameraMetadataNative mProperties;
private List<CameraCharacteristics.Key<?>> mKeys;
private List<CaptureRequest.Key<?>> mAvailableRequestKeys;
+ private List<CaptureRequest.Key<?>> mAvailableSessionKeys;
private List<CaptureResult.Key<?>> mAvailableResultKeys;
/**
@@ -251,6 +253,67 @@
}
/**
+ * <p>Returns a subset of {@link #getAvailableCaptureRequestKeys} keys that the
+ * camera device can pass as part of the capture session initialization.</p>
+ *
+ * <p>This list includes keys that are difficult to apply per-frame and
+ * can result in unexpected delays when modified during the capture session
+ * lifetime. Typical examples include parameters that require a
+ * time-consuming hardware re-configuration or internal camera pipeline
+ * change. For performance reasons we suggest clients to pass their initial
+ * values as part of {@link SessionConfiguration#setSessionParameters}. Once
+ * the camera capture session is enabled it is also recommended to avoid
+ * changing them from their initial values set in
+ * {@link SessionConfiguration#setSessionParameters }.
+ * Control over session parameters can still be exerted in capture requests
+ * but clients should be aware and expect delays during their application.
+ * An example usage scenario could look like this:</p>
+ * <ul>
+ * <li>The camera client starts by quering the session parameter key list via
+ * {@link android.hardware.camera2.CameraCharacteristics#getAvailableSessionKeys }.</li>
+ * <li>Before triggering the capture session create sequence, a capture request
+ * must be built via {@link CameraDevice#createCaptureRequest } using an
+ * appropriate template matching the particular use case.</li>
+ * <li>The client should go over the list of session parameters and check
+ * whether some of the keys listed matches with the parameters that
+ * they intend to modify as part of the first capture request.</li>
+ * <li>If there is no such match, the capture request can be passed
+ * unmodified to {@link SessionConfiguration#setSessionParameters }.</li>
+ * <li>If matches do exist, the client should update the respective values
+ * and pass the request to {@link SessionConfiguration#setSessionParameters }.</li>
+ * <li>After the capture session initialization completes the session parameter
+ * key list can continue to serve as reference when posting or updating
+ * further requests. As mentioned above further changes to session
+ * parameters should ideally be avoided, if updates are necessary
+ * however clients could expect a delay/glitch during the
+ * parameter switch.</li>
+ * </ul>
+ *
+ * <p>The list returned is not modifiable, so any attempts to modify it will throw
+ * a {@code UnsupportedOperationException}.</p>
+ *
+ * <p>Each key is only listed once in the list. The order of the keys is undefined.</p>
+ *
+ * @return List of keys that can be passed during capture session initialization. In case the
+ * camera device doesn't support such keys the list can be null.
+ */
+ @SuppressWarnings({"unchecked"})
+ public List<CaptureRequest.Key<?>> getAvailableSessionKeys() {
+ if (mAvailableSessionKeys == null) {
+ Object crKey = CaptureRequest.Key.class;
+ Class<CaptureRequest.Key<?>> crKeyTyped = (Class<CaptureRequest.Key<?>>)crKey;
+
+ int[] filterTags = get(REQUEST_AVAILABLE_SESSION_KEYS);
+ if (filterTags == null) {
+ return null;
+ }
+ mAvailableSessionKeys =
+ getAvailableKeyList(CaptureRequest.class, crKeyTyped, filterTags);
+ }
+ return mAvailableSessionKeys;
+ }
+
+ /**
* Returns the list of keys supported by this {@link CameraDevice} for querying
* with a {@link CaptureRequest}.
*
@@ -1571,6 +1634,48 @@
new Key<int[]>("android.request.availableCharacteristicsKeys", int[].class);
/**
+ * <p>A subset of the available request keys that the camera device
+ * can pass as part of the capture session initialization.</p>
+ * <p>This is a subset of android.request.availableRequestKeys which
+ * contains a list of keys that are difficult to apply per-frame and
+ * can result in unexpected delays when modified during the capture session
+ * lifetime. Typical examples include parameters that require a
+ * time-consuming hardware re-configuration or internal camera pipeline
+ * change. For performance reasons we advise clients to pass their initial
+ * values as part of {@link SessionConfiguration#setSessionParameters }. Once
+ * the camera capture session is enabled it is also recommended to avoid
+ * changing them from their initial values set in
+ * {@link SessionConfiguration#setSessionParameters }.
+ * Control over session parameters can still be exerted in capture requests
+ * but clients should be aware and expect delays during their application.
+ * An example usage scenario could look like this:</p>
+ * <ul>
+ * <li>The camera client starts by quering the session parameter key list via
+ * {@link android.hardware.camera2.CameraCharacteristics#getAvailableSessionKeys }.</li>
+ * <li>Before triggering the capture session create sequence, a capture request
+ * must be built via {@link CameraDevice#createCaptureRequest } using an
+ * appropriate template matching the particular use case.</li>
+ * <li>The client should go over the list of session parameters and check
+ * whether some of the keys listed matches with the parameters that
+ * they intend to modify as part of the first capture request.</li>
+ * <li>If there is no such match, the capture request can be passed
+ * unmodified to {@link SessionConfiguration#setSessionParameters }.</li>
+ * <li>If matches do exist, the client should update the respective values
+ * and pass the request to {@link SessionConfiguration#setSessionParameters }.</li>
+ * <li>After the capture session initialization completes the session parameter
+ * key list can continue to serve as reference when posting or updating
+ * further requests. As mentioned above further changes to session
+ * parameters should ideally be avoided, if updates are necessary
+ * however clients could expect a delay/glitch during the
+ * parameter switch.</li>
+ * </ul>
+ * <p>This key is available on all devices.</p>
+ * @hide
+ */
+ public static final Key<int[]> REQUEST_AVAILABLE_SESSION_KEYS =
+ new Key<int[]>("android.request.availableSessionKeys", int[].class);
+
+ /**
* <p>The list of image formats that are supported by this
* camera device for output streams.</p>
* <p>All camera devices will support JPEG and YUV_420_888 formats.</p>
diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java
index 55343a2..87e503d 100644
--- a/core/java/android/hardware/camera2/CameraDevice.java
+++ b/core/java/android/hardware/camera2/CameraDevice.java
@@ -26,6 +26,7 @@
import android.hardware.camera2.params.InputConfiguration;
import android.hardware.camera2.params.StreamConfigurationMap;
import android.hardware.camera2.params.OutputConfiguration;
+import android.hardware.camera2.params.SessionConfiguration;
import android.os.Handler;
import android.view.Surface;
@@ -811,6 +812,26 @@
throws CameraAccessException;
/**
+ * <p>Create a new {@link CameraCaptureSession} using a {@link SessionConfiguration} helper
+ * object that aggregates all supported parameters.</p>
+ *
+ * @param config A session configuration (see {@link SessionConfiguration}).
+ *
+ * @throws IllegalArgumentException In case the session configuration is invalid; or the output
+ * configurations are empty.
+ * @throws CameraAccessException In case the camera device is no longer connected or has
+ * encountered a fatal error.
+ * @see #createCaptureSession(List, CameraCaptureSession.StateCallback, Handler)
+ * @see #createCaptureSessionByOutputConfigurations
+ * @see #createReprocessableCaptureSession
+ * @see #createConstrainedHighSpeedCaptureSession
+ */
+ public void createCaptureSession(
+ SessionConfiguration config) throws CameraAccessException {
+ throw new UnsupportedOperationException("No default implementation");
+ }
+
+ /**
* <p>Create a {@link CaptureRequest.Builder} for new capture requests,
* initialized with template for a target use case. The settings are chosen
* to be the best options for the specific camera device, so it is not
diff --git a/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java
index 374789c..8b8bbc3 100644
--- a/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java
@@ -800,7 +800,8 @@
try {
// begin transition to unconfigured
mDeviceImpl.configureStreamsChecked(/*inputConfig*/null, /*outputs*/null,
- /*operatingMode*/ ICameraDeviceUser.NORMAL_MODE);
+ /*operatingMode*/ ICameraDeviceUser.NORMAL_MODE,
+ /*sessionParams*/ null);
} catch (CameraAccessException e) {
// OK: do not throw checked exceptions.
Log.e(TAG, mIdString + "Exception while unconfiguring outputs: ", e);
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
index 972a281..f1ffb89 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
@@ -32,6 +32,7 @@
import android.hardware.camera2.params.InputConfiguration;
import android.hardware.camera2.params.OutputConfiguration;
import android.hardware.camera2.params.ReprocessFormatsMap;
+import android.hardware.camera2.params.SessionConfiguration;
import android.hardware.camera2.params.StreamConfigurationMap;
import android.hardware.camera2.utils.SubmitInfo;
import android.hardware.camera2.utils.SurfaceUtils;
@@ -362,7 +363,7 @@
outputConfigs.add(new OutputConfiguration(s));
}
configureStreamsChecked(/*inputConfig*/null, outputConfigs,
- /*operatingMode*/ICameraDeviceUser.NORMAL_MODE);
+ /*operatingMode*/ICameraDeviceUser.NORMAL_MODE, /*sessionParams*/ null);
}
@@ -382,12 +383,13 @@
* @param outputs a list of one or more surfaces, or {@code null} to unconfigure
* @param operatingMode If the stream configuration is for a normal session,
* a constrained high speed session, or something else.
+ * @param sessionParams Session parameters.
* @return whether or not the configuration was successful
*
* @throws CameraAccessException if there were any unexpected problems during configuration
*/
public boolean configureStreamsChecked(InputConfiguration inputConfig,
- List<OutputConfiguration> outputs, int operatingMode)
+ List<OutputConfiguration> outputs, int operatingMode, CaptureRequest sessionParams)
throws CameraAccessException {
// Treat a null input the same an empty list
if (outputs == null) {
@@ -463,7 +465,11 @@
}
}
- mRemoteDevice.endConfigure(operatingMode);
+ if (sessionParams != null) {
+ mRemoteDevice.endConfigure(operatingMode, sessionParams.getNativeCopy());
+ } else {
+ mRemoteDevice.endConfigure(operatingMode, null);
+ }
success = true;
} catch (IllegalArgumentException e) {
@@ -499,7 +505,7 @@
outConfigurations.add(new OutputConfiguration(surface));
}
createCaptureSessionInternal(null, outConfigurations, callback, handler,
- /*operatingMode*/ICameraDeviceUser.NORMAL_MODE);
+ /*operatingMode*/ICameraDeviceUser.NORMAL_MODE, /*sessionParams*/ null);
}
@Override
@@ -515,7 +521,7 @@
List<OutputConfiguration> currentOutputs = new ArrayList<>(outputConfigurations);
createCaptureSessionInternal(null, currentOutputs, callback, handler,
- /*operatingMode*/ICameraDeviceUser.NORMAL_MODE);
+ /*operatingMode*/ICameraDeviceUser.NORMAL_MODE, /*sessionParams*/null);
}
@Override
@@ -535,7 +541,7 @@
outConfigurations.add(new OutputConfiguration(surface));
}
createCaptureSessionInternal(inputConfig, outConfigurations, callback, handler,
- /*operatingMode*/ICameraDeviceUser.NORMAL_MODE);
+ /*operatingMode*/ICameraDeviceUser.NORMAL_MODE, /*sessionParams*/ null);
}
@Override
@@ -563,7 +569,8 @@
currentOutputs.add(new OutputConfiguration(output));
}
createCaptureSessionInternal(inputConfig, currentOutputs,
- callback, handler, /*operatingMode*/ICameraDeviceUser.NORMAL_MODE);
+ callback, handler, /*operatingMode*/ICameraDeviceUser.NORMAL_MODE,
+ /*sessionParams*/ null);
}
@Override
@@ -574,16 +581,13 @@
throw new IllegalArgumentException(
"Output surface list must not be null and the size must be no more than 2");
}
- StreamConfigurationMap config =
- getCharacteristics().get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
- SurfaceUtils.checkConstrainedHighSpeedSurfaces(outputs, /*fpsRange*/null, config);
-
List<OutputConfiguration> outConfigurations = new ArrayList<>(outputs.size());
for (Surface surface : outputs) {
outConfigurations.add(new OutputConfiguration(surface));
}
createCaptureSessionInternal(null, outConfigurations, callback, handler,
- /*operatingMode*/ICameraDeviceUser.CONSTRAINED_HIGH_SPEED_MODE);
+ /*operatingMode*/ICameraDeviceUser.CONSTRAINED_HIGH_SPEED_MODE,
+ /*sessionParams*/ null);
}
@Override
@@ -596,13 +600,30 @@
for (OutputConfiguration output : outputs) {
currentOutputs.add(new OutputConfiguration(output));
}
- createCaptureSessionInternal(inputConfig, currentOutputs, callback, handler, operatingMode);
+ createCaptureSessionInternal(inputConfig, currentOutputs, callback, handler, operatingMode,
+ /*sessionParams*/ null);
+ }
+
+ @Override
+ public void createCaptureSession(SessionConfiguration config)
+ throws CameraAccessException {
+ if (config == null) {
+ throw new IllegalArgumentException("Invalid session configuration");
+ }
+
+ List<OutputConfiguration> outputConfigs = config.getOutputConfigurations();
+ if (outputConfigs == null) {
+ throw new IllegalArgumentException("Invalid output configurations");
+ }
+ createCaptureSessionInternal(config.getInputConfiguration(), outputConfigs,
+ config.getStateCallback(), config.getHandler(), config.getSessionType(),
+ config.getSessionParameters());
}
private void createCaptureSessionInternal(InputConfiguration inputConfig,
List<OutputConfiguration> outputConfigurations,
CameraCaptureSession.StateCallback callback, Handler handler,
- int operatingMode) throws CameraAccessException {
+ int operatingMode, CaptureRequest sessionParams) throws CameraAccessException {
synchronized(mInterfaceLock) {
if (DEBUG) {
Log.d(TAG, "createCaptureSessionInternal");
@@ -630,7 +651,7 @@
try {
// configure streams and then block until IDLE
configureSuccess = configureStreamsChecked(inputConfig, outputConfigurations,
- operatingMode);
+ operatingMode, sessionParams);
if (configureSuccess == true && inputConfig != null) {
input = mRemoteDevice.getInputSurface();
}
@@ -646,6 +667,14 @@
// Fire onConfigured if configureOutputs succeeded, fire onConfigureFailed otherwise.
CameraCaptureSessionCore newSession = null;
if (isConstrainedHighSpeed) {
+ ArrayList<Surface> surfaces = new ArrayList<>(outputConfigurations.size());
+ for (OutputConfiguration outConfig : outputConfigurations) {
+ surfaces.add(outConfig.getSurface());
+ }
+ StreamConfigurationMap config =
+ getCharacteristics().get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
+ SurfaceUtils.checkConstrainedHighSpeedSurfaces(surfaces, /*fpsRange*/null, config);
+
newSession = new CameraConstrainedHighSpeedCaptureSessionImpl(mNextSessionId++,
callback, handler, this, mDeviceHandler, configureSuccess,
mCharacteristics);
diff --git a/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java b/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java
index 0978ff8..1f4ed13 100644
--- a/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java
+++ b/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java
@@ -106,9 +106,11 @@
}
}
- public void endConfigure(int operatingMode) throws CameraAccessException {
+ public void endConfigure(int operatingMode, CameraMetadataNative sessionParams)
+ throws CameraAccessException {
try {
- mRemoteDevice.endConfigure(operatingMode);
+ mRemoteDevice.endConfigure(operatingMode, (sessionParams == null) ?
+ new CameraMetadataNative() : sessionParams);
} 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 119cca8..eccab75 100644
--- a/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java
+++ b/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java
@@ -498,7 +498,7 @@
}
@Override
- public void endConfigure(int operatingMode) {
+ public void endConfigure(int operatingMode, CameraMetadataNative sessionParams) {
if (DEBUG) {
Log.d(TAG, "endConfigure called.");
}
diff --git a/core/java/android/hardware/camera2/params/SessionConfiguration.java b/core/java/android/hardware/camera2/params/SessionConfiguration.java
new file mode 100644
index 0000000..a79a6c1
--- /dev/null
+++ b/core/java/android/hardware/camera2/params/SessionConfiguration.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.hardware.camera2.params;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.IntDef;
+import android.os.Handler;
+import android.hardware.camera2.CameraCaptureSession;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.params.InputConfiguration;
+import android.hardware.camera2.params.OutputConfiguration;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.ArrayList;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+import static com.android.internal.util.Preconditions.*;
+
+/**
+ * A helper class that aggregates all supported arguments for capture session initialization.
+ */
+public final class SessionConfiguration {
+ /**
+ * A regular session type containing instances of {@link OutputConfiguration} running
+ * at regular non high speed FPS ranges and optionally {@link InputConfiguration} for
+ * reprocessable sessions.
+ *
+ * @see CameraDevice#createCaptureSession
+ * @see CameraDevice#createReprocessableCaptureSession
+ */
+ public static final int SESSION_REGULAR = CameraDevice.SESSION_OPERATION_MODE_NORMAL;
+
+ /**
+ * A high speed session type that can only contain instances of {@link OutputConfiguration}.
+ * The outputs can run using high speed FPS ranges. Calls to {@link #setInputConfiguration}
+ * are not supported.
+ *
+ * @see CameraDevice#createConstrainedHighSpeedCaptureSession
+ */
+ public static final int SESSION_HIGH_SPEED =
+ CameraDevice.SESSION_OPERATION_MODE_CONSTRAINED_HIGH_SPEED;
+
+ /**
+ * First vendor-specific session mode
+ * @hide
+ */
+ public static final int SESSION_VENDOR_START =
+ CameraDevice.SESSION_OPERATION_MODE_VENDOR_START;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = {"SESSION_"}, value =
+ {SESSION_REGULAR,
+ SESSION_HIGH_SPEED })
+ public @interface SessionMode {};
+
+ // Camera capture session related parameters.
+ private List<OutputConfiguration> mOutputConfigurations;
+ private CameraCaptureSession.StateCallback mStateCallback;
+ private int mSessionType;
+ private Handler mHandler = null;
+ private InputConfiguration mInputConfig = null;
+ private CaptureRequest mSessionParameters = null;
+
+ /**
+ * Create a new {@link SessionConfiguration}.
+ *
+ * @param sessionType The session type.
+ * @param outputs A list of output configurations for the capture session.
+ * @param cb A state callback interface implementation.
+ * @param handler The handler on which the callback will be invoked. If it is
+ * set to null, the callback will be invoked on the current thread's
+ * {@link android.os.Looper looper}.
+ *
+ * @see #SESSION_REGULAR
+ * @see #SESSION_HIGH_SPEED
+ * @see CameraDevice#createCaptureSession(List, CameraCaptureSession.StateCallback, Handler)
+ * @see CameraDevice#createCaptureSessionByOutputConfigurations
+ * @see CameraDevice#createReprocessableCaptureSession
+ * @see CameraDevice#createConstrainedHighSpeedCaptureSession
+ */
+ public SessionConfiguration(@SessionMode int sessionType,
+ @NonNull List<OutputConfiguration> outputs,
+ @NonNull CameraCaptureSession.StateCallback cb, @Nullable Handler handler) {
+ mSessionType = sessionType;
+ mOutputConfigurations = Collections.unmodifiableList(new ArrayList<>(outputs));
+ mStateCallback = cb;
+ mHandler = handler;
+ }
+
+ /**
+ * Retrieve the type of the capture session.
+ *
+ * @return The capture session type.
+ */
+ public @SessionMode int getSessionType() {
+ return mSessionType;
+ }
+
+ /**
+ * Retrieve the {@link OutputConfiguration} list for the capture session.
+ *
+ * @return A list of output configurations for the capture session.
+ */
+ public List<OutputConfiguration> getOutputConfigurations() {
+ return mOutputConfigurations;
+ }
+
+ /**
+ * Retrieve the {@link CameraCaptureSession.StateCallback} for the capture session.
+ *
+ * @return A state callback interface implementation.
+ */
+ public CameraCaptureSession.StateCallback getStateCallback() {
+ return mStateCallback;
+ }
+
+ /**
+ * Retrieve the {@link CameraCaptureSession.StateCallback} for the capture session.
+ *
+ * @return The handler on which the callback will be invoked. If it is
+ * set to null, the callback will be invoked on the current thread's
+ * {@link android.os.Looper looper}.
+ */
+ public Handler getHandler() {
+ return mHandler;
+ }
+
+ /**
+ * Sets the {@link InputConfiguration} for a reprocessable session. Input configuration are not
+ * supported for {@link #SESSION_HIGH_SPEED}.
+ *
+ * @param input Input configuration.
+ * @throws UnsupportedOperationException In case it is called for {@link #SESSION_HIGH_SPEED}
+ * type session configuration.
+ */
+ public void setInputConfiguration(@NonNull InputConfiguration input) {
+ if (mSessionType != SESSION_HIGH_SPEED) {
+ mInputConfig = input;
+ } else {
+ throw new UnsupportedOperationException("Method not supported for high speed session" +
+ " types");
+ }
+ }
+
+ /**
+ * Retrieve the {@link InputConfiguration}.
+ *
+ * @return The capture session input configuration.
+ */
+ public InputConfiguration getInputConfiguration() {
+ return mInputConfig;
+ }
+
+ /**
+ * Sets the session wide camera parameters (see {@link CaptureRequest}). This argument can
+ * be set for every supported session type and will be passed to the camera device as part
+ * of the capture session initialization. Session parameters are a subset of the available
+ * capture request parameters (see {@link CameraCharacteristics#getAvailableSessionKeys})
+ * and their application can introduce internal camera delays. To improve camera performance
+ * it is suggested to change them sparingly within the lifetime of the capture session and
+ * to pass their initial values as part of this method.
+ *
+ * @param params A capture request that includes the initial values for any available
+ * session wide capture keys.
+ */
+ public void setSessionParameters(CaptureRequest params) {
+ mSessionParameters = params;
+ }
+
+ /**
+ * Retrieve the session wide camera parameters (see {@link CaptureRequest}).
+ *
+ * @return A capture request that includes the initial values for any available
+ * session wide capture keys.
+ */
+ public CaptureRequest getSessionParameters() {
+ return mSessionParameters;
+ }
+}
diff --git a/core/java/android/hardware/location/ContextHubClient.java b/core/java/android/hardware/location/ContextHubClient.java
index 52527ed..0a21083 100644
--- a/core/java/android/hardware/location/ContextHubClient.java
+++ b/core/java/android/hardware/location/ContextHubClient.java
@@ -15,9 +15,13 @@
*/
package android.hardware.location;
+import android.annotation.NonNull;
import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
import android.os.RemoteException;
+import com.android.internal.util.Preconditions;
+
import dalvik.system.CloseGuard;
import java.io.Closeable;
@@ -31,16 +35,12 @@
*
* @hide
*/
+@SystemApi
public class ContextHubClient implements Closeable {
/*
* The proxy to the client interface at the service.
*/
- private final IContextHubClient mClientProxy;
-
- /*
- * The callback interface associated with this client.
- */
- private final IContextHubClientCallback mCallbackInterface;
+ private IContextHubClient mClientProxy = null;
/*
* The Context Hub that this client is attached to.
@@ -51,20 +51,33 @@
private final AtomicBoolean mIsClosed = new AtomicBoolean(false);
- /* package */ ContextHubClient(
- IContextHubClient clientProxy, IContextHubClientCallback callback,
- ContextHubInfo hubInfo) {
- mClientProxy = clientProxy;
- mCallbackInterface = callback;
+ /* package */ ContextHubClient(ContextHubInfo hubInfo) {
mAttachedHub = hubInfo;
mCloseGuard.open("close");
}
/**
+ * Sets the proxy interface of the client at the service. This method should always be called
+ * by the ContextHubManager after the client is registered at the service, and should only be
+ * called once.
+ *
+ * @param clientProxy the proxy of the client at the service
+ */
+ /* package */ void setClientProxy(IContextHubClient clientProxy) {
+ Preconditions.checkNotNull(clientProxy, "IContextHubClient cannot be null");
+ if (mClientProxy != null) {
+ throw new IllegalStateException("Cannot change client proxy multiple times");
+ }
+
+ mClientProxy = clientProxy;
+ }
+
+ /**
* Returns the hub that this client is attached to.
*
* @return the ContextHubInfo of the attached hub
*/
+ @NonNull
public ContextHubInfo getAttachedHub() {
return mAttachedHub;
}
@@ -96,12 +109,16 @@
*
* @return the result of sending the message defined as in ContextHubTransaction.Result
*
+ * @throws NullPointerException if NanoAppMessage is null
+ *
* @see NanoAppMessage
* @see ContextHubTransaction.Result
*/
@RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
@ContextHubTransaction.Result
- public int sendMessageToNanoApp(NanoAppMessage message) {
+ public int sendMessageToNanoApp(@NonNull NanoAppMessage message) {
+ Preconditions.checkNotNull(message, "NanoAppMessage cannot be null");
+
try {
return mClientProxy.sendMessageToNanoApp(message);
} catch (RemoteException e) {
diff --git a/core/java/android/hardware/location/ContextHubClientCallback.java b/core/java/android/hardware/location/ContextHubClientCallback.java
index ab19d54..cc2fe65 100644
--- a/core/java/android/hardware/location/ContextHubClientCallback.java
+++ b/core/java/android/hardware/location/ContextHubClientCallback.java
@@ -15,15 +15,20 @@
*/
package android.hardware.location;
+import android.annotation.SystemApi;
+
+import java.util.concurrent.Executor;
+
/**
* A class for {@link android.hardware.location.ContextHubClient ContextHubClient} to
* receive messages and life-cycle events from nanoapps in the Context Hub at which the client is
* attached to.
*
- * This callback is registered through the
- * {@link android.hardware.location.ContextHubManager#createClient() creation} of
- * {@link android.hardware.location.ContextHubClient ContextHubClient}. Callbacks are
- * invoked in the following ways:
+ * This callback is registered through the {@link
+ * android.hardware.location.ContextHubManager#createClient(
+ * ContextHubInfo, ContextHubClientCallback, Executor) creation} of
+ * {@link android.hardware.location.ContextHubClient ContextHubClient}. Callbacks are invoked in
+ * the following ways:
* 1) Messages from nanoapps delivered through onMessageFromNanoApp may either be broadcasted
* or targeted to a specific client.
* 2) Nanoapp or Context Hub events (the remaining callbacks) are broadcasted to all clients, and
@@ -31,6 +36,7 @@
*
* @hide
*/
+@SystemApi
public class ContextHubClientCallback {
/**
* Callback invoked when receiving a message from a nanoapp.
@@ -38,48 +44,56 @@
* The message contents of this callback may either be broadcasted or targeted to the
* client receiving the invocation.
*
+ * @param client the client that is associated with this callback
* @param message the message sent by the nanoapp
*/
- public void onMessageFromNanoApp(NanoAppMessage message) {}
+ public void onMessageFromNanoApp(ContextHubClient client, NanoAppMessage message) {}
/**
* Callback invoked when the attached Context Hub has reset.
+ *
+ * @param client the client that is associated with this callback
*/
- public void onHubReset() {}
+ public void onHubReset(ContextHubClient client) {}
/**
* Callback invoked when a nanoapp aborts at the attached Context Hub.
*
+ * @param client the client that is associated with this callback
* @param nanoAppId the ID of the nanoapp that had aborted
* @param abortCode the reason for nanoapp's abort, specific to each nanoapp
*/
- public void onNanoAppAborted(long nanoAppId, int abortCode) {}
+ public void onNanoAppAborted(ContextHubClient client, long nanoAppId, int abortCode) {}
/**
* Callback invoked when a nanoapp is loaded at the attached Context Hub.
*
+ * @param client the client that is associated with this callback
* @param nanoAppId the ID of the nanoapp that had been loaded
*/
- public void onNanoAppLoaded(long nanoAppId) {}
+ public void onNanoAppLoaded(ContextHubClient client, long nanoAppId) {}
/**
* Callback invoked when a nanoapp is unloaded from the attached Context Hub.
*
+ * @param client the client that is associated with this callback
* @param nanoAppId the ID of the nanoapp that had been unloaded
*/
- public void onNanoAppUnloaded(long nanoAppId) {}
+ public void onNanoAppUnloaded(ContextHubClient client, long nanoAppId) {}
/**
* Callback invoked when a nanoapp is enabled at the attached Context Hub.
*
+ * @param client the client that is associated with this callback
* @param nanoAppId the ID of the nanoapp that had been enabled
*/
- public void onNanoAppEnabled(long nanoAppId) {}
+ public void onNanoAppEnabled(ContextHubClient client, long nanoAppId) {}
/**
* Callback invoked when a nanoapp is disabled at the attached Context Hub.
*
+ * @param client the client that is associated with this callback
* @param nanoAppId the ID of the nanoapp that had been disabled
*/
- public void onNanoAppDisabled(long nanoAppId) {}
+ public void onNanoAppDisabled(ContextHubClient client, long nanoAppId) {}
}
diff --git a/core/java/android/hardware/location/ContextHubInfo.java b/core/java/android/hardware/location/ContextHubInfo.java
index c2b2800..36123e3 100644
--- a/core/java/android/hardware/location/ContextHubInfo.java
+++ b/core/java/android/hardware/location/ContextHubInfo.java
@@ -221,9 +221,6 @@
/**
* @return the CHRE platform ID as defined in chre/version.h
- *
- * TODO(b/67734082): Expose as public API
- * @hide
*/
public long getChrePlatformId() {
return mChrePlatformId;
@@ -231,9 +228,6 @@
/**
* @return the CHRE API's major version as defined in chre/version.h
- *
- * TODO(b/67734082): Expose as public API
- * @hide
*/
public byte getChreApiMajorVersion() {
return mChreApiMajorVersion;
@@ -241,9 +235,6 @@
/**
* @return the CHRE API's minor version as defined in chre/version.h
- *
- * TODO(b/67734082): Expose as public API
- * @hide
*/
public byte getChreApiMinorVersion() {
return mChreApiMinorVersion;
@@ -251,9 +242,6 @@
/**
* @return the CHRE patch version as defined in chre/version.h
- *
- * TODO(b/67734082): Expose as public API
- * @hide
*/
public short getChrePatchVersion() {
return mChrePatchVersion;
diff --git a/core/java/android/hardware/location/ContextHubManager.java b/core/java/android/hardware/location/ContextHubManager.java
index b7ce875..be1efde 100644
--- a/core/java/android/hardware/location/ContextHubManager.java
+++ b/core/java/android/hardware/location/ContextHubManager.java
@@ -15,6 +15,8 @@
*/
package android.hardware.location;
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SuppressLint;
@@ -22,13 +24,17 @@
import android.annotation.SystemService;
import android.content.Context;
import android.os.Handler;
+import android.os.HandlerExecutor;
import android.os.Looper;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.ServiceManager.ServiceNotFoundException;
import android.util.Log;
+import com.android.internal.util.Preconditions;
+
import java.util.List;
+import java.util.concurrent.Executor;
/**
* A class that exposes the Context hubs on a device to applications.
@@ -56,7 +62,11 @@
/**
* An interface to receive asynchronous communication from the context hub.
+ *
+ * @deprecated Use the more refined {@link android.hardware.location.ContextHubClientCallback}
+ * instead for notification callbacks.
*/
+ @Deprecated
public abstract static class Callback {
protected Callback() {}
@@ -72,7 +82,7 @@
public abstract void onMessageReceipt(
int hubHandle,
int nanoAppHandle,
- ContextHubMessage message);
+ @NonNull ContextHubMessage message);
}
/**
@@ -95,8 +105,13 @@
/**
* Get a handle to all the context hubs in the system
+ *
* @return array of context hub handles
+ *
+ * @deprecated Use {@link #getContextHubs()} instead. The use of handles are deprecated in the
+ * new APIs.
*/
+ @Deprecated
@RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
public int[] getContextHubHandles() {
try {
@@ -113,7 +128,11 @@
* @return ContextHubInfo Information about the requested context hub.
*
* @see ContextHubInfo
+ *
+ * @deprecated Use {@link #getContextHubs()} instead. The use of handles are deprecated in the
+ * new APIs.
*/
+ @Deprecated
@RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
public ContextHubInfo getContextHubInfo(int hubHandle) {
try {
@@ -141,9 +160,12 @@
* -1 otherwise
*
* @see NanoApp
+ *
+ * @deprecated Use {@link #loadNanoApp(ContextHubInfo, NanoAppBinary)} instead.
*/
+ @Deprecated
@RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
- public int loadNanoApp(int hubHandle, NanoApp app) {
+ public int loadNanoApp(int hubHandle, @NonNull NanoApp app) {
try {
return mService.loadNanoApp(hubHandle, app);
} catch (RemoteException e) {
@@ -165,7 +187,10 @@
*
* @return 0 if the command for unloading was sent to the context hub;
* -1 otherwise
+ *
+ * @deprecated Use {@link #unloadNanoApp(ContextHubInfo, long)} instead.
*/
+ @Deprecated
@RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
public int unloadNanoApp(int nanoAppHandle) {
try {
@@ -196,13 +221,18 @@
* TODO(b/30943489): Have the returned NanoAppInstanceInfo contain the
* correct information.
*
- * @param nanoAppHandle handle of the nanoAppInstance
- * @return NanoAppInstanceInfo Information about the nano app instance.
+ * @param nanoAppHandle handle of the nanoapp instance
+ * @return NanoAppInstanceInfo the NanoAppInstanceInfo of the nanoapp, or null if the nanoapp
+ * does not exist
*
* @see NanoAppInstanceInfo
+ *
+ * @deprecated Use {@link #queryNanoApps(ContextHubInfo)} instead to explicitly query the hub
+ * for loaded nanoapps.
*/
+ @Deprecated
@RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
- public NanoAppInstanceInfo getNanoAppInstanceInfo(int nanoAppHandle) {
+ @Nullable public NanoAppInstanceInfo getNanoAppInstanceInfo(int nanoAppHandle) {
try {
return mService.getNanoAppInstanceInfo(nanoAppHandle);
} catch (RemoteException e) {
@@ -219,9 +249,13 @@
* @see NanoAppFilter
*
* @return int[] Array of handles to any found nano apps
+ *
+ * @deprecated Use {@link #queryNanoApps(ContextHubInfo)} instead to explicitly query the hub
+ * for loaded nanoapps.
*/
+ @Deprecated
@RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
- public int[] findNanoAppOnHub(int hubHandle, NanoAppFilter filter) {
+ @NonNull public int[] findNanoAppOnHub(int hubHandle, @NonNull NanoAppFilter filter) {
try {
return mService.findNanoAppOnHub(hubHandle, filter);
} catch (RemoteException e) {
@@ -247,9 +281,16 @@
* @see ContextHubMessage
*
* @return int 0 on success, -1 otherwise
+ *
+ * @deprecated Use {@link android.hardware.location.ContextHubClient#sendMessageToNanoApp(
+ * NanoAppMessage)} instead, after creating a
+ * {@link android.hardware.location.ContextHubClient} with
+ * {@link #createClient(ContextHubInfo, ContextHubClientCallback, Executor)}
+ * or {@link #createClient(ContextHubInfo, ContextHubClientCallback)}.
*/
+ @Deprecated
@RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
- public int sendMessage(int hubHandle, int nanoAppHandle, ContextHubMessage message) {
+ public int sendMessage(int hubHandle, int nanoAppHandle, @NonNull ContextHubMessage message) {
try {
return mService.sendMessage(hubHandle, nanoAppHandle, message);
} catch (RemoteException e) {
@@ -263,11 +304,9 @@
* @return the list of ContextHubInfo objects
*
* @see ContextHubInfo
- *
- * @hide
*/
@RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
- public List<ContextHubInfo> getContextHubs() {
+ @NonNull public List<ContextHubInfo> getContextHubs() {
try {
return mService.getContextHubs();
} catch (RemoteException e) {
@@ -339,13 +378,16 @@
*
* @return the ContextHubTransaction of the request
*
- * @see NanoAppBinary
+ * @throws NullPointerException if hubInfo or NanoAppBinary is null
*
- * @hide
+ * @see NanoAppBinary
*/
@RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
- public ContextHubTransaction<Void> loadNanoApp(
- ContextHubInfo hubInfo, NanoAppBinary appBinary) {
+ @NonNull public ContextHubTransaction<Void> loadNanoApp(
+ @NonNull ContextHubInfo hubInfo, @NonNull NanoAppBinary appBinary) {
+ Preconditions.checkNotNull(hubInfo, "ContextHubInfo cannot be null");
+ Preconditions.checkNotNull(appBinary, "NanoAppBinary cannot be null");
+
ContextHubTransaction<Void> transaction =
new ContextHubTransaction<>(ContextHubTransaction.TYPE_LOAD_NANOAPP);
IContextHubTransactionCallback callback = createTransactionCallback(transaction);
@@ -367,10 +409,13 @@
*
* @return the ContextHubTransaction of the request
*
- * @hide
+ * @throws NullPointerException if hubInfo is null
*/
@RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
- public ContextHubTransaction<Void> unloadNanoApp(ContextHubInfo hubInfo, long nanoAppId) {
+ @NonNull public ContextHubTransaction<Void> unloadNanoApp(
+ @NonNull ContextHubInfo hubInfo, long nanoAppId) {
+ Preconditions.checkNotNull(hubInfo, "ContextHubInfo cannot be null");
+
ContextHubTransaction<Void> transaction =
new ContextHubTransaction<>(ContextHubTransaction.TYPE_UNLOAD_NANOAPP);
IContextHubTransactionCallback callback = createTransactionCallback(transaction);
@@ -392,10 +437,13 @@
*
* @return the ContextHubTransaction of the request
*
- * @hide
+ * @throws NullPointerException if hubInfo is null
*/
@RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
- public ContextHubTransaction<Void> enableNanoApp(ContextHubInfo hubInfo, long nanoAppId) {
+ @NonNull public ContextHubTransaction<Void> enableNanoApp(
+ @NonNull ContextHubInfo hubInfo, long nanoAppId) {
+ Preconditions.checkNotNull(hubInfo, "ContextHubInfo cannot be null");
+
ContextHubTransaction<Void> transaction =
new ContextHubTransaction<>(ContextHubTransaction.TYPE_ENABLE_NANOAPP);
IContextHubTransactionCallback callback = createTransactionCallback(transaction);
@@ -417,10 +465,13 @@
*
* @return the ContextHubTransaction of the request
*
- * @hide
+ * @throws NullPointerException if hubInfo is null
*/
@RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
- public ContextHubTransaction<Void> disableNanoApp(ContextHubInfo hubInfo, long nanoAppId) {
+ @NonNull public ContextHubTransaction<Void> disableNanoApp(
+ @NonNull ContextHubInfo hubInfo, long nanoAppId) {
+ Preconditions.checkNotNull(hubInfo, "ContextHubInfo cannot be null");
+
ContextHubTransaction<Void> transaction =
new ContextHubTransaction<>(ContextHubTransaction.TYPE_DISABLE_NANOAPP);
IContextHubTransactionCallback callback = createTransactionCallback(transaction);
@@ -441,10 +492,13 @@
*
* @return the ContextHubTransaction of the request
*
- * @hide
+ * @throws NullPointerException if hubInfo is null
*/
@RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
- public ContextHubTransaction<List<NanoAppState>> queryNanoApps(ContextHubInfo hubInfo) {
+ @NonNull public ContextHubTransaction<List<NanoAppState>> queryNanoApps(
+ @NonNull ContextHubInfo hubInfo) {
+ Preconditions.checkNotNull(hubInfo, "ContextHubInfo cannot be null");
+
ContextHubTransaction<List<NanoAppState>> transaction =
new ContextHubTransaction<>(ContextHubTransaction.TYPE_QUERY_NANOAPPS);
IContextHubTransactionCallback callback = createQueryCallback(transaction);
@@ -466,9 +520,14 @@
* @see Callback
*
* @return int 0 on success, -1 otherwise
+ *
+ * @deprecated Use {@link #createClient(ContextHubInfo, ContextHubClientCallback, Executor)}
+ * or {@link #createClient(ContextHubInfo, ContextHubClientCallback)} instead to
+ * register a {@link android.hardware.location.ContextHubClientCallback}.
*/
+ @Deprecated
@SuppressLint("Doclava125")
- public int registerCallback(Callback callback) {
+ public int registerCallback(@NonNull Callback callback) {
return registerCallback(callback, null);
}
@@ -495,7 +554,12 @@
* @see Callback
*
* @return int 0 on success, -1 otherwise
+ *
+ * @deprecated Use {@link #createClient(ContextHubInfo, ContextHubClientCallback, Executor)}
+ * or {@link #createClient(ContextHubInfo, ContextHubClientCallback)} instead to
+ * register a {@link android.hardware.location.ContextHubClientCallback}.
*/
+ @Deprecated
@SuppressLint("Doclava125")
public int registerCallback(Callback callback, Handler handler) {
synchronized(this) {
@@ -512,47 +576,48 @@
/**
* Creates an interface to the ContextHubClient to send down to the service.
*
+ * @param client the ContextHubClient object associated with this callback
* @param callback the callback to invoke at the client process
- * @param handler the handler to post callbacks for this client
+ * @param executor the executor to invoke callbacks for this client
*
* @return the callback interface
*/
private IContextHubClientCallback createClientCallback(
- ContextHubClientCallback callback, Handler handler) {
+ ContextHubClient client, ContextHubClientCallback callback, Executor executor) {
return new IContextHubClientCallback.Stub() {
@Override
public void onMessageFromNanoApp(NanoAppMessage message) {
- handler.post(() -> callback.onMessageFromNanoApp(message));
+ executor.execute(() -> callback.onMessageFromNanoApp(client, message));
}
@Override
public void onHubReset() {
- handler.post(() -> callback.onHubReset());
+ executor.execute(() -> callback.onHubReset(client));
}
@Override
public void onNanoAppAborted(long nanoAppId, int abortCode) {
- handler.post(() -> callback.onNanoAppAborted(nanoAppId, abortCode));
+ executor.execute(() -> callback.onNanoAppAborted(client, nanoAppId, abortCode));
}
@Override
public void onNanoAppLoaded(long nanoAppId) {
- handler.post(() -> callback.onNanoAppLoaded(nanoAppId));
+ executor.execute(() -> callback.onNanoAppLoaded(client, nanoAppId));
}
@Override
public void onNanoAppUnloaded(long nanoAppId) {
- handler.post(() -> callback.onNanoAppUnloaded(nanoAppId));
+ executor.execute(() -> callback.onNanoAppUnloaded(client, nanoAppId));
}
@Override
public void onNanoAppEnabled(long nanoAppId) {
- handler.post(() -> callback.onNanoAppEnabled(nanoAppId));
+ executor.execute(() -> callback.onNanoAppEnabled(client, nanoAppId));
}
@Override
public void onNanoAppDisabled(long nanoAppId) {
- handler.post(() -> callback.onNanoAppDisabled(nanoAppId));
+ executor.execute(() -> callback.onNanoAppDisabled(client, nanoAppId));
}
};
}
@@ -564,38 +629,56 @@
* registration succeeds, the client can send messages to nanoapps through the returned
* {@link ContextHubClient} object, and receive notifications through the provided callback.
*
- * @param callback the notification callback to register
* @param hubInfo the hub to attach this client to
- * @param handler the handler to invoke the callback, if null uses the main thread's Looper
+ * @param callback the notification callback to register
+ * @param executor the executor to invoke the callback
+ * @return the registered client object
+ *
+ * @throws IllegalArgumentException if hubInfo does not represent a valid hub
+ * @throws IllegalStateException if there were too many registered clients at the service
+ * @throws NullPointerException if callback, hubInfo, or executor is null
+ *
+ * @see ContextHubClientCallback
+ */
+ @NonNull public ContextHubClient createClient(
+ @NonNull ContextHubInfo hubInfo, @NonNull ContextHubClientCallback callback,
+ @NonNull @CallbackExecutor Executor executor) {
+ Preconditions.checkNotNull(callback, "Callback cannot be null");
+ Preconditions.checkNotNull(hubInfo, "ContextHubInfo cannot be null");
+ Preconditions.checkNotNull(executor, "Executor cannot be null");
+
+ ContextHubClient client = new ContextHubClient(hubInfo);
+ IContextHubClientCallback clientInterface = createClientCallback(
+ client, callback, executor);
+
+ IContextHubClient clientProxy;
+ try {
+ clientProxy = mService.createClient(clientInterface, hubInfo.getId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+
+ client.setClientProxy(clientProxy);
+ return client;
+ }
+
+ /**
+ * Equivalent to {@link #createClient(ContextHubInfo, ContextHubClientCallback, Executor)}
+ * with the executor using the main thread's Looper.
+ *
+ * @param hubInfo the hub to attach this client to
+ * @param callback the notification callback to register
* @return the registered client object
*
* @throws IllegalArgumentException if hubInfo does not represent a valid hub
* @throws IllegalStateException if there were too many registered clients at the service
* @throws NullPointerException if callback or hubInfo is null
*
- * @hide
* @see ContextHubClientCallback
*/
- public ContextHubClient createClient(
- ContextHubClientCallback callback, ContextHubInfo hubInfo, @Nullable Handler handler) {
- if (callback == null) {
- throw new NullPointerException("Callback cannot be null");
- }
- if (hubInfo == null) {
- throw new NullPointerException("Hub info cannot be null");
- }
-
- Handler realHandler = (handler == null) ? new Handler(mMainLooper) : handler;
- IContextHubClientCallback clientInterface = createClientCallback(callback, realHandler);
-
- IContextHubClient client;
- try {
- client = mService.createClient(clientInterface, hubInfo.getId());
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
-
- return new ContextHubClient(client, clientInterface, hubInfo);
+ @NonNull public ContextHubClient createClient(
+ @NonNull ContextHubInfo hubInfo, @NonNull ContextHubClientCallback callback) {
+ return createClient(hubInfo, callback, new HandlerExecutor(Handler.getMain()));
}
/**
@@ -606,9 +689,13 @@
* @param callback method to deregister
*
* @return int 0 on success, -1 otherwise
+ *
+ * @deprecated Use {@link android.hardware.location.ContextHubClient#close()} to unregister
+ * a {@link android.hardware.location.ContextHubClientCallback}.
*/
@SuppressLint("Doclava125")
- public int unregisterCallback(Callback callback) {
+ @Deprecated
+ public int unregisterCallback(@NonNull Callback callback) {
synchronized(this) {
if (callback != mCallback) {
Log.w(TAG, "Cannot recognize callback!");
diff --git a/core/java/android/hardware/location/ContextHubTransaction.java b/core/java/android/hardware/location/ContextHubTransaction.java
index ec1e68f..bc7efef 100644
--- a/core/java/android/hardware/location/ContextHubTransaction.java
+++ b/core/java/android/hardware/location/ContextHubTransaction.java
@@ -15,15 +15,19 @@
*/
package android.hardware.location;
+import android.annotation.CallbackExecutor;
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.SystemApi;
import android.os.Handler;
-import android.os.Looper;
-import android.util.Log;
+import android.os.HandlerExecutor;
+
+import com.android.internal.util.Preconditions;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
@@ -33,18 +37,20 @@
* This object is generated as a result of an asynchronous request sent to the Context Hub
* through the ContextHubManager APIs. The caller can either retrieve the result
* synchronously through a blocking call ({@link #waitForResponse(long, TimeUnit)}) or
- * asynchronously through a user-defined callback
- * ({@link #setOnCompleteCallback(ContextHubTransaction.Callback, Handler)}).
+ * asynchronously through a user-defined listener
+ * ({@link #setOnCompleteListener(OnCompleteListener, Executor)} )}).
*
* @param <T> the type of the contents in the transaction response
*
* @hide
*/
+@SystemApi
public class ContextHubTransaction<T> {
private static final String TAG = "ContextHubTransaction";
/**
* Constants describing the type of a transaction through the Context Hub Service.
+ * {@hide}
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef(prefix = { "TYPE_" }, value = {
@@ -64,6 +70,7 @@
/**
* Constants describing the result of a transaction or request through the Context Hub Service.
+ * {@hide}
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef(prefix = { "RESULT_" }, value = {
@@ -71,7 +78,7 @@
RESULT_FAILED_UNKNOWN,
RESULT_FAILED_BAD_PARAMS,
RESULT_FAILED_UNINITIALIZED,
- RESULT_FAILED_PENDING,
+ RESULT_FAILED_BUSY,
RESULT_FAILED_AT_HUB,
RESULT_FAILED_TIMEOUT,
RESULT_FAILED_SERVICE_INTERNAL_FAILURE,
@@ -94,7 +101,7 @@
/**
* Failure mode when there are too many transactions pending.
*/
- public static final int RESULT_FAILED_PENDING = 4;
+ public static final int RESULT_FAILED_BUSY = 4;
/**
* Failure mode when the request went through, but failed asynchronously at the hub.
*/
@@ -145,20 +152,20 @@
}
/**
- * An interface describing the callback to be invoked when a transaction completes.
+ * An interface describing the listener for a transaction completion.
*
- * @param <C> the type of the contents in the transaction response
+ * @param <L> the type of the contents in the transaction response
*/
@FunctionalInterface
- public interface Callback<C> {
+ public interface OnCompleteListener<L> {
/**
- * The callback to invoke when the transaction completes.
+ * The listener function to invoke when the transaction completes.
*
* @param transaction the transaction that this callback was attached to.
* @param response the response of the transaction.
*/
void onComplete(
- ContextHubTransaction<C> transaction, ContextHubTransaction.Response<C> response);
+ ContextHubTransaction<L> transaction, ContextHubTransaction.Response<L> response);
}
/*
@@ -173,14 +180,14 @@
private ContextHubTransaction.Response<T> mResponse;
/*
- * The handler to invoke the aynsc response supplied by onComplete.
+ * The executor to invoke the onComplete async callback.
*/
- private Handler mHandler = null;
+ private Executor mExecutor = null;
/*
- * The callback to invoke when the transaction completes.
+ * The listener to be invoked when the transaction completes.
*/
- private ContextHubTransaction.Callback<T> mCallback = null;
+ private ContextHubTransaction.OnCompleteListener<T> mListener = null;
/*
* Synchronization latch used to block on response.
@@ -258,73 +265,65 @@
}
/**
- * Sets a callback to be invoked when the transaction completes.
+ * Sets the listener to be invoked invoked when the transaction completes.
*
* This function provides an asynchronous approach to retrieve the result of the
* transaction. When the transaction response has been provided by the Context Hub,
- * the given callback will be posted by the provided handler.
+ * the given listener will be invoked.
*
- * If the transaction has already completed at the time of invocation, the callback
- * will be immediately posted by the handler. If the transaction has been invalidated,
- * the callback will never be invoked.
+ * If the transaction has already completed at the time of invocation, the listener
+ * will be immediately invoked. If the transaction has been invalidated,
+ * the listener will never be invoked.
*
* A transaction can be invalidated if the process owning the transaction is no longer active
* and the reference to this object is lost.
*
- * This method or {@link #setOnCompleteCallback(ContextHubTransaction.Callback)} can only be
- * invoked once, or an IllegalStateException will be thrown.
+ * This method or {@link #setOnCompleteListener(ContextHubTransaction.OnCompleteListener)} can
+ * only be invoked once, or an IllegalStateException will be thrown.
*
- * @param callback the callback to be invoked upon completion
- * @param handler the handler to post the callback
+ * @param listener the listener to be invoked upon completion
+ * @param executor the executor to invoke the callback
*
* @throws IllegalStateException if this method is called multiple times
* @throws NullPointerException if the callback or handler is null
*/
- public void setOnCompleteCallback(
- @NonNull ContextHubTransaction.Callback<T> callback, @NonNull Handler handler) {
+ public void setOnCompleteListener(
+ @NonNull ContextHubTransaction.OnCompleteListener<T> listener,
+ @NonNull @CallbackExecutor Executor executor) {
synchronized (this) {
- if (callback == null) {
- throw new NullPointerException("Callback cannot be null");
- }
- if (handler == null) {
- throw new NullPointerException("Handler cannot be null");
- }
- if (mCallback != null) {
+ Preconditions.checkNotNull(listener, "OnCompleteListener cannot be null");
+ Preconditions.checkNotNull(executor, "Executor cannot be null");
+ if (mListener != null) {
throw new IllegalStateException(
- "Cannot set ContextHubTransaction callback multiple times");
+ "Cannot set ContextHubTransaction listener multiple times");
}
- mCallback = callback;
- mHandler = handler;
+ mListener = listener;
+ mExecutor = executor;
if (mDoneSignal.getCount() == 0) {
- boolean callbackPosted = mHandler.post(() -> {
- mCallback.onComplete(this, mResponse);
- });
-
- if (!callbackPosted) {
- Log.e(TAG, "Failed to post callback to Handler");
- }
+ mExecutor.execute(() -> mListener.onComplete(this, mResponse));
}
}
}
/**
- * Sets a callback to be invoked when the transaction completes.
+ * Sets the listener to be invoked invoked when the transaction completes.
*
- * Equivalent to {@link #setOnCompleteCallback(ContextHubTransaction.Callback, Handler)}
- * with the handler being that of the main thread's Looper.
+ * Equivalent to {@link #setOnCompleteListener(ContextHubTransaction.OnCompleteListener,
+ * Executor)} with the executor using the main thread's Looper.
*
- * This method or {@link #setOnCompleteCallback(ContextHubTransaction.Callback, Handler)}
- * can only be invoked once, or an IllegalStateException will be thrown.
+ * This method or {@link #setOnCompleteListener(ContextHubTransaction.OnCompleteListener,
+ * Executor)} can only be invoked once, or an IllegalStateException will be thrown.
*
- * @param callback the callback to be invoked upon completion
+ * @param listener the listener to be invoked upon completion
*
* @throws IllegalStateException if this method is called multiple times
* @throws NullPointerException if the callback is null
*/
- public void setOnCompleteCallback(@NonNull ContextHubTransaction.Callback<T> callback) {
- setOnCompleteCallback(callback, new Handler(Looper.getMainLooper()));
+ public void setOnCompleteListener(
+ @NonNull ContextHubTransaction.OnCompleteListener<T> listener) {
+ setOnCompleteListener(listener, new HandlerExecutor(Handler.getMain()));
}
/**
@@ -339,11 +338,9 @@
* @throws IllegalStateException if this method is invoked multiple times
* @throws NullPointerException if the response is null
*/
- void setResponse(ContextHubTransaction.Response<T> response) {
+ /* package */ void setResponse(ContextHubTransaction.Response<T> response) {
synchronized (this) {
- if (response == null) {
- throw new NullPointerException("Response cannot be null");
- }
+ Preconditions.checkNotNull(response, "Response cannot be null");
if (mIsResponseSet) {
throw new IllegalStateException(
"Cannot set response of ContextHubTransaction multiple times");
@@ -353,14 +350,8 @@
mIsResponseSet = true;
mDoneSignal.countDown();
- if (mCallback != null) {
- boolean callbackPosted = mHandler.post(() -> {
- mCallback.onComplete(this, mResponse);
- });
-
- if (!callbackPosted) {
- Log.e(TAG, "Failed to post callback to Handler");
- }
+ if (mListener != null) {
+ mExecutor.execute(() -> mListener.onComplete(this, mResponse));
}
}
}
diff --git a/core/java/android/hardware/location/NanoAppBinary.java b/core/java/android/hardware/location/NanoAppBinary.java
index 934e9e4..ba01ca2 100644
--- a/core/java/android/hardware/location/NanoAppBinary.java
+++ b/core/java/android/hardware/location/NanoAppBinary.java
@@ -15,6 +15,7 @@
*/
package android.hardware.location;
+import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Log;
@@ -27,6 +28,7 @@
/**
* @hide
*/
+@SystemApi
public final class NanoAppBinary implements Parcelable {
private static final String TAG = "NanoAppBinary";
diff --git a/core/java/android/hardware/location/NanoAppMessage.java b/core/java/android/hardware/location/NanoAppMessage.java
index 2028674..716a194 100644
--- a/core/java/android/hardware/location/NanoAppMessage.java
+++ b/core/java/android/hardware/location/NanoAppMessage.java
@@ -15,6 +15,7 @@
*/
package android.hardware.location;
+import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
@@ -25,6 +26,7 @@
*
* @hide
*/
+@SystemApi
public final class NanoAppMessage implements Parcelable {
private long mNanoAppId;
private int mMessageType;
diff --git a/core/java/android/hardware/location/NanoAppState.java b/core/java/android/hardware/location/NanoAppState.java
index 644031b..d05277d 100644
--- a/core/java/android/hardware/location/NanoAppState.java
+++ b/core/java/android/hardware/location/NanoAppState.java
@@ -15,6 +15,7 @@
*/
package android.hardware.location;
+import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
@@ -23,6 +24,7 @@
*
* @hide
*/
+@SystemApi
public final class NanoAppState implements Parcelable {
private long mNanoAppId;
private int mNanoAppVersion;
diff --git a/core/java/android/hardware/radio/RadioManager.java b/core/java/android/hardware/radio/RadioManager.java
index 4d54e31..9b6515c 100644
--- a/core/java/android/hardware/radio/RadioManager.java
+++ b/core/java/android/hardware/radio/RadioManager.java
@@ -38,6 +38,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
@@ -645,7 +646,8 @@
private final boolean mAf;
private final boolean mEa;
- FmBandDescriptor(int region, int type, int lowerLimit, int upperLimit, int spacing,
+ /** @hide */
+ public FmBandDescriptor(int region, int type, int lowerLimit, int upperLimit, int spacing,
boolean stereo, boolean rds, boolean ta, boolean af, boolean ea) {
super(region, type, lowerLimit, upperLimit, spacing);
mStereo = stereo;
@@ -771,7 +773,8 @@
private final boolean mStereo;
- AmBandDescriptor(int region, int type, int lowerLimit, int upperLimit, int spacing,
+ /** @hide */
+ public AmBandDescriptor(int region, int type, int lowerLimit, int upperLimit, int spacing,
boolean stereo) {
super(region, type, lowerLimit, upperLimit, spacing);
mStereo = stereo;
@@ -843,10 +846,10 @@
/** Radio band configuration. */
public static class BandConfig implements Parcelable {
- final BandDescriptor mDescriptor;
+ @NonNull final BandDescriptor mDescriptor;
BandConfig(BandDescriptor descriptor) {
- mDescriptor = descriptor;
+ mDescriptor = Objects.requireNonNull(descriptor);
}
BandConfig(int region, int type, int lowerLimit, int upperLimit, int spacing) {
@@ -968,7 +971,8 @@
private final boolean mAf;
private final boolean mEa;
- FmBandConfig(FmBandDescriptor descriptor) {
+ /** @hide */
+ public FmBandConfig(FmBandDescriptor descriptor) {
super((BandDescriptor)descriptor);
mStereo = descriptor.isStereoSupported();
mRds = descriptor.isRdsSupported();
@@ -1204,7 +1208,8 @@
public static class AmBandConfig extends BandConfig {
private final boolean mStereo;
- AmBandConfig(AmBandDescriptor descriptor) {
+ /** @hide */
+ public AmBandConfig(AmBandDescriptor descriptor) {
super((BandDescriptor)descriptor);
mStereo = descriptor.isStereoSupported();
}
diff --git a/core/java/android/net/NetworkWatchlistManager.java b/core/java/android/net/NetworkWatchlistManager.java
index 42e43c8..5425bf5 100644
--- a/core/java/android/net/NetworkWatchlistManager.java
+++ b/core/java/android/net/NetworkWatchlistManager.java
@@ -59,8 +59,8 @@
/**
* Report network watchlist records if necessary.
*
- * Watchlist report process will run summarize records into a single report, then the
- * report will be processed by differential privacy framework and store it on disk.
+ * Watchlist report process will summarize records into a single report, then the
+ * report will be processed by differential privacy framework and stored on disk.
*
* @hide
*/
@@ -72,4 +72,18 @@
e.rethrowFromSystemServer();
}
}
+
+ /**
+ * Reload network watchlist.
+ *
+ * @hide
+ */
+ public void reloadWatchlist() {
+ try {
+ mNetworkWatchlistManager.reloadWatchlist();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Unable to reload watchlist");
+ e.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 430c28b..d4d74f4 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -180,6 +180,11 @@
public static final int FOREGROUND_SERVICE = 22;
/**
+ * A constant indicating an aggregate wifi multicast timer
+ */
+ public static final int WIFI_AGGREGATE_MULTICAST_ENABLED = 23;
+
+ /**
* Include all of the data in the stats, including previously saved data.
*/
public static final int STATS_SINCE_CHARGED = 0;
@@ -227,8 +232,11 @@
* New in version 28:
* - Light/Deep Doze power
* - WiFi Multicast Wakelock statistics (count & duration)
+ * New in version 29:
+ * - Process states re-ordered. TOP_SLEEPING now below BACKGROUND. HEAVY_WEIGHT introduced.
+ * - CPU times per UID process state
*/
- static final int CHECKIN_VERSION = 28;
+ static final int CHECKIN_VERSION = 29;
/**
* Old version, we hit 9 and ran out of room, need to remove.
@@ -2331,6 +2339,22 @@
};
/**
+ * Returns total time for WiFi Multicast Wakelock timer.
+ * Note that this may be different from the sum of per uid timer values.
+ *
+ * {@hide}
+ */
+ public abstract long getWifiMulticastWakelockTime(long elapsedRealtimeUs, int which);
+
+ /**
+ * Returns total time for WiFi Multicast Wakelock timer
+ * Note that this may be different from the sum of per uid timer values.
+ *
+ * {@hide}
+ */
+ public abstract int getWifiMulticastWakelockCount(int which);
+
+ /**
* Returns the time in microseconds that wifi has been on while the device was
* running on battery.
*
@@ -3439,16 +3463,13 @@
screenDozeTime / 1000);
- // Calculate both wakelock and wifi multicast wakelock times across all uids.
+ // Calculate wakelock times across all uids.
long fullWakeLockTimeTotal = 0;
long partialWakeLockTimeTotal = 0;
- long multicastWakeLockTimeTotalMicros = 0;
- int multicastWakeLockCountTotal = 0;
for (int iu = 0; iu < NU; iu++) {
final Uid u = uidStats.valueAt(iu);
- // First calculating the wakelock stats
final ArrayMap<String, ? extends BatteryStats.Uid.Wakelock> wakelocks
= u.getWakelockStats();
for (int iw=wakelocks.size()-1; iw>=0; iw--) {
@@ -3466,13 +3487,6 @@
rawRealtime, which);
}
}
-
- // Now calculating the wifi multicast wakelock stats
- final Timer mcTimer = u.getMulticastWakelockStats();
- if (mcTimer != null) {
- multicastWakeLockTimeTotalMicros += mcTimer.getTotalTimeLocked(rawRealtime, which);
- multicastWakeLockCountTotal += mcTimer.getCountLocked(which);
- }
}
// Dump network stats
@@ -3589,6 +3603,9 @@
dumpLine(pw, 0 /* uid */, category, WIFI_SIGNAL_STRENGTH_COUNT_DATA, args);
// Dump Multicast total stats
+ final long multicastWakeLockTimeTotalMicros =
+ getWifiMulticastWakelockTime(rawRealtime, which);
+ final int multicastWakeLockCountTotal = getWifiMulticastWakelockCount(which);
dumpLine(pw, 0 /* uid */, category, WIFI_MULTICAST_TOTAL_DATA,
multicastWakeLockTimeTotalMicros / 1000,
multicastWakeLockCountTotal);
@@ -4453,18 +4470,15 @@
pw.print(" Connectivity changes: "); pw.println(connChanges);
}
- // Calculate both wakelock and wifi multicast wakelock times across all uids.
+ // Calculate wakelock times across all uids.
long fullWakeLockTimeTotalMicros = 0;
long partialWakeLockTimeTotalMicros = 0;
- long multicastWakeLockTimeTotalMicros = 0;
- int multicastWakeLockCountTotal = 0;
final ArrayList<TimerEntry> timers = new ArrayList<>();
for (int iu = 0; iu < NU; iu++) {
final Uid u = uidStats.valueAt(iu);
- // First calculate wakelock statistics
final ArrayMap<String, ? extends BatteryStats.Uid.Wakelock> wakelocks
= u.getWakelockStats();
for (int iw=wakelocks.size()-1; iw>=0; iw--) {
@@ -4492,13 +4506,6 @@
}
}
}
-
- // Next calculate wifi multicast wakelock statistics
- final Timer mcTimer = u.getMulticastWakelockStats();
- if (mcTimer != null) {
- multicastWakeLockTimeTotalMicros += mcTimer.getTotalTimeLocked(rawRealtime, which);
- multicastWakeLockCountTotal += mcTimer.getCountLocked(which);
- }
}
final long mobileRxTotalBytes = getNetworkActivityBytes(NETWORK_MOBILE_RX_DATA, which);
@@ -4528,6 +4535,9 @@
pw.println(sb.toString());
}
+ final long multicastWakeLockTimeTotalMicros =
+ getWifiMulticastWakelockTime(rawRealtime, which);
+ final int multicastWakeLockCountTotal = getWifiMulticastWakelockCount(which);
if (multicastWakeLockTimeTotalMicros != 0) {
sb.setLength(0);
sb.append(prefix);
@@ -7048,6 +7058,28 @@
}
}
}
+
+ for (int procState = 0; procState < Uid.NUM_PROCESS_STATE; ++procState) {
+ final long[] timesMs = u.getCpuFreqTimes(which, procState);
+ if (timesMs != null && timesMs.length == cpuFreqs.length) {
+ long[] screenOffTimesMs = u.getScreenOffCpuFreqTimes(which, procState);
+ if (screenOffTimesMs == null) {
+ screenOffTimesMs = new long[timesMs.length];
+ }
+ final long procToken = proto.start(UidProto.Cpu.BY_PROCESS_STATE);
+ proto.write(UidProto.Cpu.ByProcessState.PROCESS_STATE, procState);
+ for (int ic = 0; ic < timesMs.length; ++ic) {
+ long cToken = proto.start(UidProto.Cpu.ByProcessState.BY_FREQUENCY);
+ proto.write(UidProto.Cpu.ByFrequency.FREQUENCY_INDEX, ic + 1);
+ proto.write(UidProto.Cpu.ByFrequency.TOTAL_DURATION_MS,
+ timesMs[ic]);
+ proto.write(UidProto.Cpu.ByFrequency.SCREEN_OFF_DURATION_MS,
+ screenOffTimesMs[ic]);
+ proto.end(cToken);
+ }
+ proto.end(procToken);
+ }
+ }
proto.end(cpuToken);
// Flashlight (FLASHLIGHT_DATA)
@@ -7532,22 +7564,9 @@
proto.end(mToken);
// Wifi multicast wakelock total stats (WIFI_MULTICAST_WAKELOCK_TOTAL_DATA)
- // Calculate multicast wakelock stats across all uids.
- long multicastWakeLockTimeTotalUs = 0;
- int multicastWakeLockCountTotal = 0;
-
- for (int iu = 0; iu < uidStats.size(); iu++) {
- final Uid u = uidStats.valueAt(iu);
-
- final Timer mcTimer = u.getMulticastWakelockStats();
-
- if (mcTimer != null) {
- multicastWakeLockTimeTotalUs +=
- mcTimer.getTotalTimeLocked(rawRealtimeUs, which);
- multicastWakeLockCountTotal += mcTimer.getCountLocked(which);
- }
- }
-
+ final long multicastWakeLockTimeTotalUs =
+ getWifiMulticastWakelockTime(rawRealtimeUs, which);
+ final int multicastWakeLockCountTotal = getWifiMulticastWakelockCount(which);
final long wmctToken = proto.start(SystemProto.WIFI_MULTICAST_WAKELOCK_TOTAL);
proto.write(SystemProto.WifiMulticastWakelockTotal.DURATION_MS,
multicastWakeLockTimeTotalUs / 1000);
diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java
index b7a4645..33470f3 100644
--- a/core/java/android/os/Binder.java
+++ b/core/java/android/os/Binder.java
@@ -35,6 +35,9 @@
import java.lang.ref.WeakReference;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
/**
* Base class for a remotable object, the core part of a lightweight
@@ -901,17 +904,62 @@
keyArray[size] = key;
}
if (size >= mWarnBucketSize) {
- final int total_size = size();
+ final int totalSize = size();
Log.v(Binder.TAG, "BinderProxy map growth! bucket size = " + size
- + " total = " + total_size);
+ + " total = " + totalSize);
mWarnBucketSize += WARN_INCREMENT;
- if (Build.IS_DEBUGGABLE && total_size > CRASH_AT_SIZE) {
- throw new AssertionError("Binder ProxyMap has too many entries. "
- + "BinderProxy leak?");
+ if (Build.IS_DEBUGGABLE && totalSize > CRASH_AT_SIZE) {
+ diagnosticCrash();
}
}
}
+ /**
+ * Dump a histogram to the logcat, then throw an assertion error. Used to diagnose
+ * abnormally large proxy maps.
+ */
+ private void diagnosticCrash() {
+ Map<String, Integer> counts = new HashMap<>();
+ for (ArrayList<WeakReference<BinderProxy>> a : mMainIndexValues) {
+ if (a != null) {
+ for (WeakReference<BinderProxy> weakRef : a) {
+ BinderProxy bp = weakRef.get();
+ String key;
+ if (bp == null) {
+ key = "<cleared weak-ref>";
+ } else {
+ try {
+ key = bp.getInterfaceDescriptor();
+ } catch (Throwable t) {
+ key = "<exception during getDescriptor>";
+ }
+ }
+ Integer i = counts.get(key);
+ if (i == null) {
+ counts.put(key, 1);
+ } else {
+ counts.put(key, i + 1);
+ }
+ }
+ }
+ }
+ Map.Entry<String, Integer>[] sorted = counts.entrySet().toArray(
+ new Map.Entry[counts.size()]);
+ Arrays.sort(sorted, (Map.Entry<String, Integer> a, Map.Entry<String, Integer> b)
+ -> b.getValue().compareTo(a.getValue()));
+ Log.v(Binder.TAG, "BinderProxy descriptor histogram (top ten):");
+ int printLength = Math.min(10, sorted.length);
+ for (int i = 0; i < printLength; i++) {
+ Log.v(Binder.TAG, " #" + (i + 1) + ": " + sorted[i].getKey() + " x"
+ + sorted[i].getValue());
+ }
+
+ // Now throw an assertion.
+ final int totalSize = size();
+ throw new AssertionError("Binder ProxyMap has too many entries: " + totalSize
+ + ". BinderProxy leak?");
+ }
+
// Corresponding ArrayLists in the following two arrays always have the same size.
// They contain no empty entries. However WeakReferences in the values ArrayLists
// may have been cleared.
diff --git a/core/java/android/os/Handler.java b/core/java/android/os/Handler.java
index 3ca1005..5c5e351 100644
--- a/core/java/android/os/Handler.java
+++ b/core/java/android/os/Handler.java
@@ -388,6 +388,8 @@
* The runnable will be run on the thread to which this handler is attached.
*
* @param r The Runnable that will be executed.
+ * @param token An instance which can be used to cancel {@code r} via
+ * {@link #removeCallbacksAndMessages}.
* @param uptimeMillis The absolute time at which the callback should run,
* using the {@link android.os.SystemClock#uptimeMillis} time-base.
*
@@ -430,6 +432,32 @@
}
/**
+ * Causes the Runnable r to be added to the message queue, to be run
+ * after the specified amount of time elapses.
+ * The runnable will be run on the thread to which this handler
+ * is attached.
+ * <b>The time-base is {@link android.os.SystemClock#uptimeMillis}.</b>
+ * Time spent in deep sleep will add an additional delay to execution.
+ *
+ * @param r The Runnable that will be executed.
+ * @param token An instance which can be used to cancel {@code r} via
+ * {@link #removeCallbacksAndMessages}.
+ * @param delayMillis The delay (in milliseconds) until the Runnable
+ * will be executed.
+ *
+ * @return Returns true if the Runnable was successfully placed in to the
+ * message queue. Returns false on failure, usually because the
+ * looper processing the message queue is exiting. Note that a
+ * result of true does not mean the Runnable will be processed --
+ * if the looper is quit before the delivery time of the message
+ * occurs then the message will be dropped.
+ */
+ public final boolean postDelayed(Runnable r, Object token, long delayMillis)
+ {
+ return sendMessageDelayed(getPostMessage(r, token), delayMillis);
+ }
+
+ /**
* Posts a message to an object that implements Runnable.
* Causes the Runnable r to executed on the next iteration through the
* message queue. The runnable will be run on the thread to which this
diff --git a/core/java/android/os/IStatsManager.aidl b/core/java/android/os/IStatsManager.aidl
index 3db12ed..29812e8 100644
--- a/core/java/android/os/IStatsManager.aidl
+++ b/core/java/android/os/IStatsManager.aidl
@@ -71,7 +71,7 @@
* Fetches data for the specified configuration key. Returns a byte array representing proto
* wire-encoded of ConfigMetricsReportList.
*/
- byte[] getData(in String key);
+ byte[] getData(in long key);
/**
* Fetches metadata across statsd. Returns byte array representing wire-encoded proto.
@@ -86,7 +86,7 @@
*
* Returns if this configuration was correctly registered.
*/
- boolean addConfiguration(in String configKey, in byte[] config, in String pkg, in String cls);
+ boolean addConfiguration(in long configKey, in byte[] config, in String pkg, in String cls);
/**
* Removes the configuration with the matching config key. No-op if this config key does not
@@ -94,5 +94,5 @@
*
* Returns if this configuration key was removed.
*/
- boolean removeConfiguration(in String configKey);
+ boolean removeConfiguration(in long configKey);
}
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index 56c6391..cd6d41b 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -1540,7 +1540,7 @@
*/
public void setWorkSource(WorkSource ws) {
synchronized (mToken) {
- if (ws != null && ws.size() == 0) {
+ if (ws != null && ws.isEmpty()) {
ws = null;
}
@@ -1552,7 +1552,7 @@
changed = true;
mWorkSource = new WorkSource(ws);
} else {
- changed = mWorkSource.diff(ws);
+ changed = !mWorkSource.equals(ws);
if (changed) {
mWorkSource.set(ws);
}
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index dd9fd93..38993b7 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -2197,7 +2197,8 @@
/**
* Similar to {@link #trySetQuietModeEnabled(boolean, UserHandle)}, except you can specify
- * a target to start when user is unlocked.
+ * a target to start when user is unlocked. If {@code target} is specified, caller must have
+ * the {@link android.Manifest.permission#MANAGE_USERS} permission.
*
* @see {@link #trySetQuietModeEnabled(boolean, UserHandle)}
* @hide
diff --git a/core/java/android/os/WorkSource.java b/core/java/android/os/WorkSource.java
index bf145a0..401b4a3 100644
--- a/core/java/android/os/WorkSource.java
+++ b/core/java/android/os/WorkSource.java
@@ -456,6 +456,16 @@
}
/**
+ * Returns {@code true} iff. this work source contains zero UIDs and zero WorkChains to
+ * attribute usage to.
+ *
+ * @hide for internal use only.
+ */
+ public boolean isEmpty() {
+ return mNum == 0 && (mChains == null || mChains.isEmpty());
+ }
+
+ /**
* @return the list of {@code WorkChains} associated with this {@code WorkSource}.
* @hide
*/
@@ -842,6 +852,14 @@
return this;
}
+ /**
+ * Return the UID to which this WorkChain should be attributed to, i.e, the UID that
+ * initiated the work and not the UID performing it.
+ */
+ public int getAttributionUid() {
+ return mUids[0];
+ }
+
// TODO: The following three trivial getters are purely for testing and will be removed
// once we have higher level logic in place, e.g for serializing this WorkChain to a proto,
// diffing it etc.
@@ -932,6 +950,55 @@
};
}
+ /**
+ * Computes the differences in WorkChains contained between {@code oldWs} and {@code newWs}.
+ *
+ * Returns {@code null} if no differences exist, otherwise returns a two element array. The
+ * first element is a list of "new" chains, i.e WorkChains present in {@code newWs} but not in
+ * {@code oldWs}. The second element is a list of "gone" chains, i.e WorkChains present in
+ * {@code oldWs} but not in {@code newWs}.
+ *
+ * @hide
+ */
+ public static ArrayList<WorkChain>[] diffChains(WorkSource oldWs, WorkSource newWs) {
+ ArrayList<WorkChain> newChains = null;
+ ArrayList<WorkChain> goneChains = null;
+
+ // TODO(narayan): This is a dumb O(M*N) algorithm that determines what has changed across
+ // WorkSource objects. We can replace this with something smarter, for e.g by defining
+ // a Comparator between WorkChains. It's unclear whether that will be more efficient if
+ // the number of chains associated with a WorkSource is expected to be small
+ if (oldWs.mChains != null) {
+ for (int i = 0; i < oldWs.mChains.size(); ++i) {
+ final WorkChain wc = oldWs.mChains.get(i);
+ if (newWs.mChains == null || !newWs.mChains.contains(wc)) {
+ if (goneChains == null) {
+ goneChains = new ArrayList<>(oldWs.mChains.size());
+ }
+ goneChains.add(wc);
+ }
+ }
+ }
+
+ if (newWs.mChains != null) {
+ for (int i = 0; i < newWs.mChains.size(); ++i) {
+ final WorkChain wc = newWs.mChains.get(i);
+ if (oldWs.mChains == null || !oldWs.mChains.contains(wc)) {
+ if (newChains == null) {
+ newChains = new ArrayList<>(newWs.mChains.size());
+ }
+ newChains.add(wc);
+ }
+ }
+ }
+
+ if (newChains != null || goneChains != null) {
+ return new ArrayList[] { newChains, goneChains };
+ }
+
+ return null;
+ }
+
@Override
public int describeContents() {
return 0;
@@ -991,6 +1058,25 @@
}
proto.end(contentProto);
}
+
+ if (mChains != null) {
+ for (int i = 0; i < mChains.size(); i++) {
+ final WorkChain wc = mChains.get(i);
+ final long workChain = proto.start(WorkSourceProto.WORK_CHAINS);
+
+ final String[] tags = wc.getTags();
+ final int[] uids = wc.getUids();
+ for (int j = 0; j < tags.length; j++) {
+ final long contentProto = proto.start(WorkSourceProto.WORK_SOURCE_CONTENTS);
+ proto.write(WorkSourceProto.WorkSourceContentProto.UID, uids[j]);
+ proto.write(WorkSourceProto.WorkSourceContentProto.NAME, tags[j]);
+ proto.end(contentProto);
+ }
+
+ proto.end(workChain);
+ }
+ }
+
proto.end(workSourceToken);
}
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index 9833fe1..4c587a8 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -1115,12 +1115,14 @@
/** {@hide} */
public static Pair<String, Long> getPrimaryStoragePathAndSize() {
return Pair.create(null,
- FileUtils.roundStorageSize(Environment.getDataDirectory().getTotalSpace()));
+ FileUtils.roundStorageSize(Environment.getDataDirectory().getTotalSpace()
+ + Environment.getRootDirectory().getTotalSpace()));
}
/** {@hide} */
public long getPrimaryStorageSize() {
- return FileUtils.roundStorageSize(Environment.getDataDirectory().getTotalSpace());
+ return FileUtils.roundStorageSize(Environment.getDataDirectory().getTotalSpace()
+ + Environment.getRootDirectory().getTotalSpace());
}
/** {@hide} */
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index c6deecc..2f86514 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -5778,6 +5778,14 @@
"touch_exploration_granted_accessibility_services";
/**
+ * Uri of the slice that's presented on the keyguard.
+ * Defaults to a slice with the date and next alarm.
+ *
+ * @hide
+ */
+ public static final String KEYGUARD_SLICE_URI = "keyguard_slice_uri";
+
+ /**
* Whether to speak passwords while in accessibility mode.
*
* @deprecated The speaking of passwords is controlled by individual accessibility services.
@@ -7252,8 +7260,11 @@
* full_backup_interval_milliseconds (long)
* full_backup_require_charging (boolean)
* full_backup_required_network_type (int)
+ * backup_finished_notification_receivers (String[])
* </pre>
*
+ * backup_finished_notification_receivers uses ":" as delimeter for values.
+ *
* <p>
* Type: string
* @hide
@@ -11133,6 +11144,7 @@
INSTANT_APP_SETTINGS.add(DEBUG_VIEW_ATTRIBUTES);
INSTANT_APP_SETTINGS.add(WTF_IS_FATAL);
INSTANT_APP_SETTINGS.add(SEND_ACTION_APP_ERROR);
+ INSTANT_APP_SETTINGS.add(ZEN_MODE);
}
/**
diff --git a/core/java/android/security/IKeystoreService.aidl b/core/java/android/security/IKeystoreService.aidl
index 42282ac..57477f5 100644
--- a/core/java/android/security/IKeystoreService.aidl
+++ b/core/java/android/security/IKeystoreService.aidl
@@ -56,7 +56,7 @@
int clear_uid(long uid);
// Keymaster 0.4 methods
- int addRngEntropy(in byte[] data);
+ int addRngEntropy(in byte[] data, int flags);
int generateKey(String alias, in KeymasterArguments arguments, in byte[] entropy, int uid,
int flags, out KeyCharacteristics characteristics);
int getKeyCharacteristics(String alias, in KeymasterBlob clientId, in KeymasterBlob appId,
@@ -78,4 +78,8 @@
int attestKey(String alias, in KeymasterArguments params, out KeymasterCertificateChain chain);
int attestDeviceIds(in KeymasterArguments params, out KeymasterCertificateChain chain);
int onDeviceOffBody();
+ int importWrappedKey(in String wrappedKeyAlias, in byte[] wrappedKey,
+ in String wrappingKeyAlias, in byte[] maskingKey, in KeymasterArguments arguments,
+ in long rootSid, in long fingerprintSid,
+ out KeyCharacteristics characteristics);
}
diff --git a/core/java/android/security/recoverablekeystore/RecoverableKeyStoreLoader.java b/core/java/android/security/recoverablekeystore/RecoverableKeyStoreLoader.java
index f88768b..0cf8da5 100644
--- a/core/java/android/security/recoverablekeystore/RecoverableKeyStoreLoader.java
+++ b/core/java/android/security/recoverablekeystore/RecoverableKeyStoreLoader.java
@@ -45,6 +45,8 @@
public static final int NO_ERROR = KeyStore.NO_ERROR;
public static final int SYSTEM_ERROR = KeyStore.SYSTEM_ERROR;
public static final int UNINITIALIZED_RECOVERY_PUBLIC_KEY = 20;
+ public static final int NO_SNAPSHOT_PENDING_ERROR = 21;
+
/**
* Rate limit is enforced to prevent using too many trusted remote devices, since each device
* can have its own number of user secret guesses allowed.
@@ -209,7 +211,7 @@
* version. Version zero is used, if no snapshots were created for the account.
*
* @return Map from recovery agent accounts to snapshot versions.
- * @see KeyStoreRecoveryData.getSnapshotVersion
+ * @see KeyStoreRecoveryData#getSnapshotVersion
* @hide
*/
public @NonNull Map<byte[], Integer> getRecoverySnapshotVersions()
@@ -231,7 +233,7 @@
/**
* Server parameters used to generate new recovery key blobs. This value will be included in
* {@code KeyStoreRecoveryData.getEncryptedRecoveryKeyBlob()}. The same value must be included
- * in vaultParams {@link startRecoverySession}
+ * in vaultParams {@link #startRecoverySession}
*
* @param serverParameters included in recovery key blob.
* @see #getRecoveryData
@@ -423,19 +425,21 @@
/**
* Imports keys.
*
- * @param sessionId Id for recovery session, same as in = {@link startRecoverySession}.
+ * @param sessionId Id for recovery session, same as in
+ * {@link #startRecoverySession(String, byte[], byte[], byte[], List)}.
* @param recoveryKeyBlob Recovery blob encrypted by symmetric key generated for this session.
* @param applicationKeys Application keys. Key material can be decrypted using recoveryKeyBlob
* and session. KeyStore only uses package names from the application info in {@link
* KeyEntryRecoveryData}. Caller is responsibility to perform certificates check.
+ * @return Map from alias to raw key material.
*/
- public void recoverKeys(
+ public Map<String, byte[]> recoverKeys(
@NonNull String sessionId,
@NonNull byte[] recoveryKeyBlob,
@NonNull List<KeyEntryRecoveryData> applicationKeys)
throws RecoverableKeyStoreLoaderException {
try {
- mBinder.recoverKeys(
+ return (Map<String, byte[]>) mBinder.recoverKeys(
sessionId, recoveryKeyBlob, applicationKeys, UserHandle.getCallingUserId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -443,4 +447,21 @@
throw RecoverableKeyStoreLoaderException.fromServiceSpecificException(e);
}
}
+
+ /**
+ * Generates a key called {@code alias} and loads it into the recoverable key store. Returns the
+ * raw material of the key.
+ *
+ * @throws RecoverableKeyStoreLoaderException if an error occurred generating and storing the
+ * key.
+ */
+ public byte[] generateAndStoreKey(String alias) throws RecoverableKeyStoreLoaderException {
+ try {
+ return mBinder.generateAndStoreKey(alias);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ } catch (ServiceSpecificException e) {
+ throw RecoverableKeyStoreLoaderException.fromServiceSpecificException(e);
+ }
+ }
}
diff --git a/core/java/android/service/euicc/EuiccService.java b/core/java/android/service/euicc/EuiccService.java
index df0842f..fb53007 100644
--- a/core/java/android/service/euicc/EuiccService.java
+++ b/core/java/android/service/euicc/EuiccService.java
@@ -23,6 +23,7 @@
import android.os.RemoteException;
import android.telephony.euicc.DownloadableSubscription;
import android.telephony.euicc.EuiccInfo;
+import android.telephony.euicc.EuiccManager.OtaStatus;
import android.util.ArraySet;
import java.util.concurrent.LinkedBlockingQueue;
@@ -203,6 +204,16 @@
public abstract String onGetEid(int slotId);
/**
+ * Return the status of OTA update.
+ *
+ * @param slotId ID of the SIM slot to use for the operation. This is currently not populated
+ * but is here to future-proof the APIs.
+ * @return The status of Euicc OTA update.
+ * @see android.telephony.euicc.EuiccManager#getOtaStatus
+ */
+ public abstract @OtaStatus int onGetOtaStatus(int slotId);
+
+ /**
* Populate {@link DownloadableSubscription} metadata for the given downloadable subscription.
*
* @param slotId ID of the SIM slot to use for the operation. This is currently not populated
@@ -385,6 +396,21 @@
}
@Override
+ public void getOtaStatus(int slotId, IGetOtaStatusCallback callback) {
+ mExecutor.execute(new Runnable() {
+ @Override
+ public void run() {
+ int status = EuiccService.this.onGetOtaStatus(slotId);
+ try {
+ callback.onSuccess(status);
+ } catch (RemoteException e) {
+ // Can't communicate with the phone process; ignore.
+ }
+ }
+ });
+ }
+
+ @Override
public void getDownloadableSubscriptionMetadata(int slotId,
DownloadableSubscription subscription,
boolean forceDeactivateSim,
diff --git a/core/java/android/service/euicc/IEuiccService.aidl b/core/java/android/service/euicc/IEuiccService.aidl
index e10dd8c..a24e5c3 100644
--- a/core/java/android/service/euicc/IEuiccService.aidl
+++ b/core/java/android/service/euicc/IEuiccService.aidl
@@ -24,6 +24,7 @@
import android.service.euicc.IGetEidCallback;
import android.service.euicc.IGetEuiccInfoCallback;
import android.service.euicc.IGetEuiccProfileInfoListCallback;
+import android.service.euicc.IGetOtaStatusCallback;
import android.service.euicc.IRetainSubscriptionsForFactoryResetCallback;
import android.service.euicc.ISwitchToSubscriptionCallback;
import android.service.euicc.IUpdateSubscriptionNicknameCallback;
@@ -37,6 +38,7 @@
void getDownloadableSubscriptionMetadata(int slotId, in DownloadableSubscription subscription,
boolean forceDeactivateSim, in IGetDownloadableSubscriptionMetadataCallback callback);
void getEid(int slotId, in IGetEidCallback callback);
+ void getOtaStatus(int slotId, in IGetOtaStatusCallback callback);
void getEuiccProfileInfoList(int slotId, in IGetEuiccProfileInfoListCallback callback);
void getDefaultDownloadableSubscriptionList(int slotId, boolean forceDeactivateSim,
in IGetDefaultDownloadableSubscriptionListCallback callback);
diff --git a/core/java/android/service/euicc/IGetOtaStatusCallback.aidl b/core/java/android/service/euicc/IGetOtaStatusCallback.aidl
new file mode 100644
index 0000000..f667888
--- /dev/null
+++ b/core/java/android/service/euicc/IGetOtaStatusCallback.aidl
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.euicc;
+
+/** @hide */
+oneway interface IGetOtaStatusCallback {
+ void onSuccess(int status);
+}
\ No newline at end of file
diff --git a/core/java/android/text/format/Formatter.java b/core/java/android/text/format/Formatter.java
index 8c90156..ad3b4b6 100644
--- a/core/java/android/text/format/Formatter.java
+++ b/core/java/android/text/format/Formatter.java
@@ -20,11 +20,7 @@
import android.annotation.Nullable;
import android.content.Context;
import android.content.res.Resources;
-import android.icu.text.DecimalFormat;
import android.icu.text.MeasureFormat;
-import android.icu.text.NumberFormat;
-import android.icu.text.UnicodeSet;
-import android.icu.text.UnicodeSetSpanner;
import android.icu.util.Measure;
import android.icu.util.MeasureUnit;
import android.net.NetworkUtils;
@@ -32,8 +28,6 @@
import android.text.TextUtils;
import android.view.View;
-import java.lang.reflect.Constructor;
-import java.math.BigDecimal;
import java.util.Locale;
/**
@@ -43,8 +37,6 @@
public final class Formatter {
/** {@hide} */
- public static final int FLAG_DEFAULT = 0;
- /** {@hide} */
public static final int FLAG_SHORTER = 1 << 0;
/** {@hide} */
public static final int FLAG_CALCULATE_ROUNDED = 1 << 1;
@@ -66,9 +58,7 @@
return context.getResources().getConfiguration().getLocales().get(0);
}
- /**
- * Wraps the source string in bidi formatting characters in RTL locales.
- */
+ /* Wraps the source string in bidi formatting characters in RTL locales */
private static String bidiWrap(@NonNull Context context, String source) {
final Locale locale = localeFromContext(context);
if (TextUtils.getLayoutDirectionFromLocale(locale) == View.LAYOUT_DIRECTION_RTL) {
@@ -97,7 +87,12 @@
* @return formatted string with the number
*/
public static String formatFileSize(@Nullable Context context, long sizeBytes) {
- return formatFileSize(context, sizeBytes, FLAG_DEFAULT);
+ if (context == null) {
+ return "";
+ }
+ final BytesResult res = formatBytes(context.getResources(), sizeBytes, 0);
+ return bidiWrap(context, context.getString(com.android.internal.R.string.fileSizeSuffix,
+ res.value, res.units));
}
/**
@@ -105,207 +100,88 @@
* (showing fewer digits of precision).
*/
public static String formatShortFileSize(@Nullable Context context, long sizeBytes) {
- return formatFileSize(context, sizeBytes, FLAG_SHORTER);
- }
-
- private static String formatFileSize(@Nullable Context context, long sizeBytes, int flags) {
if (context == null) {
return "";
}
- final RoundedBytesResult res = RoundedBytesResult.roundBytes(sizeBytes, flags);
- return bidiWrap(context, formatRoundedBytesResult(context, res));
- }
-
- private static String getSuffixOverride(@NonNull Resources res, MeasureUnit unit) {
- if (unit == MeasureUnit.BYTE) {
- return res.getString(com.android.internal.R.string.byteShort);
- } else { // unit == PETABYTE
- return res.getString(com.android.internal.R.string.petabyteShort);
- }
- }
-
- private static NumberFormat getNumberFormatter(Locale locale, int fractionDigits) {
- final NumberFormat numberFormatter = NumberFormat.getInstance(locale);
- numberFormatter.setMinimumFractionDigits(fractionDigits);
- numberFormatter.setMaximumFractionDigits(fractionDigits);
- numberFormatter.setGroupingUsed(false);
- if (numberFormatter instanceof DecimalFormat) {
- // We do this only for DecimalFormat, since in the general NumberFormat case, calling
- // setRoundingMode may throw an exception.
- numberFormatter.setRoundingMode(BigDecimal.ROUND_HALF_UP);
- }
- return numberFormatter;
- }
-
- private static String deleteFirstFromString(String source, String toDelete) {
- final int location = source.indexOf(toDelete);
- if (location == -1) {
- return source;
- } else {
- return source.substring(0, location)
- + source.substring(location + toDelete.length(), source.length());
- }
- }
-
- private static String formatMeasureShort(Locale locale, NumberFormat numberFormatter,
- float value, MeasureUnit units) {
- final MeasureFormat measureFormatter = MeasureFormat.getInstance(
- locale, MeasureFormat.FormatWidth.SHORT, numberFormatter);
- return measureFormatter.format(new Measure(value, units));
- }
-
- private static final UnicodeSetSpanner SPACES_AND_CONTROLS =
- new UnicodeSetSpanner(new UnicodeSet("[[:Zs:][:Cf:]]").freeze());
-
- private static String formatRoundedBytesResult(
- @NonNull Context context, @NonNull RoundedBytesResult input) {
- final Locale locale = localeFromContext(context);
- final NumberFormat numberFormatter = getNumberFormatter(locale, input.fractionDigits);
- if (input.units == MeasureUnit.BYTE || input.units == PETABYTE) {
- // ICU spells out "byte" instead of "B", and can't format petabytes yet.
- final String formattedNumber = numberFormatter.format(input.value);
- return context.getString(com.android.internal.R.string.fileSizeSuffix,
- formattedNumber, getSuffixOverride(context.getResources(), input.units));
- } else {
- return formatMeasureShort(locale, numberFormatter, input.value, input.units);
- }
+ final BytesResult res = formatBytes(context.getResources(), sizeBytes, FLAG_SHORTER);
+ return bidiWrap(context, context.getString(com.android.internal.R.string.fileSizeSuffix,
+ res.value, res.units));
}
/** {@hide} */
public static BytesResult formatBytes(Resources res, long sizeBytes, int flags) {
- final RoundedBytesResult rounded = RoundedBytesResult.roundBytes(sizeBytes, flags);
- final Locale locale = res.getConfiguration().getLocales().get(0);
- final NumberFormat numberFormatter = getNumberFormatter(locale, rounded.fractionDigits);
- final String formattedNumber = numberFormatter.format(rounded.value);
- final String units;
- if (rounded.units == MeasureUnit.BYTE || rounded.units == PETABYTE) {
- // ICU spells out "byte" instead of "B", and can't format petabytes yet.
- units = getSuffixOverride(res, rounded.units);
- } else {
- // Since ICU does not give us access to the pattern, we need to extract the unit string
- // from ICU, which we do by taking out the formatted number out of the formatted string
- // and trimming the result of spaces and controls.
- final String formattedMeasure = formatMeasureShort(
- locale, numberFormatter, rounded.value, rounded.units);
- final String numberRemoved = deleteFirstFromString(formattedMeasure, formattedNumber);
- units = SPACES_AND_CONTROLS.trim(numberRemoved).toString();
+ final boolean isNegative = (sizeBytes < 0);
+ float result = isNegative ? -sizeBytes : sizeBytes;
+ int suffix = com.android.internal.R.string.byteShort;
+ long mult = 1;
+ if (result > 900) {
+ suffix = com.android.internal.R.string.kilobyteShort;
+ mult = 1000;
+ result = result / 1000;
}
- return new BytesResult(formattedNumber, units, rounded.roundedBytes);
- }
-
- /**
- * ICU doesn't support PETABYTE yet. Fake it so that we can treat all units the same way.
- */
- private static final MeasureUnit PETABYTE = createPetaByte();
-
- /**
- * Create a petabyte MeasureUnit without registering it with ICU.
- * ICU doesn't support user-create MeasureUnit and the only public (but hidden) method to do so
- * is {@link MeasureUnit#internalGetInstance(String, String)} which also registers the unit as
- * an available type and thus leaks it to code that doesn't expect or support it.
- * <p>This method uses reflection to create an instance of MeasureUnit to avoid leaking it. This
- * instance is <b>only</b> to be used in this class.
- */
- private static MeasureUnit createPetaByte() {
- try {
- Constructor<MeasureUnit> constructor = MeasureUnit.class
- .getDeclaredConstructor(String.class, String.class);
- constructor.setAccessible(true);
- return constructor.newInstance("digital", "petabyte");
- } catch (ReflectiveOperationException e) {
- throw new RuntimeException("Failed to create petabyte MeasureUnit", e);
+ if (result > 900) {
+ suffix = com.android.internal.R.string.megabyteShort;
+ mult *= 1000;
+ result = result / 1000;
}
- }
-
- private static class RoundedBytesResult {
- public final float value;
- public final MeasureUnit units;
- public final int fractionDigits;
- public final long roundedBytes;
-
- private RoundedBytesResult(
- float value, MeasureUnit units, int fractionDigits, long roundedBytes) {
- this.value = value;
- this.units = units;
- this.fractionDigits = fractionDigits;
- this.roundedBytes = roundedBytes;
+ if (result > 900) {
+ suffix = com.android.internal.R.string.gigabyteShort;
+ mult *= 1000;
+ result = result / 1000;
}
-
- /**
- * Returns a RoundedBytesResult object based on the input size in bytes and the rounding
- * flags. The result can be used for formatting.
- */
- static RoundedBytesResult roundBytes(long sizeBytes, int flags) {
- final boolean isNegative = (sizeBytes < 0);
- float result = isNegative ? -sizeBytes : sizeBytes;
- MeasureUnit units = MeasureUnit.BYTE;
- long mult = 1;
- if (result > 900) {
- units = MeasureUnit.KILOBYTE;
- mult = 1000;
- result = result / 1000;
- }
- if (result > 900) {
- units = MeasureUnit.MEGABYTE;
- mult *= 1000;
- result = result / 1000;
- }
- if (result > 900) {
- units = MeasureUnit.GIGABYTE;
- mult *= 1000;
- result = result / 1000;
- }
- if (result > 900) {
- units = MeasureUnit.TERABYTE;
- mult *= 1000;
- result = result / 1000;
- }
- if (result > 900) {
- units = PETABYTE;
- mult *= 1000;
- result = result / 1000;
- }
- // Note we calculate the rounded long by ourselves, but still let NumberFormat compute
- // the rounded value. NumberFormat.format(0.1) might not return "0.1" due to floating
- // point errors.
- final int roundFactor;
- final int roundDigits;
- if (mult == 1 || result >= 100) {
- roundFactor = 1;
- roundDigits = 0;
- } else if (result < 1) {
+ if (result > 900) {
+ suffix = com.android.internal.R.string.terabyteShort;
+ mult *= 1000;
+ result = result / 1000;
+ }
+ if (result > 900) {
+ suffix = com.android.internal.R.string.petabyteShort;
+ mult *= 1000;
+ result = result / 1000;
+ }
+ // Note we calculate the rounded long by ourselves, but still let String.format()
+ // compute the rounded value. String.format("%f", 0.1) might not return "0.1" due to
+ // floating point errors.
+ final int roundFactor;
+ final String roundFormat;
+ if (mult == 1 || result >= 100) {
+ roundFactor = 1;
+ roundFormat = "%.0f";
+ } else if (result < 1) {
+ roundFactor = 100;
+ roundFormat = "%.2f";
+ } else if (result < 10) {
+ if ((flags & FLAG_SHORTER) != 0) {
+ roundFactor = 10;
+ roundFormat = "%.1f";
+ } else {
roundFactor = 100;
- roundDigits = 2;
- } else if (result < 10) {
- if ((flags & FLAG_SHORTER) != 0) {
- roundFactor = 10;
- roundDigits = 1;
- } else {
- roundFactor = 100;
- roundDigits = 2;
- }
- } else { // 10 <= result < 100
- if ((flags & FLAG_SHORTER) != 0) {
- roundFactor = 1;
- roundDigits = 0;
- } else {
- roundFactor = 100;
- roundDigits = 2;
- }
+ roundFormat = "%.2f";
}
-
- if (isNegative) {
- result = -result;
+ } else { // 10 <= result < 100
+ if ((flags & FLAG_SHORTER) != 0) {
+ roundFactor = 1;
+ roundFormat = "%.0f";
+ } else {
+ roundFactor = 100;
+ roundFormat = "%.2f";
}
-
- // Note this might overflow if abs(result) >= Long.MAX_VALUE / 100, but that's like
- // 80PB so it's okay (for now)...
- final long roundedBytes =
- (flags & FLAG_CALCULATE_ROUNDED) == 0 ? 0
- : (((long) Math.round(result * roundFactor)) * mult / roundFactor);
-
- return new RoundedBytesResult(result, units, roundDigits, roundedBytes);
}
+
+ if (isNegative) {
+ result = -result;
+ }
+ final String roundedString = String.format(roundFormat, result);
+
+ // Note this might overflow if abs(result) >= Long.MAX_VALUE / 100, but that's like 80PB so
+ // it's okay (for now)...
+ final long roundedBytes =
+ (flags & FLAG_CALCULATE_ROUNDED) == 0 ? 0
+ : (((long) Math.round(result * roundFactor)) * mult / roundFactor);
+
+ final String units = res.getString(suffix);
+
+ return new BytesResult(roundedString, units, roundedBytes);
}
/**
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index 87f3bc7..d31bc1f 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -38,12 +38,12 @@
static {
DEFAULT_FLAGS = new HashMap<>();
DEFAULT_FLAGS.put("device_info_v2", "true");
- DEFAULT_FLAGS.put("new_settings_suggestion", "true");
DEFAULT_FLAGS.put("settings_search_v2", "true");
DEFAULT_FLAGS.put("settings_app_info_v2", "false");
DEFAULT_FLAGS.put("settings_connected_device_v2", "true");
DEFAULT_FLAGS.put("settings_battery_v2", "false");
DEFAULT_FLAGS.put("settings_battery_display_app_list", "false");
+ DEFAULT_FLAGS.put("settings_security_settings_v2", "false");
}
/**
diff --git a/core/java/android/util/SparseBooleanArray.java b/core/java/android/util/SparseBooleanArray.java
index 4f76463..68d347c 100644
--- a/core/java/android/util/SparseBooleanArray.java
+++ b/core/java/android/util/SparseBooleanArray.java
@@ -117,7 +117,11 @@
}
}
- /** @hide */
+ /**
+ * Removes the mapping at the specified index.
+ * <p>
+ * For indices outside of the range {@code 0...size()-1}, the behavior is undefined.
+ */
public void removeAt(int index) {
System.arraycopy(mKeys, index + 1, mKeys, index, mSize - (index + 1));
System.arraycopy(mValues, index + 1, mValues, index, mSize - (index + 1));
diff --git a/core/java/android/util/StatsManager.java b/core/java/android/util/StatsManager.java
index 26a3c36..c25b272 100644
--- a/core/java/android/util/StatsManager.java
+++ b/core/java/android/util/StatsManager.java
@@ -53,7 +53,7 @@
* @return true if successful
*/
@RequiresPermission(Manifest.permission.DUMP)
- public boolean addConfiguration(String configKey, byte[] config, String pkg, String cls) {
+ public boolean addConfiguration(long configKey, byte[] config, String pkg, String cls) {
synchronized (this) {
try {
IStatsManager service = getIStatsManagerLocked();
@@ -76,7 +76,7 @@
* @return true if successful
*/
@RequiresPermission(Manifest.permission.DUMP)
- public boolean removeConfiguration(String configKey) {
+ public boolean removeConfiguration(long configKey) {
synchronized (this) {
try {
IStatsManager service = getIStatsManagerLocked();
@@ -100,7 +100,7 @@
* @return Serialized ConfigMetricsReportList proto. Returns null on failure.
*/
@RequiresPermission(Manifest.permission.DUMP)
- public byte[] getData(String configKey) {
+ public byte[] getData(long configKey) {
synchronized (this) {
try {
IStatsManager service = getIStatsManagerLocked();
diff --git a/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java b/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java
index 0a54f3a..530937e 100644
--- a/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java
+++ b/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java
@@ -16,6 +16,21 @@
package android.util.apk;
+import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_DSA_WITH_SHA256;
+import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_ECDSA_WITH_SHA256;
+import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_ECDSA_WITH_SHA512;
+import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256;
+import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512;
+import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_RSA_PSS_WITH_SHA256;
+import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_RSA_PSS_WITH_SHA512;
+import static android.util.apk.ApkSigningBlockUtils.compareSignatureAlgorithm;
+import static android.util.apk.ApkSigningBlockUtils.getContentDigestAlgorithmJcaDigestAlgorithm;
+import static android.util.apk.ApkSigningBlockUtils.getLengthPrefixedSlice;
+import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmContentDigestAlgorithm;
+import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmJcaKeyAlgorithm;
+import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmJcaSignatureAlgorithm;
+import static android.util.apk.ApkSigningBlockUtils.readLengthPrefixedByteArray;
+
import android.util.ArrayMap;
import android.util.Pair;
@@ -23,56 +38,47 @@
import java.io.FileDescriptor;
import java.io.IOException;
import java.io.RandomAccessFile;
-import java.math.BigInteger;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.security.DigestException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
-import java.security.NoSuchProviderException;
-import java.security.Principal;
import java.security.PublicKey;
import java.security.Signature;
import java.security.SignatureException;
-import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
-import java.security.cert.CertificateExpiredException;
import java.security.cert.CertificateFactory;
-import java.security.cert.CertificateNotYetValidException;
import java.security.cert.X509Certificate;
import java.security.spec.AlgorithmParameterSpec;
import java.security.spec.InvalidKeySpecException;
-import java.security.spec.MGF1ParameterSpec;
-import java.security.spec.PSSParameterSpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Date;
import java.util.List;
import java.util.Map;
-import java.util.Set;
/**
* APK Signature Scheme v2 verifier.
*
+ * <p>APK Signature Scheme v2 is a whole-file signature scheme which aims to protect every single
+ * bit of the APK, as opposed to the JAR Signature Scheme which protects only the names and
+ * uncompressed contents of ZIP entries.
+ *
+ * @see <a href="https://source.android.com/security/apksigning/v2.html">APK Signature Scheme v2</a>
+ *
* @hide for internal use only.
*/
public class ApkSignatureSchemeV2Verifier {
/**
- * {@code .SF} file header section attribute indicating that the APK is signed not just with
- * JAR signature scheme but also with APK Signature Scheme v2 or newer. This attribute
- * facilitates v2 signature stripping detection.
- *
- * <p>The attribute contains a comma-separated set of signature scheme IDs.
+ * ID of this signature scheme as used in X-Android-APK-Signed header used in JAR signing.
*/
- public static final String SF_ATTRIBUTE_ANDROID_APK_SIGNED_NAME = "X-Android-APK-Signed";
public static final int SF_ATTRIBUTE_ANDROID_APK_SIGNED_ID = 2;
+ private static final int APK_SIGNATURE_SCHEME_V2_BLOCK_ID = 0x7109871a;
+
/**
* Returns {@code true} if the provided APK contains an APK Signature Scheme V2 signature.
*
@@ -103,7 +109,7 @@
/**
* Returns the certificates associated with each signer for the given APK without verification.
* This method is dangerous and should not be used, unless the caller is absolutely certain the
- * APK is trusted. Specifically, verification is only done for the APK Signature Scheme V2
+ * APK is trusted. Specifically, verification is only done for the APK Signature Scheme v2
* Block while gathering signer information. The APK contents are not verified.
*
* @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v2.
@@ -120,6 +126,7 @@
return verify(apk, verifyIntegrity);
}
}
+
/**
* Verifies APK Signature Scheme v2 signatures of the provided APK and returns the certificates
* associated with each signer.
@@ -144,30 +151,7 @@
*/
private static SignatureInfo findSignature(RandomAccessFile apk)
throws IOException, SignatureNotFoundException {
- // Find the ZIP End of Central Directory (EoCD) record.
- Pair<ByteBuffer, Long> eocdAndOffsetInFile = getEocd(apk);
- ByteBuffer eocd = eocdAndOffsetInFile.first;
- long eocdOffset = eocdAndOffsetInFile.second;
- if (ZipUtils.isZip64EndOfCentralDirectoryLocatorPresent(apk, eocdOffset)) {
- throw new SignatureNotFoundException("ZIP64 APK not supported");
- }
-
- // Find the APK Signing Block. The block immediately precedes the Central Directory.
- long centralDirOffset = getCentralDirOffset(eocd, eocdOffset);
- Pair<ByteBuffer, Long> apkSigningBlockAndOffsetInFile =
- findApkSigningBlock(apk, centralDirOffset);
- ByteBuffer apkSigningBlock = apkSigningBlockAndOffsetInFile.first;
- long apkSigningBlockOffset = apkSigningBlockAndOffsetInFile.second;
-
- // Find the APK Signature Scheme v2 Block inside the APK Signing Block.
- ByteBuffer apkSignatureSchemeV2Block = findApkSignatureSchemeV2Block(apkSigningBlock);
-
- return new SignatureInfo(
- apkSignatureSchemeV2Block,
- apkSigningBlockOffset,
- centralDirOffset,
- eocdOffset,
- eocd);
+ return ApkSigningBlockUtils.findSignature(apk, APK_SIGNATURE_SCHEME_V2_BLOCK_ID);
}
/**
@@ -218,7 +202,7 @@
}
if (doVerifyIntegrity) {
- verifyIntegrity(
+ ApkSigningBlockUtils.verifyIntegrity(
contentDigests,
apkFileDescriptor,
signatureInfo.apkSigningBlockOffset,
@@ -349,7 +333,8 @@
} catch (CertificateException e) {
throw new SecurityException("Failed to decode certificate #" + certificateCount, e);
}
- certificate = new VerbatimX509Certificate(certificate, encodedCert);
+ certificate = new VerbatimX509Certificate(
+ certificate, encodedCert);
certs.add(certificate);
}
@@ -363,235 +348,44 @@
"Public key mismatch between certificate and signature record");
}
+ ByteBuffer additionalAttrs = getLengthPrefixedSlice(signedData);
+ verifyAdditionalAttributes(additionalAttrs);
+
return certs.toArray(new X509Certificate[certs.size()]);
}
- private static void verifyIntegrity(
- Map<Integer, byte[]> expectedDigests,
- FileDescriptor apkFileDescriptor,
- long apkSigningBlockOffset,
- long centralDirOffset,
- long eocdOffset,
- ByteBuffer eocdBuf) throws SecurityException {
+ // Attribute to check whether a newer APK Signature Scheme signature was stripped
+ private static final int STRIPPING_PROTECTION_ATTR_ID = 0xbeeff00d;
- if (expectedDigests.isEmpty()) {
- throw new SecurityException("No digests provided");
- }
-
- // We need to verify the integrity of the following three sections of the file:
- // 1. Everything up to the start of the APK Signing Block.
- // 2. ZIP Central Directory.
- // 3. ZIP End of Central Directory (EoCD).
- // Each of these sections is represented as a separate DataSource instance below.
-
- // To handle large APKs, these sections are read in 1 MB chunks using memory-mapped I/O to
- // avoid wasting physical memory. In most APK verification scenarios, the contents of the
- // APK are already there in the OS's page cache and thus mmap does not use additional
- // physical memory.
- DataSource beforeApkSigningBlock =
- new MemoryMappedFileDataSource(apkFileDescriptor, 0, apkSigningBlockOffset);
- DataSource centralDir =
- new MemoryMappedFileDataSource(
- apkFileDescriptor, centralDirOffset, eocdOffset - centralDirOffset);
-
- // For the purposes of integrity verification, ZIP End of Central Directory's field Start of
- // Central Directory must be considered to point to the offset of the APK Signing Block.
- eocdBuf = eocdBuf.duplicate();
- eocdBuf.order(ByteOrder.LITTLE_ENDIAN);
- ZipUtils.setZipEocdCentralDirectoryOffset(eocdBuf, apkSigningBlockOffset);
- DataSource eocd = new ByteBufferDataSource(eocdBuf);
-
- int[] digestAlgorithms = new int[expectedDigests.size()];
- int digestAlgorithmCount = 0;
- for (int digestAlgorithm : expectedDigests.keySet()) {
- digestAlgorithms[digestAlgorithmCount] = digestAlgorithm;
- digestAlgorithmCount++;
- }
- byte[][] actualDigests;
- try {
- actualDigests =
- computeContentDigests(
- digestAlgorithms,
- new DataSource[] {beforeApkSigningBlock, centralDir, eocd});
- } catch (DigestException e) {
- throw new SecurityException("Failed to compute digest(s) of contents", e);
- }
- for (int i = 0; i < digestAlgorithms.length; i++) {
- int digestAlgorithm = digestAlgorithms[i];
- byte[] expectedDigest = expectedDigests.get(digestAlgorithm);
- byte[] actualDigest = actualDigests[i];
- if (!MessageDigest.isEqual(expectedDigest, actualDigest)) {
- throw new SecurityException(
- getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithm)
- + " digest of contents did not verify");
+ private static void verifyAdditionalAttributes(ByteBuffer attrs)
+ throws SecurityException, IOException {
+ while (attrs.hasRemaining()) {
+ ByteBuffer attr = getLengthPrefixedSlice(attrs);
+ if (attr.remaining() < 4) {
+ throw new IOException("Remaining buffer too short to contain additional attribute "
+ + "ID. Remaining: " + attr.remaining());
}
- }
- }
-
- private static byte[][] computeContentDigests(
- int[] digestAlgorithms,
- DataSource[] contents) throws DigestException {
- // For each digest algorithm the result is computed as follows:
- // 1. Each segment of contents is split into consecutive chunks of 1 MB in size.
- // The final chunk will be shorter iff the length of segment is not a multiple of 1 MB.
- // No chunks are produced for empty (zero length) segments.
- // 2. The digest of each chunk is computed over the concatenation of byte 0xa5, the chunk's
- // length in bytes (uint32 little-endian) and the chunk's contents.
- // 3. The output digest is computed over the concatenation of the byte 0x5a, the number of
- // chunks (uint32 little-endian) and the concatenation of digests of chunks of all
- // segments in-order.
-
- long totalChunkCountLong = 0;
- for (DataSource input : contents) {
- totalChunkCountLong += getChunkCount(input.size());
- }
- if (totalChunkCountLong >= Integer.MAX_VALUE / 1024) {
- throw new DigestException("Too many chunks: " + totalChunkCountLong);
- }
- int totalChunkCount = (int) totalChunkCountLong;
-
- byte[][] digestsOfChunks = new byte[digestAlgorithms.length][];
- for (int i = 0; i < digestAlgorithms.length; i++) {
- int digestAlgorithm = digestAlgorithms[i];
- int digestOutputSizeBytes = getContentDigestAlgorithmOutputSizeBytes(digestAlgorithm);
- byte[] concatenationOfChunkCountAndChunkDigests =
- new byte[5 + totalChunkCount * digestOutputSizeBytes];
- concatenationOfChunkCountAndChunkDigests[0] = 0x5a;
- setUnsignedInt32LittleEndian(
- totalChunkCount,
- concatenationOfChunkCountAndChunkDigests,
- 1);
- digestsOfChunks[i] = concatenationOfChunkCountAndChunkDigests;
- }
-
- byte[] chunkContentPrefix = new byte[5];
- chunkContentPrefix[0] = (byte) 0xa5;
- int chunkIndex = 0;
- MessageDigest[] mds = new MessageDigest[digestAlgorithms.length];
- for (int i = 0; i < digestAlgorithms.length; i++) {
- String jcaAlgorithmName =
- getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithms[i]);
- try {
- mds[i] = MessageDigest.getInstance(jcaAlgorithmName);
- } catch (NoSuchAlgorithmException e) {
- throw new RuntimeException(jcaAlgorithmName + " digest not supported", e);
- }
- }
- // TODO: Compute digests of chunks in parallel when beneficial. This requires some research
- // into how to parallelize (if at all) based on the capabilities of the hardware on which
- // this code is running and based on the size of input.
- DataDigester digester = new MultipleDigestDataDigester(mds);
- int dataSourceIndex = 0;
- for (DataSource input : contents) {
- long inputOffset = 0;
- long inputRemaining = input.size();
- while (inputRemaining > 0) {
- int chunkSize = (int) Math.min(inputRemaining, CHUNK_SIZE_BYTES);
- setUnsignedInt32LittleEndian(chunkSize, chunkContentPrefix, 1);
- for (int i = 0; i < mds.length; i++) {
- mds[i].update(chunkContentPrefix);
- }
- try {
- input.feedIntoDataDigester(digester, inputOffset, chunkSize);
- } catch (IOException e) {
- throw new DigestException(
- "Failed to digest chunk #" + chunkIndex + " of section #"
- + dataSourceIndex,
- e);
- }
- for (int i = 0; i < digestAlgorithms.length; i++) {
- int digestAlgorithm = digestAlgorithms[i];
- byte[] concatenationOfChunkCountAndChunkDigests = digestsOfChunks[i];
- int expectedDigestSizeBytes =
- getContentDigestAlgorithmOutputSizeBytes(digestAlgorithm);
- MessageDigest md = mds[i];
- int actualDigestSizeBytes =
- md.digest(
- concatenationOfChunkCountAndChunkDigests,
- 5 + chunkIndex * expectedDigestSizeBytes,
- expectedDigestSizeBytes);
- if (actualDigestSizeBytes != expectedDigestSizeBytes) {
- throw new RuntimeException(
- "Unexpected output size of " + md.getAlgorithm() + " digest: "
- + actualDigestSizeBytes);
+ int id = attr.getInt();
+ switch (id) {
+ case STRIPPING_PROTECTION_ATTR_ID:
+ if (attr.remaining() < 4) {
+ throw new IOException("V2 Signature Scheme Stripping Protection Attribute "
+ + " value too small. Expected 4 bytes, but found "
+ + attr.remaining());
}
- }
- inputOffset += chunkSize;
- inputRemaining -= chunkSize;
- chunkIndex++;
+ int vers = attr.getInt();
+ if (vers == ApkSignatureSchemeV3Verifier.SF_ATTRIBUTE_ANDROID_APK_SIGNED_ID) {
+ throw new SecurityException("V2 signature indicates APK is signed using APK"
+ + " Signature Scheme v3, but none was found. Signature stripped?");
+ }
+ break;
+ default:
+ // not the droid we're looking for, move along, move along.
+ break;
}
- dataSourceIndex++;
}
-
- byte[][] result = new byte[digestAlgorithms.length][];
- for (int i = 0; i < digestAlgorithms.length; i++) {
- int digestAlgorithm = digestAlgorithms[i];
- byte[] input = digestsOfChunks[i];
- String jcaAlgorithmName = getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithm);
- MessageDigest md;
- try {
- md = MessageDigest.getInstance(jcaAlgorithmName);
- } catch (NoSuchAlgorithmException e) {
- throw new RuntimeException(jcaAlgorithmName + " digest not supported", e);
- }
- byte[] output = md.digest(input);
- result[i] = output;
- }
- return result;
+ return;
}
-
- /**
- * Returns the ZIP End of Central Directory (EoCD) and its offset in the file.
- *
- * @throws IOException if an I/O error occurs while reading the file.
- * @throws SignatureNotFoundException if the EoCD could not be found.
- */
- private static Pair<ByteBuffer, Long> getEocd(RandomAccessFile apk)
- throws IOException, SignatureNotFoundException {
- Pair<ByteBuffer, Long> eocdAndOffsetInFile =
- ZipUtils.findZipEndOfCentralDirectoryRecord(apk);
- if (eocdAndOffsetInFile == null) {
- throw new SignatureNotFoundException(
- "Not an APK file: ZIP End of Central Directory record not found");
- }
- return eocdAndOffsetInFile;
- }
-
- private static long getCentralDirOffset(ByteBuffer eocd, long eocdOffset)
- throws SignatureNotFoundException {
- // Look up the offset of ZIP Central Directory.
- long centralDirOffset = ZipUtils.getZipEocdCentralDirectoryOffset(eocd);
- if (centralDirOffset > eocdOffset) {
- throw new SignatureNotFoundException(
- "ZIP Central Directory offset out of range: " + centralDirOffset
- + ". ZIP End of Central Directory offset: " + eocdOffset);
- }
- long centralDirSize = ZipUtils.getZipEocdCentralDirectorySizeBytes(eocd);
- if (centralDirOffset + centralDirSize != eocdOffset) {
- throw new SignatureNotFoundException(
- "ZIP Central Directory is not immediately followed by End of Central"
- + " Directory");
- }
- return centralDirOffset;
- }
-
- private static final long getChunkCount(long inputSizeBytes) {
- return (inputSizeBytes + CHUNK_SIZE_BYTES - 1) / CHUNK_SIZE_BYTES;
- }
-
- private static final int CHUNK_SIZE_BYTES = 1024 * 1024;
-
- private static final int SIGNATURE_RSA_PSS_WITH_SHA256 = 0x0101;
- private static final int SIGNATURE_RSA_PSS_WITH_SHA512 = 0x0102;
- private static final int SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256 = 0x0103;
- private static final int SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512 = 0x0104;
- private static final int SIGNATURE_ECDSA_WITH_SHA256 = 0x0201;
- private static final int SIGNATURE_ECDSA_WITH_SHA512 = 0x0202;
- private static final int SIGNATURE_DSA_WITH_SHA256 = 0x0301;
-
- private static final int CONTENT_DIGEST_CHUNKED_SHA256 = 1;
- private static final int CONTENT_DIGEST_CHUNKED_SHA512 = 2;
-
private static boolean isSupportedSignatureAlgorithm(int sigAlgorithm) {
switch (sigAlgorithm) {
case SIGNATURE_RSA_PSS_WITH_SHA256:
@@ -606,519 +400,4 @@
return false;
}
}
-
- private static int compareSignatureAlgorithm(int sigAlgorithm1, int sigAlgorithm2) {
- int digestAlgorithm1 = getSignatureAlgorithmContentDigestAlgorithm(sigAlgorithm1);
- int digestAlgorithm2 = getSignatureAlgorithmContentDigestAlgorithm(sigAlgorithm2);
- return compareContentDigestAlgorithm(digestAlgorithm1, digestAlgorithm2);
- }
-
- private static int compareContentDigestAlgorithm(int digestAlgorithm1, int digestAlgorithm2) {
- switch (digestAlgorithm1) {
- case CONTENT_DIGEST_CHUNKED_SHA256:
- switch (digestAlgorithm2) {
- case CONTENT_DIGEST_CHUNKED_SHA256:
- return 0;
- case CONTENT_DIGEST_CHUNKED_SHA512:
- return -1;
- default:
- throw new IllegalArgumentException(
- "Unknown digestAlgorithm2: " + digestAlgorithm2);
- }
- case CONTENT_DIGEST_CHUNKED_SHA512:
- switch (digestAlgorithm2) {
- case CONTENT_DIGEST_CHUNKED_SHA256:
- return 1;
- case CONTENT_DIGEST_CHUNKED_SHA512:
- return 0;
- default:
- throw new IllegalArgumentException(
- "Unknown digestAlgorithm2: " + digestAlgorithm2);
- }
- default:
- throw new IllegalArgumentException("Unknown digestAlgorithm1: " + digestAlgorithm1);
- }
- }
-
- private static int getSignatureAlgorithmContentDigestAlgorithm(int sigAlgorithm) {
- switch (sigAlgorithm) {
- case SIGNATURE_RSA_PSS_WITH_SHA256:
- case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256:
- case SIGNATURE_ECDSA_WITH_SHA256:
- case SIGNATURE_DSA_WITH_SHA256:
- return CONTENT_DIGEST_CHUNKED_SHA256;
- case SIGNATURE_RSA_PSS_WITH_SHA512:
- case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512:
- case SIGNATURE_ECDSA_WITH_SHA512:
- return CONTENT_DIGEST_CHUNKED_SHA512;
- default:
- throw new IllegalArgumentException(
- "Unknown signature algorithm: 0x"
- + Long.toHexString(sigAlgorithm & 0xffffffff));
- }
- }
-
- private static String getContentDigestAlgorithmJcaDigestAlgorithm(int digestAlgorithm) {
- switch (digestAlgorithm) {
- case CONTENT_DIGEST_CHUNKED_SHA256:
- return "SHA-256";
- case CONTENT_DIGEST_CHUNKED_SHA512:
- return "SHA-512";
- default:
- throw new IllegalArgumentException(
- "Unknown content digest algorthm: " + digestAlgorithm);
- }
- }
-
- private static int getContentDigestAlgorithmOutputSizeBytes(int digestAlgorithm) {
- switch (digestAlgorithm) {
- case CONTENT_DIGEST_CHUNKED_SHA256:
- return 256 / 8;
- case CONTENT_DIGEST_CHUNKED_SHA512:
- return 512 / 8;
- default:
- throw new IllegalArgumentException(
- "Unknown content digest algorthm: " + digestAlgorithm);
- }
- }
-
- private static String getSignatureAlgorithmJcaKeyAlgorithm(int sigAlgorithm) {
- switch (sigAlgorithm) {
- case SIGNATURE_RSA_PSS_WITH_SHA256:
- case SIGNATURE_RSA_PSS_WITH_SHA512:
- case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256:
- case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512:
- return "RSA";
- case SIGNATURE_ECDSA_WITH_SHA256:
- case SIGNATURE_ECDSA_WITH_SHA512:
- return "EC";
- case SIGNATURE_DSA_WITH_SHA256:
- return "DSA";
- default:
- throw new IllegalArgumentException(
- "Unknown signature algorithm: 0x"
- + Long.toHexString(sigAlgorithm & 0xffffffff));
- }
- }
-
- private static Pair<String, ? extends AlgorithmParameterSpec>
- getSignatureAlgorithmJcaSignatureAlgorithm(int sigAlgorithm) {
- switch (sigAlgorithm) {
- case SIGNATURE_RSA_PSS_WITH_SHA256:
- return Pair.create(
- "SHA256withRSA/PSS",
- new PSSParameterSpec(
- "SHA-256", "MGF1", MGF1ParameterSpec.SHA256, 256 / 8, 1));
- case SIGNATURE_RSA_PSS_WITH_SHA512:
- return Pair.create(
- "SHA512withRSA/PSS",
- new PSSParameterSpec(
- "SHA-512", "MGF1", MGF1ParameterSpec.SHA512, 512 / 8, 1));
- case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256:
- return Pair.create("SHA256withRSA", null);
- case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512:
- return Pair.create("SHA512withRSA", null);
- case SIGNATURE_ECDSA_WITH_SHA256:
- return Pair.create("SHA256withECDSA", null);
- case SIGNATURE_ECDSA_WITH_SHA512:
- return Pair.create("SHA512withECDSA", null);
- case SIGNATURE_DSA_WITH_SHA256:
- return Pair.create("SHA256withDSA", null);
- default:
- throw new IllegalArgumentException(
- "Unknown signature algorithm: 0x"
- + Long.toHexString(sigAlgorithm & 0xffffffff));
- }
- }
-
- /**
- * Returns new byte buffer whose content is a shared subsequence of this buffer's content
- * between the specified start (inclusive) and end (exclusive) positions. As opposed to
- * {@link ByteBuffer#slice()}, the returned buffer's byte order is the same as the source
- * buffer's byte order.
- */
- private static ByteBuffer sliceFromTo(ByteBuffer source, int start, int end) {
- if (start < 0) {
- throw new IllegalArgumentException("start: " + start);
- }
- if (end < start) {
- throw new IllegalArgumentException("end < start: " + end + " < " + start);
- }
- int capacity = source.capacity();
- if (end > source.capacity()) {
- throw new IllegalArgumentException("end > capacity: " + end + " > " + capacity);
- }
- int originalLimit = source.limit();
- int originalPosition = source.position();
- try {
- source.position(0);
- source.limit(end);
- source.position(start);
- ByteBuffer result = source.slice();
- result.order(source.order());
- return result;
- } finally {
- source.position(0);
- source.limit(originalLimit);
- source.position(originalPosition);
- }
- }
-
- /**
- * Relative <em>get</em> method for reading {@code size} number of bytes from the current
- * position of this buffer.
- *
- * <p>This method reads the next {@code size} bytes at this buffer's current position,
- * returning them as a {@code ByteBuffer} with start set to 0, limit and capacity set to
- * {@code size}, byte order set to this buffer's byte order; and then increments the position by
- * {@code size}.
- */
- private static ByteBuffer getByteBuffer(ByteBuffer source, int size)
- throws BufferUnderflowException {
- if (size < 0) {
- throw new IllegalArgumentException("size: " + size);
- }
- int originalLimit = source.limit();
- int position = source.position();
- int limit = position + size;
- if ((limit < position) || (limit > originalLimit)) {
- throw new BufferUnderflowException();
- }
- source.limit(limit);
- try {
- ByteBuffer result = source.slice();
- result.order(source.order());
- source.position(limit);
- return result;
- } finally {
- source.limit(originalLimit);
- }
- }
-
- private static ByteBuffer getLengthPrefixedSlice(ByteBuffer source) throws IOException {
- if (source.remaining() < 4) {
- throw new IOException(
- "Remaining buffer too short to contain length of length-prefixed field."
- + " Remaining: " + source.remaining());
- }
- int len = source.getInt();
- if (len < 0) {
- throw new IllegalArgumentException("Negative length");
- } else if (len > source.remaining()) {
- throw new IOException("Length-prefixed field longer than remaining buffer."
- + " Field length: " + len + ", remaining: " + source.remaining());
- }
- return getByteBuffer(source, len);
- }
-
- private static byte[] readLengthPrefixedByteArray(ByteBuffer buf) throws IOException {
- int len = buf.getInt();
- if (len < 0) {
- throw new IOException("Negative length");
- } else if (len > buf.remaining()) {
- throw new IOException("Underflow while reading length-prefixed value. Length: " + len
- + ", available: " + buf.remaining());
- }
- byte[] result = new byte[len];
- buf.get(result);
- return result;
- }
-
- private static void setUnsignedInt32LittleEndian(int value, byte[] result, int offset) {
- result[offset] = (byte) (value & 0xff);
- result[offset + 1] = (byte) ((value >>> 8) & 0xff);
- result[offset + 2] = (byte) ((value >>> 16) & 0xff);
- result[offset + 3] = (byte) ((value >>> 24) & 0xff);
- }
-
- private static final long APK_SIG_BLOCK_MAGIC_HI = 0x3234206b636f6c42L;
- private static final long APK_SIG_BLOCK_MAGIC_LO = 0x20676953204b5041L;
- private static final int APK_SIG_BLOCK_MIN_SIZE = 32;
-
- private static final int APK_SIGNATURE_SCHEME_V2_BLOCK_ID = 0x7109871a;
-
- private static Pair<ByteBuffer, Long> findApkSigningBlock(
- RandomAccessFile apk, long centralDirOffset)
- throws IOException, SignatureNotFoundException {
- // FORMAT:
- // OFFSET DATA TYPE DESCRIPTION
- // * @+0 bytes uint64: size in bytes (excluding this field)
- // * @+8 bytes payload
- // * @-24 bytes uint64: size in bytes (same as the one above)
- // * @-16 bytes uint128: magic
-
- if (centralDirOffset < APK_SIG_BLOCK_MIN_SIZE) {
- throw new SignatureNotFoundException(
- "APK too small for APK Signing Block. ZIP Central Directory offset: "
- + centralDirOffset);
- }
- // Read the magic and offset in file from the footer section of the block:
- // * uint64: size of block
- // * 16 bytes: magic
- ByteBuffer footer = ByteBuffer.allocate(24);
- footer.order(ByteOrder.LITTLE_ENDIAN);
- apk.seek(centralDirOffset - footer.capacity());
- apk.readFully(footer.array(), footer.arrayOffset(), footer.capacity());
- if ((footer.getLong(8) != APK_SIG_BLOCK_MAGIC_LO)
- || (footer.getLong(16) != APK_SIG_BLOCK_MAGIC_HI)) {
- throw new SignatureNotFoundException(
- "No APK Signing Block before ZIP Central Directory");
- }
- // Read and compare size fields
- long apkSigBlockSizeInFooter = footer.getLong(0);
- if ((apkSigBlockSizeInFooter < footer.capacity())
- || (apkSigBlockSizeInFooter > Integer.MAX_VALUE - 8)) {
- throw new SignatureNotFoundException(
- "APK Signing Block size out of range: " + apkSigBlockSizeInFooter);
- }
- int totalSize = (int) (apkSigBlockSizeInFooter + 8);
- long apkSigBlockOffset = centralDirOffset - totalSize;
- if (apkSigBlockOffset < 0) {
- throw new SignatureNotFoundException(
- "APK Signing Block offset out of range: " + apkSigBlockOffset);
- }
- ByteBuffer apkSigBlock = ByteBuffer.allocate(totalSize);
- apkSigBlock.order(ByteOrder.LITTLE_ENDIAN);
- apk.seek(apkSigBlockOffset);
- apk.readFully(apkSigBlock.array(), apkSigBlock.arrayOffset(), apkSigBlock.capacity());
- long apkSigBlockSizeInHeader = apkSigBlock.getLong(0);
- if (apkSigBlockSizeInHeader != apkSigBlockSizeInFooter) {
- throw new SignatureNotFoundException(
- "APK Signing Block sizes in header and footer do not match: "
- + apkSigBlockSizeInHeader + " vs " + apkSigBlockSizeInFooter);
- }
- return Pair.create(apkSigBlock, apkSigBlockOffset);
- }
-
- private static ByteBuffer findApkSignatureSchemeV2Block(ByteBuffer apkSigningBlock)
- throws SignatureNotFoundException {
- checkByteOrderLittleEndian(apkSigningBlock);
- // FORMAT:
- // OFFSET DATA TYPE DESCRIPTION
- // * @+0 bytes uint64: size in bytes (excluding this field)
- // * @+8 bytes pairs
- // * @-24 bytes uint64: size in bytes (same as the one above)
- // * @-16 bytes uint128: magic
- ByteBuffer pairs = sliceFromTo(apkSigningBlock, 8, apkSigningBlock.capacity() - 24);
-
- int entryCount = 0;
- while (pairs.hasRemaining()) {
- entryCount++;
- if (pairs.remaining() < 8) {
- throw new SignatureNotFoundException(
- "Insufficient data to read size of APK Signing Block entry #" + entryCount);
- }
- long lenLong = pairs.getLong();
- if ((lenLong < 4) || (lenLong > Integer.MAX_VALUE)) {
- throw new SignatureNotFoundException(
- "APK Signing Block entry #" + entryCount
- + " size out of range: " + lenLong);
- }
- int len = (int) lenLong;
- int nextEntryPos = pairs.position() + len;
- if (len > pairs.remaining()) {
- throw new SignatureNotFoundException(
- "APK Signing Block entry #" + entryCount + " size out of range: " + len
- + ", available: " + pairs.remaining());
- }
- int id = pairs.getInt();
- if (id == APK_SIGNATURE_SCHEME_V2_BLOCK_ID) {
- return getByteBuffer(pairs, len - 4);
- }
- pairs.position(nextEntryPos);
- }
-
- throw new SignatureNotFoundException(
- "No APK Signature Scheme v2 block in APK Signing Block");
- }
-
- private static void checkByteOrderLittleEndian(ByteBuffer buffer) {
- if (buffer.order() != ByteOrder.LITTLE_ENDIAN) {
- throw new IllegalArgumentException("ByteBuffer byte order must be little endian");
- }
- }
-
- /**
- * {@link DataDigester} that updates multiple {@link MessageDigest}s whenever data is feeded.
- */
- private static class MultipleDigestDataDigester implements DataDigester {
- private final MessageDigest[] mMds;
-
- MultipleDigestDataDigester(MessageDigest[] mds) {
- mMds = mds;
- }
-
- @Override
- public void consume(ByteBuffer buffer) {
- buffer = buffer.slice();
- for (MessageDigest md : mMds) {
- buffer.position(0);
- md.update(buffer);
- }
- }
-
- @Override
- public void finish() {}
- }
-
- /**
- * For legacy reasons we need to return exactly the original encoded certificate bytes, instead
- * of letting the underlying implementation have a shot at re-encoding the data.
- */
- private static class VerbatimX509Certificate extends WrappedX509Certificate {
- private byte[] encodedVerbatim;
-
- public VerbatimX509Certificate(X509Certificate wrapped, byte[] encodedVerbatim) {
- super(wrapped);
- this.encodedVerbatim = encodedVerbatim;
- }
-
- @Override
- public byte[] getEncoded() throws CertificateEncodingException {
- return encodedVerbatim;
- }
- }
-
- private static class WrappedX509Certificate extends X509Certificate {
- private final X509Certificate wrapped;
-
- public WrappedX509Certificate(X509Certificate wrapped) {
- this.wrapped = wrapped;
- }
-
- @Override
- public Set<String> getCriticalExtensionOIDs() {
- return wrapped.getCriticalExtensionOIDs();
- }
-
- @Override
- public byte[] getExtensionValue(String oid) {
- return wrapped.getExtensionValue(oid);
- }
-
- @Override
- public Set<String> getNonCriticalExtensionOIDs() {
- return wrapped.getNonCriticalExtensionOIDs();
- }
-
- @Override
- public boolean hasUnsupportedCriticalExtension() {
- return wrapped.hasUnsupportedCriticalExtension();
- }
-
- @Override
- public void checkValidity()
- throws CertificateExpiredException, CertificateNotYetValidException {
- wrapped.checkValidity();
- }
-
- @Override
- public void checkValidity(Date date)
- throws CertificateExpiredException, CertificateNotYetValidException {
- wrapped.checkValidity(date);
- }
-
- @Override
- public int getVersion() {
- return wrapped.getVersion();
- }
-
- @Override
- public BigInteger getSerialNumber() {
- return wrapped.getSerialNumber();
- }
-
- @Override
- public Principal getIssuerDN() {
- return wrapped.getIssuerDN();
- }
-
- @Override
- public Principal getSubjectDN() {
- return wrapped.getSubjectDN();
- }
-
- @Override
- public Date getNotBefore() {
- return wrapped.getNotBefore();
- }
-
- @Override
- public Date getNotAfter() {
- return wrapped.getNotAfter();
- }
-
- @Override
- public byte[] getTBSCertificate() throws CertificateEncodingException {
- return wrapped.getTBSCertificate();
- }
-
- @Override
- public byte[] getSignature() {
- return wrapped.getSignature();
- }
-
- @Override
- public String getSigAlgName() {
- return wrapped.getSigAlgName();
- }
-
- @Override
- public String getSigAlgOID() {
- return wrapped.getSigAlgOID();
- }
-
- @Override
- public byte[] getSigAlgParams() {
- return wrapped.getSigAlgParams();
- }
-
- @Override
- public boolean[] getIssuerUniqueID() {
- return wrapped.getIssuerUniqueID();
- }
-
- @Override
- public boolean[] getSubjectUniqueID() {
- return wrapped.getSubjectUniqueID();
- }
-
- @Override
- public boolean[] getKeyUsage() {
- return wrapped.getKeyUsage();
- }
-
- @Override
- public int getBasicConstraints() {
- return wrapped.getBasicConstraints();
- }
-
- @Override
- public byte[] getEncoded() throws CertificateEncodingException {
- return wrapped.getEncoded();
- }
-
- @Override
- public void verify(PublicKey key) throws CertificateException, NoSuchAlgorithmException,
- InvalidKeyException, NoSuchProviderException, SignatureException {
- wrapped.verify(key);
- }
-
- @Override
- public void verify(PublicKey key, String sigProvider)
- throws CertificateException, NoSuchAlgorithmException, InvalidKeyException,
- NoSuchProviderException, SignatureException {
- wrapped.verify(key, sigProvider);
- }
-
- @Override
- public String toString() {
- return wrapped.toString();
- }
-
- @Override
- public PublicKey getPublicKey() {
- return wrapped.getPublicKey();
- }
- }
}
diff --git a/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java b/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java
new file mode 100644
index 0000000..e43dee3
--- /dev/null
+++ b/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java
@@ -0,0 +1,558 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.util.apk;
+
+import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_DSA_WITH_SHA256;
+import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_ECDSA_WITH_SHA256;
+import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_ECDSA_WITH_SHA512;
+import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256;
+import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512;
+import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_RSA_PSS_WITH_SHA256;
+import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_RSA_PSS_WITH_SHA512;
+import static android.util.apk.ApkSigningBlockUtils.compareSignatureAlgorithm;
+import static android.util.apk.ApkSigningBlockUtils.getContentDigestAlgorithmJcaDigestAlgorithm;
+import static android.util.apk.ApkSigningBlockUtils.getLengthPrefixedSlice;
+import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmContentDigestAlgorithm;
+import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmJcaKeyAlgorithm;
+import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmJcaSignatureAlgorithm;
+import static android.util.apk.ApkSigningBlockUtils.readLengthPrefixedByteArray;
+
+import android.os.Build;
+import android.util.ArrayMap;
+import android.util.Pair;
+
+import java.io.ByteArrayInputStream;
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.KeyFactory;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.PublicKey;
+import java.security.Signature;
+import java.security.SignatureException;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.security.spec.AlgorithmParameterSpec;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.X509EncodedKeySpec;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * APK Signature Scheme v3 verifier.
+ *
+ * @hide for internal use only.
+ */
+public class ApkSignatureSchemeV3Verifier {
+
+ /**
+ * ID of this signature scheme as used in X-Android-APK-Signed header used in JAR signing.
+ */
+ public static final int SF_ATTRIBUTE_ANDROID_APK_SIGNED_ID = 3;
+
+ private static final int APK_SIGNATURE_SCHEME_V3_BLOCK_ID = 0xf05368c0;
+
+ /**
+ * Returns {@code true} if the provided APK contains an APK Signature Scheme V3 signature.
+ *
+ * <p><b>NOTE: This method does not verify the signature.</b>
+ */
+ public static boolean hasSignature(String apkFile) throws IOException {
+ try (RandomAccessFile apk = new RandomAccessFile(apkFile, "r")) {
+ findSignature(apk);
+ return true;
+ } catch (SignatureNotFoundException e) {
+ return false;
+ }
+ }
+
+ /**
+ * Verifies APK Signature Scheme v3 signatures of the provided APK and returns the certificates
+ * associated with each signer.
+ *
+ * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v3.
+ * @throws SecurityException if the APK Signature Scheme v3 signature of this APK does not
+ * verify.
+ * @throws IOException if an I/O error occurs while reading the APK file.
+ */
+ public static VerifiedSigner verify(String apkFile)
+ throws SignatureNotFoundException, SecurityException, IOException {
+ return verify(apkFile, true);
+ }
+
+ /**
+ * Returns the certificates associated with each signer for the given APK without verification.
+ * This method is dangerous and should not be used, unless the caller is absolutely certain the
+ * APK is trusted. Specifically, verification is only done for the APK Signature Scheme v3
+ * Block while gathering signer information. The APK contents are not verified.
+ *
+ * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v3.
+ * @throws IOException if an I/O error occurs while reading the APK file.
+ */
+ public static VerifiedSigner plsCertsNoVerifyOnlyCerts(String apkFile)
+ throws SignatureNotFoundException, SecurityException, IOException {
+ return verify(apkFile, false);
+ }
+
+ private static VerifiedSigner verify(String apkFile, boolean verifyIntegrity)
+ throws SignatureNotFoundException, SecurityException, IOException {
+ try (RandomAccessFile apk = new RandomAccessFile(apkFile, "r")) {
+ return verify(apk, verifyIntegrity);
+ }
+ }
+
+ /**
+ * Verifies APK Signature Scheme v3 signatures of the provided APK and returns the certificates
+ * associated with each signer.
+ *
+ * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v3.
+ * @throws SecurityException if an APK Signature Scheme v3 signature of this APK does not
+ * verify.
+ * @throws IOException if an I/O error occurs while reading the APK file.
+ */
+ private static VerifiedSigner verify(RandomAccessFile apk, boolean verifyIntegrity)
+ throws SignatureNotFoundException, SecurityException, IOException {
+ SignatureInfo signatureInfo = findSignature(apk);
+ return verify(apk.getFD(), signatureInfo, verifyIntegrity);
+ }
+
+ /**
+ * Returns the APK Signature Scheme v3 block contained in the provided APK file and the
+ * additional information relevant for verifying the block against the file.
+ *
+ * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v3.
+ * @throws IOException if an I/O error occurs while reading the APK file.
+ */
+ private static SignatureInfo findSignature(RandomAccessFile apk)
+ throws IOException, SignatureNotFoundException {
+ return ApkSigningBlockUtils.findSignature(apk, APK_SIGNATURE_SCHEME_V3_BLOCK_ID);
+ }
+
+ /**
+ * Verifies the contents of the provided APK file against the provided APK Signature Scheme v3
+ * Block.
+ *
+ * @param signatureInfo APK Signature Scheme v3 Block and information relevant for verifying it
+ * against the APK file.
+ */
+ private static VerifiedSigner verify(
+ FileDescriptor apkFileDescriptor,
+ SignatureInfo signatureInfo,
+ boolean doVerifyIntegrity) throws SecurityException {
+ int signerCount = 0;
+ Map<Integer, byte[]> contentDigests = new ArrayMap<>();
+ VerifiedSigner result = null;
+ CertificateFactory certFactory;
+ try {
+ certFactory = CertificateFactory.getInstance("X.509");
+ } catch (CertificateException e) {
+ throw new RuntimeException("Failed to obtain X.509 CertificateFactory", e);
+ }
+ ByteBuffer signers;
+ try {
+ signers = getLengthPrefixedSlice(signatureInfo.signatureBlock);
+ } catch (IOException e) {
+ throw new SecurityException("Failed to read list of signers", e);
+ }
+ while (signers.hasRemaining()) {
+ try {
+ ByteBuffer signer = getLengthPrefixedSlice(signers);
+ result = verifySigner(signer, contentDigests, certFactory);
+ signerCount++;
+ } catch (PlatformNotSupportedException e) {
+ // this signer is for a different platform, ignore it.
+ continue;
+ } catch (IOException | BufferUnderflowException | SecurityException e) {
+ throw new SecurityException(
+ "Failed to parse/verify signer #" + signerCount + " block",
+ e);
+ }
+ }
+
+ if (signerCount < 1 || result == null) {
+ throw new SecurityException("No signers found");
+ }
+
+ if (signerCount != 1) {
+ throw new SecurityException("APK Signature Scheme V3 only supports one signer: "
+ + "multiple signers found.");
+ }
+
+ if (contentDigests.isEmpty()) {
+ throw new SecurityException("No content digests found");
+ }
+
+ if (doVerifyIntegrity) {
+ ApkSigningBlockUtils.verifyIntegrity(
+ contentDigests,
+ apkFileDescriptor,
+ signatureInfo.apkSigningBlockOffset,
+ signatureInfo.centralDirOffset,
+ signatureInfo.eocdOffset,
+ signatureInfo.eocd);
+ }
+
+ return result;
+ }
+
+ private static VerifiedSigner verifySigner(
+ ByteBuffer signerBlock,
+ Map<Integer, byte[]> contentDigests,
+ CertificateFactory certFactory)
+ throws SecurityException, IOException, PlatformNotSupportedException {
+ ByteBuffer signedData = getLengthPrefixedSlice(signerBlock);
+ int minSdkVersion = signerBlock.getInt();
+ int maxSdkVersion = signerBlock.getInt();
+
+ if (Build.VERSION.SDK_INT < minSdkVersion || Build.VERSION.SDK_INT > maxSdkVersion) {
+ // this signature isn't meant to be used with this platform, skip it.
+ throw new PlatformNotSupportedException(
+ "Signer not supported by this platform "
+ + "version. This platform: " + Build.VERSION.SDK_INT
+ + ", signer minSdkVersion: " + minSdkVersion
+ + ", maxSdkVersion: " + maxSdkVersion);
+ }
+
+ ByteBuffer signatures = getLengthPrefixedSlice(signerBlock);
+ byte[] publicKeyBytes = readLengthPrefixedByteArray(signerBlock);
+
+ int signatureCount = 0;
+ int bestSigAlgorithm = -1;
+ byte[] bestSigAlgorithmSignatureBytes = null;
+ List<Integer> signaturesSigAlgorithms = new ArrayList<>();
+ while (signatures.hasRemaining()) {
+ signatureCount++;
+ try {
+ ByteBuffer signature = getLengthPrefixedSlice(signatures);
+ if (signature.remaining() < 8) {
+ throw new SecurityException("Signature record too short");
+ }
+ int sigAlgorithm = signature.getInt();
+ signaturesSigAlgorithms.add(sigAlgorithm);
+ if (!isSupportedSignatureAlgorithm(sigAlgorithm)) {
+ continue;
+ }
+ if ((bestSigAlgorithm == -1)
+ || (compareSignatureAlgorithm(sigAlgorithm, bestSigAlgorithm) > 0)) {
+ bestSigAlgorithm = sigAlgorithm;
+ bestSigAlgorithmSignatureBytes = readLengthPrefixedByteArray(signature);
+ }
+ } catch (IOException | BufferUnderflowException e) {
+ throw new SecurityException(
+ "Failed to parse signature record #" + signatureCount,
+ e);
+ }
+ }
+ if (bestSigAlgorithm == -1) {
+ if (signatureCount == 0) {
+ throw new SecurityException("No signatures found");
+ } else {
+ throw new SecurityException("No supported signatures found");
+ }
+ }
+
+ String keyAlgorithm = getSignatureAlgorithmJcaKeyAlgorithm(bestSigAlgorithm);
+ Pair<String, ? extends AlgorithmParameterSpec> signatureAlgorithmParams =
+ getSignatureAlgorithmJcaSignatureAlgorithm(bestSigAlgorithm);
+ String jcaSignatureAlgorithm = signatureAlgorithmParams.first;
+ AlgorithmParameterSpec jcaSignatureAlgorithmParams = signatureAlgorithmParams.second;
+ boolean sigVerified;
+ try {
+ PublicKey publicKey =
+ KeyFactory.getInstance(keyAlgorithm)
+ .generatePublic(new X509EncodedKeySpec(publicKeyBytes));
+ Signature sig = Signature.getInstance(jcaSignatureAlgorithm);
+ sig.initVerify(publicKey);
+ if (jcaSignatureAlgorithmParams != null) {
+ sig.setParameter(jcaSignatureAlgorithmParams);
+ }
+ sig.update(signedData);
+ sigVerified = sig.verify(bestSigAlgorithmSignatureBytes);
+ } catch (NoSuchAlgorithmException | InvalidKeySpecException | InvalidKeyException
+ | InvalidAlgorithmParameterException | SignatureException e) {
+ throw new SecurityException(
+ "Failed to verify " + jcaSignatureAlgorithm + " signature", e);
+ }
+ if (!sigVerified) {
+ throw new SecurityException(jcaSignatureAlgorithm + " signature did not verify");
+ }
+
+ // Signature over signedData has verified.
+
+ byte[] contentDigest = null;
+ signedData.clear();
+ ByteBuffer digests = getLengthPrefixedSlice(signedData);
+ List<Integer> digestsSigAlgorithms = new ArrayList<>();
+ int digestCount = 0;
+ while (digests.hasRemaining()) {
+ digestCount++;
+ try {
+ ByteBuffer digest = getLengthPrefixedSlice(digests);
+ if (digest.remaining() < 8) {
+ throw new IOException("Record too short");
+ }
+ int sigAlgorithm = digest.getInt();
+ digestsSigAlgorithms.add(sigAlgorithm);
+ if (sigAlgorithm == bestSigAlgorithm) {
+ contentDigest = readLengthPrefixedByteArray(digest);
+ }
+ } catch (IOException | BufferUnderflowException e) {
+ throw new IOException("Failed to parse digest record #" + digestCount, e);
+ }
+ }
+
+ if (!signaturesSigAlgorithms.equals(digestsSigAlgorithms)) {
+ throw new SecurityException(
+ "Signature algorithms don't match between digests and signatures records");
+ }
+ int digestAlgorithm = getSignatureAlgorithmContentDigestAlgorithm(bestSigAlgorithm);
+ byte[] previousSignerDigest = contentDigests.put(digestAlgorithm, contentDigest);
+ if ((previousSignerDigest != null)
+ && (!MessageDigest.isEqual(previousSignerDigest, contentDigest))) {
+ throw new SecurityException(
+ getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithm)
+ + " contents digest does not match the digest specified by a preceding signer");
+ }
+
+ ByteBuffer certificates = getLengthPrefixedSlice(signedData);
+ List<X509Certificate> certs = new ArrayList<>();
+ int certificateCount = 0;
+ while (certificates.hasRemaining()) {
+ certificateCount++;
+ byte[] encodedCert = readLengthPrefixedByteArray(certificates);
+ X509Certificate certificate;
+ try {
+ certificate = (X509Certificate)
+ certFactory.generateCertificate(new ByteArrayInputStream(encodedCert));
+ } catch (CertificateException e) {
+ throw new SecurityException("Failed to decode certificate #" + certificateCount, e);
+ }
+ certificate = new VerbatimX509Certificate(
+ certificate, encodedCert);
+ certs.add(certificate);
+ }
+
+ if (certs.isEmpty()) {
+ throw new SecurityException("No certificates listed");
+ }
+ X509Certificate mainCertificate = certs.get(0);
+ byte[] certificatePublicKeyBytes = mainCertificate.getPublicKey().getEncoded();
+ if (!Arrays.equals(publicKeyBytes, certificatePublicKeyBytes)) {
+ throw new SecurityException(
+ "Public key mismatch between certificate and signature record");
+ }
+
+ int signedMinSDK = signedData.getInt();
+ if (signedMinSDK != minSdkVersion) {
+ throw new SecurityException(
+ "minSdkVersion mismatch between signed and unsigned in v3 signer block.");
+ }
+
+ int signedMaxSDK = signedData.getInt();
+ if (signedMaxSDK != maxSdkVersion) {
+ throw new SecurityException(
+ "maxSdkVersion mismatch between signed and unsigned in v3 signer block.");
+ }
+
+ ByteBuffer additionalAttrs = getLengthPrefixedSlice(signedData);
+ return verifyAdditionalAttributes(additionalAttrs, certs, certFactory);
+ }
+
+ private static final int PROOF_OF_ROTATION_ATTR_ID = 0x3ba06f8c;
+
+ private static VerifiedSigner verifyAdditionalAttributes(ByteBuffer attrs,
+ List<X509Certificate> certs, CertificateFactory certFactory) throws IOException {
+ X509Certificate[] certChain = certs.toArray(new X509Certificate[certs.size()]);
+ VerifiedProofOfRotation por = null;
+
+ while (attrs.hasRemaining()) {
+ ByteBuffer attr = getLengthPrefixedSlice(attrs);
+ if (attr.remaining() < 4) {
+ throw new IOException("Remaining buffer too short to contain additional attribute "
+ + "ID. Remaining: " + attr.remaining());
+ }
+ int id = attr.getInt();
+ switch(id) {
+ case PROOF_OF_ROTATION_ATTR_ID:
+ if (por != null) {
+ throw new SecurityException("Encountered multiple Proof-of-rotation records"
+ + " when verifying APK Signature Scheme v3 signature");
+ }
+ por = verifyProofOfRotationStruct(attr, certFactory);
+ // make sure that the last certificate in the Proof-of-rotation record matches
+ // the one used to sign this APK.
+ try {
+ if (por.certs.size() > 0
+ && !Arrays.equals(por.certs.get(por.certs.size() - 1).getEncoded(),
+ certChain[0].getEncoded())) {
+ throw new SecurityException("Terminal certificate in Proof-of-rotation"
+ + " record does not match APK signing certificate");
+ }
+ } catch (CertificateEncodingException e) {
+ throw new SecurityException("Failed to encode certificate when comparing"
+ + " Proof-of-rotation record and signing certificate", e);
+ }
+
+ break;
+ default:
+ // not the droid we're looking for, move along, move along.
+ break;
+ }
+ }
+ return new VerifiedSigner(certChain, por);
+ }
+
+ private static VerifiedProofOfRotation verifyProofOfRotationStruct(
+ ByteBuffer porBuf,
+ CertificateFactory certFactory)
+ throws SecurityException, IOException {
+ int levelCount = 0;
+ int lastSigAlgorithm = -1;
+ X509Certificate lastCert = null;
+ List<X509Certificate> certs = new ArrayList<>();
+ List<Integer> flagsList = new ArrayList<>();
+
+ // Proof-of-rotation struct:
+ // is basically a singly linked list of nodes, called levels here, each of which have the
+ // following structure:
+ // * length-prefix for the entire level
+ // - length-prefixed signed data (if previous level exists)
+ // * length-prefixed X509 Certificate
+ // * uint32 signature algorithm ID describing how this signed data was signed
+ // - uint32 flags describing how to treat the cert contained in this level
+ // - uint32 signature algorithm ID to use to verify the signature of the next level. The
+ // algorithm here must match the one in the signed data section of the next level.
+ // - length-prefixed signature over the signed data in this level. The signature here
+ // is verified using the certificate from the previous level.
+ // The linking is provided by the certificate of each level signing the one of the next.
+ while (porBuf.hasRemaining()) {
+ levelCount++;
+ try {
+ ByteBuffer level = getLengthPrefixedSlice(porBuf);
+ ByteBuffer signedData = getLengthPrefixedSlice(level);
+ int flags = level.getInt();
+ int sigAlgorithm = level.getInt();
+ byte[] signature = readLengthPrefixedByteArray(level);
+
+ if (lastCert != null) {
+ // Use previous level cert to verify current level
+ Pair<String, ? extends AlgorithmParameterSpec> sigAlgParams =
+ getSignatureAlgorithmJcaSignatureAlgorithm(lastSigAlgorithm);
+ PublicKey publicKey = lastCert.getPublicKey();
+ Signature sig = Signature.getInstance(sigAlgParams.first);
+ sig.initVerify(publicKey);
+ if (sigAlgParams.second != null) {
+ sig.setParameter(sigAlgParams.second);
+ }
+ sig.update(signedData);
+ if (!sig.verify(signature)) {
+ throw new SecurityException("Unable to verify signature of certificate #"
+ + levelCount + " using " + sigAlgParams.first + " when verifying"
+ + " Proof-of-rotation record");
+ }
+ }
+
+ byte[] encodedCert = readLengthPrefixedByteArray(signedData);
+ int signedSigAlgorithm = signedData.getInt();
+ if (lastCert != null && lastSigAlgorithm != signedSigAlgorithm) {
+ throw new SecurityException("Signing algorithm ID mismatch for certificate #"
+ + levelCount + " when verifying Proof-of-rotation record");
+ }
+ lastCert = (X509Certificate)
+ certFactory.generateCertificate(new ByteArrayInputStream(encodedCert));
+ lastCert = new VerbatimX509Certificate(lastCert, encodedCert);
+
+ lastSigAlgorithm = sigAlgorithm;
+ certs.add(lastCert);
+ flagsList.add(flags);
+ } catch (IOException | BufferUnderflowException e) {
+ throw new IOException("Failed to parse Proof-of-rotation record", e);
+ } catch (NoSuchAlgorithmException | InvalidKeyException
+ | InvalidAlgorithmParameterException | SignatureException e) {
+ throw new SecurityException(
+ "Failed to verify signature over signed data for certificate #"
+ + levelCount + " when verifying Proof-of-rotation record", e);
+ } catch (CertificateException e) {
+ throw new SecurityException("Failed to decode certificate #" + levelCount
+ + " when verifying Proof-of-rotation record", e);
+ }
+ }
+ return new VerifiedProofOfRotation(certs, flagsList);
+ }
+
+ private static boolean isSupportedSignatureAlgorithm(int sigAlgorithm) {
+ switch (sigAlgorithm) {
+ case SIGNATURE_RSA_PSS_WITH_SHA256:
+ case SIGNATURE_RSA_PSS_WITH_SHA512:
+ case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256:
+ case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512:
+ case SIGNATURE_ECDSA_WITH_SHA256:
+ case SIGNATURE_ECDSA_WITH_SHA512:
+ case SIGNATURE_DSA_WITH_SHA256:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * Verified processed proof of rotation.
+ *
+ * @hide for internal use only.
+ */
+ public static class VerifiedProofOfRotation {
+ public final List<X509Certificate> certs;
+ public final List<Integer> flagsList;
+
+ public VerifiedProofOfRotation(List<X509Certificate> certs, List<Integer> flagsList) {
+ this.certs = certs;
+ this.flagsList = flagsList;
+ }
+ }
+
+ /**
+ * Verified APK Signature Scheme v3 signer, including the proof of rotation structure.
+ *
+ * @hide for internal use only.
+ */
+ public static class VerifiedSigner {
+ public final X509Certificate[] certs;
+ public final VerifiedProofOfRotation por;
+
+ public VerifiedSigner(X509Certificate[] certs, VerifiedProofOfRotation por) {
+ this.certs = certs;
+ this.por = por;
+ }
+
+ }
+
+ private static class PlatformNotSupportedException extends Exception {
+
+ PlatformNotSupportedException(String s) {
+ super(s);
+ }
+ }
+}
diff --git a/core/java/android/util/apk/ApkSignatureVerifier.java b/core/java/android/util/apk/ApkSignatureVerifier.java
index 17b11a9..8146729 100644
--- a/core/java/android/util/apk/ApkSignatureVerifier.java
+++ b/core/java/android/util/apk/ApkSignatureVerifier.java
@@ -54,6 +54,7 @@
public static final int VERSION_JAR_SIGNATURE_SCHEME = 1;
public static final int VERSION_APK_SIGNATURE_SCHEME_V2 = 2;
+ public static final int VERSION_APK_SIGNATURE_SCHEME_V3 = 3;
private static final AtomicReference<byte[]> sBuffer = new AtomicReference<>();
@@ -65,7 +66,45 @@
public static Result verify(String apkPath, int minSignatureSchemeVersion)
throws PackageParserException {
- // first try v2
+ if (minSignatureSchemeVersion > VERSION_APK_SIGNATURE_SCHEME_V3) {
+ // V3 and before are older than the requested minimum signing version
+ throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
+ "No signature found in package of version " + minSignatureSchemeVersion
+ + " or newer for package " + apkPath);
+ }
+
+ // first try v3
+ Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "verifyV3");
+ try {
+ ApkSignatureSchemeV3Verifier.VerifiedSigner vSigner =
+ ApkSignatureSchemeV3Verifier.verify(apkPath);
+ Certificate[][] signerCerts = new Certificate[][] { vSigner.certs };
+ Signature[] signerSigs = convertToSignatures(signerCerts);
+ return new Result(signerCerts, signerSigs, VERSION_APK_SIGNATURE_SCHEME_V3);
+ } catch (SignatureNotFoundException e) {
+ // not signed with v2, try older if allowed
+ if (minSignatureSchemeVersion >= VERSION_APK_SIGNATURE_SCHEME_V3) {
+ throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
+ "No APK Signature Scheme v3 signature in package " + apkPath, e);
+ }
+ } catch (Exception e) {
+ // APK Signature Scheme v2 signature found but did not verify
+ throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
+ "Failed to collect certificates from " + apkPath
+ + " using APK Signature Scheme v2", e);
+ } finally {
+ Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+ }
+
+ // redundant, protective version check
+ if (minSignatureSchemeVersion > VERSION_APK_SIGNATURE_SCHEME_V2) {
+ // V2 and before are older than the requested minimum signing version
+ throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
+ "No signature found in package of version " + minSignatureSchemeVersion
+ + " or newer for package " + apkPath);
+ }
+
+ // try v2
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "verifyV2");
try {
Certificate[][] signerCerts = ApkSignatureSchemeV2Verifier.verify(apkPath);
@@ -87,6 +126,14 @@
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
+ // redundant, protective version check
+ if (minSignatureSchemeVersion > VERSION_JAR_SIGNATURE_SCHEME) {
+ // V1 and is older than the requested minimum signing version
+ throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
+ "No signature found in package of version " + minSignatureSchemeVersion
+ + " or newer for package " + apkPath);
+ }
+
// v2 didn't work, try jarsigner
return verifyV1Signature(apkPath, true);
}
@@ -245,6 +292,44 @@
public static Result plsCertsNoVerifyOnlyCerts(String apkPath, int minSignatureSchemeVersion)
throws PackageParserException {
+ if (minSignatureSchemeVersion > VERSION_APK_SIGNATURE_SCHEME_V3) {
+ // V3 and before are older than the requested minimum signing version
+ throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
+ "No signature found in package of version " + minSignatureSchemeVersion
+ + " or newer for package " + apkPath);
+ }
+
+ // first try v3
+ Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "verifyV3");
+ try {
+ ApkSignatureSchemeV3Verifier.VerifiedSigner vSigner =
+ ApkSignatureSchemeV3Verifier.plsCertsNoVerifyOnlyCerts(apkPath);
+ Certificate[][] signerCerts = new Certificate[][] { vSigner.certs };
+ Signature[] signerSigs = convertToSignatures(signerCerts);
+ return new Result(signerCerts, signerSigs, VERSION_APK_SIGNATURE_SCHEME_V3);
+ } catch (SignatureNotFoundException e) {
+ // not signed with v2, try older if allowed
+ if (minSignatureSchemeVersion >= VERSION_APK_SIGNATURE_SCHEME_V3) {
+ throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
+ "No APK Signature Scheme v3 signature in package " + apkPath, e);
+ }
+ } catch (Exception e) {
+ // APK Signature Scheme v2 signature found but did not verify
+ throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
+ "Failed to collect certificates from " + apkPath
+ + " using APK Signature Scheme v2", e);
+ } finally {
+ Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+ }
+
+ // redundant, protective version check
+ if (minSignatureSchemeVersion > VERSION_APK_SIGNATURE_SCHEME_V2) {
+ // V2 and before are older than the requested minimum signing version
+ throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
+ "No signature found in package of version " + minSignatureSchemeVersion
+ + " or newer for package " + apkPath);
+ }
+
// first try v2
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "certsOnlyV2");
try {
@@ -267,6 +352,14 @@
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
+ // redundant, protective version check
+ if (minSignatureSchemeVersion > VERSION_JAR_SIGNATURE_SCHEME) {
+ // V1 and is older than the requested minimum signing version
+ throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
+ "No signature found in package of version " + minSignatureSchemeVersion
+ + " or newer for package " + apkPath);
+ }
+
// v2 didn't work, try jarsigner
return verifyV1Signature(apkPath, false);
}
diff --git a/core/java/android/util/apk/ApkSigningBlockUtils.java b/core/java/android/util/apk/ApkSigningBlockUtils.java
new file mode 100644
index 0000000..9279510
--- /dev/null
+++ b/core/java/android/util/apk/ApkSigningBlockUtils.java
@@ -0,0 +1,663 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.util.apk;
+
+import android.util.Pair;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.security.DigestException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.spec.AlgorithmParameterSpec;
+import java.security.spec.MGF1ParameterSpec;
+import java.security.spec.PSSParameterSpec;
+import java.util.Map;
+
+/**
+ * Utility class for an APK Signature Scheme using the APK Signing Block.
+ *
+ * @hide for internal use only.
+ */
+final class ApkSigningBlockUtils {
+
+ private ApkSigningBlockUtils() {
+ }
+
+ /**
+ * Returns the APK Signature Scheme block contained in the provided APK file and the
+ * additional information relevant for verifying the block against the file.
+ *
+ * @param blockId the ID value in the APK Signing Block's sequence of ID-value pairs
+ * identifying the appropriate block to find, e.g. the APK Signature Scheme v2
+ * block ID.
+ *
+ * @throws SignatureNotFoundException if the APK is not signed using this scheme.
+ * @throws IOException if an I/O error occurs while reading the APK file.
+ */
+ static SignatureInfo findSignature(RandomAccessFile apk, int blockId)
+ throws IOException, SignatureNotFoundException {
+ // Find the ZIP End of Central Directory (EoCD) record.
+ Pair<ByteBuffer, Long> eocdAndOffsetInFile = getEocd(apk);
+ ByteBuffer eocd = eocdAndOffsetInFile.first;
+ long eocdOffset = eocdAndOffsetInFile.second;
+ if (ZipUtils.isZip64EndOfCentralDirectoryLocatorPresent(apk, eocdOffset)) {
+ throw new SignatureNotFoundException("ZIP64 APK not supported");
+ }
+
+ // Find the APK Signing Block. The block immediately precedes the Central Directory.
+ long centralDirOffset = getCentralDirOffset(eocd, eocdOffset);
+ Pair<ByteBuffer, Long> apkSigningBlockAndOffsetInFile =
+ findApkSigningBlock(apk, centralDirOffset);
+ ByteBuffer apkSigningBlock = apkSigningBlockAndOffsetInFile.first;
+ long apkSigningBlockOffset = apkSigningBlockAndOffsetInFile.second;
+
+ // Find the APK Signature Scheme Block inside the APK Signing Block.
+ ByteBuffer apkSignatureSchemeBlock = findApkSignatureSchemeBlock(apkSigningBlock,
+ blockId);
+
+ return new SignatureInfo(
+ apkSignatureSchemeBlock,
+ apkSigningBlockOffset,
+ centralDirOffset,
+ eocdOffset,
+ eocd);
+ }
+
+ static void verifyIntegrity(
+ Map<Integer, byte[]> expectedDigests,
+ FileDescriptor apkFileDescriptor,
+ long apkSigningBlockOffset,
+ long centralDirOffset,
+ long eocdOffset,
+ ByteBuffer eocdBuf) throws SecurityException {
+
+ if (expectedDigests.isEmpty()) {
+ throw new SecurityException("No digests provided");
+ }
+
+ // We need to verify the integrity of the following three sections of the file:
+ // 1. Everything up to the start of the APK Signing Block.
+ // 2. ZIP Central Directory.
+ // 3. ZIP End of Central Directory (EoCD).
+ // Each of these sections is represented as a separate DataSource instance below.
+
+ // To handle large APKs, these sections are read in 1 MB chunks using memory-mapped I/O to
+ // avoid wasting physical memory. In most APK verification scenarios, the contents of the
+ // APK are already there in the OS's page cache and thus mmap does not use additional
+ // physical memory.
+ DataSource beforeApkSigningBlock =
+ new MemoryMappedFileDataSource(apkFileDescriptor, 0, apkSigningBlockOffset);
+ DataSource centralDir =
+ new MemoryMappedFileDataSource(
+ apkFileDescriptor, centralDirOffset, eocdOffset - centralDirOffset);
+
+ // For the purposes of integrity verification, ZIP End of Central Directory's field Start of
+ // Central Directory must be considered to point to the offset of the APK Signing Block.
+ eocdBuf = eocdBuf.duplicate();
+ eocdBuf.order(ByteOrder.LITTLE_ENDIAN);
+ ZipUtils.setZipEocdCentralDirectoryOffset(eocdBuf, apkSigningBlockOffset);
+ DataSource eocd = new ByteBufferDataSource(eocdBuf);
+
+ int[] digestAlgorithms = new int[expectedDigests.size()];
+ int digestAlgorithmCount = 0;
+ for (int digestAlgorithm : expectedDigests.keySet()) {
+ digestAlgorithms[digestAlgorithmCount] = digestAlgorithm;
+ digestAlgorithmCount++;
+ }
+ byte[][] actualDigests;
+ try {
+ actualDigests =
+ computeContentDigests(
+ digestAlgorithms,
+ new DataSource[] {beforeApkSigningBlock, centralDir, eocd});
+ } catch (DigestException e) {
+ throw new SecurityException("Failed to compute digest(s) of contents", e);
+ }
+ for (int i = 0; i < digestAlgorithms.length; i++) {
+ int digestAlgorithm = digestAlgorithms[i];
+ byte[] expectedDigest = expectedDigests.get(digestAlgorithm);
+ byte[] actualDigest = actualDigests[i];
+ if (!MessageDigest.isEqual(expectedDigest, actualDigest)) {
+ throw new SecurityException(
+ getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithm)
+ + " digest of contents did not verify");
+ }
+ }
+ }
+
+ private static byte[][] computeContentDigests(
+ int[] digestAlgorithms,
+ DataSource[] contents) throws DigestException {
+ // For each digest algorithm the result is computed as follows:
+ // 1. Each segment of contents is split into consecutive chunks of 1 MB in size.
+ // The final chunk will be shorter iff the length of segment is not a multiple of 1 MB.
+ // No chunks are produced for empty (zero length) segments.
+ // 2. The digest of each chunk is computed over the concatenation of byte 0xa5, the chunk's
+ // length in bytes (uint32 little-endian) and the chunk's contents.
+ // 3. The output digest is computed over the concatenation of the byte 0x5a, the number of
+ // chunks (uint32 little-endian) and the concatenation of digests of chunks of all
+ // segments in-order.
+
+ long totalChunkCountLong = 0;
+ for (DataSource input : contents) {
+ totalChunkCountLong += getChunkCount(input.size());
+ }
+ if (totalChunkCountLong >= Integer.MAX_VALUE / 1024) {
+ throw new DigestException("Too many chunks: " + totalChunkCountLong);
+ }
+ int totalChunkCount = (int) totalChunkCountLong;
+
+ byte[][] digestsOfChunks = new byte[digestAlgorithms.length][];
+ for (int i = 0; i < digestAlgorithms.length; i++) {
+ int digestAlgorithm = digestAlgorithms[i];
+ int digestOutputSizeBytes = getContentDigestAlgorithmOutputSizeBytes(digestAlgorithm);
+ byte[] concatenationOfChunkCountAndChunkDigests =
+ new byte[5 + totalChunkCount * digestOutputSizeBytes];
+ concatenationOfChunkCountAndChunkDigests[0] = 0x5a;
+ setUnsignedInt32LittleEndian(
+ totalChunkCount,
+ concatenationOfChunkCountAndChunkDigests,
+ 1);
+ digestsOfChunks[i] = concatenationOfChunkCountAndChunkDigests;
+ }
+
+ byte[] chunkContentPrefix = new byte[5];
+ chunkContentPrefix[0] = (byte) 0xa5;
+ int chunkIndex = 0;
+ MessageDigest[] mds = new MessageDigest[digestAlgorithms.length];
+ for (int i = 0; i < digestAlgorithms.length; i++) {
+ String jcaAlgorithmName =
+ getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithms[i]);
+ try {
+ mds[i] = MessageDigest.getInstance(jcaAlgorithmName);
+ } catch (NoSuchAlgorithmException e) {
+ throw new RuntimeException(jcaAlgorithmName + " digest not supported", e);
+ }
+ }
+ // TODO: Compute digests of chunks in parallel when beneficial. This requires some research
+ // into how to parallelize (if at all) based on the capabilities of the hardware on which
+ // this code is running and based on the size of input.
+ DataDigester digester = new MultipleDigestDataDigester(mds);
+ int dataSourceIndex = 0;
+ for (DataSource input : contents) {
+ long inputOffset = 0;
+ long inputRemaining = input.size();
+ while (inputRemaining > 0) {
+ int chunkSize = (int) Math.min(inputRemaining, CHUNK_SIZE_BYTES);
+ setUnsignedInt32LittleEndian(chunkSize, chunkContentPrefix, 1);
+ for (int i = 0; i < mds.length; i++) {
+ mds[i].update(chunkContentPrefix);
+ }
+ try {
+ input.feedIntoDataDigester(digester, inputOffset, chunkSize);
+ } catch (IOException e) {
+ throw new DigestException(
+ "Failed to digest chunk #" + chunkIndex + " of section #"
+ + dataSourceIndex,
+ e);
+ }
+ for (int i = 0; i < digestAlgorithms.length; i++) {
+ int digestAlgorithm = digestAlgorithms[i];
+ byte[] concatenationOfChunkCountAndChunkDigests = digestsOfChunks[i];
+ int expectedDigestSizeBytes =
+ getContentDigestAlgorithmOutputSizeBytes(digestAlgorithm);
+ MessageDigest md = mds[i];
+ int actualDigestSizeBytes =
+ md.digest(
+ concatenationOfChunkCountAndChunkDigests,
+ 5 + chunkIndex * expectedDigestSizeBytes,
+ expectedDigestSizeBytes);
+ if (actualDigestSizeBytes != expectedDigestSizeBytes) {
+ throw new RuntimeException(
+ "Unexpected output size of " + md.getAlgorithm() + " digest: "
+ + actualDigestSizeBytes);
+ }
+ }
+ inputOffset += chunkSize;
+ inputRemaining -= chunkSize;
+ chunkIndex++;
+ }
+ dataSourceIndex++;
+ }
+
+ byte[][] result = new byte[digestAlgorithms.length][];
+ for (int i = 0; i < digestAlgorithms.length; i++) {
+ int digestAlgorithm = digestAlgorithms[i];
+ byte[] input = digestsOfChunks[i];
+ String jcaAlgorithmName = getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithm);
+ MessageDigest md;
+ try {
+ md = MessageDigest.getInstance(jcaAlgorithmName);
+ } catch (NoSuchAlgorithmException e) {
+ throw new RuntimeException(jcaAlgorithmName + " digest not supported", e);
+ }
+ byte[] output = md.digest(input);
+ result[i] = output;
+ }
+ return result;
+ }
+
+ /**
+ * Returns the ZIP End of Central Directory (EoCD) and its offset in the file.
+ *
+ * @throws IOException if an I/O error occurs while reading the file.
+ * @throws SignatureNotFoundException if the EoCD could not be found.
+ */
+ static Pair<ByteBuffer, Long> getEocd(RandomAccessFile apk)
+ throws IOException, SignatureNotFoundException {
+ Pair<ByteBuffer, Long> eocdAndOffsetInFile =
+ ZipUtils.findZipEndOfCentralDirectoryRecord(apk);
+ if (eocdAndOffsetInFile == null) {
+ throw new SignatureNotFoundException(
+ "Not an APK file: ZIP End of Central Directory record not found");
+ }
+ return eocdAndOffsetInFile;
+ }
+
+ static long getCentralDirOffset(ByteBuffer eocd, long eocdOffset)
+ throws SignatureNotFoundException {
+ // Look up the offset of ZIP Central Directory.
+ long centralDirOffset = ZipUtils.getZipEocdCentralDirectoryOffset(eocd);
+ if (centralDirOffset > eocdOffset) {
+ throw new SignatureNotFoundException(
+ "ZIP Central Directory offset out of range: " + centralDirOffset
+ + ". ZIP End of Central Directory offset: " + eocdOffset);
+ }
+ long centralDirSize = ZipUtils.getZipEocdCentralDirectorySizeBytes(eocd);
+ if (centralDirOffset + centralDirSize != eocdOffset) {
+ throw new SignatureNotFoundException(
+ "ZIP Central Directory is not immediately followed by End of Central"
+ + " Directory");
+ }
+ return centralDirOffset;
+ }
+
+ private static long getChunkCount(long inputSizeBytes) {
+ return (inputSizeBytes + CHUNK_SIZE_BYTES - 1) / CHUNK_SIZE_BYTES;
+ }
+
+ private static final int CHUNK_SIZE_BYTES = 1024 * 1024;
+
+ static final int SIGNATURE_RSA_PSS_WITH_SHA256 = 0x0101;
+ static final int SIGNATURE_RSA_PSS_WITH_SHA512 = 0x0102;
+ static final int SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256 = 0x0103;
+ static final int SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512 = 0x0104;
+ static final int SIGNATURE_ECDSA_WITH_SHA256 = 0x0201;
+ static final int SIGNATURE_ECDSA_WITH_SHA512 = 0x0202;
+ static final int SIGNATURE_DSA_WITH_SHA256 = 0x0301;
+
+ static final int CONTENT_DIGEST_CHUNKED_SHA256 = 1;
+ static final int CONTENT_DIGEST_CHUNKED_SHA512 = 2;
+
+ static int compareSignatureAlgorithm(int sigAlgorithm1, int sigAlgorithm2) {
+ int digestAlgorithm1 = getSignatureAlgorithmContentDigestAlgorithm(sigAlgorithm1);
+ int digestAlgorithm2 = getSignatureAlgorithmContentDigestAlgorithm(sigAlgorithm2);
+ return compareContentDigestAlgorithm(digestAlgorithm1, digestAlgorithm2);
+ }
+
+ private static int compareContentDigestAlgorithm(int digestAlgorithm1, int digestAlgorithm2) {
+ switch (digestAlgorithm1) {
+ case CONTENT_DIGEST_CHUNKED_SHA256:
+ switch (digestAlgorithm2) {
+ case CONTENT_DIGEST_CHUNKED_SHA256:
+ return 0;
+ case CONTENT_DIGEST_CHUNKED_SHA512:
+ return -1;
+ default:
+ throw new IllegalArgumentException(
+ "Unknown digestAlgorithm2: " + digestAlgorithm2);
+ }
+ case CONTENT_DIGEST_CHUNKED_SHA512:
+ switch (digestAlgorithm2) {
+ case CONTENT_DIGEST_CHUNKED_SHA256:
+ return 1;
+ case CONTENT_DIGEST_CHUNKED_SHA512:
+ return 0;
+ default:
+ throw new IllegalArgumentException(
+ "Unknown digestAlgorithm2: " + digestAlgorithm2);
+ }
+ default:
+ throw new IllegalArgumentException("Unknown digestAlgorithm1: " + digestAlgorithm1);
+ }
+ }
+
+ static int getSignatureAlgorithmContentDigestAlgorithm(int sigAlgorithm) {
+ switch (sigAlgorithm) {
+ case SIGNATURE_RSA_PSS_WITH_SHA256:
+ case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256:
+ case SIGNATURE_ECDSA_WITH_SHA256:
+ case SIGNATURE_DSA_WITH_SHA256:
+ return CONTENT_DIGEST_CHUNKED_SHA256;
+ case SIGNATURE_RSA_PSS_WITH_SHA512:
+ case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512:
+ case SIGNATURE_ECDSA_WITH_SHA512:
+ return CONTENT_DIGEST_CHUNKED_SHA512;
+ default:
+ throw new IllegalArgumentException(
+ "Unknown signature algorithm: 0x"
+ + Long.toHexString(sigAlgorithm & 0xffffffff));
+ }
+ }
+
+ static String getContentDigestAlgorithmJcaDigestAlgorithm(int digestAlgorithm) {
+ switch (digestAlgorithm) {
+ case CONTENT_DIGEST_CHUNKED_SHA256:
+ return "SHA-256";
+ case CONTENT_DIGEST_CHUNKED_SHA512:
+ return "SHA-512";
+ default:
+ throw new IllegalArgumentException(
+ "Unknown content digest algorthm: " + digestAlgorithm);
+ }
+ }
+
+ private static int getContentDigestAlgorithmOutputSizeBytes(int digestAlgorithm) {
+ switch (digestAlgorithm) {
+ case CONTENT_DIGEST_CHUNKED_SHA256:
+ return 256 / 8;
+ case CONTENT_DIGEST_CHUNKED_SHA512:
+ return 512 / 8;
+ default:
+ throw new IllegalArgumentException(
+ "Unknown content digest algorthm: " + digestAlgorithm);
+ }
+ }
+
+ static String getSignatureAlgorithmJcaKeyAlgorithm(int sigAlgorithm) {
+ switch (sigAlgorithm) {
+ case SIGNATURE_RSA_PSS_WITH_SHA256:
+ case SIGNATURE_RSA_PSS_WITH_SHA512:
+ case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256:
+ case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512:
+ return "RSA";
+ case SIGNATURE_ECDSA_WITH_SHA256:
+ case SIGNATURE_ECDSA_WITH_SHA512:
+ return "EC";
+ case SIGNATURE_DSA_WITH_SHA256:
+ return "DSA";
+ default:
+ throw new IllegalArgumentException(
+ "Unknown signature algorithm: 0x"
+ + Long.toHexString(sigAlgorithm & 0xffffffff));
+ }
+ }
+
+ static Pair<String, ? extends AlgorithmParameterSpec>
+ getSignatureAlgorithmJcaSignatureAlgorithm(int sigAlgorithm) {
+ switch (sigAlgorithm) {
+ case SIGNATURE_RSA_PSS_WITH_SHA256:
+ return Pair.create(
+ "SHA256withRSA/PSS",
+ new PSSParameterSpec(
+ "SHA-256", "MGF1", MGF1ParameterSpec.SHA256, 256 / 8, 1));
+ case SIGNATURE_RSA_PSS_WITH_SHA512:
+ return Pair.create(
+ "SHA512withRSA/PSS",
+ new PSSParameterSpec(
+ "SHA-512", "MGF1", MGF1ParameterSpec.SHA512, 512 / 8, 1));
+ case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256:
+ return Pair.create("SHA256withRSA", null);
+ case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512:
+ return Pair.create("SHA512withRSA", null);
+ case SIGNATURE_ECDSA_WITH_SHA256:
+ return Pair.create("SHA256withECDSA", null);
+ case SIGNATURE_ECDSA_WITH_SHA512:
+ return Pair.create("SHA512withECDSA", null);
+ case SIGNATURE_DSA_WITH_SHA256:
+ return Pair.create("SHA256withDSA", null);
+ default:
+ throw new IllegalArgumentException(
+ "Unknown signature algorithm: 0x"
+ + Long.toHexString(sigAlgorithm & 0xffffffff));
+ }
+ }
+
+ /**
+ * Returns new byte buffer whose content is a shared subsequence of this buffer's content
+ * between the specified start (inclusive) and end (exclusive) positions. As opposed to
+ * {@link ByteBuffer#slice()}, the returned buffer's byte order is the same as the source
+ * buffer's byte order.
+ */
+ static ByteBuffer sliceFromTo(ByteBuffer source, int start, int end) {
+ if (start < 0) {
+ throw new IllegalArgumentException("start: " + start);
+ }
+ if (end < start) {
+ throw new IllegalArgumentException("end < start: " + end + " < " + start);
+ }
+ int capacity = source.capacity();
+ if (end > source.capacity()) {
+ throw new IllegalArgumentException("end > capacity: " + end + " > " + capacity);
+ }
+ int originalLimit = source.limit();
+ int originalPosition = source.position();
+ try {
+ source.position(0);
+ source.limit(end);
+ source.position(start);
+ ByteBuffer result = source.slice();
+ result.order(source.order());
+ return result;
+ } finally {
+ source.position(0);
+ source.limit(originalLimit);
+ source.position(originalPosition);
+ }
+ }
+
+ /**
+ * Relative <em>get</em> method for reading {@code size} number of bytes from the current
+ * position of this buffer.
+ *
+ * <p>This method reads the next {@code size} bytes at this buffer's current position,
+ * returning them as a {@code ByteBuffer} with start set to 0, limit and capacity set to
+ * {@code size}, byte order set to this buffer's byte order; and then increments the position by
+ * {@code size}.
+ */
+ static ByteBuffer getByteBuffer(ByteBuffer source, int size)
+ throws BufferUnderflowException {
+ if (size < 0) {
+ throw new IllegalArgumentException("size: " + size);
+ }
+ int originalLimit = source.limit();
+ int position = source.position();
+ int limit = position + size;
+ if ((limit < position) || (limit > originalLimit)) {
+ throw new BufferUnderflowException();
+ }
+ source.limit(limit);
+ try {
+ ByteBuffer result = source.slice();
+ result.order(source.order());
+ source.position(limit);
+ return result;
+ } finally {
+ source.limit(originalLimit);
+ }
+ }
+
+ static ByteBuffer getLengthPrefixedSlice(ByteBuffer source) throws IOException {
+ if (source.remaining() < 4) {
+ throw new IOException(
+ "Remaining buffer too short to contain length of length-prefixed field."
+ + " Remaining: " + source.remaining());
+ }
+ int len = source.getInt();
+ if (len < 0) {
+ throw new IllegalArgumentException("Negative length");
+ } else if (len > source.remaining()) {
+ throw new IOException("Length-prefixed field longer than remaining buffer."
+ + " Field length: " + len + ", remaining: " + source.remaining());
+ }
+ return getByteBuffer(source, len);
+ }
+
+ static byte[] readLengthPrefixedByteArray(ByteBuffer buf) throws IOException {
+ int len = buf.getInt();
+ if (len < 0) {
+ throw new IOException("Negative length");
+ } else if (len > buf.remaining()) {
+ throw new IOException("Underflow while reading length-prefixed value. Length: " + len
+ + ", available: " + buf.remaining());
+ }
+ byte[] result = new byte[len];
+ buf.get(result);
+ return result;
+ }
+
+ static void setUnsignedInt32LittleEndian(int value, byte[] result, int offset) {
+ result[offset] = (byte) (value & 0xff);
+ result[offset + 1] = (byte) ((value >>> 8) & 0xff);
+ result[offset + 2] = (byte) ((value >>> 16) & 0xff);
+ result[offset + 3] = (byte) ((value >>> 24) & 0xff);
+ }
+
+ private static final long APK_SIG_BLOCK_MAGIC_HI = 0x3234206b636f6c42L;
+ private static final long APK_SIG_BLOCK_MAGIC_LO = 0x20676953204b5041L;
+ private static final int APK_SIG_BLOCK_MIN_SIZE = 32;
+
+ static Pair<ByteBuffer, Long> findApkSigningBlock(
+ RandomAccessFile apk, long centralDirOffset)
+ throws IOException, SignatureNotFoundException {
+ // FORMAT:
+ // OFFSET DATA TYPE DESCRIPTION
+ // * @+0 bytes uint64: size in bytes (excluding this field)
+ // * @+8 bytes payload
+ // * @-24 bytes uint64: size in bytes (same as the one above)
+ // * @-16 bytes uint128: magic
+
+ if (centralDirOffset < APK_SIG_BLOCK_MIN_SIZE) {
+ throw new SignatureNotFoundException(
+ "APK too small for APK Signing Block. ZIP Central Directory offset: "
+ + centralDirOffset);
+ }
+ // Read the magic and offset in file from the footer section of the block:
+ // * uint64: size of block
+ // * 16 bytes: magic
+ ByteBuffer footer = ByteBuffer.allocate(24);
+ footer.order(ByteOrder.LITTLE_ENDIAN);
+ apk.seek(centralDirOffset - footer.capacity());
+ apk.readFully(footer.array(), footer.arrayOffset(), footer.capacity());
+ if ((footer.getLong(8) != APK_SIG_BLOCK_MAGIC_LO)
+ || (footer.getLong(16) != APK_SIG_BLOCK_MAGIC_HI)) {
+ throw new SignatureNotFoundException(
+ "No APK Signing Block before ZIP Central Directory");
+ }
+ // Read and compare size fields
+ long apkSigBlockSizeInFooter = footer.getLong(0);
+ if ((apkSigBlockSizeInFooter < footer.capacity())
+ || (apkSigBlockSizeInFooter > Integer.MAX_VALUE - 8)) {
+ throw new SignatureNotFoundException(
+ "APK Signing Block size out of range: " + apkSigBlockSizeInFooter);
+ }
+ int totalSize = (int) (apkSigBlockSizeInFooter + 8);
+ long apkSigBlockOffset = centralDirOffset - totalSize;
+ if (apkSigBlockOffset < 0) {
+ throw new SignatureNotFoundException(
+ "APK Signing Block offset out of range: " + apkSigBlockOffset);
+ }
+ ByteBuffer apkSigBlock = ByteBuffer.allocate(totalSize);
+ apkSigBlock.order(ByteOrder.LITTLE_ENDIAN);
+ apk.seek(apkSigBlockOffset);
+ apk.readFully(apkSigBlock.array(), apkSigBlock.arrayOffset(), apkSigBlock.capacity());
+ long apkSigBlockSizeInHeader = apkSigBlock.getLong(0);
+ if (apkSigBlockSizeInHeader != apkSigBlockSizeInFooter) {
+ throw new SignatureNotFoundException(
+ "APK Signing Block sizes in header and footer do not match: "
+ + apkSigBlockSizeInHeader + " vs " + apkSigBlockSizeInFooter);
+ }
+ return Pair.create(apkSigBlock, apkSigBlockOffset);
+ }
+
+ static ByteBuffer findApkSignatureSchemeBlock(ByteBuffer apkSigningBlock, int blockId)
+ throws SignatureNotFoundException {
+ checkByteOrderLittleEndian(apkSigningBlock);
+ // FORMAT:
+ // OFFSET DATA TYPE DESCRIPTION
+ // * @+0 bytes uint64: size in bytes (excluding this field)
+ // * @+8 bytes pairs
+ // * @-24 bytes uint64: size in bytes (same as the one above)
+ // * @-16 bytes uint128: magic
+ ByteBuffer pairs = sliceFromTo(apkSigningBlock, 8, apkSigningBlock.capacity() - 24);
+
+ int entryCount = 0;
+ while (pairs.hasRemaining()) {
+ entryCount++;
+ if (pairs.remaining() < 8) {
+ throw new SignatureNotFoundException(
+ "Insufficient data to read size of APK Signing Block entry #" + entryCount);
+ }
+ long lenLong = pairs.getLong();
+ if ((lenLong < 4) || (lenLong > Integer.MAX_VALUE)) {
+ throw new SignatureNotFoundException(
+ "APK Signing Block entry #" + entryCount
+ + " size out of range: " + lenLong);
+ }
+ int len = (int) lenLong;
+ int nextEntryPos = pairs.position() + len;
+ if (len > pairs.remaining()) {
+ throw new SignatureNotFoundException(
+ "APK Signing Block entry #" + entryCount + " size out of range: " + len
+ + ", available: " + pairs.remaining());
+ }
+ int id = pairs.getInt();
+ if (id == blockId) {
+ return getByteBuffer(pairs, len - 4);
+ }
+ pairs.position(nextEntryPos);
+ }
+
+ throw new SignatureNotFoundException(
+ "No block with ID " + blockId + " in APK Signing Block.");
+ }
+
+ private static void checkByteOrderLittleEndian(ByteBuffer buffer) {
+ if (buffer.order() != ByteOrder.LITTLE_ENDIAN) {
+ throw new IllegalArgumentException("ByteBuffer byte order must be little endian");
+ }
+ }
+
+ /**
+ * {@link DataDigester} that updates multiple {@link MessageDigest}s whenever data is fed.
+ */
+ private static class MultipleDigestDataDigester implements DataDigester {
+ private final MessageDigest[] mMds;
+
+ MultipleDigestDataDigester(MessageDigest[] mds) {
+ mMds = mds;
+ }
+
+ @Override
+ public void consume(ByteBuffer buffer) {
+ buffer = buffer.slice();
+ for (MessageDigest md : mMds) {
+ buffer.position(0);
+ md.update(buffer);
+ }
+ }
+
+ @Override
+ public void finish() {}
+ }
+
+}
diff --git a/core/java/android/util/apk/VerbatimX509Certificate.java b/core/java/android/util/apk/VerbatimX509Certificate.java
new file mode 100644
index 0000000..9984c6d
--- /dev/null
+++ b/core/java/android/util/apk/VerbatimX509Certificate.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.util.apk;
+
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.X509Certificate;
+
+/**
+ * For legacy reasons we need to return exactly the original encoded certificate bytes, instead
+ * of letting the underlying implementation have a shot at re-encoding the data.
+ */
+class VerbatimX509Certificate extends WrappedX509Certificate {
+ private final byte[] mEncodedVerbatim;
+
+ VerbatimX509Certificate(X509Certificate wrapped, byte[] encodedVerbatim) {
+ super(wrapped);
+ this.mEncodedVerbatim = encodedVerbatim;
+ }
+
+ @Override
+ public byte[] getEncoded() throws CertificateEncodingException {
+ return mEncodedVerbatim;
+ }
+}
diff --git a/core/java/android/util/apk/WrappedX509Certificate.java b/core/java/android/util/apk/WrappedX509Certificate.java
new file mode 100644
index 0000000..fdaa420
--- /dev/null
+++ b/core/java/android/util/apk/WrappedX509Certificate.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.util.apk;
+
+import java.math.BigInteger;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.Principal;
+import java.security.PublicKey;
+import java.security.SignatureException;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateExpiredException;
+import java.security.cert.CertificateNotYetValidException;
+import java.security.cert.X509Certificate;
+import java.util.Date;
+import java.util.Set;
+
+class WrappedX509Certificate extends X509Certificate {
+ private final X509Certificate mWrapped;
+
+ WrappedX509Certificate(X509Certificate wrapped) {
+ this.mWrapped = wrapped;
+ }
+
+ @Override
+ public Set<String> getCriticalExtensionOIDs() {
+ return mWrapped.getCriticalExtensionOIDs();
+ }
+
+ @Override
+ public byte[] getExtensionValue(String oid) {
+ return mWrapped.getExtensionValue(oid);
+ }
+
+ @Override
+ public Set<String> getNonCriticalExtensionOIDs() {
+ return mWrapped.getNonCriticalExtensionOIDs();
+ }
+
+ @Override
+ public boolean hasUnsupportedCriticalExtension() {
+ return mWrapped.hasUnsupportedCriticalExtension();
+ }
+
+ @Override
+ public void checkValidity()
+ throws CertificateExpiredException, CertificateNotYetValidException {
+ mWrapped.checkValidity();
+ }
+
+ @Override
+ public void checkValidity(Date date)
+ throws CertificateExpiredException, CertificateNotYetValidException {
+ mWrapped.checkValidity(date);
+ }
+
+ @Override
+ public int getVersion() {
+ return mWrapped.getVersion();
+ }
+
+ @Override
+ public BigInteger getSerialNumber() {
+ return mWrapped.getSerialNumber();
+ }
+
+ @Override
+ public Principal getIssuerDN() {
+ return mWrapped.getIssuerDN();
+ }
+
+ @Override
+ public Principal getSubjectDN() {
+ return mWrapped.getSubjectDN();
+ }
+
+ @Override
+ public Date getNotBefore() {
+ return mWrapped.getNotBefore();
+ }
+
+ @Override
+ public Date getNotAfter() {
+ return mWrapped.getNotAfter();
+ }
+
+ @Override
+ public byte[] getTBSCertificate() throws CertificateEncodingException {
+ return mWrapped.getTBSCertificate();
+ }
+
+ @Override
+ public byte[] getSignature() {
+ return mWrapped.getSignature();
+ }
+
+ @Override
+ public String getSigAlgName() {
+ return mWrapped.getSigAlgName();
+ }
+
+ @Override
+ public String getSigAlgOID() {
+ return mWrapped.getSigAlgOID();
+ }
+
+ @Override
+ public byte[] getSigAlgParams() {
+ return mWrapped.getSigAlgParams();
+ }
+
+ @Override
+ public boolean[] getIssuerUniqueID() {
+ return mWrapped.getIssuerUniqueID();
+ }
+
+ @Override
+ public boolean[] getSubjectUniqueID() {
+ return mWrapped.getSubjectUniqueID();
+ }
+
+ @Override
+ public boolean[] getKeyUsage() {
+ return mWrapped.getKeyUsage();
+ }
+
+ @Override
+ public int getBasicConstraints() {
+ return mWrapped.getBasicConstraints();
+ }
+
+ @Override
+ public byte[] getEncoded() throws CertificateEncodingException {
+ return mWrapped.getEncoded();
+ }
+
+ @Override
+ public void verify(PublicKey key) throws CertificateException, NoSuchAlgorithmException,
+ InvalidKeyException, NoSuchProviderException, SignatureException {
+ mWrapped.verify(key);
+ }
+
+ @Override
+ public void verify(PublicKey key, String sigProvider)
+ throws CertificateException, NoSuchAlgorithmException, InvalidKeyException,
+ NoSuchProviderException, SignatureException {
+ mWrapped.verify(key, sigProvider);
+ }
+
+ @Override
+ public String toString() {
+ return mWrapped.toString();
+ }
+
+ @Override
+ public PublicKey getPublicKey() {
+ return mWrapped.getPublicKey();
+ }
+}
diff --git a/core/java/android/util/jar/StrictJarVerifier.java b/core/java/android/util/jar/StrictJarVerifier.java
index debc170..4525490 100644
--- a/core/java/android/util/jar/StrictJarVerifier.java
+++ b/core/java/android/util/jar/StrictJarVerifier.java
@@ -18,6 +18,8 @@
package android.util.jar;
import android.util.apk.ApkSignatureSchemeV2Verifier;
+import android.util.apk.ApkSignatureSchemeV3Verifier;
+
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
@@ -36,6 +38,7 @@
import java.util.StringTokenizer;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
+
import sun.security.jca.Providers;
import sun.security.pkcs.PKCS7;
import sun.security.pkcs.SignerInfo;
@@ -56,6 +59,15 @@
*/
class StrictJarVerifier {
/**
+ * {@code .SF} file header section attribute indicating that the APK is signed not just with
+ * JAR signature scheme but also with APK Signature Scheme v2 or newer. This attribute
+ * facilitates v2 signature stripping detection.
+ *
+ * <p>The attribute contains a comma-separated set of signature scheme IDs.
+ */
+ private static final String SF_ATTRIBUTE_ANDROID_APK_SIGNED_NAME = "X-Android-APK-Signed";
+
+ /**
* List of accepted digest algorithms. This list is in order from most
* preferred to least preferred.
*/
@@ -373,17 +385,17 @@
return;
}
- // If requested, check whether APK Signature Scheme v2 signature was stripped.
+ // If requested, check whether a newer APK Signature Scheme signature was stripped.
if (signatureSchemeRollbackProtectionsEnforced) {
String apkSignatureSchemeIdList =
- attributes.getValue(
- ApkSignatureSchemeV2Verifier.SF_ATTRIBUTE_ANDROID_APK_SIGNED_NAME);
+ attributes.getValue(SF_ATTRIBUTE_ANDROID_APK_SIGNED_NAME);
if (apkSignatureSchemeIdList != null) {
// This field contains a comma-separated list of APK signature scheme IDs which
// were used to sign this APK. If an ID is known to us, it means signatures of that
// scheme were stripped from the APK because otherwise we wouldn't have fallen back
// to verifying the APK using the JAR signature scheme.
boolean v2SignatureGenerated = false;
+ boolean v3SignatureGenerated = false;
StringTokenizer tokenizer = new StringTokenizer(apkSignatureSchemeIdList, ",");
while (tokenizer.hasMoreTokens()) {
String idText = tokenizer.nextToken().trim();
@@ -402,6 +414,12 @@
v2SignatureGenerated = true;
break;
}
+ if (id == ApkSignatureSchemeV3Verifier.SF_ATTRIBUTE_ANDROID_APK_SIGNED_ID) {
+ // This APK was supposed to be signed with APK Signature Scheme v3 but no
+ // such signature was found.
+ v3SignatureGenerated = true;
+ break;
+ }
}
if (v2SignatureGenerated) {
@@ -409,6 +427,11 @@
+ " is signed using APK Signature Scheme v2, but no such signature was"
+ " found. Signature stripped?");
}
+ if (v3SignatureGenerated) {
+ throw new SecurityException(signatureFile + " indicates " + jarName
+ + " is signed using APK Signature Scheme v3, but no such signature was"
+ + " found. Signature stripped?");
+ }
}
}
diff --git a/core/java/android/view/accessibility/AccessibilityEvent.java b/core/java/android/view/accessibility/AccessibilityEvent.java
index 1d19a9f..9871539 100644
--- a/core/java/android/view/accessibility/AccessibilityEvent.java
+++ b/core/java/android/view/accessibility/AccessibilityEvent.java
@@ -350,21 +350,22 @@
* view.</br>
* </p>
* <p>
- * <b>Windows changed</b> - represents the event of changes in the windows shown on
+ * <b>Windows changed</b> - represents a change in the windows shown on
* the screen such as a window appeared, a window disappeared, a window size changed,
- * a window layer changed, etc.</br>
+ * a window layer changed, etc. These events should only come from the system, which is responsible
+ * for managing windows. For regions of the user interface that are presented as windows but are
+ * controlled by an app's process, use {@link #TYPE_WINDOW_STATE_CHANGED}.</br>
* <em>Type:</em> {@link #TYPE_WINDOWS_CHANGED}</br>
* <em>Properties:</em></br>
* <ul>
* <li>{@link #getEventType()} - The type of the event.</li>
* <li>{@link #getEventTime()} - The event time.</li>
+ * <li>{@link #getWindowChanges()}</li> - The specific change to the source window
* </ul>
* <em>Note:</em> You can retrieve the {@link AccessibilityWindowInfo} for the window
- * source of the event via {@link AccessibilityEvent#getSource()} to get the source
- * node on which then call {@link AccessibilityNodeInfo#getWindow()
- * AccessibilityNodeInfo.getWindow()} to get the window. Also all windows on the screen can
- * be retrieved by a call to {@link android.accessibilityservice.AccessibilityService#getWindows()
- * android.accessibilityservice.AccessibilityService.getWindows()}.
+ * source of the event by looking through the list returned by
+ * {@link android.accessibilityservice.AccessibilityService#getWindows()} for the window whose ID
+ * matches {@link #getWindowId()}.
* </p>
* <p>
* <b>NOTIFICATION TYPES</b></br>
@@ -712,6 +713,88 @@
*/
public static final int CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION = 0x00000004;
+ /**
+ * Change type for {@link #TYPE_WINDOWS_CHANGED} event:
+ * The window was added.
+ */
+ public static final int WINDOWS_CHANGE_ADDED = 0x00000001;
+
+ /**
+ * Change type for {@link #TYPE_WINDOWS_CHANGED} event:
+ * A window was removed.
+ */
+ public static final int WINDOWS_CHANGE_REMOVED = 0x00000002;
+
+ /**
+ * Change type for {@link #TYPE_WINDOWS_CHANGED} event:
+ * The window's title changed.
+ */
+ public static final int WINDOWS_CHANGE_TITLE = 0x00000004;
+
+ /**
+ * Change type for {@link #TYPE_WINDOWS_CHANGED} event:
+ * The window's bounds changed.
+ */
+ public static final int WINDOWS_CHANGE_BOUNDS = 0x00000008;
+
+ /**
+ * Change type for {@link #TYPE_WINDOWS_CHANGED} event:
+ * The window's layer changed.
+ */
+ public static final int WINDOWS_CHANGE_LAYER = 0x00000010;
+
+ /**
+ * Change type for {@link #TYPE_WINDOWS_CHANGED} event:
+ * The window's {@link AccessibilityWindowInfo#isActive()} changed.
+ */
+ public static final int WINDOWS_CHANGE_ACTIVE = 0x00000020;
+
+ /**
+ * Change type for {@link #TYPE_WINDOWS_CHANGED} event:
+ * The window's {@link AccessibilityWindowInfo#isFocused()} changed.
+ */
+ public static final int WINDOWS_CHANGE_FOCUSED = 0x00000040;
+
+ /**
+ * Change type for {@link #TYPE_WINDOWS_CHANGED} event:
+ * The window's {@link AccessibilityWindowInfo#isAccessibilityFocused()} changed.
+ */
+ public static final int WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED = 0x00000080;
+
+ /**
+ * Change type for {@link #TYPE_WINDOWS_CHANGED} event:
+ * The window's parent changed.
+ */
+ public static final int WINDOWS_CHANGE_PARENT = 0x00000100;
+
+ /**
+ * Change type for {@link #TYPE_WINDOWS_CHANGED} event:
+ * The window's children changed.
+ */
+ public static final int WINDOWS_CHANGE_CHILDREN = 0x00000200;
+
+ /**
+ * Change type for {@link #TYPE_WINDOWS_CHANGED} event:
+ * The window either entered or exited picture-in-picture mode.
+ */
+ public static final int WINDOWS_CHANGE_PIP = 0x00000400;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = true, prefix = { "WINDOWS_CHANGE_" }, value = {
+ WINDOWS_CHANGE_ADDED,
+ WINDOWS_CHANGE_REMOVED,
+ WINDOWS_CHANGE_TITLE,
+ WINDOWS_CHANGE_BOUNDS,
+ WINDOWS_CHANGE_LAYER,
+ WINDOWS_CHANGE_ACTIVE,
+ WINDOWS_CHANGE_FOCUSED,
+ WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED,
+ WINDOWS_CHANGE_PARENT,
+ WINDOWS_CHANGE_CHILDREN,
+ WINDOWS_CHANGE_PIP
+ })
+ public @interface WindowsChangeTypes {}
/** @hide */
@IntDef(flag = true, prefix = { "TYPE_" }, value = {
@@ -782,6 +865,7 @@
int mMovementGranularity;
int mAction;
int mContentChangeTypes;
+ int mWindowChangeTypes;
private ArrayList<AccessibilityRecord> mRecords;
@@ -802,6 +886,7 @@
mMovementGranularity = event.mMovementGranularity;
mAction = event.mAction;
mContentChangeTypes = event.mContentChangeTypes;
+ mWindowChangeTypes = event.mWindowChangeTypes;
mEventTime = event.mEventTime;
mPackageName = event.mPackageName;
}
@@ -919,6 +1004,43 @@
}
/**
+ * Get the bit mask of change types signaled by a {@link #TYPE_WINDOWS_CHANGED} event. A
+ * single event may represent multiple change types.
+ *
+ * @return The bit mask of change types.
+ */
+ @WindowsChangeTypes
+ public int getWindowChanges() {
+ return mWindowChangeTypes;
+ }
+
+ /** @hide */
+ public void setWindowChanges(@WindowsChangeTypes int changes) {
+ mWindowChangeTypes = changes;
+ }
+
+ private static String windowChangeTypesToString(@WindowsChangeTypes int types) {
+ return BitUtils.flagsToString(types, AccessibilityEvent::singleWindowChangeTypeToString);
+ }
+
+ private static String singleWindowChangeTypeToString(int type) {
+ switch (type) {
+ case WINDOWS_CHANGE_ADDED: return "WINDOWS_CHANGE_ADDED";
+ case WINDOWS_CHANGE_REMOVED: return "WINDOWS_CHANGE_REMOVED";
+ case WINDOWS_CHANGE_TITLE: return "WINDOWS_CHANGE_TITLE";
+ case WINDOWS_CHANGE_BOUNDS: return "WINDOWS_CHANGE_BOUNDS";
+ case WINDOWS_CHANGE_LAYER: return "WINDOWS_CHANGE_LAYER";
+ case WINDOWS_CHANGE_ACTIVE: return "WINDOWS_CHANGE_ACTIVE";
+ case WINDOWS_CHANGE_FOCUSED: return "WINDOWS_CHANGE_FOCUSED";
+ case WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED:
+ return "WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED";
+ case WINDOWS_CHANGE_PARENT: return "WINDOWS_CHANGE_PARENT";
+ case WINDOWS_CHANGE_CHILDREN: return "WINDOWS_CHANGE_CHILDREN";
+ default: return Integer.toHexString(type);
+ }
+ }
+
+ /**
* Sets the event type.
*
* @param eventType The event type.
@@ -1025,6 +1147,26 @@
}
/**
+ * Convenience method to obtain a {@link #TYPE_WINDOWS_CHANGED} event for a specific window and
+ * change set.
+ *
+ * @param windowId The ID of the window that changed
+ * @param windowChangeTypes The changes to populate
+ * @return An instance of a TYPE_WINDOWS_CHANGED, populated with the requested fields and with
+ * importantForAccessibility set to {@code true}.
+ *
+ * @hide
+ */
+ public static AccessibilityEvent obtainWindowsChangedEvent(
+ int windowId, int windowChangeTypes) {
+ final AccessibilityEvent event = AccessibilityEvent.obtain(TYPE_WINDOWS_CHANGED);
+ event.setWindowId(windowId);
+ event.setWindowChanges(windowChangeTypes);
+ event.setImportantForAccessibility(true);
+ return event;
+ }
+
+ /**
* Returns a cached instance if such is available or a new one is
* instantiated with its type property set.
*
@@ -1099,6 +1241,7 @@
mMovementGranularity = 0;
mAction = 0;
mContentChangeTypes = 0;
+ mWindowChangeTypes = 0;
mPackageName = null;
mEventTime = 0;
if (mRecords != null) {
@@ -1120,6 +1263,7 @@
mMovementGranularity = parcel.readInt();
mAction = parcel.readInt();
mContentChangeTypes = parcel.readInt();
+ mWindowChangeTypes = parcel.readInt();
mPackageName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
mEventTime = parcel.readLong();
mConnectionId = parcel.readInt();
@@ -1178,6 +1322,7 @@
parcel.writeInt(mMovementGranularity);
parcel.writeInt(mAction);
parcel.writeInt(mContentChangeTypes);
+ parcel.writeInt(mWindowChangeTypes);
TextUtils.writeToParcel(mPackageName, parcel, 0);
parcel.writeLong(mEventTime);
parcel.writeInt(mConnectionId);
@@ -1238,11 +1383,13 @@
builder.append("; PackageName: ").append(mPackageName);
builder.append("; MovementGranularity: ").append(mMovementGranularity);
builder.append("; Action: ").append(mAction);
+ builder.append("; ContentChangeTypes: ").append(
+ contentChangeTypesToString(mContentChangeTypes));
+ builder.append("; WindowChangeTypes: ").append(
+ windowChangeTypesToString(mWindowChangeTypes));
builder.append(super.toString());
if (DEBUG) {
builder.append("\n");
- builder.append("; ContentChangeTypes: ").append(
- contentChangeTypesToString(mContentChangeTypes));
builder.append("; sourceWindowId: ").append(mSourceWindowId);
builder.append("; mSourceNodeId: ").append(mSourceNodeId);
for (int i = 0; i < getRecordCount(); i++) {
diff --git a/core/java/android/view/accessibility/AccessibilityWindowInfo.java b/core/java/android/view/accessibility/AccessibilityWindowInfo.java
index ef1a3f3..c1c9174 100644
--- a/core/java/android/view/accessibility/AccessibilityWindowInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityWindowInfo.java
@@ -21,9 +21,12 @@
import android.graphics.Rect;
import android.os.Parcel;
import android.os.Parcelable;
+import android.text.TextUtils;
import android.util.LongArray;
import android.util.Pools.SynchronizedPool;
+import android.view.accessibility.AccessibilityEvent.WindowsChangeTypes;
+import java.util.Objects;
import java.util.concurrent.atomic.AtomicInteger;
/**
@@ -575,7 +578,7 @@
StringBuilder builder = new StringBuilder();
builder.append("AccessibilityWindowInfo[");
builder.append("title=").append(mTitle);
- builder.append("id=").append(mId);
+ builder.append(", id=").append(mId);
builder.append(", type=").append(typeToString(mType));
builder.append(", layer=").append(mLayer);
builder.append(", bounds=").append(mBoundsInScreen);
@@ -713,6 +716,60 @@
return false;
}
+ /**
+ * Reports how this window differs from a possibly different state of the same window. The
+ * argument must have the same id and type as neither of those properties may change.
+ *
+ * @param other The new state.
+ * @return A set of flags showing how the window has changes, or 0 if the two states are the
+ * same.
+ *
+ * @hide
+ */
+ @WindowsChangeTypes
+ public int differenceFrom(AccessibilityWindowInfo other) {
+ if (other.mId != mId) {
+ throw new IllegalArgumentException("Not same window.");
+ }
+ if (other.mType != mType) {
+ throw new IllegalArgumentException("Not same type.");
+ }
+ int changes = 0;
+ if (!TextUtils.equals(mTitle, other.mTitle)) {
+ changes |= AccessibilityEvent.WINDOWS_CHANGE_TITLE;
+ }
+
+ if (!mBoundsInScreen.equals(other.mBoundsInScreen)) {
+ changes |= AccessibilityEvent.WINDOWS_CHANGE_BOUNDS;
+ }
+ if (mLayer != other.mLayer) {
+ changes |= AccessibilityEvent.WINDOWS_CHANGE_LAYER;
+ }
+ if (getBooleanProperty(BOOLEAN_PROPERTY_ACTIVE)
+ != other.getBooleanProperty(BOOLEAN_PROPERTY_ACTIVE)) {
+ changes |= AccessibilityEvent.WINDOWS_CHANGE_ACTIVE;
+ }
+ if (getBooleanProperty(BOOLEAN_PROPERTY_FOCUSED)
+ != other.getBooleanProperty(BOOLEAN_PROPERTY_FOCUSED)) {
+ changes |= AccessibilityEvent.WINDOWS_CHANGE_FOCUSED;
+ }
+ if (getBooleanProperty(BOOLEAN_PROPERTY_ACCESSIBILITY_FOCUSED)
+ != other.getBooleanProperty(BOOLEAN_PROPERTY_ACCESSIBILITY_FOCUSED)) {
+ changes |= AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED;
+ }
+ if (getBooleanProperty(BOOLEAN_PROPERTY_PICTURE_IN_PICTURE)
+ != other.getBooleanProperty(BOOLEAN_PROPERTY_PICTURE_IN_PICTURE)) {
+ changes |= AccessibilityEvent.WINDOWS_CHANGE_PIP;
+ }
+ if (mParentId != other.mParentId) {
+ changes |= AccessibilityEvent.WINDOWS_CHANGE_PARENT;
+ }
+ if (!Objects.equals(mChildIds, other.mChildIds)) {
+ changes |= AccessibilityEvent.WINDOWS_CHANGE_CHILDREN;
+ }
+ return changes;
+ }
+
public static final Parcelable.Creator<AccessibilityWindowInfo> CREATOR =
new Creator<AccessibilityWindowInfo>() {
@Override
diff --git a/core/java/android/widget/EditText.java b/core/java/android/widget/EditText.java
index 56c3e4a..336c20c 100644
--- a/core/java/android/widget/EditText.java
+++ b/core/java/android/widget/EditText.java
@@ -105,6 +105,11 @@
@Override
public Editable getText() {
+ CharSequence text = super.getText();
+ if (text instanceof Editable) {
+ return (Editable) super.getText();
+ }
+ super.setText(text, BufferType.EDITABLE);
return (Editable) super.getText();
}
diff --git a/core/java/android/widget/Magnifier.java b/core/java/android/widget/Magnifier.java
index bd48f45..26dfcc2 100644
--- a/core/java/android/widget/Magnifier.java
+++ b/core/java/android/widget/Magnifier.java
@@ -125,7 +125,7 @@
mView.getWidth() - mBitmap.getWidth()));
final int startY = mCenterZoomCoords.y - mBitmap.getHeight() / 2;
- if (startX != mPrevStartCoordsInSurface.x || startY != mPrevStartCoordsInSurface.y) {
+ if (xPosInView != mPrevPosInView.x || yPosInView != mPrevPosInView.y) {
performPixelCopy(startX, startY);
mPrevPosInView.x = xPosInView;
diff --git a/core/java/com/android/internal/app/SuggestedLocaleAdapter.java b/core/java/com/android/internal/app/SuggestedLocaleAdapter.java
index 46f47a3..d138241 100644
--- a/core/java/com/android/internal/app/SuggestedLocaleAdapter.java
+++ b/core/java/com/android/internal/app/SuggestedLocaleAdapter.java
@@ -199,7 +199,7 @@
text.setTextLocale(item.getLocale());
text.setContentDescription(item.getContentDescription(mCountryMode));
if (mCountryMode) {
- int layoutDir = TextUtils.getLayoutDirectionFromLocale(item.getParent());
+ int layoutDir = TextUtils.getLayoutDirectionFromLocale(Locale.getDefault());
//noinspection ResourceType
convertView.setLayoutDirection(layoutDir);
text.setTextDirection(layoutDir == View.LAYOUT_DIRECTION_RTL
diff --git a/core/java/com/android/internal/net/INetworkWatchlistManager.aidl b/core/java/com/android/internal/net/INetworkWatchlistManager.aidl
index 7e88369..ee01a23 100644
--- a/core/java/com/android/internal/net/INetworkWatchlistManager.aidl
+++ b/core/java/com/android/internal/net/INetworkWatchlistManager.aidl
@@ -22,5 +22,6 @@
interface INetworkWatchlistManager {
boolean startWatchlistLogging();
boolean stopWatchlistLogging();
+ void reloadWatchlist();
void reportWatchlistIfNecessary();
}
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 9ab16d8..1739bed 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -45,6 +45,7 @@
import android.os.SystemClock;
import android.os.UserHandle;
import android.os.WorkSource;
+import android.os.WorkSource.WorkChain;
import android.telephony.DataConnectionRealTimeInfo;
import android.telephony.ModemActivityInfo;
import android.telephony.ServiceState;
@@ -79,6 +80,7 @@
import com.android.internal.util.JournaledFile;
import com.android.internal.util.XmlUtils;
+import java.util.List;
import libcore.util.EmptyArray;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -504,8 +506,8 @@
final HistoryEventTracker mActiveEvents = new HistoryEventTracker();
long mHistoryBaseTime;
- boolean mHaveBatteryLevel = false;
- boolean mRecordingHistory = false;
+ protected boolean mHaveBatteryLevel = false;
+ protected boolean mRecordingHistory = false;
int mNumHistoryItems;
final Parcel mHistoryBuffer = Parcel.obtain();
@@ -650,6 +652,14 @@
new LongSamplingCounter[NUM_NETWORK_ACTIVITY_TYPES];
/**
+ * The WiFi Overall wakelock timer
+ * This timer tracks the actual aggregate time for which MC wakelocks are enabled
+ * since addition of per UID timers would not result in an accurate value due to overlapp of
+ * per uid wakelock timers
+ */
+ StopwatchTimer mWifiMulticastWakelockTimer;
+
+ /**
* The WiFi controller activity (time in tx, rx, idle, and power consumed) for the device.
*/
ControllerActivityCounterImpl mWifiActivity;
@@ -3973,30 +3983,85 @@
addHistoryEventLocked(elapsedRealtime, uptime, HistoryItem.EVENT_JOB_FINISH, name, uid);
}
- public void noteAlarmStartLocked(String name, int uid) {
- if (!mRecordAllHistory) {
- return;
- }
- uid = mapUid(uid);
- final long elapsedRealtime = mClocks.elapsedRealtime();
- final long uptime = mClocks.uptimeMillis();
- if (!mActiveEvents.updateState(HistoryItem.EVENT_ALARM_START, name, uid, 0)) {
- return;
- }
- addHistoryEventLocked(elapsedRealtime, uptime, HistoryItem.EVENT_ALARM_START, name, uid);
+ public void noteAlarmStartLocked(String name, WorkSource workSource, int uid) {
+ noteAlarmStartOrFinishLocked(HistoryItem.EVENT_ALARM_START, name, workSource, uid);
}
- public void noteAlarmFinishLocked(String name, int uid) {
+ public void noteAlarmFinishLocked(String name, WorkSource workSource, int uid) {
+ noteAlarmStartOrFinishLocked(HistoryItem.EVENT_ALARM_FINISH, name, workSource, uid);
+ }
+
+ private void noteAlarmStartOrFinishLocked(int historyItem, String name, WorkSource workSource,
+ int uid) {
if (!mRecordAllHistory) {
return;
}
- uid = mapUid(uid);
+
final long elapsedRealtime = mClocks.elapsedRealtime();
final long uptime = mClocks.uptimeMillis();
- if (!mActiveEvents.updateState(HistoryItem.EVENT_ALARM_FINISH, name, uid, 0)) {
+
+ if (workSource != null) {
+ for (int i = 0; i < workSource.size(); ++i) {
+ uid = mapUid(workSource.get(i));
+ if (mActiveEvents.updateState(historyItem, name, uid, 0)) {
+ addHistoryEventLocked(elapsedRealtime, uptime, historyItem, name, uid);
+ }
+ }
+
+ List<WorkChain> workChains = workSource.getWorkChains();
+ if (workChains != null) {
+ for (int i = 0; i < workChains.size(); ++i) {
+ uid = mapUid(workChains.get(i).getAttributionUid());
+ if (mActiveEvents.updateState(historyItem, name, uid, 0)) {
+ addHistoryEventLocked(elapsedRealtime, uptime, historyItem, name, uid);
+ }
+ }
+ }
+ } else {
+ uid = mapUid(uid);
+
+ if (mActiveEvents.updateState(historyItem, name, uid, 0)) {
+ addHistoryEventLocked(elapsedRealtime, uptime, historyItem, name, uid);
+ }
+ }
+ }
+
+ public void noteWakupAlarmLocked(String packageName, int uid, WorkSource workSource,
+ String tag) {
+ if (!isOnBattery()) {
return;
}
- addHistoryEventLocked(elapsedRealtime, uptime, HistoryItem.EVENT_ALARM_FINISH, name, uid);
+
+ if (workSource != null) {
+ for (int i = 0; i < workSource.size(); ++i) {
+ uid = workSource.get(i);
+ final String workSourceName = workSource.getName(i);
+
+ BatteryStatsImpl.Uid.Pkg pkg = getPackageStatsLocked(uid,
+ workSourceName != null ? workSourceName : packageName);
+ pkg.noteWakeupAlarmLocked(tag);
+
+ StatsLog.write(StatsLog.WAKEUP_ALARM_OCCURRED, uid, tag);
+ }
+
+ ArrayList<WorkChain> workChains = workSource.getWorkChains();
+ if (workChains != null) {
+ for (int i = 0; i < workChains.size(); ++i) {
+ final WorkChain wc = workChains.get(i);
+ uid = wc.getAttributionUid();
+
+ BatteryStatsImpl.Uid.Pkg pkg = getPackageStatsLocked(uid, packageName);
+ pkg.noteWakeupAlarmLocked(tag);
+
+ // TODO(statsd): Log the full attribution chain here once it's available
+ StatsLog.write(StatsLog.WAKEUP_ALARM_OCCURRED, uid, tag);
+ }
+ }
+ } else {
+ BatteryStatsImpl.Uid.Pkg pkg = getPackageStatsLocked(uid, packageName);
+ pkg.noteWakeupAlarmLocked(tag);
+ StatsLog.write(StatsLog.WAKEUP_ALARM_OCCURRED, uid, tag);
+ }
}
private void requestWakelockCpuUpdate() {
@@ -4064,8 +4129,8 @@
private String mInitialAcquireWakeName;
private int mInitialAcquireWakeUid = -1;
- public void noteStartWakeLocked(int uid, int pid, String name, String historyName, int type,
- boolean unimportantForLogging, long elapsedRealtime, long uptime) {
+ public void noteStartWakeLocked(int uid, int pid, WorkChain wc, String name, String historyName,
+ int type, boolean unimportantForLogging, long elapsedRealtime, long uptime) {
uid = mapUid(uid);
if (type == WAKE_TYPE_PARTIAL) {
// Only care about partial wake locks, since full wake locks
@@ -4113,12 +4178,18 @@
}
requestWakelockCpuUpdate();
}
+
getUidStatsLocked(uid).noteStartWakeLocked(pid, name, type, elapsedRealtime);
+
+ if (wc != null) {
+ StatsLog.write(
+ StatsLog.WAKELOCK_STATE_CHANGED, wc.getUids(), wc.getTags(), type, name, 1);
+ }
}
}
- public void noteStopWakeLocked(int uid, int pid, String name, String historyName, int type,
- long elapsedRealtime, long uptime) {
+ public void noteStopWakeLocked(int uid, int pid, WorkChain wc, String name, String historyName,
+ int type, long elapsedRealtime, long uptime) {
uid = mapUid(uid);
if (type == WAKE_TYPE_PARTIAL) {
mWakeLockNesting--;
@@ -4148,7 +4219,12 @@
}
requestWakelockCpuUpdate();
}
+
getUidStatsLocked(uid).noteStopWakeLocked(pid, name, type, elapsedRealtime);
+ if (wc != null) {
+ StatsLog.write(
+ StatsLog.WAKELOCK_STATE_CHANGED, wc.getUids(), wc.getTags(), type, name, 0);
+ }
}
}
@@ -4158,8 +4234,17 @@
final long uptime = mClocks.uptimeMillis();
final int N = ws.size();
for (int i=0; i<N; i++) {
- noteStartWakeLocked(ws.get(i), pid, name, historyName, type, unimportantForLogging,
- elapsedRealtime, uptime);
+ noteStartWakeLocked(ws.get(i), pid, null, name, historyName, type,
+ unimportantForLogging, elapsedRealtime, uptime);
+ }
+
+ List<WorkChain> wcs = ws.getWorkChains();
+ if (wcs != null) {
+ for (int i = 0; i < wcs.size(); ++i) {
+ final WorkChain wc = wcs.get(i);
+ noteStartWakeLocked(wc.getAttributionUid(), pid, wc, name, historyName, type,
+ unimportantForLogging, elapsedRealtime, uptime);
+ }
}
}
@@ -4168,17 +4253,46 @@
String newHistoryName, int newType, boolean newUnimportantForLogging) {
final long elapsedRealtime = mClocks.elapsedRealtime();
final long uptime = mClocks.uptimeMillis();
+
+ List<WorkChain>[] wcs = WorkSource.diffChains(ws, newWs);
+
// For correct semantics, we start the need worksources first, so that we won't
// make inappropriate history items as if all wake locks went away and new ones
// appeared. This is okay because tracking of wake locks allows nesting.
+ //
+ // First the starts :
final int NN = newWs.size();
for (int i=0; i<NN; i++) {
- noteStartWakeLocked(newWs.get(i), newPid, newName, newHistoryName, newType,
+ noteStartWakeLocked(newWs.get(i), newPid, null, newName, newHistoryName, newType,
newUnimportantForLogging, elapsedRealtime, uptime);
}
+ if (wcs != null) {
+ List<WorkChain> newChains = wcs[0];
+ if (newChains != null) {
+ for (int i = 0; i < newChains.size(); ++i) {
+ final WorkChain newChain = newChains.get(i);
+ noteStartWakeLocked(newChain.getAttributionUid(), newPid, newChain, newName,
+ newHistoryName, newType, newUnimportantForLogging, elapsedRealtime,
+ uptime);
+ }
+ }
+ }
+
+ // Then the stops :
final int NO = ws.size();
for (int i=0; i<NO; i++) {
- noteStopWakeLocked(ws.get(i), pid, name, historyName, type, elapsedRealtime, uptime);
+ noteStopWakeLocked(ws.get(i), pid, null, name, historyName, type, elapsedRealtime,
+ uptime);
+ }
+ if (wcs != null) {
+ List<WorkChain> goneChains = wcs[1];
+ if (goneChains != null) {
+ for (int i = 0; i < goneChains.size(); ++i) {
+ final WorkChain goneChain = goneChains.get(i);
+ noteStopWakeLocked(goneChain.getAttributionUid(), pid, goneChain, name,
+ historyName, type, elapsedRealtime, uptime);
+ }
+ }
}
}
@@ -4188,7 +4302,17 @@
final long uptime = mClocks.uptimeMillis();
final int N = ws.size();
for (int i=0; i<N; i++) {
- noteStopWakeLocked(ws.get(i), pid, name, historyName, type, elapsedRealtime, uptime);
+ noteStopWakeLocked(ws.get(i), pid, null, name, historyName, type, elapsedRealtime,
+ uptime);
+ }
+
+ List<WorkChain> wcs = ws.getWorkChains();
+ if (wcs != null) {
+ for (int i = 0; i < wcs.size(); ++i) {
+ final WorkChain wc = wcs.get(i);
+ noteStopWakeLocked(wc.getAttributionUid(), pid, wc, name, historyName, type,
+ elapsedRealtime, uptime);
+ }
}
}
@@ -4432,10 +4556,10 @@
updateTimeBasesLocked(mOnBatteryTimeBase.isRunning(), state,
mClocks.uptimeMillis() * 1000, elapsedRealtime * 1000);
// Fake a wake lock, so we consider the device waked as long as the screen is on.
- noteStartWakeLocked(-1, -1, "screen", null, WAKE_TYPE_PARTIAL, false,
+ noteStartWakeLocked(-1, -1, null, "screen", null, WAKE_TYPE_PARTIAL, false,
elapsedRealtime, uptime);
} else if (isScreenOn(oldState)) {
- noteStopWakeLocked(-1, -1, "screen", "screen", WAKE_TYPE_PARTIAL,
+ noteStopWakeLocked(-1, -1, null, "screen", "screen", WAKE_TYPE_PARTIAL,
elapsedRealtime, uptime);
updateTimeBasesLocked(mOnBatteryTimeBase.isRunning(), state,
mClocks.uptimeMillis() * 1000, elapsedRealtime * 1000);
@@ -5473,6 +5597,12 @@
if (DEBUG_HISTORY) Slog.v(TAG, "WIFI multicast on to: "
+ Integer.toHexString(mHistoryCur.states));
addHistoryRecordLocked(elapsedRealtime, uptime);
+
+ // Start Wifi Multicast overall timer
+ if (!mWifiMulticastWakelockTimer.isRunningLocked()) {
+ if (DEBUG_HISTORY) Slog.v(TAG, "WiFi Multicast Overall Timer Started");
+ mWifiMulticastWakelockTimer.startRunningLocked(elapsedRealtime);
+ }
}
mWifiMulticastNesting++;
getUidStatsLocked(uid).noteWifiMulticastEnabledLocked(elapsedRealtime);
@@ -5488,6 +5618,12 @@
if (DEBUG_HISTORY) Slog.v(TAG, "WIFI multicast off to: "
+ Integer.toHexString(mHistoryCur.states));
addHistoryRecordLocked(elapsedRealtime, uptime);
+
+ // Stop Wifi Multicast overall timer
+ if (mWifiMulticastWakelockTimer.isRunningLocked()) {
+ if (DEBUG_HISTORY) Slog.v(TAG, "Multicast Overall Timer Stopped");
+ mWifiMulticastWakelockTimer.stopRunningLocked(elapsedRealtime);
+ }
}
getUidStatsLocked(uid).noteWifiMulticastDisabledLocked(elapsedRealtime);
}
@@ -5774,6 +5910,16 @@
return (int)mMobileRadioActiveUnknownCount.getCountLocked(which);
}
+ @Override public long getWifiMulticastWakelockTime(
+ long elapsedRealtimeUs, int which) {
+ return mWifiMulticastWakelockTimer.getTotalTimeLocked(
+ elapsedRealtimeUs, which);
+ }
+
+ @Override public int getWifiMulticastWakelockCount(int which) {
+ return mWifiMulticastWakelockTimer.getCountLocked(which);
+ }
+
@Override public long getWifiOnTime(long elapsedRealtimeUs, int which) {
return mWifiOnTimer.getTotalTimeLocked(elapsedRealtimeUs, which);
}
@@ -9227,8 +9373,6 @@
Wakelock wl = mWakelockStats.startObject(name);
if (wl != null) {
getWakelockTimerLocked(wl, type).startRunningLocked(elapsedRealtimeMs);
- // TODO(statsd): Hopefully use a worksource instead of a uid (so move elsewhere)
- StatsLog.write(StatsLog.WAKELOCK_STATE_CHANGED, getUid(), type, name, 1);
}
if (type == WAKE_TYPE_PARTIAL) {
createAggregatedPartialWakelockTimerLocked().startRunningLocked(elapsedRealtimeMs);
@@ -9247,8 +9391,6 @@
StopwatchTimer wlt = getWakelockTimerLocked(wl, type);
wlt.stopRunningLocked(elapsedRealtimeMs);
if (!wlt.isRunningLocked()) { // only tell statsd if truly stopped
- // TODO(statsd): Possibly use a worksource instead of a uid.
- StatsLog.write(StatsLog.WAKELOCK_STATE_CHANGED, getUid(), type, name, 0);
}
}
if (type == WAKE_TYPE_PARTIAL) {
@@ -9378,6 +9520,8 @@
mMobileRadioActiveAdjustedTime = new LongSamplingCounter(mOnBatteryTimeBase);
mMobileRadioActiveUnknownTime = new LongSamplingCounter(mOnBatteryTimeBase);
mMobileRadioActiveUnknownCount = new LongSamplingCounter(mOnBatteryTimeBase);
+ mWifiMulticastWakelockTimer = new StopwatchTimer(mClocks, null,
+ WIFI_AGGREGATE_MULTICAST_ENABLED, null, mOnBatteryTimeBase);
mWifiOnTimer = new StopwatchTimer(mClocks, null, -4, null, mOnBatteryTimeBase);
mGlobalWifiRunningTimer = new StopwatchTimer(mClocks, null, -5, null, mOnBatteryTimeBase);
for (int i=0; i<NUM_WIFI_STATES; i++) {
@@ -10079,6 +10223,7 @@
for (int i=0; i<NUM_WIFI_SIGNAL_STRENGTH_BINS; i++) {
mWifiSignalStrengthsTimer[i].reset(false);
}
+ mWifiMulticastWakelockTimer.reset(false);
mWifiActivity.reset(false);
mBluetoothActivity.reset(false);
mModemActivity.reset(false);
@@ -12525,6 +12670,7 @@
mMobileRadioActiveAdjustedTime.readSummaryFromParcelLocked(in);
mMobileRadioActiveUnknownTime.readSummaryFromParcelLocked(in);
mMobileRadioActiveUnknownCount.readSummaryFromParcelLocked(in);
+ mWifiMulticastWakelockTimer.readSummaryFromParcelLocked(in);
mWifiRadioPowerState = DataConnectionRealTimeInfo.DC_POWER_STATE_LOW;
mWifiOn = false;
mWifiOnTimer.readSummaryFromParcelLocked(in);
@@ -12965,6 +13111,7 @@
mMobileRadioActiveAdjustedTime.writeSummaryFromParcelLocked(out);
mMobileRadioActiveUnknownTime.writeSummaryFromParcelLocked(out);
mMobileRadioActiveUnknownCount.writeSummaryFromParcelLocked(out);
+ mWifiMulticastWakelockTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
mWifiOnTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
mGlobalWifiRunningTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
for (int i=0; i<NUM_WIFI_STATES; i++) {
@@ -13428,6 +13575,8 @@
mMobileRadioActiveAdjustedTime = new LongSamplingCounter(mOnBatteryTimeBase, in);
mMobileRadioActiveUnknownTime = new LongSamplingCounter(mOnBatteryTimeBase, in);
mMobileRadioActiveUnknownCount = new LongSamplingCounter(mOnBatteryTimeBase, in);
+ mWifiMulticastWakelockTimer = new StopwatchTimer(mClocks, null, -4, null,
+ mOnBatteryTimeBase, in);
mWifiRadioPowerState = DataConnectionRealTimeInfo.DC_POWER_STATE_LOW;
mWifiOn = false;
mWifiOnTimer = new StopwatchTimer(mClocks, null, -4, null, mOnBatteryTimeBase, in);
@@ -13634,6 +13783,7 @@
mMobileRadioActiveAdjustedTime.writeToParcel(out);
mMobileRadioActiveUnknownTime.writeToParcel(out);
mMobileRadioActiveUnknownCount.writeToParcel(out);
+ mWifiMulticastWakelockTimer.writeToParcel(out, uSecRealtime);
mWifiOnTimer.writeToParcel(out, uSecRealtime);
mGlobalWifiRunningTimer.writeToParcel(out, uSecRealtime);
for (int i=0; i<NUM_WIFI_STATES; i++) {
@@ -13820,6 +13970,8 @@
mMobileRadioActiveTimer.logState(pr, " ");
pr.println("*** Mobile network active adjusted timer:");
mMobileRadioActiveAdjustedTime.logState(pr, " ");
+ pr.println("*** Wifi Multicast WakeLock Timer:");
+ mWifiMulticastWakelockTimer.logState(pr, " ");
pr.println("*** mWifiRadioPowerState=" + mWifiRadioPowerState);
pr.println("*** Wifi timer:");
mWifiOnTimer.logState(pr, " ");
diff --git a/core/java/com/android/internal/util/function/pooled/PooledLambda.java b/core/java/com/android/internal/util/function/pooled/PooledLambda.java
index 17b140d..87c25e9 100755
--- a/core/java/com/android/internal/util/function/pooled/PooledLambda.java
+++ b/core/java/com/android/internal/util/function/pooled/PooledLambda.java
@@ -59,6 +59,21 @@
* You can fill the 'missing argument' spot with {@link #__()}
* (which is the factory function for {@link ArgumentPlaceholder})
*
+ * NOTE: It is highly recommended to <b>only</b> use {@code ClassName::methodName}
+ * (aka unbounded method references) as the 1st argument for any of the
+ * factories ({@code obtain*(...)}) to avoid unwanted allocations.
+ * This means <b>not</b> using:
+ * <ul>
+ * <li>{@code someVar::methodName} or {@code this::methodName} as it captures the reference
+ * on the left of {@code ::}, resulting in an allocation on each evaluation of such
+ * bounded method references</li>
+ *
+ * <li>A lambda expression, e.g. {@code () -> toString()} due to how easy it is to accidentally
+ * capture state from outside. In the above lambda expression for example, no variable from
+ * outer scope is explicitly mentioned, yet one is still captured due to {@code toString()}
+ * being an equivalent of {@code this.toString()}</li>
+ * </ul>
+ *
* @hide
*/
@SuppressWarnings({"unchecked", "unused", "WeakerAccess"})
diff --git a/core/java/com/android/internal/widget/ButtonBarLayout.java b/core/java/com/android/internal/widget/ButtonBarLayout.java
index 82affe2..ab8be33 100644
--- a/core/java/com/android/internal/widget/ButtonBarLayout.java
+++ b/core/java/com/android/internal/widget/ButtonBarLayout.java
@@ -30,9 +30,6 @@
* orientation when it can't fit its child views horizontally.
*/
public class ButtonBarLayout extends LinearLayout {
- /** Minimum screen height required for button stacking. */
- private static final int ALLOW_STACKING_MIN_HEIGHT_DP = 320;
-
/** Amount of the second button to "peek" above the fold when stacked. */
private static final int PEEK_BUTTON_DP = 16;
@@ -46,12 +43,8 @@
public ButtonBarLayout(Context context, AttributeSet attrs) {
super(context, attrs);
- final boolean allowStackingDefault =
- context.getResources().getConfiguration().screenHeightDp
- >= ALLOW_STACKING_MIN_HEIGHT_DP;
final TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ButtonBarLayout);
- mAllowStacking = ta.getBoolean(R.styleable.ButtonBarLayout_allowStacking,
- allowStackingDefault);
+ mAllowStacking = ta.getBoolean(R.styleable.ButtonBarLayout_allowStacking, true);
ta.recycle();
}
diff --git a/core/java/com/android/internal/widget/ILockSettings.aidl b/core/java/com/android/internal/widget/ILockSettings.aidl
index 43536a5..77250eb 100644
--- a/core/java/com/android/internal/widget/ILockSettings.aidl
+++ b/core/java/com/android/internal/widget/ILockSettings.aidl
@@ -66,6 +66,7 @@
void initRecoveryService(in String rootCertificateAlias, in byte[] signedPublicKeyList,
int userId);
KeyStoreRecoveryData getRecoveryData(in byte[] account, int userId);
+ byte[] generateAndStoreKey(String alias);
void setSnapshotCreatedPendingIntent(in PendingIntent intent, int userId);
Map getRecoverySnapshotVersions(int userId);
void setServerParameters(long serverParameters, int userId);
@@ -78,6 +79,6 @@
byte[] startRecoverySession(in String sessionId,
in byte[] verifierPublicKey, in byte[] vaultParams, in byte[] vaultChallenge,
in List<KeyStoreRecoveryMetadata> secrets, int userId);
- void recoverKeys(in String sessionId, in byte[] recoveryKeyBlob,
+ Map/*<String, byte[]>*/ recoverKeys(in String sessionId, in byte[] recoveryKeyBlob,
in List<KeyEntryRecoveryData> applicationKeys, int userId);
}
diff --git a/core/proto/android/os/batterystats.proto b/core/proto/android/os/batterystats.proto
index 0fa428e..da21258 100644
--- a/core/proto/android/os/batterystats.proto
+++ b/core/proto/android/os/batterystats.proto
@@ -537,7 +537,24 @@
// Screen-off CPU time in milliseconds.
optional int64 screen_off_duration_ms = 3;
}
+ // CPU times accumulated across all process states.
repeated ByFrequency by_frequency = 3;
+
+ enum ProcessState {
+ TOP = 0;
+ FOREGROUND_SERVICE = 1;
+ FOREGROUND = 2;
+ BACKGROUND = 3;
+ TOP_SLEEPING = 4;
+ HEAVY_WEIGHT = 5;
+ CACHED = 6;
+ }
+ // CPU times at different process states.
+ message ByProcessState {
+ optional ProcessState process_state = 1;
+ repeated ByFrequency by_frequency = 2;
+ }
+ repeated ByProcessState by_process_state = 4;
}
optional Cpu cpu = 7;
diff --git a/core/proto/android/os/batterytype.proto b/core/proto/android/os/batterytype.proto
new file mode 100644
index 0000000..75d0dd3
--- /dev/null
+++ b/core/proto/android/os/batterytype.proto
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+syntax = "proto2";
+
+package android.os;
+
+option java_multiple_files = true;
+
+message BatteryTypeProto {
+ optional string type = 1;
+}
diff --git a/core/proto/android/os/cpuinfo.proto b/core/proto/android/os/cpuinfo.proto
index 522ff24..cd151e2 100644
--- a/core/proto/android/os/cpuinfo.proto
+++ b/core/proto/android/os/cpuinfo.proto
@@ -81,7 +81,9 @@
optional string virt = 8; // virtual memory size, i.e. 14.0G, 13.5M
optional string res = 9; // Resident size, i.e. 0, 3.1G
- // How Android memory manager will treat the task
+ // How Android memory manager will treat the task.
+ // TODO: use PsDumpProto.Process.Policy instead once we extern variables
+ // and are able to include the same .h file in two files.
enum Policy {
POLICY_UNKNOWN = 0;
POLICY_fg = 1; // foreground, the name is lower case for parsing the value
diff --git a/core/proto/android/os/incident.proto b/core/proto/android/os/incident.proto
index ac8f26d..a6db31f 100644
--- a/core/proto/android/os/incident.proto
+++ b/core/proto/android/os/incident.proto
@@ -18,12 +18,14 @@
option java_multiple_files = true;
option java_outer_classname = "IncidentProtoMetadata";
+import "frameworks/base/core/proto/android/os/batterytype.proto";
import "frameworks/base/core/proto/android/os/cpufreq.proto";
import "frameworks/base/core/proto/android/os/cpuinfo.proto";
import "frameworks/base/core/proto/android/os/incidentheader.proto";
import "frameworks/base/core/proto/android/os/kernelwake.proto";
import "frameworks/base/core/proto/android/os/pagetypeinfo.proto";
import "frameworks/base/core/proto/android/os/procrank.proto";
+import "frameworks/base/core/proto/android/os/ps.proto";
import "frameworks/base/core/proto/android/os/system_properties.proto";
import "frameworks/base/core/proto/android/providers/settings.proto";
import "frameworks/base/core/proto/android/server/activitymanagerservice.proto";
@@ -65,7 +67,7 @@
// Device information
optional SystemPropertiesProto system_properties = 1000 [
(section).type = SECTION_COMMAND,
- (section).args = "/system/bin/getprop"
+ (section).args = "getprop"
];
// Linux services
@@ -86,7 +88,7 @@
optional CpuInfo cpu_info = 2003 [
(section).type = SECTION_COMMAND,
- (section).args = "/system/bin/top -b -n 1 -H -s 6 -o pid,tid,user,pr,ni,%cpu,s,virt,res,pcy,cmd,name"
+ (section).args = "top -b -n 1 -H -s 6 -o pid,tid,user,pr,ni,%cpu,s,virt,res,pcy,cmd,name"
];
optional CpuFreq cpu_freq = 2004 [
@@ -94,6 +96,16 @@
(section).args = "/sys/devices/system/cpu/cpufreq/all_time_in_state"
];
+ optional PsDumpProto processes_and_threads = 2005 [
+ (section).type = SECTION_COMMAND,
+ (section).args = "ps -A -T -Z -O pri,nice,rtprio,sched,pcy,time"
+ ];
+
+ optional BatteryTypeProto battery_type = 2006 [
+ (section).type = SECTION_FILE,
+ (section).args = "/sys/class/power_supply/bms/battery_type"
+ ];
+
// System Services
optional com.android.server.fingerprint.FingerprintServiceDumpProto fingerprint = 3000 [
(section).type = SECTION_DUMPSYS,
diff --git a/core/proto/android/os/ps.proto b/core/proto/android/os/ps.proto
new file mode 100644
index 0000000..88c6609
--- /dev/null
+++ b/core/proto/android/os/ps.proto
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 android.os;
+
+option java_multiple_files = true;
+
+message PsDumpProto {
+ message Process {
+ // Security label, most commonly used for SELinux context data.
+ optional string label = 1;
+ optional string user = 2;
+ // Process ID number.
+ optional int32 pid = 3;
+ // The unique number representing a dispatchable entity (alias lwp,
+ // spid). This value may also appear as: a process ID (pid); a process
+ // group ID (pgrp); a session ID for the session leader (sid); a thread
+ // group ID for the thread group leader (tgid); and a tty process group
+ // ID for the process group leader (tpgid).
+ optional int32 tid = 4;
+ // Parent process ID.
+ optional int32 ppid = 5;
+ // Virtual set size (memory size) of the process, in KiB.
+ optional int32 vsz = 6;
+ // Resident set size. How many physical pages are associated with the
+ // process; real memory usage, in KiB.
+ optional int32 rss = 7;
+ // Name of the kernel function in which the process is sleeping, a "-"
+ // if the process is running, or a "*" if the process is multi-threaded
+ // and ps is not displaying threads.
+ optional string wchan = 8;
+ // Memory address of the process.
+ optional string addr = 9;
+
+ enum ProcessStateCode {
+ STATE_UNKNOWN = 0;
+ // Uninterruptible sleep (usually IO).
+ STATE_D = 1;
+ // Running or runnable (on run queue).
+ STATE_R = 2;
+ // Interruptible sleep (waiting for an event to complete).
+ STATE_S = 3;
+ // Stopped by job control signal.
+ STATE_T = 4;
+ // Stopped by debugger during the tracing.
+ STATE_TRACING = 5;
+ // Dead (should never be seen).
+ STATE_X = 6;
+ // Defunct ("zombie") process. Terminated but not reaped by its
+ // parent.
+ STATE_Z = 7;
+ }
+ // Minimal state display
+ optional ProcessStateCode s = 10;
+ // Priority of the process. Higher number means lower priority.
+ optional int32 pri = 11;
+ // Nice value. This ranges from 19 (nicest) to -20 (not nice to others).
+ optional sint32 ni = 12;
+ // Realtime priority.
+ optional string rtprio = 13; // Number or -
+
+ enum SchedulingPolicy {
+ option allow_alias = true;
+
+ // Regular names conflict with macros defined in
+ // bionic/libc/kernel/uapi/linux/sched.h.
+ SCH_OTHER = 0;
+ SCH_NORMAL = 0;
+
+ SCH_FIFO = 1;
+ SCH_RR = 2;
+ SCH_BATCH = 3;
+ SCH_ISO = 4;
+ SCH_IDLE = 5;
+ }
+ // Scheduling policy of the process.
+ optional SchedulingPolicy sch = 14;
+
+ // How Android memory manager will treat the task
+ enum Policy {
+ POLICY_UNKNOWN = 0;
+ // Foreground.
+ POLICY_FG = 1;
+ // Background.
+ POLICY_BG = 2;
+ POLICY_TA = 3; // TODO: figure out what this value is
+ }
+ optional Policy pcy = 15;
+ // Total CPU time, "[DD-]HH:MM:SS" format.
+ optional string time = 16;
+ // Command with all its arguments as a string.
+ optional string cmd = 17;
+ }
+ repeated Process processes = 1;
+}
diff --git a/core/proto/android/os/worksource.proto b/core/proto/android/os/worksource.proto
index c60edfc..2f8b2fb 100644
--- a/core/proto/android/os/worksource.proto
+++ b/core/proto/android/os/worksource.proto
@@ -25,5 +25,10 @@
optional string name = 2;
}
+ message WorkChain {
+ repeated WorkSourceContentProto nodes = 1;
+ }
+
repeated WorkSourceContentProto work_source_contents = 1;
-}
\ No newline at end of file
+ repeated WorkChain work_chains = 2;
+}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 86d2ee3..52bfcde 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -3534,11 +3534,19 @@
@hide -->
<permission android:name="android.permission.ACCESS_INSTANT_APPS"
android:protectionLevel="signature|installer|verifier" />
+ <uses-permission android:name="android.permission.ACCESS_INSTANT_APPS"/>
<!-- Allows the holder to view the instant applications on the device.
@hide -->
<permission android:name="android.permission.VIEW_INSTANT_APPS"
- android:protectionLevel="signature|preinstalled" />
+ android:protectionLevel="signature|preinstalled" />
+
+ <!-- Allows the holder to manage whether the system can bind to services
+ provided by instant apps. This permission is intended to protect
+ test/development fucntionality and should be used only in such cases.
+ @hide -->
+ <permission android:name="android.permission.MANAGE_BIND_INSTANT_SERVICE"
+ android:protectionLevel="signature" />
<!-- Allows receiving the usage of media resource e.g. video/audio codec and
graphic memory.
@@ -3862,6 +3870,14 @@
</intent-filter>
</receiver>
+ <receiver android:name="com.android.server.updates.NetworkWatchlistInstallReceiver"
+ android:permission="android.permission.UPDATE_CONFIG">
+ <intent-filter>
+ <action android:name="android.intent.action.UPDATE_NETWORK_WATCHLIST" />
+ <data android:scheme="content" android:host="*" android:mimeType="*/*" />
+ </intent-filter>
+ </receiver>
+
<receiver android:name="com.android.server.updates.ApnDbInstallReceiver"
android:permission="android.permission.UPDATE_CONFIG">
<intent-filter>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 64febf1..1b3d6ce 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -8699,8 +8699,11 @@
common values are 400 for regular weight and 700 for bold weight. If unspecified, the value
in the font's header tables will be used. -->
<attr name="fontWeight" format="integer" />
- <!-- The index of the font in the tcc font file. If the font file referenced is not in the
- tcc format, this attribute needs not be specified. -->
+ <!-- The index of the font in the ttc (TrueType Collection) font file. If the font file
+ referenced is not in the ttc format, this attribute needs not be specified.
+ {@see android.graphics.Typeface#Builder.setTtcIndex(int)}.
+ The default value is 0. More details about the TrueType Collection font format can be found
+ here: https://en.wikipedia.org/wiki/TrueType#TrueType_Collection. -->
<attr name="ttcIndex" format="integer" />
<!-- The variation settings to be applied to the font. The string should be in the following
format: "'tag1' value1, 'tag2' value2, ...". If the default variation settings should be
diff --git a/core/res/res/values/colors_device_defaults.xml b/core/res/res/values/colors_device_defaults.xml
index e2498df7..7048511 100644
--- a/core/res/res/values/colors_device_defaults.xml
+++ b/core/res/res/values/colors_device_defaults.xml
@@ -31,10 +31,8 @@
<color name="tertiary_device_default_settings">@color/tertiary_material_settings</color>
<color name="quaternary_device_default_settings">@color/quaternary_material_settings</color>
- <color name="accent_device_default_700">@color/accent_material_700</color>
<color name="accent_device_default_light">@color/accent_material_light</color>
<color name="accent_device_default_dark">@color/accent_material_dark</color>
- <color name="accent_device_default_50">@color/accent_material_50</color>
<color name="background_device_default_dark">@color/background_material_dark</color>
<color name="background_device_default_light">@color/background_material_light</color>
diff --git a/core/res/res/values/colors_material.xml b/core/res/res/values/colors_material.xml
index 8c37d4b..0413100 100644
--- a/core/res/res/values/colors_material.xml
+++ b/core/res/res/values/colors_material.xml
@@ -39,10 +39,8 @@
<color name="tertiary_material_settings">@color/material_blue_grey_700</color>
<color name="quaternary_material_settings">@color/material_blue_grey_200</color>
- <color name="accent_material_700">@color/material_deep_teal_700</color>
<color name="accent_material_light">@color/material_deep_teal_500</color>
<color name="accent_material_dark">@color/material_deep_teal_200</color>
- <color name="accent_material_50">@color/material_deep_teal_50</color>
<color name="button_material_dark">#ff5a595b</color>
<color name="button_material_light">#ffd6d7d7</color>
@@ -95,12 +93,10 @@
<color name="material_grey_100">#fff5f5f5</color>
<color name="material_grey_50">#fffafafa</color>
- <color name="material_deep_teal_50">#ffe0f2f1</color>
<color name="material_deep_teal_100">#ffb2dfdb</color>
<color name="material_deep_teal_200">#ff80cbc4</color>
<color name="material_deep_teal_300">#ff4db6ac</color>
<color name="material_deep_teal_500">#ff009688</color>
- <color name="material_deep_teal_700">#ff00796b</color>
<color name="material_blue_grey_200">#ffb0bec5</color>
<color name="material_blue_grey_700">#ff455a64</color>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 5783435..c54f799 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -20,13 +20,23 @@
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<!-- Suffix added to a number to signify size in bytes. -->
<string name="byteShort">B</string>
+ <!-- Suffix added to a number to signify size in kilobytes (1000 bytes).
+ If you retain the Latin script for the localization, please use the lowercase
+ 'k', as it signifies 1000 bytes as opposed to 1024 bytes. -->
+ <string name="kilobyteShort">kB</string>
+ <!-- Suffix added to a number to signify size in megabytes. -->
+ <string name="megabyteShort">MB</string>
+ <!-- Suffix added to a number to signify size in gigabytes. -->
+ <string name="gigabyteShort">GB</string>
+ <!-- Suffix added to a number to signify size in terabytes. -->
+ <string name="terabyteShort">TB</string>
<!-- Suffix added to a number to signify size in petabytes. -->
<string name="petabyteShort">PB</string>
- <!-- Format string used to add a suffix like "B" or "PB" to a number
- to display a size in bytes or petabytes.
- Some languages may want to remove the space between the placeholders
- or replace it with a non-breaking space. -->
- <string name="fileSizeSuffix"><xliff:g id="number" example="123">%1$s</xliff:g> <xliff:g id="unit" example="B">%2$s</xliff:g></string>
+ <!-- Format string used to add a suffix like "kB" or "MB" to a number
+ to display a size in kilobytes, megabytes, or other size units.
+ Some languages (like French) will want to add a space between
+ the placeholders. -->
+ <string name="fileSizeSuffix"><xliff:g id="number" example="123">%1$s</xliff:g> <xliff:g id="unit" example="MB">%2$s</xliff:g></string>
<!-- Used in Contacts for a field that has no label and in Note Pad
for a note with no name. -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index d2dabcf..4343ba0 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -705,6 +705,7 @@
<java-symbol type="string" name="fileSizeSuffix" />
<java-symbol type="string" name="force_close" />
<java-symbol type="string" name="gadget_host_error_inflating" />
+ <java-symbol type="string" name="gigabyteShort" />
<java-symbol type="string" name="gpsNotifMessage" />
<java-symbol type="string" name="gpsNotifTicker" />
<java-symbol type="string" name="gpsNotifTitle" />
@@ -760,6 +761,7 @@
<java-symbol type="string" name="keyboardview_keycode_enter" />
<java-symbol type="string" name="keyboardview_keycode_mode_change" />
<java-symbol type="string" name="keyboardview_keycode_shift" />
+ <java-symbol type="string" name="kilobyteShort" />
<java-symbol type="string" name="last_month" />
<java-symbol type="string" name="launchBrowserDefault" />
<java-symbol type="string" name="lock_to_app_toast" />
@@ -779,6 +781,7 @@
<java-symbol type="string" name="lockscreen_emergency_call" />
<java-symbol type="string" name="lockscreen_return_to_call" />
<java-symbol type="string" name="low_memory" />
+ <java-symbol type="string" name="megabyteShort" />
<java-symbol type="string" name="midnight" />
<java-symbol type="string" name="mismatchPin" />
<java-symbol type="string" name="mmiComplete" />
@@ -981,6 +984,7 @@
<java-symbol type="string" name="sync_really_delete" />
<java-symbol type="string" name="sync_too_many_deletes_desc" />
<java-symbol type="string" name="sync_undo_deletes" />
+ <java-symbol type="string" name="terabyteShort" />
<java-symbol type="string" name="text_copied" />
<java-symbol type="string" name="time_of_day" />
<java-symbol type="string" name="time_picker_decrement_hour_button" />
diff --git a/core/tests/coretests/src/android/os/WorkSourceTest.java b/core/tests/coretests/src/android/os/WorkSourceTest.java
index 704b780..90b4575 100644
--- a/core/tests/coretests/src/android/os/WorkSourceTest.java
+++ b/core/tests/coretests/src/android/os/WorkSourceTest.java
@@ -20,6 +20,7 @@
import junit.framework.TestCase;
+import java.util.ArrayList;
import java.util.List;
/**
@@ -218,4 +219,116 @@
assertEquals(20, ws2.get(0));
assertEquals("foo", ws2.getName(0));
}
+
+ public void testDiffChains_noChanges() {
+ // WorkSources with no chains.
+ assertEquals(null, WorkSource.diffChains(new WorkSource(), new WorkSource()));
+
+ // WorkSources with the same chains.
+ WorkSource ws1 = new WorkSource();
+ ws1.createWorkChain().addNode(50, "tag");
+ ws1.createWorkChain().addNode(60, "tag2");
+
+ WorkSource ws2 = new WorkSource();
+ ws2.createWorkChain().addNode(50, "tag");
+ ws2.createWorkChain().addNode(60, "tag2");
+
+ assertEquals(null, WorkSource.diffChains(ws1, ws1));
+ assertEquals(null, WorkSource.diffChains(ws2, ws1));
+ }
+
+ public void testDiffChains_noChains() {
+ // Diffs against a worksource with no chains.
+ WorkSource ws1 = new WorkSource();
+ WorkSource ws2 = new WorkSource();
+ ws2.createWorkChain().addNode(70, "tag");
+ ws2.createWorkChain().addNode(60, "tag2");
+
+ // The "old" work source has no chains, so "newChains" should be non-null.
+ ArrayList<WorkChain>[] diffs = WorkSource.diffChains(ws1, ws2);
+ assertNotNull(diffs[0]);
+ assertNull(diffs[1]);
+ assertEquals(2, diffs[0].size());
+ assertEquals(ws2.getWorkChains(), diffs[0]);
+
+ // The "new" work source has no chains, so "oldChains" should be non-null.
+ diffs = WorkSource.diffChains(ws2, ws1);
+ assertNull(diffs[0]);
+ assertNotNull(diffs[1]);
+ assertEquals(2, diffs[1].size());
+ assertEquals(ws2.getWorkChains(), diffs[1]);
+ }
+
+ public void testDiffChains_onlyAdditionsOrRemovals() {
+ WorkSource ws1 = new WorkSource();
+ WorkSource ws2 = new WorkSource();
+ ws2.createWorkChain().addNode(70, "tag");
+ ws2.createWorkChain().addNode(60, "tag2");
+
+ // Both work sources have WorkChains : test the case where changes were only added
+ // or were only removed.
+ ws1.createWorkChain().addNode(70, "tag");
+
+ // The "new" work source only contains additions (60, "tag2") in this case.
+ ArrayList<WorkChain>[] diffs = WorkSource.diffChains(ws1, ws2);
+ assertNotNull(diffs[0]);
+ assertNull(diffs[1]);
+ assertEquals(1, diffs[0].size());
+ assertEquals(new WorkChain().addNode(60, "tag2"), diffs[0].get(0));
+
+ // The "new" work source only contains removals (60, "tag2") in this case.
+ diffs = WorkSource.diffChains(ws2, ws1);
+ assertNull(diffs[0]);
+ assertNotNull(diffs[1]);
+ assertEquals(1, diffs[1].size());
+ assertEquals(new WorkChain().addNode(60, "tag2"), diffs[1].get(0));
+ }
+
+
+ public void testDiffChains_generalCase() {
+ WorkSource ws1 = new WorkSource();
+ WorkSource ws2 = new WorkSource();
+
+ // Both work sources have WorkChains, test the case where chains were added AND removed.
+ ws1.createWorkChain().addNode(0, "tag0");
+ ws2.createWorkChain().addNode(0, "tag0_changed");
+ ArrayList<WorkChain>[] diffs = WorkSource.diffChains(ws1, ws2);
+ assertNotNull(diffs[0]);
+ assertNotNull(diffs[1]);
+ assertEquals(ws2.getWorkChains(), diffs[0]);
+ assertEquals(ws1.getWorkChains(), diffs[1]);
+
+ // Give both WorkSources a chain in common; it should not be a part of any diffs.
+ ws1.createWorkChain().addNode(1, "tag1");
+ ws2.createWorkChain().addNode(1, "tag1");
+ diffs = WorkSource.diffChains(ws1, ws2);
+ assertNotNull(diffs[0]);
+ assertNotNull(diffs[1]);
+ assertEquals(1, diffs[0].size());
+ assertEquals(1, diffs[1].size());
+ assertEquals(new WorkChain().addNode(0, "tag0_changed"), diffs[0].get(0));
+ assertEquals(new WorkChain().addNode(0, "tag0"), diffs[1].get(0));
+
+ // Finally, test the case where more than one chain was added / removed.
+ ws1.createWorkChain().addNode(2, "tag2");
+ ws2.createWorkChain().addNode(2, "tag2_changed");
+ diffs = WorkSource.diffChains(ws1, ws2);
+ assertNotNull(diffs[0]);
+ assertNotNull(diffs[1]);
+ assertEquals(2, diffs[0].size());
+ assertEquals(2, diffs[1].size());
+ assertEquals(new WorkChain().addNode(0, "tag0_changed"), diffs[0].get(0));
+ assertEquals(new WorkChain().addNode(2, "tag2_changed"), diffs[0].get(1));
+ assertEquals(new WorkChain().addNode(0, "tag0"), diffs[1].get(0));
+ assertEquals(new WorkChain().addNode(2, "tag2"), diffs[1].get(1));
+ }
+
+ public void testGetAttributionId() {
+ WorkSource ws1 = new WorkSource();
+ WorkChain wc = ws1.createWorkChain();
+ wc.addNode(100, "tag");
+ assertEquals(100, wc.getAttributionUid());
+ wc.addNode(200, "tag2");
+ assertEquals(100, wc.getAttributionUid());
+ }
}
diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index eef9866..fc86500 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -525,7 +525,8 @@
Settings.Secure.VOICE_INTERACTION_SERVICE,
Settings.Secure.VOICE_RECOGNITION_SERVICE,
Settings.Secure.INSTANT_APPS_ENABLED,
- Settings.Secure.BACKUP_MANAGER_CONSTANTS);
+ Settings.Secure.BACKUP_MANAGER_CONSTANTS,
+ Settings.Secure.KEYGUARD_SLICE_URI);
@Test
public void systemSettingsBackedUpOrBlacklisted() {
diff --git a/core/tests/coretests/src/android/text/format/FormatterTest.java b/core/tests/coretests/src/android/text/format/FormatterTest.java
index 9c544f4..04d2dad 100644
--- a/core/tests/coretests/src/android/text/format/FormatterTest.java
+++ b/core/tests/coretests/src/android/text/format/FormatterTest.java
@@ -17,7 +17,6 @@
package android.text.format;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
import android.content.Context;
import android.content.res.Configuration;
@@ -210,24 +209,4 @@
Locale.setDefault(locale);
}
-
- /**
- * Verifies that Formatter doesn't "leak" the locally defined petabyte unit into ICU via the
- * {@link MeasureUnit} registry. This test can fail for two reasons:
- * 1. we regressed and started leaking again. In this case the code needs to be fixed.
- * 2. ICU started supporting petabyte as a unit, in which case change one needs to revert this
- * change (I494fb59a3b3742f35cbdf6b8705817f404a2c6b0), remove Formatter.PETABYTE and replace any
- * usages of that field with just MeasureUnit.PETABYTE.
- */
- // http://b/65632959
- @Test
- public void doesNotLeakPetabyte() {
- // Ensure that the Formatter class is loaded when we call .getAvailable().
- Formatter.formatFileSize(mContext, Long.MAX_VALUE);
- Set<MeasureUnit> digitalUnits = MeasureUnit.getAvailable("digital");
- for (MeasureUnit unit : digitalUnits) {
- // This assert can fail if we don't leak PETABYTE, but ICU has added it, see #2 above.
- assertNotEquals("petabyte", unit.getSubtype());
- }
- }
}
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityEventTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityEventTest.java
new file mode 100644
index 0000000..fed197e
--- /dev/null
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityEventTest.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.accessibility;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import android.os.Parcel;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * AccessibilityEvent is public, so CTS covers it pretty well. Verifying hidden methods here.
+ */
+@RunWith(AndroidJUnit4.class)
+public class AccessibilityEventTest {
+ @Test
+ public void testImportantForAccessibiity_getSetWorkAcrossParceling() {
+ AccessibilityEvent event = AccessibilityEvent.obtain();
+ event.setImportantForAccessibility(true);
+ assertTrue(copyEventViaParcel(event).isImportantForAccessibility());
+
+ event.setImportantForAccessibility(false);
+ assertFalse(copyEventViaParcel(event).isImportantForAccessibility());
+ }
+
+ @Test
+ public void testSouceNodeId_getSetWorkAcrossParceling() {
+ final long sourceNodeId = 0x1234567890ABCDEFL;
+ AccessibilityEvent event = AccessibilityEvent.obtain();
+ event.setSourceNodeId(sourceNodeId);
+ assertEquals(sourceNodeId, copyEventViaParcel(event).getSourceNodeId());
+ }
+
+ @Test
+ public void testWindowChanges_getSetWorkAcrossParceling() {
+ final int windowChanges = AccessibilityEvent.WINDOWS_CHANGE_TITLE
+ | AccessibilityEvent.WINDOWS_CHANGE_ACTIVE
+ | AccessibilityEvent.WINDOWS_CHANGE_FOCUSED;
+ AccessibilityEvent event = AccessibilityEvent.obtain();
+ event.setWindowChanges(windowChanges);
+ assertEquals(windowChanges, copyEventViaParcel(event).getWindowChanges());
+ }
+
+ private AccessibilityEvent copyEventViaParcel(AccessibilityEvent event) {
+ Parcel parcel = Parcel.obtain();
+ event.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ return AccessibilityEvent.CREATOR.createFromParcel(parcel);
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/TextViewTest.java b/core/tests/coretests/src/android/widget/TextViewTest.java
index 7875c17..5dec42e 100644
--- a/core/tests/coretests/src/android/widget/TextViewTest.java
+++ b/core/tests/coretests/src/android/widget/TextViewTest.java
@@ -35,6 +35,7 @@
import android.text.Layout;
import android.text.Selection;
import android.text.Spannable;
+import android.util.TypedValue;
import android.view.View;
import org.junit.Before;
@@ -274,7 +275,7 @@
new FontFallbackSetup("DynamicLayout", testFontFiles, xml)) {
mTextView = new TextView(mActivity);
mTextView.setTypeface(setup.getTypefaceFor("sans-serif"));
- mTextView.setTextSize(100);
+ mTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX, 100);
mTextView.setText("aaaaa aabaa aaaaa"); // This should result in three lines.
mTextView.setPadding(0, 0, 0, 0);
mTextView.setIncludeFontPadding(false);
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
index 0afec34..a55563a 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
@@ -15,16 +15,19 @@
*/
package com.android.internal.os;
+import static android.os.BatteryStats.STATS_CURRENT;
import static android.os.BatteryStats.STATS_SINCE_CHARGED;
import static android.os.BatteryStats.WAKE_TYPE_PARTIAL;
import android.app.ActivityManager;
import android.os.BatteryManager;
import android.os.BatteryStats;
+import android.os.BatteryStats.HistoryItem;
import android.os.WorkSource;
import android.support.test.filters.SmallTest;
import android.view.Display;
+import com.android.internal.os.BatteryStatsImpl.Uid;
import junit.framework.TestCase;
import java.util.ArrayList;
@@ -44,11 +47,14 @@
* Run: adb shell am instrument -e class com.android.internal.os.BatteryStatsNoteTest -w \
* com.android.frameworks.coretests/android.support.test.runner.AndroidJUnitRunner
*/
-public class BatteryStatsNoteTest extends TestCase{
+public class BatteryStatsNoteTest extends TestCase {
+
private static final int UID = 10500;
private static final WorkSource WS = new WorkSource(UID);
- /** Test BatteryStatsImpl.Uid.noteBluetoothScanResultLocked. */
+ /**
+ * Test BatteryStatsImpl.Uid.noteBluetoothScanResultLocked.
+ */
@SmallTest
public void testNoteBluetoothScanResultLocked() throws Exception {
MockBatteryStatsImpl bi = new MockBatteryStatsImpl(new MockClocks());
@@ -75,7 +81,9 @@
.getCountLocked(STATS_SINCE_CHARGED));
}
- /** Test BatteryStatsImpl.Uid.noteStartWakeLocked. */
+ /**
+ * Test BatteryStatsImpl.Uid.noteStartWakeLocked.
+ */
@SmallTest
public void testNoteStartWakeLocked() throws Exception {
final MockClocks clocks = new MockClocks(); // holds realtime and uptime in ms
@@ -86,7 +94,8 @@
bi.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0);
bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_TOP);
- bi.getUidStatsLocked(UID).noteStartWakeLocked(pid, name, WAKE_TYPE_PARTIAL, clocks.realtime);
+ bi.getUidStatsLocked(UID)
+ .noteStartWakeLocked(pid, name, WAKE_TYPE_PARTIAL, clocks.realtime);
clocks.realtime = clocks.uptime = 100;
bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
@@ -94,7 +103,8 @@
clocks.realtime = clocks.uptime = 220;
bi.getUidStatsLocked(UID).noteStopWakeLocked(pid, name, WAKE_TYPE_PARTIAL, clocks.realtime);
- BatteryStats.Timer aggregTimer = bi.getUidStats().get(UID).getAggregatedPartialWakelockTimer();
+ BatteryStats.Timer aggregTimer = bi.getUidStats().get(UID)
+ .getAggregatedPartialWakelockTimer();
long actualTime = aggregTimer.getTotalTimeLocked(300_000, STATS_SINCE_CHARGED);
long bgTime = aggregTimer.getSubTimer().getTotalTimeLocked(300_000, STATS_SINCE_CHARGED);
assertEquals(220_000, actualTime);
@@ -102,7 +112,9 @@
}
- /** Test BatteryStatsImpl.noteUidProcessStateLocked. */
+ /**
+ * Test BatteryStatsImpl.noteUidProcessStateLocked.
+ */
@SmallTest
public void testNoteUidProcessStateLocked() throws Exception {
final MockClocks clocks = new MockClocks();
@@ -145,7 +157,6 @@
expectedRunTimeMs = stateRuntimeMap.get(ActivityManager.PROCESS_STATE_TOP);
assertEquals(expectedRunTimeMs * 1000, actualRunTimeUs);
-
actualRunTimeUs = uid.getProcessStateTime(BatteryStats.Uid.PROCESS_STATE_FOREGROUND_SERVICE,
elapsedTimeUs, STATS_SINCE_CHARGED);
expectedRunTimeMs = stateRuntimeMap.get(
@@ -153,19 +164,16 @@
+ stateRuntimeMap.get(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
assertEquals(expectedRunTimeMs * 1000, actualRunTimeUs);
-
actualRunTimeUs = uid.getProcessStateTime(BatteryStats.Uid.PROCESS_STATE_TOP_SLEEPING,
elapsedTimeUs, STATS_SINCE_CHARGED);
expectedRunTimeMs = stateRuntimeMap.get(ActivityManager.PROCESS_STATE_TOP_SLEEPING);
assertEquals(expectedRunTimeMs * 1000, actualRunTimeUs);
-
actualRunTimeUs = uid.getProcessStateTime(BatteryStats.Uid.PROCESS_STATE_FOREGROUND,
elapsedTimeUs, STATS_SINCE_CHARGED);
expectedRunTimeMs = stateRuntimeMap.get(ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND);
assertEquals(expectedRunTimeMs * 1000, actualRunTimeUs);
-
actualRunTimeUs = uid.getProcessStateTime(BatteryStats.Uid.PROCESS_STATE_BACKGROUND,
elapsedTimeUs, STATS_SINCE_CHARGED);
expectedRunTimeMs = stateRuntimeMap.get(ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND)
@@ -174,7 +182,6 @@
+ stateRuntimeMap.get(ActivityManager.PROCESS_STATE_RECEIVER);
assertEquals(expectedRunTimeMs * 1000, actualRunTimeUs);
-
actualRunTimeUs = uid.getProcessStateTime(BatteryStats.Uid.PROCESS_STATE_CACHED,
elapsedTimeUs, STATS_SINCE_CHARGED);
expectedRunTimeMs = stateRuntimeMap.get(ActivityManager.PROCESS_STATE_HOME)
@@ -191,7 +198,9 @@
assertEquals(expectedRunTimeMs * 1000, actualRunTimeUs);
}
- /** Test BatteryStatsImpl.updateTimeBasesLocked. */
+ /**
+ * Test BatteryStatsImpl.updateTimeBasesLocked.
+ */
@SmallTest
public void testUpdateTimeBasesLocked() throws Exception {
final MockClocks clocks = new MockClocks(); // holds realtime and uptime in ms
@@ -213,7 +222,9 @@
assertTrue(bi.getOnBatteryScreenOffTimeBase().isRunning());
}
- /** Test BatteryStatsImpl.noteScreenStateLocked sets timebases and screen states correctly. */
+ /**
+ * Test BatteryStatsImpl.noteScreenStateLocked sets timebases and screen states correctly.
+ */
@SmallTest
public void testNoteScreenStateLocked() throws Exception {
final MockClocks clocks = new MockClocks(); // holds realtime and uptime in ms
@@ -232,15 +243,13 @@
assertEquals(bi.getScreenState(), Display.STATE_OFF);
}
- /** Test BatteryStatsImpl.noteScreenStateLocked updates timers correctly.
+ /**
+ * Test BatteryStatsImpl.noteScreenStateLocked updates timers correctly.
*
- * Unknown and doze should both be subset of off state
+ * Unknown and doze should both be subset of off state
*
- * Timeline 0----100----200----310----400------------1000
- * Unknown -------
- * On -------
- * Off ------- ----------------------
- * Doze ----------------
+ * Timeline 0----100----200----310----400------------1000 Unknown ------- On ------- Off
+ * ------- ---------------------- Doze ----------------
*/
@SmallTest
public void testNoteScreenStateTimersLocked() throws Exception {
@@ -280,4 +289,161 @@
assertEquals(600_000, bi.getScreenDozeTime(1500_000, STATS_SINCE_CHARGED));
}
+ @SmallTest
+ public void testAlarmStartAndFinishLocked() throws Exception {
+ final MockClocks clocks = new MockClocks(); // holds realtime and uptime in ms
+ MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
+ bi.setRecordAllHistoryLocked(true);
+ bi.forceRecordAllHistory();
+
+ bi.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0);
+ bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_TOP);
+
+ clocks.realtime = clocks.uptime = 100;
+ bi.noteAlarmStartLocked("foo", null, UID);
+ clocks.realtime = clocks.uptime = 5000;
+ bi.noteAlarmFinishLocked("foo", null, UID);
+
+ HistoryItem item = new HistoryItem();
+ assertTrue(bi.startIteratingHistoryLocked());
+
+ assertTrue(bi.getNextHistoryLocked(item));
+ assertEquals(HistoryItem.EVENT_ALARM_START, item.eventCode);
+ assertEquals("foo", item.eventTag.string);
+ assertEquals(UID, item.eventTag.uid);
+
+ // TODO(narayan): Figure out why this event is written to the history buffer. See
+ // test below where it is being interspersed between multiple START events too.
+ assertTrue(bi.getNextHistoryLocked(item));
+ assertEquals(HistoryItem.EVENT_NONE, item.eventCode);
+
+ assertTrue(bi.getNextHistoryLocked(item));
+ assertEquals(HistoryItem.EVENT_ALARM_FINISH, item.eventCode);
+ assertTrue(item.isDeltaData());
+ assertEquals("foo", item.eventTag.string);
+ assertEquals(UID, item.eventTag.uid);
+
+ assertFalse(bi.getNextHistoryLocked(item));
+ }
+
+ @SmallTest
+ public void testAlarmStartAndFinishLocked_workSource() throws Exception {
+ final MockClocks clocks = new MockClocks(); // holds realtime and uptime in ms
+ MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
+ bi.setRecordAllHistoryLocked(true);
+ bi.forceRecordAllHistory();
+
+ bi.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0);
+ bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_TOP);
+
+ WorkSource ws = new WorkSource();
+ ws.add(100);
+ ws.createWorkChain().addNode(500, "tag");
+ bi.noteAlarmStartLocked("foo", ws, UID);
+ clocks.realtime = clocks.uptime = 5000;
+ bi.noteAlarmFinishLocked("foo", ws, UID);
+
+ HistoryItem item = new HistoryItem();
+ assertTrue(bi.startIteratingHistoryLocked());
+
+ assertTrue(bi.getNextHistoryLocked(item));
+ assertEquals(HistoryItem.EVENT_ALARM_START, item.eventCode);
+ assertEquals("foo", item.eventTag.string);
+ assertEquals(100, item.eventTag.uid);
+
+ assertTrue(bi.getNextHistoryLocked(item));
+ assertEquals(HistoryItem.EVENT_NONE, item.eventCode);
+
+ assertTrue(bi.getNextHistoryLocked(item));
+ assertEquals(HistoryItem.EVENT_ALARM_START, item.eventCode);
+ assertEquals("foo", item.eventTag.string);
+ assertEquals(500, item.eventTag.uid);
+
+ assertTrue(bi.getNextHistoryLocked(item));
+ assertEquals(HistoryItem.EVENT_ALARM_FINISH, item.eventCode);
+ assertEquals("foo", item.eventTag.string);
+ assertEquals(100, item.eventTag.uid);
+
+ assertTrue(bi.getNextHistoryLocked(item));
+ assertEquals(HistoryItem.EVENT_ALARM_FINISH, item.eventCode);
+ assertEquals("foo", item.eventTag.string);
+ assertEquals(500, item.eventTag.uid);
+ }
+
+ @SmallTest
+ public void testNoteWakupAlarmLocked() {
+ final MockClocks clocks = new MockClocks(); // holds realtime and uptime in ms
+ MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
+ bi.setRecordAllHistoryLocked(true);
+ bi.forceRecordAllHistory();
+ bi.mForceOnBattery = true;
+
+ bi.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0);
+ bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_TOP);
+
+ bi.noteWakupAlarmLocked("com.foo.bar", UID, null, "tag");
+
+ Uid.Pkg pkg = bi.getPackageStatsLocked(UID, "com.foo.bar");
+ assertEquals(1, pkg.getWakeupAlarmStats().get("tag").getCountLocked(STATS_CURRENT));
+ assertEquals(1, pkg.getWakeupAlarmStats().size());
+ }
+
+ @SmallTest
+ public void testNoteWakupAlarmLocked_workSource_uid() {
+ final MockClocks clocks = new MockClocks(); // holds realtime and uptime in ms
+ MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
+ bi.setRecordAllHistoryLocked(true);
+ bi.forceRecordAllHistory();
+ bi.mForceOnBattery = true;
+
+ bi.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0);
+ bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_TOP);
+
+ WorkSource ws = new WorkSource();
+ ws.add(100);
+
+ // When a WorkSource is present, "UID" should not be used - only the uids present in the
+ // WorkSource should be reported.
+ bi.noteWakupAlarmLocked("com.foo.bar", UID, ws, "tag");
+ Uid.Pkg pkg = bi.getPackageStatsLocked(UID, "com.foo.bar");
+ assertEquals(0, pkg.getWakeupAlarmStats().size());
+ pkg = bi.getPackageStatsLocked(100, "com.foo.bar");
+ assertEquals(1, pkg.getWakeupAlarmStats().size());
+
+ // If the WorkSource contains a "name", it should be interpreted as a package name and
+ // the packageName supplied as an argument must be ignored.
+ ws = new WorkSource();
+ ws.add(100, "com.foo.baz_alternate");
+ bi.noteWakupAlarmLocked("com.foo.baz", UID, ws, "tag");
+ pkg = bi.getPackageStatsLocked(100, "com.foo.baz");
+ assertEquals(0, pkg.getWakeupAlarmStats().size());
+ pkg = bi.getPackageStatsLocked(100, "com.foo.baz_alternate");
+ assertEquals(1, pkg.getWakeupAlarmStats().size());
+ }
+
+ @SmallTest
+ public void testNoteWakupAlarmLocked_workSource_workChain() {
+ final MockClocks clocks = new MockClocks(); // holds realtime and uptime in ms
+ MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
+ bi.setRecordAllHistoryLocked(true);
+ bi.forceRecordAllHistory();
+ bi.mForceOnBattery = true;
+
+ bi.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0);
+ bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_TOP);
+
+ WorkSource ws = new WorkSource();
+ ws.createWorkChain().addNode(100, "com.foo.baz_alternate");
+ bi.noteWakupAlarmLocked("com.foo.bar", UID, ws, "tag");
+
+ // For WorkChains, again we must only attribute to the uids present in the WorkSource
+ // (and not to "UID"). However, unlike the older "tags" we do not change the packagename
+ // supplied as an argument, given that we're logging the entire attribution chain.
+ Uid.Pkg pkg = bi.getPackageStatsLocked(UID, "com.foo.bar");
+ assertEquals(0, pkg.getWakeupAlarmStats().size());
+ pkg = bi.getPackageStatsLocked(100, "com.foo.bar");
+ assertEquals(1, pkg.getWakeupAlarmStats().size());
+ pkg = bi.getPackageStatsLocked(100, "com.foo.baz_alternate");
+ assertEquals(0, pkg.getWakeupAlarmStats().size());
+ }
}
diff --git a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
index de2fd12..f19ff67 100644
--- a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
+++ b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
@@ -16,6 +16,8 @@
package com.android.internal.os;
+import android.os.Handler;
+import android.os.Looper;
import android.util.SparseIntArray;
import java.util.ArrayList;
@@ -37,6 +39,9 @@
mOnBatteryTimeBase);
mBluetoothScanTimer = new StopwatchTimer(mClocks, null, -14, null, mOnBatteryTimeBase);
setExternalStatsSyncLocked(new DummyExternalStatsSync());
+
+ // A no-op handler.
+ mHandler = new Handler(Looper.getMainLooper()) {};
}
MockBatteryStatsImpl() {
@@ -59,6 +64,12 @@
return mForceOnBattery ? true : super.isOnBattery();
}
+ public void forceRecordAllHistory() {
+ mHaveBatteryLevel = true;
+ mRecordingHistory = true;
+ mRecordAllHistory = true;
+ }
+
public TimeBase getOnBatteryBackgroundTimeBase(int uid) {
return getUidStatsLocked(uid).mOnBatteryBackgroundTimeBase;
}
diff --git a/data/etc/platform.xml b/data/etc/platform.xml
index 2333fec..1affba0 100644
--- a/data/etc/platform.xml
+++ b/data/etc/platform.xml
@@ -150,6 +150,7 @@
<assign-permission name="android.permission.WAKE_LOCK" uid="audioserver" />
<assign-permission name="android.permission.UPDATE_DEVICE_STATS" uid="audioserver" />
<assign-permission name="android.permission.UPDATE_APP_OPS_STATS" uid="audioserver" />
+ <assign-permission name="android.permission.PACKAGE_USAGE_STATS" uid="audioserver" />
<assign-permission name="android.permission.MODIFY_AUDIO_SETTINGS" uid="cameraserver" />
<assign-permission name="android.permission.ACCESS_SURFACE_FLINGER" uid="cameraserver" />
diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java
index 3d65bd2..68b7ac2 100644
--- a/graphics/java/android/graphics/Typeface.java
+++ b/graphics/java/android/graphics/Typeface.java
@@ -429,7 +429,7 @@
}
/**
- * Sets an index of the font collection.
+ * Sets an index of the font collection. See {@link android.R.attr#ttcIndex}.
*
* Can not be used for Typeface source. build() method will return null for invalid index.
* @param ttcIndex An index of the font collection. If the font source is not font
diff --git a/keystore/java/android/security/IKeyChainService.aidl b/keystore/java/android/security/IKeyChainService.aidl
index 7c7417d..5a8fa07 100644
--- a/keystore/java/android/security/IKeyChainService.aidl
+++ b/keystore/java/android/security/IKeyChainService.aidl
@@ -34,7 +34,8 @@
void setUserSelectable(String alias, boolean isUserSelectable);
boolean generateKeyPair(in String algorithm, in ParcelableKeyGenParameterSpec spec);
- boolean attestKey(in String alias, in byte[] challenge, out KeymasterCertificateChain chain);
+ boolean attestKey(in String alias, in byte[] challenge, in int[] idAttestationFlags,
+ out KeymasterCertificateChain chain);
boolean setKeyPairCertificate(String alias, in byte[] userCert, in byte[] certChain);
// APIs used by CertInstaller and DevicePolicyManager
diff --git a/keystore/java/android/security/KeyStore.java b/keystore/java/android/security/KeyStore.java
index 399dddd..fabcdf0 100644
--- a/keystore/java/android/security/KeyStore.java
+++ b/keystore/java/android/security/KeyStore.java
@@ -95,6 +95,16 @@
public static final int FLAG_ENCRYPTED = 1;
/**
+ * Select Software keymaster device, which as of this writing is the lowest security
+ * level available on an android device. If neither FLAG_STRONGBOX nor FLAG_SOFTWARE is provided
+ * A TEE based keymaster implementation is implied.
+ *
+ * Need to be in sync with KeyStoreFlag in system/security/keystore/include/keystore/keystore.h
+ * For historical reasons this corresponds to the KEYSTORE_FLAG_FALLBACK flag.
+ */
+ public static final int FLAG_SOFTWARE = 1 << 1;
+
+ /**
* A private flag that's only available to system server to indicate that this key is part of
* device encryption flow so it receives special treatment from keystore. For example this key
* will not be super encrypted, and it will be stored separately under an unique UID instead
@@ -104,6 +114,16 @@
*/
public static final int FLAG_CRITICAL_TO_DEVICE_ENCRYPTION = 1 << 3;
+ /**
+ * Select Strongbox keymaster device, which as of this writing the the highest security level
+ * available an android devices. If neither FLAG_STRONGBOX nor FLAG_SOFTWARE is provided
+ * A TEE based keymaster implementation is implied.
+ *
+ * Need to be in sync with KeyStoreFlag in system/security/keystore/include/keystore/keystore.h
+ */
+ public static final int FLAG_STRONGBOX = 1 << 4;
+
+
// States
public enum State { UNLOCKED, LOCKED, UNINITIALIZED };
@@ -440,9 +460,9 @@
return mError;
}
- public boolean addRngEntropy(byte[] data) {
+ public boolean addRngEntropy(byte[] data, int flags) {
try {
- return mBinder.addRngEntropy(data) == NO_ERROR;
+ return mBinder.addRngEntropy(data, flags) == NO_ERROR;
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to keystore", e);
return false;
diff --git a/keystore/java/android/security/keystore/AttestationUtils.java b/keystore/java/android/security/keystore/AttestationUtils.java
index 0811100..efee8b4 100644
--- a/keystore/java/android/security/keystore/AttestationUtils.java
+++ b/keystore/java/android/security/keystore/AttestationUtils.java
@@ -99,48 +99,35 @@
}
}
- /**
- * Performs attestation of the device's identifiers. This method returns a certificate chain
- * whose first element contains the requested device identifiers in an extension. The device's
- * manufacturer, model, brand, device and product are always also included in the attestation.
- * If the device supports attestation in secure hardware, the chain will be rooted at a
- * trustworthy CA key. Otherwise, the chain will be rooted at an untrusted certificate. See
- * <a href="https://developer.android.com/training/articles/security-key-attestation.html">
- * Key Attestation</a> for the format of the certificate extension.
- * <p>
- * Attestation will only be successful when all of the following are true:
- * 1) The device has been set up to support device identifier attestation at the factory.
- * 2) The user has not permanently disabled device identifier attestation.
- * 3) You have permission to access the device identifiers you are requesting attestation for.
- * <p>
- * For privacy reasons, you cannot distinguish between (1) and (2). If attestation is
- * unsuccessful, the device may not support it in general or the user may have permanently
- * disabled it.
- *
- * @param context the context to use for retrieving device identifiers.
- * @param idTypes the types of device identifiers to attest.
- * @param attestationChallenge a blob to include in the certificate alongside the device
- * identifiers.
- *
- * @return a certificate chain containing the requested device identifiers in the first element
- *
- * @exception SecurityException if you are not permitted to obtain an attestation of the
- * device's identifiers.
- * @exception DeviceIdAttestationException if the attestation operation fails.
- */
- @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
- @NonNull public static X509Certificate[] attestDeviceIds(Context context,
- @NonNull int[] idTypes, @NonNull byte[] attestationChallenge) throws
+ @NonNull private static KeymasterArguments prepareAttestationArgumentsForDeviceId(
+ Context context, @NonNull int[] idTypes, @NonNull byte[] attestationChallenge) throws
DeviceIdAttestationException {
- // Check method arguments, retrieve requested device IDs and prepare attestation arguments.
+ // Verify that device ID attestation types are provided.
if (idTypes == null) {
throw new NullPointerException("Missing id types");
}
+
+ return prepareAttestationArguments(context, idTypes, attestationChallenge);
+ }
+
+ /**
+ * Prepares Keymaster Arguments with attestation data.
+ * @hide should only be used by KeyChain.
+ */
+ @NonNull public static KeymasterArguments prepareAttestationArguments(Context context,
+ @NonNull int[] idTypes, @NonNull byte[] attestationChallenge) throws
+ DeviceIdAttestationException {
+ // Check method arguments, retrieve requested device IDs and prepare attestation arguments.
if (attestationChallenge == null) {
throw new NullPointerException("Missing attestation challenge");
}
final KeymasterArguments attestArgs = new KeymasterArguments();
attestArgs.addBytes(KeymasterDefs.KM_TAG_ATTESTATION_CHALLENGE, attestationChallenge);
+ // Return early if the caller did not request any device identifiers to be included in the
+ // attestation record.
+ if (idTypes == null) {
+ return attestArgs;
+ }
final Set<Integer> idTypesSet = new ArraySet<>(idTypes.length);
for (int idType : idTypes) {
idTypesSet.add(idType);
@@ -191,6 +178,44 @@
Build.MANUFACTURER.getBytes(StandardCharsets.UTF_8));
attestArgs.addBytes(KeymasterDefs.KM_TAG_ATTESTATION_ID_MODEL,
Build.MODEL.getBytes(StandardCharsets.UTF_8));
+ return attestArgs;
+ }
+
+ /**
+ * Performs attestation of the device's identifiers. This method returns a certificate chain
+ * whose first element contains the requested device identifiers in an extension. The device's
+ * manufacturer, model, brand, device and product are always also included in the attestation.
+ * If the device supports attestation in secure hardware, the chain will be rooted at a
+ * trustworthy CA key. Otherwise, the chain will be rooted at an untrusted certificate. See
+ * <a href="https://developer.android.com/training/articles/security-key-attestation.html">
+ * Key Attestation</a> for the format of the certificate extension.
+ * <p>
+ * Attestation will only be successful when all of the following are true:
+ * 1) The device has been set up to support device identifier attestation at the factory.
+ * 2) The user has not permanently disabled device identifier attestation.
+ * 3) You have permission to access the device identifiers you are requesting attestation for.
+ * <p>
+ * For privacy reasons, you cannot distinguish between (1) and (2). If attestation is
+ * unsuccessful, the device may not support it in general or the user may have permanently
+ * disabled it.
+ *
+ * @param context the context to use for retrieving device identifiers.
+ * @param idTypes the types of device identifiers to attest.
+ * @param attestationChallenge a blob to include in the certificate alongside the device
+ * identifiers.
+ *
+ * @return a certificate chain containing the requested device identifiers in the first element
+ *
+ * @exception SecurityException if you are not permitted to obtain an attestation of the
+ * device's identifiers.
+ * @exception DeviceIdAttestationException if the attestation operation fails.
+ */
+ @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+ @NonNull public static X509Certificate[] attestDeviceIds(Context context,
+ @NonNull int[] idTypes, @NonNull byte[] attestationChallenge) throws
+ DeviceIdAttestationException {
+ final KeymasterArguments attestArgs = prepareAttestationArgumentsForDeviceId(
+ context, idTypes, attestationChallenge);
// Perform attestation.
final KeymasterCertificateChain outChain = new KeymasterCertificateChain();
diff --git a/libs/hwui/tests/common/TestUtils.cpp b/libs/hwui/tests/common/TestUtils.cpp
index 1e42425..4a0d6ee 100644
--- a/libs/hwui/tests/common/TestUtils.cpp
+++ b/libs/hwui/tests/common/TestUtils.cpp
@@ -122,15 +122,19 @@
void TestUtils::drawUtf8ToCanvas(Canvas* canvas, const char* text, const SkPaint& paint, float x,
float y) {
auto utf16 = asciiToUtf16(text);
- canvas->drawText(utf16.get(), 0, strlen(text), strlen(text), x, y, minikin::Bidi::LTR, paint,
- nullptr);
+ SkPaint glyphPaint(paint);
+ glyphPaint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
+ canvas->drawText(utf16.get(), 0, strlen(text), strlen(text), x, y, minikin::Bidi::LTR,
+ glyphPaint, nullptr);
}
void TestUtils::drawUtf8ToCanvas(Canvas* canvas, const char* text, const SkPaint& paint,
const SkPath& path) {
auto utf16 = asciiToUtf16(text);
- canvas->drawTextOnPath(utf16.get(), strlen(text), minikin::Bidi::LTR, path, 0, 0, paint,
- nullptr);
+ SkPaint glyphPaint(paint);
+ glyphPaint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
+ canvas->drawTextOnPath(utf16.get(), strlen(text), minikin::Bidi::LTR, path, 0, 0, glyphPaint,
+ nullptr);
}
void TestUtils::TestTask::run() {
diff --git a/libs/hwui/tests/common/scenes/GlyphStressAnimation.cpp b/libs/hwui/tests/common/scenes/GlyphStressAnimation.cpp
index bf0aed98..38999cb 100644
--- a/libs/hwui/tests/common/scenes/GlyphStressAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/GlyphStressAnimation.cpp
@@ -40,22 +40,18 @@
}
void doFrame(int frameNr) override {
- std::unique_ptr<uint16_t[]> text =
- TestUtils::asciiToUtf16("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ");
- ssize_t textLength = 26 * 2;
+ const char* text = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
std::unique_ptr<Canvas> canvas(
Canvas::create_recording_canvas(container->stagingProperties().getWidth(),
container->stagingProperties().getHeight()));
Paint paint;
- paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
paint.setAntiAlias(true);
paint.setColor(Color::Black);
for (int i = 0; i < 5; i++) {
paint.setTextSize(10 + (frameNr % 20) + i * 20);
- canvas->drawText(text.get(), 0, textLength, textLength, 0, 100 * (i + 2),
- minikin::Bidi::FORCE_LTR, paint, nullptr);
+ TestUtils::drawUtf8ToCanvas(canvas.get(), text, paint, 0, 100 * (i + 2));
}
container->setStagingDisplayList(canvas->finishRecording());
diff --git a/libs/hwui/tests/common/scenes/ListOfFadedTextAnimation.cpp b/libs/hwui/tests/common/scenes/ListOfFadedTextAnimation.cpp
index 6bae80c..58c9980 100644
--- a/libs/hwui/tests/common/scenes/ListOfFadedTextAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/ListOfFadedTextAnimation.cpp
@@ -36,7 +36,6 @@
SkPaint textPaint;
textPaint.setTextSize(dp(20));
textPaint.setAntiAlias(true);
- textPaint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
TestUtils::drawUtf8ToCanvas(&canvas, "not that long long text", textPaint, dp(10), dp(30));
SkPoint pts[2];
diff --git a/libs/hwui/tests/common/scenes/ListViewAnimation.cpp b/libs/hwui/tests/common/scenes/ListViewAnimation.cpp
index d7ec288..fd8c252 100644
--- a/libs/hwui/tests/common/scenes/ListViewAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/ListViewAnimation.cpp
@@ -83,7 +83,6 @@
canvas.drawRoundRect(0, 0, itemWidth, itemHeight, dp(6), dp(6), roundRectPaint);
SkPaint textPaint;
- textPaint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
textPaint.setColor(rand() % 2 ? Color::Black : Color::Grey_500);
textPaint.setTextSize(dp(20));
textPaint.setAntiAlias(true);
diff --git a/libs/hwui/tests/common/scenes/MagnifierAnimation.cpp b/libs/hwui/tests/common/scenes/MagnifierAnimation.cpp
index 9eddc20..aa537b4 100644
--- a/libs/hwui/tests/common/scenes/MagnifierAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/MagnifierAnimation.cpp
@@ -38,7 +38,6 @@
card = TestUtils::createNode(
0, 0, width, height, [&](RenderProperties& props, Canvas& canvas) {
SkPaint paint;
- paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
paint.setAntiAlias(true);
paint.setTextSize(50);
diff --git a/libs/hwui/tests/common/scenes/SaveLayer2Animation.cpp b/libs/hwui/tests/common/scenes/SaveLayer2Animation.cpp
index fee0659..3befce4 100644
--- a/libs/hwui/tests/common/scenes/SaveLayer2Animation.cpp
+++ b/libs/hwui/tests/common/scenes/SaveLayer2Animation.cpp
@@ -42,10 +42,8 @@
mBluePaint.setColor(SkColorSetARGB(255, 0, 0, 255));
mBluePaint.setTextSize(padding);
- mBluePaint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
mGreenPaint.setColor(SkColorSetARGB(255, 0, 255, 0));
mGreenPaint.setTextSize(padding);
- mGreenPaint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
// interleave drawText and drawRect with saveLayer ops
for (int i = 0; i < regions; i++, top += smallRectHeight) {
@@ -54,18 +52,15 @@
canvas.drawColor(SkColorSetARGB(255, 255, 255, 0), SkBlendMode::kSrcOver);
std::string stri = std::to_string(i);
std::string offscreen = "offscreen line " + stri;
- std::unique_ptr<uint16_t[]> offtext = TestUtils::asciiToUtf16(offscreen.c_str());
- canvas.drawText(offtext.get(), 0, offscreen.length(), offscreen.length(), bounds.fLeft,
- top + padding, minikin::Bidi::FORCE_LTR, mBluePaint, nullptr);
+ TestUtils::drawUtf8ToCanvas(&canvas, offscreen.c_str(), mBluePaint, bounds.fLeft,
+ top + padding);
canvas.restore();
canvas.drawRect(bounds.fLeft, top + padding, bounds.fRight,
top + smallRectHeight - padding, mBluePaint);
std::string onscreen = "onscreen line " + stri;
- std::unique_ptr<uint16_t[]> ontext = TestUtils::asciiToUtf16(onscreen.c_str());
- canvas.drawText(ontext.get(), 0, onscreen.length(), onscreen.length(), bounds.fLeft,
- top + smallRectHeight - padding, minikin::Bidi::FORCE_LTR, mGreenPaint,
- nullptr);
+ TestUtils::drawUtf8ToCanvas(&canvas, onscreen.c_str(), mGreenPaint, bounds.fLeft,
+ top + smallRectHeight - padding);
}
}
void doFrame(int frameNr) override {}
diff --git a/libs/hwui/tests/common/scenes/TextAnimation.cpp b/libs/hwui/tests/common/scenes/TextAnimation.cpp
index a502116..a16b1784 100644
--- a/libs/hwui/tests/common/scenes/TextAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/TextAnimation.cpp
@@ -29,7 +29,6 @@
card = TestUtils::createNode(0, 0, width, height, [](RenderProperties& props,
Canvas& canvas) {
SkPaint paint;
- paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
paint.setAntiAlias(true);
paint.setTextSize(50);
diff --git a/libs/hwui/tests/common/scenes/TvApp.cpp b/libs/hwui/tests/common/scenes/TvApp.cpp
index c845e6c..003d8e9 100644
--- a/libs/hwui/tests/common/scenes/TvApp.cpp
+++ b/libs/hwui/tests/common/scenes/TvApp.cpp
@@ -117,7 +117,6 @@
canvas.drawColor(0xFFFFEEEE, SkBlendMode::kSrcOver);
SkPaint paint;
- paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
paint.setAntiAlias(true);
paint.setTextSize(24);
diff --git a/libs/hwui/tests/unit/FrameBuilderTests.cpp b/libs/hwui/tests/unit/FrameBuilderTests.cpp
index e56d2f8..4eb7751 100644
--- a/libs/hwui/tests/unit/FrameBuilderTests.cpp
+++ b/libs/hwui/tests/unit/FrameBuilderTests.cpp
@@ -537,7 +537,6 @@
canvas.save(SaveFlags::MatrixClip);
canvas.clipPath(&path, SkClipOp::kIntersect);
SkPaint paint;
- paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
paint.setAntiAlias(true);
paint.setTextSize(50);
TestUtils::drawUtf8ToCanvas(&canvas, "Test string1", paint, 100, 100);
@@ -569,7 +568,6 @@
auto node = TestUtils::createNode<RecordingCanvas>(0, 0, 400, 400, [](RenderProperties& props,
RecordingCanvas& canvas) {
SkPaint paint;
- paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
paint.setAntiAlias(true);
paint.setTextSize(50);
TestUtils::drawUtf8ToCanvas(&canvas, "Test string1", paint, 100, 0); // will be top clipped
@@ -603,7 +601,6 @@
textPaint.setAntiAlias(true);
textPaint.setTextSize(20);
textPaint.setFlags(textPaint.getFlags() | SkPaint::kStrikeThruText_ReserveFlag);
- textPaint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
for (int i = 0; i < LOOPS; i++) {
TestUtils::drawUtf8ToCanvas(&canvas, "test text", textPaint, 10, 100 * (i + 1));
}
@@ -654,7 +651,6 @@
auto node = TestUtils::createNode<RecordingCanvas>(
0, 0, 400, 400, [](RenderProperties& props, RecordingCanvas& canvas) {
SkPaint paint;
- paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
paint.setAntiAlias(true);
paint.setTextSize(50);
paint.setStrokeWidth(10);
diff --git a/libs/hwui/tests/unit/RecordingCanvasTests.cpp b/libs/hwui/tests/unit/RecordingCanvasTests.cpp
index 5aae15f..8a9e34f 100644
--- a/libs/hwui/tests/unit/RecordingCanvasTests.cpp
+++ b/libs/hwui/tests/unit/RecordingCanvasTests.cpp
@@ -175,7 +175,6 @@
SkPaint paint;
paint.setAntiAlias(true);
paint.setTextSize(20);
- paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
TestUtils::drawUtf8ToCanvas(&canvas, "test text", paint, 25, 25);
});
@@ -196,7 +195,6 @@
SkPaint paint;
paint.setAntiAlias(true);
paint.setTextSize(20);
- paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 2; j++) {
uint32_t flags = paint.getFlags();
@@ -238,7 +236,6 @@
SkPaint paint;
paint.setAntiAlias(true);
paint.setTextSize(20);
- paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
paint.setTextAlign(SkPaint::kLeft_Align);
TestUtils::drawUtf8ToCanvas(&canvas, "test text", paint, 25, 25);
paint.setTextAlign(SkPaint::kCenter_Align);
@@ -805,9 +802,7 @@
Paint paint;
paint.setAntiAlias(true);
paint.setTextSize(20);
- paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
- std::unique_ptr<uint16_t[]> dst = TestUtils::asciiToUtf16("HELLO");
- canvas.drawText(dst.get(), 0, 5, 5, 25, 25, minikin::Bidi::FORCE_LTR, paint, NULL);
+ TestUtils::drawUtf8ToCanvas(&canvas, "HELLO", paint, 25, 25);
});
int count = 0;
@@ -829,9 +824,7 @@
paint.setColor(SK_ColorWHITE);
paint.setAntiAlias(true);
paint.setTextSize(20);
- paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
- std::unique_ptr<uint16_t[]> dst = TestUtils::asciiToUtf16("HELLO");
- canvas.drawText(dst.get(), 0, 5, 5, 25, 25, minikin::Bidi::FORCE_LTR, paint, NULL);
+ TestUtils::drawUtf8ToCanvas(&canvas, "HELLO", paint, 25, 25);
});
Properties::enableHighContrastText = false;
diff --git a/libs/hwui/tests/unit/SkiaCanvasTests.cpp b/libs/hwui/tests/unit/SkiaCanvasTests.cpp
index 4138f59..1d7dc3d 100644
--- a/libs/hwui/tests/unit/SkiaCanvasTests.cpp
+++ b/libs/hwui/tests/unit/SkiaCanvasTests.cpp
@@ -36,7 +36,6 @@
SkPaint paint;
paint.setAntiAlias(true);
paint.setTextSize(20);
- paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
static const char* text = "testing text bounds";
// draw text directly into Recording canvas
diff --git a/libs/hwui/tests/unit/TextDropShadowCacheTests.cpp b/libs/hwui/tests/unit/TextDropShadowCacheTests.cpp
index 78d75d6..92d05e4 100644
--- a/libs/hwui/tests/unit/TextDropShadowCacheTests.cpp
+++ b/libs/hwui/tests/unit/TextDropShadowCacheTests.cpp
@@ -29,6 +29,7 @@
RENDERTHREAD_OPENGL_PIPELINE_TEST(TextDropShadowCache, addRemove) {
SkPaint paint;
paint.setTextSize(20);
+ paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
GammaFontRenderer gammaFontRenderer;
FontRenderer& fontRenderer = gammaFontRenderer.getFontRenderer();
diff --git a/media/java/android/media/AudioFocusInfo.java b/media/java/android/media/AudioFocusInfo.java
index 6d9c5e2..5d0c8e2 100644
--- a/media/java/android/media/AudioFocusInfo.java
+++ b/media/java/android/media/AudioFocusInfo.java
@@ -130,13 +130,11 @@
dest.writeInt(mSdkTarget);
}
- @SystemApi
@Override
public int hashCode() {
return Objects.hash(mAttributes, mClientUid, mClientId, mPackageName, mGainRequest, mFlags);
}
- @SystemApi
@Override
public boolean equals(Object obj) {
if (this == obj)
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index f87c846..913b5e8 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -1635,6 +1635,21 @@
}
/**
+ * Broadcast Action: microphone muting state changed.
+ *
+ * You <em>cannot</em> receive this through components declared
+ * in manifests, only by explicitly registering for it with
+ * {@link Context#registerReceiver(BroadcastReceiver, IntentFilter)
+ * Context.registerReceiver()}.
+ *
+ * <p>The intent has no extra values, use {@link #isMicrophoneMute} to check whether the
+ * microphone is muted.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_MICROPHONE_MUTE_CHANGED =
+ "android.media.action.MICROPHONE_MUTE_CHANGED";
+
+ /**
* Sets the audio mode.
* <p>
* The audio mode encompasses audio routing AND the behavior of
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothManager.java
index 1993b45..3732471 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothManager.java
@@ -73,6 +73,7 @@
mCachedDeviceManager, context);
mProfileManager = new LocalBluetoothProfileManager(context,
mLocalAdapter, mCachedDeviceManager, mEventManager);
+ mEventManager.readPairedDevices();
}
public LocalBluetoothAdapter getBluetoothAdapter() {
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
index 58f1226..754b881 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
@@ -622,6 +622,14 @@
return mRssi;
}
+ public ConcurrentHashMap<String, ScanResult> getScanResults() {
+ return mScanResultCache;
+ }
+
+ public Map<String, TimestampedScoredNetwork> getScoredNetworkCache() {
+ return mScoredNetworkCache;
+ }
+
/**
* Updates {@link #mRssi}.
*
@@ -845,41 +853,8 @@
}
if (WifiTracker.sVerboseLogging) {
- // Add RSSI/band information for this config, what was seen up to 6 seconds ago
- // verbose WiFi Logging is only turned on thru developers settings
- if (isActive() && mInfo != null) {
- summary.append(" f=" + Integer.toString(mInfo.getFrequency()));
- }
- summary.append(" " + getVisibilityStatus());
- if (config != null && !config.getNetworkSelectionStatus().isNetworkEnabled()) {
- summary.append(" (" + config.getNetworkSelectionStatus().getNetworkStatusString());
- if (config.getNetworkSelectionStatus().getDisableTime() > 0) {
- long now = System.currentTimeMillis();
- long diff = (now - config.getNetworkSelectionStatus().getDisableTime()) / 1000;
- long sec = diff%60; //seconds
- long min = (diff/60)%60; //minutes
- long hour = (min/60)%60; //hours
- summary.append(", ");
- if (hour > 0) summary.append(Long.toString(hour) + "h ");
- summary.append( Long.toString(min) + "m ");
- summary.append( Long.toString(sec) + "s ");
- }
- summary.append(")");
- }
-
- if (config != null) {
- WifiConfiguration.NetworkSelectionStatus networkStatus =
- config.getNetworkSelectionStatus();
- for (int index = WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_ENABLE;
- index < WifiConfiguration.NetworkSelectionStatus
- .NETWORK_SELECTION_DISABLED_MAX; index++) {
- if (networkStatus.getDisableReasonCounter(index) != 0) {
- summary.append(" " + WifiConfiguration.NetworkSelectionStatus
- .getNetworkDisableReasonString(index) + "="
- + networkStatus.getDisableReasonCounter(index));
- }
- }
- }
+ evictOldScanResults();
+ summary.append(WifiUtils.buildLoggingSummary(this, config));
}
// If Speed label and summary are both present, use the preference combination to combine
@@ -897,127 +872,6 @@
}
/**
- * Returns the visibility status of the WifiConfiguration.
- *
- * @return autojoin debugging information
- * TODO: use a string formatter
- * ["rssi 5Ghz", "num results on 5GHz" / "rssi 5Ghz", "num results on 5GHz"]
- * For instance [-40,5/-30,2]
- */
- private String getVisibilityStatus() {
- StringBuilder visibility = new StringBuilder();
- StringBuilder scans24GHz = new StringBuilder();
- StringBuilder scans5GHz = new StringBuilder();
- String bssid = null;
-
- long now = System.currentTimeMillis();
-
- if (isActive() && mInfo != null) {
- bssid = mInfo.getBSSID();
- if (bssid != null) {
- visibility.append(" ").append(bssid);
- }
- visibility.append(" rssi=").append(mInfo.getRssi());
- visibility.append(" ");
- visibility.append(" score=").append(mInfo.score);
- if (mSpeed != Speed.NONE) {
- visibility.append(" speed=").append(getSpeedLabel());
- }
- visibility.append(String.format(" tx=%.1f,", mInfo.txSuccessRate));
- visibility.append(String.format("%.1f,", mInfo.txRetriesRate));
- visibility.append(String.format("%.1f ", mInfo.txBadRate));
- visibility.append(String.format("rx=%.1f", mInfo.rxSuccessRate));
- }
-
- int maxRssi5 = WifiConfiguration.INVALID_RSSI;
- int maxRssi24 = WifiConfiguration.INVALID_RSSI;
- final int maxDisplayedScans = 4;
- int num5 = 0; // number of scanned BSSID on 5GHz band
- int num24 = 0; // number of scanned BSSID on 2.4Ghz band
- int numBlackListed = 0;
- evictOldScanResults();
-
- // TODO: sort list by RSSI or age
- long nowMs = SystemClock.elapsedRealtime();
- for (ScanResult result : mScanResultCache.values()) {
- if (result.frequency >= LOWER_FREQ_5GHZ
- && result.frequency <= HIGHER_FREQ_5GHZ) {
- // Strictly speaking: [4915, 5825]
- num5++;
-
- if (result.level > maxRssi5) {
- maxRssi5 = result.level;
- }
- if (num5 <= maxDisplayedScans) {
- scans5GHz.append(verboseScanResultSummary(result, bssid, nowMs));
- }
- } else if (result.frequency >= LOWER_FREQ_24GHZ
- && result.frequency <= HIGHER_FREQ_24GHZ) {
- // Strictly speaking: [2412, 2482]
- num24++;
-
- if (result.level > maxRssi24) {
- maxRssi24 = result.level;
- }
- if (num24 <= maxDisplayedScans) {
- scans24GHz.append(verboseScanResultSummary(result, bssid, nowMs));
- }
- }
- }
- visibility.append(" [");
- if (num24 > 0) {
- visibility.append("(").append(num24).append(")");
- if (num24 > maxDisplayedScans) {
- visibility.append("max=").append(maxRssi24).append(",");
- }
- visibility.append(scans24GHz.toString());
- }
- visibility.append(";");
- if (num5 > 0) {
- visibility.append("(").append(num5).append(")");
- if (num5 > maxDisplayedScans) {
- visibility.append("max=").append(maxRssi5).append(",");
- }
- visibility.append(scans5GHz.toString());
- }
- if (numBlackListed > 0)
- visibility.append("!").append(numBlackListed);
- visibility.append("]");
-
- return visibility.toString();
- }
-
- @VisibleForTesting
- /* package */ String verboseScanResultSummary(ScanResult result, String bssid, long nowMs) {
- StringBuilder stringBuilder = new StringBuilder();
- stringBuilder.append(" \n{").append(result.BSSID);
- if (result.BSSID.equals(bssid)) {
- stringBuilder.append("*");
- }
- stringBuilder.append("=").append(result.frequency);
- stringBuilder.append(",").append(result.level);
- int speed = getSpecificApSpeed(result);
- if (speed != Speed.NONE) {
- stringBuilder.append(",")
- .append(getSpeedLabel(speed));
- }
- int ageSeconds = (int) (nowMs - result.timestamp / 1000) / 1000;
- stringBuilder.append(",").append(ageSeconds).append("s");
- stringBuilder.append("}");
- return stringBuilder.toString();
- }
-
- @Speed private int getSpecificApSpeed(ScanResult result) {
- TimestampedScoredNetwork timedScore = mScoredNetworkCache.get(result.BSSID);
- if (timedScore == null) {
- return Speed.NONE;
- }
- // For debugging purposes we may want to use mRssi rather than result.level as the average
- // speed wil be determined by mRssi
- return timedScore.getScore().calculateBadge(result.level);
- }
-
- /**
* Return whether this is the active connection.
* For ephemeral connections (networkId is invalid), this returns false if the network is
* disconnected.
@@ -1275,7 +1129,7 @@
}
@Nullable
- private String getSpeedLabel(@Speed int speed) {
+ String getSpeedLabel(@Speed int speed) {
switch (speed) {
case Speed.VERY_FAST:
return mContext.getString(R.string.speed_label_very_fast);
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java
new file mode 100644
index 0000000..932c6fd
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.wifi;
+
+import android.net.wifi.ScanResult;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiInfo;
+import android.os.SystemClock;
+import android.support.annotation.VisibleForTesting;
+
+import java.util.Map;
+
+public class WifiUtils {
+
+ public static String buildLoggingSummary(AccessPoint accessPoint, WifiConfiguration config) {
+ final StringBuilder summary = new StringBuilder();
+ final WifiInfo info = accessPoint.getInfo();
+ // Add RSSI/band information for this config, what was seen up to 6 seconds ago
+ // verbose WiFi Logging is only turned on thru developers settings
+ if (accessPoint.isActive() && info != null) {
+ summary.append(" f=" + Integer.toString(info.getFrequency()));
+ }
+ summary.append(" " + getVisibilityStatus(accessPoint));
+ if (config != null && !config.getNetworkSelectionStatus().isNetworkEnabled()) {
+ summary.append(" (" + config.getNetworkSelectionStatus().getNetworkStatusString());
+ if (config.getNetworkSelectionStatus().getDisableTime() > 0) {
+ long now = System.currentTimeMillis();
+ long diff = (now - config.getNetworkSelectionStatus().getDisableTime()) / 1000;
+ long sec = diff % 60; //seconds
+ long min = (diff / 60) % 60; //minutes
+ long hour = (min / 60) % 60; //hours
+ summary.append(", ");
+ if (hour > 0) summary.append(Long.toString(hour) + "h ");
+ summary.append(Long.toString(min) + "m ");
+ summary.append(Long.toString(sec) + "s ");
+ }
+ summary.append(")");
+ }
+
+ if (config != null) {
+ WifiConfiguration.NetworkSelectionStatus networkStatus =
+ config.getNetworkSelectionStatus();
+ for (int index = WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_ENABLE;
+ index < WifiConfiguration.NetworkSelectionStatus
+ .NETWORK_SELECTION_DISABLED_MAX; index++) {
+ if (networkStatus.getDisableReasonCounter(index) != 0) {
+ summary.append(" " + WifiConfiguration.NetworkSelectionStatus
+ .getNetworkDisableReasonString(index) + "="
+ + networkStatus.getDisableReasonCounter(index));
+ }
+ }
+ }
+
+ return summary.toString();
+ }
+
+ /**
+ * Returns the visibility status of the WifiConfiguration.
+ *
+ * @return autojoin debugging information
+ * TODO: use a string formatter
+ * ["rssi 5Ghz", "num results on 5GHz" / "rssi 5Ghz", "num results on 5GHz"]
+ * For instance [-40,5/-30,2]
+ */
+ private static String getVisibilityStatus(AccessPoint accessPoint) {
+ final WifiInfo info = accessPoint.getInfo();
+ StringBuilder visibility = new StringBuilder();
+ StringBuilder scans24GHz = new StringBuilder();
+ StringBuilder scans5GHz = new StringBuilder();
+ String bssid = null;
+
+ if (accessPoint.isActive() && info != null) {
+ bssid = info.getBSSID();
+ if (bssid != null) {
+ visibility.append(" ").append(bssid);
+ }
+ visibility.append(" rssi=").append(info.getRssi());
+ visibility.append(" ");
+ visibility.append(" score=").append(info.score);
+ if (accessPoint.getSpeed() != AccessPoint.Speed.NONE) {
+ visibility.append(" speed=").append(accessPoint.getSpeedLabel());
+ }
+ visibility.append(String.format(" tx=%.1f,", info.txSuccessRate));
+ visibility.append(String.format("%.1f,", info.txRetriesRate));
+ visibility.append(String.format("%.1f ", info.txBadRate));
+ visibility.append(String.format("rx=%.1f", info.rxSuccessRate));
+ }
+
+ int maxRssi5 = WifiConfiguration.INVALID_RSSI;
+ int maxRssi24 = WifiConfiguration.INVALID_RSSI;
+ final int maxDisplayedScans = 4;
+ int num5 = 0; // number of scanned BSSID on 5GHz band
+ int num24 = 0; // number of scanned BSSID on 2.4Ghz band
+ int numBlackListed = 0;
+
+ // TODO: sort list by RSSI or age
+ long nowMs = SystemClock.elapsedRealtime();
+ for (ScanResult result : accessPoint.getScanResults().values()) {
+ if (result.frequency >= AccessPoint.LOWER_FREQ_5GHZ
+ && result.frequency <= AccessPoint.HIGHER_FREQ_5GHZ) {
+ // Strictly speaking: [4915, 5825]
+ num5++;
+
+ if (result.level > maxRssi5) {
+ maxRssi5 = result.level;
+ }
+ if (num5 <= maxDisplayedScans) {
+ scans5GHz.append(
+ verboseScanResultSummary(accessPoint, result, bssid,
+ nowMs));
+ }
+ } else if (result.frequency >= AccessPoint.LOWER_FREQ_24GHZ
+ && result.frequency <= AccessPoint.HIGHER_FREQ_24GHZ) {
+ // Strictly speaking: [2412, 2482]
+ num24++;
+
+ if (result.level > maxRssi24) {
+ maxRssi24 = result.level;
+ }
+ if (num24 <= maxDisplayedScans) {
+ scans24GHz.append(
+ verboseScanResultSummary(accessPoint, result, bssid,
+ nowMs));
+ }
+ }
+ }
+ visibility.append(" [");
+ if (num24 > 0) {
+ visibility.append("(").append(num24).append(")");
+ if (num24 > maxDisplayedScans) {
+ visibility.append("max=").append(maxRssi24).append(",");
+ }
+ visibility.append(scans24GHz.toString());
+ }
+ visibility.append(";");
+ if (num5 > 0) {
+ visibility.append("(").append(num5).append(")");
+ if (num5 > maxDisplayedScans) {
+ visibility.append("max=").append(maxRssi5).append(",");
+ }
+ visibility.append(scans5GHz.toString());
+ }
+ if (numBlackListed > 0) {
+ visibility.append("!").append(numBlackListed);
+ }
+ visibility.append("]");
+
+ return visibility.toString();
+ }
+
+ @VisibleForTesting
+ /* package */ static String verboseScanResultSummary(AccessPoint accessPoint, ScanResult result,
+ String bssid, long nowMs) {
+ StringBuilder stringBuilder = new StringBuilder();
+ stringBuilder.append(" \n{").append(result.BSSID);
+ if (result.BSSID.equals(bssid)) {
+ stringBuilder.append("*");
+ }
+ stringBuilder.append("=").append(result.frequency);
+ stringBuilder.append(",").append(result.level);
+ int speed = getSpecificApSpeed(result, accessPoint.getScoredNetworkCache());
+ if (speed != AccessPoint.Speed.NONE) {
+ stringBuilder.append(",")
+ .append(accessPoint.getSpeedLabel(speed));
+ }
+ int ageSeconds = (int) (nowMs - result.timestamp / 1000) / 1000;
+ stringBuilder.append(",").append(ageSeconds).append("s");
+ stringBuilder.append("}");
+ return stringBuilder.toString();
+ }
+
+ @AccessPoint.Speed
+ private static int getSpecificApSpeed(ScanResult result,
+ Map<String, TimestampedScoredNetwork> scoredNetworkCache) {
+ TimestampedScoredNetwork timedScore = scoredNetworkCache.get(result.BSSID);
+ if (timedScore == null) {
+ return AccessPoint.Speed.NONE;
+ }
+ // For debugging purposes we may want to use mRssi rather than result.level as the average
+ // speed wil be determined by mRssi
+ return timedScore.getScore().calculateBadge(result.level);
+ }
+}
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java
index 66f4a01..ec594a6 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java
@@ -66,7 +66,6 @@
public class AccessPointTest {
private static final String TEST_SSID = "\"test_ssid\"";
- private static final int NUM_SCAN_RESULTS = 5;
private static final ArrayList<ScanResult> SCAN_RESULTS = buildScanResultCache();
@@ -439,26 +438,6 @@
}
@Test
- public void testVerboseSummaryString_showsScanResultSpeedLabel() {
- WifiTracker.sVerboseLogging = true;
-
- Bundle bundle = new Bundle();
- ArrayList<ScanResult> scanResults = buildScanResultCache();
- bundle.putParcelableArrayList(AccessPoint.KEY_SCANRESULTCACHE, scanResults);
- AccessPoint ap = new AccessPoint(mContext, bundle);
-
- when(mockWifiNetworkScoreCache.getScoredNetwork(any(ScanResult.class)))
- .thenReturn(buildScoredNetworkWithMockBadgeCurve());
- when(mockBadgeCurve.lookupScore(anyInt())).thenReturn((byte) AccessPoint.Speed.VERY_FAST);
-
- ap.update(mockWifiNetworkScoreCache, true /* scoringUiEnabled */,
- MAX_SCORE_CACHE_AGE_MILLIS);
- String summary = ap.verboseScanResultSummary(scanResults.get(0), null, 0);
-
- assertThat(summary.contains(mContext.getString(R.string.speed_label_very_fast))).isTrue();
- }
-
- @Test
public void testSummaryString_concatenatesSpeedLabel() {
AccessPoint ap = createAccessPointWithScanResultCache();
ap.update(new WifiConfiguration());
@@ -559,7 +538,6 @@
private ScoredNetwork buildScoredNetworkWithMockBadgeCurve() {
return buildScoredNetworkWithGivenBadgeCurve(mockBadgeCurve);
-
}
private ScoredNetwork buildScoredNetworkWithGivenBadgeCurve(RssiCurve badgeCurve) {
@@ -570,7 +548,6 @@
badgeCurve,
false /* meteredHint */,
attr1);
-
}
private AccessPoint createAccessPointWithScanResultCache() {
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiUtilsTest.java
new file mode 100644
index 0000000..c5795d3
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiUtilsTest.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.settingslib.wifi;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.net.NetworkKey;
+import android.net.RssiCurve;
+import android.net.ScoredNetwork;
+import android.net.WifiKey;
+import android.net.wifi.ScanResult;
+import android.net.wifi.WifiNetworkScoreCache;
+import android.os.Bundle;
+import android.os.SystemClock;
+import android.text.format.DateUtils;
+
+import com.android.settingslib.R;
+import com.android.settingslib.SettingsLibRobolectricTestRunner;
+import com.android.settingslib.TestConfig;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+import java.util.ArrayList;
+
+@RunWith(SettingsLibRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class WifiUtilsTest {
+ private static final String TEST_SSID = "\"test_ssid\"";
+ private static final String TEST_BSSID = "00:00:00:00:00:00";
+ private static final long MAX_SCORE_CACHE_AGE_MILLIS =
+ 20 * DateUtils.MINUTE_IN_MILLIS;
+
+ private Context mContext;
+ @Mock
+ private RssiCurve mockBadgeCurve;
+ @Mock
+ private WifiNetworkScoreCache mockWifiNetworkScoreCache;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mContext = RuntimeEnvironment.application;
+ }
+
+ @Test
+ public void testVerboseSummaryString_showsScanResultSpeedLabel() {
+ WifiTracker.sVerboseLogging = true;
+
+ Bundle bundle = new Bundle();
+ ArrayList<ScanResult> scanResults = buildScanResultCache();
+ bundle.putParcelableArrayList(AccessPoint.KEY_SCANRESULTCACHE, scanResults);
+ AccessPoint ap = new AccessPoint(mContext, bundle);
+
+ when(mockWifiNetworkScoreCache.getScoredNetwork(any(ScanResult.class)))
+ .thenReturn(buildScoredNetworkWithGivenBadgeCurve(mockBadgeCurve));
+ when(mockBadgeCurve.lookupScore(anyInt())).thenReturn((byte) AccessPoint.Speed.VERY_FAST);
+
+ ap.update(mockWifiNetworkScoreCache, true /* scoringUiEnabled */,
+ MAX_SCORE_CACHE_AGE_MILLIS);
+ String summary = WifiUtils.verboseScanResultSummary(ap, scanResults.get(0), null, 0);
+
+ assertThat(summary.contains(mContext.getString(R.string.speed_label_very_fast))).isTrue();
+ }
+
+ private static ArrayList<ScanResult> buildScanResultCache() {
+ ArrayList<ScanResult> scanResults = new ArrayList<>();
+ for (int i = 0; i < 5; i++) {
+ ScanResult scanResult = createScanResult(TEST_SSID, "bssid-" + i, i);
+ scanResults.add(scanResult);
+ }
+ return scanResults;
+ }
+
+ private static ScanResult createScanResult(String ssid, String bssid, int rssi) {
+ ScanResult scanResult = new ScanResult();
+ scanResult.SSID = ssid;
+ scanResult.level = rssi;
+ scanResult.BSSID = bssid;
+ scanResult.timestamp = SystemClock.elapsedRealtime() * 1000;
+ scanResult.capabilities = "";
+ return scanResult;
+ }
+
+ private ScoredNetwork buildScoredNetworkWithGivenBadgeCurve(RssiCurve badgeCurve) {
+ Bundle attr1 = new Bundle();
+ attr1.putParcelable(ScoredNetwork.ATTRIBUTES_KEY_BADGING_CURVE, badgeCurve);
+ return new ScoredNetwork(
+ new NetworkKey(new WifiKey(TEST_SSID, TEST_BSSID)),
+ badgeCurve,
+ false /* meteredHint */,
+ attr1);
+ }
+}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index f4ec936..bef2bcb 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -1710,18 +1710,9 @@
}
private List<String> getSettingsNamesLocked(int settingsType, int userId) {
- boolean instantApp;
- if (UserHandle.getAppId(Binder.getCallingUid()) < Process.FIRST_APPLICATION_UID) {
- instantApp = false;
- } else {
- ApplicationInfo ai = getCallingApplicationInfoOrThrow();
- instantApp = ai.isInstantApp();
- }
- if (instantApp) {
- return new ArrayList<String>(getInstantAppAccessibleSettings(settingsType));
- } else {
- return mSettingsRegistry.getSettingsNamesLocked(settingsType, userId);
- }
+ // Don't enforce the instant app whitelist for now -- its too prone to unintended breakage
+ // in the current form.
+ return mSettingsRegistry.getSettingsNamesLocked(settingsType, userId);
}
private void enforceSettingReadable(String settingName, int settingsType, int userId) {
@@ -1734,8 +1725,10 @@
}
if (!getInstantAppAccessibleSettings(settingsType).contains(settingName)
&& !getOverlayInstantAppAccessibleSettings(settingsType).contains(settingName)) {
- throw new SecurityException("Setting " + settingName + " is not accessible from"
- + " ephemeral package " + getCallingPackage());
+ // Don't enforce the instant app whitelist for now -- its too prone to unintended
+ // breakage in the current form.
+ Slog.w(LOG_TAG, "Instant App " + ai.packageName
+ + " trying to access unexposed setting, this will be an error in the future.");
}
}
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index eab42da..d675a7a 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -132,6 +132,7 @@
<uses-permission android:name="android.permission.CHANGE_OVERLAY_PACKAGES" />
<!-- Permission needed to access privileged VR APIs -->
<uses-permission android:name="android.permission.RESTRICTED_VR_ACCESS" />
+ <uses-permission android:name="android.permission.MANAGE_BIND_INSTANT_SERVICE" />
<application android:label="@string/app_label"
android:defaultToDeviceProtectedStorage="true"
diff --git a/packages/SystemUI/Android.mk b/packages/SystemUI/Android.mk
index 2c5eb27..73fcdd7 100644
--- a/packages/SystemUI/Android.mk
+++ b/packages/SystemUI/Android.mk
@@ -39,7 +39,12 @@
android-support-v7-mediarouter \
android-support-v7-palette \
android-support-v14-preference \
- android-support-v17-leanback
+ android-support-v17-leanback \
+ android-slices-core \
+ android-slices-view \
+ android-slices-builders \
+ apptoolkit-arch-core-runtime \
+ apptoolkit-lifecycle-extensions \
LOCAL_STATIC_JAVA_LIBRARIES := \
SystemUI-tags \
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_status_area.xml b/packages/SystemUI/res-keyguard/layout/keyguard_status_area.xml
index 020cfee..b154d46 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_status_area.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_status_area.xml
@@ -24,31 +24,20 @@
android:layout_marginEnd="16dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginTop="@dimen/date_owner_info_margin"
android:layout_gravity="center_horizontal"
- android:paddingTop="4dp"
android:clipToPadding="false"
android:orientation="vertical"
android:layout_centerHorizontal="true">
<TextView android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:singleLine="true"
- android:ellipsize="end"
- android:fadingEdge="horizontal"
- android:gravity="center"
- android:textSize="22sp"
- android:textColor="?attr/wallpaperTextColor"
+ android:layout_marginBottom="@dimen/widget_vertical_padding"
+ android:theme="@style/TextAppearance.Keyguard"
/>
- <TextView android:id="@+id/text"
+ <LinearLayout android:id="@+id/row"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:singleLine="true"
+ android:orientation="horizontal"
android:gravity="center"
- android:visibility="gone"
- android:textSize="16sp"
- android:textColor="?attr/wallpaperTextColor"
- android:layout_marginTop="4dp"
- android:ellipsize="end"
/>
</com.android.keyguard.KeyguardSliceView>
\ No newline at end of file
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_status_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_status_view.xml
index 138733e..c97cfc4 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_status_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_status_view.xml
@@ -34,6 +34,7 @@
android:orientation="vertical">
<RelativeLayout
android:id="@+id/keyguard_clock_container"
+ android:animateLayoutChanges="true"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal|top">
@@ -59,14 +60,23 @@
android:layout_toEndOf="@id/clock_view"
android:visibility="invisible"
android:src="@drawable/ic_aod_charging_24dp"
- android:contentDescription="@string/accessibility_ambient_display_charging"
- />
+ android:contentDescription="@string/accessibility_ambient_display_charging" />
+ <View
+ android:id="@+id/clock_separator"
+ android:layout_width="16dp"
+ android:layout_height="1dp"
+ android:layout_marginTop="10dp"
+ android:layout_below="@id/clock_view"
+ android:background="#f00"
+ android:layout_centerHorizontal="true" />
<include layout="@layout/keyguard_status_area"
android:id="@+id/keyguard_status_area"
+ android:layout_marginTop="10dp"
+ android:layout_marginBottom="@dimen/widget_vertical_padding"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_below="@id/clock_view" />
+ android:layout_below="@id/clock_separator" />
</RelativeLayout>
<TextView
@@ -83,6 +93,5 @@
android:letterSpacing="0.05"
android:ellipsize="marquee"
android:singleLine="true" />
-
</LinearLayout>
</com.android.keyguard.KeyguardStatusView>
diff --git a/packages/SystemUI/res-keyguard/values-h560dp/dimens.xml b/packages/SystemUI/res-keyguard/values-h560dp/dimens.xml
index 1b6fa4c..3fb86d0 100644
--- a/packages/SystemUI/res-keyguard/values-h560dp/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values-h560dp/dimens.xml
@@ -16,5 +16,5 @@
-->
<resources>
- <dimen name="widget_big_font_size">72dp</dimen>
+ <dimen name="widget_big_font_size">64dp</dimen>
</resources>
\ No newline at end of file
diff --git a/packages/SystemUI/res-keyguard/values-h650dp/dimens.xml b/packages/SystemUI/res-keyguard/values-h650dp/dimens.xml
index 1b6fa4c..3fb86d0 100644
--- a/packages/SystemUI/res-keyguard/values-h650dp/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values-h650dp/dimens.xml
@@ -16,5 +16,5 @@
-->
<resources>
- <dimen name="widget_big_font_size">72dp</dimen>
+ <dimen name="widget_big_font_size">64dp</dimen>
</resources>
\ No newline at end of file
diff --git a/packages/SystemUI/res-keyguard/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml
index bcac072..463af61 100644
--- a/packages/SystemUI/res-keyguard/values/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values/dimens.xml
@@ -42,9 +42,22 @@
<dimen name="eca_overlap">-10dip</dimen>
<!-- Default clock parameters -->
- <dimen name="bottom_text_spacing_digital">-1dp</dimen>
- <dimen name="widget_label_font_size">14sp</dimen>
- <dimen name="widget_big_font_size">72dp</dimen>
+ <dimen name="bottom_text_spacing_digital">-10dp</dimen>
+ <!-- Slice header -->
+ <dimen name="widget_title_font_size">28sp</dimen>
+ <!-- Slice subtitle -->
+ <dimen name="widget_label_font_size">16sp</dimen>
+ <!-- Clock without header -->
+ <dimen name="widget_big_font_size">64dp</dimen>
+ <!-- Clock with header -->
+ <dimen name="widget_small_font_size">22dp</dimen>
+ <!-- Dash between clock and header -->
+ <dimen name="widget_vertical_padding">16dp</dimen>
+ <!-- Subtitle paddings -->
+ <dimen name="widget_separator_thickness">2dp</dimen>
+ <dimen name="widget_horizontal_padding">8dp</dimen>
+ <dimen name="widget_icon_size">16dp</dimen>
+ <dimen name="widget_icon_padding">4dp</dimen>
<!-- The y translation to apply at the start in appear animations. -->
<dimen name="appear_y_translation_start">32dp</dimen>
diff --git a/packages/SystemUI/res-keyguard/values/styles.xml b/packages/SystemUI/res-keyguard/values/styles.xml
index 826e3ea..d50bab5 100644
--- a/packages/SystemUI/res-keyguard/values/styles.xml
+++ b/packages/SystemUI/res-keyguard/values/styles.xml
@@ -78,4 +78,18 @@
<item name="wallpaperTextColorSecondary">@*android:color/secondary_text_material_dark</item>
</style>
+ <style name="TextAppearance.Keyguard" parent="Theme.SystemUI">
+ <item name="android:textSize">@dimen/widget_title_font_size</item>
+ <item name="android:gravity">center</item>
+ <item name="android:ellipsize">end</item>
+ <item name="android:maxLines">2</item>
+ </style>
+
+ <style name="TextAppearance.Keyguard.Secondary">
+ <item name="android:layout_width">wrap_content</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:textSize">@dimen/widget_label_font_size</item>
+ <item name="android:singleLine">true</item>
+ </style>
+
</resources>
diff --git a/packages/SystemUI/res/layout/pip_menu_activity.xml b/packages/SystemUI/res/layout/pip_menu_activity.xml
index 8b7f692..03d587b 100644
--- a/packages/SystemUI/res/layout/pip_menu_activity.xml
+++ b/packages/SystemUI/res/layout/pip_menu_activity.xml
@@ -60,14 +60,24 @@
</FrameLayout>
</FrameLayout>
- <ImageView
- android:id="@+id/dismiss"
- android:layout_width="@dimen/pip_action_size"
- android:layout_height="@dimen/pip_action_size"
- android:layout_gravity="top|end"
- android:padding="@dimen/pip_action_padding"
- android:contentDescription="@string/pip_phone_close"
- android:src="@drawable/ic_close_white"
- android:background="?android:selectableItemBackgroundBorderless" />
+ <ImageView
+ android:id="@+id/settings"
+ android:layout_width="@dimen/pip_action_size"
+ android:layout_height="@dimen/pip_action_size"
+ android:layout_gravity="top|start"
+ android:padding="@dimen/pip_action_padding"
+ android:contentDescription="@string/pip_phone_settings"
+ android:src="@drawable/ic_settings"
+ android:background="?android:selectableItemBackgroundBorderless" />
+
+ <ImageView
+ android:id="@+id/dismiss"
+ android:layout_width="@dimen/pip_action_size"
+ android:layout_height="@dimen/pip_action_size"
+ android:layout_gravity="top|end"
+ android:padding="@dimen/pip_action_padding"
+ android:contentDescription="@string/pip_phone_close"
+ android:src="@drawable/ic_close_white"
+ android:background="?android:selectableItemBackgroundBorderless" />
</FrameLayout>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index fd205dd..98537a1 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1895,6 +1895,9 @@
<!-- Label for PIP close button [CHAR LIMIT=NONE]-->
<string name="pip_phone_close">Close</string>
+ <!-- Label for PIP settings button [CHAR LIMIT=NONE]-->
+ <string name="pip_phone_settings">Settings</string>
+
<!-- Label for PIP the drag to dismiss hint [CHAR LIMIT=NONE]-->
<string name="pip_phone_dismiss_hint">Drag down to dismiss</string>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
index eb2d12e..1c99d38 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
@@ -413,15 +413,4 @@
Log.w(TAG, "Failed to cancel window transition for task=" + taskId, e);
}
}
-
- /**
- * Cancels the current thumbnail transtion to/from Recents for the given task id.
- */
- public void cancelThumbnailTransition(int taskId) {
- try {
- ActivityManager.getService().cancelTaskThumbnailTransition(taskId);
- } catch (RemoteException e) {
- Log.w(TAG, "Failed to cancel window transition for task=" + taskId, e);
- }
- }
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
index cb3d59c..b9bf80d 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
@@ -17,38 +17,56 @@
package com.android.keyguard;
import android.app.PendingIntent;
-import android.app.slice.Slice;
-import android.app.slice.SliceItem;
-import android.app.slice.SliceQuery;
+import android.arch.lifecycle.LiveData;
+import android.arch.lifecycle.Observer;
import android.content.Context;
-import android.database.ContentObserver;
+import android.graphics.Canvas;
import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.drawable.Drawable;
import android.net.Uri;
-import android.os.Handler;
+import android.provider.Settings;
import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.android.internal.graphics.ColorUtils;
+import com.android.settingslib.Utils;
+import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.keyguard.KeyguardSliceProvider;
+import com.android.systemui.tuner.TunerService;
-import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.function.Consumer;
+
+import androidx.app.slice.Slice;
+import androidx.app.slice.SliceItem;
+import androidx.app.slice.core.SliceQuery;
+import androidx.app.slice.widget.SliceLiveData;
/**
* View visible under the clock on the lock screen and AoD.
*/
-public class KeyguardSliceView extends LinearLayout {
+public class KeyguardSliceView extends LinearLayout implements View.OnClickListener,
+ Observer<Slice>, TunerService.Tunable {
- private final Uri mKeyguardSliceUri;
+ private static final String TAG = "KeyguardSliceView";
+ private final HashMap<View, PendingIntent> mClickActions;
+ private Uri mKeyguardSliceUri;
private TextView mTitle;
- private TextView mText;
- private Slice mSlice;
- private PendingIntent mSliceAction;
+ private LinearLayout mRow;
private int mTextColor;
private float mDarkAmount = 0;
- private final ContentObserver mObserver;
+ private LiveData<Slice> mLiveData;
+ private int mIconSize;
+ private Consumer<Boolean> mListener;
+ private boolean mHasHeader;
public KeyguardSliceView(Context context) {
this(context, null, 0);
@@ -60,16 +78,20 @@
public KeyguardSliceView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
- mObserver = new KeyguardSliceObserver(new Handler());
- mKeyguardSliceUri = Uri.parse(KeyguardSliceProvider.KEYGUARD_SLICE_URI);;
+
+ TunerService tunerService = Dependency.get(TunerService.class);
+ tunerService.addTunable(this, Settings.Secure.KEYGUARD_SLICE_URI);
+
+ mClickActions = new HashMap<>();
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mTitle = findViewById(R.id.title);
- mText = findViewById(R.id.text);
- mTextColor = mTitle.getCurrentTextColor();
+ mRow = findViewById(R.id.row);
+ mTextColor = Utils.getColorAttr(mContext, R.attr.wallpaperTextColor);
+ mIconSize = (int) mContext.getResources().getDimension(R.dimen.widget_icon_size);
}
@Override
@@ -77,57 +99,103 @@
super.onAttachedToWindow();
// Set initial content
- showSlice(Slice.bindSlice(getContext().getContentResolver(), mKeyguardSliceUri,
- Collections.emptyList()));
+ showSlice(Slice.bindSlice(getContext(), mKeyguardSliceUri));
// Make sure we always have the most current slice
- getContext().getContentResolver().registerContentObserver(mKeyguardSliceUri,
- false /* notifyDescendants */, mObserver);
+ mLiveData.observeForever(this);
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
- getContext().getContentResolver().unregisterContentObserver(mObserver);
+ mLiveData.removeObserver(this);
}
private void showSlice(Slice slice) {
- // Items will be wrapped into an action when they have tap targets.
- SliceItem actionSlice = SliceQuery.find(slice, SliceItem.FORMAT_ACTION);
- if (actionSlice != null) {
- mSlice = actionSlice.getSlice();
- mSliceAction = actionSlice.getAction();
- } else {
- mSlice = slice;
- mSliceAction = null;
- }
- if (mSlice == null) {
- setVisibility(GONE);
- return;
- }
+ // Main area
+ SliceItem mainItem = SliceQuery.find(slice, android.app.slice.SliceItem.FORMAT_SLICE,
+ null /* hints */, new String[]{android.app.slice.Slice.HINT_LIST_ITEM});
+ mHasHeader = mainItem != null;
- SliceItem title = SliceQuery.find(mSlice, SliceItem.FORMAT_TEXT, Slice.HINT_TITLE, null);
- if (title == null) {
+ List<SliceItem> subItems = SliceQuery.findAll(slice,
+ android.app.slice.SliceItem.FORMAT_SLICE,
+ new String[]{android.app.slice.Slice.HINT_LIST_ITEM},
+ null /* nonHints */);
+
+ if (!mHasHeader) {
mTitle.setVisibility(GONE);
} else {
mTitle.setVisibility(VISIBLE);
- mTitle.setText(title.getText());
+ SliceItem mainTitle = SliceQuery.find(mainItem.getSlice(),
+ android.app.slice.SliceItem.FORMAT_TEXT,
+ new String[]{android.app.slice.Slice.HINT_TITLE},
+ null /* nonHints */);
+ mTitle.setText(mainTitle.getText());
}
- SliceItem text = SliceQuery.find(mSlice, SliceItem.FORMAT_TEXT, null, Slice.HINT_TITLE);
- if (text == null) {
- mText.setVisibility(GONE);
- } else {
- mText.setVisibility(VISIBLE);
- mText.setText(text.getText());
+ mClickActions.clear();
+ final int subItemsCount = subItems.size();
+
+ for (int i = 0; i < subItemsCount; i++) {
+ SliceItem item = subItems.get(i);
+ final Uri itemTag = item.getSlice().getUri();
+ // Try to reuse the view if already exists in the layout
+ KeyguardSliceButton button = mRow.findViewWithTag(itemTag);
+ if (button == null) {
+ button = new KeyguardSliceButton(mContext);
+ button.setTextColor(mTextColor);
+ button.setTag(itemTag);
+ } else {
+ mRow.removeView(button);
+ }
+ button.setHasDivider(i < subItemsCount - 1);
+ mRow.addView(button, i);
+
+ PendingIntent pendingIntent;
+ try {
+ pendingIntent = item.getAction();
+ } catch (RuntimeException e) {
+ Log.w(TAG, "Cannot retrieve action from keyguard slice", e);
+ pendingIntent = null;
+ }
+ mClickActions.put(button, pendingIntent);
+
+ SliceItem title = SliceQuery.find(item.getSlice(),
+ android.app.slice.SliceItem.FORMAT_TEXT,
+ new String[]{android.app.slice.Slice.HINT_TITLE},
+ null /* nonHints */);
+ button.setText(title.getText());
+
+ Drawable iconDrawable = null;
+ SliceItem icon = SliceQuery.find(item.getSlice(),
+ android.app.slice.SliceItem.FORMAT_IMAGE);
+ if (icon != null) {
+ iconDrawable = icon.getIcon().loadDrawable(mContext);
+ final int width = (int) (iconDrawable.getIntrinsicWidth()
+ / (float) iconDrawable.getIntrinsicHeight() * mIconSize);
+ iconDrawable.setBounds(0, 0, Math.max(width, 1), mIconSize);
+ }
+ button.setCompoundDrawablesRelative(iconDrawable, null, null, null);
+ button.setOnClickListener(this);
}
- final int visibility = title == null && text == null ? GONE : VISIBLE;
+ // Removing old views
+ for (int i = 0; i < mRow.getChildCount(); i++) {
+ View child = mRow.getChildAt(i);
+ if (!mClickActions.containsKey(child)) {
+ mRow.removeView(child);
+ i--;
+ }
+ }
+
+ final int visibility = mHasHeader || subItemsCount > 0 ? VISIBLE : GONE;
if (visibility != getVisibility()) {
setVisibility(visibility);
}
+
+ mListener.accept(mHasHeader);
}
public void setDark(float darkAmount) {
@@ -135,30 +203,113 @@
updateTextColors();
}
- public void setTextColor(int textColor) {
- mTextColor = textColor;
- }
-
private void updateTextColors() {
final int blendedColor = ColorUtils.blendARGB(mTextColor, Color.WHITE, mDarkAmount);
mTitle.setTextColor(blendedColor);
- mText.setTextColor(blendedColor);
+ int childCount = mRow.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ View v = mRow.getChildAt(i);
+ if (v instanceof Button) {
+ ((Button) v).setTextColor(blendedColor);
+ }
+ }
}
- private class KeyguardSliceObserver extends ContentObserver {
- KeyguardSliceObserver(Handler handler) {
- super(handler);
+ @Override
+ public void onClick(View v) {
+ final PendingIntent action = mClickActions.get(v);
+ if (action != null) {
+ try {
+ action.send();
+ } catch (PendingIntent.CanceledException e) {
+ Log.i(TAG, "Pending intent cancelled, nothing to launch", e);
+ }
+ }
+ }
+
+ public void setListener(Consumer<Boolean> listener) {
+ mListener = listener;
+ }
+
+ public boolean hasHeader() {
+ return mHasHeader;
+ }
+
+ /**
+ * LiveData observer lifecycle.
+ * @param slice the new slice content.
+ */
+ @Override
+ public void onChanged(Slice slice) {
+ showSlice(slice);
+ }
+
+ @Override
+ public void onTuningChanged(String key, String newValue) {
+ setupUri(newValue);
+ }
+
+ public void setupUri(String uriString) {
+ if (uriString == null) {
+ uriString = KeyguardSliceProvider.KEYGUARD_SLICE_URI;
+ }
+
+ boolean wasObserving = false;
+ if (mLiveData != null && mLiveData.hasActiveObservers()) {
+ wasObserving = true;
+ mLiveData.removeObserver(this);
+ }
+
+ mKeyguardSliceUri = Uri.parse(uriString);
+ mLiveData = SliceLiveData.fromUri(mContext, mKeyguardSliceUri);
+
+ if (wasObserving) {
+ mLiveData.observeForever(this);
+ showSlice(Slice.bindSlice(getContext(), mKeyguardSliceUri));
+ }
+ }
+
+ /**
+ * Representation of an item that appears under the clock on main keyguard message.
+ * Shows optional separator.
+ */
+ private class KeyguardSliceButton extends Button {
+
+ private final Paint mPaint;
+ private boolean mHasDivider;
+
+ public KeyguardSliceButton(Context context) {
+ super(context, null /* attrs */,
+ com.android.keyguard.R.style.TextAppearance_Keyguard_Secondary);
+ mPaint = new Paint();
+ mPaint.setStyle(Paint.Style.STROKE);
+ float dividerWidth = context.getResources()
+ .getDimension(R.dimen.widget_separator_thickness);
+ mPaint.setStrokeWidth(dividerWidth);
+ int horizontalPadding = (int) context.getResources()
+ .getDimension(R.dimen.widget_horizontal_padding);
+ setPadding(horizontalPadding, 0, horizontalPadding, 0);
+ setCompoundDrawablePadding((int) context.getResources()
+ .getDimension(R.dimen.widget_icon_padding));
+ }
+
+ public void setHasDivider(boolean hasDivider) {
+ mHasDivider = hasDivider;
}
@Override
- public void onChange(boolean selfChange) {
- this.onChange(selfChange, null);
+ public void setTextColor(int color) {
+ super.setTextColor(color);
+ mPaint.setColor(color);
}
@Override
- public void onChange(boolean selfChange, Uri uri) {
- showSlice(Slice.bindSlice(getContext().getContentResolver(), mKeyguardSliceUri,
- Collections.emptyList()));
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+ if (mHasDivider) {
+ final int lineX = getLayoutDirection() == LAYOUT_DIRECTION_RTL ? 0 : getWidth();
+ canvas.drawLine(lineX, 0, lineX, getHeight(), mPaint);
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
index 78cf2b9..4b9a874 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
@@ -28,6 +28,7 @@
import android.support.v4.graphics.ColorUtils;
import android.text.TextUtils;
import android.text.format.DateFormat;
+import android.util.ArraySet;
import android.util.AttributeSet;
import android.util.Log;
import android.util.Slog;
@@ -38,11 +39,11 @@
import android.widget.TextClock;
import android.widget.TextView;
-import com.android.internal.util.ArrayUtils;
import com.android.internal.widget.LockPatternUtils;
-import com.android.settingslib.Utils;
import com.android.systemui.ChargingView;
+import com.google.android.collect.Sets;
+
import java.util.Locale;
public class KeyguardStatusView extends GridLayout {
@@ -52,8 +53,11 @@
private final LockPatternUtils mLockPatternUtils;
private final AlarmManager mAlarmManager;
+ private final float mSmallClockScale;
+ private final float mWidgetPadding;
private TextClock mClockView;
+ private View mClockSeparator;
private TextView mOwnerInfo;
private ViewGroup mClockContainer;
private ChargingView mBatteryDoze;
@@ -61,7 +65,7 @@
private Runnable mPendingMarqueeStart;
private Handler mHandler;
- private View[] mVisibleInDoze;
+ private ArraySet<View> mVisibleInDoze;
private boolean mPulsing;
private float mDarkAmount = 0;
private int mTextColor;
@@ -112,6 +116,9 @@
mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
mLockPatternUtils = new LockPatternUtils(getContext());
mHandler = new Handler(Looper.myLooper());
+ mSmallClockScale = getResources().getDimension(R.dimen.widget_small_font_size)
+ / getResources().getDimension(R.dimen.widget_big_font_size);
+ mWidgetPadding = getResources().getDimension(R.dimen.widget_vertical_padding);
}
private void setEnableMarquee(boolean enabled) {
@@ -150,9 +157,14 @@
mOwnerInfo = findViewById(R.id.owner_info);
mBatteryDoze = findViewById(R.id.battery_doze);
mKeyguardSlice = findViewById(R.id.keyguard_status_area);
- mVisibleInDoze = new View[]{mBatteryDoze, mClockView, mKeyguardSlice};
+ mClockSeparator = findViewById(R.id.clock_separator);
+ mVisibleInDoze = Sets.newArraySet(mBatteryDoze, mClockView, mKeyguardSlice,
+ mClockSeparator);
mTextColor = mClockView.getCurrentTextColor();
+ mKeyguardSlice.setListener(this::onSliceContentChanged);
+ onSliceContentChanged(mKeyguardSlice.hasHeader());
+
boolean shouldMarquee = KeyguardUpdateMonitor.getInstance(mContext).isDeviceInteractive();
setEnableMarquee(shouldMarquee);
refresh();
@@ -163,6 +175,22 @@
mClockView.setElegantTextHeight(false);
}
+ private void onSliceContentChanged(boolean hasHeader) {
+ final float clockScale = hasHeader ? mSmallClockScale : 1;
+ float translation = (mClockView.getHeight() - (mClockView.getHeight() * clockScale)) / 2f;
+ if (hasHeader) {
+ translation -= mWidgetPadding;
+ }
+ mClockView.setTranslationY(translation);
+ mClockView.setScaleX(clockScale);
+ mClockView.setScaleY(clockScale);
+ final float batteryTranslation =
+ -(mClockView.getWidth() - (mClockView.getWidth() * clockScale)) / 2;
+ mBatteryDoze.setTranslationX(batteryTranslation);
+ mBatteryDoze.setTranslationY(translation);
+ mClockSeparator.setVisibility(hasHeader ? VISIBLE : GONE);
+ }
+
@Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
@@ -201,17 +229,6 @@
return mClockView.getTextSize();
}
- public static String formatNextAlarm(Context context, AlarmManager.AlarmClockInfo info) {
- if (info == null) {
- return "";
- }
- String skeleton = DateFormat.is24HourFormat(context, ActivityManager.getCurrentUser())
- ? "EHm"
- : "Ehma";
- String pattern = DateFormat.getBestDateTimePattern(Locale.getDefault(), skeleton);
- return DateFormat.format(pattern, info.getTriggerTime()).toString();
- }
-
private void updateOwnerInfo() {
if (mOwnerInfo == null) return;
String ownerInfo = getOwnerInfo();
@@ -303,7 +320,7 @@
final int N = mClockContainer.getChildCount();
for (int i = 0; i < N; i++) {
View child = mClockContainer.getChildAt(i);
- if (ArrayUtils.contains(mVisibleInDoze, child)) {
+ if (mVisibleInDoze.contains(child)) {
continue;
}
child.setAlpha(dark ? 0 : 1);
@@ -312,10 +329,12 @@
mOwnerInfo.setAlpha(dark ? 0 : 1);
}
+ final int blendedTextColor = ColorUtils.blendARGB(mTextColor, Color.WHITE, darkAmount);
updateDozeVisibleViews();
mBatteryDoze.setDark(dark);
mKeyguardSlice.setDark(darkAmount);
- mClockView.setTextColor(ColorUtils.blendARGB(mTextColor, Color.WHITE, darkAmount));
+ mClockView.setTextColor(blendedTextColor);
+ mClockSeparator.setBackgroundColor(blendedTextColor);
}
public void setPulsing(boolean pulsing) {
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
index 3177c03..0f3daf5 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
@@ -23,18 +23,23 @@
import android.view.View;
import android.view.ViewGroup;
+import com.android.internal.logging.MetricsLogger;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.ViewMediatorCallback;
import com.android.systemui.Dependency.DependencyProvider;
import com.android.systemui.keyguard.DismissCallbackRegistry;
import com.android.systemui.qs.QSTileHost;
import com.android.systemui.statusbar.KeyguardIndicationController;
+import com.android.systemui.statusbar.NotificationEntryManager;
import com.android.systemui.statusbar.NotificationGutsManager;
-import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationListener;
+import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationLogger;
+import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
+import com.android.systemui.statusbar.NotificationViewHierarchyManager;
import com.android.systemui.statusbar.ScrimView;
+import com.android.systemui.statusbar.notification.VisualStabilityManager;
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.phone.KeyguardBouncer;
import com.android.systemui.statusbar.phone.LightBarController;
@@ -46,6 +51,7 @@
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import java.util.function.Consumer;
@@ -118,16 +124,16 @@
Context context) {
providers.put(NotificationLockscreenUserManager.class,
() -> new NotificationLockscreenUserManager(context));
+ providers.put(VisualStabilityManager.class, VisualStabilityManager::new);
providers.put(NotificationGroupManager.class, NotificationGroupManager::new);
- providers.put(NotificationGutsManager.class, () -> new NotificationGutsManager(
- Dependency.get(NotificationLockscreenUserManager.class), context));
+ providers.put(NotificationMediaManager.class, () -> new NotificationMediaManager(context));
+ providers.put(NotificationGutsManager.class, () -> new NotificationGutsManager(context));
providers.put(NotificationRemoteInputManager.class,
- () -> new NotificationRemoteInputManager(
- Dependency.get(NotificationLockscreenUserManager.class), context));
- providers.put(NotificationListener.class, () -> new NotificationListener(
- Dependency.get(NotificationRemoteInputManager.class), context));
- providers.put(NotificationLogger.class, () -> new NotificationLogger(
- Dependency.get(NotificationListener.class),
- Dependency.get(UiOffloadThread.class)));
+ () -> new NotificationRemoteInputManager(context));
+ providers.put(NotificationListener.class, () -> new NotificationListener(context));
+ providers.put(NotificationLogger.class, NotificationLogger::new);
+ providers.put(NotificationViewHierarchyManager.class,
+ () -> new NotificationViewHierarchyManager(context));
+ providers.put(NotificationEntryManager.class, () -> new NotificationEntryManager(context));
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/car/CarNotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/car/CarNotificationEntryManager.java
new file mode 100644
index 0000000..a89a8ef
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/car/CarNotificationEntryManager.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.systemui.car;
+
+import android.content.Context;
+import android.service.notification.StatusBarNotification;
+
+import com.android.systemui.statusbar.ExpandableNotificationRow;
+import com.android.systemui.statusbar.NotificationData;
+import com.android.systemui.statusbar.NotificationEntryManager;
+
+public class CarNotificationEntryManager extends NotificationEntryManager {
+ public CarNotificationEntryManager(Context context) {
+ super(context);
+ }
+
+ /**
+ * Returns the
+ * {@link com.android.systemui.statusbar.ExpandableNotificationRow.LongPressListener} that will
+ * be triggered when a notification card is long-pressed.
+ */
+ @Override
+ public ExpandableNotificationRow.LongPressListener getNotificationLongClicker() {
+ // For the automative use case, we do not want to the user to be able to interact with
+ // a notification other than a regular click. As a result, just return null for the
+ // long click listener.
+ return null;
+ }
+
+ @Override
+ public boolean shouldPeek(NotificationData.Entry entry, StatusBarNotification sbn) {
+ // Because space is usually constrained in the auto use-case, there should not be a
+ // pinned notification when the shade has been expanded. Ensure this by not pinning any
+ // notification if the shade is already opened.
+ if (!mPresenter.isPresenterFullyCollapsed()) {
+ return false;
+ }
+
+ return super.shouldPeek(entry, sbn);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/car/CarSystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/car/CarSystemUIFactory.java
index 5a19e7d..174584d 100644
--- a/packages/SystemUI/src/com/android/systemui/car/CarSystemUIFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/car/CarSystemUIFactory.java
@@ -18,9 +18,22 @@
import android.content.Context;
import android.util.ArrayMap;
+import com.android.internal.logging.MetricsLogger;
+import com.android.systemui.Dependency;
import com.android.systemui.Dependency.DependencyProvider;
+import com.android.systemui.ForegroundServiceController;
import com.android.systemui.SystemUIFactory;
+import com.android.systemui.UiOffloadThread;
import com.android.systemui.plugins.VolumeDialogController;
+import com.android.systemui.statusbar.NotificationEntryManager;
+import com.android.systemui.statusbar.NotificationGutsManager;
+import com.android.systemui.statusbar.NotificationListener;
+import com.android.systemui.statusbar.NotificationLockscreenUserManager;
+import com.android.systemui.statusbar.NotificationMediaManager;
+import com.android.systemui.statusbar.NotificationRemoteInputManager;
+import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.phone.NotificationGroupManager;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.volume.car.CarVolumeDialogController;
/**
@@ -32,5 +45,7 @@
Context context) {
super.injectDependencies(providers, context);
providers.put(VolumeDialogController.class, () -> new CarVolumeDialogController(context));
+ providers.put(NotificationEntryManager.class,
+ () -> new CarNotificationEntryManager(context));
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
index 6ddc76b..bd46c5f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
@@ -16,38 +16,55 @@
package com.android.systemui.keyguard;
+import android.app.ActivityManager;
+import android.app.AlarmManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.graphics.drawable.Icon;
import android.icu.text.DateFormat;
import android.icu.text.DisplayContext;
import android.net.Uri;
import android.os.Handler;
-import android.app.slice.Slice;
-import android.app.slice.SliceProvider;
+import android.text.TextUtils;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.R;
+import com.android.systemui.statusbar.policy.NextAlarmController;
+import com.android.systemui.statusbar.policy.NextAlarmControllerImpl;
import java.util.Date;
import java.util.Locale;
+import androidx.app.slice.Slice;
+import androidx.app.slice.SliceProvider;
+import androidx.app.slice.builders.ListBuilder;
+import androidx.app.slice.builders.ListBuilder.RowBuilder;
+
/**
* Simple Slice provider that shows the current date.
*/
-public class KeyguardSliceProvider extends SliceProvider {
+public class KeyguardSliceProvider extends SliceProvider implements
+ NextAlarmController.NextAlarmChangeCallback {
public static final String KEYGUARD_SLICE_URI = "content://com.android.systemui.keyguard/main";
+ public static final String KEYGUARD_DATE_URI = "content://com.android.systemui.keyguard/date";
+ public static final String KEYGUARD_NEXT_ALARM_URI =
+ "content://com.android.systemui.keyguard/alarm";
private final Date mCurrentTime = new Date();
protected final Uri mSliceUri;
+ protected final Uri mDateUri;
+ protected final Uri mAlarmUri;
private final Handler mHandler;
private String mDatePattern;
private DateFormat mDateFormat;
private String mLastText;
private boolean mRegistered;
private boolean mRegisteredEveryMinute;
+ private String mNextAlarm;
+ private NextAlarmController mNextAlarmController;
/**
* Receiver responsible for time ticking and updating the date format.
@@ -80,23 +97,49 @@
KeyguardSliceProvider(Handler handler) {
mHandler = handler;
mSliceUri = Uri.parse(KEYGUARD_SLICE_URI);
+ mDateUri = Uri.parse(KEYGUARD_DATE_URI);
+ mAlarmUri = Uri.parse(KEYGUARD_NEXT_ALARM_URI);
}
@Override
public Slice onBindSlice(Uri sliceUri) {
- return new Slice.Builder(sliceUri).addText(mLastText, null, Slice.HINT_TITLE).build();
+ ListBuilder builder = new ListBuilder(mSliceUri)
+ .addRow(new RowBuilder(mDateUri).setTitle(mLastText));
+ if (!TextUtils.isEmpty(mNextAlarm)) {
+ Icon icon = Icon.createWithResource(getContext(), R.drawable.ic_access_alarms_big);
+ builder.addRow(new RowBuilder(mAlarmUri).setTitle(mNextAlarm).addEndItem(icon));
+ }
+
+ return builder.build();
}
@Override
- public boolean onCreate() {
-
+ public boolean onCreateSliceProvider() {
+ mNextAlarmController = new NextAlarmControllerImpl(getContext());
+ mNextAlarmController.addCallback(this);
mDatePattern = getContext().getString(R.string.system_ui_date_pattern);
-
registerClockUpdate(false /* everyMinute */);
updateClock();
return true;
}
+ public static String formatNextAlarm(Context context, AlarmManager.AlarmClockInfo info) {
+ if (info == null) {
+ return "";
+ }
+ String skeleton = android.text.format.DateFormat
+ .is24HourFormat(context, ActivityManager.getCurrentUser()) ? "EHm" : "Ehma";
+ String pattern = android.text.format.DateFormat
+ .getBestDateTimePattern(Locale.getDefault(), skeleton);
+ return android.text.format.DateFormat.format(pattern, info.getTriggerTime()).toString();
+ }
+
+ /**
+ * Registers a broadcast receiver for clock updates, include date, time zone and manually
+ * changing the date/time via the settings app.
+ *
+ * @param everyMinute {@code true} if you also want updates every minute.
+ */
protected void registerClockUpdate(boolean everyMinute) {
if (mRegistered) {
if (mRegisteredEveryMinute == everyMinute) {
@@ -156,4 +199,10 @@
void cleanDateFormat() {
mDateFormat = null;
}
+
+ @Override
+ public void onNextAlarmChanged(AlarmManager.AlarmClockInfo nextAlarm) {
+ mNextAlarm = formatNextAlarm(getContext(), nextAlarm);
+ getContext().getContentResolver().notifyChange(mSliceUri, null /* observer */);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/NotificationPlayer.java b/packages/SystemUI/src/com/android/systemui/media/NotificationPlayer.java
index b5c0d53..f5f06db 100644
--- a/packages/SystemUI/src/com/android/systemui/media/NotificationPlayer.java
+++ b/packages/SystemUI/src/com/android/systemui/media/NotificationPlayer.java
@@ -133,12 +133,19 @@
+ mNotificationRampTimeMs + "ms"); }
try {
Thread.sleep(mNotificationRampTimeMs);
- player.start();
} catch (InterruptedException e) {
Log.e(mTag, "Exception while sleeping to sync notification playback"
+ " with ducking", e);
}
- if (DEBUG) { Log.d(mTag, "player.start"); }
+ try {
+ player.start();
+ if (DEBUG) { Log.d(mTag, "player.start"); }
+ } catch (Exception e) {
+ player.release();
+ player = null;
+ // playing the notification didn't work, revert the focus request
+ abandonAudioFocusAfterError();
+ }
if (mPlayer != null) {
if (DEBUG) { Log.d(mTag, "mPlayer.release"); }
mPlayer.release();
@@ -147,6 +154,8 @@
}
catch (Exception e) {
Log.w(mTag, "error loading sound for " + mCmd.uri, e);
+ // playing the notification didn't work, revert the focus request
+ abandonAudioFocusAfterError();
}
this.notify();
}
@@ -154,6 +163,16 @@
}
};
+ private void abandonAudioFocusAfterError() {
+ synchronized (mQueueAudioFocusLock) {
+ if (mAudioManagerWithAudioFocus != null) {
+ if (DEBUG) Log.d(mTag, "abandoning focus after playback error");
+ mAudioManagerWithAudioFocus.abandonAudioFocus(null);
+ mAudioManagerWithAudioFocus = null;
+ }
+ }
+ }
+
private void startSound(Command cmd) {
// Preparing can be slow, so if there is something else
// is playing, let it continue until we're done, so there
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipAppOpsListener.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipAppOpsListener.java
new file mode 100644
index 0000000..f0e4ccc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipAppOpsListener.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.pip.phone;
+
+import static android.app.AppOpsManager.MODE_ALLOWED;
+import static android.app.AppOpsManager.OP_PICTURE_IN_PICTURE;
+
+import android.app.AppOpsManager;
+import android.app.AppOpsManager.OnOpChangedListener;
+import android.app.IActivityManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.util.Pair;
+
+public class PipAppOpsListener {
+ private static final String TAG = PipAppOpsListener.class.getSimpleName();
+
+ private Context mContext;
+ private IActivityManager mActivityManager;
+ private AppOpsManager mAppOpsManager;
+
+ private PipMotionHelper mMotionHelper;
+
+ private AppOpsManager.OnOpChangedListener mAppOpsChangedListener = new OnOpChangedListener() {
+ @Override
+ public void onOpChanged(String op, String packageName) {
+ try {
+ // Dismiss the PiP once the user disables the app ops setting for that package
+ final Pair<ComponentName, Integer> topPipActivityInfo =
+ PipUtils.getTopPinnedActivity(mContext, mActivityManager);
+ if (topPipActivityInfo.first != null) {
+ final ApplicationInfo appInfo = mContext.getPackageManager()
+ .getApplicationInfoAsUser(packageName, 0, topPipActivityInfo.second);
+ if (appInfo.packageName.equals(topPipActivityInfo.first.getPackageName()) &&
+ mAppOpsManager.checkOpNoThrow(OP_PICTURE_IN_PICTURE, appInfo.uid,
+ packageName) != MODE_ALLOWED) {
+ mMotionHelper.dismissPip();
+ }
+ }
+ } catch (NameNotFoundException e) {
+ // Unregister the listener if the package can't be found
+ unregisterAppOpsListener();
+ }
+ }
+ };
+
+ public PipAppOpsListener(Context context, IActivityManager activityManager,
+ PipMotionHelper motionHelper) {
+ mContext = context;
+ mActivityManager = activityManager;
+ mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
+ mMotionHelper = motionHelper;
+ }
+
+ public void onActivityPinned(String packageName) {
+ // Register for changes to the app ops setting for this package while it is in PiP
+ registerAppOpsListener(packageName);
+ }
+
+ public void onActivityUnpinned() {
+ // Unregister for changes to the previously PiP'ed package
+ unregisterAppOpsListener();
+ }
+
+ private void registerAppOpsListener(String packageName) {
+ mAppOpsManager.startWatchingMode(OP_PICTURE_IN_PICTURE, packageName,
+ mAppOpsChangedListener);
+ }
+
+ private void unregisterAppOpsListener() {
+ mAppOpsManager.stopWatchingMode(mAppOpsChangedListener);
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
index 2963506..36531bb 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
@@ -64,8 +64,8 @@
private InputConsumerController mInputConsumerController;
private PipMenuActivityController mMenuController;
private PipMediaController mMediaController;
- private PipNotificationController mNotificationController;
private PipTouchHandler mTouchHandler;
+ private PipAppOpsListener mAppOpsListener;
/**
* Handler for system task stack changes.
@@ -76,8 +76,7 @@
mTouchHandler.onActivityPinned();
mMediaController.onActivityPinned();
mMenuController.onActivityPinned();
- mNotificationController.onActivityPinned(packageName, userId,
- true /* deferUntilAnimationEnds */);
+ mAppOpsListener.onActivityPinned(packageName);
SystemServicesProxy.getInstance(mContext).setPipVisibility(true);
}
@@ -90,7 +89,7 @@
final int userId = topActivity != null ? topPipActivityInfo.second : 0;
mMenuController.onActivityUnpinned();
mTouchHandler.onActivityUnpinned(topActivity);
- mNotificationController.onActivityUnpinned(topActivity, userId);
+ mAppOpsListener.onActivityUnpinned();
SystemServicesProxy.getInstance(mContext).setPipVisibility(topActivity != null);
}
@@ -107,7 +106,6 @@
mTouchHandler.setTouchEnabled(true);
mTouchHandler.onPinnedStackAnimationEnded();
mMenuController.onPinnedStackAnimationEnded();
- mNotificationController.onPinnedStackAnimationEnded();
}
@Override
@@ -182,7 +180,7 @@
mInputConsumerController);
mTouchHandler = new PipTouchHandler(context, mActivityManager, mMenuController,
mInputConsumerController);
- mNotificationController = new PipNotificationController(context, mActivityManager,
+ mAppOpsListener = new PipAppOpsListener(context, mActivityManager,
mTouchHandler.getMotionHelper());
EventBus.getDefault().register(this);
}
@@ -198,20 +196,6 @@
* Expands the PIP.
*/
public final void onBusEvent(ExpandPipEvent event) {
- if (event.clearThumbnailWindows) {
- try {
- StackInfo stackInfo = mActivityManager.getStackInfo(
- WINDOWING_MODE_PINNED, ACTIVITY_TYPE_UNDEFINED);
- if (stackInfo != null && stackInfo.taskIds != null) {
- ActivityManagerWrapper am = ActivityManagerWrapper.getInstance();
- for (int taskId : stackInfo.taskIds) {
- am.cancelThumbnailTransition(taskId);
- }
- }
- } catch (RemoteException e) {
- // Do nothing
- }
- }
mTouchHandler.getMotionHelper().expandPip(false /* skipAnimation */);
}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
index 90f7b8d..bfe07a9 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
@@ -16,6 +16,10 @@
package com.android.systemui.pip.phone;
+import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+import static android.provider.Settings.ACTION_PICTURE_IN_PICTURE_SETTINGS;
+
import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_ACTIONS;
import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_ALLOW_TIMEOUT;
import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_CONTROLLER_MESSENGER;
@@ -39,6 +43,7 @@
import android.app.ActivityManager;
import android.app.PendingIntent.CanceledException;
import android.app.RemoteAction;
+import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.ParceledListSlice;
import android.graphics.Color;
@@ -46,12 +51,15 @@
import android.graphics.Rect;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
+import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
+import android.os.UserHandle;
import android.util.Log;
+import android.util.Pair;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
@@ -105,6 +113,7 @@
private Drawable mBackgroundDrawable;
private View mMenuContainer;
private LinearLayout mActionsGroup;
+ private View mSettingsButton;
private View mDismissButton;
private ImageView mExpandButton;
private int mBetweenActionPaddingLand;
@@ -218,6 +227,11 @@
}
return true;
});
+ mSettingsButton = findViewById(R.id.settings);
+ mSettingsButton.setAlpha(0);
+ mSettingsButton.setOnClickListener((v) -> {
+ showSettings();
+ });
mDismissButton = findViewById(R.id.dismiss);
mDismissButton.setAlpha(0);
mDismissButton.setOnClickListener((v) -> {
@@ -352,12 +366,14 @@
ObjectAnimator menuAnim = ObjectAnimator.ofFloat(mMenuContainer, View.ALPHA,
mMenuContainer.getAlpha(), 1f);
menuAnim.addUpdateListener(mMenuBgUpdateListener);
+ ObjectAnimator settingsAnim = ObjectAnimator.ofFloat(mSettingsButton, View.ALPHA,
+ mSettingsButton.getAlpha(), 1f);
ObjectAnimator dismissAnim = ObjectAnimator.ofFloat(mDismissButton, View.ALPHA,
mDismissButton.getAlpha(), 1f);
if (menuState == MENU_STATE_FULL) {
- mMenuContainerAnimator.playTogether(menuAnim, dismissAnim);
+ mMenuContainerAnimator.playTogether(menuAnim, settingsAnim, dismissAnim);
} else {
- mMenuContainerAnimator.play(dismissAnim);
+ mMenuContainerAnimator.playTogether(settingsAnim, dismissAnim);
}
mMenuContainerAnimator.setInterpolator(Interpolators.ALPHA_IN);
mMenuContainerAnimator.setDuration(MENU_FADE_DURATION);
@@ -394,9 +410,11 @@
ObjectAnimator menuAnim = ObjectAnimator.ofFloat(mMenuContainer, View.ALPHA,
mMenuContainer.getAlpha(), 0f);
menuAnim.addUpdateListener(mMenuBgUpdateListener);
+ ObjectAnimator settingsAnim = ObjectAnimator.ofFloat(mSettingsButton, View.ALPHA,
+ mSettingsButton.getAlpha(), 0f);
ObjectAnimator dismissAnim = ObjectAnimator.ofFloat(mDismissButton, View.ALPHA,
mDismissButton.getAlpha(), 0f);
- mMenuContainerAnimator.playTogether(menuAnim, dismissAnim);
+ mMenuContainerAnimator.playTogether(menuAnim, settingsAnim, dismissAnim);
mMenuContainerAnimator.setInterpolator(Interpolators.ALPHA_OUT);
mMenuContainerAnimator.setDuration(MENU_FADE_DURATION);
mMenuContainerAnimator.addListener(new AnimatorListenerAdapter() {
@@ -526,12 +544,14 @@
final float menuAlpha = 1 - fraction;
if (mMenuState == MENU_STATE_FULL) {
mMenuContainer.setAlpha(menuAlpha);
+ mSettingsButton.setAlpha(menuAlpha);
mDismissButton.setAlpha(menuAlpha);
final float interpolatedAlpha =
MENU_BACKGROUND_ALPHA * menuAlpha + DISMISS_BACKGROUND_ALPHA * fraction;
alpha = (int) (interpolatedAlpha * 255);
} else {
if (mMenuState == MENU_STATE_CLOSE) {
+ mSettingsButton.setAlpha(menuAlpha);
mDismissButton.setAlpha(menuAlpha);
}
alpha = (int) (fraction * DISMISS_BACKGROUND_ALPHA * 255);
@@ -588,6 +608,19 @@
sendMessage(m, "Could not notify controller to show PIP menu");
}
+ private void showSettings() {
+ final Pair<ComponentName, Integer> topPipActivityInfo =
+ PipUtils.getTopPinnedActivity(this, ActivityManager.getService());
+ if (topPipActivityInfo.first != null) {
+ final UserHandle user = UserHandle.of(topPipActivityInfo.second);
+ final Intent settingsIntent = new Intent(ACTION_PICTURE_IN_PICTURE_SETTINGS,
+ Uri.fromParts("package", topPipActivityInfo.first.getPackageName(), null));
+ settingsIntent.putExtra(Intent.EXTRA_USER_HANDLE, user);
+ settingsIntent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK);
+ startActivity(settingsIntent);
+ }
+ }
+
private void notifyActivityCallback(Messenger callback) {
Message m = Message.obtain();
m.what = PipMenuActivityController.MESSAGE_UPDATE_ACTIVITY_CALLBACK;
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipNotificationController.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipNotificationController.java
deleted file mode 100644
index 6d083e9..0000000
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipNotificationController.java
+++ /dev/null
@@ -1,231 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.pip.phone;
-
-import static android.app.AppOpsManager.MODE_ALLOWED;
-import static android.app.AppOpsManager.OP_PICTURE_IN_PICTURE;
-import static android.app.PendingIntent.FLAG_CANCEL_CURRENT;
-import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
-import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
-import static android.provider.Settings.ACTION_PICTURE_IN_PICTURE_SETTINGS;
-
-import android.app.AppOpsManager;
-import android.app.AppOpsManager.OnOpChangedListener;
-import android.app.IActivityManager;
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.graphics.Bitmap;
-import android.graphics.Bitmap.Config;
-import android.graphics.Canvas;
-import android.graphics.drawable.Drawable;
-import android.net.Uri;
-import android.os.UserHandle;
-import android.util.IconDrawableFactory;
-import android.util.Log;
-import android.util.Pair;
-
-import com.android.systemui.R;
-import com.android.systemui.SystemUI;
-import com.android.systemui.util.NotificationChannels;
-
-/**
- * Manages the BTW notification that shows whenever an activity enters or leaves picture-in-picture.
- */
-public class PipNotificationController {
- private static final String TAG = PipNotificationController.class.getSimpleName();
-
- private static final String NOTIFICATION_TAG = PipNotificationController.class.getName();
- private static final int NOTIFICATION_ID = 0;
-
- private Context mContext;
- private IActivityManager mActivityManager;
- private AppOpsManager mAppOpsManager;
- private NotificationManager mNotificationManager;
- private IconDrawableFactory mIconDrawableFactory;
-
- private PipMotionHelper mMotionHelper;
-
- // Used when building a deferred notification
- private String mDeferredNotificationPackageName;
- private int mDeferredNotificationUserId;
-
- private AppOpsManager.OnOpChangedListener mAppOpsChangedListener = new OnOpChangedListener() {
- @Override
- public void onOpChanged(String op, String packageName) {
- try {
- // Dismiss the PiP once the user disables the app ops setting for that package
- final Pair<ComponentName, Integer> topPipActivityInfo =
- PipUtils.getTopPinnedActivity(mContext, mActivityManager);
- if (topPipActivityInfo.first != null) {
- final ApplicationInfo appInfo = mContext.getPackageManager()
- .getApplicationInfoAsUser(packageName, 0, topPipActivityInfo.second);
- if (appInfo.packageName.equals(topPipActivityInfo.first.getPackageName()) &&
- mAppOpsManager.checkOpNoThrow(OP_PICTURE_IN_PICTURE, appInfo.uid,
- packageName) != MODE_ALLOWED) {
- mMotionHelper.dismissPip();
- }
- }
- } catch (NameNotFoundException e) {
- // Unregister the listener if the package can't be found
- unregisterAppOpsListener();
- }
- }
- };
-
- public PipNotificationController(Context context, IActivityManager activityManager,
- PipMotionHelper motionHelper) {
- mContext = context;
- mActivityManager = activityManager;
- mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
- mNotificationManager = NotificationManager.from(context);
- mMotionHelper = motionHelper;
- mIconDrawableFactory = IconDrawableFactory.newInstance(context);
- }
-
- public void onActivityPinned(String packageName, int userId, boolean deferUntilAnimationEnds) {
- // Clear any existing notification
- mNotificationManager.cancel(NOTIFICATION_TAG, NOTIFICATION_ID);
-
- if (deferUntilAnimationEnds) {
- mDeferredNotificationPackageName = packageName;
- mDeferredNotificationUserId = userId;
- } else {
- showNotificationForApp(packageName, userId);
- }
-
- // Register for changes to the app ops setting for this package while it is in PiP
- registerAppOpsListener(packageName);
- }
-
- public void onPinnedStackAnimationEnded() {
- if (mDeferredNotificationPackageName != null) {
- showNotificationForApp(mDeferredNotificationPackageName, mDeferredNotificationUserId);
- mDeferredNotificationPackageName = null;
- mDeferredNotificationUserId = 0;
- }
- }
-
- public void onActivityUnpinned(ComponentName topPipActivity, int userId) {
- // Unregister for changes to the previously PiP'ed package
- unregisterAppOpsListener();
-
- // Reset the deferred notification package
- mDeferredNotificationPackageName = null;
- mDeferredNotificationUserId = 0;
-
- if (topPipActivity != null) {
- // onActivityUnpinned() is only called after the transition is complete, so we don't
- // need to defer until the animation ends to update the notification
- onActivityPinned(topPipActivity.getPackageName(), userId,
- false /* deferUntilAnimationEnds */);
- } else {
- mNotificationManager.cancel(NOTIFICATION_TAG, NOTIFICATION_ID);
- }
- }
-
- /**
- * Builds and shows the notification for the given app.
- */
- private void showNotificationForApp(String packageName, int userId) {
- // Build a new notification
- try {
- final UserHandle user = UserHandle.of(userId);
- final Context userContext = mContext.createPackageContextAsUser(
- mContext.getPackageName(), 0, user);
- final Notification.Builder builder =
- new Notification.Builder(userContext, NotificationChannels.GENERAL)
- .setLocalOnly(true)
- .setOngoing(true)
- .setSmallIcon(R.drawable.pip_notification_icon)
- .setColor(mContext.getColor(
- com.android.internal.R.color.system_notification_accent_color));
- if (updateNotificationForApp(builder, packageName, user)) {
- SystemUI.overrideNotificationAppName(mContext, builder);
-
- // Show the new notification
- mNotificationManager.notify(NOTIFICATION_TAG, NOTIFICATION_ID, builder.build());
- }
- } catch (NameNotFoundException e) {
- Log.e(TAG, "Could not show notification for application", e);
- }
- }
-
- /**
- * Updates the notification builder with app-specific information, returning whether it was
- * successful.
- */
- private boolean updateNotificationForApp(Notification.Builder builder, String packageName,
- UserHandle user) throws NameNotFoundException {
- final PackageManager pm = mContext.getPackageManager();
- final ApplicationInfo appInfo;
- try {
- appInfo = pm.getApplicationInfoAsUser(packageName, 0, user.getIdentifier());
- } catch (NameNotFoundException e) {
- Log.e(TAG, "Could not update notification for application", e);
- return false;
- }
-
- if (appInfo != null) {
- final String appName = pm.getUserBadgedLabel(pm.getApplicationLabel(appInfo), user)
- .toString();
- final String message = mContext.getString(R.string.pip_notification_message, appName);
- final Intent settingsIntent = new Intent(ACTION_PICTURE_IN_PICTURE_SETTINGS,
- Uri.fromParts("package", packageName, null));
- settingsIntent.putExtra(Intent.EXTRA_USER_HANDLE, user);
- settingsIntent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK);
-
- final Drawable iconDrawable = mIconDrawableFactory.getBadgedIcon(appInfo);
- builder.setContentTitle(mContext.getString(R.string.pip_notification_title, appName))
- .setContentText(message)
- .setContentIntent(PendingIntent.getActivityAsUser(mContext, packageName.hashCode(),
- settingsIntent, FLAG_CANCEL_CURRENT, null, user))
- .setStyle(new Notification.BigTextStyle().bigText(message))
- .setLargeIcon(createBitmap(iconDrawable).createAshmemBitmap());
- return true;
- }
- return false;
- }
-
- private void registerAppOpsListener(String packageName) {
- mAppOpsManager.startWatchingMode(OP_PICTURE_IN_PICTURE, packageName,
- mAppOpsChangedListener);
- }
-
- private void unregisterAppOpsListener() {
- mAppOpsManager.stopWatchingMode(mAppOpsChangedListener);
- }
-
- /**
- * Bakes a drawable into a bitmap.
- */
- private Bitmap createBitmap(Drawable d) {
- Bitmap bitmap = Bitmap.createBitmap(d.getIntrinsicWidth(), d.getIntrinsicHeight(),
- Config.ARGB_8888);
- Canvas c = new Canvas(bitmap);
- d.setBounds(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
- d.draw(c);
- c.setBitmap(null);
- return bitmap;
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java
index 0b7b6d5..927a49c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java
@@ -18,11 +18,6 @@
import static android.app.StatusBarManager.DISABLE2_QUICK_SETTINGS;
-import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_QS_DATE;
-
-import android.app.ActivityManager;
-import android.app.AlarmManager;
-import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
@@ -31,7 +26,6 @@
import android.graphics.drawable.Drawable;
import android.graphics.drawable.RippleDrawable;
import android.os.UserManager;
-import android.provider.AlarmClock;
import android.support.annotation.Nullable;
import android.support.annotation.VisibleForTesting;
import android.util.AttributeSet;
@@ -39,24 +33,19 @@
import android.view.View.OnClickListener;
import android.widget.FrameLayout;
import android.widget.ImageView;
-import android.widget.TextView;
import android.widget.Toast;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto;
-import com.android.keyguard.KeyguardStatusView;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.settingslib.Utils;
import com.android.settingslib.drawable.UserIconDrawable;
import com.android.systemui.Dependency;
-import com.android.systemui.FontSizeUtils;
import com.android.systemui.R;
import com.android.systemui.R.dimen;
-import com.android.systemui.R.id;
import com.android.systemui.SysUiServiceProvider;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.qs.TouchAnimator.Builder;
-import com.android.systemui.qs.TouchAnimator.ListenerAdapter;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.phone.ExpandableIndicator;
import com.android.systemui.statusbar.phone.MultiUserSwitch;
@@ -65,8 +54,6 @@
import com.android.systemui.statusbar.policy.NetworkController;
import com.android.systemui.statusbar.policy.NetworkController.EmergencyListener;
import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
-import com.android.systemui.statusbar.policy.NextAlarmController;
-import com.android.systemui.statusbar.policy.NextAlarmController.NextAlarmChangeCallback;
import com.android.systemui.statusbar.policy.UserInfoController;
import com.android.systemui.statusbar.policy.UserInfoController.OnUserInfoChangedListener;
import com.android.systemui.tuner.TunerService;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index ca9a553..06dfd18 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -709,7 +709,6 @@
(event.launchTask == null || launchToTaskId != event.launchTask.key.id)) {
ActivityManagerWrapper am = ActivityManagerWrapper.getInstance();
am.cancelWindowTransition(launchState.launchedToTaskId);
- am.cancelThumbnailTransition(getTaskId());
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/component/ExpandPipEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/component/ExpandPipEvent.java
index 8fe4975..37266f6 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/events/component/ExpandPipEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/component/ExpandPipEvent.java
@@ -22,5 +22,4 @@
* This is sent when the PiP should be expanded due to being relaunched.
*/
public class ExpandPipEvent extends EventBus.Event {
- public final boolean clearThumbnailWindows = true;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationEntryManager.java
new file mode 100644
index 0000000..6bbd09f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationEntryManager.java
@@ -0,0 +1,960 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.systemui.statusbar;
+
+import static com.android.systemui.statusbar.NotificationRemoteInputManager.ENABLE_REMOTE_INPUT;
+import static com.android.systemui.statusbar.NotificationRemoteInputManager
+ .FORCE_REMOTE_INPUT_HISTORY;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.database.ContentObserver;
+import android.os.Build;
+import android.os.PowerManager;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.provider.Settings;
+import android.service.notification.NotificationListenerService;
+import android.service.notification.NotificationStats;
+import android.service.notification.StatusBarNotification;
+import android.util.ArraySet;
+import android.util.EventLog;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.statusbar.IStatusBarService;
+import com.android.internal.util.NotificationMessagingUtil;
+import com.android.systemui.DejankUtils;
+import com.android.systemui.Dependency;
+import com.android.systemui.Dumpable;
+import com.android.systemui.EventLogTags;
+import com.android.systemui.ForegroundServiceController;
+import com.android.systemui.R;
+import com.android.systemui.UiOffloadThread;
+import com.android.systemui.recents.misc.SystemServicesProxy;
+import com.android.systemui.statusbar.notification.InflationException;
+import com.android.systemui.statusbar.notification.NotificationInflater;
+import com.android.systemui.statusbar.notification.RowInflaterTask;
+import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.phone.NotificationGroupManager;
+import com.android.systemui.statusbar.phone.StatusBar;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.util.leak.LeakDetector;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * NotificationEntryManager is responsible for the adding, removing, and updating of notifications.
+ * It also handles tasks such as their inflation and their interaction with other
+ * Notification.*Manager objects.
+ */
+public class NotificationEntryManager implements Dumpable, NotificationInflater.InflationCallback,
+ ExpandableNotificationRow.ExpansionLogger, NotificationUpdateHandler,
+ VisualStabilityManager.Callback {
+ private static final String TAG = "NotificationEntryManager";
+ protected static final boolean DEBUG = false;
+ protected static final boolean ENABLE_HEADS_UP = true;
+ protected static final String SETTING_HEADS_UP_TICKER = "ticker_gets_heads_up";
+
+ protected final NotificationMessagingUtil mMessagingUtil;
+ protected final Context mContext;
+ protected final HashMap<String, NotificationData.Entry> mPendingNotifications = new HashMap<>();
+ protected final NotificationClicker mNotificationClicker = new NotificationClicker();
+ protected final ArraySet<NotificationData.Entry> mHeadsUpEntriesToRemoveOnSwitch =
+ new ArraySet<>();
+
+ // Dependencies:
+ protected final NotificationLockscreenUserManager mLockscreenUserManager =
+ Dependency.get(NotificationLockscreenUserManager.class);
+ protected final NotificationGroupManager mGroupManager =
+ Dependency.get(NotificationGroupManager.class);
+ protected final NotificationGutsManager mGutsManager =
+ Dependency.get(NotificationGutsManager.class);
+ protected final NotificationRemoteInputManager mRemoteInputManager =
+ Dependency.get(NotificationRemoteInputManager.class);
+ protected final NotificationMediaManager mMediaManager =
+ Dependency.get(NotificationMediaManager.class);
+ protected final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
+ protected final DeviceProvisionedController mDeviceProvisionedController =
+ Dependency.get(DeviceProvisionedController.class);
+ protected final VisualStabilityManager mVisualStabilityManager =
+ Dependency.get(VisualStabilityManager.class);
+ protected final UiOffloadThread mUiOffloadThread = Dependency.get(UiOffloadThread.class);
+ protected final ForegroundServiceController mForegroundServiceController =
+ Dependency.get(ForegroundServiceController.class);
+ protected final NotificationListener mNotificationListener =
+ Dependency.get(NotificationListener.class);
+
+ protected IStatusBarService mBarService;
+ protected NotificationPresenter mPresenter;
+ protected Callback mCallback;
+ protected PowerManager mPowerManager;
+ protected SystemServicesProxy mSystemServicesProxy;
+ protected NotificationListenerService.RankingMap mLatestRankingMap;
+ protected HeadsUpManager mHeadsUpManager;
+ protected NotificationData mNotificationData;
+ protected ContentObserver mHeadsUpObserver;
+ protected boolean mUseHeadsUp = false;
+ protected boolean mDisableNotificationAlerts;
+ protected NotificationListContainer mListContainer;
+
+ private final class NotificationClicker implements View.OnClickListener {
+
+ @Override
+ public void onClick(final View v) {
+ if (!(v instanceof ExpandableNotificationRow)) {
+ Log.e(TAG, "NotificationClicker called on a view that is not a notification row.");
+ return;
+ }
+
+ mPresenter.wakeUpIfDozing(SystemClock.uptimeMillis(), v);
+
+ final ExpandableNotificationRow row = (ExpandableNotificationRow) v;
+ final StatusBarNotification sbn = row.getStatusBarNotification();
+ if (sbn == null) {
+ Log.e(TAG, "NotificationClicker called on an unclickable notification,");
+ return;
+ }
+
+ // Check if the notification is displaying the menu, if so slide notification back
+ if (row.getProvider() != null && row.getProvider().isMenuVisible()) {
+ row.animateTranslateNotification(0);
+ return;
+ }
+
+ // Mark notification for one frame.
+ row.setJustClicked(true);
+ DejankUtils.postAfterTraversal(() -> row.setJustClicked(false));
+
+ mCallback.onNotificationClicked(sbn, row);
+ }
+
+ public void register(ExpandableNotificationRow row, StatusBarNotification sbn) {
+ Notification notification = sbn.getNotification();
+ if (notification.contentIntent != null || notification.fullScreenIntent != null) {
+ row.setOnClickListener(this);
+ } else {
+ row.setOnClickListener(null);
+ }
+ }
+ }
+
+ private final DeviceProvisionedController.DeviceProvisionedListener
+ mDeviceProvisionedListener =
+ new DeviceProvisionedController.DeviceProvisionedListener() {
+ @Override
+ public void onDeviceProvisionedChanged() {
+ updateNotifications();
+ }
+ };
+
+ public NotificationListenerService.RankingMap getLatestRankingMap() {
+ return mLatestRankingMap;
+ }
+
+ public void setLatestRankingMap(NotificationListenerService.RankingMap latestRankingMap) {
+ mLatestRankingMap = latestRankingMap;
+ }
+
+ public void setDisableNotificationAlerts(boolean disableNotificationAlerts) {
+ mDisableNotificationAlerts = disableNotificationAlerts;
+ mHeadsUpObserver.onChange(true);
+ }
+
+ public void destroy() {
+ mDeviceProvisionedController.removeCallback(mDeviceProvisionedListener);
+ }
+
+ public void onHeadsUpStateChanged(NotificationData.Entry entry, boolean isHeadsUp) {
+ if (!isHeadsUp && mHeadsUpEntriesToRemoveOnSwitch.contains(entry)) {
+ removeNotification(entry.key, getLatestRankingMap());
+ mHeadsUpEntriesToRemoveOnSwitch.remove(entry);
+ if (mHeadsUpEntriesToRemoveOnSwitch.isEmpty()) {
+ setLatestRankingMap(null);
+ }
+ } else {
+ updateNotificationRanking(null);
+ }
+ }
+
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println("NotificationEntryManager state:");
+ pw.print(" mPendingNotifications=");
+ if (mPendingNotifications.size() == 0) {
+ pw.println("null");
+ } else {
+ for (NotificationData.Entry entry : mPendingNotifications.values()) {
+ pw.println(entry.notification);
+ }
+ }
+ pw.print(" mUseHeadsUp=");
+ pw.println(mUseHeadsUp);
+ }
+
+ public NotificationEntryManager(Context context) {
+ mContext = context;
+ mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+ mBarService = IStatusBarService.Stub.asInterface(
+ ServiceManager.getService(Context.STATUS_BAR_SERVICE));
+ mMessagingUtil = new NotificationMessagingUtil(context);
+ mSystemServicesProxy = SystemServicesProxy.getInstance(mContext);
+ }
+
+ public void setUpWithPresenter(NotificationPresenter presenter,
+ NotificationListContainer listContainer, Callback callback,
+ HeadsUpManager headsUpManager) {
+ mPresenter = presenter;
+ mCallback = callback;
+ mNotificationData = new NotificationData(presenter);
+ mHeadsUpManager = headsUpManager;
+ mNotificationData.setHeadsUpManager(mHeadsUpManager);
+ mListContainer = listContainer;
+
+ mHeadsUpObserver = new ContentObserver(mPresenter.getHandler()) {
+ @Override
+ public void onChange(boolean selfChange) {
+ boolean wasUsing = mUseHeadsUp;
+ mUseHeadsUp = ENABLE_HEADS_UP && !mDisableNotificationAlerts
+ && Settings.Global.HEADS_UP_OFF != Settings.Global.getInt(
+ mContext.getContentResolver(),
+ Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED,
+ Settings.Global.HEADS_UP_OFF);
+ Log.d(TAG, "heads up is " + (mUseHeadsUp ? "enabled" : "disabled"));
+ if (wasUsing != mUseHeadsUp) {
+ if (!mUseHeadsUp) {
+ Log.d(TAG,
+ "dismissing any existing heads up notification on disable event");
+ mHeadsUpManager.releaseAllImmediately();
+ }
+ }
+ }
+ };
+
+ if (ENABLE_HEADS_UP) {
+ mContext.getContentResolver().registerContentObserver(
+ Settings.Global.getUriFor(Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED),
+ true,
+ mHeadsUpObserver);
+ mContext.getContentResolver().registerContentObserver(
+ Settings.Global.getUriFor(SETTING_HEADS_UP_TICKER), true,
+ mHeadsUpObserver);
+ }
+
+ mDeviceProvisionedController.addCallback(mDeviceProvisionedListener);
+
+ mHeadsUpObserver.onChange(true); // set up
+ }
+
+ public NotificationData getNotificationData() {
+ return mNotificationData;
+ }
+
+ public ExpandableNotificationRow.LongPressListener getNotificationLongClicker() {
+ return mGutsManager::openGuts;
+ }
+
+ @Override
+ public void logNotificationExpansion(String key, boolean userAction, boolean expanded) {
+ mUiOffloadThread.submit(() -> {
+ try {
+ mBarService.onNotificationExpansionChanged(key, userAction, expanded);
+ } catch (RemoteException e) {
+ // Ignore.
+ }
+ });
+ }
+
+ @Override
+ public void onReorderingAllowed() {
+ updateNotifications();
+ }
+
+ private boolean shouldSuppressFullScreenIntent(String key) {
+ if (mPresenter.isDeviceInVrMode()) {
+ return true;
+ }
+
+ if (mPowerManager.isInteractive()) {
+ return mNotificationData.shouldSuppressScreenOn(key);
+ } else {
+ return mNotificationData.shouldSuppressScreenOff(key);
+ }
+ }
+
+ private void inflateViews(NotificationData.Entry entry, ViewGroup parent) {
+ PackageManager pmUser = StatusBar.getPackageManagerForUser(mContext,
+ entry.notification.getUser().getIdentifier());
+
+ final StatusBarNotification sbn = entry.notification;
+ if (entry.row != null) {
+ entry.reset();
+ updateNotification(entry, pmUser, sbn, entry.row);
+ } else {
+ new RowInflaterTask().inflate(mContext, parent, entry,
+ row -> {
+ bindRow(entry, pmUser, sbn, row);
+ updateNotification(entry, pmUser, sbn, row);
+ });
+ }
+ }
+
+ private void bindRow(NotificationData.Entry entry, PackageManager pmUser,
+ StatusBarNotification sbn, ExpandableNotificationRow row) {
+ row.setExpansionLogger(this, entry.notification.getKey());
+ row.setGroupManager(mGroupManager);
+ row.setHeadsUpManager(mHeadsUpManager);
+ row.setOnExpandClickListener(mPresenter);
+ row.setInflationCallback(this);
+ row.setLongPressListener(getNotificationLongClicker());
+ mRemoteInputManager.bindRow(row);
+
+ // Get the app name.
+ // Note that Notification.Builder#bindHeaderAppName has similar logic
+ // but since this field is used in the guts, it must be accurate.
+ // Therefore we will only show the application label, or, failing that, the
+ // package name. No substitutions.
+ final String pkg = sbn.getPackageName();
+ String appname = pkg;
+ try {
+ final ApplicationInfo info = pmUser.getApplicationInfo(pkg,
+ PackageManager.MATCH_UNINSTALLED_PACKAGES
+ | PackageManager.MATCH_DISABLED_COMPONENTS);
+ if (info != null) {
+ appname = String.valueOf(pmUser.getApplicationLabel(info));
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ // Do nothing
+ }
+ row.setAppName(appname);
+ row.setOnDismissRunnable(() ->
+ performRemoveNotification(row.getStatusBarNotification()));
+ row.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
+ if (ENABLE_REMOTE_INPUT) {
+ row.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS);
+ }
+
+ mCallback.onBindRow(entry, pmUser, sbn, row);
+ }
+
+ public void performRemoveNotification(StatusBarNotification n) {
+ NotificationData.Entry entry = mNotificationData.get(n.getKey());
+ mRemoteInputManager.onPerformRemoveNotification(n, entry);
+ final String pkg = n.getPackageName();
+ final String tag = n.getTag();
+ final int id = n.getId();
+ final int userId = n.getUserId();
+ try {
+ int dismissalSurface = NotificationStats.DISMISSAL_SHADE;
+ if (isHeadsUp(n.getKey())) {
+ dismissalSurface = NotificationStats.DISMISSAL_PEEK;
+ } else if (mListContainer.hasPulsingNotifications()) {
+ dismissalSurface = NotificationStats.DISMISSAL_AOD;
+ }
+ mBarService.onNotificationClear(pkg, tag, id, userId, n.getKey(), dismissalSurface);
+ removeNotification(n.getKey(), null);
+
+ } catch (RemoteException ex) {
+ // system process is dead if we're here.
+ }
+
+ mCallback.onPerformRemoveNotification(n);
+ }
+
+ /**
+ * Cancel this notification and tell the StatusBarManagerService / NotificationManagerService
+ * about the failure.
+ *
+ * WARNING: this will call back into us. Don't hold any locks.
+ */
+ void handleNotificationError(StatusBarNotification n, String message) {
+ removeNotification(n.getKey(), null);
+ try {
+ mBarService.onNotificationError(n.getPackageName(), n.getTag(), n.getId(), n.getUid(),
+ n.getInitialPid(), message, n.getUserId());
+ } catch (RemoteException ex) {
+ // The end is nigh.
+ }
+ }
+
+ private void abortExistingInflation(String key) {
+ if (mPendingNotifications.containsKey(key)) {
+ NotificationData.Entry entry = mPendingNotifications.get(key);
+ entry.abortTask();
+ mPendingNotifications.remove(key);
+ }
+ NotificationData.Entry addedEntry = mNotificationData.get(key);
+ if (addedEntry != null) {
+ addedEntry.abortTask();
+ }
+ }
+
+ @Override
+ public void handleInflationException(StatusBarNotification notification, Exception e) {
+ handleNotificationError(notification, e.getMessage());
+ }
+
+ private void addEntry(NotificationData.Entry shadeEntry) {
+ boolean isHeadsUped = shouldPeek(shadeEntry);
+ if (isHeadsUped) {
+ mHeadsUpManager.showNotification(shadeEntry);
+ // Mark as seen immediately
+ setNotificationShown(shadeEntry.notification);
+ }
+ addNotificationViews(shadeEntry);
+ mCallback.onNotificationAdded(shadeEntry);
+ }
+
+ @Override
+ public void onAsyncInflationFinished(NotificationData.Entry entry) {
+ mPendingNotifications.remove(entry.key);
+ // If there was an async task started after the removal, we don't want to add it back to
+ // the list, otherwise we might get leaks.
+ boolean isNew = mNotificationData.get(entry.key) == null;
+ if (isNew && !entry.row.isRemoved()) {
+ addEntry(entry);
+ } else if (!isNew && entry.row.hasLowPriorityStateUpdated()) {
+ mVisualStabilityManager.onLowPriorityUpdated(entry);
+ mPresenter.updateNotificationViews();
+ }
+ entry.row.setLowPriorityStateUpdated(false);
+ }
+
+ @Override
+ public void removeNotification(String key, NotificationListenerService.RankingMap ranking) {
+ boolean deferRemoval = false;
+ abortExistingInflation(key);
+ if (mHeadsUpManager.isHeadsUp(key)) {
+ // A cancel() in response to a remote input shouldn't be delayed, as it makes the
+ // sending look longer than it takes.
+ // Also we should not defer the removal if reordering isn't allowed since otherwise
+ // some notifications can't disappear before the panel is closed.
+ boolean ignoreEarliestRemovalTime = mRemoteInputManager.getController().isSpinning(key)
+ && !FORCE_REMOTE_INPUT_HISTORY
+ || !mVisualStabilityManager.isReorderingAllowed();
+ deferRemoval = !mHeadsUpManager.removeNotification(key, ignoreEarliestRemovalTime);
+ }
+ mMediaManager.onNotificationRemoved(key);
+
+ NotificationData.Entry entry = mNotificationData.get(key);
+ if (FORCE_REMOTE_INPUT_HISTORY && mRemoteInputManager.getController().isSpinning(key)
+ && entry.row != null && !entry.row.isDismissed()) {
+ StatusBarNotification sbn = entry.notification;
+
+ Notification.Builder b = Notification.Builder
+ .recoverBuilder(mContext, sbn.getNotification().clone());
+ CharSequence[] oldHistory = sbn.getNotification().extras
+ .getCharSequenceArray(Notification.EXTRA_REMOTE_INPUT_HISTORY);
+ CharSequence[] newHistory;
+ if (oldHistory == null) {
+ newHistory = new CharSequence[1];
+ } else {
+ newHistory = new CharSequence[oldHistory.length + 1];
+ System.arraycopy(oldHistory, 0, newHistory, 1, oldHistory.length);
+ }
+ newHistory[0] = String.valueOf(entry.remoteInputText);
+ b.setRemoteInputHistory(newHistory);
+
+ Notification newNotification = b.build();
+
+ // Undo any compatibility view inflation
+ newNotification.contentView = sbn.getNotification().contentView;
+ newNotification.bigContentView = sbn.getNotification().bigContentView;
+ newNotification.headsUpContentView = sbn.getNotification().headsUpContentView;
+
+ StatusBarNotification newSbn = new StatusBarNotification(sbn.getPackageName(),
+ sbn.getOpPkg(),
+ sbn.getId(), sbn.getTag(), sbn.getUid(), sbn.getInitialPid(),
+ newNotification, sbn.getUser(), sbn.getOverrideGroupKey(), sbn.getPostTime());
+ boolean updated = false;
+ try {
+ updateNotificationInternal(newSbn, null);
+ updated = true;
+ } catch (InflationException e) {
+ deferRemoval = false;
+ }
+ if (updated) {
+ Log.w(TAG, "Keeping notification around after sending remote input "+ entry.key);
+ mRemoteInputManager.getKeysKeptForRemoteInput().add(entry.key);
+ return;
+ }
+ }
+ if (deferRemoval) {
+ mLatestRankingMap = ranking;
+ mHeadsUpEntriesToRemoveOnSwitch.add(mHeadsUpManager.getEntry(key));
+ return;
+ }
+
+ if (mRemoteInputManager.onRemoveNotification(entry)) {
+ mLatestRankingMap = ranking;
+ return;
+ }
+
+ if (entry != null && mGutsManager.getExposedGuts() != null
+ && mGutsManager.getExposedGuts() == entry.row.getGuts()
+ && entry.row.getGuts() != null && !entry.row.getGuts().isLeavebehind()) {
+ Log.w(TAG, "Keeping notification because it's showing guts. " + key);
+ mLatestRankingMap = ranking;
+ mGutsManager.setKeyToRemoveOnGutsClosed(key);
+ return;
+ }
+
+ if (entry != null) {
+ mForegroundServiceController.removeNotification(entry.notification);
+ }
+
+ if (entry != null && entry.row != null) {
+ entry.row.setRemoved();
+ mListContainer.cleanUpViewState(entry.row);
+ }
+ // Let's remove the children if this was a summary
+ handleGroupSummaryRemoved(key);
+ StatusBarNotification old = removeNotificationViews(key, ranking);
+
+ mCallback.onNotificationRemoved(key, old);
+ }
+
+ private StatusBarNotification removeNotificationViews(String key,
+ NotificationListenerService.RankingMap ranking) {
+ NotificationData.Entry entry = mNotificationData.remove(key, ranking);
+ if (entry == null) {
+ Log.w(TAG, "removeNotification for unknown key: " + key);
+ return null;
+ }
+ updateNotifications();
+ Dependency.get(LeakDetector.class).trackGarbage(entry);
+ return entry.notification;
+ }
+
+ /**
+ * Ensures that the group children are cancelled immediately when the group summary is cancelled
+ * instead of waiting for the notification manager to send all cancels. Otherwise this could
+ * lead to flickers.
+ *
+ * This also ensures that the animation looks nice and only consists of a single disappear
+ * animation instead of multiple.
+ * @param key the key of the notification was removed
+ *
+ */
+ private void handleGroupSummaryRemoved(String key) {
+ NotificationData.Entry entry = mNotificationData.get(key);
+ if (entry != null && entry.row != null
+ && entry.row.isSummaryWithChildren()) {
+ if (entry.notification.getOverrideGroupKey() != null && !entry.row.isDismissed()) {
+ // We don't want to remove children for autobundled notifications as they are not
+ // always cancelled. We only remove them if they were dismissed by the user.
+ return;
+ }
+ List<ExpandableNotificationRow> notificationChildren =
+ entry.row.getNotificationChildren();
+ for (int i = 0; i < notificationChildren.size(); i++) {
+ ExpandableNotificationRow row = notificationChildren.get(i);
+ if ((row.getStatusBarNotification().getNotification().flags
+ & Notification.FLAG_FOREGROUND_SERVICE) != 0) {
+ // the child is a foreground service notification which we can't remove!
+ continue;
+ }
+ row.setKeepInParent(true);
+ // we need to set this state earlier as otherwise we might generate some weird
+ // animations
+ row.setRemoved();
+ }
+ }
+ }
+
+ public void updateNotificationsOnDensityOrFontScaleChanged() {
+ ArrayList<NotificationData.Entry> activeNotifications =
+ mNotificationData.getActiveNotifications();
+ for (int i = 0; i < activeNotifications.size(); i++) {
+ NotificationData.Entry entry = activeNotifications.get(i);
+ boolean exposedGuts = mGutsManager.getExposedGuts() != null
+ && entry.row.getGuts() == mGutsManager.getExposedGuts();
+ entry.row.onDensityOrFontScaleChanged();
+ if (exposedGuts) {
+ mGutsManager.setExposedGuts(entry.row.getGuts());
+ mGutsManager.bindGuts(entry.row);
+ }
+ }
+ }
+
+ private void updateNotification(NotificationData.Entry entry, PackageManager pmUser,
+ StatusBarNotification sbn, ExpandableNotificationRow row) {
+ row.setNeedsRedaction(mLockscreenUserManager.needsRedaction(entry));
+ boolean isLowPriority = mNotificationData.isAmbient(sbn.getKey());
+ boolean isUpdate = mNotificationData.get(entry.key) != null;
+ boolean wasLowPriority = row.isLowPriority();
+ row.setIsLowPriority(isLowPriority);
+ row.setLowPriorityStateUpdated(isUpdate && (wasLowPriority != isLowPriority));
+ // bind the click event to the content area
+ mNotificationClicker.register(row, sbn);
+
+ // Extract target SDK version.
+ try {
+ ApplicationInfo info = pmUser.getApplicationInfo(sbn.getPackageName(), 0);
+ entry.targetSdk = info.targetSdkVersion;
+ } catch (PackageManager.NameNotFoundException ex) {
+ Log.e(TAG, "Failed looking up ApplicationInfo for " + sbn.getPackageName(), ex);
+ }
+ row.setLegacy(entry.targetSdk >= Build.VERSION_CODES.GINGERBREAD
+ && entry.targetSdk < Build.VERSION_CODES.LOLLIPOP);
+ entry.setIconTag(R.id.icon_is_pre_L, entry.targetSdk < Build.VERSION_CODES.LOLLIPOP);
+ entry.autoRedacted = entry.notification.getNotification().publicVersion == null;
+
+ entry.row = row;
+ entry.row.setOnActivatedListener(mPresenter);
+
+ boolean useIncreasedCollapsedHeight = mMessagingUtil.isImportantMessaging(sbn,
+ mNotificationData.getImportance(sbn.getKey()));
+ boolean useIncreasedHeadsUp = useIncreasedCollapsedHeight
+ && !mPresenter.isPresenterFullyCollapsed();
+ row.setUseIncreasedCollapsedHeight(useIncreasedCollapsedHeight);
+ row.setUseIncreasedHeadsUpHeight(useIncreasedHeadsUp);
+ row.updateNotification(entry);
+ }
+
+
+ protected void addNotificationViews(NotificationData.Entry entry) {
+ if (entry == null) {
+ return;
+ }
+ // Add the expanded view and icon.
+ mNotificationData.add(entry);
+ updateNotifications();
+ }
+
+ protected NotificationData.Entry createNotificationViews(StatusBarNotification sbn)
+ throws InflationException {
+ if (DEBUG) {
+ Log.d(TAG, "createNotificationViews(notification=" + sbn);
+ }
+ NotificationData.Entry entry = new NotificationData.Entry(sbn);
+ Dependency.get(LeakDetector.class).trackInstance(entry);
+ entry.createIcons(mContext, sbn);
+ // Construct the expanded view.
+ inflateViews(entry, mListContainer.getViewParentForNotification(entry));
+ return entry;
+ }
+
+ private void addNotificationInternal(StatusBarNotification notification,
+ NotificationListenerService.RankingMap ranking) throws InflationException {
+ String key = notification.getKey();
+ if (DEBUG) Log.d(TAG, "addNotification key=" + key);
+
+ mNotificationData.updateRanking(ranking);
+ NotificationData.Entry shadeEntry = createNotificationViews(notification);
+ boolean isHeadsUped = shouldPeek(shadeEntry);
+ if (!isHeadsUped && notification.getNotification().fullScreenIntent != null) {
+ if (shouldSuppressFullScreenIntent(key)) {
+ if (DEBUG) {
+ Log.d(TAG, "No Fullscreen intent: suppressed by DND: " + key);
+ }
+ } else if (mNotificationData.getImportance(key)
+ < NotificationManager.IMPORTANCE_HIGH) {
+ if (DEBUG) {
+ Log.d(TAG, "No Fullscreen intent: not important enough: "
+ + key);
+ }
+ } else {
+ // Stop screensaver if the notification has a fullscreen intent.
+ // (like an incoming phone call)
+ SystemServicesProxy.getInstance(mContext).awakenDreamsAsync();
+
+ // not immersive & a fullscreen alert should be shown
+ if (DEBUG)
+ Log.d(TAG, "Notification has fullScreenIntent; sending fullScreenIntent");
+ try {
+ EventLog.writeEvent(EventLogTags.SYSUI_FULLSCREEN_NOTIFICATION,
+ key);
+ notification.getNotification().fullScreenIntent.send();
+ shadeEntry.notifyFullScreenIntentLaunched();
+ mMetricsLogger.count("note_fullscreen", 1);
+ } catch (PendingIntent.CanceledException e) {
+ }
+ }
+ }
+ abortExistingInflation(key);
+
+ mForegroundServiceController.addNotification(notification,
+ mNotificationData.getImportance(key));
+
+ mPendingNotifications.put(key, shadeEntry);
+ }
+
+ @Override
+ public void addNotification(StatusBarNotification notification,
+ NotificationListenerService.RankingMap ranking) {
+ try {
+ addNotificationInternal(notification, ranking);
+ } catch (InflationException e) {
+ handleInflationException(notification, e);
+ }
+ }
+
+ private boolean alertAgain(NotificationData.Entry oldEntry, Notification newNotification) {
+ return oldEntry == null || !oldEntry.hasInterrupted()
+ || (newNotification.flags & Notification.FLAG_ONLY_ALERT_ONCE) == 0;
+ }
+
+ private void updateNotificationInternal(StatusBarNotification notification,
+ NotificationListenerService.RankingMap ranking) throws InflationException {
+ if (DEBUG) Log.d(TAG, "updateNotification(" + notification + ")");
+
+ final String key = notification.getKey();
+ abortExistingInflation(key);
+ NotificationData.Entry entry = mNotificationData.get(key);
+ if (entry == null) {
+ return;
+ }
+ mHeadsUpEntriesToRemoveOnSwitch.remove(entry);
+ mRemoteInputManager.onUpdateNotification(entry);
+
+ if (key.equals(mGutsManager.getKeyToRemoveOnGutsClosed())) {
+ mGutsManager.setKeyToRemoveOnGutsClosed(null);
+ Log.w(TAG, "Notification that was kept for guts was updated. " + key);
+ }
+
+ Notification n = notification.getNotification();
+ mNotificationData.updateRanking(ranking);
+
+ final StatusBarNotification oldNotification = entry.notification;
+ entry.notification = notification;
+ mGroupManager.onEntryUpdated(entry, oldNotification);
+
+ entry.updateIcons(mContext, notification);
+ inflateViews(entry, mListContainer.getViewParentForNotification(entry));
+
+ mForegroundServiceController.updateNotification(notification,
+ mNotificationData.getImportance(key));
+
+ boolean shouldPeek = shouldPeek(entry, notification);
+ boolean alertAgain = alertAgain(entry, n);
+
+ updateHeadsUp(key, entry, shouldPeek, alertAgain);
+ updateNotifications();
+
+ if (!notification.isClearable()) {
+ // The user may have performed a dismiss action on the notification, since it's
+ // not clearable we should snap it back.
+ mListContainer.snapViewIfNeeded(entry.row);
+ }
+
+ if (DEBUG) {
+ // Is this for you?
+ boolean isForCurrentUser = mPresenter.isNotificationForCurrentProfiles(notification);
+ Log.d(TAG, "notification is " + (isForCurrentUser ? "" : "not ") + "for you");
+ }
+
+ mCallback.onNotificationUpdated(notification);
+ }
+
+ @Override
+ public void updateNotification(StatusBarNotification notification,
+ NotificationListenerService.RankingMap ranking) {
+ try {
+ updateNotificationInternal(notification, ranking);
+ } catch (InflationException e) {
+ handleInflationException(notification, e);
+ }
+ }
+
+ public void updateNotifications() {
+ mNotificationData.filterAndSort();
+
+ mPresenter.updateNotificationViews();
+ }
+
+ public void updateNotificationRanking(NotificationListenerService.RankingMap ranking) {
+ mNotificationData.updateRanking(ranking);
+ updateNotifications();
+ }
+
+ protected boolean shouldPeek(NotificationData.Entry entry) {
+ return shouldPeek(entry, entry.notification);
+ }
+
+ public boolean shouldPeek(NotificationData.Entry entry, StatusBarNotification sbn) {
+ if (!mUseHeadsUp || mPresenter.isDeviceInVrMode()) {
+ if (DEBUG) Log.d(TAG, "No peeking: no huns or vr mode");
+ return false;
+ }
+
+ if (mNotificationData.shouldFilterOut(sbn)) {
+ if (DEBUG) Log.d(TAG, "No peeking: filtered notification: " + sbn.getKey());
+ return false;
+ }
+
+ boolean inUse = mPowerManager.isScreenOn() && !mSystemServicesProxy.isDreaming();
+
+ if (!inUse && !mPresenter.isDozing()) {
+ if (DEBUG) {
+ Log.d(TAG, "No peeking: not in use: " + sbn.getKey());
+ }
+ return false;
+ }
+
+ if (!mPresenter.isDozing() && mNotificationData.shouldSuppressScreenOn(sbn.getKey())) {
+ if (DEBUG) Log.d(TAG, "No peeking: suppressed by DND: " + sbn.getKey());
+ return false;
+ }
+
+ if (mPresenter.isDozing() && mNotificationData.shouldSuppressScreenOff(sbn.getKey())) {
+ if (DEBUG) Log.d(TAG, "No peeking: suppressed by DND: " + sbn.getKey());
+ return false;
+ }
+
+ if (entry.hasJustLaunchedFullScreenIntent()) {
+ if (DEBUG) Log.d(TAG, "No peeking: recent fullscreen: " + sbn.getKey());
+ return false;
+ }
+
+ if (isSnoozedPackage(sbn)) {
+ if (DEBUG) Log.d(TAG, "No peeking: snoozed package: " + sbn.getKey());
+ return false;
+ }
+
+ // Allow peeking for DEFAULT notifications only if we're on Ambient Display.
+ int importanceLevel = mPresenter.isDozing() ? NotificationManager.IMPORTANCE_DEFAULT
+ : NotificationManager.IMPORTANCE_HIGH;
+ if (mNotificationData.getImportance(sbn.getKey()) < importanceLevel) {
+ if (DEBUG) Log.d(TAG, "No peeking: unimportant notification: " + sbn.getKey());
+ return false;
+ }
+
+ // Don't peek notifications that are suppressed due to group alert behavior
+ if (sbn.isGroup() && sbn.getNotification().suppressAlertingDueToGrouping()) {
+ if (DEBUG) Log.d(TAG, "No peeking: suppressed due to group alert behavior");
+ return false;
+ }
+
+ if (!mCallback.shouldPeek(entry, sbn)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ protected void setNotificationShown(StatusBarNotification n) {
+ setNotificationsShown(new String[]{n.getKey()});
+ }
+
+ protected void setNotificationsShown(String[] keys) {
+ try {
+ mNotificationListener.setNotificationsShown(keys);
+ } catch (RuntimeException e) {
+ Log.d(TAG, "failed setNotificationsShown: ", e);
+ }
+ }
+
+ protected boolean isSnoozedPackage(StatusBarNotification sbn) {
+ return mHeadsUpManager.isSnoozed(sbn.getPackageName());
+ }
+
+ protected void updateHeadsUp(String key, NotificationData.Entry entry, boolean shouldPeek,
+ boolean alertAgain) {
+ final boolean wasHeadsUp = isHeadsUp(key);
+ if (wasHeadsUp) {
+ if (!shouldPeek) {
+ // We don't want this to be interrupting anymore, lets remove it
+ mHeadsUpManager.removeNotification(key, false /* ignoreEarliestRemovalTime */);
+ } else {
+ mHeadsUpManager.updateNotification(entry, alertAgain);
+ }
+ } else if (shouldPeek && alertAgain) {
+ // This notification was updated to be a heads-up, show it!
+ mHeadsUpManager.showNotification(entry);
+ }
+ }
+
+ protected boolean isHeadsUp(String key) {
+ return mHeadsUpManager.isHeadsUp(key);
+ }
+
+ /**
+ * Callback for NotificationEntryManager.
+ */
+ public interface Callback {
+
+ /**
+ * Called when a new entry is created.
+ *
+ * @param shadeEntry entry that was created
+ */
+ void onNotificationAdded(NotificationData.Entry shadeEntry);
+
+ /**
+ * Called when a notification was updated.
+ *
+ * @param notification notification that was updated
+ */
+ void onNotificationUpdated(StatusBarNotification notification);
+
+ /**
+ * Called when a notification was removed.
+ *
+ * @param key key of notification that was removed
+ * @param old StatusBarNotification of the notification before it was removed
+ */
+ void onNotificationRemoved(String key, StatusBarNotification old);
+
+
+ /**
+ * Called when a notification is clicked.
+ *
+ * @param sbn notification that was clicked
+ * @param row row for that notification
+ */
+ void onNotificationClicked(StatusBarNotification sbn, ExpandableNotificationRow row);
+
+ /**
+ * Called when a new notification and row is created.
+ *
+ * @param entry entry for the notification
+ * @param pmUser package manager for user
+ * @param sbn notification
+ * @param row row for the notification
+ */
+ void onBindRow(NotificationData.Entry entry, PackageManager pmUser,
+ StatusBarNotification sbn, ExpandableNotificationRow row);
+
+ /**
+ * Removes a notification immediately.
+ *
+ * @param statusBarNotification notification that is being removed
+ */
+ void onPerformRemoveNotification(StatusBarNotification statusBarNotification);
+
+ /**
+ * Returns true if NotificationEntryManager should peek this notification.
+ *
+ * @param entry entry of the notification that might be peeked
+ * @param sbn notification that might be peeked
+ * @return true if the notification should be peeked
+ */
+ boolean shouldPeek(NotificationData.Entry entry, StatusBarNotification sbn);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGutsManager.java
index 2e572e1..87ad6f6b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGutsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGutsManager.java
@@ -42,7 +42,6 @@
import com.android.systemui.Interpolators;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
import com.android.systemui.statusbar.phone.StatusBar;
-import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.stack.StackStateAnimator;
import java.io.FileDescriptor;
@@ -67,24 +66,22 @@
private final Set<String> mNonBlockablePkgs;
private final Context mContext;
private final AccessibilityManager mAccessibilityManager;
- private final NotificationLockscreenUserManager mLockscreenUserManager;
+
+ // Dependencies:
+ private final NotificationLockscreenUserManager mLockscreenUserManager =
+ Dependency.get(NotificationLockscreenUserManager.class);
// which notification is currently being longpress-examined by the user
private NotificationGuts mNotificationGutsExposed;
private NotificationMenuRowPlugin.MenuItem mGutsMenuItem;
- private NotificationPresenter mPresenter;
-
- // TODO: Create NotificationListContainer interface and use it instead of
- // NotificationStackScrollLayout here
- private NotificationStackScrollLayout mStackScroller;
+ protected NotificationPresenter mPresenter;
+ protected NotificationEntryManager mEntryManager;
+ private NotificationListContainer mListContainer;
private NotificationInfo.CheckSaveListener mCheckSaveListener;
private OnSettingsClickListener mOnSettingsClickListener;
private String mKeyToRemoveOnGutsClosed;
- public NotificationGutsManager(
- NotificationLockscreenUserManager lockscreenUserManager,
- Context context) {
- mLockscreenUserManager = lockscreenUserManager;
+ public NotificationGutsManager(Context context) {
mContext = context;
Resources res = context.getResources();
@@ -96,12 +93,13 @@
mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
}
- public void setUp(NotificationPresenter presenter,
- NotificationStackScrollLayout stackScroller,
+ public void setUpWithPresenter(NotificationPresenter presenter,
+ NotificationEntryManager entryManager, NotificationListContainer listContainer,
NotificationInfo.CheckSaveListener checkSaveListener,
OnSettingsClickListener onSettingsClickListener) {
mPresenter = presenter;
- mStackScroller = stackScroller;
+ mEntryManager = entryManager;
+ mListContainer = listContainer;
mCheckSaveListener = checkSaveListener;
mOnSettingsClickListener = onSettingsClickListener;
}
@@ -158,7 +156,7 @@
final NotificationGuts guts = row.getGuts();
guts.setClosedListener((NotificationGuts g) -> {
if (!g.willBeRemoved() && !row.isRemoved()) {
- mStackScroller.onHeightChanged(
+ mListContainer.onHeightChanged(
row, !mPresenter.isPresenterFullyCollapsed() /* needsAnimation */);
}
if (mNotificationGutsExposed == g) {
@@ -168,18 +166,18 @@
String key = sbn.getKey();
if (key.equals(mKeyToRemoveOnGutsClosed)) {
mKeyToRemoveOnGutsClosed = null;
- mPresenter.removeNotification(key, mPresenter.getLatestRankingMap());
+ mEntryManager.removeNotification(key, mEntryManager.getLatestRankingMap());
}
});
View gutsView = item.getGutsView();
if (gutsView instanceof NotificationSnooze) {
NotificationSnooze snoozeGuts = (NotificationSnooze) gutsView;
- snoozeGuts.setSnoozeListener(mStackScroller.getSwipeActionHelper());
+ snoozeGuts.setSnoozeListener(mListContainer.getSwipeActionHelper());
snoozeGuts.setStatusBarNotification(sbn);
snoozeGuts.setSnoozeOptions(row.getEntry().snoozeCriteria);
guts.setHeightChangedListener((NotificationGuts g) -> {
- mStackScroller.onHeightChanged(row, row.isShown() /* needsAnimation */);
+ mListContainer.onHeightChanged(row, row.isShown() /* needsAnimation */);
});
}
@@ -257,7 +255,7 @@
mNotificationGutsExposed.closeControls(removeLeavebehinds, removeControls, x, y, force);
}
if (resetMenu) {
- mStackScroller.resetExposedMenuView(false /* animate */, true /* force */);
+ mListContainer.resetExposedMenuView(false /* animate */, true /* force */);
}
}
@@ -350,7 +348,7 @@
!mAccessibilityManager.isTouchExplorationEnabled());
guts.setExposed(true /* exposed */, needsFalsingProtection);
row.closeRemoteInput();
- mStackScroller.onHeightChanged(row, true /* needsAnimation */);
+ mListContainer.onHeightChanged(row, true /* needsAnimation */);
mNotificationGutsExposed = guts;
mGutsMenuItem = item;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListContainer.java
new file mode 100644
index 0000000..43be44d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListContainer.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar;
+
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
+
+/**
+ * Interface representing the entity that contains notifications. It can have
+ * notification views added and removed from it, and will manage displaying them to the user.
+ */
+public interface NotificationListContainer {
+
+ /**
+ * Called when a child is being transferred.
+ *
+ * @param childTransferInProgress whether child transfer is in progress
+ */
+ void setChildTransferInProgress(boolean childTransferInProgress);
+
+ /**
+ * Change the position of child to a new location
+ *
+ * @param child the view to change the position for
+ * @param newIndex the new index
+ */
+ void changeViewPosition(View child, int newIndex);
+
+ /**
+ * Called when a child was added to a group.
+ *
+ * @param row row of the group child that was added
+ */
+ void notifyGroupChildAdded(View row);
+
+ /**
+ * Called when a child was removed from a group.
+ *
+ * @param row row of the child that was removed
+ * @param childrenContainer ViewGroup of the group that the child was removed from
+ */
+ void notifyGroupChildRemoved(View row, ViewGroup childrenContainer);
+
+ /**
+ * Generate an animation for an added child view.
+ *
+ * @param child The view to be added.
+ * @param fromMoreCard Whether this add is coming from the "more" card on lockscreen.
+ */
+ void generateAddAnimation(View child, boolean fromMoreCard);
+
+ /**
+ * Generate a child order changed event.
+ */
+ void generateChildOrderChangedEvent();
+
+ /**
+ * Returns the number of children in the NotificationListContainer.
+ *
+ * @return the number of children in the NotificationListContainer
+ */
+ int getContainerChildCount();
+
+ /**
+ * Gets the ith child in the NotificationListContainer.
+ *
+ * @param i ith child to get
+ * @return the ith child in the list container
+ */
+ View getContainerChildAt(int i);
+
+ /**
+ * Remove a view from the container
+ *
+ * @param v view to remove
+ */
+ void removeContainerView(View v);
+
+ /**
+ * Add a view to the container
+ *
+ * @param v view to add
+ */
+ void addContainerView(View v);
+
+ /**
+ * Sets the maximum number of notifications to display.
+ *
+ * @param maxNotifications max number of notifications to display
+ */
+ void setMaxDisplayedNotifications(int maxNotifications);
+
+ /**
+ * Handle snapping a non-dismissable row back if the user tried to dismiss it.
+ *
+ * @param row row to snap back
+ */
+ void snapViewIfNeeded(ExpandableNotificationRow row);
+
+ /**
+ * Get the view parent for a notification entry. For example, NotificationStackScrollLayout.
+ *
+ * @param entry entry to get the view parent for
+ * @return the view parent for entry
+ */
+ ViewGroup getViewParentForNotification(NotificationData.Entry entry);
+
+ /**
+ * Called when the height of an expandable view changes.
+ *
+ * @param view view whose height changed
+ * @param animate whether this change should be animated
+ */
+ void onHeightChanged(ExpandableView view, boolean animate);
+
+ /**
+ * Resets the currently exposed menu view.
+ *
+ * @param animate whether to animate the closing/change of menu view
+ * @param force reset the menu view even if it looks like it is already reset
+ */
+ void resetExposedMenuView(boolean animate, boolean force);
+
+ /**
+ * Returns the NotificationSwipeActionHelper for the NotificationListContainer.
+ *
+ * @return swipe action helper for the list container
+ */
+ NotificationSwipeActionHelper getSwipeActionHelper();
+
+ /**
+ * Called when a notification is removed from the shade. This cleans up the state for a
+ * given view.
+ *
+ * @param view view to clean up view state for
+ */
+ void cleanUpViewState(View view);
+
+ /**
+ * Returns whether an ExpandableNotificationRow is in a visible location or not.
+ *
+ * @param row
+ * @return true if row is in a visible location
+ */
+ boolean isInVisibleLocation(ExpandableNotificationRow row);
+
+ /**
+ * Sets a listener to listen for changes in notification locations.
+ *
+ * @param listener listener to set
+ */
+ void setChildLocationsChangedListener(
+ NotificationLogger.OnChildLocationsChangedListener listener);
+
+ /**
+ * Called when an update to the notification view hierarchy is completed.
+ */
+ default void onNotificationViewUpdateFinished() {}
+
+ /**
+ * Returns true if there are pulsing notifications.
+ *
+ * @return true if has pulsing notifications
+ */
+ boolean hasPulsingNotifications();
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
index a72e8ac..0144f42 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
@@ -27,6 +27,7 @@
import android.service.notification.StatusBarNotification;
import android.util.Log;
+import com.android.systemui.Dependency;
import com.android.systemui.statusbar.phone.NotificationListenerWithPlugins;
/**
@@ -36,14 +37,16 @@
public class NotificationListener extends NotificationListenerWithPlugins {
private static final String TAG = "NotificationListener";
- private final NotificationRemoteInputManager mRemoteInputManager;
+ // Dependencies:
+ private final NotificationRemoteInputManager mRemoteInputManager =
+ Dependency.get(NotificationRemoteInputManager.class);
+
private final Context mContext;
- private NotificationPresenter mPresenter;
+ protected NotificationPresenter mPresenter;
+ protected NotificationEntryManager mEntryManager;
- public NotificationListener(NotificationRemoteInputManager remoteInputManager,
- Context context) {
- mRemoteInputManager = remoteInputManager;
+ public NotificationListener(Context context) {
mContext = context;
}
@@ -59,7 +62,7 @@
final RankingMap currentRanking = getCurrentRanking();
mPresenter.getHandler().post(() -> {
for (StatusBarNotification sbn : notifications) {
- mPresenter.addNotification(sbn, currentRanking);
+ mEntryManager.addNotification(sbn, currentRanking);
}
});
}
@@ -73,7 +76,8 @@
processForRemoteInput(sbn.getNotification(), mContext);
String key = sbn.getKey();
mRemoteInputManager.getKeysKeptForRemoteInput().remove(key);
- boolean isUpdate = mPresenter.getNotificationData().get(key) != null;
+ boolean isUpdate =
+ mEntryManager.getNotificationData().get(key) != null;
// In case we don't allow child notifications, we ignore children of
// notifications that have a summary, since` we're not going to show them
// anyway. This is true also when the summary is canceled,
@@ -86,16 +90,17 @@
// Remove existing notification to avoid stale data.
if (isUpdate) {
- mPresenter.removeNotification(key, rankingMap);
+ mEntryManager.removeNotification(key, rankingMap);
} else {
- mPresenter.getNotificationData().updateRanking(rankingMap);
+ mEntryManager.getNotificationData()
+ .updateRanking(rankingMap);
}
return;
}
if (isUpdate) {
- mPresenter.updateNotification(sbn, rankingMap);
+ mEntryManager.updateNotification(sbn, rankingMap);
} else {
- mPresenter.addNotification(sbn, rankingMap);
+ mEntryManager.addNotification(sbn, rankingMap);
}
});
}
@@ -107,7 +112,9 @@
if (DEBUG) Log.d(TAG, "onNotificationRemoved: " + sbn);
if (sbn != null && !onPluginNotificationRemoved(sbn, rankingMap)) {
final String key = sbn.getKey();
- mPresenter.getHandler().post(() -> mPresenter.removeNotification(key, rankingMap));
+ mPresenter.getHandler().post(() -> {
+ mEntryManager.removeNotification(key, rankingMap);
+ });
}
}
@@ -116,12 +123,16 @@
if (DEBUG) Log.d(TAG, "onRankingUpdate");
if (rankingMap != null) {
RankingMap r = onPluginRankingUpdate(rankingMap);
- mPresenter.getHandler().post(() -> mPresenter.updateNotificationRanking(r));
+ mPresenter.getHandler().post(() -> {
+ mEntryManager.updateNotificationRanking(r);
+ });
}
}
- public void setUpWithPresenter(NotificationPresenter presenter) {
+ public void setUpWithPresenter(NotificationPresenter presenter,
+ NotificationEntryManager entryManager) {
mPresenter = presenter;
+ mEntryManager = entryManager;
try {
registerAsSystemService(mContext,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java
index 644d834..bcdc269 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java
@@ -81,7 +81,7 @@
isCurrentProfile(getSendingUserId())) {
mUsersAllowingPrivateNotifications.clear();
updateLockscreenNotificationSetting();
- mPresenter.updateNotifications();
+ mEntryManager.updateNotifications();
} else if (Intent.ACTION_DEVICE_LOCKED_CHANGED.equals(action)) {
if (userId != mCurrentUserId && isCurrentProfile(userId)) {
mPresenter.onWorkChallengeChanged();
@@ -108,29 +108,15 @@
// Start the overview connection to the launcher service
Dependency.get(OverviewProxyService.class).startConnectionToCurrentUser();
} else if (Intent.ACTION_USER_PRESENT.equals(action)) {
- List<ActivityManager.RecentTaskInfo> recentTask = null;
try {
- recentTask = ActivityManager.getService().getRecentTasks(1,
- ActivityManager.RECENT_WITH_EXCLUDED,
- mCurrentUserId).getList();
+ final int lastResumedActivityUserId =
+ ActivityManager.getService().getLastResumedActivityUserId();
+ if (mUserManager.isManagedProfile(lastResumedActivityUserId)) {
+ showForegroundManagedProfileActivityToast();
+ }
} catch (RemoteException e) {
// Abandon hope activity manager not running.
}
- if (recentTask != null && recentTask.size() > 0) {
- UserInfo user = mUserManager.getUserInfo(recentTask.get(0).userId);
- if (user != null && user.isManagedProfile()) {
- Toast toast = Toast.makeText(mContext,
- R.string.managed_profile_foreground_toast,
- Toast.LENGTH_SHORT);
- TextView text = toast.getView().findViewById(android.R.id.message);
- text.setCompoundDrawablesRelativeWithIntrinsicBounds(
- R.drawable.stat_sys_managed_profile_status, 0, 0, 0);
- int paddingPx = mContext.getResources().getDimensionPixelSize(
- R.dimen.managed_profile_toast_padding);
- text.setCompoundDrawablePadding(paddingPx);
- toast.show();
- }
- }
} else if (NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION.equals(action)) {
final IntentSender intentSender = intent.getParcelableExtra(Intent.EXTRA_INTENT);
final String notificationKey = intent.getStringExtra(Intent.EXTRA_INDEX);
@@ -157,6 +143,7 @@
protected int mCurrentUserId = 0;
protected NotificationPresenter mPresenter;
+ protected NotificationEntryManager mEntryManager;
protected ContentObserver mLockscreenSettingsObserver;
protected ContentObserver mSettingsObserver;
@@ -170,8 +157,10 @@
ServiceManager.getService(Context.STATUS_BAR_SERVICE));
}
- public void setUpWithPresenter(NotificationPresenter presenter) {
+ public void setUpWithPresenter(NotificationPresenter presenter,
+ NotificationEntryManager entryManager) {
mPresenter = presenter;
+ mEntryManager = entryManager;
mLockscreenSettingsObserver = new ContentObserver(mPresenter.getHandler()) {
@Override
@@ -182,7 +171,7 @@
mUsersAllowingNotifications.clear();
// ... and refresh all the notifications
updateLockscreenNotificationSetting();
- mPresenter.updateNotifications();
+ mEntryManager.updateNotifications();
}
};
@@ -191,7 +180,7 @@
public void onChange(boolean selfChange) {
updateLockscreenNotificationSetting();
if (mDeviceProvisionedController.isDeviceProvisioned()) {
- mPresenter.updateNotifications();
+ mEntryManager.updateNotifications();
}
}
};
@@ -242,6 +231,19 @@
mSettingsObserver.onChange(false); // set up
}
+ private void showForegroundManagedProfileActivityToast() {
+ Toast toast = Toast.makeText(mContext,
+ R.string.managed_profile_foreground_toast,
+ Toast.LENGTH_SHORT);
+ TextView text = toast.getView().findViewById(android.R.id.message);
+ text.setCompoundDrawablesRelativeWithIntrinsicBounds(
+ R.drawable.stat_sys_managed_profile_status, 0, 0, 0);
+ int paddingPx = mContext.getResources().getDimensionPixelSize(
+ R.dimen.managed_profile_toast_padding);
+ text.setCompoundDrawablePadding(paddingPx);
+ toast.show();
+ }
+
public boolean shouldShowLockscreenNotifications() {
return mShowLockscreenNotifications;
}
@@ -271,13 +273,13 @@
*/
public boolean shouldHideNotifications(String key) {
return isLockscreenPublicMode(mCurrentUserId)
- && mPresenter.getNotificationData().getVisibilityOverride(key) ==
+ && mEntryManager.getNotificationData().getVisibilityOverride(key) ==
Notification.VISIBILITY_SECRET;
}
public boolean shouldShowOnKeyguard(StatusBarNotification sbn) {
return mShowLockscreenNotifications
- && !mPresenter.getNotificationData().isAmbient(sbn.getKey());
+ && !mEntryManager.getNotificationData().isAmbient(sbn.getKey());
}
private void setShowLockscreenNotifications(boolean show) {
@@ -395,7 +397,7 @@
}
private boolean packageHasVisibilityOverride(String key) {
- return mPresenter.getNotificationData().getVisibilityOverride(key) ==
+ return mEntryManager.getNotificationData().getVisibilityOverride(key) ==
Notification.VISIBILITY_PRIVATE;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLogger.java
index e58d801..4225f83 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLogger.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLogger.java
@@ -27,8 +27,8 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.statusbar.NotificationVisibility;
+import com.android.systemui.Dependency;
import com.android.systemui.UiOffloadThread;
-import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
import java.util.ArrayList;
import java.util.Collection;
@@ -47,21 +47,22 @@
/** Keys of notifications currently visible to the user. */
private final ArraySet<NotificationVisibility> mCurrentlyVisibleNotifications =
new ArraySet<>();
- private final NotificationListenerService mNotificationListener;
- private final UiOffloadThread mUiOffloadThread;
- protected NotificationPresenter mPresenter;
+ // Dependencies:
+ private final NotificationListenerService mNotificationListener =
+ Dependency.get(NotificationListener.class);
+ private final UiOffloadThread mUiOffloadThread = Dependency.get(UiOffloadThread.class);
+
+ protected NotificationEntryManager mEntryManager;
protected Handler mHandler = new Handler();
protected IStatusBarService mBarService;
private long mLastVisibilityReportUptimeMs;
- private NotificationStackScrollLayout mStackScroller;
+ private NotificationListContainer mListContainer;
- protected final NotificationStackScrollLayout.OnChildLocationsChangedListener
- mNotificationLocationsChangedListener =
- new NotificationStackScrollLayout.OnChildLocationsChangedListener() {
+ protected final OnChildLocationsChangedListener mNotificationLocationsChangedListener =
+ new OnChildLocationsChangedListener() {
@Override
- public void onChildLocationsChanged(
- NotificationStackScrollLayout stackScrollLayout) {
+ public void onChildLocationsChanged() {
if (mHandler.hasCallbacks(mVisibilityReporter)) {
// Visibilities will be reported when the existing
// callback is executed.
@@ -99,13 +100,13 @@
// notifications.
// 3. Report newly visible and no-longer visible notifications.
// 4. Keep currently visible notifications for next report.
- ArrayList<NotificationData.Entry> activeNotifications = mPresenter.
- getNotificationData().getActiveNotifications();
+ ArrayList<NotificationData.Entry> activeNotifications = mEntryManager
+ .getNotificationData().getActiveNotifications();
int N = activeNotifications.size();
for (int i = 0; i < N; i++) {
NotificationData.Entry entry = activeNotifications.get(i);
String key = entry.notification.getKey();
- boolean isVisible = mStackScroller.isInVisibleLocation(entry.row);
+ boolean isVisible = mListContainer.isInVisibleLocation(entry.row);
NotificationVisibility visObj = NotificationVisibility.obtain(key, i, isVisible);
boolean previouslyVisible = mCurrentlyVisibleNotifications.contains(visObj);
if (isVisible) {
@@ -135,19 +136,15 @@
}
};
- public NotificationLogger(NotificationListenerService notificationListener,
- UiOffloadThread uiOffloadThread) {
- mNotificationListener = notificationListener;
- mUiOffloadThread = uiOffloadThread;
+ public NotificationLogger() {
mBarService = IStatusBarService.Stub.asInterface(
ServiceManager.getService(Context.STATUS_BAR_SERVICE));
}
- // TODO: Remove dependency on NotificationStackScrollLayout.
- public void setUpWithPresenter(NotificationPresenter presenter,
- NotificationStackScrollLayout stackScroller) {
- mPresenter = presenter;
- mStackScroller = stackScroller;
+ public void setUpWithEntryManager(NotificationEntryManager entryManager,
+ NotificationListContainer listContainer) {
+ mEntryManager = entryManager;
+ mListContainer = listContainer;
}
public void stopNotificationLogging() {
@@ -159,18 +156,18 @@
recycleAllVisibilityObjects(mCurrentlyVisibleNotifications);
}
mHandler.removeCallbacks(mVisibilityReporter);
- mStackScroller.setChildLocationsChangedListener(null);
+ mListContainer.setChildLocationsChangedListener(null);
}
public void startNotificationLogging() {
- mStackScroller.setChildLocationsChangedListener(mNotificationLocationsChangedListener);
+ mListContainer.setChildLocationsChangedListener(mNotificationLocationsChangedListener);
// Some transitions like mVisibleToUser=false -> mVisibleToUser=true don't
// cause the scroller to emit child location events. Hence generate
// one ourselves to guarantee that we're reporting visible
// notifications.
// (Note that in cases where the scroller does emit events, this
// additional event doesn't break anything.)
- mNotificationLocationsChangedListener.onChildLocationsChanged(mStackScroller);
+ mNotificationLocationsChangedListener.onChildLocationsChanged();
}
private void logNotificationVisibilityChanges(
@@ -220,4 +217,11 @@
public Runnable getVisibilityReporter() {
return mVisibilityReporter;
}
+
+ /**
+ * A listener that is notified when some child locations might have changed.
+ */
+ public interface OnChildLocationsChangedListener {
+ void onChildLocationsChanged();
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
index 283a6e3..852239a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
@@ -40,9 +40,11 @@
private static final String TAG = "NotificationMediaManager";
public static final boolean DEBUG_MEDIA = false;
- private final NotificationPresenter mPresenter;
private final Context mContext;
private final MediaSessionManager mMediaSessionManager;
+
+ protected NotificationPresenter mPresenter;
+ protected NotificationEntryManager mEntryManager;
private MediaController mMediaController;
private String mMediaNotificationKey;
private MediaMetadata mMediaMetadata;
@@ -73,8 +75,7 @@
}
};
- public NotificationMediaManager(NotificationPresenter presenter, Context context) {
- mPresenter = presenter;
+ public NotificationMediaManager(Context context) {
mContext = context;
mMediaSessionManager
= (MediaSessionManager) mContext.getSystemService(Context.MEDIA_SESSION_SERVICE);
@@ -82,6 +83,12 @@
// in session state
}
+ public void setUpWithPresenter(NotificationPresenter presenter,
+ NotificationEntryManager entryManager) {
+ mPresenter = presenter;
+ mEntryManager = entryManager;
+ }
+
public void onNotificationRemoved(String key) {
if (key.equals(mMediaNotificationKey)) {
clearCurrentMediaNotification();
@@ -100,8 +107,8 @@
public void findAndUpdateMediaNotifications() {
boolean metaDataChanged = false;
- synchronized (mPresenter.getNotificationData()) {
- ArrayList<NotificationData.Entry> activeNotifications = mPresenter
+ synchronized (mEntryManager.getNotificationData()) {
+ ArrayList<NotificationData.Entry> activeNotifications = mEntryManager
.getNotificationData().getActiveNotifications();
final int N = activeNotifications.size();
@@ -188,7 +195,7 @@
}
if (metaDataChanged) {
- mPresenter.updateNotifications();
+ mEntryManager.updateNotifications();
}
mPresenter.updateMediaMetaData(metaDataChanged, true);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java
index 33c7253..12641a0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java
@@ -16,12 +16,11 @@
package com.android.systemui.statusbar;
import android.content.Intent;
+import android.content.pm.PackageManager;
import android.os.Handler;
-import android.service.notification.NotificationListenerService;
+import android.service.notification.StatusBarNotification;
import android.view.View;
-import java.util.Set;
-
/**
* An abstraction of something that presents notifications, e.g. StatusBar. Contains methods
* for both querying the state of the system (some modularised piece of functionality may
@@ -29,9 +28,11 @@
* for affecting the state of the system (e.g. starting an intent, given that the presenter may
* want to perform some action before doing so).
*/
-public interface NotificationPresenter extends NotificationUpdateHandler,
- NotificationData.Environment, NotificationRemoteInputManager.Callback {
-
+public interface NotificationPresenter extends NotificationData.Environment,
+ NotificationRemoteInputManager.Callback,
+ ExpandableNotificationRow.OnExpandClickListener,
+ ActivatableNotificationView.OnActivatedListener,
+ NotificationEntryManager.Callback {
/**
* Returns true if the presenter is not visible. For example, it may not be necessary to do
* animations if this returns true.
@@ -50,32 +51,15 @@
void startNotificationGutsIntent(Intent intent, int appUid);
/**
- * Returns NotificationData.
- */
- NotificationData getNotificationData();
-
- /**
* Returns the Handler for NotificationPresenter.
*/
Handler getHandler();
- // TODO: Create NotificationEntryManager and move this method to there.
- /**
- * Signals that some notifications have changed, and NotificationPresenter should update itself.
- */
- void updateNotifications();
-
/**
* Refresh or remove lockscreen artwork from media metadata or the lockscreen wallpaper.
*/
void updateMediaMetaData(boolean metaDataChanged, boolean allowEnterAnimation);
- // TODO: Create NotificationEntryManager and move this method to there.
- /**
- * Gets the latest ranking map.
- */
- NotificationListenerService.RankingMap getLatestRankingMap();
-
/**
* Called when the locked status of the device is changed for a work profile.
*/
@@ -107,4 +91,32 @@
* @return true iff the device is locked
*/
boolean isDeviceLocked(int userId);
+
+ /**
+ * @return true iff the device is in vr mode
+ */
+ boolean isDeviceInVrMode();
+
+ /**
+ * Updates the visual representation of the notifications.
+ */
+ void updateNotificationViews();
+
+ /**
+ * @return true iff the device is dozing
+ */
+ boolean isDozing();
+
+ /**
+ * Returns the maximum number of notifications to show while locked.
+ *
+ * @param recompute whether something has changed that means we should recompute this value
+ * @return the maximum number of notifications to show while locked
+ */
+ int getMaxNotificationsWhileLocked(boolean recompute);
+
+ /**
+ * Called when the row states are updated by NotificationViewHierarchyManager.
+ */
+ void onUpdateRowStates();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
index 7827f62..f25379a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
@@ -39,6 +39,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.statusbar.IStatusBarService;
+import com.android.systemui.Dependency;
import com.android.systemui.Dumpable;
import com.android.systemui.statusbar.policy.RemoteInputView;
@@ -70,7 +71,10 @@
protected final ArraySet<NotificationData.Entry> mRemoteInputEntriesToRemoveOnCollapse =
new ArraySet<>();
- protected final NotificationLockscreenUserManager mLockscreenUserManager;
+
+ // Dependencies:
+ protected final NotificationLockscreenUserManager mLockscreenUserManager =
+ Dependency.get(NotificationLockscreenUserManager.class);
/**
* Notifications with keys in this set are not actually around anymore. We kept them around
@@ -83,6 +87,7 @@
protected RemoteInputController mRemoteInputController;
protected NotificationPresenter mPresenter;
+ protected NotificationEntryManager mEntryManager;
protected IStatusBarService mBarService;
protected Callback mCallback;
@@ -263,9 +268,7 @@
}
};
- public NotificationRemoteInputManager(NotificationLockscreenUserManager lockscreenUserManager,
- Context context) {
- mLockscreenUserManager = lockscreenUserManager;
+ public NotificationRemoteInputManager(Context context) {
mContext = context;
mBarService = IStatusBarService.Stub.asInterface(
ServiceManager.getService(Context.STATUS_BAR_SERVICE));
@@ -273,16 +276,18 @@
}
public void setUpWithPresenter(NotificationPresenter presenter,
+ NotificationEntryManager entryManager,
Callback callback,
RemoteInputController.Delegate delegate) {
mPresenter = presenter;
+ mEntryManager = entryManager;
mCallback = callback;
mRemoteInputController = new RemoteInputController(delegate);
mRemoteInputController.addCallback(new RemoteInputController.Callback() {
@Override
public void onRemoteInputSent(NotificationData.Entry entry) {
if (FORCE_REMOTE_INPUT_HISTORY && mKeysKeptForRemoteInput.contains(entry.key)) {
- mPresenter.removeNotification(entry.key, null);
+ mEntryManager.removeNotification(entry.key, null);
} else if (mRemoteInputEntriesToRemoveOnCollapse.contains(entry)) {
// We're currently holding onto this notification, but from the apps point of
// view it is already canceled, so we'll need to cancel it on the apps behalf
@@ -290,7 +295,7 @@
// bit.
mPresenter.getHandler().postDelayed(() -> {
if (mRemoteInputEntriesToRemoveOnCollapse.remove(entry)) {
- mPresenter.removeNotification(entry.key, null);
+ mEntryManager.removeNotification(entry.key, null);
}
}, REMOTE_INPUT_KEPT_ENTRY_AUTO_CANCEL_DELAY);
}
@@ -336,7 +341,7 @@
for (int i = 0; i < mRemoteInputEntriesToRemoveOnCollapse.size(); i++) {
NotificationData.Entry entry = mRemoteInputEntriesToRemoveOnCollapse.valueAt(i);
mRemoteInputController.removeRemoteInput(entry, null);
- mPresenter.removeNotification(entry.key, mPresenter.getLatestRankingMap());
+ mEntryManager.removeNotification(entry.key, mEntryManager.getLatestRankingMap());
}
mRemoteInputEntriesToRemoveOnCollapse.clear();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
new file mode 100644
index 0000000..266c09b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
@@ -0,0 +1,345 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.systemui.Dependency;
+import com.android.systemui.R;
+import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.phone.NotificationGroupManager;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Stack;
+
+/**
+ * NotificationViewHierarchyManager manages updating the view hierarchy of notification views based
+ * on their group structure. For example, if a notification becomes bundled with another,
+ * NotificationViewHierarchyManager will update the view hierarchy to reflect that. It also will
+ * tell NotificationListContainer which notifications to display, and inform it of changes to those
+ * notifications that might affect their display.
+ */
+public class NotificationViewHierarchyManager {
+ private static final String TAG = "NotificationViewHierarchyManager";
+
+ private final HashMap<ExpandableNotificationRow, List<ExpandableNotificationRow>>
+ mTmpChildOrderMap = new HashMap<>();
+
+ // Dependencies:
+ protected final NotificationLockscreenUserManager mLockscreenUserManager =
+ Dependency.get(NotificationLockscreenUserManager.class);
+ protected final NotificationGroupManager mGroupManager =
+ Dependency.get(NotificationGroupManager.class);
+ protected final VisualStabilityManager mVisualStabilityManager =
+ Dependency.get(VisualStabilityManager.class);
+
+ /**
+ * {@code true} if notifications not part of a group should by default be rendered in their
+ * expanded state. If {@code false}, then only the first notification will be expanded if
+ * possible.
+ */
+ private final boolean mAlwaysExpandNonGroupedNotification;
+
+ private NotificationPresenter mPresenter;
+ private NotificationEntryManager mEntryManager;
+ private NotificationListContainer mListContainer;
+
+ public NotificationViewHierarchyManager(Context context) {
+ Resources res = context.getResources();
+ mAlwaysExpandNonGroupedNotification =
+ res.getBoolean(R.bool.config_alwaysExpandNonGroupedNotifications);
+ }
+
+ public void setUpWithPresenter(NotificationPresenter presenter,
+ NotificationEntryManager entryManager, NotificationListContainer listContainer) {
+ mPresenter = presenter;
+ mEntryManager = entryManager;
+ mListContainer = listContainer;
+ }
+
+ /**
+ * Updates the visual representation of the notifications.
+ */
+ public void updateNotificationViews() {
+ ArrayList<NotificationData.Entry> activeNotifications = mEntryManager.getNotificationData()
+ .getActiveNotifications();
+ ArrayList<ExpandableNotificationRow> toShow = new ArrayList<>(activeNotifications.size());
+ final int N = activeNotifications.size();
+ for (int i = 0; i < N; i++) {
+ NotificationData.Entry ent = activeNotifications.get(i);
+ if (ent.row.isDismissed() || ent.row.isRemoved()) {
+ // we don't want to update removed notifications because they could
+ // temporarily become children if they were isolated before.
+ continue;
+ }
+ int userId = ent.notification.getUserId();
+
+ // Display public version of the notification if we need to redact.
+ // TODO: This area uses a lot of calls into NotificationLockscreenUserManager.
+ // We can probably move some of this code there.
+ boolean devicePublic = mLockscreenUserManager.isLockscreenPublicMode(
+ mLockscreenUserManager.getCurrentUserId());
+ boolean userPublic = devicePublic
+ || mLockscreenUserManager.isLockscreenPublicMode(userId);
+ boolean needsRedaction = mLockscreenUserManager.needsRedaction(ent);
+ boolean sensitive = userPublic && needsRedaction;
+ boolean deviceSensitive = devicePublic
+ && !mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(
+ mLockscreenUserManager.getCurrentUserId());
+ ent.row.setSensitive(sensitive, deviceSensitive);
+ ent.row.setNeedsRedaction(needsRedaction);
+ if (mGroupManager.isChildInGroupWithSummary(ent.row.getStatusBarNotification())) {
+ ExpandableNotificationRow summary = mGroupManager.getGroupSummary(
+ ent.row.getStatusBarNotification());
+ List<ExpandableNotificationRow> orderedChildren =
+ mTmpChildOrderMap.get(summary);
+ if (orderedChildren == null) {
+ orderedChildren = new ArrayList<>();
+ mTmpChildOrderMap.put(summary, orderedChildren);
+ }
+ orderedChildren.add(ent.row);
+ } else {
+ toShow.add(ent.row);
+ }
+
+ }
+
+ ArrayList<ExpandableNotificationRow> toRemove = new ArrayList<>();
+ for (int i=0; i< mListContainer.getContainerChildCount(); i++) {
+ View child = mListContainer.getContainerChildAt(i);
+ if (!toShow.contains(child) && child instanceof ExpandableNotificationRow) {
+ toRemove.add((ExpandableNotificationRow) child);
+ }
+ }
+
+ for (ExpandableNotificationRow remove : toRemove) {
+ if (mGroupManager.isChildInGroupWithSummary(remove.getStatusBarNotification())) {
+ // we are only transferring this notification to its parent, don't generate an
+ // animation
+ mListContainer.setChildTransferInProgress(true);
+ }
+ if (remove.isSummaryWithChildren()) {
+ remove.removeAllChildren();
+ }
+ mListContainer.removeContainerView(remove);
+ mListContainer.setChildTransferInProgress(false);
+ }
+
+ removeNotificationChildren();
+
+ for (int i = 0; i < toShow.size(); i++) {
+ View v = toShow.get(i);
+ if (v.getParent() == null) {
+ mVisualStabilityManager.notifyViewAddition(v);
+ mListContainer.addContainerView(v);
+ }
+ }
+
+ addNotificationChildrenAndSort();
+
+ // So after all this work notifications still aren't sorted correctly.
+ // Let's do that now by advancing through toShow and mListContainer in
+ // lock-step, making sure mListContainer matches what we see in toShow.
+ int j = 0;
+ for (int i = 0; i < mListContainer.getContainerChildCount(); i++) {
+ View child = mListContainer.getContainerChildAt(i);
+ if (!(child instanceof ExpandableNotificationRow)) {
+ // We don't care about non-notification views.
+ continue;
+ }
+
+ ExpandableNotificationRow targetChild = toShow.get(j);
+ if (child != targetChild) {
+ // Oops, wrong notification at this position. Put the right one
+ // here and advance both lists.
+ if (mVisualStabilityManager.canReorderNotification(targetChild)) {
+ mListContainer.changeViewPosition(targetChild, i);
+ } else {
+ mVisualStabilityManager.addReorderingAllowedCallback(mEntryManager);
+ }
+ }
+ j++;
+
+ }
+
+ mVisualStabilityManager.onReorderingFinished();
+ // clear the map again for the next usage
+ mTmpChildOrderMap.clear();
+
+ updateRowStates();
+
+ mListContainer.onNotificationViewUpdateFinished();
+ }
+
+ private void addNotificationChildrenAndSort() {
+ // Let's now add all notification children which are missing
+ boolean orderChanged = false;
+ for (int i = 0; i < mListContainer.getContainerChildCount(); i++) {
+ View view = mListContainer.getContainerChildAt(i);
+ if (!(view instanceof ExpandableNotificationRow)) {
+ // We don't care about non-notification views.
+ continue;
+ }
+
+ ExpandableNotificationRow parent = (ExpandableNotificationRow) view;
+ List<ExpandableNotificationRow> children = parent.getNotificationChildren();
+ List<ExpandableNotificationRow> orderedChildren = mTmpChildOrderMap.get(parent);
+
+ for (int childIndex = 0; orderedChildren != null && childIndex < orderedChildren.size();
+ childIndex++) {
+ ExpandableNotificationRow childView = orderedChildren.get(childIndex);
+ if (children == null || !children.contains(childView)) {
+ if (childView.getParent() != null) {
+ Log.wtf(TAG, "trying to add a notification child that already has " +
+ "a parent. class:" + childView.getParent().getClass() +
+ "\n child: " + childView);
+ // This shouldn't happen. We can recover by removing it though.
+ ((ViewGroup) childView.getParent()).removeView(childView);
+ }
+ mVisualStabilityManager.notifyViewAddition(childView);
+ parent.addChildNotification(childView, childIndex);
+ mListContainer.notifyGroupChildAdded(childView);
+ }
+ }
+
+ // Finally after removing and adding has been performed we can apply the order.
+ orderChanged |= parent.applyChildOrder(orderedChildren, mVisualStabilityManager,
+ mEntryManager);
+ }
+ if (orderChanged) {
+ mListContainer.generateChildOrderChangedEvent();
+ }
+ }
+
+ private void removeNotificationChildren() {
+ // First let's remove all children which don't belong in the parents
+ ArrayList<ExpandableNotificationRow> toRemove = new ArrayList<>();
+ for (int i = 0; i < mListContainer.getContainerChildCount(); i++) {
+ View view = mListContainer.getContainerChildAt(i);
+ if (!(view instanceof ExpandableNotificationRow)) {
+ // We don't care about non-notification views.
+ continue;
+ }
+
+ ExpandableNotificationRow parent = (ExpandableNotificationRow) view;
+ List<ExpandableNotificationRow> children = parent.getNotificationChildren();
+ List<ExpandableNotificationRow> orderedChildren = mTmpChildOrderMap.get(parent);
+
+ if (children != null) {
+ toRemove.clear();
+ for (ExpandableNotificationRow childRow : children) {
+ if ((orderedChildren == null
+ || !orderedChildren.contains(childRow))
+ && !childRow.keepInParent()) {
+ toRemove.add(childRow);
+ }
+ }
+ for (ExpandableNotificationRow remove : toRemove) {
+ parent.removeChildNotification(remove);
+ if (mEntryManager.getNotificationData().get(
+ remove.getStatusBarNotification().getKey()) == null) {
+ // We only want to add an animation if the view is completely removed
+ // otherwise it's just a transfer
+ mListContainer.notifyGroupChildRemoved(remove,
+ parent.getChildrenContainer());
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Updates expanded, dimmed and locked states of notification rows.
+ */
+ public void updateRowStates() {
+ final int N = mListContainer.getContainerChildCount();
+
+ int visibleNotifications = 0;
+ boolean isLocked = mPresenter.isPresenterLocked();
+ int maxNotifications = -1;
+ if (isLocked) {
+ maxNotifications = mPresenter.getMaxNotificationsWhileLocked(true /* recompute */);
+ }
+ mListContainer.setMaxDisplayedNotifications(maxNotifications);
+ Stack<ExpandableNotificationRow> stack = new Stack<>();
+ for (int i = N - 1; i >= 0; i--) {
+ View child = mListContainer.getContainerChildAt(i);
+ if (!(child instanceof ExpandableNotificationRow)) {
+ continue;
+ }
+ stack.push((ExpandableNotificationRow) child);
+ }
+ while(!stack.isEmpty()) {
+ ExpandableNotificationRow row = stack.pop();
+ NotificationData.Entry entry = row.getEntry();
+ boolean isChildNotification =
+ mGroupManager.isChildInGroupWithSummary(entry.notification);
+
+ row.setOnKeyguard(isLocked);
+
+ if (!isLocked) {
+ // If mAlwaysExpandNonGroupedNotification is false, then only expand the
+ // very first notification and if it's not a child of grouped notifications.
+ row.setSystemExpanded(mAlwaysExpandNonGroupedNotification
+ || (visibleNotifications == 0 && !isChildNotification
+ && !row.isLowPriority()));
+ }
+
+ entry.row.setShowAmbient(mPresenter.isDozing());
+ int userId = entry.notification.getUserId();
+ boolean suppressedSummary = mGroupManager.isSummaryOfSuppressedGroup(
+ entry.notification) && !entry.row.isRemoved();
+ boolean showOnKeyguard = mLockscreenUserManager.shouldShowOnKeyguard(entry
+ .notification);
+ if (suppressedSummary
+ || (mLockscreenUserManager.isLockscreenPublicMode(userId)
+ && !mLockscreenUserManager.shouldShowLockscreenNotifications())
+ || (isLocked && !showOnKeyguard)) {
+ entry.row.setVisibility(View.GONE);
+ } else {
+ boolean wasGone = entry.row.getVisibility() == View.GONE;
+ if (wasGone) {
+ entry.row.setVisibility(View.VISIBLE);
+ }
+ if (!isChildNotification && !entry.row.isRemoved()) {
+ if (wasGone) {
+ // notify the scroller of a child addition
+ mListContainer.generateAddAnimation(entry.row,
+ !showOnKeyguard /* fromMoreCard */);
+ }
+ visibleNotifications++;
+ }
+ }
+ if (row.isSummaryWithChildren()) {
+ List<ExpandableNotificationRow> notificationChildren =
+ row.getNotificationChildren();
+ int size = notificationChildren.size();
+ for (int i = size - 1; i >= 0; i--) {
+ stack.push(notificationChildren.get(i));
+ }
+ }
+ }
+
+ mPresenter.onUpdateRowStates();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
index 5ba6f6a..3ebeb4d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
@@ -247,19 +247,6 @@
return null;
}
- /**
- * Returns the
- * {@link com.android.systemui.statusbar.ExpandableNotificationRow.LongPressListener} that will
- * be triggered when a notification card is long-pressed.
- */
- @Override
- protected ExpandableNotificationRow.LongPressListener getNotificationLongClicker() {
- // For the automative use case, we do not want to the user to be able to interact with
- // a notification other than a regular click. As a result, just return null for the
- // long click listener.
- return null;
- }
-
@Override
public void showBatteryView() {
if (Log.isLoggable(TAG, Log.DEBUG)) {
@@ -388,18 +375,6 @@
}
@Override
- protected boolean shouldPeek(NotificationData.Entry entry, StatusBarNotification sbn) {
- // Because space is usually constrained in the auto use-case, there should not be a
- // pinned notification when the shade has been expanded. Ensure this by not pinning any
- // notification if the shade is already opened.
- if (mPanelExpanded) {
- return false;
- }
-
- return super.shouldPeek(entry, sbn);
- }
-
- @Override
public void animateExpandNotificationsPanel() {
// Because space is usually constrained in the auto use-case, there should not be a
// pinned notification when the shade has been expanded. Ensure this by removing all heads-
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 61dd22f..f0bd1f9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -455,7 +455,7 @@
mTopPaddingAdjustment = 0;
} else {
mClockPositionAlgorithm.setup(
- mStatusBar.getMaxKeyguardNotifications(),
+ mStatusBar.getMaxNotificationsWhileLocked(),
getMaxPanelHeight(),
getExpandedHeight(),
mNotificationStackScroller.getNotGoneChildCount(),
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index c5349d1..2da1e4d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -28,10 +28,6 @@
.NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION;
import static com.android.systemui.statusbar.NotificationLockscreenUserManager.PERMISSION_SELF;
import static com.android.systemui.statusbar.NotificationMediaManager.DEBUG_MEDIA;
-import static com.android.systemui.statusbar.NotificationRemoteInputManager.ENABLE_REMOTE_INPUT;
-import static com.android.systemui.statusbar.NotificationRemoteInputManager
- .FORCE_REMOTE_INPUT_HISTORY;
-import static com.android.systemui.statusbar.notification.NotificationInflater.InflationCallback;
import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT;
import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT_TRANSPARENT;
import static com.android.systemui.statusbar.phone.BarTransitions.MODE_OPAQUE;
@@ -66,14 +62,12 @@
import android.content.IntentSender;
import android.content.om.IOverlayManager;
import android.content.om.OverlayInfo;
-import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.UserInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
-import android.database.ContentObserver;
import android.graphics.Bitmap;
import android.graphics.Point;
import android.graphics.PointF;
@@ -88,7 +82,6 @@
import android.metrics.LogMaker;
import android.net.Uri;
import android.os.AsyncTask;
-import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
@@ -103,12 +96,9 @@
import android.os.UserManager;
import android.os.Vibrator;
import android.provider.Settings;
-import android.service.notification.NotificationListenerService.RankingMap;
-import android.service.notification.NotificationStats;
import android.service.notification.StatusBarNotification;
import android.service.vr.IVrManager;
import android.service.vr.IVrStateCallbacks;
-import android.util.ArraySet;
import android.util.DisplayMetrics;
import android.util.EventLog;
import android.util.Log;
@@ -139,7 +129,6 @@
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.statusbar.StatusBarIcon;
-import com.android.internal.util.NotificationMessagingUtil;
import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.MessagingGroup;
import com.android.internal.widget.MessagingMessage;
@@ -149,11 +138,9 @@
import com.android.keyguard.ViewMediatorCallback;
import com.android.systemui.ActivityStarterDelegate;
import com.android.systemui.AutoReinflateContainer;
-import com.android.systemui.DejankUtils;
import com.android.systemui.DemoMode;
import com.android.systemui.Dependency;
import com.android.systemui.EventLogTags;
-import com.android.systemui.ForegroundServiceController;
import com.android.systemui.Interpolators;
import com.android.systemui.Prefs;
import com.android.systemui.R;
@@ -200,6 +187,7 @@
import com.android.systemui.statusbar.KeyguardIndicationController;
import com.android.systemui.statusbar.NotificationData;
import com.android.systemui.statusbar.NotificationData.Entry;
+import com.android.systemui.statusbar.NotificationEntryManager;
import com.android.systemui.statusbar.NotificationGutsManager;
import com.android.systemui.statusbar.NotificationInfo;
import com.android.systemui.statusbar.NotificationListener;
@@ -209,13 +197,12 @@
import com.android.systemui.statusbar.NotificationPresenter;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.NotificationShelf;
+import com.android.systemui.statusbar.NotificationViewHierarchyManager;
import com.android.systemui.statusbar.RemoteInputController;
import com.android.systemui.statusbar.ScrimView;
import com.android.systemui.statusbar.SignalClusterView;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.notification.AboveShelfObserver;
-import com.android.systemui.statusbar.notification.InflationException;
-import com.android.systemui.statusbar.notification.RowInflaterTask;
import com.android.systemui.statusbar.notification.VisualStabilityManager;
import com.android.systemui.statusbar.phone.UnlockMethodCache.OnUnlockMethodChangedListener;
import com.android.systemui.statusbar.policy.BatteryController;
@@ -239,7 +226,6 @@
import com.android.systemui.statusbar.policy.UserSwitcherController;
import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
import com.android.systemui.util.NotificationChannels;
-import com.android.systemui.util.leak.LeakDetector;
import com.android.systemui.volume.VolumeComponent;
import java.io.FileDescriptor;
@@ -247,17 +233,12 @@
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collection;
-import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import java.util.Stack;
public class StatusBar extends SystemUI implements DemoMode,
DragDownHelper.DragDownCallback, ActivityStarter, OnUnlockMethodChangedListener,
- OnHeadsUpChangedListener, VisualStabilityManager.Callback, CommandQueue.Callbacks,
- ActivatableNotificationView.OnActivatedListener,
- ExpandableNotificationRow.ExpansionLogger, NotificationData.Environment,
- ExpandableNotificationRow.OnExpandClickListener, InflationCallback,
+ OnHeadsUpChangedListener, CommandQueue.Callbacks,
ColorExtractor.OnColorsChangedListener, ConfigurationListener, NotificationPresenter {
public static final boolean MULTIUSER_DEBUG = false;
@@ -270,10 +251,6 @@
protected static final int MSG_TOGGLE_KEYBOARD_SHORTCUTS_MENU = 1026;
protected static final int MSG_DISMISS_KEYBOARD_SHORTCUTS_MENU = 1027;
- protected static final boolean ENABLE_HEADS_UP = true;
- protected static final String SETTING_HEADS_UP_TICKER = "ticker_gets_heads_up";
-
-
// Should match the values in PhoneWindowManager
public static final String SYSTEM_DIALOG_REASON_HOME_KEY = "homekey";
public static final String SYSTEM_DIALOG_REASON_RECENT_APPS = "recentapps";
@@ -309,7 +286,7 @@
// Time after we abort the launch transition.
private static final long LAUNCH_TRANSITION_TIMEOUT_MS = 5000;
- private static final boolean CLOSE_PANEL_WHEN_EMPTIED = true;
+ protected static final boolean CLOSE_PANEL_WHEN_EMPTIED = true;
private static final int STATUS_OR_NAV_TRANSIENT =
View.STATUS_BAR_TRANSIENT | View.NAVIGATION_BAR_TRANSIENT;
@@ -392,13 +369,6 @@
protected NotificationPanelView mNotificationPanel; // the sliding/resizing panel within the notification window
private TextView mNotificationPanelDebugText;
- /**
- * {@code true} if notifications not part of a group should by default be rendered in their
- * expanded state. If {@code false}, then only the first notification will be expanded if
- * possible.
- */
- private boolean mAlwaysExpandNonGroupedNotification;
-
// settings
private QSPanel mQSPanel;
@@ -427,6 +397,8 @@
private NotificationGutsManager mGutsManager;
protected NotificationLogger mNotificationLogger;
+ protected NotificationEntryManager mEntryManager;
+ protected NotificationViewHierarchyManager mViewHierarchyManager;
// for disabling the status bar
private int mDisabled1 = 0;
@@ -478,23 +450,6 @@
};
protected final H mHandler = createHandler();
- final private ContentObserver mHeadsUpObserver = new ContentObserver(mHandler) {
- @Override
- public void onChange(boolean selfChange) {
- boolean wasUsing = mUseHeadsUp;
- mUseHeadsUp = ENABLE_HEADS_UP && !mDisableNotificationAlerts
- && Settings.Global.HEADS_UP_OFF != Settings.Global.getInt(
- mContext.getContentResolver(), Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED,
- Settings.Global.HEADS_UP_OFF);
- Log.d(TAG, "heads up is " + (mUseHeadsUp ? "enabled" : "disabled"));
- if (wasUsing != mUseHeadsUp) {
- if (!mUseHeadsUp) {
- Log.d(TAG, "dismissing any existing heads up notification on disable event");
- mHeadsUpManager.releaseAllImmediately();
- }
- }
- }
- };
private int mInteractingWindows;
private boolean mAutohideSuspended;
@@ -588,7 +543,6 @@
}
};
- private NotificationMessagingUtil mMessagingUtil;
private KeyguardUserSwitcher mKeyguardUserSwitcher;
private UserSwitcherController mUserSwitcherController;
private NetworkController mNetworkController;
@@ -603,11 +557,9 @@
private final LockscreenGestureLogger mLockscreenGestureLogger = new LockscreenGestureLogger();
protected NotificationIconAreaController mNotificationIconAreaController;
private boolean mReinflateNotificationsOnUserSwitched;
- private final HashMap<String, Entry> mPendingNotifications = new HashMap<>();
private boolean mClearAllEnabled;
@Nullable private View mAmbientIndicationContainer;
private SysuiColorExtractor mColorExtractor;
- private ForegroundServiceController mForegroundServiceController;
private ScreenLifecycle mScreenLifecycle;
@VisibleForTesting WakefulnessLifecycle mWakefulnessLifecycle;
@@ -617,9 +569,6 @@
goToLockedShade(null);
}
};
- private final HashMap<ExpandableNotificationRow, List<ExpandableNotificationRow>>
- mTmpChildOrderMap = new HashMap<>();
- private RankingMap mLatestRankingMap;
private boolean mNoAnimationOnNextBarModeChange;
private FalsingManager mFalsingManager;
@@ -639,8 +588,11 @@
@Override
public void start() {
mGroupManager = Dependency.get(NotificationGroupManager.class);
+ mVisualStabilityManager = Dependency.get(VisualStabilityManager.class);
mNotificationLogger = Dependency.get(NotificationLogger.class);
mRemoteInputManager = Dependency.get(NotificationRemoteInputManager.class);
+ mNotificationListener = Dependency.get(NotificationListener.class);
+ mGroupManager = Dependency.get(NotificationGroupManager.class);
mNetworkController = Dependency.get(NetworkController.class);
mUserSwitcherController = Dependency.get(UserSwitcherController.class);
mScreenLifecycle = Dependency.get(ScreenLifecycle.class);
@@ -649,27 +601,25 @@
mWakefulnessLifecycle.addObserver(mWakefulnessObserver);
mBatteryController = Dependency.get(BatteryController.class);
mAssistManager = Dependency.get(AssistManager.class);
- mSystemServicesProxy = SystemServicesProxy.getInstance(mContext);
mOverlayManager = IOverlayManager.Stub.asInterface(
ServiceManager.getService(Context.OVERLAY_SERVICE));
mLockscreenUserManager = Dependency.get(NotificationLockscreenUserManager.class);
mGutsManager = Dependency.get(NotificationGutsManager.class);
+ mMediaManager = Dependency.get(NotificationMediaManager.class);
+ mEntryManager = Dependency.get(NotificationEntryManager.class);
+ mViewHierarchyManager = Dependency.get(NotificationViewHierarchyManager.class);
mColorExtractor = Dependency.get(SysuiColorExtractor.class);
mColorExtractor.addOnColorsChangedListener(this);
mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
- mForegroundServiceController = Dependency.get(ForegroundServiceController.class);
-
mDisplay = mWindowManager.getDefaultDisplay();
updateDisplaySize();
Resources res = mContext.getResources();
mScrimSrcModeEnabled = res.getBoolean(R.bool.config_status_bar_scrim_behind_use_src);
mClearAllEnabled = res.getBoolean(R.bool.config_enableNotificationsClearAll);
- mAlwaysExpandNonGroupedNotification =
- res.getBoolean(R.bool.config_alwaysExpandNonGroupedNotifications);
DateTimeView.setReceiverHandler(Dependency.get(Dependency.TIME_TICK_HANDLER));
putComponent(StatusBar.class, this);
@@ -679,16 +629,12 @@
mDevicePolicyManager = (DevicePolicyManager) mContext.getSystemService(
Context.DEVICE_POLICY_SERVICE);
- mNotificationData = new NotificationData(this);
- mMessagingUtil = new NotificationMessagingUtil(mContext);
-
mAccessibilityManager = (AccessibilityManager)
mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
mDeviceProvisionedController = Dependency.get(DeviceProvisionedController.class);
- mDeviceProvisionedController.addCallback(mDeviceProvisionedListener);
mBarService = IStatusBarService.Stub.asInterface(
ServiceManager.getService(Context.STATUS_BAR_SERVICE));
@@ -698,8 +644,7 @@
mKeyguardManager = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
mLockPatternUtils = new LockPatternUtils(mContext);
- mMediaManager = new NotificationMediaManager(this, mContext);
- mLockscreenUserManager.setUpWithPresenter(this);
+ mMediaManager.setUpWithPresenter(this, mEntryManager);
// Connect in to the status bar manager service
mCommandQueue = getComponent(CommandQueue.class);
@@ -725,6 +670,7 @@
mContext.registerReceiver(mWallpaperChangedReceiver, wallpaperChangedFilter);
mWallpaperChangedReceiver.onReceive(mContext, null);
+ mLockscreenUserManager.setUpWithPresenter(this, mEntryManager);
mCommandQueue.disable(switches[0], switches[6], false /* animate */);
setSystemUiVisibility(switches[1], switches[7], switches[8], 0xffffffff,
fullscreenStackBounds, dockedStackBounds);
@@ -739,8 +685,7 @@
}
// Set up the initial notification state.
- mNotificationListener = Dependency.get(NotificationListener.class);
- mNotificationListener.setUpWithPresenter(this);
+ mNotificationListener.setUpWithPresenter(this, mEntryManager);
if (DEBUG) {
Log.d(TAG, String.format(
@@ -774,15 +719,6 @@
// Lastly, call to the icon policy to install/update all the icons.
mIconPolicy = new PhoneStatusBarPolicy(mContext, mIconController);
- mHeadsUpObserver.onChange(true); // set up
- if (ENABLE_HEADS_UP) {
- mContext.getContentResolver().registerContentObserver(
- Settings.Global.getUriFor(Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED), true,
- mHeadsUpObserver);
- mContext.getContentResolver().registerContentObserver(
- Settings.Global.getUriFor(SETTING_HEADS_UP_TICKER), true,
- mHeadsUpObserver);
- }
mUnlockMethodCache = UnlockMethodCache.getInstance(mContext);
mUnlockMethodCache.addListener(this);
startKeyguard();
@@ -817,7 +753,7 @@
// into fragments, but the rest here, it leaves some awkward lifecycle and whatnot.
mNotificationPanel = mStatusBarWindow.findViewById(R.id.notification_panel);
mStackScroller = mStatusBarWindow.findViewById(R.id.notification_stack_scroller);
- mGutsManager.setUp(this, mStackScroller, mCheckSaveListener,
+ mGutsManager.setUpWithPresenter(this, mEntryManager, mStackScroller, mCheckSaveListener,
key -> {
try {
mBarService.onNotificationSettingsViewed(key);
@@ -825,7 +761,7 @@
// if we're here we're dead
}
});
- mNotificationLogger.setUpWithPresenter(this, mStackScroller);
+ mNotificationLogger.setUpWithEntryManager(mEntryManager, mStackScroller);
mNotificationPanel.setStatusBar(this);
mNotificationPanel.setGroupManager(mGroupManager);
mAboveShelfObserver = new AboveShelfObserver(mStackScroller);
@@ -864,11 +800,13 @@
mHeadsUpManager.addListener(mGroupManager);
mHeadsUpManager.addListener(mVisualStabilityManager);
mNotificationPanel.setHeadsUpManager(mHeadsUpManager);
- mNotificationData.setHeadsUpManager(mHeadsUpManager);
mGroupManager.setHeadsUpManager(mHeadsUpManager);
mHeadsUpManager.setVisualStabilityManager(mVisualStabilityManager);
putComponent(HeadsUpManager.class, mHeadsUpManager);
+ mEntryManager.setUpWithPresenter(this, mStackScroller, this, mHeadsUpManager);
+ mViewHierarchyManager.setUpWithPresenter(this, mEntryManager, mStackScroller);
+
if (MULTIUSER_DEBUG) {
mNotificationPanelDebugText = mNotificationPanel.findViewById(R.id.header_debug_info);
mNotificationPanelDebugText.setVisibility(View.VISIBLE);
@@ -884,7 +822,7 @@
// no window manager? good luck with that
}
- mStackScroller.setLongPressListener(getNotificationLongClicker());
+ mStackScroller.setLongPressListener(mEntryManager.getNotificationLongClicker());
mStackScroller.setStatusBar(this);
mStackScroller.setGroupManager(mGroupManager);
mStackScroller.setHeadsUpManager(mHeadsUpManager);
@@ -1107,7 +1045,7 @@
MessagingGroup.dropCache();
// start old BaseStatusBar.onDensityOrFontScaleChanged().
if (!KeyguardUpdateMonitor.getInstance(mContext).isSwitchingUser()) {
- updateNotificationsOnDensityOrFontScaleChanged();
+ mEntryManager.updateNotificationsOnDensityOrFontScaleChanged();
} else {
mReinflateNotificationsOnUserSwitched = true;
}
@@ -1171,20 +1109,6 @@
}
}
- private void updateNotificationsOnDensityOrFontScaleChanged() {
- ArrayList<Entry> activeNotifications = mNotificationData.getActiveNotifications();
- for (int i = 0; i < activeNotifications.size(); i++) {
- Entry entry = activeNotifications.get(i);
- boolean exposedGuts = mGutsManager.getExposedGuts() != null
- && entry.row.getGuts() == mGutsManager.getExposedGuts();
- entry.row.onDensityOrFontScaleChanged();
- if (exposedGuts) {
- mGutsManager.setExposedGuts(entry.row.getGuts());
- mGutsManager.bindGuts(entry.row);
- }
- }
- }
-
private void inflateSignalClusters() {
if (mKeyguardStatusBar != null) reinflateSignalCluster(mKeyguardStatusBar);
}
@@ -1298,7 +1222,7 @@
mStackScroller.setDismissAllInProgress(false);
for (ExpandableNotificationRow rowToRemove : viewsToRemove) {
if (mStackScroller.canChildBeDismissed(rowToRemove)) {
- removeNotification(rowToRemove.getEntry().key, null);
+ mEntryManager.removeNotification(rowToRemove.getEntry().key, null);
} else {
rowToRemove.resetTranslation();
}
@@ -1406,213 +1330,53 @@
return true;
}
- void awakenDreams() {
- SystemServicesProxy.getInstance(mContext).awakenDreamsAsync();
+ @Override
+ public void onPerformRemoveNotification(StatusBarNotification n) {
+ if (mStackScroller.hasPulsingNotifications() && mHeadsUpManager.getAllEntries().isEmpty()) {
+ // We were showing a pulse for a notification, but no notifications are pulsing anymore.
+ // Finish the pulse.
+ mDozeScrimController.pulseOutNow();
+ }
}
@Override
- public void addNotification(StatusBarNotification notification, RankingMap ranking) {
- String key = notification.getKey();
- if (DEBUG) Log.d(TAG, "addNotification key=" + key);
+ public void updateNotificationViews() {
+ // The function updateRowStates depends on both of these being non-null, so check them here.
+ // We may be called before they are set from DeviceProvisionedController's callback.
+ if (mStackScroller == null || mScrimController == null) return;
- mNotificationData.updateRanking(ranking);
- Entry shadeEntry = null;
- try {
- shadeEntry = createNotificationViews(notification);
- } catch (InflationException e) {
- handleInflationException(notification, e);
+ // Do not modify the notifications during collapse.
+ if (isCollapsing()) {
+ addPostCollapseAction(this::updateNotificationViews);
return;
}
- boolean isHeadsUped = shouldPeek(shadeEntry);
- if (!isHeadsUped && notification.getNotification().fullScreenIntent != null) {
- if (shouldSuppressFullScreenIntent(key)) {
- if (DEBUG) {
- Log.d(TAG, "No Fullscreen intent: suppressed by DND: " + key);
- }
- } else if (mNotificationData.getImportance(key)
- < NotificationManager.IMPORTANCE_HIGH) {
- if (DEBUG) {
- Log.d(TAG, "No Fullscreen intent: not important enough: "
- + key);
- }
- } else {
- // Stop screensaver if the notification has a fullscreen intent.
- // (like an incoming phone call)
- awakenDreams();
- // not immersive & a fullscreen alert should be shown
- if (DEBUG)
- Log.d(TAG, "Notification has fullScreenIntent; sending fullScreenIntent");
- try {
- EventLog.writeEvent(EventLogTags.SYSUI_FULLSCREEN_NOTIFICATION,
- key);
- notification.getNotification().fullScreenIntent.send();
- shadeEntry.notifyFullScreenIntentLaunched();
- mMetricsLogger.count("note_fullscreen", 1);
- } catch (PendingIntent.CanceledException e) {
- }
- }
- }
- abortExistingInflation(key);
+ mViewHierarchyManager.updateNotificationViews();
- mForegroundServiceController.addNotification(notification,
- mNotificationData.getImportance(key));
+ updateSpeedBumpIndex();
+ updateClearAll();
+ updateEmptyShadeView();
- mPendingNotifications.put(key, shadeEntry);
+ updateQsExpansionEnabled();
+
+ // Let's also update the icons
+ mNotificationIconAreaController.updateNotificationIcons(
+ mEntryManager.getNotificationData());
}
- private void abortExistingInflation(String key) {
- if (mPendingNotifications.containsKey(key)) {
- Entry entry = mPendingNotifications.get(key);
- entry.abortTask();
- mPendingNotifications.remove(key);
- }
- Entry addedEntry = mNotificationData.get(key);
- if (addedEntry != null) {
- addedEntry.abortTask();
- }
- }
-
- private void addEntry(Entry shadeEntry) {
- boolean isHeadsUped = shouldPeek(shadeEntry);
- if (isHeadsUped) {
- mHeadsUpManager.showNotification(shadeEntry);
- // Mark as seen immediately
- setNotificationShown(shadeEntry.notification);
- }
- addNotificationViews(shadeEntry);
+ @Override
+ public void onNotificationAdded(Entry shadeEntry) {
// Recalculate the position of the sliding windows and the titles.
setAreThereNotifications();
}
@Override
- public void handleInflationException(StatusBarNotification notification, Exception e) {
- handleNotificationError(notification, e.getMessage());
+ public void onNotificationUpdated(StatusBarNotification notification) {
+ setAreThereNotifications();
}
@Override
- public void onAsyncInflationFinished(Entry entry) {
- mPendingNotifications.remove(entry.key);
- // If there was an async task started after the removal, we don't want to add it back to
- // the list, otherwise we might get leaks.
- boolean isNew = mNotificationData.get(entry.key) == null;
- if (isNew && !entry.row.isRemoved()) {
- addEntry(entry);
- } else if (!isNew && entry.row.hasLowPriorityStateUpdated()) {
- mVisualStabilityManager.onLowPriorityUpdated(entry);
- updateNotificationShade();
- }
- entry.row.setLowPriorityStateUpdated(false);
- }
-
- private boolean shouldSuppressFullScreenIntent(String key) {
- if (isDeviceInVrMode()) {
- return true;
- }
-
- if (mPowerManager.isInteractive()) {
- return mNotificationData.shouldSuppressScreenOn(key);
- } else {
- return mNotificationData.shouldSuppressScreenOff(key);
- }
- }
-
- @Override
- public void updateNotificationRanking(RankingMap ranking) {
- mNotificationData.updateRanking(ranking);
- updateNotifications();
- }
-
- @Override
- public void removeNotification(String key, RankingMap ranking) {
- boolean deferRemoval = false;
- abortExistingInflation(key);
- if (mHeadsUpManager.isHeadsUp(key)) {
- // A cancel() in response to a remote input shouldn't be delayed, as it makes the
- // sending look longer than it takes.
- // Also we should not defer the removal if reordering isn't allowed since otherwise
- // some notifications can't disappear before the panel is closed.
- boolean ignoreEarliestRemovalTime = mRemoteInputManager.getController().isSpinning(key)
- && !FORCE_REMOTE_INPUT_HISTORY
- || !mVisualStabilityManager.isReorderingAllowed();
- deferRemoval = !mHeadsUpManager.removeNotification(key, ignoreEarliestRemovalTime);
- }
- mMediaManager.onNotificationRemoved(key);
-
- Entry entry = mNotificationData.get(key);
- if (FORCE_REMOTE_INPUT_HISTORY && mRemoteInputManager.getController().isSpinning(key)
- && entry.row != null && !entry.row.isDismissed()) {
- StatusBarNotification sbn = entry.notification;
-
- Notification.Builder b = Notification.Builder
- .recoverBuilder(mContext, sbn.getNotification().clone());
- CharSequence[] oldHistory = sbn.getNotification().extras
- .getCharSequenceArray(Notification.EXTRA_REMOTE_INPUT_HISTORY);
- CharSequence[] newHistory;
- if (oldHistory == null) {
- newHistory = new CharSequence[1];
- } else {
- newHistory = new CharSequence[oldHistory.length + 1];
- System.arraycopy(oldHistory, 0, newHistory, 1, oldHistory.length);
- }
- newHistory[0] = String.valueOf(entry.remoteInputText);
- b.setRemoteInputHistory(newHistory);
-
- Notification newNotification = b.build();
-
- // Undo any compatibility view inflation
- newNotification.contentView = sbn.getNotification().contentView;
- newNotification.bigContentView = sbn.getNotification().bigContentView;
- newNotification.headsUpContentView = sbn.getNotification().headsUpContentView;
-
- StatusBarNotification newSbn = new StatusBarNotification(sbn.getPackageName(),
- sbn.getOpPkg(),
- sbn.getId(), sbn.getTag(), sbn.getUid(), sbn.getInitialPid(),
- newNotification, sbn.getUser(), sbn.getOverrideGroupKey(), sbn.getPostTime());
- boolean updated = false;
- try {
- updateNotificationInternal(newSbn, null);
- updated = true;
- } catch (InflationException e) {
- deferRemoval = false;
- }
- if (updated) {
- Log.w(TAG, "Keeping notification around after sending remote input "+ entry.key);
- mRemoteInputManager.getKeysKeptForRemoteInput().add(entry.key);
- return;
- }
- }
- if (deferRemoval) {
- mLatestRankingMap = ranking;
- mHeadsUpEntriesToRemoveOnSwitch.add(mHeadsUpManager.getEntry(key));
- return;
- }
-
- if (mRemoteInputManager.onRemoveNotification(entry)) {
- mLatestRankingMap = ranking;
- return;
- }
-
- if (entry != null && mGutsManager.getExposedGuts() != null
- && mGutsManager.getExposedGuts() == entry.row.getGuts()
- && entry.row.getGuts() != null && !entry.row.getGuts().isLeavebehind()) {
- Log.w(TAG, "Keeping notification because it's showing guts. " + key);
- mLatestRankingMap = ranking;
- mGutsManager.setKeyToRemoveOnGutsClosed(key);
- return;
- }
-
- if (entry != null) {
- mForegroundServiceController.removeNotification(entry.notification);
- }
-
- if (entry != null && entry.row != null) {
- entry.row.setRemoved();
- mStackScroller.cleanUpViewState(entry.row);
- }
- // Let's remove the children if this was a summary
- handleGroupSummaryRemoved(key);
- StatusBarNotification old = removeNotificationViews(key, ranking);
+ public void onNotificationRemoved(String key, StatusBarNotification old) {
if (SPEW) Log.d(TAG, "removeNotification key=" + key + " old=" + old);
if (old != null) {
@@ -1629,195 +1393,6 @@
}
/**
- * Ensures that the group children are cancelled immediately when the group summary is cancelled
- * instead of waiting for the notification manager to send all cancels. Otherwise this could
- * lead to flickers.
- *
- * This also ensures that the animation looks nice and only consists of a single disappear
- * animation instead of multiple.
- * @param key the key of the notification was removed
- *
- */
- private void handleGroupSummaryRemoved(String key) {
- Entry entry = mNotificationData.get(key);
- if (entry != null && entry.row != null
- && entry.row.isSummaryWithChildren()) {
- if (entry.notification.getOverrideGroupKey() != null && !entry.row.isDismissed()) {
- // We don't want to remove children for autobundled notifications as they are not
- // always cancelled. We only remove them if they were dismissed by the user.
- return;
- }
- List<ExpandableNotificationRow> notificationChildren =
- entry.row.getNotificationChildren();
- for (int i = 0; i < notificationChildren.size(); i++) {
- ExpandableNotificationRow row = notificationChildren.get(i);
- if ((row.getStatusBarNotification().getNotification().flags
- & Notification.FLAG_FOREGROUND_SERVICE) != 0) {
- // the child is a foreground service notification which we can't remove!
- continue;
- }
- row.setKeepInParent(true);
- // we need to set this state earlier as otherwise we might generate some weird
- // animations
- row.setRemoved();
- }
- }
- }
-
- protected void performRemoveNotification(StatusBarNotification n) {
- Entry entry = mNotificationData.get(n.getKey());
- mRemoteInputManager.onPerformRemoveNotification(n, entry);
- // start old BaseStatusBar.performRemoveNotification.
- final String pkg = n.getPackageName();
- final String tag = n.getTag();
- final int id = n.getId();
- final int userId = n.getUserId();
- try {
- int dismissalSurface = NotificationStats.DISMISSAL_SHADE;
- if (isHeadsUp(n.getKey())) {
- dismissalSurface = NotificationStats.DISMISSAL_PEEK;
- } else if (mStackScroller.hasPulsingNotifications()) {
- dismissalSurface = NotificationStats.DISMISSAL_AOD;
- }
- mBarService.onNotificationClear(pkg, tag, id, userId, n.getKey(), dismissalSurface);
- removeNotification(n.getKey(), null);
-
- } catch (RemoteException ex) {
- // system process is dead if we're here.
- }
- if (mStackScroller.hasPulsingNotifications() && mHeadsUpManager.getAllEntries().isEmpty()) {
- // We were showing a pulse for a notification, but no notifications are pulsing anymore.
- // Finish the pulse.
- mDozeScrimController.pulseOutNow();
- }
- // end old BaseStatusBar.performRemoveNotification.
- }
-
- private void updateNotificationShade() {
- if (mStackScroller == null) return;
-
- // Do not modify the notifications during collapse.
- if (isCollapsing()) {
- addPostCollapseAction(this::updateNotificationShade);
- return;
- }
-
- ArrayList<Entry> activeNotifications = mNotificationData.getActiveNotifications();
- ArrayList<ExpandableNotificationRow> toShow = new ArrayList<>(activeNotifications.size());
- final int N = activeNotifications.size();
- for (int i = 0; i < N; i++) {
- Entry ent = activeNotifications.get(i);
- if (ent.row.isDismissed() || ent.row.isRemoved()) {
- // we don't want to update removed notifications because they could
- // temporarily become children if they were isolated before.
- continue;
- }
- int userId = ent.notification.getUserId();
-
- // Display public version of the notification if we need to redact.
- // TODO: This area uses a lot of calls into NotificationLockscreenUserManager.
- // We can probably move some of this code there.
- boolean devicePublic = mLockscreenUserManager.isLockscreenPublicMode(
- mLockscreenUserManager.getCurrentUserId());
- boolean userPublic = devicePublic
- || mLockscreenUserManager.isLockscreenPublicMode(userId);
- boolean needsRedaction = mLockscreenUserManager.needsRedaction(ent);
- boolean sensitive = userPublic && needsRedaction;
- boolean deviceSensitive = devicePublic
- && !mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(
- mLockscreenUserManager.getCurrentUserId());
- ent.row.setSensitive(sensitive, deviceSensitive);
- ent.row.setNeedsRedaction(needsRedaction);
- if (mGroupManager.isChildInGroupWithSummary(ent.row.getStatusBarNotification())) {
- ExpandableNotificationRow summary = mGroupManager.getGroupSummary(
- ent.row.getStatusBarNotification());
- List<ExpandableNotificationRow> orderedChildren =
- mTmpChildOrderMap.get(summary);
- if (orderedChildren == null) {
- orderedChildren = new ArrayList<>();
- mTmpChildOrderMap.put(summary, orderedChildren);
- }
- orderedChildren.add(ent.row);
- } else {
- toShow.add(ent.row);
- }
-
- }
-
- ArrayList<ExpandableNotificationRow> toRemove = new ArrayList<>();
- for (int i=0; i< mStackScroller.getChildCount(); i++) {
- View child = mStackScroller.getChildAt(i);
- if (!toShow.contains(child) && child instanceof ExpandableNotificationRow) {
- toRemove.add((ExpandableNotificationRow) child);
- }
- }
-
- for (ExpandableNotificationRow remove : toRemove) {
- if (mGroupManager.isChildInGroupWithSummary(remove.getStatusBarNotification())) {
- // we are only transferring this notification to its parent, don't generate an
- // animation
- mStackScroller.setChildTransferInProgress(true);
- }
- if (remove.isSummaryWithChildren()) {
- remove.removeAllChildren();
- }
- mStackScroller.removeView(remove);
- mStackScroller.setChildTransferInProgress(false);
- }
-
- removeNotificationChildren();
-
- for (int i = 0; i < toShow.size(); i++) {
- View v = toShow.get(i);
- if (v.getParent() == null) {
- mVisualStabilityManager.notifyViewAddition(v);
- mStackScroller.addView(v);
- }
- }
-
- addNotificationChildrenAndSort();
-
- // So after all this work notifications still aren't sorted correctly.
- // Let's do that now by advancing through toShow and mStackScroller in
- // lock-step, making sure mStackScroller matches what we see in toShow.
- int j = 0;
- for (int i = 0; i < mStackScroller.getChildCount(); i++) {
- View child = mStackScroller.getChildAt(i);
- if (!(child instanceof ExpandableNotificationRow)) {
- // We don't care about non-notification views.
- continue;
- }
-
- ExpandableNotificationRow targetChild = toShow.get(j);
- if (child != targetChild) {
- // Oops, wrong notification at this position. Put the right one
- // here and advance both lists.
- if (mVisualStabilityManager.canReorderNotification(targetChild)) {
- mStackScroller.changeViewPosition(targetChild, i);
- } else {
- mVisualStabilityManager.addReorderingAllowedCallback(this);
- }
- }
- j++;
-
- }
-
- mVisualStabilityManager.onReorderingFinished();
- // clear the map again for the next usage
- mTmpChildOrderMap.clear();
-
- updateRowStates();
- updateSpeedBumpIndex();
- updateClearAll();
- updateEmptyShadeView();
-
- updateQsExpansionEnabled();
-
- // Let's also update the icons
- mNotificationIconAreaController.updateNotificationIcons(mNotificationData);
- }
-
- /**
* Disable QS if device not provisioned.
* If the user switcher is simple then disable QS during setup because
* the user intends to use the lock screen user switcher, QS in not needed.
@@ -1832,81 +1407,6 @@
&& !ONLY_CORE_APPS);
}
- private void addNotificationChildrenAndSort() {
- // Let's now add all notification children which are missing
- boolean orderChanged = false;
- for (int i = 0; i < mStackScroller.getChildCount(); i++) {
- View view = mStackScroller.getChildAt(i);
- if (!(view instanceof ExpandableNotificationRow)) {
- // We don't care about non-notification views.
- continue;
- }
-
- ExpandableNotificationRow parent = (ExpandableNotificationRow) view;
- List<ExpandableNotificationRow> children = parent.getNotificationChildren();
- List<ExpandableNotificationRow> orderedChildren = mTmpChildOrderMap.get(parent);
-
- for (int childIndex = 0; orderedChildren != null && childIndex < orderedChildren.size();
- childIndex++) {
- ExpandableNotificationRow childView = orderedChildren.get(childIndex);
- if (children == null || !children.contains(childView)) {
- if (childView.getParent() != null) {
- Log.wtf(TAG, "trying to add a notification child that already has " +
- "a parent. class:" + childView.getParent().getClass() +
- "\n child: " + childView);
- // This shouldn't happen. We can recover by removing it though.
- ((ViewGroup) childView.getParent()).removeView(childView);
- }
- mVisualStabilityManager.notifyViewAddition(childView);
- parent.addChildNotification(childView, childIndex);
- mStackScroller.notifyGroupChildAdded(childView);
- }
- }
-
- // Finally after removing and adding has been performed we can apply the order.
- orderChanged |= parent.applyChildOrder(orderedChildren, mVisualStabilityManager, this);
- }
- if (orderChanged) {
- mStackScroller.generateChildOrderChangedEvent();
- }
- }
-
- private void removeNotificationChildren() {
- // First let's remove all children which don't belong in the parents
- ArrayList<ExpandableNotificationRow> toRemove = new ArrayList<>();
- for (int i = 0; i < mStackScroller.getChildCount(); i++) {
- View view = mStackScroller.getChildAt(i);
- if (!(view instanceof ExpandableNotificationRow)) {
- // We don't care about non-notification views.
- continue;
- }
-
- ExpandableNotificationRow parent = (ExpandableNotificationRow) view;
- List<ExpandableNotificationRow> children = parent.getNotificationChildren();
- List<ExpandableNotificationRow> orderedChildren = mTmpChildOrderMap.get(parent);
-
- if (children != null) {
- toRemove.clear();
- for (ExpandableNotificationRow childRow : children) {
- if ((orderedChildren == null
- || !orderedChildren.contains(childRow))
- && !childRow.keepInParent()) {
- toRemove.add(childRow);
- }
- }
- for (ExpandableNotificationRow remove : toRemove) {
- parent.removeChildNotification(remove);
- if (mNotificationData.get(remove.getStatusBarNotification().getKey()) == null) {
- // We only want to add an animation if the view is completely removed
- // otherwise it's just a transfer
- mStackScroller.notifyGroupChildRemoved(remove,
- parent.getChildrenContainer());
- }
- }
- }
- }
- }
-
public void addQsTile(ComponentName tile) {
mQSPanel.getHost().addTile(tile);
}
@@ -1949,7 +1449,7 @@
private void updateEmptyShadeView() {
boolean showEmptyShadeView =
mState != StatusBarState.KEYGUARD &&
- mNotificationData.getActiveNotifications().size() == 0;
+ mEntryManager.getNotificationData().getActiveNotifications().size() == 0;
mNotificationPanel.showEmptyShadeView(showEmptyShadeView);
}
@@ -1964,7 +1464,8 @@
}
ExpandableNotificationRow row = (ExpandableNotificationRow) view;
currentIndex++;
- if (!mNotificationData.isAmbient(row.getStatusBarNotification().getKey())) {
+ if (!mEntryManager.getNotificationData().isAmbient(
+ row.getStatusBarNotification().getKey())) {
speedBumpIndex = currentIndex;
}
}
@@ -1976,15 +1477,9 @@
return entry.row.getParent() instanceof NotificationStackScrollLayout;
}
- @Override
- public void updateNotifications() {
- mNotificationData.filterAndSort();
-
- updateNotificationShade();
- }
public void requestNotificationUpdate() {
- updateNotifications();
+ mEntryManager.updateNotifications();
}
protected void setAreThereNotifications() {
@@ -1993,7 +1488,7 @@
final boolean clearable = hasActiveNotifications() &&
hasActiveClearableNotifications();
Log.d(TAG, "setAreThereNotifications: N=" +
- mNotificationData.getActiveNotifications().size() + " any=" +
+ mEntryManager.getNotificationData().getActiveNotifications().size() + " any=" +
hasActiveNotifications() + " clearable=" + clearable);
}
@@ -2278,9 +1773,8 @@
}
if ((diff1 & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0) {
- mDisableNotificationAlerts =
- (state1 & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0;
- mHeadsUpObserver.onChange(true);
+ mEntryManager.setDisableNotificationAlerts(
+ (state1 & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0);
}
if ((diff2 & StatusBarManager.DISABLE2_QUICK_SETTINGS) != 0) {
@@ -2343,10 +1837,40 @@
return getBarState() == StatusBarState.KEYGUARD;
}
+ @Override
public boolean isDozing() {
return mDozing;
}
+ @Override
+ public boolean shouldPeek(Entry entry, StatusBarNotification sbn) {
+ if (mIsOccluded && !isDozing()) {
+ boolean devicePublic = mLockscreenUserManager.
+ isLockscreenPublicMode(mLockscreenUserManager.getCurrentUserId());
+ boolean userPublic = devicePublic
+ || mLockscreenUserManager.isLockscreenPublicMode(sbn.getUserId());
+ boolean needsRedaction = mLockscreenUserManager.needsRedaction(entry);
+ if (userPublic && needsRedaction) {
+ return false;
+ }
+ }
+
+ if (sbn.getNotification().fullScreenIntent != null) {
+ if (mAccessibilityManager.isTouchExplorationEnabled()) {
+ if (DEBUG) Log.d(TAG, "No peeking: accessible fullscreen: " + sbn.getKey());
+ return false;
+ } else if (isDozing()) {
+ // We never want heads up when we are dozing.
+ return false;
+ } else {
+ // we only allow head-up on the lockscreen if it doesn't have a fullscreen intent
+ return !mStatusBarKeyguardViewManager.isShowing()
+ || mStatusBarKeyguardViewManager.isOccluded();
+ }
+ }
+ return true;
+ }
+
@Override // NotificationData.Environment
public String getCurrentMediaNotificationKey() {
return mMediaManager.getMediaNotificationKey();
@@ -2415,34 +1939,10 @@
@Override
public void onHeadsUpStateChanged(Entry entry, boolean isHeadsUp) {
- if (!isHeadsUp && mHeadsUpEntriesToRemoveOnSwitch.contains(entry)) {
- removeNotification(entry.key, mLatestRankingMap);
- mHeadsUpEntriesToRemoveOnSwitch.remove(entry);
- if (mHeadsUpEntriesToRemoveOnSwitch.isEmpty()) {
- mLatestRankingMap = null;
- }
- } else {
- updateNotificationRanking(null);
- if (isHeadsUp) {
- mDozeServiceHost.fireNotificationHeadsUp();
- }
- }
+ mEntryManager.onHeadsUpStateChanged(entry, isHeadsUp);
- }
-
- protected void updateHeadsUp(String key, Entry entry, boolean shouldPeek,
- boolean alertAgain) {
- final boolean wasHeadsUp = isHeadsUp(key);
- if (wasHeadsUp) {
- if (!shouldPeek) {
- // We don't want this to be interrupting anymore, lets remove it
- mHeadsUpManager.removeNotification(key, false /* ignoreEarliestRemovalTime */);
- } else {
- mHeadsUpManager.updateNotification(entry, alertAgain);
- }
- } else if (shouldPeek && alertAgain) {
- // This notification was updated to be a heads-up, show it!
- mHeadsUpManager.showNotification(entry);
+ if (isHeadsUp) {
+ mDozeServiceHost.fireNotificationHeadsUp();
}
}
@@ -2452,14 +1952,6 @@
}
}
- public boolean isHeadsUp(String key) {
- return mHeadsUpManager.isHeadsUp(key);
- }
-
- protected boolean isSnoozedPackage(StatusBarNotification sbn) {
- return mHeadsUpManager.isSnoozed(sbn.getPackageName());
- }
-
public boolean isKeyguardCurrentlySecure() {
return !mUnlockMethodCache.canSkipBouncer();
}
@@ -2489,11 +1981,6 @@
return mDozeScrimController != null && mDozeScrimController.isPulsing();
}
- @Override
- public void onReorderingAllowed() {
- updateNotifications();
- }
-
public boolean isLaunchTransitionFadingAway() {
return mLaunchTransitionFadingAway;
}
@@ -3127,14 +2614,6 @@
+ " scroll " + mStackScroller.getScrollX()
+ "," + mStackScroller.getScrollY());
}
- pw.print(" mPendingNotifications=");
- if (mPendingNotifications.size() == 0) {
- pw.println("null");
- } else {
- for (Entry entry : mPendingNotifications.values()) {
- pw.println(entry.notification);
- }
- }
pw.print(" mInteractingWindows="); pw.println(mInteractingWindows);
pw.print(" mStatusBarWindowState=");
@@ -3146,8 +2625,7 @@
pw.println(Settings.Global.zenModeToString(Settings.Global.getInt(
mContext.getContentResolver(), Settings.Global.ZEN_MODE,
Settings.Global.ZEN_MODE_OFF)));
- pw.print(" mUseHeadsUp=");
- pw.println(mUseHeadsUp);
+
if (mStatusBarView != null) {
dumpBarTransitions(pw, "mStatusBarView", mStatusBarView.getBarTransitions());
}
@@ -3189,8 +2667,8 @@
}
if (DUMPTRUCK) {
- synchronized (mNotificationData) {
- mNotificationData.dump(pw, " ");
+ synchronized (mEntryManager.getNotificationData()) {
+ mEntryManager.getNotificationData().dump(pw, " ");
}
if (false) {
@@ -3250,19 +2728,20 @@
private void addStatusBarWindow() {
makeStatusBarView();
mStatusBarWindowManager = Dependency.get(StatusBarWindowManager.class);
- mRemoteInputManager.setUpWithPresenter(this, this, new RemoteInputController.Delegate() {
- public void setRemoteInputActive(NotificationData.Entry entry,
- boolean remoteInputActive) {
- mHeadsUpManager.setRemoteInputActive(entry, remoteInputActive);
- }
- public void lockScrollTo(NotificationData.Entry entry) {
- mStackScroller.lockScrollTo(entry.row);
- }
- public void requestDisallowLongPressAndDismiss() {
- mStackScroller.requestDisallowLongPress();
- mStackScroller.requestDisallowDismiss();
- }
- });
+ mRemoteInputManager.setUpWithPresenter(this, mEntryManager, this,
+ new RemoteInputController.Delegate() {
+ public void setRemoteInputActive(NotificationData.Entry entry,
+ boolean remoteInputActive) {
+ mHeadsUpManager.setRemoteInputActive(entry, remoteInputActive);
+ }
+ public void lockScrollTo(NotificationData.Entry entry) {
+ mStackScroller.lockScrollTo(entry.row);
+ }
+ public void requestDisallowLongPressAndDismiss() {
+ mStackScroller.requestDisallowLongPress();
+ mStackScroller.requestDisallowDismiss();
+ }
+ });
mStatusBarWindowManager.add(mStatusBarWindow, getStatusBarHeight());
}
@@ -3429,7 +2908,8 @@
};
public void resetUserExpandedStates() {
- ArrayList<Entry> activeNotifications = mNotificationData.getActiveNotifications();
+ ArrayList<Entry> activeNotifications = mEntryManager.getNotificationData()
+ .getActiveNotifications();
final int notificationCount = activeNotifications.size();
for (int i = 0; i < notificationCount; i++) {
NotificationData.Entry entry = activeNotifications.get(i);
@@ -3472,7 +2952,7 @@
Log.v(TAG, "configuration changed: " + mContext.getResources().getConfiguration());
}
- updateRowStates();
+ mViewHierarchyManager.updateRowStates();
mScreenPinningRequest.onConfigurationChanged();
}
@@ -3484,12 +2964,12 @@
if (MULTIUSER_DEBUG) mNotificationPanelDebugText.setText("USER " + newUserId);
animateCollapsePanels();
updatePublicMode();
- mNotificationData.filterAndSort();
+ mEntryManager.getNotificationData().filterAndSort();
if (mReinflateNotificationsOnUserSwitched) {
- updateNotificationsOnDensityOrFontScaleChanged();
+ mEntryManager.updateNotificationsOnDensityOrFontScaleChanged();
mReinflateNotificationsOnUserSwitched = false;
}
- updateNotificationShade();
+ updateNotificationViews();
mMediaManager.clearCurrentMediaNotification();
setLockscreenUser(newUserId);
}
@@ -3499,6 +2979,13 @@
return mLockscreenUserManager;
}
+ @Override
+ public void onBindRow(Entry entry, PackageManager pmUser,
+ StatusBarNotification sbn, ExpandableNotificationRow row) {
+ row.setAboveShelfChangedListener(mAboveShelfObserver);
+ row.setSecureStateProvider(this::isKeyguardCurrentlySecure);
+ }
+
protected void setLockscreenUser(int newUserId) {
mLockscreenWallpaper.setCurrentUser(newUserId);
mScrimController.setCurrentUser(newUserId);
@@ -3559,7 +3046,8 @@
try {
// consider the transition from peek to expanded to be a panel open,
// but not one that clears notification effects.
- int notificationLoad = mNotificationData.getActiveNotifications().size();
+ int notificationLoad = mEntryManager.getNotificationData()
+ .getActiveNotifications().size();
mBarService.onPanelRevealed(false, notificationLoad);
} catch (RemoteException ex) {
// Won't fail unless the world has ended.
@@ -3577,7 +3065,8 @@
!isPresenterFullyCollapsed() &&
(mState == StatusBarState.SHADE
|| mState == StatusBarState.SHADE_LOCKED);
- int notificationLoad = mNotificationData.getActiveNotifications().size();
+ int notificationLoad = mEntryManager.getNotificationData().getActiveNotifications()
+ .size();
if (pinnedHeadsUp && isPresenterFullyCollapsed()) {
notificationLoad = 1;
}
@@ -3712,7 +3201,7 @@
} catch (RemoteException e) {
// Ignore.
}
- mDeviceProvisionedController.removeCallback(mDeviceProvisionedListener);
+ mEntryManager.destroy();
// End old BaseStatusBar.destroy().
if (mStatusBarWindow != null) {
mWindowManager.removeViewImmediate(mStatusBarWindow);
@@ -4165,7 +3654,7 @@
updateDozingState();
updatePublicMode();
updateStackScrollerState(goingToFullShade, fromShadeLocked);
- updateNotifications();
+ mEntryManager.updateNotifications();
checkBarModes();
updateScrimController();
updateMediaMetaData(false, mState != StatusBarState.KEYGUARD);
@@ -4236,7 +3725,7 @@
mKeyguardIndicationController.setDozing(mDozing);
mNotificationPanel.setDark(mDozing, animate);
updateQsExpansionEnabled();
- updateRowStates();
+ mViewHierarchyManager.updateRowStates();
Trace.endSection();
}
@@ -4440,7 +3929,8 @@
}
}
- protected int getMaxKeyguardNotifications(boolean recompute) {
+ @Override
+ public int getMaxNotificationsWhileLocked(boolean recompute) {
if (recompute) {
mMaxKeyguardNotifications = Math.max(1,
mNotificationPanel.computeMaxKeyguardNotifications(
@@ -4450,8 +3940,8 @@
return mMaxKeyguardNotifications;
}
- public int getMaxKeyguardNotifications() {
- return getMaxKeyguardNotifications(false /* recompute */);
+ public int getMaxNotificationsWhileLocked() {
+ return getMaxNotificationsWhileLocked(false /* recompute */);
}
// TODO: Figure out way to remove these.
@@ -4679,7 +4169,7 @@
@Override
public void onWorkChallengeChanged() {
updatePublicMode();
- updateNotifications();
+ mEntryManager.updateNotifications();
if (mPendingWorkRemoteInputView != null
&& !mLockscreenUserManager.isAnyProfilePublicMode()) {
// Expand notification panel and the notification row, then click on remote input view
@@ -4889,7 +4379,7 @@
}
public boolean hasActiveNotifications() {
- return !mNotificationData.getActiveNotifications().isEmpty();
+ return !mEntryManager.getNotificationData().getActiveNotifications().isEmpty();
}
@Override
@@ -5268,7 +4758,6 @@
protected IStatusBarService mBarService;
// all notifications
- protected NotificationData mNotificationData;
protected NotificationStackScrollLayout mStackScroller;
protected NotificationGroupManager mGroupManager;
@@ -5280,22 +4769,17 @@
private AboveShelfObserver mAboveShelfObserver;
// handling reordering
- protected final VisualStabilityManager mVisualStabilityManager = new VisualStabilityManager();
-
+ protected VisualStabilityManager mVisualStabilityManager;
protected AccessibilityManager mAccessibilityManager;
protected boolean mDeviceInteractive;
protected boolean mVisible;
- protected final ArraySet<Entry> mHeadsUpEntriesToRemoveOnSwitch = new ArraySet<>();
// mScreenOnFromKeyguard && mVisible.
private boolean mVisibleToUser;
- protected boolean mUseHeadsUp = false;
- protected boolean mDisableNotificationAlerts = false;
-
protected DevicePolicyManager mDevicePolicyManager;
protected PowerManager mPowerManager;
protected StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
@@ -5304,7 +4788,6 @@
private LockPatternUtils mLockPatternUtils;
private DeviceProvisionedController mDeviceProvisionedController
= Dependency.get(DeviceProvisionedController.class);
- protected SystemServicesProxy mSystemServicesProxy;
// UI-specific methods
@@ -5319,8 +4802,6 @@
protected DismissView mDismissView;
protected EmptyShadeView mEmptyShadeView;
- private final NotificationClicker mNotificationClicker = new NotificationClicker();
-
protected AssistManager mAssistManager;
protected boolean mVrMode;
@@ -5345,14 +4826,6 @@
return mVrMode;
}
- private final DeviceProvisionedListener mDeviceProvisionedListener =
- new DeviceProvisionedListener() {
- @Override
- public void onDeviceProvisionedChanged() {
- updateNotifications();
- }
- };
-
private final BroadcastReceiver mBannerActionBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -5377,6 +4850,121 @@
}
};
+ @Override
+ public void onNotificationClicked(StatusBarNotification sbn, ExpandableNotificationRow row) {
+ Notification notification = sbn.getNotification();
+ final PendingIntent intent = notification.contentIntent != null
+ ? notification.contentIntent
+ : notification.fullScreenIntent;
+ final String notificationKey = sbn.getKey();
+
+ final boolean afterKeyguardGone = intent.isActivity()
+ && PreviewInflater.wouldLaunchResolverActivity(mContext, intent.getIntent(),
+ mLockscreenUserManager.getCurrentUserId());
+ dismissKeyguardThenExecute(() -> {
+ // TODO: Some of this code may be able to move to NotificationEntryManager.
+ if (mHeadsUpManager != null && mHeadsUpManager.isHeadsUp(notificationKey)) {
+ // Release the HUN notification to the shade.
+
+ if (isPresenterFullyCollapsed()) {
+ HeadsUpManager.setIsClickedNotification(row, true);
+ }
+ //
+ // In most cases, when FLAG_AUTO_CANCEL is set, the notification will
+ // become canceled shortly by NoMan, but we can't assume that.
+ mHeadsUpManager.releaseImmediately(notificationKey);
+ }
+ StatusBarNotification parentToCancel = null;
+ if (shouldAutoCancel(sbn) && mGroupManager.isOnlyChildInGroup(sbn)) {
+ StatusBarNotification summarySbn =
+ mGroupManager.getLogicalGroupSummary(sbn).getStatusBarNotification();
+ if (shouldAutoCancel(summarySbn)) {
+ parentToCancel = summarySbn;
+ }
+ }
+ final StatusBarNotification parentToCancelFinal = parentToCancel;
+ final Runnable runnable = () -> {
+ try {
+ // The intent we are sending is for the application, which
+ // won't have permission to immediately start an activity after
+ // the user switches to home. We know it is safe to do at this
+ // point, so make sure new activity switches are now allowed.
+ ActivityManager.getService().resumeAppSwitches();
+ } catch (RemoteException e) {
+ }
+ if (intent != null) {
+ // If we are launching a work activity and require to launch
+ // separate work challenge, we defer the activity action and cancel
+ // notification until work challenge is unlocked.
+ if (intent.isActivity()) {
+ final int userId = intent.getCreatorUserHandle().getIdentifier();
+ if (mLockPatternUtils.isSeparateProfileChallengeEnabled(userId)
+ && mKeyguardManager.isDeviceLocked(userId)) {
+ // TODO(b/28935539): should allow certain activities to
+ // bypass work challenge
+ if (startWorkChallengeIfNecessary(userId, intent.getIntentSender(),
+ notificationKey)) {
+ // Show work challenge, do not run PendingIntent and
+ // remove notification
+ return;
+ }
+ }
+ }
+ try {
+ intent.send(null, 0, null, null, null, null, getActivityOptions());
+ } catch (PendingIntent.CanceledException e) {
+ // the stack trace isn't very helpful here.
+ // Just log the exception message.
+ Log.w(TAG, "Sending contentIntent failed: " + e);
+
+ // TODO: Dismiss Keyguard.
+ }
+ if (intent.isActivity()) {
+ mAssistManager.hideAssist();
+ }
+ }
+
+ try {
+ mBarService.onNotificationClick(notificationKey);
+ } catch (RemoteException ex) {
+ // system process is dead if we're here.
+ }
+ if (parentToCancelFinal != null) {
+ // We have to post it to the UI thread for synchronization
+ mHandler.post(() -> {
+ Runnable removeRunnable =
+ () -> mEntryManager.performRemoveNotification(parentToCancelFinal);
+ if (isCollapsing()) {
+ // To avoid lags we're only performing the remove
+ // after the shade was collapsed
+ addPostCollapseAction(removeRunnable);
+ } else {
+ removeRunnable.run();
+ }
+ });
+ }
+ };
+
+ if (mStatusBarKeyguardViewManager.isShowing()
+ && mStatusBarKeyguardViewManager.isOccluded()) {
+ mStatusBarKeyguardViewManager.addAfterKeyguardGoneRunnable(runnable);
+ } else {
+ new Thread(runnable).start();
+ }
+
+ if (!mNotificationPanel.isFullyCollapsed()) {
+ // close the shade if it was open
+ animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */,
+ true /* delayed */);
+ visibilityChanged(false);
+
+ return true;
+ } else {
+ return false;
+ }
+ }, afterKeyguardGone);
+ }
+
protected NotificationListener mNotificationListener;
protected void notifyUserAboutHiddenNotifications() {
@@ -5438,14 +5026,6 @@
return mLockscreenUserManager.isCurrentProfile(notificationUserId);
}
- protected void setNotificationShown(StatusBarNotification n) {
- try {
- mNotificationListener.setNotificationsShown(new String[]{n.getKey()});
- } catch (RuntimeException e) {
- Log.d(TAG, "failed setNotificationsShown: ", e);
- }
- }
-
@Override
public NotificationGroupManager getGroupManager() {
return mGroupManager;
@@ -5475,15 +5055,15 @@
}
}
- protected ExpandableNotificationRow.LongPressListener getNotificationLongClicker() {
- return (v, x, y, item) -> mGutsManager.openGuts(v, x, y, item);
- }
-
@Override
public void toggleSplitScreen() {
toggleSplitScreenMode(-1 /* metricsDockAction */, -1 /* metricsUndockAction */);
}
+ void awakenDreams() {
+ SystemServicesProxy.getInstance(mContext).awakenDreamsAsync();
+ }
+
@Override
public void preloadRecentApps() {
int msg = MSG_PRELOAD_RECENT_APPS;
@@ -5561,104 +5141,14 @@
if (mState == StatusBarState.KEYGUARD) {
// Since the number of notifications is determined based on the height of the view, we
// need to update them.
- int maxBefore = getMaxKeyguardNotifications(false /* recompute */);
- int maxNotifications = getMaxKeyguardNotifications(true /* recompute */);
+ int maxBefore = getMaxNotificationsWhileLocked(false /* recompute */);
+ int maxNotifications = getMaxNotificationsWhileLocked(true /* recompute */);
if (maxBefore != maxNotifications) {
- updateRowStates();
+ mViewHierarchyManager.updateRowStates();
}
}
}
- protected void inflateViews(Entry entry, ViewGroup parent) {
- PackageManager pmUser = getPackageManagerForUser(mContext,
- entry.notification.getUser().getIdentifier());
-
- final StatusBarNotification sbn = entry.notification;
- if (entry.row != null) {
- entry.reset();
- updateNotification(entry, pmUser, sbn, entry.row);
- } else {
- new RowInflaterTask().inflate(mContext, parent, entry,
- row -> {
- bindRow(entry, pmUser, sbn, row);
- updateNotification(entry, pmUser, sbn, row);
- });
- }
-
- }
-
- private void bindRow(Entry entry, PackageManager pmUser,
- StatusBarNotification sbn, ExpandableNotificationRow row) {
- row.setExpansionLogger(this, entry.notification.getKey());
- row.setGroupManager(mGroupManager);
- row.setHeadsUpManager(mHeadsUpManager);
- row.setAboveShelfChangedListener(mAboveShelfObserver);
- row.setOnExpandClickListener(this);
- row.setInflationCallback(this);
- row.setSecureStateProvider(this::isKeyguardCurrentlySecure);
- row.setLongPressListener(getNotificationLongClicker());
- mRemoteInputManager.bindRow(row);
-
- // Get the app name.
- // Note that Notification.Builder#bindHeaderAppName has similar logic
- // but since this field is used in the guts, it must be accurate.
- // Therefore we will only show the application label, or, failing that, the
- // package name. No substitutions.
- final String pkg = sbn.getPackageName();
- String appname = pkg;
- try {
- final ApplicationInfo info = pmUser.getApplicationInfo(pkg,
- PackageManager.MATCH_UNINSTALLED_PACKAGES
- | PackageManager.MATCH_DISABLED_COMPONENTS);
- if (info != null) {
- appname = String.valueOf(pmUser.getApplicationLabel(info));
- }
- } catch (NameNotFoundException e) {
- // Do nothing
- }
- row.setAppName(appname);
- row.setOnDismissRunnable(() ->
- performRemoveNotification(row.getStatusBarNotification()));
- row.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
- if (ENABLE_REMOTE_INPUT) {
- row.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS);
- }
- }
-
- private void updateNotification(Entry entry, PackageManager pmUser,
- StatusBarNotification sbn, ExpandableNotificationRow row) {
- row.setNeedsRedaction(mLockscreenUserManager.needsRedaction(entry));
- boolean isLowPriority = mNotificationData.isAmbient(sbn.getKey());
- boolean isUpdate = mNotificationData.get(entry.key) != null;
- boolean wasLowPriority = row.isLowPriority();
- row.setIsLowPriority(isLowPriority);
- row.setLowPriorityStateUpdated(isUpdate && (wasLowPriority != isLowPriority));
- // bind the click event to the content area
- mNotificationClicker.register(row, sbn);
-
- // Extract target SDK version.
- try {
- ApplicationInfo info = pmUser.getApplicationInfo(sbn.getPackageName(), 0);
- entry.targetSdk = info.targetSdkVersion;
- } catch (NameNotFoundException ex) {
- Log.e(TAG, "Failed looking up ApplicationInfo for " + sbn.getPackageName(), ex);
- }
- row.setLegacy(entry.targetSdk >= Build.VERSION_CODES.GINGERBREAD
- && entry.targetSdk < Build.VERSION_CODES.LOLLIPOP);
- entry.setIconTag(R.id.icon_is_pre_L, entry.targetSdk < Build.VERSION_CODES.LOLLIPOP);
- entry.autoRedacted = entry.notification.getNotification().publicVersion == null;
-
- entry.row = row;
- entry.row.setOnActivatedListener(this);
-
- boolean useIncreasedCollapsedHeight = mMessagingUtil.isImportantMessaging(sbn,
- mNotificationData.getImportance(sbn.getKey()));
- boolean useIncreasedHeadsUp = useIncreasedCollapsedHeight && mPanelExpanded;
- row.setUseIncreasedCollapsedHeight(useIncreasedCollapsedHeight);
- row.setUseIncreasedHeadsUpHeight(useIncreasedHeadsUp);
- row.updateNotification(entry);
- }
-
public void startPendingIntentDismissingKeyguard(final PendingIntent intent) {
if (!isDeviceProvisioned()) return;
@@ -5702,166 +5192,15 @@
}, afterKeyguardGone);
}
-
- private final class NotificationClicker implements View.OnClickListener {
-
- @Override
- public void onClick(final View v) {
- if (!(v instanceof ExpandableNotificationRow)) {
- Log.e(TAG, "NotificationClicker called on a view that is not a notification row.");
- return;
- }
-
- wakeUpIfDozing(SystemClock.uptimeMillis(), v);
-
- final ExpandableNotificationRow row = (ExpandableNotificationRow) v;
- final StatusBarNotification sbn = row.getStatusBarNotification();
- if (sbn == null) {
- Log.e(TAG, "NotificationClicker called on an unclickable notification,");
- return;
- }
-
- // Check if the notification is displaying the menu, if so slide notification back
- if (row.getProvider() != null && row.getProvider().isMenuVisible()) {
- row.animateTranslateNotification(0);
- return;
- }
-
- Notification notification = sbn.getNotification();
- final PendingIntent intent = notification.contentIntent != null
- ? notification.contentIntent
- : notification.fullScreenIntent;
- final String notificationKey = sbn.getKey();
-
- // Mark notification for one frame.
- row.setJustClicked(true);
- DejankUtils.postAfterTraversal(() -> row.setJustClicked(false));
-
- final boolean afterKeyguardGone = intent.isActivity()
- && PreviewInflater.wouldLaunchResolverActivity(mContext, intent.getIntent(),
- mLockscreenUserManager.getCurrentUserId());
- dismissKeyguardThenExecute(() -> {
- if (mHeadsUpManager != null && mHeadsUpManager.isHeadsUp(notificationKey)) {
- // Release the HUN notification to the shade.
-
- if (isPresenterFullyCollapsed()) {
- HeadsUpManager.setIsClickedNotification(row, true);
- }
- //
- // In most cases, when FLAG_AUTO_CANCEL is set, the notification will
- // become canceled shortly by NoMan, but we can't assume that.
- mHeadsUpManager.releaseImmediately(notificationKey);
- }
- StatusBarNotification parentToCancel = null;
- if (shouldAutoCancel(sbn) && mGroupManager.isOnlyChildInGroup(sbn)) {
- StatusBarNotification summarySbn =
- mGroupManager.getLogicalGroupSummary(sbn).getStatusBarNotification();
- if (shouldAutoCancel(summarySbn)) {
- parentToCancel = summarySbn;
- }
- }
- final StatusBarNotification parentToCancelFinal = parentToCancel;
- final Runnable runnable = () -> {
- try {
- // The intent we are sending is for the application, which
- // won't have permission to immediately start an activity after
- // the user switches to home. We know it is safe to do at this
- // point, so make sure new activity switches are now allowed.
- ActivityManager.getService().resumeAppSwitches();
- } catch (RemoteException e) {
- }
- if (intent != null) {
- // If we are launching a work activity and require to launch
- // separate work challenge, we defer the activity action and cancel
- // notification until work challenge is unlocked.
- if (intent.isActivity()) {
- final int userId = intent.getCreatorUserHandle().getIdentifier();
- if (mLockPatternUtils.isSeparateProfileChallengeEnabled(userId)
- && mKeyguardManager.isDeviceLocked(userId)) {
- // TODO(b/28935539): should allow certain activities to
- // bypass work challenge
- if (startWorkChallengeIfNecessary(userId, intent.getIntentSender(),
- notificationKey)) {
- // Show work challenge, do not run PendingIntent and
- // remove notification
- return;
- }
- }
- }
- try {
- intent.send(null, 0, null, null, null, null, getActivityOptions());
- } catch (PendingIntent.CanceledException e) {
- // the stack trace isn't very helpful here.
- // Just log the exception message.
- Log.w(TAG, "Sending contentIntent failed: " + e);
-
- // TODO: Dismiss Keyguard.
- }
- if (intent.isActivity()) {
- mAssistManager.hideAssist();
- }
- }
-
- try {
- mBarService.onNotificationClick(notificationKey);
- } catch (RemoteException ex) {
- // system process is dead if we're here.
- }
- if (parentToCancelFinal != null) {
- // We have to post it to the UI thread for synchronization
- mHandler.post(() -> {
- Runnable removeRunnable =
- () -> performRemoveNotification(parentToCancelFinal);
- if (isCollapsing()) {
- // To avoid lags we're only performing the remove
- // after the shade was collapsed
- addPostCollapseAction(removeRunnable);
- } else {
- removeRunnable.run();
- }
- });
- }
- };
-
- if (mStatusBarKeyguardViewManager.isShowing()
- && mStatusBarKeyguardViewManager.isOccluded()) {
- mStatusBarKeyguardViewManager.addAfterKeyguardGoneRunnable(runnable);
- } else {
- new Thread(runnable).start();
- }
-
- if (!mNotificationPanel.isFullyCollapsed()) {
- // close the shade if it was open
- animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */,
- true /* delayed */);
- visibilityChanged(false);
-
- return true;
- } else {
- return false;
- }
- }, afterKeyguardGone);
+ private boolean shouldAutoCancel(StatusBarNotification sbn) {
+ int flags = sbn.getNotification().flags;
+ if ((flags & Notification.FLAG_AUTO_CANCEL) != Notification.FLAG_AUTO_CANCEL) {
+ return false;
}
-
- private boolean shouldAutoCancel(StatusBarNotification sbn) {
- int flags = sbn.getNotification().flags;
- if ((flags & Notification.FLAG_AUTO_CANCEL) != Notification.FLAG_AUTO_CANCEL) {
- return false;
- }
- if ((flags & Notification.FLAG_FOREGROUND_SERVICE) != 0) {
- return false;
- }
- return true;
+ if ((flags & Notification.FLAG_FOREGROUND_SERVICE) != 0) {
+ return false;
}
-
- public void register(ExpandableNotificationRow row, StatusBarNotification sbn) {
- Notification notification = sbn.getNotification();
- if (notification.contentIntent != null || notification.fullScreenIntent != null) {
- row.setOnClickListener(this);
- } else {
- row.setOnClickListener(null);
- }
- }
+ return true;
}
protected Bundle getActivityOptions() {
@@ -5904,127 +5243,10 @@
}
/**
- * Cancel this notification and tell the StatusBarManagerService / NotificationManagerService
- * about the failure.
- *
- * WARNING: this will call back into us. Don't hold any locks.
- */
- void handleNotificationError(StatusBarNotification n, String message) {
- removeNotification(n.getKey(), null);
- try {
- mBarService.onNotificationError(n.getPackageName(), n.getTag(), n.getId(), n.getUid(),
- n.getInitialPid(), message, n.getUserId());
- } catch (RemoteException ex) {
- // The end is nigh.
- }
- }
-
- protected StatusBarNotification removeNotificationViews(String key, RankingMap ranking) {
- NotificationData.Entry entry = mNotificationData.remove(key, ranking);
- if (entry == null) {
- Log.w(TAG, "removeNotification for unknown key: " + key);
- return null;
- }
- updateNotifications();
- Dependency.get(LeakDetector.class).trackGarbage(entry);
- return entry.notification;
- }
-
- protected NotificationData.Entry createNotificationViews(StatusBarNotification sbn)
- throws InflationException {
- if (DEBUG) {
- Log.d(TAG, "createNotificationViews(notification=" + sbn);
- }
- NotificationData.Entry entry = new NotificationData.Entry(sbn);
- Dependency.get(LeakDetector.class).trackInstance(entry);
- entry.createIcons(mContext, sbn);
- // Construct the expanded view.
- inflateViews(entry, mStackScroller);
- return entry;
- }
-
- protected void addNotificationViews(Entry entry) {
- if (entry == null) {
- return;
- }
- // Add the expanded view and icon.
- mNotificationData.add(entry);
- updateNotifications();
- }
-
- /**
* Updates expanded, dimmed and locked states of notification rows.
*/
- protected void updateRowStates() {
- final int N = mStackScroller.getChildCount();
-
- int visibleNotifications = 0;
- boolean onKeyguard = mState == StatusBarState.KEYGUARD;
- int maxNotifications = -1;
- if (onKeyguard) {
- maxNotifications = getMaxKeyguardNotifications(true /* recompute */);
- }
- mStackScroller.setMaxDisplayedNotifications(maxNotifications);
- Stack<ExpandableNotificationRow> stack = new Stack<>();
- for (int i = N - 1; i >= 0; i--) {
- View child = mStackScroller.getChildAt(i);
- if (!(child instanceof ExpandableNotificationRow)) {
- continue;
- }
- stack.push((ExpandableNotificationRow) child);
- }
- while(!stack.isEmpty()) {
- ExpandableNotificationRow row = stack.pop();
- NotificationData.Entry entry = row.getEntry();
- boolean isChildNotification =
- mGroupManager.isChildInGroupWithSummary(entry.notification);
-
- row.setOnKeyguard(onKeyguard);
-
- if (!onKeyguard) {
- // If mAlwaysExpandNonGroupedNotification is false, then only expand the
- // very first notification and if it's not a child of grouped notifications.
- row.setSystemExpanded(mAlwaysExpandNonGroupedNotification
- || (visibleNotifications == 0 && !isChildNotification
- && !row.isLowPriority()));
- }
-
- entry.row.setShowAmbient(isDozing());
- int userId = entry.notification.getUserId();
- boolean suppressedSummary = mGroupManager.isSummaryOfSuppressedGroup(
- entry.notification) && !entry.row.isRemoved();
- boolean showOnKeyguard = mLockscreenUserManager.shouldShowOnKeyguard(entry
- .notification);
- if (suppressedSummary
- || (mLockscreenUserManager.isLockscreenPublicMode(userId)
- && !mLockscreenUserManager.shouldShowLockscreenNotifications())
- || (onKeyguard && !showOnKeyguard)) {
- entry.row.setVisibility(View.GONE);
- } else {
- boolean wasGone = entry.row.getVisibility() == View.GONE;
- if (wasGone) {
- entry.row.setVisibility(View.VISIBLE);
- }
- if (!isChildNotification && !entry.row.isRemoved()) {
- if (wasGone) {
- // notify the scroller of a child addition
- mStackScroller.generateAddAnimation(entry.row,
- !showOnKeyguard /* fromMoreCard */);
- }
- visibleNotifications++;
- }
- }
- if (row.isSummaryWithChildren()) {
- List<ExpandableNotificationRow> notificationChildren =
- row.getNotificationChildren();
- int size = notificationChildren.size();
- for (int i = size - 1; i >= 0; i--) {
- stack.push(notificationChildren.get(i));
- }
- }
- }
- mNotificationPanel.setNoVisibleNotifications(visibleNotifications == 0);
-
+ @Override
+ public void onUpdateRowStates() {
// The following views will be moved to the end of mStackScroller. This counter represents
// the offset from the last child. Initialized to 1 for the very last position. It is post-
// incremented in the following "changeViewPosition" calls so that its value is correct for
@@ -6047,163 +5269,10 @@
mScrimController.setNotificationCount(mStackScroller.getNotGoneChildCount());
}
- // TODO: Move this to NotificationEntryManager once it is created.
- private void updateNotificationInternal(StatusBarNotification notification,
- RankingMap ranking) throws InflationException {
- if (DEBUG) Log.d(TAG, "updateNotification(" + notification + ")");
-
- final String key = notification.getKey();
- abortExistingInflation(key);
- Entry entry = mNotificationData.get(key);
- if (entry == null) {
- return;
- }
- mHeadsUpEntriesToRemoveOnSwitch.remove(entry);
- mRemoteInputManager.onUpdateNotification(entry);
-
- if (key.equals(mGutsManager.getKeyToRemoveOnGutsClosed())) {
- mGutsManager.setKeyToRemoveOnGutsClosed(null);
- Log.w(TAG, "Notification that was kept for guts was updated. " + key);
- }
-
- Notification n = notification.getNotification();
- mNotificationData.updateRanking(ranking);
-
- final StatusBarNotification oldNotification = entry.notification;
- entry.notification = notification;
- mGroupManager.onEntryUpdated(entry, oldNotification);
-
- entry.updateIcons(mContext, notification);
- inflateViews(entry, mStackScroller);
-
- mForegroundServiceController.updateNotification(notification,
- mNotificationData.getImportance(key));
-
- boolean shouldPeek = shouldPeek(entry, notification);
- boolean alertAgain = alertAgain(entry, n);
-
- updateHeadsUp(key, entry, shouldPeek, alertAgain);
- updateNotifications();
-
- if (!notification.isClearable()) {
- // The user may have performed a dismiss action on the notification, since it's
- // not clearable we should snap it back.
- mStackScroller.snapViewIfNeeded(entry.row);
- }
-
- if (DEBUG) {
- // Is this for you?
- boolean isForCurrentUser = isNotificationForCurrentProfiles(notification);
- Log.d(TAG, "notification is " + (isForCurrentUser ? "" : "not ") + "for you");
- }
-
- setAreThereNotifications();
- }
-
- @Override
- public void updateNotification(StatusBarNotification notification, RankingMap ranking) {
- try {
- updateNotificationInternal(notification, ranking);
- } catch (InflationException e) {
- handleInflationException(notification, e);
- }
- }
-
protected void notifyHeadsUpGoingToSleep() {
maybeEscalateHeadsUp();
}
- private boolean alertAgain(Entry oldEntry, Notification newNotification) {
- return oldEntry == null || !oldEntry.hasInterrupted()
- || (newNotification.flags & Notification.FLAG_ONLY_ALERT_ONCE) == 0;
- }
-
- protected boolean shouldPeek(Entry entry) {
- return shouldPeek(entry, entry.notification);
- }
-
- protected boolean shouldPeek(Entry entry, StatusBarNotification sbn) {
- if (!mUseHeadsUp || isDeviceInVrMode()) {
- if (DEBUG) Log.d(TAG, "No peeking: no huns or vr mode");
- return false;
- }
-
- if (mNotificationData.shouldFilterOut(sbn)) {
- if (DEBUG) Log.d(TAG, "No peeking: filtered notification: " + sbn.getKey());
- return false;
- }
-
- boolean inUse = mPowerManager.isScreenOn() && !mSystemServicesProxy.isDreaming();
-
- if (!inUse && !isDozing()) {
- if (DEBUG) {
- Log.d(TAG, "No peeking: not in use: " + sbn.getKey());
- }
- return false;
- }
-
- if (!isDozing() && mNotificationData.shouldSuppressScreenOn(sbn.getKey())) {
- if (DEBUG) Log.d(TAG, "No peeking: suppressed by DND: " + sbn.getKey());
- return false;
- }
-
- if (isDozing() && mNotificationData.shouldSuppressScreenOff(sbn.getKey())) {
- if (DEBUG) Log.d(TAG, "No peeking: suppressed by DND: " + sbn.getKey());
- return false;
- }
-
- if (entry.hasJustLaunchedFullScreenIntent()) {
- if (DEBUG) Log.d(TAG, "No peeking: recent fullscreen: " + sbn.getKey());
- return false;
- }
-
- if (isSnoozedPackage(sbn)) {
- if (DEBUG) Log.d(TAG, "No peeking: snoozed package: " + sbn.getKey());
- return false;
- }
-
- // Allow peeking for DEFAULT notifications only if we're on Ambient Display.
- int importanceLevel = isDozing() ? NotificationManager.IMPORTANCE_DEFAULT
- : NotificationManager.IMPORTANCE_HIGH;
- if (mNotificationData.getImportance(sbn.getKey()) < importanceLevel) {
- if (DEBUG) Log.d(TAG, "No peeking: unimportant notification: " + sbn.getKey());
- return false;
- }
-
- if (mIsOccluded && !isDozing()) {
- boolean devicePublic = mLockscreenUserManager.
- isLockscreenPublicMode(mLockscreenUserManager.getCurrentUserId());
- boolean userPublic = devicePublic
- || mLockscreenUserManager.isLockscreenPublicMode(sbn.getUserId());
- boolean needsRedaction = mLockscreenUserManager.needsRedaction(entry);
- if (userPublic && needsRedaction) {
- return false;
- }
- }
-
- if (sbn.getNotification().fullScreenIntent != null) {
- if (mAccessibilityManager.isTouchExplorationEnabled()) {
- if (DEBUG) Log.d(TAG, "No peeking: accessible fullscreen: " + sbn.getKey());
- return false;
- } else if (mDozing) {
- // We never want heads up when we are dozing.
- return false;
- } else {
- // we only allow head-up on the lockscreen if it doesn't have a fullscreen intent
- return !mStatusBarKeyguardViewManager.isShowing()
- || mStatusBarKeyguardViewManager.isOccluded();
- }
- }
-
- // Don't peek notifications that are suppressed due to group alert behavior
- if (sbn.isGroup() && sbn.getNotification().suppressAlertingDueToGrouping()) {
- if (DEBUG) Log.d(TAG, "No peeking: suppressed due to group alert behavior");
- return false;
- }
-
- return true;
- }
-
/**
* @return Whether the security bouncer from Keyguard is showing.
*/
@@ -6233,17 +5302,6 @@
return contextForUser.getPackageManager();
}
- @Override
- public void logNotificationExpansion(String key, boolean userAction, boolean expanded) {
- mUiOffloadThread.submit(() -> {
- try {
- mBarService.onNotificationExpansionChanged(key, userAction, expanded);
- } catch (RemoteException e) {
- // Ignore.
- }
- });
- }
-
public boolean isKeyguardSecure() {
if (mStatusBarKeyguardViewManager == null) {
// startKeyguard() hasn't been called yet, so we don't know.
@@ -6291,20 +5349,10 @@
}
@Override
- public NotificationData getNotificationData() {
- return mNotificationData;
- }
-
- @Override
public Handler getHandler() {
return mHandler;
}
- @Override
- public RankingMap getLatestRankingMap() {
- return mLatestRankingMap;
- }
-
private final NotificationInfo.CheckSaveListener mCheckSaveListener =
(Runnable saveImportance, StatusBarNotification sbn) -> {
// If the user has security enabled, show challenge if the setting is changed.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
index fe39a89..369e7ff 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -80,6 +80,8 @@
import com.android.systemui.statusbar.ExpandableView;
import com.android.systemui.statusbar.NotificationData;
import com.android.systemui.statusbar.NotificationGuts;
+import com.android.systemui.statusbar.NotificationListContainer;
+import com.android.systemui.statusbar.NotificationLogger;
import com.android.systemui.statusbar.NotificationShelf;
import com.android.systemui.statusbar.NotificationSnooze;
import com.android.systemui.statusbar.StackScrollerDecorView;
@@ -112,7 +114,8 @@
public class NotificationStackScrollLayout extends ViewGroup
implements SwipeHelper.Callback, ExpandHelper.Callback, ScrollAdapter,
ExpandableView.OnHeightChangedListener, NotificationGroupManager.OnGroupChangeListener,
- NotificationMenuRowPlugin.OnMenuEventListener, VisibilityLocationProvider {
+ NotificationMenuRowPlugin.OnMenuEventListener, VisibilityLocationProvider,
+ NotificationListContainer {
public static final float BACKGROUND_ALPHA_DIMMED = 0.7f;
private static final String TAG = "StackScroller";
@@ -207,7 +210,7 @@
* The raw amount of the overScroll on the bottom, which is not rubber-banded.
*/
private float mOverScrolledBottomPixels;
- private OnChildLocationsChangedListener mListener;
+ private NotificationLogger.OnChildLocationsChangedListener mListener;
private OnOverscrollTopChangedListener mOverscrollTopChangedListener;
private ExpandableView.OnHeightChangedListener mOnHeightChangedListener;
private OnEmptySpaceClickListener mOnEmptySpaceClickListener;
@@ -447,6 +450,7 @@
}
}
+ @Override
public NotificationSwipeActionHelper getSwipeActionHelper() {
return mSwipeHelper;
}
@@ -614,7 +618,9 @@
mNoAmbient = noAmbient;
}
- public void setChildLocationsChangedListener(OnChildLocationsChangedListener listener) {
+ @Override
+ public void setChildLocationsChangedListener(
+ NotificationLogger.OnChildLocationsChangedListener listener) {
mListener = listener;
}
@@ -1325,6 +1331,7 @@
true /* isDismissAll */);
}
+ @Override
public void snapViewIfNeeded(ExpandableNotificationRow child) {
boolean animate = mIsExpanded || isPinnedHeadsUp(child);
// If the child is showing the notification menu snap to that
@@ -1333,6 +1340,11 @@
}
@Override
+ public ViewGroup getViewParentForNotification(NotificationData.Entry entry) {
+ return this;
+ }
+
+ @Override
public boolean onTouchEvent(MotionEvent ev) {
boolean isCancelOrUp = ev.getActionMasked() == MotionEvent.ACTION_CANCEL
|| ev.getActionMasked()== MotionEvent.ACTION_UP;
@@ -2053,6 +2065,7 @@
return mAmbientState.isPulsing(entry);
}
+ @Override
public boolean hasPulsingNotifications() {
return mPulsing != null;
}
@@ -2610,10 +2623,7 @@
}
}
- /**
- * Called when a notification is removed from the shade. This cleans up the state for a given
- * view.
- */
+ @Override
public void cleanUpViewState(View child) {
if (child == mTranslatingParentView) {
mTranslatingParentView = null;
@@ -2922,10 +2932,12 @@
}
}
+ @Override
public void notifyGroupChildRemoved(View row, ViewGroup childrenContainer) {
onViewRemovedInternal(row, childrenContainer);
}
+ @Override
public void notifyGroupChildAdded(View row) {
onViewAddedInternal(row);
}
@@ -2963,12 +2975,8 @@
return mNeedsAnimation
&& (!mChildrenToAddAnimated.isEmpty() || !mChildrenToRemoveAnimated.isEmpty());
}
- /**
- * Generate an animation for an added child view.
- *
- * @param child The view to be added.
- * @param fromMoreCard Whether this add is coming from the "more" card on lockscreen.
- */
+
+ @Override
public void generateAddAnimation(View child, boolean fromMoreCard) {
if (mIsExpanded && mAnimationsEnabled && !mChangePositionInProgress) {
// Generate Animations
@@ -2984,12 +2992,7 @@
}
}
- /**
- * Change the position of child to a new location
- *
- * @param child the view to change the position for
- * @param newIndex the new index
- */
+ @Override
public void changeViewPosition(View child, int newIndex) {
int currentIndex = indexOfChild(child);
if (child != null && child.getParent() == this && currentIndex != newIndex) {
@@ -3705,7 +3708,7 @@
private void applyCurrentState() {
mCurrentStackScrollState.apply();
if (mListener != null) {
- mListener.onChildLocationsChanged(this);
+ mListener.onChildLocationsChanged();
}
runAnimationFinishedRunnables();
setAnimationRunning(false);
@@ -4189,6 +4192,26 @@
}
}
+ @Override
+ public int getContainerChildCount() {
+ return getChildCount();
+ }
+
+ @Override
+ public View getContainerChildAt(int i) {
+ return getChildAt(i);
+ }
+
+ @Override
+ public void removeContainerView(View v) {
+ removeView(v);
+ }
+
+ @Override
+ public void addContainerView(View v) {
+ addView(v);
+ }
+
public void runAfterAnimationFinished(Runnable runnable) {
mAnimationFinishedRunnables.add(runnable);
}
@@ -4445,13 +4468,6 @@
}
/**
- * A listener that is notified when some child locations might have changed.
- */
- public interface OnChildLocationsChangedListener {
- void onChildLocationsChanged(NotificationStackScrollLayout stackScrollLayout);
- }
-
- /**
* A listener that is notified when the empty space below the notifications is clicked on
*/
public interface OnEmptySpaceClickListener {
@@ -4706,6 +4722,7 @@
}
}
+ @Override
public void resetExposedMenuView(boolean animate, boolean force) {
mSwipeHelper.resetExposedMenuView(animate, force);
}
diff --git a/packages/SystemUI/tests/Android.mk b/packages/SystemUI/tests/Android.mk
index 9d44895..066cfe5 100644
--- a/packages/SystemUI/tests/Android.mk
+++ b/packages/SystemUI/tests/Android.mk
@@ -46,7 +46,12 @@
android-support-v7-mediarouter \
android-support-v7-palette \
android-support-v14-preference \
- android-support-v17-leanback
+ android-support-v17-leanback \
+ android-slices-core \
+ android-slices-view \
+ android-slices-builders \
+ apptoolkit-arch-core-runtime \
+ apptoolkit-lifecycle-extensions \
LOCAL_STATIC_JAVA_LIBRARIES := \
metrics-helper-lib \
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java
index 4eae342..be28569 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java
@@ -16,11 +16,10 @@
package com.android.systemui.keyguard;
-import android.app.slice.Slice;
-import android.app.slice.SliceItem;
-import android.app.slice.SliceQuery;
+import androidx.app.slice.Slice;
import android.content.Intent;
import android.net.Uri;
+import android.os.Debug;
import android.os.Handler;
import android.support.test.filters.SmallTest;
import android.testing.AndroidTestingRunner;
@@ -34,6 +33,9 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import androidx.app.slice.SliceItem;
+import androidx.app.slice.core.SliceQuery;
+
@SmallTest
@RunWith(AndroidTestingRunner.class)
@RunWithLooper(setAsMainLooper = true)
@@ -63,7 +65,8 @@
@Test
public void returnsValidSlice() {
Slice slice = mProvider.onBindSlice(Uri.parse(KeyguardSliceProvider.KEYGUARD_SLICE_URI));
- SliceItem text = SliceQuery.find(slice, SliceItem.FORMAT_TEXT, Slice.HINT_TITLE,
+ SliceItem text = SliceQuery.find(slice, android.app.slice.SliceItem.FORMAT_TEXT,
+ android.app.slice.Slice.HINT_TITLE,
null /* nonHints */);
Assert.assertNotNull("Slice must provide a title.", text);
}
@@ -78,9 +81,10 @@
@Test
public void updatesClock() {
+ mProvider.mUpdateClockInvokations = 0;
mProvider.mIntentReceiver.onReceive(getContext(), new Intent(Intent.ACTION_TIME_TICK));
TestableLooper.get(this).processAllMessages();
- Assert.assertEquals("Clock should have been updated.", 2 /* expected */,
+ Assert.assertEquals("Clock should have been updated.", 1 /* expected */,
mProvider.mUpdateClockInvokations);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationEntryManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationEntryManagerTest.java
new file mode 100644
index 0000000..0a68389
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationEntryManagerTest.java
@@ -0,0 +1,249 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar;
+
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertNull;
+import static junit.framework.Assert.assertTrue;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManager;
+import android.app.Notification;
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.UserHandle;
+import android.service.notification.NotificationListenerService;
+import android.service.notification.StatusBarNotification;
+import android.support.test.filters.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.widget.FrameLayout;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.statusbar.IStatusBarService;
+import com.android.systemui.ForegroundServiceController;
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.UiOffloadThread;
+import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.phone.NotificationGroupManager;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+import com.android.systemui.statusbar.policy.HeadsUpManager;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class NotificationEntryManagerTest extends SysuiTestCase {
+ private static final String TEST_PACKAGE_NAME = "test";
+ private static final int TEST_UID = 0;
+
+ @Mock private NotificationPresenter mPresenter;
+ @Mock private ExpandableNotificationRow mRow;
+ @Mock private NotificationListContainer mListContainer;
+ @Mock private NotificationEntryManager.Callback mCallback;
+ @Mock private HeadsUpManager mHeadsUpManager;
+ @Mock private NotificationListenerService.RankingMap mRankingMap;
+ @Mock private RemoteInputController mRemoteInputController;
+ @Mock private IStatusBarService mBarService;
+
+ // Dependency mocks:
+ @Mock private ForegroundServiceController mForegroundServiceController;
+ @Mock private NotificationLockscreenUserManager mLockscreenUserManager;
+ @Mock private NotificationGroupManager mGroupManager;
+ @Mock private NotificationGutsManager mGutsManager;
+ @Mock private NotificationRemoteInputManager mRemoteInputManager;
+ @Mock private NotificationMediaManager mMediaManager;
+ @Mock private NotificationListener mNotificationListener;
+ @Mock private DeviceProvisionedController mDeviceProvisionedController;
+ @Mock private VisualStabilityManager mVisualStabilityManager;
+ @Mock private MetricsLogger mMetricsLogger;
+
+ private NotificationData.Entry mEntry;
+ private StatusBarNotification mSbn;
+ private Handler mHandler;
+ private TestableNotificationEntryManager mEntryManager;
+ private CountDownLatch mCountDownLatch;
+
+ private class TestableNotificationEntryManager extends NotificationEntryManager {
+ private final CountDownLatch mCountDownLatch;
+
+ public TestableNotificationEntryManager(Context context, IStatusBarService barService) {
+ super(context);
+ mBarService = barService;
+ mCountDownLatch = new CountDownLatch(1);
+ mUseHeadsUp = true;
+ }
+
+ @Override
+ public void onAsyncInflationFinished(NotificationData.Entry entry) {
+ super.onAsyncInflationFinished(entry);
+
+ mCountDownLatch.countDown();
+ }
+
+ public CountDownLatch getCountDownLatch() {
+ return mCountDownLatch;
+ }
+ }
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mDependency.injectTestDependency(ForegroundServiceController.class,
+ mForegroundServiceController);
+ mDependency.injectTestDependency(NotificationLockscreenUserManager.class,
+ mLockscreenUserManager);
+ mDependency.injectTestDependency(NotificationGroupManager.class, mGroupManager);
+ mDependency.injectTestDependency(NotificationGutsManager.class, mGutsManager);
+ mDependency.injectTestDependency(NotificationRemoteInputManager.class, mRemoteInputManager);
+ mDependency.injectTestDependency(NotificationMediaManager.class, mMediaManager);
+ mDependency.injectTestDependency(NotificationListener.class, mNotificationListener);
+ mDependency.injectTestDependency(DeviceProvisionedController.class,
+ mDeviceProvisionedController);
+ mDependency.injectTestDependency(VisualStabilityManager.class, mVisualStabilityManager);
+ mDependency.injectTestDependency(MetricsLogger.class, mMetricsLogger);
+
+ mHandler = new Handler(Looper.getMainLooper());
+ mCountDownLatch = new CountDownLatch(1);
+
+ when(mPresenter.getHandler()).thenReturn(mHandler);
+ when(mPresenter.getNotificationLockscreenUserManager()).thenReturn(mLockscreenUserManager);
+ when(mPresenter.getGroupManager()).thenReturn(mGroupManager);
+ when(mRemoteInputManager.getController()).thenReturn(mRemoteInputController);
+ when(mListContainer.getViewParentForNotification(any())).thenReturn(
+ new FrameLayout(mContext));
+
+ Notification.Builder n = new Notification.Builder(mContext, "")
+ .setSmallIcon(R.drawable.ic_person)
+ .setContentTitle("Title")
+ .setContentText("Text");
+ mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, 0, null, TEST_UID,
+ 0, n.build(), new UserHandle(ActivityManager.getCurrentUser()), null, 0);
+ mEntry = new NotificationData.Entry(mSbn);
+ mEntry.expandedIcon = mock(StatusBarIconView.class);
+
+ mEntryManager = new TestableNotificationEntryManager(mContext, mBarService);
+ mEntryManager.setUpWithPresenter(mPresenter, mListContainer, mCallback, mHeadsUpManager);
+ }
+
+ @Test
+ public void testAddNotification() throws Exception {
+ com.android.systemui.util.Assert.isNotMainThread();
+
+ doAnswer(invocation -> {
+ mCountDownLatch.countDown();
+ return null;
+ }).when(mCallback).onBindRow(any(), any(), any(), any());
+
+ // Post on main thread, otherwise we will be stuck waiting here for the inflation finished
+ // callback forever, since it won't execute until the tests ends.
+ mHandler.post(() -> {
+ mEntryManager.addNotification(mSbn, mRankingMap);
+ });
+ assertTrue(mCountDownLatch.await(1, TimeUnit.MINUTES));
+ assertTrue(mEntryManager.getCountDownLatch().await(1, TimeUnit.MINUTES));
+ waitForIdleSync(mHandler);
+
+ // Check that no inflation error occurred.
+ verify(mBarService, never()).onNotificationError(any(), any(), anyInt(), anyInt(), anyInt(),
+ any(), anyInt());
+ verify(mForegroundServiceController).addNotification(eq(mSbn), anyInt());
+
+ // Row inflation:
+ ArgumentCaptor<NotificationData.Entry> entryCaptor = ArgumentCaptor.forClass(
+ NotificationData.Entry.class);
+ verify(mCallback).onBindRow(entryCaptor.capture(), any(), eq(mSbn), any());
+ NotificationData.Entry entry = entryCaptor.getValue();
+ verify(mRemoteInputManager).bindRow(entry.row);
+
+ // Row content inflation:
+ verify(mCallback).onNotificationAdded(entry);
+ verify(mPresenter).updateNotificationViews();
+
+ assertEquals(mEntryManager.getNotificationData().get(mSbn.getKey()), entry);
+ assertNotNull(entry.row);
+ }
+
+ @Test
+ public void testUpdateNotification() throws Exception {
+ com.android.systemui.util.Assert.isNotMainThread();
+
+ mEntryManager.getNotificationData().add(mEntry);
+
+ mHandler.post(() -> {
+ mEntryManager.updateNotification(mSbn, mRankingMap);
+ });
+ // Wait for content update.
+ mEntryManager.getCountDownLatch().await(1, TimeUnit.MINUTES);
+ waitForIdleSync(mHandler);
+
+ verify(mBarService, never()).onNotificationError(any(), any(), anyInt(), anyInt(), anyInt(),
+ any(), anyInt());
+
+ verify(mRemoteInputManager).onUpdateNotification(mEntry);
+ verify(mPresenter).updateNotificationViews();
+ verify(mForegroundServiceController).updateNotification(eq(mSbn), anyInt());
+ verify(mCallback).onNotificationUpdated(mSbn);
+ assertNotNull(mEntry.row);
+ }
+
+ @Test
+ public void testRemoveNotification() throws Exception {
+ com.android.systemui.util.Assert.isNotMainThread();
+
+ mEntry.row = mRow;
+ mEntryManager.getNotificationData().add(mEntry);
+
+ mHandler.post(() -> {
+ mEntryManager.removeNotification(mSbn.getKey(), mRankingMap);
+ });
+ waitForIdleSync(mHandler);
+
+ verify(mBarService, never()).onNotificationError(any(), any(), anyInt(), anyInt(), anyInt(),
+ any(), anyInt());
+
+ verify(mMediaManager).onNotificationRemoved(mSbn.getKey());
+ verify(mRemoteInputManager).onRemoveNotification(mEntry);
+ verify(mForegroundServiceController).removeNotification(mSbn);
+ verify(mListContainer).cleanUpViewState(mRow);
+ verify(mPresenter).updateNotificationViews();
+ verify(mCallback).onNotificationRemoved(mSbn.getKey(), mSbn);
+ verify(mRow).setRemoved();
+
+ assertNull(mEntryManager.getNotificationData().get(mSbn.getKey()));
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java
index ccc3006..ef5f071 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java
@@ -19,7 +19,6 @@
import static junit.framework.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -38,6 +37,8 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
import java.util.HashSet;
import java.util.Set;
@@ -49,40 +50,45 @@
private static final String TEST_PACKAGE_NAME = "test";
private static final int TEST_UID = 0;
- private NotificationPresenter mPresenter;
- private Handler mHandler;
+ @Mock private NotificationPresenter mPresenter;
+ @Mock private NotificationListenerService.RankingMap mRanking;
+ @Mock private NotificationData mNotificationData;
+
+ // Dependency mocks:
+ @Mock private NotificationEntryManager mEntryManager;
+ @Mock private NotificationRemoteInputManager mRemoteInputManager;
+
private NotificationListener mListener;
+ private Handler mHandler;
private StatusBarNotification mSbn;
- private NotificationListenerService.RankingMap mRanking;
private Set<String> mKeysKeptForRemoteInput;
- private NotificationData mNotificationData;
- private NotificationRemoteInputManager mRemoteInputManager;
@Before
public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mDependency.injectTestDependency(NotificationEntryManager.class, mEntryManager);
+ mDependency.injectTestDependency(NotificationRemoteInputManager.class,
+ mRemoteInputManager);
+
mHandler = new Handler(Looper.getMainLooper());
- mPresenter = mock(NotificationPresenter.class);
- mNotificationData = mock(NotificationData.class);
- mRanking = mock(NotificationListenerService.RankingMap.class);
- mRemoteInputManager = mock(NotificationRemoteInputManager.class);
mKeysKeptForRemoteInput = new HashSet<>();
when(mPresenter.getHandler()).thenReturn(mHandler);
- when(mPresenter.getNotificationData()).thenReturn(mNotificationData);
+ when(mEntryManager.getNotificationData()).thenReturn(mNotificationData);
when(mRemoteInputManager.getKeysKeptForRemoteInput()).thenReturn(mKeysKeptForRemoteInput);
- mListener = new NotificationListener(mRemoteInputManager, mContext);
+ mListener = new NotificationListener(mContext);
mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, 0, null, TEST_UID, 0,
new Notification(), UserHandle.CURRENT, null, 0);
- mListener.setUpWithPresenter(mPresenter);
+ mListener.setUpWithPresenter(mPresenter, mEntryManager);
}
@Test
public void testNotificationAddCallsAddNotification() {
mListener.onNotificationPosted(mSbn, mRanking);
waitForIdleSync(mHandler);
- verify(mPresenter).addNotification(mSbn, mRanking);
+ verify(mEntryManager).addNotification(mSbn, mRanking);
}
@Test
@@ -98,14 +104,14 @@
when(mNotificationData.get(mSbn.getKey())).thenReturn(new NotificationData.Entry(mSbn));
mListener.onNotificationPosted(mSbn, mRanking);
waitForIdleSync(mHandler);
- verify(mPresenter).updateNotification(mSbn, mRanking);
+ verify(mEntryManager).updateNotification(mSbn, mRanking);
}
@Test
public void testNotificationRemovalCallsRemoveNotification() {
mListener.onNotificationRemoved(mSbn, mRanking);
waitForIdleSync(mHandler);
- verify(mPresenter).removeNotification(mSbn.getKey(), mRanking);
+ verify(mEntryManager).removeNotification(mSbn.getKey(), mRanking);
}
@Test
@@ -113,6 +119,6 @@
mListener.onNotificationRankingUpdate(mRanking);
waitForIdleSync(mHandler);
// RankingMap may be modified by plugins.
- verify(mPresenter).updateNotificationRanking(any());
+ verify(mEntryManager).updateNotificationRanking(any());
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
index 4045995..cb8f7ce 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
@@ -22,7 +22,6 @@
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
-import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -49,42 +48,47 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
public class NotificationLockscreenUserManagerTest extends SysuiTestCase {
- private NotificationPresenter mPresenter;
- private TestNotificationLockscreenUserManager mLockscreenUserManager;
- private DeviceProvisionedController mDeviceProvisionedController;
+ @Mock private NotificationPresenter mPresenter;
+ @Mock private UserManager mUserManager;
+
+ // Dependency mocks:
+ @Mock private NotificationEntryManager mEntryManager;
+ @Mock private DeviceProvisionedController mDeviceProvisionedController;
+
private int mCurrentUserId;
private Handler mHandler;
- private UserManager mUserManager;
+ private TestNotificationLockscreenUserManager mLockscreenUserManager;
@Before
public void setUp() {
- mUserManager = mock(UserManager.class);
- mContext.addMockSystemService(UserManager.class, mUserManager);
+ MockitoAnnotations.initMocks(this);
+ mDependency.injectTestDependency(NotificationEntryManager.class, mEntryManager);
+ mDependency.injectTestDependency(DeviceProvisionedController.class,
+ mDeviceProvisionedController);
+
mHandler = new Handler(Looper.getMainLooper());
- mDependency.injectMockDependency(DeviceProvisionedController.class);
- mDeviceProvisionedController = mDependency.get(DeviceProvisionedController.class);
- mLockscreenUserManager = new TestNotificationLockscreenUserManager(mContext);
- mDependency.injectTestDependency(NotificationLockscreenUserManager.class,
- mLockscreenUserManager);
+ mContext.addMockSystemService(UserManager.class, mUserManager);
+ mCurrentUserId = ActivityManager.getCurrentUser();
when(mUserManager.getProfiles(mCurrentUserId)).thenReturn(Lists.newArrayList(
new UserInfo(mCurrentUserId, "", 0), new UserInfo(mCurrentUserId + 1, "", 0)));
-
- mPresenter = mock(NotificationPresenter.class);
when(mPresenter.getHandler()).thenReturn(mHandler);
- mLockscreenUserManager.setUpWithPresenter(mPresenter);
- mCurrentUserId = ActivityManager.getCurrentUser();
+
+ mLockscreenUserManager = new TestNotificationLockscreenUserManager(mContext);
+ mLockscreenUserManager.setUpWithPresenter(mPresenter, mEntryManager);
}
@Test
public void testLockScreenShowNotificationsChangeUpdatesNotifications() {
mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
- verify(mPresenter, times(1)).updateNotifications();
+ verify(mEntryManager, times(1)).updateNotifications();
}
@Test
@@ -123,7 +127,7 @@
public void testSettingsObserverUpdatesNotifications() {
when(mDeviceProvisionedController.isDeviceProvisioned()).thenReturn(true);
mLockscreenUserManager.getSettingsObserverForTest().onChange(false);
- verify(mPresenter, times(1)).updateNotifications();
+ verify(mEntryManager, times(1)).updateNotifications();
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLoggerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLoggerTest.java
index 142ce63..726810e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLoggerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLoggerTest.java
@@ -36,7 +36,6 @@
import com.android.internal.statusbar.NotificationVisibility;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.UiOffloadThread;
-import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
import com.google.android.collect.Lists;
@@ -55,12 +54,15 @@
private static final int TEST_UID = 0;
@Mock private NotificationPresenter mPresenter;
- @Mock private NotificationListener mListener;
- @Mock private NotificationStackScrollLayout mStackScroller;
+ @Mock private NotificationListContainer mListContainer;
@Mock private IStatusBarService mBarService;
@Mock private NotificationData mNotificationData;
@Mock private ExpandableNotificationRow mRow;
+ // Dependency mocks:
+ @Mock private NotificationEntryManager mEntryManager;
+ @Mock private NotificationListener mListener;
+
private NotificationData.Entry mEntry;
private StatusBarNotification mSbn;
private TestableNotificationLogger mLogger;
@@ -68,24 +70,25 @@
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
+ mDependency.injectTestDependency(NotificationEntryManager.class, mEntryManager);
+ mDependency.injectTestDependency(NotificationListener.class, mListener);
- when(mPresenter.getNotificationData()).thenReturn(mNotificationData);
+ when(mEntryManager.getNotificationData()).thenReturn(mNotificationData);
mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, 0, null, TEST_UID,
0, new Notification(), UserHandle.CURRENT, null, 0);
mEntry = new NotificationData.Entry(mSbn);
mEntry.row = mRow;
- mLogger = new TestableNotificationLogger(mListener, mDependency.get(UiOffloadThread.class),
- mBarService);
- mLogger.setUpWithPresenter(mPresenter, mStackScroller);
+ mLogger = new TestableNotificationLogger(mBarService);
+ mLogger.setUpWithEntryManager(mEntryManager, mListContainer);
}
@Test
public void testOnChildLocationsChangedReportsVisibilityChanged() throws Exception {
- when(mStackScroller.isInVisibleLocation(any())).thenReturn(true);
+ when(mListContainer.isInVisibleLocation(any())).thenReturn(true);
when(mNotificationData.getActiveNotifications()).thenReturn(Lists.newArrayList(mEntry));
- mLogger.getChildLocationsChangedListenerForTest().onChildLocationsChanged(mStackScroller);
+ mLogger.getChildLocationsChangedListenerForTest().onChildLocationsChanged();
waitForIdleSync(mLogger.getHandlerForTest());
waitForUiOffloadThread();
@@ -97,7 +100,7 @@
// |mEntry| won't change visibility, so it shouldn't be reported again:
Mockito.reset(mBarService);
- mLogger.getChildLocationsChangedListenerForTest().onChildLocationsChanged(mStackScroller);
+ mLogger.getChildLocationsChangedListenerForTest().onChildLocationsChanged();
waitForIdleSync(mLogger.getHandlerForTest());
waitForUiOffloadThread();
@@ -107,9 +110,9 @@
@Test
public void testStoppingNotificationLoggingReportsCurrentNotifications()
throws Exception {
- when(mStackScroller.isInVisibleLocation(any())).thenReturn(true);
+ when(mListContainer.isInVisibleLocation(any())).thenReturn(true);
when(mNotificationData.getActiveNotifications()).thenReturn(Lists.newArrayList(mEntry));
- mLogger.getChildLocationsChangedListenerForTest().onChildLocationsChanged(mStackScroller);
+ mLogger.getChildLocationsChangedListenerForTest().onChildLocationsChanged();
waitForIdleSync(mLogger.getHandlerForTest());
waitForUiOffloadThread();
Mockito.reset(mBarService);
@@ -123,17 +126,13 @@
private class TestableNotificationLogger extends NotificationLogger {
- public TestableNotificationLogger(
- NotificationListenerService notificationListener,
- UiOffloadThread uiOffloadThread,
- IStatusBarService barService) {
- super(notificationListener, uiOffloadThread);
+ public TestableNotificationLogger(IStatusBarService barService) {
mBarService = barService;
// Make this on the main thread so we can wait for it during tests.
mHandler = new Handler(Looper.getMainLooper());
}
- public NotificationStackScrollLayout.OnChildLocationsChangedListener
+ public OnChildLocationsChangedListener
getChildLocationsChangedListenerForTest() {
return mNotificationLocationsChangedListener;
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java
index b881c09..4829cb2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java
@@ -34,36 +34,41 @@
private static final String TEST_PACKAGE_NAME = "test";
private static final int TEST_UID = 0;
- private Handler mHandler;
- private TestableNotificationRemoteInputManager mRemoteInputManager;
- private StatusBarNotification mSbn;
- private NotificationData.Entry mEntry;
-
@Mock private NotificationPresenter mPresenter;
@Mock private RemoteInputController.Delegate mDelegate;
- @Mock private NotificationLockscreenUserManager mLockscreenUserManager;
@Mock private NotificationRemoteInputManager.Callback mCallback;
@Mock private RemoteInputController mController;
@Mock private NotificationListenerService.RankingMap mRanking;
@Mock private ExpandableNotificationRow mRow;
+ // Dependency mocks:
+ @Mock private NotificationEntryManager mEntryManager;
+ @Mock private NotificationLockscreenUserManager mLockscreenUserManager;
+
+ private Handler mHandler;
+ private TestableNotificationRemoteInputManager mRemoteInputManager;
+ private StatusBarNotification mSbn;
+ private NotificationData.Entry mEntry;
+
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
+ mDependency.injectTestDependency(NotificationEntryManager.class, mEntryManager);
+ mDependency.injectTestDependency(NotificationLockscreenUserManager.class,
+ mLockscreenUserManager);
mHandler = new Handler(Looper.getMainLooper());
when(mPresenter.getHandler()).thenReturn(mHandler);
- when(mPresenter.getLatestRankingMap()).thenReturn(mRanking);
+ when(mEntryManager.getLatestRankingMap()).thenReturn(mRanking);
- mRemoteInputManager = new TestableNotificationRemoteInputManager(mLockscreenUserManager,
- mContext);
+ mRemoteInputManager = new TestableNotificationRemoteInputManager(mContext);
mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, 0, null, TEST_UID,
0, new Notification(), UserHandle.CURRENT, null, 0);
mEntry = new NotificationData.Entry(mSbn);
mEntry.row = mRow;
- mRemoteInputManager.setUpWithPresenterForTest(mPresenter, mCallback, mDelegate,
- mController);
+ mRemoteInputManager.setUpWithPresenterForTest(mPresenter, mEntryManager, mCallback,
+ mDelegate, mController);
}
@Test
@@ -97,21 +102,21 @@
assertTrue(mRemoteInputManager.getRemoteInputEntriesToRemoveOnCollapse().isEmpty());
verify(mController).removeRemoteInput(mEntry, null);
- verify(mPresenter).removeNotification(mEntry.key, mRanking);
+ verify(mEntryManager).removeNotification(mEntry.key, mRanking);
}
private class TestableNotificationRemoteInputManager extends NotificationRemoteInputManager {
- public TestableNotificationRemoteInputManager(
- NotificationLockscreenUserManager lockscreenUserManager, Context context) {
- super(lockscreenUserManager, context);
+ public TestableNotificationRemoteInputManager(Context context) {
+ super(context);
}
public void setUpWithPresenterForTest(NotificationPresenter presenter,
+ NotificationEntryManager entryManager,
Callback callback,
RemoteInputController.Delegate delegate,
RemoteInputController controller) {
- super.setUpWithPresenter(presenter, callback, delegate);
+ super.setUpWithPresenter(presenter, entryManager, callback, delegate);
mRemoteInputController = controller;
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
new file mode 100644
index 0000000..fbe730a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
@@ -0,0 +1,259 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar;
+
+import static junit.framework.Assert.assertTrue;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.support.test.filters.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
+import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.phone.NotificationGroupManager;
+
+import com.google.android.collect.Lists;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.Spy;
+
+import java.util.List;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class NotificationViewHierarchyManagerTest extends SysuiTestCase {
+ @Mock private NotificationPresenter mPresenter;
+ @Mock private NotificationData mNotificationData;
+ @Spy private FakeListContainer mListContainer = new FakeListContainer();
+
+ // Dependency mocks:
+ @Mock private NotificationEntryManager mEntryManager;
+ @Mock private NotificationLockscreenUserManager mLockscreenUserManager;
+ @Mock private NotificationGroupManager mGroupManager;
+ @Mock private VisualStabilityManager mVisualStabilityManager;
+
+ private NotificationViewHierarchyManager mViewHierarchyManager;
+ private NotificationTestHelper mHelper = new NotificationTestHelper(mContext);;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mDependency.injectTestDependency(NotificationEntryManager.class, mEntryManager);
+ mDependency.injectTestDependency(NotificationLockscreenUserManager.class,
+ mLockscreenUserManager);
+ mDependency.injectTestDependency(NotificationGroupManager.class, mGroupManager);
+ mDependency.injectTestDependency(VisualStabilityManager.class, mVisualStabilityManager);
+
+ when(mEntryManager.getNotificationData()).thenReturn(mNotificationData);
+
+ mViewHierarchyManager = new NotificationViewHierarchyManager(mContext);
+ mViewHierarchyManager.setUpWithPresenter(mPresenter, mEntryManager, mListContainer);
+ }
+
+ private NotificationData.Entry createEntry() throws Exception {
+ ExpandableNotificationRow row = mHelper.createRow();
+ NotificationData.Entry entry = new NotificationData.Entry(row.getStatusBarNotification());
+ entry.row = row;
+ return entry;
+ }
+
+ @Test
+ public void testNotificationsBecomingBundled() throws Exception {
+ // Tests 3 top level notifications becoming a single bundled notification with |entry0| as
+ // the summary.
+ NotificationData.Entry entry0 = createEntry();
+ NotificationData.Entry entry1 = createEntry();
+ NotificationData.Entry entry2 = createEntry();
+
+ // Set up the prior state to look like three top level notifications.
+ mListContainer.addContainerView(entry0.row);
+ mListContainer.addContainerView(entry1.row);
+ mListContainer.addContainerView(entry2.row);
+ when(mNotificationData.getActiveNotifications()).thenReturn(
+ Lists.newArrayList(entry0, entry1, entry2));
+
+ // Set up group manager to report that they should be bundled now.
+ when(mGroupManager.isChildInGroupWithSummary(entry0.notification)).thenReturn(false);
+ when(mGroupManager.isChildInGroupWithSummary(entry1.notification)).thenReturn(true);
+ when(mGroupManager.isChildInGroupWithSummary(entry2.notification)).thenReturn(true);
+ when(mGroupManager.getGroupSummary(entry1.notification)).thenReturn(entry0.row);
+ when(mGroupManager.getGroupSummary(entry2.notification)).thenReturn(entry0.row);
+
+ // Run updateNotifications - the view hierarchy should be reorganized.
+ mViewHierarchyManager.updateNotificationViews();
+
+ verify(mListContainer).notifyGroupChildAdded(entry1.row);
+ verify(mListContainer).notifyGroupChildAdded(entry2.row);
+ assertTrue(Lists.newArrayList(entry0.row).equals(mListContainer.mRows));
+ }
+
+ @Test
+ public void testNotificationsBecomingUnbundled() throws Exception {
+ // Tests a bundled notification becoming three top level notifications.
+ NotificationData.Entry entry0 = createEntry();
+ NotificationData.Entry entry1 = createEntry();
+ NotificationData.Entry entry2 = createEntry();
+ entry0.row.addChildNotification(entry1.row);
+ entry0.row.addChildNotification(entry2.row);
+
+ // Set up the prior state to look like one top level notification.
+ mListContainer.addContainerView(entry0.row);
+ when(mNotificationData.getActiveNotifications()).thenReturn(
+ Lists.newArrayList(entry0, entry1, entry2));
+
+ // Set up group manager to report that they should not be bundled now.
+ when(mGroupManager.isChildInGroupWithSummary(entry0.notification)).thenReturn(false);
+ when(mGroupManager.isChildInGroupWithSummary(entry1.notification)).thenReturn(false);
+ when(mGroupManager.isChildInGroupWithSummary(entry2.notification)).thenReturn(false);
+
+ // Run updateNotifications - the view hierarchy should be reorganized.
+ mViewHierarchyManager.updateNotificationViews();
+
+ verify(mListContainer).notifyGroupChildRemoved(
+ entry1.row, entry0.row.getChildrenContainer());
+ verify(mListContainer).notifyGroupChildRemoved(
+ entry2.row, entry0.row.getChildrenContainer());
+ assertTrue(Lists.newArrayList(entry0.row, entry1.row, entry2.row).equals(mListContainer.mRows));
+ }
+
+ @Test
+ public void testNotificationsBecomingSuppressed() throws Exception {
+ // Tests two top level notifications becoming a suppressed summary and a child.
+ NotificationData.Entry entry0 = createEntry();
+ NotificationData.Entry entry1 = createEntry();
+ entry0.row.addChildNotification(entry1.row);
+
+ // Set up the prior state to look like a top level notification.
+ mListContainer.addContainerView(entry0.row);
+ when(mNotificationData.getActiveNotifications()).thenReturn(
+ Lists.newArrayList(entry0, entry1));
+
+ // Set up group manager to report a suppressed summary now.
+ when(mGroupManager.isChildInGroupWithSummary(entry0.notification)).thenReturn(false);
+ when(mGroupManager.isChildInGroupWithSummary(entry1.notification)).thenReturn(false);
+ when(mGroupManager.isSummaryOfSuppressedGroup(entry0.notification)).thenReturn(true);
+
+ // Run updateNotifications - the view hierarchy should be reorganized.
+ mViewHierarchyManager.updateNotificationViews();
+
+ verify(mListContainer).notifyGroupChildRemoved(
+ entry1.row, entry0.row.getChildrenContainer());
+ assertTrue(Lists.newArrayList(entry0.row, entry1.row).equals(mListContainer.mRows));
+ assertEquals(View.GONE, entry0.row.getVisibility());
+ assertEquals(View.VISIBLE, entry1.row.getVisibility());
+ }
+
+ private class FakeListContainer implements NotificationListContainer {
+ final LinearLayout mLayout = new LinearLayout(mContext);
+ final List<View> mRows = Lists.newArrayList();
+
+ @Override
+ public void setChildTransferInProgress(boolean childTransferInProgress) {}
+
+ @Override
+ public void changeViewPosition(View child, int newIndex) {
+ mRows.remove(child);
+ mRows.add(newIndex, child);
+ }
+
+ @Override
+ public void notifyGroupChildAdded(View row) {}
+
+ @Override
+ public void notifyGroupChildRemoved(View row, ViewGroup childrenContainer) {}
+
+ @Override
+ public void generateAddAnimation(View child, boolean fromMoreCard) {}
+
+ @Override
+ public void generateChildOrderChangedEvent() {}
+
+ @Override
+ public int getContainerChildCount() {
+ return mRows.size();
+ }
+
+ @Override
+ public View getContainerChildAt(int i) {
+ return mRows.get(i);
+ }
+
+ @Override
+ public void removeContainerView(View v) {
+ mLayout.removeView(v);
+ mRows.remove(v);
+ }
+
+ @Override
+ public void addContainerView(View v) {
+ mLayout.addView(v);
+ mRows.add(v);
+ }
+
+ @Override
+ public void setMaxDisplayedNotifications(int maxNotifications) {}
+
+ @Override
+ public void snapViewIfNeeded(ExpandableNotificationRow row) {}
+
+ @Override
+ public ViewGroup getViewParentForNotification(NotificationData.Entry entry) {
+ return null;
+ }
+
+ @Override
+ public void onHeightChanged(ExpandableView view, boolean animate) {}
+
+ @Override
+ public void resetExposedMenuView(boolean animate, boolean force) {}
+
+ @Override
+ public NotificationSwipeActionHelper getSwipeActionHelper() {
+ return null;
+ }
+
+ @Override
+ public void cleanUpViewState(View view) {}
+
+ @Override
+ public boolean isInVisibleLocation(ExpandableNotificationRow row) {
+ return true;
+ }
+
+ @Override
+ public void setChildLocationsChangedListener(
+ NotificationLogger.OnChildLocationsChangedListener listener) {}
+
+ @Override
+ public boolean hasPulsingNotifications() {
+ return false;
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
index 0732866..99202f4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
@@ -37,6 +37,7 @@
import android.app.Notification;
import android.app.StatusBarManager;
import android.app.trust.TrustManager;
+import android.content.Context;
import android.hardware.fingerprint.FingerprintManager;
import android.metrics.LogMaker;
import android.os.Binder;
@@ -52,7 +53,6 @@
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
-import android.util.DisplayMetrics;
import android.util.SparseArray;
import android.view.ViewGroup.LayoutParams;
@@ -61,6 +61,7 @@
import com.android.internal.logging.testing.FakeMetricsLogger;
import com.android.internal.statusbar.IStatusBarService;
import com.android.keyguard.KeyguardHostView.OnDismissAction;
+import com.android.systemui.ForegroundServiceController;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.UiOffloadThread;
@@ -72,10 +73,18 @@
import com.android.systemui.statusbar.KeyguardIndicationController;
import com.android.systemui.statusbar.NotificationData;
import com.android.systemui.statusbar.NotificationData.Entry;
+import com.android.systemui.statusbar.NotificationEntryManager;
+import com.android.systemui.statusbar.NotificationGutsManager;
+import com.android.systemui.statusbar.NotificationListContainer;
import com.android.systemui.statusbar.NotificationListener;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationLogger;
+import com.android.systemui.statusbar.NotificationMediaManager;
+import com.android.systemui.statusbar.NotificationPresenter;
+import com.android.systemui.statusbar.NotificationRemoteInputManager;
+import com.android.systemui.statusbar.NotificationViewHierarchyManager;
import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.notification.VisualStabilityManager;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.KeyguardMonitor;
@@ -85,6 +94,8 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
import java.io.ByteArrayOutputStream;
import java.io.PrintWriter;
@@ -94,70 +105,71 @@
@RunWith(AndroidTestingRunner.class)
@RunWithLooper
public class StatusBarTest extends SysuiTestCase {
+ @Mock private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+ @Mock private UnlockMethodCache mUnlockMethodCache;
+ @Mock private KeyguardIndicationController mKeyguardIndicationController;
+ @Mock private NotificationStackScrollLayout mStackScroller;
+ @Mock private HeadsUpManager mHeadsUpManager;
+ @Mock private SystemServicesProxy mSystemServicesProxy;
+ @Mock private NotificationPanelView mNotificationPanelView;
+ @Mock private IStatusBarService mBarService;
+ @Mock private ScrimController mScrimController;
+ @Mock private ArrayList<Entry> mNotificationList;
+ @Mock private FingerprintUnlockController mFingerprintUnlockController;
+ @Mock private NotificationData mNotificationData;
- StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
- UnlockMethodCache mUnlockMethodCache;
- KeyguardIndicationController mKeyguardIndicationController;
- NotificationStackScrollLayout mStackScroller;
- TestableStatusBar mStatusBar;
- FakeMetricsLogger mMetricsLogger;
- HeadsUpManager mHeadsUpManager;
- NotificationData mNotificationData;
- PowerManager mPowerManager;
- SystemServicesProxy mSystemServicesProxy;
- NotificationPanelView mNotificationPanelView;
- ScrimController mScrimController;
- IStatusBarService mBarService;
- NotificationListener mNotificationListener;
- NotificationLogger mNotificationLogger;
- ArrayList<Entry> mNotificationList;
- FingerprintUnlockController mFingerprintUnlockController;
- private DisplayMetrics mDisplayMetrics = new DisplayMetrics();
+ // Mock dependencies:
+ @Mock private NotificationViewHierarchyManager mViewHierarchyManager;
+ @Mock private VisualStabilityManager mVisualStabilityManager;
+ @Mock private NotificationListener mNotificationListener;
+
+ private TestableStatusBar mStatusBar;
+ private FakeMetricsLogger mMetricsLogger;
+ private PowerManager mPowerManager;
+ private TestableNotificationEntryManager mEntryManager;
+ private NotificationLogger mNotificationLogger;
@Before
public void setup() throws Exception {
- mContext.setTheme(R.style.Theme_SystemUI_Light);
+ MockitoAnnotations.initMocks(this);
mDependency.injectMockDependency(AssistManager.class);
mDependency.injectMockDependency(DeviceProvisionedController.class);
+ mDependency.injectMockDependency(NotificationGroupManager.class);
+ mDependency.injectMockDependency(NotificationGutsManager.class);
+ mDependency.injectMockDependency(NotificationRemoteInputManager.class);
+ mDependency.injectMockDependency(NotificationMediaManager.class);
+ mDependency.injectMockDependency(ForegroundServiceController.class);
+ mDependency.injectTestDependency(NotificationViewHierarchyManager.class,
+ mViewHierarchyManager);
+ mDependency.injectTestDependency(VisualStabilityManager.class, mVisualStabilityManager);
+ mDependency.injectTestDependency(NotificationListener.class, mNotificationListener);
mDependency.injectTestDependency(KeyguardMonitor.class, mock(KeyguardMonitorImpl.class));
- CommandQueue commandQueue = mock(CommandQueue.class);
- when(commandQueue.asBinder()).thenReturn(new Binder());
- mContext.putComponent(CommandQueue.class, commandQueue);
+
mContext.addMockSystemService(TrustManager.class, mock(TrustManager.class));
mContext.addMockSystemService(FingerprintManager.class, mock(FingerprintManager.class));
- mStatusBarKeyguardViewManager = mock(StatusBarKeyguardViewManager.class);
- mUnlockMethodCache = mock(UnlockMethodCache.class);
- mKeyguardIndicationController = mock(KeyguardIndicationController.class);
- mStackScroller = mock(NotificationStackScrollLayout.class);
- when(mStackScroller.generateLayoutParams(any())).thenReturn(new LayoutParams(0, 0));
+
mMetricsLogger = new FakeMetricsLogger();
- mHeadsUpManager = mock(HeadsUpManager.class);
- mNotificationData = mock(NotificationData.class);
- mSystemServicesProxy = mock(SystemServicesProxy.class);
- mNotificationPanelView = mock(NotificationPanelView.class);
- when(mNotificationPanelView.getLayoutParams()).thenReturn(new LayoutParams(0, 0));
- mNotificationList = mock(ArrayList.class);
- mScrimController = mock(ScrimController.class);
- mFingerprintUnlockController = mock(FingerprintUnlockController.class);
+ mDependency.injectTestDependency(MetricsLogger.class, mMetricsLogger);
+ mNotificationLogger = new NotificationLogger();
+ mDependency.injectTestDependency(NotificationLogger.class, mNotificationLogger);
+
IPowerManager powerManagerService = mock(IPowerManager.class);
HandlerThread handlerThread = new HandlerThread("TestThread");
handlerThread.start();
mPowerManager = new PowerManager(mContext, powerManagerService,
new Handler(handlerThread.getLooper()));
- when(powerManagerService.isInteractive()).thenReturn(true);
- mBarService = mock(IStatusBarService.class);
- mNotificationListener = mock(NotificationListener.class);
- mNotificationLogger = new NotificationLogger(mNotificationListener, mDependency.get(
- UiOffloadThread.class));
- mDependency.injectTestDependency(MetricsLogger.class, mMetricsLogger);
- mStatusBar = new TestableStatusBar(mStatusBarKeyguardViewManager, mUnlockMethodCache,
- mKeyguardIndicationController, mStackScroller, mHeadsUpManager,
- mNotificationData, mPowerManager, mSystemServicesProxy, mNotificationPanelView,
- mBarService, mNotificationListener, mNotificationLogger, mScrimController,
- mFingerprintUnlockController);
- mStatusBar.mContext = mContext;
- mStatusBar.mComponents = mContext.getComponents();
+ CommandQueue commandQueue = mock(CommandQueue.class);
+ when(commandQueue.asBinder()).thenReturn(new Binder());
+ mContext.putComponent(CommandQueue.class, commandQueue);
+
+ mContext.setTheme(R.style.Theme_SystemUI_Light);
+
+ when(mStackScroller.generateLayoutParams(any())).thenReturn(new LayoutParams(0, 0));
+ when(mNotificationPanelView.getLayoutParams()).thenReturn(new LayoutParams(0, 0));
+ when(powerManagerService.isInteractive()).thenReturn(true);
+ when(mStackScroller.getActivatedChild()).thenReturn(null);
+
doAnswer(invocation -> {
OnDismissAction onDismissAction = (OnDismissAction) invocation.getArguments()[0];
onDismissAction.onDismiss();
@@ -170,9 +182,19 @@
return null;
}).when(mStatusBarKeyguardViewManager).addAfterKeyguardGoneRunnable(any());
- mNotificationLogger.setUpWithPresenter(mStatusBar, mStackScroller);
+ mEntryManager = new TestableNotificationEntryManager(mSystemServicesProxy, mPowerManager,
+ mContext);
+ mStatusBar = new TestableStatusBar(mStatusBarKeyguardViewManager, mUnlockMethodCache,
+ mKeyguardIndicationController, mStackScroller, mHeadsUpManager,
+ mPowerManager, mNotificationPanelView, mBarService, mNotificationListener,
+ mNotificationLogger, mVisualStabilityManager, mViewHierarchyManager,
+ mEntryManager, mScrimController, mFingerprintUnlockController);
+ mStatusBar.mContext = mContext;
+ mStatusBar.mComponents = mContext.getComponents();
+ mEntryManager.setUpForTest(mStatusBar, mStackScroller, mStatusBar, mHeadsUpManager,
+ mNotificationData);
+ mNotificationLogger.setUpWithEntryManager(mEntryManager, mStackScroller);
- when(mStackScroller.getActivatedChild()).thenReturn(null);
TestableLooper.get(this).setMessageHandler(m -> {
if (m.getCallback() == mStatusBar.mNotificationLogger.getVisibilityReporter()) {
return false;
@@ -334,7 +356,7 @@
UserHandle.of(0), null, 0);
NotificationData.Entry entry = new NotificationData.Entry(sbn);
- assertTrue(mStatusBar.shouldPeek(entry, sbn));
+ assertTrue(mEntryManager.shouldPeek(entry, sbn));
}
@Test
@@ -355,7 +377,7 @@
UserHandle.of(0), null, 0);
NotificationData.Entry entry = new NotificationData.Entry(sbn);
- assertFalse(mStatusBar.shouldPeek(entry, sbn));
+ assertFalse(mEntryManager.shouldPeek(entry, sbn));
}
@Test
@@ -375,7 +397,7 @@
UserHandle.of(0), null, 0);
NotificationData.Entry entry = new NotificationData.Entry(sbn);
- assertTrue(mStatusBar.shouldPeek(entry, sbn));
+ assertTrue(mEntryManager.shouldPeek(entry, sbn));
}
@Test
@@ -394,7 +416,7 @@
StatusBarNotification sbn = new StatusBarNotification("a", "a", 0, "a", 0, 0, n,
UserHandle.of(0), null, 0);
NotificationData.Entry entry = new NotificationData.Entry(sbn);
- assertFalse(mStatusBar.shouldPeek(entry, sbn));
+ assertFalse(mEntryManager.shouldPeek(entry, sbn));
}
@Test
public void testShouldPeek_suppressedScreenOff_dozing() {
@@ -412,7 +434,7 @@
StatusBarNotification sbn = new StatusBarNotification("a", "a", 0, "a", 0, 0, n,
UserHandle.of(0), null, 0);
NotificationData.Entry entry = new NotificationData.Entry(sbn);
- assertFalse(mStatusBar.shouldPeek(entry, sbn));
+ assertFalse(mEntryManager.shouldPeek(entry, sbn));
}
@Test
@@ -431,7 +453,7 @@
StatusBarNotification sbn = new StatusBarNotification("a", "a", 0, "a", 0, 0, n,
UserHandle.of(0), null, 0);
NotificationData.Entry entry = new NotificationData.Entry(sbn);
- assertTrue(mStatusBar.shouldPeek(entry, sbn));
+ assertTrue(mEntryManager.shouldPeek(entry, sbn));
}
@@ -564,25 +586,28 @@
static class TestableStatusBar extends StatusBar {
public TestableStatusBar(StatusBarKeyguardViewManager man,
UnlockMethodCache unlock, KeyguardIndicationController key,
- NotificationStackScrollLayout stack, HeadsUpManager hum, NotificationData nd,
- PowerManager pm, SystemServicesProxy ssp, NotificationPanelView panelView,
+ NotificationStackScrollLayout stack, HeadsUpManager hum,
+ PowerManager pm, NotificationPanelView panelView,
IStatusBarService barService, NotificationListener notificationListener,
- NotificationLogger notificationLogger, ScrimController scrimController,
+ NotificationLogger notificationLogger,
+ VisualStabilityManager visualStabilityManager,
+ NotificationViewHierarchyManager viewHierarchyManager,
+ TestableNotificationEntryManager entryManager, ScrimController scrimController,
FingerprintUnlockController fingerprintUnlockController) {
mStatusBarKeyguardViewManager = man;
mUnlockMethodCache = unlock;
mKeyguardIndicationController = key;
mStackScroller = stack;
mHeadsUpManager = hum;
- mNotificationData = nd;
- mUseHeadsUp = true;
mPowerManager = pm;
- mSystemServicesProxy = ssp;
mNotificationPanel = panelView;
mBarService = barService;
mNotificationListener = notificationListener;
mNotificationLogger = notificationLogger;
mWakefulnessLifecycle = createAwakeWakefulnessLifecycle();
+ mVisualStabilityManager = visualStabilityManager;
+ mViewHierarchyManager = viewHierarchyManager;
+ mEntryManager = entryManager;
mScrimController = scrimController;
mFingerprintUnlockController = fingerprintUnlockController;
}
@@ -606,5 +631,26 @@
public void setUserSetupForTest(boolean userSetup) {
mUserSetup = userSetup;
}
+
+ }
+
+ private class TestableNotificationEntryManager extends NotificationEntryManager {
+
+ public TestableNotificationEntryManager(SystemServicesProxy systemServicesProxy,
+ PowerManager powerManager, Context context) {
+ super(context);
+ mSystemServicesProxy = systemServicesProxy;
+ mPowerManager = powerManager;
+ }
+
+ public void setUpForTest(NotificationPresenter presenter,
+ NotificationListContainer listContainer,
+ Callback callback,
+ HeadsUpManager headsUpManager,
+ NotificationData notificationData) {
+ super.setUpWithPresenter(presenter, listContainer, callback, headsUpManager);
+ mNotificationData = notificationData;
+ mUseHeadsUp = true;
+ }
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/leak/LeakDetectorTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/leak/LeakDetectorTest.java
index f1965a2..56de32d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/leak/LeakDetectorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/leak/LeakDetectorTest.java
@@ -33,6 +33,8 @@
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
@SmallTest
@RunWith(AndroidJUnit4.class)
@@ -40,6 +42,30 @@
private LeakDetector mLeakDetector;
+ // The references for which collection is observed are stored in fields. The allocation and
+ // of these references happens in separate methods (trackObjectWith/trackCollectionWith)
+ // from where they are set to null. The generated code might keep the allocated reference
+ // alive in a dex register when compiling in release mode. As R8 is used to compile this
+ // test the --dontoptimize flag is also required to ensure that these methods are not
+ // inlined, as that would defeat the purpose of having the mutation in methods.
+ private Object mObject;
+ private Collection<?> mCollection;
+
+ private CollectionWaiter trackObjectWith(Consumer<Object> tracker) {
+ mObject = new Object();
+ CollectionWaiter collectionWaiter = ReferenceTestUtils.createCollectionWaiter(mObject);
+ tracker.accept(mObject);
+ return collectionWaiter;
+ }
+
+ private CollectionWaiter trackCollectionWith(
+ BiConsumer<? super Collection<?>, String> tracker) {
+ mCollection = new ArrayList<>();
+ CollectionWaiter collectionWaiter = ReferenceTestUtils.createCollectionWaiter(mCollection);
+ tracker.accept(mCollection, "tag");
+ return collectionWaiter;
+ }
+
@Before
public void setup() {
mLeakDetector = LeakDetector.create();
@@ -51,31 +77,22 @@
@Test
public void trackInstance_doesNotLeakTrackedObject() {
- Object object = new Object();
- CollectionWaiter collectionWaiter = ReferenceTestUtils.createCollectionWaiter(object);
-
- mLeakDetector.trackInstance(object);
- object = null;
+ CollectionWaiter collectionWaiter = trackObjectWith(mLeakDetector::trackInstance);
+ mObject = null;
collectionWaiter.waitForCollection();
}
@Test
public void trackCollection_doesNotLeakTrackedObject() {
- Collection<?> object = new ArrayList<>();
- CollectionWaiter collectionWaiter = ReferenceTestUtils.createCollectionWaiter(object);
-
- mLeakDetector.trackCollection(object, "tag");
- object = null;
+ CollectionWaiter collectionWaiter = trackCollectionWith(mLeakDetector::trackCollection);
+ mCollection = null;
collectionWaiter.waitForCollection();
}
@Test
public void trackGarbage_doesNotLeakTrackedObject() {
- Object object = new Object();
- CollectionWaiter collectionWaiter = ReferenceTestUtils.createCollectionWaiter(object);
-
- mLeakDetector.trackGarbage(object);
- object = null;
+ CollectionWaiter collectionWaiter = trackObjectWith(mLeakDetector::trackGarbage);
+ mObject = null;
collectionWaiter.waitForCollection();
}
@@ -108,4 +125,4 @@
FileOutputStream fos = new FileOutputStream("/dev/null");
mLeakDetector.dump(fos.getFD(), new PrintWriter(fos), new String[0]);
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/leak/WeakIdentityHashMapTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/leak/WeakIdentityHashMapTest.java
index 9787df9..ce6212e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/leak/WeakIdentityHashMapTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/leak/WeakIdentityHashMapTest.java
@@ -41,6 +41,13 @@
mMap = new WeakIdentityHashMap<>();
}
+ private CollectionWaiter addObjectToMap(WeakIdentityHashMap<Object, Object> map) {
+ Object object = new Object();
+ CollectionWaiter collectionWaiter = ReferenceTestUtils.createCollectionWaiter(object);
+ map.put(object, "value");
+ return collectionWaiter;
+ }
+
@Test
public void testUsesIdentity() {
String a1 = new String("a");
@@ -56,11 +63,12 @@
@Test
public void testWeaklyReferences() {
- Object object = new Object();
- CollectionWaiter collectionWaiter = ReferenceTestUtils.createCollectionWaiter(object);
-
- mMap.put(object, "value");
- object = null;
+ // Allocate and add an object to the weak map in a separate method to avoid a live
+ // reference to the allocated object in a dex register. As R8 is used to compile this
+ // test the --dontoptimize flag is also required to ensure that the method is not
+ // inlined, as that would defeat the purpose of having the allocation in a separate
+ // method.
+ CollectionWaiter collectionWaiter = addObjectToMap(mMap);
// Wait until object has been collected. We'll also need to wait for mMap to become empty,
// because our collection waiter may be told about the collection earlier than mMap.
@@ -70,4 +78,4 @@
assertEquals(0, mMap.size());
assertTrue(mMap.isEmpty());
}
-}
\ No newline at end of file
+}
diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto
index 935b787..1aaa538 100644
--- a/proto/src/metrics_constants.proto
+++ b/proto/src/metrics_constants.proto
@@ -5094,6 +5094,36 @@
// Tag used to report autofill field classification scores
FIELD_AUTOFILL_MATCH_SCORE = 1274;
+ // ACTION: Usb config has been changed to charging
+ // CATEGORY: SETTINGS
+ // OS: P
+ ACTION_USB_CONFIG_CHARGING = 1275;
+
+ // ACTION: Usb config has been changed to mtp (file transfer)
+ // CATEGORY: SETTINGS
+ // OS: P
+ ACTION_USB_CONFIG_MTP = 1276;
+
+ // ACTION: Usb config has been changed to ptp (photo transfer)
+ // CATEGORY: SETTINGS
+ // OS: P
+ ACTION_USB_CONFIG_PTP = 1277;
+
+ // ACTION: Usb config has been changed to rndis (usb tethering)
+ // CATEGORY: SETTINGS
+ // OS: P
+ ACTION_USB_CONFIG_RNDIS = 1278;
+
+ // ACTION: Usb config has been changed to midi
+ // CATEGORY: SETTINGS
+ // OS: P
+ ACTION_USB_CONFIG_MIDI = 1279;
+
+ // ACTION: Usb config has been changed to accessory
+ // CATEGORY: SETTINGS
+ // OS: P
+ ACTION_USB_CONFIG_ACCESSORY = 1280;
+
// ---- End P Constants, all P constants go above this line ----
// Add new aosp constants above this line.
// END OF AOSP CONSTANTS
diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
index 3d7d6b7..ed068b9 100644
--- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
@@ -30,6 +30,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
+import android.content.pm.PackageManager;
import android.content.pm.ParceledListSlice;
import android.graphics.Region;
import android.os.Binder;
@@ -740,6 +741,9 @@
@Override
public boolean isFingerprintGestureDetectionAvailable() {
+ if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) {
+ return false;
+ }
if (isCapturingFingerprintGestures()) {
FingerprintGestureDispatcher dispatcher =
mSystemSupport.getFingerprintGestureDispatcher();
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index d83f6ae..ba8ce59 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -17,6 +17,7 @@
package com.android.server.accessibility;
import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY;
+import static android.view.accessibility.AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED;
import static android.view.accessibility.AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS;
import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS;
@@ -64,7 +65,9 @@
import android.os.Process;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
+import android.os.ResultReceiver;
import android.os.ServiceManager;
+import android.os.ShellCallback;
import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserManager;
@@ -299,6 +302,14 @@
return state;
}
+ boolean getBindInstantServiceAllowed(int userId) {
+ return mSecurityPolicy.getBindInstantServiceAllowed(userId);
+ }
+
+ void setBindInstantServiceAllowed(int userId, boolean allowed) {
+ mSecurityPolicy.setBindInstantServiceAllowed(userId, allowed);
+ }
+
private void registerBroadcastReceivers() {
PackageMonitor monitor = new PackageMonitor() {
@Override
@@ -752,7 +763,6 @@
mPictureInPictureActionReplacingConnection = wrapper;
wrapper.linkToDeath();
}
- mSecurityPolicy.notifyWindowsChanged();
}
}
@@ -1218,14 +1228,18 @@
private boolean readInstalledAccessibilityServiceLocked(UserState userState) {
mTempAccessibilityServiceInfoList.clear();
+ int flags = PackageManager.GET_SERVICES
+ | PackageManager.GET_META_DATA
+ | PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
+ | PackageManager.MATCH_DIRECT_BOOT_AWARE
+ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
+
+ if (userState.mBindInstantServiceAllowed) {
+ flags |= PackageManager.MATCH_INSTANT;
+ }
+
List<ResolveInfo> installedServices = mPackageManager.queryIntentServicesAsUser(
- new Intent(AccessibilityService.SERVICE_INTERFACE),
- PackageManager.GET_SERVICES
- | PackageManager.GET_META_DATA
- | PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
- | PackageManager.MATCH_DIRECT_BOOT_AWARE
- | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
- mCurrentUserId);
+ new Intent(AccessibilityService.SERVICE_INTERFACE), flags, mCurrentUserId);
for (int i = 0, count = installedServices.size(); i < count; i++) {
ResolveInfo resolveInfo = installedServices.get(i);
@@ -2269,6 +2283,14 @@
}
}
+ private void sendAccessibilityEventLocked(AccessibilityEvent event, int userId) {
+ // Resync to avoid calling out with the lock held
+ event.setEventTime(SystemClock.uptimeMillis());
+ mMainHandler.obtainMessage(
+ MainHandler.MSG_SEND_ACCESSIBILITY_EVENT, userId, 0 /* unused */, event)
+ .sendToTarget();
+ }
+
/**
* AIDL-exposed method. System only.
* Inform accessibility that a fingerprint gesture was performed
@@ -2405,6 +2427,7 @@
public static final int MSG_SEND_ACCESSIBILITY_BUTTON_TO_INPUT_FILTER = 13;
public static final int MSG_SHOW_ACCESSIBILITY_BUTTON_CHOOSER = 14;
public static final int MSG_INIT_SERVICE = 15;
+ public static final int MSG_SEND_ACCESSIBILITY_EVENT = 16;
public MainHandler(Looper looper) {
super(looper);
@@ -2505,6 +2528,12 @@
(AccessibilityServiceConnection) msg.obj;
service.initializeService();
} break;
+
+ case MSG_SEND_ACCESSIBILITY_EVENT: {
+ final AccessibilityEvent event = (AccessibilityEvent) msg.obj;
+ final int userId = msg.arg1;
+ sendAccessibilityEvent(event, userId);
+ }
}
}
@@ -2519,7 +2548,7 @@
AccessibilityEvent event = AccessibilityEvent.obtain(
AccessibilityEvent.TYPE_ANNOUNCEMENT);
event.getText().add(message);
- sendAccessibilityEvent(event, mCurrentUserId);
+ sendAccessibilityEventLocked(event, mCurrentUserId);
}
}
}
@@ -2709,6 +2738,14 @@
}
}
+ @Override
+ public void onShellCommand(FileDescriptor in, FileDescriptor out,
+ FileDescriptor err, String[] args, ShellCallback callback,
+ ResultReceiver resultReceiver) {
+ new AccessibilityShellCommand(this).exec(this, in, out, err, args,
+ callback, resultReceiver);
+ }
+
final class WindowsForAccessibilityCallback implements
WindowManagerInternal.WindowsForAccessibilityCallback {
@@ -2939,21 +2976,21 @@
public class SecurityPolicy {
public static final int INVALID_WINDOW_ID = -1;
- private static final int RETRIEVAL_ALLOWING_EVENT_TYPES =
- AccessibilityEvent.TYPE_VIEW_CLICKED
- | AccessibilityEvent.TYPE_VIEW_FOCUSED
- | AccessibilityEvent.TYPE_VIEW_HOVER_ENTER
- | AccessibilityEvent.TYPE_VIEW_HOVER_EXIT
- | AccessibilityEvent.TYPE_VIEW_LONG_CLICKED
- | AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED
- | AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED
- | AccessibilityEvent.TYPE_VIEW_SELECTED
- | AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED
- | AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED
- | AccessibilityEvent.TYPE_VIEW_SCROLLED
- | AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED
- | AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED
- | AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY;
+ private static final int KEEP_SOURCE_EVENT_TYPES = AccessibilityEvent.TYPE_VIEW_CLICKED
+ | AccessibilityEvent.TYPE_VIEW_FOCUSED
+ | AccessibilityEvent.TYPE_VIEW_HOVER_ENTER
+ | AccessibilityEvent.TYPE_VIEW_HOVER_EXIT
+ | AccessibilityEvent.TYPE_VIEW_LONG_CLICKED
+ | AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED
+ | AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED
+ | AccessibilityEvent.TYPE_WINDOWS_CHANGED
+ | AccessibilityEvent.TYPE_VIEW_SELECTED
+ | AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED
+ | AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED
+ | AccessibilityEvent.TYPE_VIEW_SCROLLED
+ | AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED
+ | AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED
+ | AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY;
// In Z order
public List<AccessibilityWindowInfo> mWindows;
@@ -3076,6 +3113,32 @@
return uidPackages;
}
+ private boolean getBindInstantServiceAllowed(int userId) {
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.MANAGE_BIND_INSTANT_SERVICE,
+ "getBindInstantServiceAllowed");
+ UserState state = mUserStates.get(userId);
+ return (state != null) && state.mBindInstantServiceAllowed;
+ }
+
+ private void setBindInstantServiceAllowed(int userId, boolean allowed) {
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.MANAGE_BIND_INSTANT_SERVICE,
+ "setBindInstantServiceAllowed");
+ UserState state = mUserStates.get(userId);
+ if (state == null) {
+ if (!allowed) {
+ return;
+ }
+ state = new UserState(userId);
+ mUserStates.put(userId, state);
+ }
+ if (state.mBindInstantServiceAllowed != allowed) {
+ state.mBindInstantServiceAllowed = allowed;
+ onUserStateChangedLocked(state);
+ }
+ }
+
public void clearWindowsLocked() {
List<WindowInfo> windows = Collections.emptyList();
final int activeWindowId = mActiveWindowId;
@@ -3089,10 +3152,10 @@
mWindows = new ArrayList<>();
}
- final int oldWindowCount = mWindows.size();
- for (int i = oldWindowCount - 1; i >= 0; i--) {
- mWindows.remove(i).recycle();
- }
+ List<AccessibilityWindowInfo> oldWindowList = new ArrayList<>(mWindows);
+ SparseArray<AccessibilityWindowInfo> oldWindowsById = mA11yWindowInfoById.clone();
+
+ mWindows.clear();
mA11yWindowInfoById.clear();
for (int i = 0; i < mWindowInfoById.size(); i++) {
@@ -3154,7 +3217,49 @@
}
}
- notifyWindowsChanged();
+ sendEventsForChangedWindowsLocked(oldWindowList, oldWindowsById);
+
+ final int oldWindowCount = oldWindowList.size();
+ for (int i = oldWindowCount - 1; i >= 0; i--) {
+ oldWindowList.remove(i).recycle();
+ }
+ }
+
+ private void sendEventsForChangedWindowsLocked(List<AccessibilityWindowInfo> oldWindows,
+ SparseArray<AccessibilityWindowInfo> oldWindowsById) {
+ List<AccessibilityEvent> events = new ArrayList<>();
+ // Send events for all removed windows
+ final int oldWindowsCount = oldWindows.size();
+ for (int i = 0; i < oldWindowsCount; i++) {
+ final AccessibilityWindowInfo window = oldWindows.get(i);
+ if (mA11yWindowInfoById.get(window.getId()) == null) {
+ events.add(AccessibilityEvent.obtainWindowsChangedEvent(
+ window.getId(), AccessibilityEvent.WINDOWS_CHANGE_REMOVED));
+ }
+ }
+
+ // Look for other changes
+ int oldWindowIndex = 0;
+ final int newWindowCount = mWindows.size();
+ for (int i = 0; i < newWindowCount; i++) {
+ final AccessibilityWindowInfo newWindow = mWindows.get(i);
+ final AccessibilityWindowInfo oldWindow = oldWindowsById.get(newWindow.getId());
+ if (oldWindow == null) {
+ events.add(AccessibilityEvent.obtainWindowsChangedEvent(
+ newWindow.getId(), AccessibilityEvent.WINDOWS_CHANGE_ADDED));
+ } else {
+ int changes = newWindow.differenceFrom(oldWindow);
+ if (changes != 0) {
+ events.add(AccessibilityEvent.obtainWindowsChangedEvent(
+ newWindow.getId(), changes));
+ }
+ }
+ }
+
+ final int numEvents = events.size();
+ for (int i = 0; i < numEvents; i++) {
+ sendAccessibilityEventLocked(events.get(i), mCurrentUserId);
+ }
}
public boolean computePartialInteractiveRegionForWindowLocked(int windowId,
@@ -3195,7 +3300,7 @@
}
public void updateEventSourceLocked(AccessibilityEvent event) {
- if ((event.getEventType() & RETRIEVAL_ALLOWING_EVENT_TYPES) == 0) {
+ if ((event.getEventType() & KEEP_SOURCE_EVENT_TYPES) == 0) {
event.setSource((View) null);
}
}
@@ -3309,46 +3414,55 @@
private void setActiveWindowLocked(int windowId) {
if (mActiveWindowId != windowId) {
+ sendAccessibilityEventLocked(
+ AccessibilityEvent.obtainWindowsChangedEvent(
+ mActiveWindowId, AccessibilityEvent.WINDOWS_CHANGE_ACTIVE),
+ mCurrentUserId);
+
mActiveWindowId = windowId;
if (mWindows != null) {
final int windowCount = mWindows.size();
for (int i = 0; i < windowCount; i++) {
AccessibilityWindowInfo window = mWindows.get(i);
- window.setActive(window.getId() == windowId);
+ if (window.getId() == windowId) {
+ window.setActive(true);
+ sendAccessibilityEventLocked(
+ AccessibilityEvent.obtainWindowsChangedEvent(windowId,
+ AccessibilityEvent.WINDOWS_CHANGE_ACTIVE),
+ mCurrentUserId);
+ } else {
+ window.setActive(false);
+ }
}
}
- notifyWindowsChanged();
}
}
private void setAccessibilityFocusedWindowLocked(int windowId) {
if (mAccessibilityFocusedWindowId != windowId) {
+ sendAccessibilityEventLocked(
+ AccessibilityEvent.obtainWindowsChangedEvent(
+ mAccessibilityFocusedWindowId,
+ WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED),
+ mCurrentUserId);
+
mAccessibilityFocusedWindowId = windowId;
if (mWindows != null) {
final int windowCount = mWindows.size();
for (int i = 0; i < windowCount; i++) {
AccessibilityWindowInfo window = mWindows.get(i);
- window.setAccessibilityFocused(window.getId() == windowId);
+ if (window.getId() == windowId) {
+ window.setAccessibilityFocused(true);
+ sendAccessibilityEventLocked(
+ AccessibilityEvent.obtainWindowsChangedEvent(
+ windowId, WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED),
+ mCurrentUserId);
+
+ } else {
+ window.setAccessibilityFocused(false);
+ }
}
}
-
- notifyWindowsChanged();
- }
- }
-
- public void notifyWindowsChanged() {
- if (mWindowsForAccessibilityCallback == null) {
- return;
- }
- final long identity = Binder.clearCallingIdentity();
- try {
- // Let the client know the windows changed.
- AccessibilityEvent event = AccessibilityEvent.obtain(
- AccessibilityEvent.TYPE_WINDOWS_CHANGED);
- event.setEventTime(SystemClock.uptimeMillis());
- sendAccessibilityEvent(event, mCurrentUserId);
- } finally {
- Binder.restoreCallingIdentity(identity);
}
}
@@ -3558,6 +3672,8 @@
public boolean mIsFilterKeyEventsEnabled;
public boolean mAccessibilityFocusOnlyInActiveWindow;
+ public boolean mBindInstantServiceAllowed;
+
public UserState(int userId) {
mUserId = userId;
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
index 5f6efb6..96b8979 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
@@ -91,10 +91,12 @@
if (userState == null) return;
final long identity = Binder.clearCallingIdentity();
try {
+ int flags = Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE;
+ if (userState.mBindInstantServiceAllowed) {
+ flags |= Context.BIND_ALLOW_INSTANT;
+ }
if (mService == null && mContext.bindServiceAsUser(
- mIntent, this,
- Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE,
- new UserHandle(userState.mUserId))) {
+ mIntent, this, flags, new UserHandle(userState.mUserId))) {
userState.getBindingServicesLocked().add(mComponentName);
}
} finally {
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityShellCommand.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityShellCommand.java
new file mode 100644
index 0000000..ff59c24
--- /dev/null
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityShellCommand.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.accessibility;
+
+import android.annotation.NonNull;
+import android.os.ShellCommand;
+import android.os.UserHandle;
+
+import java.io.PrintWriter;
+
+/**
+ * Shell command implementation for the accessibility manager service
+ */
+final class AccessibilityShellCommand extends ShellCommand {
+ final @NonNull AccessibilityManagerService mService;
+
+ AccessibilityShellCommand(@NonNull AccessibilityManagerService service) {
+ mService = service;
+ }
+
+ @Override
+ public int onCommand(String cmd) {
+ if (cmd == null) {
+ return handleDefaultCommands(cmd);
+ }
+ switch (cmd) {
+ case "get-bind-instant-service-allowed": {
+ return runGetBindInstantServiceAllowed();
+ }
+ case "set-bind-instant-service-allowed": {
+ return runSetBindInstantServiceAllowed();
+ }
+ }
+ return -1;
+ }
+
+ private int runGetBindInstantServiceAllowed() {
+ final Integer userId = parseUserId();
+ if (userId == null) {
+ return -1;
+ }
+ getOutPrintWriter().println(Boolean.toString(
+ mService.getBindInstantServiceAllowed(userId)));
+ return 0;
+ }
+
+ private int runSetBindInstantServiceAllowed() {
+ final Integer userId = parseUserId();
+ if (userId == null) {
+ return -1;
+ }
+ final String allowed = getNextArgRequired();
+ if (allowed == null) {
+ getErrPrintWriter().println("Error: no true/false specified");
+ return -1;
+ }
+ mService.setBindInstantServiceAllowed(userId,
+ Boolean.parseBoolean(allowed));
+ return 0;
+ }
+
+ private Integer parseUserId() {
+ final String option = getNextOption();
+ if (option != null) {
+ if (option.equals("--user")) {
+ return UserHandle.parseUserArg(getNextArgRequired());
+ } else {
+ getErrPrintWriter().println("Unknown option: " + option);
+ return null;
+ }
+ }
+ return UserHandle.USER_SYSTEM;
+ }
+
+ @Override
+ public void onHelp() {
+ PrintWriter pw = getOutPrintWriter();
+ pw.println("Accessibility service (accessibility) commands:");
+ pw.println(" help");
+ pw.println(" Print this help text.");
+ pw.println(" set-bind-instant-service-allowed [--user <USER_ID>] true|false ");
+ pw.println(" Set whether binding to services provided by instant apps is allowed.");
+ pw.println(" get-bind-instant-service-allowed [--user <USER_ID>]");
+ pw.println(" Get whether binding to services provided by instant apps is allowed.");
+ }
+}
\ No newline at end of file
diff --git a/services/accessibility/java/com/android/server/accessibility/GestureUtils.java b/services/accessibility/java/com/android/server/accessibility/GestureUtils.java
index abfdb68..d5b53bc 100644
--- a/services/accessibility/java/com/android/server/accessibility/GestureUtils.java
+++ b/services/accessibility/java/com/android/server/accessibility/GestureUtils.java
@@ -40,12 +40,6 @@
return (deltaTime >= timeout);
}
- public static boolean isSamePointerContext(MotionEvent first, MotionEvent second) {
- return (first.getPointerIdBits() == second.getPointerIdBits()
- && first.getPointerId(first.getActionIndex())
- == second.getPointerId(second.getActionIndex()));
- }
-
/**
* Determines whether a two pointer gesture is a dragging one.
*
diff --git a/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java
index 9b2b4eb..74d2ddd 100644
--- a/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java
@@ -229,7 +229,7 @@
}
void clearAndTransitionToStateDetecting() {
- mCurrentState = mDelegatingState;
+ mCurrentState = mDetectingState;
mDetectingState.clear();
mViewportDraggingState.clear();
mPanningScalingState.clear();
@@ -649,14 +649,19 @@
break;
case ACTION_MOVE: {
if (isFingerDown()
- && distance(mLastDown, /* move */ event) > mSwipeMinDistance
- // For convenience, viewport dragging on 3tap&hold takes precedence
- // over insta-delegating on 3tap&swipe
- // (which is a rare combo to be used aside from magnification)
- && !isMultiTapTriggered(2 /* taps */)) {
+ && distance(mLastDown, /* move */ event) > mSwipeMinDistance) {
- // Swipe detected - delegate skipping timeout
- transitionToDelegatingStateAndClear();
+ // Swipe detected - transition immediately
+
+ // For convenience, viewport dragging takes precedence
+ // over insta-delegating on 3tap&swipe
+ // (which is a rare combo to be used aside from magnification)
+ if (isMultiTapTriggered(2 /* taps */)) {
+ transitionTo(mViewportDraggingState);
+ clear();
+ } else {
+ transitionToDelegatingStateAndClear();
+ }
}
}
break;
@@ -755,10 +760,10 @@
int policyFlags) {
if (event.getActionMasked() == ACTION_DOWN) {
mPreLastDown = mLastDown;
- mLastDown = event;
+ mLastDown = MotionEvent.obtain(event);
} else if (event.getActionMasked() == ACTION_UP) {
mPreLastUp = mLastUp;
- mLastUp = event;
+ mLastUp = MotionEvent.obtain(event);
}
MotionEventInfo info = MotionEventInfo.obtain(event, rawEvent,
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
index 9ecf63d..4cdfd62 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -400,7 +400,7 @@
return;
}
- session.logContextCommittedLocked();
+ session.logContextCommitted();
final boolean finished = session.showSaveLocked();
if (sVerbose) Slog.v(TAG, "finishSessionLocked(): session finished on save? " + finished);
@@ -713,7 +713,7 @@
/**
* Updates the last fill response when an autofill context is committed.
*/
- void logContextCommitted(int sessionId, @Nullable Bundle clientState,
+ void logContextCommittedLocked(int sessionId, @Nullable Bundle clientState,
@Nullable ArrayList<String> selectedDatasets,
@Nullable ArraySet<String> ignoredDatasets,
@Nullable ArrayList<AutofillId> changedFieldIds,
@@ -723,44 +723,42 @@
@Nullable ArrayList<AutofillId> detectedFieldIdsList,
@Nullable ArrayList<FieldClassification> detectedFieldClassificationsList,
@NonNull String appPackageName) {
- synchronized (mLock) {
- if (isValidEventLocked("logDatasetNotSelected()", sessionId)) {
- AutofillId[] detectedFieldsIds = null;
- FieldClassification[] detectedFieldClassifications = null;
- if (detectedFieldIdsList != null) {
- detectedFieldsIds = new AutofillId[detectedFieldIdsList.size()];
- detectedFieldIdsList.toArray(detectedFieldsIds);
- detectedFieldClassifications =
- new FieldClassification[detectedFieldClassificationsList.size()];
- detectedFieldClassificationsList.toArray(detectedFieldClassifications);
+ if (isValidEventLocked("logDatasetNotSelected()", sessionId)) {
+ AutofillId[] detectedFieldsIds = null;
+ FieldClassification[] detectedFieldClassifications = null;
+ if (detectedFieldIdsList != null) {
+ detectedFieldsIds = new AutofillId[detectedFieldIdsList.size()];
+ detectedFieldIdsList.toArray(detectedFieldsIds);
+ detectedFieldClassifications =
+ new FieldClassification[detectedFieldClassificationsList.size()];
+ detectedFieldClassificationsList.toArray(detectedFieldClassifications);
- final int numberFields = detectedFieldsIds.length;
- int totalSize = 0;
- float totalScore = 0;
- for (int i = 0; i < numberFields; i++) {
- final FieldClassification fc = detectedFieldClassifications[i];
- final List<Match> matches = fc.getMatches();
- final int size = matches.size();
- totalSize += size;
- for (int j = 0; j < size; j++) {
- totalScore += matches.get(j).getScore();
- }
+ final int numberFields = detectedFieldsIds.length;
+ int totalSize = 0;
+ float totalScore = 0;
+ for (int i = 0; i < numberFields; i++) {
+ final FieldClassification fc = detectedFieldClassifications[i];
+ final List<Match> matches = fc.getMatches();
+ final int size = matches.size();
+ totalSize += size;
+ for (int j = 0; j < size; j++) {
+ totalScore += matches.get(j).getScore();
}
-
- final int averageScore = (int) ((totalScore * 100) / totalSize);
- mMetricsLogger.write(Helper
- .newLogMaker(MetricsEvent.AUTOFILL_FIELD_CLASSIFICATION_MATCHES,
- appPackageName, getServicePackageName())
- .setCounterValue(numberFields)
- .addTaggedData(MetricsEvent.FIELD_AUTOFILL_MATCH_SCORE,
- averageScore));
}
- mEventHistory.addEvent(new Event(Event.TYPE_CONTEXT_COMMITTED, null,
- clientState, selectedDatasets, ignoredDatasets,
- changedFieldIds, changedDatasetIds,
- manuallyFilledFieldIds, manuallyFilledDatasetIds,
- detectedFieldsIds, detectedFieldClassifications));
+
+ final int averageScore = (int) ((totalScore * 100) / totalSize);
+ mMetricsLogger.write(Helper
+ .newLogMaker(MetricsEvent.AUTOFILL_FIELD_CLASSIFICATION_MATCHES,
+ appPackageName, getServicePackageName())
+ .setCounterValue(numberFields)
+ .addTaggedData(MetricsEvent.FIELD_AUTOFILL_MATCH_SCORE,
+ averageScore));
}
+ mEventHistory.addEvent(new Event(Event.TYPE_CONTEXT_COMMITTED, null,
+ clientState, selectedDatasets, ignoredDatasets,
+ changedFieldIds, changedDatasetIds,
+ manuallyFilledFieldIds, manuallyFilledDatasetIds,
+ detectedFieldsIds, detectedFieldClassifications));
}
}
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 6d4a525..01f9084 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -906,7 +906,15 @@
* Generates a {@link android.service.autofill.FillEventHistory.Event#TYPE_CONTEXT_COMMITTED}
* when necessary.
*/
- public void logContextCommittedLocked() {
+ public void logContextCommitted() {
+ mHandlerCaller.getHandler().post(() -> {
+ synchronized (mLock) {
+ logContextCommittedLocked();
+ }
+ });
+ }
+
+ private void logContextCommittedLocked() {
final FillResponse lastResponse = getLastResponseLocked("logContextCommited()");
if (lastResponse == null) return;
@@ -1115,7 +1123,7 @@
}
}
- mService.logContextCommitted(id, mClientState, mSelectedDatasetIds, ignoredDatasets,
+ mService.logContextCommittedLocked(id, mClientState, mSelectedDatasetIds, ignoredDatasets,
changedFieldIds, changedDatasetIds,
manuallyFilledFieldIds, manuallyFilledDatasetIds,
detectedFieldIds, detectedFieldClassifications, mComponentName.getPackageName());
@@ -1359,7 +1367,10 @@
}
if (sDebug) Slog.d(TAG, "Good news, everyone! All checks passed, show save UI!");
- mService.logSaveShown(id, mClientState);
+
+ // Use handler so logContextCommitted() is logged first
+ mHandlerCaller.getHandler().post(() -> mService.logSaveShown(id, mClientState));
+
final IAutoFillManagerClient client = getClient();
mPendingSaveUi = new PendingUi(mActivityToken, id, client);
getUiForShowing().showSaveUi(mService.getServiceLabel(), mService.getServiceIcon(),
diff --git a/services/backup/java/com/android/server/backup/BackupManagerConstants.java b/services/backup/java/com/android/server/backup/BackupManagerConstants.java
index 245241c..b17f794 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerConstants.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerConstants.java
@@ -24,6 +24,7 @@
import android.net.Uri;
import android.os.Handler;
import android.provider.Settings;
+import android.text.TextUtils;
import android.util.KeyValueListParser;
import android.util.Slog;
@@ -51,6 +52,8 @@
"full_backup_require_charging";
private static final String FULL_BACKUP_REQUIRED_NETWORK_TYPE =
"full_backup_required_network_type";
+ private static final String BACKUP_FINISHED_NOTIFICATION_RECEIVERS =
+ "backup_finished_notification_receivers";
// Hard coded default values.
private static final long DEFAULT_KEY_VALUE_BACKUP_INTERVAL_MILLISECONDS =
@@ -63,6 +66,7 @@
24 * AlarmManager.INTERVAL_HOUR;
private static final boolean DEFAULT_FULL_BACKUP_REQUIRE_CHARGING = true;
private static final int DEFAULT_FULL_BACKUP_REQUIRED_NETWORK_TYPE = 2;
+ private static final String DEFAULT_BACKUP_FINISHED_NOTIFICATION_RECEIVERS = "";
// Backup manager constants.
private long mKeyValueBackupIntervalMilliseconds;
@@ -72,6 +76,7 @@
private long mFullBackupIntervalMilliseconds;
private boolean mFullBackupRequireCharging;
private int mFullBackupRequiredNetworkType;
+ private String[] mBackupFinishedNotificationReceivers;
private ContentResolver mResolver;
private final KeyValueListParser mParser = new KeyValueListParser(',');
@@ -116,6 +121,14 @@
DEFAULT_FULL_BACKUP_REQUIRE_CHARGING);
mFullBackupRequiredNetworkType = mParser.getInt(FULL_BACKUP_REQUIRED_NETWORK_TYPE,
DEFAULT_FULL_BACKUP_REQUIRED_NETWORK_TYPE);
+ String backupFinishedNotificationReceivers = mParser.getString(
+ BACKUP_FINISHED_NOTIFICATION_RECEIVERS,
+ DEFAULT_BACKUP_FINISHED_NOTIFICATION_RECEIVERS);
+ if (backupFinishedNotificationReceivers.isEmpty()) {
+ mBackupFinishedNotificationReceivers = new String[] {};
+ } else {
+ mBackupFinishedNotificationReceivers = backupFinishedNotificationReceivers.split(":");
+ }
}
// The following are access methods for the individual parameters.
@@ -167,7 +180,6 @@
Slog.v(TAG, "getFullBackupRequireCharging(...) returns " + mFullBackupRequireCharging);
}
return mFullBackupRequireCharging;
-
}
public synchronized int getFullBackupRequiredNetworkType() {
@@ -177,4 +189,15 @@
}
return mFullBackupRequiredNetworkType;
}
+
+ /**
+ * Returns an array of package names that should be notified whenever a backup finishes.
+ */
+ public synchronized String[] getBackupFinishedNotificationReceivers() {
+ if (RefactoredBackupManagerService.DEBUG_SCHEDULING) {
+ Slog.v(TAG, "getBackupFinishedNotificationReceivers(...) returns "
+ + TextUtils.join(", ", mBackupFinishedNotificationReceivers));
+ }
+ return mBackupFinishedNotificationReceivers;
+ }
}
diff --git a/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java b/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java
index 94b06b6..51c44e1 100644
--- a/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java
@@ -203,6 +203,8 @@
public static final String RUN_BACKUP_ACTION = "android.app.backup.intent.RUN";
public static final String RUN_INITIALIZE_ACTION = "android.app.backup.intent.INIT";
+ public static final String BACKUP_FINISHED_ACTION = "android.intent.action.BACKUP_FINISHED";
+ public static final String BACKUP_FINISHED_PACKAGE_EXTRA = "packageName";
// Timeout interval for deciding that a bind or clear-data has taken too long
private static final long TIMEOUT_INTERVAL = 10 * 1000;
@@ -1084,34 +1086,34 @@
}
/**
- * Maintain persistent state around whether need to do an initialize operation.
- * Must be called with the queue lock held.
+ * Maintain persistent state around whether need to do an initialize operation. This will lock
+ * on {@link #getQueueLock()}.
*/
- @GuardedBy("mQueueLock")
- public void recordInitPendingLocked(
+ public void recordInitPending(
boolean isPending, String transportName, String transportDirName) {
- if (MORE_DEBUG) {
- Slog.i(TAG, "recordInitPendingLocked: " + isPending
- + " on transport " + transportName);
- }
-
- File stateDir = new File(mBaseStateDir, transportDirName);
- File initPendingFile = new File(stateDir, INIT_SENTINEL_FILE_NAME);
-
- if (isPending) {
- // We need an init before we can proceed with sending backup data.
- // Record that with an entry in our set of pending inits, as well as
- // journaling it via creation of a sentinel file.
- mPendingInits.add(transportName);
- try {
- (new FileOutputStream(initPendingFile)).close();
- } catch (IOException ioe) {
- // Something is badly wrong with our permissions; just try to move on
+ synchronized (mQueueLock) {
+ if (MORE_DEBUG) {
+ Slog.i(TAG, "recordInitPending(" + isPending + ") on transport " + transportName);
}
- } else {
- // No more initialization needed; wipe the journal and reset our state.
- initPendingFile.delete();
- mPendingInits.remove(transportName);
+
+ File stateDir = new File(mBaseStateDir, transportDirName);
+ File initPendingFile = new File(stateDir, INIT_SENTINEL_FILE_NAME);
+
+ if (isPending) {
+ // We need an init before we can proceed with sending backup data.
+ // Record that with an entry in our set of pending inits, as well as
+ // journaling it via creation of a sentinel file.
+ mPendingInits.add(transportName);
+ try {
+ (new FileOutputStream(initPendingFile)).close();
+ } catch (IOException ioe) {
+ // Something is badly wrong with our permissions; just try to move on
+ }
+ } else {
+ // No more initialization needed; wipe the journal and reset our state.
+ initPendingFile.delete();
+ mPendingInits.remove(transportName);
+ }
}
}
@@ -1418,6 +1420,16 @@
public void logBackupComplete(String packageName) {
if (packageName.equals(PACKAGE_MANAGER_SENTINEL)) return;
+ for (String receiver : mConstants.getBackupFinishedNotificationReceivers()) {
+ final Intent notification = new Intent();
+ notification.setAction(BACKUP_FINISHED_ACTION);
+ notification.setPackage(receiver);
+ notification.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES |
+ Intent.FLAG_RECEIVER_FOREGROUND);
+ notification.putExtra(BACKUP_FINISHED_PACKAGE_EXTRA, packageName);
+ mContext.sendBroadcastAsUser(notification, UserHandle.OWNER);
+ }
+
mProcessedPackagesJournal.addPackage(packageName);
}
@@ -2311,7 +2323,9 @@
final long oldId = Binder.clearCallingIdentity();
try {
mWakelock.acquire();
- mBackupHandler.post(new PerformInitializeTask(this, transportNames, observer));
+ OnTaskFinishedListener listener = caller -> mWakelock.release();
+ mBackupHandler.post(
+ new PerformInitializeTask(this, transportNames, observer, listener));
} finally {
Binder.restoreCallingIdentity(oldId);
}
@@ -2802,7 +2816,7 @@
// build the set of transports for which we are posting an init
for (int i = 0; i < transportNames.size(); i++) {
- recordInitPendingLocked(
+ recordInitPending(
true,
transportNames.get(i),
transportDirNames.get(i));
diff --git a/services/backup/java/com/android/server/backup/internal/PerformInitializeTask.java b/services/backup/java/com/android/server/backup/internal/PerformInitializeTask.java
index b21b072..c624698 100644
--- a/services/backup/java/com/android/server/backup/internal/PerformInitializeTask.java
+++ b/services/backup/java/com/android/server/backup/internal/PerformInitializeTask.java
@@ -18,6 +18,7 @@
import static com.android.server.backup.RefactoredBackupManagerService.TAG;
+import android.annotation.Nullable;
import android.app.AlarmManager;
import android.app.backup.BackupTransport;
import android.app.backup.IBackupObserver;
@@ -26,23 +27,63 @@
import android.util.EventLog;
import android.util.Slog;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.backup.IBackupTransport;
import com.android.server.EventLogTags;
import com.android.server.backup.RefactoredBackupManagerService;
+import com.android.server.backup.TransportManager;
+import com.android.server.backup.transport.TransportClient;
import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+/**
+ * Attempts to call {@link BackupTransport#initializeDevice()} followed by
+ * {@link BackupTransport#finishBackup()} for the transport names passed in with the intent of
+ * wiping backup data from the transport.
+ *
+ * If the transport returns error, it will record the operation as pending and schedule it to run in
+ * a future time according to {@link BackupTransport#requestBackupTime()}. The result status
+ * reported to observers will be the last unsuccessful status reported by the transports. If every
+ * operation was successful then it's {@link BackupTransport#TRANSPORT_OK}.
+ */
public class PerformInitializeTask implements Runnable {
+ private final RefactoredBackupManagerService mBackupManagerService;
+ private final TransportManager mTransportManager;
+ private final String[] mQueue;
+ private final File mBaseStateDir;
+ private final OnTaskFinishedListener mListener;
+ @Nullable private IBackupObserver mObserver;
- private RefactoredBackupManagerService backupManagerService;
- String[] mQueue;
- IBackupObserver mObserver;
+ public PerformInitializeTask(
+ RefactoredBackupManagerService backupManagerService,
+ String[] transportNames,
+ @Nullable IBackupObserver observer,
+ OnTaskFinishedListener listener) {
+ this(
+ backupManagerService,
+ backupManagerService.getTransportManager(),
+ transportNames,
+ observer,
+ listener,
+ backupManagerService.getBaseStateDir());
+ }
- public PerformInitializeTask(RefactoredBackupManagerService backupManagerService,
- String[] transportNames, IBackupObserver observer) {
- this.backupManagerService = backupManagerService;
+ @VisibleForTesting
+ PerformInitializeTask(
+ RefactoredBackupManagerService backupManagerService,
+ TransportManager transportManager,
+ String[] transportNames,
+ @Nullable IBackupObserver observer,
+ OnTaskFinishedListener listener,
+ File baseStateDir) {
+ mBackupManagerService = backupManagerService;
+ mTransportManager = transportManager;
mQueue = transportNames;
mObserver = observer;
+ mListener = listener;
+ mBaseStateDir = baseStateDir;
}
private void notifyResult(String target, int status) {
@@ -67,21 +108,25 @@
public void run() {
// mWakelock is *acquired* when execution begins here
+ String callerLogString = "PerformInitializeTask.run()";
+ List<TransportClient> transportClientsToDisposeOf = new ArrayList<>(mQueue.length);
int result = BackupTransport.TRANSPORT_OK;
try {
for (String transportName : mQueue) {
- IBackupTransport transport =
- backupManagerService.getTransportManager().getTransportBinder(
- transportName);
- if (transport == null) {
+ TransportClient transportClient =
+ mTransportManager.getTransportClient(transportName, callerLogString);
+ if (transportClient == null) {
Slog.e(TAG, "Requested init for " + transportName + " but not found");
continue;
}
+ transportClientsToDisposeOf.add(transportClient);
Slog.i(TAG, "Initializing (wiping) backup transport storage: " + transportName);
- String transportDirName = transport.transportDirName();
+ String transportDirName = transportClient.getTransportDirName();
EventLog.writeEvent(EventLogTags.BACKUP_START, transportDirName);
long startRealtime = SystemClock.elapsedRealtime();
+
+ IBackupTransport transport = transportClient.connectOrThrow(callerLogString);
int status = transport.initializeDevice();
if (status == BackupTransport.TRANSPORT_OK) {
@@ -93,42 +138,38 @@
Slog.i(TAG, "Device init successful");
int millis = (int) (SystemClock.elapsedRealtime() - startRealtime);
EventLog.writeEvent(EventLogTags.BACKUP_INITIALIZE);
- backupManagerService
- .resetBackupState(new File(backupManagerService.getBaseStateDir(),
- transportDirName));
+ File stateFileDir = new File(mBaseStateDir, transportDirName);
+ mBackupManagerService.resetBackupState(stateFileDir);
EventLog.writeEvent(EventLogTags.BACKUP_SUCCESS, 0, millis);
- synchronized (backupManagerService.getQueueLock()) {
- backupManagerService.recordInitPendingLocked(
- false, transportName, transportDirName);
- }
+ mBackupManagerService.recordInitPending(false, transportName, transportDirName);
notifyResult(transportName, BackupTransport.TRANSPORT_OK);
} else {
// If this didn't work, requeue this one and try again
// after a suitable interval
Slog.e(TAG, "Transport error in initializeDevice()");
EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_FAILURE, "(initialize)");
- synchronized (backupManagerService.getQueueLock()) {
- backupManagerService.recordInitPendingLocked(
- true, transportName, transportDirName);
- }
+ mBackupManagerService.recordInitPending(true, transportName, transportDirName);
notifyResult(transportName, status);
result = status;
// do this via another alarm to make sure of the wakelock states
long delay = transport.requestBackupTime();
Slog.w(TAG, "Init failed on " + transportName + " resched in " + delay);
- backupManagerService.getAlarmManager().set(AlarmManager.RTC_WAKEUP,
+ mBackupManagerService.getAlarmManager().set(
+ AlarmManager.RTC_WAKEUP,
System.currentTimeMillis() + delay,
- backupManagerService.getRunInitIntent());
+ mBackupManagerService.getRunInitIntent());
}
}
} catch (Exception e) {
Slog.e(TAG, "Unexpected error performing init", e);
result = BackupTransport.TRANSPORT_ERROR;
} finally {
- // Done; release the wakelock
+ for (TransportClient transportClient : transportClientsToDisposeOf) {
+ mTransportManager.disposeOfTransportClient(transportClient, callerLogString);
+ }
notifyFinished(result);
- backupManagerService.getWakelock().release();
+ mListener.onFinished(callerLogString);
}
}
}
diff --git a/services/backup/java/com/android/server/backup/internal/RunInitializeReceiver.java b/services/backup/java/com/android/server/backup/internal/RunInitializeReceiver.java
index 1df0bf0..6c160a3 100644
--- a/services/backup/java/com/android/server/backup/internal/RunInitializeReceiver.java
+++ b/services/backup/java/com/android/server/backup/internal/RunInitializeReceiver.java
@@ -23,37 +23,41 @@
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
+import android.os.PowerManager;
import android.util.ArraySet;
import android.util.Slog;
import com.android.server.backup.RefactoredBackupManagerService;
public class RunInitializeReceiver extends BroadcastReceiver {
-
- private RefactoredBackupManagerService backupManagerService;
+ private final RefactoredBackupManagerService mBackupManagerService;
public RunInitializeReceiver(RefactoredBackupManagerService backupManagerService) {
- this.backupManagerService = backupManagerService;
+ mBackupManagerService = backupManagerService;
}
public void onReceive(Context context, Intent intent) {
if (RUN_INITIALIZE_ACTION.equals(intent.getAction())) {
- synchronized (backupManagerService.getQueueLock()) {
- final ArraySet<String> pendingInits = backupManagerService.getPendingInits();
+ synchronized (mBackupManagerService.getQueueLock()) {
+ final ArraySet<String> pendingInits = mBackupManagerService.getPendingInits();
if (DEBUG) {
Slog.v(TAG, "Running a device init; " + pendingInits.size() + " pending");
}
if (pendingInits.size() > 0) {
- final String[] transports = pendingInits.toArray(new String[pendingInits.size()]);
- PerformInitializeTask initTask = new PerformInitializeTask(backupManagerService,
- transports, null);
+ final String[] transports =
+ pendingInits.toArray(new String[pendingInits.size()]);
- // Acquire the wakelock and pass it to the init thread. it will
- // be released once init concludes.
- backupManagerService.clearPendingInits();
- backupManagerService.getWakelock().acquire();
- backupManagerService.getBackupHandler().post(initTask);
+ mBackupManagerService.clearPendingInits();
+
+ PowerManager.WakeLock wakelock = mBackupManagerService.getWakelock();
+ wakelock.acquire();
+ OnTaskFinishedListener listener = caller -> wakelock.release();
+
+ Runnable task =
+ new PerformInitializeTask(
+ mBackupManagerService, transports, null, listener);
+ mBackupManagerService.getBackupHandler().post(task);
}
}
}
diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java
index 06cf982..472723d 100644
--- a/services/core/java/com/android/server/AlarmManagerService.java
+++ b/services/core/java/com/android/server/AlarmManagerService.java
@@ -3035,15 +3035,8 @@
Slog.v(TAG, "sending alarm " + alarm);
}
if (RECORD_ALARMS_IN_HISTORY) {
- if (alarm.workSource != null && alarm.workSource.size() > 0) {
- for (int wi=0; wi<alarm.workSource.size(); wi++) {
- ActivityManager.noteAlarmStart(
- alarm.operation, alarm.workSource.get(wi), alarm.statsTag);
- }
- } else {
- ActivityManager.noteAlarmStart(
- alarm.operation, alarm.uid, alarm.statsTag);
- }
+ ActivityManager.noteAlarmStart(alarm.operation, alarm.workSource, alarm.uid,
+ alarm.statsTag);
}
mDeliveryTracker.deliverLocked(alarm, nowELAPSED, allowWhileIdle);
} catch (RuntimeException e) {
@@ -3553,15 +3546,8 @@
fs.aggregateTime += nowELAPSED - fs.startTime;
}
if (RECORD_ALARMS_IN_HISTORY) {
- if (inflight.mWorkSource != null && inflight.mWorkSource.size() > 0) {
- for (int wi=0; wi<inflight.mWorkSource.size(); wi++) {
- ActivityManager.noteAlarmFinish(
- inflight.mPendingIntent, inflight.mWorkSource.get(wi), inflight.mTag);
- }
- } else {
- ActivityManager.noteAlarmFinish(
- inflight.mPendingIntent, inflight.mUid, inflight.mTag);
- }
+ ActivityManager.noteAlarmFinish(inflight.mPendingIntent, inflight.mWorkSource,
+ inflight.mUid, inflight.mTag);
}
}
@@ -3771,18 +3757,9 @@
|| alarm.type == RTC_WAKEUP) {
bs.numWakeup++;
fs.numWakeup++;
- if (alarm.workSource != null && alarm.workSource.size() > 0) {
- for (int wi=0; wi<alarm.workSource.size(); wi++) {
- final String wsName = alarm.workSource.getName(wi);
- ActivityManager.noteWakeupAlarm(
- alarm.operation, alarm.workSource.get(wi),
- (wsName != null) ? wsName : alarm.packageName,
- alarm.statsTag);
- }
- } else {
- ActivityManager.noteWakeupAlarm(
- alarm.operation, alarm.uid, alarm.packageName, alarm.statsTag);
- }
+ ActivityManager.noteWakeupAlarm(
+ alarm.operation, alarm.workSource, alarm.uid, alarm.packageName,
+ alarm.statsTag);
}
}
}
diff --git a/services/core/java/com/android/server/EntropyMixer.java b/services/core/java/com/android/server/EntropyMixer.java
index 9877717..5e6e9d34 100644
--- a/services/core/java/com/android/server/EntropyMixer.java
+++ b/services/core/java/com/android/server/EntropyMixer.java
@@ -196,11 +196,14 @@
* Mixes in the output from HW RNG (if present) into the Linux RNG.
*/
private void addHwRandomEntropy() {
+ if (!new File(hwRandomDevice).exists()) {
+ // HW RNG not present/exposed -- ignore
+ return;
+ }
+
try {
RandomBlock.fromFile(hwRandomDevice).toFile(randomDevice, false);
Slog.i(TAG, "Added HW RNG output to entropy pool");
- } catch (FileNotFoundException ignored) {
- // HW RNG not present/exposed -- ignore
} catch (IOException e) {
Slog.w(TAG, "Failed to add HW RNG output to entropy pool", e);
}
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index 31aea63..46eea78 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -5595,25 +5595,26 @@
long ident = Binder.clearCallingIdentity();
try {
packages = mPackageManager.getPackagesForUid(callingUid);
+ if (packages != null) {
+ for (String name : packages) {
+ try {
+ PackageInfo packageInfo =
+ mPackageManager.getPackageInfo(name, 0 /* flags */);
+ if (packageInfo != null
+ && (packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM)
+ != 0) {
+ return true;
+ }
+ } catch (NameNotFoundException e) {
+ Log.w(TAG, String.format("Could not find package [%s]", name), e);
+ }
+ }
+ } else {
+ Log.w(TAG, "No known packages with uid " + callingUid);
+ }
} finally {
Binder.restoreCallingIdentity(ident);
}
- if (packages != null) {
- for (String name : packages) {
- try {
- PackageInfo packageInfo = mPackageManager.getPackageInfo(name, 0 /* flags */);
- if (packageInfo != null
- && (packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM)
- != 0) {
- return true;
- }
- } catch (NameNotFoundException e) {
- Log.w(TAG, String.format("Could not find package [%s]", name), e);
- }
- }
- } else {
- Log.w(TAG, "No known packages with uid " + callingUid);
- }
return false;
}
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 088ddea..2f7d4c1 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -348,7 +348,7 @@
ServiceLookupResult res =
retrieveServiceLocked(service, resolvedType, callingPackage,
- callingPid, callingUid, userId, true, callerFg, false);
+ callingPid, callingUid, userId, true, callerFg, false, false);
if (res == null) {
return null;
}
@@ -597,7 +597,7 @@
// If this service is active, make sure it is stopped.
ServiceLookupResult r = retrieveServiceLocked(service, resolvedType, null,
- Binder.getCallingPid(), Binder.getCallingUid(), userId, false, false, false);
+ Binder.getCallingPid(), Binder.getCallingUid(), userId, false, false, false, false);
if (r != null) {
if (r.record != null) {
final long origId = Binder.clearCallingIdentity();
@@ -658,7 +658,7 @@
IBinder peekServiceLocked(Intent service, String resolvedType, String callingPackage) {
ServiceLookupResult r = retrieveServiceLocked(service, resolvedType, callingPackage,
Binder.getCallingPid(), Binder.getCallingUid(),
- UserHandle.getCallingUserId(), false, false, false);
+ UserHandle.getCallingUserId(), false, false, false, false);
IBinder ret = null;
if (r != null) {
@@ -1282,12 +1282,19 @@
+ ") set BIND_ALLOW_WHITELIST_MANAGEMENT when binding service " + service);
}
+ if ((flags & Context.BIND_ALLOW_INSTANT) != 0 && !isCallerSystem) {
+ throw new SecurityException(
+ "Non-system caller " + caller + " (pid=" + Binder.getCallingPid()
+ + ") set BIND_ALLOW_INSTANT when binding service " + service);
+ }
+
final boolean callerFg = callerApp.setSchedGroup != ProcessList.SCHED_GROUP_BACKGROUND;
final boolean isBindExternal = (flags & Context.BIND_EXTERNAL_SERVICE) != 0;
+ final boolean allowInstant = (flags & Context.BIND_ALLOW_INSTANT) != 0;
ServiceLookupResult res =
retrieveServiceLocked(service, resolvedType, callingPackage, Binder.getCallingPid(),
- Binder.getCallingUid(), userId, true, callerFg, isBindExternal);
+ Binder.getCallingUid(), userId, true, callerFg, isBindExternal, allowInstant);
if (res == null) {
return 0;
}
@@ -1657,7 +1664,8 @@
private ServiceLookupResult retrieveServiceLocked(Intent service,
String resolvedType, String callingPackage, int callingPid, int callingUid, int userId,
- boolean createIfNeeded, boolean callingFromFg, boolean isBindExternal) {
+ boolean createIfNeeded, boolean callingFromFg, boolean isBindExternal,
+ boolean allowInstant) {
ServiceRecord r = null;
if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "retrieveServiceLocked: " + service
+ " type=" + resolvedType + " callingUid=" + callingUid);
@@ -1685,11 +1693,14 @@
}
if (r == null) {
try {
+ int flags = ActivityManagerService.STOCK_PM_FLAGS
+ | PackageManager.MATCH_DEBUG_TRIAGED_MISSING;
+ if (allowInstant) {
+ flags |= PackageManager.MATCH_INSTANT;
+ }
// TODO: come back and remove this assumption to triage all services
ResolveInfo rInfo = mAm.getPackageManagerInternalLocked().resolveService(service,
- resolvedType, ActivityManagerService.STOCK_PM_FLAGS
- | PackageManager.MATCH_DEBUG_TRIAGED_MISSING,
- userId, callingUid);
+ resolvedType, flags, userId, callingUid);
ServiceInfo sInfo =
rInfo != null ? rInfo.serviceInfo : null;
if (sInfo == null) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 7dfde56..5936ce1 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -10364,26 +10364,6 @@
}
@Override
- public void cancelTaskThumbnailTransition(int taskId) {
- enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS,
- "cancelTaskThumbnailTransition()");
- final long ident = Binder.clearCallingIdentity();
- try {
- synchronized (this) {
- final TaskRecord task = mStackSupervisor.anyTaskForIdLocked(taskId,
- MATCH_TASK_IN_STACKS_ONLY);
- if (task == null) {
- Slog.w(TAG, "cancelTaskThumbnailTransition: taskId=" + taskId + " not found");
- return;
- }
- task.cancelThumbnailTransition();
- }
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
-
- @Override
public TaskSnapshot getTaskSnapshot(int taskId, boolean reducedResolution) {
enforceCallerIsRecentsOrHasPermission(READ_FRAME_BUFFER, "getTaskSnapshot()");
final long ident = Binder.clearCallingIdentity();
@@ -13878,68 +13858,100 @@
Context.WINDOW_SERVICE)).addView(v, lp);
}
- public void noteWakeupAlarm(IIntentSender sender, int sourceUid, String sourcePkg, String tag) {
- if (sender != null && !(sender instanceof PendingIntentRecord)) {
- return;
+ @Override
+ public void noteWakeupAlarm(IIntentSender sender, WorkSource workSource, int sourceUid,
+ String sourcePkg, String tag) {
+ if (workSource != null && workSource.isEmpty()) {
+ workSource = null;
}
- final PendingIntentRecord rec = (PendingIntentRecord)sender;
- final BatteryStatsImpl stats = mBatteryStatsService.getActiveStatistics();
- synchronized (stats) {
- if (mBatteryStatsService.isOnBattery()) {
- mBatteryStatsService.enforceCallingPermission();
- int MY_UID = Binder.getCallingUid();
- final int uid;
- if (sender == null) {
- uid = sourceUid;
- } else {
- uid = rec.uid == MY_UID ? SYSTEM_UID : rec.uid;
+
+ if (sourceUid <= 0 && workSource == null) {
+ // Try and derive a UID to attribute things to based on the caller.
+ if (sender != null) {
+ if (!(sender instanceof PendingIntentRecord)) {
+ return;
}
- BatteryStatsImpl.Uid.Pkg pkg =
- stats.getPackageStatsLocked(sourceUid >= 0 ? sourceUid : uid,
- sourcePkg != null ? sourcePkg : rec.key.packageName);
- pkg.noteWakeupAlarmLocked(tag);
- StatsLog.write(StatsLog.WAKEUP_ALARM_OCCURRED, sourceUid >= 0 ? sourceUid : uid,
- tag);
+
+ final PendingIntentRecord rec = (PendingIntentRecord) sender;
+ final int callerUid = Binder.getCallingUid();
+ sourceUid = rec.uid == callerUid ? SYSTEM_UID : rec.uid;
+ } else {
+ // TODO(narayan): Should we throw an exception in this case ? It means that we
+ // haven't been able to derive a UID to attribute things to.
+ return;
}
}
+
+ if (DEBUG_POWER) {
+ Slog.w(TAG, "noteWakupAlarm[ sourcePkg=" + sourcePkg + ", sourceUid=" + sourceUid
+ + ", workSource=" + workSource + ", tag=" + tag + "]");
+ }
+
+ mBatteryStatsService.noteWakupAlarm(sourcePkg, sourceUid, workSource, tag);
}
- public void noteAlarmStart(IIntentSender sender, int sourceUid, String tag) {
- if (sender != null && !(sender instanceof PendingIntentRecord)) {
- return;
+ @Override
+ public void noteAlarmStart(IIntentSender sender, WorkSource workSource, int sourceUid,
+ String tag) {
+ if (workSource != null && workSource.isEmpty()) {
+ workSource = null;
}
- final PendingIntentRecord rec = (PendingIntentRecord)sender;
- final BatteryStatsImpl stats = mBatteryStatsService.getActiveStatistics();
- synchronized (stats) {
- mBatteryStatsService.enforceCallingPermission();
- int MY_UID = Binder.getCallingUid();
- final int uid;
- if (sender == null) {
- uid = sourceUid;
+
+ if (sourceUid <= 0 && workSource == null) {
+ // Try and derive a UID to attribute things to based on the caller.
+ if (sender != null) {
+ if (!(sender instanceof PendingIntentRecord)) {
+ return;
+ }
+
+ final PendingIntentRecord rec = (PendingIntentRecord) sender;
+ final int callerUid = Binder.getCallingUid();
+ sourceUid = rec.uid == callerUid ? SYSTEM_UID : rec.uid;
} else {
- uid = rec.uid == MY_UID ? SYSTEM_UID : rec.uid;
+ // TODO(narayan): Should we throw an exception in this case ? It means that we
+ // haven't been able to derive a UID to attribute things to.
+ return;
}
- mBatteryStatsService.noteAlarmStart(tag, sourceUid >= 0 ? sourceUid : uid);
}
+
+ if (DEBUG_POWER) {
+ Slog.w(TAG, "noteAlarmStart[sourceUid=" + sourceUid + ", workSource=" + workSource +
+ ", tag=" + tag + "]");
+ }
+
+ mBatteryStatsService.noteAlarmStart(tag, workSource, sourceUid);
}
- public void noteAlarmFinish(IIntentSender sender, int sourceUid, String tag) {
- if (sender != null && !(sender instanceof PendingIntentRecord)) {
- return;
+ @Override
+ public void noteAlarmFinish(IIntentSender sender, WorkSource workSource, int sourceUid,
+ String tag) {
+ if (workSource != null && workSource.isEmpty()) {
+ workSource = null;
}
- final PendingIntentRecord rec = (PendingIntentRecord)sender;
- final BatteryStatsImpl stats = mBatteryStatsService.getActiveStatistics();
- synchronized (stats) {
- mBatteryStatsService.enforceCallingPermission();
- int MY_UID = Binder.getCallingUid();
- final int uid;
- if (sender == null) {
- uid = sourceUid;
+
+ if (sourceUid <= 0 && workSource == null) {
+ // Try and derive a UID to attribute things to based on the caller.
+ if (sender != null) {
+ if (!(sender instanceof PendingIntentRecord)) {
+ return;
+ }
+
+ final PendingIntentRecord rec = (PendingIntentRecord) sender;
+ final int callerUid = Binder.getCallingUid();
+ sourceUid = rec.uid == callerUid ? SYSTEM_UID : rec.uid;
} else {
- uid = rec.uid == MY_UID ? SYSTEM_UID : rec.uid;
+ // TODO(narayan): Should we throw an exception in this case ? It means that we
+ // haven't been able to derive a UID to attribute things to.
+ return;
}
- mBatteryStatsService.noteAlarmFinish(tag, sourceUid >= 0 ? sourceUid : uid);
}
+
+ if (DEBUG_POWER) {
+ Slog.w(TAG, "noteAlarmFinish[sourceUid=" + sourceUid + ", workSource=" + workSource +
+ ", tag=" + tag + "]");
+ }
+
+ mBatteryStatsService.noteAlarmFinish(tag, workSource, sourceUid);
}
public boolean killPids(int[] pids, String pReason, boolean secure) {
@@ -19900,16 +19912,14 @@
userId = mUserController.handleIncomingUser(callingPid, callingUid, userId, true,
ALLOW_NON_FULL, "broadcast", callerPackage);
- // Make sure that the user who is receiving this broadcast is running.
- // If not, we will just skip it. Make an exception for shutdown broadcasts
- // and upgrade steps.
-
- if (userId != UserHandle.USER_ALL && !mUserController.isUserRunning(userId, 0)) {
+ // Make sure that the user who is receiving this broadcast or its parent is running.
+ // If not, we will just skip it. Make an exception for shutdown broadcasts, upgrade steps.
+ if (userId != UserHandle.USER_ALL && !mUserController.isUserOrItsParentRunning(userId)) {
if ((callingUid != SYSTEM_UID
|| (intent.getFlags() & Intent.FLAG_RECEIVER_BOOT_UPGRADE) == 0)
&& !Intent.ACTION_SHUTDOWN.equals(intent.getAction())) {
Slog.w(TAG, "Skipping broadcast of " + intent
- + ": user " + userId + " is stopped");
+ + ": user " + userId + " and its parent (if any) are stopped");
return ActivityManager.BROADCAST_FAILED_USER_STOPPED;
}
}
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index b920b57..430320a 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -38,6 +38,7 @@
import android.os.UserHandle;
import android.os.UserManagerInternal;
import android.os.WorkSource;
+import android.os.WorkSource.WorkChain;
import android.os.connectivity.CellularBatteryStats;
import android.os.health.HealthStatsParceler;
import android.os.health.HealthStatsWriter;
@@ -66,6 +67,7 @@
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CodingErrorAction;
import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ExecutionException;
@@ -446,17 +448,24 @@
}
}
- public void noteAlarmStart(String name, int uid) {
+ public void noteWakupAlarm(String name, int uid, WorkSource workSource, String tag) {
enforceCallingPermission();
synchronized (mStats) {
- mStats.noteAlarmStartLocked(name, uid);
+ mStats.noteWakupAlarmLocked(name, uid, workSource, tag);
}
}
- public void noteAlarmFinish(String name, int uid) {
+ public void noteAlarmStart(String name, WorkSource workSource, int uid) {
enforceCallingPermission();
synchronized (mStats) {
- mStats.noteAlarmFinishLocked(name, uid);
+ mStats.noteAlarmStartLocked(name, workSource, uid);
+ }
+ }
+
+ public void noteAlarmFinish(String name, WorkSource workSource, int uid) {
+ enforceCallingPermission();
+ synchronized (mStats) {
+ mStats.noteAlarmFinishLocked(name, workSource, uid);
}
}
@@ -464,15 +473,16 @@
boolean unimportantForLogging) {
enforceCallingPermission();
synchronized (mStats) {
- mStats.noteStartWakeLocked(uid, pid, name, historyName, type, unimportantForLogging,
- SystemClock.elapsedRealtime(), SystemClock.uptimeMillis());
+ mStats.noteStartWakeLocked(uid, pid, null, name, historyName, type,
+ unimportantForLogging, SystemClock.elapsedRealtime(),
+ SystemClock.uptimeMillis());
}
}
public void noteStopWakelock(int uid, int pid, String name, String historyName, int type) {
enforceCallingPermission();
synchronized (mStats) {
- mStats.noteStopWakeLocked(uid, pid, name, historyName, type,
+ mStats.noteStopWakeLocked(uid, pid, null, name, historyName, type,
SystemClock.elapsedRealtime(), SystemClock.uptimeMillis());
}
}
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index 91b3315..4aef95d 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -772,10 +772,6 @@
mWindowContainerController.cancelWindowTransition();
}
- void cancelThumbnailTransition() {
- mWindowContainerController.cancelThumbnailTransition();
- }
-
/**
* DO NOT HOLD THE ACTIVITY MANAGER LOCK WHEN CALLING THIS METHOD!
*/
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 14260c5..34621e0 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -1737,6 +1737,19 @@
}
}
+ boolean isUserOrItsParentRunning(int userId) {
+ synchronized (mLock) {
+ if (isUserRunning(userId, 0)) {
+ return true;
+ }
+ final int parentUserId = mUserProfileGroupIds.get(userId, UserInfo.NO_PROFILE_GROUP_ID);
+ if (parentUserId == UserInfo.NO_PROFILE_GROUP_ID) {
+ return false;
+ }
+ return isUserRunning(parentUserId, 0);
+ }
+ }
+
boolean isCurrentProfile(int userId) {
synchronized (mLock) {
return ArrayUtils.contains(mCurrentProfileIds, userId);
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 6e7b43e..799f2a9 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -2251,12 +2251,15 @@
if (DEBUG_VOL) {
Log.d(TAG, String.format("Mic mute %s, user=%d", on, userId));
}
- // If mute is for current user actually mute, else just persist the setting
- // which will be loaded on user switch.
+ // only mute for the current user
if (getCurrentUserId() == userId) {
+ final boolean currentMute = AudioSystem.isMicrophoneMuted();
AudioSystem.muteMicrophone(on);
+ if (on != currentMute) {
+ mContext.sendBroadcast(new Intent(AudioManager.ACTION_MICROPHONE_MUTE_CHANGED)
+ .setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY));
+ }
}
- // Post a persist microphone msg.
}
@Override
diff --git a/services/core/java/com/android/server/broadcastradio/BroadcastRadioService.java b/services/core/java/com/android/server/broadcastradio/BroadcastRadioService.java
index 3064144..ef51665 100644
--- a/services/core/java/com/android/server/broadcastradio/BroadcastRadioService.java
+++ b/services/core/java/com/android/server/broadcastradio/BroadcastRadioService.java
@@ -25,6 +25,7 @@
import android.hardware.radio.ITunerCallback;
import android.hardware.radio.RadioManager;
import android.os.ParcelableException;
+import android.util.Slog;
import com.android.server.SystemService;
@@ -33,6 +34,8 @@
import java.util.OptionalInt;
public class BroadcastRadioService extends SystemService {
+ private static final String TAG = "BcRadioSrv";
+
private final ServiceImpl mServiceImpl = new ServiceImpl();
private final com.android.server.broadcastradio.hal1.BroadcastRadioService mHal1 =
@@ -84,13 +87,14 @@
@Override
public ITuner openTuner(int moduleId, RadioManager.BandConfig bandConfig,
boolean withAudio, ITunerCallback callback) {
+ Slog.i(TAG, "openTuner(" + moduleId + ", _, " + withAudio + ", _)");
enforcePolicyAccess();
if (callback == null) {
throw new IllegalArgumentException("Callback must not be empty");
}
synchronized (mLock) {
if (mHal2.hasModule(moduleId)) {
- throw new RuntimeException("Not implemented");
+ return mHal2.openSession(moduleId, callback);
} else {
return mHal1.openTuner(moduleId, bandConfig, withAudio, callback);
}
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java b/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java
index 7629477..413a27c 100644
--- a/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java
+++ b/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java
@@ -17,6 +17,8 @@
package com.android.server.broadcastradio.hal2;
import android.annotation.NonNull;
+import android.hardware.radio.ITuner;
+import android.hardware.radio.ITunerCallback;
import android.hardware.radio.RadioManager;
import android.hardware.broadcastradio.V2_0.IBroadcastRadio;
import android.hidl.manager.V1_0.IServiceManager;
@@ -28,6 +30,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.stream.Collectors;
public class BroadcastRadioService {
@@ -76,4 +79,15 @@
public boolean hasModule(int id) {
return mModules.containsKey(id);
}
+
+ public ITuner openSession(int moduleId, @NonNull ITunerCallback callback) {
+ Objects.requireNonNull(callback);
+
+ RadioModule module = mModules.get(moduleId);
+ if (module == null) {
+ throw new IllegalArgumentException("Invalid module ID");
+ }
+
+ return module.openSession(callback);
+ }
}
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/Convert.java b/services/core/java/com/android/server/broadcastradio/hal2/Convert.java
index c3394e9..434e262 100644
--- a/services/core/java/com/android/server/broadcastradio/hal2/Convert.java
+++ b/services/core/java/com/android/server/broadcastradio/hal2/Convert.java
@@ -18,12 +18,17 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.hardware.broadcastradio.V2_0.AmFmBandRange;
+import android.hardware.broadcastradio.V2_0.AmFmRegionConfig;
import android.hardware.broadcastradio.V2_0.Properties;
+import android.hardware.broadcastradio.V2_0.Result;
import android.hardware.broadcastradio.V2_0.VendorKeyValue;
import android.hardware.radio.ProgramSelector;
import android.hardware.radio.RadioManager;
+import android.os.ParcelableException;
import android.util.Slog;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
@@ -35,6 +40,28 @@
class Convert {
private static final String TAG = "BcRadio2Srv.convert";
+ static void throwOnError(String action, int result) {
+ switch (result) {
+ case Result.OK:
+ return;
+ case Result.UNKNOWN_ERROR:
+ throw new ParcelableException(new RuntimeException(action + ": UNKNOWN_ERROR"));
+ case Result.INTERNAL_ERROR:
+ throw new ParcelableException(new RuntimeException(action + ": INTERNAL_ERROR"));
+ case Result.INVALID_ARGUMENTS:
+ throw new IllegalArgumentException(action + ": INVALID_ARGUMENTS");
+ case Result.INVALID_STATE:
+ throw new IllegalStateException(action + ": INVALID_STATE");
+ case Result.NOT_SUPPORTED:
+ throw new UnsupportedOperationException(action + ": NOT_SUPPORTED");
+ case Result.TIMEOUT:
+ throw new ParcelableException(new RuntimeException(action + ": TIMEOUT"));
+ default:
+ throw new ParcelableException(new RuntimeException(
+ action + ": unknown error (" + result + ")"));
+ }
+ }
+
private static @NonNull Map<String, String>
vendorInfoFromHal(@Nullable List<VendorKeyValue> info) {
if (info == null) return Collections.emptyMap();
@@ -94,12 +121,47 @@
return pTypes.stream().mapToInt(Integer::intValue).toArray();
}
- static @NonNull RadioManager.ModuleProperties
- propertiesFromHal(int id, @NonNull String serviceName, Properties prop) {
- Objects.requireNonNull(prop);
+ private static @NonNull RadioManager.BandDescriptor[]
+ amfmConfigToBands(@Nullable AmFmRegionConfig config) {
+ if (config == null) return new RadioManager.BandDescriptor[0];
- // TODO(b/69958423): implement region info
- RadioManager.BandDescriptor[] bands = new RadioManager.BandDescriptor[0];
+ int len = config.ranges.size();
+ List<RadioManager.BandDescriptor> bands = new ArrayList<>(len);
+
+ // Just a dummy value.
+ int region = RadioManager.REGION_ITU_1;
+
+ for (AmFmBandRange range : config.ranges) {
+ FrequencyBand bandType = Utils.getBand(range.lowerBound);
+ if (bandType == FrequencyBand.UNKNOWN) {
+ Slog.e(TAG, "Unknown frequency band at " + range.lowerBound + "kHz");
+ continue;
+ }
+ if (bandType == FrequencyBand.FM) {
+ bands.add(new RadioManager.FmBandDescriptor(region, RadioManager.BAND_FM,
+ range.lowerBound, range.upperBound, range.spacing,
+
+ // TODO(b/69958777): stereo, rds, ta, af, ea
+ true, true, true, true, true
+ ));
+ } else { // AM
+ bands.add(new RadioManager.AmBandDescriptor(region, RadioManager.BAND_AM,
+ range.lowerBound, range.upperBound, range.spacing,
+
+ // TODO(b/69958777): stereo
+ true
+ ));
+ }
+ }
+
+ return bands.toArray(new RadioManager.BandDescriptor[bands.size()]);
+ }
+
+ static @NonNull RadioManager.ModuleProperties
+ propertiesFromHal(int id, @NonNull String serviceName, @NonNull Properties prop,
+ @Nullable AmFmRegionConfig amfmConfig) {
+ Objects.requireNonNull(serviceName);
+ Objects.requireNonNull(prop);
int[] supportedIdentifierTypes = prop.supportedIdentifierTypes.stream().
mapToInt(Integer::intValue).toArray();
@@ -123,7 +185,7 @@
1, // numAudioSources
false, // isCaptureSupported
- bands,
+ amfmConfigToBands(amfmConfig),
true, // isBgScanSupported is deprecated
supportedProgramTypes,
supportedIdentifierTypes,
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/Mutable.java b/services/core/java/com/android/server/broadcastradio/hal2/Mutable.java
new file mode 100644
index 0000000..a9d8054
--- /dev/null
+++ b/services/core/java/com/android/server/broadcastradio/hal2/Mutable.java
@@ -0,0 +1,46 @@
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.broadcastradio.hal2;
+
+/**
+ * A wrapper class for mutable objects to be used in non-mutable contexts
+ * (i.e. final variables catched in lambda closures).
+ *
+ * @param <E> type of boxed value.
+ */
+final class Mutable<E> {
+ /**
+ * A mutable value.
+ */
+ public E value;
+
+ /**
+ * Initialize value with null pointer.
+ */
+ public Mutable() {
+ value = null;
+ }
+
+ /**
+ * Initialize value with specific value.
+ *
+ * @param value initial value.
+ */
+ public Mutable(E value) {
+ this.value = value;
+ }
+}
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java b/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java
index 34c1b0c..8a7ac73 100644
--- a/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java
+++ b/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java
@@ -18,9 +18,15 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.hardware.radio.ITuner;
import android.hardware.radio.RadioManager;
+import android.hardware.broadcastradio.V2_0.AmFmRegionConfig;
import android.hardware.broadcastradio.V2_0.IBroadcastRadio;
+import android.hardware.broadcastradio.V2_0.ITunerSession;
+import android.hardware.broadcastradio.V2_0.Result;
+import android.os.ParcelableException;
import android.os.RemoteException;
+import android.util.MutableInt;
import android.util.Slog;
import java.util.Objects;
@@ -42,8 +48,13 @@
IBroadcastRadio service = IBroadcastRadio.getService();
if (service == null) return null;
+ Mutable<AmFmRegionConfig> amfmConfig = new Mutable<>();
+ service.getAmFmRegionConfig(false, (int result, AmFmRegionConfig config) -> {
+ if (result == Result.OK) amfmConfig.value = config;
+ });
+
RadioManager.ModuleProperties prop =
- Convert.propertiesFromHal(idx, fqName, service.getProperties());
+ Convert.propertiesFromHal(idx, fqName, service.getProperties(), amfmConfig.value);
return new RadioModule(service, prop);
} catch (RemoteException ex) {
@@ -51,4 +62,44 @@
return null;
}
}
+
+ public @NonNull ITuner openSession(@NonNull android.hardware.radio.ITunerCallback userCb) {
+ TunerCallback cb = new TunerCallback(Objects.requireNonNull(userCb));
+ Mutable<ITunerSession> hwSession = new Mutable<>();
+ MutableInt halResult = new MutableInt(Result.UNKNOWN_ERROR);
+
+ try {
+ mService.openSession(cb, (int result, ITunerSession session) -> {
+ hwSession.value = session;
+ halResult.value = result;
+ });
+ } catch (RemoteException ex) {
+ Slog.e(TAG, "failed to open session", ex);
+ throw new ParcelableException(ex);
+ }
+
+ Convert.throwOnError("openSession", halResult.value);
+ Objects.requireNonNull(hwSession.value);
+
+ TunerSession session = new TunerSession(hwSession.value, cb);
+
+ // send out legacy callback about band configuration
+ RadioManager.BandDescriptor[] bands = mProperties.getBands();
+ if (bands != null && bands.length > 0) {
+ RadioManager.BandDescriptor descr = bands[0]; // just pick first
+ Mutable<RadioManager.BandConfig> config = new Mutable<>();
+ if (descr instanceof RadioManager.FmBandDescriptor) {
+ config.value = new RadioManager.FmBandConfig((RadioManager.FmBandDescriptor)descr);
+ } else if (descr instanceof RadioManager.AmBandDescriptor) {
+ config.value = new RadioManager.AmBandConfig((RadioManager.AmBandDescriptor)descr);
+ } else {
+ Slog.w(TAG, "Descriptor is neither AM nor FM");
+ }
+ if (config.value != null) {
+ TunerCallback.dispatch(() -> userCb.onConfigurationChanged(config.value));
+ }
+ }
+
+ return session;
+ }
}
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/TunerCallback.java b/services/core/java/com/android/server/broadcastradio/hal2/TunerCallback.java
new file mode 100644
index 0000000..5ee6a4c
--- /dev/null
+++ b/services/core/java/com/android/server/broadcastradio/hal2/TunerCallback.java
@@ -0,0 +1,66 @@
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.broadcastradio.hal2;
+
+import android.annotation.NonNull;
+import android.hardware.broadcastradio.V2_0.ITunerCallback;
+import android.hardware.broadcastradio.V2_0.ProgramInfo;
+import android.hardware.broadcastradio.V2_0.ProgramListChunk;
+import android.hardware.broadcastradio.V2_0.ProgramSelector;
+import android.hardware.broadcastradio.V2_0.VendorKeyValue;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import java.util.ArrayList;
+import java.util.Objects;
+
+class TunerCallback extends ITunerCallback.Stub {
+ private static final String TAG = "BcRadio2Srv.cb";
+
+ final android.hardware.radio.ITunerCallback mCb;
+
+ interface RunnableThrowingRemoteException {
+ void run() throws RemoteException;
+ }
+
+ TunerCallback(@NonNull android.hardware.radio.ITunerCallback clientCallback) {
+ mCb = Objects.requireNonNull(clientCallback);
+ }
+
+ static void dispatch(RunnableThrowingRemoteException func) {
+ try {
+ func.run();
+ } catch (RemoteException ex) {
+ Slog.e(TAG, "callback call failed", ex);
+ }
+ }
+
+ @Override
+ public void onTuneFailed(int result, ProgramSelector selector) {}
+
+ @Override
+ public void onCurrentProgramInfoChanged(ProgramInfo info) {}
+
+ @Override
+ public void onProgramListUpdated(ProgramListChunk chunk) {}
+
+ @Override
+ public void onAntennaStateChange(boolean connected) {}
+
+ @Override
+ public void onParametersUpdated(ArrayList<VendorKeyValue> parameters) {}
+}
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java b/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java
new file mode 100644
index 0000000..e8faf3d
--- /dev/null
+++ b/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java
@@ -0,0 +1,137 @@
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.broadcastradio.hal2;
+
+import android.annotation.NonNull;
+import android.graphics.Bitmap;
+import android.hardware.broadcastradio.V2_0.ITunerSession;
+import android.hardware.radio.ITuner;
+import android.hardware.radio.ProgramSelector;
+import android.hardware.radio.RadioManager;
+import android.util.Slog;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+class TunerSession extends ITuner.Stub {
+ private static final String TAG = "BcRadio2Srv.session";
+
+ private final Object mLock = new Object();
+
+ private final ITunerSession mHwSession;
+ private final TunerCallback mCallback;
+ private boolean mIsClosed = false;
+
+ TunerSession(@NonNull ITunerSession hwSession, @NonNull TunerCallback callback) {
+ mHwSession = Objects.requireNonNull(hwSession);
+ mCallback = Objects.requireNonNull(callback);
+ }
+
+ @Override
+ public void close() {
+ synchronized (mLock) {
+ if (mIsClosed) return;
+ mIsClosed = true;
+ }
+ }
+
+ @Override
+ public boolean isClosed() {
+ return mIsClosed;
+ }
+
+ private void checkNotClosedLocked() {
+ if (mIsClosed) {
+ throw new IllegalStateException("Tuner is closed, no further operations are allowed");
+ }
+ }
+
+ @Override
+ public void setConfiguration(RadioManager.BandConfig config) {}
+
+ @Override
+ public RadioManager.BandConfig getConfiguration() {
+ return null;
+ }
+
+ @Override
+ public void setMuted(boolean mute) {}
+
+ @Override
+ public boolean isMuted() {
+ return false;
+ }
+
+ @Override
+ public void step(boolean directionDown, boolean skipSubChannel) {}
+
+ @Override
+ public void scan(boolean directionDown, boolean skipSubChannel) {}
+
+ @Override
+ public void tune(ProgramSelector selector) {}
+
+ @Override
+ public void cancel() {}
+
+ @Override
+ public void cancelAnnouncement() {}
+
+ @Override
+ public RadioManager.ProgramInfo getProgramInformation() {
+ return null;
+ }
+
+ @Override
+ public Bitmap getImage(int id) {
+ return null;
+ }
+
+ @Override
+ public boolean startBackgroundScan() {
+ return false;
+ }
+
+ @Override
+ public List<RadioManager.ProgramInfo> getProgramList(Map vendorFilter) {
+ return null;
+ }
+
+ @Override
+ public boolean isAnalogForced() {
+ return false;
+ }
+
+ @Override
+ public void setAnalogForced(boolean isForced) {}
+
+ @Override
+ public Map setParameters(Map parameters) {
+ return null;
+ }
+
+ @Override
+ public Map getParameters(List<String> keys) {
+ return null;
+ }
+
+ @Override
+ public boolean isAntennaConnected() {
+ return true;
+ }
+}
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/Utils.java b/services/core/java/com/android/server/broadcastradio/hal2/Utils.java
new file mode 100644
index 0000000..3520f37
--- /dev/null
+++ b/services/core/java/com/android/server/broadcastradio/hal2/Utils.java
@@ -0,0 +1,40 @@
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.broadcastradio.hal2;
+
+enum FrequencyBand {
+ UNKNOWN,
+ FM,
+ AM_LW,
+ AM_MW,
+ AM_SW,
+};
+
+class Utils {
+ private static final String TAG = "BcRadio2Srv.utils";
+
+ static FrequencyBand getBand(int freq) {
+ // keep in sync with hardware/interfaces/broadcastradio/common/utils2x/Utils.cpp
+ if (freq < 30) return FrequencyBand.UNKNOWN;
+ if (freq < 500) return FrequencyBand.AM_LW;
+ if (freq < 1705) return FrequencyBand.AM_MW;
+ if (freq < 30000) return FrequencyBand.AM_SW;
+ if (freq < 60000) return FrequencyBand.UNKNOWN;
+ if (freq < 110000) return FrequencyBand.FM;
+ return FrequencyBand.UNKNOWN;
+ }
+}
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 7715727..c7a4315 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -305,6 +305,7 @@
} else {
for (Network underlying : underlyingNetworks) {
final NetworkCapabilities underlyingCaps = cm.getNetworkCapabilities(underlying);
+ if (underlyingCaps == null) continue;
for (int underlyingType : underlyingCaps.getTransportTypes()) {
transportTypes = ArrayUtils.appendInt(transportTypes, underlyingType);
}
diff --git a/services/core/java/com/android/server/location/ContextHubServiceUtil.java b/services/core/java/com/android/server/location/ContextHubServiceUtil.java
index c356b63..033437a 100644
--- a/services/core/java/com/android/server/location/ContextHubServiceUtil.java
+++ b/services/core/java/com/android/server/location/ContextHubServiceUtil.java
@@ -221,7 +221,7 @@
case Result.NOT_INIT:
return ContextHubTransaction.RESULT_FAILED_UNINITIALIZED;
case Result.TRANSACTION_PENDING:
- return ContextHubTransaction.RESULT_FAILED_PENDING;
+ return ContextHubTransaction.RESULT_FAILED_BUSY;
case Result.TRANSACTION_FAILED:
case Result.UNKNOWN_FAILURE:
default: /* fall through */
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index eef4d9b..02218ff 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -1581,6 +1581,8 @@
userId, progressCallback);
// The user employs synthetic password based credential.
if (response != null) {
+ mRecoverableKeyStoreManager.lockScreenSecretAvailable(credentialType, credential,
+ userId);
return response;
}
@@ -1705,6 +1707,9 @@
/* TODO(roosa): keep the same password quality */, userId);
if (!hasChallenge) {
notifyActivePasswordMetricsAvailable(credential, userId);
+ // Use credentials to create recoverable keystore snapshot.
+ mRecoverableKeyStoreManager.lockScreenSecretAvailable(
+ storedHash.type, credential, userId);
return VerifyCredentialResponse.OK;
}
// Fall through to get the auth token. Technically this should never happen,
@@ -2021,11 +2026,17 @@
}
@Override
- public void recoverKeys(@NonNull String sessionId, @NonNull byte[] recoveryKeyBlob,
- @NonNull List<KeyEntryRecoveryData> applicationKeys, @UserIdInt int userId)
+ public Map<String, byte[]> recoverKeys(@NonNull String sessionId,
+ @NonNull byte[] recoveryKeyBlob, @NonNull List<KeyEntryRecoveryData> applicationKeys,
+ @UserIdInt int userId)
throws RemoteException {
- mRecoverableKeyStoreManager.recoverKeys(sessionId, recoveryKeyBlob, applicationKeys,
- userId);
+ return mRecoverableKeyStoreManager.recoverKeys(
+ sessionId, recoveryKeyBlob, applicationKeys, userId);
+ }
+
+ @Override
+ public byte[] generateAndStoreKey(@NonNull String alias) throws RemoteException {
+ return mRecoverableKeyStoreManager.generateAndStoreKey(alias);
}
private static final String[] VALID_SETTINGS = new String[] {
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java
index 00a8203..e385833 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java
@@ -16,32 +16,44 @@
package com.android.server.locksettings.recoverablekeystore;
+import static android.security.recoverablekeystore.KeyStoreRecoveryMetadata.TYPE_LOCKSCREEN;
+
import android.annotation.NonNull;
import android.content.Context;
+import android.security.recoverablekeystore.KeyDerivationParameters;
+import android.security.recoverablekeystore.KeyEntryRecoveryData;
+import android.security.recoverablekeystore.KeyStoreRecoveryData;
import android.security.recoverablekeystore.KeyStoreRecoveryMetadata;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.widget.LockPatternUtils;
import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb;
+import com.android.server.locksettings.recoverablekeystore.storage.RecoverySnapshotStorage;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets;
+import java.security.GeneralSecurityException;
import java.security.InvalidKeyException;
import java.security.KeyStoreException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.SecureRandom;
+import java.security.UnrecoverableKeyException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
import javax.crypto.KeyGenerator;
+import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
/**
* Task to sync application keys to a remote vault service.
*
- * TODO: implement fully
+ * @hide
*/
public class KeySyncTask implements Runnable {
private static final String TAG = "KeySyncTask";
@@ -51,26 +63,33 @@
private static final int SALT_LENGTH_BYTES = 16;
private static final int LENGTH_PREFIX_BYTES = Integer.BYTES;
private static final String LOCK_SCREEN_HASH_ALGORITHM = "SHA-256";
+ private static final int TRUSTED_HARDWARE_MAX_ATTEMPTS = 10;
- private final Context mContext;
private final RecoverableKeyStoreDb mRecoverableKeyStoreDb;
private final int mUserId;
private final int mCredentialType;
private final String mCredential;
+ private final PlatformKeyManager.Factory mPlatformKeyManagerFactory;
+ private final RecoverySnapshotStorage mRecoverySnapshotStorage;
+ private final RecoverySnapshotListenersStorage mSnapshotListenersStorage;
public static KeySyncTask newInstance(
Context context,
RecoverableKeyStoreDb recoverableKeyStoreDb,
+ RecoverySnapshotStorage snapshotStorage,
+ RecoverySnapshotListenersStorage recoverySnapshotListenersStorage,
int userId,
int credentialType,
String credential
) throws NoSuchAlgorithmException, KeyStoreException, InsecureUserException {
return new KeySyncTask(
- context.getApplicationContext(),
recoverableKeyStoreDb,
+ snapshotStorage,
+ recoverySnapshotListenersStorage,
userId,
credentialType,
- credential);
+ credential,
+ () -> PlatformKeyManager.getInstance(context, recoverableKeyStoreDb, userId));
}
/**
@@ -80,19 +99,26 @@
* @param userId The uid of the user whose profile has been unlocked.
* @param credentialType The type of credential - i.e., pattern or password.
* @param credential The credential, encoded as a {@link String}.
+ * @param platformKeyManagerFactory Instantiates a {@link PlatformKeyManager} for the user.
+ * This is a factory to enable unit testing, as otherwise it would be impossible to test
+ * without a screen unlock occurring!
*/
@VisibleForTesting
KeySyncTask(
- Context context,
RecoverableKeyStoreDb recoverableKeyStoreDb,
+ RecoverySnapshotStorage snapshotStorage,
+ RecoverySnapshotListenersStorage recoverySnapshotListenersStorage,
int userId,
int credentialType,
- String credential) {
- mContext = context;
+ String credential,
+ PlatformKeyManager.Factory platformKeyManagerFactory) {
+ mSnapshotListenersStorage = recoverySnapshotListenersStorage;
mRecoverableKeyStoreDb = recoverableKeyStoreDb;
mUserId = userId;
mCredentialType = credentialType;
mCredential = credential;
+ mPlatformKeyManagerFactory = platformKeyManagerFactory;
+ mRecoverySnapshotStorage = snapshotStorage;
}
@Override
@@ -105,10 +131,51 @@
}
private void syncKeys() {
+ if (!isSyncPending()) {
+ Log.d(TAG, "Key sync not needed.");
+ return;
+ }
+
+ int recoveryAgentUid = mRecoverableKeyStoreDb.getRecoveryAgentUid(mUserId);
+ if (recoveryAgentUid == -1) {
+ Log.w(TAG, "No recovery agent initialized for user " + mUserId);
+ return;
+ }
+ if (!mSnapshotListenersStorage.hasListener(recoveryAgentUid)) {
+ Log.w(TAG, "No pending intent registered for recovery agent " + recoveryAgentUid);
+ return;
+ }
+
+ PublicKey publicKey = getVaultPublicKey();
+ if (publicKey == null) {
+ Log.w(TAG, "Not initialized for KeySync: no public key set. Cancelling task.");
+ return;
+ }
+
+ Long deviceId = mRecoverableKeyStoreDb.getServerParameters(mUserId, recoveryAgentUid);
+ if (deviceId == null) {
+ Log.w(TAG, "No device ID set for user " + mUserId);
+ return;
+ }
+
byte[] salt = generateSalt();
byte[] localLskfHash = hashCredentials(salt, mCredential);
- // TODO: decrypt local wrapped application keys, ready for sync
+ Map<String, SecretKey> rawKeys;
+ try {
+ rawKeys = getKeysToSync();
+ } catch (GeneralSecurityException e) {
+ Log.e(TAG, "Failed to load recoverable keys for sync", e);
+ return;
+ } catch (InsecureUserException e) {
+ Log.wtf(TAG, "A screen unlock triggered the key sync flow, so user must have "
+ + "lock screen. This should be impossible.", e);
+ return;
+ } catch (BadPlatformKeyException e) {
+ Log.wtf(TAG, "Loaded keys for same generation ID as platform key, so "
+ + "BadPlatformKeyException should be impossible.", e);
+ return;
+ }
SecretKey recoveryKey;
try {
@@ -118,17 +185,28 @@
return;
}
- // TODO: encrypt each application key with recovery key
-
- PublicKey vaultKey = getVaultPublicKey();
-
- // TODO: construct vault params and vault metadata
- byte[] vaultParams = {};
-
- byte[] locallyEncryptedRecoveryKey;
+ Map<String, byte[]> encryptedApplicationKeys;
try {
- locallyEncryptedRecoveryKey = KeySyncUtils.thmEncryptRecoveryKey(
- vaultKey,
+ encryptedApplicationKeys = KeySyncUtils.encryptKeysWithRecoveryKey(
+ recoveryKey, rawKeys);
+ } catch (InvalidKeyException | NoSuchAlgorithmException e) {
+ Log.wtf(TAG,
+ "Should be impossible: could not encrypt application keys with random key",
+ e);
+ return;
+ }
+
+ // TODO: where do we get counter_id from here?
+ byte[] vaultParams = KeySyncUtils.packVaultParams(
+ publicKey,
+ /*counterId=*/ 1,
+ TRUSTED_HARDWARE_MAX_ATTEMPTS,
+ deviceId);
+
+ byte[] encryptedRecoveryKey;
+ try {
+ encryptedRecoveryKey = KeySyncUtils.thmEncryptRecoveryKey(
+ publicKey,
localLskfHash,
vaultParams,
recoveryKey);
@@ -140,12 +218,48 @@
return;
}
- // TODO: send RECOVERABLE_KEYSTORE_SNAPSHOT intent
+ KeyStoreRecoveryMetadata metadata = new KeyStoreRecoveryMetadata(
+ /*userSecretType=*/ TYPE_LOCKSCREEN,
+ /*lockScreenUiFormat=*/ mCredentialType,
+ /*keyDerivationParameters=*/ KeyDerivationParameters.createSHA256Parameters(salt),
+ /*secret=*/ new byte[0]);
+ ArrayList<KeyStoreRecoveryMetadata> metadataList = new ArrayList<>();
+ metadataList.add(metadata);
+
+ // TODO: implement snapshot version
+ mRecoverySnapshotStorage.put(mUserId, new KeyStoreRecoveryData(
+ /*snapshotVersion=*/ 1,
+ /*recoveryMetadata=*/ metadataList,
+ /*applicationKeyBlobs=*/ createApplicationKeyEntries(encryptedApplicationKeys),
+ /*encryptedRecoveryKeyblob=*/ encryptedRecoveryKey));
+ mSnapshotListenersStorage.recoverySnapshotAvailable(recoveryAgentUid);
}
private PublicKey getVaultPublicKey() {
- // TODO: fill this in
- throw new UnsupportedOperationException("TODO: get vault public key.");
+ return mRecoverableKeyStoreDb.getRecoveryServicePublicKey(mUserId);
+ }
+
+ /**
+ * Returns all of the recoverable keys for the user.
+ */
+ private Map<String, SecretKey> getKeysToSync()
+ throws InsecureUserException, KeyStoreException, UnrecoverableKeyException,
+ NoSuchAlgorithmException, NoSuchPaddingException, BadPlatformKeyException {
+ PlatformKeyManager platformKeyManager = mPlatformKeyManagerFactory.newInstance();
+ PlatformDecryptionKey decryptKey = platformKeyManager.getDecryptKey();
+ Map<String, WrappedKey> wrappedKeys = mRecoverableKeyStoreDb.getAllKeys(
+ mUserId, decryptKey.getGenerationId());
+ return WrappedKey.unwrapKeys(decryptKey, wrappedKeys);
+ }
+
+ /**
+ * Returns {@code true} if a sync is pending.
+ */
+ private boolean isSyncPending() {
+ // TODO: implement properly. For now just always syncing if the user has any recoverable
+ // keys. We need to keep track of when the store's state actually changes.
+ return !mRecoverableKeyStoreDb.getAllKeys(
+ mUserId, mRecoverableKeyStoreDb.getPlatformKeyGenerationId(mUserId)).isEmpty();
}
/**
@@ -221,4 +335,16 @@
keyGenerator.init(RECOVERY_KEY_SIZE_BITS);
return keyGenerator.generateKey();
}
+
+ private static List<KeyEntryRecoveryData> createApplicationKeyEntries(
+ Map<String, byte[]> encryptedApplicationKeys) {
+ ArrayList<KeyEntryRecoveryData> keyEntries = new ArrayList<>();
+ for (String alias : encryptedApplicationKeys.keySet()) {
+ keyEntries.add(
+ new KeyEntryRecoveryData(
+ alias.getBytes(StandardCharsets.UTF_8),
+ encryptedApplicationKeys.get(alias)));
+ }
+ return keyEntries;
+ }
}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncUtils.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncUtils.java
index 4597fad..e851d8c 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncUtils.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncUtils.java
@@ -18,6 +18,8 @@
import com.android.internal.annotations.VisibleForTesting;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
@@ -60,6 +62,7 @@
private static final byte[] THM_KF_HASH_PREFIX = "THM_KF_hash".getBytes(StandardCharsets.UTF_8);
private static final int KEY_CLAIMANT_LENGTH_BYTES = 16;
+ private static final int VAULT_PARAMS_LENGTH_BYTES = 85;
/**
* Encrypts the recovery key using both the lock screen hash and the remote storage's public
@@ -281,6 +284,26 @@
}
/**
+ * Packs vault params into a binary format.
+ *
+ * @param thmPublicKey Public key of the trusted hardware module.
+ * @param counterId ID referring to the specific counter in the hardware module.
+ * @param maxAttempts Maximum allowed guesses before trusted hardware wipes key.
+ * @param deviceId ID of the device.
+ * @return The binary vault params, ready for sync.
+ */
+ public static byte[] packVaultParams(
+ PublicKey thmPublicKey, long counterId, int maxAttempts, long deviceId) {
+ return ByteBuffer.allocate(VAULT_PARAMS_LENGTH_BYTES)
+ .order(ByteOrder.LITTLE_ENDIAN)
+ .put(SecureBox.encodePublicKey(thmPublicKey))
+ .putLong(counterId)
+ .putLong(deviceId)
+ .putInt(maxAttempts)
+ .array();
+ }
+
+ /**
* Returns the concatenation of all the given {@code arrays}.
*/
@VisibleForTesting
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/ListenersStorage.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/ListenersStorage.java
deleted file mode 100644
index 0f17294..0000000
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/ListenersStorage.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.locksettings.recoverablekeystore;
-
-import android.annotation.Nullable;
-import android.app.PendingIntent;
-
-import com.android.internal.annotations.VisibleForTesting;
-
-import java.util.Map;
-import java.util.HashMap;
-
-/**
- * In memory storage for listeners to be notified when new recovery snapshot is available.
- * Note: implementation is not thread safe and it is used to mock final {@link PendingIntent}
- * class.
- *
- * @hide
- */
-public class ListenersStorage {
- private Map<Integer, PendingIntent> mAgentIntents = new HashMap<>();
-
- private static final ListenersStorage mInstance = new ListenersStorage();
- public static ListenersStorage getInstance() {
- return mInstance;
- }
-
- /**
- * Sets new listener for the recovery agent, identified by {@code uid}
- *
- * @param recoveryAgentUid uid
- * @param intent PendingIntent which will be triggered than new snapshot is available.
- */
- public void setSnapshotListener(int recoveryAgentUid, @Nullable PendingIntent intent) {
- mAgentIntents.put(recoveryAgentUid, intent);
- }
-
- /**
- * Notifies recovery agent, that new snapshot is available.
- * Does nothing if a listener was not registered.
- *
- * @param recoveryAgentUid uid.
- */
- public void recoverySnapshotAvailable(int recoveryAgentUid) {
- PendingIntent intent = mAgentIntents.get(recoveryAgentUid);
- if (intent != null) {
- try {
- intent.send();
- } catch (PendingIntent.CanceledException e) {
- // Ignore - sending intent is not allowed.
- }
- }
- }
-}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java
index 24f3f65..a8b8361 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java
@@ -88,7 +88,8 @@
*
* @hide
*/
- public static PlatformKeyManager getInstance(Context context, RecoverableKeyStoreDb database, int userId)
+ public static PlatformKeyManager getInstance(Context context, RecoverableKeyStoreDb database,
+ int userId)
throws KeyStoreException, NoSuchAlgorithmException, InsecureUserException {
context = context.getApplicationContext();
PlatformKeyManager keyManager = new PlatformKeyManager(
@@ -115,16 +116,12 @@
/**
* Returns the current generation ID of the platform key. This increments whenever a platform
* key has to be replaced. (e.g., because the user has removed and then re-added their lock
- * screen).
+ * screen). Returns -1 if no key has been generated yet.
*
* @hide
*/
public int getGenerationId() {
- int generationId = mDatabase.getPlatformKeyGenerationId(mUserId);
- if (generationId == -1) {
- return 1;
- }
- return generationId;
+ return mDatabase.getPlatformKeyGenerationId(mUserId);
}
/**
@@ -149,7 +146,6 @@
public void regenerate() throws NoSuchAlgorithmException, KeyStoreException {
int nextId = getGenerationId() + 1;
generateAndLoadKey(nextId);
- setGenerationId(nextId);
}
/**
@@ -207,13 +203,20 @@
Locale.US, "Platform key generation %d exists already.", generationId));
return;
}
- if (generationId == 1) {
+ if (generationId == -1) {
Log.i(TAG, "Generating initial platform ID.");
} else {
Log.w(TAG, String.format(Locale.US, "Platform generation ID was %d but no "
+ "entry was present in AndroidKeyStore. Generating fresh key.", generationId));
}
+ if (generationId == -1) {
+ generationId = 1;
+ } else {
+ // Had to generate a fresh key, bump the generation id
+ generationId++;
+ }
+
generateAndLoadKey(generationId);
}
@@ -296,6 +299,8 @@
.setBoundToSpecificSecureUserId(mUserId)
.build());
+ setGenerationId(generationId);
+
try {
secretKey.destroy();
} catch (DestroyFailedException e) {
@@ -332,4 +337,17 @@
}
return keyStore;
}
+
+ /**
+ * @hide
+ */
+ public interface Factory {
+ /**
+ * New PlatformKeyManager instance.
+ *
+ * @hide
+ */
+ PlatformKeyManager newInstance()
+ throws NoSuchAlgorithmException, InsecureUserException, KeyStoreException;
+ }
}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGenerator.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGenerator.java
index bb40fd8..8c23d9b 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGenerator.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGenerator.java
@@ -16,22 +16,15 @@
package com.android.server.locksettings.recoverablekeystore;
-import android.security.keystore.KeyProperties;
-import android.security.keystore.KeyProtection;
-import android.util.Log;
-
import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb;
import java.security.InvalidKeyException;
-import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
-import java.security.NoSuchProviderException;
import java.util.Locale;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
-import javax.security.auth.DestroyFailedException;
/**
* Generates keys and stores them both in AndroidKeyStore and on disk, in wrapped form.
@@ -43,8 +36,6 @@
* @hide
*/
public class RecoverableKeyGenerator {
- private static final String TAG = "RecoverableKeyGenerator";
-
private static final int RESULT_CANNOT_INSERT_ROW = -1;
private static final String KEY_GENERATOR_ALGORITHM = "AES";
private static final int KEY_SIZE_BITS = 256;
@@ -62,20 +53,16 @@
// NB: This cannot use AndroidKeyStore as the provider, as we need access to the raw key
// material, so that it can be synced to disk in encrypted form.
KeyGenerator keyGenerator = KeyGenerator.getInstance(KEY_GENERATOR_ALGORITHM);
- return new RecoverableKeyGenerator(
- keyGenerator, database, new AndroidKeyStoreFactory.Impl());
+ return new RecoverableKeyGenerator(keyGenerator, database);
}
private final KeyGenerator mKeyGenerator;
private final RecoverableKeyStoreDb mDatabase;
- private final AndroidKeyStoreFactory mAndroidKeyStoreFactory;
private RecoverableKeyGenerator(
KeyGenerator keyGenerator,
- RecoverableKeyStoreDb recoverableKeyStoreDb,
- AndroidKeyStoreFactory androidKeyStoreFactory) {
+ RecoverableKeyStoreDb recoverableKeyStoreDb) {
mKeyGenerator = keyGenerator;
- mAndroidKeyStoreFactory = androidKeyStoreFactory;
mDatabase = recoverableKeyStoreDb;
}
@@ -89,69 +76,29 @@
* @param platformKey The user's platform key, with which to wrap the generated key.
* @param userId The user ID of the profile to which the calling app belongs.
* @param uid The uid of the application that will own the key.
- * @param alias The alias by which the key will be known in AndroidKeyStore.
+ * @param alias The alias by which the key will be known in the recoverable key store.
* @throws RecoverableKeyStorageException if there is some error persisting the key either to
- * the AndroidKeyStore or the database.
+ * the database.
* @throws KeyStoreException if there is a KeyStore error wrapping the generated key.
* @throws InvalidKeyException if the platform key cannot be used to wrap keys.
*
* @hide
*/
- public void generateAndStoreKey(
+ public byte[] generateAndStoreKey(
PlatformEncryptionKey platformKey, int userId, int uid, String alias)
throws RecoverableKeyStorageException, KeyStoreException, InvalidKeyException {
mKeyGenerator.init(KEY_SIZE_BITS);
SecretKey key = mKeyGenerator.generateKey();
- KeyStoreProxy keyStore;
-
- try {
- keyStore = mAndroidKeyStoreFactory.getKeyStoreForUid(uid);
- } catch (NoSuchProviderException e) {
- throw new RecoverableKeyStorageException(
- "Impossible: AndroidKeyStore provider did not exist", e);
- } catch (KeyStoreException e) {
- throw new RecoverableKeyStorageException(
- "Could not load AndroidKeyStore for " + uid, e);
- }
-
- try {
- keyStore.setEntry(
- alias,
- new KeyStore.SecretKeyEntry(key),
- new KeyProtection.Builder(
- KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
- .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
- .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
- .build());
- } catch (KeyStoreException e) {
- throw new RecoverableKeyStorageException(
- "Failed to load (%d, %s) into AndroidKeyStore", e);
- }
-
WrappedKey wrappedKey = WrappedKey.fromSecretKey(platformKey, key);
- try {
- // Keep raw key material in memory for minimum possible time.
- key.destroy();
- } catch (DestroyFailedException e) {
- Log.w(TAG, "Could not destroy SecretKey.");
- }
long result = mDatabase.insertKey(userId, uid, alias, wrappedKey);
if (result == RESULT_CANNOT_INSERT_ROW) {
- // Attempt to clean up
- try {
- keyStore.deleteEntry(alias);
- } catch (KeyStoreException e) {
- Log.e(TAG, String.format(Locale.US,
- "Could not delete recoverable key (%d, %s) from "
- + "AndroidKeyStore after error writing to database.", uid, alias),
- e);
- }
-
throw new RecoverableKeyStorageException(
String.format(
Locale.US, "Failed writing (%d, %s) to database.", uid, alias));
}
+
+ return key.getEncoded();
}
}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
index bb9099a..eccf241 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
@@ -34,14 +34,18 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb;
import com.android.server.locksettings.recoverablekeystore.storage.RecoverySessionStorage;
+import com.android.server.locksettings.recoverablekeystore.storage.RecoverySnapshotStorage;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.KeyStoreException;
+import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
+import java.security.UnrecoverableKeyException;
import java.security.spec.InvalidKeySpecException;
-import java.util.ArrayList;
+import java.security.spec.X509EncodedKeySpec;
+import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
@@ -58,13 +62,20 @@
*/
public class RecoverableKeyStoreManager {
private static final String TAG = "RecoverableKeyStoreMgr";
+
+ private static final int ERROR_INSECURE_USER = 1;
+ private static final int ERROR_KEYSTORE_INTERNAL_ERROR = 2;
+ private static final int ERROR_DATABASE_ERROR = 3;
+
private static RecoverableKeyStoreManager mInstance;
private final Context mContext;
private final RecoverableKeyStoreDb mDatabase;
private final RecoverySessionStorage mRecoverySessionStorage;
private final ExecutorService mExecutorService;
- private final ListenersStorage mListenersStorage;
+ private final RecoverySnapshotListenersStorage mListenersStorage;
+ private final RecoverableKeyGenerator mRecoverableKeyGenerator;
+ private final RecoverySnapshotStorage mSnapshotStorage;
/**
* Returns a new or existing instance.
@@ -79,7 +90,8 @@
db,
new RecoverySessionStorage(),
Executors.newSingleThreadExecutor(),
- ListenersStorage.getInstance());
+ new RecoverySnapshotStorage(),
+ new RecoverySnapshotListenersStorage());
}
return mInstance;
}
@@ -90,20 +102,40 @@
RecoverableKeyStoreDb recoverableKeyStoreDb,
RecoverySessionStorage recoverySessionStorage,
ExecutorService executorService,
- ListenersStorage listenersStorage) {
+ RecoverySnapshotStorage snapshotStorage,
+ RecoverySnapshotListenersStorage listenersStorage) {
mContext = context;
mDatabase = recoverableKeyStoreDb;
mRecoverySessionStorage = recoverySessionStorage;
mExecutorService = executorService;
mListenersStorage = listenersStorage;
+ mSnapshotStorage = snapshotStorage;
+ try {
+ mRecoverableKeyGenerator = RecoverableKeyGenerator.newInstance(mDatabase);
+ } catch (NoSuchAlgorithmException e) {
+ // Impossible: all AOSP implementations must support AES.
+ throw new RuntimeException(e);
+ }
}
- public int initRecoveryService(
+ public void initRecoveryService(
@NonNull String rootCertificateAlias, @NonNull byte[] signedPublicKeyList, int userId)
throws RemoteException {
checkRecoverKeyStorePermission();
- // TODO open /system/etc/security/... cert file
- throw new UnsupportedOperationException();
+ // TODO: open /system/etc/security/... cert file, and check the signature on the public keys
+ PublicKey publicKey;
+ try {
+ KeyFactory kf = KeyFactory.getInstance("EC");
+ // TODO: Randomly choose a key from the list -- right now we just use the whole input
+ X509EncodedKeySpec pkSpec = new X509EncodedKeySpec(signedPublicKeyList);
+ publicKey = kf.generatePublic(pkSpec);
+ } catch (NoSuchAlgorithmException e) {
+ // Should never happen
+ throw new RuntimeException(e);
+ } catch (InvalidKeySpecException e) {
+ throw new RemoteException("Invalid public key for the recovery service");
+ }
+ mDatabase.setRecoveryServicePublicKey(userId, Binder.getCallingUid(), publicKey);
}
/**
@@ -115,24 +147,12 @@
public @NonNull KeyStoreRecoveryData getRecoveryData(@NonNull byte[] account, int userId)
throws RemoteException {
checkRecoverKeyStorePermission();
- final int callingUid = Binder.getCallingUid(); // Recovery agent uid.
- final int callingUserId = UserHandle.getCallingUserId();
- final long callingIdentiy = Binder.clearCallingIdentity();
- try {
- // TODO: Return the latest snapshot for the calling recovery agent.
- } finally {
- Binder.restoreCallingIdentity(callingIdentiy);
- }
- // KeyStoreRecoveryData without application keys and empty recovery blob.
- KeyStoreRecoveryData recoveryData =
- new KeyStoreRecoveryData(
- /*snapshotVersion=*/ 1,
- new ArrayList<KeyStoreRecoveryMetadata>(),
- new ArrayList<KeyEntryRecoveryData>(),
- /*encryptedRecoveryKeyBlob=*/ new byte[] {});
- throw new ServiceSpecificException(
- RecoverableKeyStoreLoader.UNINITIALIZED_RECOVERY_PUBLIC_KEY);
+ KeyStoreRecoveryData snapshot = mSnapshotStorage.get(UserHandle.getCallingUserId());
+ if (snapshot == null) {
+ throw new ServiceSpecificException(RecoverableKeyStoreLoader.NO_SNAPSHOT_PENDING_ERROR);
+ }
+ return snapshot;
}
public void setSnapshotCreatedPendingIntent(@Nullable PendingIntent intent, int userId)
@@ -156,7 +176,7 @@
public void setServerParameters(long serverParameters, int userId) throws RemoteException {
checkRecoverKeyStorePermission();
- throw new UnsupportedOperationException();
+ mDatabase.setServerParameters(userId, Binder.getCallingUid(), serverParameters);
}
/**
@@ -196,7 +216,6 @@
// Any application should be able to check status for its own keys.
// If caller is a recovery agent it can check statuses for other packages, but
// only for recoverable keys it manages.
- checkRecoverKeyStorePermission();
return mDatabase.getStatusForAllKeys(Binder.getCallingUid());
}
@@ -209,7 +228,8 @@
@NonNull @KeyStoreRecoveryMetadata.UserSecretType int[] secretTypes, int userId)
throws RemoteException {
checkRecoverKeyStorePermission();
- throw new UnsupportedOperationException();
+ mDatabase.setRecoverySecretTypes(UserHandle.getCallingUserId(), Binder.getCallingUid(),
+ secretTypes);
}
/**
@@ -220,7 +240,8 @@
*/
public @NonNull int[] getRecoverySecretTypes(int userId) throws RemoteException {
checkRecoverKeyStorePermission();
- throw new UnsupportedOperationException();
+ return mDatabase.getRecoverySecretTypes(UserHandle.getCallingUserId(),
+ Binder.getCallingUid());
}
/**
@@ -308,8 +329,6 @@
* Invoked by a recovery agent after a successful recovery claim is sent to the remote vault
* service.
*
- * <p>TODO: should also load into AndroidKeyStore.
- *
* @param sessionId The session ID used to generate the claim. See
* {@link #startRecoverySession(String, byte[], byte[], byte[], List, int)}.
* @param encryptedRecoveryKey The encrypted recovery key blob returned by the remote vault
@@ -317,9 +336,10 @@
* @param applicationKeys The encrypted key blobs returned by the remote vault service. These
* were wrapped with the recovery key.
* @param uid The uid of the recovery agent.
+ * @return Map from alias to raw key material.
* @throws RemoteException if an error occurred recovering the keys.
*/
- public void recoverKeys(
+ public Map<String, byte[]> recoverKeys(
@NonNull String sessionId,
@NonNull byte[] encryptedRecoveryKey,
@NonNull List<KeyEntryRecoveryData> applicationKeys,
@@ -335,13 +355,49 @@
try {
byte[] recoveryKey = decryptRecoveryKey(sessionEntry, encryptedRecoveryKey);
- recoverApplicationKeys(recoveryKey, applicationKeys);
+ return recoverApplicationKeys(recoveryKey, applicationKeys);
} finally {
sessionEntry.destroy();
mRecoverySessionStorage.remove(uid);
}
}
+ /**
+ * Generates a key named {@code alias} in the recoverable store for the calling uid. Then
+ * returns the raw key material.
+ *
+ * <p>TODO: Once AndroidKeyStore has added move api, do not return raw bytes.
+ *
+ * @hide
+ */
+ public byte[] generateAndStoreKey(@NonNull String alias) throws RemoteException {
+ int uid = Binder.getCallingUid();
+ int userId = Binder.getCallingUserHandle().getIdentifier();
+
+ PlatformEncryptionKey encryptionKey;
+
+ try {
+ PlatformKeyManager platformKeyManager = PlatformKeyManager.getInstance(
+ mContext, mDatabase, userId);
+ encryptionKey = platformKeyManager.getEncryptKey();
+ } catch (NoSuchAlgorithmException e) {
+ // Impossible: all algorithms must be supported by AOSP
+ throw new RuntimeException(e);
+ } catch (KeyStoreException | UnrecoverableKeyException e) {
+ throw new ServiceSpecificException(ERROR_KEYSTORE_INTERNAL_ERROR, e.getMessage());
+ } catch (InsecureUserException e) {
+ throw new ServiceSpecificException(ERROR_INSECURE_USER, e.getMessage());
+ }
+
+ try {
+ return mRecoverableKeyGenerator.generateAndStoreKey(encryptionKey, userId, uid, alias);
+ } catch (KeyStoreException | InvalidKeyException e) {
+ throw new ServiceSpecificException(ERROR_KEYSTORE_INTERNAL_ERROR, e.getMessage());
+ } catch (RecoverableKeyStorageException e) {
+ throw new ServiceSpecificException(ERROR_DATABASE_ERROR, e.getMessage());
+ }
+ }
+
private byte[] decryptRecoveryKey(
RecoverySessionStorage.Entry sessionEntry, byte[] encryptedClaimResponse)
throws RemoteException {
@@ -370,20 +426,21 @@
/**
* Uses {@code recoveryKey} to decrypt {@code applicationKeys}.
*
- * <p>TODO: and load them into store?
- *
+ * @return Map from alias to raw key material.
* @throws RemoteException if an error occurred decrypting the keys.
*/
- private void recoverApplicationKeys(
+ private Map<String, byte[]> recoverApplicationKeys(
@NonNull byte[] recoveryKey,
@NonNull List<KeyEntryRecoveryData> applicationKeys) throws RemoteException {
+ HashMap<String, byte[]> keyMaterialByAlias = new HashMap<>();
for (KeyEntryRecoveryData applicationKey : applicationKeys) {
String alias = new String(applicationKey.getAlias(), StandardCharsets.UTF_8);
byte[] encryptedKeyMaterial = applicationKey.getEncryptedKeyMaterial();
try {
- // TODO: put decrypted key material in appropriate AndroidKeyStore
- KeySyncUtils.decryptApplicationKey(recoveryKey, encryptedKeyMaterial);
+ byte[] keyMaterial =
+ KeySyncUtils.decryptApplicationKey(recoveryKey, encryptedKeyMaterial);
+ keyMaterialByAlias.put(alias, keyMaterial);
} catch (NoSuchAlgorithmException e) {
// Should never happen: all the algorithms used are required by AOSP implementations
throw new RemoteException(
@@ -399,12 +456,13 @@
/*writeableStackTrace=*/ true);
}
}
+ return keyMaterialByAlias;
}
/**
* This function can only be used inside LockSettingsService.
*
- * @param storedHashType from {@Code CredentialHash}
+ * @param storedHashType from {@code CredentialHash}
* @param credential - unencrypted String. Password length should be at most 16 symbols {@code
* mPasswordMaxLength}
* @param userId for user who just unlocked the device.
@@ -415,7 +473,13 @@
// So as not to block the critical path unlocking the phone, defer to another thread.
try {
mExecutorService.execute(KeySyncTask.newInstance(
- mContext, mDatabase, userId, storedHashType, credential));
+ mContext,
+ mDatabase,
+ mSnapshotStorage,
+ mListenersStorage,
+ userId,
+ storedHashType,
+ credential));
} catch (NoSuchAlgorithmException e) {
Log.wtf(TAG, "Should never happen - algorithm unavailable for KeySync", e);
} catch (KeyStoreException e) {
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverySnapshotListenersStorage.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverySnapshotListenersStorage.java
new file mode 100644
index 0000000..c925329
--- /dev/null
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverySnapshotListenersStorage.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.locksettings.recoverablekeystore;
+
+import android.annotation.Nullable;
+import android.app.PendingIntent;
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+
+/**
+ * In memory storage for listeners to be notified when new recovery snapshot is available. This
+ * class is thread-safe. It is used on two threads - the service thread and the thread that runs the
+ * {@link KeySyncTask}.
+ *
+ * @hide
+ */
+public class RecoverySnapshotListenersStorage {
+ private static final String TAG = "RecoverySnapshotLstnrs";
+
+ @GuardedBy("this")
+ private SparseArray<PendingIntent> mAgentIntents = new SparseArray<>();
+
+ /**
+ * Sets new listener for the recovery agent, identified by {@code uid}.
+ *
+ * @param recoveryAgentUid uid of the recovery agent.
+ * @param intent PendingIntent which will be triggered when new snapshot is available.
+ */
+ public synchronized void setSnapshotListener(
+ int recoveryAgentUid, @Nullable PendingIntent intent) {
+ Log.i(TAG, "Registered listener for agent with uid " + recoveryAgentUid);
+ mAgentIntents.put(recoveryAgentUid, intent);
+ }
+
+ /**
+ * Returns {@code true} if a listener has been set for the recovery agent.
+ */
+ public synchronized boolean hasListener(int recoveryAgentUid) {
+ return mAgentIntents.get(recoveryAgentUid) != null;
+ }
+
+ /**
+ * Notifies recovery agent that new snapshot is available. Does nothing if a listener was not
+ * registered.
+ *
+ * @param recoveryAgentUid uid of recovery agent.
+ */
+ public synchronized void recoverySnapshotAvailable(int recoveryAgentUid) {
+ PendingIntent intent = mAgentIntents.get(recoveryAgentUid);
+ if (intent != null) {
+ try {
+ intent.send();
+ } catch (PendingIntent.CanceledException e) {
+ Log.e(TAG,
+ "Failed to trigger PendingIntent for " + recoveryAgentUid,
+ e);
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/SecureBox.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/SecureBox.java
index 801d4de..807ee03 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/SecureBox.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/SecureBox.java
@@ -372,7 +372,13 @@
}
}
- @VisibleForTesting
+ /**
+ * Encodes public key in format expected by the secure hardware module. This is used as part
+ * of the vault params.
+ *
+ * @param publicKey The public key.
+ * @return The key packed into a 65-byte array.
+ */
static byte[] encodePublicKey(PublicKey publicKey) {
ECPoint point = ((ECPublicKey) publicKey).getW();
byte[] x = point.getAffineX().toByteArray();
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDb.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDb.java
index 56804e0..5ca5da4 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDb.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDb.java
@@ -22,16 +22,24 @@
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
-import android.security.recoverablekeystore.RecoverableKeyStoreLoader;
+import android.text.TextUtils;
import android.util.Log;
import com.android.server.locksettings.recoverablekeystore.WrappedKey;
import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDbContract.KeysEntry;
+import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDbContract.RecoveryServiceMetadataEntry;
import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDbContract.UserMetadataEntry;
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.PublicKey;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.X509EncodedKeySpec;
+import java.util.Arrays;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
+import java.util.StringJoiner;
/**
* Database of recoverable key information.
@@ -296,11 +304,366 @@
}
/**
+ * Updates the public key of the recovery service into the database.
+ *
+ * @param userId The uid of the profile the application is running under.
+ * @param uid The uid of the application to whom the key belongs.
+ * @param publicKey The public key of the recovery service.
+ * @return The primary key of the inserted row, or -1 if failed.
+ *
+ * @hide
+ */
+ public long setRecoveryServicePublicKey(int userId, int uid, PublicKey publicKey) {
+ SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase();
+ ContentValues values = new ContentValues();
+ values.put(RecoveryServiceMetadataEntry.COLUMN_NAME_PUBLIC_KEY, publicKey.getEncoded());
+ String selection =
+ RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + " = ? AND "
+ + RecoveryServiceMetadataEntry.COLUMN_NAME_UID + " = ?";
+ String[] selectionArguments = {Integer.toString(userId), Integer.toString(uid)};
+
+ ensureRecoveryServiceMetadataEntryExists(userId, uid);
+ return db.update(
+ RecoveryServiceMetadataEntry.TABLE_NAME, values, selection, selectionArguments);
+ }
+
+ /**
+ * Returns the uid of the recovery agent for the given user, or -1 if none is set.
+ */
+ public int getRecoveryAgentUid(int userId) {
+ SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase();
+
+ String[] projection = { RecoveryServiceMetadataEntry.COLUMN_NAME_UID };
+ String selection = RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + " = ?";
+ String[] selectionArguments = { Integer.toString(userId) };
+
+ try (
+ Cursor cursor = db.query(
+ RecoveryServiceMetadataEntry.TABLE_NAME,
+ projection,
+ selection,
+ selectionArguments,
+ /*groupBy=*/ null,
+ /*having=*/ null,
+ /*orderBy=*/ null)
+ ) {
+ int count = cursor.getCount();
+ if (count == 0) {
+ return -1;
+ }
+ cursor.moveToFirst();
+ return cursor.getInt(
+ cursor.getColumnIndexOrThrow(RecoveryServiceMetadataEntry.COLUMN_NAME_UID));
+ }
+ }
+
+ /**
+ * Returns the public key of the recovery service.
+ *
+ * @param userId The uid of the profile the application is running under.
+ * @param uid The uid of the application who initializes the local recovery components.
+ *
+ * @hide
+ */
+ @Nullable
+ public PublicKey getRecoveryServicePublicKey(int userId, int uid) {
+ SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase();
+
+ String[] projection = {
+ RecoveryServiceMetadataEntry._ID,
+ RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID,
+ RecoveryServiceMetadataEntry.COLUMN_NAME_UID,
+ RecoveryServiceMetadataEntry.COLUMN_NAME_PUBLIC_KEY};
+ String selection =
+ RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + " = ? AND "
+ + RecoveryServiceMetadataEntry.COLUMN_NAME_UID + " = ?";
+ String[] selectionArguments = {Integer.toString(userId), Integer.toString(uid)};
+
+ try (
+ Cursor cursor = db.query(
+ RecoveryServiceMetadataEntry.TABLE_NAME,
+ projection,
+ selection,
+ selectionArguments,
+ /*groupBy=*/ null,
+ /*having=*/ null,
+ /*orderBy=*/ null)
+ ) {
+ int count = cursor.getCount();
+ if (count == 0) {
+ return null;
+ }
+ if (count > 1) {
+ Log.wtf(TAG,
+ String.format(Locale.US,
+ "%d PublicKey entries found for userId=%d uid=%d. "
+ + "Should only ever be 0 or 1.", count, userId, uid));
+ return null;
+ }
+ cursor.moveToFirst();
+ int idx = cursor.getColumnIndexOrThrow(
+ RecoveryServiceMetadataEntry.COLUMN_NAME_PUBLIC_KEY);
+ if (cursor.isNull(idx)) {
+ return null;
+ }
+ byte[] keyBytes = cursor.getBlob(idx);
+ try {
+ return decodeX509Key(keyBytes);
+ } catch (InvalidKeySpecException e) {
+ Log.wtf(TAG,
+ String.format(Locale.US,
+ "Recovery service public key entry cannot be decoded for "
+ + "userId=%d uid=%d.",
+ userId, uid));
+ return null;
+ }
+ }
+ }
+
+ /**
+ * Updates the list of user secret types used for end-to-end encryption.
+ * If no secret types are set, recovery snapshot will not be created.
+ * See {@code KeyStoreRecoveryMetadata}
+ *
+ * @param userId The uid of the profile the application is running under.
+ * @param uid The uid of the application.
+ * @param secretTypes list of secret types
+ * @return The primary key of the updated row, or -1 if failed.
+ *
+ * @hide
+ */
+ public long setRecoverySecretTypes(int userId, int uid, int[] secretTypes) {
+ SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase();
+ ContentValues values = new ContentValues();
+ StringJoiner joiner = new StringJoiner(",");
+ Arrays.stream(secretTypes).forEach(i -> joiner.add(Integer.toString(i)));
+ String typesAsCsv = joiner.toString();
+ values.put(RecoveryServiceMetadataEntry.COLUMN_NAME_SECRET_TYPES, typesAsCsv);
+ String selection =
+ RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + " = ? AND "
+ + RecoveryServiceMetadataEntry.COLUMN_NAME_UID + " = ?";
+ ensureRecoveryServiceMetadataEntryExists(userId, uid);
+ return db.update(RecoveryServiceMetadataEntry.TABLE_NAME, values, selection,
+ new String[] {String.valueOf(userId), String.valueOf(uid)});
+ }
+
+ /**
+ * Returns the list of secret types used for end-to-end encryption.
+ *
+ * @param userId The uid of the profile the application is running under.
+ * @param uid The uid of the application who initialized the local recovery components.
+ * @return Secret types or empty array, if types were not set.
+ *
+ * @hide
+ */
+ public @NonNull int[] getRecoverySecretTypes(int userId, int uid) {
+ SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase();
+
+ String[] projection = {
+ RecoveryServiceMetadataEntry._ID,
+ RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID,
+ RecoveryServiceMetadataEntry.COLUMN_NAME_UID,
+ RecoveryServiceMetadataEntry.COLUMN_NAME_SECRET_TYPES};
+ String selection =
+ RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + " = ? AND "
+ + RecoveryServiceMetadataEntry.COLUMN_NAME_UID + " = ?";
+ String[] selectionArguments = {Integer.toString(userId), Integer.toString(uid)};
+
+ try (
+ Cursor cursor = db.query(
+ RecoveryServiceMetadataEntry.TABLE_NAME,
+ projection,
+ selection,
+ selectionArguments,
+ /*groupBy=*/ null,
+ /*having=*/ null,
+ /*orderBy=*/ null)
+ ) {
+ int count = cursor.getCount();
+ if (count == 0) {
+ return new int[]{};
+ }
+ if (count > 1) {
+ Log.wtf(TAG,
+ String.format(Locale.US,
+ "%d deviceId entries found for userId=%d uid=%d. "
+ + "Should only ever be 0 or 1.", count, userId, uid));
+ return new int[]{};
+ }
+ cursor.moveToFirst();
+ int idx = cursor.getColumnIndexOrThrow(
+ RecoveryServiceMetadataEntry.COLUMN_NAME_SECRET_TYPES);
+ if (cursor.isNull(idx)) {
+ return new int[]{};
+ }
+ String csv = cursor.getString(idx);
+ if (TextUtils.isEmpty(csv)) {
+ return new int[]{};
+ }
+ String[] types = csv.split(",");
+ int[] result = new int[types.length];
+ for (int i = 0; i < types.length; i++) {
+ try {
+ result[i] = Integer.parseInt(types[i]);
+ } catch (NumberFormatException e) {
+ Log.wtf(TAG, "String format error " + e);
+ }
+ }
+ return result;
+ }
+ }
+
+ /**
+ * Returns the first (and only?) public key for {@code userId}.
+ *
+ * @param userId The uid of the profile whose keys are to be synced.
+ * @return The public key, or null if none exists.
+ */
+ @Nullable
+ public PublicKey getRecoveryServicePublicKey(int userId) {
+ SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase();
+
+ String[] projection = { RecoveryServiceMetadataEntry.COLUMN_NAME_PUBLIC_KEY };
+ String selection =
+ RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + " = ?";
+ String[] selectionArguments = { Integer.toString(userId) };
+
+ try (
+ Cursor cursor = db.query(
+ RecoveryServiceMetadataEntry.TABLE_NAME,
+ projection,
+ selection,
+ selectionArguments,
+ /*groupBy=*/ null,
+ /*having=*/ null,
+ /*orderBy=*/ null)
+ ) {
+ if (cursor.getCount() < 1) {
+ return null;
+ }
+
+ cursor.moveToFirst();
+ byte[] keyBytes = cursor.getBlob(cursor.getColumnIndexOrThrow(
+ RecoveryServiceMetadataEntry.COLUMN_NAME_PUBLIC_KEY));
+
+ try {
+ return decodeX509Key(keyBytes);
+ } catch (InvalidKeySpecException e) {
+ Log.wtf(TAG, "Could not decode public key for " + userId);
+ return null;
+ }
+ }
+ }
+
+ /**
+ * Updates the server parameters given by the application initializing the local recovery
+ * components.
+ *
+ * @param userId The uid of the profile the application is running under.
+ * @param uid The uid of the application.
+ * @param serverParameters The server parameters.
+ * @return The primary key of the inserted row, or -1 if failed.
+ *
+ * @hide
+ */
+ public long setServerParameters(int userId, int uid, long serverParameters) {
+ SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase();
+ ContentValues values = new ContentValues();
+ values.put(RecoveryServiceMetadataEntry.COLUMN_NAME_SERVER_PARAMETERS, serverParameters);
+ String selection =
+ RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + " = ? AND "
+ + RecoveryServiceMetadataEntry.COLUMN_NAME_UID + " = ?";
+ String[] selectionArguments = {Integer.toString(userId), Integer.toString(uid)};
+
+ ensureRecoveryServiceMetadataEntryExists(userId, uid);
+ return db.update(
+ RecoveryServiceMetadataEntry.TABLE_NAME, values, selection, selectionArguments);
+ }
+
+ /**
+ * Returns the server paramters that was previously set by the application who initialized the
+ * local recovery service components.
+ *
+ * @param userId The uid of the profile the application is running under.
+ * @param uid The uid of the application who initialized the local recovery components.
+ * @return The server parameters that were previously set, or null if there's none.
+ *
+ * @hide
+ */
+ @Nullable
+ public Long getServerParameters(int userId, int uid) {
+ SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase();
+
+ String[] projection = {
+ RecoveryServiceMetadataEntry._ID,
+ RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID,
+ RecoveryServiceMetadataEntry.COLUMN_NAME_UID,
+ RecoveryServiceMetadataEntry.COLUMN_NAME_SERVER_PARAMETERS};
+ String selection =
+ RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + " = ? AND "
+ + RecoveryServiceMetadataEntry.COLUMN_NAME_UID + " = ?";
+ String[] selectionArguments = {Integer.toString(userId), Integer.toString(uid)};
+
+ try (
+ Cursor cursor = db.query(
+ RecoveryServiceMetadataEntry.TABLE_NAME,
+ projection,
+ selection,
+ selectionArguments,
+ /*groupBy=*/ null,
+ /*having=*/ null,
+ /*orderBy=*/ null)
+ ) {
+ int count = cursor.getCount();
+ if (count == 0) {
+ return null;
+ }
+ if (count > 1) {
+ Log.wtf(TAG,
+ String.format(Locale.US,
+ "%d deviceId entries found for userId=%d uid=%d. "
+ + "Should only ever be 0 or 1.", count, userId, uid));
+ return null;
+ }
+ cursor.moveToFirst();
+ int idx = cursor.getColumnIndexOrThrow(
+ RecoveryServiceMetadataEntry.COLUMN_NAME_SERVER_PARAMETERS);
+ if (cursor.isNull(idx)) {
+ return null;
+ } else {
+ return cursor.getLong(idx);
+ }
+ }
+ }
+
+ /**
+ * Creates an empty row in the recovery service metadata table if such a row doesn't exist for
+ * the given userId and uid, so db.update will succeed.
+ */
+ private void ensureRecoveryServiceMetadataEntryExists(int userId, int uid) {
+ SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase();
+ ContentValues values = new ContentValues();
+ values.put(RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID, userId);
+ values.put(RecoveryServiceMetadataEntry.COLUMN_NAME_UID, uid);
+ db.insertWithOnConflict(RecoveryServiceMetadataEntry.TABLE_NAME, /*nullColumnHack=*/ null,
+ values, SQLiteDatabase.CONFLICT_IGNORE);
+ }
+
+ /**
* Closes all open connections to the database.
*/
public void close() {
mKeyStoreDbHelper.close();
}
- // TODO: Add method for updating the 'last synced' time.
+ @Nullable
+ private static PublicKey decodeX509Key(byte[] keyBytes) throws InvalidKeySpecException {
+ X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(keyBytes);
+ try {
+ return KeyFactory.getInstance("EC").generatePublic(publicKeySpec);
+ } catch (NoSuchAlgorithmException e) {
+ // Should never happen
+ throw new RuntimeException(e);
+ }
+ }
}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbContract.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbContract.java
index 82e5650..8f773dd 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbContract.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbContract.java
@@ -86,4 +86,36 @@
*/
static final String COLUMN_NAME_PLATFORM_KEY_GENERATION_ID = "platform_key_generation_id";
}
+
+ /**
+ * Table holding metadata of the recovery service.
+ */
+ static class RecoveryServiceMetadataEntry implements BaseColumns {
+ static final String TABLE_NAME = "recovery_service_metadata";
+
+ /**
+ * The user id of the profile the application is running under.
+ */
+ static final String COLUMN_NAME_USER_ID = "user_id";
+
+ /**
+ * The uid of the application that initializes the local recovery components.
+ */
+ static final String COLUMN_NAME_UID = "uid";
+
+ /**
+ * The public key of the recovery service.
+ */
+ static final String COLUMN_NAME_PUBLIC_KEY = "public_key";
+
+ /**
+ * Secret types used for end-to-end encryption.
+ */
+ static final String COLUMN_NAME_SECRET_TYPES = "secret_types";
+
+ /**
+ * The server parameters of the recovery service.
+ */
+ static final String COLUMN_NAME_SERVER_PARAMETERS = "server_parameters";
+ }
}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelper.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelper.java
index 46846e1..5b07f3e 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelper.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelper.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package com.android.server.locksettings.recoverablekeystore.storage;
import android.content.Context;
@@ -5,6 +21,7 @@
import android.database.sqlite.SQLiteOpenHelper;
import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDbContract.KeysEntry;
+import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDbContract.RecoveryServiceMetadataEntry;
import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDbContract.UserMetadataEntry;
/**
@@ -34,12 +51,27 @@
+ UserMetadataEntry.COLUMN_NAME_USER_ID + " INTEGER UNIQUE,"
+ UserMetadataEntry.COLUMN_NAME_PLATFORM_KEY_GENERATION_ID + " INTEGER)";
+ private static final String SQL_CREATE_RECOVERY_SERVICE_PUBLIC_KEY_ENTRY =
+ "CREATE TABLE " + RecoveryServiceMetadataEntry.TABLE_NAME + " ("
+ + RecoveryServiceMetadataEntry._ID + " INTEGER PRIMARY KEY,"
+ + RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + " INTEGER,"
+ + RecoveryServiceMetadataEntry.COLUMN_NAME_UID + " INTEGER,"
+ + RecoveryServiceMetadataEntry.COLUMN_NAME_PUBLIC_KEY + " BLOB,"
+ + RecoveryServiceMetadataEntry.COLUMN_NAME_SECRET_TYPES + " TEXT,"
+ + RecoveryServiceMetadataEntry.COLUMN_NAME_SERVER_PARAMETERS + " INTEGER,"
+ + "UNIQUE("
+ + RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + ","
+ + RecoveryServiceMetadataEntry.COLUMN_NAME_UID + "))";
+
private static final String SQL_DELETE_KEYS_ENTRY =
"DROP TABLE IF EXISTS " + KeysEntry.TABLE_NAME;
private static final String SQL_DELETE_USER_METADATA_ENTRY =
"DROP TABLE IF EXISTS " + UserMetadataEntry.TABLE_NAME;
+ private static final String SQL_DELETE_RECOVERY_SERVICE_PUBLIC_KEY_ENTRY =
+ "DROP TABLE IF EXISTS " + RecoveryServiceMetadataEntry.TABLE_NAME;
+
RecoverableKeyStoreDbHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
@@ -48,12 +80,14 @@
public void onCreate(SQLiteDatabase db) {
db.execSQL(SQL_CREATE_KEYS_ENTRY);
db.execSQL(SQL_CREATE_USER_METADATA_ENTRY);
+ db.execSQL(SQL_CREATE_RECOVERY_SERVICE_PUBLIC_KEY_ENTRY);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL(SQL_DELETE_KEYS_ENTRY);
db.execSQL(SQL_DELETE_USER_METADATA_ENTRY);
+ db.execSQL(SQL_DELETE_RECOVERY_SERVICE_PUBLIC_KEY_ENTRY);
onCreate(db);
}
}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverySnapshotStorage.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverySnapshotStorage.java
new file mode 100644
index 0000000..d1a1629
--- /dev/null
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverySnapshotStorage.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.locksettings.recoverablekeystore.storage;
+
+import android.annotation.Nullable;
+import android.security.recoverablekeystore.KeyStoreRecoveryData;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+
+/**
+ * In-memory storage for recovery snapshots.
+ *
+ * <p>Recovery snapshots are generated after a successful screen unlock. They are only generated if
+ * the recoverable keystore has been mutated since the previous snapshot. This class stores only the
+ * latest snapshot for each user.
+ *
+ * <p>This class is thread-safe. It is used both on the service thread and the
+ * {@link com.android.server.locksettings.recoverablekeystore.KeySyncTask} thread.
+ */
+public class RecoverySnapshotStorage {
+ @GuardedBy("this")
+ private final SparseArray<KeyStoreRecoveryData> mSnapshotByUserId = new SparseArray<>();
+
+ /**
+ * Sets the latest {@code snapshot} for the user {@code userId}.
+ */
+ public synchronized void put(int userId, KeyStoreRecoveryData snapshot) {
+ mSnapshotByUserId.put(userId, snapshot);
+ }
+
+ /**
+ * Returns the latest snapshot for user {@code userId}, or null if none exists.
+ */
+ @Nullable
+ public synchronized KeyStoreRecoveryData get(int userId) {
+ return mSnapshotByUserId.get(userId);
+ }
+
+ /**
+ * Removes any (if any) snapshot associated with user {@code userId}.
+ */
+ public synchronized void remove(int userId) {
+ mSnapshotByUserId.remove(userId);
+ }
+}
diff --git a/services/core/java/com/android/server/net/watchlist/NetworkWatchlistService.java b/services/core/java/com/android/server/net/watchlist/NetworkWatchlistService.java
index 171703a..f35e6ec 100644
--- a/services/core/java/com/android/server/net/watchlist/NetworkWatchlistService.java
+++ b/services/core/java/com/android/server/net/watchlist/NetworkWatchlistService.java
@@ -33,6 +33,7 @@
import android.util.Slog;
import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.DumpUtils;
import com.android.internal.net.INetworkWatchlistManager;
@@ -92,6 +93,7 @@
}
}
+ @GuardedBy("mLoggingSwitchLock")
private volatile boolean mIsLoggingEnabled = false;
private final Object mLoggingSwitchLock = new Object();
@@ -220,36 +222,11 @@
}
}
- /**
- * Set a new network watchlist.
- * This method should be called by ConfigUpdater only.
- *
- * @return True if network watchlist is updated.
- */
- public boolean setNetworkSecurityWatchlist(List<byte[]> domainsCrc32Digests,
- List<byte[]> domainsSha256Digests,
- List<byte[]> ipAddressesCrc32Digests,
- List<byte[]> ipAddressesSha256Digests) {
- Slog.i(TAG, "Setting network watchlist");
- if (domainsCrc32Digests == null || domainsSha256Digests == null
- || ipAddressesCrc32Digests == null || ipAddressesSha256Digests == null) {
- Slog.e(TAG, "Parameters cannot be null");
- return false;
- }
- if (domainsCrc32Digests.size() != domainsSha256Digests.size()
- || ipAddressesCrc32Digests.size() != ipAddressesSha256Digests.size()) {
- Slog.e(TAG, "Must need to have the same number of CRC32 and SHA256 digests");
- return false;
- }
- if (domainsSha256Digests.size() + ipAddressesSha256Digests.size()
- > MAX_NUM_OF_WATCHLIST_DIGESTS) {
- Slog.e(TAG, "Total watchlist size cannot exceed " + MAX_NUM_OF_WATCHLIST_DIGESTS);
- return false;
- }
- mSettings.writeSettingsToDisk(domainsCrc32Digests, domainsSha256Digests,
- ipAddressesCrc32Digests, ipAddressesSha256Digests);
- Slog.i(TAG, "Set network watchlist: Success");
- return true;
+ @Override
+ public void reloadWatchlist() throws RemoteException {
+ enforceWatchlistLoggingPermission();
+ Slog.i(TAG, "Reloading watchlist");
+ mSettings.reloadSettings();
}
@Override
diff --git a/services/core/java/com/android/server/net/watchlist/WatchlistReportDbHelper.java b/services/core/java/com/android/server/net/watchlist/WatchlistReportDbHelper.java
index f48463f..838aa53 100644
--- a/services/core/java/com/android/server/net/watchlist/WatchlistReportDbHelper.java
+++ b/services/core/java/com/android/server/net/watchlist/WatchlistReportDbHelper.java
@@ -21,10 +21,12 @@
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
+import android.os.Environment;
import android.util.Pair;
import com.android.internal.util.HexDump;
+import java.io.File;
import java.util.ArrayList;
import java.util.GregorianCalendar;
import java.util.HashMap;
@@ -83,9 +85,12 @@
HashMap<String, String> appDigestCNCList;
}
+ static File getSystemWatchlistDbFile() {
+ return new File(Environment.getDataSystemDirectory(), NAME);
+ }
+
private WatchlistReportDbHelper(Context context) {
- super(context, WatchlistSettings.getSystemWatchlistFile(NAME).getAbsolutePath(),
- null, VERSION);
+ super(context, getSystemWatchlistDbFile().getAbsolutePath(), null, VERSION);
// Memory optimization - close idle connections after 30s of inactivity
setIdleConnectionTimeout(IDLE_CONNECTION_TIMEOUT_MS);
}
diff --git a/services/core/java/com/android/server/net/watchlist/WatchlistSettings.java b/services/core/java/com/android/server/net/watchlist/WatchlistSettings.java
index c50f0d5..70002ea 100644
--- a/services/core/java/com/android/server/net/watchlist/WatchlistSettings.java
+++ b/services/core/java/com/android/server/net/watchlist/WatchlistSettings.java
@@ -19,8 +19,10 @@
import android.os.Environment;
import android.util.AtomicFile;
import android.util.Log;
+import android.util.Slog;
import android.util.Xml;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.FastXmlSerializer;
import com.android.internal.util.HexDump;
@@ -51,10 +53,9 @@
class WatchlistSettings {
private static final String TAG = "WatchlistSettings";
- // Settings xml will be stored in /data/system/network_watchlist/watchlist_settings.xml
- static final String SYSTEM_WATCHLIST_DIR = "network_watchlist";
-
- private static final String WATCHLIST_XML_FILE = "watchlist_settings.xml";
+ // Watchlist config that pushed by ConfigUpdater.
+ private static final String NETWORK_WATCHLIST_DB_PATH =
+ "/data/misc/network_watchlist/network_watchlist.xml";
private static class XmlTags {
private static final String WATCHLIST_SETTINGS = "watchlist-settings";
@@ -65,86 +66,74 @@
private static final String HASH = "hash";
}
- private static WatchlistSettings sInstance = new WatchlistSettings();
- private final AtomicFile mXmlFile;
- private final Object mLock = new Object();
- private HarmfulDigests mCrc32DomainDigests = new HarmfulDigests(new ArrayList<>());
- private HarmfulDigests mSha256DomainDigests = new HarmfulDigests(new ArrayList<>());
- private HarmfulDigests mCrc32IpDigests = new HarmfulDigests(new ArrayList<>());
- private HarmfulDigests mSha256IpDigests = new HarmfulDigests(new ArrayList<>());
+ private static class CrcShaDigests {
+ final HarmfulDigests crc32Digests;
+ final HarmfulDigests sha256Digests;
- public static synchronized WatchlistSettings getInstance() {
+ public CrcShaDigests(HarmfulDigests crc32Digests, HarmfulDigests sha256Digests) {
+ this.crc32Digests = crc32Digests;
+ this.sha256Digests = sha256Digests;
+ }
+ }
+
+ private final static WatchlistSettings sInstance = new WatchlistSettings();
+ private final AtomicFile mXmlFile;
+
+ private volatile CrcShaDigests mDomainDigests;
+ private volatile CrcShaDigests mIpDigests;
+
+ public static WatchlistSettings getInstance() {
return sInstance;
}
private WatchlistSettings() {
- this(getSystemWatchlistFile(WATCHLIST_XML_FILE));
+ this(new File(NETWORK_WATCHLIST_DB_PATH));
}
@VisibleForTesting
protected WatchlistSettings(File xmlFile) {
mXmlFile = new AtomicFile(xmlFile);
- readSettingsLocked();
+ reloadSettings();
}
- static File getSystemWatchlistFile(String filename) {
- final File dataSystemDir = Environment.getDataSystemDirectory();
- final File systemWatchlistDir = new File(dataSystemDir, SYSTEM_WATCHLIST_DIR);
- systemWatchlistDir.mkdirs();
- return new File(systemWatchlistDir, filename);
- }
-
- private void readSettingsLocked() {
- synchronized (mLock) {
- FileInputStream stream;
- try {
- stream = mXmlFile.openRead();
- } catch (FileNotFoundException e) {
- Log.i(TAG, "No watchlist settings: " + mXmlFile.getBaseFile().getAbsolutePath());
- return;
- }
+ public void reloadSettings() {
+ try (FileInputStream stream = mXmlFile.openRead()){
final List<byte[]> crc32DomainList = new ArrayList<>();
final List<byte[]> sha256DomainList = new ArrayList<>();
final List<byte[]> crc32IpList = new ArrayList<>();
final List<byte[]> sha256IpList = new ArrayList<>();
- try {
- XmlPullParser parser = Xml.newPullParser();
- parser.setInput(stream, StandardCharsets.UTF_8.name());
- parser.nextTag();
- parser.require(XmlPullParser.START_TAG, null, XmlTags.WATCHLIST_SETTINGS);
- while (parser.nextTag() == XmlPullParser.START_TAG) {
- String tagName = parser.getName();
- switch (tagName) {
- case XmlTags.CRC32_DOMAIN:
- parseHash(parser, tagName, crc32DomainList);
- break;
- case XmlTags.CRC32_IP:
- parseHash(parser, tagName, crc32IpList);
- break;
- case XmlTags.SHA256_DOMAIN:
- parseHash(parser, tagName, sha256DomainList);
- break;
- case XmlTags.SHA256_IP:
- parseHash(parser, tagName, sha256IpList);
- break;
- default:
- Log.w(TAG, "Unknown element: " + parser.getName());
- XmlUtils.skipCurrentTag(parser);
- }
- }
- parser.require(XmlPullParser.END_TAG, null, XmlTags.WATCHLIST_SETTINGS);
- writeSettingsToMemory(crc32DomainList, sha256DomainList, crc32IpList, sha256IpList);
- } catch (IllegalStateException | NullPointerException | NumberFormatException |
- XmlPullParserException | IOException | IndexOutOfBoundsException e) {
- Log.w(TAG, "Failed parsing " + e);
- } finally {
- try {
- stream.close();
- } catch (IOException e) {
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(stream, StandardCharsets.UTF_8.name());
+ parser.nextTag();
+ parser.require(XmlPullParser.START_TAG, null, XmlTags.WATCHLIST_SETTINGS);
+ while (parser.nextTag() == XmlPullParser.START_TAG) {
+ String tagName = parser.getName();
+ switch (tagName) {
+ case XmlTags.CRC32_DOMAIN:
+ parseHash(parser, tagName, crc32DomainList);
+ break;
+ case XmlTags.CRC32_IP:
+ parseHash(parser, tagName, crc32IpList);
+ break;
+ case XmlTags.SHA256_DOMAIN:
+ parseHash(parser, tagName, sha256DomainList);
+ break;
+ case XmlTags.SHA256_IP:
+ parseHash(parser, tagName, sha256IpList);
+ break;
+ default:
+ Log.w(TAG, "Unknown element: " + parser.getName());
+ XmlUtils.skipCurrentTag(parser);
}
}
+ parser.require(XmlPullParser.END_TAG, null, XmlTags.WATCHLIST_SETTINGS);
+ writeSettingsToMemory(crc32DomainList, sha256DomainList, crc32IpList, sha256IpList);
+ Log.i(TAG, "Reload watchlist done");
+ } catch (IllegalStateException | NullPointerException | NumberFormatException |
+ XmlPullParserException | IOException | IndexOutOfBoundsException e) {
+ Slog.e(TAG, "Failed parsing xml", e);
}
}
@@ -161,101 +150,61 @@
}
/**
- * Write network watchlist settings to disk.
- * Adb should not use it, should use writeSettingsToMemory directly instead.
- */
- public void writeSettingsToDisk(List<byte[]> newCrc32DomainList,
- List<byte[]> newSha256DomainList,
- List<byte[]> newCrc32IpList,
- List<byte[]> newSha256IpList) {
- synchronized (mLock) {
- FileOutputStream stream;
- try {
- stream = mXmlFile.startWrite();
- } catch (IOException e) {
- Log.w(TAG, "Failed to write display settings: " + e);
- return;
- }
-
- try {
- XmlSerializer out = new FastXmlSerializer();
- out.setOutput(stream, StandardCharsets.UTF_8.name());
- out.startDocument(null, true);
- out.startTag(null, XmlTags.WATCHLIST_SETTINGS);
-
- writeHashSetToXml(out, XmlTags.SHA256_DOMAIN, newSha256DomainList);
- writeHashSetToXml(out, XmlTags.SHA256_IP, newSha256IpList);
- writeHashSetToXml(out, XmlTags.CRC32_DOMAIN, newCrc32DomainList);
- writeHashSetToXml(out, XmlTags.CRC32_IP, newCrc32IpList);
-
- out.endTag(null, XmlTags.WATCHLIST_SETTINGS);
- out.endDocument();
- mXmlFile.finishWrite(stream);
- writeSettingsToMemory(newCrc32DomainList, newSha256DomainList, newCrc32IpList,
- newSha256IpList);
- } catch (IOException e) {
- Log.w(TAG, "Failed to write display settings, restoring backup.", e);
- mXmlFile.failWrite(stream);
- }
- }
- }
-
- /**
* Write network watchlist settings to memory.
*/
public void writeSettingsToMemory(List<byte[]> newCrc32DomainList,
List<byte[]> newSha256DomainList,
List<byte[]> newCrc32IpList,
List<byte[]> newSha256IpList) {
- synchronized (mLock) {
- mCrc32DomainDigests = new HarmfulDigests(newCrc32DomainList);
- mCrc32IpDigests = new HarmfulDigests(newCrc32IpList);
- mSha256DomainDigests = new HarmfulDigests(newSha256DomainList);
- mSha256IpDigests = new HarmfulDigests(newSha256IpList);
- }
- }
-
- private static void writeHashSetToXml(XmlSerializer out, String tagName, List<byte[]> hashSet)
- throws IOException {
- out.startTag(null, tagName);
- for (byte[] hash : hashSet) {
- out.startTag(null, XmlTags.HASH);
- out.text(HexDump.toHexString(hash));
- out.endTag(null, XmlTags.HASH);
- }
- out.endTag(null, tagName);
+ mDomainDigests = new CrcShaDigests(new HarmfulDigests(newCrc32DomainList),
+ new HarmfulDigests(newSha256DomainList));
+ mIpDigests = new CrcShaDigests(new HarmfulDigests(newCrc32IpList),
+ new HarmfulDigests(newSha256IpList));
}
public boolean containsDomain(String domain) {
+ final CrcShaDigests domainDigests = mDomainDigests;
+ if (domainDigests == null) {
+ Slog.wtf(TAG, "domainDigests should not be null");
+ return false;
+ }
// First it does a quick CRC32 check.
final byte[] crc32 = getCrc32(domain);
- if (!mCrc32DomainDigests.contains(crc32)) {
+ if (!domainDigests.crc32Digests.contains(crc32)) {
return false;
}
// Now we do a slow SHA256 check.
final byte[] sha256 = getSha256(domain);
- return mSha256DomainDigests.contains(sha256);
+ return domainDigests.sha256Digests.contains(sha256);
}
public boolean containsIp(String ip) {
+ final CrcShaDigests ipDigests = mIpDigests;
+ if (ipDigests == null) {
+ Slog.wtf(TAG, "ipDigests should not be null");
+ return false;
+ }
// First it does a quick CRC32 check.
final byte[] crc32 = getCrc32(ip);
- if (!mCrc32IpDigests.contains(crc32)) {
+ if (!ipDigests.crc32Digests.contains(crc32)) {
return false;
}
// Now we do a slow SHA256 check.
final byte[] sha256 = getSha256(ip);
- return mSha256IpDigests.contains(sha256);
+ return ipDigests.sha256Digests.contains(sha256);
}
- /** Get CRC32 of a string */
+ /** Get CRC32 of a string
+ *
+ * TODO: Review if we should use CRC32 or other algorithms
+ */
private byte[] getCrc32(String str) {
final CRC32 crc = new CRC32();
crc.update(str.getBytes());
final long tmp = crc.getValue();
- return new byte[]{(byte)(tmp >> 24 & 255), (byte)(tmp >> 16 & 255),
- (byte)(tmp >> 8 & 255), (byte)(tmp & 255)};
+ return new byte[]{(byte) (tmp >> 24 & 255), (byte) (tmp >> 16 & 255),
+ (byte) (tmp >> 8 & 255), (byte) (tmp & 255)};
}
/** Get SHA256 of a string */
@@ -273,12 +222,12 @@
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.println("Domain CRC32 digest list:");
- mCrc32DomainDigests.dump(fd, pw, args);
+ mDomainDigests.crc32Digests.dump(fd, pw, args);
pw.println("Domain SHA256 digest list:");
- mSha256DomainDigests.dump(fd, pw, args);
+ mDomainDigests.sha256Digests.dump(fd, pw, args);
pw.println("Ip CRC32 digest list:");
- mCrc32IpDigests.dump(fd, pw, args);
+ mIpDigests.crc32Digests.dump(fd, pw, args);
pw.println("Ip SHA256 digest list:");
- mSha256IpDigests.dump(fd, pw, args);
+ mIpDigests.sha256Digests.dump(fd, pw, args);
}
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 696d895..1157af4 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -163,10 +163,12 @@
import android.content.pm.PackageInfo;
import android.content.pm.PackageInfoLite;
import android.content.pm.PackageInstaller;
+import android.content.pm.PackageList;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.PackageManager.LegacyPackageDeleteObserver;
import android.content.pm.PackageManager.PackageInfoFlags;
+import android.content.pm.PackageManagerInternal.PackageListObserver;
import android.content.pm.PackageParser;
import android.content.pm.PackageParser.ActivityIntentInfo;
import android.content.pm.PackageParser.Package;
@@ -757,6 +759,9 @@
@GuardedBy("mPackages")
final SparseArray<Map<String, Integer>> mChangedPackagesSequenceNumbers = new SparseArray<>();
+ @GuardedBy("mPackages")
+ final private ArraySet<PackageListObserver> mPackageListObservers = new ArraySet<>();
+
class PackageParserCallback implements PackageParser.Callback {
@Override public final boolean hasFeature(String feature) {
return PackageManagerService.this.hasSystemFeature(feature, 0);
@@ -2095,6 +2100,10 @@
}
}
+ if (allNewUsers && !update) {
+ notifyPackageAdded(packageName);
+ }
+
// Log current value of "unknown sources" setting
EventLog.writeEvent(EventLogTags.UNKNOWN_SOURCES_ENABLED,
getUnknownSourcesSettings());
@@ -12983,6 +12992,34 @@
});
}
+ @Override
+ public void notifyPackageAdded(String packageName) {
+ final PackageListObserver[] observers;
+ synchronized (mPackages) {
+ if (mPackageListObservers.size() == 0) {
+ return;
+ }
+ observers = (PackageListObserver[]) mPackageListObservers.toArray();
+ }
+ for (int i = observers.length - 1; i >= 0; --i) {
+ observers[i].onPackageAdded(packageName);
+ }
+ }
+
+ @Override
+ public void notifyPackageRemoved(String packageName) {
+ final PackageListObserver[] observers;
+ synchronized (mPackages) {
+ if (mPackageListObservers.size() == 0) {
+ return;
+ }
+ observers = (PackageListObserver[]) mPackageListObservers.toArray();
+ }
+ for (int i = observers.length - 1; i >= 0; --i) {
+ observers[i].onPackageRemoved(packageName);
+ }
+ }
+
/**
* Sends a broadcast for the given action.
* <p>If {@code isInstantApp} is {@code true}, then the broadcast is protected with
@@ -17640,6 +17677,7 @@
removedPackage, extras,
Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND,
null, null, broadcastUsers, instantUserIds);
+ packageSender.notifyPackageRemoved(removedPackage);
}
}
if (removedAppId >= 0) {
@@ -20395,10 +20433,9 @@
}
}
sUserManager.systemReady();
-
// If we upgraded grant all default permissions before kicking off.
for (int userId : grantPermissionsUserIds) {
- mDefaultPermissionPolicy.grantDefaultPermissions(mPackages.values(), userId);
+ mDefaultPermissionPolicy.grantDefaultPermissions(userId);
}
if (grantPermissionsUserIds == EMPTY_INT_ARRAY) {
@@ -22445,8 +22482,8 @@
}
void onNewUserCreated(final int userId) {
+ mDefaultPermissionPolicy.grantDefaultPermissions(userId);
synchronized(mPackages) {
- mDefaultPermissionPolicy.grantDefaultPermissions(mPackages.values(), userId);
// If permission review for legacy apps is required, we represent
// dagerous permissions for such apps as always granted runtime
// permissions to keep per user flag state whether review is needed.
@@ -22933,6 +22970,29 @@
}
@Override
+ public PackageList getPackageList(PackageListObserver observer) {
+ synchronized (mPackages) {
+ final int N = mPackages.size();
+ final ArrayList<String> list = new ArrayList<>(N);
+ for (int i = 0; i < N; i++) {
+ list.add(mPackages.keyAt(i));
+ }
+ final PackageList packageList = new PackageList(list, observer);
+ if (observer != null) {
+ mPackageListObservers.add(packageList);
+ }
+ return packageList;
+ }
+ }
+
+ @Override
+ public void removePackageListObserver(PackageListObserver observer) {
+ synchronized (mPackages) {
+ mPackageListObservers.remove(observer);
+ }
+ }
+
+ @Override
public PackageParser.Package getDisabledPackage(String packageName) {
synchronized (mPackages) {
final PackageSetting ps = mSettings.getDisabledSystemPkgLPr(packageName);
@@ -23594,4 +23654,6 @@
final IIntentReceiver finishedReceiver, final int[] userIds, int[] instantUserIds);
void sendPackageAddedForNewUsers(String packageName, boolean sendBootCompleted,
boolean includeStopped, int appId, int[] userIds, int[] instantUserIds);
+ void notifyPackageAdded(String packageName);
+ void notifyPackageRemoved(String packageName);
}
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 768eb8f..c3dce31 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -27,7 +27,6 @@
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.ActivityManagerNative;
-import android.app.AppOpsManager;
import android.app.IActivityManager;
import android.app.IStopUserCallback;
import android.app.KeyguardManager;
@@ -795,12 +794,7 @@
"target should only be specified when we are disabling quiet mode.");
}
- if (!isAllowedToSetWorkMode(callingPackage, Binder.getCallingUid())) {
- throw new SecurityException("Not allowed to call trySetQuietModeEnabled, "
- + "caller is foreground default launcher "
- + "nor with MANAGE_USERS/MODIFY_QUIET_MODE permission");
- }
-
+ ensureCanModifyQuietMode(callingPackage, Binder.getCallingUid(), target != null);
final long identity = Binder.clearCallingIdentity();
try {
if (enableQuietMode) {
@@ -824,35 +818,44 @@
}
/**
- * An app can modify quiet mode if the caller meets one of the condition:
+ * The caller can modify quiet mode if it meets one of these conditions:
* <ul>
* <li>Has system UID or root UID</li>
* <li>Has {@link Manifest.permission#MODIFY_QUIET_MODE}</li>
* <li>Has {@link Manifest.permission#MANAGE_USERS}</li>
* </ul>
+ * <p>
+ * If caller wants to start an intent after disabling the quiet mode, it must has
+ * {@link Manifest.permission#MANAGE_USERS}.
*/
- private boolean isAllowedToSetWorkMode(String callingPackage, int callingUid) {
+ private void ensureCanModifyQuietMode(String callingPackage, int callingUid,
+ boolean startIntent) {
if (hasManageUsersPermission()) {
- return true;
+ return;
}
-
+ if (startIntent) {
+ throw new SecurityException("MANAGE_USERS permission is required to start intent "
+ + "after disabling quiet mode.");
+ }
final boolean hasModifyQuietModePermission = ActivityManager.checkComponentPermission(
Manifest.permission.MODIFY_QUIET_MODE,
callingUid, -1, true) == PackageManager.PERMISSION_GRANTED;
if (hasModifyQuietModePermission) {
- return true;
+ return;
}
+ verifyCallingPackage(callingPackage, callingUid);
final ShortcutServiceInternal shortcutInternal =
LocalServices.getService(ShortcutServiceInternal.class);
if (shortcutInternal != null) {
boolean isForegroundLauncher =
shortcutInternal.isForegroundDefaultLauncher(callingPackage, callingUid);
if (isForegroundLauncher) {
- return true;
+ return;
}
}
- return false;
+ throw new SecurityException("Can't modify quiet mode, caller is neither foreground "
+ + "default launcher nor has MANAGE_USERS/MODIFY_QUIET_MODE permission");
}
private void setQuietModeEnabled(
@@ -3932,4 +3935,16 @@
return false;
}
}
+
+ /**
+ * Check if the calling package name matches with the calling UID, throw
+ * {@link SecurityException} if not.
+ */
+ private void verifyCallingPackage(String callingPackage, int callingUid) {
+ int packageUid = mPm.getPackageUid(callingPackage, 0, UserHandle.getUserId(callingUid));
+ if (packageUid != callingUid) {
+ throw new SecurityException("Specified package " + callingPackage
+ + " does not match the calling uid " + callingUid);
+ }
+ }
}
diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
index 01f3c57..d38dc9a 100644
--- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -30,6 +30,7 @@
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
+import android.content.pm.PackageList;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.PackageParser;
@@ -252,11 +253,11 @@
}
}
- public void grantDefaultPermissions(Collection<PackageParser.Package> packages, int userId) {
+ public void grantDefaultPermissions(int userId) {
if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_EMBEDDED, 0)) {
- grantAllRuntimePermissions(packages, userId);
+ grantAllRuntimePermissions(userId);
} else {
- grantPermissionsToSysComponentsAndPrivApps(packages, userId);
+ grantPermissionsToSysComponentsAndPrivApps(userId);
grantDefaultSystemHandlerPermissions(userId);
grantDefaultPermissionExceptions(userId);
}
@@ -278,10 +279,14 @@
}
}
- private void grantAllRuntimePermissions(
- Collection<PackageParser.Package> packages, int userId) {
+ private void grantAllRuntimePermissions(int userId) {
Log.i(TAG, "Granting all runtime permissions for user " + userId);
- for (PackageParser.Package pkg : packages) {
+ final PackageList packageList = mServiceInternal.getPackageList();
+ for (String packageName : packageList.getPackageNames()) {
+ final PackageParser.Package pkg = mServiceInternal.getPackage(packageName);
+ if (pkg == null) {
+ continue;
+ }
grantRuntimePermissionsForPackage(userId, pkg);
}
}
@@ -290,10 +295,14 @@
mHandler.sendEmptyMessage(MSG_READ_DEFAULT_PERMISSION_EXCEPTIONS);
}
- private void grantPermissionsToSysComponentsAndPrivApps(
- Collection<PackageParser.Package> packages, int userId) {
+ private void grantPermissionsToSysComponentsAndPrivApps(int userId) {
Log.i(TAG, "Granting permissions to platform components for user " + userId);
- for (PackageParser.Package pkg : packages) {
+ final PackageList packageList = mServiceInternal.getPackageList();
+ for (String packageName : packageList.getPackageNames()) {
+ final PackageParser.Package pkg = mServiceInternal.getPackage(packageName);
+ if (pkg == null) {
+ continue;
+ }
if (!isSysComponentOrPersistentPlatformSignedPrivApp(pkg)
|| !doesPackageSupportRuntimePermissions(pkg)
|| pkg.requestedPermissions.isEmpty()) {
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 9752a57..076c0e4 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -2126,14 +2126,14 @@
mWindowManagerInternal.registerAppTransitionListener(new AppTransitionListener() {
@Override
public int onAppTransitionStartingLocked(int transit, IBinder openToken,
- IBinder closeToken,
- Animation openAnimation, Animation closeAnimation) {
- return handleStartTransitionForKeyguardLw(transit, openAnimation);
+ IBinder closeToken, long duration, long statusBarAnimationStartTime,
+ long statusBarAnimationDuration) {
+ return handleStartTransitionForKeyguardLw(transit, duration);
}
@Override
public void onAppTransitionCancelledLocked(int transit) {
- handleStartTransitionForKeyguardLw(transit, null /* transit */);
+ handleStartTransitionForKeyguardLw(transit, 0 /* duration */);
}
});
mKeyguardDelegate = new KeyguardServiceDelegate(mContext,
@@ -3989,7 +3989,7 @@
}
}
- private int handleStartTransitionForKeyguardLw(int transit, @Nullable Animation anim) {
+ private int handleStartTransitionForKeyguardLw(int transit, long duration) {
if (mKeyguardOccludedChanged) {
if (DEBUG_KEYGUARD) Slog.d(TAG, "transition/occluded changed occluded="
+ mPendingKeyguardOccluded);
@@ -4000,13 +4000,7 @@
}
if (AppTransition.isKeyguardGoingAwayTransit(transit)) {
if (DEBUG_KEYGUARD) Slog.d(TAG, "Starting keyguard exit animation");
- final long startTime = anim != null
- ? SystemClock.uptimeMillis() + anim.getStartOffset()
- : SystemClock.uptimeMillis();
- final long duration = anim != null
- ? anim.getDuration()
- : 0;
- startKeyguardExitAnimation(startTime, duration);
+ startKeyguardExitAnimation(SystemClock.uptimeMillis(), duration);
}
return 0;
}
diff --git a/services/core/java/com/android/server/policy/StatusBarController.java b/services/core/java/com/android/server/policy/StatusBarController.java
index af7e91c..e6e4d7f 100644
--- a/services/core/java/com/android/server/policy/StatusBarController.java
+++ b/services/core/java/com/android/server/policy/StatusBarController.java
@@ -37,8 +37,6 @@
*/
public class StatusBarController extends BarController {
- private static final long TRANSITION_DURATION = 120L;
-
private final AppTransitionListener mAppTransitionListener
= new AppTransitionListener() {
@@ -57,17 +55,15 @@
@Override
public int onAppTransitionStartingLocked(int transit, IBinder openToken,
- IBinder closeToken, final Animation openAnimation, final Animation closeAnimation) {
+ IBinder closeToken, long duration, long statusBarAnimationStartTime,
+ long statusBarAnimationDuration) {
mHandler.post(new Runnable() {
@Override
public void run() {
StatusBarManagerInternal statusbar = getStatusBarInternal();
if (statusbar != null) {
- long startTime = calculateStatusBarTransitionStartTime(openAnimation,
- closeAnimation);
- long duration = closeAnimation != null || openAnimation != null
- ? TRANSITION_DURATION : 0;
- statusbar.appTransitionStarting(startTime, duration);
+ statusbar.appTransitionStarting(statusBarAnimationStartTime,
+ statusBarAnimationDuration);
}
}
});
@@ -128,72 +124,4 @@
public AppTransitionListener getAppTransitionListener() {
return mAppTransitionListener;
}
-
- /**
- * For a given app transition with {@code openAnimation} and {@code closeAnimation}, this
- * calculates the timings for the corresponding status bar transition.
- *
- * @return the desired start time of the status bar transition, in uptime millis
- */
- private static long calculateStatusBarTransitionStartTime(Animation openAnimation,
- Animation closeAnimation) {
- if (openAnimation != null && closeAnimation != null) {
- TranslateAnimation openTranslateAnimation = findTranslateAnimation(openAnimation);
- TranslateAnimation closeTranslateAnimation = findTranslateAnimation(closeAnimation);
- if (openTranslateAnimation != null) {
-
- // Some interpolators are extremely quickly mostly finished, but not completely. For
- // our purposes, we need to find the fraction for which ther interpolator is mostly
- // there, and use that value for the calculation.
- float t = findAlmostThereFraction(openTranslateAnimation.getInterpolator());
- return SystemClock.uptimeMillis()
- + openTranslateAnimation.getStartOffset()
- + (long)(openTranslateAnimation.getDuration()*t) - TRANSITION_DURATION;
- } else if (closeTranslateAnimation != null) {
- return SystemClock.uptimeMillis();
- } else {
- return SystemClock.uptimeMillis();
- }
- } else {
- return SystemClock.uptimeMillis();
- }
- }
-
- /**
- * Tries to find a {@link TranslateAnimation} inside the {@code animation}.
- *
- * @return the found animation, {@code null} otherwise
- */
- private static TranslateAnimation findTranslateAnimation(Animation animation) {
- if (animation instanceof TranslateAnimation) {
- return (TranslateAnimation) animation;
- } else if (animation instanceof AnimationSet) {
- AnimationSet set = (AnimationSet) animation;
- for (int i = 0; i < set.getAnimations().size(); i++) {
- Animation a = set.getAnimations().get(i);
- if (a instanceof TranslateAnimation) {
- return (TranslateAnimation) a;
- }
- }
- }
- return null;
- }
-
- /**
- * Binary searches for a {@code t} such that there exists a {@code -0.01 < eps < 0.01} for which
- * {@code interpolator(t + eps) > 0.99}.
- */
- private static float findAlmostThereFraction(Interpolator interpolator) {
- float val = 0.5f;
- float adj = 0.25f;
- while (adj >= 0.01f) {
- if (interpolator.getInterpolation(val) < 0.99f) {
- val += adj;
- } else {
- val -= adj;
- }
- adj /= 2;
- }
- return val;
- }
}
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index c78d1e5..86b22bb 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -57,6 +57,7 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.os.WorkSource;
+import android.os.WorkSource.WorkChain;
import android.provider.Settings;
import android.provider.Settings.SettingNotFoundException;
import android.service.dreams.DreamManagerInternal;
@@ -1976,6 +1977,16 @@
return true;
}
}
+
+ final ArrayList<WorkChain> workChains = wakeLock.mWorkSource.getWorkChains();
+ if (workChains != null) {
+ for (int k = 0; k < workChains.size(); k++) {
+ final int uid = workChains.get(k).getAttributionUid();
+ if (userId == UserHandle.getUserId(uid)) {
+ return true;
+ }
+ }
+ }
}
return userId == UserHandle.getUserId(wakeLock.mOwnerUid);
}
@@ -4328,7 +4339,7 @@
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.DEVICE_POWER, null);
}
- if (ws != null && ws.size() != 0) {
+ if (ws != null && !ws.isEmpty()) {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.UPDATE_DEVICE_STATS, null);
} else {
@@ -4383,7 +4394,7 @@
}
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.WAKE_LOCK, null);
- if (ws != null && ws.size() != 0) {
+ if (ws != null && !ws.isEmpty()) {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.UPDATE_DEVICE_STATS, null);
} else {
diff --git a/services/core/java/com/android/server/updates/NetworkWatchlistInstallReceiver.java b/services/core/java/com/android/server/updates/NetworkWatchlistInstallReceiver.java
new file mode 100644
index 0000000..3b7ddc2
--- /dev/null
+++ b/services/core/java/com/android/server/updates/NetworkWatchlistInstallReceiver.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.updates;
+
+import android.content.Context;
+import android.content.Intent;
+import android.net.NetworkWatchlistManager;
+import android.os.RemoteException;
+import android.util.Slog;
+
+public class NetworkWatchlistInstallReceiver extends ConfigUpdateInstallReceiver {
+
+ public NetworkWatchlistInstallReceiver() {
+ super("/data/misc/network_watchlist/", "network_watchlist.xml", "metadata/", "version");
+ }
+
+ @Override
+ protected void postInstall(Context context, Intent intent) {
+ try {
+ context.getSystemService(NetworkWatchlistManager.class).reloadWatchlist();
+ } catch (Exception e) {
+ // Network Watchlist is not available
+ Slog.wtf("NetworkWatchlistInstallReceiver", "Unable to reload watchlist");
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/wm/AnimationAdapter.java b/services/core/java/com/android/server/wm/AnimationAdapter.java
index 84d47b4..ed4543e 100644
--- a/services/core/java/com/android/server/wm/AnimationAdapter.java
+++ b/services/core/java/com/android/server/wm/AnimationAdapter.java
@@ -30,6 +30,8 @@
*/
interface AnimationAdapter {
+ long STATUS_BAR_TRANSITION_DURATION = 120L;
+
/**
* @return Whether we should detach the wallpaper during the animation.
* @see Animation#setDetachWallpaper
@@ -66,4 +68,13 @@
* @return The approximate duration of the animation, in milliseconds.
*/
long getDurationHint();
+
+ /**
+ * If this animation is run as an app opening animation, this calculates the start time for all
+ * status bar transitions that happen as part of the app opening animation, which will be
+ * forwarded to SystemUI.
+ *
+ * @return the desired start time of the status bar transition, in uptime millis
+ */
+ long getStatusBarTransitionsStartTime();
}
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index 4a74e29..2ac7583 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -38,7 +38,6 @@
import static com.android.internal.R.styleable.WindowAnimation_wallpaperIntraOpenExitAnimation;
import static com.android.internal.R.styleable.WindowAnimation_wallpaperOpenEnterAnimation;
import static com.android.internal.R.styleable.WindowAnimation_wallpaperOpenExitAnimation;
-import static com.android.server.wm.AppWindowAnimator.PROLONG_ANIMATION_AT_START;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_APP_TRANSITIONS;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -63,6 +62,7 @@
import android.os.IBinder;
import android.os.IRemoteCallback;
import android.os.RemoteException;
+import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.util.ArraySet;
@@ -415,17 +415,23 @@
* @return bit-map of WindowManagerPolicy#FINISH_LAYOUT_REDO_* to indicate whether another
* layout pass needs to be done
*/
- int goodToGo(int transit, AppWindowAnimator topOpeningAppAnimator,
- AppWindowAnimator topClosingAppAnimator, ArraySet<AppWindowToken> openingApps,
+ int goodToGo(int transit, AppWindowToken topOpeningApp,
+ AppWindowToken topClosingApp, ArraySet<AppWindowToken> openingApps,
ArraySet<AppWindowToken> closingApps) {
mNextAppTransition = TRANSIT_UNSET;
mNextAppTransitionFlags = 0;
setAppTransitionState(APP_STATE_RUNNING);
+ final AnimationAdapter topOpeningAnim = topOpeningApp != null
+ ? topOpeningApp.getAnimation()
+ : null;
int redoLayout = notifyAppTransitionStartingLocked(transit,
- topOpeningAppAnimator != null ? topOpeningAppAnimator.mAppToken.token : null,
- topClosingAppAnimator != null ? topClosingAppAnimator.mAppToken.token : null,
- topOpeningAppAnimator != null ? topOpeningAppAnimator.animation : null,
- topClosingAppAnimator != null ? topClosingAppAnimator.animation : null);
+ topOpeningApp != null ? topOpeningApp.token : null,
+ topClosingApp != null ? topClosingApp.token : null,
+ topOpeningAnim != null ? topOpeningAnim.getDurationHint() : 0,
+ topOpeningAnim != null
+ ? topOpeningAnim.getStatusBarTransitionsStartTime()
+ : SystemClock.uptimeMillis(),
+ AnimationAdapter.STATUS_BAR_TRANSITION_DURATION);
mService.getDefaultDisplayContentLocked().getDockedDividerController()
.notifyAppTransitionStarting(openingApps, transit);
@@ -433,8 +439,8 @@
// ended it already then we don't need to wait.
if (transit == TRANSIT_DOCK_TASK_FROM_RECENTS && !mProlongedAnimationsEnded) {
for (int i = openingApps.size() - 1; i >= 0; i--) {
- final AppWindowAnimator appAnimator = openingApps.valueAt(i).mAppAnimator;
- appAnimator.startProlongAnimation(PROLONG_ANIMATION_AT_START);
+ final AppWindowToken app = openingApps.valueAt(i);
+ app.startDelayingAnimationStart();
}
}
return redoLayout;
@@ -504,11 +510,12 @@
}
private int notifyAppTransitionStartingLocked(int transit, IBinder openToken,
- IBinder closeToken, Animation openAnimation, Animation closeAnimation) {
+ IBinder closeToken, long duration, long statusBarAnimationStartTime,
+ long statusBarAnimationDuration) {
int redoLayout = 0;
for (int i = 0; i < mListeners.size(); i++) {
redoLayout |= mListeners.get(i).onAppTransitionStartingLocked(transit, openToken,
- closeToken, openAnimation, closeAnimation);
+ closeToken, duration, statusBarAnimationStartTime, statusBarAnimationDuration);
}
return redoLayout;
}
@@ -1885,9 +1892,6 @@
mNextAppTransitionFutureCallback, null /* finishedCallback */,
mNextAppTransitionScaleUp);
mNextAppTransitionFutureCallback = null;
- if (specs != null) {
- mService.prolongAnimationsFromSpecs(specs, mNextAppTransitionScaleUp);
- }
}
mService.requestTraversal();
});
diff --git a/services/core/java/com/android/server/wm/AppWindowAnimator.java b/services/core/java/com/android/server/wm/AppWindowAnimator.java
deleted file mode 100644
index 5c1d5b2..0000000
--- a/services/core/java/com/android/server/wm/AppWindowAnimator.java
+++ /dev/null
@@ -1,495 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm;
-
-import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM;
-import static com.android.server.wm.AppTransition.TRANSIT_UNSET;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYERS;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_VISIBILITY;
-import static com.android.server.wm.WindowManagerDebugConfig.SHOW_TRANSACTIONS;
-import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
-import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
-import static com.android.server.wm.WindowManagerService.TYPE_LAYER_OFFSET;
-import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_BEFORE_ANIM;
-
-import android.graphics.Matrix;
-import android.util.Slog;
-import android.util.TimeUtils;
-import android.view.Choreographer;
-import android.view.Display;
-import android.view.SurfaceControl;
-import android.view.animation.Animation;
-import android.view.animation.Transformation;
-
-import java.io.PrintWriter;
-import java.util.ArrayList;
-
-public class AppWindowAnimator {
- static final String TAG = TAG_WITH_CLASS_NAME ? "AppWindowAnimator" : TAG_WM;
-
- private static final int PROLONG_ANIMATION_DISABLED = 0;
- static final int PROLONG_ANIMATION_AT_END = 1;
- static final int PROLONG_ANIMATION_AT_START = 2;
-
- final AppWindowToken mAppToken;
- final WindowManagerService mService;
- final WindowAnimator mAnimator;
-
- boolean animating;
- boolean wasAnimating;
- Animation animation;
- boolean hasTransformation;
- final Transformation transformation = new Transformation();
-
- // Have we been asked to have this token keep the screen frozen?
- // Protect with mAnimator.
- boolean freezingScreen;
-
- /**
- * How long we last kept the screen frozen.
- */
- int lastFreezeDuration;
-
- // Offset to the window of all layers in the token, for use by
- // AppWindowToken animations.
- int animLayerAdjustment;
-
- // Propagated from AppWindowToken.allDrawn, to determine when
- // the state changes.
- boolean allDrawn;
-
- // Special surface for thumbnail animation. If deferThumbnailDestruction is enabled, then we
- // will make sure that the thumbnail is destroyed after the other surface is completed. This
- // requires that the duration of the two animations are the same.
- SurfaceControl thumbnail;
- int thumbnailTransactionSeq;
- private int mThumbnailLayer;
-
- Animation thumbnailAnimation;
- final Transformation thumbnailTransformation = new Transformation();
- // This flag indicates that the destruction of the thumbnail surface is synchronized with
- // another animation, so defer the destruction of this thumbnail surface for a single frame
- // after the secondary animation completes.
- boolean deferThumbnailDestruction;
- // This flag is set if the animator has deferThumbnailDestruction set and has reached the final
- // frame of animation. It will extend the animation by one frame and then clean up afterwards.
- boolean deferFinalFrameCleanup;
- // If true when the animation hits the last frame, it will keep running on that last frame.
- // This is used to synchronize animation with Recents and we wait for Recents to tell us to
- // finish or for a new animation be set as fail-safe mechanism.
- private int mProlongAnimation;
- // Whether the prolong animation can be removed when animation is set. The purpose of this is
- // that if recents doesn't tell us to remove the prolonged animation, we will get rid of it
- // when new animation is set.
- private boolean mClearProlongedAnimation;
- private int mTransit;
- private int mTransitFlags;
-
- /** WindowStateAnimator from mAppAnimator.allAppWindows as of last performLayout */
- ArrayList<WindowStateAnimator> mAllAppWinAnimators = new ArrayList<>();
-
- /** True if the current animation was transferred from another AppWindowAnimator.
- * See {@link #transferCurrentAnimation}*/
- boolean usingTransferredAnimation = false;
-
- private boolean mSkipFirstFrame = false;
- private int mStackClip = STACK_CLIP_BEFORE_ANIM;
-
- static final Animation sDummyAnimation = new DummyAnimation();
-
- public AppWindowAnimator(final AppWindowToken atoken, WindowManagerService service) {
- mAppToken = atoken;
- mService = service;
- mAnimator = mService.mAnimator;
- }
-
- public void setAnimation(Animation anim, int width, int height, int parentWidth,
- int parentHeight, boolean skipFirstFrame, int stackClip, int transit,
- int transitFlags) {
- if (WindowManagerService.localLOGV) Slog.v(TAG, "Setting animation in " + mAppToken
- + ": " + anim + " wxh=" + width + "x" + height
- + " hasContentToDisplay=" + mAppToken.hasContentToDisplay());
- animation = anim;
- animating = false;
- if (!anim.isInitialized()) {
- anim.initialize(width, height, parentWidth, parentHeight);
- }
- anim.restrictDuration(WindowManagerService.MAX_ANIMATION_DURATION);
- anim.scaleCurrentDuration(mService.getTransitionAnimationScaleLocked());
- int zorder = anim.getZAdjustment();
- int adj = 0;
- if (zorder == Animation.ZORDER_TOP) {
- adj = TYPE_LAYER_OFFSET;
- } else if (zorder == Animation.ZORDER_BOTTOM) {
- adj = -TYPE_LAYER_OFFSET;
- }
-
- if (animLayerAdjustment != adj) {
- animLayerAdjustment = adj;
- updateLayers();
- }
- // Start out animation gone if window is gone, or visible if window is visible.
- transformation.clear();
- transformation.setAlpha(mAppToken.isVisible() ? 1 : 0);
- hasTransformation = true;
- mStackClip = stackClip;
-
- mSkipFirstFrame = skipFirstFrame;
- mTransit = transit;
- mTransitFlags = transitFlags;
-
- if (!mAppToken.fillsParent()) {
- anim.setBackgroundColor(0);
- }
- if (mClearProlongedAnimation) {
- mProlongAnimation = PROLONG_ANIMATION_DISABLED;
- } else {
- mClearProlongedAnimation = true;
- }
- }
-
- public void setDummyAnimation() {
- if (WindowManagerService.localLOGV) Slog.v(TAG, "Setting dummy animation in " + mAppToken
- + " hasContentToDisplay=" + mAppToken.hasContentToDisplay());
- animation = sDummyAnimation;
- hasTransformation = true;
- transformation.clear();
- transformation.setAlpha(mAppToken.isVisible() ? 1 : 0);
- }
-
- void setNullAnimation() {
- animation = null;
- usingTransferredAnimation = false;
- }
-
- public void clearAnimation() {
- if (animation != null) {
- animating = true;
- }
- clearThumbnail();
- setNullAnimation();
- if (mAppToken.deferClearAllDrawn) {
- mAppToken.clearAllDrawn();
- }
- mStackClip = STACK_CLIP_BEFORE_ANIM;
- mTransit = TRANSIT_UNSET;
- mTransitFlags = 0;
- }
-
- public boolean isAnimating() {
- return animation != null || mAppToken.inPendingTransaction;
- }
-
- /**
- * @return whether an animation is about to start, i.e. the animation is set already but we
- * haven't processed the first frame yet.
- */
- boolean isAnimationStarting() {
- return animation != null && !animating;
- }
-
- public int getTransit() {
- return mTransit;
- }
-
- int getTransitFlags() {
- return mTransitFlags;
- }
-
- public void clearThumbnail() {
- if (thumbnail != null) {
- thumbnail.hide();
- mService.mWindowPlacerLocked.destroyAfterTransaction(thumbnail);
- thumbnail = null;
- }
- deferThumbnailDestruction = false;
- }
-
- int getStackClip() {
- return mStackClip;
- }
-
- void transferCurrentAnimation(
- AppWindowAnimator toAppAnimator, WindowStateAnimator transferWinAnimator) {
-
- if (animation != null) {
- toAppAnimator.animation = animation;
- toAppAnimator.animating = animating;
- toAppAnimator.animLayerAdjustment = animLayerAdjustment;
- setNullAnimation();
- animLayerAdjustment = 0;
- toAppAnimator.updateLayers();
- updateLayers();
- toAppAnimator.usingTransferredAnimation = true;
- toAppAnimator.mTransit = mTransit;
- }
- if (transferWinAnimator != null) {
- mAllAppWinAnimators.remove(transferWinAnimator);
- toAppAnimator.mAllAppWinAnimators.add(transferWinAnimator);
- toAppAnimator.hasTransformation = transferWinAnimator.mAppAnimator.hasTransformation;
- if (toAppAnimator.hasTransformation) {
- toAppAnimator.transformation.set(transferWinAnimator.mAppAnimator.transformation);
- } else {
- toAppAnimator.transformation.clear();
- }
- transferWinAnimator.mAppAnimator = toAppAnimator;
- }
- }
-
- private void updateLayers() {
- mAppToken.getDisplayContent().assignWindowLayers(false /* relayoutNeeded */);
- }
-
- private void stepThumbnailAnimation(long currentTime) {
- thumbnailTransformation.clear();
- final long animationFrameTime = getAnimationFrameTime(thumbnailAnimation, currentTime);
- thumbnailAnimation.getTransformation(animationFrameTime, thumbnailTransformation);
-
- ScreenRotationAnimation screenRotationAnimation =
- mAnimator.getScreenRotationAnimationLocked(Display.DEFAULT_DISPLAY);
- final boolean screenAnimation = screenRotationAnimation != null
- && screenRotationAnimation.isAnimating();
- if (screenAnimation) {
- thumbnailTransformation.postCompose(screenRotationAnimation.getEnterTransformation());
- }
- // cache often used attributes locally
- final float tmpFloats[] = mService.mTmpFloats;
- thumbnailTransformation.getMatrix().getValues(tmpFloats);
- if (SHOW_TRANSACTIONS) WindowManagerService.logSurface(thumbnail,
- "thumbnail", "POS " + tmpFloats[Matrix.MTRANS_X]
- + ", " + tmpFloats[Matrix.MTRANS_Y]);
- thumbnail.setPosition(tmpFloats[Matrix.MTRANS_X], tmpFloats[Matrix.MTRANS_Y]);
- if (SHOW_TRANSACTIONS) WindowManagerService.logSurface(thumbnail,
- "thumbnail", "alpha=" + thumbnailTransformation.getAlpha()
- + " layer=" + mThumbnailLayer
- + " matrix=[" + tmpFloats[Matrix.MSCALE_X]
- + "," + tmpFloats[Matrix.MSKEW_Y]
- + "][" + tmpFloats[Matrix.MSKEW_X]
- + "," + tmpFloats[Matrix.MSCALE_Y] + "]");
- thumbnail.setAlpha(thumbnailTransformation.getAlpha());
- thumbnail.setMatrix(tmpFloats[Matrix.MSCALE_X], tmpFloats[Matrix.MSKEW_Y],
- tmpFloats[Matrix.MSKEW_X], tmpFloats[Matrix.MSCALE_Y]);
- thumbnail.setWindowCrop(thumbnailTransformation.getClipRect());
- }
-
- /**
- * Sometimes we need to synchronize the first frame of animation with some external event, e.g.
- * Recents hiding some of its content. To achieve this, we prolong the start of the animaiton
- * and keep producing the first frame of the animation.
- */
- private long getAnimationFrameTime(Animation animation, long currentTime) {
- if (mProlongAnimation == PROLONG_ANIMATION_AT_START) {
- animation.setStartTime(currentTime);
- return currentTime + 1;
- }
- return currentTime;
- }
-
- private boolean stepAnimation(long currentTime) {
- if (animation == null) {
- return false;
- }
- transformation.clear();
- final long animationFrameTime = getAnimationFrameTime(animation, currentTime);
- boolean hasMoreFrames = animation.getTransformation(animationFrameTime, transformation);
- if (!hasMoreFrames) {
- if (deferThumbnailDestruction && !deferFinalFrameCleanup) {
- // We are deferring the thumbnail destruction, so extend the animation for one more
- // (dummy) frame before we clean up
- deferFinalFrameCleanup = true;
- hasMoreFrames = true;
- } else {
- if (false && DEBUG_ANIM) Slog.v(TAG,
- "Stepped animation in " + mAppToken + ": more=" + hasMoreFrames +
- ", xform=" + transformation + ", mProlongAnimation=" + mProlongAnimation);
- deferFinalFrameCleanup = false;
- if (mProlongAnimation == PROLONG_ANIMATION_AT_END) {
- hasMoreFrames = true;
- } else {
- setNullAnimation();
- clearThumbnail();
- if (DEBUG_ANIM) Slog.v(TAG, "Finished animation in " + mAppToken + " @ "
- + currentTime);
- }
- }
- }
- hasTransformation = hasMoreFrames;
- return hasMoreFrames;
- }
-
- private long getStartTimeCorrection() {
- if (mSkipFirstFrame) {
-
- // If the transition is an animation in which the first frame doesn't change the screen
- // contents at all, we can just skip it and start at the second frame. So we shift the
- // start time of the animation forward by minus the frame duration.
- return -Choreographer.getInstance().getFrameIntervalNanos() / TimeUtils.NANOS_PER_MS;
- } else {
- return 0;
- }
- }
-
- // This must be called while inside a transaction.
- boolean stepAnimationLocked(long currentTime) {
- if (mAppToken.okToAnimate()) {
- // We will run animations as long as the display isn't frozen.
-
- if (animation == sDummyAnimation) {
- // This guy is going to animate, but not yet. For now count
- // it as not animating for purposes of scheduling transactions;
- // when it is really time to animate, this will be set to
- // a real animation and the next call will execute normally.
- return false;
- }
-
- if ((mAppToken.allDrawn || animating || mAppToken.startingDisplayed)
- && animation != null) {
- if (!animating) {
- if (DEBUG_ANIM) Slog.v(TAG,
- "Starting animation in " + mAppToken +
- " @ " + currentTime + " scale="
- + mService.getTransitionAnimationScaleLocked()
- + " allDrawn=" + mAppToken.allDrawn + " animating=" + animating);
- long correction = getStartTimeCorrection();
- animation.setStartTime(currentTime + correction);
- animating = true;
- if (thumbnail != null) {
- thumbnail.show();
- thumbnailAnimation.setStartTime(currentTime + correction);
- }
- mSkipFirstFrame = false;
- }
- if (stepAnimation(currentTime)) {
- // animation isn't over, step any thumbnail and that's
- // it for now.
- if (thumbnail != null) {
- stepThumbnailAnimation(currentTime);
- }
- return true;
- }
- }
- } else if (animation != null) {
- // If the display is frozen, and there is a pending animation,
- // clear it and make sure we run the cleanup code.
- animating = true;
- animation = null;
- }
-
- hasTransformation = false;
-
- if (!animating && animation == null) {
- return false;
- }
-
- mAppToken.setAppLayoutChanges(FINISH_LAYOUT_REDO_ANIM, "AppWindowToken");
-
- clearAnimation();
- animating = false;
- if (animLayerAdjustment != 0) {
- animLayerAdjustment = 0;
- updateLayers();
- }
- if (mService.mInputMethodTarget != null
- && mService.mInputMethodTarget.mAppToken == mAppToken) {
- mAppToken.getDisplayContent().computeImeTarget(true /* updateImeTarget */);
- }
-
- if (DEBUG_ANIM) Slog.v(TAG, "Animation done in " + mAppToken
- + ": reportedVisible=" + mAppToken.reportedVisible
- + " okToDisplay=" + mAppToken.okToDisplay()
- + " okToAnimate=" + mAppToken.okToAnimate()
- + " startingDisplayed=" + mAppToken.startingDisplayed);
-
- transformation.clear();
-
- final int numAllAppWinAnimators = mAllAppWinAnimators.size();
- for (int i = 0; i < numAllAppWinAnimators; i++) {
- mAllAppWinAnimators.get(i).mWin.onExitAnimationDone();
- }
- mService.mAppTransition.notifyAppTransitionFinishedLocked(mAppToken.token);
- return false;
- }
-
- // This must be called while inside a transaction.
- boolean showAllWindowsLocked() {
- boolean isAnimating = false;
- final int NW = mAllAppWinAnimators.size();
- for (int i=0; i<NW; i++) {
- WindowStateAnimator winAnimator = mAllAppWinAnimators.get(i);
- if (DEBUG_VISIBILITY) Slog.v(TAG, "performing show on: " + winAnimator);
- winAnimator.mWin.performShowLocked();
- isAnimating |= winAnimator.isAnimationSet();
- }
- return isAnimating;
- }
-
- void dump(PrintWriter pw, String prefix) {
- pw.print(prefix); pw.print("mAppToken="); pw.println(mAppToken);
- pw.print(prefix); pw.print("mAnimator="); pw.println(mAnimator);
- pw.print(prefix); pw.print("freezingScreen="); pw.print(freezingScreen);
- pw.print(" allDrawn="); pw.print(allDrawn);
- pw.print(" animLayerAdjustment="); pw.println(animLayerAdjustment);
- if (lastFreezeDuration != 0) {
- pw.print(prefix); pw.print("lastFreezeDuration=");
- TimeUtils.formatDuration(lastFreezeDuration, pw); pw.println();
- }
- if (animating || animation != null) {
- pw.print(prefix); pw.print("animating="); pw.println(animating);
- pw.print(prefix); pw.print("animation="); pw.println(animation);
- pw.print(prefix); pw.print("mTransit="); pw.println(mTransit);
- pw.print(prefix); pw.print("mTransitFlags="); pw.println(mTransitFlags);
- }
- if (hasTransformation) {
- pw.print(prefix); pw.print("XForm: ");
- transformation.printShortString(pw);
- pw.println();
- }
- if (thumbnail != null) {
- pw.print(prefix); pw.print("thumbnail="); pw.print(thumbnail);
- pw.print(" layer="); pw.println(mThumbnailLayer);
- pw.print(prefix); pw.print("thumbnailAnimation="); pw.println(thumbnailAnimation);
- pw.print(prefix); pw.print("thumbnailTransformation=");
- pw.println(thumbnailTransformation.toShortString());
- }
- for (int i=0; i<mAllAppWinAnimators.size(); i++) {
- WindowStateAnimator wanim = mAllAppWinAnimators.get(i);
- pw.print(prefix); pw.print("App Win Anim #"); pw.print(i);
- pw.print(": "); pw.println(wanim);
- }
- }
-
- void startProlongAnimation(int prolongType) {
- mProlongAnimation = prolongType;
- mClearProlongedAnimation = false;
- }
-
- void endProlongedAnimation() {
- mProlongAnimation = PROLONG_ANIMATION_DISABLED;
- }
-
- // This is an animation that does nothing: it just immediately finishes
- // itself every time it is called. It is used as a stub animation in cases
- // where we want to synchronize multiple things that may be animating.
- static final class DummyAnimation extends Animation {
- @Override
- public boolean getTransformation(long currentTime, Transformation outTransformation) {
- return false;
- }
- }
-
-}
diff --git a/services/core/java/com/android/server/wm/AppWindowContainerController.java b/services/core/java/com/android/server/wm/AppWindowContainerController.java
index 00a0d3d..ae9f28b 100644
--- a/services/core/java/com/android/server/wm/AppWindowContainerController.java
+++ b/services/core/java/com/android/server/wm/AppWindowContainerController.java
@@ -17,7 +17,6 @@
package com.android.server.wm;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
-import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
import static com.android.server.wm.AppTransition.TRANSIT_DOCK_TASK_FROM_RECENTS;
@@ -25,7 +24,6 @@
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ADD_REMOVE;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_APP_TRANSITIONS;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ORIENTATION;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SCREENSHOT;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STARTING_WINDOW;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TOKEN_MOVEMENT;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_VISIBILITY;
@@ -34,16 +32,13 @@
import android.app.ActivityManager.TaskSnapshot;
import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
-import android.graphics.Bitmap;
import android.graphics.Rect;
import android.os.Debug;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
-import android.os.Trace;
import android.util.Slog;
-import android.view.DisplayInfo;
import android.view.IApplicationToken;
import com.android.internal.annotations.VisibleForTesting;
@@ -339,7 +334,7 @@
if (DEBUG_APP_TRANSITIONS || DEBUG_ORIENTATION) Slog.v(TAG_WM, "setAppVisibility("
+ mToken + ", visible=" + visible + "): " + mService.mAppTransition
- + " hidden=" + wtoken.hidden + " hiddenRequested="
+ + " hidden=" + wtoken.isHidden() + " hiddenRequested="
+ wtoken.hiddenRequested + " Callers=" + Debug.getCallers(6));
mService.mOpeningApps.remove(wtoken);
@@ -364,11 +359,11 @@
wtoken.startingMoved = false;
// If the token is currently hidden (should be the common case), or has been
// stopped, then we need to set up to wait for its windows to be ready.
- if (wtoken.hidden || wtoken.mAppStopped) {
+ if (wtoken.isHidden() || wtoken.mAppStopped) {
wtoken.clearAllDrawn();
// If the app was already visible, don't reset the waitingToShow state.
- if (wtoken.hidden) {
+ if (wtoken.isHidden()) {
wtoken.waitingToShow = true;
}
@@ -389,21 +384,6 @@
// If we are preparing an app transition, then delay changing
// the visibility of this token until we execute that transition.
if (wtoken.okToAnimate() && mService.mAppTransition.isTransitionSet()) {
- // A dummy animation is a placeholder animation which informs others that an
- // animation is going on (in this case an application transition). If the animation
- // was transferred from another application/animator, no dummy animator should be
- // created since an animation is already in progress.
- if (wtoken.mAppAnimator.usingTransferredAnimation
- && wtoken.mAppAnimator.animation == null) {
- Slog.wtf(TAG_WM, "Will NOT set dummy animation on: " + wtoken
- + ", using null transferred animation!");
- }
- if (!wtoken.mAppAnimator.usingTransferredAnimation &&
- (!wtoken.startingDisplayed || mService.mSkipAppTransitionAnimation)) {
- if (DEBUG_APP_TRANSITIONS) Slog.v(
- TAG_WM, "Setting dummy animation on: " + wtoken);
- wtoken.mAppAnimator.setDummyAnimation();
- }
wtoken.inPendingTransaction = true;
if (visible) {
mService.mOpeningApps.add(wtoken);
@@ -423,7 +403,7 @@
if (DEBUG_APP_TRANSITIONS) Slog.d(TAG_WM, "TRANSIT_TASK_OPEN_BEHIND, "
+ " adding " + focusedToken + " to mOpeningApps");
// Force animation to be loaded.
- focusedToken.hidden = true;
+ focusedToken.setHidden(true);
mService.mOpeningApps.add(focusedToken);
}
}
@@ -710,7 +690,7 @@
return;
}
if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Clear freezing of " + mToken + ": hidden="
- + mContainer.hidden + " freezing=" + mContainer.mAppAnimator.freezingScreen);
+ + mContainer.isHidden() + " freezing=" + mContainer.isFreezingScreen());
mContainer.stopFreezingScreen(true, force);
}
}
diff --git a/services/core/java/com/android/server/wm/AppWindowThumbnail.java b/services/core/java/com/android/server/wm/AppWindowThumbnail.java
new file mode 100644
index 0000000..c16a531
--- /dev/null
+++ b/services/core/java/com/android/server/wm/AppWindowThumbnail.java
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.wm;
+
+import static com.android.server.wm.WindowManagerDebugConfig.SHOW_TRANSACTIONS;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+import static com.android.server.wm.WindowManagerService.MAX_ANIMATION_DURATION;
+
+import android.graphics.GraphicBuffer;
+import android.graphics.PixelFormat;
+import android.graphics.Point;
+import android.os.Binder;
+import android.util.Slog;
+import android.view.Surface;
+import android.view.SurfaceControl;
+import android.view.SurfaceControl.Builder;
+import android.view.SurfaceControl.Transaction;
+import android.view.animation.Animation;
+
+import com.android.server.wm.SurfaceAnimator.Animatable;
+
+/**
+ * Represents a surface that is displayed over an {@link AppWindowToken}
+ */
+class AppWindowThumbnail implements Animatable {
+
+ private static final String TAG = TAG_WITH_CLASS_NAME ? "AppWindowThumbnail" : TAG_WM;
+
+ private final AppWindowToken mAppToken;
+ private final SurfaceControl mSurfaceControl;
+ private final SurfaceAnimator mSurfaceAnimator;
+ private final int mWidth;
+ private final int mHeight;
+
+ AppWindowThumbnail(Transaction t, AppWindowToken appToken, GraphicBuffer thumbnailHeader) {
+ mAppToken = appToken;
+ mSurfaceAnimator = new SurfaceAnimator(this, this::onAnimationFinished, appToken.mService);
+ mWidth = thumbnailHeader.getWidth();
+ mHeight = thumbnailHeader.getHeight();
+
+ // Create a new surface for the thumbnail
+ WindowState window = appToken.findMainWindow();
+
+ // TODO: This should be attached as a child to the app token, once the thumbnail animations
+ // use relative coordinates. Once we start animating task we can also consider attaching
+ // this to the task.
+ mSurfaceControl = appToken.makeSurface()
+ .setName("thumbnail anim: " + appToken.toString())
+ .setSize(mWidth, mHeight)
+ .setFormat(PixelFormat.TRANSLUCENT)
+ .setMetadata(appToken.windowType,
+ window != null ? window.mOwnerUid : Binder.getCallingUid())
+ .build();
+
+ if (SHOW_TRANSACTIONS) {
+ Slog.i(TAG, " THUMBNAIL " + mSurfaceControl + ": CREATE");
+ }
+
+ // Transfer the thumbnail to the surface
+ Surface drawSurface = new Surface();
+ drawSurface.copyFrom(mSurfaceControl);
+ drawSurface.attachAndQueueBuffer(thumbnailHeader);
+ drawSurface.release();
+ t.show(mSurfaceControl);
+
+ // We parent the thumbnail to the task, and just place it on top of anything else in the
+ // task.
+ t.setLayer(mSurfaceControl, Integer.MAX_VALUE);
+ }
+
+ void startAnimation(Transaction t, Animation anim) {
+ anim.restrictDuration(MAX_ANIMATION_DURATION);
+ anim.scaleCurrentDuration(mAppToken.mService.getTransitionAnimationScaleLocked());
+ mSurfaceAnimator.startAnimation(t, new LocalAnimationAdapter(
+ new WindowAnimationSpec(anim, null /* position */,
+ mAppToken.mService.mAppTransition.canSkipFirstFrame()),
+ mAppToken.mService.mSurfaceAnimationRunner), false /* hidden */);
+ }
+
+ private void onAnimationFinished() {
+ }
+
+ void setShowing(Transaction pendingTransaction, boolean show) {
+ // TODO: Not needed anymore once thumbnail is attached to the app.
+ if (show) {
+ pendingTransaction.show(mSurfaceControl);
+ } else {
+ pendingTransaction.hide(mSurfaceControl);
+ }
+ }
+
+ void destroy() {
+ mSurfaceAnimator.cancelAnimation();
+ mSurfaceControl.destroy();
+ }
+
+ @Override
+ public Transaction getPendingTransaction() {
+ return mAppToken.getPendingTransaction();
+ }
+
+ @Override
+ public void commitPendingTransaction() {
+ mAppToken.commitPendingTransaction();
+ }
+
+ @Override
+ public void destroyAfterPendingTransaction(SurfaceControl surface) {
+ mAppToken.destroyAfterPendingTransaction(surface);
+ }
+
+ @Override
+ public void onAnimationLeashCreated(Transaction t, SurfaceControl leash) {
+ t.setLayer(leash, Integer.MAX_VALUE);
+ }
+
+ @Override
+ public void onAnimationLeashDestroyed(Transaction t) {
+
+ // TODO: Once attached to app token, we don't need to hide it immediately if thumbnail
+ // became visible.
+ t.hide(mSurfaceControl);
+ }
+
+ @Override
+ public Builder makeAnimationLeash() {
+ return mAppToken.makeSurface();
+ }
+
+ @Override
+ public SurfaceControl getSurfaceControl() {
+ return mSurfaceControl;
+ }
+
+ @Override
+ public SurfaceControl getAnimationLeashParent() {
+ return mAppToken.getAppAnimationLayer();
+ }
+
+ @Override
+ public SurfaceControl getParentSurfaceControl() {
+ return mAppToken.getParentSurfaceControl();
+ }
+
+ @Override
+ public int getSurfaceWidth() {
+ return mWidth;
+ }
+
+ @Override
+ public int getSurfaceHeight() {
+ return mHeight;
+ }
+}
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index 94a0cb7..3f99591 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -20,6 +20,7 @@
import static android.content.pm.ActivityInfo.CONFIG_SCREEN_SIZE;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
+import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD;
import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
@@ -51,22 +52,25 @@
import static com.android.server.wm.proto.AppWindowTokenProto.WINDOW_TOKEN;
import android.annotation.CallSuper;
-import android.annotation.NonNull;
import android.app.Activity;
import android.content.res.Configuration;
+import android.graphics.GraphicBuffer;
+import android.graphics.Point;
import android.graphics.Rect;
import android.os.Binder;
import android.os.Debug;
import android.os.IBinder;
import android.os.RemoteException;
-import android.os.SystemClock;
+import android.os.Trace;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
+import android.view.DisplayInfo;
+import android.view.SurfaceControl.Transaction;
+import android.view.animation.Animation;
import android.view.IApplicationToken;
import android.view.SurfaceControl;
import android.view.WindowManager;
import android.view.WindowManager.LayoutParams;
-import android.view.animation.Transformation;
import com.android.internal.util.ToBooleanFunction;
import com.android.server.input.InputApplicationHandle;
@@ -88,11 +92,14 @@
class AppWindowToken extends WindowToken implements WindowManagerService.AppFreezeListener {
private static final String TAG = TAG_WITH_CLASS_NAME ? "AppWindowToken" : TAG_WM;
+ /**
+ * Value to increment the z-layer when boosting a layer during animations. BOOST in l33tsp34k.
+ */
+ private static final int Z_BOOST_BASE = 800570000;
+
// Non-null only for application tokens.
final IApplicationToken appToken;
- @NonNull final AppWindowAnimator mAppAnimator;
-
final boolean mVoiceInteraction;
/** @see WindowContainer#fillsParent() */
@@ -120,6 +127,8 @@
private int mNumDrawnWindows;
boolean inPendingTransaction;
boolean allDrawn;
+ private boolean mLastAllDrawn;
+
// Set to true when this app creates a surface while in the middle of an animation. In that
// case do not clear allDrawn until the animation completes.
boolean deferClearAllDrawn;
@@ -189,6 +198,32 @@
*/
private boolean mCanTurnScreenOn = true;
+ /**
+ * If we are running an animation, this determines the transition type. Must be one of
+ * AppTransition.TRANSIT_* constants.
+ */
+ private int mTransit;
+
+ /**
+ * If we are running an animation, this determines the flags during this animation. Must be a
+ * bitwise combination of AppTransition.TRANSIT_FLAG_* constants.
+ */
+ private int mTransitFlags;
+
+ /** Whether our surface was set to be showing in the last call to {@link #prepareSurfaces} */
+ private boolean mLastSurfaceShowing = true;
+
+ private AppWindowThumbnail mThumbnail;
+
+ /** Have we been asked to have this token keep the screen frozen? */
+ private boolean mFreezingScreen;
+
+ /** Whether this token should be boosted at the top of all app window tokens. */
+ private boolean mNeedsZBoost;
+
+ private final Point mTmpPoint = new Point();
+ private final Rect mTmpRect = new Rect();
+
AppWindowToken(WindowManagerService service, IApplicationToken token, boolean voiceInteraction,
DisplayContent dc, long inputDispatchingTimeoutNanos, boolean fullscreen,
boolean showForAllUsers, int targetSdk, int orientation, int rotationAnimationHint,
@@ -206,7 +241,7 @@
mRotationAnimationHint = rotationAnimationHint;
// Application tokens start out hidden.
- hidden = true;
+ setHidden(true);
hiddenRequested = true;
}
@@ -218,7 +253,6 @@
mVoiceInteraction = voiceInteraction;
mFillsParent = fillsParent;
mInputApplicationHandle = new InputApplicationHandle(this);
- mAppAnimator = new AppWindowAnimator(this, service);
}
void onFirstWindowDrawn(WindowState win, WindowStateAnimator winAnimator) {
@@ -262,7 +296,7 @@
boolean nowGone = mReportedVisibilityResults.nowGone;
boolean nowDrawn = numInteresting > 0 && numDrawn >= numInteresting;
- boolean nowVisible = numInteresting > 0 && numVisible >= numInteresting && !hidden;
+ boolean nowVisible = numInteresting > 0 && numVisible >= numInteresting && !isHidden();
if (!nowGone) {
// If the app is not yet gone, then it can only become visible/drawn.
if (!nowDrawn) {
@@ -325,19 +359,16 @@
// transition animation
// * or this is an opening app and windows are being replaced.
boolean visibilityChanged = false;
- if (hidden == visible || (hidden && mIsExiting) || (visible && waitingForReplacement())) {
+ if (isHidden() == visible || (isHidden() && mIsExiting) || (visible && waitingForReplacement())) {
final AccessibilityController accessibilityController = mService.mAccessibilityController;
boolean changed = false;
if (DEBUG_APP_TRANSITIONS) Slog.v(TAG_WM,
- "Changing app " + this + " hidden=" + hidden + " performLayout=" + performLayout);
+ "Changing app " + this + " hidden=" + isHidden() + " performLayout=" + performLayout);
boolean runningAppAnimation = false;
- if (mAppAnimator.animation == AppWindowAnimator.sDummyAnimation) {
- mAppAnimator.setNullAnimation();
- }
if (transit != AppTransition.TRANSIT_UNSET) {
- if (mService.applyAnimationLocked(this, lp, transit, visible, isVoiceInteraction)) {
+ if (applyAnimationLocked(lp, transit, visible, isVoiceInteraction)) {
delayed = runningAppAnimation = true;
}
final WindowState window = findMainWindow();
@@ -355,7 +386,8 @@
changed |= win.onAppVisibilityChanged(visible, runningAppAnimation);
}
- hidden = hiddenRequested = !visible;
+ setHidden(!visible);
+ hiddenRequested = !visible;
visibilityChanged = true;
if (!visible) {
stopFreezingScreen(true, true);
@@ -373,7 +405,7 @@
}
if (DEBUG_APP_TRANSITIONS) Slog.v(TAG_WM, "setVisibility: " + this
- + ": hidden=" + hidden + " hiddenRequested=" + hiddenRequested);
+ + ": hidden=" + isHidden() + " hiddenRequested=" + hiddenRequested);
if (changed) {
mService.mInputMonitor.setUpdateInputWindowsNeededLw();
@@ -386,7 +418,7 @@
}
}
- if (mAppAnimator.animation != null) {
+ if (isReallyAnimating()) {
delayed = true;
}
@@ -414,7 +446,7 @@
// no animation but there will still be a transition set.
// We still need to delay hiding the surface such that it
// can be synchronized with showing the next surface in the transition.
- if (hidden && !delayed && !mService.mAppTransition.isTransitionSet()) {
+ if (isHidden() && !delayed && !mService.mAppTransition.isTransitionSet()) {
SurfaceControl.openTransaction();
for (int i = mChildren.size() - 1; i >= 0; i--) {
mChildren.get(i).mWinAnimator.hide("immediately hidden");
@@ -487,7 +519,7 @@
boolean isVisible() {
// If the app token isn't hidden then it is considered visible and there is no need to check
// its children windows to see if they are visible.
- return !hidden;
+ return !isHidden();
}
@Override
@@ -533,7 +565,7 @@
}
if (DEBUG_APP_TRANSITIONS) Slog.v(TAG_WM, "Removing app " + this + " delayed=" + delayed
- + " animation=" + mAppAnimator.animation + " animating=" + mAppAnimator.animating);
+ + " animation=" + getAnimation() + " animating=" + isSelfAnimating());
if (DEBUG_ADD_REMOVE || DEBUG_TOKEN_MOVEMENT) Slog.v(TAG_WM, "removeAppToken: "
+ this + " delayed=" + delayed + " Callers=" + Debug.getCallers(4));
@@ -545,7 +577,7 @@
// If this window was animating, then we need to ensure that the app transition notifies
// that animations have completed in WMS.handleAnimatingStoppedAndTransitionLocked(), so
// add to that list now
- if (mAppAnimator.animating) {
+ if (isSelfAnimating()) {
mService.mNoAnimationNotifyOnTransitionFinished.add(token);
}
@@ -561,8 +593,7 @@
} else {
// Make sure there is no animation running on this token, so any windows associated
// with it will be removed as soon as their animations are complete
- mAppAnimator.clearAnimation();
- mAppAnimator.animating = false;
+ cancelAnimation();
if (stack != null) {
stack.mExitingAppTokens.remove(this);
}
@@ -710,7 +741,7 @@
// We set the hidden state to false for the token from a transferred starting window.
// We now reset it back to true since the starting window was the last window in the
// token.
- hidden = true;
+ setHidden(true);
}
} else if (mChildren.size() == 1 && startingSurface != null && !isRelaunching()) {
// If this is the last window except for a starting transition window,
@@ -756,14 +787,6 @@
final WindowState w = mChildren.get(i);
w.setWillReplaceWindow(animate);
}
- if (animate) {
- // Set-up dummy animation so we can start treating windows associated with this
- // token like they are in transition before the new app window is ready for us to
- // run the real transition animation.
- if (DEBUG_APP_TRANSITIONS) Slog.v(TAG_WM,
- "setWillReplaceWindow() Setting dummy animation on: " + this);
- mAppAnimator.setDummyAnimation();
- }
}
void setWillReplaceChildWindows() {
@@ -1007,13 +1030,12 @@
void startFreezingScreen() {
if (DEBUG_ORIENTATION) logWithStack(TAG, "Set freezing of " + appToken + ": hidden="
- + hidden + " freezing=" + mAppAnimator.freezingScreen + " hiddenRequested="
+ + isHidden() + " freezing=" + mFreezingScreen + " hiddenRequested="
+ hiddenRequested);
if (!hiddenRequested) {
- if (!mAppAnimator.freezingScreen) {
- mAppAnimator.freezingScreen = true;
+ if (!mFreezingScreen) {
+ mFreezingScreen = true;
mService.registerAppFreezeListener(this);
- mAppAnimator.lastFreezeDuration = 0;
mService.mAppsFreezingScreen++;
if (mService.mAppsFreezingScreen == 1) {
mService.startFreezingDisplayLocked(false, 0, 0, getDisplayContent());
@@ -1030,7 +1052,7 @@
}
void stopFreezingScreen(boolean unfreezeSurfaceNow, boolean force) {
- if (!mAppAnimator.freezingScreen) {
+ if (!mFreezingScreen) {
return;
}
if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Clear freezing of " + this + " force=" + force);
@@ -1042,10 +1064,8 @@
}
if (force || unfrozeWindows) {
if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "No longer freezing: " + this);
- mAppAnimator.freezingScreen = false;
+ mFreezingScreen = false;
mService.unregisterAppFreezeListener(this);
- mAppAnimator.lastFreezeDuration =
- (int)(SystemClock.elapsedRealtime() - mService.mDisplayFreezeTime);
mService.mAppsFreezingScreen--;
mService.mLastFinishedFreezeSource = this;
}
@@ -1111,14 +1131,19 @@
if (fromToken.firstWindowDrawn) {
firstWindowDrawn = true;
}
- if (!fromToken.hidden) {
- hidden = false;
+ if (!fromToken.isHidden()) {
+ setHidden(false);
hiddenRequested = false;
mHiddenSetFromTransferredStartingWindow = true;
}
setClientHidden(fromToken.mClientHidden);
- fromToken.mAppAnimator.transferCurrentAnimation(
- mAppAnimator, tStartingWindow.mWinAnimator);
+
+ transferAnimation(fromToken);
+
+ // When transferring an animation, we no longer need to apply an animation to the
+ // the token we transfer the animation over. Thus, remove the animation from
+ // pending opening apps.
+ mService.mOpeningApps.remove(this);
mService.updateFocusedWindowLocked(
UPDATE_FOCUS_WILL_PLACE_SURFACES, true /*updateInputWindows*/);
@@ -1142,17 +1167,8 @@
return true;
}
- final AppWindowAnimator tAppAnimator = fromToken.mAppAnimator;
- final AppWindowAnimator wAppAnimator = mAppAnimator;
- if (tAppAnimator.thumbnail != null) {
- // The old token is animating with a thumbnail, transfer that to the new token.
- if (wAppAnimator.thumbnail != null) {
- wAppAnimator.thumbnail.destroy();
- }
- wAppAnimator.thumbnail = tAppAnimator.thumbnail;
- wAppAnimator.thumbnailAnimation = tAppAnimator.thumbnailAnimation;
- tAppAnimator.thumbnail = null;
- }
+ // TODO: Transfer thumbnail
+
return false;
}
@@ -1160,16 +1176,6 @@
return mChildren.size() == 1 && mChildren.get(0) == win;
}
- void setAllAppWinAnimators() {
- final ArrayList<WindowStateAnimator> allAppWinAnimators = mAppAnimator.mAllAppWinAnimators;
- allAppWinAnimators.clear();
-
- final int windowsCount = mChildren.size();
- for (int j = 0; j < windowsCount; j++) {
- (mChildren.get(j)).addWinAnimatorToList(allAppWinAnimators);
- }
- }
-
@Override
void onAppTransitionDone() {
sendingToBottom = false;
@@ -1206,18 +1212,18 @@
@Override
void checkAppWindowsReadyToShow() {
- if (allDrawn == mAppAnimator.allDrawn) {
+ if (allDrawn == mLastAllDrawn) {
return;
}
- mAppAnimator.allDrawn = allDrawn;
+ mLastAllDrawn = allDrawn;
if (!allDrawn) {
return;
}
// The token has now changed state to having all windows shown... what to do, what to do?
- if (mAppAnimator.freezingScreen) {
- mAppAnimator.showAllWindowsLocked();
+ if (mFreezingScreen) {
+ showAllWindowsLocked();
stopFreezingScreen(false, true);
if (DEBUG_ORIENTATION) Slog.i(TAG,
"Setting mOrientationChangeComplete=true because wtoken " + this
@@ -1230,7 +1236,7 @@
// We can now show all of the drawn windows!
if (!mService.mOpeningApps.contains(this)) {
- mService.mAnimator.orAnimating(mAppAnimator.showAllWindowsLocked());
+ showAllWindowsLocked();
}
}
}
@@ -1300,10 +1306,10 @@
if (DEBUG_STARTING_WINDOW_VERBOSE && w == startingWindow) {
Slog.d(TAG, "updateWindows: starting " + w + " isOnScreen=" + w.isOnScreen()
- + " allDrawn=" + allDrawn + " freezingScreen=" + mAppAnimator.freezingScreen);
+ + " allDrawn=" + allDrawn + " freezingScreen=" + mFreezingScreen);
}
- if (allDrawn && !mAppAnimator.freezingScreen) {
+ if (allDrawn && !mFreezingScreen) {
return false;
}
@@ -1320,13 +1326,13 @@
if (!allDrawn && w.mightAffectAllDrawn()) {
if (DEBUG_VISIBILITY || DEBUG_ORIENTATION) {
Slog.v(TAG, "Eval win " + w + ": isDrawn=" + w.isDrawnLw()
- + ", isAnimationSet=" + winAnimator.isAnimationSet());
+ + ", isAnimationSet=" + isSelfAnimating());
if (!w.isDrawnLw()) {
Slog.v(TAG, "Not displayed: s=" + winAnimator.mSurfaceController
+ " pv=" + w.mPolicyVisibility
+ " mDrawState=" + winAnimator.drawStateToString()
+ " ph=" + w.isParentWindowHidden() + " th=" + hiddenRequested
- + " a=" + winAnimator.isAnimationSet());
+ + " a=" + isSelfAnimating());
}
}
@@ -1338,7 +1344,7 @@
if (DEBUG_VISIBILITY || DEBUG_ORIENTATION) Slog.v(TAG, "tokenMayBeDrawn: "
+ this + " w=" + w + " numInteresting=" + mNumInterestingWindows
- + " freezingScreen=" + mAppAnimator.freezingScreen
+ + " freezingScreen=" + mFreezingScreen
+ " mAppFreezing=" + w.mAppFreezing);
isInterestingAndDrawn = true;
@@ -1356,21 +1362,6 @@
}
@Override
- void stepAppWindowsAnimation(long currentTime) {
- mAppAnimator.wasAnimating = mAppAnimator.animating;
- if (mAppAnimator.stepAnimationLocked(currentTime)) {
- mAppAnimator.animating = true;
- mService.mAnimator.setAnimating(true);
- mService.mAnimator.mAppWindowAnimating = true;
- } else if (mAppAnimator.wasAnimating) {
- // stopped animating, do one more pass through the layout
- setAppLayoutChanges(FINISH_LAYOUT_REDO_WALLPAPER,
- DEBUG_LAYOUT_REPEATS ? "appToken " + this + " done" : null);
- if (DEBUG_ANIM) Slog.v(TAG, "updateWindowsApps...: done animating " + this);
- }
- }
-
- @Override
boolean forAllWindows(ToBooleanFunction<WindowState> callback, boolean traverseTopToBottom) {
// For legacy reasons we process the TaskStack.mExitingAppTokens first in DisplayContent
// before the non-exiting app tokens. So, we skip the exiting app tokens here.
@@ -1521,18 +1512,268 @@
}
@Override
- int getAnimLayerAdjustment() {
- return mAppAnimator.animLayerAdjustment;
+ public SurfaceControl getAnimationLeashParent() {
+ return getAppAnimationLayer();
+ }
+
+ boolean applyAnimationLocked(WindowManager.LayoutParams lp, int transit, boolean enter,
+ boolean isVoiceInteraction) {
+
+ if (mService.mDisableTransitionAnimation) {
+ if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) {
+ Slog.v(TAG_WM, "applyAnimation: transition animation is disabled. atoken=" + this);
+ }
+ cancelAnimation();
+ return false;
+ }
+
+ // Only apply an animation if the display isn't frozen. If it is frozen, there is no reason
+ // to animate and it can cause strange artifacts when we unfreeze the display if some
+ // different animation is running.
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "AWT#applyAnimationLocked");
+ if (okToAnimate()) {
+ final Animation a = loadAnimation(lp, transit, enter, isVoiceInteraction);
+ if (a != null) {
+ final TaskStack stack = getStack();
+ mTmpPoint.set(0, 0);
+ mTmpRect.setEmpty();
+ if (stack != null) {
+ stack.getRelativePosition(mTmpPoint);
+ stack.getBounds(mTmpRect);
+ }
+ final AnimationAdapter adapter = new LocalAnimationAdapter(
+ new WindowAnimationSpec(a, mTmpPoint, mTmpRect,
+ mService.mAppTransition.canSkipFirstFrame(),
+ mService.mAppTransition.getAppStackClipMode()),
+ mService.mSurfaceAnimationRunner);
+ if (a.getZAdjustment() == Animation.ZORDER_TOP) {
+ mNeedsZBoost = true;
+ }
+ startAnimation(getPendingTransaction(), adapter, !isVisible());
+ mTransit = transit;
+ mTransitFlags = mService.mAppTransition.getTransitFlags();
+ }
+ } else {
+ cancelAnimation();
+ }
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+
+ return isReallyAnimating();
+ }
+
+ private Animation loadAnimation(WindowManager.LayoutParams lp, int transit, boolean enter,
+ boolean isVoiceInteraction) {
+ final DisplayContent displayContent = getTask().getDisplayContent();
+ final DisplayInfo displayInfo = displayContent.getDisplayInfo();
+ final int width = displayInfo.appWidth;
+ final int height = displayInfo.appHeight;
+ if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG_WM,
+ "applyAnimation: atoken=" + this);
+
+ // Determine the visible rect to calculate the thumbnail clip
+ final WindowState win = findMainWindow();
+ final Rect frame = new Rect(0, 0, width, height);
+ final Rect displayFrame = new Rect(0, 0,
+ displayInfo.logicalWidth, displayInfo.logicalHeight);
+ final Rect insets = new Rect();
+ final Rect stableInsets = new Rect();
+ Rect surfaceInsets = null;
+ final boolean freeform = win != null && win.inFreeformWindowingMode();
+ if (win != null) {
+ // Containing frame will usually cover the whole screen, including dialog windows.
+ // For freeform workspace windows it will not cover the whole screen and it also
+ // won't exactly match the final freeform window frame (e.g. when overlapping with
+ // the status bar). In that case we need to use the final frame.
+ if (freeform) {
+ frame.set(win.mFrame);
+ } else {
+ frame.set(win.mContainingFrame);
+ }
+ surfaceInsets = win.getAttrs().surfaceInsets;
+ insets.set(win.mContentInsets);
+ stableInsets.set(win.mStableInsets);
+ }
+
+ if (mLaunchTaskBehind) {
+ // Differentiate the two animations. This one which is briefly on the screen
+ // gets the !enter animation, and the other activity which remains on the
+ // screen gets the enter animation. Both appear in the mOpeningApps set.
+ enter = false;
+ }
+ if (DEBUG_APP_TRANSITIONS) Slog.d(TAG_WM, "Loading animation for app transition."
+ + " transit=" + AppTransition.appTransitionToString(transit) + " enter=" + enter
+ + " frame=" + frame + " insets=" + insets + " surfaceInsets=" + surfaceInsets);
+ final Configuration displayConfig = displayContent.getConfiguration();
+ final Animation a = mService.mAppTransition.loadAnimation(lp, transit, enter,
+ displayConfig.uiMode, displayConfig.orientation, frame, displayFrame, insets,
+ surfaceInsets, stableInsets, isVoiceInteraction, freeform, getTask().mTaskId);
+ if (a != null) {
+ if (DEBUG_ANIM) logWithStack(TAG, "Loaded animation " + a + " for " + this);
+ final int containingWidth = frame.width();
+ final int containingHeight = frame.height();
+ a.initialize(containingWidth, containingHeight, width, height);
+ a.scaleCurrentDuration(mService.getTransitionAnimationScaleLocked());
+ }
+ return a;
+ }
+
+ @Override
+ protected void setLayer(Transaction t, int layer) {
+ if (!mSurfaceAnimator.hasLeash()) {
+ t.setLayer(mSurfaceControl, layer);
+ }
+ }
+
+ @Override
+ protected void setRelativeLayer(Transaction t, SurfaceControl relativeTo, int layer) {
+ if (!mSurfaceAnimator.hasLeash()) {
+ t.setRelativeLayer(mSurfaceControl, relativeTo, layer);
+ }
+ }
+
+ @Override
+ protected void reparentSurfaceControl(Transaction t, SurfaceControl newParent) {
+ if (!mSurfaceAnimator.hasLeash()) {
+ t.reparent(mSurfaceControl, newParent.getHandle());
+ }
+ }
+
+ @Override
+ public void onAnimationLeashCreated(Transaction t, SurfaceControl leash) {
+
+ // The leash is parented to the animation layer. We need to preserve the z-order by using
+ // the prefix order index, but we boost if necessary.
+ int layer = getPrefixOrderIndex();
+ if (mNeedsZBoost) {
+ layer += Z_BOOST_BASE;
+ }
+ leash.setLayer(layer);
+ }
+
+ /**
+ * This must be called while inside a transaction.
+ */
+ void showAllWindowsLocked() {
+ forAllWindows(windowState -> {
+ if (DEBUG_VISIBILITY) Slog.v(TAG, "performing show on: " + windowState);
+ windowState.performShowLocked();
+ }, false /* traverseTopToBottom */);
+ }
+
+ @Override
+ protected void onAnimationFinished() {
+ super.onAnimationFinished();
+
+ mTransit = TRANSIT_UNSET;
+ mTransitFlags = 0;
+ mNeedsZBoost = false;
+
+ setAppLayoutChanges(FINISH_LAYOUT_REDO_ANIM | FINISH_LAYOUT_REDO_WALLPAPER,
+ "AppWindowToken");
+
+ clearThumbnail();
+
+ if (mService.mInputMethodTarget != null && mService.mInputMethodTarget.mAppToken == this) {
+ getDisplayContent().computeImeTarget(true /* updateImeTarget */);
+ }
+
+ if (DEBUG_ANIM) Slog.v(TAG, "Animation done in " + this
+ + ": reportedVisible=" + reportedVisible
+ + " okToDisplay=" + okToDisplay()
+ + " okToAnimate=" + okToAnimate()
+ + " startingDisplayed=" + startingDisplayed);
+
+ // WindowState.onExitAnimationDone might modify the children list, so make a copy and then
+ // traverse the copy.
+ final ArrayList<WindowState> children = new ArrayList<>(mChildren);
+ children.forEach(WindowState::onExitAnimationDone);
+
+ mService.mAppTransition.notifyAppTransitionFinishedLocked(token);
+ scheduleAnimation();
+ }
+
+ @Override
+ boolean isAppAnimating() {
+ return isSelfAnimating();
}
@Override
boolean isSelfAnimating() {
- return mAppAnimator.isAnimating();
+ // If we are about to start a transition, we also need to be considered animating.
+ return isWaitingForTransitionStart() || isReallyAnimating();
+ }
+
+ /**
+ * @return True if and only if we are actually running an animation. Note that
+ * {@link #isSelfAnimating} also returns true if we are waiting for an animation to
+ * start.
+ */
+ private boolean isReallyAnimating() {
+ return super.isSelfAnimating();
}
@Override
- void dump(PrintWriter pw, String prefix) {
- super.dump(pw, prefix);
+ void cancelAnimation() {
+ super.cancelAnimation();
+ clearThumbnail();
+ }
+
+ boolean isWaitingForTransitionStart() {
+ return mService.mAppTransition.isTransitionSet()
+ && (mService.mOpeningApps.contains(this) || mService.mClosingApps.contains(this));
+ }
+
+ public int getTransit() {
+ return mTransit;
+ }
+
+ int getTransitFlags() {
+ return mTransitFlags;
+ }
+
+ void attachThumbnailAnimation() {
+ if (!isReallyAnimating()) {
+ return;
+ }
+ final int taskId = getTask().mTaskId;
+ final GraphicBuffer thumbnailHeader =
+ mService.mAppTransition.getAppTransitionThumbnailHeader(taskId);
+ if (thumbnailHeader == null) {
+ if (DEBUG_APP_TRANSITIONS) Slog.d(TAG, "No thumbnail header bitmap for: " + taskId);
+ return;
+ }
+ clearThumbnail();
+ mThumbnail = new AppWindowThumbnail(getPendingTransaction(), this, thumbnailHeader);
+ mThumbnail.startAnimation(getPendingTransaction(), loadThumbnailAnimation(thumbnailHeader));
+ }
+
+ private Animation loadThumbnailAnimation(GraphicBuffer thumbnailHeader) {
+ final DisplayInfo displayInfo = mDisplayContent.getDisplayInfo();
+
+ // If this is a multi-window scenario, we use the windows frame as
+ // destination of the thumbnail header animation. If this is a full screen
+ // window scenario, we use the whole display as the target.
+ WindowState win = findMainWindow();
+ Rect appRect = win != null ? win.getContentFrameLw() :
+ new Rect(0, 0, displayInfo.appWidth, displayInfo.appHeight);
+ Rect insets = win != null ? win.mContentInsets : null;
+ final Configuration displayConfig = mDisplayContent.getConfiguration();
+ return mService.mAppTransition.createThumbnailAspectScaleAnimationLocked(
+ appRect, insets, thumbnailHeader, getTask().mTaskId, displayConfig.uiMode,
+ displayConfig.orientation);
+ }
+
+ private void clearThumbnail() {
+ if (mThumbnail == null) {
+ return;
+ }
+ mThumbnail.destroy();
+ mThumbnail = null;
+ }
+
+ @Override
+ void dump(PrintWriter pw, String prefix, boolean dumpAll) {
+ super.dump(pw, prefix, dumpAll);
if (appToken != null) {
pw.println(prefix + "app=true mVoiceInteraction=" + mVoiceInteraction);
}
@@ -1549,13 +1790,13 @@
pw.print(prefix); pw.print("mAppStopped="); pw.println(mAppStopped);
}
if (mNumInterestingWindows != 0 || mNumDrawnWindows != 0
- || allDrawn || mAppAnimator.allDrawn) {
+ || allDrawn || mLastAllDrawn) {
pw.print(prefix); pw.print("mNumInterestingWindows=");
pw.print(mNumInterestingWindows);
pw.print(" mNumDrawnWindows="); pw.print(mNumDrawnWindows);
pw.print(" inPendingTransaction="); pw.print(inPendingTransaction);
pw.print(" allDrawn="); pw.print(allDrawn);
- pw.print(" (animator="); pw.print(mAppAnimator.allDrawn);
+ pw.print(" lastAllDrawn="); pw.print(mLastAllDrawn);
pw.println(")");
}
if (inPendingTransaction) {
@@ -1590,9 +1831,39 @@
if (mRemovingFromDisplay) {
pw.println(prefix + "mRemovingFromDisplay=" + mRemovingFromDisplay);
}
- if (mAppAnimator.isAnimating()) {
- mAppAnimator.dump(pw, prefix + " ");
+ }
+
+ @Override
+ void setHidden(boolean hidden) {
+ super.setHidden(hidden);
+ scheduleAnimation();
+ }
+
+ @Override
+ void prepareSurfaces() {
+ // isSelfAnimating also returns true when we are about to start a transition, so we need
+ // to check super here.
+ final boolean reallyAnimating = super.isSelfAnimating();
+ final boolean show = !isHidden() || reallyAnimating;
+ if (show && !mLastSurfaceShowing) {
+ mPendingTransaction.show(mSurfaceControl);
+ } else if (!show && mLastSurfaceShowing) {
+ mPendingTransaction.hide(mSurfaceControl);
}
+ if (mThumbnail != null) {
+ mThumbnail.setShowing(mPendingTransaction, show);
+ }
+ mLastSurfaceShowing = show;
+ super.prepareSurfaces();
+ }
+
+ boolean isFreezingScreen() {
+ return mFreezingScreen;
+ }
+
+ @Override
+ boolean needsZBoost() {
+ return mNeedsZBoost || super.needsZBoost();
}
@CallSuper
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index f458457..63dfbc2 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -69,7 +69,6 @@
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_FOCUS;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_FOCUS_LIGHT;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_INPUT_METHOD;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYERS;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT_REPEATS;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ORIENTATION;
@@ -91,8 +90,6 @@
import static com.android.server.wm.WindowManagerService.LAYOUT_REPEAT_THRESHOLD;
import static com.android.server.wm.WindowManagerService.MAX_ANIMATION_DURATION;
import static com.android.server.wm.WindowManagerService.SEAMLESS_ROTATION_TIMEOUT_DURATION;
-import static com.android.server.wm.WindowManagerService.TYPE_LAYER_MULTIPLIER;
-import static com.android.server.wm.WindowManagerService.TYPE_LAYER_OFFSET;
import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_WILL_PLACE_SURFACES;
import static com.android.server.wm.WindowManagerService.WINDOWS_FREEZING_SCREENS_ACTIVE;
import static com.android.server.wm.WindowManagerService.WINDOWS_FREEZING_SCREENS_TIMEOUT;
@@ -123,7 +120,6 @@
import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
import android.graphics.Bitmap;
-import android.graphics.GraphicBuffer;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.graphics.RectF;
@@ -350,7 +346,6 @@
private boolean mDisplayReady = false;
WallpaperController mWallpaperController;
- int mInputMethodAnimLayerAdjustment;
private final SurfaceSession mSession = new SurfaceSession();
@@ -398,14 +393,6 @@
}
}
}
- final AppWindowAnimator appAnimator = winAnimator.mAppAnimator;
- if (appAnimator != null && appAnimator.thumbnail != null) {
- if (appAnimator.thumbnailTransactionSeq
- != mTmpWindowAnimator.mAnimTransactionSequence) {
- appAnimator.thumbnailTransactionSeq =
- mTmpWindowAnimator.mAnimTransactionSequence;
- }
- }
};
private final Consumer<WindowState> mUpdateWallpaperForAnimator = w -> {
@@ -436,15 +423,14 @@
// If this window's app token is running a detached wallpaper animation, make a note so
// we can ensure the wallpaper is displayed behind it.
- final AppWindowAnimator appAnimator = winAnimator.mAppAnimator;
- if (appAnimator != null && appAnimator.animation != null
- && appAnimator.animating) {
- if ((flags & FLAG_SHOW_WALLPAPER) != 0
- && appAnimator.animation.getDetachWallpaper()) {
+ final AppWindowToken atoken = winAnimator.mWin.mAppToken;
+ final AnimationAdapter animation = atoken != null ? atoken.getAnimation() : null;
+ if (animation != null) {
+ if ((flags & FLAG_SHOW_WALLPAPER) != 0 && animation.getDetachWallpaper()) {
mTmpWindow = w;
}
- final int color = appAnimator.animation.getBackgroundColor();
+ final int color = animation.getBackgroundColor();
if (color != 0) {
final TaskStack stack = w.getStack();
if (stack != null) {
@@ -527,11 +513,11 @@
+ " screen changed=" + w.isConfigChanged());
final AppWindowToken atoken = w.mAppToken;
if (gone) Slog.v(TAG, " GONE: mViewVisibility=" + w.mViewVisibility
- + " mRelayoutCalled=" + w.mRelayoutCalled + " hidden=" + w.mToken.hidden
+ + " mRelayoutCalled=" + w.mRelayoutCalled + " hidden=" + w.mToken.isHidden()
+ " hiddenRequested=" + (atoken != null && atoken.hiddenRequested)
+ " parentHidden=" + w.isParentWindowHidden());
else Slog.v(TAG, " VIS: mViewVisibility=" + w.mViewVisibility
- + " mRelayoutCalled=" + w.mRelayoutCalled + " hidden=" + w.mToken.hidden
+ + " mRelayoutCalled=" + w.mRelayoutCalled + " hidden=" + w.mToken.isHidden()
+ " hiddenRequested=" + (atoken != null && atoken.hiddenRequested)
+ " parentHidden=" + w.isParentWindowHidden());
}
@@ -706,7 +692,7 @@
}
}
final TaskStack stack = w.getStack();
- if ((!winAnimator.isWaitingForOpening())
+ if (!winAnimator.isWaitingForOpening()
|| (stack != null && stack.isAnimatingBounds())) {
// Updates the shown frame before we set up the surface. This is needed
// because the resizing could change the top-left position (in addition to
@@ -724,9 +710,13 @@
winAnimator.setSurfaceBoundariesLocked(mTmpRecoveringMemory /* recoveringMemory */);
// Since setSurfaceBoundariesLocked applies the clipping, we need to apply the position
- // to the surface of the window container as well. Use mTmpTransaction instead of
- // mPendingTransaction to avoid committing any existing changes in there.
+ // to the surface of the window container and also the position of the stack window
+ // container as well. Use mTmpTransaction instead of mPendingTransaction to avoid
+ // committing any existing changes in there.
w.updateSurfacePosition(mTmpTransaction);
+ if (stack != null) {
+ stack.updateSurfaceBounds(mTmpTransaction);
+ }
SurfaceControl.mergeToGlobalTransaction(mTmpTransaction);
}
@@ -1933,6 +1923,8 @@
mService.unregisterPointerEventListener(mService.mMousePositionTracker);
}
}
+ mService.mAnimator.removeDisplayLocked(mDisplayId);
+
// The pending transaction won't be applied so we should
// just clean up any surfaces pending destruction.
onPendingTransactionApplied();
@@ -2056,12 +2048,6 @@
mPinnedStackControllerLocked.setAdjustedForIme(imeVisible, imeHeight);
}
- void setInputMethodAnimLayerAdjustment(int adj) {
- if (DEBUG_LAYERS) Slog.v(TAG_WM, "Setting im layer adj to " + adj);
- mInputMethodAnimLayerAdjustment = adj;
- assignWindowLayers(false /* relayoutNeeded */);
- }
-
/**
* If a window that has an animation specifying a colored background and the current wallpaper
* is visible, then the color goes *below* the wallpaper so we don't cause the wallpaper to
@@ -2168,7 +2154,9 @@
proto.end(token);
}
- public void dump(String prefix, PrintWriter pw) {
+ @Override
+ public void dump(PrintWriter pw, String prefix, boolean dumpAll) {
+ super.dump(pw, prefix, dumpAll);
pw.print(prefix); pw.print("Display: mDisplayId="); pw.println(mDisplayId);
final String subPrefix = " " + prefix;
pw.print(subPrefix); pw.print("init="); pw.print(mInitialDisplayWidth); pw.print("x");
@@ -2202,7 +2190,7 @@
pw.println(prefix + "Application tokens in top down Z order:");
for (int stackNdx = mTaskStackContainers.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
final TaskStack stack = mTaskStackContainers.getChildAt(stackNdx);
- stack.dump(prefix + " ", pw);
+ stack.dump(pw, prefix + " ", dumpAll);
}
pw.println();
@@ -2214,7 +2202,7 @@
pw.print(" Exiting #"); pw.print(i);
pw.print(' '); pw.print(token);
pw.println(':');
- token.dump(pw, " ");
+ token.dump(pw, " ", dumpAll);
}
}
@@ -2239,11 +2227,6 @@
pw.println();
mPinnedStackControllerLocked.dump(prefix, pw);
- if (mInputMethodAnimLayerAdjustment != 0) {
- pw.println(subPrefix
- + "mInputMethodAnimLayerAdjustment=" + mInputMethodAnimLayerAdjustment);
- }
-
pw.println();
mDisplayFrames.dump(prefix, pw);
}
@@ -2403,7 +2386,7 @@
if (updateImeTarget) {
if (DEBUG_INPUT_METHOD) Slog.w(TAG_WM, "Moving IM target from "
+ mService.mInputMethodTarget + " to null since mInputMethodWindow is null");
- setInputMethodTarget(null, mService.mInputMethodTargetWaitingAnim, 0);
+ setInputMethodTarget(null, mService.mInputMethodTargetWaitingAnim);
}
return null;
}
@@ -2452,7 +2435,7 @@
if (DEBUG_INPUT_METHOD) Slog.w(TAG_WM, "Moving IM target from " + curTarget
+ " to null." + (SHOW_STACK_CRAWLS ? " Callers="
+ Debug.getCallers(4) : ""));
- setInputMethodTarget(null, mService.mInputMethodTargetWaitingAnim, 0);
+ setInputMethodTarget(null, mService.mInputMethodTargetWaitingAnim);
}
return null;
@@ -2466,7 +2449,7 @@
// to look at all windows below the current target that are in this app, finding the
// highest visible one in layering.
WindowState highestTarget = null;
- if (token.mAppAnimator.animating || token.mAppAnimator.animation != null) {
+ if (token.isSelfAnimating()) {
highestTarget = token.getHighestAnimLayerWindow(curTarget);
}
@@ -2480,14 +2463,14 @@
if (appTransition.isTransitionSet()) {
// If we are currently setting up for an animation, hold everything until we
// can find out what will happen.
- setInputMethodTarget(highestTarget, true, mInputMethodAnimLayerAdjustment);
+ setInputMethodTarget(highestTarget, true);
return highestTarget;
} else if (highestTarget.mWinAnimator.isAnimationSet() &&
highestTarget.mWinAnimator.mAnimLayer > target.mWinAnimator.mAnimLayer) {
// If the window we are currently targeting is involved with an animation,
// and it is on top of the next target we will be over, then hold off on
// moving until that is done.
- setInputMethodTarget(highestTarget, true, mInputMethodAnimLayerAdjustment);
+ setInputMethodTarget(highestTarget, true);
return highestTarget;
}
}
@@ -2495,23 +2478,20 @@
if (DEBUG_INPUT_METHOD) Slog.w(TAG_WM, "Moving IM target from " + curTarget + " to "
+ target + (SHOW_STACK_CRAWLS ? " Callers=" + Debug.getCallers(4) : ""));
- setInputMethodTarget(target, false, target.mAppToken != null
- ? target.mAppToken.getAnimLayerAdjustment() : 0);
+ setInputMethodTarget(target, false);
}
return target;
}
- private void setInputMethodTarget(WindowState target, boolean targetWaitingAnim, int layerAdj) {
+ private void setInputMethodTarget(WindowState target, boolean targetWaitingAnim) {
if (target == mService.mInputMethodTarget
- && mService.mInputMethodTargetWaitingAnim == targetWaitingAnim
- && mInputMethodAnimLayerAdjustment == layerAdj) {
+ && mService.mInputMethodTargetWaitingAnim == targetWaitingAnim) {
return;
}
mService.mInputMethodTarget = target;
mService.mInputMethodTargetWaitingAnim = targetWaitingAnim;
- setInputMethodAnimLayerAdjustment(layerAdj);
assignWindowLayers(false /* setLayoutNeeded */);
}
@@ -2573,7 +2553,7 @@
pw.print(token);
if (dumpAll) {
pw.println(':');
- token.dump(pw, " ");
+ token.dump(pw, " ", dumpAll);
} else {
pw.println();
}
@@ -3197,7 +3177,7 @@
/**
* A control placed at the appropriate level for transitions to occur.
*/
- SurfaceControl mAnimationLayer = null;
+ SurfaceControl mAppAnimationLayer = null;
// Cached reference to some special stacks we tend to get a lot so we don't need to loop
// through the list to find them.
@@ -3462,8 +3442,7 @@
// Make sure there is no animation running on this token, so any windows
// associated with it will be removed as soon as their animations are
// complete.
- token.mAppAnimator.clearAnimation();
- token.mAppAnimator.animating = false;
+ cancelAnimation();
if (DEBUG_ADD_REMOVE || DEBUG_TOKEN_MOVEMENT) Slog.v(TAG,
"performLayout: App token exiting now removed" + token);
token.removeIfPossible();
@@ -3544,19 +3523,28 @@
// The appropriate place for App-Transitions to occur is right
// above all other animations but still below things in the Picture-and-Picture
// windowing mode.
- if (mAnimationLayer != null) {
- t.setLayer(mAnimationLayer, layer++);
+ if (mAppAnimationLayer != null) {
+ t.setLayer(mAppAnimationLayer, layer++);
}
}
@Override
+ SurfaceControl getAppAnimationLayer() {
+ return mAppAnimationLayer;
+ }
+
+ @Override
void onParentSet() {
super.onParentSet();
if (getParent() != null) {
- mAnimationLayer = makeSurface().build();
+ mAppAnimationLayer = makeChildSurface(null)
+ .setName("animationLayer")
+ .build();
+ getPendingTransaction().show(mAppAnimationLayer);
+ scheduleAnimation();
} else {
- mAnimationLayer.destroy();
- mAnimationLayer = null;
+ mAppAnimationLayer.destroy();
+ mAppAnimationLayer = null;
}
}
}
diff --git a/services/core/java/com/android/server/wm/LocalAnimationAdapter.java b/services/core/java/com/android/server/wm/LocalAnimationAdapter.java
index 5fe4565..2173fa3 100644
--- a/services/core/java/com/android/server/wm/LocalAnimationAdapter.java
+++ b/services/core/java/com/android/server/wm/LocalAnimationAdapter.java
@@ -16,10 +16,9 @@
package com.android.server.wm;
-import android.graphics.Point;
+import android.os.SystemClock;
import android.view.SurfaceControl;
import android.view.SurfaceControl.Transaction;
-import android.view.animation.Animation;
import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback;
@@ -30,6 +29,7 @@
class LocalAnimationAdapter implements AnimationAdapter {
private final AnimationSpec mSpec;
+
private final SurfaceAnimationRunner mAnimator;
LocalAnimationAdapter(AnimationSpec spec, SurfaceAnimationRunner animator) {
@@ -64,6 +64,11 @@
return mSpec.getDuration();
}
+ @Override
+ public long getStatusBarTransitionsStartTime() {
+ return mSpec.calculateStatusBarTransitionStartTime();
+ }
+
/**
* Describes how to apply an animation.
*/
@@ -84,6 +89,13 @@
}
/**
+ * @see AnimationAdapter#getStatusBarTransitionsStartTime
+ */
+ default long calculateStatusBarTransitionStartTime() {
+ return SystemClock.uptimeMillis();
+ }
+
+ /**
* @return The duration of the animation.
*/
long getDuration();
@@ -91,10 +103,17 @@
/**
* Called when the spec needs to apply the current animation state to the leash.
*
- * @param t The transaction to use to apply a transform.
- * @param leash The leash to apply the state to.
+ * @param t The transaction to use to apply a transform.
+ * @param leash The leash to apply the state to.
* @param currentPlayTime The current time of the animation.
*/
void apply(Transaction t, SurfaceControl leash, long currentPlayTime);
+
+ /**
+ * @see AppTransition#canSkipFirstFrame
+ */
+ default boolean canSkipFirstFrame() {
+ return false;
+ }
}
}
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index e653e7d..2a77c92 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -612,7 +612,7 @@
defaultDisplay.pendingLayoutChanges);
}
- if (!mService.mAnimator.mAppWindowAnimating && mService.mAppTransition.isRunning()) {
+ if (!isAppAnimating() && mService.mAppTransition.isRunning()) {
// We have finished the animation of an app transition. To do this, we have delayed a
// lot of operations like showing and hiding apps, moving apps in Z-order, etc. The app
// token list reflects the correct Z-order, but the window list may now be out of sync
@@ -1035,7 +1035,7 @@
final int count = mChildren.size();
for (int i = 0; i < count; ++i) {
final DisplayContent displayContent = mChildren.get(i);
- displayContent.dump(" ", pw);
+ displayContent.dump(pw, " ", true /* dumpAll */);
}
} else {
pw.println(" NO DISPLAY");
diff --git a/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java b/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java
index 3ef9b3f..3a41eb0 100644
--- a/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java
+++ b/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java
@@ -16,6 +16,7 @@
package com.android.server.wm;
+import static android.util.TimeUtils.NANOS_PER_MS;
import static android.view.Choreographer.CALLBACK_TRAVERSAL;
import static android.view.Choreographer.getSfInstance;
@@ -142,6 +143,7 @@
// Transaction will be applied in the commit phase.
scheduleApplyTransaction();
});
+
anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
@@ -158,6 +160,7 @@
mRunningAnimations.remove(a.mLeash);
synchronized (mCancelLock) {
if (!a.mCancelled) {
+
// Post on other thread that we can push final state without jank.
AnimationThread.getHandler().post(a.mFinishCallback);
}
@@ -166,6 +169,14 @@
}
});
anim.start();
+ if (a.mAnimSpec.canSkipFirstFrame()) {
+ // If we can skip the first frame, we start one frame later.
+ anim.setCurrentPlayTime(mChoreographer.getFrameIntervalNanos() / NANOS_PER_MS);
+ }
+
+ // Immediately start the animation by manually applying an animation frame. Otherwise, the
+ // start time would only be set in the next frame, leading to a delay.
+ anim.doAnimationFrame(mChoreographer.getFrameTime());
a.mAnim = anim;
mRunningAnimations.put(a.mLeash, a);
}
@@ -189,6 +200,7 @@
}
private void applyTransaction() {
+ mFrameTransaction.setAnimationTransaction();
mFrameTransaction.apply();
mApplyScheduled = false;
}
diff --git a/services/core/java/com/android/server/wm/SurfaceAnimator.java b/services/core/java/com/android/server/wm/SurfaceAnimator.java
index e165211..a32e711 100644
--- a/services/core/java/com/android/server/wm/SurfaceAnimator.java
+++ b/services/core/java/com/android/server/wm/SurfaceAnimator.java
@@ -22,10 +22,13 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.util.ArrayMap;
import android.util.Slog;
import android.view.SurfaceControl;
import android.view.SurfaceControl.Transaction;
+import com.android.internal.annotations.VisibleForTesting;
+
import java.io.PrintWriter;
/**
@@ -41,7 +44,9 @@
private static final String TAG = TAG_WITH_CLASS_NAME ? "SurfaceAnimator" : TAG_WM;
private final WindowManagerService mService;
private AnimationAdapter mAnimation;
- private SurfaceControl mLeash;
+
+ @VisibleForTesting
+ SurfaceControl mLeash;
private final Animatable mAnimatable;
private final OnAnimationFinishedCallback mInnerAnimationFinishedCallback;
private final Runnable mAnimationFinishedCallback;
@@ -62,23 +67,34 @@
private OnAnimationFinishedCallback getFinishedCallback(Runnable animationFinishedCallback) {
return anim -> {
synchronized (mService.mWindowMap) {
- if (anim != mAnimation) {
- // Callback was from another animation - ignore.
+ final SurfaceAnimator target = mService.mAnimationTransferMap.remove(anim);
+ if (target != null) {
+ target.mInnerAnimationFinishedCallback.onAnimationFinished(anim);
return;
}
- final Transaction t = new Transaction();
- SurfaceControl.openTransaction();
- try {
- reset(t);
- animationFinishedCallback.run();
- } finally {
- // TODO: This should use pendingTransaction eventually, but right now things
- // happening on the animation finished callback are happening on the global
- // transaction.
- SurfaceControl.mergeToGlobalTransaction(t);
- SurfaceControl.closeTransaction();
- }
+ // TODO: This should use pendingTransaction eventually, but right now things
+ // happening on the animation finished callback are happening on the global
+ // transaction.
+ // For now we need to run this after it's guaranteed that the transaction that
+ // reparents the surface onto the leash is executed already. Otherwise this may be
+ // executed first, leading to surface loss, as the reparent operations wouldn't
+ // be in order.
+ mService.mAnimator.addAfterPrepareSurfacesRunnable(() -> {
+ if (anim != mAnimation) {
+ // Callback was from another animation - ignore.
+ return;
+ }
+ final Transaction t = new Transaction();
+ SurfaceControl.openTransaction();
+ try {
+ reset(t, true /* destroyLeash */);
+ animationFinishedCallback.run();
+ } finally {
+ SurfaceControl.mergeToGlobalTransaction(t);
+ SurfaceControl.closeTransaction();
+ }
+ });
}
};
}
@@ -95,7 +111,7 @@
* handing it to the component that is responsible to run the animation.
*/
void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden) {
- cancelAnimation(t, true /* restarting */);
+ cancelAnimation(t, true /* restarting */, true /* forwardCancel */);
mAnimation = anim;
final SurfaceControl surface = mAnimatable.getSurfaceControl();
if (surface == null) {
@@ -158,7 +174,8 @@
* Cancels any currently running animation.
*/
void cancelAnimation() {
- cancelAnimation(mAnimatable.getPendingTransaction(), false /* restarting */);
+ cancelAnimation(mAnimatable.getPendingTransaction(), false /* restarting */,
+ true /* forwardCancel */);
mAnimatable.commitPendingTransaction();
}
@@ -197,13 +214,47 @@
return mLeash != null;
}
- private void cancelAnimation(Transaction t, boolean restarting) {
+ void transferAnimation(SurfaceAnimator from) {
+ if (from.mLeash == null) {
+ return;
+ }
+ final SurfaceControl surface = mAnimatable.getSurfaceControl();
+ final SurfaceControl parent = mAnimatable.getAnimationLeashParent();
+ if (surface == null || parent == null) {
+ Slog.w(TAG, "Unable to transfer animation, surface or parent is null");
+ cancelAnimation();
+ return;
+ }
+ endDelayingAnimationStart();
+ final Transaction t = mAnimatable.getPendingTransaction();
+ cancelAnimation(t, true /* restarting */, true /* forwardCancel */);
+ mLeash = from.mLeash;
+ mAnimation = from.mAnimation;
+
+ // Cancel source animation, but don't let animation runner cancel the animation.
+ from.cancelAnimation(t, false /* restarting */, false /* forwardCancel */);
+ t.reparent(surface, mLeash.getHandle());
+ t.reparent(mLeash, parent.getHandle());
+ mAnimatable.onAnimationLeashCreated(t, mLeash);
+ mService.mAnimationTransferMap.put(mAnimation, this);
+ }
+
+ /**
+ * Cancels the animation, and resets the leash.
+ *
+ * @param t The transaction to use for all cancelling surface operations.
+ * @param restarting Whether we are restarting the animation.
+ * @param forwardCancel Whether to forward the cancel signal to the adapter executing the
+ * animation. This will be set to false when just transferring an animation
+ * to another animator.
+ */
+ private void cancelAnimation(Transaction t, boolean restarting, boolean forwardCancel) {
if (DEBUG_ANIM) Slog.i(TAG, "Cancelling animation restarting=" + restarting);
final SurfaceControl leash = mLeash;
final AnimationAdapter animation = mAnimation;
- reset(t);
+ reset(t, forwardCancel);
if (animation != null) {
- if (!mAnimationStartDelayed) {
+ if (!mAnimationStartDelayed && forwardCancel) {
animation.onAnimationCancelled(leash);
}
if (!restarting) {
@@ -215,7 +266,7 @@
}
}
- private void reset(Transaction t) {
+ private void reset(Transaction t, boolean destroyLeash) {
final SurfaceControl surface = mAnimatable.getSurfaceControl();
final SurfaceControl parent = mAnimatable.getParentSurfaceControl();
@@ -225,7 +276,8 @@
if (DEBUG_ANIM) Slog.i(TAG, "Reparenting to original parent");
t.reparent(surface, parent.getHandle());
}
- if (mLeash != null) {
+ mService.mAnimationTransferMap.remove(mAnimation);
+ if (mLeash != null && destroyLeash) {
mAnimatable.destroyAfterPendingTransaction(mLeash);
}
mLeash = null;
@@ -241,6 +293,7 @@
int height, boolean hidden) {
if (DEBUG_ANIM) Slog.i(TAG, "Reparenting to leash");
final SurfaceControl.Builder builder = mAnimatable.makeAnimationLeash()
+ .setParent(mAnimatable.getAnimationLeashParent())
.setName(surface + " - animation-leash")
.setSize(width, height);
final SurfaceControl leash = builder.build();
@@ -309,6 +362,11 @@
SurfaceControl.Builder makeAnimationLeash();
/**
+ * @return The parent that should be used for the animation leash.
+ */
+ @Nullable SurfaceControl getAnimationLeashParent();
+
+ /**
* @return The surface of the object to be animated.
*/
@Nullable SurfaceControl getSurfaceControl();
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 244eb66..3c96ca1 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -536,14 +536,7 @@
/** Cancels any running app transitions associated with the task. */
void cancelTaskWindowTransition() {
for (int i = mChildren.size() - 1; i >= 0; --i) {
- mChildren.get(i).mAppAnimator.clearAnimation();
- }
- }
-
- /** Cancels any running thumbnail transitions associated with the task. */
- void cancelTaskThumbnailTransition() {
- for (int i = mChildren.size() - 1; i >= 0; --i) {
- mChildren.get(i).mAppAnimator.clearThumbnail();
+ mChildren.get(i).cancelAnimation();
}
}
@@ -656,6 +649,9 @@
mDimmer.resetDimStates();
super.prepareSurfaces();
getDimBounds(mTmpDimBoundsRect);
+
+ // Bounds need to be relative, as the dim layer is a child.
+ mTmpDimBoundsRect.offsetTo(0, 0);
if (mDimmer.updateDims(getPendingTransaction(), mTmpDimBoundsRect)) {
scheduleAnimation();
}
@@ -677,7 +673,9 @@
proto.end(token);
}
- public void dump(String prefix, PrintWriter pw) {
+ @Override
+ public void dump(PrintWriter pw, String prefix, boolean dumpAll) {
+ super.dump(pw, prefix, dumpAll);
final String doublePrefix = prefix + " ";
pw.println(prefix + "taskId=" + mTaskId);
@@ -691,7 +689,7 @@
for (int i = mChildren.size() - 1; i >= 0; i--) {
final AppWindowToken wtoken = mChildren.get(i);
pw.println(triplePrefix + "Activity #" + i + " " + wtoken);
- wtoken.dump(pw, triplePrefix);
+ wtoken.dump(pw, triplePrefix, dumpAll);
}
}
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java
index c091157..f79719c 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotController.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java
@@ -93,6 +93,8 @@
private final ArraySet<Task> mTmpTasks = new ArraySet<>();
private final Handler mHandler = new Handler();
+ private final Rect mTmpRect = new Rect();
+
/**
* Flag indicating whether we are running on an Android TV device.
*/
@@ -223,11 +225,11 @@
final boolean isLowRamDevice = ActivityManager.isLowRamDeviceStatic();
final float scaleFraction = isLowRamDevice ? REDUCED_SCALE : 1f;
- final Rect taskFrame = new Rect();
- task.getBounds(taskFrame);
+ task.getBounds(mTmpRect);
+ mTmpRect.offsetTo(0, 0);
final GraphicBuffer buffer = SurfaceControl.captureLayers(
- task.getSurfaceControl().getHandle(), taskFrame, scaleFraction);
+ task.getSurfaceControl().getHandle(), mTmpRect, scaleFraction);
if (buffer == null || buffer.getWidth() <= 1 || buffer.getHeight() <= 1) {
if (DEBUG_SCREENSHOT) {
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index 9946c6a..eb8eae1 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -31,9 +31,8 @@
import static android.view.WindowManager.DOCKED_LEFT;
import static android.view.WindowManager.DOCKED_RIGHT;
import static android.view.WindowManager.DOCKED_TOP;
-import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
+
import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_DOCKED_DIVIDER;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TASK_MOVEMENT;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import static com.android.server.wm.proto.StackProto.ANIMATION_BACKGROUND_SURFACE_IS_DIMMING;
@@ -45,6 +44,7 @@
import android.annotation.CallSuper;
import android.content.res.Configuration;
+import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.Region;
import android.os.RemoteException;
@@ -146,6 +146,7 @@
* For {@link #prepareSurfaces}.
*/
final Rect mTmpDimBoundsRect = new Rect();
+ private final Point mLastSurfaceSize = new Point();
TaskStack(WindowManagerService service, int stackId, StackWindowController controller) {
super(service);
@@ -220,6 +221,8 @@
}
alignTasksToAdjustedBounds(adjusted ? mAdjustedBounds : getRawBounds(), insetBounds);
mDisplayContent.setLayoutNeeded();
+
+ updateSurfaceBounds();
}
private void alignTasksToAdjustedBounds(Rect adjustedBounds, Rect tempInsetBounds) {
@@ -241,10 +244,11 @@
return;
}
getRawBounds(mTmpRect);
- // TODO: Should be in relative coordinates.
- getPendingTransaction().setSize(mAnimationBackgroundSurface, mTmpRect.width(),
- mTmpRect.height()).setPosition(mAnimationBackgroundSurface, mTmpRect.left,
- mTmpRect.top);
+ final Rect stackBounds = getBounds();
+ getPendingTransaction()
+ .setSize(mAnimationBackgroundSurface, mTmpRect.width(), mTmpRect.height())
+ .setPosition(mAnimationBackgroundSurface, mTmpRect.left - stackBounds.left,
+ mTmpRect.top - stackBounds.top);
scheduleAnimation();
}
@@ -297,6 +301,7 @@
updateAdjustedBounds();
+ updateSurfaceBounds();
return result;
}
@@ -317,7 +322,7 @@
if (matchParentBounds()
|| !inSplitScreenSecondaryWindowingMode()
|| mDisplayContent == null
- || mDisplayContent.getSplitScreenPrimaryStack() != null) {
+ || mDisplayContent.getSplitScreenPrimaryStackIgnoringVisibility() != null) {
return true;
}
return false;
@@ -711,8 +716,12 @@
@Override
public void onConfigurationChanged(Configuration newParentConfig) {
final int prevWindowingMode = getWindowingMode();
+ // Only need to update surface size here since the super method will handle updating
+ // surface position.
+ updateSurfaceSize(getPendingTransaction());
super.onConfigurationChanged(newParentConfig);
final int windowingMode = getWindowingMode();
+
if (mDisplayContent == null || prevWindowingMode == windowingMode) {
return;
}
@@ -720,6 +729,31 @@
updateBoundsForWindowModeChange();
}
+ private void updateSurfaceBounds() {
+ updateSurfaceBounds(getPendingTransaction());
+ scheduleAnimation();
+ }
+
+ void updateSurfaceBounds(SurfaceControl.Transaction transaction) {
+ updateSurfaceSize(transaction);
+ updateSurfacePosition(transaction);
+ }
+
+ private void updateSurfaceSize(SurfaceControl.Transaction transaction) {
+ if (mSurfaceControl == null) {
+ return;
+ }
+
+ final Rect stackBounds = getBounds();
+ final int width = stackBounds.width();
+ final int height = stackBounds.height();
+ if (width == mLastSurfaceSize.x && height == mLastSurfaceSize.y) {
+ return;
+ }
+ transaction.setSize(mSurfaceControl, width, height);
+ mLastSurfaceSize.set(width, height);
+ }
+
@Override
void onDisplayChanged(DisplayContent dc) {
if (mDisplayContent != null) {
@@ -1284,7 +1318,8 @@
proto.end(token);
}
- public void dump(String prefix, PrintWriter pw) {
+ @Override
+ void dump(PrintWriter pw, String prefix, boolean dumpAll) {
pw.println(prefix + "mStackId=" + mStackId);
pw.println(prefix + "mDeferRemoval=" + mDeferRemoval);
pw.println(prefix + "mBounds=" + getRawBounds().toShortString());
@@ -1300,7 +1335,7 @@
pw.println(prefix + "mAdjustedBounds=" + mAdjustedBounds.toShortString());
}
for (int taskNdx = mChildren.size() - 1; taskNdx >= 0; taskNdx--) {
- mChildren.get(taskNdx).dump(prefix + " ", pw);
+ mChildren.get(taskNdx).dump(pw, prefix + " ", dumpAll);
}
if (mAnimationBackgroundSurfaceIsShown) {
pw.println(prefix + "mWindowAnimationBackgroundSurface is shown");
@@ -1313,7 +1348,7 @@
pw.print(" Exiting App #"); pw.print(i);
pw.print(' '); pw.print(token);
pw.println(':');
- token.dump(pw, " ");
+ token.dump(pw, " ", dumpAll);
}
}
}
@@ -1653,35 +1688,6 @@
return super.checkCompleteDeferredRemoval();
}
- void stepAppWindowsAnimation(long currentTime) {
- super.stepAppWindowsAnimation(currentTime);
-
- // TODO: Why aren't we just using the loop above for this? mAppAnimator.animating isn't set
- // below but is set in the loop above. See if it really matters...
-
- // Clear before using.
- mTmpAppTokens.clear();
- // We copy the list as things can be removed from the exiting token list while we are
- // processing.
- mTmpAppTokens.addAll(mExitingAppTokens);
- for (int i = 0; i < mTmpAppTokens.size(); i++) {
- final AppWindowAnimator appAnimator = mTmpAppTokens.get(i).mAppAnimator;
- appAnimator.wasAnimating = appAnimator.animating;
- if (appAnimator.stepAnimationLocked(currentTime)) {
- mService.mAnimator.setAnimating(true);
- mService.mAnimator.mAppWindowAnimating = true;
- } else if (appAnimator.wasAnimating) {
- // stopped animating, do one more pass through the layout
- appAnimator.mAppToken.setAppLayoutChanges(FINISH_LAYOUT_REDO_WALLPAPER,
- "exiting appToken " + appAnimator.mAppToken + " done");
- if (DEBUG_ANIM) Slog.v(TAG_WM,
- "updateWindowsApps...: done animating exiting " + appAnimator.mAppToken);
- }
- }
- // Clear to avoid holding reference to tokens.
- mTmpAppTokens.clear();
- }
-
@Override
int getOrientation() {
return (canSpecifyOrientation()) ? super.getOrientation() : SCREEN_ORIENTATION_UNSET;
@@ -1705,6 +1711,9 @@
mDimmer.resetDimStates();
super.prepareSurfaces();
getDimBounds(mTmpDimBoundsRect);
+
+ // Bounds need to be relative, as the dim layer is a child.
+ mTmpDimBoundsRect.offsetTo(0, 0);
if (mDimmer.updateDims(getPendingTransaction(), mTmpDimBoundsRect)) {
scheduleAnimation();
}
diff --git a/services/core/java/com/android/server/wm/TaskWindowContainerController.java b/services/core/java/com/android/server/wm/TaskWindowContainerController.java
index 5caae32..d83f28c 100644
--- a/services/core/java/com/android/server/wm/TaskWindowContainerController.java
+++ b/services/core/java/com/android/server/wm/TaskWindowContainerController.java
@@ -199,16 +199,6 @@
}
}
- public void cancelThumbnailTransition() {
- synchronized (mWindowMap) {
- if (mContainer == null) {
- Slog.w(TAG_WM, "cancelThumbnailTransition: taskId " + mTaskId + " not found.");
- return;
- }
- mContainer.cancelTaskThumbnailTransition();
- }
- }
-
public void setTaskDescription(TaskDescription taskDescription) {
synchronized (mWindowMap) {
if (mContainer == null) {
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index 3ae4549..ac0919d 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -64,8 +64,6 @@
// to another, and this is the previous wallpaper target.
private WindowState mPrevWallpaperTarget = null;
- private int mWallpaperAnimLayerAdjustment;
-
private float mLastWallpaperX = -1;
private float mLastWallpaperY = -1;
private float mLastWallpaperXStep = -1;
@@ -112,7 +110,7 @@
mFindResults.resetTopWallpaper = true;
if (w != winAnimator.mWindowDetachedWallpaper && w.mAppToken != null) {
// If this window's app token is hidden and not animating, it is of no interest to us.
- if (w.mAppToken.hidden && w.mAppToken.mAppAnimator.animation == null) {
+ if (w.mAppToken.isHidden() && !w.mAppToken.isSelfAnimating()) {
if (DEBUG_WALLPAPER) Slog.v(TAG,
"Skipping hidden and not animating token: " + w);
return false;
@@ -130,10 +128,10 @@
}
final boolean keyguardGoingAwayWithWallpaper = (w.mAppToken != null
- && AppTransition.isKeyguardGoingAwayTransit(
- w.mAppToken.mAppAnimator.getTransit())
- && (w.mAppToken.mAppAnimator.getTransitFlags()
- & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER) != 0);
+ && w.mAppToken.isSelfAnimating()
+ && AppTransition.isKeyguardGoingAwayTransit(w.mAppToken.getTransit())
+ && (w.mAppToken.getTransitFlags()
+ & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER) != 0);
boolean needsShowWhenLockedWallpaper = false;
if ((w.mAttrs.flags & FLAG_SHOW_WHEN_LOCKED) != 0
@@ -204,18 +202,19 @@
private boolean isWallpaperVisible(WindowState wallpaperTarget) {
if (DEBUG_WALLPAPER) Slog.v(TAG, "Wallpaper vis: target " + wallpaperTarget + ", obscured="
+ (wallpaperTarget != null ? Boolean.toString(wallpaperTarget.mObscured) : "??")
- + " anim=" + ((wallpaperTarget != null && wallpaperTarget.mAppToken != null)
- ? wallpaperTarget.mAppToken.mAppAnimator.animation : null)
+ + " animating=" + ((wallpaperTarget != null && wallpaperTarget.mAppToken != null)
+ ? wallpaperTarget.mAppToken.isSelfAnimating() : null)
+ " prev=" + mPrevWallpaperTarget);
return (wallpaperTarget != null
&& (!wallpaperTarget.mObscured || (wallpaperTarget.mAppToken != null
- && wallpaperTarget.mAppToken.mAppAnimator.animation != null)))
+ && wallpaperTarget.mAppToken.isSelfAnimating())))
|| mPrevWallpaperTarget != null;
}
boolean isWallpaperTargetAnimating() {
return mWallpaperTarget != null && mWallpaperTarget.mWinAnimator.isAnimationSet()
- && !mWallpaperTarget.mWinAnimator.isDummyAnimation();
+ && (mWallpaperTarget.mAppToken == null
+ || !mWallpaperTarget.mAppToken.isWaitingForTransitionStart());
}
void updateWallpaperVisibility() {
@@ -250,7 +249,7 @@
for (int i = mWallpaperTokens.size() - 1; i >= 0; i--) {
final WallpaperWindowToken token = mWallpaperTokens.get(i);
token.hideWallpaperToken(wasDeferred, "hideWallpapers");
- if (DEBUG_WALLPAPER_LIGHT && !token.hidden) Slog.d(TAG, "Hiding wallpaper " + token
+ if (DEBUG_WALLPAPER_LIGHT && !token.isHidden()) Slog.d(TAG, "Hiding wallpaper " + token
+ " from " + winGoingAway + " target=" + mWallpaperTarget + " prev="
+ mPrevWallpaperTarget + "\n" + Debug.getCallers(5, " "));
}
@@ -441,10 +440,6 @@
}
}
- int getAnimLayerAdjustment() {
- return mWallpaperAnimLayerAdjustment;
- }
-
private void findWallpaperTarget(DisplayContent dc) {
mFindResults.reset();
if (dc.isStackVisible(WINDOWING_MODE_FREEFORM)) {
@@ -546,7 +541,7 @@
private void updateWallpaperTokens(boolean visible) {
for (int curTokenNdx = mWallpaperTokens.size() - 1; curTokenNdx >= 0; curTokenNdx--) {
final WallpaperWindowToken token = mWallpaperTokens.get(curTokenNdx);
- token.updateWallpaperWindows(visible, mWallpaperAnimLayerAdjustment);
+ token.updateWallpaperWindows(visible);
token.getDisplayContent().assignWindowLayers(false);
}
}
@@ -565,12 +560,6 @@
if (DEBUG_WALLPAPER) Slog.v(TAG, "Wallpaper visibility: " + visible);
if (visible) {
- // If the wallpaper target is animating, we may need to copy its layer adjustment.
- // Only do this if we are not transferring between two wallpaper targets.
- mWallpaperAnimLayerAdjustment =
- (mPrevWallpaperTarget == null && mWallpaperTarget.mAppToken != null)
- ? mWallpaperTarget.mAppToken.getAnimLayerAdjustment() : 0;
-
if (mWallpaperTarget.mWallpaperX >= 0) {
mLastWallpaperX = mWallpaperTarget.mWallpaperX;
mLastWallpaperXStep = mWallpaperTarget.mWallpaperXStep;
@@ -681,10 +670,6 @@
pw.print("mLastWallpaperDisplayOffsetX="); pw.print(mLastWallpaperDisplayOffsetX);
pw.print(" mLastWallpaperDisplayOffsetY="); pw.println(mLastWallpaperDisplayOffsetY);
}
-
- if (mWallpaperAnimLayerAdjustment != 0) {
- pw.println(prefix + "mWallpaperAnimLayerAdjustment=" + mWallpaperAnimLayerAdjustment);
- }
}
/** Helper class for storing the results of a wallpaper target find operation. */
diff --git a/services/core/java/com/android/server/wm/WallpaperWindowToken.java b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
index 3389f71..2ae5c7b 100644
--- a/services/core/java/com/android/server/wm/WallpaperWindowToken.java
+++ b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
@@ -16,12 +16,9 @@
package com.android.server.wm;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD;
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ADD_REMOVE;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYERS;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER_LIGHT;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WINDOW_MOVEMENT;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
@@ -56,7 +53,7 @@
final WindowState wallpaper = mChildren.get(j);
wallpaper.hideWallpaperWindow(wasDeferred, reason);
}
- hidden = true;
+ setHidden(true);
}
void sendWindowWallpaperCommand(
@@ -92,8 +89,9 @@
final int dw = displayInfo.logicalWidth;
final int dh = displayInfo.logicalHeight;
- if (hidden == visible) {
- hidden = !visible;
+ if (isHidden() == visible) {
+ setHidden(!visible);
+
// Need to do a layout to ensure the wallpaper now has the correct size.
mDisplayContent.setLayoutNeeded();
}
@@ -119,12 +117,12 @@
}
}
- void updateWallpaperWindows(boolean visible, int animLayerAdj) {
+ void updateWallpaperWindows(boolean visible) {
- if (hidden == visible) {
+ if (isHidden() == visible) {
if (DEBUG_WALLPAPER_LIGHT) Slog.d(TAG,
"Wallpaper token " + token + " hidden=" + !visible);
- hidden = !visible;
+ setHidden(!visible);
// Need to do a layout to ensure the wallpaper now has the correct size.
mDisplayContent.setLayoutNeeded();
}
diff --git a/services/core/java/com/android/server/wm/WindowAnimationSpec.java b/services/core/java/com/android/server/wm/WindowAnimationSpec.java
index bb25297..9865293 100644
--- a/services/core/java/com/android/server/wm/WindowAnimationSpec.java
+++ b/services/core/java/com/android/server/wm/WindowAnimationSpec.java
@@ -16,11 +16,20 @@
package com.android.server.wm;
+import static com.android.server.wm.AnimationAdapter.STATUS_BAR_TRANSITION_DURATION;
+import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_AFTER_ANIM;
+import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_NONE;
+
import android.graphics.Point;
+import android.graphics.Rect;
+import android.os.SystemClock;
import android.view.SurfaceControl;
import android.view.SurfaceControl.Transaction;
import android.view.animation.Animation;
+import android.view.animation.AnimationSet;
+import android.view.animation.Interpolator;
import android.view.animation.Transformation;
+import android.view.animation.TranslateAnimation;
import com.android.server.wm.LocalAnimationAdapter.AnimationSpec;
@@ -32,10 +41,26 @@
private Animation mAnimation;
private final Point mPosition = new Point();
private final ThreadLocal<TmpValues> mThreadLocalTmps = ThreadLocal.withInitial(TmpValues::new);
+ private final boolean mCanSkipFirstFrame;
+ private final Rect mStackBounds = new Rect();
+ private int mStackClipMode;
+ private final Rect mTmpRect = new Rect();
- public WindowAnimationSpec(Animation animation, Point position) {
+ public WindowAnimationSpec(Animation animation, Point position, boolean canSkipFirstFrame) {
+ this(animation, position, null /* stackBounds */, canSkipFirstFrame, STACK_CLIP_NONE);
+ }
+
+ public WindowAnimationSpec(Animation animation, Point position, Rect stackBounds,
+ boolean canSkipFirstFrame, int stackClipMode) {
mAnimation = animation;
- mPosition.set(position.x, position.y);
+ if (position != null) {
+ mPosition.set(position.x, position.y);
+ }
+ mCanSkipFirstFrame = canSkipFirstFrame;
+ mStackClipMode = stackClipMode;
+ if (stackBounds != null) {
+ mStackBounds.set(stackBounds);
+ }
}
@Override
@@ -61,6 +86,77 @@
tmp.transformation.getMatrix().postTranslate(mPosition.x, mPosition.y);
t.setMatrix(leash, tmp.transformation.getMatrix(), tmp.floats);
t.setAlpha(leash, tmp.transformation.getAlpha());
+ if (mStackClipMode == STACK_CLIP_NONE) {
+ t.setWindowCrop(leash, tmp.transformation.getClipRect());
+ } else if (mStackClipMode == STACK_CLIP_AFTER_ANIM) {
+ t.setFinalCrop(leash, mStackBounds);
+ t.setWindowCrop(leash, tmp.transformation.getClipRect());
+ } else {
+ mTmpRect.set(tmp.transformation.getClipRect());
+ mTmpRect.intersect(mStackBounds);
+ t.setWindowCrop(leash, mTmpRect);
+ }
+ }
+
+ @Override
+ public long calculateStatusBarTransitionStartTime() {
+ TranslateAnimation openTranslateAnimation = findTranslateAnimation(mAnimation);
+ if (openTranslateAnimation != null) {
+
+ // Some interpolators are extremely quickly mostly finished, but not completely. For
+ // our purposes, we need to find the fraction for which ther interpolator is mostly
+ // there, and use that value for the calculation.
+ float t = findAlmostThereFraction(openTranslateAnimation.getInterpolator());
+ return SystemClock.uptimeMillis()
+ + openTranslateAnimation.getStartOffset()
+ + (long)(openTranslateAnimation.getDuration() * t)
+ - STATUS_BAR_TRANSITION_DURATION;
+ } else {
+ return SystemClock.uptimeMillis();
+ }
+ }
+
+ @Override
+ public boolean canSkipFirstFrame() {
+ return mCanSkipFirstFrame;
+ }
+
+ /**
+ * Tries to find a {@link TranslateAnimation} inside the {@code animation}.
+ *
+ * @return the found animation, {@code null} otherwise
+ */
+ private static TranslateAnimation findTranslateAnimation(Animation animation) {
+ if (animation instanceof TranslateAnimation) {
+ return (TranslateAnimation) animation;
+ } else if (animation instanceof AnimationSet) {
+ AnimationSet set = (AnimationSet) animation;
+ for (int i = 0; i < set.getAnimations().size(); i++) {
+ Animation a = set.getAnimations().get(i);
+ if (a instanceof TranslateAnimation) {
+ return (TranslateAnimation) a;
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Binary searches for a {@code t} such that there exists a {@code -0.01 < eps < 0.01} for which
+ * {@code interpolator(t + eps) > 0.99}.
+ */
+ private static float findAlmostThereFraction(Interpolator interpolator) {
+ float val = 0.5f;
+ float adj = 0.25f;
+ while (adj >= 0.01f) {
+ if (interpolator.getInterpolation(val) < 0.99f) {
+ val += adj;
+ } else {
+ val -= adj;
+ }
+ adj /= 2;
+ }
+ return val;
}
private static class TmpValues {
diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java
index 7c56f00..7295873 100644
--- a/services/core/java/com/android/server/wm/WindowAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowAnimator.java
@@ -35,6 +35,7 @@
import com.android.server.policy.WindowManagerPolicy;
import java.io.PrintWriter;
+import java.util.ArrayList;
/**
* Singleton class that carries out the animations and Surface operations in a separate task
@@ -50,16 +51,14 @@
/** Is any window animating? */
private boolean mAnimating;
- private boolean mLastAnimating;
-
- /** Is any app window animating? */
- boolean mAppWindowAnimating;
+ private boolean mLastRootAnimating;
final Choreographer.FrameCallback mAnimationFrameCallback;
/** Time of current animation step. Reset on each iteration */
long mCurrentTime;
+ boolean mAppWindowAnimating;
/** Skip repeated AppWindowTokens initialization. Note that AppWindowsToken's version of this
* is a long initialized to Long.MIN_VALUE so that it doesn't match this value on startup. */
int mAnimTransactionSequence;
@@ -89,6 +88,12 @@
*/
private boolean mAnimationFrameCallbackScheduled;
+ /**
+ * A list of runnable that need to be run after {@link WindowContainer#prepareSurfaces} is
+ * executed and the corresponding transaction is closed and applied.
+ */
+ private final ArrayList<Runnable> mAfterPrepareSurfacesRunnables = new ArrayList<>();
+
WindowAnimator(final WindowManagerService service) {
mService = service;
mContext = service.mContext;
@@ -142,21 +147,10 @@
scheduleAnimation();
}
- // Simulate back-pressure by opening and closing an empty animation transaction. This makes
- // sure that an animation frame is at least presented once on the screen. We do this outside
- // of the regular transaction such that we can avoid holding the window manager lock in case
- // we receive back-pressure from SurfaceFlinger. Since closing an animation transaction
- // without the window manager locks leads to ordering issues (as the transaction will be
- // processed only at the beginning of the next frame which may result in another transaction
- // that was executed later in WM side gets executed first on SF side), we don't update any
- // Surface properties here such that reordering doesn't cause issues.
- mService.executeEmptyAnimationTransaction();
-
synchronized (mService.mWindowMap) {
mCurrentTime = frameTimeNs / TimeUtils.NANOS_PER_MS;
mBulkUpdateParams = SET_ORIENTATION_CHANGE_COMPLETE;
mAnimating = false;
- mAppWindowAnimating = false;
if (DEBUG_WINDOW_TRACE) {
Slog.i(TAG, "!!! animate: entry time=" + mCurrentTime);
}
@@ -170,7 +164,6 @@
for (int i = 0; i < numDisplays; i++) {
final int displayId = mDisplayContentsAnimators.keyAt(i);
final DisplayContent dc = mService.mRoot.getDisplayContentOrCreate(displayId);
- dc.stepAppWindowsAnimation(mCurrentTime);
DisplayContentsAnimator displayAnimator = mDisplayContentsAnimators.valueAt(i);
final ScreenRotationAnimation screenRotationAnimation =
@@ -251,7 +244,8 @@
mWindowPlacerLocked.requestTraversal();
}
- if (mAnimating && !mLastAnimating) {
+ final boolean rootAnimating = mService.mRoot.isSelfOrChildAnimating();
+ if (rootAnimating && !mLastRootAnimating) {
// Usually app transitions but quite a load onto the system already (with all the
// things happening in app), so pause task snapshot persisting to not increase the
@@ -259,13 +253,13 @@
mService.mTaskSnapshotController.setPersisterPaused(true);
Trace.asyncTraceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "animating", 0);
}
- if (!mAnimating && mLastAnimating) {
+ if (!rootAnimating && mLastRootAnimating) {
mWindowPlacerLocked.requestTraversal();
mService.mTaskSnapshotController.setPersisterPaused(false);
Trace.asyncTraceEnd(Trace.TRACE_TAG_WINDOW_MANAGER, "animating", 0);
}
- mLastAnimating = mAnimating;
+ mLastRootAnimating = rootAnimating;
if (mRemoveReplacedWindows) {
mService.mRoot.removeReplacedWindows();
@@ -275,6 +269,7 @@
mService.destroyPreservedSurfaceLocked();
mService.mWindowPlacerLocked.destroyPendingSurfaces();
+ executeAfterPrepareSurfacesRunnables();
if (DEBUG_WINDOW_TRACE) {
Slog.i(TAG, "!!! animate: exit mAnimating=" + mAnimating
@@ -438,4 +433,23 @@
void orAnimating(boolean animating) {
mAnimating |= animating;
}
+
+ /**
+ * Adds a runnable to be executed after {@link WindowContainer#prepareSurfaces} is called and
+ * the corresponding transaction is closed and applied.
+ */
+ void addAfterPrepareSurfacesRunnable(Runnable r) {
+ mAfterPrepareSurfacesRunnables.add(r);
+ scheduleAnimation();
+ }
+
+ private void executeAfterPrepareSurfacesRunnables() {
+
+ // Traverse in order they were added.
+ final int size = mAfterPrepareSurfacesRunnables.size();
+ for (int i = 0; i < size; i++) {
+ mAfterPrepareSurfacesRunnables.get(i).run();
+ }
+ mAfterPrepareSurfacesRunnables.clear();
+ }
}
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index b2b6119..af31410 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -29,7 +29,8 @@
import android.annotation.CallSuper;
import android.content.res.Configuration;
-import android.graphics.PixelFormat.Opacity;
+import android.graphics.Point;
+import android.graphics.Rect;
import android.util.Slog;
import android.view.MagnificationSpec;
import android.view.SurfaceControl;
@@ -93,6 +94,12 @@
protected final SurfaceAnimator mSurfaceAnimator;
protected final WindowManagerService mService;
+ private final Point mTmpPos = new Point();
+ protected final Point mLastSurfacePosition = new Point();
+
+ /** Total number of elements in this subtree, including our own hierarchy element. */
+ private int mTreeWeight = 1;
+
WindowContainer(WindowManagerService service) {
mService = service;
mPendingTransaction = service.mTransactionFactory.make();
@@ -114,6 +121,13 @@
return mChildren.get(index);
}
+ @Override
+ public void onConfigurationChanged(Configuration newParentConfig) {
+ super.onConfigurationChanged(newParentConfig);
+ updateSurfacePosition(getPendingTransaction());
+ scheduleAnimation();
+ }
+
final protected void setParent(WindowContainer<WindowContainer> parent) {
mParent = parent;
// Removing parent usually means that we've detached this entity to destroy it or to attach
@@ -147,7 +161,7 @@
// surface animator such that hierarchy is preserved when animating, i.e.
// mSurfaceControl stays attached to the leash and we just reparent the leash to the
// new parent.
- mSurfaceAnimator.reparent(getPendingTransaction(), mParent.mSurfaceControl);
+ reparentSurfaceControl(getPendingTransaction(), mParent.mSurfaceControl);
}
// Either way we need to ask the parent to assign us a Z-order.
@@ -190,6 +204,8 @@
} else {
mChildren.add(positionToAdd, child);
}
+ onChildAdded(child);
+
// Set the parent after we've actually added a child in case a subclass depends on this.
child.setParent(this);
}
@@ -203,10 +219,21 @@
+ " can't add to container=" + getName());
}
mChildren.add(index, child);
+ onChildAdded(child);
+
// Set the parent after we've actually added a child in case a subclass depends on this.
child.setParent(this);
}
+ private void onChildAdded(WindowContainer child) {
+ mTreeWeight += child.mTreeWeight;
+ WindowContainer parent = getParent();
+ while (parent != null) {
+ parent.mTreeWeight += child.mTreeWeight;
+ parent = parent.getParent();
+ }
+ }
+
/**
* Removes the input child container from this container which is its parent.
*
@@ -215,6 +242,7 @@
@CallSuper
void removeChild(E child) {
if (mChildren.remove(child)) {
+ onChildRemoved(child);
child.setParent(null);
} else {
throw new IllegalArgumentException("removeChild: container=" + child.getName()
@@ -222,6 +250,15 @@
}
}
+ private void onChildRemoved(WindowContainer child) {
+ mTreeWeight -= child.mTreeWeight;
+ WindowContainer parent = getParent();
+ while (parent != null) {
+ parent.mTreeWeight -= child.mTreeWeight;
+ parent = parent.getParent();
+ }
+ }
+
/**
* Removes this window container and its children with no regard for what else might be going on
* in the system. For example, the container will be removed during animation if this method is
@@ -236,7 +273,9 @@
// Need to do this after calling remove on the child because the child might try to
// remove/detach itself from its parent which will cause an exception if we remove
// it before calling remove on the child.
- mChildren.remove(child);
+ if (mChildren.remove(child)) {
+ onChildRemoved(child);
+ }
}
if (mSurfaceControl != null) {
@@ -255,6 +294,34 @@
}
/**
+ * @return The index of this element in the hierarchy tree in prefix order.
+ */
+ int getPrefixOrderIndex() {
+ if (mParent == null) {
+ return 0;
+ }
+ return mParent.getPrefixOrderIndex(this);
+ }
+
+ private int getPrefixOrderIndex(WindowContainer child) {
+ int order = 0;
+ for (int i = 0; i < mChildren.size(); i++) {
+ final WindowContainer childI = mChildren.get(i);
+ if (child == childI) {
+ break;
+ }
+ order += childI.mTreeWeight;
+ }
+ if (mParent != null) {
+ order += mParent.getPrefixOrderIndex(this);
+ }
+
+ // We also need to count ourselves.
+ order++;
+ return order;
+ }
+
+ /**
* Removes this window container and its children taking care not to remove them during a
* critical stage in the system. For example, some containers will not be removed during
* animation if this method is called.
@@ -446,6 +513,20 @@
}
/**
+ * @return {@code true} if in this subtree of the hierarchy we have an {@link AppWindowToken}
+ * that is {@link #isSelfAnimating}; {@code false} otherwise.
+ */
+ boolean isAppAnimating() {
+ for (int j = mChildren.size() - 1; j >= 0; j--) {
+ final WindowContainer wc = mChildren.get(j);
+ if (wc.isAppAnimating()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
* @return Whether our own container running an animation at the moment.
*/
boolean isSelfAnimating() {
@@ -532,14 +613,6 @@
}
}
- /** Step currently ongoing animation for App window containers. */
- void stepAppWindowsAnimation(long currentTime) {
- for (int i = mChildren.size() - 1; i >= 0; --i) {
- final WindowContainer wc = mChildren.get(i);
- wc.stepAppWindowsAnimation(currentTime);
- }
- }
-
void onAppTransitionDone() {
for (int i = mChildren.size() - 1; i >= 0; --i) {
final WindowContainer wc = mChildren.get(i);
@@ -815,10 +888,7 @@
void assignLayer(Transaction t, int layer) {
final boolean changed = layer != mLastLayer || mLastRelativeToLayer != null;
if (mSurfaceControl != null && changed) {
-
- // Route through surface animator to accommodate that our surface control might be
- // attached to the leash, and leash is attached to parent container.
- mSurfaceAnimator.setLayer(t, layer);
+ setLayer(t, layer);
mLastLayer = layer;
mLastRelativeToLayer = null;
}
@@ -827,15 +897,30 @@
void assignRelativeLayer(Transaction t, SurfaceControl relativeTo, int layer) {
final boolean changed = layer != mLastLayer || mLastRelativeToLayer != relativeTo;
if (mSurfaceControl != null && changed) {
-
- // Route through surface animator to accommodate that our surface control might be
- // attached to the leash, and leash is attached to parent container.
- mSurfaceAnimator.setRelativeLayer(t, relativeTo, layer);
+ setRelativeLayer(t, relativeTo, layer);
mLastLayer = layer;
mLastRelativeToLayer = relativeTo;
}
}
+ protected void setLayer(Transaction t, int layer) {
+
+ // Route through surface animator to accommodate that our surface control might be
+ // attached to the leash, and leash is attached to parent container.
+ mSurfaceAnimator.setLayer(t, layer);
+ }
+
+ protected void setRelativeLayer(Transaction t, SurfaceControl relativeTo, int layer) {
+
+ // Route through surface animator to accommodate that our surface control might be
+ // attached to the leash, and leash is attached to parent container.
+ mSurfaceAnimator.setRelativeLayer(t, relativeTo, layer);
+ }
+
+ protected void reparentSurfaceControl(Transaction t, SurfaceControl newParent) {
+ mSurfaceAnimator.reparent(t, newParent);
+ }
+
void assignChildLayers(Transaction t) {
int layer = 0;
@@ -991,6 +1076,10 @@
mSurfaceAnimator.startAnimation(t, anim, hidden);
}
+ void transferAnimation(WindowContainer from) {
+ mSurfaceAnimator.transferAnimation(from.mSurfaceAnimator);
+ }
+
void cancelAnimation() {
mSurfaceAnimator.cancelAnimation();
}
@@ -1001,6 +1090,22 @@
}
@Override
+ public SurfaceControl getAnimationLeashParent() {
+ return getParentSurfaceControl();
+ }
+
+ /**
+ * @return The layer on which all app animations are happening.
+ */
+ SurfaceControl getAppAnimationLayer() {
+ final WindowContainer parent = getParent();
+ if (parent != null) {
+ return parent.getAppAnimationLayer();
+ }
+ return null;
+ }
+
+ @Override
public void commitPendingTransaction() {
scheduleAnimation();
}
@@ -1059,10 +1164,39 @@
return mSurfaceControl.getHeight();
}
+ @CallSuper
void dump(PrintWriter pw, String prefix, boolean dumpAll) {
if (mSurfaceAnimator.isAnimating()) {
pw.print(prefix); pw.println("ContainerAnimator:");
mSurfaceAnimator.dump(pw, prefix + " ");
}
}
+
+ void updateSurfacePosition(SurfaceControl.Transaction transaction) {
+ if (mSurfaceControl == null) {
+ return;
+ }
+
+ getRelativePosition(mTmpPos);
+ if (mTmpPos.equals(mLastSurfacePosition)) {
+ return;
+ }
+
+ transaction.setPosition(mSurfaceControl, mTmpPos.x, mTmpPos.y);
+ mLastSurfacePosition.set(mTmpPos.x, mTmpPos.y);
+
+ for (int i = mChildren.size() - 1; i >= 0; i--) {
+ mChildren.get(i).updateSurfacePosition(transaction);
+ }
+ }
+
+ void getRelativePosition(Point outPos) {
+ final Rect bounds = getBounds();
+ outPos.set(bounds.left, bounds.top);
+ final WindowContainer parent = getParent();
+ if (parent != null) {
+ final Rect parentBounds = parent.getBounds();
+ outPos.offset(-parentBounds.left, -parentBounds.top);
+ }
+ }
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index 62d2e7d..1935a44 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -118,8 +118,11 @@
* of AppTransition.TRANSIT_* values
* @param openToken the token for the opening app
* @param closeToken the token for the closing app
- * @param openAnimation the animation for the opening app
- * @param closeAnimation the animation for the closing app
+ * @param duration the total duration of the transition
+ * @param statusBarAnimationStartTime the desired start time for all visual animations in
+ * the status bar caused by this app transition in uptime millis
+ * @param statusBarAnimationDuration the duration for all visual animations in the status
+ * bar caused by this app transition in millis
*
* @return Return any bit set of {@link WindowManagerPolicy#FINISH_LAYOUT_REDO_LAYOUT},
* {@link WindowManagerPolicy#FINISH_LAYOUT_REDO_CONFIG},
@@ -127,7 +130,7 @@
* or {@link WindowManagerPolicy#FINISH_LAYOUT_REDO_ANIM}.
*/
public int onAppTransitionStartingLocked(int transit, IBinder openToken, IBinder closeToken,
- Animation openAnimation, Animation closeAnimation) {
+ long duration, long statusBarAnimationStartTime, long statusBarAnimationDuration) {
return 0;
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 58a5ca4..0a2ffbc 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -71,13 +71,9 @@
import static com.android.server.LockGuard.installLock;
import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT;
import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
-import static com.android.server.wm.AppTransition.TRANSIT_UNSET;
-import static com.android.server.wm.AppWindowAnimator.PROLONG_ANIMATION_AT_END;
-import static com.android.server.wm.AppWindowAnimator.PROLONG_ANIMATION_AT_START;
import static com.android.server.wm.KeyguardDisableHandler.KEYGUARD_POLICY_CHANGED;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ADD_REMOVE;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_APP_TRANSITIONS;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_BOOT;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_CONFIGURATION;
@@ -177,6 +173,7 @@
import android.os.WorkSource;
import android.provider.Settings;
import android.text.format.DateUtils;
+import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.DisplayMetrics;
import android.util.EventLog;
@@ -566,12 +563,10 @@
int mDockedStackCreateMode = SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
Rect mDockedStackCreateBounds;
- private final SparseIntArray mTmpTaskIds = new SparseIntArray();
-
boolean mForceResizableTasks = false;
boolean mSupportsPictureInPicture = false;
- private boolean mDisableTransitionAnimation = false;
+ boolean mDisableTransitionAnimation = false;
int getDragLayerLocked() {
return mPolicy.getWindowLayerFromTypeLw(TYPE_DRAG) * TYPE_LAYER_MULTIPLIER + TYPE_LAYER_OFFSET;
@@ -769,6 +764,11 @@
final WindowAnimator mAnimator;
final SurfaceAnimationRunner mSurfaceAnimationRunner;
+ /**
+ * Keeps track of which animations got transferred to which animators. Entries will get cleaned
+ * up when the animation finishes.
+ */
+ final ArrayMap<AnimationAdapter, SurfaceAnimator> mAnimationTransferMap = new ArrayMap<>();
final BoundsAnimationController mBoundsAnimationController;
private final PointerEventDispatcher mPointerEventDispatcher;
@@ -852,35 +852,6 @@
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
}
-
- /**
- * Executes an empty animation transaction without holding the WM lock to simulate
- * back-pressure. See {@link WindowAnimator#animate} why this is needed.
- */
- void executeEmptyAnimationTransaction() {
- try {
- Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "openSurfaceTransaction");
- synchronized (mWindowMap) {
- if (mRoot.mSurfaceTraceEnabled) {
- mRoot.mRemoteEventTrace.openSurfaceTransaction();
- }
- SurfaceControl.openTransaction();
- SurfaceControl.setAnimationTransaction();
- if (mRoot.mSurfaceTraceEnabled) {
- mRoot.mRemoteEventTrace.closeSurfaceTransaction();
- }
- }
- } finally {
- Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
- }
- try {
- Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "closeSurfaceTransaction");
- SurfaceControl.closeTransaction();
- } finally {
- Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
- }
- }
-
/** Listener to notify activity manager about app transitions. */
final WindowManagerInternal.AppTransitionListener mActivityManagerAppTransitionNotifier
= new WindowManagerInternal.AppTransitionListener() {
@@ -2279,83 +2250,6 @@
}
}
- boolean applyAnimationLocked(AppWindowToken atoken, WindowManager.LayoutParams lp,
- int transit, boolean enter, boolean isVoiceInteraction) {
- if (mDisableTransitionAnimation) {
- if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) {
- Slog.v(TAG_WM,
- "applyAnimation: transition animation is disabled. atoken=" + atoken);
- }
- atoken.mAppAnimator.clearAnimation();
- return false;
- }
- // Only apply an animation if the display isn't frozen. If it is
- // frozen, there is no reason to animate and it can cause strange
- // artifacts when we unfreeze the display if some different animation
- // is running.
- Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "WM#applyAnimationLocked");
- if (atoken.okToAnimate()) {
- final DisplayContent displayContent = atoken.getTask().getDisplayContent();
- final DisplayInfo displayInfo = displayContent.getDisplayInfo();
- final int width = displayInfo.appWidth;
- final int height = displayInfo.appHeight;
- if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG_WM,
- "applyAnimation: atoken=" + atoken);
-
- // Determine the visible rect to calculate the thumbnail clip
- final WindowState win = atoken.findMainWindow();
- final Rect frame = new Rect(0, 0, width, height);
- final Rect displayFrame = new Rect(0, 0,
- displayInfo.logicalWidth, displayInfo.logicalHeight);
- final Rect insets = new Rect();
- final Rect stableInsets = new Rect();
- Rect surfaceInsets = null;
- final boolean freeform = win != null && win.inFreeformWindowingMode();
- if (win != null) {
- // Containing frame will usually cover the whole screen, including dialog windows.
- // For freeform workspace windows it will not cover the whole screen and it also
- // won't exactly match the final freeform window frame (e.g. when overlapping with
- // the status bar). In that case we need to use the final frame.
- if (freeform) {
- frame.set(win.mFrame);
- } else {
- frame.set(win.mContainingFrame);
- }
- surfaceInsets = win.getAttrs().surfaceInsets;
- insets.set(win.mContentInsets);
- stableInsets.set(win.mStableInsets);
- }
-
- if (atoken.mLaunchTaskBehind) {
- // Differentiate the two animations. This one which is briefly on the screen
- // gets the !enter animation, and the other activity which remains on the
- // screen gets the enter animation. Both appear in the mOpeningApps set.
- enter = false;
- }
- if (DEBUG_APP_TRANSITIONS) Slog.d(TAG_WM, "Loading animation for app transition."
- + " transit=" + AppTransition.appTransitionToString(transit) + " enter=" + enter
- + " frame=" + frame + " insets=" + insets + " surfaceInsets=" + surfaceInsets);
- final Configuration displayConfig = displayContent.getConfiguration();
- Animation a = mAppTransition.loadAnimation(lp, transit, enter, displayConfig.uiMode,
- displayConfig.orientation, frame, displayFrame, insets, surfaceInsets,
- stableInsets, isVoiceInteraction, freeform, atoken.getTask().mTaskId);
- if (a != null) {
- if (DEBUG_ANIM) logWithStack(TAG, "Loaded animation " + a + " for " + atoken);
- final int containingWidth = frame.width();
- final int containingHeight = frame.height();
- atoken.mAppAnimator.setAnimation(a, containingWidth, containingHeight, width,
- height, mAppTransition.canSkipFirstFrame(),
- mAppTransition.getAppStackClipMode(),
- transit, mAppTransition.getTransitFlags());
- }
- } else {
- atoken.mAppAnimator.clearAnimation();
- }
- Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
-
- return atoken.mAppAnimator.animation != null;
- }
-
boolean checkCallingPermission(String permission, String func) {
// Quick check: if the calling permission is me, it's all okay.
if (Binder.getCallingPid() == myPid()) {
@@ -2701,7 +2595,6 @@
synchronized (mWindowMap) {
mAppTransition.overridePendingAppTransitionMultiThumb(specs, onAnimationStartedCallback,
onAnimationFinishedCallback, scaleUp);
- prolongAnimationsFromSpecs(specs, scaleUp);
}
}
@@ -2712,27 +2605,6 @@
}
}
- void prolongAnimationsFromSpecs(@NonNull AppTransitionAnimationSpec[] specs, boolean scaleUp) {
- // This is used by freeform <-> recents windows transition. We need to synchronize
- // the animation with the appearance of the content of recents, so we will make
- // animation stay on the first or last frame a little longer.
- mTmpTaskIds.clear();
- for (int i = specs.length - 1; i >= 0; i--) {
- mTmpTaskIds.put(specs[i].taskId, 0);
- }
- for (final WindowState win : mWindowMap.values()) {
- final Task task = win.getTask();
- if (task != null && mTmpTaskIds.get(task.mTaskId, -1) != -1
- && task.inFreeformWindowingMode()) {
- final AppWindowToken appToken = win.mAppToken;
- if (appToken != null && appToken.mAppAnimator != null) {
- appToken.mAppAnimator.startProlongAnimation(scaleUp ?
- PROLONG_ANIMATION_AT_START : PROLONG_ANIMATION_AT_END);
- }
- }
- }
- }
-
@Override
public void overridePendingAppTransitionInPlace(String packageName, int anim) {
synchronized(mWindowMap) {
@@ -2755,8 +2627,8 @@
synchronized (mWindowMap) {
for (final WindowState win : mWindowMap.values()) {
final AppWindowToken appToken = win.mAppToken;
- if (appToken != null && appToken.mAppAnimator != null) {
- appToken.mAppAnimator.endProlongedAnimation();
+ if (appToken != null) {
+ appToken.endDelayingAnimationStart();
}
}
mAppTransition.notifyProlongedAnimationsEnded();
@@ -2811,15 +2683,6 @@
}
}
- void updateTokenInPlaceLocked(AppWindowToken wtoken, int transit) {
- if (transit != TRANSIT_UNSET) {
- if (wtoken.mAppAnimator.animation == AppWindowAnimator.sDummyAnimation) {
- wtoken.mAppAnimator.setNullAnimation();
- }
- applyAnimationLocked(wtoken, null, transit, false, false);
- }
- }
-
public void setDockedStackCreateState(int mode, Rect bounds) {
synchronized (mWindowMap) {
setDockedStackCreateStateLocked(mode, bounds);
@@ -5614,7 +5477,9 @@
/** Note that Locked in this case is on mLayoutToAnim */
void scheduleAnimationLocked() {
- mAnimator.scheduleAnimation();
+ if (mAnimator != null) {
+ mAnimator.scheduleAnimation();
+ }
}
// TODO: Move to DisplayContent
diff --git a/services/core/java/com/android/server/wm/WindowManagerThreadPriorityBooster.java b/services/core/java/com/android/server/wm/WindowManagerThreadPriorityBooster.java
index 1b2eb46..dd89b3b 100644
--- a/services/core/java/com/android/server/wm/WindowManagerThreadPriorityBooster.java
+++ b/services/core/java/com/android/server/wm/WindowManagerThreadPriorityBooster.java
@@ -36,6 +36,7 @@
private final Object mLock = new Object();
private final int mAnimationThreadId;
+ private final int mSurfaceAnimationThreadId;
@GuardedBy("mLock")
private boolean mAppTransitionRunning;
@@ -45,14 +46,16 @@
WindowManagerThreadPriorityBooster() {
super(THREAD_PRIORITY_DISPLAY, INDEX_WINDOW);
mAnimationThreadId = AnimationThread.get().getThreadId();
+ mSurfaceAnimationThreadId = SurfaceAnimationThread.get().getThreadId();
}
@Override
public void boost() {
- // Do not boost the animation thread. As the animation thread is changing priorities,
+ // Do not boost the animation threads. As the animation threads are changing priorities,
// boosting it might mess up the priority because we reset it the the previous priority.
- if (myTid() == mAnimationThreadId) {
+ final int myTid = myTid();
+ if (myTid == mAnimationThreadId || myTid == mSurfaceAnimationThreadId) {
return;
}
super.boost();
@@ -62,7 +65,8 @@
public void reset() {
// See comment in boost().
- if (myTid() == mAnimationThreadId) {
+ final int myTid = myTid();
+ if (myTid == mAnimationThreadId || myTid == mSurfaceAnimationThreadId) {
return;
}
super.reset();
@@ -92,5 +96,6 @@
? TOP_APP_PRIORITY_BOOST : THREAD_PRIORITY_DISPLAY;
setBoostToPriority(priority);
setThreadPriority(mAnimationThreadId, priority);
+ setThreadPriority(mSurfaceAnimationThreadId, priority);
}
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index ddc1eac..c0aff4c 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -142,6 +142,7 @@
import android.os.Trace;
import android.os.UserHandle;
import android.os.WorkSource;
+import android.text.TextUtils;
import android.util.DisplayMetrics;
import android.util.MergedConfiguration;
import android.util.Slog;
@@ -1432,7 +1433,7 @@
*/
// TODO: Can we consolidate this with #isVisible() or have a more appropriate name for this?
boolean isWinVisibleLw() {
- return (mAppToken == null || !mAppToken.hiddenRequested || mAppToken.mAppAnimator.animating)
+ return (mAppToken == null || !mAppToken.hiddenRequested || mAppToken.isSelfAnimating())
&& isVisible();
}
@@ -1441,7 +1442,7 @@
* not the pending requested hidden state.
*/
boolean isVisibleNow() {
- return (!mToken.hidden || mAttrs.type == TYPE_APPLICATION_STARTING)
+ return (!mToken.isHidden() || mAttrs.type == TYPE_APPLICATION_STARTING)
&& isVisible();
}
@@ -1479,7 +1480,7 @@
final AppWindowToken atoken = mAppToken;
if (atoken != null) {
return ((!isParentWindowHidden() && !atoken.hiddenRequested)
- || mWinAnimator.isAnimationSet() || atoken.mAppAnimator.animation != null);
+ || mWinAnimator.isAnimationSet());
}
return !isParentWindowHidden() || mWinAnimator.isAnimationSet();
}
@@ -1501,7 +1502,7 @@
*/
boolean isInteresting() {
return mAppToken != null && !mAppDied
- && (!mAppToken.mAppAnimator.freezingScreen || !mAppFreezing);
+ && (!mAppToken.isFreezingScreen() || !mAppFreezing);
}
/**
@@ -1513,33 +1514,21 @@
return false;
}
return mHasSurface && mPolicyVisibility && !mDestroying
- && ((!isParentWindowHidden() && mViewVisibility == View.VISIBLE && !mToken.hidden)
- || mWinAnimator.isAnimationSet()
- || ((mAppToken != null) && (mAppToken.mAppAnimator.animation != null)));
+ && ((!isParentWindowHidden() && mViewVisibility == View.VISIBLE && !mToken.isHidden())
+ || mWinAnimator.isAnimationSet());
}
// TODO: Another visibility method that was added late in the release to minimize risk.
@Override
public boolean canAffectSystemUiFlags() {
- final boolean shown = mWinAnimator.getShown();
-
- // We only consider the app to be exiting when the animation has started. After the app
- // transition is executed the windows are marked exiting before the new windows have been
- // shown. Thus, wait considering a window to be exiting after the animation has actually
- // started.
- final boolean appAnimationStarting = mAppToken != null
- && mAppToken.mAppAnimator.isAnimationStarting();
- final boolean exitingSelf = mAnimatingExit && !appAnimationStarting;
- final boolean appExiting = mAppToken != null && mAppToken.hidden && !appAnimationStarting;
-
- final boolean exiting = exitingSelf || mDestroying || appExiting;
final boolean translucent = mAttrs.alpha == 0.0f;
-
- // If we are entering with a dummy animation, avoid affecting SystemUI flags until the
- // transition is starting.
- final boolean enteringWithDummyAnimation =
- mWinAnimator.isDummyAnimation() && mWinAnimator.mShownAlpha == 0f;
- return shown && !exiting && !translucent && !enteringWithDummyAnimation;
+ if (mAppToken == null) {
+ final boolean shown = mWinAnimator.getShown();
+ final boolean exiting = mAnimatingExit || mDestroying;
+ return shown && !exiting && !translucent;
+ } else {
+ return !mAppToken.isHidden();
+ }
}
/**
@@ -1550,10 +1539,8 @@
public boolean isDisplayedLw() {
final AppWindowToken atoken = mAppToken;
return isDrawnLw() && mPolicyVisibility
- && ((!isParentWindowHidden() &&
- (atoken == null || !atoken.hiddenRequested))
- || mWinAnimator.isAnimationSet()
- || (atoken != null && atoken.mAppAnimator.animation != null));
+ && ((!isParentWindowHidden() && (atoken == null || !atoken.hiddenRequested))
+ || mWinAnimator.isAnimationSet());
}
/**
@@ -1561,8 +1548,7 @@
*/
@Override
public boolean isAnimatingLw() {
- return mWinAnimator.isAnimationSet()
- || (mAppToken != null && mAppToken.mAppAnimator.animation != null);
+ return isAnimating();
}
@Override
@@ -1570,7 +1556,7 @@
final AppWindowToken atoken = mAppToken;
return mViewVisibility == View.GONE
|| !mRelayoutCalled
- || (atoken == null && mToken.hidden)
+ || (atoken == null && mToken.isHidden())
|| (atoken != null && atoken.hiddenRequested)
|| isParentWindowHidden()
|| (mAnimatingExit && !isAnimatingLw())
@@ -1608,8 +1594,7 @@
// to determine if it's occluding apps.
return ((!mIsWallpaper && mAttrs.format == PixelFormat.OPAQUE)
|| (mIsWallpaper && mWallpaperVisible))
- && isDrawnLw() && !mWinAnimator.isAnimationSet()
- && (mAppToken == null || mAppToken.mAppAnimator.animation == null);
+ && isDrawnLw() && !mWinAnimator.isAnimationSet();
}
@Override
@@ -1631,7 +1616,7 @@
// Starting window that's exiting will be removed when the animation finishes.
// Mark all relevant flags for that onExitAnimationDone will proceed all the way
// to actually remove it.
- if (!visible && isVisibleNow() && mAppToken.mAppAnimator.isAnimating()) {
+ if (!visible && isVisibleNow() && mAppToken.isSelfAnimating()) {
mAnimatingExit = true;
mRemoveOnExit = true;
mWindowRemovalAllowed = true;
@@ -1908,7 +1893,7 @@
+ " surfaceShowing=" + mWinAnimator.getShown()
+ " isAnimationSet=" + mWinAnimator.isAnimationSet()
+ " app-animation="
- + (mAppToken != null ? mAppToken.mAppAnimator.animation : null)
+ + (mAppToken != null ? mAppToken.isSelfAnimating() : "false")
+ " mWillReplaceWindow=" + mWillReplaceWindow
+ " inPendingTransaction="
+ (mAppToken != null ? mAppToken.inPendingTransaction : false)
@@ -1968,8 +1953,8 @@
mService.mAccessibilityController.onWindowTransitionLocked(this, transit);
}
}
- final boolean isAnimating =
- mWinAnimator.isAnimationSet() && !mWinAnimator.isDummyAnimation();
+ final boolean isAnimating = mWinAnimator.isAnimationSet()
+ && (mAppToken == null || !mAppToken.isWaitingForTransitionStart());
final boolean lastWindowIsStartingWindow = startingWindow && mAppToken != null
&& mAppToken.isLastWindow(this);
// We delay the removal of a window if it has a showing surface that can be used to run
@@ -2019,28 +2004,6 @@
mHasSurface = hasSurface;
}
- int getAnimLayerAdjustment() {
- if (mIsImWindow && mService.mInputMethodTarget != null) {
- final AppWindowToken appToken = mService.mInputMethodTarget.mAppToken;
- if (appToken != null) {
- return appToken.getAnimLayerAdjustment();
- }
- }
-
- return mToken.getAnimLayerAdjustment();
- }
-
- int getSpecialWindowAnimLayerAdjustment() {
- int specialAdjustment = 0;
- if (mIsImWindow) {
- specialAdjustment = getDisplayContent().mInputMethodAnimLayerAdjustment;
- } else if (mIsWallpaper) {
- specialAdjustment = getDisplayContent().mWallpaperController.getAnimLayerAdjustment();
- }
-
- return mLayer + specialAdjustment;
- }
-
boolean canBeImeTarget() {
if (mIsImWindow) {
// IME windows can't be IME targets. IME targets are required to be below the IME
@@ -2260,12 +2223,13 @@
mWinAnimator + ": " + mPolicyVisibilityAfterAnim);
}
mPolicyVisibility = mPolicyVisibilityAfterAnim;
- setDisplayLayoutNeeded();
if (!mPolicyVisibility) {
+ mWinAnimator.hide("checkPolicyVisibilityChange");
if (mService.mCurrentFocus == this) {
if (DEBUG_FOCUS_LIGHT) Slog.i(TAG,
"setAnimationLocked: setting mFocusMayChange true");
mService.mFocusMayChange = true;
+ setDisplayLayoutNeeded();
}
// Window is no longer visible -- make sure if we were waiting
// for it to be displayed before enabling the display, that
@@ -3170,7 +3134,6 @@
pw.print(prefix); pw.print("mBaseLayer="); pw.print(mBaseLayer);
pw.print(" mSubLayer="); pw.print(mSubLayer);
pw.print(" mAnimLayer="); pw.print(mLayer); pw.print("+");
- pw.print(getAnimLayerAdjustment());
pw.print("="); pw.print(mWinAnimator.mAnimLayer);
pw.print(" mLastLayer="); pw.println(mWinAnimator.mLastLayer);
}
@@ -3697,10 +3660,10 @@
+ " parentHidden=" + isParentWindowHidden()
+ " tok.hiddenRequested="
+ (mAppToken != null && mAppToken.hiddenRequested)
- + " tok.hidden=" + (mAppToken != null && mAppToken.hidden)
+ + " tok.hidden=" + (mAppToken != null && mAppToken.isHidden())
+ " animationSet=" + mWinAnimator.isAnimationSet()
+ " tok animating="
- + (mWinAnimator.mAppAnimator != null && mWinAnimator.mAppAnimator.animating)
+ + (mAppToken != null && mAppToken.isSelfAnimating())
+ " Callers=" + Debug.getCallers(4));
}
}
@@ -3714,6 +3677,13 @@
windowInfo.activityToken = mAppToken.appToken.asBinder();
}
windowInfo.title = mAttrs.accessibilityTitle;
+ // Panel windows have no public way to set the a11y title directly. Use the
+ // regular title as a fallback.
+ if (TextUtils.isEmpty(windowInfo.title)
+ && (mAttrs.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW)
+ && (mAttrs.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW)) {
+ windowInfo.title = mAttrs.getTitle();
+ }
windowInfo.accessibilityIdOfAnchor = mAttrs.accessibilityIdOfAnchor;
windowInfo.focused = isFocused();
Task task = getTask();
@@ -4292,7 +4262,8 @@
anim.restrictDuration(MAX_ANIMATION_DURATION);
anim.scaleCurrentDuration(mService.getWindowAnimationScaleLocked());
final AnimationAdapter adapter = new LocalAnimationAdapter(
- new WindowAnimationSpec(anim, mSurfacePosition), mService.mSurfaceAnimationRunner);
+ new WindowAnimationSpec(anim, mSurfacePosition, false /* canSkipFirstFrame */),
+ mService.mSurfaceAnimationRunner);
startAnimation(mPendingTransaction, adapter);
commitPendingTransaction();
}
@@ -4329,8 +4300,22 @@
float9[Matrix.MSKEW_Y] = mWinAnimator.mDtDx;
float9[Matrix.MSKEW_X] = mWinAnimator.mDtDy;
float9[Matrix.MSCALE_Y] = mWinAnimator.mDsDy;
- float9[Matrix.MTRANS_X] = mSurfacePosition.x + mShownPosition.x;
- float9[Matrix.MTRANS_Y] = mSurfacePosition.y + mShownPosition.y;
+ int x = mSurfacePosition.x + mShownPosition.x;
+ int y = mSurfacePosition.y + mShownPosition.y;
+
+ // If changed, also adjust transformFrameToSurfacePosition
+ final WindowContainer parent = getParent();
+ if (isChildWindow()) {
+ final WindowState parentWindow = getParentWindow();
+ x += parentWindow.mFrame.left - parentWindow.mAttrs.surfaceInsets.left;
+ y += parentWindow.mFrame.top - parentWindow.mAttrs.surfaceInsets.top;
+ } else if (parent != null) {
+ final Rect parentBounds = parent.getBounds();
+ x += parentBounds.left;
+ y += parentBounds.top;
+ }
+ float9[Matrix.MTRANS_X] = x;
+ float9[Matrix.MTRANS_Y] = y;
float9[Matrix.MPERSP_0] = 0;
float9[Matrix.MPERSP_1] = 0;
float9[Matrix.MPERSP_2] = 1;
@@ -4415,7 +4400,13 @@
@Override
boolean needsZBoost() {
- return getAnimLayerAdjustment() > 0 || mWillReplaceWindow;
+ if (mIsImWindow && mService.mInputMethodTarget != null) {
+ final AppWindowToken appToken = mService.mInputMethodTarget.mAppToken;
+ if (appToken != null) {
+ return appToken.needsZBoost();
+ }
+ }
+ return mWillReplaceWindow;
}
private void applyDims(Dimmer dimmer) {
@@ -4449,6 +4440,7 @@
// Leash is now responsible for position, so set our position to 0.
t.setPosition(mSurfaceControl, 0, 0);
+ mLastSurfacePosition.set(0, 0);
}
@Override
@@ -4457,24 +4449,36 @@
updateSurfacePosition(t);
}
+ @Override
void updateSurfacePosition(Transaction t) {
if (mSurfaceControl == null) {
return;
}
transformFrameToSurfacePosition(mFrame.left, mFrame.top, mSurfacePosition);
- if (!mSurfaceAnimator.hasLeash()) {
+ if (!mSurfaceAnimator.hasLeash() && !mLastSurfacePosition.equals(mSurfacePosition)) {
t.setPosition(mSurfaceControl, mSurfacePosition.x, mSurfacePosition.y);
+ mLastSurfacePosition.set(mSurfacePosition.x, mSurfacePosition.y);
}
}
private void transformFrameToSurfacePosition(int left, int top, Point outPoint) {
outPoint.set(left, top);
+
+ // If changed, also adjust getTransformationMatrix
+ final WindowContainer parentWindowContainer = getParent();
if (isChildWindow()) {
// TODO: This probably falls apart at some point and we should
// actually compute relative coordinates.
+
+ // Since the parent was outset by its surface insets, we need to undo the outsetting
+ // with insetting by the same amount.
final WindowState parent = getParentWindow();
- outPoint.offset(-parent.mFrame.left, -parent.mFrame.top);
+ outPoint.offset(-parent.mFrame.left + parent.mAttrs.surfaceInsets.left,
+ -parent.mFrame.top + parent.mAttrs.surfaceInsets.top);
+ } else if (parentWindowContainer != null) {
+ final Rect parentBounds = parentWindowContainer.getBounds();
+ outPoint.offset(-parentBounds.left, -parentBounds.top);
}
// Expand for surface insets. See WindowState.expandForSurfaceInsets.
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 0eabc89..d2247ac 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -25,7 +25,6 @@
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM;
import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
-import static com.android.server.wm.AppWindowAnimator.sDummyAnimation;
import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_FREEFORM;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT_REPEATS;
@@ -46,7 +45,6 @@
import static com.android.server.wm.proto.WindowStateAnimatorProto.LAST_CLIP_RECT;
import static com.android.server.wm.proto.WindowStateAnimatorProto.SURFACE;
-import android.app.WindowConfiguration;
import android.content.Context;
import android.graphics.Matrix;
import android.graphics.PixelFormat;
@@ -65,7 +63,6 @@
import android.view.WindowManager.LayoutParams;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
-import android.view.animation.Transformation;
import com.android.server.policy.WindowManagerPolicy;
@@ -103,7 +100,6 @@
final WindowState mWin;
private final WindowStateAnimator mParentWinAnimator;
final WindowAnimator mAnimator;
- AppWindowAnimator mAppAnimator;
final Session mSession;
final WindowManagerPolicy mPolicy;
final Context mContext;
@@ -228,7 +224,6 @@
mWin = win;
mParentWinAnimator = !win.isChildWindow() ? null : win.getParentWindow().mWinAnimator;
- mAppAnimator = win.mAppToken == null ? null : win.mAppToken.mAppAnimator;
mSession = win.mSession;
mAttrType = win.mAttrs.type;
mIsWallpaper = win.mIsWallpaper;
@@ -242,17 +237,12 @@
return mWin.isAnimating();
}
- /** Is the window animating the DummyAnimation? */
- boolean isDummyAnimation() {
- return mAppAnimator != null
- && mAppAnimator.animation == sDummyAnimation;
- }
-
/**
* Is this window currently waiting to run an opening animation?
*/
boolean isWaitingForOpening() {
- return mService.mAppTransition.isTransitionSet() && isDummyAnimation()
+ return mService.mAppTransition.isTransitionSet()
+ && (mWin.mAppToken != null && mWin.mAppToken.isHidden())
&& mService.mOpeningApps.contains(mWin.mAppToken);
}
@@ -423,7 +413,7 @@
return;
}
- if (mWin.mAppToken.mAppAnimator.animation == null) {
+ if (!mWin.mAppToken.isSelfAnimating()) {
mWin.mAppToken.clearAllDrawn();
} else {
// Currently animating, persist current state of allDrawn until animation
@@ -667,8 +657,6 @@
}
void computeShownFrameLocked() {
- Transformation appTransformation = (mAppAnimator != null && mAppAnimator.hasTransformation)
- ? mAppAnimator.transformation : null;
final int displayId = mWin.getDisplayId();
final ScreenRotationAnimation screenRotationAnimation =
@@ -677,14 +665,14 @@
screenRotationAnimation != null && screenRotationAnimation.isAnimating();
mHasClipRect = false;
- if (appTransformation != null || screenAnimation) {
+ if (screenAnimation) {
// cache often used attributes locally
final Rect frame = mWin.mFrame;
final float tmpFloats[] = mService.mTmpFloats;
final Matrix tmpMatrix = mWin.mTmpMatrix;
// Compute the desired transformation.
- if (screenAnimation && screenRotationAnimation.isRotating()) {
+ if (screenRotationAnimation.isRotating()) {
// If we are doing a screen animation, the global rotation
// applied to windows can result in windows that are carefully
// aligned with each other to slightly separate, allowing you
@@ -702,6 +690,7 @@
} else {
tmpMatrix.reset();
}
+
tmpMatrix.postScale(mWin.mGlobalScale, mWin.mGlobalScale);
// WindowState.prepareSurfaces expands for surface insets (in order they don't get
@@ -709,9 +698,6 @@
tmpMatrix.postTranslate(mWin.mXOffset + mWin.mAttrs.surfaceInsets.left,
mWin.mYOffset + mWin.mAttrs.surfaceInsets.top);
- if (appTransformation != null) {
- tmpMatrix.postConcat(appTransformation.getMatrix());
- }
// "convert" it into SurfaceFlinger's format
// (a 2x2 matrix + an offset)
@@ -740,24 +726,6 @@
|| (mWin.isIdentityMatrix(mDsDx, mDtDx, mDtDy, mDsDy)
&& x == frame.left && y == frame.top))) {
//Slog.i(TAG_WM, "Applying alpha transform");
- if (appTransformation != null) {
- mShownAlpha *= appTransformation.getAlpha();
- if (appTransformation.hasClipRect()) {
- mClipRect.set(appTransformation.getClipRect());
- mHasClipRect = true;
- // The app transformation clip will be in the coordinate space of the main
- // activity window, which the animation correctly assumes will be placed at
- // (0,0)+(insets) relative to the containing frame. This isn't necessarily
- // true for child windows though which can have an arbitrary frame position
- // relative to their containing frame. We need to offset the difference
- // between the containing frame as used to calculate the crop and our
- // bounds to compensate for this.
- if (mWin.layoutInParentFrame()) {
- mClipRect.offset( (mWin.mContainingFrame.left - mWin.mFrame.left),
- mWin.mContainingFrame.top - mWin.mFrame.top );
- }
- }
- }
if (screenAnimation) {
mShownAlpha *= screenRotationAnimation.getEnterTransformation().getAlpha();
}
@@ -768,7 +736,6 @@
if ((DEBUG_ANIM || WindowManagerService.localLOGV)
&& (mShownAlpha == 1.0 || mShownAlpha == 0.0)) Slog.v(
TAG, "computeShownFrameLocked: Animating " + this + " mAlpha=" + mAlpha
- + " app=" + (appTransformation == null ? "null" : appTransformation.getAlpha())
+ " screen=" + (screenAnimation ?
screenRotationAnimation.getEnterTransformation().getAlpha() : "null"));
return;
@@ -800,47 +767,6 @@
}
/**
- * In some scenarios we use a screen space clip rect (so called, final clip rect)
- * to crop to stack bounds. Generally because it's easier to deal with while
- * animating.
- *
- * @return True in scenarios where we use the final clip rect for stack clipping.
- */
- private boolean useFinalClipRect() {
- return (isAnimationSet() && resolveStackClip() == STACK_CLIP_AFTER_ANIM)
- || mDestroyPreservedSurfaceUponRedraw || mWin.inPinnedWindowingMode();
- }
-
- /**
- * Calculate the screen-space crop rect and fill finalClipRect.
- * @return true if finalClipRect has been filled, otherwise,
- * no screen space crop should be applied.
- */
- private boolean calculateFinalCrop(Rect finalClipRect) {
- final WindowState w = mWin;
- final DisplayContent displayContent = w.getDisplayContent();
- finalClipRect.setEmpty();
-
- if (displayContent == null) {
- return false;
- }
-
- if (!shouldCropToStackBounds() || !useFinalClipRect()) {
- return false;
- }
-
- // Task is non-null per shouldCropToStackBounds
- final TaskStack stack = w.getTask().mStack;
- stack.getDimBounds(finalClipRect);
-
- if (stack.getWindowConfiguration().tasksAreFloating()) {
- w.expandForSurfaceInsets(finalClipRect);
- }
-
- return true;
- }
-
- /**
* Calculate the window-space crop rect and fill clipRect.
* @return true if clipRect has been filled otherwise, no window space crop should be applied.
*/
@@ -900,9 +826,6 @@
// so we need to translate to match the actual surface coordinates.
clipRect.offset(w.mAttrs.surfaceInsets.left, w.mAttrs.surfaceInsets.top);
- if (!useFinalClipRect()) {
- adjustCropToStackBounds(clipRect, isFreeformResizing);
- }
if (DEBUG_WINDOW_CROP) Slog.d(TAG,
"win=" + w + " Clip rect after stack adjustment=" + clipRect);
@@ -911,9 +834,9 @@
return true;
}
- private void applyCrop(Rect clipRect, Rect finalClipRect, boolean recoveringMemory) {
+ private void applyCrop(Rect clipRect, boolean recoveringMemory) {
if (DEBUG_WINDOW_CROP) Slog.d(TAG, "applyCrop: win=" + mWin
- + " clipRect=" + clipRect + " finalClipRect=" + finalClipRect);
+ + " clipRect=" + clipRect);
if (clipRect != null) {
if (!clipRect.equals(mLastClipRect)) {
mLastClipRect.set(clipRect);
@@ -922,93 +845,6 @@
} else {
mSurfaceController.clearCropInTransaction(recoveringMemory);
}
-
- if (finalClipRect == null) {
- finalClipRect = mService.mTmpRect;
- finalClipRect.setEmpty();
- }
- if (!finalClipRect.equals(mLastFinalClipRect)) {
- mLastFinalClipRect.set(finalClipRect);
- mSurfaceController.setFinalCropInTransaction(finalClipRect);
- if (mDestroyPreservedSurfaceUponRedraw && mPendingDestroySurface != null) {
- mPendingDestroySurface.setFinalCropInTransaction(finalClipRect);
- }
- }
- }
-
- private int resolveStackClip() {
- // App animation overrides window animation stack clip mode.
- if (mAppAnimator != null && mAppAnimator.animation != null) {
- return mAppAnimator.getStackClip();
- } else {
- return STACK_CLIP_AFTER_ANIM;
- }
- }
-
- private boolean shouldCropToStackBounds() {
- final WindowState w = mWin;
- final DisplayContent displayContent = w.getDisplayContent();
- if (displayContent != null && !displayContent.isDefaultDisplay) {
- // There are some windows that live on other displays while their app and main window
- // live on the default display (e.g. casting...). We don't want to crop this windows
- // to the stack bounds which is only currently supported on the default display.
- // TODO(multi-display): Need to support cropping to stack bounds on other displays
- // when we have stacks on other displays.
- return false;
- }
-
- final Task task = w.getTask();
- if (task == null || !task.cropWindowsToStackBounds()) {
- return false;
- }
-
- final int stackClip = resolveStackClip();
-
- // It's animating and we don't want to clip it to stack bounds during animation - abort.
- if (isAnimationSet() && stackClip == STACK_CLIP_NONE) {
- return false;
- }
- return true;
- }
-
- private void adjustCropToStackBounds(Rect clipRect,
- boolean isFreeformResizing) {
- final WindowState w = mWin;
-
- if (!shouldCropToStackBounds()) {
- return;
- }
-
- final TaskStack stack = w.getTask().mStack;
- stack.getDimBounds(mTmpStackBounds);
- final Rect surfaceInsets = w.getAttrs().surfaceInsets;
- // When we resize we use the big surface approach, which means we can't trust the
- // window frame bounds anymore. Instead, the window will be placed at 0, 0, but to avoid
- // hardcoding it, we use surface coordinates.
- final int frameX = isFreeformResizing ? (int) mSurfaceController.getX() :
- w.mFrame.left + mWin.mXOffset - surfaceInsets.left;
- final int frameY = isFreeformResizing ? (int) mSurfaceController.getY() :
- w.mFrame.top + mWin.mYOffset - surfaceInsets.top;
-
- // We need to do some acrobatics with surface position, because their clip region is
- // relative to the inside of the surface, but the stack bounds aren't.
- final WindowConfiguration winConfig = w.getWindowConfiguration();
- if (winConfig.hasWindowShadow() && !winConfig.canResizeTask()) {
- // The windows in this stack display drop shadows and the fill the entire stack
- // area. Adjust the stack bounds we will use to cropping take into account the
- // offsets we use to display the drop shadow so it doesn't get cropped.
- mTmpStackBounds.inset(-surfaceInsets.left, -surfaceInsets.top,
- -surfaceInsets.right, -surfaceInsets.bottom);
- }
-
- clipRect.left = Math.max(0,
- Math.max(mTmpStackBounds.left, frameX + clipRect.left) - frameX);
- clipRect.top = Math.max(0,
- Math.max(mTmpStackBounds.top, frameY + clipRect.top) - frameY);
- clipRect.right = Math.max(0,
- Math.min(mTmpStackBounds.right, frameX + clipRect.right) - frameX);
- clipRect.bottom = Math.max(0,
- Math.min(mTmpStackBounds.bottom, frameY + clipRect.bottom) - frameY);
}
void setSurfaceBoundariesLocked(final boolean recoveringMemory) {
@@ -1053,13 +889,10 @@
// updates until a resize occurs.
mService.markForSeamlessRotation(w, w.mSeamlesslyRotated && !mSurfaceResized);
- Rect clipRect = null, finalClipRect = null;
+ Rect clipRect = null;
if (calculateCrop(mTmpClipRect)) {
clipRect = mTmpClipRect;
}
- if (calculateFinalCrop(mTmpFinalClipRect)) {
- finalClipRect = mTmpFinalClipRect;
- }
float surfaceWidth = mSurfaceController.getWidth();
float surfaceHeight = mSurfaceController.getHeight();
@@ -1124,7 +957,6 @@
// Always clip to the stack bounds since the surface can be larger with the current
// scale
clipRect = null;
- finalClipRect = mTmpStackBounds;
} else {
// We want to calculate the scaling based on the content area, not based on
// the entire surface, so that we scale in sync with windows that don't have insets.
@@ -1135,7 +967,6 @@
// expose the whole window in buffer space, and not risk extending
// past where the system would have cropped us
clipRect = null;
- finalClipRect = null;
}
// In the case of ForceScaleToStack we scale entire tasks together,
@@ -1183,7 +1014,7 @@
}
if (!w.mSeamlesslyRotated) {
- applyCrop(clipRect, finalClipRect, recoveringMemory);
+ applyCrop(clipRect, recoveringMemory);
mSurfaceController.setMatrixInTransaction(mDsDx * w.mHScale * mExtraHScale,
mDtDx * w.mVScale * mExtraVScale,
mDtDy * w.mHScale * mExtraHScale,
@@ -1381,7 +1212,7 @@
mService.openSurfaceTransaction();
mSurfaceController.setPositionInTransaction(mWin.mFrame.left + left,
mWin.mFrame.top + top, false);
- applyCrop(null, null, false);
+ applyCrop(null, false);
} catch (RuntimeException e) {
Slog.w(TAG, "Error positioning surface of " + mWin
+ " pos=(" + left + "," + top + ")", e);
diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
index d8e7457..bdab9c7 100644
--- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
+++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
@@ -97,9 +97,6 @@
static final int SET_TURN_ON_SCREEN = 1 << 4;
static final int SET_WALLPAPER_ACTION_PENDING = 1 << 5;
- private final Rect mTmpStartRect = new Rect();
- private final Rect mTmpContentRect = new Rect();
-
private boolean mTraversalScheduled;
private int mDeferDepth = 0;
@@ -361,14 +358,9 @@
mService.mAppTransition.setLastAppTransition(transit, topOpeningApp, topClosingApp);
- final AppWindowAnimator openingAppAnimator = (topOpeningApp == null) ? null :
- topOpeningApp.mAppAnimator;
- final AppWindowAnimator closingAppAnimator = (topClosingApp == null) ? null :
- topClosingApp.mAppAnimator;
-
final int flags = mService.mAppTransition.getTransitFlags();
- int layoutRedo = mService.mAppTransition.goodToGo(transit, openingAppAnimator,
- closingAppAnimator, mService.mOpeningApps, mService.mClosingApps);
+ int layoutRedo = mService.mAppTransition.goodToGo(transit, topOpeningApp,
+ topClosingApp, mService.mOpeningApps, mService.mClosingApps);
handleNonAppWindowsInTransition(transit, flags);
mService.mAppTransition.postAnimationCallback();
mService.mAppTransition.clear();
@@ -405,14 +397,8 @@
final int appsCount = mService.mOpeningApps.size();
for (int i = 0; i < appsCount; i++) {
AppWindowToken wtoken = mService.mOpeningApps.valueAt(i);
- final AppWindowAnimator appAnimator = wtoken.mAppAnimator;
if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "Now opening app" + wtoken);
- if (!appAnimator.usingTransferredAnimation) {
- appAnimator.clearThumbnail();
- appAnimator.setNullAnimation();
- }
-
if (!wtoken.setVisibility(animLp, true, transit, false, voiceInteraction)){
// This token isn't going to be animating. Add it to the list of tokens to
// be notified of app transition complete since the notification will not be
@@ -421,19 +407,17 @@
}
wtoken.updateReportedVisibilityLocked();
wtoken.waitingToShow = false;
- wtoken.setAllAppWinAnimators();
if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
">>> OPEN TRANSACTION handleAppTransitionReadyLocked()");
mService.openSurfaceTransaction();
try {
- mService.mAnimator.orAnimating(appAnimator.showAllWindowsLocked());
+ wtoken.showAllWindowsLocked();
} finally {
mService.closeSurfaceTransaction("handleAppTransitionReadyLocked");
if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
"<<< CLOSE TRANSACTION handleAppTransitionReadyLocked()");
}
- mService.mAnimator.mAppWindowAnimating |= appAnimator.isAnimating();
if (animLp != null) {
final int layer = wtoken.getHighestAnimLayer();
@@ -443,7 +427,7 @@
}
}
if (mService.mAppTransition.isNextAppTransitionThumbnailUp()) {
- createThumbnailAppAnimator(transit, wtoken);
+ wtoken.attachThumbnailAnimation();
}
}
return topOpeningApp;
@@ -456,18 +440,11 @@
for (int i = 0; i < appsCount; i++) {
AppWindowToken wtoken = mService.mClosingApps.valueAt(i);
- final AppWindowAnimator appAnimator = wtoken.mAppAnimator;
if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "Now closing app " + wtoken);
- appAnimator.clearThumbnail();
- appAnimator.setNullAnimation();
// TODO: Do we need to add to mNoAnimationNotifyOnTransitionFinished like above if not
// animating?
- wtoken.setAllAppWinAnimators();
wtoken.setVisibility(animLp, false, transit, false, voiceInteraction);
wtoken.updateReportedVisibilityLocked();
- // setAllAppWinAnimators so the windows get onExitAnimationDone once the animation is
- // done.
- wtoken.setAllAppWinAnimators();
// Force the allDrawn flag, because we want to start
// this guy's animations regardless of whether it's
// gotten drawn.
@@ -479,7 +456,6 @@
&& wtoken.getController() != null) {
wtoken.getController().removeStartingWindow();
}
- mService.mAnimator.mAppWindowAnimating |= appAnimator.isAnimating();
if (animLp != null) {
int layer = wtoken.getHighestAnimLayer();
@@ -489,7 +465,7 @@
}
}
if (mService.mAppTransition.isNextAppTransitionThumbnailDown()) {
- createThumbnailAppAnimator(transit, wtoken);
+ wtoken.attachThumbnailAnimation();
}
}
}
@@ -666,102 +642,16 @@
final WindowState win = mService.getDefaultDisplayContentLocked().findFocusedWindow();
if (win != null) {
final AppWindowToken wtoken = win.mAppToken;
- final AppWindowAnimator appAnimator = wtoken.mAppAnimator;
if (DEBUG_APP_TRANSITIONS)
Slog.v(TAG, "Now animating app in place " + wtoken);
- appAnimator.clearThumbnail();
- appAnimator.setNullAnimation();
- mService.updateTokenInPlaceLocked(wtoken, transit);
+ wtoken.cancelAnimation();
+ wtoken.applyAnimationLocked(null, transit, false, false);
wtoken.updateReportedVisibilityLocked();
- wtoken.setAllAppWinAnimators();
- mService.mAnimator.mAppWindowAnimating |= appAnimator.isAnimating();
- mService.mAnimator.orAnimating(appAnimator.showAllWindowsLocked());
+ wtoken.showAllWindowsLocked();
}
}
}
- private void createThumbnailAppAnimator(int transit, AppWindowToken appToken) {
- AppWindowAnimator openingAppAnimator = (appToken == null) ? null : appToken.mAppAnimator;
- if (openingAppAnimator == null || openingAppAnimator.animation == null) {
- return;
- }
- final int taskId = appToken.getTask().mTaskId;
- final GraphicBuffer thumbnailHeader =
- mService.mAppTransition.getAppTransitionThumbnailHeader(taskId);
- if (thumbnailHeader == null) {
- if (DEBUG_APP_TRANSITIONS) Slog.d(TAG, "No thumbnail header bitmap for: " + taskId);
- return;
- }
- // This thumbnail animation is very special, we need to have
- // an extra surface with the thumbnail included with the animation.
- Rect dirty = new Rect(0, 0, thumbnailHeader.getWidth(), thumbnailHeader.getHeight());
- try {
- // TODO(multi-display): support other displays
- final DisplayContent displayContent = mService.getDefaultDisplayContentLocked();
- final Display display = displayContent.getDisplay();
- final DisplayInfo displayInfo = displayContent.getDisplayInfo();
-
- // Create a new surface for the thumbnail
- WindowState window = appToken.findMainWindow();
- final SurfaceControl surfaceControl = appToken.makeSurface()
- .setName("thumbnail anim")
- .setSize(dirty.width(), dirty.height())
- .setFormat(PixelFormat.TRANSLUCENT)
- .setMetadata(appToken.windowType,
- window != null ? window.mOwnerUid : Binder.getCallingUid())
- .build();
-
- if (SHOW_TRANSACTIONS) {
- Slog.i(TAG, " THUMBNAIL " + surfaceControl + ": CREATE");
- }
-
- // Transfer the thumbnail to the surface
- Surface drawSurface = new Surface();
- drawSurface.copyFrom(surfaceControl);
- drawSurface.attachAndQueueBuffer(thumbnailHeader);
- drawSurface.release();
-
- // Get the thumbnail animation
- Animation anim;
- if (mService.mAppTransition.isNextThumbnailTransitionAspectScaled()) {
- // If this is a multi-window scenario, we use the windows frame as
- // destination of the thumbnail header animation. If this is a full screen
- // window scenario, we use the whole display as the target.
- WindowState win = appToken.findMainWindow();
- Rect appRect = win != null ? win.getContentFrameLw() :
- new Rect(0, 0, displayInfo.appWidth, displayInfo.appHeight);
- Rect insets = win != null ? win.mContentInsets : null;
- final Configuration displayConfig = displayContent.getConfiguration();
- // For the new aspect-scaled transition, we want it to always show
- // above the animating opening/closing window, and we want to
- // synchronize its thumbnail surface with the surface for the
- // open/close animation (only on the way down)
- anim = mService.mAppTransition.createThumbnailAspectScaleAnimationLocked(appRect,
- insets, thumbnailHeader, taskId, displayConfig.uiMode,
- displayConfig.orientation);
- openingAppAnimator.deferThumbnailDestruction =
- !mService.mAppTransition.isNextThumbnailTransitionScaleUp();
- } else {
- anim = mService.mAppTransition.createThumbnailScaleAnimationLocked(
- displayInfo.appWidth, displayInfo.appHeight, transit, thumbnailHeader);
- }
- anim.restrictDuration(MAX_ANIMATION_DURATION);
- anim.scaleCurrentDuration(mService.getTransitionAnimationScaleLocked());
-
- openingAppAnimator.thumbnail = surfaceControl;
- openingAppAnimator.thumbnailAnimation = anim;
- mService.mAppTransition.getNextAppTransitionStartRect(taskId, mTmpStartRect);
-
- // We parent the thumbnail to the app token, and just place it
- // on top of anything else in the app token.
- surfaceControl.setLayer(Integer.MAX_VALUE);
- } catch (Surface.OutOfResourcesException e) {
- Slog.e(TAG, "Can't allocate thumbnail/Canvas surface w="
- + dirty.width() + " h=" + dirty.height(), e);
- openingAppAnimator.clearThumbnail();
- }
- }
-
void requestTraversal() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index 5bcf59c..bad9bf5 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -63,7 +63,7 @@
boolean paused = false;
// Should this token's windows be hidden?
- boolean hidden;
+ private boolean mHidden;
// Temporary for finding which tokens no longer have visible windows.
boolean hasVisible;
@@ -112,6 +112,16 @@
onDisplayChanged(dc);
}
+ void setHidden(boolean hidden) {
+ if (hidden != mHidden) {
+ mHidden = hidden;
+ }
+ }
+
+ boolean isHidden() {
+ return mHidden;
+ }
+
void removeAllWindowsIfPossible() {
for (int i = mChildren.size() - 1; i >= 0; --i) {
final WindowState win = mChildren.get(i);
@@ -130,7 +140,7 @@
// This token is exiting, so allow it to be removed when it no longer contains any windows.
mPersistOnEmpty = false;
- if (hidden) {
+ if (mHidden) {
return;
}
@@ -146,7 +156,7 @@
changed |= win.onSetAppExiting();
}
- hidden = true;
+ setHidden(true);
if (changed) {
mService.mWindowPlacerLocked.performSurfacePlacement();
@@ -189,11 +199,6 @@
return mChildren.isEmpty();
}
- // Used by AppWindowToken.
- int getAnimLayerAdjustment() {
- return 0;
- }
-
WindowState getReplacingWindow() {
for (int i = mChildren.size() - 1; i >= 0; i--) {
final WindowState win = mChildren.get(i);
@@ -274,10 +279,11 @@
proto.end(token);
}
- void dump(PrintWriter pw, String prefix) {
+ void dump(PrintWriter pw, String prefix, boolean dumpAll) {
+ super.dump(pw, prefix, dumpAll);
pw.print(prefix); pw.print("windows="); pw.println(mChildren);
pw.print(prefix); pw.print("windowType="); pw.print(windowType);
- pw.print(" hidden="); pw.print(hidden);
+ pw.print(" hidden="); pw.print(mHidden);
pw.print(" hasVisible="); pw.println(hasVisible);
if (waitingToShow || sendingToBottom) {
pw.print(prefix); pw.print("waitingToShow="); pw.print(waitingToShow);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
index e55d4ea..c1e95eb 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
@@ -67,7 +67,8 @@
public void transferOwner(ComponentName admin, ComponentName target, PersistableBundle bundle) {}
public boolean generateKeyPair(ComponentName who, String callerPackage, String algorithm,
- ParcelableKeyGenParameterSpec keySpec, KeymasterCertificateChain attestationChain) {
+ ParcelableKeyGenParameterSpec keySpec, int idAttestationFlags,
+ KeymasterCertificateChain attestationChain) {
return false;
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index e5351b4..11fce4d 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -45,6 +45,10 @@
import static android.app.admin.DevicePolicyManager.DELEGATION_KEEP_UNINSTALLED_PACKAGES;
import static android.app.admin.DevicePolicyManager.DELEGATION_PACKAGE_ACCESS;
import static android.app.admin.DevicePolicyManager.DELEGATION_PERMISSION_GRANT;
+import static android.app.admin.DevicePolicyManager.ID_TYPE_BASE_INFO;
+import static android.app.admin.DevicePolicyManager.ID_TYPE_IMEI;
+import static android.app.admin.DevicePolicyManager.ID_TYPE_MEID;
+import static android.app.admin.DevicePolicyManager.ID_TYPE_SERIAL;
import static android.app.admin.DevicePolicyManager.LEAVE_ALL_SYSTEM_APPS_ENABLED;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
@@ -217,8 +221,10 @@
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
+import java.util.HashMap;
import java.util.List;
import java.util.Map.Entry;
+import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -5052,17 +5058,82 @@
return false;
}
+ private void enforceIsDeviceOwnerOrCertInstallerOfDeviceOwner(
+ ComponentName who, String callerPackage, int callerUid) throws SecurityException {
+ if (who == null) {
+ if (!mOwners.hasDeviceOwner()) {
+ throw new SecurityException("Not in Device Owner mode.");
+ }
+ if (UserHandle.getUserId(callerUid) != mOwners.getDeviceOwnerUserId()) {
+ throw new SecurityException("Caller not from device owner user");
+ }
+ if (!isCallerDelegate(callerPackage, DELEGATION_CERT_INSTALL)) {
+ throw new SecurityException("Caller with uid " + mInjector.binderGetCallingUid() +
+ "has no permission to generate keys.");
+ }
+ } else {
+ // Caller provided - check it is the device owner.
+ synchronized (this) {
+ getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
+ }
+ }
+ }
+
+ @VisibleForTesting
+ public static int[] translateIdAttestationFlags(
+ int idAttestationFlags) {
+ Map<Integer, Integer> idTypeToAttestationFlag = new HashMap();
+ idTypeToAttestationFlag.put(ID_TYPE_SERIAL, AttestationUtils.ID_TYPE_SERIAL);
+ idTypeToAttestationFlag.put(ID_TYPE_IMEI, AttestationUtils.ID_TYPE_IMEI);
+ idTypeToAttestationFlag.put(ID_TYPE_MEID, AttestationUtils.ID_TYPE_MEID);
+
+ int numFlagsSet = Integer.bitCount(idAttestationFlags);
+ // No flags are set - return null to indicate no device ID attestation information should
+ // be included in the attestation record.
+ if (numFlagsSet == 0) {
+ return null;
+ }
+
+ // If the ID_TYPE_BASE_INFO is set, make sure that a non-null array is returned, even if
+ // no other flag is set. That will lead to inclusion of general device make data in the
+ // attestation record, but no specific device identifiers.
+ if ((idAttestationFlags & ID_TYPE_BASE_INFO) != 0) {
+ numFlagsSet -= 1;
+ idAttestationFlags = idAttestationFlags & (~ID_TYPE_BASE_INFO);
+ }
+
+ int[] attestationUtilsFlags = new int[numFlagsSet];
+ int i = 0;
+ for (Integer idType: idTypeToAttestationFlag.keySet()) {
+ if ((idType & idAttestationFlags) != 0) {
+ attestationUtilsFlags[i++] = idTypeToAttestationFlag.get(idType);
+ }
+ }
+
+ return attestationUtilsFlags;
+ }
+
@Override
public boolean generateKeyPair(ComponentName who, String callerPackage, String algorithm,
ParcelableKeyGenParameterSpec parcelableKeySpec,
+ int idAttestationFlags,
KeymasterCertificateChain attestationChain) {
- enforceCanManageScope(who, callerPackage, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER,
- DELEGATION_CERT_INSTALL);
+ // Get attestation flags, if any.
+ final int[] attestationUtilsFlags = translateIdAttestationFlags(idAttestationFlags);
+ final boolean deviceIdAttestationRequired = attestationUtilsFlags != null;
+ final int callingUid = mInjector.binderGetCallingUid();
+
+ if (deviceIdAttestationRequired && attestationUtilsFlags.length > 0) {
+ enforceIsDeviceOwnerOrCertInstallerOfDeviceOwner(who, callerPackage, callingUid);
+ } else {
+ enforceCanManageScope(who, callerPackage, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER,
+ DELEGATION_CERT_INSTALL);
+ }
final KeyGenParameterSpec keySpec = parcelableKeySpec.getSpec();
- if (TextUtils.isEmpty(keySpec.getKeystoreAlias())) {
+ final String alias = keySpec.getKeystoreAlias();
+ if (TextUtils.isEmpty(alias)) {
throw new IllegalArgumentException("Empty alias provided.");
}
- final String alias = keySpec.getKeystoreAlias();
// As the caller will be granted access to the key, ensure no UID was specified, as
// it will not have the desired effect.
if (keySpec.getUid() != KeyStore.UID_SELF) {
@@ -5070,7 +5141,10 @@
return false;
}
- final int callingUid = mInjector.binderGetCallingUid();
+ if (deviceIdAttestationRequired && (keySpec.getAttestationChallenge() == null)) {
+ throw new IllegalArgumentException(
+ "Requested Device ID attestation but challenge is empty.");
+ }
final UserHandle userHandle = mInjector.binderGetCallingUserHandle();
final long id = mInjector.binderClearCallingIdentity();
@@ -5103,7 +5177,7 @@
final byte[] attestationChallenge = keySpec.getAttestationChallenge();
if (attestationChallenge != null) {
final boolean attestationResult = keyChain.attestKey(
- alias, attestationChallenge, attestationChain);
+ alias, attestationChallenge, attestationUtilsFlags, attestationChain);
if (!attestationResult) {
Log.e(LOG_TAG, String.format(
"Attestation for %s failed, deleting key.", alias));
@@ -11802,10 +11876,14 @@
final long id = mInjector.binderClearCallingIdentity();
try {
- //STOPSHIP add support for COMP, DO, edge cases when device is rebooted/work mode off,
+ //STOPSHIP add support for COMP, edge cases when device is rebooted/work mode off,
//transfer callbacks and broadcast
- if (isProfileOwner(admin, callingUserId)) {
- transferProfileOwner(admin, target, callingUserId);
+ synchronized (this) {
+ if (isProfileOwner(admin, callingUserId)) {
+ transferProfileOwnerLocked(admin, target, callingUserId);
+ } else if (isDeviceOwner(admin, callingUserId)) {
+ transferDeviceOwnerLocked(admin, target, callingUserId);
+ }
}
} finally {
mInjector.binderRestoreCallingIdentity(id);
@@ -11815,15 +11893,25 @@
/**
* Transfers the profile owner for user with id profileOwnerUserId from admin to target.
*/
- private void transferProfileOwner(ComponentName admin, ComponentName target,
+ private void transferProfileOwnerLocked(ComponentName admin, ComponentName target,
int profileOwnerUserId) {
- synchronized (this) {
- transferActiveAdminUncheckedLocked(target, admin, profileOwnerUserId);
- mOwners.transferProfileOwner(target, profileOwnerUserId);
- Slog.i(LOG_TAG, "Profile owner set: " + target + " on user " + profileOwnerUserId);
- mOwners.writeProfileOwner(profileOwnerUserId);
- mDeviceAdminServiceController.startServiceForOwner(
- target.getPackageName(), profileOwnerUserId, "transfer-profile-owner");
- }
+ transferActiveAdminUncheckedLocked(target, admin, profileOwnerUserId);
+ mOwners.transferProfileOwner(target, profileOwnerUserId);
+ Slog.i(LOG_TAG, "Profile owner set: " + target + " on user " + profileOwnerUserId);
+ mOwners.writeProfileOwner(profileOwnerUserId);
+ mDeviceAdminServiceController.startServiceForOwner(
+ target.getPackageName(), profileOwnerUserId, "transfer-profile-owner");
+ }
+
+ /**
+ * Transfers the device owner for user with id userId from admin to target.
+ */
+ private void transferDeviceOwnerLocked(ComponentName admin, ComponentName target, int userId) {
+ transferActiveAdminUncheckedLocked(target, admin, userId);
+ mOwners.transferDeviceOwner(target);
+ Slog.i(LOG_TAG, "Device owner set: " + target + " on user " + userId);
+ mOwners.writeDeviceOwner();
+ mDeviceAdminServiceController.startServiceForOwner(
+ target.getPackageName(), userId, "transfer-device-owner");
}
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
index 9042a8d..2a23888 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
@@ -288,6 +288,17 @@
}
}
+ void transferDeviceOwner(ComponentName target) {
+ synchronized (mLock) {
+ // We don't set a name because it's not used anyway.
+ // See DevicePolicyManagerService#getDeviceOwnerName
+ mDeviceOwner = new OwnerInfo(null, target,
+ mDeviceOwner.userRestrictionsMigrated, mDeviceOwner.remoteBugreportUri,
+ mDeviceOwner.remoteBugreportHash);
+ pushToPackageManagerLocked();
+ }
+ }
+
ComponentName getProfileOwnerComponent(int userId) {
synchronized (mLock) {
OwnerInfo profileOwner = mProfileOwners.get(userId);
diff --git a/services/robotests/src/com/android/server/backup/BackupManagerConstantsTest.java b/services/robotests/src/com/android/server/backup/BackupManagerConstantsTest.java
new file mode 100644
index 0000000..a473bc6
--- /dev/null
+++ b/services/robotests/src/com/android/server/backup/BackupManagerConstantsTest.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.backup;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.AlarmManager;
+import android.content.Context;
+import android.os.Handler;
+import android.platform.test.annotations.Presubmit;
+import android.provider.Settings;
+
+import com.android.server.testing.FrameworkRobolectricTestRunner;
+import com.android.server.testing.SystemLoaderClasses;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+@RunWith(FrameworkRobolectricTestRunner.class)
+@Config(manifest = Config.NONE, sdk = 26)
+@SystemLoaderClasses({BackupManagerConstants.class})
+@Presubmit
+public class BackupManagerConstantsTest {
+ private static final String PACKAGE_NAME = "some.package.name";
+ private static final String ANOTHER_PACKAGE_NAME = "another.package.name";
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ @Test
+ public void testDefaultValues() throws Exception {
+ final Context context = RuntimeEnvironment.application.getApplicationContext();
+ final Handler handler = new Handler();
+
+ Settings.Secure.putString(
+ context.getContentResolver(), Settings.Secure.BACKUP_MANAGER_CONSTANTS, null);
+
+ final BackupManagerConstants constants =
+ new BackupManagerConstants(handler, context.getContentResolver());
+ constants.start();
+
+ assertThat(constants.getKeyValueBackupIntervalMilliseconds())
+ .isEqualTo(4 * AlarmManager.INTERVAL_HOUR);
+ assertThat(constants.getKeyValueBackupFuzzMilliseconds()).isEqualTo(10 * 60 * 1000);
+ assertThat(constants.getKeyValueBackupRequireCharging()).isEqualTo(true);
+ assertThat(constants.getKeyValueBackupRequiredNetworkType()).isEqualTo(1);
+
+ assertThat(constants.getFullBackupIntervalMilliseconds())
+ .isEqualTo(24 * AlarmManager.INTERVAL_HOUR);
+ assertThat(constants.getFullBackupRequireCharging()).isEqualTo(true);
+ assertThat(constants.getFullBackupRequiredNetworkType()).isEqualTo(2);
+ assertThat(constants.getBackupFinishedNotificationReceivers()).isEqualTo(new String[0]);
+ }
+
+ @Test
+ public void testParseNotificationReceivers() throws Exception {
+ final Context context = RuntimeEnvironment.application.getApplicationContext();
+ final Handler handler = new Handler();
+
+ final String recieversSetting =
+ "backup_finished_notification_receivers="
+ + PACKAGE_NAME
+ + ':'
+ + ANOTHER_PACKAGE_NAME;
+ Settings.Secure.putString(
+ context.getContentResolver(),
+ Settings.Secure.BACKUP_MANAGER_CONSTANTS,
+ recieversSetting);
+
+ final BackupManagerConstants constants =
+ new BackupManagerConstants(handler, context.getContentResolver());
+ constants.start();
+
+ assertThat(constants.getBackupFinishedNotificationReceivers())
+ .isEqualTo(new String[] {PACKAGE_NAME, ANOTHER_PACKAGE_NAME});
+ }
+}
diff --git a/services/robotests/src/com/android/server/backup/TransportManagerTest.java b/services/robotests/src/com/android/server/backup/TransportManagerTest.java
index ced9b1e..82830fe 100644
--- a/services/robotests/src/com/android/server/backup/TransportManagerTest.java
+++ b/services/robotests/src/com/android/server/backup/TransportManagerTest.java
@@ -18,8 +18,6 @@
import static com.google.common.truth.Truth.assertThat;
-import static junit.framework.Assert.fail;
-
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.robolectric.shadow.api.Shadow.extract;
@@ -58,7 +56,6 @@
import org.robolectric.shadows.ShadowLog;
import org.robolectric.shadows.ShadowLooper;
import org.robolectric.shadows.ShadowPackageManager;
-import org.testng.Assert.ThrowingRunnable;
import java.util.ArrayList;
import java.util.Arrays;
diff --git a/services/robotests/src/com/android/server/backup/internal/PerformInitializeTaskTest.java b/services/robotests/src/com/android/server/backup/internal/PerformInitializeTaskTest.java
new file mode 100644
index 0000000..73f1c2f
--- /dev/null
+++ b/services/robotests/src/com/android/server/backup/internal/PerformInitializeTaskTest.java
@@ -0,0 +1,411 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.backup.internal;
+
+import static android.app.backup.BackupTransport.TRANSPORT_ERROR;
+import static android.app.backup.BackupTransport.TRANSPORT_OK;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.annotation.Nullable;
+import android.app.AlarmManager;
+import android.app.Application;
+import android.app.PendingIntent;
+import android.app.backup.IBackupObserver;
+import android.os.DeadObjectException;
+import android.platform.test.annotations.Presubmit;
+
+import com.android.internal.backup.IBackupTransport;
+import com.android.server.backup.RefactoredBackupManagerService;
+import com.android.server.backup.TransportManager;
+import com.android.server.backup.transport.TransportClient;
+import com.android.server.backup.transport.TransportNotAvailableException;
+import com.android.server.testing.FrameworkRobolectricTestRunner;
+import com.android.server.testing.SystemLoaderClasses;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.List;
+
+@RunWith(FrameworkRobolectricTestRunner.class)
+@Config(manifest = Config.NONE, sdk = 26)
+@SystemLoaderClasses({PerformInitializeTaskTest.class, TransportManager.class})
+@Presubmit
+public class PerformInitializeTaskTest {
+ private static final String[] TRANSPORT_NAMES = {
+ "android/com.android.internal.backup.LocalTransport",
+ "com.google.android.gms/.backup.migrate.service.D2dTransport",
+ "com.google.android.gms/.backup.BackupTransportService"
+ };
+
+ private static final String TRANSPORT_NAME = TRANSPORT_NAMES[0];
+
+ @Mock private RefactoredBackupManagerService mBackupManagerService;
+ @Mock private TransportManager mTransportManager;
+ @Mock private OnTaskFinishedListener mListener;
+ @Mock private IBackupTransport mTransport;
+ @Mock private IBackupObserver mObserver;
+ @Mock private AlarmManager mAlarmManager;
+ @Mock private PendingIntent mRunInitIntent;
+ private File mBaseStateDir;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ Application context = RuntimeEnvironment.application;
+ mBaseStateDir = new File(context.getCacheDir(), "base_state_dir");
+ assertThat(mBaseStateDir.mkdir()).isTrue();
+
+ when(mBackupManagerService.getAlarmManager()).thenReturn(mAlarmManager);
+ when(mBackupManagerService.getRunInitIntent()).thenReturn(mRunInitIntent);
+ }
+
+ @Test
+ public void testRun_callsTransportCorrectly() throws Exception {
+ setUpTransport(TRANSPORT_NAME);
+ configureTransport(mTransport, TRANSPORT_OK, TRANSPORT_OK);
+ PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME);
+
+ performInitializeTask.run();
+
+ verify(mTransport).initializeDevice();
+ verify(mTransport).finishBackup();
+ }
+
+ @Test
+ public void testRun_callsBackupManagerCorrectly() throws Exception {
+ setUpTransport(TRANSPORT_NAME);
+ configureTransport(mTransport, TRANSPORT_OK, TRANSPORT_OK);
+ PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME);
+
+ performInitializeTask.run();
+
+ verify(mBackupManagerService)
+ .recordInitPending(false, TRANSPORT_NAME, dirName(TRANSPORT_NAME));
+ verify(mBackupManagerService)
+ .resetBackupState(eq(new File(mBaseStateDir, dirName(TRANSPORT_NAME))));
+ }
+
+ @Test
+ public void testRun_callsObserverAndListenerCorrectly() throws Exception {
+ setUpTransport(TRANSPORT_NAME);
+ configureTransport(mTransport, TRANSPORT_OK, TRANSPORT_OK);
+ PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME);
+
+ performInitializeTask.run();
+
+ verify(mObserver).onResult(eq(TRANSPORT_NAME), eq(TRANSPORT_OK));
+ verify(mObserver).backupFinished(eq(TRANSPORT_OK));
+ verify(mListener).onFinished(any());
+ }
+
+ @Test
+ public void testRun_whenInitializeDeviceFails() throws Exception {
+ setUpTransport(TRANSPORT_NAME);
+ configureTransport(mTransport, TRANSPORT_ERROR, 0);
+ PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME);
+
+ performInitializeTask.run();
+
+ verify(mTransport).initializeDevice();
+ verify(mTransport, never()).finishBackup();
+ verify(mBackupManagerService)
+ .recordInitPending(true, TRANSPORT_NAME, dirName(TRANSPORT_NAME));
+ }
+
+ @Test
+ public void testRun_whenInitializeDeviceFails_callsObserverAndListenerCorrectly()
+ throws Exception {
+ setUpTransport(TRANSPORT_NAME);
+ configureTransport(mTransport, TRANSPORT_ERROR, 0);
+ PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME);
+
+ performInitializeTask.run();
+
+ verify(mObserver).onResult(eq(TRANSPORT_NAME), eq(TRANSPORT_ERROR));
+ verify(mObserver).backupFinished(eq(TRANSPORT_ERROR));
+ verify(mListener).onFinished(any());
+ }
+
+ @Test
+ public void testRun_whenInitializeDeviceFails_schedulesAlarm() throws Exception {
+ setUpTransport(TRANSPORT_NAME);
+ configureTransport(mTransport, TRANSPORT_ERROR, 0);
+ PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME);
+
+ performInitializeTask.run();
+
+ verify(mAlarmManager).set(anyInt(), anyLong(), eq(mRunInitIntent));
+ }
+
+ @Test
+ public void testRun_whenFinishBackupFails() throws Exception {
+ setUpTransport(TRANSPORT_NAME);
+ configureTransport(mTransport, TRANSPORT_OK, TRANSPORT_ERROR);
+ PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME);
+
+ performInitializeTask.run();
+
+ verify(mTransport).initializeDevice();
+ verify(mTransport).finishBackup();
+ verify(mBackupManagerService)
+ .recordInitPending(true, TRANSPORT_NAME, dirName(TRANSPORT_NAME));
+ }
+
+ @Test
+ public void testRun_whenFinishBackupFails_callsObserverAndListenerCorrectly() throws Exception {
+ setUpTransport(TRANSPORT_NAME);
+ configureTransport(mTransport, TRANSPORT_OK, TRANSPORT_ERROR);
+ PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME);
+
+ performInitializeTask.run();
+
+ verify(mObserver).onResult(eq(TRANSPORT_NAME), eq(TRANSPORT_ERROR));
+ verify(mObserver).backupFinished(eq(TRANSPORT_ERROR));
+ verify(mListener).onFinished(any());
+ }
+
+ @Test
+ public void testRun_whenFinishBackupFails_schedulesAlarm() throws Exception {
+ setUpTransport(TRANSPORT_NAME);
+ configureTransport(mTransport, TRANSPORT_OK, TRANSPORT_ERROR);
+ PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME);
+
+ performInitializeTask.run();
+
+ verify(mAlarmManager).set(anyInt(), anyLong(), eq(mRunInitIntent));
+ }
+
+ @Test
+ public void testRun_whenOnlyOneTransportFails() throws Exception {
+ List<TransportData> transports = setUpTransports(TRANSPORT_NAMES[0], TRANSPORT_NAMES[1]);
+ configureTransport(transports.get(0).transportMock, TRANSPORT_ERROR, 0);
+ configureTransport(transports.get(1).transportMock, TRANSPORT_OK, TRANSPORT_OK);
+ PerformInitializeTask performInitializeTask =
+ createPerformInitializeTask(TRANSPORT_NAMES[0], TRANSPORT_NAMES[1]);
+
+ performInitializeTask.run();
+
+ verify(transports.get(1).transportMock).initializeDevice();
+ verify(mObserver).onResult(eq(TRANSPORT_NAMES[0]), eq(TRANSPORT_ERROR));
+ verify(mObserver).onResult(eq(TRANSPORT_NAMES[1]), eq(TRANSPORT_OK));
+ verify(mObserver).backupFinished(eq(TRANSPORT_ERROR));
+ }
+
+ @Test
+ public void testRun_withMultipleTransports() throws Exception {
+ List<TransportData> transports = setUpTransports(TRANSPORT_NAMES);
+ configureTransport(transports.get(0).transportMock, TRANSPORT_OK, TRANSPORT_OK);
+ configureTransport(transports.get(1).transportMock, TRANSPORT_OK, TRANSPORT_OK);
+ configureTransport(transports.get(2).transportMock, TRANSPORT_OK, TRANSPORT_OK);
+ PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAMES);
+
+ performInitializeTask.run();
+
+ for (TransportData transport : transports) {
+ verify(mTransportManager).getTransportClient(eq(transport.transportName), any());
+ verify(mTransportManager)
+ .disposeOfTransportClient(eq(transport.transportClientMock), any());
+ }
+ }
+
+ @Test
+ public void testRun_whenOnlyOneTransportFails_disposesAllTransports() throws Exception {
+ List<TransportData> transports = setUpTransports(TRANSPORT_NAMES[0], TRANSPORT_NAMES[1]);
+ configureTransport(transports.get(0).transportMock, TRANSPORT_ERROR, 0);
+ configureTransport(transports.get(1).transportMock, TRANSPORT_OK, TRANSPORT_OK);
+ PerformInitializeTask performInitializeTask =
+ createPerformInitializeTask(TRANSPORT_NAMES[0], TRANSPORT_NAMES[1]);
+
+ performInitializeTask.run();
+
+ verify(mTransportManager)
+ .disposeOfTransportClient(eq(transports.get(0).transportClientMock), any());
+ verify(mTransportManager)
+ .disposeOfTransportClient(eq(transports.get(1).transportClientMock), any());
+ }
+
+ @Test
+ public void testRun_whenTransportNotRegistered() throws Exception {
+ setUpTransport(new TransportData(TRANSPORT_NAME, null, null));
+ PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME);
+
+ performInitializeTask.run();
+
+ verify(mTransportManager, never()).disposeOfTransportClient(any(), any());
+ verify(mObserver, never()).onResult(any(), anyInt());
+ verify(mObserver).backupFinished(eq(TRANSPORT_OK));
+ }
+
+ @Test
+ public void testRun_whenOnlyOneTransportNotRegistered() throws Exception {
+ List<TransportData> transports =
+ setUpTransports(
+ new TransportData(TRANSPORT_NAMES[0], null, null),
+ new TransportData(TRANSPORT_NAMES[1]));
+ String registeredTransportName = transports.get(1).transportName;
+ IBackupTransport registeredTransport = transports.get(1).transportMock;
+ TransportClient registeredTransportClient = transports.get(1).transportClientMock;
+ PerformInitializeTask performInitializeTask =
+ createPerformInitializeTask(TRANSPORT_NAMES[0], TRANSPORT_NAMES[1]);
+
+ performInitializeTask.run();
+
+ verify(registeredTransport).initializeDevice();
+ verify(mTransportManager).disposeOfTransportClient(eq(registeredTransportClient), any());
+ verify(mObserver).onResult(eq(registeredTransportName), eq(TRANSPORT_OK));
+ }
+
+ @Test
+ public void testRun_whenTransportNotAvailable() throws Exception {
+ TransportClient transportClient = mock(TransportClient.class);
+ setUpTransport(new TransportData(TRANSPORT_NAME, null, transportClient));
+ PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME);
+
+ performInitializeTask.run();
+
+ verify(mTransportManager).disposeOfTransportClient(eq(transportClient), any());
+ verify(mObserver).backupFinished(eq(TRANSPORT_ERROR));
+ verify(mListener).onFinished(any());
+ }
+
+ @Test
+ public void testRun_whenTransportThrowsDeadObjectException() throws Exception {
+ TransportClient transportClient = mock(TransportClient.class);
+ setUpTransport(new TransportData(TRANSPORT_NAME, mTransport, transportClient));
+ when(mTransport.initializeDevice()).thenThrow(DeadObjectException.class);
+ PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME);
+
+ performInitializeTask.run();
+
+ verify(mTransportManager).disposeOfTransportClient(eq(transportClient), any());
+ verify(mObserver).backupFinished(eq(TRANSPORT_ERROR));
+ verify(mListener).onFinished(any());
+ }
+
+ private PerformInitializeTask createPerformInitializeTask(String... transportNames) {
+ return new PerformInitializeTask(
+ mBackupManagerService,
+ mTransportManager,
+ transportNames,
+ mObserver,
+ mListener,
+ mBaseStateDir);
+ }
+
+ private void configureTransport(
+ IBackupTransport transportMock, int initializeDeviceStatus, int finishBackupStatus)
+ throws Exception {
+ when(transportMock.initializeDevice()).thenReturn(initializeDeviceStatus);
+ when(transportMock.finishBackup()).thenReturn(finishBackupStatus);
+ }
+
+ private List<TransportData> setUpTransports(String... transportNames) throws Exception {
+ return setUpTransports(
+ Arrays.stream(transportNames)
+ .map(TransportData::new)
+ .toArray(TransportData[]::new));
+ }
+
+ /** @see #setUpTransport(TransportData) */
+ private List<TransportData> setUpTransports(TransportData... transports) throws Exception {
+ for (TransportData transport : transports) {
+ setUpTransport(transport);
+ }
+ return Arrays.asList(transports);
+ }
+
+ private void setUpTransport(String transportName) throws Exception {
+ setUpTransport(new TransportData(transportName, mTransport, mock(TransportClient.class)));
+ }
+
+ /**
+ * Configures transport according to {@link TransportData}:
+ *
+ * <ul>
+ * <li>{@link TransportData#transportMock} {@code null} means {@link
+ * TransportClient#connectOrThrow(String)} throws {@link TransportNotAvailableException}.
+ * <li>{@link TransportData#transportClientMock} {@code null} means {@link
+ * TransportManager#getTransportClient(String, String)} returns {@code null}.
+ * </ul>
+ */
+ private void setUpTransport(TransportData transport) throws Exception {
+ String transportName = transport.transportName;
+ String transportDirName = dirName(transportName);
+ IBackupTransport transportMock = transport.transportMock;
+ TransportClient transportClientMock = transport.transportClientMock;
+
+ if (transportMock != null) {
+ when(transportMock.name()).thenReturn(transportName);
+ when(transportMock.transportDirName()).thenReturn(transportDirName);
+ }
+
+ if (transportClientMock != null) {
+ when(transportClientMock.getTransportDirName()).thenReturn(transportDirName);
+ if (transportMock != null) {
+ when(transportClientMock.connectOrThrow(any())).thenReturn(transportMock);
+ } else {
+ when(transportClientMock.connectOrThrow(any()))
+ .thenThrow(TransportNotAvailableException.class);
+ }
+ }
+
+ when(mTransportManager.getTransportClient(eq(transportName), any()))
+ .thenReturn(transportClientMock);
+ }
+
+ private String dirName(String transportName) {
+ return transportName + "_dir_name";
+ }
+
+ private static class TransportData {
+ private final String transportName;
+ @Nullable private final IBackupTransport transportMock;
+ @Nullable private final TransportClient transportClientMock;
+
+ private TransportData(
+ String transportName,
+ @Nullable IBackupTransport transportMock,
+ @Nullable TransportClient transportClientMock) {
+ this.transportName = transportName;
+ this.transportMock = transportMock;
+ this.transportClientMock = transportClientMock;
+ }
+
+ private TransportData(String transportName) {
+ this(transportName, mock(IBackupTransport.class), mock(TransportClient.class));
+ }
+ }
+}
diff --git a/services/robotests/src/com/android/server/testing/FrameworkRobolectricTestRunner.java b/services/robotests/src/com/android/server/testing/FrameworkRobolectricTestRunner.java
index 78ac4ed..6c7313b 100644
--- a/services/robotests/src/com/android/server/testing/FrameworkRobolectricTestRunner.java
+++ b/services/robotests/src/com/android/server/testing/FrameworkRobolectricTestRunner.java
@@ -27,14 +27,9 @@
import org.robolectric.internal.bytecode.SandboxClassLoader;
import org.robolectric.util.Util;
-import java.io.File;
-import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
-import java.io.OutputStreamWriter;
-import java.io.PrintWriter;
import java.net.URL;
-import java.util.Arrays;
import java.util.Set;
import javax.annotation.Nonnull;
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/MagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationGestureHandlerTest.java
index 8a54c4e..8d5556e 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/MagnificationGestureHandlerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationGestureHandlerTest.java
@@ -32,6 +32,7 @@
import android.annotation.NonNull;
import android.content.Context;
+import android.os.Message;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
import android.util.DebugUtils;
@@ -48,6 +49,34 @@
import java.util.function.IntConsumer;
+/**
+ * Tests the state transitions of {@link MagnificationGestureHandler}
+ *
+ * Here's a dot graph describing the transitions being tested:
+ * {@code
+ * digraph {
+ * IDLE -> SHORTCUT_TRIGGERED [label="a11y\nbtn"]
+ * SHORTCUT_TRIGGERED -> IDLE [label="a11y\nbtn"]
+ * IDLE -> DOUBLE_TAP [label="2tap"]
+ * DOUBLE_TAP -> IDLE [label="timeout"]
+ * DOUBLE_TAP -> TRIPLE_TAP_AND_HOLD [label="down"]
+ * SHORTCUT_TRIGGERED -> TRIPLE_TAP_AND_HOLD [label="down"]
+ * TRIPLE_TAP_AND_HOLD -> ZOOMED [label="up"]
+ * TRIPLE_TAP_AND_HOLD -> DRAGGING_TMP [label="hold/\nswipe"]
+ * DRAGGING_TMP -> IDLE [label="release"]
+ * ZOOMED -> ZOOMED_DOUBLE_TAP [label="2tap"]
+ * ZOOMED_DOUBLE_TAP -> ZOOMED [label="timeout"]
+ * ZOOMED_DOUBLE_TAP -> DRAGGING [label="hold"]
+ * ZOOMED_DOUBLE_TAP -> IDLE [label="tap"]
+ * DRAGGING -> ZOOMED [label="release"]
+ * ZOOMED -> IDLE [label="a11y\nbtn"]
+ * ZOOMED -> PANNING [label="2hold"]
+ * PANNING -> PANNING_SCALING [label="pinch"]
+ * PANNING_SCALING -> ZOOMED [label="release"]
+ * PANNING -> ZOOMED [label="release"]
+ * }
+ * }
+ */
@RunWith(AndroidJUnit4.class)
public class MagnificationGestureHandlerTest {
@@ -76,6 +105,8 @@
private MagnificationGestureHandler mMgh;
private TestHandler mHandler;
+ private long mLastDownTime = Integer.MIN_VALUE;
+
@Before
public void setUp() {
mContext = InstrumentationRegistry.getContext();
@@ -104,7 +135,13 @@
MagnificationGestureHandler h = new MagnificationGestureHandler(
mContext, mMagnificationController,
detectTripleTap, detectShortcutTrigger);
- mHandler = new TestHandler(h.mDetectingState, mClock);
+ mHandler = new TestHandler(h.mDetectingState, mClock) {
+ @Override
+ protected String messageToString(Message m) {
+ return DebugUtils.valueToString(
+ MagnificationGestureHandler.DetectingState.class, "MESSAGE_", m.what);
+ }
+ };
h.mDetectingState.mHandler = mHandler;
h.setNext(strictMock(EventStreamTransformation.class));
return h;
@@ -184,11 +221,11 @@
fastForward1sec();
}, STATE_ZOOMED);
- // tap+tap+swipe gets delegated
- assertTransition(STATE_2TAPS, () -> {
- allowEventDelegation();
- swipe();
- }, STATE_IDLE);
+ // tap+tap+swipe doesn't get delegated
+ assertTransition(STATE_2TAPS, () -> swipe(), STATE_IDLE);
+
+ // tap+tap+swipe initiates viewport dragging immediately
+ assertTransition(STATE_2TAPS, () -> swipeAndHold(), STATE_DRAGGING_TMP);
}
@Test
@@ -439,23 +476,24 @@
}
private void tap() {
- MotionEvent downEvent = downEvent();
- send(downEvent);
- send(upEvent(downEvent.getDownTime()));
+ send(downEvent());
+ send(upEvent());
}
private void swipe() {
- MotionEvent downEvent = downEvent();
- send(downEvent);
+ swipeAndHold();
+ send(upEvent());
+ }
+
+ private void swipeAndHold() {
+ send(downEvent());
send(moveEvent(DEFAULT_X * 2, DEFAULT_Y * 2));
- send(upEvent(downEvent.getDownTime()));
}
private void longTap() {
- MotionEvent downEvent = downEvent();
- send(downEvent);
+ send(downEvent());
fastForward(2000);
- send(upEvent(downEvent.getDownTime()));
+ send(upEvent());
}
private void triggerShortcut() {
@@ -473,16 +511,17 @@
}
private MotionEvent moveEvent(float x, float y) {
- return MotionEvent.obtain(defaultDownTime(), mClock.now(), ACTION_MOVE, x, y, 0);
+ return MotionEvent.obtain(mLastDownTime, mClock.now(), ACTION_MOVE, x, y, 0);
}
private MotionEvent downEvent() {
- return MotionEvent.obtain(mClock.now(), mClock.now(),
+ mLastDownTime = mClock.now();
+ return MotionEvent.obtain(mLastDownTime, mLastDownTime,
ACTION_DOWN, DEFAULT_X, DEFAULT_Y, 0);
}
private MotionEvent upEvent() {
- return upEvent(defaultDownTime());
+ return upEvent(mLastDownTime);
}
private MotionEvent upEvent(long downTime) {
@@ -490,11 +529,6 @@
MotionEvent.ACTION_UP, DEFAULT_X, DEFAULT_Y, 0);
}
- private long defaultDownTime() {
- MotionEvent lastDown = mMgh.mDetectingState.mLastDown;
- return lastDown == null ? mClock.now() - 1 : lastDown.getDownTime();
- }
-
private MotionEvent pointerEvent(int action, float x, float y) {
MotionEvent.PointerProperties defPointerProperties = new MotionEvent.PointerProperties();
defPointerProperties.id = 0;
diff --git a/services/tests/servicestests/src/com/android/server/am/RecentTasksTest.java b/services/tests/servicestests/src/com/android/server/am/RecentTasksTest.java
index f384044..5a21102 100644
--- a/services/tests/servicestests/src/com/android/server/am/RecentTasksTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/RecentTasksTest.java
@@ -572,7 +572,6 @@
});
assertSecurityException(expectCallable, () -> mService.getTaskDescription(0));
assertSecurityException(expectCallable, () -> mService.cancelTaskWindowTransition(0));
- assertSecurityException(expectCallable, () -> mService.cancelTaskThumbnailTransition(0));
assertSecurityException(expectCallable, () -> mService.startRecentsActivity(null, null,
null, 0));
}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index 60783db..58ac7d2 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -17,6 +17,10 @@
import static android.app.admin.DevicePolicyManager.DELEGATION_APP_RESTRICTIONS;
import static android.app.admin.DevicePolicyManager.DELEGATION_CERT_INSTALL;
+import static android.app.admin.DevicePolicyManager.ID_TYPE_BASE_INFO;
+import static android.app.admin.DevicePolicyManager.ID_TYPE_IMEI;
+import static android.app.admin.DevicePolicyManager.ID_TYPE_MEID;
+import static android.app.admin.DevicePolicyManager.ID_TYPE_SERIAL;
import static android.app.admin.DevicePolicyManager.WIPE_EUICC;
import static android.os.UserManagerInternal.CAMERA_DISABLED_GLOBALLY;
import static android.os.UserManagerInternal.CAMERA_DISABLED_LOCALLY;
@@ -71,6 +75,7 @@
import android.platform.test.annotations.Presubmit;
import android.provider.Settings;
import android.security.KeyChain;
+import android.security.keystore.AttestationUtils;
import android.telephony.TelephonyManager;
import android.test.MoreAsserts;
import android.test.suitebuilder.annotation.SmallTest;
@@ -4459,6 +4464,47 @@
});
}
+ private void assertAttestationFlags(int attestationFlags, int[] expectedFlags) {
+ int[] gotFlags = DevicePolicyManagerService.translateIdAttestationFlags(attestationFlags);
+ Arrays.sort(gotFlags);
+ Arrays.sort(expectedFlags);
+ assertTrue(Arrays.equals(expectedFlags, gotFlags));
+ }
+
+ public void testTranslationOfIdAttestationFlag() {
+ int[] allIdTypes = new int[]{ID_TYPE_SERIAL, ID_TYPE_IMEI, ID_TYPE_MEID};
+ int[] correspondingAttUtilsTypes = new int[]{
+ AttestationUtils.ID_TYPE_SERIAL, AttestationUtils.ID_TYPE_IMEI,
+ AttestationUtils.ID_TYPE_MEID};
+
+ // Test translation of zero flags
+ assertNull(DevicePolicyManagerService.translateIdAttestationFlags(0));
+
+ // Test translation of the ID_TYPE_BASE_INFO flag, which should yield an empty, but
+ // non-null array
+ assertAttestationFlags(ID_TYPE_BASE_INFO, new int[] {});
+
+ // Test translation of a single flag
+ assertAttestationFlags(ID_TYPE_BASE_INFO | ID_TYPE_SERIAL,
+ new int[] {AttestationUtils.ID_TYPE_SERIAL});
+ assertAttestationFlags(ID_TYPE_SERIAL, new int[] {AttestationUtils.ID_TYPE_SERIAL});
+
+ // Test translation of two flags
+ assertAttestationFlags(ID_TYPE_SERIAL | ID_TYPE_IMEI,
+ new int[] {AttestationUtils.ID_TYPE_IMEI, AttestationUtils.ID_TYPE_SERIAL});
+ assertAttestationFlags(ID_TYPE_BASE_INFO | ID_TYPE_MEID | ID_TYPE_SERIAL,
+ new int[] {AttestationUtils.ID_TYPE_MEID, AttestationUtils.ID_TYPE_SERIAL});
+
+ // Test translation of all three flags
+ assertAttestationFlags(ID_TYPE_SERIAL | ID_TYPE_IMEI | ID_TYPE_MEID,
+ new int[] {AttestationUtils.ID_TYPE_IMEI, AttestationUtils.ID_TYPE_SERIAL,
+ AttestationUtils.ID_TYPE_MEID});
+ // Test translation of all three flags
+ assertAttestationFlags(ID_TYPE_SERIAL | ID_TYPE_IMEI | ID_TYPE_MEID | ID_TYPE_BASE_INFO,
+ new int[] {AttestationUtils.ID_TYPE_IMEI, AttestationUtils.ID_TYPE_SERIAL,
+ AttestationUtils.ID_TYPE_MEID});
+ }
+
private void setUserSetupCompleteForUser(boolean isUserSetupComplete, int userhandle) {
when(getServices().settings.settingsSecureGetIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 0,
userhandle)).thenReturn(isUserSetupComplete ? 1 : 0);
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/OverlayPackagesProviderTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/OverlayPackagesProviderTest.java
index 4447fe9..939a272 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/OverlayPackagesProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/OverlayPackagesProviderTest.java
@@ -261,7 +261,7 @@
}
private void verifyAppsAreNonRequired(String action, String... appArray) {
- assertEquals(listFromArray(appArray),
+ assertEquals(setFromArray(appArray),
mHelper.getNonRequiredApps(TEST_MDM_COMPONENT_NAME, TEST_USER_ID, action));
}
@@ -347,14 +347,7 @@
if (array == null) {
return null;
}
- return new HashSet<T>(Arrays.asList(array));
- }
-
- private <T> List<T> listFromArray(T... array) {
- if (array == null) {
- return null;
- }
- return Arrays.asList(array);
+ return new HashSet<>(Arrays.asList(array));
}
class FakePackageManager extends MockPackageManager {
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java
index da552b9..1aac8ce 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java
@@ -26,21 +26,103 @@
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import android.content.Context;
+import android.security.keystore.AndroidKeyStoreSecretKey;
+import android.security.keystore.KeyGenParameterSpec;
+import android.security.keystore.KeyProperties;
+import android.security.recoverablekeystore.KeyDerivationParameters;
+import android.security.recoverablekeystore.KeyEntryRecoveryData;
+import android.security.recoverablekeystore.KeyStoreRecoveryData;
+import android.support.test.InstrumentationRegistry;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
+import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb;
+import com.android.server.locksettings.recoverablekeystore.storage.RecoverySnapshotStorage;
+
+import org.junit.After;
+import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import java.io.File;
import java.nio.charset.StandardCharsets;
+import java.security.KeyPair;
import java.util.Arrays;
+import java.util.List;
import java.util.Random;
+import javax.crypto.KeyGenerator;
+import javax.crypto.SecretKey;
+
@SmallTest
@RunWith(AndroidJUnit4.class)
public class KeySyncTaskTest {
+ private static final String KEY_ALGORITHM = "AES";
+ private static final String ANDROID_KEY_STORE_PROVIDER = "AndroidKeyStore";
+ private static final String WRAPPING_KEY_ALIAS = "KeySyncTaskTest/WrappingKey";
+ private static final String DATABASE_FILE_NAME = "recoverablekeystore.db";
+ private static final int TEST_USER_ID = 1000;
+ private static final int TEST_APP_UID = 10009;
+ private static final int TEST_RECOVERY_AGENT_UID = 90873;
+ private static final long TEST_DEVICE_ID = 13295035643L;
+ private static final String TEST_APP_KEY_ALIAS = "rcleaver";
+ private static final int TEST_GENERATION_ID = 2;
+ private static final int TEST_CREDENTIAL_TYPE = CREDENTIAL_TYPE_PASSWORD;
+ private static final String TEST_CREDENTIAL = "password1234";
+ private static final byte[] THM_ENCRYPTED_RECOVERY_KEY_HEADER =
+ "V1 THM_encrypted_recovery_key".getBytes(StandardCharsets.UTF_8);
+
+ @Mock private PlatformKeyManager mPlatformKeyManager;
+ @Mock private RecoverySnapshotListenersStorage mSnapshotListenersStorage;
+
+ private RecoverySnapshotStorage mRecoverySnapshotStorage;
+ private RecoverableKeyStoreDb mRecoverableKeyStoreDb;
+ private File mDatabaseFile;
+ private KeyPair mKeyPair;
+ private AndroidKeyStoreSecretKey mWrappingKey;
+ private PlatformEncryptionKey mEncryptKey;
+
+ private KeySyncTask mKeySyncTask;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ Context context = InstrumentationRegistry.getTargetContext();
+ mDatabaseFile = context.getDatabasePath(DATABASE_FILE_NAME);
+ mRecoverableKeyStoreDb = RecoverableKeyStoreDb.newInstance(context);
+ mKeyPair = SecureBox.genKeyPair();
+
+ mRecoverySnapshotStorage = new RecoverySnapshotStorage();
+
+ mKeySyncTask = new KeySyncTask(
+ mRecoverableKeyStoreDb,
+ mRecoverySnapshotStorage,
+ mSnapshotListenersStorage,
+ TEST_USER_ID,
+ TEST_CREDENTIAL_TYPE,
+ TEST_CREDENTIAL,
+ () -> mPlatformKeyManager);
+
+ mWrappingKey = generateAndroidKeyStoreKey();
+ mEncryptKey = new PlatformEncryptionKey(TEST_GENERATION_ID, mWrappingKey);
+ when(mPlatformKeyManager.getDecryptKey()).thenReturn(
+ new PlatformDecryptionKey(TEST_GENERATION_ID, mWrappingKey));
+ }
+
+ @After
+ public void tearDown() {
+ mRecoverableKeyStoreDb.close();
+ mDatabaseFile.delete();
+ }
@Test
public void isPin_isTrueForNumericString() {
@@ -114,6 +196,141 @@
}
+ @Test
+ public void run_doesNotSendAnythingIfNoKeysToSync() throws Exception {
+ // TODO: proper test here, once we have proper implementation for checking that keys need
+ // to be synced.
+ mKeySyncTask.run();
+
+ assertNull(mRecoverySnapshotStorage.get(TEST_USER_ID));
+ }
+
+ @Test
+ public void run_doesNotSendAnythingIfNoRecoveryAgentSet() throws Exception {
+ SecretKey applicationKey = generateKey();
+ mRecoverableKeyStoreDb.setPlatformKeyGenerationId(TEST_USER_ID, TEST_GENERATION_ID);
+ mRecoverableKeyStoreDb.insertKey(
+ TEST_USER_ID,
+ TEST_APP_UID,
+ TEST_APP_KEY_ALIAS,
+ WrappedKey.fromSecretKey(mEncryptKey, applicationKey));
+ when(mSnapshotListenersStorage.hasListener(TEST_RECOVERY_AGENT_UID)).thenReturn(true);
+
+ mKeySyncTask.run();
+
+ assertNull(mRecoverySnapshotStorage.get(TEST_USER_ID));
+ }
+
+ @Test
+ public void run_doesNotSendAnythingIfNoRecoveryAgentPendingIntentRegistered() throws Exception {
+ SecretKey applicationKey = generateKey();
+ mRecoverableKeyStoreDb.setServerParameters(
+ TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_DEVICE_ID);
+ mRecoverableKeyStoreDb.setPlatformKeyGenerationId(TEST_USER_ID, TEST_GENERATION_ID);
+ mRecoverableKeyStoreDb.insertKey(
+ TEST_USER_ID,
+ TEST_APP_UID,
+ TEST_APP_KEY_ALIAS,
+ WrappedKey.fromSecretKey(mEncryptKey, applicationKey));
+ mRecoverableKeyStoreDb.setRecoveryServicePublicKey(
+ TEST_USER_ID, TEST_RECOVERY_AGENT_UID, mKeyPair.getPublic());
+
+ mKeySyncTask.run();
+
+ assertNull(mRecoverySnapshotStorage.get(TEST_USER_ID));
+ }
+
+ @Test
+ public void run_doesNotSendAnythingIfNoDeviceIdIsSet() throws Exception {
+ SecretKey applicationKey = generateKey();
+ mRecoverableKeyStoreDb.setPlatformKeyGenerationId(TEST_USER_ID, TEST_GENERATION_ID);
+ mRecoverableKeyStoreDb.insertKey(
+ TEST_USER_ID,
+ TEST_APP_UID,
+ TEST_APP_KEY_ALIAS,
+ WrappedKey.fromSecretKey(mEncryptKey, applicationKey));
+ mRecoverableKeyStoreDb.setRecoveryServicePublicKey(
+ TEST_USER_ID, TEST_RECOVERY_AGENT_UID, mKeyPair.getPublic());
+ when(mSnapshotListenersStorage.hasListener(TEST_RECOVERY_AGENT_UID)).thenReturn(true);
+
+ mKeySyncTask.run();
+
+ assertNull(mRecoverySnapshotStorage.get(TEST_USER_ID));
+ }
+
+ @Test
+ public void run_sendsEncryptedKeysIfAvailableToSync() throws Exception {
+ SecretKey applicationKey = generateKey();
+ mRecoverableKeyStoreDb.setServerParameters(
+ TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_DEVICE_ID);
+ mRecoverableKeyStoreDb.setPlatformKeyGenerationId(TEST_USER_ID, TEST_GENERATION_ID);
+ mRecoverableKeyStoreDb.insertKey(
+ TEST_USER_ID,
+ TEST_APP_UID,
+ TEST_APP_KEY_ALIAS,
+ WrappedKey.fromSecretKey(mEncryptKey, applicationKey));
+ mRecoverableKeyStoreDb.setRecoveryServicePublicKey(
+ TEST_USER_ID, TEST_RECOVERY_AGENT_UID, mKeyPair.getPublic());
+ when(mSnapshotListenersStorage.hasListener(TEST_RECOVERY_AGENT_UID)).thenReturn(true);
+
+ mKeySyncTask.run();
+
+ KeyStoreRecoveryData recoveryData = mRecoverySnapshotStorage.get(TEST_USER_ID);
+ KeyDerivationParameters keyDerivationParameters =
+ recoveryData.getRecoveryMetadata().get(0).getKeyDerivationParameters();
+ assertEquals(KeyDerivationParameters.ALGORITHM_SHA256,
+ keyDerivationParameters.getAlgorithm());
+ verify(mSnapshotListenersStorage).recoverySnapshotAvailable(TEST_RECOVERY_AGENT_UID);
+ byte[] lockScreenHash = KeySyncTask.hashCredentials(
+ keyDerivationParameters.getSalt(),
+ TEST_CREDENTIAL);
+ // TODO: what should counter_id be here?
+ byte[] recoveryKey = decryptThmEncryptedKey(
+ lockScreenHash,
+ recoveryData.getEncryptedRecoveryKeyBlob(),
+ /*vaultParams=*/ KeySyncUtils.packVaultParams(
+ mKeyPair.getPublic(),
+ /*counterId=*/ 1,
+ /*maxAttempts=*/ 10,
+ TEST_DEVICE_ID));
+ List<KeyEntryRecoveryData> applicationKeys = recoveryData.getApplicationKeyBlobs();
+ assertEquals(1, applicationKeys.size());
+ KeyEntryRecoveryData keyData = applicationKeys.get(0);
+ assertArrayEquals(TEST_APP_KEY_ALIAS.getBytes(StandardCharsets.UTF_8), keyData.getAlias());
+ byte[] appKey = KeySyncUtils.decryptApplicationKey(
+ recoveryKey, keyData.getEncryptedKeyMaterial());
+ assertArrayEquals(applicationKey.getEncoded(), appKey);
+ }
+
+ private byte[] decryptThmEncryptedKey(
+ byte[] lockScreenHash, byte[] encryptedKey, byte[] vaultParams) throws Exception {
+ byte[] locallyEncryptedKey = SecureBox.decrypt(
+ mKeyPair.getPrivate(),
+ /*sharedSecret=*/ KeySyncUtils.calculateThmKfHash(lockScreenHash),
+ /*header=*/ KeySyncUtils.concat(THM_ENCRYPTED_RECOVERY_KEY_HEADER, vaultParams),
+ encryptedKey
+ );
+ return KeySyncUtils.decryptRecoveryKey(lockScreenHash, locallyEncryptedKey);
+ }
+
+ private SecretKey generateKey() throws Exception {
+ KeyGenerator keyGenerator = KeyGenerator.getInstance(KEY_ALGORITHM);
+ keyGenerator.init(/*keySize=*/ 256);
+ return keyGenerator.generateKey();
+ }
+
+ private AndroidKeyStoreSecretKey generateAndroidKeyStoreKey() throws Exception {
+ KeyGenerator keyGenerator = KeyGenerator.getInstance(
+ KEY_ALGORITHM,
+ ANDROID_KEY_STORE_PROVIDER);
+ keyGenerator.init(new KeyGenParameterSpec.Builder(
+ WRAPPING_KEY_ALIAS, KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
+ .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
+ .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
+ .build());
+ return (AndroidKeyStoreSecretKey) keyGenerator.generateKey();
+ }
+
private static byte[] utf8Bytes(String s) {
return s.getBytes(StandardCharsets.UTF_8);
}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncUtilsTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncUtilsTest.java
index 6254d52..114da1a 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncUtilsTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncUtilsTest.java
@@ -30,9 +30,12 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets;
import java.security.KeyPair;
import java.security.MessageDigest;
+import java.security.PublicKey;
import java.util.Arrays;
import java.util.Map;
import java.util.Random;
@@ -57,6 +60,8 @@
"V1 KF_claim".getBytes(StandardCharsets.UTF_8);
private static final byte[] RECOVERY_RESPONSE_HEADER =
"V1 reencrypted_recovery_key".getBytes(StandardCharsets.UTF_8);
+ private static final int PUBLIC_KEY_LENGTH_BYTES = 65;
+ private static final int VAULT_PARAMS_LENGTH_BYTES = 85;
@Test
public void calculateThmKfHash_isShaOfLockScreenHashWithPrefix() throws Exception {
@@ -336,6 +341,83 @@
}
}
+ @Test
+ public void packVaultParams_returns85Bytes() throws Exception {
+ PublicKey thmPublicKey = SecureBox.genKeyPair().getPublic();
+
+ byte[] packedForm = KeySyncUtils.packVaultParams(
+ thmPublicKey,
+ /*counterId=*/ 1001L,
+ /*maxAttempts=*/ 10,
+ /*deviceId=*/ 1L);
+
+ assertEquals(VAULT_PARAMS_LENGTH_BYTES, packedForm.length);
+ }
+
+ @Test
+ public void packVaultParams_encodesPublicKeyInFirst65Bytes() throws Exception {
+ PublicKey thmPublicKey = SecureBox.genKeyPair().getPublic();
+
+ byte[] packedForm = KeySyncUtils.packVaultParams(
+ thmPublicKey,
+ /*counterId=*/ 1001L,
+ /*maxAttempts=*/ 10,
+ /*deviceId=*/ 1L);
+
+ assertArrayEquals(
+ SecureBox.encodePublicKey(thmPublicKey),
+ Arrays.copyOf(packedForm, PUBLIC_KEY_LENGTH_BYTES));
+ }
+
+ @Test
+ public void packVaultParams_encodesCounterIdAsSecondParam() throws Exception {
+ long counterId = 103502L;
+
+ byte[] packedForm = KeySyncUtils.packVaultParams(
+ SecureBox.genKeyPair().getPublic(),
+ counterId,
+ /*maxAttempts=*/ 10,
+ /*deviceId=*/ 1L);
+
+ ByteBuffer byteBuffer = ByteBuffer.wrap(packedForm)
+ .order(ByteOrder.LITTLE_ENDIAN);
+ byteBuffer.position(PUBLIC_KEY_LENGTH_BYTES);
+ assertEquals(counterId, byteBuffer.getLong());
+ }
+
+ @Test
+ public void packVaultParams_encodesDeviceIdAsThirdParam() throws Exception {
+ long deviceId = 102942158152L;
+
+ byte[] packedForm = KeySyncUtils.packVaultParams(
+ SecureBox.genKeyPair().getPublic(),
+ /*counterId=*/ 10021L,
+ /*maxAttempts=*/ 10,
+ deviceId);
+
+ ByteBuffer byteBuffer = ByteBuffer.wrap(packedForm)
+ .order(ByteOrder.LITTLE_ENDIAN);
+ byteBuffer.position(PUBLIC_KEY_LENGTH_BYTES + Long.BYTES);
+ assertEquals(deviceId, byteBuffer.getLong());
+ }
+
+ @Test
+ public void packVaultParams_encodesMaxAttemptsAsLastParam() throws Exception {
+ int maxAttempts = 10;
+
+ byte[] packedForm = KeySyncUtils.packVaultParams(
+ SecureBox.genKeyPair().getPublic(),
+ /*counterId=*/ 1001L,
+ maxAttempts,
+ /*deviceId=*/ 1L);
+
+ ByteBuffer byteBuffer = ByteBuffer.wrap(packedForm)
+ .order(ByteOrder.LITTLE_ENDIAN);
+ byteBuffer.position(PUBLIC_KEY_LENGTH_BYTES + 2 * Long.BYTES);
+ assertEquals(maxAttempts, byteBuffer.getInt());
+ }
+
+
private static byte[] randomBytes(int n) {
byte[] bytes = new byte[n];
new Random().nextBytes(bytes);
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java
index e20f664..97fbca2 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java
@@ -29,7 +29,6 @@
import android.app.KeyguardManager;
import android.content.Context;
-import android.content.SharedPreferences;
import android.security.keystore.KeyProperties;
import android.security.keystore.KeyProtection;
import android.support.test.InstrumentationRegistry;
@@ -206,6 +205,14 @@
}
@Test
+ public void init_savesGenerationIdToDatabase() throws Exception {
+ mPlatformKeyManager.init();
+
+ assertEquals(1,
+ mRecoverableKeyStoreDb.getPlatformKeyGenerationId(USER_ID_FIXTURE));
+ }
+
+ @Test
public void init_setsGenerationIdTo1() throws Exception {
mPlatformKeyManager.init();
@@ -213,7 +220,38 @@
}
@Test
+ public void init_incrementsGenerationIdIfKeyIsUnavailable() throws Exception {
+ mPlatformKeyManager.init();
+
+ mPlatformKeyManager.init();
+
+ assertEquals(2, mPlatformKeyManager.getGenerationId());
+ }
+
+ @Test
+ public void init_doesNotIncrementGenerationIdIfKeyAvailable() throws Exception {
+ mPlatformKeyManager.init();
+ when(mKeyStoreProxy
+ .containsAlias("com.android.server.locksettings.recoverablekeystore/"
+ + "platform/42/1/decrypt")).thenReturn(true);
+ when(mKeyStoreProxy
+ .containsAlias("com.android.server.locksettings.recoverablekeystore/"
+ + "platform/42/1/encrypt")).thenReturn(true);
+
+ mPlatformKeyManager.init();
+
+ assertEquals(1, mPlatformKeyManager.getGenerationId());
+ }
+
+ @Test
+ public void getGenerationId_returnsMinusOneIfNotInitialized() throws Exception {
+ assertEquals(-1, mPlatformKeyManager.getGenerationId());
+ }
+
+ @Test
public void getDecryptKey_getsDecryptKeyWithCorrectAlias() throws Exception {
+ mPlatformKeyManager.init();
+
mPlatformKeyManager.getDecryptKey();
verify(mKeyStoreProxy).getKey(
@@ -223,6 +261,8 @@
@Test
public void getEncryptKey_getsDecryptKeyWithCorrectAlias() throws Exception {
+ mPlatformKeyManager.init();
+
mPlatformKeyManager.getEncryptKey();
verify(mKeyStoreProxy).getKey(
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGeneratorTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGeneratorTest.java
index b3dbf93..8a461ac 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGeneratorTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGeneratorTest.java
@@ -16,13 +16,12 @@
package com.android.server.locksettings.recoverablekeystore;
-import static junit.framework.Assert.fail;
+import static junit.framework.Assert.assertNotNull;
import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertEquals;
import android.content.Context;
-import android.security.keystore.AndroidKeyStoreProvider;
import android.security.keystore.AndroidKeyStoreSecretKey;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties;
@@ -32,8 +31,6 @@
import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb;
-import com.google.common.collect.ImmutableMap;
-
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -41,13 +38,10 @@
import java.io.File;
import java.nio.charset.StandardCharsets;
-import java.security.InvalidKeyException;
import java.security.KeyStore;
-import java.util.Arrays;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
-import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
@SmallTest
@@ -57,14 +51,13 @@
private static final int TEST_GENERATION_ID = 3;
private static final String ANDROID_KEY_STORE_PROVIDER = "AndroidKeyStore";
private static final String KEY_ALGORITHM = "AES";
- private static final String SUPPORTED_CIPHER_ALGORITHM = "AES/GCM/NoPadding";
- private static final String UNSUPPORTED_CIPHER_ALGORITHM = "AES/CTR/NoPadding";
+ private static final int KEY_SIZE_BYTES = 32;
+ private static final String KEY_WRAP_ALGORITHM = "AES/GCM/NoPadding";
private static final String TEST_ALIAS = "karlin";
private static final String WRAPPING_KEY_ALIAS = "RecoverableKeyGeneratorTestWrappingKey";
private static final int TEST_USER_ID = 1000;
private static final int KEYSTORE_UID_SELF = -1;
private static final int GCM_TAG_LENGTH_BITS = 128;
- private static final int GCM_NONCE_LENGTH_BYTES = 12;
private PlatformEncryptionKey mPlatformKey;
private PlatformDecryptionKey mDecryptKey;
@@ -95,67 +88,33 @@
}
@Test
- public void generateAndStoreKey_setsKeyInKeyStore() throws Exception {
- mRecoverableKeyGenerator.generateAndStoreKey(
- mPlatformKey, TEST_USER_ID, KEYSTORE_UID_SELF, TEST_ALIAS);
-
- KeyStore keyStore = AndroidKeyStoreProvider.getKeyStoreForUid(KEYSTORE_UID_SELF);
- assertTrue(keyStore.containsAlias(TEST_ALIAS));
- }
-
- @Test
- public void generateAndStoreKey_storesKeyEnabledForAesGcmNoPaddingEncryptDecrypt()
- throws Exception {
- mRecoverableKeyGenerator.generateAndStoreKey(
- mPlatformKey, TEST_USER_ID, KEYSTORE_UID_SELF, TEST_ALIAS);
-
- KeyStore keyStore = AndroidKeyStoreProvider.getKeyStoreForUid(KEYSTORE_UID_SELF);
- SecretKey key = (SecretKey) keyStore.getKey(TEST_ALIAS, /*password=*/ null);
- Cipher cipher = Cipher.getInstance(SUPPORTED_CIPHER_ALGORITHM);
- cipher.init(Cipher.ENCRYPT_MODE, key);
- byte[] nonce = new byte[GCM_NONCE_LENGTH_BYTES];
- Arrays.fill(nonce, (byte) 0);
- cipher.init(Cipher.DECRYPT_MODE, key, new GCMParameterSpec(GCM_TAG_LENGTH_BITS, nonce));
- }
-
- @Test
- public void generateAndStoreKey_storesKeyDisabledForOtherModes() throws Exception {
- mRecoverableKeyGenerator.generateAndStoreKey(
- mPlatformKey, TEST_USER_ID, KEYSTORE_UID_SELF, TEST_ALIAS);
-
- KeyStore keyStore = AndroidKeyStoreProvider.getKeyStoreForUid(KEYSTORE_UID_SELF);
- SecretKey key = (SecretKey) keyStore.getKey(TEST_ALIAS, /*password=*/ null);
- Cipher cipher = Cipher.getInstance(UNSUPPORTED_CIPHER_ALGORITHM);
-
- try {
- cipher.init(Cipher.ENCRYPT_MODE, key);
- fail("Should not be able to use key for " + UNSUPPORTED_CIPHER_ALGORITHM);
- } catch (InvalidKeyException e) {
- // expected
- }
- }
-
- @Test
public void generateAndStoreKey_storesWrappedKey() throws Exception {
mRecoverableKeyGenerator.generateAndStoreKey(
mPlatformKey, TEST_USER_ID, KEYSTORE_UID_SELF, TEST_ALIAS);
- KeyStore keyStore = AndroidKeyStoreProvider.getKeyStoreForUid(KEYSTORE_UID_SELF);
- SecretKey key = (SecretKey) keyStore.getKey(TEST_ALIAS, /*password=*/ null);
WrappedKey wrappedKey = mRecoverableKeyStoreDb.getKey(KEYSTORE_UID_SELF, TEST_ALIAS);
- SecretKey unwrappedKey = WrappedKey
- .unwrapKeys(mDecryptKey, ImmutableMap.of(TEST_ALIAS, wrappedKey))
- .get(TEST_ALIAS);
+ assertNotNull(wrappedKey);
+ }
- // key and unwrappedKey should be equivalent. let's check!
- byte[] plaintext = getUtf8Bytes("dtianpos");
- Cipher cipher = Cipher.getInstance(SUPPORTED_CIPHER_ALGORITHM);
- cipher.init(Cipher.ENCRYPT_MODE, key);
- byte[] encrypted = cipher.doFinal(plaintext);
- byte[] iv = cipher.getIV();
- cipher.init(Cipher.DECRYPT_MODE, unwrappedKey, new GCMParameterSpec(128, iv));
- byte[] decrypted = cipher.doFinal(encrypted);
- assertArrayEquals(decrypted, plaintext);
+ @Test
+ public void generateAndStoreKey_returnsRawMaterialOfCorrectLength() throws Exception {
+ byte[] rawKey = mRecoverableKeyGenerator.generateAndStoreKey(
+ mPlatformKey, TEST_USER_ID, KEYSTORE_UID_SELF, TEST_ALIAS);
+
+ assertEquals(KEY_SIZE_BYTES, rawKey.length);
+ }
+
+ @Test
+ public void generateAndStoreKey_storesTheWrappedVersionOfTheRawMaterial() throws Exception {
+ byte[] rawMaterial = mRecoverableKeyGenerator.generateAndStoreKey(
+ mPlatformKey, TEST_USER_ID, KEYSTORE_UID_SELF, TEST_ALIAS);
+
+ WrappedKey wrappedKey = mRecoverableKeyStoreDb.getKey(KEYSTORE_UID_SELF, TEST_ALIAS);
+ Cipher cipher = Cipher.getInstance(KEY_WRAP_ALGORITHM);
+ cipher.init(Cipher.DECRYPT_MODE, mDecryptKey.getKey(),
+ new GCMParameterSpec(GCM_TAG_LENGTH_BITS, wrappedKey.getNonce()));
+ byte[] unwrappedMaterial = cipher.doFinal(wrappedKey.getKeyMaterial());
+ assertArrayEquals(rawMaterial, unwrappedMaterial);
}
private AndroidKeyStoreSecretKey generateAndroidKeyStoreKey() throws Exception {
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java
index 953ea62..88df62b 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java
@@ -24,10 +24,14 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import android.app.KeyguardManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
@@ -44,6 +48,7 @@
import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb;
import com.android.server.locksettings.recoverablekeystore.storage.RecoverySessionStorage;
+import com.android.server.locksettings.recoverablekeystore.storage.RecoverySnapshotStorage;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
@@ -61,7 +66,9 @@
import java.util.Map;
import java.util.Random;
+import javax.crypto.Cipher;
import javax.crypto.SecretKey;
+import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
@SmallTest
@@ -69,6 +76,7 @@
public class RecoverableKeyStoreManagerTest {
private static final String DATABASE_FILE_NAME = "recoverablekeystore.db";
+ private static final String KEY_WRAP_CIPHER_ALGORITHM = "AES/GCM/NoPadding";
private static final String TEST_SESSION_ID = "karlin";
private static final byte[] TEST_PUBLIC_KEY = new byte[] {
(byte) 0x30, (byte) 0x59, (byte) 0x30, (byte) 0x13, (byte) 0x06, (byte) 0x07, (byte) 0x2a,
@@ -93,18 +101,21 @@
private static final byte[] RECOVERY_RESPONSE_HEADER =
"V1 reencrypted_recovery_key".getBytes(StandardCharsets.UTF_8);
private static final String TEST_ALIAS = "nick";
-
+ private static final int RECOVERABLE_KEY_SIZE_BYTES = 32;
private static final int GENERATION_ID = 1;
private static final byte[] NONCE = getUtf8Bytes("nonce");
private static final byte[] KEY_MATERIAL = getUtf8Bytes("keymaterial");
+ private static final int GCM_TAG_SIZE_BITS = 128;
@Mock private Context mMockContext;
- @Mock private ListenersStorage mMockListenersStorage;
+ @Mock private RecoverySnapshotListenersStorage mMockListenersStorage;
+ @Mock private KeyguardManager mKeyguardManager;
private RecoverableKeyStoreDb mRecoverableKeyStoreDb;
private File mDatabaseFile;
private RecoverableKeyStoreManager mRecoverableKeyStoreManager;
private RecoverySessionStorage mRecoverySessionStorage;
+ private RecoverySnapshotStorage mRecoverySnapshotStorage;
@Before
public void setUp() {
@@ -113,12 +124,20 @@
Context context = InstrumentationRegistry.getTargetContext();
mDatabaseFile = context.getDatabasePath(DATABASE_FILE_NAME);
mRecoverableKeyStoreDb = RecoverableKeyStoreDb.newInstance(context);
+
mRecoverySessionStorage = new RecoverySessionStorage();
+
+ when(mMockContext.getSystemService(anyString())).thenReturn(mKeyguardManager);
+ when(mMockContext.getSystemServiceName(any())).thenReturn("test");
+ when(mMockContext.getApplicationContext()).thenReturn(mMockContext);
+ when(mKeyguardManager.isDeviceSecure(anyInt())).thenReturn(true);
+
mRecoverableKeyStoreManager = new RecoverableKeyStoreManager(
mMockContext,
mRecoverableKeyStoreDb,
mRecoverySessionStorage,
Executors.newSingleThreadExecutor(),
+ mRecoverySnapshotStorage,
mMockListenersStorage);
}
@@ -129,6 +148,21 @@
}
@Test
+ public void generateAndStoreKey_storesTheKey() throws Exception {
+ int uid = Binder.getCallingUid();
+
+ mRecoverableKeyStoreManager.generateAndStoreKey(TEST_ALIAS);
+
+ assertThat(mRecoverableKeyStoreDb.getKey(uid, TEST_ALIAS)).isNotNull();
+ }
+
+ @Test
+ public void generateAndStoreKey_returnsAKeyOfAppropriateSize() throws Exception {
+ assertThat(mRecoverableKeyStoreManager.generateAndStoreKey(TEST_ALIAS))
+ .hasLength(RECOVERABLE_KEY_SIZE_BYTES);
+ }
+
+ @Test
public void startRecoverySession_checksPermissionFirst() throws Exception {
mRecoverableKeyStoreManager.startRecoverySession(
TEST_SESSION_ID,
@@ -285,7 +319,7 @@
}
@Test
- public void recoverKeys_doesNotThrowIfAllIsOk() throws Exception {
+ public void recoverKeys_returnsDecryptedKeys() throws Exception {
mRecoverableKeyStoreManager.startRecoverySession(
TEST_SESSION_ID,
TEST_PUBLIC_KEY,
@@ -302,16 +336,19 @@
SecretKey recoveryKey = randomRecoveryKey();
byte[] encryptedClaimResponse = encryptClaimResponse(
keyClaimant, TEST_SECRET, TEST_VAULT_PARAMS, recoveryKey);
+ byte[] applicationKeyBytes = randomBytes(32);
KeyEntryRecoveryData applicationKey = new KeyEntryRecoveryData(
TEST_ALIAS.getBytes(StandardCharsets.UTF_8),
- randomEncryptedApplicationKey(recoveryKey)
- );
+ encryptedApplicationKey(recoveryKey, applicationKeyBytes));
- mRecoverableKeyStoreManager.recoverKeys(
+ Map<String, byte[]> recoveredKeys = mRecoverableKeyStoreManager.recoverKeys(
TEST_SESSION_ID,
encryptedClaimResponse,
ImmutableList.of(applicationKey),
TEST_USER_ID);
+
+ assertThat(recoveredKeys).hasSize(1);
+ assertThat(recoveredKeys.get(TEST_ALIAS)).isEqualTo(applicationKeyBytes);
}
@Test
@@ -325,6 +362,26 @@
}
@Test
+ public void setRecoverySecretTypes() throws Exception {
+ int userId = UserHandle.getCallingUserId();
+ int[] types1 = new int[]{11, 2000};
+ int[] types2 = new int[]{1, 2, 3};
+ int[] types3 = new int[]{};
+
+ mRecoverableKeyStoreManager.setRecoverySecretTypes(types1, userId);
+ assertThat(mRecoverableKeyStoreManager.getRecoverySecretTypes(userId)).isEqualTo(
+ types1);
+
+ mRecoverableKeyStoreManager.setRecoverySecretTypes(types2, userId);
+ assertThat(mRecoverableKeyStoreManager.getRecoverySecretTypes(userId)).isEqualTo(
+ types2);
+
+ mRecoverableKeyStoreManager.setRecoverySecretTypes(types3, userId);
+ assertThat(mRecoverableKeyStoreManager.getRecoverySecretTypes(userId)).isEqualTo(
+ types3);
+ }
+
+ @Test
public void setRecoveryStatus_forOneAlias() throws Exception {
int userId = UserHandle.getCallingUserId();
int uid = Binder.getCallingUid();
@@ -387,9 +444,10 @@
assertThat(statuses).containsEntry(alias2, status); // updated
}
- private static byte[] randomEncryptedApplicationKey(SecretKey recoveryKey) throws Exception {
+ private static byte[] encryptedApplicationKey(
+ SecretKey recoveryKey, byte[] applicationKey) throws Exception {
return KeySyncUtils.encryptKeysWithRecoveryKey(recoveryKey, ImmutableMap.of(
- "alias", new SecretKeySpec(randomBytes(32), "AES")
+ "alias", new SecretKeySpec(applicationKey, "AES")
)).get("alias");
}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverySnapshotListenersStorageTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverySnapshotListenersStorageTest.java
new file mode 100644
index 0000000..b9c1764
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverySnapshotListenersStorageTest.java
@@ -0,0 +1,37 @@
+package com.android.server.locksettings.recoverablekeystore;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.app.PendingIntent;
+import android.content.Intent;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class RecoverySnapshotListenersStorageTest {
+
+ private final RecoverySnapshotListenersStorage mStorage =
+ new RecoverySnapshotListenersStorage();
+
+ @Test
+ public void hasListener_isFalseForUnregisteredUid() {
+ assertFalse(mStorage.hasListener(1000));
+ }
+
+ @Test
+ public void hasListener_isTrueForRegisteredUid() {
+ int recoveryAgentUid = 1000;
+ PendingIntent intent = PendingIntent.getBroadcast(
+ InstrumentationRegistry.getTargetContext(), /*requestCode=*/1,
+ new Intent(), /*flags=*/ 0);
+ mStorage.setSnapshotListener(recoveryAgentUid, intent);
+
+ assertTrue(mStorage.hasListener(recoveryAgentUid));
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbTest.java
index aaeaf7c..a8c7d5e 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbTest.java
@@ -28,6 +28,7 @@
import org.junit.runner.RunWith;
import android.content.Context;
+import android.content.SharedPreferences;
import android.security.recoverablekeystore.RecoverableKeyStoreLoader;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.SmallTest;
@@ -36,6 +37,9 @@
import java.io.File;
import java.nio.charset.StandardCharsets;
+import java.security.KeyPairGenerator;
+import java.security.PublicKey;
+import java.security.spec.ECGenParameterSpec;
import java.util.Map;
@SmallTest
@@ -290,7 +294,249 @@
assertThat(statuses).hasSize(0);
}
+ @Test
+ public void setRecoveryServicePublicKey_replaceOldKey() throws Exception {
+ int userId = 12;
+ int uid = 10009;
+ PublicKey pubkey1 = genRandomPublicKey();
+ PublicKey pubkey2 = genRandomPublicKey();
+ mRecoverableKeyStoreDb.setRecoveryServicePublicKey(userId, uid, pubkey1);
+ mRecoverableKeyStoreDb.setRecoveryServicePublicKey(userId, uid, pubkey2);
+ assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId, uid)).isEqualTo(
+ pubkey2);
+ }
+
+ @Test
+ public void getRecoveryServicePublicKey_returnsNullIfNoKey() throws Exception {
+ int userId = 12;
+ int uid = 10009;
+ assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId, uid)).isNull();
+
+ long serverParams = 123456L;
+ mRecoverableKeyStoreDb.setServerParameters(userId, uid, serverParams);
+ assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId, uid)).isNull();
+ }
+
+ @Test
+ public void getRecoveryServicePublicKey_returnsInsertedKey() throws Exception {
+ int userId = 12;
+ int uid = 10009;
+ PublicKey pubkey = genRandomPublicKey();
+ mRecoverableKeyStoreDb.setRecoveryServicePublicKey(userId, uid, pubkey);
+ assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId, uid)).isEqualTo(
+ pubkey);
+ }
+
+ @Test
+ public void getRecoveryAgentUid_returnsUidIfSet() throws Exception {
+ int userId = 12;
+ int uid = 190992;
+ mRecoverableKeyStoreDb.setRecoveryServicePublicKey(userId, uid, genRandomPublicKey());
+
+ assertThat(mRecoverableKeyStoreDb.getRecoveryAgentUid(userId)).isEqualTo(uid);
+ }
+
+ @Test
+ public void getRecoveryAgentUid_returnsMinusOneForNonexistentAgent() throws Exception {
+ assertThat(mRecoverableKeyStoreDb.getRecoveryAgentUid(12)).isEqualTo(-1);
+ }
+
+ public void setRecoverySecretTypes_emptyDefaultValue() throws Exception {
+ int userId = 12;
+ int uid = 10009;
+ assertThat(mRecoverableKeyStoreDb.getRecoverySecretTypes(userId, uid)).isEqualTo(
+ new int[]{}); // default
+ }
+
+ @Test
+ public void setRecoverySecretTypes_updateValue() throws Exception {
+ int userId = 12;
+ int uid = 10009;
+ int[] types1 = new int[]{1};
+ int[] types2 = new int[]{2};
+
+ mRecoverableKeyStoreDb.setRecoverySecretTypes(userId, uid, types1);
+ assertThat(mRecoverableKeyStoreDb.getRecoverySecretTypes(userId, uid)).isEqualTo(
+ types1);
+ mRecoverableKeyStoreDb.setRecoverySecretTypes(userId, uid, types2);
+ assertThat(mRecoverableKeyStoreDb.getRecoverySecretTypes(userId, uid)).isEqualTo(
+ types2);
+ }
+
+ @Test
+ public void setRecoverySecretTypes_withMultiElementArrays() throws Exception {
+ int userId = 12;
+ int uid = 10009;
+ int[] types1 = new int[]{11, 2000};
+ int[] types2 = new int[]{1, 2, 3};
+ int[] types3 = new int[]{};
+
+ mRecoverableKeyStoreDb.setRecoverySecretTypes(userId, uid, types1);
+ assertThat(mRecoverableKeyStoreDb.getRecoverySecretTypes(userId, uid)).isEqualTo(
+ types1);
+ mRecoverableKeyStoreDb.setRecoverySecretTypes(userId, uid, types2);
+ assertThat(mRecoverableKeyStoreDb.getRecoverySecretTypes(userId, uid)).isEqualTo(
+ types2);
+ mRecoverableKeyStoreDb.setRecoverySecretTypes(userId, uid, types3);
+ assertThat(mRecoverableKeyStoreDb.getRecoverySecretTypes(userId, uid)).isEqualTo(
+ types3);
+ }
+
+ @Test
+ public void setRecoverySecretTypes_withDifferentUid() throws Exception {
+ int userId = 12;
+ int uid1 = 10011;
+ int uid2 = 10012;
+ int[] types1 = new int[]{1};
+ int[] types2 = new int[]{2};
+
+ mRecoverableKeyStoreDb.setRecoverySecretTypes(userId, uid1, types1);
+ mRecoverableKeyStoreDb.setRecoverySecretTypes(userId, uid2, types2);
+ assertThat(mRecoverableKeyStoreDb.getRecoverySecretTypes(userId, uid1)).isEqualTo(
+ types1);
+ assertThat(mRecoverableKeyStoreDb.getRecoverySecretTypes(userId, uid2)).isEqualTo(
+ types2);
+ }
+
+ @Test
+ public void setRecoveryServiceMetadataMethods() throws Exception {
+ int userId = 12;
+ int uid = 10009;
+
+ PublicKey pubkey1 = genRandomPublicKey();
+ int[] types1 = new int[]{1};
+ long serverParams1 = 111L;
+
+ PublicKey pubkey2 = genRandomPublicKey();
+ int[] types2 = new int[]{2};
+ long serverParams2 = 222L;
+
+ mRecoverableKeyStoreDb.setRecoveryServicePublicKey(userId, uid, pubkey1);
+ mRecoverableKeyStoreDb.setRecoverySecretTypes(userId, uid, types1);
+ mRecoverableKeyStoreDb.setServerParameters(userId, uid, serverParams1);
+
+ assertThat(mRecoverableKeyStoreDb.getRecoverySecretTypes(userId, uid)).isEqualTo(
+ types1);
+ assertThat(mRecoverableKeyStoreDb.getServerParameters(userId, uid)).isEqualTo(
+ serverParams1);
+ assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId, uid)).isEqualTo(
+ pubkey1);
+
+ // Check that the methods don't interfere with each other.
+ mRecoverableKeyStoreDb.setRecoveryServicePublicKey(userId, uid, pubkey2);
+ mRecoverableKeyStoreDb.setRecoverySecretTypes(userId, uid, types2);
+ mRecoverableKeyStoreDb.setServerParameters(userId, uid, serverParams2);
+
+ assertThat(mRecoverableKeyStoreDb.getRecoverySecretTypes(userId, uid)).isEqualTo(
+ types2);
+ assertThat(mRecoverableKeyStoreDb.getServerParameters(userId, uid)).isEqualTo(
+ serverParams2);
+ assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId, uid)).isEqualTo(
+ pubkey2);
+ }
+
+ @Test
+ public void getRecoveryServicePublicKey_returnsFirstKey() throws Exception {
+ int userId = 68;
+ int uid = 12904;
+ PublicKey publicKey = genRandomPublicKey();
+
+ mRecoverableKeyStoreDb.setRecoveryServicePublicKey(userId, uid, publicKey);
+
+ assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId)).isEqualTo(publicKey);
+ }
+
+ @Test
+ public void setServerParameters_replaceOldValue() throws Exception {
+ int userId = 12;
+ int uid = 10009;
+ long serverParams1 = 111L;
+ long serverParams2 = 222L;
+ mRecoverableKeyStoreDb.setServerParameters(userId, uid, serverParams1);
+ mRecoverableKeyStoreDb.setServerParameters(userId, uid, serverParams2);
+ assertThat(mRecoverableKeyStoreDb.getServerParameters(userId, uid)).isEqualTo(
+ serverParams2);
+ }
+
+ @Test
+ public void getServerParameters_returnsNullIfNoValue() throws Exception {
+ int userId = 12;
+ int uid = 10009;
+ assertThat(mRecoverableKeyStoreDb.getServerParameters(userId, uid)).isNull();
+
+ PublicKey pubkey = genRandomPublicKey();
+ mRecoverableKeyStoreDb.setRecoveryServicePublicKey(userId, uid, pubkey);
+ assertThat(mRecoverableKeyStoreDb.getServerParameters(userId, uid)).isNull();
+ }
+
+ @Test
+ public void getServerParameters_returnsInsertedValue() throws Exception {
+ int userId = 12;
+ int uid = 10009;
+ long serverParams = 123456L;
+ mRecoverableKeyStoreDb.setServerParameters(userId, uid, serverParams);
+ assertThat(mRecoverableKeyStoreDb.getServerParameters(userId, uid)).isEqualTo(serverParams);
+ }
+
+ @Test
+ public void setRecoveryServiceMetadataEntry_allowsAUserToHaveTwoUids() throws Exception {
+ int userId = 12;
+ int uid1 = 10009;
+ int uid2 = 20009;
+ PublicKey pubkey = genRandomPublicKey();
+ mRecoverableKeyStoreDb.setRecoveryServicePublicKey(userId, uid1, pubkey);
+ mRecoverableKeyStoreDb.setRecoveryServicePublicKey(userId, uid2, pubkey);
+ assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId, uid1)).isEqualTo(
+ pubkey);
+ assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId, uid2)).isEqualTo(
+ pubkey);
+ }
+
+ @Test
+ public void setRecoveryServiceMetadataEntry_allowsTwoUsersToHaveTheSameUid() throws Exception {
+ int userId1 = 12;
+ int userId2 = 23;
+ int uid = 10009;
+ PublicKey pubkey = genRandomPublicKey();
+ mRecoverableKeyStoreDb.setRecoveryServicePublicKey(userId1, uid, pubkey);
+ mRecoverableKeyStoreDb.setRecoveryServicePublicKey(userId2, uid, pubkey);
+ assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId1, uid)).isEqualTo(
+ pubkey);
+ assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId2, uid)).isEqualTo(
+ pubkey);
+ }
+
+ @Test
+ public void setRecoveryServiceMetadataEntry_updatesColumnsSeparately() throws Exception {
+ int userId = 12;
+ int uid = 10009;
+ PublicKey pubkey1 = genRandomPublicKey();
+ PublicKey pubkey2 = genRandomPublicKey();
+ long serverParams = 123456L;
+
+ mRecoverableKeyStoreDb.setRecoveryServicePublicKey(userId, uid, pubkey1);
+ assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId, uid)).isEqualTo(
+ pubkey1);
+ assertThat(mRecoverableKeyStoreDb.getServerParameters(userId, uid)).isNull();
+
+ mRecoverableKeyStoreDb.setServerParameters(userId, uid, serverParams);
+ assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId, uid)).isEqualTo(
+ pubkey1);
+ assertThat(mRecoverableKeyStoreDb.getServerParameters(userId, uid)).isEqualTo(serverParams);
+
+ mRecoverableKeyStoreDb.setRecoveryServicePublicKey(userId, uid, pubkey2);
+ assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId, uid)).isEqualTo(
+ pubkey2);
+ assertThat(mRecoverableKeyStoreDb.getServerParameters(userId, uid)).isEqualTo(serverParams);
+ }
+
private static byte[] getUtf8Bytes(String s) {
return s.getBytes(StandardCharsets.UTF_8);
}
+
+ private static PublicKey genRandomPublicKey() throws Exception {
+ KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("EC");
+ keyPairGenerator.initialize(new ECGenParameterSpec("secp256r1"));
+ return keyPairGenerator.generateKeyPair().getPublic();
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverySnapshotStorageTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverySnapshotStorageTest.java
new file mode 100644
index 0000000..2759e39
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverySnapshotStorageTest.java
@@ -0,0 +1,53 @@
+package com.android.server.locksettings.recoverablekeystore.storage;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import android.security.recoverablekeystore.KeyStoreRecoveryData;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class RecoverySnapshotStorageTest {
+
+ private final RecoverySnapshotStorage mRecoverySnapshotStorage = new RecoverySnapshotStorage();
+
+ @Test
+ public void get_isNullForNonExistentSnapshot() {
+ assertNull(mRecoverySnapshotStorage.get(1000));
+ }
+
+ @Test
+ public void get_returnsSetSnapshot() {
+ int userId = 1000;
+ KeyStoreRecoveryData recoveryData = new KeyStoreRecoveryData(
+ /*snapshotVersion=*/ 1,
+ new ArrayList<>(),
+ new ArrayList<>(),
+ new byte[0]);
+ mRecoverySnapshotStorage.put(userId, recoveryData);
+
+ assertEquals(recoveryData, mRecoverySnapshotStorage.get(userId));
+ }
+
+ @Test
+ public void remove_removesSnapshots() {
+ int userId = 1000;
+ KeyStoreRecoveryData recoveryData = new KeyStoreRecoveryData(
+ /*snapshotVersion=*/ 1,
+ new ArrayList<>(),
+ new ArrayList<>(),
+ new byte[0]);
+ mRecoverySnapshotStorage.put(userId, recoveryData);
+
+ mRecoverySnapshotStorage.remove(userId);
+
+ assertNull(mRecoverySnapshotStorage.get(1000));
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/net/ConnOnActivityStartTest.java b/services/tests/servicestests/src/com/android/server/net/ConnOnActivityStartTest.java
index 0c35fcb..c7fa62e 100644
--- a/services/tests/servicestests/src/com/android/server/net/ConnOnActivityStartTest.java
+++ b/services/tests/servicestests/src/com/android/server/net/ConnOnActivityStartTest.java
@@ -38,6 +38,7 @@
import android.support.test.filters.LargeTest;
import android.support.test.runner.AndroidJUnit4;
import android.support.test.uiautomator.UiDevice;
+import android.text.TextUtils;
import android.util.Log;
import org.junit.AfterClass;
@@ -81,7 +82,7 @@
private static final long NETWORK_CHECK_TIMEOUT_MS = 4000; // 4 sec
- private static final long SCREEN_ON_DELAY_MS = 1000; // 1 sec
+ private static final long SCREEN_ON_DELAY_MS = 2000; // 2 sec
private static final long BIND_SERVICE_TIMEOUT_SEC = 4;
@@ -214,7 +215,6 @@
try{
turnBatteryOff();
setAppIdle(true);
- SystemClock.sleep(30000);
turnScreenOn();
startActivityAndCheckNetworkAccess();
} finally {
@@ -239,7 +239,6 @@
try {
Log.d(TAG, testName + " Start #" + i);
turnScreenOn();
- SystemClock.sleep(SCREEN_ON_DELAY_MS);
startActivityAndCheckNetworkAccess();
} finally {
finishActivity();
@@ -287,7 +286,7 @@
private void setAppIdle(boolean enabled) throws Exception {
executeCommand("am set-inactive " + TEST_PKG + " " + enabled);
assertDelayedCommandResult("am get-inactive " + TEST_PKG, "Idle=" + enabled,
- 10 /* maxTries */, 2000 /* napTimeMs */);
+ 15 /* maxTries */, 2000 /* napTimeMs */);
}
private void updateRestrictBackgroundBlacklist(boolean add) throws Exception {
@@ -345,6 +344,8 @@
private void turnScreenOn() throws Exception {
executeCommand("input keyevent KEYCODE_WAKEUP");
executeCommand("wm dismiss-keyguard");
+ // Wait for screen-on state to propagate through the system.
+ SystemClock.sleep(SCREEN_ON_DELAY_MS);
}
private String executeCommand(String cmd) throws IOException {
@@ -404,15 +405,38 @@
}
private static void dumpOnFailure() throws Exception {
- Log.d(TAG, ">>> Begin network_management dump");
- Log.printlns(Log.LOG_ID_MAIN, Log.DEBUG,
- TAG, executeSilentCommand("dumpsys network_management"), null);
- Log.d(TAG, "<<< End network_management dump");
+ dump("network_management");
+ dump("netpolicy");
+ dumpUsageStats();
+ }
- Log.d(TAG, ">>> Begin netpolicy dump");
- Log.printlns(Log.LOG_ID_MAIN, Log.DEBUG,
- TAG, executeSilentCommand("dumpsys netpolicy"), null);
- Log.d(TAG, "<<< End netpolicy dump");
+ private static void dumpUsageStats() throws Exception {
+ final String output = executeSilentCommand("dumpsys usagestats");
+ final StringBuilder sb = new StringBuilder();
+ final TextUtils.SimpleStringSplitter splitter = new TextUtils.SimpleStringSplitter('\n');
+ splitter.setString(output);
+ String str;
+ while (splitter.hasNext()) {
+ str = splitter.next();
+ if (str.contains("package=") && !str.contains(TEST_PKG)) {
+ continue;
+ }
+ if (str.trim().startsWith("config=") || str.trim().startsWith("time=")) {
+ continue;
+ }
+ sb.append(str).append('\n');
+ }
+ dump("usagestats", sb.toString());
+ }
+
+ private static void dump(String service) throws Exception {
+ dump(service, executeSilentCommand("dumpsys " + service));
+ }
+
+ private static void dump(String service, String dump) throws Exception {
+ Log.d(TAG, ">>> Begin dump " + service);
+ Log.printlns(Log.LOG_ID_MAIN, Log.DEBUG, TAG, dump, null);
+ Log.d(TAG, "<<< End dump " + service);
}
private void finishActivity() throws Exception {
diff --git a/services/tests/servicestests/src/com/android/server/net/watchlist/WatchlistSettingsTests.java b/services/tests/servicestests/src/com/android/server/net/watchlist/WatchlistSettingsTests.java
index f3cb980..212d25d 100644
--- a/services/tests/servicestests/src/com/android/server/net/watchlist/WatchlistSettingsTests.java
+++ b/services/tests/servicestests/src/com/android/server/net/watchlist/WatchlistSettingsTests.java
@@ -95,41 +95,6 @@
}
@Test
- public void testWatchlistSettings_writeSettingsToDisk() throws Exception {
- copyWatchlistSettingsXml(mContext, TEST_XML_1, mTestXmlFile);
- WatchlistSettings settings = new WatchlistSettings(mTestXmlFile);
- settings.writeSettingsToDisk(Arrays.asList(TEST_NEW_CC_DOMAIN_CRC32),
- Arrays.asList(TEST_NEW_CC_DOMAIN_SHA256), Arrays.asList(TEST_NEW_CC_IP_CRC32),
- Arrays.asList(TEST_NEW_CC_IP_SHA256));
- // Ensure old watchlist is not in memory
- assertFalse(settings.containsDomain(TEST_CC_DOMAIN));
- assertFalse(settings.containsIp(TEST_CC_IP));
- assertFalse(settings.containsDomain(TEST_NOT_EXIST_CC_DOMAIN));
- assertFalse(settings.containsIp(TEST_NOT_EXIST_CC_IP));
- assertFalse(settings.containsDomain(TEST_SHA256_ONLY_DOMAIN));
- assertFalse(settings.containsIp(TEST_SHA256_ONLY_IP));
- assertFalse(settings.containsDomain(TEST_CRC32_ONLY_DOMAIN));
- assertFalse(settings.containsIp(TEST_CRC32_ONLY_IP));
- // Ensure new watchlist is in memory
- assertTrue(settings.containsDomain(TEST_NEW_CC_DOMAIN));
- assertTrue(settings.containsIp(TEST_NEW_CC_IP));
- // Reload settings from disk and test again
- settings = new WatchlistSettings(mTestXmlFile);
- // Ensure old watchlist is not in memory
- assertFalse(settings.containsDomain(TEST_CC_DOMAIN));
- assertFalse(settings.containsIp(TEST_CC_IP));
- assertFalse(settings.containsDomain(TEST_NOT_EXIST_CC_DOMAIN));
- assertFalse(settings.containsIp(TEST_NOT_EXIST_CC_IP));
- assertFalse(settings.containsDomain(TEST_SHA256_ONLY_DOMAIN));
- assertFalse(settings.containsIp(TEST_SHA256_ONLY_IP));
- assertFalse(settings.containsDomain(TEST_CRC32_ONLY_DOMAIN));
- assertFalse(settings.containsIp(TEST_CRC32_ONLY_IP));
- // Ensure new watchlist is in memory
- assertTrue(settings.containsDomain(TEST_NEW_CC_DOMAIN));
- assertTrue(settings.containsIp(TEST_NEW_CC_IP));
- }
-
- @Test
public void testWatchlistSettings_writeSettingsToMemory() throws Exception {
copyWatchlistSettingsXml(mContext, TEST_XML_1, mTestXmlFile);
WatchlistSettings settings = new WatchlistSettings(mTestXmlFile);
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java
index e12a8da..cdac516 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java
@@ -17,75 +17,93 @@
package com.android.server.pm;
import android.content.IIntentReceiver;
-
import android.os.Bundle;
+import android.support.test.runner.AndroidJUnit4;
-import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
import java.io.File;
// runtest -c com.android.server.pm.PackageManagerServiceTest frameworks-services
-
-@SmallTest
-public class PackageManagerServiceTest extends AndroidTestCase {
- @Override
- protected void setUp() throws Exception {
- super.setUp();
+// bit FrameworksServicesTests:com.android.server.pm.PackageManagerServiceTest
+@RunWith(AndroidJUnit4.class)
+public class PackageManagerServiceTest {
+ @Before
+ public void setUp() throws Exception {
}
- @Override
- protected void tearDown() throws Exception {
- super.tearDown();
+ @After
+ public void tearDown() throws Exception {
}
+ @Test
public void testPackageRemoval() throws Exception {
- class PackageSenderImpl implements PackageSender {
- public void sendPackageBroadcast(final String action, final String pkg,
- final Bundle extras, final int flags, final String targetPkg,
- final IIntentReceiver finishedReceiver, final int[] userIds,
- int[] instantUserIds) {
+ class PackageSenderImpl implements PackageSender {
+ public void sendPackageBroadcast(final String action, final String pkg,
+ final Bundle extras, final int flags, final String targetPkg,
+ final IIntentReceiver finishedReceiver, final int[] userIds,
+ int[] instantUserIds) {
+ }
+
+ public void sendPackageAddedForNewUsers(String packageName,
+ boolean sendBootComplete, boolean includeStopped, int appId,
+ int[] userIds, int[] instantUserIds) {
+ }
+
+ @Override
+ public void notifyPackageAdded(String packageName) {
+ }
+
+ @Override
+ public void notifyPackageRemoved(String packageName) {
+ }
}
- public void sendPackageAddedForNewUsers(String packageName,
- boolean sendBootComplete, boolean includeStopped, int appId,
- int[] userIds, int[] instantUserIds) {
- }
- }
+ PackageSenderImpl sender = new PackageSenderImpl();
+ PackageSetting setting = null;
+ PackageManagerService.PackageRemovedInfo pri =
+ new PackageManagerService.PackageRemovedInfo(sender);
- PackageSenderImpl sender = new PackageSenderImpl();
- PackageSetting setting = null;
- PackageManagerService.PackageRemovedInfo pri =
- new PackageManagerService.PackageRemovedInfo(sender);
+ // Initial conditions: nothing there
+ Assert.assertNull(pri.removedUsers);
+ Assert.assertNull(pri.broadcastUsers);
- // Initial conditions: nothing there
- assertNull(pri.removedUsers);
- assertNull(pri.broadcastUsers);
+ // populateUsers with nothing leaves nothing
+ pri.populateUsers(null, setting);
+ Assert.assertNull(pri.broadcastUsers);
- // populateUsers with nothing leaves nothing
- pri.populateUsers(null, setting);
- assertNull(pri.broadcastUsers);
+ // Create a real (non-null) PackageSetting and confirm that the removed
+ // users are copied properly
+ setting = new PackageSetting("name", "realName", new File("codePath"),
+ new File("resourcePath"), "legacyNativeLibraryPathString",
+ "primaryCpuAbiString", "secondaryCpuAbiString",
+ "cpuAbiOverrideString", 0, 0, 0, "parentPackageName", null, 0,
+ null, null);
+ pri.populateUsers(new int[] {
+ 1, 2, 3, 4, 5
+ }, setting);
+ Assert.assertNotNull(pri.broadcastUsers);
+ Assert.assertEquals(5, pri.broadcastUsers.length);
+ Assert.assertNotNull(pri.instantUserIds);
+ Assert.assertEquals(0, pri.instantUserIds.length);
- // Create a real (non-null) PackageSetting and confirm that the removed
- // users are copied properly
- setting = new PackageSetting("name", "realName", new File("codePath"),
- new File("resourcePath"), "legacyNativeLibraryPathString",
- "primaryCpuAbiString", "secondaryCpuAbiString",
- "cpuAbiOverrideString", 0, 0, 0, "parentPackageName", null, 0,
- null, null);
- pri.populateUsers(new int[] {1, 2, 3, 4, 5}, setting);
- assertNotNull(pri.broadcastUsers);
- assertEquals(5, pri.broadcastUsers.length);
+ // Exclude a user
+ pri.broadcastUsers = null;
+ final int EXCLUDED_USER_ID = 4;
+ setting.setInstantApp(true, EXCLUDED_USER_ID);
+ pri.populateUsers(new int[] {
+ 1, 2, 3, EXCLUDED_USER_ID, 5
+ }, setting);
+ Assert.assertNotNull(pri.broadcastUsers);
+ Assert.assertEquals(4, pri.broadcastUsers.length);
+ Assert.assertNotNull(pri.instantUserIds);
+ Assert.assertEquals(1, pri.instantUserIds.length);
- // Exclude a user
- pri.broadcastUsers = null;
- final int EXCLUDED_USER_ID = 4;
- setting.setInstantApp(true, EXCLUDED_USER_ID);
- pri.populateUsers(new int[] {1, 2, 3, EXCLUDED_USER_ID, 5}, setting);
- assertNotNull(pri.broadcastUsers);
- assertEquals(5 - 1, pri.broadcastUsers.length);
-
- // TODO: test that sendApplicationHiddenForUser() actually fills in
- // broadcastUsers
+ // TODO: test that sendApplicationHiddenForUser() actually fills in
+ // broadcastUsers
}
}
diff --git a/services/tests/servicestests/src/com/android/server/testutils/TestHandler.java b/services/tests/servicestests/src/com/android/server/testutils/TestHandler.java
index 2d4bc0f..029d9f1 100644
--- a/services/tests/servicestests/src/com/android/server/testutils/TestHandler.java
+++ b/services/tests/servicestests/src/com/android/server/testutils/TestHandler.java
@@ -104,6 +104,15 @@
return new PriorityQueue<>(mMessages);
}
+ /**
+ * Optionally-overridable to allow deciphering message types
+ *
+ * @see android.util.DebugUtils#valueToString - a handy utility to use when overriding this
+ */
+ protected String messageToString(Message message) {
+ return message.toString();
+ }
+
private void dispatch(MsgInfo msg) {
int msgId = msg.message.what;
@@ -148,7 +157,7 @@
@Override
public String toString() {
return "MsgInfo{" +
- "message=" + message +
+ "message=" + messageToString(message) +
", sendTime=" + sendTime +
'}';
}
diff --git a/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java b/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java
index d9ab5c8..759894b 100644
--- a/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java
@@ -193,7 +193,7 @@
assertEquals(SCREEN_ORIENTATION_LANDSCAPE, mToken.getOrientation());
mToken.setFillsParent(true);
- mToken.hidden = true;
+ mToken.setHidden(true);
mToken.sendingToBottom = true;
// Can not specify orientation if app isn't visible even though it fills parent.
assertEquals(SCREEN_ORIENTATION_UNSET, mToken.getOrientation());
diff --git a/services/tests/servicestests/src/com/android/server/wm/BoundsAnimationControllerTests.java b/services/tests/servicestests/src/com/android/server/wm/BoundsAnimationControllerTests.java
index 53a5899..2bfe274 100644
--- a/services/tests/servicestests/src/com/android/server/wm/BoundsAnimationControllerTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/BoundsAnimationControllerTests.java
@@ -110,7 +110,7 @@
}
public void notifyTransitionStarting(int transit) {
- mListener.onAppTransitionStartingLocked(transit, null, null, null, null);
+ mListener.onAppTransitionStartingLocked(transit, null, null, 0, 0, 0);
}
public void notifyTransitionFinished() {
diff --git a/services/tests/servicestests/src/com/android/server/wm/SurfaceAnimationRunnerTest.java b/services/tests/servicestests/src/com/android/server/wm/SurfaceAnimationRunnerTest.java
index c309611..17fe642 100644
--- a/services/tests/servicestests/src/com/android/server/wm/SurfaceAnimationRunnerTest.java
+++ b/services/tests/servicestests/src/com/android/server/wm/SurfaceAnimationRunnerTest.java
@@ -181,7 +181,7 @@
final Animation a = new TranslateAnimation(-10, 10, 0, 0);
a.initialize(0, 0, 0, 0);
a.setDuration(50);
- return new WindowAnimationSpec(a, new Point(0, 0));
+ return new WindowAnimationSpec(a, new Point(0, 0), false /* canSkipFirstFrame */);
}
/**
diff --git a/services/tests/servicestests/src/com/android/server/wm/SurfaceAnimatorTest.java b/services/tests/servicestests/src/com/android/server/wm/SurfaceAnimatorTest.java
index 6f739ca..6e57f47 100644
--- a/services/tests/servicestests/src/com/android/server/wm/SurfaceAnimatorTest.java
+++ b/services/tests/servicestests/src/com/android/server/wm/SurfaceAnimatorTest.java
@@ -16,6 +16,7 @@
package com.android.server.wm;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
@@ -44,6 +45,7 @@
import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
+import java.util.concurrent.CountDownLatch;
/**
* Test class for {@link SurfaceAnimatorTest}.
@@ -61,12 +63,14 @@
private SurfaceSession mSession = new SurfaceSession();
private MyAnimatable mAnimatable;
+ private MyAnimatable mAnimatable2;
@Before
public void setUp() throws Exception {
super.setUp();
MockitoAnnotations.initMocks(this);
mAnimatable = new MyAnimatable();
+ mAnimatable2 = new MyAnimatable();
}
@Test
@@ -74,15 +78,13 @@
mAnimatable.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */);
final ArgumentCaptor<OnAnimationFinishedCallback> callbackCaptor = ArgumentCaptor.forClass(
OnAnimationFinishedCallback.class);
-
- assertTrue(mAnimatable.mSurfaceAnimator.isAnimating());
- assertNotNull(mAnimatable.mSurfaceAnimator.getAnimation());
+ assertAnimating(mAnimatable);
verify(mTransaction).reparent(eq(mAnimatable.mSurface), eq(mAnimatable.mLeash.getHandle()));
verify(mSpec).startAnimation(any(), any(), callbackCaptor.capture());
callbackCaptor.getValue().onAnimationFinished(mSpec);
- assertFalse(mAnimatable.mSurfaceAnimator.isAnimating());
- assertNull(mAnimatable.mSurfaceAnimator.getAnimation());
+ waitUntilPrepareSurfaces();
+ assertNotAnimating(mAnimatable);
assertTrue(mAnimatable.mFinishedCallbackCalled);
assertTrue(mAnimatable.mPendingDestroySurfaces.contains(mAnimatable.mLeash));
// TODO: Verify reparenting once we use mPendingTransaction to reparent it back
@@ -99,27 +101,28 @@
final ArgumentCaptor<OnAnimationFinishedCallback> callbackCaptor = ArgumentCaptor.forClass(
OnAnimationFinishedCallback.class);
- assertTrue(mAnimatable.mSurfaceAnimator.isAnimating());
- assertNotNull(mAnimatable.mSurfaceAnimator.getAnimation());
+ assertAnimating(mAnimatable);
verify(mSpec).startAnimation(any(), any(), callbackCaptor.capture());
// First animation was finished, but this shouldn't cancel the second animation
callbackCaptor.getValue().onAnimationFinished(mSpec);
+ waitUntilPrepareSurfaces();
assertTrue(mAnimatable.mSurfaceAnimator.isAnimating());
// Second animation was finished
verify(mSpec2).startAnimation(any(), any(), callbackCaptor.capture());
callbackCaptor.getValue().onAnimationFinished(mSpec2);
- assertFalse(mAnimatable.mSurfaceAnimator.isAnimating());
+ waitUntilPrepareSurfaces();
+ assertNotAnimating(mAnimatable);
assertTrue(mAnimatable.mFinishedCallbackCalled);
}
@Test
public void testCancelAnimation() throws Exception {
mAnimatable.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */);
- assertTrue(mAnimatable.mSurfaceAnimator.isAnimating());
+ assertAnimating(mAnimatable);
mAnimatable.mSurfaceAnimator.cancelAnimation();
- assertFalse(mAnimatable.mSurfaceAnimator.isAnimating());
+ assertNotAnimating(mAnimatable);
verify(mSpec).onAnimationCancelled(any());
assertTrue(mAnimatable.mFinishedCallbackCalled);
assertTrue(mAnimatable.mPendingDestroySurfaces.contains(mAnimatable.mLeash));
@@ -130,7 +133,7 @@
mAnimatable.mSurfaceAnimator.startDelayingAnimationStart();
mAnimatable.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */);
verifyZeroInteractions(mSpec);
- assertTrue(mAnimatable.mSurfaceAnimator.isAnimating());
+ assertAnimating(mAnimatable);
mAnimatable.mSurfaceAnimator.endDelayingAnimationStart();
verify(mSpec).startAnimation(any(), any(), any());
}
@@ -141,11 +144,50 @@
mAnimatable.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */);
mAnimatable.mSurfaceAnimator.cancelAnimation();
verifyZeroInteractions(mSpec);
- assertFalse(mAnimatable.mSurfaceAnimator.isAnimating());
+ assertNotAnimating(mAnimatable);
assertTrue(mAnimatable.mFinishedCallbackCalled);
assertTrue(mAnimatable.mPendingDestroySurfaces.contains(mAnimatable.mLeash));
}
+ @Test
+ public void testTransferAnimation() throws Exception {
+ mAnimatable.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */);
+
+ final ArgumentCaptor<OnAnimationFinishedCallback> callbackCaptor = ArgumentCaptor.forClass(
+ OnAnimationFinishedCallback.class);
+ verify(mSpec).startAnimation(any(), any(), callbackCaptor.capture());
+ final SurfaceControl leash = mAnimatable.mLeash;
+
+ mAnimatable2.mSurfaceAnimator.transferAnimation(mAnimatable.mSurfaceAnimator);
+ assertNotAnimating(mAnimatable);
+ assertAnimating(mAnimatable2);
+ assertEquals(leash, mAnimatable2.mSurfaceAnimator.mLeash);
+ assertFalse(mAnimatable.mPendingDestroySurfaces.contains(leash));
+ callbackCaptor.getValue().onAnimationFinished(mSpec);
+ waitUntilPrepareSurfaces();
+ assertNotAnimating(mAnimatable2);
+ assertTrue(mAnimatable2.mFinishedCallbackCalled);
+ assertTrue(mAnimatable2.mPendingDestroySurfaces.contains(leash));
+ }
+
+ private void assertAnimating(MyAnimatable animatable) {
+ assertTrue(animatable.mSurfaceAnimator.isAnimating());
+ assertNotNull(animatable.mSurfaceAnimator.getAnimation());
+ }
+
+ private void assertNotAnimating(MyAnimatable animatable) {
+ assertFalse(animatable.mSurfaceAnimator.isAnimating());
+ assertNull(animatable.mSurfaceAnimator.getAnimation());
+ }
+
+ private void waitUntilPrepareSurfaces() throws Exception {
+ final CountDownLatch latch = new CountDownLatch(1);
+ synchronized (sWm.mWindowMap) {
+ sWm.mAnimator.addAfterPrepareSurfacesRunnable(latch::countDown);
+ }
+ latch.await();
+ }
+
private class MyAnimatable implements Animatable {
final SurfaceControl mParent;
@@ -204,6 +246,11 @@
}
@Override
+ public SurfaceControl getAnimationLeashParent() {
+ return mParent;
+ }
+
+ @Override
public SurfaceControl getSurfaceControl() {
return mSurface;
}
@@ -223,8 +270,6 @@
return 1;
}
- private final Runnable mFinishedCallback = () -> {
- mFinishedCallbackCalled = true;
- };
+ private final Runnable mFinishedCallback = () -> mFinishedCallbackCalled = true;
}
}
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/servicestests/src/com/android/server/wm/WindowContainerTests.java
index 307deb4..196b4a9 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowContainerTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowContainerTests.java
@@ -564,6 +564,86 @@
assertEquals(1, child2223.compareTo(child21));
}
+ @Test
+ public void testPrefixOrderIndex() throws Exception {
+ final TestWindowContainerBuilder builder = new TestWindowContainerBuilder();
+ final TestWindowContainer root = builder.build();
+
+ final TestWindowContainer child1 = root.addChildWindow();
+
+ final TestWindowContainer child11 = child1.addChildWindow();
+ final TestWindowContainer child12 = child1.addChildWindow();
+
+ final TestWindowContainer child2 = root.addChildWindow();
+
+ final TestWindowContainer child21 = child2.addChildWindow();
+ final TestWindowContainer child22 = child2.addChildWindow();
+
+ final TestWindowContainer child221 = child22.addChildWindow();
+ final TestWindowContainer child222 = child22.addChildWindow();
+ final TestWindowContainer child223 = child22.addChildWindow();
+
+ final TestWindowContainer child23 = child2.addChildWindow();
+
+ assertEquals(0, root.getPrefixOrderIndex());
+ assertEquals(1, child1.getPrefixOrderIndex());
+ assertEquals(2, child11.getPrefixOrderIndex());
+ assertEquals(3, child12.getPrefixOrderIndex());
+ assertEquals(4, child2.getPrefixOrderIndex());
+ assertEquals(5, child21.getPrefixOrderIndex());
+ assertEquals(6, child22.getPrefixOrderIndex());
+ assertEquals(7, child221.getPrefixOrderIndex());
+ assertEquals(8, child222.getPrefixOrderIndex());
+ assertEquals(9, child223.getPrefixOrderIndex());
+ assertEquals(10, child23.getPrefixOrderIndex());
+ }
+
+ @Test
+ public void testPrefixOrder_addEntireSubtree() throws Exception {
+ final TestWindowContainerBuilder builder = new TestWindowContainerBuilder();
+ final TestWindowContainer root = builder.build();
+ final TestWindowContainer subtree = builder.build();
+ final TestWindowContainer subtree2 = builder.build();
+
+ final TestWindowContainer child1 = subtree.addChildWindow();
+ final TestWindowContainer child11 = child1.addChildWindow();
+ final TestWindowContainer child2 = subtree2.addChildWindow();
+ final TestWindowContainer child3 = subtree2.addChildWindow();
+ subtree.addChild(subtree2, 1);
+ root.addChild(subtree, 0);
+
+ assertEquals(0, root.getPrefixOrderIndex());
+ assertEquals(1, subtree.getPrefixOrderIndex());
+ assertEquals(2, child1.getPrefixOrderIndex());
+ assertEquals(3, child11.getPrefixOrderIndex());
+ assertEquals(4, subtree2.getPrefixOrderIndex());
+ assertEquals(5, child2.getPrefixOrderIndex());
+ assertEquals(6, child3.getPrefixOrderIndex());
+ }
+
+ @Test
+ public void testPrefixOrder_remove() throws Exception {
+ final TestWindowContainerBuilder builder = new TestWindowContainerBuilder();
+ final TestWindowContainer root = builder.build();
+
+ final TestWindowContainer child1 = root.addChildWindow();
+
+ final TestWindowContainer child11 = child1.addChildWindow();
+ final TestWindowContainer child12 = child1.addChildWindow();
+
+ final TestWindowContainer child2 = root.addChildWindow();
+
+ assertEquals(0, root.getPrefixOrderIndex());
+ assertEquals(1, child1.getPrefixOrderIndex());
+ assertEquals(2, child11.getPrefixOrderIndex());
+ assertEquals(3, child12.getPrefixOrderIndex());
+ assertEquals(4, child2.getPrefixOrderIndex());
+
+ root.removeChild(child1);
+
+ assertEquals(1, child2.getPrefixOrderIndex());
+ }
+
/* Used so we can gain access to some protected members of the {@link WindowContainer} class */
private class TestWindowContainer extends WindowContainer<TestWindowContainer> {
private final int mLayer;
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowTestUtils.java b/services/tests/servicestests/src/com/android/server/wm/WindowTestUtils.java
index 5f58744..012fc23 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowTestUtils.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowTestUtils.java
@@ -160,7 +160,6 @@
/* Used so we can gain access to some protected members of the {@link WindowToken} class */
public static class TestWindowToken extends WindowToken {
- int adj = 0;
TestWindowToken(int type, DisplayContent dc) {
this(type, dc, false /* persistOnEmpty */);
@@ -178,11 +177,6 @@
boolean hasWindow(WindowState w) {
return mChildren.contains(w);
}
-
- @Override
- int getAnimLayerAdjustment() {
- return adj;
- }
}
/* Used so we can gain access to some protected members of the {@link Task} class */
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
index c699a94..ff840f3 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
@@ -103,6 +103,7 @@
context.getDisplay().getDisplayInfo(mDisplayInfo);
mDisplayContent = createNewDisplay();
+ sWm.mAnimator.mInitialized = true;
sWm.mDisplayEnabled = true;
sWm.mDisplayReady = true;
diff --git a/telephony/java/android/telephony/Telephony.java b/telephony/java/android/provider/Telephony.java
similarity index 100%
rename from telephony/java/android/telephony/Telephony.java
rename to telephony/java/android/provider/Telephony.java
diff --git a/telephony/java/android/telephony/CellIdentityCdma.aidl b/telephony/java/android/telephony/CellIdentityCdma.aidl
new file mode 100644
index 0000000..b31ad0b
--- /dev/null
+++ b/telephony/java/android/telephony/CellIdentityCdma.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/** @hide */
+package android.telephony;
+
+parcelable CellIdentityCdma;
diff --git a/telephony/java/android/telephony/CellIdentityGsm.aidl b/telephony/java/android/telephony/CellIdentityGsm.aidl
new file mode 100644
index 0000000..bcc0751
--- /dev/null
+++ b/telephony/java/android/telephony/CellIdentityGsm.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/** @hide */
+package android.telephony;
+
+parcelable CellIdentityGsm;
diff --git a/telephony/java/android/telephony/CellIdentityLte.aidl b/telephony/java/android/telephony/CellIdentityLte.aidl
new file mode 100644
index 0000000..940d170
--- /dev/null
+++ b/telephony/java/android/telephony/CellIdentityLte.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/** @hide */
+package android.telephony;
+
+parcelable CellIdentityLte;
diff --git a/telephony/java/android/telephony/CellIdentityWcdma.aidl b/telephony/java/android/telephony/CellIdentityWcdma.aidl
new file mode 100644
index 0000000..462ce2c
--- /dev/null
+++ b/telephony/java/android/telephony/CellIdentityWcdma.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/** @hide */
+package android.telephony;
+
+parcelable CellIdentityWcdma;
diff --git a/telephony/java/android/telephony/euicc/EuiccManager.java b/telephony/java/android/telephony/euicc/EuiccManager.java
index 973df31..176057d 100644
--- a/telephony/java/android/telephony/euicc/EuiccManager.java
+++ b/telephony/java/android/telephony/euicc/EuiccManager.java
@@ -15,8 +15,10 @@
*/
package android.telephony.euicc;
+import android.annotation.IntDef;
import android.annotation.Nullable;
import android.annotation.SdkConstant;
+import android.annotation.SystemApi;
import android.app.Activity;
import android.app.PendingIntent;
import android.content.Context;
@@ -29,6 +31,9 @@
import com.android.internal.telephony.euicc.IEuiccController;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/**
* EuiccManager is the application interface to eUICCs, or eSIMs/embedded SIMs.
*
@@ -167,6 +172,35 @@
*/
public static final String META_DATA_CARRIER_ICON = "android.telephony.euicc.carriericon";
+ /**
+ * Euicc OTA update status which can be got by {@link #getOtaStatus}
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = {"EUICC_OTA_"}, value = {
+ EUICC_OTA_IN_PROGRESS,
+ EUICC_OTA_FAILED,
+ EUICC_OTA_SUCCEEDED,
+ EUICC_OTA_NOT_NEEDED,
+ EUICC_OTA_STATUS_UNAVAILABLE
+
+ })
+ public @interface OtaStatus{}
+
+ /**
+ * An OTA is in progress. During this time, the eUICC is not available and the user may lose
+ * network access.
+ */
+ public static final int EUICC_OTA_IN_PROGRESS = 1;
+ /** The OTA update failed. */
+ public static final int EUICC_OTA_FAILED = 2;
+ /** The OTA update finished successfully. */
+ public static final int EUICC_OTA_SUCCEEDED = 3;
+ /** The OTA update not needed since current eUICC OS is latest. */
+ public static final int EUICC_OTA_NOT_NEEDED = 4;
+ /** The OTA status is unavailable since eUICC service is unavailable. */
+ public static final int EUICC_OTA_STATUS_UNAVAILABLE = 5;
+
private final Context mContext;
/** @hide */
@@ -211,6 +245,26 @@
}
/**
+ * Returns the current status of eUICC OTA.
+ *
+ * <p>Requires the {@link android.Manifest.permission#WRITE_EMBEDDED_SUBSCRIPTIONS} permission.
+ *
+ * @return the status of eUICC OTA. If {@link #isEnabled()} is false or the eUICC is not ready,
+ * {@link OtaStatus#EUICC_OTA_STATUS_UNAVAILABLE} will be returned.
+ */
+ @SystemApi
+ public int getOtaStatus() {
+ if (!isEnabled()) {
+ return EUICC_OTA_STATUS_UNAVAILABLE;
+ }
+ try {
+ return getIEuiccController().getOtaStatus();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Attempt to download the given {@link DownloadableSubscription}.
*
* <p>Requires the {@link android.Manifest.permission#WRITE_EMBEDDED_SUBSCRIPTIONS} permission,
diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java
index e2d25b8..f804cb0 100644
--- a/telephony/java/com/android/internal/telephony/RILConstants.java
+++ b/telephony/java/com/android/internal/telephony/RILConstants.java
@@ -417,6 +417,8 @@
int RIL_REQUEST_SET_CARRIER_INFO_IMSI_ENCRYPTION = 141;
int RIL_REQUEST_START_NETWORK_SCAN = 142;
int RIL_REQUEST_STOP_NETWORK_SCAN = 143;
+ int RIL_REQUEST_GET_SLOT_STATUS = 144;
+ int RIL_REQUEST_SET_LOGICAL_TO_PHYSICAL_SLOT_MAPPING = 145;
int RIL_RESPONSE_ACKNOWLEDGEMENT = 800;
@@ -471,4 +473,5 @@
int RIL_UNSOL_MODEM_RESTART = 1047;
int RIL_UNSOL_CARRIER_INFO_IMSI_ENCRYPTION = 1048;
int RIL_UNSOL_NETWORK_SCAN_RESULT = 1049;
+ int RIL_UNSOL_ICC_SLOT_STATUS = 1050;
}
diff --git a/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl b/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl
index b3fc90d..0a0ad90 100644
--- a/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl
+++ b/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl
@@ -30,6 +30,7 @@
oneway void getDefaultDownloadableSubscriptionList(
String callingPackage, in PendingIntent callbackIntent);
String getEid();
+ int getOtaStatus();
oneway void downloadSubscription(in DownloadableSubscription subscription,
boolean switchAfterDownload, String callingPackage, in PendingIntent callbackIntent);
EuiccInfo getEuiccInfo();
diff --git a/tests/DexLoggerIntegrationTests/Android.mk b/tests/DexLoggerIntegrationTests/Android.mk
new file mode 100644
index 0000000..7187a37
--- /dev/null
+++ b/tests/DexLoggerIntegrationTests/Android.mk
@@ -0,0 +1,49 @@
+#
+# Copyright 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+LOCAL_PATH:= $(call my-dir)
+
+# Build a tiny library that the test app can dynamically load
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+LOCAL_MODULE := DexLoggerTestLibrary
+LOCAL_SRC_FILES := $(call all-java-files-under, src/com/android/dcl)
+
+include $(BUILD_JAVA_LIBRARY)
+
+dexloggertest_jar := $(LOCAL_BUILT_MODULE)
+
+
+# Build the test app itself
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+LOCAL_PACKAGE_NAME := DexLoggerIntegrationTests
+LOCAL_COMPATIBILITY_SUITE := device-tests
+LOCAL_CERTIFICATE := platform
+LOCAL_SRC_FILES := $(call all-java-files-under, src/com/android/server/pm)
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ android-support-test \
+ truth-prebuilt \
+
+# This gets us the javalib.jar built by DexLoggerTestLibrary above.
+LOCAL_JAVA_RESOURCE_FILES := $(dexloggertest_jar)
+
+include $(BUILD_PACKAGE)
diff --git a/tests/DexLoggerIntegrationTests/AndroidManifest.xml b/tests/DexLoggerIntegrationTests/AndroidManifest.xml
new file mode 100644
index 0000000..a847e8f
--- /dev/null
+++ b/tests/DexLoggerIntegrationTests/AndroidManifest.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.frameworks.dexloggertest">
+
+ <!-- Tests feature introduced in P (27) -->
+ <uses-sdk
+ android:minSdkVersion="27"
+ android:targetSdkVersion="27" />
+
+ <uses-permission android:name="android.permission.READ_LOGS" />
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation
+ android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.frameworks.dexloggertest"
+ android:label="Integration test for DexLogger" />
+</manifest>
diff --git a/tests/DexLoggerIntegrationTests/AndroidTest.xml b/tests/DexLoggerIntegrationTests/AndroidTest.xml
new file mode 100644
index 0000000..8ed19f8
--- /dev/null
+++ b/tests/DexLoggerIntegrationTests/AndroidTest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration description="Runs DexLogger Integration Tests">
+ <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+ <option name="test-file-name" value="DexLoggerIntegrationTests.apk"/>
+ <option name="cleanup-apks" value="true"/>
+ </target_preparer>
+
+ <option name="test-suite-tag" value="apct"/>
+ <option name="test-tag" value="DexLoggerIntegrationTests"/>
+
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+ <option name="package" value="com.android.frameworks.dexloggertest"/>
+ <option name="runner" value="android.support.test.runner.AndroidJUnitRunner"/>
+ </test>
+</configuration>
diff --git a/tests/DexLoggerIntegrationTests/src/com/android/dcl/Simple.java b/tests/DexLoggerIntegrationTests/src/com/android/dcl/Simple.java
new file mode 100644
index 0000000..e995a26
--- /dev/null
+++ b/tests/DexLoggerIntegrationTests/src/com/android/dcl/Simple.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dcl;
+
+/** Dummy class which is built into a jar purely so we can pass it to DexClassLoader. */
+public final class Simple {
+ public Simple() {}
+}
diff --git a/tests/DexLoggerIntegrationTests/src/com/android/server/pm/DexLoggerIntegrationTests.java b/tests/DexLoggerIntegrationTests/src/com/android/server/pm/DexLoggerIntegrationTests.java
new file mode 100644
index 0000000..d9f34d5
--- /dev/null
+++ b/tests/DexLoggerIntegrationTests/src/com/android/server/pm/DexLoggerIntegrationTests.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.support.test.InstrumentationRegistry;
+import android.util.EventLog;
+
+import dalvik.system.DexClassLoader;
+
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.MessageDigest;
+import java.util.ArrayList;
+import java.util.Formatter;
+import java.util.List;
+
+/**
+ * Integration tests for {@link com.android.server.pm.dex.DexLogger}.
+ *
+ * The setup for the test dynamically loads code in a jar extracted
+ * from our assets (a secondary dex file).
+ *
+ * We then use adb to trigger secondary dex file reconcilation (and
+ * wait for it to complete). As a side-effect of this DexLogger should
+ * be notified of the file and should log the hash of the file's name
+ * and content. We verify that this message appears in the event log.
+ *
+ * Run with "atest DexLoggerIntegrationTests".
+ */
+@RunWith(JUnit4.class)
+public final class DexLoggerIntegrationTests {
+
+ private static final String TAG = DexLoggerIntegrationTests.class.getSimpleName();
+
+ private static final String PACKAGE_NAME = "com.android.frameworks.dexloggertest";
+
+ private static final int SNET_TAG = 0x534e4554;
+ private static final String DCL_SUBTAG = "dcl";
+
+ // Obtained via "echo -n copied.jar | sha256sum"
+ private static final String EXPECTED_NAME_HASH =
+ "1B6C71DB26F36582867432CCA12FB6A517470C9F9AABE9198DD4C5C030D6DC0C";
+
+ private static String expectedContentHash;
+
+ @BeforeClass
+ public static void setUpAll() throws Exception {
+ Context context = InstrumentationRegistry.getTargetContext();
+ MessageDigest hasher = MessageDigest.getInstance("SHA-256");
+
+ // Copy the jar from our Java resources to a private data directory
+ File privateCopy = new File(context.getDir("jars", Context.MODE_PRIVATE), "copied.jar");
+ try (InputStream input = DexLoggerIntegrationTests.class.getResourceAsStream("/javalib.jar");
+ OutputStream output = new FileOutputStream(privateCopy)) {
+ byte[] buffer = new byte[1024];
+ while (true) {
+ int numRead = input.read(buffer);
+ if (numRead < 0) {
+ break;
+ }
+ output.write(buffer, 0, numRead);
+ hasher.update(buffer, 0, numRead);
+ }
+ }
+
+ // Remember the SHA-256 of the file content to check that it is the same as
+ // the value we see logged.
+ Formatter formatter = new Formatter();
+ for (byte b : hasher.digest()) {
+ formatter.format("%02X", b);
+ }
+ expectedContentHash = formatter.toString();
+
+ // Feed the jar to a class loader and make sure it contains what we expect.
+ ClassLoader loader =
+ new DexClassLoader(
+ privateCopy.toString(), null, null, context.getClass().getClassLoader());
+ loader.loadClass("com.android.dcl.Simple");
+ }
+
+ @Test
+ public void testDexLoggerReconcileGeneratesEvents() throws Exception {
+ int[] tagList = new int[] { SNET_TAG };
+ List<EventLog.Event> events = new ArrayList<>();
+
+ // There may already be events in the event log - figure out the most recent one
+ EventLog.readEvents(tagList, events);
+ long previousEventNanos = events.isEmpty() ? 0 : events.get(events.size() - 1).getTimeNanos();
+ events.clear();
+
+ Process process = Runtime.getRuntime().exec(
+ "cmd package reconcile-secondary-dex-files " + PACKAGE_NAME);
+ int exitCode = process.waitFor();
+ assertThat(exitCode).isEqualTo(0);
+
+ int myUid = android.os.Process.myUid();
+ String expectedMessage = EXPECTED_NAME_HASH + " " + expectedContentHash;
+
+ EventLog.readEvents(tagList, events);
+ boolean found = false;
+ for (EventLog.Event event : events) {
+ if (event.getTimeNanos() <= previousEventNanos) {
+ continue;
+ }
+ Object[] data = (Object[]) event.getData();
+
+ // We only care about DCL events that we generated.
+ String subTag = (String) data[0];
+ if (!DCL_SUBTAG.equals(subTag)) {
+ continue;
+ }
+ int uid = (int) data[1];
+ if (uid != myUid) {
+ continue;
+ }
+
+ String message = (String) data[2];
+ assertThat(message).isEqualTo(expectedMessage);
+ found = true;
+ }
+
+ assertThat(found).isTrue();
+ }
+}
diff --git a/tests/JankBench/Android.mk b/tests/JankBench/Android.mk
new file mode 100644
index 0000000..12568a09
--- /dev/null
+++ b/tests/JankBench/Android.mk
@@ -0,0 +1,38 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_MANIFEST_FILE := app/src/main/AndroidManifest.xml
+
+LOCAL_SDK_VERSION := current
+
+LOCAL_USE_AAPT2 := true
+
+# omit gradle 'build' dir
+LOCAL_SRC_FILES := $(call all-java-files-under,app/src/main/java)
+
+# use appcompat/support lib from the tree, so improvements/
+# regressions are reflected in test data
+LOCAL_RESOURCE_DIR := \
+ $(LOCAL_PATH)/app/src/main/res \
+
+
+LOCAL_STATIC_ANDROID_LIBRARIES := \
+ android-support-design \
+ android-support-v4 \
+ android-support-v7-appcompat \
+ android-support-v7-cardview \
+ android-support-v7-recyclerview \
+ android-support-v17-leanback \
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ apache-commons-math \
+ junit
+
+
+LOCAL_PACKAGE_NAME := JankBench
+
+LOCAL_COMPATIBILITY_SUITE := device-tests
+
+include $(BUILD_PACKAGE)
diff --git a/tests/JankBench/app/src/androidTest/java/com/android/benchmark/ApplicationTest.java b/tests/JankBench/app/src/androidTest/java/com/android/benchmark/ApplicationTest.java
new file mode 100644
index 0000000..79aff90
--- /dev/null
+++ b/tests/JankBench/app/src/androidTest/java/com/android/benchmark/ApplicationTest.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the License);
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an AS IS BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and limitations under the
+ * License.
+ *
+ */
+
+package com.android.benchmark;
+
+import android.app.Application;
+import android.test.ApplicationTestCase;
+
+/**
+ * <a href="http://d.android.com/tools/testing/testing_android.html">Testing Fundamentals</a>
+ */
+public class ApplicationTest extends ApplicationTestCase<Application> {
+ public ApplicationTest() {
+ super(Application.class);
+ }
+}
diff --git a/tests/JankBench/app/src/main/AndroidManifest.xml b/tests/JankBench/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..58aa66f
--- /dev/null
+++ b/tests/JankBench/app/src/main/AndroidManifest.xml
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and limitations under the
+ ~ License.
+ ~
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.benchmark">
+
+ <uses-sdk android:minSdkVersion="24" />
+
+ <android:uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+ <android:uses-permission android:name="android.permission.READ_PHONE_STATE" />
+ <android:uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+
+ <application
+ android:allowBackup="true"
+ android:icon="@mipmap/ic_launcher"
+ android:label="@string/app_name"
+ android:supportsRtl="true"
+ android:theme="@style/AppTheme">
+ <activity
+ android:name=".app.HomeActivity"
+ android:label="@string/app_name"
+ android:theme="@style/AppTheme.NoActionBar">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ <activity
+ android:name=".app.RunLocalBenchmarksActivity"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="com.android.benchmark.ACTION_BENCHMARK" />
+ </intent-filter>
+
+ <meta-data
+ android:name="com.android.benchmark.benchmark_group"
+ android:resource="@xml/benchmark" />
+ </activity>
+ <activity android:name=".ui.ListViewScrollActivity" />
+ <activity android:name=".ui.ImageListViewScrollActivity" />
+ <activity android:name=".ui.ShadowGridActivity" />
+ <activity android:name=".ui.TextScrollActivity" />
+ <activity android:name=".ui.EditTextInputActivity" />
+ <activity android:name=".synthetic.MemoryActivity" />
+ <activity android:name=".ui.FullScreenOverdrawActivity"></activity>
+ <activity android:name=".ui.BitmapUploadActivity"></activity>
+ </application>
+
+</manifest>
\ No newline at end of file
diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/app/BenchmarkDashboardFragment.java b/tests/JankBench/app/src/main/java/com/android/benchmark/app/BenchmarkDashboardFragment.java
new file mode 100644
index 0000000..b0a97ae0
--- /dev/null
+++ b/tests/JankBench/app/src/main/java/com/android/benchmark/app/BenchmarkDashboardFragment.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and limitations under the
+ * License.
+ *
+ */
+
+package com.android.benchmark.app;
+
+import android.support.v4.app.Fragment;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.benchmark.R;
+
+/**
+ * Fragment for the Benchmark dashboard
+ */
+public class BenchmarkDashboardFragment extends Fragment {
+
+ public BenchmarkDashboardFragment() {
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ return inflater.inflate(R.layout.fragment_dashboard, container, false);
+ }
+}
diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/app/BenchmarkListAdapter.java b/tests/JankBench/app/src/main/java/com/android/benchmark/app/BenchmarkListAdapter.java
new file mode 100644
index 0000000..7419b30
--- /dev/null
+++ b/tests/JankBench/app/src/main/java/com/android/benchmark/app/BenchmarkListAdapter.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and limitations under the
+ * License.
+ *
+ */
+
+package com.android.benchmark.app;
+
+import android.graphics.Typeface;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseExpandableListAdapter;
+import android.widget.CheckBox;
+import android.widget.TextView;
+
+import com.android.benchmark.registry.BenchmarkGroup;
+import com.android.benchmark.registry.BenchmarkRegistry;
+import com.android.benchmark.R;
+
+/**
+ *
+ */
+public class BenchmarkListAdapter extends BaseExpandableListAdapter {
+
+ private final LayoutInflater mInflater;
+ private final BenchmarkRegistry mRegistry;
+
+ BenchmarkListAdapter(LayoutInflater inflater,
+ BenchmarkRegistry registry) {
+ mInflater = inflater;
+ mRegistry = registry;
+ }
+
+ @Override
+ public int getGroupCount() {
+ return mRegistry.getGroupCount();
+ }
+
+ @Override
+ public int getChildrenCount(int groupPosition) {
+ return mRegistry.getBenchmarkCount(groupPosition);
+ }
+
+ @Override
+ public Object getGroup(int groupPosition) {
+ return mRegistry.getBenchmarkGroup(groupPosition);
+ }
+
+ @Override
+ public Object getChild(int groupPosition, int childPosition) {
+ BenchmarkGroup benchmarkGroup = mRegistry.getBenchmarkGroup(groupPosition);
+
+ if (benchmarkGroup != null) {
+ return benchmarkGroup.getBenchmarks()[childPosition];
+ }
+
+ return null;
+ }
+
+ @Override
+ public long getGroupId(int groupPosition) {
+ return groupPosition;
+ }
+
+ @Override
+ public long getChildId(int groupPosition, int childPosition) {
+ return childPosition;
+ }
+
+ @Override
+ public boolean hasStableIds() {
+ return false;
+ }
+
+ @Override
+ public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) {
+ BenchmarkGroup group = (BenchmarkGroup) getGroup(groupPosition);
+ if (convertView == null) {
+ convertView = mInflater.inflate(R.layout.benchmark_list_group_row, null);
+ }
+
+ TextView title = (TextView) convertView.findViewById(R.id.group_name);
+ title.setTypeface(null, Typeface.BOLD);
+ title.setText(group.getTitle());
+ return convertView;
+ }
+
+ @Override
+ public View getChildView(int groupPosition, int childPosition, boolean isLastChild,
+ View convertView, ViewGroup parent) {
+ BenchmarkGroup.Benchmark benchmark =
+ (BenchmarkGroup.Benchmark) getChild(groupPosition, childPosition);
+ if (convertView == null) {
+ convertView = mInflater.inflate(R.layout.benchmark_list_item, null);
+ }
+
+ TextView name = (TextView) convertView.findViewById(R.id.benchmark_name);
+ name.setText(benchmark.getName());
+ CheckBox enabledBox = (CheckBox) convertView.findViewById(R.id.benchmark_enable_checkbox);
+ enabledBox.setOnClickListener(benchmark);
+ enabledBox.setChecked(benchmark.isEnabled());
+
+ return convertView;
+ }
+
+ @Override
+ public boolean isChildSelectable(int groupPosition, int childPosition) {
+ return true;
+ }
+
+ public int getChildrenHeight() {
+ // TODO
+ return 1024;
+ }
+}
diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/app/HomeActivity.java b/tests/JankBench/app/src/main/java/com/android/benchmark/app/HomeActivity.java
new file mode 100644
index 0000000..79bafd6
--- /dev/null
+++ b/tests/JankBench/app/src/main/java/com/android/benchmark/app/HomeActivity.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and limitations under the
+ * License.
+ *
+ */
+
+package com.android.benchmark.app;
+
+import android.content.Intent;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.support.design.widget.FloatingActionButton;
+import android.support.v7.app.AppCompatActivity;
+import android.support.v7.widget.Toolbar;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.ExpandableListView;
+import android.widget.Toast;
+
+import com.android.benchmark.registry.BenchmarkRegistry;
+import com.android.benchmark.R;
+import com.android.benchmark.results.GlobalResultsStore;
+
+import java.io.IOException;
+import java.util.LinkedList;
+import java.util.Queue;
+
+public class HomeActivity extends AppCompatActivity implements Button.OnClickListener {
+
+ private FloatingActionButton mStartButton;
+ private BenchmarkRegistry mRegistry;
+ private Queue<Intent> mRunnableBenchmarks;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_home);
+
+ Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
+ setSupportActionBar(toolbar);
+
+ mStartButton = (FloatingActionButton) findViewById(R.id.start_button);
+ mStartButton.setActivated(true);
+ mStartButton.setOnClickListener(this);
+
+ mRegistry = new BenchmarkRegistry(this);
+
+ mRunnableBenchmarks = new LinkedList<>();
+
+ ExpandableListView listView = (ExpandableListView) findViewById(R.id.test_list);
+ BenchmarkListAdapter adapter =
+ new BenchmarkListAdapter(LayoutInflater.from(this), mRegistry);
+ listView.setAdapter(adapter);
+
+ adapter.notifyDataSetChanged();
+ ViewGroup.LayoutParams layoutParams = listView.getLayoutParams();
+ layoutParams.height = 2048;
+ listView.setLayoutParams(layoutParams);
+ listView.requestLayout();
+ System.out.println(System.getProperties().stringPropertyNames());
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ // Inflate the menu; this adds items to the action bar if it is present.
+ getMenuInflater().inflate(R.menu.menu_main, menu);
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ // Handle action bar item clicks here. The action bar will
+ // automatically handle clicks on the Home/Up button, so long
+ // as you specify a parent activity in AndroidManifest.xml.
+ int id = item.getItemId();
+
+ //noinspection SimplifiableIfStatement
+ if (id == R.id.action_settings) {
+ new AsyncTask<Void, Void, Void>() {
+ @Override
+ protected Void doInBackground(Void... voids) {
+ try {
+ HomeActivity.this.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ Toast.makeText(HomeActivity.this, "Exporting...", Toast.LENGTH_LONG).show();
+ }
+ });
+ GlobalResultsStore.getInstance(HomeActivity.this).exportToCsv();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(Void aVoid) {
+ HomeActivity.this.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ Toast.makeText(HomeActivity.this, "Done", Toast.LENGTH_LONG).show();
+ }
+ });
+ }
+ }.execute();
+
+ return true;
+ }
+
+ return super.onOptionsItemSelected(item);
+ }
+
+ @Override
+ public void onClick(View v) {
+ final int groupCount = mRegistry.getGroupCount();
+ for (int i = 0; i < groupCount; i++) {
+
+ Intent intent = mRegistry.getBenchmarkGroup(i).getIntent();
+ if (intent != null) {
+ mRunnableBenchmarks.add(intent);
+ }
+ }
+
+ handleNextBenchmark();
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+
+ }
+
+ private void handleNextBenchmark() {
+ Intent nextIntent = mRunnableBenchmarks.peek();
+ startActivityForResult(nextIntent, 0);
+ }
+}
diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/app/PerfTimeline.java b/tests/JankBench/app/src/main/java/com/android/benchmark/app/PerfTimeline.java
new file mode 100644
index 0000000..1c82d6d
--- /dev/null
+++ b/tests/JankBench/app/src/main/java/com/android/benchmark/app/PerfTimeline.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.benchmark.app;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.*;
+import android.text.TextPaint;
+import android.util.AttributeSet;
+import android.view.View;
+
+import com.android.benchmark.R;
+
+
+/**
+ * TODO: document your custom view class.
+ */
+public class PerfTimeline extends View {
+ private String mExampleString; // TODO: use a default from R.string...
+ private int mExampleColor = Color.RED; // TODO: use a default from R.color...
+ private float mExampleDimension = 300; // TODO: use a default from R.dimen...
+
+ private TextPaint mTextPaint;
+ private float mTextWidth;
+ private float mTextHeight;
+
+ private Paint mPaintBaseLow;
+ private Paint mPaintBaseHigh;
+ private Paint mPaintValue;
+
+
+ public float[] mLinesLow;
+ public float[] mLinesHigh;
+ public float[] mLinesValue;
+
+ public PerfTimeline(Context context) {
+ super(context);
+ init(null, 0);
+ }
+
+ public PerfTimeline(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init(attrs, 0);
+ }
+
+ public PerfTimeline(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ init(attrs, defStyle);
+ }
+
+ private void init(AttributeSet attrs, int defStyle) {
+ // Load attributes
+ final TypedArray a = getContext().obtainStyledAttributes(
+ attrs, R.styleable.PerfTimeline, defStyle, 0);
+
+ mExampleString = "xx";//a.getString(R.styleable.PerfTimeline_exampleString, "xx");
+ mExampleColor = a.getColor(R.styleable.PerfTimeline_exampleColor, mExampleColor);
+ // Use getDimensionPixelSize or getDimensionPixelOffset when dealing with
+ // values that should fall on pixel boundaries.
+ mExampleDimension = a.getDimension(
+ R.styleable.PerfTimeline_exampleDimension,
+ mExampleDimension);
+
+ a.recycle();
+
+ // Set up a default TextPaint object
+ mTextPaint = new TextPaint();
+ mTextPaint.setFlags(Paint.ANTI_ALIAS_FLAG);
+ mTextPaint.setTextAlign(Paint.Align.LEFT);
+
+ // Update TextPaint and text measurements from attributes
+ invalidateTextPaintAndMeasurements();
+
+ mPaintBaseLow = new Paint(Paint.ANTI_ALIAS_FLAG);
+ mPaintBaseLow.setStyle(Paint.Style.FILL);
+ mPaintBaseLow.setColor(0xff000000);
+
+ mPaintBaseHigh = new Paint(Paint.ANTI_ALIAS_FLAG);
+ mPaintBaseHigh.setStyle(Paint.Style.FILL);
+ mPaintBaseHigh.setColor(0x7f7f7f7f);
+
+ mPaintValue = new Paint(Paint.ANTI_ALIAS_FLAG);
+ mPaintValue.setStyle(Paint.Style.FILL);
+ mPaintValue.setColor(0x7fff0000);
+
+ }
+
+ private void invalidateTextPaintAndMeasurements() {
+ mTextPaint.setTextSize(mExampleDimension);
+ mTextPaint.setColor(mExampleColor);
+ mTextWidth = mTextPaint.measureText(mExampleString);
+
+ Paint.FontMetrics fontMetrics = mTextPaint.getFontMetrics();
+ mTextHeight = fontMetrics.bottom;
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+
+ // TODO: consider storing these as member variables to reduce
+ // allocations per draw cycle.
+ int paddingLeft = getPaddingLeft();
+ int paddingTop = getPaddingTop();
+ int paddingRight = getPaddingRight();
+ int paddingBottom = getPaddingBottom();
+
+ int contentWidth = getWidth() - paddingLeft - paddingRight;
+ int contentHeight = getHeight() - paddingTop - paddingBottom;
+
+ // Draw the text.
+ //canvas.drawText(mExampleString,
+ // paddingLeft + (contentWidth - mTextWidth) / 2,
+ // paddingTop + (contentHeight + mTextHeight) / 2,
+ // mTextPaint);
+
+
+
+
+ // Draw the shadow
+ //RectF rf = new RectF(10.f, 10.f, 100.f, 100.f);
+ //canvas.drawOval(rf, mShadowPaint);
+
+ if (mLinesLow != null) {
+ canvas.drawLines(mLinesLow, mPaintBaseLow);
+ }
+ if (mLinesHigh != null) {
+ canvas.drawLines(mLinesHigh, mPaintBaseHigh);
+ }
+ if (mLinesValue != null) {
+ canvas.drawLines(mLinesValue, mPaintValue);
+ }
+
+
+/*
+ // Draw the pie slices
+ for (int i = 0; i < mData.size(); ++i) {
+ Item it = mData.get(i);
+ mPiePaint.setShader(it.mShader);
+ canvas.drawArc(mBounds,
+ 360 - it.mEndAngle,
+ it.mEndAngle - it.mStartAngle,
+ true, mPiePaint);
+ }
+*/
+ // Draw the pointer
+ //canvas.drawLine(mTextX, mPointerY, mPointerX, mPointerY, mTextPaint);
+ //canvas.drawCircle(mPointerX, mPointerY, mPointerSize, mTextPaint);
+ }
+
+ /**
+ * Gets the example string attribute value.
+ *
+ * @return The example string attribute value.
+ */
+ public String getExampleString() {
+ return mExampleString;
+ }
+
+ /**
+ * Sets the view's example string attribute value. In the example view, this string
+ * is the text to draw.
+ *
+ * @param exampleString The example string attribute value to use.
+ */
+ public void setExampleString(String exampleString) {
+ mExampleString = exampleString;
+ invalidateTextPaintAndMeasurements();
+ }
+
+ /**
+ * Gets the example color attribute value.
+ *
+ * @return The example color attribute value.
+ */
+ public int getExampleColor() {
+ return mExampleColor;
+ }
+
+ /**
+ * Sets the view's example color attribute value. In the example view, this color
+ * is the font color.
+ *
+ * @param exampleColor The example color attribute value to use.
+ */
+ public void setExampleColor(int exampleColor) {
+ mExampleColor = exampleColor;
+ invalidateTextPaintAndMeasurements();
+ }
+
+ /**
+ * Gets the example dimension attribute value.
+ *
+ * @return The example dimension attribute value.
+ */
+ public float getExampleDimension() {
+ return mExampleDimension;
+ }
+
+ /**
+ * Sets the view's example dimension attribute value. In the example view, this dimension
+ * is the font size.
+ *
+ * @param exampleDimension The example dimension attribute value to use.
+ */
+ public void setExampleDimension(float exampleDimension) {
+ mExampleDimension = exampleDimension;
+ invalidateTextPaintAndMeasurements();
+ }
+}
diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/app/RunLocalBenchmarksActivity.java b/tests/JankBench/app/src/main/java/com/android/benchmark/app/RunLocalBenchmarksActivity.java
new file mode 100644
index 0000000..7641d00
--- /dev/null
+++ b/tests/JankBench/app/src/main/java/com/android/benchmark/app/RunLocalBenchmarksActivity.java
@@ -0,0 +1,415 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and limitations under the
+ * License.
+ *
+ */
+
+package com.android.benchmark.app;
+
+import android.content.Intent;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.Handler;
+import android.support.v4.app.FragmentManager;
+import android.support.v4.app.FragmentTransaction;
+import android.support.v4.app.ListFragment;
+import android.support.v7.app.AppCompatActivity;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import com.android.benchmark.R;
+import com.android.benchmark.registry.BenchmarkGroup;
+import com.android.benchmark.registry.BenchmarkRegistry;
+import com.android.benchmark.results.GlobalResultsStore;
+import com.android.benchmark.results.UiBenchmarkResult;
+import com.android.benchmark.synthetic.MemoryActivity;
+import com.android.benchmark.ui.BitmapUploadActivity;
+import com.android.benchmark.ui.EditTextInputActivity;
+import com.android.benchmark.ui.FullScreenOverdrawActivity;
+import com.android.benchmark.ui.ImageListViewScrollActivity;
+import com.android.benchmark.ui.ListViewScrollActivity;
+import com.android.benchmark.ui.ShadowGridActivity;
+import com.android.benchmark.ui.TextScrollActivity;
+
+import org.apache.commons.math.stat.descriptive.SummaryStatistics;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+public class RunLocalBenchmarksActivity extends AppCompatActivity {
+
+ public static final int RUN_COUNT = 5;
+
+ private ArrayList<LocalBenchmark> mBenchmarksToRun;
+ private int mBenchmarkCursor;
+ private int mCurrentRunId;
+ private boolean mFinish;
+
+ private Handler mHandler = new Handler();
+
+ private static final int[] ALL_TESTS = new int[] {
+ R.id.benchmark_list_view_scroll,
+ R.id.benchmark_image_list_view_scroll,
+ R.id.benchmark_shadow_grid,
+ R.id.benchmark_text_high_hitrate,
+ R.id.benchmark_text_low_hitrate,
+ R.id.benchmark_edit_text_input,
+ R.id.benchmark_overdraw,
+ };
+
+ public static class LocalBenchmarksList extends ListFragment {
+ private ArrayList<LocalBenchmark> mBenchmarks;
+ private int mRunId;
+
+ public void setBenchmarks(ArrayList<LocalBenchmark> benchmarks) {
+ mBenchmarks = benchmarks;
+ }
+
+ public void setRunId(int id) {
+ mRunId = id;
+ }
+
+ @Override
+ public void onListItemClick(ListView l, View v, int position, long id) {
+ if (getActivity().findViewById(R.id.list_fragment_container) != null) {
+ FragmentManager fm = getActivity().getSupportFragmentManager();
+ UiResultsFragment resultsView = new UiResultsFragment();
+ String testName = BenchmarkRegistry.getBenchmarkName(v.getContext(),
+ mBenchmarks.get(position).id);
+ resultsView.setRunInfo(testName, mRunId);
+ FragmentTransaction fragmentTransaction = fm.beginTransaction();
+ fragmentTransaction.replace(R.id.list_fragment_container, resultsView);
+ fragmentTransaction.addToBackStack(null);
+ fragmentTransaction.commit();
+ }
+ }
+ }
+
+
+ private class LocalBenchmark {
+ int id;
+ int runCount = 0;
+ int totalCount = 0;
+ ArrayList<String> mResultsUri = new ArrayList<>();
+
+ LocalBenchmark(int id, int runCount) {
+ this.id = id;
+ this.runCount = 0;
+ this.totalCount = runCount;
+ }
+
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_running_list);
+
+ initLocalBenchmarks(getIntent());
+
+ if (findViewById(R.id.list_fragment_container) != null) {
+ FragmentManager fm = getSupportFragmentManager();
+ LocalBenchmarksList listView = new LocalBenchmarksList();
+ listView.setListAdapter(new LocalBenchmarksListAdapter(LayoutInflater.from(this)));
+ listView.setBenchmarks(mBenchmarksToRun);
+ listView.setRunId(mCurrentRunId);
+ fm.beginTransaction().add(R.id.list_fragment_container, listView).commit();
+ }
+
+ TextView scoreView = (TextView) findViewById(R.id.score_text_view);
+ scoreView.setText("Running tests!");
+ }
+
+ private int translateBenchmarkIndex(int index) {
+ if (index >= 0 && index < ALL_TESTS.length) {
+ return ALL_TESTS[index];
+ }
+
+ return -1;
+ }
+
+ private void initLocalBenchmarks(Intent intent) {
+ mBenchmarksToRun = new ArrayList<>();
+ int[] enabledIds = intent.getIntArrayExtra(BenchmarkGroup.BENCHMARK_EXTRA_ENABLED_TESTS);
+ int runCount = intent.getIntExtra(BenchmarkGroup.BENCHMARK_EXTRA_RUN_COUNT, RUN_COUNT);
+ mFinish = intent.getBooleanExtra(BenchmarkGroup.BENCHMARK_EXTRA_FINISH, false);
+
+ if (enabledIds == null) {
+ // run all tests
+ enabledIds = ALL_TESTS;
+ }
+
+ StringBuilder idString = new StringBuilder();
+ idString.append(runCount);
+ idString.append(System.currentTimeMillis());
+
+ for (int i = 0; i < enabledIds.length; i++) {
+ int id = enabledIds[i];
+ System.out.println("considering " + id);
+ if (!isValidBenchmark(id)) {
+ System.out.println("not valid " + id);
+ id = translateBenchmarkIndex(id);
+ System.out.println("got out " + id);
+ System.out.println("expected: " + R.id.benchmark_overdraw);
+ }
+
+ if (isValidBenchmark(id)) {
+ int localRunCount = runCount;
+ if (isCompute(id)) {
+ localRunCount = 1;
+ }
+ mBenchmarksToRun.add(new LocalBenchmark(id, localRunCount));
+ idString.append(id);
+ }
+ }
+
+ mBenchmarkCursor = 0;
+ mCurrentRunId = idString.toString().hashCode();
+ }
+
+ private boolean isCompute(int id) {
+ switch (id) {
+ case R.id.benchmark_cpu_gflops:
+ case R.id.benchmark_cpu_heat_soak:
+ case R.id.benchmark_memory_bandwidth:
+ case R.id.benchmark_memory_latency:
+ case R.id.benchmark_power_management:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ private static boolean isValidBenchmark(int benchmarkId) {
+ switch (benchmarkId) {
+ case R.id.benchmark_list_view_scroll:
+ case R.id.benchmark_image_list_view_scroll:
+ case R.id.benchmark_shadow_grid:
+ case R.id.benchmark_text_high_hitrate:
+ case R.id.benchmark_text_low_hitrate:
+ case R.id.benchmark_edit_text_input:
+ case R.id.benchmark_overdraw:
+ case R.id.benchmark_memory_bandwidth:
+ case R.id.benchmark_memory_latency:
+ case R.id.benchmark_power_management:
+ case R.id.benchmark_cpu_heat_soak:
+ case R.id.benchmark_cpu_gflops:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ mHandler.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ runNextBenchmark();
+ }
+ }, 1000);
+ }
+
+ private void computeOverallScore() {
+ final TextView scoreView = (TextView) findViewById(R.id.score_text_view);
+ scoreView.setText("Computing score...");
+ new AsyncTask<Void, Void, Integer>() {
+ @Override
+ protected Integer doInBackground(Void... voids) {
+ GlobalResultsStore gsr =
+ GlobalResultsStore.getInstance(RunLocalBenchmarksActivity.this);
+ ArrayList<Double> testLevelScores = new ArrayList<>();
+ final SummaryStatistics stats = new SummaryStatistics();
+ for (LocalBenchmark b : mBenchmarksToRun) {
+ HashMap<String, ArrayList<UiBenchmarkResult>> detailedResults =
+ gsr.loadDetailedResults(mCurrentRunId);
+ for (ArrayList<UiBenchmarkResult> testResult : detailedResults.values()) {
+ for (UiBenchmarkResult res : testResult) {
+ int score = res.getScore();
+ if (score == 0) {
+ score = 1;
+ }
+ stats.addValue(score);
+ }
+
+ testLevelScores.add(stats.getGeometricMean());
+ stats.clear();
+ }
+
+ }
+
+ for (double score : testLevelScores) {
+ stats.addValue(score);
+ }
+
+ return (int)Math.round(stats.getGeometricMean());
+ }
+
+ @Override
+ protected void onPostExecute(Integer score) {
+ TextView view = (TextView)
+ RunLocalBenchmarksActivity.this.findViewById(R.id.score_text_view);
+ view.setText("Score: " + score);
+ }
+ }.execute();
+ }
+
+ private void runNextBenchmark() {
+ LocalBenchmark benchmark = mBenchmarksToRun.get(mBenchmarkCursor);
+ boolean runAgain = false;
+
+ if (benchmark.runCount < benchmark.totalCount) {
+ runBenchmarkForId(mBenchmarksToRun.get(mBenchmarkCursor).id, benchmark.runCount++);
+ } else if (mBenchmarkCursor + 1 < mBenchmarksToRun.size()) {
+ mBenchmarkCursor++;
+ benchmark = mBenchmarksToRun.get(mBenchmarkCursor);
+ runBenchmarkForId(benchmark.id, benchmark.runCount++);
+ } else if (runAgain) {
+ mBenchmarkCursor = 0;
+ initLocalBenchmarks(getIntent());
+
+ runBenchmarkForId(mBenchmarksToRun.get(mBenchmarkCursor).id, benchmark.runCount);
+ } else if (mFinish) {
+ finish();
+ } else {
+ Log.i("BENCH", "BenchmarkDone!");
+ computeOverallScore();
+ }
+ }
+
+ private void runBenchmarkForId(int id, int iteration) {
+ Intent intent;
+ int syntheticTestId = -1;
+
+ System.out.println("iteration: " + iteration);
+
+ switch (id) {
+ case R.id.benchmark_list_view_scroll:
+ intent = new Intent(getApplicationContext(), ListViewScrollActivity.class);
+ break;
+ case R.id.benchmark_image_list_view_scroll:
+ intent = new Intent(getApplicationContext(), ImageListViewScrollActivity.class);
+ break;
+ case R.id.benchmark_shadow_grid:
+ intent = new Intent(getApplicationContext(), ShadowGridActivity.class);
+ break;
+ case R.id.benchmark_text_high_hitrate:
+ intent = new Intent(getApplicationContext(), TextScrollActivity.class);
+ intent.putExtra(TextScrollActivity.EXTRA_HIT_RATE, 80);
+ intent.putExtra(BenchmarkRegistry.EXTRA_ID, id);
+ break;
+ case R.id.benchmark_text_low_hitrate:
+ intent = new Intent(getApplicationContext(), TextScrollActivity.class);
+ intent.putExtra(TextScrollActivity.EXTRA_HIT_RATE, 20);
+ intent.putExtra(BenchmarkRegistry.EXTRA_ID, id);
+ break;
+ case R.id.benchmark_edit_text_input:
+ intent = new Intent(getApplicationContext(), EditTextInputActivity.class);
+ break;
+ case R.id.benchmark_overdraw:
+ intent = new Intent(getApplicationContext(), BitmapUploadActivity.class);
+ break;
+ case R.id.benchmark_memory_bandwidth:
+ syntheticTestId = 0;
+ intent = new Intent(getApplicationContext(), MemoryActivity.class);
+ intent.putExtra("test", syntheticTestId);
+ break;
+ case R.id.benchmark_memory_latency:
+ syntheticTestId = 1;
+ intent = new Intent(getApplicationContext(), MemoryActivity.class);
+ intent.putExtra("test", syntheticTestId);
+ break;
+ case R.id.benchmark_power_management:
+ syntheticTestId = 2;
+ intent = new Intent(getApplicationContext(), MemoryActivity.class);
+ intent.putExtra("test", syntheticTestId);
+ break;
+ case R.id.benchmark_cpu_heat_soak:
+ syntheticTestId = 3;
+ intent = new Intent(getApplicationContext(), MemoryActivity.class);
+ intent.putExtra("test", syntheticTestId);
+ break;
+ case R.id.benchmark_cpu_gflops:
+ syntheticTestId = 4;
+ intent = new Intent(getApplicationContext(), MemoryActivity.class);
+ intent.putExtra("test", syntheticTestId);
+ break;
+
+ default:
+ intent = null;
+ }
+
+ if (intent != null) {
+ intent.putExtra("com.android.benchmark.RUN_ID", mCurrentRunId);
+ intent.putExtra("com.android.benchmark.ITERATION", iteration);
+ startActivityForResult(intent, id & 0xffff, null);
+ }
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ switch (requestCode) {
+ case R.id.benchmark_shadow_grid:
+ case R.id.benchmark_list_view_scroll:
+ case R.id.benchmark_image_list_view_scroll:
+ case R.id.benchmark_text_high_hitrate:
+ case R.id.benchmark_text_low_hitrate:
+ case R.id.benchmark_edit_text_input:
+ break;
+ default:
+ }
+ }
+
+ class LocalBenchmarksListAdapter extends BaseAdapter {
+
+ private final LayoutInflater mInflater;
+
+ LocalBenchmarksListAdapter(LayoutInflater inflater) {
+ mInflater = inflater;
+ }
+
+ @Override
+ public int getCount() {
+ return mBenchmarksToRun.size();
+ }
+
+ @Override
+ public Object getItem(int i) {
+ return mBenchmarksToRun.get(i);
+ }
+
+ @Override
+ public long getItemId(int i) {
+ return mBenchmarksToRun.get(i).id;
+ }
+
+ @Override
+ public View getView(int i, View convertView, ViewGroup parent) {
+ if (convertView == null) {
+ convertView = mInflater.inflate(R.layout.running_benchmark_list_item, null);
+ }
+
+ TextView name = (TextView) convertView.findViewById(R.id.benchmark_name);
+ name.setText(BenchmarkRegistry.getBenchmarkName(
+ RunLocalBenchmarksActivity.this, mBenchmarksToRun.get(i).id));
+ return convertView;
+ }
+
+ }
+}
diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/app/UiResultsFragment.java b/tests/JankBench/app/src/main/java/com/android/benchmark/app/UiResultsFragment.java
new file mode 100644
index 0000000..56e94d5
--- /dev/null
+++ b/tests/JankBench/app/src/main/java/com/android/benchmark/app/UiResultsFragment.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the License);
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an AS IS BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.benchmark.app;
+
+import android.annotation.TargetApi;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.v4.app.ListFragment;
+import android.util.Log;
+import android.view.FrameMetrics;
+import android.widget.SimpleAdapter;
+
+import com.android.benchmark.R;
+import com.android.benchmark.registry.BenchmarkGroup;
+import com.android.benchmark.registry.BenchmarkRegistry;
+import com.android.benchmark.results.GlobalResultsStore;
+import com.android.benchmark.results.UiBenchmarkResult;
+
+import org.apache.commons.math.stat.descriptive.SummaryStatistics;
+
+import java.io.FileWriter;
+import java.io.IOException;
+import java.net.URI;
+import java.text.DecimalFormat;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+
+@TargetApi(24)
+public class UiResultsFragment extends ListFragment {
+ private static final String TAG = "UiResultsFragment";
+ private static final int NUM_FIELDS = 20;
+
+ private ArrayList<UiBenchmarkResult> mResults = new ArrayList<>();
+
+ private AsyncTask<Void, Void, ArrayList<Map<String, String>>> mLoadScoresTask =
+ new AsyncTask<Void, Void, ArrayList<Map<String, String>>>() {
+ @Override
+ protected ArrayList<Map<String, String>> doInBackground(Void... voids) {
+ String[] data;
+ if (mResults.size() == 0 || mResults.get(0) == null) {
+ data = new String[] {
+ "No metrics reported", ""
+ };
+ } else {
+ data = new String[NUM_FIELDS * (1 + mResults.size()) + 2];
+ SummaryStatistics stats = new SummaryStatistics();
+ int totalFrameCount = 0;
+ double totalAvgFrameDuration = 0;
+ double total99FrameDuration = 0;
+ double total95FrameDuration = 0;
+ double total90FrameDuration = 0;
+ double totalLongestFrame = 0;
+ double totalShortestFrame = 0;
+
+ for (int i = 0; i < mResults.size(); i++) {
+ int start = (i * NUM_FIELDS) + + NUM_FIELDS;
+ data[(start++)] = "Iteration";
+ data[(start++)] = "" + i;
+ data[(start++)] = "Total Frames";
+ int currentFrameCount = mResults.get(i).getTotalFrameCount();
+ totalFrameCount += currentFrameCount;
+ data[(start++)] = Integer.toString(currentFrameCount);
+ data[(start++)] = "Average frame duration:";
+ double currentAvgFrameDuration = mResults.get(i).getAverage(FrameMetrics.TOTAL_DURATION);
+ totalAvgFrameDuration += currentAvgFrameDuration;
+ data[(start++)] = String.format("%.2f", currentAvgFrameDuration);
+ data[(start++)] = "Frame duration 99th:";
+ double current99FrameDuration = mResults.get(i).getPercentile(FrameMetrics.TOTAL_DURATION, 99);
+ total99FrameDuration += current99FrameDuration;
+ data[(start++)] = String.format("%.2f", current99FrameDuration);
+ data[(start++)] = "Frame duration 95th:";
+ double current95FrameDuration = mResults.get(i).getPercentile(FrameMetrics.TOTAL_DURATION, 95);
+ total95FrameDuration += current95FrameDuration;
+ data[(start++)] = String.format("%.2f", current95FrameDuration);
+ data[(start++)] = "Frame duration 90th:";
+ double current90FrameDuration = mResults.get(i).getPercentile(FrameMetrics.TOTAL_DURATION, 90);
+ total90FrameDuration += current90FrameDuration;
+ data[(start++)] = String.format("%.2f", current90FrameDuration);
+ data[(start++)] = "Longest frame:";
+ double longestFrame = mResults.get(i).getMaximum(FrameMetrics.TOTAL_DURATION);
+ if (totalLongestFrame == 0 || longestFrame > totalLongestFrame) {
+ totalLongestFrame = longestFrame;
+ }
+ data[(start++)] = String.format("%.2f", longestFrame);
+ data[(start++)] = "Shortest frame:";
+ double shortestFrame = mResults.get(i).getMinimum(FrameMetrics.TOTAL_DURATION);
+ if (totalShortestFrame == 0 || totalShortestFrame > shortestFrame) {
+ totalShortestFrame = shortestFrame;
+ }
+ data[(start++)] = String.format("%.2f", shortestFrame);
+ data[(start++)] = "Score:";
+ double score = mResults.get(i).getScore();
+ stats.addValue(score);
+ data[(start++)] = String.format("%.2f", score);
+ data[(start++)] = "==============";
+ data[(start++)] = "============================";
+ };
+
+ int start = 0;
+ data[0] = "Overall: ";
+ data[1] = "";
+ data[(start++)] = "Total Frames";
+ data[(start++)] = Integer.toString(totalFrameCount);
+ data[(start++)] = "Average frame duration:";
+ data[(start++)] = String.format("%.2f", totalAvgFrameDuration / mResults.size());
+ data[(start++)] = "Frame duration 99th:";
+ data[(start++)] = String.format("%.2f", total99FrameDuration / mResults.size());
+ data[(start++)] = "Frame duration 95th:";
+ data[(start++)] = String.format("%.2f", total95FrameDuration / mResults.size());
+ data[(start++)] = "Frame duration 90th:";
+ data[(start++)] = String.format("%.2f", total90FrameDuration / mResults.size());
+ data[(start++)] = "Longest frame:";
+ data[(start++)] = String.format("%.2f", totalLongestFrame);
+ data[(start++)] = "Shortest frame:";
+ data[(start++)] = String.format("%.2f", totalShortestFrame);
+ data[(start++)] = "Score:";
+ data[(start++)] = String.format("%.2f", stats.getGeometricMean());
+ data[(start++)] = "==============";
+ data[(start++)] = "============================";
+ }
+
+ ArrayList<Map<String, String>> dataMap = new ArrayList<>();
+ for (int i = 0; i < data.length - 1; i += 2) {
+ HashMap<String, String> map = new HashMap<>();
+ map.put("name", data[i]);
+ map.put("value", data[i + 1]);
+ dataMap.add(map);
+ }
+
+ return dataMap;
+ }
+
+ @Override
+ protected void onPostExecute(ArrayList<Map<String, String>> dataMap) {
+ setListAdapter(new SimpleAdapter(getActivity(), dataMap, R.layout.results_list_item,
+ new String[] {"name", "value"}, new int[] { R.id.result_name, R.id.result_value }));
+ setListShown(true);
+ }
+ };
+
+ @Override
+ public void onActivityCreated(@Nullable Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+ setListShown(false);
+ mLoadScoresTask.execute();
+ }
+
+ public void setRunInfo(String name, int runId) {
+ mResults = GlobalResultsStore.getInstance(getActivity()).loadTestResults(name, runId);
+ }
+}
diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/registry/BenchmarkCategory.java b/tests/JankBench/app/src/main/java/com/android/benchmark/registry/BenchmarkCategory.java
new file mode 100644
index 0000000..d91e579
--- /dev/null
+++ b/tests/JankBench/app/src/main/java/com/android/benchmark/registry/BenchmarkCategory.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and limitations under the
+ * License.
+ *
+ */
+
+package com.android.benchmark.registry;
+
+import android.support.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Represents the category of a particular benchmark.
+ */
+@Retention(RetentionPolicy.SOURCE)
+@IntDef({BenchmarkCategory.GENERIC, BenchmarkCategory.UI, BenchmarkCategory.COMPUTE})
+@interface BenchmarkCategory {
+ int GENERIC = 0;
+ int UI = 1;
+ int COMPUTE = 2;
+}
diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/registry/BenchmarkGroup.java b/tests/JankBench/app/src/main/java/com/android/benchmark/registry/BenchmarkGroup.java
new file mode 100644
index 0000000..4cb7716
--- /dev/null
+++ b/tests/JankBench/app/src/main/java/com/android/benchmark/registry/BenchmarkGroup.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and limitations under the
+ * License.
+ *
+ */
+
+package com.android.benchmark.registry;
+
+import android.content.ComponentName;
+import android.content.Intent;
+import android.view.View;
+import android.widget.CheckBox;
+
+/**
+ * Logical grouping of benchmarks
+ */
+public class BenchmarkGroup {
+ public static final String BENCHMARK_EXTRA_ENABLED_TESTS =
+ "com.android.benchmark.EXTRA_ENABLED_BENCHMARK_IDS";
+
+ public static final String BENCHMARK_EXTRA_RUN_COUNT =
+ "com.android.benchmark.EXTRA_RUN_COUNT";
+ public static final String BENCHMARK_EXTRA_FINISH = "com.android.benchmark.FINISH_WHEN_DONE";
+
+ public static class Benchmark implements CheckBox.OnClickListener {
+ /** The name of this individual benchmark test */
+ private final String mName;
+
+ /** The category of this individual benchmark test */
+ private final @BenchmarkCategory int mCategory;
+
+ /** Human-readable description of the benchmark */
+ private final String mDescription;
+
+ private final int mId;
+
+ private boolean mEnabled;
+
+ Benchmark(int id, String name, @BenchmarkCategory int category, String description) {
+ mId = id;
+ mName = name;
+ mCategory = category;
+ mDescription = description;
+ mEnabled = true;
+ }
+
+ public boolean isEnabled() { return mEnabled; }
+
+ public void setEnabled(boolean enabled) { mEnabled = enabled; }
+
+ public int getId() { return mId; }
+
+ public String getDescription() { return mDescription; }
+
+ public @BenchmarkCategory int getCategory() { return mCategory; }
+
+ public String getName() { return mName; }
+
+ @Override
+ public void onClick(View view) {
+ setEnabled(((CheckBox)view).isChecked());
+ }
+ }
+
+ /**
+ * Component for this benchmark group.
+ */
+ private final ComponentName mComponentName;
+
+ /**
+ * Benchmark title, showed in the {@link android.widget.ListView}
+ */
+ private final String mTitle;
+
+ /**
+ * List of all benchmarks exported by this group
+ */
+ private final Benchmark[] mBenchmarks;
+
+ /**
+ * The intent to launch the benchmark
+ */
+ private final Intent mIntent;
+
+ /** Human-readable description of the benchmark group */
+ private final String mDescription;
+
+ BenchmarkGroup(ComponentName componentName, String title,
+ String description, Benchmark[] benchmarks, Intent intent) {
+ mComponentName = componentName;
+ mTitle = title;
+ mBenchmarks = benchmarks;
+ mDescription = description;
+ mIntent = intent;
+ }
+
+ public Intent getIntent() {
+ int[] enabledBenchmarksIds = getEnabledBenchmarksIds();
+ if (enabledBenchmarksIds.length != 0) {
+ mIntent.putExtra(BENCHMARK_EXTRA_ENABLED_TESTS, enabledBenchmarksIds);
+ return mIntent;
+ }
+
+ return null;
+ }
+
+ public ComponentName getComponentName() {
+ return mComponentName;
+ }
+
+ public String getTitle() {
+ return mTitle;
+ }
+
+ public Benchmark[] getBenchmarks() {
+ return mBenchmarks;
+ }
+
+ public String getDescription() {
+ return mDescription;
+ }
+
+ private int[] getEnabledBenchmarksIds() {
+ int enabledBenchmarkCount = 0;
+ for (int i = 0; i < mBenchmarks.length; i++) {
+ if (mBenchmarks[i].isEnabled()) {
+ enabledBenchmarkCount++;
+ }
+ }
+
+ int writeIndex = 0;
+ int[] enabledBenchmarks = new int[enabledBenchmarkCount];
+ for (int i = 0; i < mBenchmarks.length; i++) {
+ if (mBenchmarks[i].isEnabled()) {
+ enabledBenchmarks[writeIndex++] = mBenchmarks[i].getId();
+ }
+ }
+
+ return enabledBenchmarks;
+ }
+}
diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/registry/BenchmarkRegistry.java b/tests/JankBench/app/src/main/java/com/android/benchmark/registry/BenchmarkRegistry.java
new file mode 100644
index 0000000..89c6aed
--- /dev/null
+++ b/tests/JankBench/app/src/main/java/com/android/benchmark/registry/BenchmarkRegistry.java
@@ -0,0 +1,236 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and limitations under the
+ * License.
+ *
+ */
+
+package com.android.benchmark.registry;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.util.AttributeSet;
+import android.util.SparseArray;
+import android.util.Xml;
+
+import com.android.benchmark.R;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ */
+public class BenchmarkRegistry {
+
+ /** Metadata key for benchmark XML data */
+ private static final String BENCHMARK_GROUP_META_KEY =
+ "com.android.benchmark.benchmark_group";
+
+ /** Intent action specifying an activity that runs a single benchmark test. */
+ private static final String ACTION_BENCHMARK = "com.android.benchmark.ACTION_BENCHMARK";
+ public static final String EXTRA_ID = "com.android.benchmark.EXTRA_ID";
+
+ private static final String TAG_BENCHMARK_GROUP = "com.android.benchmark.BenchmarkGroup";
+ private static final String TAG_BENCHMARK = "com.android.benchmark.Benchmark";
+
+ private List<BenchmarkGroup> mGroups;
+
+ private final Context mContext;
+
+ public BenchmarkRegistry(Context context) {
+ mContext = context;
+ mGroups = new ArrayList<>();
+ loadBenchmarks();
+ }
+
+ private Intent getIntentFromInfo(ActivityInfo inf) {
+ Intent intent = new Intent();
+ intent.setClassName(inf.packageName, inf.name);
+ return intent;
+ }
+
+ public void loadBenchmarks() {
+ Intent intent = new Intent(ACTION_BENCHMARK);
+ intent.setPackage(mContext.getPackageName());
+
+ PackageManager pm = mContext.getPackageManager();
+ List<ResolveInfo> resolveInfos = pm.queryIntentActivities(intent,
+ PackageManager.GET_ACTIVITIES | PackageManager.GET_META_DATA);
+
+ for (ResolveInfo inf : resolveInfos) {
+ List<BenchmarkGroup> groups = parseBenchmarkGroup(inf.activityInfo);
+ if (groups != null) {
+ mGroups.addAll(groups);
+ }
+ }
+ }
+
+ private boolean seekToTag(XmlPullParser parser, String tag)
+ throws XmlPullParserException, IOException {
+ int eventType = parser.getEventType();
+ while (eventType != XmlPullParser.START_TAG && eventType != XmlPullParser.END_DOCUMENT) {
+ eventType = parser.next();
+ }
+ return eventType != XmlPullParser.END_DOCUMENT && tag.equals(parser.getName());
+ }
+
+ @BenchmarkCategory int getCategory(int category) {
+ switch (category) {
+ case BenchmarkCategory.COMPUTE:
+ return BenchmarkCategory.COMPUTE;
+ case BenchmarkCategory.UI:
+ return BenchmarkCategory.UI;
+ default:
+ return BenchmarkCategory.GENERIC;
+ }
+ }
+
+ private List<BenchmarkGroup> parseBenchmarkGroup(ActivityInfo activityInfo) {
+ PackageManager pm = mContext.getPackageManager();
+
+ ComponentName componentName = new ComponentName(
+ activityInfo.packageName, activityInfo.name);
+
+ SparseArray<List<BenchmarkGroup.Benchmark>> benchmarks = new SparseArray<>();
+ String groupName, groupDescription;
+ try (XmlResourceParser parser = activityInfo.loadXmlMetaData(pm, BENCHMARK_GROUP_META_KEY)) {
+
+ if (!seekToTag(parser, TAG_BENCHMARK_GROUP)) {
+ return null;
+ }
+
+ Resources res = pm.getResourcesForActivity(componentName);
+ AttributeSet attributeSet = Xml.asAttributeSet(parser);
+ TypedArray groupAttribs = res.obtainAttributes(attributeSet, R.styleable.BenchmarkGroup);
+
+ groupName = groupAttribs.getString(R.styleable.BenchmarkGroup_name);
+ groupDescription = groupAttribs.getString(R.styleable.BenchmarkGroup_description);
+ groupAttribs.recycle();
+ parser.next();
+
+ while (seekToTag(parser, TAG_BENCHMARK)) {
+ TypedArray benchAttribs =
+ res.obtainAttributes(Xml.asAttributeSet(parser), R.styleable.Benchmark);
+ int id = benchAttribs.getResourceId(R.styleable.Benchmark_id, -1);
+ String testName = benchAttribs.getString(R.styleable.Benchmark_name);
+ String testDescription = benchAttribs.getString(R.styleable.Benchmark_description);
+ int testCategory = benchAttribs.getInt(R.styleable.Benchmark_category,
+ BenchmarkCategory.GENERIC);
+ int category = getCategory(testCategory);
+ BenchmarkGroup.Benchmark benchmark = new BenchmarkGroup.Benchmark(
+ id, testName, category, testDescription);
+ List<BenchmarkGroup.Benchmark> benches = benchmarks.get(category);
+ if (benches == null) {
+ benches = new ArrayList<>();
+ benchmarks.append(category, benches);
+ }
+
+ benches.add(benchmark);
+
+ benchAttribs.recycle();
+ parser.next();
+ }
+ } catch (PackageManager.NameNotFoundException | XmlPullParserException | IOException e) {
+ return null;
+ }
+
+ List<BenchmarkGroup> result = new ArrayList<>();
+ Intent testIntent = getIntentFromInfo(activityInfo);
+ for (int i = 0; i < benchmarks.size(); i++) {
+ int cat = benchmarks.keyAt(i);
+ List<BenchmarkGroup.Benchmark> thisGroup = benchmarks.get(cat);
+ BenchmarkGroup.Benchmark[] benchmarkArray =
+ new BenchmarkGroup.Benchmark[thisGroup.size()];
+ thisGroup.toArray(benchmarkArray);
+ result.add(new BenchmarkGroup(componentName,
+ groupName + " - " + getCategoryString(cat), groupDescription, benchmarkArray,
+ testIntent));
+ }
+
+ return result;
+ }
+
+ public int getGroupCount() {
+ return mGroups.size();
+ }
+
+ public int getBenchmarkCount(int benchmarkIndex) {
+ BenchmarkGroup group = getBenchmarkGroup(benchmarkIndex);
+ if (group != null) {
+ return group.getBenchmarks().length;
+ }
+ return 0;
+ }
+
+ public BenchmarkGroup getBenchmarkGroup(int benchmarkIndex) {
+ if (benchmarkIndex >= mGroups.size()) {
+ return null;
+ }
+
+ return mGroups.get(benchmarkIndex);
+ }
+
+ public static String getCategoryString(int category) {
+ switch (category) {
+ case BenchmarkCategory.UI:
+ return "UI";
+ case BenchmarkCategory.COMPUTE:
+ return "Compute";
+ case BenchmarkCategory.GENERIC:
+ return "Generic";
+ default:
+ return "";
+ }
+ }
+
+ public static String getBenchmarkName(Context context, int benchmarkId) {
+ switch (benchmarkId) {
+ case R.id.benchmark_list_view_scroll:
+ return context.getString(R.string.list_view_scroll_name);
+ case R.id.benchmark_image_list_view_scroll:
+ return context.getString(R.string.image_list_view_scroll_name);
+ case R.id.benchmark_shadow_grid:
+ return context.getString(R.string.shadow_grid_name);
+ case R.id.benchmark_text_high_hitrate:
+ return context.getString(R.string.text_high_hitrate_name);
+ case R.id.benchmark_text_low_hitrate:
+ return context.getString(R.string.text_low_hitrate_name);
+ case R.id.benchmark_edit_text_input:
+ return context.getString(R.string.edit_text_input_name);
+ case R.id.benchmark_memory_bandwidth:
+ return context.getString(R.string.memory_bandwidth_name);
+ case R.id.benchmark_memory_latency:
+ return context.getString(R.string.memory_latency_name);
+ case R.id.benchmark_power_management:
+ return context.getString(R.string.power_management_name);
+ case R.id.benchmark_cpu_heat_soak:
+ return context.getString(R.string.cpu_heat_soak_name);
+ case R.id.benchmark_cpu_gflops:
+ return context.getString(R.string.cpu_gflops_name);
+ case R.id.benchmark_overdraw:
+ return context.getString(R.string.overdraw_name);
+ default:
+ return "Some Benchmark";
+ }
+ }
+}
diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/results/GlobalResultsStore.java b/tests/JankBench/app/src/main/java/com/android/benchmark/results/GlobalResultsStore.java
new file mode 100644
index 0000000..5d0cba2
--- /dev/null
+++ b/tests/JankBench/app/src/main/java/com/android/benchmark/results/GlobalResultsStore.java
@@ -0,0 +1,389 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the License);
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an AS IS BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.benchmark.results;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.view.FrameMetrics;
+import android.widget.Toast;
+
+import org.apache.commons.math.stat.descriptive.DescriptiveStatistics;
+
+import java.io.FileWriter;
+import java.io.IOException;
+import java.text.DateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+
+public class GlobalResultsStore extends SQLiteOpenHelper {
+ private static final int VERSION = 2;
+
+ private static GlobalResultsStore sInstance;
+ private static final String UI_RESULTS_TABLE = "ui_results";
+
+ private final Context mContext;
+
+ private GlobalResultsStore(Context context) {
+ super(context, "BenchmarkResults", null, VERSION);
+ mContext = context;
+ }
+
+ public static GlobalResultsStore getInstance(Context context) {
+ if (sInstance == null) {
+ sInstance = new GlobalResultsStore(context.getApplicationContext());
+ }
+
+ return sInstance;
+ }
+
+ @Override
+ public void onCreate(SQLiteDatabase sqLiteDatabase) {
+ sqLiteDatabase.execSQL("CREATE TABLE " + UI_RESULTS_TABLE + " (" +
+ " _id INTEGER PRIMARY KEY AUTOINCREMENT," +
+ " name TEXT," +
+ " run_id INTEGER," +
+ " iteration INTEGER," +
+ " timestamp TEXT," +
+ " unknown_delay REAL," +
+ " input REAL," +
+ " animation REAL," +
+ " layout REAL," +
+ " draw REAL," +
+ " sync REAL," +
+ " command_issue REAL," +
+ " swap_buffers REAL," +
+ " total_duration REAL," +
+ " jank_frame BOOLEAN, " +
+ " device_charging INTEGER);");
+ }
+
+ public void storeRunResults(String testName, int runId, int iteration,
+ UiBenchmarkResult result) {
+ SQLiteDatabase db = getWritableDatabase();
+ db.beginTransaction();
+
+ try {
+ String date = DateFormat.getDateTimeInstance().format(new Date());
+ int jankIndexIndex = 0;
+ int[] sortedJankIndices = result.getSortedJankFrameIndices();
+ int totalFrameCount = result.getTotalFrameCount();
+ for (int frameIdx = 0; frameIdx < totalFrameCount; frameIdx++) {
+ ContentValues cv = new ContentValues();
+ cv.put("name", testName);
+ cv.put("run_id", runId);
+ cv.put("iteration", iteration);
+ cv.put("timestamp", date);
+ cv.put("unknown_delay",
+ result.getMetricAtIndex(frameIdx, FrameMetrics.UNKNOWN_DELAY_DURATION));
+ cv.put("input",
+ result.getMetricAtIndex(frameIdx, FrameMetrics.INPUT_HANDLING_DURATION));
+ cv.put("animation",
+ result.getMetricAtIndex(frameIdx, FrameMetrics.ANIMATION_DURATION));
+ cv.put("layout",
+ result.getMetricAtIndex(frameIdx, FrameMetrics.LAYOUT_MEASURE_DURATION));
+ cv.put("draw",
+ result.getMetricAtIndex(frameIdx, FrameMetrics.DRAW_DURATION));
+ cv.put("sync",
+ result.getMetricAtIndex(frameIdx, FrameMetrics.SYNC_DURATION));
+ cv.put("command_issue",
+ result.getMetricAtIndex(frameIdx, FrameMetrics.COMMAND_ISSUE_DURATION));
+ cv.put("swap_buffers",
+ result.getMetricAtIndex(frameIdx, FrameMetrics.SWAP_BUFFERS_DURATION));
+ cv.put("total_duration",
+ result.getMetricAtIndex(frameIdx, FrameMetrics.TOTAL_DURATION));
+ if (jankIndexIndex < sortedJankIndices.length &&
+ sortedJankIndices[jankIndexIndex] == frameIdx) {
+ jankIndexIndex++;
+ cv.put("jank_frame", true);
+ } else {
+ cv.put("jank_frame", false);
+ }
+ db.insert(UI_RESULTS_TABLE, null, cv);
+ }
+ db.setTransactionSuccessful();
+ Toast.makeText(mContext, "Score: " + result.getScore()
+ + " Jank: " + (100 * sortedJankIndices.length) / (float) totalFrameCount + "%",
+ Toast.LENGTH_LONG).show();
+ } finally {
+ db.endTransaction();
+ }
+
+ }
+
+ public ArrayList<UiBenchmarkResult> loadTestResults(String testName, int runId) {
+ SQLiteDatabase db = getReadableDatabase();
+ ArrayList<UiBenchmarkResult> resultList = new ArrayList<>();
+ try {
+ String[] columnsToQuery = new String[] {
+ "name",
+ "run_id",
+ "iteration",
+ "unknown_delay",
+ "input",
+ "animation",
+ "layout",
+ "draw",
+ "sync",
+ "command_issue",
+ "swap_buffers",
+ "total_duration",
+ };
+
+ Cursor cursor = db.query(
+ UI_RESULTS_TABLE, columnsToQuery, "run_id=? AND name=?",
+ new String[] { Integer.toString(runId), testName }, null, null, "iteration");
+
+ double[] values = new double[columnsToQuery.length - 3];
+
+ while (cursor.moveToNext()) {
+ int iteration = cursor.getInt(cursor.getColumnIndexOrThrow("iteration"));
+
+ values[0] = cursor.getDouble(
+ cursor.getColumnIndexOrThrow("unknown_delay"));
+ values[1] = cursor.getDouble(
+ cursor.getColumnIndexOrThrow("input"));
+ values[2] = cursor.getDouble(
+ cursor.getColumnIndexOrThrow("animation"));
+ values[3] = cursor.getDouble(
+ cursor.getColumnIndexOrThrow("layout"));
+ values[4] = cursor.getDouble(
+ cursor.getColumnIndexOrThrow("draw"));
+ values[5] = cursor.getDouble(
+ cursor.getColumnIndexOrThrow("sync"));
+ values[6] = cursor.getDouble(
+ cursor.getColumnIndexOrThrow("command_issue"));
+ values[7] = cursor.getDouble(
+ cursor.getColumnIndexOrThrow("swap_buffers"));
+ values[8] = cursor.getDouble(
+ cursor.getColumnIndexOrThrow("total_duration"));
+
+ UiBenchmarkResult iterationResult;
+ if (resultList.size() == iteration) {
+ iterationResult = new UiBenchmarkResult(values);
+ resultList.add(iteration, iterationResult);
+ } else {
+ iterationResult = resultList.get(iteration);
+ iterationResult.update(values);
+ }
+ }
+
+ cursor.close();
+ } finally {
+ db.close();
+ }
+
+ int total = resultList.get(0).getTotalFrameCount();
+ for (int i = 0; i < total; i++) {
+ System.out.println(""+ resultList.get(0).getMetricAtIndex(0, FrameMetrics.TOTAL_DURATION));
+ }
+
+ return resultList;
+ }
+
+ public HashMap<String, ArrayList<UiBenchmarkResult>> loadDetailedResults(int runId) {
+ SQLiteDatabase db = getReadableDatabase();
+ HashMap<String, ArrayList<UiBenchmarkResult>> results = new HashMap<>();
+ try {
+ String[] columnsToQuery = new String[] {
+ "name",
+ "run_id",
+ "iteration",
+ "unknown_delay",
+ "input",
+ "animation",
+ "layout",
+ "draw",
+ "sync",
+ "command_issue",
+ "swap_buffers",
+ "total_duration",
+ };
+
+ Cursor cursor = db.query(
+ UI_RESULTS_TABLE, columnsToQuery, "run_id=?",
+ new String[] { Integer.toString(runId) }, null, null, "name, iteration");
+
+ double[] values = new double[columnsToQuery.length - 3];
+ while (cursor.moveToNext()) {
+ int iteration = cursor.getInt(cursor.getColumnIndexOrThrow("iteration"));
+ String name = cursor.getString(cursor.getColumnIndexOrThrow("name"));
+ ArrayList<UiBenchmarkResult> resultList = results.get(name);
+ if (resultList == null) {
+ resultList = new ArrayList<>();
+ results.put(name, resultList);
+ }
+
+ values[0] = cursor.getDouble(
+ cursor.getColumnIndexOrThrow("unknown_delay"));
+ values[1] = cursor.getDouble(
+ cursor.getColumnIndexOrThrow("input"));
+ values[2] = cursor.getDouble(
+ cursor.getColumnIndexOrThrow("animation"));
+ values[3] = cursor.getDouble(
+ cursor.getColumnIndexOrThrow("layout"));
+ values[4] = cursor.getDouble(
+ cursor.getColumnIndexOrThrow("draw"));
+ values[5] = cursor.getDouble(
+ cursor.getColumnIndexOrThrow("sync"));
+ values[6] = cursor.getDouble(
+ cursor.getColumnIndexOrThrow("command_issue"));
+ values[7] = cursor.getDouble(
+ cursor.getColumnIndexOrThrow("swap_buffers"));
+ values[8] = cursor.getDouble(
+ cursor.getColumnIndexOrThrow("total_duration"));
+ values[8] = cursor.getDouble(
+ cursor.getColumnIndexOrThrow("total_duration"));
+
+ UiBenchmarkResult iterationResult;
+ if (resultList.size() == iteration) {
+ iterationResult = new UiBenchmarkResult(values);
+ resultList.add(iterationResult);
+ } else {
+ iterationResult = resultList.get(iteration);
+ iterationResult.update(values);
+ }
+ }
+
+ cursor.close();
+ } finally {
+ db.close();
+ }
+
+ return results;
+ }
+
+ public void exportToCsv() throws IOException {
+ String path = mContext.getFilesDir() + "/results-" + System.currentTimeMillis() + ".csv";
+ SQLiteDatabase db = getReadableDatabase();
+
+ // stats across metrics for each run and each test
+ HashMap<String, DescriptiveStatistics> stats = new HashMap<>();
+
+ Cursor runIdCursor = db.query(
+ UI_RESULTS_TABLE, new String[] { "run_id" }, null, null, "run_id", null, null);
+
+ while (runIdCursor.moveToNext()) {
+
+ int runId = runIdCursor.getInt(runIdCursor.getColumnIndexOrThrow("run_id"));
+ HashMap<String, ArrayList<UiBenchmarkResult>> detailedResults =
+ loadDetailedResults(runId);
+
+ writeRawResults(runId, detailedResults);
+
+ DescriptiveStatistics overall = new DescriptiveStatistics();
+ try (FileWriter writer = new FileWriter(path, true)) {
+ writer.write("Run ID, " + runId + "\n");
+ writer.write("Test, Iteration, Score, Jank Penalty, Consistency Bonus, 95th, " +
+ "90th\n");
+ for (String testName : detailedResults.keySet()) {
+ ArrayList<UiBenchmarkResult> results = detailedResults.get(testName);
+ DescriptiveStatistics scoreStats = new DescriptiveStatistics();
+ DescriptiveStatistics jankPenalty = new DescriptiveStatistics();
+ DescriptiveStatistics consistencyBonus = new DescriptiveStatistics();
+ for (int i = 0; i < results.size(); i++) {
+ UiBenchmarkResult result = results.get(i);
+ int score = result.getScore();
+ scoreStats.addValue(score);
+ overall.addValue(score);
+ jankPenalty.addValue(result.getJankPenalty());
+ consistencyBonus.addValue(result.getConsistencyBonus());
+
+ writer.write(testName);
+ writer.write(",");
+ writer.write("" + i);
+ writer.write(",");
+ writer.write("" + score);
+ writer.write(",");
+ writer.write("" + result.getJankPenalty());
+ writer.write(",");
+ writer.write("" + result.getConsistencyBonus());
+ writer.write(",");
+ writer.write(Double.toString(
+ result.getPercentile(FrameMetrics.TOTAL_DURATION, 95)));
+ writer.write(",");
+ writer.write(Double.toString(
+ result.getPercentile(FrameMetrics.TOTAL_DURATION, 90)));
+ writer.write("\n");
+ }
+
+ writer.write("Score CV," +
+ (100 * scoreStats.getStandardDeviation()
+ / scoreStats.getMean()) + "%\n");
+ writer.write("Jank Penalty CV, " +
+ (100 * jankPenalty.getStandardDeviation()
+ / jankPenalty.getMean()) + "%\n");
+ writer.write("Consistency Bonus CV, " +
+ (100 * consistencyBonus.getStandardDeviation()
+ / consistencyBonus.getMean()) + "%\n");
+ writer.write("\n");
+ }
+
+ writer.write("Overall Score CV," +
+ (100 * overall.getStandardDeviation() / overall.getMean()) + "%\n");
+ writer.flush();
+ }
+ }
+
+ runIdCursor.close();
+ }
+
+ private void writeRawResults(int runId,
+ HashMap<String, ArrayList<UiBenchmarkResult>> detailedResults) {
+ StringBuilder path = new StringBuilder();
+ path.append(mContext.getFilesDir());
+ path.append("/");
+ path.append(Integer.toString(runId));
+ path.append(".csv");
+ try (FileWriter writer = new FileWriter(path.toString())) {
+ for (String test : detailedResults.keySet()) {
+ writer.write("Test, " + test + "\n");
+ writer.write("iteration, unknown delay, input, animation, layout, draw, sync, " +
+ "command issue, swap buffers\n");
+ ArrayList<UiBenchmarkResult> runs = detailedResults.get(test);
+ for (int i = 0; i < runs.size(); i++) {
+ UiBenchmarkResult run = runs.get(i);
+ for (int j = 0; j < run.getTotalFrameCount(); j++) {
+ writer.write(Integer.toString(i) + "," +
+ run.getMetricAtIndex(j, FrameMetrics.UNKNOWN_DELAY_DURATION) + "," +
+ run.getMetricAtIndex(j, FrameMetrics.INPUT_HANDLING_DURATION) + "," +
+ run.getMetricAtIndex(j, FrameMetrics.ANIMATION_DURATION) + "," +
+ run.getMetricAtIndex(j, FrameMetrics.LAYOUT_MEASURE_DURATION) + "," +
+ run.getMetricAtIndex(j, FrameMetrics.DRAW_DURATION) + "," +
+ run.getMetricAtIndex(j, FrameMetrics.SYNC_DURATION) + "," +
+ run.getMetricAtIndex(j, FrameMetrics.COMMAND_ISSUE_DURATION) + "," +
+ run.getMetricAtIndex(j, FrameMetrics.SWAP_BUFFERS_DURATION) + "," +
+ run.getMetricAtIndex(j, FrameMetrics.TOTAL_DURATION) + "\n");
+ }
+ }
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase sqLiteDatabase, int oldVersion, int currentVersion) {
+ if (oldVersion < VERSION) {
+ sqLiteDatabase.execSQL("ALTER TABLE "
+ + UI_RESULTS_TABLE + " ADD COLUMN timestamp TEXT;");
+ }
+ }
+}
diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/results/UiBenchmarkResult.java b/tests/JankBench/app/src/main/java/com/android/benchmark/results/UiBenchmarkResult.java
new file mode 100644
index 0000000..da6e05a
--- /dev/null
+++ b/tests/JankBench/app/src/main/java/com/android/benchmark/results/UiBenchmarkResult.java
@@ -0,0 +1,245 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the License);
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an AS IS BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.benchmark.results;
+
+import android.annotation.TargetApi;
+import android.view.FrameMetrics;
+
+import org.apache.commons.math.stat.descriptive.DescriptiveStatistics;
+import org.apache.commons.math.stat.descriptive.SummaryStatistics;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Utility for storing and analyzing UI benchmark results.
+ */
+@TargetApi(24)
+public class UiBenchmarkResult {
+ private static final int BASE_SCORE = 100;
+ private static final int ZERO_SCORE_TOTAL_DURATION_MS = 32;
+ private static final int JANK_PENALTY_THRESHOLD_MS = 12;
+ private static final int ZERO_SCORE_ABOVE_THRESHOLD_MS =
+ ZERO_SCORE_TOTAL_DURATION_MS - JANK_PENALTY_THRESHOLD_MS;
+ private static final double JANK_PENALTY_PER_MS_ABOVE_THRESHOLD =
+ BASE_SCORE / (double)ZERO_SCORE_ABOVE_THRESHOLD_MS;
+ private static final int CONSISTENCY_BONUS_MAX = 100;
+
+ private static final int METRIC_WAS_JANKY = -1;
+
+ private static final int[] METRICS = new int[] {
+ FrameMetrics.UNKNOWN_DELAY_DURATION,
+ FrameMetrics.INPUT_HANDLING_DURATION,
+ FrameMetrics.ANIMATION_DURATION,
+ FrameMetrics.LAYOUT_MEASURE_DURATION,
+ FrameMetrics.DRAW_DURATION,
+ FrameMetrics.SYNC_DURATION,
+ FrameMetrics.COMMAND_ISSUE_DURATION,
+ FrameMetrics.SWAP_BUFFERS_DURATION,
+ FrameMetrics.TOTAL_DURATION,
+ };
+ public static final int FRAME_PERIOD_MS = 16;
+
+ private final DescriptiveStatistics[] mStoredStatistics;
+
+ public UiBenchmarkResult(List<FrameMetrics> instances) {
+ mStoredStatistics = new DescriptiveStatistics[METRICS.length];
+ insertMetrics(instances);
+ }
+
+ public UiBenchmarkResult(double[] values) {
+ mStoredStatistics = new DescriptiveStatistics[METRICS.length];
+ insertValues(values);
+ }
+
+ public void update(List<FrameMetrics> instances) {
+ insertMetrics(instances);
+ }
+
+ public void update(double[] values) {
+ insertValues(values);
+ }
+
+ public double getAverage(int id) {
+ int pos = getMetricPosition(id);
+ return mStoredStatistics[pos].getMean();
+ }
+
+ public double getMinimum(int id) {
+ int pos = getMetricPosition(id);
+ return mStoredStatistics[pos].getMin();
+ }
+
+ public double getMaximum(int id) {
+ int pos = getMetricPosition(id);
+ return mStoredStatistics[pos].getMax();
+ }
+
+ public int getMaximumIndex(int id) {
+ int pos = getMetricPosition(id);
+ double[] storedMetrics = mStoredStatistics[pos].getValues();
+ int maxIdx = 0;
+ for (int i = 0; i < storedMetrics.length; i++) {
+ if (storedMetrics[i] >= storedMetrics[maxIdx]) {
+ maxIdx = i;
+ }
+ }
+
+ return maxIdx;
+ }
+
+ public double getMetricAtIndex(int index, int metricId) {
+ return mStoredStatistics[getMetricPosition(metricId)].getElement(index);
+ }
+
+ public double getPercentile(int id, int percentile) {
+ if (percentile > 100) percentile = 100;
+ if (percentile < 0) percentile = 0;
+
+ int metricPos = getMetricPosition(id);
+ return mStoredStatistics[metricPos].getPercentile(percentile);
+ }
+
+ public int getTotalFrameCount() {
+ if (mStoredStatistics.length == 0) {
+ return 0;
+ }
+
+ return (int) mStoredStatistics[0].getN();
+ }
+
+ public int getScore() {
+ SummaryStatistics badFramesStats = new SummaryStatistics();
+
+ int totalFrameCount = getTotalFrameCount();
+ for (int i = 0; i < totalFrameCount; i++) {
+ double totalDuration = getMetricAtIndex(i, FrameMetrics.TOTAL_DURATION);
+ if (totalDuration >= 12) {
+ badFramesStats.addValue(totalDuration);
+ }
+ }
+
+ int length = getSortedJankFrameIndices().length;
+ double jankFrameCount = 100 * length / (double) totalFrameCount;
+
+ System.out.println("Mean: " + badFramesStats.getMean() + " JankP: " + jankFrameCount
+ + " StdDev: " + badFramesStats.getStandardDeviation() +
+ " Count Bad: " + badFramesStats.getN() + " Count Jank: " + length);
+
+ return (int) Math.round(
+ (badFramesStats.getMean()) * jankFrameCount * badFramesStats.getStandardDeviation());
+ }
+
+ public int getJankPenalty() {
+ double total95th = mStoredStatistics[getMetricPosition(FrameMetrics.TOTAL_DURATION)]
+ .getPercentile(95);
+ System.out.println("95: " + total95th);
+ double aboveThreshold = total95th - JANK_PENALTY_THRESHOLD_MS;
+ if (aboveThreshold <= 0) {
+ return 0;
+ }
+
+ if (aboveThreshold > ZERO_SCORE_ABOVE_THRESHOLD_MS) {
+ return BASE_SCORE;
+ }
+
+ return (int) Math.ceil(JANK_PENALTY_PER_MS_ABOVE_THRESHOLD * aboveThreshold);
+ }
+
+ public int getConsistencyBonus() {
+ DescriptiveStatistics totalDurationStats =
+ mStoredStatistics[getMetricPosition(FrameMetrics.TOTAL_DURATION)];
+
+ double standardDeviation = totalDurationStats.getStandardDeviation();
+ if (standardDeviation == 0) {
+ return CONSISTENCY_BONUS_MAX;
+ }
+
+ // 1 / CV of the total duration.
+ double bonus = totalDurationStats.getMean() / standardDeviation;
+ return (int) Math.min(Math.round(bonus), CONSISTENCY_BONUS_MAX);
+ }
+
+ public int[] getSortedJankFrameIndices() {
+ ArrayList<Integer> jankFrameIndices = new ArrayList<>();
+ boolean tripleBuffered = false;
+ int totalFrameCount = getTotalFrameCount();
+ int totalDurationPos = getMetricPosition(FrameMetrics.TOTAL_DURATION);
+
+ for (int i = 0; i < totalFrameCount; i++) {
+ double thisDuration = mStoredStatistics[totalDurationPos].getElement(i);
+ if (!tripleBuffered) {
+ if (thisDuration > FRAME_PERIOD_MS) {
+ tripleBuffered = true;
+ jankFrameIndices.add(i);
+ }
+ } else {
+ if (thisDuration > 2 * FRAME_PERIOD_MS) {
+ tripleBuffered = false;
+ jankFrameIndices.add(i);
+ }
+ }
+ }
+
+ int[] res = new int[jankFrameIndices.size()];
+ int i = 0;
+ for (Integer index : jankFrameIndices) {
+ res[i++] = index;
+ }
+ return res;
+ }
+
+ private int getMetricPosition(int id) {
+ for (int i = 0; i < METRICS.length; i++) {
+ if (id == METRICS[i]) {
+ return i;
+ }
+ }
+
+ return -1;
+ }
+
+ private void insertMetrics(List<FrameMetrics> instances) {
+ for (FrameMetrics frame : instances) {
+ for (int i = 0; i < METRICS.length; i++) {
+ DescriptiveStatistics stats = mStoredStatistics[i];
+ if (stats == null) {
+ stats = new DescriptiveStatistics();
+ mStoredStatistics[i] = stats;
+ }
+
+ mStoredStatistics[i].addValue(frame.getMetric(METRICS[i]) / (double) 1000000);
+ }
+ }
+ }
+
+ private void insertValues(double[] values) {
+ if (values.length != METRICS.length) {
+ throw new IllegalArgumentException("invalid values array");
+ }
+
+ for (int i = 0; i < values.length; i++) {
+ DescriptiveStatistics stats = mStoredStatistics[i];
+ if (stats == null) {
+ stats = new DescriptiveStatistics();
+ mStoredStatistics[i] = stats;
+ }
+
+ mStoredStatistics[i].addValue(values[i]);
+ }
+ }
+ }
diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/synthetic/MemoryActivity.java b/tests/JankBench/app/src/main/java/com/android/benchmark/synthetic/MemoryActivity.java
new file mode 100644
index 0000000..aba16d5
--- /dev/null
+++ b/tests/JankBench/app/src/main/java/com/android/benchmark/synthetic/MemoryActivity.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.benchmark.synthetic;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.TextView;
+
+import com.android.benchmark.R;
+import com.android.benchmark.app.PerfTimeline;
+
+import junit.framework.Test;
+
+
+public class MemoryActivity extends Activity {
+ private TextView mTextStatus;
+ private TextView mTextMin;
+ private TextView mTextMax;
+ private TextView mTextTypical;
+ private PerfTimeline mTimeline;
+
+ TestInterface mTI;
+ int mActiveTest;
+
+ private class SyntheticTestCallback extends TestInterface.TestResultCallback {
+ @Override
+ void onTestResult(int command, float result) {
+ Intent resultIntent = new Intent();
+ resultIntent.putExtra("com.android.benchmark.synthetic.TEST_RESULT", result);
+ setResult(RESULT_OK, resultIntent);
+ finish();
+ }
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_memory);
+
+ mTextStatus = (TextView) findViewById(R.id.textView_status);
+ mTextMin = (TextView) findViewById(R.id.textView_min);
+ mTextMax = (TextView) findViewById(R.id.textView_max);
+ mTextTypical = (TextView) findViewById(R.id.textView_typical);
+
+ mTimeline = (PerfTimeline) findViewById(R.id.mem_timeline);
+
+ mTI = new TestInterface(mTimeline, 2, new SyntheticTestCallback());
+ mTI.mTextMax = mTextMax;
+ mTI.mTextMin = mTextMin;
+ mTI.mTextStatus = mTextStatus;
+ mTI.mTextTypical = mTextTypical;
+
+ mTimeline.mLinesLow = mTI.mLinesLow;
+ mTimeline.mLinesHigh = mTI.mLinesHigh;
+ mTimeline.mLinesValue = mTI.mLinesValue;
+
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ Intent i = getIntent();
+ mActiveTest = i.getIntExtra("test", 0);
+
+ switch (mActiveTest) {
+ case 0:
+ mTI.runMemoryBandwidth();
+ break;
+ case 1:
+ mTI.runMemoryLatency();
+ break;
+ case 2:
+ mTI.runPowerManagement();
+ break;
+ case 3:
+ mTI.runCPUHeatSoak();
+ break;
+ case 4:
+ mTI.runCPUGFlops();
+ break;
+ default:
+ break;
+
+ }
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ // Inflate the menu; this adds items to the action bar if it is present.
+ getMenuInflater().inflate(R.menu.menu_memory, menu);
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ // Handle action bar item clicks here. The action bar will
+ // automatically handle clicks on the Home/Up button, so long
+ // as you specify a parent activity in AndroidManifest.xml.
+ int id = item.getItemId();
+
+ //noinspection SimplifiableIfStatement
+ if (id == R.id.action_settings) {
+ return true;
+ }
+
+ return super.onOptionsItemSelected(item);
+ }
+
+ public void onCpuBandwidth(View v) {
+
+
+ }
+
+
+
+
+}
diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/synthetic/TestInterface.java b/tests/JankBench/app/src/main/java/com/android/benchmark/synthetic/TestInterface.java
new file mode 100644
index 0000000..8f083a2
--- /dev/null
+++ b/tests/JankBench/app/src/main/java/com/android/benchmark/synthetic/TestInterface.java
@@ -0,0 +1,451 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.benchmark.synthetic;
+
+import android.view.View;
+import android.widget.TextView;
+
+import org.apache.commons.math.stat.StatUtils;
+import org.apache.commons.math.stat.descriptive.SummaryStatistics;
+
+import java.util.LinkedList;
+import java.util.Queue;
+
+
+public class TestInterface {
+ native long nInit(long options);
+ native long nDestroy(long b);
+ native float nGetData(long b, float[] data);
+ native boolean nRunPowerManagementTest(long b, long options);
+ native boolean nRunCPUHeatSoakTest(long b, long options);
+
+ native boolean nMemTestStart(long b);
+ native float nMemTestBandwidth(long b, long size);
+ native float nMemTestLatency(long b, long size);
+ native void nMemTestEnd(long b);
+
+ native float nGFlopsTest(long b, long opt);
+
+ public static class TestResultCallback {
+ void onTestResult(int command, float result) { }
+ }
+
+ static {
+ System.loadLibrary("nativebench");
+ }
+
+ float[] mLinesLow;
+ float[] mLinesHigh;
+ float[] mLinesValue;
+ TextView mTextStatus;
+ TextView mTextMin;
+ TextView mTextMax;
+ TextView mTextTypical;
+
+ private View mViewToUpdate;
+
+ private LooperThread mLT;
+
+ TestInterface(View v, int runtimeSeconds, TestResultCallback callback) {
+ int buckets = runtimeSeconds * 1000;
+ mLinesLow = new float[buckets * 4];
+ mLinesHigh = new float[buckets * 4];
+ mLinesValue = new float[buckets * 4];
+ mViewToUpdate = v;
+
+ mLT = new LooperThread(this, callback);
+ mLT.start();
+ }
+
+ static class LooperThread extends Thread {
+ public static final int CommandExit = 1;
+ public static final int TestPowerManagement = 2;
+ public static final int TestMemoryBandwidth = 3;
+ public static final int TestMemoryLatency = 4;
+ public static final int TestHeatSoak = 5;
+ public static final int TestGFlops = 6;
+
+ private volatile boolean mRun = true;
+ private TestInterface mTI;
+ private TestResultCallback mCallback;
+
+ Queue<Integer> mCommandQueue = new LinkedList<Integer>();
+
+ LooperThread(TestInterface ti, TestResultCallback callback) {
+ super("BenchmarkTestThread");
+ mTI = ti;
+ mCallback = callback;
+ }
+
+ void runCommand(int command) {
+ Integer i = Integer.valueOf(command);
+
+ synchronized (this) {
+ mCommandQueue.add(i);
+ notifyAll();
+ }
+ }
+
+ public void run() {
+ long b = mTI.nInit(0);
+ if (b == 0) {
+ return;
+ }
+
+ while (mRun) {
+ int command = 0;
+ synchronized (this) {
+ if (mCommandQueue.isEmpty()) {
+ try {
+ wait();
+ } catch (InterruptedException e) {
+ }
+ }
+
+ if (!mCommandQueue.isEmpty()) {
+ command = mCommandQueue.remove();
+ }
+ }
+
+ switch (command) {
+ case CommandExit:
+ mRun = false;
+ break;
+ case TestPowerManagement:
+ float score = mTI.testPowerManagement(b);
+ mCallback.onTestResult(command, 0);
+ break;
+ case TestMemoryBandwidth:
+ mTI.testCPUMemoryBandwidth(b);
+ break;
+ case TestMemoryLatency:
+ mTI.testCPUMemoryLatency(b);
+ break;
+ case TestHeatSoak:
+ mTI.testCPUHeatSoak(b);
+ break;
+ case TestGFlops:
+ mTI.testCPUGFlops(b);
+ break;
+
+ }
+
+ //mViewToUpdate.post(new Runnable() {
+ // public void run() {
+ // mViewToUpdate.invalidate();
+ //}
+ //});
+ }
+
+ mTI.nDestroy(b);
+ }
+
+ void exit() {
+ mRun = false;
+ }
+ }
+
+ void postTextToView(TextView v, String s) {
+ final TextView tv = v;
+ final String ts = s;
+
+ v.post(new Runnable() {
+ public void run() {
+ tv.setText(ts);
+ }
+ });
+
+ }
+
+ float calcAverage(float[] data) {
+ float total = 0.f;
+ for (int ct=0; ct < data.length; ct++) {
+ total += data[ct];
+ }
+ return total / data.length;
+ }
+
+ void makeGraph(float[] data, float[] lines) {
+ for (int ct = 0; ct < data.length; ct++) {
+ lines[ct * 4 + 0] = (float)ct;
+ lines[ct * 4 + 1] = 500.f - data[ct];
+ lines[ct * 4 + 2] = (float)ct;
+ lines[ct * 4 + 3] = 500.f;
+ }
+ }
+
+ float testPowerManagement(long b) {
+ float[] dat = new float[mLinesLow.length / 4];
+ postTextToView(mTextStatus, "Running single-threaded");
+ nRunPowerManagementTest(b, 1);
+ nGetData(b, dat);
+ makeGraph(dat, mLinesLow);
+ mViewToUpdate.postInvalidate();
+ float avgMin = calcAverage(dat);
+
+ postTextToView(mTextMin, "Single threaded " + avgMin + " per second");
+
+ postTextToView(mTextStatus, "Running multi-threaded");
+ nRunPowerManagementTest(b, 4);
+ nGetData(b, dat);
+ makeGraph(dat, mLinesHigh);
+ mViewToUpdate.postInvalidate();
+ float avgMax = calcAverage(dat);
+ postTextToView(mTextMax, "Multi threaded " + avgMax + " per second");
+
+ postTextToView(mTextStatus, "Running typical");
+ nRunPowerManagementTest(b, 0);
+ nGetData(b, dat);
+ makeGraph(dat, mLinesValue);
+ mViewToUpdate.postInvalidate();
+ float avgTypical = calcAverage(dat);
+
+ float ofIdeal = avgTypical / (avgMax + avgMin) * 200.f;
+ postTextToView(mTextTypical, String.format("Typical mix (50/50) %%%2.0f of ideal", ofIdeal));
+ return ofIdeal * (avgMax + avgMin);
+ }
+
+ float testCPUHeatSoak(long b) {
+ float[] dat = new float[1000];
+ postTextToView(mTextStatus, "Running heat soak test");
+ for (int t = 0; t < 1000; t++) {
+ mLinesLow[t * 4 + 0] = (float)t;
+ mLinesLow[t * 4 + 1] = 498.f;
+ mLinesLow[t * 4 + 2] = (float)t;
+ mLinesLow[t * 4 + 3] = 500.f;
+ }
+
+ float peak = 0.f;
+ float total = 0.f;
+ float dThroughput = 0;
+ float prev = 0;
+ SummaryStatistics stats = new SummaryStatistics();
+ for (int t = 0; t < 1000; t++) {
+ nRunCPUHeatSoakTest(b, 1);
+ nGetData(b, dat);
+
+ float p = calcAverage(dat);
+ if (prev != 0) {
+ dThroughput += (prev - p);
+ }
+
+ prev = p;
+
+ mLinesLow[t * 4 + 1] = 499.f - p;
+ if (peak < p) {
+ peak = p;
+ }
+ for (float f : dat) {
+ stats.addValue(f);
+ }
+
+ total += p;
+
+ mViewToUpdate.postInvalidate();
+ postTextToView(mTextMin, "Peak " + peak + " per second");
+ postTextToView(mTextMax, "Current " + p + " per second");
+ postTextToView(mTextTypical, "Average " + (total / (t + 1)) + " per second");
+ }
+
+
+ float decreaseOverTime = dThroughput / 1000;
+
+ System.out.println("dthroughput/dt: " + decreaseOverTime);
+
+ float score = (float) (stats.getMean() / (stats.getStandardDeviation() * decreaseOverTime));
+
+ postTextToView(mTextStatus, "Score: " + score);
+ return score;
+ }
+
+ void testCPUMemoryBandwidth(long b) {
+ int[] sizeK = {1, 2, 3, 4, 5, 6, 7,
+ 8, 10, 12, 14, 16, 20, 24, 28,
+ 32, 40, 48, 56, 64, 80, 96, 112,
+ 128, 160, 192, 224, 256, 320, 384, 448,
+ 512, 640, 768, 896, 1024, 1280, 1536, 1792,
+ 2048, 2560, 3584, 4096, 5120, 6144, 7168,
+ 8192, 10240, 12288, 14336, 16384
+ };
+ final int subSteps = 15;
+ float[] results = new float[sizeK.length * subSteps];
+
+ nMemTestStart(b);
+
+ float[] dat = new float[1000];
+ postTextToView(mTextStatus, "Running Memory Bandwidth test");
+ for (int t = 0; t < 1000; t++) {
+ mLinesLow[t * 4 + 0] = (float)t;
+ mLinesLow[t * 4 + 1] = 498.f;
+ mLinesLow[t * 4 + 2] = (float)t;
+ mLinesLow[t * 4 + 3] = 500.f;
+ }
+
+ for (int i = 0; i < sizeK.length; i++) {
+ postTextToView(mTextStatus, "Running " + sizeK[i] + " K");
+
+ float rtot = 0.f;
+ for (int j = 0; j < subSteps; j++) {
+ float ret = nMemTestBandwidth(b, sizeK[i] * 1024);
+ rtot += ret;
+ results[i * subSteps + j] = ret;
+ mLinesLow[(i * subSteps + j) * 4 + 1] = 499.f - (results[i*15+j] * 20.f);
+ mViewToUpdate.postInvalidate();
+ }
+ rtot /= subSteps;
+
+ if (sizeK[i] == 2) {
+ postTextToView(mTextMin, "2K " + rtot + " GB/s");
+ }
+ if (sizeK[i] == 128) {
+ postTextToView(mTextMax, "128K " + rtot + " GB/s");
+ }
+ if (sizeK[i] == 8192) {
+ postTextToView(mTextTypical, "8M " + rtot + " GB/s");
+ }
+
+ }
+
+ nMemTestEnd(b);
+ postTextToView(mTextStatus, "Done");
+ }
+
+ void testCPUMemoryLatency(long b) {
+ int[] sizeK = {1, 2, 3, 4, 5, 6, 7,
+ 8, 10, 12, 14, 16, 20, 24, 28,
+ 32, 40, 48, 56, 64, 80, 96, 112,
+ 128, 160, 192, 224, 256, 320, 384, 448,
+ 512, 640, 768, 896, 1024, 1280, 1536, 1792,
+ 2048, 2560, 3584, 4096, 5120, 6144, 7168,
+ 8192, 10240, 12288, 14336, 16384
+ };
+ final int subSteps = 15;
+ float[] results = new float[sizeK.length * subSteps];
+
+ nMemTestStart(b);
+
+ float[] dat = new float[1000];
+ postTextToView(mTextStatus, "Running Memory Latency test");
+ for (int t = 0; t < 1000; t++) {
+ mLinesLow[t * 4 + 0] = (float)t;
+ mLinesLow[t * 4 + 1] = 498.f;
+ mLinesLow[t * 4 + 2] = (float)t;
+ mLinesLow[t * 4 + 3] = 500.f;
+ }
+
+ for (int i = 0; i < sizeK.length; i++) {
+ postTextToView(mTextStatus, "Running " + sizeK[i] + " K");
+
+ float rtot = 0.f;
+ for (int j = 0; j < subSteps; j++) {
+ float ret = nMemTestLatency(b, sizeK[i] * 1024);
+ rtot += ret;
+ results[i * subSteps + j] = ret;
+
+ if (ret > 400.f) ret = 400.f;
+ if (ret < 0.f) ret = 0.f;
+ mLinesLow[(i * subSteps + j) * 4 + 1] = 499.f - ret;
+ //android.util.Log.e("bench", "test bw " + sizeK[i] + " - " + ret);
+ mViewToUpdate.postInvalidate();
+ }
+ rtot /= subSteps;
+
+ if (sizeK[i] == 2) {
+ postTextToView(mTextMin, "2K " + rtot + " ns");
+ }
+ if (sizeK[i] == 128) {
+ postTextToView(mTextMax, "128K " + rtot + " ns");
+ }
+ if (sizeK[i] == 8192) {
+ postTextToView(mTextTypical, "8M " + rtot + " ns");
+ }
+
+ }
+
+ nMemTestEnd(b);
+ postTextToView(mTextStatus, "Done");
+ }
+
+ void testCPUGFlops(long b) {
+ int[] sizeK = {1, 2, 3, 4, 5, 6, 7
+ };
+ final int subSteps = 15;
+ float[] results = new float[sizeK.length * subSteps];
+
+ nMemTestStart(b);
+
+ float[] dat = new float[1000];
+ postTextToView(mTextStatus, "Running Memory Latency test");
+ for (int t = 0; t < 1000; t++) {
+ mLinesLow[t * 4 + 0] = (float)t;
+ mLinesLow[t * 4 + 1] = 498.f;
+ mLinesLow[t * 4 + 2] = (float)t;
+ mLinesLow[t * 4 + 3] = 500.f;
+ }
+
+ for (int i = 0; i < sizeK.length; i++) {
+ postTextToView(mTextStatus, "Running " + sizeK[i] + " K");
+
+ float rtot = 0.f;
+ for (int j = 0; j < subSteps; j++) {
+ float ret = nGFlopsTest(b, sizeK[i] * 1024);
+ rtot += ret;
+ results[i * subSteps + j] = ret;
+
+ if (ret > 400.f) ret = 400.f;
+ if (ret < 0.f) ret = 0.f;
+ mLinesLow[(i * subSteps + j) * 4 + 1] = 499.f - ret;
+ mViewToUpdate.postInvalidate();
+ }
+ rtot /= subSteps;
+
+ if (sizeK[i] == 2) {
+ postTextToView(mTextMin, "2K " + rtot + " ns");
+ }
+ if (sizeK[i] == 128) {
+ postTextToView(mTextMax, "128K " + rtot + " ns");
+ }
+ if (sizeK[i] == 8192) {
+ postTextToView(mTextTypical, "8M " + rtot + " ns");
+ }
+
+ }
+
+ nMemTestEnd(b);
+ postTextToView(mTextStatus, "Done");
+ }
+
+ public void runPowerManagement() {
+ mLT.runCommand(mLT.TestPowerManagement);
+ }
+
+ public void runMemoryBandwidth() {
+ mLT.runCommand(mLT.TestMemoryBandwidth);
+ }
+
+ public void runMemoryLatency() {
+ mLT.runCommand(mLT.TestMemoryLatency);
+ }
+
+ public void runCPUHeatSoak() {
+ mLT.runCommand(mLT.TestHeatSoak);
+ }
+
+ public void runCPUGFlops() {
+ mLT.runCommand(mLT.TestGFlops);
+ }
+}
diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/ui/BitmapUploadActivity.java b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/BitmapUploadActivity.java
new file mode 100644
index 0000000..f6a528a
--- /dev/null
+++ b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/BitmapUploadActivity.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the License);
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an AS IS BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.benchmark.ui;
+
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.support.v7.app.AppCompatActivity;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.view.MotionEvent;
+import android.view.View;
+
+import com.android.benchmark.R;
+import com.android.benchmark.ui.automation.Automator;
+import com.android.benchmark.ui.automation.Interaction;
+
+/**
+ *
+ */
+public class BitmapUploadActivity extends AppCompatActivity {
+ private Automator mAutomator;
+
+ public static class UploadView extends View {
+ private int mColorValue;
+ private Bitmap mBitmap;
+ private final DisplayMetrics mMetrics = new DisplayMetrics();
+ private final Rect mRect = new Rect();
+
+ public UploadView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @SuppressWarnings("unused")
+ public void setColorValue(int colorValue) {
+ if (colorValue == mColorValue) return;
+
+ mColorValue = colorValue;
+
+ // modify the bitmap's color to ensure it's uploaded to the GPU
+ mBitmap.eraseColor(Color.rgb(mColorValue, 255 - mColorValue, 255));
+
+ invalidate();
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+
+ getDisplay().getMetrics(mMetrics);
+ int minDisplayDimen = Math.min(mMetrics.widthPixels, mMetrics.heightPixels);
+ int bitmapSize = Math.min((int) (minDisplayDimen * 0.75), 720);
+ if (mBitmap == null
+ || mBitmap.getWidth() != bitmapSize
+ || mBitmap.getHeight() != bitmapSize) {
+ mBitmap = Bitmap.createBitmap(bitmapSize, bitmapSize, Bitmap.Config.ARGB_8888);
+ }
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ if (mBitmap != null) {
+ mRect.set(0, 0, getWidth(), getHeight());
+ canvas.drawBitmap(mBitmap, null, mRect, null);
+ }
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ // animate color to force bitmap uploads
+ return super.onTouchEvent(event);
+ }
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_bitmap_upload);
+
+ final View uploadRoot = findViewById(R.id.upload_root);
+ uploadRoot.setKeepScreenOn(true);
+ uploadRoot.setOnTouchListener(new View.OnTouchListener() {
+ @Override
+ public boolean onTouch(View view, MotionEvent motionEvent) {
+ UploadView uploadView = (UploadView) findViewById(R.id.upload_view);
+ ObjectAnimator colorValueAnimator =
+ ObjectAnimator.ofInt(uploadView, "colorValue", 0, 255);
+ colorValueAnimator.setRepeatMode(ValueAnimator.REVERSE);
+ colorValueAnimator.setRepeatCount(100);
+ colorValueAnimator.start();
+
+ // animate scene root to guarantee there's a minimum amount of GPU rendering work
+ ObjectAnimator yAnimator = ObjectAnimator.ofFloat(
+ view, "translationY", 0, 100);
+ yAnimator.setRepeatMode(ValueAnimator.REVERSE);
+ yAnimator.setRepeatCount(100);
+ yAnimator.start();
+
+ return true;
+ }
+ });
+
+ final UploadView uploadView = (UploadView) findViewById(R.id.upload_view);
+ final int runId = getIntent().getIntExtra("com.android.benchmark.RUN_ID", 0);
+ final int iteration = getIntent().getIntExtra("com.android.benchmark.ITERATION", -1);
+
+ mAutomator = new Automator("BMUpload", runId, iteration, getWindow(),
+ new Automator.AutomateCallback() {
+ @Override
+ public void onPostAutomate() {
+ Intent result = new Intent();
+ setResult(RESULT_OK, result);
+ finish();
+ }
+
+ @Override
+ public void onAutomate() {
+ int[] coordinates = new int[2];
+ uploadRoot.getLocationOnScreen(coordinates);
+
+ int x = coordinates[0];
+ int y = coordinates[1];
+
+ float width = uploadRoot.getWidth();
+ float height = uploadRoot.getHeight();
+
+ float middleX = (x + width) / 5;
+ float middleY = (y + height) / 5;
+
+ addInteraction(Interaction.newTap(middleX, middleY));
+ }
+ });
+
+ mAutomator.start();
+ }
+
+}
diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/ui/EditTextInputActivity.java b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/EditTextInputActivity.java
new file mode 100644
index 0000000..ea6fb58
--- /dev/null
+++ b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/EditTextInputActivity.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the License);
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an AS IS BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.benchmark.ui;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.v7.app.ActionBar;
+import android.support.v7.app.AppCompatActivity;
+import android.view.KeyEvent;
+import android.widget.EditText;
+
+import com.android.benchmark.R;
+import com.android.benchmark.ui.automation.Automator;
+import com.android.benchmark.ui.automation.Interaction;
+
+public class EditTextInputActivity extends AppCompatActivity {
+
+ private Automator mAutomator;
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ final EditText editText = new EditText(this);
+ final int runId = getIntent().getIntExtra("com.android.benchmark.RUN_ID", 0);
+ final int iteration = getIntent().getIntExtra("com.android.benchmark.ITERATION", -1);
+
+ editText.setWidth(400);
+ editText.setHeight(200);
+ setContentView(editText);
+
+ String testName = getString(R.string.edit_text_input_name);
+
+ ActionBar actionBar = getSupportActionBar();
+ if (actionBar != null) {
+ actionBar.setTitle(testName);
+ }
+
+ mAutomator = new Automator(testName, runId, iteration, getWindow(),
+ new Automator.AutomateCallback() {
+ @Override
+ public void onPostAutomate() {
+ Intent result = new Intent();
+ setResult(RESULT_OK, result);
+ finish();
+ }
+
+ @Override
+ public void onAutomate() {
+
+ int[] coordinates = new int[2];
+ editText.getLocationOnScreen(coordinates);
+
+ int x = coordinates[0];
+ int y = coordinates[1];
+
+ float width = editText.getWidth();
+ float height = editText.getHeight();
+
+ float middleX = (x + width) / 2;
+ float middleY = (y + height) / 2;
+
+ Interaction tap = Interaction.newTap(middleX, middleY);
+ addInteraction(tap);
+
+ int[] alphabet = {
+ KeyEvent.KEYCODE_A,
+ KeyEvent.KEYCODE_B,
+ KeyEvent.KEYCODE_C,
+ KeyEvent.KEYCODE_D,
+ KeyEvent.KEYCODE_E,
+ KeyEvent.KEYCODE_F,
+ KeyEvent.KEYCODE_G,
+ KeyEvent.KEYCODE_H,
+ KeyEvent.KEYCODE_I,
+ KeyEvent.KEYCODE_J,
+ KeyEvent.KEYCODE_K,
+ KeyEvent.KEYCODE_L,
+ KeyEvent.KEYCODE_M,
+ KeyEvent.KEYCODE_N,
+ KeyEvent.KEYCODE_O,
+ KeyEvent.KEYCODE_P,
+ KeyEvent.KEYCODE_Q,
+ KeyEvent.KEYCODE_R,
+ KeyEvent.KEYCODE_S,
+ KeyEvent.KEYCODE_T,
+ KeyEvent.KEYCODE_U,
+ KeyEvent.KEYCODE_V,
+ KeyEvent.KEYCODE_W,
+ KeyEvent.KEYCODE_X,
+ KeyEvent.KEYCODE_Y,
+ KeyEvent.KEYCODE_Z,
+ KeyEvent.KEYCODE_SPACE
+ };
+ Interaction typeAlphabet = Interaction.newKeyInput(new int[] {
+ KeyEvent.KEYCODE_A,
+ KeyEvent.KEYCODE_B,
+ KeyEvent.KEYCODE_C,
+ KeyEvent.KEYCODE_D,
+ KeyEvent.KEYCODE_E,
+ KeyEvent.KEYCODE_F,
+ KeyEvent.KEYCODE_G,
+ KeyEvent.KEYCODE_H,
+ KeyEvent.KEYCODE_I,
+ KeyEvent.KEYCODE_J,
+ KeyEvent.KEYCODE_K,
+ KeyEvent.KEYCODE_L,
+ KeyEvent.KEYCODE_M,
+ KeyEvent.KEYCODE_N,
+ KeyEvent.KEYCODE_O,
+ KeyEvent.KEYCODE_P,
+ KeyEvent.KEYCODE_Q,
+ KeyEvent.KEYCODE_R,
+ KeyEvent.KEYCODE_S,
+ KeyEvent.KEYCODE_T,
+ KeyEvent.KEYCODE_U,
+ KeyEvent.KEYCODE_V,
+ KeyEvent.KEYCODE_W,
+ KeyEvent.KEYCODE_X,
+ KeyEvent.KEYCODE_Y,
+ KeyEvent.KEYCODE_Z,
+ KeyEvent.KEYCODE_SPACE,
+ });
+
+ for (int i = 0; i < 5; i++) {
+ addInteraction(typeAlphabet);
+ }
+ }
+ });
+ mAutomator.start();
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ if (mAutomator != null) {
+ mAutomator.cancel();
+ mAutomator = null;
+ }
+ }
+
+ private String getRunFilename() {
+ StringBuilder builder = new StringBuilder();
+ builder.append(getClass().getSimpleName());
+ builder.append(System.currentTimeMillis());
+ return builder.toString();
+ }
+}
diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/ui/FullScreenOverdrawActivity.java b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/FullScreenOverdrawActivity.java
new file mode 100644
index 0000000..95fce38
--- /dev/null
+++ b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/FullScreenOverdrawActivity.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the License);
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an AS IS BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.benchmark.ui;
+
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.support.v7.app.AppCompatActivity;
+import android.os.Bundle;
+import android.view.MotionEvent;
+import android.view.View;
+
+import com.android.benchmark.R;
+import com.android.benchmark.registry.BenchmarkRegistry;
+import com.android.benchmark.ui.automation.Automator;
+import com.android.benchmark.ui.automation.Interaction;
+
+public class FullScreenOverdrawActivity extends AppCompatActivity {
+
+ private Automator mAutomator;
+
+ private class OverdrawView extends View {
+ Paint paint = new Paint();
+ int mColorValue = 0;
+
+ public OverdrawView(Context context) {
+ super(context);
+ }
+
+ @SuppressWarnings("unused")
+ public void setColorValue(int colorValue) {
+ mColorValue = colorValue;
+ invalidate();
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ ObjectAnimator objectAnimator = ObjectAnimator.ofInt(this, "colorValue", 0, 255);
+ objectAnimator.setRepeatMode(ValueAnimator.REVERSE);
+ objectAnimator.setRepeatCount(100);
+ objectAnimator.start();
+ return super.onTouchEvent(event);
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ paint.setColor(Color.rgb(mColorValue, 255 - mColorValue, 255));
+
+ for (int i = 0; i < 10; i++) {
+ canvas.drawRect(0, 0, getWidth(), getHeight(), paint);
+ }
+ }
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ final OverdrawView overdrawView = new OverdrawView(this);
+ overdrawView.setKeepScreenOn(true);
+ setContentView(overdrawView);
+
+ final int runId = getIntent().getIntExtra("com.android.benchmark.RUN_ID", 0);
+ final int iteration = getIntent().getIntExtra("com.android.benchmark.ITERATION", -1);
+
+ String name = BenchmarkRegistry.getBenchmarkName(this, R.id.benchmark_overdraw);
+
+ mAutomator = new Automator(name, runId, iteration, getWindow(),
+ new Automator.AutomateCallback() {
+ @Override
+ public void onPostAutomate() {
+ Intent result = new Intent();
+ setResult(RESULT_OK, result);
+ finish();
+ }
+
+ @Override
+ public void onAutomate() {
+ int[] coordinates = new int[2];
+ overdrawView.getLocationOnScreen(coordinates);
+
+ int x = coordinates[0];
+ int y = coordinates[1];
+
+ float width = overdrawView.getWidth();
+ float height = overdrawView.getHeight();
+
+ float middleX = (x + width) / 5;
+ float middleY = (y + height) / 5;
+
+ addInteraction(Interaction.newTap(middleX, middleY));
+ }
+ });
+
+ mAutomator.start();
+ }
+}
diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/ui/ImageListViewScrollActivity.java b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/ImageListViewScrollActivity.java
new file mode 100644
index 0000000..4644ea1
--- /dev/null
+++ b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/ImageListViewScrollActivity.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the License);
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an AS IS BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.benchmark.ui;
+
+import android.graphics.Bitmap;
+import android.os.AsyncTask;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.ImageView;
+import android.widget.ListAdapter;
+import android.widget.TextView;
+
+import com.android.benchmark.R;
+
+import java.lang.ref.WeakReference;
+import java.util.HashMap;
+
+public class ImageListViewScrollActivity extends ListViewScrollActivity {
+
+ private static final int LIST_SIZE = 100;
+
+ private static final int[] IMG_RES_ID = new int[]{
+ R.drawable.img1,
+ R.drawable.img2,
+ R.drawable.img3,
+ R.drawable.img4,
+ R.drawable.img1,
+ R.drawable.img2,
+ R.drawable.img3,
+ R.drawable.img4,
+ R.drawable.img1,
+ R.drawable.img2,
+ R.drawable.img3,
+ R.drawable.img4,
+ R.drawable.img1,
+ R.drawable.img2,
+ R.drawable.img3,
+ R.drawable.img4,
+ };
+
+ private static Bitmap[] mBitmapCache = new Bitmap[IMG_RES_ID.length];
+
+ private static final String[] WORDS = Utils.buildStringList(LIST_SIZE);
+
+ private HashMap<View, BitmapWorkerTask> mInFlight = new HashMap<>();
+
+ @Override
+ protected ListAdapter createListAdapter() {
+ return new ImageListAdapter();
+ }
+
+ @Override
+ protected String getName() {
+ return getString(R.string.image_list_view_scroll_name);
+ }
+
+ class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
+ private final WeakReference<ImageView> imageViewReference;
+ private int data = 0;
+ private int cacheIdx = 0;
+ volatile boolean cancelled = false;
+
+ public BitmapWorkerTask(ImageView imageView, int cacheIdx) {
+ // Use a WeakReference to ensure the ImageView can be garbage collected
+ imageViewReference = new WeakReference<>(imageView);
+ this.cacheIdx = cacheIdx;
+ }
+
+ // Decode image in background.
+ @Override
+ protected Bitmap doInBackground(Integer... params) {
+ data = params[0];
+ return Utils.decodeSampledBitmapFromResource(getResources(), data, 100, 100);
+ }
+
+ // Once complete, see if ImageView is still around and set bitmap.
+ @Override
+ protected void onPostExecute(Bitmap bitmap) {
+ if (bitmap != null) {
+ final ImageView imageView = imageViewReference.get();
+ if (imageView != null) {
+ if (!cancelled) {
+ imageView.setImageBitmap(bitmap);
+ }
+ mBitmapCache[cacheIdx] = bitmap;
+ }
+ }
+ }
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ for (int i = 0; i < mBitmapCache.length; i++) {
+ mBitmapCache[i] = null;
+ }
+ }
+
+ class ImageListAdapter extends BaseAdapter {
+
+ @Override
+ public int getCount() {
+ return LIST_SIZE;
+ }
+
+ @Override
+ public Object getItem(int postition) {
+ return null;
+ }
+
+ @Override
+ public long getItemId(int postition) {
+ return postition;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ if (convertView == null) {
+ convertView = LayoutInflater.from(getBaseContext())
+ .inflate(R.layout.image_scroll_list_item, parent, false);
+ }
+
+ ImageView imageView = (ImageView) convertView.findViewById(R.id.image_scroll_image);
+ BitmapWorkerTask inFlight = mInFlight.get(convertView);
+ if (inFlight != null) {
+ inFlight.cancelled = true;
+ mInFlight.remove(convertView);
+ }
+
+ int cacheIdx = position % IMG_RES_ID.length;
+ Bitmap bitmap = mBitmapCache[(cacheIdx)];
+ if (bitmap == null) {
+ BitmapWorkerTask bitmapWorkerTask = new BitmapWorkerTask(imageView, cacheIdx);
+ bitmapWorkerTask.execute(IMG_RES_ID[(cacheIdx)]);
+ mInFlight.put(convertView, bitmapWorkerTask);
+ }
+
+ imageView.setImageBitmap(bitmap);
+
+ TextView textView = (TextView) convertView.findViewById(R.id.image_scroll_text);
+ textView.setText(WORDS[position]);
+
+ return convertView;
+ }
+ }
+}
diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/ui/ListActivityBase.java b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/ListActivityBase.java
new file mode 100644
index 0000000..b973bc7
--- /dev/null
+++ b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/ListActivityBase.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and limitations under the
+ * License.
+ *
+ */
+
+package com.android.benchmark.ui;
+
+import android.app.ActionBar;
+import android.os.Bundle;
+import android.support.v4.app.FragmentManager;
+import android.support.v4.app.ListFragment;
+import android.support.v7.app.AppCompatActivity;
+import android.view.Window;
+import android.widget.ListAdapter;
+
+import com.android.benchmark.R;
+
+/**
+ * Simple list activity base class
+ */
+public abstract class ListActivityBase extends AppCompatActivity {
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_list_fragment);
+
+ ActionBar actionBar = getActionBar();
+ if (actionBar != null) {
+ actionBar.setTitle(getName());
+ }
+
+ if (findViewById(R.id.list_fragment_container) != null) {
+ FragmentManager fm = getSupportFragmentManager();
+ ListFragment listView = new ListFragment();
+ listView.setListAdapter(createListAdapter());
+ fm.beginTransaction().add(R.id.list_fragment_container, listView).commit();
+ }
+ }
+
+ protected abstract ListAdapter createListAdapter();
+ protected abstract String getName();
+}
+
diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/ui/ListViewScrollActivity.java b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/ListViewScrollActivity.java
new file mode 100644
index 0000000..3ffb770
--- /dev/null
+++ b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/ListViewScrollActivity.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and limitations under the
+ * License.
+ *
+ */
+
+package com.android.benchmark.ui;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.v7.app.ActionBar;
+import android.view.FrameMetrics;
+import android.view.MotionEvent;
+import android.widget.ArrayAdapter;
+import android.widget.FrameLayout;
+import android.widget.ListAdapter;
+
+import com.android.benchmark.R;
+import com.android.benchmark.ui.automation.Automator;
+import com.android.benchmark.ui.automation.Interaction;
+
+import java.io.File;
+import java.util.List;
+
+public class ListViewScrollActivity extends ListActivityBase {
+
+ private static final int LIST_SIZE = 400;
+ private static final int INTERACTION_COUNT = 4;
+
+ private Automator mAutomator;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ final int runId = getIntent().getIntExtra("com.android.benchmark.RUN_ID", 0);
+ final int iteration = getIntent().getIntExtra("com.android.benchmark.ITERATION", -1);
+
+ ActionBar actionBar = getSupportActionBar();
+ if (actionBar != null) {
+ actionBar.setTitle(getTitle());
+ }
+
+ mAutomator = new Automator(getName(), runId, iteration, getWindow(),
+ new Automator.AutomateCallback() {
+ @Override
+ public void onPostAutomate() {
+ Intent result = new Intent();
+ setResult(RESULT_OK, result);
+ finish();
+ }
+
+ @Override
+ public void onPostInteraction(List<FrameMetrics> metrics) {}
+
+ @Override
+ public void onAutomate() {
+ FrameLayout v = (FrameLayout) findViewById(R.id.list_fragment_container);
+
+ int[] coordinates = new int[2];
+ v.getLocationOnScreen(coordinates);
+
+ int x = coordinates[0];
+ int y = coordinates[1];
+
+ float width = v.getWidth();
+ float height = v.getHeight();
+
+ float middleX = (x + width) / 5;
+ float middleY = (y + height) / 5;
+
+ Interaction flingUp = Interaction.newFlingUp(middleX, middleY);
+ Interaction flingDown = Interaction.newFlingDown(middleX, middleY);
+
+ for (int i = 0; i < INTERACTION_COUNT; i++) {
+ addInteraction(flingUp);
+ addInteraction(flingDown);
+ }
+ }
+ });
+
+ mAutomator.start();
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ if (mAutomator != null) {
+ mAutomator.cancel();
+ mAutomator = null;
+ }
+ }
+
+ @Override
+ protected ListAdapter createListAdapter() {
+ return new ArrayAdapter<>(this, android.R.layout.simple_list_item_1,
+ Utils.buildStringList(LIST_SIZE));
+ }
+
+ @Override
+ protected String getName() {
+ return getString(R.string.list_view_scroll_name);
+ }
+}
diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/ui/ShadowGridActivity.java b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/ShadowGridActivity.java
new file mode 100644
index 0000000..68f75a3
--- /dev/null
+++ b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/ShadowGridActivity.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.benchmark.ui;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.v4.app.FragmentManager;
+import android.support.v4.app.ListFragment;
+import android.support.v7.app.AppCompatActivity;
+import android.view.View;
+import android.widget.ArrayAdapter;
+import android.widget.ListView;
+
+import com.android.benchmark.R;
+import com.android.benchmark.ui.automation.Automator;
+import com.android.benchmark.ui.automation.Interaction;
+
+public class ShadowGridActivity extends AppCompatActivity {
+ private Automator mAutomator;
+ public static class MyListFragment extends ListFragment {
+ @Override
+ public void onViewCreated(View view, Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+ getListView().setDivider(null);
+ }
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ final int runId = getIntent().getIntExtra("com.android.benchmark.RUN_ID", 0);
+ final int iteration = getIntent().getIntExtra("com.android.benchmark.ITERATION", -1);
+
+ FragmentManager fm = getSupportFragmentManager();
+ if (fm.findFragmentById(android.R.id.content) == null) {
+ ListFragment listFragment = new MyListFragment();
+
+ listFragment.setListAdapter(new ArrayAdapter<>(this,
+ R.layout.card_row, R.id.card_text, Utils.buildStringList(200)));
+ fm.beginTransaction().add(android.R.id.content, listFragment).commit();
+
+ String testName = getString(R.string.shadow_grid_name);
+
+ mAutomator = new Automator(testName, runId, iteration, getWindow(),
+ new Automator.AutomateCallback() {
+ @Override
+ public void onPostAutomate() {
+ Intent result = new Intent();
+ setResult(RESULT_OK, result);
+ finish();
+ }
+
+ @Override
+ public void onAutomate() {
+ ListView v = (ListView) findViewById(android.R.id.list);
+
+ int[] coordinates = new int[2];
+ v.getLocationOnScreen(coordinates);
+
+ int x = coordinates[0];
+ int y = coordinates[1];
+
+ float width = v.getWidth();
+ float height = v.getHeight();
+
+ float middleX = (x + width) / 2;
+ float middleY = (y + height) / 2;
+
+ Interaction flingUp = Interaction.newFlingUp(middleX, middleY);
+ Interaction flingDown = Interaction.newFlingDown(middleX, middleY);
+
+ addInteraction(flingUp);
+ addInteraction(flingDown);
+ addInteraction(flingUp);
+ addInteraction(flingDown);
+ addInteraction(flingUp);
+ addInteraction(flingDown);
+ addInteraction(flingUp);
+ addInteraction(flingDown);
+ }
+ });
+ mAutomator.start();
+ }
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ if (mAutomator != null) {
+ mAutomator.cancel();
+ mAutomator = null;
+ }
+ }
+}
diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/ui/TextScrollActivity.java b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/TextScrollActivity.java
new file mode 100644
index 0000000..fcd168e
--- /dev/null
+++ b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/TextScrollActivity.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the License);
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an AS IS BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.benchmark.ui;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.widget.ArrayAdapter;
+import android.widget.ListAdapter;
+import android.widget.ListView;
+
+import com.android.benchmark.registry.BenchmarkRegistry;
+import com.android.benchmark.ui.automation.Automator;
+import com.android.benchmark.ui.automation.Interaction;
+
+import java.io.File;
+
+public class TextScrollActivity extends ListActivityBase {
+
+ public static final String EXTRA_HIT_RATE = ".TextScrollActivity.EXTRA_HIT_RATE";
+
+ private static final int PARAGRAPH_COUNT = 200;
+
+ private int mHitPercentage = 100;
+ private Automator mAutomator;
+ private String mName;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ mHitPercentage = getIntent().getIntExtra(EXTRA_HIT_RATE,
+ mHitPercentage);
+ super.onCreate(savedInstanceState);
+ final int runId = getIntent().getIntExtra("com.android.benchmark.RUN_ID", 0);
+ final int iteration = getIntent().getIntExtra("com.android.benchmark.ITERATION", -1);
+ final int id = getIntent().getIntExtra(BenchmarkRegistry.EXTRA_ID, -1);
+
+ if (id == -1) {
+ finish();
+ return;
+ }
+
+ mName = BenchmarkRegistry.getBenchmarkName(this, id);
+
+ mAutomator = new Automator(getName(), runId, iteration, getWindow(),
+ new Automator.AutomateCallback() {
+ @Override
+ public void onPostAutomate() {
+ Intent result = new Intent();
+ setResult(RESULT_OK, result);
+ finish();
+ }
+
+ @Override
+ public void onAutomate() {
+ ListView v = (ListView) findViewById(android.R.id.list);
+
+ int[] coordinates = new int[2];
+ v.getLocationOnScreen(coordinates);
+
+ int x = coordinates[0];
+ int y = coordinates[1];
+
+ float width = v.getWidth();
+ float height = v.getHeight();
+
+ float middleX = (x + width) / 2;
+ float middleY = (y + height) / 2;
+
+ Interaction flingUp = Interaction.newFlingUp(middleX, middleY);
+ Interaction flingDown = Interaction.newFlingDown(middleX, middleY);
+
+ addInteraction(flingUp);
+ addInteraction(flingDown);
+ addInteraction(flingUp);
+ addInteraction(flingDown);
+ addInteraction(flingUp);
+ addInteraction(flingDown);
+ addInteraction(flingUp);
+ addInteraction(flingDown);
+ }
+ });
+
+ mAutomator.start();
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ if (mAutomator != null) {
+ mAutomator.cancel();
+ mAutomator = null;
+ }
+ }
+
+ @Override
+ protected ListAdapter createListAdapter() {
+ return new ArrayAdapter<>(this, android.R.layout.simple_list_item_1,
+ Utils.buildParagraphListWithHitPercentage(PARAGRAPH_COUNT, 80));
+ }
+
+ @Override
+ protected String getName() {
+ return mName;
+ }
+}
diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/ui/Utils.java b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/Utils.java
new file mode 100644
index 0000000..39f9206
--- /dev/null
+++ b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/Utils.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the License);
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an AS IS BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.benchmark.ui;
+
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+
+import java.util.Random;
+
+public class Utils {
+
+ private static final int RANDOM_WORD_LENGTH = 10;
+
+ public static String getRandomWord(Random random, int length) {
+ StringBuilder builder = new StringBuilder();
+ for (int i = 0; i < length; i++) {
+ char base = random.nextBoolean() ? 'A' : 'a';
+ char nextChar = (char)(random.nextInt(26) + base);
+ builder.append(nextChar);
+ }
+ return builder.toString();
+ }
+
+ public static String[] buildStringList(int count) {
+ Random random = new Random(0);
+ String[] result = new String[count];
+ for (int i = 0; i < count; i++) {
+ result[i] = getRandomWord(random, RANDOM_WORD_LENGTH);
+ }
+
+ return result;
+ }
+
+ // a small number of strings reused frequently, expected to hit
+ // in the word-granularity text layout cache
+ static final String[] CACHE_HIT_STRINGS = new String[] {
+ "a",
+ "small",
+ "number",
+ "of",
+ "strings",
+ "reused",
+ "frequently"
+ };
+
+ private static final int WORDS_IN_PARAGRAPH = 150;
+
+ // misses are fairly long 'words' to ensure they miss
+ private static final int PARAGRAPH_MISS_MIN_LENGTH = 4;
+ private static final int PARAGRAPH_MISS_MAX_LENGTH = 9;
+
+ static String[] buildParagraphListWithHitPercentage(int paragraphCount, int hitPercentage) {
+ if (hitPercentage < 0 || hitPercentage > 100) throw new IllegalArgumentException();
+
+ String[] strings = new String[paragraphCount];
+ Random random = new Random(0);
+ for (int i = 0; i < strings.length; i++) {
+ StringBuilder result = new StringBuilder();
+ for (int word = 0; word < WORDS_IN_PARAGRAPH; word++) {
+ if (word != 0) {
+ result.append(" ");
+ }
+ if (random.nextInt(100) < hitPercentage) {
+ // add a common word, which is very likely to hit in the cache
+ result.append(CACHE_HIT_STRINGS[random.nextInt(CACHE_HIT_STRINGS.length)]);
+ } else {
+ // construct a random word, which will *most likely* miss
+ int length = PARAGRAPH_MISS_MIN_LENGTH;
+ length += random.nextInt(PARAGRAPH_MISS_MAX_LENGTH - PARAGRAPH_MISS_MIN_LENGTH);
+
+ result.append(getRandomWord(random, length));
+ }
+ }
+ strings[i] = result.toString();
+ }
+
+ return strings;
+ }
+
+
+ public static int calculateInSampleSize(
+ BitmapFactory.Options options, int reqWidth, int reqHeight) {
+ // Raw height and width of image
+ final int height = options.outHeight;
+ final int width = options.outWidth;
+ int inSampleSize = 1;
+
+ if (height > reqHeight || width > reqWidth) {
+
+ final int halfHeight = height / 2;
+ final int halfWidth = width / 2;
+
+ // Calculate the largest inSampleSize value that is a power of 2 and keeps both
+ // height and width larger than the requested height and width.
+ while ((halfHeight / inSampleSize) > reqHeight
+ && (halfWidth / inSampleSize) > reqWidth) {
+ inSampleSize *= 2;
+ }
+ }
+
+ return inSampleSize;
+ }
+
+ public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
+ int reqWidth, int reqHeight) {
+
+ // First decode with inJustDecodeBounds=true to check dimensions
+ final BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inJustDecodeBounds = true;
+ BitmapFactory.decodeResource(res, resId, options);
+
+ // Calculate inSampleSize
+ options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
+
+ // Decode bitmap with inSampleSize set
+ options.inJustDecodeBounds = false;
+ return BitmapFactory.decodeResource(res, resId, options);
+ }
+
+}
diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/ui/automation/Automator.java b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/automation/Automator.java
new file mode 100644
index 0000000..1efd6bc
--- /dev/null
+++ b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/automation/Automator.java
@@ -0,0 +1,269 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and limitations under the
+ * License.
+ *
+ */
+
+package com.android.benchmark.ui.automation;
+
+import android.annotation.TargetApi;
+import android.app.Instrumentation;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.view.FrameMetrics;
+import android.view.MotionEvent;
+import android.view.ViewTreeObserver;
+import android.view.Window;
+
+import com.android.benchmark.results.GlobalResultsStore;
+import com.android.benchmark.results.UiBenchmarkResult;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+
+@TargetApi(24)
+public class Automator extends HandlerThread
+ implements ViewTreeObserver.OnGlobalLayoutListener, CollectorThread.CollectorListener {
+ public static final long FRAME_PERIOD_MILLIS = 16;
+
+ private static final int PRE_READY_STATE_COUNT = 3;
+ private static final String TAG = "Benchmark.Automator";
+ private final AtomicInteger mReadyState;
+
+ private AutomateCallback mCallback;
+ private Window mWindow;
+ private AutomatorHandler mHandler;
+ private CollectorThread mCollectorThread;
+ private int mRunId;
+ private int mIteration;
+ private String mTestName;
+
+ public static class AutomateCallback {
+ public void onAutomate() {}
+ public void onPostInteraction(List<FrameMetrics> metrics) {}
+ public void onPostAutomate() {}
+
+ protected final void addInteraction(Interaction interaction) {
+ if (mInteractions == null) {
+ return;
+ }
+
+ mInteractions.add(interaction);
+ }
+
+ protected final void setInteractions(List<Interaction> interactions) {
+ mInteractions = interactions;
+ }
+
+ private List<Interaction> mInteractions;
+ }
+
+ private static final class AutomatorHandler extends Handler {
+ public static final int MSG_NEXT_INTERACTION = 0;
+ public static final int MSG_ON_AUTOMATE = 1;
+ public static final int MSG_ON_POST_INTERACTION = 2;
+ private final String mTestName;
+ private final int mRunId;
+ private final int mIteration;
+
+ private Instrumentation mInstrumentation;
+ private volatile boolean mCancelled;
+ private CollectorThread mCollectorThread;
+ private AutomateCallback mCallback;
+ private Window mWindow;
+
+ LinkedList<Interaction> mInteractions;
+ private UiBenchmarkResult mResults;
+
+ AutomatorHandler(Looper looper, Window window, CollectorThread collectorThread,
+ AutomateCallback callback, String testName, int runId, int iteration) {
+ super(looper);
+
+ mInstrumentation = new Instrumentation();
+
+ mCallback = callback;
+ mWindow = window;
+ mCollectorThread = collectorThread;
+ mInteractions = new LinkedList<>();
+ mTestName = testName;
+ mRunId = runId;
+ mIteration = iteration;
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ if (mCancelled) {
+ return;
+ }
+
+ switch (msg.what) {
+ case MSG_NEXT_INTERACTION:
+ if (!nextInteraction()) {
+ stopCollector();
+ writeResults();
+ mCallback.onPostAutomate();
+ }
+ break;
+ case MSG_ON_AUTOMATE:
+ mCollectorThread.attachToWindow(mWindow);
+ mCallback.setInteractions(mInteractions);
+ mCallback.onAutomate();
+ postNextInteraction();
+ break;
+ case MSG_ON_POST_INTERACTION:
+ List<FrameMetrics> collectedStats = (List<FrameMetrics>)msg.obj;
+ persistResults(collectedStats);
+ mCallback.onPostInteraction(collectedStats);
+ postNextInteraction();
+ break;
+ }
+ }
+
+ public void cancel() {
+ mCancelled = true;
+ stopCollector();
+ }
+
+ private void stopCollector() {
+ mCollectorThread.quitCollector();
+ }
+
+ private boolean nextInteraction() {
+
+ Interaction interaction = mInteractions.poll();
+ if (interaction != null) {
+ doInteraction(interaction);
+ return true;
+ }
+ return false;
+ }
+
+ private void doInteraction(Interaction interaction) {
+ if (mCancelled) {
+ return;
+ }
+
+ mCollectorThread.markInteractionStart();
+
+ if (interaction.getType() == Interaction.Type.KEY_EVENT) {
+ for (int code : interaction.getKeyCodes()) {
+ if (!mCancelled) {
+ mInstrumentation.sendKeyDownUpSync(code);
+ } else {
+ break;
+ }
+ }
+ } else {
+ for (MotionEvent event : interaction.getEvents()) {
+ if (!mCancelled) {
+ mInstrumentation.sendPointerSync(event);
+ } else {
+ break;
+ }
+ }
+ }
+ }
+
+ protected void postNextInteraction() {
+ final Message msg = obtainMessage(AutomatorHandler.MSG_NEXT_INTERACTION);
+ sendMessage(msg);
+ }
+
+ private void persistResults(List<FrameMetrics> stats) {
+ if (stats.isEmpty()) {
+ return;
+ }
+
+ if (mResults == null) {
+ mResults = new UiBenchmarkResult(stats);
+ } else {
+ mResults.update(stats);
+ }
+ }
+
+ private void writeResults() {
+ GlobalResultsStore.getInstance(mWindow.getContext())
+ .storeRunResults(mTestName, mRunId, mIteration, mResults);
+ }
+ }
+
+ private void initHandler() {
+ mHandler = new AutomatorHandler(getLooper(), mWindow, mCollectorThread, mCallback,
+ mTestName, mRunId, mIteration);
+ mWindow = null;
+ mCallback = null;
+ mCollectorThread = null;
+ mTestName = null;
+ mRunId = 0;
+ mIteration = 0;
+ }
+
+ @Override
+ public final void onGlobalLayout() {
+ if (!mCollectorThread.isAlive()) {
+ mCollectorThread.start();
+ mWindow.getDecorView().getViewTreeObserver().removeOnGlobalLayoutListener(this);
+ mReadyState.decrementAndGet();
+ }
+ }
+
+ @Override
+ public void onCollectorThreadReady() {
+ if (mReadyState.decrementAndGet() == 0) {
+ initHandler();
+ postOnAutomate();
+ }
+ }
+
+ @Override
+ protected void onLooperPrepared() {
+ if (mReadyState.decrementAndGet() == 0) {
+ initHandler();
+ postOnAutomate();
+ }
+ }
+
+ @Override
+ public void onPostInteraction(List<FrameMetrics> stats) {
+ Message m = mHandler.obtainMessage(AutomatorHandler.MSG_ON_POST_INTERACTION, stats);
+ mHandler.sendMessage(m);
+ }
+
+ protected void postOnAutomate() {
+ final Message msg = mHandler.obtainMessage(AutomatorHandler.MSG_ON_AUTOMATE);
+ mHandler.sendMessage(msg);
+ }
+
+ public void cancel() {
+ mHandler.removeMessages(AutomatorHandler.MSG_NEXT_INTERACTION);
+ mHandler.cancel();
+ mHandler = null;
+ }
+
+ public Automator(String testName, int runId, int iteration,
+ Window window, AutomateCallback callback) {
+ super("AutomatorThread");
+
+ mTestName = testName;
+ mRunId = runId;
+ mIteration = iteration;
+ mCallback = callback;
+ mWindow = window;
+ mWindow.getDecorView().getViewTreeObserver().addOnGlobalLayoutListener(this);
+ mCollectorThread = new CollectorThread(this);
+ mReadyState = new AtomicInteger(PRE_READY_STATE_COUNT);
+ }
+}
diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/ui/automation/CollectorThread.java b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/automation/CollectorThread.java
new file mode 100644
index 0000000..806c704
--- /dev/null
+++ b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/automation/CollectorThread.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the License);
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an AS IS BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.benchmark.ui.automation;
+
+import android.annotation.TargetApi;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Message;
+import android.os.SystemClock;
+import android.view.FrameMetrics;
+import android.view.Window;
+
+import java.lang.ref.WeakReference;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ *
+ */
+final class CollectorThread extends HandlerThread {
+ private FrameStatsCollector mCollector;
+ private Window mAttachedWindow;
+ private List<FrameMetrics> mFrameTimingStats;
+ private long mLastFrameTime;
+ private WatchdogHandler mWatchdog;
+ private WeakReference<CollectorListener> mListener;
+
+ private volatile boolean mCollecting;
+
+
+ interface CollectorListener {
+ void onCollectorThreadReady();
+ void onPostInteraction(List<FrameMetrics> stats);
+ }
+
+ private final class WatchdogHandler extends Handler {
+ private static final long SCHEDULE_INTERVAL_MILLIS = 20 * Automator.FRAME_PERIOD_MILLIS;
+
+ private static final int MSG_SCHEDULE = 0;
+
+ @Override
+ public void handleMessage(Message msg) {
+ if (!mCollecting) {
+ return;
+ }
+
+ long currentTime = SystemClock.uptimeMillis();
+ if (mLastFrameTime + SCHEDULE_INTERVAL_MILLIS <= currentTime) {
+ // haven't seen a frame in a while, interaction is probably done
+ mCollecting = false;
+ CollectorListener listener = mListener.get();
+ if (listener != null) {
+ listener.onPostInteraction(mFrameTimingStats);
+ }
+ } else {
+ schedule();
+ }
+ }
+
+ public void schedule() {
+ sendMessageDelayed(obtainMessage(MSG_SCHEDULE), SCHEDULE_INTERVAL_MILLIS);
+ }
+
+ public void deschedule() {
+ removeMessages(MSG_SCHEDULE);
+ }
+ }
+
+ static boolean tripleBuffered = false;
+ static int janks = 0;
+ static int total = 0;
+ @TargetApi(24)
+ private class FrameStatsCollector implements Window.OnFrameMetricsAvailableListener {
+ @Override
+ public void onFrameMetricsAvailable(Window window, FrameMetrics frameMetrics, int dropCount) {
+ if (!mCollecting) {
+ return;
+ }
+ mFrameTimingStats.add(new FrameMetrics(frameMetrics));
+ mLastFrameTime = SystemClock.uptimeMillis();
+ }
+ }
+
+ public CollectorThread(CollectorListener listener) {
+ super("FrameStatsCollectorThread");
+ mFrameTimingStats = new LinkedList<>();
+ mListener = new WeakReference<>(listener);
+ }
+
+ @TargetApi(24)
+ public void attachToWindow(Window window) {
+ if (mAttachedWindow != null) {
+ mAttachedWindow.removeOnFrameMetricsAvailableListener(mCollector);
+ }
+
+ mAttachedWindow = window;
+ window.addOnFrameMetricsAvailableListener(mCollector, new Handler(getLooper()));
+ }
+
+ @TargetApi(24)
+ public synchronized void detachFromWindow() {
+ if (mAttachedWindow != null) {
+ mAttachedWindow.removeOnFrameMetricsAvailableListener(mCollector);
+ }
+
+ mAttachedWindow = null;
+ }
+
+ @TargetApi(24)
+ @Override
+ protected void onLooperPrepared() {
+ super.onLooperPrepared();
+ mCollector = new FrameStatsCollector();
+ mWatchdog = new WatchdogHandler();
+
+ CollectorListener listener = mListener.get();
+ if (listener != null) {
+ listener.onCollectorThreadReady();
+ }
+ }
+
+ public boolean quitCollector() {
+ stopCollecting();
+ detachFromWindow();
+ System.out.println("Jank Percentage: " + (100 * janks/ (double) total) + "%");
+ tripleBuffered = false;
+ total = 0;
+ janks = 0;
+ return quit();
+ }
+
+ void stopCollecting() {
+ if (!mCollecting) {
+ return;
+ }
+
+ mCollecting = false;
+ mWatchdog.deschedule();
+
+
+ }
+
+ public void markInteractionStart() {
+ mLastFrameTime = 0;
+ mFrameTimingStats.clear();
+ mCollecting = true;
+
+ mWatchdog.schedule();
+ }
+}
diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/ui/automation/FrameTimingStats.java b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/automation/FrameTimingStats.java
new file mode 100644
index 0000000..1fd0998
--- /dev/null
+++ b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/automation/FrameTimingStats.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and limitations under the
+ * License.
+ *
+ */
+
+package com.android.benchmark.ui.automation;
+
+import android.support.annotation.IntDef;
+
+import java.io.DataInput;
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.util.Arrays;
+
+public class FrameTimingStats {
+ @IntDef ({
+ Index.FLAGS,
+ Index.INTENDED_VSYNC,
+ Index.VSYNC,
+ Index.OLDEST_INPUT_EVENT,
+ Index.NEWEST_INPUT_EVENT,
+ Index.HANDLE_INPUT_START,
+ Index.ANIMATION_START,
+ Index.PERFORM_TRAVERSALS_START,
+ Index.DRAW_START,
+ Index.SYNC_QUEUED,
+ Index.SYNC_START,
+ Index.ISSUE_DRAW_COMMANDS_START,
+ Index.SWAP_BUFFERS,
+ Index.FRAME_COMPLETED,
+ })
+ public @interface Index {
+ int FLAGS = 0;
+ int INTENDED_VSYNC = 1;
+ int VSYNC = 2;
+ int OLDEST_INPUT_EVENT = 3;
+ int NEWEST_INPUT_EVENT = 4;
+ int HANDLE_INPUT_START = 5;
+ int ANIMATION_START = 6;
+ int PERFORM_TRAVERSALS_START = 7;
+ int DRAW_START = 8;
+ int SYNC_QUEUED = 9;
+ int SYNC_START = 10;
+ int ISSUE_DRAW_COMMANDS_START = 11;
+ int SWAP_BUFFERS = 12;
+ int FRAME_COMPLETED = 13;
+
+ int FRAME_STATS_COUNT = 14; // must always be last
+ }
+
+ private final long[] mStats;
+
+ FrameTimingStats(long[] stats) {
+ mStats = Arrays.copyOf(stats, Index.FRAME_STATS_COUNT);
+ }
+
+ public FrameTimingStats(DataInputStream inputStream) throws IOException {
+ mStats = new long[Index.FRAME_STATS_COUNT];
+ update(inputStream);
+ }
+
+ public void update(DataInputStream inputStream) throws IOException {
+ for (int i = 0; i < mStats.length; i++) {
+ mStats[i] = inputStream.readLong();
+ }
+ }
+
+ public long get(@Index int index) {
+ return mStats[index];
+ }
+
+ public long[] data() {
+ return mStats;
+ }
+}
diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/ui/automation/Interaction.java b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/automation/Interaction.java
new file mode 100644
index 0000000..370fed2
--- /dev/null
+++ b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/automation/Interaction.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and limitations under the
+ * License.
+ *
+ */
+
+package com.android.benchmark.ui.automation;
+
+import android.os.SystemClock;
+import android.support.annotation.IntDef;
+import android.view.MotionEvent;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Encodes a UI interaction as a series of MotionEvents
+ */
+public class Interaction {
+ private static final int STEP_COUNT = 20;
+ // TODO: scale to device display density
+ private static final int DEFAULT_FLING_SIZE_PX = 500;
+ private static final int DEFAULT_FLING_DURATION_MS = 20;
+ private static final int DEFAULT_TAP_DURATION_MS = 20;
+ private List<MotionEvent> mEvents;
+
+ // Interaction parameters
+ private final float[] mXPositions;
+ private final float[] mYPositions;
+ private final long mDuration;
+ private final int[] mKeyCodes;
+ private final @Interaction.Type int mType;
+
+ @IntDef({
+ Interaction.Type.TAP,
+ Interaction.Type.FLING,
+ Interaction.Type.PINCH,
+ Interaction.Type.KEY_EVENT})
+ public @interface Type {
+ int TAP = 0;
+ int FLING = 1;
+ int PINCH = 2;
+ int KEY_EVENT = 3;
+ }
+
+ public static Interaction newFling(float startX, float startY,
+ float endX, float endY, long duration) {
+ return new Interaction(Interaction.Type.FLING, new float[]{startX, endX},
+ new float[]{startY, endY}, duration);
+ }
+
+ public static Interaction newFlingDown(float startX, float startY) {
+ return new Interaction(Interaction.Type.FLING,
+ new float[]{startX, startX},
+ new float[]{startY, startY + DEFAULT_FLING_SIZE_PX}, DEFAULT_FLING_DURATION_MS);
+ }
+
+ public static Interaction newFlingUp(float startX, float startY) {
+ return new Interaction(Interaction.Type.FLING,
+ new float[]{startX, startX}, new float[]{startY, startY - DEFAULT_FLING_SIZE_PX},
+ DEFAULT_FLING_DURATION_MS);
+ }
+
+ public static Interaction newTap(float startX, float startY) {
+ return new Interaction(Interaction.Type.TAP,
+ new float[]{startX, startX}, new float[]{startY, startY},
+ DEFAULT_FLING_DURATION_MS);
+ }
+
+ public static Interaction newKeyInput(int[] keyCodes) {
+ return new Interaction(keyCodes);
+ }
+
+ public List<MotionEvent> getEvents() {
+ switch (mType) {
+ case Type.FLING:
+ mEvents = createInterpolatedEventList(mXPositions, mYPositions, mDuration);
+ break;
+ case Type.PINCH:
+ break;
+ case Type.TAP:
+ mEvents = createInterpolatedEventList(mXPositions, mYPositions, mDuration);
+ break;
+ }
+
+ return mEvents;
+ }
+
+ public int getType() {
+ return mType;
+ }
+
+ public int[] getKeyCodes() {
+ return mKeyCodes;
+ }
+
+ private static List<MotionEvent> createInterpolatedEventList(
+ float[] xPos, float[] yPos, long duration) {
+ long startTime = SystemClock.uptimeMillis() + 100;
+ List<MotionEvent> result = new ArrayList<>();
+
+ float startX = xPos[0];
+ float startY = yPos[0];
+
+ MotionEvent downEvent = MotionEvent.obtain(
+ startTime, startTime, MotionEvent.ACTION_DOWN, startX, startY, 0);
+ result.add(downEvent);
+
+ for (int i = 1; i < xPos.length; i++) {
+ float endX = xPos[i];
+ float endY = yPos[i];
+ float stepX = (endX - startX) / STEP_COUNT;
+ float stepY = (endY - startY) / STEP_COUNT;
+ float stepT = duration / STEP_COUNT;
+
+ for (int j = 0; j < STEP_COUNT; j++) {
+ long deltaT = Math.round(j * stepT);
+ long deltaX = Math.round(j * stepX);
+ long deltaY = Math.round(j * stepY);
+ MotionEvent moveEvent = MotionEvent.obtain(startTime, startTime + deltaT,
+ MotionEvent.ACTION_MOVE, startX + deltaX, startY + deltaY, 0);
+ result.add(moveEvent);
+ }
+
+ startX = endX;
+ startY = endY;
+ }
+
+ float lastX = xPos[xPos.length - 1];
+ float lastY = yPos[yPos.length - 1];
+ MotionEvent lastEvent = MotionEvent.obtain(startTime, startTime + duration,
+ MotionEvent.ACTION_UP, lastX, lastY, 0);
+ result.add(lastEvent);
+
+ return result;
+ }
+
+ private Interaction(@Interaction.Type int type,
+ float[] xPos, float[] yPos, long duration) {
+ mType = type;
+ mXPositions = xPos;
+ mYPositions = yPos;
+ mDuration = duration;
+ mKeyCodes = null;
+ }
+
+ private Interaction(int[] codes) {
+ mKeyCodes = codes;
+ mType = Type.KEY_EVENT;
+ mYPositions = null;
+ mXPositions = null;
+ mDuration = 0;
+ }
+
+ private Interaction(@Interaction.Type int type,
+ List<Float> xPositions, List<Float> yPositions, long duration) {
+ if (xPositions.size() != yPositions.size()) {
+ throw new IllegalArgumentException("must have equal number of x and y positions");
+ }
+
+ int current = 0;
+ mXPositions = new float[xPositions.size()];
+ for (float p : xPositions) {
+ mXPositions[current++] = p;
+ }
+
+ current = 0;
+ mYPositions = new float[yPositions.size()];
+ for (float p : xPositions) {
+ mXPositions[current++] = p;
+ }
+
+ mType = type;
+ mDuration = duration;
+ mKeyCodes = null;
+ }
+}
diff --git a/tests/JankBench/app/src/main/jni/Android.mk b/tests/JankBench/app/src/main/jni/Android.mk
new file mode 100644
index 0000000..8ba874de
--- /dev/null
+++ b/tests/JankBench/app/src/main/jni/Android.mk
@@ -0,0 +1,31 @@
+# Copyright (C) 2015 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH := $(call my-dir)
+LOCAL_SDK_VERSION := 26
+
+include $(CLEAR_VARS)
+
+LOCAL_CFLAGS = -Wno-unused-parameter
+
+LOCAL_MODULE:= libnativebench
+
+LOCAL_SRC_FILES := \
+ Bench.cpp \
+ WorkerPool.cpp \
+ test.cpp
+
+LOCAL_LDLIBS := -llog
+
+include $(BUILD_SHARED_LIBRARY)
diff --git a/tests/JankBench/app/src/main/jni/Application.mk b/tests/JankBench/app/src/main/jni/Application.mk
new file mode 100644
index 0000000..09bc0ac
--- /dev/null
+++ b/tests/JankBench/app/src/main/jni/Application.mk
@@ -0,0 +1,17 @@
+# Copyright (C) 2015 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+APP_ABI := armeabi
+
+APP_MODULES := nativebench
diff --git a/tests/JankBench/app/src/main/jni/Bench.cpp b/tests/JankBench/app/src/main/jni/Bench.cpp
new file mode 100644
index 0000000..fbb4f11
--- /dev/null
+++ b/tests/JankBench/app/src/main/jni/Bench.cpp
@@ -0,0 +1,373 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android/log.h>
+#include <math.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "Bench.h"
+
+
+Bench::Bench()
+{
+ mTimeBucket = NULL;
+ mTimeBuckets = 0;
+ mTimeBucketDivisor = 1;
+
+ mMemLatencyLastSize = 0;
+ mMemDst = NULL;
+ mMemSrc = NULL;
+ mMemLoopCount = 0;
+}
+
+
+Bench::~Bench()
+{
+}
+
+uint64_t Bench::getTimeNanos() const
+{
+ struct timespec t;
+ clock_gettime(CLOCK_MONOTONIC, &t);
+ return t.tv_nsec + ((uint64_t)t.tv_sec * 1000 * 1000 * 1000);
+}
+
+uint64_t Bench::getTimeMillis() const
+{
+ return getTimeNanos() / 1000000;
+}
+
+
+void Bench::testWork(void *usr, uint32_t idx)
+{
+ Bench *b = (Bench *)usr;
+ //__android_log_print(ANDROID_LOG_INFO, "bench", "test %i %p", idx, b);
+
+ float f1 = 0.f;
+ float f2 = 0.f;
+ float f3 = 0.f;
+ float f4 = 0.f;
+
+ float *ipk = b->mIpKernel[idx];
+ volatile float *src = b->mSrcBuf[idx];
+ volatile float *out = b->mOutBuf[idx];
+
+ //__android_log_print(ANDROID_LOG_INFO, "bench", "test %p %p %p", ipk, src, out);
+
+ do {
+
+ for (int i = 0; i < 1024; i++) {
+ f1 += src[i * 4] * ipk[i];
+ f2 += src[i * 4 + 1] * ipk[i];
+ f3 += src[i * 4 + 2] * ipk[i];
+ f4 += sqrtf(f1 + f2 + f3);
+ }
+ out[0] = f1;
+ out[1] = f2;
+ out[2] = f3;
+ out[3] = f4;
+
+ } while (b->incTimeBucket());
+}
+
+bool Bench::initIP() {
+ int workers = mWorkers.getWorkerCount();
+
+ mIpKernel = new float *[workers];
+ mSrcBuf = new float *[workers];
+ mOutBuf = new float *[workers];
+
+ for (int i = 0; i < workers; i++) {
+ mIpKernel[i] = new float[1024];
+ mSrcBuf[i] = new float[4096];
+ mOutBuf[i] = new float[4];
+ }
+
+ return true;
+}
+
+bool Bench::runPowerManagementTest(uint64_t options) {
+ //__android_log_print(ANDROID_LOG_INFO, "bench", "rpmt x %i", options);
+
+ mTimeBucketDivisor = 1000 * 1000; // use ms
+ allocateBuckets(2 * 1000);
+
+ usleep(2 * 1000 * 1000);
+
+ //__android_log_print(ANDROID_LOG_INFO, "bench", "rpmt 2 b %i", mTimeBuckets);
+
+ mTimeStartNanos = getTimeNanos();
+ mTimeEndNanos = mTimeStartNanos + mTimeBuckets * mTimeBucketDivisor;
+ memset(mTimeBucket, 0, sizeof(uint32_t) * mTimeBuckets);
+
+ bool useMT = false;
+
+ //__android_log_print(ANDROID_LOG_INFO, "bench", "rpmt 2.1 b %i", mTimeBuckets);
+ mTimeEndGroupNanos = mTimeStartNanos;
+ do {
+ // Advance 8ms
+ mTimeEndGroupNanos += 8 * 1000 * 1000;
+
+ int threads = useMT ? 1 : 0;
+ useMT = !useMT;
+ if ((options & 0x1f) != 0) {
+ threads = options & 0x1f;
+ }
+
+ //__android_log_print(ANDROID_LOG_INFO, "bench", "threads %i", threads);
+
+ mWorkers.launchWork(testWork, this, threads);
+ } while (mTimeEndGroupNanos <= mTimeEndNanos);
+
+ return true;
+}
+
+bool Bench::allocateBuckets(size_t bucketCount) {
+ if (bucketCount == mTimeBuckets) {
+ return true;
+ }
+
+ if (mTimeBucket != NULL) {
+ delete[] mTimeBucket;
+ mTimeBucket = NULL;
+ }
+
+ mTimeBuckets = bucketCount;
+ if (mTimeBuckets > 0) {
+ mTimeBucket = new uint32_t[mTimeBuckets];
+ }
+
+ return true;
+}
+
+bool Bench::init() {
+ mWorkers.init();
+
+ initIP();
+ //ALOGV("%p Launching thread(s), CPUs %i", mRSC, mWorkers.mCount + 1);
+
+ return true;
+}
+
+bool Bench::incTimeBucket() const {
+ uint64_t time = getTimeNanos();
+ uint64_t bucket = (time - mTimeStartNanos) / mTimeBucketDivisor;
+
+ if (bucket >= mTimeBuckets) {
+ return false;
+ }
+
+ __sync_fetch_and_add(&mTimeBucket[bucket], 1);
+
+ return time < mTimeEndGroupNanos;
+}
+
+void Bench::getData(float *data, size_t count) const {
+ if (count > mTimeBuckets) {
+ count = mTimeBuckets;
+ }
+ for (size_t ct = 0; ct < count; ct++) {
+ data[ct] = (float)mTimeBucket[ct];
+ }
+}
+
+bool Bench::runCPUHeatSoak(uint64_t /* options */)
+{
+ mTimeBucketDivisor = 1000 * 1000; // use ms
+ allocateBuckets(1000);
+
+ mTimeStartNanos = getTimeNanos();
+ mTimeEndNanos = mTimeStartNanos + mTimeBuckets * mTimeBucketDivisor;
+ memset(mTimeBucket, 0, sizeof(uint32_t) * mTimeBuckets);
+
+ mTimeEndGroupNanos = mTimeEndNanos;
+ mWorkers.launchWork(testWork, this, 0);
+ return true;
+}
+
+float Bench::runMemoryBandwidthTest(uint64_t size)
+{
+ uint64_t t1 = getTimeMillis();
+ for (size_t ct = mMemLoopCount; ct > 0; ct--) {
+ memcpy(mMemDst, mMemSrc, size);
+ }
+ double dt = getTimeMillis() - t1;
+ dt /= 1000;
+
+ double bw = ((double)size) * mMemLoopCount / dt;
+ bw /= 1024 * 1024 * 1024;
+
+ float targetTime = 0.2f;
+ if (dt > targetTime) {
+ mMemLoopCount = (size_t)((double)mMemLoopCount / (dt / targetTime));
+ }
+
+ return (float)bw;
+}
+
+float Bench::runMemoryLatencyTest(uint64_t size)
+{
+ //__android_log_print(ANDROID_LOG_INFO, "bench", "latency %i", (int)size);
+ void ** sp = (void **)mMemSrc;
+ size_t maxIndex = size / sizeof(void *);
+ size_t loops = ((maxIndex / 2) & (~3));
+ //loops = 10;
+
+ if (size != mMemLatencyLastSize) {
+ __android_log_print(ANDROID_LOG_INFO, "bench", "latency build %i %i", (int)maxIndex, loops);
+ mMemLatencyLastSize = size;
+ memset((void *)mMemSrc, 0, mMemLatencyLastSize);
+
+ size_t lastIdx = 0;
+ for (size_t ct = 0; ct < loops; ct++) {
+ size_t ni = rand() * rand();
+ ni = ni % maxIndex;
+ while ((sp[ni] != NULL) || (ni == lastIdx)) {
+ ni++;
+ if (ni >= maxIndex) {
+ ni = 1;
+ }
+ // __android_log_print(ANDROID_LOG_INFO, "bench", "gen ni loop %i %i", lastIdx, ni);
+ }
+ // __android_log_print(ANDROID_LOG_INFO, "bench", "gen ct = %i %i %i %p %p", (int)ct, lastIdx, ni, &sp[lastIdx], &sp[ni]);
+ sp[lastIdx] = &sp[ni];
+ lastIdx = ni;
+ }
+ sp[lastIdx] = 0;
+ }
+ //__android_log_print(ANDROID_LOG_INFO, "bench", "latency testing");
+
+ uint64_t t1 = getTimeNanos();
+ for (size_t ct = mMemLoopCount; ct > 0; ct--) {
+ size_t lc = 1;
+ volatile void *p = sp[0];
+ while (p != NULL) {
+ // Unroll once to minimize branching overhead.
+ void **pn = (void **)p;
+ p = pn[0];
+ pn = (void **)p;
+ p = pn[0];
+ }
+ }
+ //__android_log_print(ANDROID_LOG_INFO, "bench", "v %i %i", loops * mMemLoopCount, v);
+
+ double dt = getTimeNanos() - t1;
+ double dts = dt / 1000000000;
+ double lat = dt / (loops * mMemLoopCount);
+ __android_log_print(ANDROID_LOG_INFO, "bench", "latency ret %f", lat);
+
+ float targetTime = 0.2f;
+ if (dts > targetTime) {
+ mMemLoopCount = (size_t)((double)mMemLoopCount / (dts / targetTime));
+ if (mMemLoopCount < 1) {
+ mMemLoopCount = 1;
+ }
+ }
+
+ return (float)lat;
+}
+
+bool Bench::startMemTests()
+{
+ mMemSrc = (uint8_t *)malloc(1024*1024*64);
+ mMemDst = (uint8_t *)malloc(1024*1024*64);
+
+ memset(mMemSrc, 0, 1024*1024*16);
+ memset(mMemDst, 0, 1024*1024*16);
+
+ mMemLoopCount = 1;
+ uint64_t start = getTimeMillis();
+ while((getTimeMillis() - start) < 500) {
+ memcpy(mMemDst, mMemSrc, 1024);
+ mMemLoopCount++;
+ }
+ mMemLatencyLastSize = 0;
+ return true;
+}
+
+void Bench::endMemTests()
+{
+ free(mMemSrc);
+ free(mMemDst);
+ mMemSrc = NULL;
+ mMemDst = NULL;
+ mMemLatencyLastSize = 0;
+}
+
+void Bench::GflopKernelC() {
+ int halfKX = (mGFlop.kernelXSize / 2);
+ for (int x = halfKX; x < (mGFlop.imageXSize - halfKX - 1); x++) {
+ const float * krnPtr = mGFlop.kernelBuffer;
+ float sum = 0.f;
+
+ int srcInc = mGFlop.imageXSize - mGFlop.kernelXSize;
+ const float * srcPtr = &mGFlop.srcBuffer[x - halfKX];
+
+ for (int ix = 0; ix < mGFlop.kernelXSize; ix++) {
+ sum += srcPtr[0] * krnPtr[0];
+ krnPtr++;
+ srcPtr++;
+ }
+
+ float * dstPtr = &mGFlop.dstBuffer[x];
+ dstPtr[0] = sum;
+
+ }
+
+}
+
+void Bench::GflopKernelC_y3() {
+}
+
+float Bench::runGFlopsTest(uint64_t /* options */)
+{
+ mTimeBucketDivisor = 1000 * 1000; // use ms
+ allocateBuckets(1000);
+
+ mTimeStartNanos = getTimeNanos();
+ mTimeEndNanos = mTimeStartNanos + mTimeBuckets * mTimeBucketDivisor;
+ memset(mTimeBucket, 0, sizeof(uint32_t) * mTimeBuckets);
+
+ mTimeEndGroupNanos = mTimeEndNanos;
+ mWorkers.launchWork(testWork, this, 0);
+
+ // Simulate image convolve
+ mGFlop.kernelXSize = 27;
+ mGFlop.imageXSize = 1024 * 1024;
+
+ mGFlop.srcBuffer = (float *)malloc(mGFlop.imageXSize * sizeof(float));
+ mGFlop.dstBuffer = (float *)malloc(mGFlop.imageXSize * sizeof(float));
+ mGFlop.kernelBuffer = (float *)malloc(mGFlop.kernelXSize * sizeof(float));
+
+ double ops = mGFlop.kernelXSize;
+ ops = ops * 2.f - 1.f;
+ ops *= mGFlop.imageXSize;
+
+ uint64_t t1 = getTimeNanos();
+ GflopKernelC();
+ double dt = getTimeNanos() - t1;
+
+ dt /= 1000.f * 1000.f * 1000.f;
+
+ double gflops = ops / dt / 1000000000.f;
+
+ __android_log_print(ANDROID_LOG_INFO, "bench", "v %f %f %f", dt, ops, gflops);
+
+ return (float)gflops;
+}
+
+
diff --git a/tests/JankBench/app/src/main/jni/Bench.h b/tests/JankBench/app/src/main/jni/Bench.h
new file mode 100644
index 0000000..43a9066
--- /dev/null
+++ b/tests/JankBench/app/src/main/jni/Bench.h
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_BENCH_H
+#define ANDROID_BENCH_H
+
+#include <pthread.h>
+
+#include "WorkerPool.h"
+
+#include <string.h>
+
+
+
+class Bench {
+public:
+ Bench();
+ ~Bench();
+
+ struct GFlop {
+ int kernelXSize;
+ //int kernelYSize;
+ int imageXSize;
+ //int imageYSize;
+
+ float *srcBuffer;
+ float *kernelBuffer;
+ float *dstBuffer;
+
+
+ };
+ GFlop mGFlop;
+
+ bool init();
+
+ bool runPowerManagementTest(uint64_t options);
+ bool runCPUHeatSoak(uint64_t options);
+
+ bool startMemTests();
+ void endMemTests();
+ float runMemoryBandwidthTest(uint64_t options);
+ float runMemoryLatencyTest(uint64_t options);
+
+ float runGFlopsTest(uint64_t options);
+
+ void getData(float *data, size_t count) const;
+
+
+ void finish();
+
+ void setPriority(int32_t p);
+ void destroyWorkerThreadResources();
+
+ uint64_t getTimeNanos() const;
+ uint64_t getTimeMillis() const;
+
+ // Adds a work unit completed to the timeline and returns
+ // true if the test is ongoing, false if time is up
+ bool incTimeBucket() const;
+
+
+protected:
+ WorkerPool mWorkers;
+
+ bool mExit;
+ bool mPaused;
+
+ static void testWork(void *usr, uint32_t idx);
+
+private:
+ uint8_t * volatile mMemSrc;
+ uint8_t * volatile mMemDst;
+ size_t mMemLoopCount;
+ size_t mMemLatencyLastSize;
+
+
+ float ** mIpKernel;
+ float * volatile * mSrcBuf;
+ float * volatile * mOutBuf;
+ uint32_t * mTimeBucket;
+
+ uint64_t mTimeStartNanos;
+ uint64_t mTimeEndNanos;
+ uint64_t mTimeBucketDivisor;
+ uint32_t mTimeBuckets;
+
+ uint64_t mTimeEndGroupNanos;
+
+ bool initIP();
+ void GflopKernelC();
+ void GflopKernelC_y3();
+
+ bool allocateBuckets(size_t);
+
+
+};
+
+
+#endif
diff --git a/tests/JankBench/app/src/main/jni/WorkerPool.cpp b/tests/JankBench/app/src/main/jni/WorkerPool.cpp
new file mode 100644
index 0000000..a92ac91
--- /dev/null
+++ b/tests/JankBench/app/src/main/jni/WorkerPool.cpp
@@ -0,0 +1,266 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "WorkerPool.h"
+//#include <atomic>
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <android/log.h>
+
+
+//static pthread_key_t gThreadTLSKey = 0;
+//static uint32_t gThreadTLSKeyCount = 0;
+//static pthread_mutex_t gInitMutex = PTHREAD_MUTEX_INITIALIZER;
+
+
+WorkerPool::Signal::Signal() {
+ mSet = true;
+}
+
+WorkerPool::Signal::~Signal() {
+ pthread_mutex_destroy(&mMutex);
+ pthread_cond_destroy(&mCondition);
+}
+
+bool WorkerPool::Signal::init() {
+ int status = pthread_mutex_init(&mMutex, NULL);
+ if (status) {
+ __android_log_print(ANDROID_LOG_INFO, "bench", "WorkerPool mutex init failure");
+ return false;
+ }
+
+ status = pthread_cond_init(&mCondition, NULL);
+ if (status) {
+ __android_log_print(ANDROID_LOG_INFO, "bench", "WorkerPool condition init failure");
+ pthread_mutex_destroy(&mMutex);
+ return false;
+ }
+
+ return true;
+}
+
+void WorkerPool::Signal::set() {
+ int status;
+
+ status = pthread_mutex_lock(&mMutex);
+ if (status) {
+ __android_log_print(ANDROID_LOG_INFO, "bench", "WorkerPool: error %i locking for set condition.", status);
+ return;
+ }
+
+ mSet = true;
+
+ status = pthread_cond_signal(&mCondition);
+ if (status) {
+ __android_log_print(ANDROID_LOG_INFO, "bench", "WorkerPool: error %i on set condition.", status);
+ }
+
+ status = pthread_mutex_unlock(&mMutex);
+ if (status) {
+ __android_log_print(ANDROID_LOG_INFO, "bench", "WorkerPool: error %i unlocking for set condition.", status);
+ }
+}
+
+bool WorkerPool::Signal::wait(uint64_t timeout) {
+ int status;
+ bool ret = false;
+
+ status = pthread_mutex_lock(&mMutex);
+ if (status) {
+ __android_log_print(ANDROID_LOG_INFO, "bench", "WorkerPool: error %i locking for condition.", status);
+ return false;
+ }
+
+ if (!mSet) {
+ if (!timeout) {
+ status = pthread_cond_wait(&mCondition, &mMutex);
+ } else {
+#if defined(HAVE_PTHREAD_COND_TIMEDWAIT_RELATIVE)
+ status = pthread_cond_timeout_np(&mCondition, &mMutex, timeout / 1000000);
+#else
+ // This is safe it will just make things less reponsive
+ status = pthread_cond_wait(&mCondition, &mMutex);
+#endif
+ }
+ }
+
+ if (!status) {
+ mSet = false;
+ ret = true;
+ } else {
+#ifndef RS_SERVER
+ if (status != ETIMEDOUT) {
+ __android_log_print(ANDROID_LOG_INFO, "bench", "WorkerPool: error %i waiting for condition.", status);
+ }
+#endif
+ }
+
+ status = pthread_mutex_unlock(&mMutex);
+ if (status) {
+ __android_log_print(ANDROID_LOG_INFO, "bench", "WorkerPool: error %i unlocking for condition.", status);
+ }
+
+ return ret;
+}
+
+
+
+WorkerPool::WorkerPool() {
+ mExit = false;
+ mRunningCount = 0;
+ mLaunchCount = 0;
+ mCount = 0;
+ mThreadId = NULL;
+ mNativeThreadId = NULL;
+ mLaunchSignals = NULL;
+ mLaunchCallback = NULL;
+
+
+}
+
+
+WorkerPool::~WorkerPool() {
+__android_log_print(ANDROID_LOG_INFO, "bench", "~wp");
+ mExit = true;
+ mLaunchData = NULL;
+ mLaunchCallback = NULL;
+ mRunningCount = mCount;
+
+ __sync_synchronize();
+ for (uint32_t ct = 0; ct < mCount; ct++) {
+ mLaunchSignals[ct].set();
+ }
+ void *res;
+ for (uint32_t ct = 0; ct < mCount; ct++) {
+ pthread_join(mThreadId[ct], &res);
+ }
+ //rsAssert(__sync_fetch_and_or(&mRunningCount, 0) == 0);
+ free(mThreadId);
+ free(mNativeThreadId);
+ delete[] mLaunchSignals;
+}
+
+bool WorkerPool::init(int threadCount) {
+ int cpu = sysconf(_SC_NPROCESSORS_CONF);
+ if (threadCount > 0) {
+ cpu = threadCount;
+ }
+ if (cpu < 1) {
+ return false;
+ }
+ mCount = (uint32_t)cpu;
+
+ __android_log_print(ANDROID_LOG_INFO, "Bench", "ThreadLaunch %i", mCount);
+
+ mThreadId = (pthread_t *) calloc(mCount, sizeof(pthread_t));
+ mNativeThreadId = (pid_t *) calloc(mCount, sizeof(pid_t));
+ mLaunchSignals = new Signal[mCount];
+ mLaunchCallback = NULL;
+
+ mCompleteSignal.init();
+ mRunningCount = mCount;
+ mLaunchCount = 0;
+ __sync_synchronize();
+
+ pthread_attr_t threadAttr;
+ int status = pthread_attr_init(&threadAttr);
+ if (status) {
+ __android_log_print(ANDROID_LOG_INFO, "bench", "Failed to init thread attribute.");
+ return false;
+ }
+
+ for (uint32_t ct=0; ct < mCount; ct++) {
+ status = pthread_create(&mThreadId[ct], &threadAttr, helperThreadProc, this);
+ if (status) {
+ mCount = ct;
+ __android_log_print(ANDROID_LOG_INFO, "bench", "Created fewer than expected number of threads.");
+ return false;
+ }
+ }
+ while (__sync_fetch_and_or(&mRunningCount, 0) != 0) {
+ usleep(100);
+ }
+
+ pthread_attr_destroy(&threadAttr);
+ return true;
+}
+
+void * WorkerPool::helperThreadProc(void *vwp) {
+ WorkerPool *wp = (WorkerPool *)vwp;
+
+ uint32_t idx = __sync_fetch_and_add(&wp->mLaunchCount, 1);
+
+ wp->mLaunchSignals[idx].init();
+ wp->mNativeThreadId[idx] = gettid();
+
+ while (!wp->mExit) {
+ wp->mLaunchSignals[idx].wait();
+ if (wp->mLaunchCallback) {
+ // idx +1 is used because the calling thread is always worker 0.
+ wp->mLaunchCallback(wp->mLaunchData, idx);
+ }
+ __sync_fetch_and_sub(&wp->mRunningCount, 1);
+ wp->mCompleteSignal.set();
+ }
+
+ //ALOGV("RS helperThread exited %p idx=%i", dc, idx);
+ return NULL;
+}
+
+
+void WorkerPool::waitForAll() const {
+}
+
+void WorkerPool::waitFor(uint64_t) const {
+}
+
+
+
+uint64_t WorkerPool::launchWork(WorkerCallback_t cb, void *usr, int maxThreads) {
+ //__android_log_print(ANDROID_LOG_INFO, "bench", "lw 1");
+ mLaunchData = usr;
+ mLaunchCallback = cb;
+
+ if (maxThreads < 1) {
+ maxThreads = mCount;
+ }
+ if ((uint32_t)maxThreads > mCount) {
+ //__android_log_print(ANDROID_LOG_INFO, "bench", "launchWork max > count", maxThreads, mCount);
+ maxThreads = mCount;
+ }
+
+ //__android_log_print(ANDROID_LOG_INFO, "bench", "lw 2 %i %i %i", maxThreads, mRunningCount, mCount);
+ mRunningCount = maxThreads;
+ __sync_synchronize();
+
+ for (int ct = 0; ct < maxThreads; ct++) {
+ mLaunchSignals[ct].set();
+ }
+
+ //__android_log_print(ANDROID_LOG_INFO, "bench", "lw 3 %i", mRunningCount);
+ while (__sync_fetch_and_or(&mRunningCount, 0) != 0) {
+ //__android_log_print(ANDROID_LOG_INFO, "bench", "lw 3.1 %i", mRunningCount);
+ mCompleteSignal.wait();
+ }
+
+ //__android_log_print(ANDROID_LOG_INFO, "bench", "lw 4 %i", mRunningCount);
+ return 0;
+
+}
+
+
+
diff --git a/tests/JankBench/app/src/main/jni/WorkerPool.h b/tests/JankBench/app/src/main/jni/WorkerPool.h
new file mode 100644
index 0000000..f8985d2
--- /dev/null
+++ b/tests/JankBench/app/src/main/jni/WorkerPool.h
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_WORKER_POOL_H
+#define ANDROID_WORKER_POOL_H
+
+#include <pthread.h>
+#include <string.h>
+
+
+
+class WorkerPool {
+public:
+ WorkerPool();
+ ~WorkerPool();
+
+ typedef void (*WorkerCallback_t)(void *usr, uint32_t idx);
+
+ bool init(int threadCount = -1);
+ int getWorkerCount() const {return mCount;}
+
+ void waitForAll() const;
+ void waitFor(uint64_t) const;
+ uint64_t launchWork(WorkerCallback_t cb, void *usr, int maxThreads = -1);
+
+
+
+
+protected:
+ class Signal {
+ public:
+ Signal();
+ ~Signal();
+
+ bool init();
+ void set();
+
+ // returns true if the signal occured
+ // false for timeout
+ bool wait(uint64_t timeout = 0);
+
+ protected:
+ bool mSet;
+ pthread_mutex_t mMutex;
+ pthread_cond_t mCondition;
+ };
+
+ bool mExit;
+ volatile int mRunningCount;
+ volatile int mLaunchCount;
+ uint32_t mCount;
+ pthread_t *mThreadId;
+ pid_t *mNativeThreadId;
+ Signal mCompleteSignal;
+ Signal *mLaunchSignals;
+ WorkerCallback_t mLaunchCallback;
+ void *mLaunchData;
+
+
+
+
+private:
+ //static void * threadProc(void *);
+ static void * helperThreadProc(void *);
+
+
+};
+
+
+#endif
diff --git a/tests/JankBench/app/src/main/jni/test.cpp b/tests/JankBench/app/src/main/jni/test.cpp
new file mode 100644
index 0000000..e163daa
--- /dev/null
+++ b/tests/JankBench/app/src/main/jni/test.cpp
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+//#include <fcntl.h>
+//#include <unistd.h>
+#include <math.h>
+#include <inttypes.h>
+#include <time.h>
+#include <android/log.h>
+
+#include "jni.h"
+#include "Bench.h"
+
+#define FUNC(name) Java_com_android_benchmark_synthetic_TestInterface_##name
+
+static uint64_t GetTime() {
+ struct timespec t;
+ clock_gettime(CLOCK_MONOTONIC, &t);
+ return t.tv_nsec + ((uint64_t)t.tv_sec * 1000 * 1000 * 1000);
+}
+
+extern "C" {
+
+jlong Java_com_android_benchmark_synthetic_TestInterface_nInit(JNIEnv *_env, jobject _this, jlong options) {
+ Bench *b = new Bench();
+ bool ret = b->init();
+
+ if (ret) {
+ return (jlong)b;
+ }
+
+ delete b;
+ return 0;
+}
+
+void Java_com_android_benchmark_synthetic_TestInterface_nDestroy(JNIEnv *_env, jobject _this, jlong _b) {
+ Bench *b = (Bench *)_b;
+
+ delete b;
+}
+
+jboolean Java_com_android_benchmark_synthetic_TestInterface_nRunPowerManagementTest(
+ JNIEnv *_env, jobject _this, jlong _b, jlong options) {
+ Bench *b = (Bench *)_b;
+ return b->runPowerManagementTest(options);
+}
+
+jboolean Java_com_android_benchmark_synthetic_TestInterface_nRunCPUHeatSoakTest(
+ JNIEnv *_env, jobject _this, jlong _b, jlong options) {
+ Bench *b = (Bench *)_b;
+ return b->runCPUHeatSoak(options);
+}
+
+float Java_com_android_benchmark_synthetic_TestInterface_nGetData(
+ JNIEnv *_env, jobject _this, jlong _b, jfloatArray data) {
+ Bench *b = (Bench *)_b;
+
+ jsize len = _env->GetArrayLength(data);
+ float * ptr = _env->GetFloatArrayElements(data, 0);
+
+ b->getData(ptr, len);
+
+ _env->ReleaseFloatArrayElements(data, (jfloat *)ptr, 0);
+
+ return 0;
+}
+
+jboolean Java_com_android_benchmark_synthetic_TestInterface_nMemTestStart(
+ JNIEnv *_env, jobject _this, jlong _b) {
+ Bench *b = (Bench *)_b;
+ return b->startMemTests();
+}
+
+float Java_com_android_benchmark_synthetic_TestInterface_nMemTestBandwidth(
+ JNIEnv *_env, jobject _this, jlong _b, jlong opt) {
+ Bench *b = (Bench *)_b;
+ return b->runMemoryBandwidthTest(opt);
+}
+
+float Java_com_android_benchmark_synthetic_TestInterface_nGFlopsTest(
+ JNIEnv *_env, jobject _this, jlong _b, jlong opt) {
+ Bench *b = (Bench *)_b;
+ return b->runGFlopsTest(opt);
+}
+
+float Java_com_android_benchmark_synthetic_TestInterface_nMemTestLatency(
+ JNIEnv *_env, jobject _this, jlong _b, jlong opt) {
+ Bench *b = (Bench *)_b;
+ return b->runMemoryLatencyTest(opt);
+}
+
+void Java_com_android_benchmark_synthetic_TestInterface_nMemTestEnd(
+ JNIEnv *_env, jobject _this, jlong _b) {
+ Bench *b = (Bench *)_b;
+ b->endMemTests();
+}
+
+float Java_com_android_benchmark_synthetic_TestInterface_nMemoryTest(
+ JNIEnv *_env, jobject _this, jint subtest) {
+
+ uint8_t * volatile m1 = (uint8_t *)malloc(1024*1024*64);
+ uint8_t * m2 = (uint8_t *)malloc(1024*1024*64);
+
+ memset(m1, 0, 1024*1024*16);
+ memset(m2, 0, 1024*1024*16);
+
+ //__android_log_print(ANDROID_LOG_INFO, "bench", "test %i %p %p", subtest, m1, m2);
+
+
+ size_t loopCount = 0;
+ uint64_t start = GetTime();
+ while((GetTime() - start) < 1000000000) {
+ memcpy(m1, m2, subtest);
+ loopCount++;
+ }
+ if (loopCount == 0) {
+ loopCount = 1;
+ }
+
+ size_t count = loopCount;
+ uint64_t t1 = GetTime();
+ while (loopCount > 0) {
+ memcpy(m1, m2, subtest);
+ loopCount--;
+ }
+ uint64_t t2 = GetTime();
+
+ double dt = t2 - t1;
+ dt /= 1000 * 1000 * 1000;
+ double bw = ((double)subtest) * count / dt;
+
+ bw /= 1024 * 1024 * 1024;
+
+ __android_log_print(ANDROID_LOG_INFO, "bench", "size %i, bw %f", subtest, bw);
+
+ free (m1);
+ free (m2);
+ return (float)bw;
+}
+
+jlong Java_com_android_benchmark_synthetic_MemoryAvailableLoad1_nMemTestMalloc(
+ JNIEnv *_env, jobject _this, jint bytes) {
+ uint8_t *p = (uint8_t *)malloc(bytes);
+ memset(p, 0, bytes);
+ return (jlong)p;
+}
+
+void Java_com_android_benchmark_synthetic_MemoryAvailableLoad1_nMemTestFree(
+ JNIEnv *_env, jobject _this, jlong ptr) {
+ free((void *)ptr);
+}
+
+jlong Java_com_android_benchmark_synthetic_MemoryAvailableLoad2_nMemTestMalloc(
+ JNIEnv *_env, jobject _this, jint bytes) {
+ return Java_com_android_benchmark_synthetic_MemoryAvailableLoad1_nMemTestMalloc(_env, _this, bytes);
+}
+
+void Java_com_android_benchmark_synthetic_MemoryAvailableLoad2_nMemTestFree(
+ JNIEnv *_env, jobject _this, jlong ptr) {
+ Java_com_android_benchmark_synthetic_MemoryAvailableLoad1_nMemTestFree(_env, _this, ptr);
+}
+
+}; // extern "C"
diff --git a/tests/JankBench/app/src/main/res/drawable/ic_play.png b/tests/JankBench/app/src/main/res/drawable/ic_play.png
new file mode 100644
index 0000000..13ed283
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/drawable/ic_play.png
Binary files differ
diff --git a/tests/JankBench/app/src/main/res/drawable/img1.jpg b/tests/JankBench/app/src/main/res/drawable/img1.jpg
new file mode 100644
index 0000000..33c1fed
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/drawable/img1.jpg
Binary files differ
diff --git a/tests/JankBench/app/src/main/res/drawable/img2.jpg b/tests/JankBench/app/src/main/res/drawable/img2.jpg
new file mode 100644
index 0000000..1ea97f2
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/drawable/img2.jpg
Binary files differ
diff --git a/tests/JankBench/app/src/main/res/drawable/img3.jpg b/tests/JankBench/app/src/main/res/drawable/img3.jpg
new file mode 100644
index 0000000..ff99269
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/drawable/img3.jpg
Binary files differ
diff --git a/tests/JankBench/app/src/main/res/drawable/img4.jpg b/tests/JankBench/app/src/main/res/drawable/img4.jpg
new file mode 100644
index 0000000..d9cbd2f
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/drawable/img4.jpg
Binary files differ
diff --git a/tests/JankBench/app/src/main/res/layout/activity_bitmap_upload.xml b/tests/JankBench/app/src/main/res/layout/activity_bitmap_upload.xml
new file mode 100644
index 0000000..6b3c899
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/layout/activity_bitmap_upload.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/upload_root"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:padding="10dp"
+ android:clipToPadding="false">
+ <android.support.v7.widget.CardView
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="1">
+ <view class="com.android.benchmark.ui.BitmapUploadActivity$UploadView"
+ android:id="@+id/upload_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"/>
+ </android.support.v7.widget.CardView>
+
+ <android.support.v4.widget.Space
+ android:layout_height="10dp"
+ android:layout_width="match_parent" />
+
+ <android.support.v7.widget.CardView
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="1" />
+
+ <android.support.v4.widget.Space
+ android:layout_height="10dp"
+ android:layout_width="match_parent" />
+
+ <android.support.v7.widget.CardView
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="1" />
+</LinearLayout>
\ No newline at end of file
diff --git a/tests/JankBench/app/src/main/res/layout/activity_home.xml b/tests/JankBench/app/src/main/res/layout/activity_home.xml
new file mode 100644
index 0000000..c4f4299
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/layout/activity_home.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and limitations under the
+ ~ License.
+ ~
+ -->
+
+<android.support.design.widget.CoordinatorLayout 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"
+ android:fitsSystemWindows="true"
+ tools:context=".app.HomeActivity">
+
+ <android.support.design.widget.AppBarLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:theme="@style/AppTheme.AppBarOverlay">
+
+ <android.support.v7.widget.Toolbar
+ android:id="@+id/toolbar"
+ android:layout_width="match_parent"
+ android:layout_height="?attr/actionBarSize"
+ android:background="?attr/colorPrimary"
+ app:popupTheme="@style/AppTheme.PopupOverlay" />
+
+ </android.support.design.widget.AppBarLayout>
+
+ <include layout="@layout/content_main" />
+
+</android.support.design.widget.CoordinatorLayout>
diff --git a/tests/JankBench/app/src/main/res/layout/activity_list_fragment.xml b/tests/JankBench/app/src/main/res/layout/activity_list_fragment.xml
new file mode 100644
index 0000000..0aaadde
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/layout/activity_list_fragment.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and limitations under the
+ ~ License.
+ ~
+ -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:paddingBottom="@dimen/activity_vertical_margin"
+ android:paddingLeft="@dimen/activity_horizontal_margin"
+ android:paddingRight="@dimen/activity_horizontal_margin"
+ android:paddingTop="@dimen/activity_vertical_margin"
+ tools:context=".app.HomeActivity"
+ android:orientation="vertical">
+
+ <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/list_fragment_container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+
+</LinearLayout>
diff --git a/tests/JankBench/app/src/main/res/layout/activity_memory.xml b/tests/JankBench/app/src/main/res/layout/activity_memory.xml
new file mode 100644
index 0000000..fd5cadc
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/layout/activity_memory.xml
@@ -0,0 +1,49 @@
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
+ android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
+ android:paddingRight="@dimen/activity_horizontal_margin"
+ android:paddingTop="@dimen/activity_vertical_margin"
+ android:paddingBottom="@dimen/activity_vertical_margin"
+ tools:context="com.android.benchmark.synthetic.MemoryActivity">
+
+ <LinearLayout
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceLarge"
+ android:text="Large Text"
+ android:id="@+id/textView_status" />
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:text=""
+ android:id="@+id/textView_min" />
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:text=""
+ android:id="@+id/textView_max" />
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:text=""
+ android:id="@+id/textView_typical" />
+
+ <view
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ class="com.android.benchmark.app.PerfTimeline"
+ android:id="@+id/mem_timeline" />
+
+ </LinearLayout>
+</RelativeLayout>
diff --git a/tests/JankBench/app/src/main/res/layout/activity_running_list.xml b/tests/JankBench/app/src/main/res/layout/activity_running_list.xml
new file mode 100644
index 0000000..7b7b930
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/layout/activity_running_list.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical" android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:paddingBottom="@dimen/activity_vertical_margin"
+ android:paddingLeft="@dimen/activity_horizontal_margin"
+ android:paddingRight="@dimen/activity_horizontal_margin"
+ android:paddingTop="@dimen/activity_vertical_margin"
+ tools:context=".app.HomeActivity"
+ android:orientation="vertical">
+
+ <TextView
+ android:id="@+id/score_text_view"
+ android:textSize="20sp"
+ android:textStyle="bold"
+ android:layout_width="match_parent"
+ android:layout_height="30dp"
+ />
+
+ <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/list_fragment_container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+
+ </LinearLayout>
+</LinearLayout>
\ No newline at end of file
diff --git a/tests/JankBench/app/src/main/res/layout/benchmark_list_group_row.xml b/tests/JankBench/app/src/main/res/layout/benchmark_list_group_row.xml
new file mode 100644
index 0000000..5375dbc
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/layout/benchmark_list_group_row.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and limitations under the
+ ~ License.
+ ~
+ -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical" android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <TextView
+ android:id="@+id/group_name"
+ android:paddingLeft="?android:attr/expandableListPreferredItemPaddingLeft"
+ android:textSize="17dp"
+ android:paddingTop="10dp"
+ android:paddingBottom="10dp"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/tests/JankBench/app/src/main/res/layout/benchmark_list_item.xml b/tests/JankBench/app/src/main/res/layout/benchmark_list_item.xml
new file mode 100644
index 0000000..5282e14
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/layout/benchmark_list_item.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and limitations under the
+ ~ License.
+ ~
+ -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="horizontal"
+ android:paddingLeft="?android:expandableListPreferredChildPaddingLeft"
+ android:layout_width="match_parent"
+ android:layout_height="55dip">
+
+
+ <CheckBox
+ android:id="@+id/benchmark_enable_checkbox"
+ android:checked="true"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <TextView
+ android:id="@+id/benchmark_name"
+ android:textSize="17dp"
+ android:paddingLeft="10dp"
+ android:paddingTop="10dp"
+ android:paddingBottom="10dp"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/tests/JankBench/app/src/main/res/layout/card_row.xml b/tests/JankBench/app/src/main/res/layout/card_row.xml
new file mode 100644
index 0000000..215f9df
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/layout/card_row.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="100dp"
+ android:paddingStart="10dp"
+ android:paddingEnd="10dp"
+ android:paddingTop="5dp"
+ android:paddingBottom="5dp"
+ android:clipToPadding="false"
+ android:background="@null">
+ <android.support.v7.widget.CardView
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="1">
+ <TextView
+ android:id="@+id/card_text"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"/>
+ </android.support.v7.widget.CardView>
+
+ <android.support.v4.widget.Space
+ android:layout_height="match_parent"
+ android:layout_width="10dp" />
+
+ <android.support.v7.widget.CardView
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="1" />
+</LinearLayout>
\ No newline at end of file
diff --git a/tests/JankBench/app/src/main/res/layout/content_main.xml b/tests/JankBench/app/src/main/res/layout/content_main.xml
new file mode 100644
index 0000000..201bd66a
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/layout/content_main.xml
@@ -0,0 +1,26 @@
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and limitations under the
+ ~ License.
+ ~
+ -->
+
+<fragment 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:id="@+id/fragment_start_button"
+ android:name="com.android.benchmark.app.BenchmarkDashboardFragment"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ app:layout_behavior="@string/appbar_scrolling_view_behavior"
+ tools:layout="@layout/fragment_dashboard" />
+
diff --git a/tests/JankBench/app/src/main/res/layout/fragment_dashboard.xml b/tests/JankBench/app/src/main/res/layout/fragment_dashboard.xml
new file mode 100644
index 0000000..f3100c7
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/layout/fragment_dashboard.xml
@@ -0,0 +1,91 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and limitations under the
+ ~ License.
+ ~
+ -->
+
+<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/main_content"
+ android:layout_width="match_parent"
+ android:layout_height="fill_parent"
+ android:fitsSystemWindows="true">
+
+ <android.support.design.widget.AppBarLayout
+ android:id="@+id/appbar"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/detail_backdrop_height"
+ android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
+ android:fitsSystemWindows="true">
+
+ <android.support.design.widget.CollapsingToolbarLayout
+ android:id="@+id/collapsing_toolbar"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ app:layout_scrollFlags="scroll"
+ android:fitsSystemWindows="true"
+ app:contentScrim="?attr/colorPrimary"
+ app:expandedTitleMarginStart="48dp"
+ app:expandedTitleMarginEnd="64dp">
+
+ <android.support.v7.widget.Toolbar
+ android:id="@+id/toolbar"
+ android:layout_width="match_parent"
+ android:layout_height="?attr/actionBarSize"
+ app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
+ app:layout_collapseMode="parallax" />
+
+ <ImageView
+ android:id="@+id/backdrop"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:src="@mipmap/ic_launcher"
+ android:scaleType="centerCrop"
+ android:fitsSystemWindows="true"
+ app:layout_collapseMode="parallax" />
+
+ </android.support.design.widget.CollapsingToolbarLayout>
+
+ </android.support.design.widget.AppBarLayout>
+
+ <android.support.v4.widget.NestedScrollView
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ app:layout_behavior="@string/appbar_scrolling_view_behavior">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical" >
+
+ <ExpandableListView
+ android:id="@+id/test_list"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+
+ </LinearLayout>
+
+ </android.support.v4.widget.NestedScrollView>
+
+ <android.support.design.widget.FloatingActionButton
+ android:id="@+id/start_button"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:src="@drawable/ic_play"
+ app:layout_anchor="@id/appbar"
+ app:layout_anchorGravity="bottom|right|end"
+ android:layout_margin="@dimen/fab_margin"
+ android:clickable="true"/>
+
+</android.support.design.widget.CoordinatorLayout>
diff --git a/tests/JankBench/app/src/main/res/layout/fragment_ui_results_detail.xml b/tests/JankBench/app/src/main/res/layout/fragment_ui_results_detail.xml
new file mode 100644
index 0000000..74d9891
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/layout/fragment_ui_results_detail.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical" android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <ExpandableListView
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/tests/JankBench/app/src/main/res/layout/image_scroll_list_item.xml b/tests/JankBench/app/src/main/res/layout/image_scroll_list_item.xml
new file mode 100644
index 0000000..c1662ea
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/layout/image_scroll_list_item.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <ImageView
+ android:id="@+id/image_scroll_image"
+ android:scaleType="centerCrop"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
+
+ <TextView
+ android:id="@+id/image_scroll_text"
+ android:layout_gravity="right"
+ android:textSize="12sp"
+ android:paddingLeft="20dp"
+ android:layout_width="100dp"
+ android:layout_height="wrap_content" />
+</LinearLayout>
diff --git a/tests/JankBench/app/src/main/res/layout/results_list_item.xml b/tests/JankBench/app/src/main/res/layout/results_list_item.xml
new file mode 100644
index 0000000..f38b147
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/layout/results_list_item.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="horizontal" android:layout_width="match_parent"
+ android:padding="8dp"
+ android:layout_height="match_parent">
+
+ <TextView
+ android:id="@+id/result_name"
+ android:textSize="16sp"
+ android:layout_gravity="left"
+ android:layout_width="200dp"
+ android:layout_height="wrap_content" />
+
+ <TextView
+ android:id="@+id/result_value"
+ android:textSize="16sp"
+ android:layout_gravity="right"
+ android:layout_width="200dp"
+ android:layout_height="wrap_content" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/tests/JankBench/app/src/main/res/layout/running_benchmark_list_item.xml b/tests/JankBench/app/src/main/res/layout/running_benchmark_list_item.xml
new file mode 100644
index 0000000..8a9d015
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/layout/running_benchmark_list_item.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and limitations under the
+ ~ License.
+ ~
+ -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical" android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <TextView
+ android:id="@+id/benchmark_name"
+ android:textSize="17sp"
+ android:paddingLeft="?android:listPreferredItemPaddingLeft"
+ android:paddingTop="10dp"
+ android:paddingBottom="10dp"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/tests/JankBench/app/src/main/res/menu/menu_main.xml b/tests/JankBench/app/src/main/res/menu/menu_main.xml
new file mode 100644
index 0000000..1633acd
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/menu/menu_main.xml
@@ -0,0 +1,26 @@
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and limitations under the
+ ~ License.
+ ~
+ -->
+
+<menu 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"
+ tools:context=".app.HomeActivity">
+ <item
+ android:id="@+id/action_settings"
+ android:orderInCategory="100"
+ android:title="@string/action_export"
+ app:showAsAction="never" />
+</menu>
diff --git a/tests/JankBench/app/src/main/res/menu/menu_memory.xml b/tests/JankBench/app/src/main/res/menu/menu_memory.xml
new file mode 100644
index 0000000..f2df7c9
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/menu/menu_memory.xml
@@ -0,0 +1,5 @@
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools" tools:context="com.android.benchmark.Memory">
+ <item android:id="@+id/action_settings" android:title="@string/action_export"
+ android:orderInCategory="100" />
+</menu>
diff --git a/tests/JankBench/app/src/main/res/mipmap-hdpi/ic_launcher.png b/tests/JankBench/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..cde69bc
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/mipmap-hdpi/ic_launcher.png
Binary files differ
diff --git a/tests/JankBench/app/src/main/res/mipmap-mdpi/ic_launcher.png b/tests/JankBench/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..c133a0c
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/mipmap-mdpi/ic_launcher.png
Binary files differ
diff --git a/tests/JankBench/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/tests/JankBench/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..bfa42f0
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/mipmap-xhdpi/ic_launcher.png
Binary files differ
diff --git a/tests/JankBench/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/tests/JankBench/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..324e72c
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/tests/JankBench/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/tests/JankBench/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..aee44e1
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Binary files differ
diff --git a/tests/JankBench/app/src/main/res/values-v21/styles.xml b/tests/JankBench/app/src/main/res/values-v21/styles.xml
new file mode 100644
index 0000000..99ed094
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/values-v21/styles.xml
@@ -0,0 +1,25 @@
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and limitations under the
+ ~ License.
+ ~
+ -->
+
+<resources>
+
+ <style name="AppTheme.NoActionBar">
+ <item name="windowActionBar">false</item>
+ <item name="windowNoTitle">true</item>
+ <item name="android:windowDrawsSystemBarBackgrounds">true</item>
+ <item name="android:statusBarColor">@android:color/transparent</item>
+ </style>
+</resources>
diff --git a/tests/JankBench/app/src/main/res/values-w820dp/dimens.xml b/tests/JankBench/app/src/main/res/values-w820dp/dimens.xml
new file mode 100644
index 0000000..e783e5d
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/values-w820dp/dimens.xml
@@ -0,0 +1,22 @@
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and limitations under the
+ ~ License.
+ ~
+ -->
+
+<resources>
+ <!-- Example customization of dimensions originally defined in res/values/dimens.xml
+ (such as screen margins) for screens with more than 820dp of available width. This
+ would include 7" and 10" devices in landscape (~960dp and ~1280dp respectively). -->
+ <dimen name="activity_horizontal_margin">64dp</dimen>
+</resources>
diff --git a/tests/JankBench/app/src/main/res/values/attrs.xml b/tests/JankBench/app/src/main/res/values/attrs.xml
new file mode 100644
index 0000000..a4286f1
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/values/attrs.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and limitations under the
+ ~ License.
+ ~
+ -->
+
+<resources>
+ <!-- Root tag for benchmarks -->
+ <declare-styleable name="AndroidBenchmarks" />
+
+ <declare-styleable name="BenchmarkGroup">
+ <attr name="name" format="reference|string" />
+ <attr name="description" format="reference|string" />
+ </declare-styleable>
+
+ <declare-styleable name="Benchmark">
+ <attr name="name" />
+ <attr name="description" />
+ <attr name="id" format="reference" />
+ <attr name="category" format="enum">
+ <enum name="generic" value="0" />
+ <enum name="ui" value="1" />
+ <enum name="compute" value="2" />
+ </attr>
+ </declare-styleable>
+
+ <declare-styleable name="PerfTimeline"><attr name="exampleString" format="string"/>
+ <attr name="exampleDimension" format="dimension"/>
+ <attr name="exampleColor" format="color"/>
+ <attr name="exampleDrawable" format="color|reference"/>
+ </declare-styleable>
+
+</resources>
+
diff --git a/tests/JankBench/app/src/main/res/values/colors.xml b/tests/JankBench/app/src/main/res/values/colors.xml
new file mode 100644
index 0000000..59156ee
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/values/colors.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and limitations under the
+ ~ License.
+ ~
+ -->
+
+<resources>
+ <color name="colorPrimary">#3F51B5</color>
+ <color name="colorPrimaryDark">#303F9F</color>
+ <color name="colorAccent">#FF4081</color>
+</resources>
diff --git a/tests/JankBench/app/src/main/res/values/dimens.xml b/tests/JankBench/app/src/main/res/values/dimens.xml
new file mode 100644
index 0000000..9da649a
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/values/dimens.xml
@@ -0,0 +1,27 @@
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and limitations under the
+ ~ License.
+ ~
+ -->
+
+<resources>
+ <!-- Default screen margins, per the Android Design guidelines. -->
+ <dimen name="detail_backdrop_height">256dp</dimen>
+ <dimen name="card_margin">16dp</dimen>
+ <dimen name="activity_horizontal_margin">16dp</dimen>
+ <dimen name="activity_vertical_margin">16dp</dimen>
+ <dimen name="fab_margin">16dp</dimen>
+ <dimen name="app_bar_height">200dp</dimen>
+ <dimen name="item_width">200dp</dimen>
+ <dimen name="text_margin">16dp</dimen>
+</resources>
diff --git a/tests/JankBench/app/src/main/res/values/ids.xml b/tests/JankBench/app/src/main/res/values/ids.xml
new file mode 100644
index 0000000..6801fd9
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/values/ids.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and limitations under the
+ ~ License.
+ ~
+ -->
+
+<resources>
+ <item name="benchmark_list_view_scroll" type="id" />
+ <item name="benchmark_image_list_view_scroll" type="id" />
+ <item name="benchmark_shadow_grid" type="id" />
+ <item name="benchmark_text_high_hitrate" type="id" />
+ <item name="benchmark_text_low_hitrate" type="id" />
+ <item name="benchmark_edit_text_input" type="id" />
+ <item name="benchmark_overdraw" type="id" />
+ <item name="benchmark_memory_bandwidth" type="id" />
+ <item name="benchmark_memory_latency" type="id" />
+ <item name="benchmark_power_management" type="id" />
+ <item name="benchmark_cpu_heat_soak" type="id" />
+ <item name="benchmark_cpu_gflops" type="id" />
+</resources>
\ No newline at end of file
diff --git a/tests/JankBench/app/src/main/res/values/strings.xml b/tests/JankBench/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..270adf8
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/values/strings.xml
@@ -0,0 +1,51 @@
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and limitations under the
+ ~ License.
+ ~
+ -->
+
+<resources>
+ <string name="app_name">Benchmark</string>
+
+ <string name="action_export">Export to CSV</string>
+
+ <string name="list_view_scroll_name">List View Fling</string>
+ <string name="list_view_scroll_description">Tests list view fling performance</string>
+ <string name="image_list_view_scroll_name">Image List View Fling</string>
+ <string name="image_list_view_scroll_description">Tests list view fling performance with images</string>
+ <string name="shadow_grid_name">Shadow Grid Fling</string>
+ <string name="shadow_grid_description">Tests shadow grid fling performance with images</string>
+ <string name="text_high_hitrate_name">High-hitrate text render</string>
+ <string name="text_high_hitrate_description">Tests high hitrate text rendering</string>
+ <string name="text_low_hitrate_name">Low-hitrate text render</string>
+ <string name="text_low_hitrate_description">Tests low-hitrate text rendering</string>
+ <string name="edit_text_input_name">Edit Text Input</string>
+ <string name="edit_text_input_description">Tests edit text input</string>
+ <string name="overdraw_name">Overdraw Test</string>
+ <string name="overdraw_description">Tests how the device handles overdraw</string>
+ <string name="memory_bandwidth_name">Memory Bandwidth</string>
+ <string name="memory_bandwidth_description">Test device\'s memory bandwidth</string>
+ <string name="memory_latency_name">Memory Latency</string>
+ <string name="memory_latency_description">Test device\'s memory latency</string>
+ <string name="power_management_name">Power Management</string>
+ <string name="power_management_description">Test device\'s power management</string>
+ <string name="cpu_heat_soak_name">CPU Heat Soak</string>
+ <string name="cpu_heat_soak_description">How hot can we make it?</string>
+ <string name="cpu_gflops_name">CPU GFlops</string>
+ <string name="cpu_gflops_description">How many gigaflops can the device attain?</string>
+
+ <string name="benchmark_category_ui">UI</string>
+ <string name="benchmark_category_compute">Compute</string>
+ <string name="benchmark_category_generic">Generic</string>
+ <string name="title_activity_image_list_view_scroll">ImageListViewScroll</string>
+</resources>
diff --git a/tests/JankBench/app/src/main/res/values/styles.xml b/tests/JankBench/app/src/main/res/values/styles.xml
new file mode 100644
index 0000000..25ce730
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/values/styles.xml
@@ -0,0 +1,43 @@
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and limitations under the
+ ~ License.
+ ~
+ -->
+
+<resources>
+
+ <!-- Base application theme. -->
+ <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
+ <!-- Customize your theme here. -->
+ <item name="colorPrimary">@color/colorPrimary</item>
+ <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
+ <item name="colorAccent">@color/colorAccent</item>
+ </style>
+
+ <style name="AppTheme.NoActionBar">
+ <item name="windowActionBar">false</item>
+ <item name="windowNoTitle">true</item>
+ </style>
+
+ <style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar" />
+
+ <style name="AppTheme.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" />
+
+ <style name="Widget.CardContent" parent="android:Widget">
+ <item name="android:paddingLeft">16dp</item>
+ <item name="android:paddingRight">16dp</item>
+ <item name="android:paddingTop">24dp</item>
+ <item name="android:paddingBottom">24dp</item>
+ <item name="android:orientation">vertical</item>
+ </style>
+</resources>
diff --git a/tests/JankBench/app/src/main/res/xml/benchmark.xml b/tests/JankBench/app/src/main/res/xml/benchmark.xml
new file mode 100644
index 0000000..07c453c
--- /dev/null
+++ b/tests/JankBench/app/src/main/res/xml/benchmark.xml
@@ -0,0 +1,97 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and limitations under the
+ ~ License.
+ ~
+ -->
+
+<com.android.benchmark.BenchmarkGroup
+ xmlns:benchmark="http://schemas.android.com/apk/res-auto"
+ benchmark:description="Benchmarks of the Android system"
+ benchmark:name="Android Benchmarks">
+
+ <com.android.benchmark.Benchmark
+ benchmark:name="@string/list_view_scroll_name"
+ benchmark:id="@id/benchmark_list_view_scroll"
+ benchmark:category="ui"
+ benchmark:description="@string/list_view_scroll_description" />
+
+ <com.android.benchmark.Benchmark
+ benchmark:name="@string/image_list_view_scroll_name"
+ benchmark:id="@id/benchmark_image_list_view_scroll"
+ benchmark:category="ui"
+ benchmark:description="@string/image_list_view_scroll_description" />
+
+ <com.android.benchmark.Benchmark
+ benchmark:name="@string/shadow_grid_name"
+ benchmark:id="@id/benchmark_shadow_grid"
+ benchmark:category="ui"
+ benchmark:description="@string/shadow_grid_description" />
+
+ <com.android.benchmark.Benchmark
+ benchmark:name="@string/text_low_hitrate_name"
+ benchmark:id="@id/benchmark_text_low_hitrate"
+ benchmark:category="ui"
+ benchmark:description="@string/text_low_hitrate_description" />
+
+ <com.android.benchmark.Benchmark
+ benchmark:name="@string/text_high_hitrate_name"
+ benchmark:id="@id/benchmark_text_high_hitrate"
+ benchmark:category="ui"
+ benchmark:description="@string/text_high_hitrate_description" />
+
+ <com.android.benchmark.Benchmark
+ benchmark:name="@string/edit_text_input_name"
+ benchmark:id="@id/benchmark_edit_text_input"
+ benchmark:category="ui"
+ benchmark:description="@string/edit_text_input_description" />
+
+ <com.android.benchmark.Benchmark
+ benchmark:name="@string/overdraw_name"
+ benchmark:id="@id/benchmark_overdraw"
+ benchmark:category="ui"
+ benchmark:description="@string/overdraw_description" />
+
+ <!--
+ <com.android.benchmark.Benchmark
+ benchmark:name="@string/memory_bandwidth_name"
+ benchmark:id="@id/benchmark_memory_bandwidth"
+ benchmark:category="compute"
+ benchmark:description="@string/memory_bandwidth_description" />
+
+ <com.android.benchmark.Benchmark
+ benchmark:name="@string/memory_latency_name"
+ benchmark:id="@id/benchmark_memory_latency"
+ benchmark:category="compute"
+ benchmark:description="@string/memory_latency_description" />
+
+ <com.android.benchmark.Benchmark
+ benchmark:name="@string/power_management_name"
+ benchmark:id="@id/benchmark_power_management"
+ benchmark:category="compute"
+ benchmark:description="@string/power_management_description" />
+
+ <com.android.benchmark.Benchmark
+ benchmark:name="@string/cpu_heat_soak_name"
+ benchmark:id="@id/benchmark_cpu_heat_soak"
+ benchmark:category="compute"
+ benchmark:description="@string/cpu_heat_soak_description" />
+
+ <com.android.benchmark.Benchmark
+ benchmark:name="@string/cpu_gflops_name"
+ benchmark:id="@id/benchmark_cpu_gflops"
+ benchmark:category="compute"
+ benchmark:description="@string/cpu_gflops_description" />
+ -->
+
+</com.android.benchmark.BenchmarkGroup>
\ No newline at end of file
diff --git a/tests/JankBench/app/src/test/java/com/android/benchmark/ExampleUnitTest.java b/tests/JankBench/app/src/test/java/com/android/benchmark/ExampleUnitTest.java
new file mode 100644
index 0000000..4464e87
--- /dev/null
+++ b/tests/JankBench/app/src/test/java/com/android/benchmark/ExampleUnitTest.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.benchmark;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * To work on unit tests, switch the Test Artifact in the Build Variants view.
+ */
+public class ExampleUnitTest {
+ @Test
+ public void addition_isCorrect() throws Exception {
+ assertEquals(4, 2 + 2);
+ }
+}
diff --git a/tests/JankBench/scripts/adbutil.py b/tests/JankBench/scripts/adbutil.py
new file mode 100644
index 0000000..ad9f7d9
--- /dev/null
+++ b/tests/JankBench/scripts/adbutil.py
@@ -0,0 +1,62 @@
+import subprocess
+import re
+import threading
+
+ATRACE_PATH="/android/catapult/systrace/systrace/systrace.py"
+
+class AdbError(RuntimeError):
+ def __init__(self, arg):
+ self.args = arg
+
+def am(serial, cmd, args):
+ if not isinstance(args, list):
+ args = [args]
+ full_args = ["am"] + [cmd] + args
+ __call_adb(serial, full_args, False)
+
+def pm(serial, cmd, args):
+ if not isinstance(args, list):
+ args = [args]
+ full_args = ["pm"] + [cmd] + args
+ __call_adb(serial, full_args, False)
+
+def dumpsys(serial, topic):
+ return __call_adb(serial, ["dumpsys"] + [topic], True)
+
+def trace(serial,
+ tags = ["gfx", "sched", "view", "freq", "am", "wm", "power", "load", "memreclaim"],
+ time = "10"):
+ args = [ATRACE_PATH, "-e", serial, "-t" + time, "-b32768"] + tags
+ subprocess.call(args)
+
+def wake(serial):
+ output = dumpsys(serial, "power")
+ wakefulness = re.search('mWakefulness=([a-zA-Z]+)', output)
+ if wakefulness.group(1) != "Awake":
+ __call_adb(serial, ["input", "keyevent", "KEYCODE_POWER"], False)
+
+def root(serial):
+ subprocess.call(["adb", "-s", serial, "root"])
+
+def pull(serial, path, dest):
+ subprocess.call(["adb", "-s", serial, "wait-for-device", "pull"] + [path] + [dest])
+
+def shell(serial, cmd):
+ __call_adb(serial, cmd, False)
+
+def track_logcat(serial, awaited_string, callback):
+ threading.Thread(target=__track_logcat, name=serial + "-waiter", args=(serial, awaited_string, callback)).start()
+
+def __call_adb(serial, args, block):
+ full_args = ["adb", "-s", serial, "wait-for-device", "shell"] + args
+ print full_args
+ output = None
+ try:
+ if block:
+ output = subprocess.check_output(full_args)
+ else:
+ subprocess.call(full_args)
+ except subprocess.CalledProcessError:
+ raise AdbError("Error calling " + " ".join(args))
+
+ return output
diff --git a/tests/JankBench/scripts/collect.py b/tests/JankBench/scripts/collect.py
new file mode 100755
index 0000000..87a0594
--- /dev/null
+++ b/tests/JankBench/scripts/collect.py
@@ -0,0 +1,240 @@
+#!/usr/bin/python
+
+import optparse
+import sys
+import sqlite3
+import scipy.stats
+import numpy
+from math import log10, floor
+import matplotlib
+
+matplotlib.use("Agg")
+
+import matplotlib.pyplot as plt
+import pylab
+
+import adbutil
+from devices import DEVICES
+
+DB_PATH="/data/data/com.android.benchmark/databases/BenchmarkResults"
+OUT_PATH = "db/"
+
+QUERY_BAD_FRAME = ("select run_id, name, iteration, total_duration from ui_results "
+ "where total_duration >= 16 order by run_id, name, iteration")
+QUERY_PERCENT_JANK = ("select run_id, name, iteration, sum(jank_frame) as jank_count, count (*) as total "
+ "from ui_results group by run_id, name, iteration")
+
+SKIP_TESTS = [
+ # "BMUpload",
+ # "Low-hitrate text render",
+ # "High-hitrate text render",
+ # "Edit Text Input",
+ # "List View Fling"
+]
+
+INCLUDE_TESTS = [
+ #"BMUpload"
+ #"Shadow Grid Fling"
+ #"Image List View Fling"
+ #"Edit Text Input"
+]
+
+class IterationResult:
+ def __init__(self):
+ self.durations = []
+ self.jank_count = 0
+ self.total_count = 0
+
+
+def get_scoremap(dbpath):
+ db = sqlite3.connect(dbpath)
+ rows = db.execute(QUERY_BAD_FRAME)
+
+ scoremap = {}
+ for row in rows:
+ run_id = row[0]
+ name = row[1]
+ iteration = row[2]
+ total_duration = row[3]
+
+ if not run_id in scoremap:
+ scoremap[run_id] = {}
+
+ if not name in scoremap[run_id]:
+ scoremap[run_id][name] = {}
+
+ if not iteration in scoremap[run_id][name]:
+ scoremap[run_id][name][iteration] = IterationResult()
+
+ scoremap[run_id][name][iteration].durations.append(float(total_duration))
+
+ for row in db.execute(QUERY_PERCENT_JANK):
+ run_id = row[0]
+ name = row[1]
+ iteration = row[2]
+ jank_count = row[3]
+ total_count = row[4]
+
+ if run_id in scoremap.keys() and name in scoremap[run_id].keys() and iteration in scoremap[run_id][name].keys():
+ scoremap[run_id][name][iteration].jank_count = long(jank_count)
+ scoremap[run_id][name][iteration].total_count = long(total_count)
+
+ db.close()
+ return scoremap
+
+def round_to_2(val):
+ return val
+ if val == 0:
+ return val
+ return round(val , -int(floor(log10(abs(val)))) + 1)
+
+def score_device(name, serial, pull = False, verbose = False):
+ dbpath = OUT_PATH + name + ".db"
+
+ if pull:
+ adbutil.root(serial)
+ adbutil.pull(serial, DB_PATH, dbpath)
+
+ scoremap = None
+ try:
+ scoremap = get_scoremap(dbpath)
+ except sqlite3.DatabaseError:
+ print "Database corrupt, fetching..."
+ adbutil.root(serial)
+ adbutil.pull(serial, DB_PATH, dbpath)
+ scoremap = get_scoremap(dbpath)
+
+ per_test_score = {}
+ per_test_sample_count = {}
+ global_overall = {}
+
+ for run_id in iter(scoremap):
+ overall = []
+ if len(scoremap[run_id]) < 1:
+ if verbose:
+ print "Skipping short run %s" % run_id
+ continue
+ print "Run: %s" % run_id
+ for test in iter(scoremap[run_id]):
+ if test in SKIP_TESTS:
+ continue
+ if INCLUDE_TESTS and test not in INCLUDE_TESTS:
+ continue
+ if verbose:
+ print "\t%s" % test
+ scores = []
+ means = []
+ stddevs = []
+ pjs = []
+ sample_count = 0
+ hit_min_count = 0
+ # try pooling together all iterations
+ for iteration in iter(scoremap[run_id][test]):
+ res = scoremap[run_id][test][iteration]
+ stddev = round_to_2(numpy.std(res.durations))
+ mean = round_to_2(numpy.mean(res.durations))
+ sample_count += len(res.durations)
+ pj = round_to_2(100 * res.jank_count / float(res.total_count))
+ score = stddev * mean * pj
+ score = 100 * len(res.durations) / float(res.total_count)
+ if score == 0:
+ score = 1
+ scores.append(score)
+ means.append(mean)
+ stddevs.append(stddev)
+ pjs.append(pj)
+ if verbose:
+ print "\t%s: Score = %f x %f x %f = %f (%d samples)" % (iteration, stddev, mean, pj, score, len(res.durations))
+
+ if verbose:
+ print "\tHit min: %d" % hit_min_count
+ print "\tMean Variation: %0.2f%%" % (100 * scipy.stats.variation(means))
+ print "\tStdDev Variation: %0.2f%%" % (100 * scipy.stats.variation(stddevs))
+ print "\tPJ Variation: %0.2f%%" % (100 * scipy.stats.variation(pjs))
+
+ geo_run = numpy.mean(scores)
+ if test not in per_test_score:
+ per_test_score[test] = []
+
+ if test not in per_test_sample_count:
+ per_test_sample_count[test] = []
+
+ sample_count /= len(scoremap[run_id][test])
+
+ per_test_score[test].append(geo_run)
+ per_test_sample_count[test].append(int(sample_count))
+ overall.append(geo_run)
+
+ if not verbose:
+ print "\t%s:\t%0.2f (%0.2f avg. sample count)" % (test, geo_run, sample_count)
+ else:
+ print "\tOverall:\t%0.2f (%0.2f avg. sample count)" % (geo_run, sample_count)
+ print ""
+
+ global_overall[run_id] = scipy.stats.gmean(overall)
+ print "Run Overall: %f" % global_overall[run_id]
+ print ""
+
+ print ""
+ print "Variability (CV) - %s:" % name
+
+ worst_offender_test = None
+ worst_offender_variation = 0
+ for test in per_test_score:
+ variation = 100 * scipy.stats.variation(per_test_score[test])
+ if worst_offender_variation < variation:
+ worst_offender_test = test
+ worst_offender_variation = variation
+ print "\t%s:\t%0.2f%% (%0.2f avg sample count)" % (test, variation, numpy.mean(per_test_sample_count[test]))
+
+ print "\tOverall: %0.2f%%" % (100 * scipy.stats.variation([x for x in global_overall.values()]))
+ print ""
+
+ return {
+ "overall": global_overall.values(),
+ "worst_offender_test": (name, worst_offender_test, worst_offender_variation)
+ }
+
+def parse_options(argv):
+ usage = 'Usage: %prog [options]'
+ desc = 'Example: %prog'
+ parser = optparse.OptionParser(usage=usage, description=desc)
+ parser.add_option("-p", dest='pull', action="store_true")
+ parser.add_option("-d", dest='device', action="store")
+ parser.add_option("-v", dest='verbose', action="store_true")
+ options, categories = parser.parse_args(argv[1:])
+ return options
+
+def main():
+ options = parse_options(sys.argv)
+ if options.device != None:
+ score_device(options.device, DEVICES[options.device], options.pull, options.verbose)
+ else:
+ device_scores = []
+ worst_offenders = []
+ for name, serial in DEVICES.iteritems():
+ print "======== %s =========" % name
+ result = score_device(name, serial, options.pull, options.verbose)
+ device_scores.append((name, result["overall"]))
+ worst_offenders.append(result["worst_offender_test"])
+
+
+ device_scores.sort(cmp=(lambda x, y: cmp(x[1], y[1])))
+ print "Ranking by max overall score:"
+ for name, score in device_scores:
+ plt.plot([0, 1, 2, 3, 4, 5], score, label=name)
+ print "\t%s: %s" % (name, score)
+
+ plt.ylabel("Jank %")
+ plt.xlabel("Iteration")
+ plt.title("Jank Percentage")
+ plt.legend()
+ pylab.savefig("holy.png", bbox_inches="tight")
+
+ print "Worst offender tests:"
+ for device, test, variation in worst_offenders:
+ print "\t%s: %s %.2f%%" % (device, test, variation)
+
+if __name__ == "__main__":
+ main()
+
diff --git a/tests/JankBench/scripts/devices.py b/tests/JankBench/scripts/devices.py
new file mode 100644
index 0000000..c8266c0
--- /dev/null
+++ b/tests/JankBench/scripts/devices.py
@@ -0,0 +1,11 @@
+#!/usr/bin/python
+
+DEVICES = {
+ 'bullhead': '00606a370e3ca155',
+ 'volantis': 'HT4BTWV00612',
+ 'angler': '84B5T15A29021748',
+ 'seed': '1285c85e',
+ 'ryu': '5A27000599',
+ 'shamu': 'ZX1G22W24R',
+}
+
diff --git a/tests/JankBench/scripts/external/__init__.py b/tests/JankBench/scripts/external/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/JankBench/scripts/external/__init__.py
diff --git a/tests/JankBench/scripts/external/statistics.py b/tests/JankBench/scripts/external/statistics.py
new file mode 100644
index 0000000..518f546
--- /dev/null
+++ b/tests/JankBench/scripts/external/statistics.py
@@ -0,0 +1,638 @@
+## Module statistics.py
+##
+## Copyright (c) 2013 Steven D'Aprano <steve+python@pearwood.info>.
+##
+## Licensed under the Apache License, Version 2.0 (the "License");
+## you may not use this file except in compliance with the License.
+## You may obtain a copy of the License at
+##
+## http://www.apache.org/licenses/LICENSE-2.0
+##
+## Unless required by applicable law or agreed to in writing, software
+## distributed under the License is distributed on an "AS IS" BASIS,
+## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+## See the License for the specific language governing permissions and
+## limitations under the License.
+
+
+"""
+Basic statistics module.
+
+This module provides functions for calculating statistics of data, including
+averages, variance, and standard deviation.
+
+Calculating averages
+--------------------
+
+================== =============================================
+Function Description
+================== =============================================
+mean Arithmetic mean (average) of data.
+median Median (middle value) of data.
+median_low Low median of data.
+median_high High median of data.
+median_grouped Median, or 50th percentile, of grouped data.
+mode Mode (most common value) of data.
+================== =============================================
+
+Calculate the arithmetic mean ("the average") of data:
+
+>>> mean([-1.0, 2.5, 3.25, 5.75])
+2.625
+
+
+Calculate the standard median of discrete data:
+
+>>> median([2, 3, 4, 5])
+3.5
+
+
+Calculate the median, or 50th percentile, of data grouped into class intervals
+centred on the data values provided. E.g. if your data points are rounded to
+the nearest whole number:
+
+>>> median_grouped([2, 2, 3, 3, 3, 4]) #doctest: +ELLIPSIS
+2.8333333333...
+
+This should be interpreted in this way: you have two data points in the class
+interval 1.5-2.5, three data points in the class interval 2.5-3.5, and one in
+the class interval 3.5-4.5. The median of these data points is 2.8333...
+
+
+Calculating variability or spread
+---------------------------------
+
+================== =============================================
+Function Description
+================== =============================================
+pvariance Population variance of data.
+variance Sample variance of data.
+pstdev Population standard deviation of data.
+stdev Sample standard deviation of data.
+================== =============================================
+
+Calculate the standard deviation of sample data:
+
+>>> stdev([2.5, 3.25, 5.5, 11.25, 11.75]) #doctest: +ELLIPSIS
+4.38961843444...
+
+If you have previously calculated the mean, you can pass it as the optional
+second argument to the four "spread" functions to avoid recalculating it:
+
+>>> data = [1, 2, 2, 4, 4, 4, 5, 6]
+>>> mu = mean(data)
+>>> pvariance(data, mu)
+2.5
+
+
+Exceptions
+----------
+
+A single exception is defined: StatisticsError is a subclass of ValueError.
+
+"""
+
+__all__ = [ 'StatisticsError',
+ 'pstdev', 'pvariance', 'stdev', 'variance',
+ 'median', 'median_low', 'median_high', 'median_grouped',
+ 'mean', 'mode',
+ ]
+
+
+import collections
+import math
+
+from fractions import Fraction
+from decimal import Decimal
+from itertools import groupby
+
+
+
+# === Exceptions ===
+
+class StatisticsError(ValueError):
+ pass
+
+
+# === Private utilities ===
+
+def _sum(data, start=0):
+ """_sum(data [, start]) -> (type, sum, count)
+
+ Return a high-precision sum of the given numeric data as a fraction,
+ together with the type to be converted to and the count of items.
+
+ If optional argument ``start`` is given, it is added to the total.
+ If ``data`` is empty, ``start`` (defaulting to 0) is returned.
+
+
+ Examples
+ --------
+
+ >>> _sum([3, 2.25, 4.5, -0.5, 1.0], 0.75)
+ (<class 'float'>, Fraction(11, 1), 5)
+
+ Some sources of round-off error will be avoided:
+
+ >>> _sum([1e50, 1, -1e50] * 1000) # Built-in sum returns zero.
+ (<class 'float'>, Fraction(1000, 1), 3000)
+
+ Fractions and Decimals are also supported:
+
+ >>> from fractions import Fraction as F
+ >>> _sum([F(2, 3), F(7, 5), F(1, 4), F(5, 6)])
+ (<class 'fractions.Fraction'>, Fraction(63, 20), 4)
+
+ >>> from decimal import Decimal as D
+ >>> data = [D("0.1375"), D("0.2108"), D("0.3061"), D("0.0419")]
+ >>> _sum(data)
+ (<class 'decimal.Decimal'>, Fraction(6963, 10000), 4)
+
+ Mixed types are currently treated as an error, except that int is
+ allowed.
+ """
+ count = 0
+ n, d = _exact_ratio(start)
+ partials = {d: n}
+ partials_get = partials.get
+ T = _coerce(int, type(start))
+ for typ, values in groupby(data, type):
+ T = _coerce(T, typ) # or raise TypeError
+ for n,d in map(_exact_ratio, values):
+ count += 1
+ partials[d] = partials_get(d, 0) + n
+ if None in partials:
+ # The sum will be a NAN or INF. We can ignore all the finite
+ # partials, and just look at this special one.
+ total = partials[None]
+ assert not _isfinite(total)
+ else:
+ # Sum all the partial sums using builtin sum.
+ # FIXME is this faster if we sum them in order of the denominator?
+ total = sum(Fraction(n, d) for d, n in sorted(partials.items()))
+ return (T, total, count)
+
+
+def _isfinite(x):
+ try:
+ return x.is_finite() # Likely a Decimal.
+ except AttributeError:
+ return math.isfinite(x) # Coerces to float first.
+
+
+def _coerce(T, S):
+ """Coerce types T and S to a common type, or raise TypeError.
+
+ Coercion rules are currently an implementation detail. See the CoerceTest
+ test class in test_statistics for details.
+ """
+ # See http://bugs.python.org/issue24068.
+ assert T is not bool, "initial type T is bool"
+ # If the types are the same, no need to coerce anything. Put this
+ # first, so that the usual case (no coercion needed) happens as soon
+ # as possible.
+ if T is S: return T
+ # Mixed int & other coerce to the other type.
+ if S is int or S is bool: return T
+ if T is int: return S
+ # If one is a (strict) subclass of the other, coerce to the subclass.
+ if issubclass(S, T): return S
+ if issubclass(T, S): return T
+ # Ints coerce to the other type.
+ if issubclass(T, int): return S
+ if issubclass(S, int): return T
+ # Mixed fraction & float coerces to float (or float subclass).
+ if issubclass(T, Fraction) and issubclass(S, float):
+ return S
+ if issubclass(T, float) and issubclass(S, Fraction):
+ return T
+ # Any other combination is disallowed.
+ msg = "don't know how to coerce %s and %s"
+ raise TypeError(msg % (T.__name__, S.__name__))
+
+
+def _exact_ratio(x):
+ """Return Real number x to exact (numerator, denominator) pair.
+
+ >>> _exact_ratio(0.25)
+ (1, 4)
+
+ x is expected to be an int, Fraction, Decimal or float.
+ """
+ try:
+ # Optimise the common case of floats. We expect that the most often
+ # used numeric type will be builtin floats, so try to make this as
+ # fast as possible.
+ if type(x) is float:
+ return x.as_integer_ratio()
+ try:
+ # x may be an int, Fraction, or Integral ABC.
+ return (x.numerator, x.denominator)
+ except AttributeError:
+ try:
+ # x may be a float subclass.
+ return x.as_integer_ratio()
+ except AttributeError:
+ try:
+ # x may be a Decimal.
+ return _decimal_to_ratio(x)
+ except AttributeError:
+ # Just give up?
+ pass
+ except (OverflowError, ValueError):
+ # float NAN or INF.
+ assert not math.isfinite(x)
+ return (x, None)
+ msg = "can't convert type '{}' to numerator/denominator"
+ raise TypeError(msg.format(type(x).__name__))
+
+
+# FIXME This is faster than Fraction.from_decimal, but still too slow.
+def _decimal_to_ratio(d):
+ """Convert Decimal d to exact integer ratio (numerator, denominator).
+
+ >>> from decimal import Decimal
+ >>> _decimal_to_ratio(Decimal("2.6"))
+ (26, 10)
+
+ """
+ sign, digits, exp = d.as_tuple()
+ if exp in ('F', 'n', 'N'): # INF, NAN, sNAN
+ assert not d.is_finite()
+ return (d, None)
+ num = 0
+ for digit in digits:
+ num = num*10 + digit
+ if exp < 0:
+ den = 10**-exp
+ else:
+ num *= 10**exp
+ den = 1
+ if sign:
+ num = -num
+ return (num, den)
+
+
+def _convert(value, T):
+ """Convert value to given numeric type T."""
+ if type(value) is T:
+ # This covers the cases where T is Fraction, or where value is
+ # a NAN or INF (Decimal or float).
+ return value
+ if issubclass(T, int) and value.denominator != 1:
+ T = float
+ try:
+ # FIXME: what do we do if this overflows?
+ return T(value)
+ except TypeError:
+ if issubclass(T, Decimal):
+ return T(value.numerator)/T(value.denominator)
+ else:
+ raise
+
+
+def _counts(data):
+ # Generate a table of sorted (value, frequency) pairs.
+ table = collections.Counter(iter(data)).most_common()
+ if not table:
+ return table
+ # Extract the values with the highest frequency.
+ maxfreq = table[0][1]
+ for i in range(1, len(table)):
+ if table[i][1] != maxfreq:
+ table = table[:i]
+ break
+ return table
+
+
+# === Measures of central tendency (averages) ===
+
+def mean(data):
+ """Return the sample arithmetic mean of data.
+
+ >>> mean([1, 2, 3, 4, 4])
+ 2.8
+
+ >>> from fractions import Fraction as F
+ >>> mean([F(3, 7), F(1, 21), F(5, 3), F(1, 3)])
+ Fraction(13, 21)
+
+ >>> from decimal import Decimal as D
+ >>> mean([D("0.5"), D("0.75"), D("0.625"), D("0.375")])
+ Decimal('0.5625')
+
+ If ``data`` is empty, StatisticsError will be raised.
+ """
+ if iter(data) is data:
+ data = list(data)
+ n = len(data)
+ if n < 1:
+ raise StatisticsError('mean requires at least one data point')
+ T, total, count = _sum(data)
+ assert count == n
+ return _convert(total/n, T)
+
+
+# FIXME: investigate ways to calculate medians without sorting? Quickselect?
+def median(data):
+ """Return the median (middle value) of numeric data.
+
+ When the number of data points is odd, return the middle data point.
+ When the number of data points is even, the median is interpolated by
+ taking the average of the two middle values:
+
+ >>> median([1, 3, 5])
+ 3
+ >>> median([1, 3, 5, 7])
+ 4.0
+
+ """
+ data = sorted(data)
+ n = len(data)
+ if n == 0:
+ raise StatisticsError("no median for empty data")
+ if n%2 == 1:
+ return data[n//2]
+ else:
+ i = n//2
+ return (data[i - 1] + data[i])/2
+
+
+def median_low(data):
+ """Return the low median of numeric data.
+
+ When the number of data points is odd, the middle value is returned.
+ When it is even, the smaller of the two middle values is returned.
+
+ >>> median_low([1, 3, 5])
+ 3
+ >>> median_low([1, 3, 5, 7])
+ 3
+
+ """
+ data = sorted(data)
+ n = len(data)
+ if n == 0:
+ raise StatisticsError("no median for empty data")
+ if n%2 == 1:
+ return data[n//2]
+ else:
+ return data[n//2 - 1]
+
+
+def median_high(data):
+ """Return the high median of data.
+
+ When the number of data points is odd, the middle value is returned.
+ When it is even, the larger of the two middle values is returned.
+
+ >>> median_high([1, 3, 5])
+ 3
+ >>> median_high([1, 3, 5, 7])
+ 5
+
+ """
+ data = sorted(data)
+ n = len(data)
+ if n == 0:
+ raise StatisticsError("no median for empty data")
+ return data[n//2]
+
+
+def median_grouped(data, interval=1):
+ """Return the 50th percentile (median) of grouped continuous data.
+
+ >>> median_grouped([1, 2, 2, 3, 4, 4, 4, 4, 4, 5])
+ 3.7
+ >>> median_grouped([52, 52, 53, 54])
+ 52.5
+
+ This calculates the median as the 50th percentile, and should be
+ used when your data is continuous and grouped. In the above example,
+ the values 1, 2, 3, etc. actually represent the midpoint of classes
+ 0.5-1.5, 1.5-2.5, 2.5-3.5, etc. The middle value falls somewhere in
+ class 3.5-4.5, and interpolation is used to estimate it.
+
+ Optional argument ``interval`` represents the class interval, and
+ defaults to 1. Changing the class interval naturally will change the
+ interpolated 50th percentile value:
+
+ >>> median_grouped([1, 3, 3, 5, 7], interval=1)
+ 3.25
+ >>> median_grouped([1, 3, 3, 5, 7], interval=2)
+ 3.5
+
+ This function does not check whether the data points are at least
+ ``interval`` apart.
+ """
+ data = sorted(data)
+ n = len(data)
+ if n == 0:
+ raise StatisticsError("no median for empty data")
+ elif n == 1:
+ return data[0]
+ # Find the value at the midpoint. Remember this corresponds to the
+ # centre of the class interval.
+ x = data[n//2]
+ for obj in (x, interval):
+ if isinstance(obj, (str, bytes)):
+ raise TypeError('expected number but got %r' % obj)
+ try:
+ L = x - interval/2 # The lower limit of the median interval.
+ except TypeError:
+ # Mixed type. For now we just coerce to float.
+ L = float(x) - float(interval)/2
+ cf = data.index(x) # Number of values below the median interval.
+ # FIXME The following line could be more efficient for big lists.
+ f = data.count(x) # Number of data points in the median interval.
+ return L + interval*(n/2 - cf)/f
+
+
+def mode(data):
+ """Return the most common data point from discrete or nominal data.
+
+ ``mode`` assumes discrete data, and returns a single value. This is the
+ standard treatment of the mode as commonly taught in schools:
+
+ >>> mode([1, 1, 2, 3, 3, 3, 3, 4])
+ 3
+
+ This also works with nominal (non-numeric) data:
+
+ >>> mode(["red", "blue", "blue", "red", "green", "red", "red"])
+ 'red'
+
+ If there is not exactly one most common value, ``mode`` will raise
+ StatisticsError.
+ """
+ # Generate a table of sorted (value, frequency) pairs.
+ table = _counts(data)
+ if len(table) == 1:
+ return table[0][0]
+ elif table:
+ raise StatisticsError(
+ 'no unique mode; found %d equally common values' % len(table)
+ )
+ else:
+ raise StatisticsError('no mode for empty data')
+
+
+# === Measures of spread ===
+
+# See http://mathworld.wolfram.com/Variance.html
+# http://mathworld.wolfram.com/SampleVariance.html
+# http://en.wikipedia.org/wiki/Algorithms_for_calculating_variance
+#
+# Under no circumstances use the so-called "computational formula for
+# variance", as that is only suitable for hand calculations with a small
+# amount of low-precision data. It has terrible numeric properties.
+#
+# See a comparison of three computational methods here:
+# http://www.johndcook.com/blog/2008/09/26/comparing-three-methods-of-computing-standard-deviation/
+
+def _ss(data, c=None):
+ """Return sum of square deviations of sequence data.
+
+ If ``c`` is None, the mean is calculated in one pass, and the deviations
+ from the mean are calculated in a second pass. Otherwise, deviations are
+ calculated from ``c`` as given. Use the second case with care, as it can
+ lead to garbage results.
+ """
+ if c is None:
+ c = mean(data)
+ T, total, count = _sum((x-c)**2 for x in data)
+ # The following sum should mathematically equal zero, but due to rounding
+ # error may not.
+ U, total2, count2 = _sum((x-c) for x in data)
+ assert T == U and count == count2
+ total -= total2**2/len(data)
+ assert not total < 0, 'negative sum of square deviations: %f' % total
+ return (T, total)
+
+
+def variance(data, xbar=None):
+ """Return the sample variance of data.
+
+ data should be an iterable of Real-valued numbers, with at least two
+ values. The optional argument xbar, if given, should be the mean of
+ the data. If it is missing or None, the mean is automatically calculated.
+
+ Use this function when your data is a sample from a population. To
+ calculate the variance from the entire population, see ``pvariance``.
+
+ Examples:
+
+ >>> data = [2.75, 1.75, 1.25, 0.25, 0.5, 1.25, 3.5]
+ >>> variance(data)
+ 1.3720238095238095
+
+ If you have already calculated the mean of your data, you can pass it as
+ the optional second argument ``xbar`` to avoid recalculating it:
+
+ >>> m = mean(data)
+ >>> variance(data, m)
+ 1.3720238095238095
+
+ This function does not check that ``xbar`` is actually the mean of
+ ``data``. Giving arbitrary values for ``xbar`` may lead to invalid or
+ impossible results.
+
+ Decimals and Fractions are supported:
+
+ >>> from decimal import Decimal as D
+ >>> variance([D("27.5"), D("30.25"), D("30.25"), D("34.5"), D("41.75")])
+ Decimal('31.01875')
+
+ >>> from fractions import Fraction as F
+ >>> variance([F(1, 6), F(1, 2), F(5, 3)])
+ Fraction(67, 108)
+
+ """
+ if iter(data) is data:
+ data = list(data)
+ n = len(data)
+ if n < 2:
+ raise StatisticsError('variance requires at least two data points')
+ T, ss = _ss(data, xbar)
+ return _convert(ss/(n-1), T)
+
+
+def pvariance(data, mu=None):
+ """Return the population variance of ``data``.
+
+ data should be an iterable of Real-valued numbers, with at least one
+ value. The optional argument mu, if given, should be the mean of
+ the data. If it is missing or None, the mean is automatically calculated.
+
+ Use this function to calculate the variance from the entire population.
+ To estimate the variance from a sample, the ``variance`` function is
+ usually a better choice.
+
+ Examples:
+
+ >>> data = [0.0, 0.25, 0.25, 1.25, 1.5, 1.75, 2.75, 3.25]
+ >>> pvariance(data)
+ 1.25
+
+ If you have already calculated the mean of the data, you can pass it as
+ the optional second argument to avoid recalculating it:
+
+ >>> mu = mean(data)
+ >>> pvariance(data, mu)
+ 1.25
+
+ This function does not check that ``mu`` is actually the mean of ``data``.
+ Giving arbitrary values for ``mu`` may lead to invalid or impossible
+ results.
+
+ Decimals and Fractions are supported:
+
+ >>> from decimal import Decimal as D
+ >>> pvariance([D("27.5"), D("30.25"), D("30.25"), D("34.5"), D("41.75")])
+ Decimal('24.815')
+
+ >>> from fractions import Fraction as F
+ >>> pvariance([F(1, 4), F(5, 4), F(1, 2)])
+ Fraction(13, 72)
+
+ """
+ if iter(data) is data:
+ data = list(data)
+ n = len(data)
+ if n < 1:
+ raise StatisticsError('pvariance requires at least one data point')
+ ss = _ss(data, mu)
+ T, ss = _ss(data, mu)
+ return _convert(ss/n, T)
+
+
+def stdev(data, xbar=None):
+ """Return the square root of the sample variance.
+
+ See ``variance`` for arguments and other details.
+
+ >>> stdev([1.5, 2.5, 2.5, 2.75, 3.25, 4.75])
+ 1.0810874155219827
+
+ """
+ var = variance(data, xbar)
+ try:
+ return var.sqrt()
+ except AttributeError:
+ return math.sqrt(var)
+
+
+def pstdev(data, mu=None):
+ """Return the square root of the population variance.
+
+ See ``pvariance`` for arguments and other details.
+
+ >>> pstdev([1.5, 2.5, 2.5, 2.75, 3.25, 4.75])
+ 0.986893273527251
+
+ """
+ var = pvariance(data, mu)
+ try:
+ return var.sqrt()
+ except AttributeError:
+ return math.sqrt(var)
diff --git a/tests/JankBench/scripts/itr_collect.py b/tests/JankBench/scripts/itr_collect.py
new file mode 100755
index 0000000..76499a4
--- /dev/null
+++ b/tests/JankBench/scripts/itr_collect.py
@@ -0,0 +1,154 @@
+#!/usr/bin/python
+
+import optparse
+import sys
+import sqlite3
+import scipy.stats
+import numpy
+
+import adbutil
+from devices import DEVICES
+
+DB_PATH="/data/data/com.android.benchmark/databases/BenchmarkResults"
+OUT_PATH = "db/"
+
+QUERY_BAD_FRAME = ("select run_id, name, total_duration from ui_results "
+ "where total_duration >=12 order by run_id, name")
+QUERY_PERCENT_JANK = ("select run_id, name, sum(jank_frame) as jank_count, count (*) as total "
+ "from ui_results group by run_id, name")
+
+class IterationResult:
+ def __init__(self):
+ self.durations = []
+ self.jank_count = 0
+ self.total_count = 0
+
+
+def get_scoremap(dbpath):
+ db = sqlite3.connect(dbpath)
+ rows = db.execute(QUERY_BAD_FRAME)
+
+ scoremap = {}
+ for row in rows:
+ run_id = row[0]
+ name = row[1]
+ total_duration = row[2]
+
+ if not run_id in scoremap:
+ scoremap[run_id] = {}
+
+ if not name in scoremap[run_id]:
+ scoremap[run_id][name] = IterationResult()
+
+
+ scoremap[run_id][name].durations.append(float(total_duration))
+
+ for row in db.execute(QUERY_PERCENT_JANK):
+ run_id = row[0]
+ name = row[1]
+ jank_count = row[2]
+ total_count = row[3]
+
+ if run_id in scoremap.keys() and name in scoremap[run_id].keys():
+ scoremap[run_id][name].jank_count = long(jank_count)
+ scoremap[run_id][name].total_count = long(total_count)
+
+
+ db.close()
+ return scoremap
+
+def score_device(name, serial, pull = False, verbose = False):
+ dbpath = OUT_PATH + name + ".db"
+
+ if pull:
+ adbutil.root(serial)
+ adbutil.pull(serial, DB_PATH, dbpath)
+
+ scoremap = None
+ try:
+ scoremap = get_scoremap(dbpath)
+ except sqlite3.DatabaseError:
+ print "Database corrupt, fetching..."
+ adbutil.root(serial)
+ adbutil.pull(serial, DB_PATH, dbpath)
+ scoremap = get_scoremap(dbpath)
+
+ per_test_score = {}
+ per_test_sample_count = {}
+ global_overall = {}
+
+ for run_id in iter(scoremap):
+ overall = []
+ if len(scoremap[run_id]) < 1:
+ if verbose:
+ print "Skipping short run %s" % run_id
+ continue
+ print "Run: %s" % run_id
+ for test in iter(scoremap[run_id]):
+ if verbose:
+ print "\t%s" % test
+ scores = []
+ sample_count = 0
+ res = scoremap[run_id][test]
+ stddev = numpy.std(res.durations)
+ mean = numpy.mean(res.durations)
+ sample_count = len(res.durations)
+ pj = 100 * res.jank_count / float(res.total_count)
+ score = stddev * mean *pj
+ if score == 0:
+ score = 1
+ scores.append(score)
+ if verbose:
+ print "\tScore = %f x %f x %f = %f (%d samples)" % (stddev, mean, pj, score, len(res.durations))
+
+ geo_run = scipy.stats.gmean(scores)
+ if test not in per_test_score:
+ per_test_score[test] = []
+
+ if test not in per_test_sample_count:
+ per_test_sample_count[test] = []
+
+ per_test_score[test].append(geo_run)
+ per_test_sample_count[test].append(int(sample_count))
+ overall.append(geo_run)
+
+ if not verbose:
+ print "\t%s:\t%0.2f (%0.2f avg. sample count)" % (test, geo_run, sample_count)
+ else:
+ print "\tOverall:\t%0.2f (%0.2f avg. sample count)" % (geo_run, sample_count)
+ print ""
+
+ global_overall[run_id] = scipy.stats.gmean(overall)
+ print "Run Overall: %f" % global_overall[run_id]
+ print ""
+
+ print ""
+ print "Variability (CV) - %s:" % name
+
+ for test in per_test_score:
+ print "\t%s:\t%0.2f%% (%0.2f avg sample count)" % (test, 100 * scipy.stats.variation(per_test_score[test]), numpy.mean(per_test_sample_count[test]))
+
+ print "\tOverall: %0.2f%%" % (100 * scipy.stats.variation([x for x in global_overall.values()]))
+ print ""
+
+def parse_options(argv):
+ usage = 'Usage: %prog [options]'
+ desc = 'Example: %prog'
+ parser = optparse.OptionParser(usage=usage, description=desc)
+ parser.add_option("-p", dest='pull', action="store_true")
+ parser.add_option("-d", dest='device', action="store")
+ parser.add_option("-v", dest='verbose', action="store_true")
+ options, categories = parser.parse_args(argv[1:])
+ return options
+
+def main():
+ options = parse_options(sys.argv)
+ if options.device != None:
+ score_device(options.device, DEVICES[options.device], options.pull, options.verbose)
+ else:
+ for name, serial in DEVICES.iteritems():
+ print "======== %s =========" % name
+ score_device(name, serial, options.pull, options.verbose)
+
+if __name__ == "__main__":
+ main()
diff --git a/tests/JankBench/scripts/runall.py b/tests/JankBench/scripts/runall.py
new file mode 100755
index 0000000..d9a0662
--- /dev/null
+++ b/tests/JankBench/scripts/runall.py
@@ -0,0 +1,65 @@
+#!/usr/bin/python
+
+import optparse
+import sys
+import time
+
+import adbutil
+from devices import DEVICES
+
+def parse_options(argv):
+ usage = 'Usage: %prog [options]'
+ desc = 'Example: %prog'
+ parser = optparse.OptionParser(usage=usage, description=desc)
+ parser.add_option("-c", dest='clear', action="store_true")
+ parser.add_option("-d", dest='device', action="store",)
+ parser.add_option("-t", dest='trace', action="store_true")
+ options, categories = parser.parse_args(argv[1:])
+ return (options, categories)
+
+def clear_data(device = None):
+ if device != None:
+ dev = DEVICES[device]
+ adbutil.root(dev)
+ adbutil.pm(dev, "clear", "com.android.benchmark")
+ else:
+ for name, dev in DEVICES.iteritems():
+ print("Clearing " + name)
+ adbutil.root(dev)
+ adbutil.pm(dev, "clear", "com.android.benchmark")
+
+def start_device(name, dev):
+ print("Go " + name + "!")
+ try:
+ adbutil.am(dev, "force-stop", "com.android.benchmark")
+ adbutil.wake(dev)
+ adbutil.am(dev, "start",
+ ["-n", "\"com.android.benchmark/.app.RunLocalBenchmarksActivity\"",
+ "--eia", "\"com.android.benchmark.EXTRA_ENABLED_BENCHMARK_IDS\"", "\"0,1,2,3,4,5,6\"",
+ "--ei", "\"com.android.benchmark.EXTRA_RUN_COUNT\"", "\"5\""])
+ except adbutil.AdbError:
+ print "Couldn't launch " + name + "."
+
+def start_benchmark(device, trace):
+ if device != None:
+ start_device(device, DEVICES[device])
+ if trace:
+ time.sleep(3)
+ adbutil.trace(DEVICES[device])
+ else:
+ if trace:
+ print("Note: -t only valid with -d, can't trace")
+ for name, dev in DEVICES.iteritems():
+ start_device(name, dev)
+
+def main():
+ options, categories = parse_options(sys.argv)
+ if options.clear:
+ print options.device
+ clear_data(options.device)
+ else:
+ start_benchmark(options.device, options.trace)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/tools/aapt2/cmd/Compile.cpp b/tools/aapt2/cmd/Compile.cpp
index 7c1e96e..3bec082 100644
--- a/tools/aapt2/cmd/Compile.cpp
+++ b/tools/aapt2/cmd/Compile.cpp
@@ -186,6 +186,12 @@
out_path_data->push_back(std::move(path_data.value()));
}
}
+
+ // File-system directory enumeration order is platform-dependent. Sort the result to remove any
+ // inconsistencies between platforms.
+ std::sort(
+ out_path_data->begin(), out_path_data->end(),
+ [](const ResourcePathData& a, const ResourcePathData& b) { return a.source < b.source; });
return true;
}
diff --git a/tools/aapt2/integration-tests/AutoVersionTest/AndroidManifest.xml b/tools/aapt2/integration-tests/AutoVersionTest/AndroidManifest.xml
index e66d709..dd452399 100644
--- a/tools/aapt2/integration-tests/AutoVersionTest/AndroidManifest.xml
+++ b/tools/aapt2/integration-tests/AutoVersionTest/AndroidManifest.xml
@@ -18,4 +18,7 @@
package="com.android.aapt.autoversiontest">
<uses-sdk android:minSdkVersion="7" />
+ <application>
+ <uses-library android:name="clockwork-system" />
+ </application>
</manifest>
diff --git a/tools/aapt2/link/ManifestFixer.cpp b/tools/aapt2/link/ManifestFixer.cpp
index da05dc3..ccc3470 100644
--- a/tools/aapt2/link/ManifestFixer.cpp
+++ b/tools/aapt2/link/ManifestFixer.cpp
@@ -29,6 +29,22 @@
namespace aapt {
+static bool RequiredNameIsNotEmpty(xml::Element* el, SourcePathDiagnostics* diag) {
+ xml::Attribute* attr = el->FindAttribute(xml::kSchemaAndroid, "name");
+ if (attr == nullptr) {
+ diag->Error(DiagMessage(el->line_number)
+ << "<" << el->name << "> is missing attribute 'android:name'");
+ return false;
+ }
+
+ if (attr->value.empty()) {
+ diag->Error(DiagMessage(el->line_number)
+ << "attribute 'android:name' in <" << el->name << "> tag must not be empty");
+ return false;
+ }
+ return true;
+}
+
// This is how PackageManager builds class names from AndroidManifest.xml entries.
static bool NameIsJavaClassName(xml::Element* el, xml::Attribute* attr,
SourcePathDiagnostics* diag) {
@@ -59,21 +75,29 @@
}
static bool RequiredNameIsJavaClassName(xml::Element* el, SourcePathDiagnostics* diag) {
- if (xml::Attribute* attr = el->FindAttribute(xml::kSchemaAndroid, "name")) {
- return NameIsJavaClassName(el, attr, diag);
+ xml::Attribute* attr = el->FindAttribute(xml::kSchemaAndroid, "name");
+ if (attr == nullptr) {
+ diag->Error(DiagMessage(el->line_number)
+ << "<" << el->name << "> is missing attribute 'android:name'");
+ return false;
}
- diag->Error(DiagMessage(el->line_number)
- << "<" << el->name << "> is missing attribute 'android:name'");
- return false;
+ return NameIsJavaClassName(el, attr, diag);
}
static bool RequiredNameIsJavaPackage(xml::Element* el, SourcePathDiagnostics* diag) {
- if (xml::Attribute* attr = el->FindAttribute(xml::kSchemaAndroid, "name")) {
- return util::IsJavaPackageName(attr->value);
+ xml::Attribute* attr = el->FindAttribute(xml::kSchemaAndroid, "name");
+ if (attr == nullptr) {
+ diag->Error(DiagMessage(el->line_number)
+ << "<" << el->name << "> is missing attribute 'android:name'");
+ return false;
}
- diag->Error(DiagMessage(el->line_number)
- << "<" << el->name << "> is missing attribute 'android:name'");
- return false;
+
+ if (!util::IsJavaPackageName(attr->value)) {
+ diag->Error(DiagMessage(el->line_number) << "attribute 'android:name' in <" << el->name
+ << "> tag must be a valid Java package name");
+ return false;
+ }
+ return true;
}
static xml::XmlNodeAction::ActionFuncWithDiag RequiredAndroidAttribute(const std::string& attr) {
@@ -213,8 +237,8 @@
// Common <intent-filter> actions.
xml::XmlNodeAction intent_filter_action;
- intent_filter_action["action"];
- intent_filter_action["category"];
+ intent_filter_action["action"].Action(RequiredNameIsNotEmpty);
+ intent_filter_action["category"].Action(RequiredNameIsNotEmpty);
intent_filter_action["data"];
// Common <meta-data> actions.
@@ -317,8 +341,8 @@
xml::XmlNodeAction& application_action = manifest_action["application"];
application_action.Action(OptionalNameIsJavaClassName);
- application_action["uses-library"].Action(RequiredNameIsJavaPackage);
- application_action["library"].Action(RequiredNameIsJavaPackage);
+ application_action["uses-library"].Action(RequiredNameIsNotEmpty);
+ application_action["library"].Action(RequiredNameIsNotEmpty);
xml::XmlNodeAction& static_library_action = application_action["static-library"];
static_library_action.Action(RequiredNameIsJavaPackage);
diff --git a/tools/aapt2/link/ManifestFixer_test.cpp b/tools/aapt2/link/ManifestFixer_test.cpp
index c6f895b..ed98d71 100644
--- a/tools/aapt2/link/ManifestFixer_test.cpp
+++ b/tools/aapt2/link/ManifestFixer_test.cpp
@@ -494,4 +494,34 @@
ASSERT_THAT(manifest, IsNull());
}
+
+TEST_F(ManifestFixerTest, UsesLibraryMustHaveNonEmptyName) {
+ std::string input = R"(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android">
+ <application>
+ <uses-library android:name="" />
+ </application>
+ </manifest>)";
+ EXPECT_THAT(Verify(input), IsNull());
+
+ input = R"(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android">
+ <application>
+ <uses-library />
+ </application>
+ </manifest>)";
+ EXPECT_THAT(Verify(input), IsNull());
+
+ input = R"(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android">
+ <application>
+ <uses-library android:name="blahhh" />
+ </application>
+ </manifest>)";
+ EXPECT_THAT(Verify(input), NotNull());
+}
+
} // namespace aapt
diff --git a/tools/sdkparcelables/Android.bp b/tools/sdkparcelables/Android.bp
new file mode 100644
index 0000000..00fb8aa
--- /dev/null
+++ b/tools/sdkparcelables/Android.bp
@@ -0,0 +1,22 @@
+java_binary_host {
+ name: "sdkparcelables",
+ manifest: "manifest.txt",
+ srcs: [
+ "src/**/*.kt",
+ ],
+ static_libs: [
+ "asm-6.0",
+ ],
+}
+
+java_library_host {
+ name: "sdkparcelables_test",
+ manifest: "manifest.txt",
+ srcs: [
+ "tests/**/*.kt",
+ ],
+ static_libs: [
+ "sdkparcelables",
+ "junit",
+ ],
+}
diff --git a/tools/sdkparcelables/manifest.txt b/tools/sdkparcelables/manifest.txt
new file mode 100644
index 0000000..cd5420c
--- /dev/null
+++ b/tools/sdkparcelables/manifest.txt
@@ -0,0 +1 @@
+Main-class: com.android.sdkparcelables.MainKt
diff --git a/tools/sdkparcelables/src/com/android/sdkparcelables/AncestorCollector.kt b/tools/sdkparcelables/src/com/android/sdkparcelables/AncestorCollector.kt
new file mode 100644
index 0000000..f278aec
--- /dev/null
+++ b/tools/sdkparcelables/src/com/android/sdkparcelables/AncestorCollector.kt
@@ -0,0 +1,28 @@
+package com.android.sdkparcelables
+
+import org.objectweb.asm.ClassVisitor
+import java.util.*
+
+data class Ancestors(val superName: String?, val interfaces: List<String>?)
+
+/** A class that implements an ASM ClassVisitor that collects super class and
+ * implemented interfaces for each class that it visits.
+ */
+class AncestorCollector(api: Int, dest: ClassVisitor?) : ClassVisitor(api, dest) {
+ private val _ancestors = LinkedHashMap<String, Ancestors>()
+
+ val ancestors: Map<String, Ancestors>
+ get() = _ancestors
+
+ override fun visit(version: Int, access: Int, name: String?, signature: String?,
+ superName: String?, interfaces: Array<out String>?) {
+ name!!
+
+ val old = _ancestors.put(name, Ancestors(superName, interfaces?.toList()))
+ if (old != null) {
+ throw RuntimeException("class $name already found")
+ }
+
+ super.visit(version, access, name, signature, superName, interfaces)
+ }
+}
\ No newline at end of file
diff --git a/tools/sdkparcelables/src/com/android/sdkparcelables/Main.kt b/tools/sdkparcelables/src/com/android/sdkparcelables/Main.kt
new file mode 100644
index 0000000..3e9d92c
--- /dev/null
+++ b/tools/sdkparcelables/src/com/android/sdkparcelables/Main.kt
@@ -0,0 +1,56 @@
+package com.android.sdkparcelables
+
+import org.objectweb.asm.ClassReader
+import org.objectweb.asm.Opcodes
+import java.io.File
+import java.io.IOException
+import java.util.zip.ZipFile
+
+fun main(args: Array<String>) {
+ if (args.size != 2) {
+ usage()
+ }
+
+ val zipFileName = args[0]
+ val aidlFileName = args[1]
+
+ val zipFile: ZipFile
+
+ try {
+ zipFile = ZipFile(zipFileName)
+ } catch (e: IOException) {
+ System.err.println("error reading input jar: ${e.message}")
+ kotlin.system.exitProcess(2)
+ }
+
+ val ancestorCollector = AncestorCollector(Opcodes.ASM6, null)
+
+ for (entry in zipFile.entries()) {
+ if (entry.name.endsWith(".class")) {
+ val reader = ClassReader(zipFile.getInputStream(entry))
+ reader.accept(ancestorCollector,
+ ClassReader.SKIP_CODE or ClassReader.SKIP_DEBUG or ClassReader.SKIP_FRAMES)
+ }
+ }
+
+ val parcelables = ParcelableDetector.ancestorsToParcelables(ancestorCollector.ancestors)
+
+ try {
+ val outFile = File(aidlFileName)
+ val outWriter = outFile.bufferedWriter()
+ for (parcelable in parcelables) {
+ outWriter.write("parcelable ")
+ outWriter.write(parcelable.replace('/', '.').replace('$', '.'))
+ outWriter.write(";\n")
+ }
+ outWriter.flush()
+ } catch (e: IOException) {
+ System.err.println("error writing output aidl: ${e.message}")
+ kotlin.system.exitProcess(2)
+ }
+}
+
+fun usage() {
+ System.err.println("Usage: <input jar> <output aidl>")
+ kotlin.system.exitProcess(1)
+}
\ No newline at end of file
diff --git a/tools/sdkparcelables/src/com/android/sdkparcelables/ParcelableDetector.kt b/tools/sdkparcelables/src/com/android/sdkparcelables/ParcelableDetector.kt
new file mode 100644
index 0000000..620f798
--- /dev/null
+++ b/tools/sdkparcelables/src/com/android/sdkparcelables/ParcelableDetector.kt
@@ -0,0 +1,52 @@
+package com.android.sdkparcelables
+
+/** A class that uses an ancestor map to find all classes that
+ * implement android.os.Parcelable, including indirectly through
+ * super classes or super interfaces.
+ */
+class ParcelableDetector {
+ companion object {
+ fun ancestorsToParcelables(ancestors: Map<String, Ancestors>): List<String> {
+ val impl = Impl(ancestors)
+ impl.build()
+ return impl.parcelables
+ }
+ }
+
+ private class Impl(val ancestors: Map<String, Ancestors>) {
+ val isParcelableCache = HashMap<String, Boolean>()
+ val parcelables = ArrayList<String>()
+
+ fun build() {
+ val classList = ancestors.keys
+ classList.filterTo(parcelables, this::isParcelable)
+ parcelables.sort()
+ }
+
+ private fun isParcelable(c: String?): Boolean {
+ if (c == null) {
+ return false
+ }
+
+ if (c == "android/os/Parcelable") {
+ return true
+ }
+
+ val old = isParcelableCache[c]
+ if (old != null) {
+ return old
+ }
+
+ val cAncestors = ancestors[c] ?:
+ throw RuntimeException("class $c missing ancestor information")
+
+ val seq = (cAncestors.interfaces?.asSequence() ?: emptySequence()) +
+ cAncestors.superName
+
+ val ancestorIsParcelable = seq.any(this::isParcelable)
+
+ isParcelableCache[c] = ancestorIsParcelable
+ return ancestorIsParcelable
+ }
+ }
+}
diff --git a/tools/sdkparcelables/tests/com/android/sdkparcelables/ParcelableDetectorTest.kt b/tools/sdkparcelables/tests/com/android/sdkparcelables/ParcelableDetectorTest.kt
new file mode 100644
index 0000000..edfc825
--- /dev/null
+++ b/tools/sdkparcelables/tests/com/android/sdkparcelables/ParcelableDetectorTest.kt
@@ -0,0 +1,57 @@
+package com.android.sdkparcelables
+
+import junit.framework.TestCase.assertEquals
+import org.junit.Test
+
+class ParcelableDetectorTest {
+ @Test
+ fun `detect implements`() {
+ val ancestorMap = mapOf(
+ testAncestors("android/test/Parcelable",null, "android/os/Parcelable"),
+ testAncestors("android/os/Parcelable", null))
+
+ val parcelables = ParcelableDetector.ancestorsToParcelables(ancestorMap)
+
+ assertEquals(parcelables, listOf("android/os/Parcelable", "android/test/Parcelable"))
+ }
+
+ @Test
+ fun `detect implements in reverse order`() {
+ val ancestorMap = mapOf(
+ testAncestors("android/os/Parcelable", null),
+ testAncestors("android/test/Parcelable",null, "android/os/Parcelable"))
+
+ val parcelables = ParcelableDetector.ancestorsToParcelables(ancestorMap)
+
+ assertEquals(parcelables, listOf("android/os/Parcelable", "android/test/Parcelable"))
+ }
+
+ @Test
+ fun `detect super implements`() {
+ val ancestorMap = mapOf(
+ testAncestors("android/test/SuperParcelable",null, "android/os/Parcelable"),
+ testAncestors("android/test/Parcelable","android/test/SuperParcelable"),
+ testAncestors("android/os/Parcelable", null))
+
+ val parcelables = ParcelableDetector.ancestorsToParcelables(ancestorMap)
+
+ assertEquals(parcelables, listOf("android/os/Parcelable", "android/test/Parcelable", "android/test/SuperParcelable"))
+ }
+
+ @Test
+ fun `detect super interface`() {
+ val ancestorMap = mapOf(
+ testAncestors("android/test/IParcelable",null, "android/os/Parcelable"),
+ testAncestors("android/test/Parcelable",null, "android/test/IParcelable"),
+ testAncestors("android/os/Parcelable", null))
+
+ val parcelables = ParcelableDetector.ancestorsToParcelables(ancestorMap)
+
+ assertEquals(parcelables, listOf("android/os/Parcelable", "android/test/IParcelable", "android/test/Parcelable"))
+ }
+
+}
+
+private fun testAncestors(name: String, superName: String?, vararg interfaces: String): Pair<String, Ancestors> {
+ return Pair(name, Ancestors(superName, interfaces.toList()))
+}
diff --git a/tools/stats_log_api_gen/Collation.cpp b/tools/stats_log_api_gen/Collation.cpp
index a25784d..80853b1 100644
--- a/tools/stats_log_api_gen/Collation.cpp
+++ b/tools/stats_log_api_gen/Collation.cpp
@@ -112,7 +112,7 @@
case FieldDescriptor::TYPE_MESSAGE:
// TODO: not the final package name
if (field->message_type()->full_name() ==
- "android.os.statsd.AttributionChain") {
+ "android.os.statsd.AttributionNode") {
return JAVA_TYPE_ATTRIBUTION_CHAIN;
} else {
return JAVA_TYPE_OBJECT;
diff --git a/tools/stats_log_api_gen/main.cpp b/tools/stats_log_api_gen/main.cpp
index 5e93c08..89749fb 100644
--- a/tools/stats_log_api_gen/main.cpp
+++ b/tools/stats_log_api_gen/main.cpp
@@ -769,7 +769,7 @@
AtomDecl attributionDecl;
vector<java_type_t> attributionSignature;
- collate_atom(android::os::statsd::Attribution::descriptor(),
+ collate_atom(android::os::statsd::AttributionNode::descriptor(),
&attributionDecl, &attributionSignature);
// Write the .cpp file
diff --git a/tools/stats_log_api_gen/test.proto b/tools/stats_log_api_gen/test.proto
index 84b22cda..66cbee8 100644
--- a/tools/stats_log_api_gen/test.proto
+++ b/tools/stats_log_api_gen/test.proto
@@ -39,7 +39,7 @@
}
message AllTypesAtom {
- optional android.os.statsd.AttributionChain attribution_chain = 1;
+ repeated android.os.statsd.AttributionNode attribution_chain = 1;
optional double double_field = 2;
optional float float_field = 3;
optional int64 int64_field = 4;
@@ -99,12 +99,12 @@
}
}
-message BadAttributionChainPositionAtom {
+message BadAttributionNodePositionAtom {
optional int32 field1 = 1;
- optional android.os.statsd.AttributionChain attribution = 2;
+ repeated android.os.statsd.AttributionNode attribution = 2;
}
-message BadAttributionChainPosition {
- oneof event { BadAttributionChainPositionAtom bad = 1; }
+message BadAttributionNodePosition {
+ oneof event { BadAttributionNodePositionAtom bad = 1; }
}
diff --git a/tools/stats_log_api_gen/test_collation.cpp b/tools/stats_log_api_gen/test_collation.cpp
index b2b7298..9e22cd9 100644
--- a/tools/stats_log_api_gen/test_collation.cpp
+++ b/tools/stats_log_api_gen/test_collation.cpp
@@ -191,10 +191,10 @@
* Test that atoms that have an attribution chain not in the first position are
* rejected.
*/
-TEST(CollationTest, FailBadAttributionChainPosition) {
+TEST(CollationTest, FailBadAttributionNodePosition) {
Atoms atoms;
int errorCount =
- collate_atoms(BadAttributionChainPosition::descriptor(), &atoms);
+ collate_atoms(BadAttributionNodePosition::descriptor(), &atoms);
EXPECT_EQ(1, errorCount);
}
diff --git a/wifi/java/android/net/wifi/WifiScanner.java b/wifi/java/android/net/wifi/WifiScanner.java
index e3752ac..928a1da 100644
--- a/wifi/java/android/net/wifi/WifiScanner.java
+++ b/wifi/java/android/net/wifi/WifiScanner.java
@@ -160,6 +160,24 @@
*/
public static final int REPORT_EVENT_NO_BATCH = (1 << 2);
+ /**
+ * This is used to indicate the purpose of the scan to the wifi chip in
+ * {@link ScanSettings#type}.
+ * On devices with multiple hardware radio chains (and hence different modes of scan),
+ * this type serves as an indication to the hardware on what mode of scan to perform.
+ * Only apps holding android.Manifest.permission.NETWORK_STACK permission can set this value.
+ *
+ * Note: This serves as an intent and not as a stipulation, the wifi chip
+ * might honor or ignore the indication based on the current radio conditions. Always
+ * use the {@link ScanResult#radioChainInfos} to figure out the radio chain configuration used
+ * to receive the corresponding scan result.
+ */
+ /** {@hide} */
+ public static final int TYPE_LOW_LATENCY = 0;
+ /** {@hide} */
+ public static final int TYPE_LOW_POWER = 1;
+ /** {@hide} */
+ public static final int TYPE_HIGH_ACCURACY = 2;
/** {@hide} */
public static final String SCAN_PARAMS_SCAN_SETTINGS_KEY = "ScanSettings";
@@ -193,7 +211,8 @@
* list of hidden networks to scan for. Explicit probe requests are sent out for such
* networks during scan. Only valid for single scan requests.
* {@hide}
- * */
+ */
+ @RequiresPermission(android.Manifest.permission.NETWORK_STACK)
public HiddenNetwork[] hiddenNetworks;
/** period of background scan; in millisecond, 0 => single shot scan */
public int periodInMs;
@@ -223,6 +242,13 @@
* {@hide}
*/
public boolean isPnoScan;
+ /**
+ * Indicate the type of scan to be performed by the wifi chip.
+ * Default value: {@link #TYPE_LOW_LATENCY}.
+ * {@hide}
+ */
+ @RequiresPermission(android.Manifest.permission.NETWORK_STACK)
+ public int type = TYPE_LOW_LATENCY;
/** Implement the Parcelable interface {@hide} */
public int describeContents() {
@@ -239,6 +265,7 @@
dest.writeInt(maxPeriodInMs);
dest.writeInt(stepCount);
dest.writeInt(isPnoScan ? 1 : 0);
+ dest.writeInt(type);
if (channels != null) {
dest.writeInt(channels.length);
for (int i = 0; i < channels.length; i++) {
@@ -272,6 +299,7 @@
settings.maxPeriodInMs = in.readInt();
settings.stepCount = in.readInt();
settings.isPnoScan = in.readInt() == 1;
+ settings.type = in.readInt();
int num_channels = in.readInt();
settings.channels = new ChannelSpec[num_channels];
for (int i = 0; i < num_channels; i++) {
diff --git a/wifi/java/android/net/wifi/hotspot2/ProvisioningCallback.java b/wifi/java/android/net/wifi/hotspot2/ProvisioningCallback.java
index 8b86cdd..2ea6e79 100644
--- a/wifi/java/android/net/wifi/hotspot2/ProvisioningCallback.java
+++ b/wifi/java/android/net/wifi/hotspot2/ProvisioningCallback.java
@@ -26,13 +26,49 @@
*/
public abstract class ProvisioningCallback {
- /**
+ /**
* The reason code for Provisioning Failure due to connection failure to OSU AP.
* @hide
*/
public static final int OSU_FAILURE_AP_CONNECTION = 1;
/**
+ * The reason code for Provisioning Failure due to connection failure to OSU AP.
+ * @hide
+ */
+ public static final int OSU_FAILURE_SERVER_URL_INVALID = 2;
+
+ /**
+ * The reason code for Provisioning Failure due to connection failure to OSU AP.
+ * @hide
+ */
+ public static final int OSU_FAILURE_SERVER_CONNECTION = 3;
+
+ /**
+ * The reason code for Provisioning Failure due to connection failure to OSU AP.
+ * @hide
+ */
+ public static final int OSU_FAILURE_SERVER_VALIDATION = 4;
+
+ /**
+ * The reason code for Provisioning Failure due to connection failure to OSU AP.
+ * @hide
+ */
+ public static final int OSU_FAILURE_PROVIDER_VERIFICATION = 5;
+
+ /**
+ * The reason code for Provisioning Failure when a provisioning flow is aborted.
+ * @hide
+ */
+ public static final int OSU_FAILURE_PROVISIONING_ABORTED = 6;
+
+ /**
+ * The reason code for Provisioning Failure when a provisioning flow is aborted.
+ * @hide
+ */
+ public static final int OSU_FAILURE_PROVISIONING_NOT_AVAILABLE = 7;
+
+ /**
* The status code for Provisioning flow to indicate connecting to OSU AP
* @hide
*/
@@ -45,6 +81,24 @@
public static final int OSU_STATUS_AP_CONNECTED = 2;
/**
+ * The status code for Provisioning flow to indicate connecting to OSU AP
+ * @hide
+ */
+ public static final int OSU_STATUS_SERVER_CONNECTED = 3;
+
+ /**
+ * The status code for Provisioning flow to indicate connecting to OSU AP
+ * @hide
+ */
+ public static final int OSU_STATUS_SERVER_VALIDATED = 4;
+
+ /**
+ * The status code for Provisioning flow to indicate connecting to OSU AP
+ * @hide
+ */
+ public static final int OSU_STATUS_PROVIDER_VERIFIED = 5;
+
+ /**
* Provisioning status for OSU failure
* @param status indicates error condition
*/
diff --git a/wifi/java/android/net/wifi/rtt/ResponderConfig.java b/wifi/java/android/net/wifi/rtt/ResponderConfig.java
index 8be7782..c3e1007 100644
--- a/wifi/java/android/net/wifi/rtt/ResponderConfig.java
+++ b/wifi/java/android/net/wifi/rtt/ResponderConfig.java
@@ -37,7 +37,7 @@
*
* @hide (@SystemApi)
*/
-public class ResponderConfig implements Parcelable {
+public final class ResponderConfig implements Parcelable {
private static final int AWARE_BAND_2_DISCOVERY_CHANNEL = 2437;
/** @hide */
@@ -122,15 +122,14 @@
/**
* The MAC address of the Responder. Will be null if a Wi-Fi Aware peer identifier (the
* peerHandle field) ise used to identify the Responder.
- * TODO: convert to MacAddress
*/
- public MacAddress macAddress;
+ public final MacAddress macAddress;
/**
* The peer identifier of a Wi-Fi Aware Responder. Will be null if a MAC Address (the macAddress
* field) is used to identify the Responder.
*/
- public PeerHandle peerHandle;
+ public final PeerHandle peerHandle;
/**
* The device type of the Responder.
@@ -171,7 +170,7 @@
public final int preamble;
/**
- * Constructs Responder configuration.
+ * Constructs Responder configuration, using a MAC address to identify the Responder.
*
* @param macAddress The MAC address of the Responder.
* @param responderType The type of the responder device, specified using
@@ -210,7 +209,7 @@
}
/**
- * Constructs Responder configuration.
+ * Constructs Responder configuration, using a Wi-Fi Aware PeerHandle to identify the Responder.
*
* @param peerHandle The Wi-Fi Aware peer identifier of the Responder.
* @param responderType The type of the responder device, specified using
@@ -245,6 +244,45 @@
}
/**
+ * Constructs Responder configuration. This is an internal-only constructor which specifies both
+ * a MAC address and a Wi-Fi PeerHandle to identify the Responder.
+ *
+ * @param macAddress The MAC address of the Responder.
+ * @param peerHandle The Wi-Fi Aware peer identifier of the Responder.
+ * @param responderType The type of the responder device, specified using
+ * {@link ResponderType}.
+ * @param supports80211mc Indicates whether the responder supports IEEE 802.11mc.
+ * @param channelWidth Responder channel bandwidth, specified using {@link ChannelWidth}.
+ * @param frequency The primary 20 MHz frequency (in MHz) of the channel of the Responder.
+ * @param centerFreq0 Not used if the {@code channelWidth} is 20 MHz. If the Responder uses
+ * 40, 80 or 160 MHz, this is the center frequency (in MHz), if the
+ * Responder uses 80 + 80 MHz, this is the center frequency of the first
+ * segment (in MHz).
+ * @param centerFreq1 Only used if the {@code channelWidth} is 80 + 80 MHz. If the
+ * Responder
+ * uses 80 + 80 MHz, this is the center frequency of the second segment
+ * (in
+ * MHz).
+ * @param preamble The preamble used by the Responder, specified using
+ * {@link PreambleType}.
+ * @hide
+ */
+ public ResponderConfig(@NonNull MacAddress macAddress, @NonNull PeerHandle peerHandle,
+ @ResponderType int responderType, boolean supports80211mc,
+ @ChannelWidth int channelWidth, int frequency, int centerFreq0, int centerFreq1,
+ @PreambleType int preamble) {
+ this.macAddress = macAddress;
+ this.peerHandle = peerHandle;
+ this.responderType = responderType;
+ this.supports80211mc = supports80211mc;
+ this.channelWidth = channelWidth;
+ this.frequency = frequency;
+ this.centerFreq0 = centerFreq0;
+ this.centerFreq1 = centerFreq1;
+ this.preamble = preamble;
+ }
+
+ /**
* Creates a Responder configuration from a {@link ScanResult} corresponding to an Access
* Point (AP), which can be obtained from {@link android.net.wifi.WifiManager#getScanResults()}.
*/
diff --git a/wifi/tests/src/android/net/wifi/WifiScannerTest.java b/wifi/tests/src/android/net/wifi/WifiScannerTest.java
index e542789..a4366d4 100644
--- a/wifi/tests/src/android/net/wifi/WifiScannerTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiScannerTest.java
@@ -16,19 +16,23 @@
package android.net.wifi;
+import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.validateMockitoUsage;
import static org.mockito.Mockito.when;
import android.content.Context;
import android.os.Handler;
+import android.os.Parcel;
import android.os.test.TestLooper;
import android.test.suitebuilder.annotation.SmallTest;
+import android.net.wifi.WifiScanner.ScanSettings;
import com.android.internal.util.test.BidirectionalAsyncChannelServer;
import org.junit.After;
import org.junit.Before;
+import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -70,4 +74,50 @@
validateMockitoUsage();
}
+ /**
+ * Verify parcel read/write for ScanSettings.
+ */
+ @Test
+ public void verifyScanSettingsParcelWithBand() throws Exception {
+ ScanSettings writeSettings = new ScanSettings();
+ writeSettings.type = WifiScanner.TYPE_LOW_POWER;
+ writeSettings.band = WifiScanner.WIFI_BAND_BOTH_WITH_DFS;
+
+ ScanSettings readSettings = parcelWriteRead(writeSettings);
+ assertEquals(readSettings.type, writeSettings.type);
+ assertEquals(readSettings.band, writeSettings.band);
+ assertEquals(0, readSettings.channels.length);
+ }
+
+ /**
+ * Verify parcel read/write for ScanSettings.
+ */
+ @Test
+ public void verifyScanSettingsParcelWithChannels() throws Exception {
+ ScanSettings writeSettings = new ScanSettings();
+ writeSettings.type = WifiScanner.TYPE_HIGH_ACCURACY;
+ writeSettings.band = WifiScanner.WIFI_BAND_UNSPECIFIED;
+ writeSettings.channels = new WifiScanner.ChannelSpec[] {
+ new WifiScanner.ChannelSpec(5),
+ new WifiScanner.ChannelSpec(7)
+ };
+
+ ScanSettings readSettings = parcelWriteRead(writeSettings);
+ assertEquals(writeSettings.type, readSettings.type);
+ assertEquals(writeSettings.band, readSettings.band);
+ assertEquals(2, readSettings.channels.length);
+ assertEquals(5, readSettings.channels[0].frequency);
+ assertEquals(7, readSettings.channels[1].frequency);
+ }
+
+ /**
+ * Write the provided {@link ScanSettings} to a parcel and deserialize it.
+ */
+ private static ScanSettings parcelWriteRead(ScanSettings writeSettings) throws Exception {
+ Parcel parcel = Parcel.obtain();
+ writeSettings.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0); // Rewind data position back to the beginning for read.
+ return ScanSettings.CREATOR.createFromParcel(parcel);
+ }
+
}