Merge "Make SurfaceControl parcelable (1/2)"
diff --git a/Android.bp b/Android.bp
index 32adc1c..81cd65c 100644
--- a/Android.bp
+++ b/Android.bp
@@ -639,11 +639,9 @@
"soong_zip",
],
- // Append protoc-gen-javastream tool's PATH otherwise aprotoc can't find the plugin tool
- cmd: "export PATH=$$PATH:$$(dirname $(location protoc-gen-javastream)) " +
- "&& mkdir -p $(genDir)/$(in) " +
+ cmd: "mkdir -p $(genDir)/$(in) " +
"&& $(location aprotoc) " +
- " --plugin=protoc-gen-java-stream=protoc-gen-javastream " +
+ " --plugin=$(location protoc-gen-javastream) " +
" --dependency_out=$(depfile) " +
" --javastream_out=$(genDir)/$(in) " +
" -Iexternal/protobuf/src " +
diff --git a/Android.mk b/Android.mk
index 8ce2616..ce504a5 100644
--- a/Android.mk
+++ b/Android.mk
@@ -296,10 +296,9 @@
$(SOONG_FRAMEWORK_SRCS)))
# Find all generated files that were used to compile framework.jar
-files_to_check_apis += \
- $(addprefix ../../,\
- $(filter $(OUT_DIR)/%,\
- $(SOONG_FRAMEWORK_SRCS)))
+files_to_check_apis_generated := \
+ $(filter $(OUT_DIR)/%,\
+ $(SOONG_FRAMEWORK_SRCS))
# These are relative to frameworks/base
# FRAMEWORKS_BASE_SUBDIRS comes from build/core/pathmap.mk
@@ -340,8 +339,9 @@
framework_docs_LOCAL_SRCJARS := $(SOONG_FRAMEWORK_SRCJARS)
-framework_docs_LOCAL_INTERMEDIATE_SOURCES := \
- $(patsubst $(TARGET_OUT_COMMON_INTERMEDIATES)/%,%,$(libcore_to_document_generated))
+framework_docs_LOCAL_GENERATED_SOURCES := \
+ $(libcore_to_document_generated) \
+ $(files_to_check_apis_generated) \
framework_docs_LOCAL_API_CHECK_JAVA_LIBRARIES := \
core-oj \
@@ -462,7 +462,7 @@
include $(CLEAR_VARS)
LOCAL_SRC_FILES := $(framework_docs_LOCAL_API_CHECK_SRC_FILES)
-LOCAL_INTERMEDIATE_SOURCES := $(framework_docs_LOCAL_INTERMEDIATE_SOURCES)
+LOCAL_GENERATED_SOURCES := $(framework_docs_LOCAL_GENERATED_SOURCES)
LOCAL_SRCJARS:=$(framework_docs_LOCAL_SRCJARS)
LOCAL_JAVA_LIBRARIES := $(framework_docs_LOCAL_API_CHECK_JAVA_LIBRARIES)
LOCAL_MODULE_CLASS := $(framework_docs_LOCAL_MODULE_CLASS)
@@ -488,7 +488,7 @@
include $(CLEAR_VARS)
LOCAL_SRC_FILES := $(framework_docs_LOCAL_API_CHECK_SRC_FILES)
-LOCAL_INTERMEDIATE_SOURCES := $(framework_docs_LOCAL_INTERMEDIATE_SOURCES)
+LOCAL_GENERATED_SOURCES := $(framework_docs_LOCAL_GENERATED_SOURCES)
LOCAL_SRCJARS:=$(framework_docs_LOCAL_SRCJARS)
LOCAL_JAVA_LIBRARIES := $(framework_docs_LOCAL_API_CHECK_JAVA_LIBRARIES)
LOCAL_MODULE_CLASS := $(framework_docs_LOCAL_MODULE_CLASS)
@@ -514,7 +514,7 @@
include $(CLEAR_VARS)
LOCAL_SRC_FILES:=$(framework_docs_LOCAL_API_CHECK_SRC_FILES)
-LOCAL_INTERMEDIATE_SOURCES:=$(framework_docs_LOCAL_INTERMEDIATE_SOURCES)
+LOCAL_GENERATED_SOURCES:=$(framework_docs_LOCAL_GENERATED_SOURCES)
LOCAL_SRCJARS:=$(framework_docs_LOCAL_SRCJARS)
LOCAL_JAVA_LIBRARIES:=$(framework_docs_LOCAL_API_CHECK_JAVA_LIBRARIES)
LOCAL_MODULE_CLASS:=$(framework_docs_LOCAL_MODULE_CLASS)
@@ -549,7 +549,7 @@
include $(CLEAR_VARS)
LOCAL_SRC_FILES:=$(framework_docs_LOCAL_API_CHECK_SRC_FILES)
-LOCAL_INTERMEDIATE_SOURCES:=$(framework_docs_LOCAL_INTERMEDIATE_SOURCES)
+LOCAL_GENERATED_SOURCES:=$(framework_docs_LOCAL_GENERATED_SOURCES)
LOCAL_SRCJARS:=$(framework_docs_LOCAL_SRCJARS)
LOCAL_JAVA_LIBRARIES:=$(framework_docs_LOCAL_API_CHECK_JAVA_LIBRARIES)
LOCAL_MODULE_CLASS:=$(framework_docs_LOCAL_MODULE_CLASS)
@@ -586,7 +586,7 @@
include $(CLEAR_VARS)
LOCAL_SRC_FILES:=$(framework_docs_LOCAL_API_CHECK_SRC_FILES)
-LOCAL_INTERMEDIATE_SOURCES:=$(framework_docs_LOCAL_INTERMEDIATE_SOURCES)
+LOCAL_GENERATED_SOURCES:=$(framework_docs_LOCAL_GENERATED_SOURCES)
LOCAL_SRCJARS:=$(framework_docs_LOCAL_SRCJARS)
LOCAL_JAVA_LIBRARIES:=$(framework_docs_LOCAL_API_CHECK_JAVA_LIBRARIES)
LOCAL_MODULE_CLASS:=$(framework_docs_LOCAL_MODULE_CLASS)
@@ -624,7 +624,7 @@
include $(CLEAR_VARS)
LOCAL_SRC_FILES:=$(framework_docs_LOCAL_SRC_FILES)
-LOCAL_INTERMEDIATE_SOURCES:=$(framework_docs_LOCAL_INTERMEDIATE_SOURCES)
+LOCAL_GENERATED_SOURCES:=$(framework_docs_LOCAL_GENERATED_SOURCES)
LOCAL_SRCJARS:=$(framework_docs_LOCAL_SRCJARS)
LOCAL_JAVA_LIBRARIES:=$(framework_docs_LOCAL_JAVA_LIBRARIES)
LOCAL_MODULE_CLASS:=$(framework_docs_LOCAL_MODULE_CLASS)
@@ -658,7 +658,7 @@
include $(CLEAR_VARS)
LOCAL_SRC_FILES:=$(framework_docs_LOCAL_SRC_FILES)
-LOCAL_INTERMEDIATE_SOURCES:=$(framework_docs_LOCAL_INTERMEDIATE_SOURCES)
+LOCAL_GENERATED_SOURCES:=$(framework_docs_LOCAL_GENERATED_SOURCES)
LOCAL_SRCJARS:=$(framework_docs_LOCAL_SRCJARS)
LOCAL_JAVA_LIBRARIES:=$(framework_docs_LOCAL_JAVA_LIBRARIES)
LOCAL_MODULE_CLASS:=$(framework_docs_LOCAL_MODULE_CLASS)
@@ -694,7 +694,7 @@
include $(CLEAR_VARS)
LOCAL_SRC_FILES:=$(framework_docs_LOCAL_SRC_FILES)
-LOCAL_INTERMEDIATE_SOURCES:=$(framework_docs_LOCAL_INTERMEDIATE_SOURCES)
+LOCAL_GENERATED_SOURCES:=$(framework_docs_LOCAL_GENERATED_SOURCES)
LOCAL_SRCJARS:=$(framework_docs_LOCAL_SRCJARS)
LOCAL_JAVA_LIBRARIES:=$(framework_docs_LOCAL_JAVA_LIBRARIES)
LOCAL_MODULE_CLASS:=$(framework_docs_LOCAL_MODULE_CLASS)
@@ -736,7 +736,7 @@
include $(CLEAR_VARS)
LOCAL_SRC_FILES:=$(framework_docs_LOCAL_SRC_FILES)
-LOCAL_INTERMEDIATE_SOURCES:=$(framework_docs_LOCAL_INTERMEDIATE_SOURCES)
+LOCAL_GENERATED_SOURCES:=$(framework_docs_LOCAL_GENERATED_SOURCES)
LOCAL_SRCJARS:=$(framework_docs_LOCAL_SRCJARS)
LOCAL_JAVA_LIBRARIES:=$(framework_docs_LOCAL_JAVA_LIBRARIES)
LOCAL_MODULE_CLASS:=$(framework_docs_LOCAL_MODULE_CLASS)
@@ -781,7 +781,7 @@
include $(CLEAR_VARS)
LOCAL_SRC_FILES:=$(framework_docs_LOCAL_SRC_FILES)
-LOCAL_INTERMEDIATE_SOURCES:=$(framework_docs_LOCAL_INTERMEDIATE_SOURCES)
+LOCAL_GENERATED_SOURCES:=$(framework_docs_LOCAL_GENERATED_SOURCES)
LOCAL_SRCJARS:=$(framework_docs_LOCAL_SRCJARS)
LOCAL_STATIC_JAVA_LIBRARIES:=$(framework_docs_LOCAL_STATIC_JAVA_LIBRARIES)
LOCAL_JAVA_LIBRARIES:=$(framework_docs_LOCAL_JAVA_LIBRARIES)
@@ -810,7 +810,7 @@
include $(CLEAR_VARS)
LOCAL_SRC_FILES:=$(framework_docs_LOCAL_SRC_FILES)
-LOCAL_INTERMEDIATE_SOURCES:=$(framework_docs_LOCAL_INTERMEDIATE_SOURCES)
+LOCAL_GENERATED_SOURCES:=$(framework_docs_LOCAL_GENERATED_SOURCES)
LOCAL_SRCJARS:=$(framework_docs_LOCAL_SRCJARS)
LOCAL_STATIC_JAVA_LIBRARIES:=$(framework_docs_LOCAL_STATIC_JAVA_LIBRARIES)
LOCAL_JAVA_LIBRARIES:=$(framework_docs_LOCAL_JAVA_LIBRARIES)
@@ -846,7 +846,7 @@
# ==== docs for the web (on the devsite app engine server) =======================
include $(CLEAR_VARS)
LOCAL_SRC_FILES:=$(framework_docs_LOCAL_SRC_FILES)
-LOCAL_INTERMEDIATE_SOURCES:=$(framework_docs_LOCAL_INTERMEDIATE_SOURCES)
+LOCAL_GENERATED_SOURCES:=$(framework_docs_LOCAL_GENERATED_SOURCES)
LOCAL_SRCJARS:=$(framework_docs_LOCAL_SRCJARS)
LOCAL_STATIC_JAVA_LIBRARIES:=$(framework_docs_LOCAL_STATIC_JAVA_LIBRARIES)
LOCAL_JAVA_LIBRARIES:=$(framework_docs_LOCAL_JAVA_LIBRARIES)
@@ -876,7 +876,7 @@
# ==== docs for the web (on the devsite app engine server) =======================
include $(CLEAR_VARS)
LOCAL_SRC_FILES:=$(framework_docs_LOCAL_SRC_FILES)
-LOCAL_INTERMEDIATE_SOURCES:=$(framework_docs_LOCAL_INTERMEDIATE_SOURCES)
+LOCAL_GENERATED_SOURCES:=$(framework_docs_LOCAL_GENERATED_SOURCES)
LOCAL_SRCJARS:=$(framework_docs_LOCAL_SRCJARS)
LOCAL_STATIC_JAVA_LIBRARIES:=$(framework_docs_LOCAL_STATIC_JAVA_LIBRARIES)
LOCAL_JAVA_LIBRARIES:=$(framework_docs_LOCAL_JAVA_LIBRARIES)
@@ -906,7 +906,7 @@
include $(CLEAR_VARS)
LOCAL_SRC_FILES:=$(framework_docs_LOCAL_SRC_FILES)
-LOCAL_INTERMEDIATE_SOURCES:=$(framework_docs_LOCAL_INTERMEDIATE_SOURCES)
+LOCAL_GENERATED_SOURCES:=$(framework_docs_LOCAL_GENERATED_SOURCES)
LOCAL_SRCJARS:=$(framework_docs_LOCAL_SRCJARS)
LOCAL_STATIC_JAVA_LIBRARIES:=$(framework_docs_LOCAL_STATIC_JAVA_LIBRARIES)
LOCAL_JAVA_LIBRARIES:=$(framework_docs_LOCAL_JAVA_LIBRARIES)
@@ -932,7 +932,7 @@
include $(CLEAR_VARS)
LOCAL_SRC_FILES:=$(framework_docs_LOCAL_SRC_FILES)
-LOCAL_INTERMEDIATE_SOURCES:=$(framework_docs_LOCAL_INTERMEDIATE_SOURCES)
+LOCAL_GENERATED_SOURCES:=$(framework_docs_LOCAL_GENERATED_SOURCES)
LOCAL_SRCJARS:=$(framework_docs_LOCAL_SRCJARS)
LOCAL_STATIC_JAVA_LIBRARIES:=$(framework_docs_LOCAL_STATIC_JAVA_LIBRARIES)
LOCAL_JAVA_LIBRARIES:=$(framework_docs_LOCAL_JAVA_LIBRARIES)
@@ -961,7 +961,7 @@
include $(CLEAR_VARS)
LOCAL_SRC_FILES:=$(framework_docs_LOCAL_SRC_FILES)
-LOCAL_INTERMEDIATE_SOURCES:=$(framework_docs_LOCAL_INTERMEDIATE_SOURCES)
+LOCAL_GENERATED_SOURCES:=$(framework_docs_LOCAL_GENERATED_SOURCES)
LOCAL_SRCJARS:=$(framework_docs_LOCAL_SRCJARS)
LOCAL_JAVA_LIBRARIES:=$(framework_docs_LOCAL_JAVA_LIBRARIES)
LOCAL_MODULE_CLASS:=$(framework_docs_LOCAL_MODULE_CLASS)
diff --git a/api/current.txt b/api/current.txt
index 279a6f2..d230aea 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -6465,6 +6465,7 @@
method public void setShortSupportMessage(android.content.ComponentName, java.lang.CharSequence);
method public boolean setStatusBarDisabled(android.content.ComponentName, boolean);
method public int setStorageEncryption(android.content.ComponentName, boolean);
+ method public void setSystemSetting(android.content.ComponentName, java.lang.String, java.lang.String);
method public void setSystemUpdatePolicy(android.content.ComponentName, android.app.admin.SystemUpdatePolicy);
method public boolean setTime(android.content.ComponentName, long);
method public boolean setTimeZone(android.content.ComponentName, java.lang.String);
@@ -26171,6 +26172,7 @@
public class TrafficStats {
ctor public TrafficStats();
method public static void clearThreadStatsTag();
+ method public static void clearThreadStatsUid();
method public static int getAndSetThreadStatsTag(int);
method public static long getMobileRxBytes();
method public static long getMobileRxPackets();
@@ -26196,9 +26198,12 @@
method public static void incrementOperationCount(int);
method public static void incrementOperationCount(int, int);
method public static void setThreadStatsTag(int);
+ method public static void setThreadStatsUidSelf();
method public static void tagDatagramSocket(java.net.DatagramSocket) throws java.net.SocketException;
+ method public static void tagFileDescriptor(java.io.FileDescriptor) throws java.io.IOException;
method public static void tagSocket(java.net.Socket) throws java.net.SocketException;
method public static void untagDatagramSocket(java.net.DatagramSocket) throws java.net.SocketException;
+ method public static void untagFileDescriptor(java.io.FileDescriptor) throws java.io.IOException;
method public static void untagSocket(java.net.Socket) throws java.net.SocketException;
field public static final int UNSUPPORTED = -1; // 0xffffffff
}
@@ -34296,6 +34301,8 @@
field public static final java.lang.String IS_READ_ONLY = "is_read_only";
field public static final java.lang.String IS_SUPER_PRIMARY = "is_super_primary";
field public static final java.lang.String MIMETYPE = "mimetype";
+ field public static final java.lang.String PREFERRED_PHONE_ACCOUNT_COMPONENT_NAME = "preferred_phone_account_component_name";
+ field public static final java.lang.String PREFERRED_PHONE_ACCOUNT_ID = "preferred_phone_account_id";
field public static final java.lang.String RAW_CONTACT_ID = "raw_contact_id";
field public static final java.lang.String RES_PACKAGE = "res_package";
field public static final java.lang.String SYNC1 = "data_sync1";
@@ -35962,6 +35969,7 @@
field public static final java.lang.String DATE = "date";
field public static final java.lang.String DELETED = "deleted";
field public static final java.lang.String DIRTY = "dirty";
+ field public static final int DIRTY_RETAIN = -1; // 0xffffffff
field public static final java.lang.String DIR_TYPE = "vnd.android.cursor.dir/voicemails";
field public static final java.lang.String DURATION = "duration";
field public static final java.lang.String HAS_CONTENT = "has_content";
@@ -37478,6 +37486,8 @@
method public android.service.autofill.FillResponse.Builder setAuthentication(android.view.autofill.AutofillId[], android.content.IntentSender, android.widget.RemoteViews);
method public android.service.autofill.FillResponse.Builder setClientState(android.os.Bundle);
method public android.service.autofill.FillResponse.Builder setFlags(int);
+ method public android.service.autofill.FillResponse.Builder setFooter(android.widget.RemoteViews);
+ method public android.service.autofill.FillResponse.Builder setHeader(android.widget.RemoteViews);
method public android.service.autofill.FillResponse.Builder setIgnoredIds(android.view.autofill.AutofillId...);
method public android.service.autofill.FillResponse.Builder setSaveInfo(android.service.autofill.SaveInfo);
}
@@ -39278,6 +39288,7 @@
field public static final int HANDOVER_FAILURE_DEST_INVALID_PERM = 3; // 0x3
field public static final int HANDOVER_FAILURE_DEST_NOT_SUPPORTED = 2; // 0x2
field public static final int HANDOVER_FAILURE_DEST_USER_REJECTED = 4; // 0x4
+ field public static final int HANDOVER_FAILURE_ONGOING_EMERG_CALL = 5; // 0x5
}
public static class Call.Details {
@@ -49058,31 +49069,32 @@
package android.view.textclassifier {
public final class TextClassification {
- method public int getActionCount();
method public float getConfidenceScore(java.lang.String);
method public java.lang.String getEntity(int);
method public int getEntityCount();
- method public android.graphics.drawable.Drawable getIcon(int);
method public android.graphics.drawable.Drawable getIcon();
- method public android.content.Intent getIntent(int);
method public android.content.Intent getIntent();
- method public java.lang.CharSequence getLabel(int);
method public java.lang.CharSequence getLabel();
- method public android.view.View.OnClickListener getOnClickListener(int);
method public android.view.View.OnClickListener getOnClickListener();
+ method public int getSecondaryActionsCount();
+ method public android.graphics.drawable.Drawable getSecondaryIcon(int);
+ method public android.content.Intent getSecondaryIntent(int);
+ method public java.lang.CharSequence getSecondaryLabel(int);
+ method public android.view.View.OnClickListener getSecondaryOnClickListener(int);
method public java.lang.String getText();
}
public static final class TextClassification.Builder {
ctor public TextClassification.Builder();
- method public android.view.textclassifier.TextClassification.Builder addAction(android.content.Intent, java.lang.String, android.graphics.drawable.Drawable, android.view.View.OnClickListener);
+ method public android.view.textclassifier.TextClassification.Builder addSecondaryAction(android.content.Intent, java.lang.String, android.graphics.drawable.Drawable, android.view.View.OnClickListener);
method public android.view.textclassifier.TextClassification build();
- method public android.view.textclassifier.TextClassification.Builder clearActions();
+ method public android.view.textclassifier.TextClassification.Builder clearSecondaryActions();
method public android.view.textclassifier.TextClassification.Builder setEntityType(java.lang.String, float);
method public android.view.textclassifier.TextClassification.Builder setIcon(android.graphics.drawable.Drawable);
method public android.view.textclassifier.TextClassification.Builder setIntent(android.content.Intent);
method public android.view.textclassifier.TextClassification.Builder setLabel(java.lang.String);
method public android.view.textclassifier.TextClassification.Builder setOnClickListener(android.view.View.OnClickListener);
+ method public android.view.textclassifier.TextClassification.Builder setPrimaryAction(android.content.Intent, java.lang.String, android.graphics.drawable.Drawable, android.view.View.OnClickListener);
method public android.view.textclassifier.TextClassification.Builder setText(java.lang.String);
}
@@ -54873,7 +54885,6 @@
method public java.lang.Object[] getSigners();
method public java.lang.String getSimpleName();
method public java.lang.Class<? super T> getSuperclass();
- method public java.lang.String getTypeName();
method public synchronized java.lang.reflect.TypeVariable<java.lang.Class<T>>[] getTypeParameters();
method public boolean isAnnotation();
method public boolean isAnonymousClass();
@@ -56534,6 +56545,11 @@
ctor public MalformedParameterizedTypeException();
}
+ public class MalformedParametersException extends java.lang.RuntimeException {
+ ctor public MalformedParametersException();
+ ctor public MalformedParametersException(java.lang.String);
+ }
+
public abstract interface Member {
method public abstract java.lang.Class<?> getDeclaringClass();
method public abstract int getModifiers();
@@ -56631,6 +56647,7 @@
}
public abstract interface Type {
+ method public default java.lang.String getTypeName();
}
public abstract interface TypeVariable<D extends java.lang.reflect.GenericDeclaration> implements java.lang.reflect.Type {
diff --git a/api/system-current.txt b/api/system-current.txt
index 50a8ea4..16404e4 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -4332,6 +4332,7 @@
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 byte[] getMetadata();
method public boolean removeConfiguration(java.lang.String);
}
diff --git a/api/test-current.txt b/api/test-current.txt
index b181538..ef898a4 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -18,7 +18,7 @@
method public void removeStacksWithActivityTypes(int[]) throws java.lang.SecurityException;
method public void resizeStack(int, android.graphics.Rect) throws java.lang.SecurityException;
method public void setTaskWindowingMode(int, int, boolean) throws java.lang.SecurityException;
- method public void setTaskWindowingModeSplitScreenPrimary(int, int, boolean, boolean, android.graphics.Rect) throws java.lang.SecurityException;
+ method public void setTaskWindowingModeSplitScreenPrimary(int, int, boolean, boolean, android.graphics.Rect, boolean) throws java.lang.SecurityException;
method public static boolean supportsMultiWindow(android.content.Context);
method public static boolean supportsSplitScreenMultiWindow(android.content.Context);
field public static final int SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT = 1; // 0x1
@@ -457,19 +457,12 @@
method public void apply(android.service.autofill.ValueFinder, android.widget.RemoteViews, int) throws java.lang.Exception;
}
- public final class FieldsDetection implements android.os.Parcelable {
- ctor public FieldsDetection(android.view.autofill.AutofillId, java.lang.String, java.lang.String);
- method public int describeContents();
- method public void writeToParcel(android.os.Parcel, int);
- field public static final android.os.Parcelable.Creator<android.service.autofill.FieldsDetection> CREATOR;
- }
-
public static final class FillEventHistory.Event {
- method public java.util.Map<java.lang.String, java.lang.Integer> getDetectedFields();
+ method public java.util.Map<java.lang.String, java.lang.Integer> getFieldsClassification();
}
public static final class FillResponse.Builder {
- method public android.service.autofill.FillResponse.Builder setFieldsDetection(android.service.autofill.FieldsDetection);
+ method public android.service.autofill.FillResponse.Builder setFieldClassificationIds(android.view.autofill.AutofillId...);
}
public final class ImageTransformation extends android.service.autofill.InternalTransformation implements android.os.Parcelable android.service.autofill.Transformation {
@@ -501,6 +494,22 @@
method public android.view.autofill.AutofillValue sanitize(android.view.autofill.AutofillValue);
}
+ public final class UserData implements android.os.Parcelable {
+ method public int describeContents();
+ method public static int getMaxFieldClassificationIdsSize();
+ method public static int getMaxUserDataSize();
+ method public static int getMaxValueLength();
+ method public static int getMinValueLength();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.service.autofill.UserData> CREATOR;
+ }
+
+ public static final class UserData.Builder {
+ ctor public UserData.Builder(java.lang.String, java.lang.String);
+ method public android.service.autofill.UserData.Builder add(java.lang.String, java.lang.String);
+ method public android.service.autofill.UserData build();
+ }
+
public abstract interface ValueFinder {
method public abstract java.lang.String findByAutofillId(android.view.autofill.AutofillId);
}
@@ -983,6 +992,11 @@
ctor public AutofillId(int);
}
+ public final class AutofillManager {
+ method public android.service.autofill.UserData getUserData();
+ method public void setUserData(android.service.autofill.UserData);
+ }
+
}
package android.widget {
diff --git a/cmds/screencap/screencap.cpp b/cmds/screencap/screencap.cpp
index 6ded246..3172281 100644
--- a/cmds/screencap/screencap.cpp
+++ b/cmds/screencap/screencap.cpp
@@ -159,7 +159,7 @@
void const* mapbase = MAP_FAILED;
ssize_t mapsize = -1;
- void const* base = NULL;
+ void* base = NULL;
uint32_t w, s, h, f;
android_dataspace d;
size_t size = 0;
@@ -179,7 +179,6 @@
ProcessState::self()->setThreadPoolMaxThreadCount(0);
ProcessState::self()->startThreadPool();
- ScreenshotClient screenshot;
sp<IBinder> display = SurfaceComposerClient::getBuiltInDisplay(displayId);
if (display == NULL) {
fprintf(stderr, "Unable to get handle for display %d\n", displayId);
@@ -199,51 +198,57 @@
uint8_t displayOrientation = configs[activeConfig].orientation;
uint32_t captureOrientation = ORIENTATION_MAP[displayOrientation];
- status_t result = screenshot.update(display, Rect(),
- 0 /* reqWidth */, 0 /* reqHeight */,
- INT32_MIN, INT32_MAX, /* all layers */
- false, captureOrientation);
- if (result == NO_ERROR) {
- base = screenshot.getPixels();
- w = screenshot.getWidth();
- h = screenshot.getHeight();
- s = screenshot.getStride();
- f = screenshot.getFormat();
- d = screenshot.getDataSpace();
- size = screenshot.getSize();
+ sp<GraphicBuffer> outBuffer;
+ status_t result = ScreenshotClient::capture(display, Rect(), 0 /* reqWidth */,
+ 0 /* reqHeight */, INT32_MIN, INT32_MAX, /* all layers */ false, captureOrientation,
+ &outBuffer);
+ if (result != NO_ERROR) {
+ close(fd);
+ _exit(1);
}
- if (base != NULL) {
- if (png) {
- const SkImageInfo info =
- SkImageInfo::Make(w, h, flinger2skia(f), kPremul_SkAlphaType,
- dataSpaceToColorSpace(d));
- SkPixmap pixmap(info, base, s * bytesPerPixel(f));
- struct FDWStream final : public SkWStream {
- size_t fBytesWritten = 0;
- int fFd;
- FDWStream(int f) : fFd(f) {}
- size_t bytesWritten() const override { return fBytesWritten; }
- bool write(const void* buffer, size_t size) override {
- fBytesWritten += size;
- return size == 0 || ::write(fFd, buffer, size) > 0;
- }
- } fdStream(fd);
- (void)SkEncodeImage(&fdStream, pixmap, SkEncodedImageFormat::kPNG, 100);
- if (fn != NULL) {
- notifyMediaScanner(fn);
- }
- } else {
- uint32_t c = dataSpaceToInt(d);
- write(fd, &w, 4);
- write(fd, &h, 4);
- write(fd, &f, 4);
- write(fd, &c, 4);
- size_t Bpp = bytesPerPixel(f);
- for (size_t y=0 ; y<h ; y++) {
- write(fd, base, w*Bpp);
- base = (void *)((char *)base + s*Bpp);
- }
+ result = outBuffer->lock(GraphicBuffer::USAGE_SW_READ_OFTEN, &base);
+
+ if (base == NULL) {
+ close(fd);
+ _exit(1);
+ }
+
+ w = outBuffer->getWidth();
+ h = outBuffer->getHeight();
+ s = outBuffer->getStride();
+ f = outBuffer->getPixelFormat();
+ d = HAL_DATASPACE_UNKNOWN;
+ size = s * h * bytesPerPixel(f);
+
+ if (png) {
+ const SkImageInfo info =
+ SkImageInfo::Make(w, h, flinger2skia(f), kPremul_SkAlphaType, dataSpaceToColorSpace(d));
+ SkPixmap pixmap(info, base, s * bytesPerPixel(f));
+ struct FDWStream final : public SkWStream {
+ size_t fBytesWritten = 0;
+ int fFd;
+ FDWStream(int f) : fFd(f) {}
+ size_t bytesWritten() const override { return fBytesWritten; }
+ bool write(const void* buffer, size_t size) override {
+ fBytesWritten += size;
+ return size == 0 || ::write(fFd, buffer, size) > 0;
+ }
+ } fdStream(fd);
+ (void)SkEncodeImage(&fdStream, pixmap, SkEncodedImageFormat::kPNG, 100);
+ if (fn != NULL) {
+ notifyMediaScanner(fn);
+ }
+ } else {
+ uint32_t c = dataSpaceToInt(d);
+ write(fd, &w, 4);
+ write(fd, &h, 4);
+ write(fd, &f, 4);
+ write(fd, &c, 4);
+ size_t Bpp = bytesPerPixel(f);
+ for (size_t y=0 ; y<h ; y++) {
+ write(fd, base, w*Bpp);
+ base = (void *)((char *)base + s*Bpp);
}
}
close(fd);
@@ -253,4 +258,4 @@
// b/36066697: Avoid running static destructors.
_exit(0);
-}
+}
\ No newline at end of file
diff --git a/cmds/statsd/src/StatsLogProcessor.cpp b/cmds/statsd/src/StatsLogProcessor.cpp
index 2fd7947..bc63f59 100644
--- a/cmds/statsd/src/StatsLogProcessor.cpp
+++ b/cmds/statsd/src/StatsLogProcessor.cpp
@@ -221,7 +221,14 @@
std::lock_guard<std::mutex> lock(mBroadcastTimesMutex);
size_t totalBytes = metricsManager->byteSize() + mUidMap->getBytesUsed();
- if (totalBytes > .9 * kMaxSerializedBytes) { // Send broadcast so that receivers can pull data.
+ // TODO: Find a way to test that the dropping and broadcasts are sent when memory is exceeded.
+ if (totalBytes > kMaxSerializedBytes) { // Too late. We need to start clearing data.
+ // We ignore the return value so we force each metric producer to clear its contents.
+ metricsManager->onDumpReport();
+ StatsdStats::getInstance().noteDataDropped(key);
+ VLOG("StatsD had to toss out metrics for %s", key.ToString().c_str());
+ } else if (totalBytes >
+ .9 * kMaxSerializedBytes) { // Send broadcast so that receivers can pull data.
auto lastFlushNs = mLastBroadcastTimes.find(key);
if (lastFlushNs != mLastBroadcastTimes.end()) {
if (timestampNs - lastFlushNs->second < kMinBroadcastPeriod) {
@@ -232,11 +239,6 @@
VLOG("StatsD requesting broadcast for %s", key.ToString().c_str());
mSendBroadcast(key);
StatsdStats::getInstance().noteBroadcastSent(key);
- } else if (totalBytes > kMaxSerializedBytes) { // Too late. We need to start clearing data.
- // We ignore the return value so we force each metric producer to clear its contents.
- metricsManager->onDumpReport();
- StatsdStats::getInstance().noteDataDropped(key);
- VLOG("StatsD had to toss out metrics for %s", key.ToString().c_str());
}
}
diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp
index eb3ea0b..0e9cd3b 100644
--- a/cmds/statsd/src/StatsService.cpp
+++ b/cmds/statsd/src/StatsService.cpp
@@ -715,6 +715,18 @@
}
}
+Status StatsService::getMetadata(vector<uint8_t>* output) {
+ IPCThreadState* ipc = IPCThreadState::self();
+ VLOG("StatsService::getMetadata with Pid %i, Uid %i", ipc->getCallingPid(),
+ ipc->getCallingUid());
+ if (checkCallingPermission(String16(kPermissionDump))) {
+ StatsdStats::getInstance().dumpStats(output, false); // Don't reset the counters.
+ return Status::ok();
+ } else {
+ return Status::fromExceptionCode(binder::Status::EX_SECURITY);
+ }
+}
+
Status StatsService::addConfiguration(const String16& key,
const vector <uint8_t>& config,
const String16& package, const String16& cls,
diff --git a/cmds/statsd/src/StatsService.h b/cmds/statsd/src/StatsService.h
index 007227e..03bc6d9 100644
--- a/cmds/statsd/src/StatsService.h
+++ b/cmds/statsd/src/StatsService.h
@@ -76,6 +76,11 @@
virtual Status getData(const String16& 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.
*/
diff --git a/cmds/statsd/src/config/ConfigManager.cpp b/cmds/statsd/src/config/ConfigManager.cpp
index 4b7e49a..4b82e68 100644
--- a/cmds/statsd/src/config/ConfigManager.cpp
+++ b/cmds/statsd/src/config/ConfigManager.cpp
@@ -324,6 +324,16 @@
durationMetric->set_aggregation_type(DurationMetric_AggregationType_SUM);
durationMetric->set_what("SCREEN_IS_ON");
+ // Anomaly threshold for background count.
+ alert = config.add_alert();
+ alert->set_name("ALERT_8");
+ alert->set_metric_name("METRIC_8");
+ alert->set_number_of_buckets(4);
+ alert->set_trigger_if_sum_gt(2000000000); // 2 seconds
+ alert->set_refractory_period_secs(120);
+ details = alert->mutable_incidentd_details();
+ details->add_section(-1);
+
// Value metric to count KERNEL_WAKELOCK when screen turned on
ValueMetric* valueMetric = config.add_value_metric();
valueMetric->set_name("METRIC_6");
@@ -348,69 +358,69 @@
gaugeMetric->mutable_bucket()->set_bucket_size_millis(60 * 1000L);
// Event matchers............
- LogEntryMatcher* temperatureEntryMatcher = config.add_log_entry_matcher();
- temperatureEntryMatcher->set_name("DEVICE_TEMPERATURE");
- temperatureEntryMatcher->mutable_simple_log_entry_matcher()->set_tag(
+ AtomMatcher* temperatureAtomMatcher = config.add_atom_matcher();
+ temperatureAtomMatcher->set_name("DEVICE_TEMPERATURE");
+ temperatureAtomMatcher->mutable_simple_atom_matcher()->set_tag(
DEVICE_TEMPERATURE_TAG_ID);
- LogEntryMatcher* eventMatcher = config.add_log_entry_matcher();
+ AtomMatcher* eventMatcher = config.add_atom_matcher();
eventMatcher->set_name("SCREEN_TURNED_ON");
- SimpleLogEntryMatcher* simpleLogEntryMatcher = eventMatcher->mutable_simple_log_entry_matcher();
- simpleLogEntryMatcher->set_tag(SCREEN_EVENT_TAG_ID);
- KeyValueMatcher* keyValueMatcher = simpleLogEntryMatcher->add_key_value_matcher();
+ 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);
- eventMatcher = config.add_log_entry_matcher();
+ eventMatcher = config.add_atom_matcher();
eventMatcher->set_name("SCREEN_TURNED_OFF");
- simpleLogEntryMatcher = eventMatcher->mutable_simple_log_entry_matcher();
- simpleLogEntryMatcher->set_tag(SCREEN_EVENT_TAG_ID);
- keyValueMatcher = simpleLogEntryMatcher->add_key_value_matcher();
+ 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);
- eventMatcher = config.add_log_entry_matcher();
+ eventMatcher = config.add_atom_matcher();
eventMatcher->set_name("PROCESS_STATE_CHANGE");
- simpleLogEntryMatcher = eventMatcher->mutable_simple_log_entry_matcher();
- simpleLogEntryMatcher->set_tag(UID_PROCESS_STATE_TAG_ID);
+ simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher();
+ simpleAtomMatcher->set_tag(UID_PROCESS_STATE_TAG_ID);
- eventMatcher = config.add_log_entry_matcher();
+ eventMatcher = config.add_atom_matcher();
eventMatcher->set_name("APP_GOES_BACKGROUND");
- simpleLogEntryMatcher = eventMatcher->mutable_simple_log_entry_matcher();
- simpleLogEntryMatcher->set_tag(APP_USAGE_ID);
- keyValueMatcher = simpleLogEntryMatcher->add_key_value_matcher();
+ 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);
- eventMatcher = config.add_log_entry_matcher();
+ eventMatcher = config.add_atom_matcher();
eventMatcher->set_name("APP_GOES_FOREGROUND");
- simpleLogEntryMatcher = eventMatcher->mutable_simple_log_entry_matcher();
- simpleLogEntryMatcher->set_tag(APP_USAGE_ID);
- keyValueMatcher = simpleLogEntryMatcher->add_key_value_matcher();
+ 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);
- eventMatcher = config.add_log_entry_matcher();
+ eventMatcher = config.add_atom_matcher();
eventMatcher->set_name("APP_GET_WL");
- simpleLogEntryMatcher = eventMatcher->mutable_simple_log_entry_matcher();
- simpleLogEntryMatcher->set_tag(WAKE_LOCK_TAG_ID);
- keyValueMatcher = simpleLogEntryMatcher->add_key_value_matcher();
+ 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);
- eventMatcher = config.add_log_entry_matcher();
+ eventMatcher = config.add_atom_matcher();
eventMatcher->set_name("APP_RELEASE_WL");
- simpleLogEntryMatcher = eventMatcher->mutable_simple_log_entry_matcher();
- simpleLogEntryMatcher->set_tag(WAKE_LOCK_TAG_ID);
- keyValueMatcher = simpleLogEntryMatcher->add_key_value_matcher();
+ 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);
// pulled events
- eventMatcher = config.add_log_entry_matcher();
+ eventMatcher = config.add_atom_matcher();
eventMatcher->set_name("KERNEL_WAKELOCK");
- simpleLogEntryMatcher = eventMatcher->mutable_simple_log_entry_matcher();
- simpleLogEntryMatcher->set_tag(KERNEL_WAKELOCK_TAG_ID);
+ simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher();
+ simpleAtomMatcher->set_tag(KERNEL_WAKELOCK_TAG_ID);
// Conditions.............
Condition* condition = config.add_condition();
diff --git a/cmds/statsd/src/matchers/CombinationLogMatchingTracker.cpp b/cmds/statsd/src/matchers/CombinationLogMatchingTracker.cpp
index 78ba762..51a38b6 100644
--- a/cmds/statsd/src/matchers/CombinationLogMatchingTracker.cpp
+++ b/cmds/statsd/src/matchers/CombinationLogMatchingTracker.cpp
@@ -36,7 +36,7 @@
CombinationLogMatchingTracker::~CombinationLogMatchingTracker() {
}
-bool CombinationLogMatchingTracker::init(const vector<LogEntryMatcher>& allLogMatchers,
+bool CombinationLogMatchingTracker::init(const vector<AtomMatcher>& allLogMatchers,
const vector<sp<LogMatchingTracker>>& allTrackers,
const unordered_map<string, int>& matcherMap,
vector<bool>& stack) {
@@ -47,7 +47,7 @@
// mark this node as visited in the recursion stack.
stack[mIndex] = true;
- LogEntryMatcher_Combination matcher = allLogMatchers[mIndex].combination();
+ AtomMatcher_Combination matcher = allLogMatchers[mIndex].combination();
// LogicalOperation is missing in the config
if (!matcher.has_operation()) {
diff --git a/cmds/statsd/src/matchers/CombinationLogMatchingTracker.h b/cmds/statsd/src/matchers/CombinationLogMatchingTracker.h
index adb691e..81f6e80 100644
--- a/cmds/statsd/src/matchers/CombinationLogMatchingTracker.h
+++ b/cmds/statsd/src/matchers/CombinationLogMatchingTracker.h
@@ -28,12 +28,12 @@
namespace os {
namespace statsd {
-// Represents a LogEntryMatcher_Combination in the StatsdConfig.
+// Represents a AtomMatcher_Combination in the StatsdConfig.
class CombinationLogMatchingTracker : public virtual LogMatchingTracker {
public:
CombinationLogMatchingTracker(const std::string& name, const int index);
- bool init(const std::vector<LogEntryMatcher>& allLogMatchers,
+ bool init(const std::vector<AtomMatcher>& allLogMatchers,
const std::vector<sp<LogMatchingTracker>>& allTrackers,
const std::unordered_map<std::string, int>& matcherMap,
std::vector<bool>& stack);
diff --git a/cmds/statsd/src/matchers/LogMatchingTracker.h b/cmds/statsd/src/matchers/LogMatchingTracker.h
index fea3e9b..8162c44 100644
--- a/cmds/statsd/src/matchers/LogMatchingTracker.h
+++ b/cmds/statsd/src/matchers/LogMatchingTracker.h
@@ -39,14 +39,14 @@
virtual ~LogMatchingTracker(){};
// Initialize this LogMatchingTracker.
- // allLogMatchers: the list of the LogEntryMatcher proto config. This is needed because we don't
+ // allLogMatchers: the list of the AtomMatcher proto config. This is needed because we don't
// store the proto object in memory. We only need it during initilization.
// allTrackers: the list of the LogMatchingTracker objects. It's a one-to-one mapping with
// allLogMatchers. This is needed because the initialization is done recursively
// for CombinationLogMatchingTrackers using DFS.
// stack: a bit map to record which matcher has been visited on the stack. This is for detecting
// circle dependency.
- virtual bool init(const std::vector<LogEntryMatcher>& allLogMatchers,
+ virtual bool init(const std::vector<AtomMatcher>& allLogMatchers,
const std::vector<sp<LogMatchingTracker>>& allTrackers,
const std::unordered_map<std::string, int>& matcherMap,
std::vector<bool>& stack) = 0;
diff --git a/cmds/statsd/src/matchers/SimpleLogMatchingTracker.cpp b/cmds/statsd/src/matchers/SimpleLogMatchingTracker.cpp
index ad37b01..ac217ab 100644
--- a/cmds/statsd/src/matchers/SimpleLogMatchingTracker.cpp
+++ b/cmds/statsd/src/matchers/SimpleLogMatchingTracker.cpp
@@ -30,7 +30,7 @@
SimpleLogMatchingTracker::SimpleLogMatchingTracker(const string& name, const int index,
- const SimpleLogEntryMatcher& matcher)
+ const SimpleAtomMatcher& matcher)
: LogMatchingTracker(name, index), mMatcher(matcher) {
if (!matcher.has_tag()) {
mInitialized = false;
@@ -43,7 +43,7 @@
SimpleLogMatchingTracker::~SimpleLogMatchingTracker() {
}
-bool SimpleLogMatchingTracker::init(const vector<LogEntryMatcher>& allLogMatchers,
+bool SimpleLogMatchingTracker::init(const vector<AtomMatcher>& allLogMatchers,
const vector<sp<LogMatchingTracker>>& allTrackers,
const unordered_map<string, int>& matcherMap,
vector<bool>& stack) {
diff --git a/cmds/statsd/src/matchers/SimpleLogMatchingTracker.h b/cmds/statsd/src/matchers/SimpleLogMatchingTracker.h
index 5dca55e..2c188c1 100644
--- a/cmds/statsd/src/matchers/SimpleLogMatchingTracker.h
+++ b/cmds/statsd/src/matchers/SimpleLogMatchingTracker.h
@@ -32,11 +32,11 @@
class SimpleLogMatchingTracker : public virtual LogMatchingTracker {
public:
SimpleLogMatchingTracker(const std::string& name, const int index,
- const SimpleLogEntryMatcher& matcher);
+ const SimpleAtomMatcher& matcher);
~SimpleLogMatchingTracker();
- bool init(const std::vector<LogEntryMatcher>& allLogMatchers,
+ bool init(const std::vector<AtomMatcher>& allLogMatchers,
const std::vector<sp<LogMatchingTracker>>& allTrackers,
const std::unordered_map<std::string, int>& matcherMap,
std::vector<bool>& stack) override;
@@ -46,7 +46,7 @@
std::vector<MatchingState>& matcherResults) override;
private:
- const SimpleLogEntryMatcher mMatcher;
+ const SimpleAtomMatcher mMatcher;
};
} // namespace statsd
diff --git a/cmds/statsd/src/matchers/matcher_util.cpp b/cmds/statsd/src/matchers/matcher_util.cpp
index f7352cd..534d54e 100644
--- a/cmds/statsd/src/matchers/matcher_util.cpp
+++ b/cmds/statsd/src/matchers/matcher_util.cpp
@@ -91,7 +91,7 @@
return matched;
}
-bool matchesSimple(const SimpleLogEntryMatcher& simpleMatcher, const LogEvent& event) {
+bool matchesSimple(const SimpleAtomMatcher& simpleMatcher, const LogEvent& event) {
const int tagId = event.GetTagId();
if (simpleMatcher.tag() != tagId) {
diff --git a/cmds/statsd/src/matchers/matcher_util.h b/cmds/statsd/src/matchers/matcher_util.h
index 4ea6f0b..f54ab36 100644
--- a/cmds/statsd/src/matchers/matcher_util.h
+++ b/cmds/statsd/src/matchers/matcher_util.h
@@ -42,7 +42,7 @@
bool combinationMatch(const std::vector<int>& children, const LogicalOperation& operation,
const std::vector<MatchingState>& matcherResults);
-bool matchesSimple(const SimpleLogEntryMatcher& simpleMatcher, const LogEvent& wrapper);
+bool matchesSimple(const SimpleAtomMatcher& simpleMatcher, const LogEvent& wrapper);
std::vector<KeyValuePair> getDimensionKey(const LogEvent& event,
const std::vector<KeyMatcher>& dimensions);
diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.cpp b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
index 66e8c61..83fe84a 100644
--- a/cmds/statsd/src/metrics/DurationMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
@@ -103,6 +103,17 @@
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(),
+ (long long)mBucketSizeNs);
+ return nullptr;
+ }
+ // TODO: return a DurationAnomalyTracker (which should sublclass AnomalyTracker)
+ return new AnomalyTracker(alert);
+}
+
void DurationMetricProducer::startNewProtoOutputStreamLocked(long long startTime) {
mProto = std::make_unique<ProtoOutputStream>();
mProto->write(FIELD_TYPE_STRING | FIELD_ID_NAME, mMetric.name());
diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.h b/cmds/statsd/src/metrics/DurationMetricProducer.h
index 8e32d14..2653df8 100644
--- a/cmds/statsd/src/metrics/DurationMetricProducer.h
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.h
@@ -45,6 +45,8 @@
virtual ~DurationMetricProducer();
+ virtual sp<AnomalyTracker> createAnomalyTracker(const Alert &alert) override;
+
void finish() override;
// TODO: Implement this later.
@@ -79,13 +81,13 @@
const DurationMetric mMetric;
- // Index of the SimpleLogEntryMatcher which defines the start.
+ // Index of the SimpleAtomMatcher which defines the start.
const size_t mStartIndex;
- // Index of the SimpleLogEntryMatcher which defines the stop.
+ // Index of the SimpleAtomMatcher which defines the stop.
const size_t mStopIndex;
- // Index of the SimpleLogEntryMatcher which defines the stop all for all dimensions.
+ // Index of the SimpleAtomMatcher which defines the stop all for all dimensions.
const size_t mStopAllIndex;
// nest counting -- for the same key, stops must match the number of starts to make real stop
@@ -118,4 +120,3 @@
} // namespace statsd
} // namespace os
} // namespace android
-
diff --git a/cmds/statsd/src/metrics/MetricProducer.h b/cmds/statsd/src/metrics/MetricProducer.h
index 5df712c..819df77 100644
--- a/cmds/statsd/src/metrics/MetricProducer.h
+++ b/cmds/statsd/src/metrics/MetricProducer.h
@@ -93,6 +93,10 @@
return byteSizeLocked();
}
+ virtual sp<AnomalyTracker> createAnomalyTracker(const Alert &alert) {
+ return new AnomalyTracker(alert);
+ }
+
void addAnomalyTracker(sp<AnomalyTracker> tracker) {
std::lock_guard<std::mutex> lock(mMutex);
mAnomalyTrackers.push_back(tracker);
diff --git a/cmds/statsd/src/metrics/MetricsManager.cpp b/cmds/statsd/src/metrics/MetricsManager.cpp
index 8da8316a..9fdc6fa 100644
--- a/cmds/statsd/src/metrics/MetricsManager.cpp
+++ b/cmds/statsd/src/metrics/MetricsManager.cpp
@@ -39,7 +39,7 @@
MetricsManager::MetricsManager(const ConfigKey& key, const StatsdConfig& config) : mConfigKey(key) {
mConfigValid =
- initStatsdConfig(key, config, mTagIds, mAllLogEntryMatchers, mAllConditionTrackers,
+ initStatsdConfig(key, config, mTagIds, mAllAtomMatchers, mAllConditionTrackers,
mAllMetricProducers, mAllAnomalyTrackers, mConditionToMetricMap,
mTrackerToMetricMap, mTrackerToConditionMap);
@@ -47,11 +47,11 @@
// no matter whether this config is valid, log it in the stats.
StatsdStats::getInstance().noteConfigReceived(key, mAllMetricProducers.size(),
mAllConditionTrackers.size(),
- mAllLogEntryMatchers.size(), 0, mConfigValid);
+ mAllAtomMatchers.size(), 0, mConfigValid);
// Guardrail. Reject the config if it's too big.
if (mAllMetricProducers.size() > StatsdStats::kMaxMetricCountPerConfig ||
mAllConditionTrackers.size() > StatsdStats::kMaxConditionCountPerConfig ||
- mAllLogEntryMatchers.size() > StatsdStats::kMaxMatcherCountPerConfig) {
+ mAllAtomMatchers.size() > StatsdStats::kMaxMatcherCountPerConfig) {
ALOGE("This config is too big! Reject!");
mConfigValid = false;
}
@@ -95,10 +95,10 @@
return;
}
- vector<MatchingState> matcherCache(mAllLogEntryMatchers.size(), MatchingState::kNotComputed);
+ vector<MatchingState> matcherCache(mAllAtomMatchers.size(), MatchingState::kNotComputed);
- for (auto& matcher : mAllLogEntryMatchers) {
- matcher->onLogEvent(event, mAllLogEntryMatchers, matcherCache);
+ for (auto& matcher : mAllAtomMatchers) {
+ matcher->onLogEvent(event, mAllAtomMatchers, matcherCache);
}
// A bitmap to see which ConditionTracker needs to be re-evaluated.
@@ -149,11 +149,11 @@
}
}
- // For matched LogEntryMatchers, tell relevant metrics that a matched event has come.
- for (size_t i = 0; i < mAllLogEntryMatchers.size(); i++) {
+ // For matched AtomMatchers, tell relevant metrics that a matched event has come.
+ for (size_t i = 0; i < mAllAtomMatchers.size(); i++) {
if (matcherCache[i] == MatchingState::kMatched) {
StatsdStats::getInstance().noteMatcherMatched(mConfigKey,
- mAllLogEntryMatchers[i]->getName());
+ mAllAtomMatchers[i]->getName());
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 62b4c87..4e6a0ce 100644
--- a/cmds/statsd/src/metrics/MetricsManager.h
+++ b/cmds/statsd/src/metrics/MetricsManager.h
@@ -71,8 +71,8 @@
// holds A's sp. (2) When we evaluate matcher results, or condition results, we can quickly get
// the related results from a cache using the index.
- // Hold all the log entry matchers from the config.
- std::vector<sp<LogMatchingTracker>> mAllLogEntryMatchers;
+ // Hold all the atom matchers from the config.
+ std::vector<sp<LogMatchingTracker>> mAllAtomMatchers;
// Hold all the conditions from the config.
std::vector<sp<ConditionTracker>> mAllConditionTrackers;
diff --git a/cmds/statsd/src/metrics/metrics_manager_util.cpp b/cmds/statsd/src/metrics/metrics_manager_util.cpp
index 33683f0..00048f5 100644
--- a/cmds/statsd/src/metrics/metrics_manager_util.cpp
+++ b/cmds/statsd/src/metrics/metrics_manager_util.cpp
@@ -40,17 +40,17 @@
bool handleMetricWithLogTrackers(const string what, const int metricIndex,
const bool usedForDimension,
- const vector<sp<LogMatchingTracker>>& allLogEntryMatchers,
+ const vector<sp<LogMatchingTracker>>& allAtomMatchers,
const unordered_map<string, int>& logTrackerMap,
unordered_map<int, std::vector<int>>& trackerToMetricMap,
int& logTrackerIndex) {
auto logTrackerIt = logTrackerMap.find(what);
if (logTrackerIt == logTrackerMap.end()) {
- ALOGW("cannot find the LogEntryMatcher \"%s\" in config", what.c_str());
+ ALOGW("cannot find the AtomMatcher \"%s\" in config", what.c_str());
return false;
}
- if (usedForDimension && allLogEntryMatchers[logTrackerIt->second]->getTagIds().size() > 1) {
- ALOGE("LogEntryMatcher \"%s\" has more than one tag ids. When a metric has dimension, "
+ if (usedForDimension && allAtomMatchers[logTrackerIt->second]->getTagIds().size() > 1) {
+ ALOGE("AtomMatcher \"%s\" has more than one tag ids. When a metric has dimension, "
"the \"what\" can only about one atom type.",
what.c_str());
return false;
@@ -93,23 +93,23 @@
}
bool initLogTrackers(const StatsdConfig& config, unordered_map<string, int>& logTrackerMap,
- vector<sp<LogMatchingTracker>>& allLogEntryMatchers, set<int>& allTagIds) {
- vector<LogEntryMatcher> matcherConfigs;
- const int logEntryMatcherCount = config.log_entry_matcher_size();
- matcherConfigs.reserve(logEntryMatcherCount);
- allLogEntryMatchers.reserve(logEntryMatcherCount);
+ vector<sp<LogMatchingTracker>>& allAtomMatchers, set<int>& allTagIds) {
+ vector<AtomMatcher> matcherConfigs;
+ const int atomMatcherCount = config.atom_matcher_size();
+ matcherConfigs.reserve(atomMatcherCount);
+ allAtomMatchers.reserve(atomMatcherCount);
- for (int i = 0; i < logEntryMatcherCount; i++) {
- const LogEntryMatcher& logMatcher = config.log_entry_matcher(i);
+ for (int i = 0; i < atomMatcherCount; i++) {
+ const AtomMatcher& logMatcher = config.atom_matcher(i);
- int index = allLogEntryMatchers.size();
+ int index = allAtomMatchers.size();
switch (logMatcher.contents_case()) {
- case LogEntryMatcher::ContentsCase::kSimpleLogEntryMatcher:
- allLogEntryMatchers.push_back(new SimpleLogMatchingTracker(
- logMatcher.name(), index, logMatcher.simple_log_entry_matcher()));
+ case AtomMatcher::ContentsCase::kSimpleAtomMatcher:
+ allAtomMatchers.push_back(new SimpleLogMatchingTracker(
+ logMatcher.name(), index, logMatcher.simple_atom_matcher()));
break;
- case LogEntryMatcher::ContentsCase::kCombination:
- allLogEntryMatchers.push_back(
+ case AtomMatcher::ContentsCase::kCombination:
+ allAtomMatchers.push_back(
new CombinationLogMatchingTracker(logMatcher.name(), index));
break;
default:
@@ -118,16 +118,16 @@
// continue;
}
if (logTrackerMap.find(logMatcher.name()) != logTrackerMap.end()) {
- ALOGE("Duplicate LogEntryMatcher found!");
+ ALOGE("Duplicate AtomMatcher found!");
return false;
}
logTrackerMap[logMatcher.name()] = index;
matcherConfigs.push_back(logMatcher);
}
- vector<bool> stackTracker2(allLogEntryMatchers.size(), false);
- for (auto& matcher : allLogEntryMatchers) {
- if (!matcher->init(matcherConfigs, allLogEntryMatchers, logTrackerMap, stackTracker2)) {
+ vector<bool> stackTracker2(allAtomMatchers.size(), false);
+ for (auto& matcher : allAtomMatchers) {
+ if (!matcher->init(matcherConfigs, allAtomMatchers, logTrackerMap, stackTracker2)) {
return false;
}
// Collect all the tag ids that are interesting. TagIds exist in leaf nodes only.
@@ -191,7 +191,7 @@
bool initMetrics(const ConfigKey& key, const StatsdConfig& config,
const unordered_map<string, int>& logTrackerMap,
const unordered_map<string, int>& conditionTrackerMap,
- const vector<sp<LogMatchingTracker>>& allLogEntryMatchers,
+ const vector<sp<LogMatchingTracker>>& allAtomMatchers,
vector<sp<ConditionTracker>>& allConditionTrackers,
vector<sp<MetricProducer>>& allMetricProducers,
unordered_map<int, std::vector<int>>& conditionToMetricMap,
@@ -217,7 +217,7 @@
metricMap.insert({metric.name(), metricIndex});
int trackerIndex;
if (!handleMetricWithLogTrackers(metric.what(), metricIndex, metric.dimension_size() > 0,
- allLogEntryMatchers, logTrackerMap, trackerToMetricMap,
+ allAtomMatchers, logTrackerMap, trackerToMetricMap,
trackerIndex)) {
return false;
}
@@ -268,7 +268,7 @@
int trackerIndices[3] = {-1, -1, -1};
if (!simpleCondition.has_start() ||
!handleMetricWithLogTrackers(simpleCondition.start(), metricIndex,
- metric.dimension_size() > 0, allLogEntryMatchers,
+ metric.dimension_size() > 0, allAtomMatchers,
logTrackerMap, trackerToMetricMap, trackerIndices[0])) {
ALOGE("Duration metrics must specify a valid the start event matcher");
return false;
@@ -276,14 +276,14 @@
if (simpleCondition.has_stop() &&
!handleMetricWithLogTrackers(simpleCondition.stop(), metricIndex,
- metric.dimension_size() > 0, allLogEntryMatchers,
+ metric.dimension_size() > 0, allAtomMatchers,
logTrackerMap, trackerToMetricMap, trackerIndices[1])) {
return false;
}
if (simpleCondition.has_stop_all() &&
!handleMetricWithLogTrackers(simpleCondition.stop_all(), metricIndex,
- metric.dimension_size() > 0, allLogEntryMatchers,
+ metric.dimension_size() > 0, allAtomMatchers,
logTrackerMap, trackerToMetricMap, trackerIndices[2])) {
return false;
}
@@ -325,7 +325,7 @@
return false;
}
int trackerIndex;
- if (!handleMetricWithLogTrackers(metric.what(), metricIndex, false, allLogEntryMatchers,
+ if (!handleMetricWithLogTrackers(metric.what(), metricIndex, false, allAtomMatchers,
logTrackerMap, trackerToMetricMap, trackerIndex)) {
return false;
}
@@ -363,12 +363,12 @@
metricMap.insert({metric.name(), metricIndex});
int trackerIndex;
if (!handleMetricWithLogTrackers(metric.what(), metricIndex, metric.dimension_size() > 0,
- allLogEntryMatchers, logTrackerMap, trackerToMetricMap,
+ allAtomMatchers, logTrackerMap, trackerToMetricMap,
trackerIndex)) {
return false;
}
- sp<LogMatchingTracker> atomMatcher = allLogEntryMatchers.at(trackerIndex);
+ sp<LogMatchingTracker> atomMatcher = allAtomMatchers.at(trackerIndex);
// If it is pulled atom, it should be simple matcher with one tagId.
int pullTagId = -1;
for (int tagId : atomMatcher->getTagIds()) {
@@ -412,12 +412,12 @@
metricMap.insert({metric.name(), metricIndex});
int trackerIndex;
if (!handleMetricWithLogTrackers(metric.what(), metricIndex, metric.dimension_size() > 0,
- allLogEntryMatchers, logTrackerMap, trackerToMetricMap,
+ allAtomMatchers, logTrackerMap, trackerToMetricMap,
trackerIndex)) {
return false;
}
- sp<LogMatchingTracker> atomMatcher = allLogEntryMatchers.at(trackerIndex);
+ sp<LogMatchingTracker> atomMatcher = allAtomMatchers.at(trackerIndex);
// If it is pulled atom, it should be simple matcher with one tagId.
int pullTagId = -1;
for (int tagId : atomMatcher->getTagIds()) {
@@ -468,26 +468,18 @@
return false;
}
const int metricIndex = itr->second;
- if (alert.trigger_if_sum_gt() >
- (int64_t)alert.number_of_buckets() *
- allMetricProducers[metricIndex]->getBuckeSizeInNs()) {
- ALOGW("invalid alert: threshold (%lld) > possible recordable value (%d x %lld)",
- alert.trigger_if_sum_gt(), alert.number_of_buckets(),
- (long long)allMetricProducers[metricIndex]->getBuckeSizeInNs());
- return false;
+ sp<MetricProducer> metric = allMetricProducers[metricIndex];
+ sp<AnomalyTracker> anomalyTracker = metric->createAnomalyTracker(alert);
+ if (anomalyTracker != nullptr) {
+ metric->addAnomalyTracker(anomalyTracker);
+ allAnomalyTrackers.push_back(anomalyTracker);
}
-
- // TODO: Give each MetricProducer a method called createAnomalyTracker(alert), which
- // creates either an AnomalyTracker or a DurationAnomalyTracker and returns it.
- sp<AnomalyTracker> anomalyTracker = new AnomalyTracker(alert);
- allMetricProducers[metricIndex]->addAnomalyTracker(anomalyTracker);
- allAnomalyTrackers.push_back(anomalyTracker);
}
return true;
}
bool initStatsdConfig(const ConfigKey& key, const StatsdConfig& config, set<int>& allTagIds,
- vector<sp<LogMatchingTracker>>& allLogEntryMatchers,
+ vector<sp<LogMatchingTracker>>& allAtomMatchers,
vector<sp<ConditionTracker>>& allConditionTrackers,
vector<sp<MetricProducer>>& allMetricProducers,
vector<sp<AnomalyTracker>>& allAnomalyTrackers,
@@ -498,7 +490,7 @@
unordered_map<string, int> conditionTrackerMap;
unordered_map<string, int> metricProducerMap;
- if (!initLogTrackers(config, logTrackerMap, allLogEntryMatchers, allTagIds)) {
+ if (!initLogTrackers(config, logTrackerMap, allAtomMatchers, allTagIds)) {
ALOGE("initLogMatchingTrackers failed");
return false;
}
@@ -510,7 +502,7 @@
return false;
}
- if (!initMetrics(key, config, logTrackerMap, conditionTrackerMap, allLogEntryMatchers,
+ if (!initMetrics(key, config, logTrackerMap, conditionTrackerMap, allAtomMatchers,
allConditionTrackers, allMetricProducers, conditionToMetricMap,
trackerToMetricMap, metricProducerMap)) {
ALOGE("initMetricProducers failed");
diff --git a/cmds/statsd/src/metrics/metrics_manager_util.h b/cmds/statsd/src/metrics/metrics_manager_util.h
index e7cbd53..751d8df 100644
--- a/cmds/statsd/src/metrics/metrics_manager_util.h
+++ b/cmds/statsd/src/metrics/metrics_manager_util.h
@@ -40,11 +40,11 @@
// [config]: the input StatsdConfig
// output:
// [logTrackerMap]: this map should contain matcher name to index mapping
-// [allLogEntryMatchers]: should store the sp to all the LogMatchingTracker
+// [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,
- std::vector<sp<LogMatchingTracker>>& allLogEntryMatchers,
+ std::vector<sp<LogMatchingTracker>>& allAtomMatchers,
std::set<int>& allTagIds);
// Initialize ConditionTrackers
@@ -80,7 +80,7 @@
const std::unordered_map<std::string, int>& logTrackerMap,
const std::unordered_map<std::string, int>& conditionTrackerMap,
const std::unordered_map<int, std::vector<EventConditionLink>>& eventConditionLinks,
- const vector<sp<LogMatchingTracker>>& allLogEntryMatchers,
+ const vector<sp<LogMatchingTracker>>& allAtomMatchers,
vector<sp<ConditionTracker>>& allConditionTrackers,
std::vector<sp<MetricProducer>>& allMetricProducers,
std::unordered_map<int, std::vector<int>>& conditionToMetricMap,
@@ -89,7 +89,7 @@
// Initialize MetricsManager from StatsdConfig.
// Parameters are the members of MetricsManager. See MetricsManager for declaration.
bool initStatsdConfig(const ConfigKey& key, const StatsdConfig& config, std::set<int>& allTagIds,
- std::vector<sp<LogMatchingTracker>>& allLogEntryMatchers,
+ std::vector<sp<LogMatchingTracker>>& allAtomMatchers,
std::vector<sp<ConditionTracker>>& allConditionTrackers,
std::vector<sp<MetricProducer>>& allMetricProducers,
vector<sp<AnomalyTracker>>& allAnomalyTrackers,
diff --git a/cmds/statsd/src/statsd_config.proto b/cmds/statsd/src/statsd_config.proto
index c8fa155..6837be0 100644
--- a/cmds/statsd/src/statsd_config.proto
+++ b/cmds/statsd/src/statsd_config.proto
@@ -23,93 +23,93 @@
option java_outer_classname = "StatsdConfigProto";
message KeyMatcher {
- optional int32 key = 1;
+ optional int32 key = 1;
- optional bool as_package_name = 2 [ default = false ];
+ optional bool as_package_name = 2 [default = false];
}
message KeyValueMatcher {
- optional KeyMatcher key_matcher = 1;
+ optional KeyMatcher key_matcher = 1;
- oneof value_matcher {
- bool eq_bool = 2;
- string eq_string = 3;
- int32 eq_int = 4;
+ oneof value_matcher {
+ bool eq_bool = 2;
+ string eq_string = 3;
+ int32 eq_int = 4;
- int64 lt_int = 5;
- int64 gt_int = 6;
- float lt_float = 7;
- float gt_float = 8;
+ int64 lt_int = 5;
+ int64 gt_int = 6;
+ float lt_float = 7;
+ float gt_float = 8;
- int64 lte_int = 9;
- int64 gte_int = 10;
- }
+ int64 lte_int = 9;
+ int64 gte_int = 10;
+ }
}
enum LogicalOperation {
- LOGICAL_OPERATION_UNSPECIFIED = 0;
- AND = 1;
- OR = 2;
- NOT = 3;
- NAND = 4;
- NOR = 5;
+ LOGICAL_OPERATION_UNSPECIFIED = 0;
+ AND = 1;
+ OR = 2;
+ NOT = 3;
+ NAND = 4;
+ NOR = 5;
}
-message SimpleLogEntryMatcher {
- optional int32 tag = 1;
+message SimpleAtomMatcher {
+ optional int32 tag = 1;
- repeated KeyValueMatcher key_value_matcher = 2;
+ repeated KeyValueMatcher key_value_matcher = 2;
}
-message LogEntryMatcher {
- optional string name = 1;
+message AtomMatcher {
+ optional string name = 1;
- message Combination {
- optional LogicalOperation operation = 1;
+ message Combination {
+ optional LogicalOperation operation = 1;
- repeated string matcher = 2;
- }
- oneof contents {
- SimpleLogEntryMatcher simple_log_entry_matcher = 2;
- Combination combination = 3;
- }
+ repeated string matcher = 2;
+ }
+ oneof contents {
+ SimpleAtomMatcher simple_atom_matcher = 2;
+ Combination combination = 3;
+ }
}
message SimpleCondition {
- optional string start = 1;
+ optional string start = 1;
- optional string stop = 2;
+ optional string stop = 2;
- optional bool count_nesting = 3 [default = true];
+ optional bool count_nesting = 3 [default = true];
- optional string stop_all = 4;
+ optional string stop_all = 4;
- enum InitialValue {
- UNKNOWN = 0;
- FALSE = 1;
- }
- optional InitialValue initial_value = 5 [default = FALSE];
+ enum InitialValue {
+ UNKNOWN = 0;
+ FALSE = 1;
+ }
+ optional InitialValue initial_value = 5 [default = FALSE];
- repeated KeyMatcher dimension = 6;
+ repeated KeyMatcher dimension = 6;
}
message Condition {
- optional string name = 1;
+ optional string name = 1;
- message Combination {
- optional LogicalOperation operation = 1;
+ message Combination {
+ optional LogicalOperation operation = 1;
- repeated string condition = 2;
- }
+ repeated string condition = 2;
+ }
- oneof contents {
- SimpleCondition simple_condition = 2;
- Combination combination = 3;
- }
+ oneof contents {
+ SimpleCondition simple_condition = 2;
+ Combination combination = 3;
+ }
}
message Bucket {
- optional int64 bucket_size_millis = 1;
+ optional int64 bucket_size_millis = 1;
}
message EventConditionLink {
@@ -154,9 +154,9 @@
repeated EventConditionLink links = 4;
enum AggregationType {
- SUM = 1;
+ SUM = 1;
- MAX_SPARSE = 2;
+ MAX_SPARSE = 2;
}
optional AggregationType aggregation_type = 5 [default = SUM];
@@ -196,9 +196,7 @@
repeated EventConditionLink links = 7;
- enum AggregationType {
- SUM = 1;
- }
+ enum AggregationType { SUM = 1; }
optional AggregationType aggregation_type = 8 [default = SUM];
}
@@ -208,7 +206,7 @@
optional string metric_name = 2;
message IncidentdDetails {
- repeated int32 section = 1;
+ repeated int32 section = 1;
}
optional IncidentdDetails incidentd_details = 3;
@@ -232,7 +230,7 @@
repeated DurationMetric duration_metric = 6;
- repeated LogEntryMatcher log_entry_matcher = 7;
+ repeated AtomMatcher atom_matcher = 7;
repeated Condition condition = 8;
diff --git a/cmds/statsd/tests/LogEntryMatcher_test.cpp b/cmds/statsd/tests/LogEntryMatcher_test.cpp
index 40c0e9d..1ec9155 100644
--- a/cmds/statsd/tests/LogEntryMatcher_test.cpp
+++ b/cmds/statsd/tests/LogEntryMatcher_test.cpp
@@ -36,10 +36,10 @@
extern "C" void android_log_rewind(android_log_context ctx);
#ifdef __ANDROID__
-TEST(LogEntryMatcherTest, TestSimpleMatcher) {
+TEST(AtomMatcherTest, TestSimpleMatcher) {
// Set up the matcher
- LogEntryMatcher matcher;
- auto simpleMatcher = matcher.mutable_simple_log_entry_matcher();
+ AtomMatcher matcher;
+ auto simpleMatcher = matcher.mutable_simple_atom_matcher();
simpleMatcher->set_tag(TAG_ID);
LogEvent event(TAG_ID, 0);
@@ -49,10 +49,10 @@
EXPECT_TRUE(matchesSimple(*simpleMatcher, event));
}
-TEST(LogEntryMatcherTest, TestBoolMatcher) {
+TEST(AtomMatcherTest, TestBoolMatcher) {
// Set up the matcher
- LogEntryMatcher matcher;
- auto simpleMatcher = matcher.mutable_simple_log_entry_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);
@@ -84,10 +84,10 @@
EXPECT_FALSE(matchesSimple(*simpleMatcher, event));
}
-TEST(LogEntryMatcherTest, TestStringMatcher) {
+TEST(AtomMatcherTest, TestStringMatcher) {
// Set up the matcher
- LogEntryMatcher matcher;
- auto simpleMatcher = matcher.mutable_simple_log_entry_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);
@@ -103,10 +103,10 @@
EXPECT_TRUE(matchesSimple(*simpleMatcher, event));
}
-TEST(LogEntryMatcherTest, TestMultiFieldsMatcher) {
+TEST(AtomMatcherTest, TestMultiFieldsMatcher) {
// Set up the matcher
- LogEntryMatcher matcher;
- auto simpleMatcher = matcher.mutable_simple_log_entry_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);
@@ -135,10 +135,10 @@
EXPECT_FALSE(matchesSimple(*simpleMatcher, event));
}
-TEST(LogEntryMatcherTest, TestIntComparisonMatcher) {
+TEST(AtomMatcherTest, TestIntComparisonMatcher) {
// Set up the matcher
- LogEntryMatcher matcher;
- auto simpleMatcher = matcher.mutable_simple_log_entry_matcher();
+ AtomMatcher matcher;
+ auto simpleMatcher = matcher.mutable_simple_atom_matcher();
simpleMatcher->set_tag(TAG_ID);
auto keyValue = simpleMatcher->add_key_value_matcher();
@@ -192,10 +192,10 @@
EXPECT_FALSE(matchesSimple(*simpleMatcher, event));
}
-TEST(LogEntryMatcherTest, TestFloatComparisonMatcher) {
+TEST(AtomMatcherTest, TestFloatComparisonMatcher) {
// Set up the matcher
- LogEntryMatcher matcher;
- auto simpleMatcher = matcher.mutable_simple_log_entry_matcher();
+ AtomMatcher matcher;
+ auto simpleMatcher = matcher.mutable_simple_atom_matcher();
simpleMatcher->set_tag(TAG_ID);
auto keyValue = simpleMatcher->add_key_value_matcher();
@@ -225,14 +225,14 @@
}
// Helper for the composite matchers.
-void addSimpleMatcher(SimpleLogEntryMatcher* simpleMatcher, int tag, int key, int val) {
+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);
keyValue->set_eq_int(val);
}
-TEST(LogEntryMatcherTest, TestAndMatcher) {
+TEST(AtomMatcherTest, TestAndMatcher) {
// Set up the matcher
LogicalOperation operation = LogicalOperation::AND;
@@ -256,7 +256,7 @@
EXPECT_TRUE(combinationMatch(children, operation, matcherResults));
}
-TEST(LogEntryMatcherTest, TestOrMatcher) {
+TEST(AtomMatcherTest, TestOrMatcher) {
// Set up the matcher
LogicalOperation operation = LogicalOperation::OR;
@@ -280,7 +280,7 @@
EXPECT_FALSE(combinationMatch(children, operation, matcherResults));
}
-TEST(LogEntryMatcherTest, TestNotMatcher) {
+TEST(AtomMatcherTest, TestNotMatcher) {
// Set up the matcher
LogicalOperation operation = LogicalOperation::NOT;
@@ -297,7 +297,7 @@
EXPECT_TRUE(combinationMatch(children, operation, matcherResults));
}
-TEST(LogEntryMatcherTest, TestNandMatcher) {
+TEST(AtomMatcherTest, TestNandMatcher) {
// Set up the matcher
LogicalOperation operation = LogicalOperation::NAND;
@@ -322,7 +322,7 @@
EXPECT_FALSE(combinationMatch(children, operation, matcherResults));
}
-TEST(LogEntryMatcherTest, TestNorMatcher) {
+TEST(AtomMatcherTest, TestNorMatcher) {
// Set up the matcher
LogicalOperation operation = LogicalOperation::NOR;
diff --git a/cmds/statsd/tests/MetricsManager_test.cpp b/cmds/statsd/tests/MetricsManager_test.cpp
index 5384e0c..e4750e9 100644
--- a/cmds/statsd/tests/MetricsManager_test.cpp
+++ b/cmds/statsd/tests/MetricsManager_test.cpp
@@ -46,30 +46,30 @@
StatsdConfig config;
config.set_name("12345");
- LogEntryMatcher* eventMatcher = config.add_log_entry_matcher();
+ AtomMatcher* eventMatcher = config.add_atom_matcher();
eventMatcher->set_name("SCREEN_IS_ON");
- SimpleLogEntryMatcher* simpleLogEntryMatcher = eventMatcher->mutable_simple_log_entry_matcher();
- simpleLogEntryMatcher->set_tag(2 /*SCREEN_STATE_CHANGE*/);
- simpleLogEntryMatcher->add_key_value_matcher()->mutable_key_matcher()->set_key(
+ SimpleAtomMatcher* simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher();
+ simpleAtomMatcher->set_tag(2 /*SCREEN_STATE_CHANGE*/);
+ simpleAtomMatcher->add_key_value_matcher()->mutable_key_matcher()->set_key(
1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE*/);
- simpleLogEntryMatcher->mutable_key_value_matcher(0)->set_eq_int(
+ simpleAtomMatcher->mutable_key_value_matcher(0)->set_eq_int(
2 /*SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_ON*/);
- eventMatcher = config.add_log_entry_matcher();
+ eventMatcher = config.add_atom_matcher();
eventMatcher->set_name("SCREEN_IS_OFF");
- simpleLogEntryMatcher = eventMatcher->mutable_simple_log_entry_matcher();
- simpleLogEntryMatcher->set_tag(2 /*SCREEN_STATE_CHANGE*/);
- simpleLogEntryMatcher->add_key_value_matcher()->mutable_key_matcher()->set_key(
+ simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher();
+ simpleAtomMatcher->set_tag(2 /*SCREEN_STATE_CHANGE*/);
+ simpleAtomMatcher->add_key_value_matcher()->mutable_key_matcher()->set_key(
1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE*/);
- simpleLogEntryMatcher->mutable_key_value_matcher(0)->set_eq_int(
+ simpleAtomMatcher->mutable_key_value_matcher(0)->set_eq_int(
1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_OFF*/);
- eventMatcher = config.add_log_entry_matcher();
+ eventMatcher = config.add_atom_matcher();
eventMatcher->set_name("SCREEN_ON_OR_OFF");
- LogEntryMatcher_Combination* combination = eventMatcher->mutable_combination();
+ AtomMatcher_Combination* combination = eventMatcher->mutable_combination();
combination->set_operation(LogicalOperation::OR);
combination->add_matcher("SCREEN_IS_ON");
combination->add_matcher("SCREEN_IS_OFF");
@@ -94,20 +94,20 @@
StatsdConfig config;
config.set_name("12345");
- LogEntryMatcher* eventMatcher = config.add_log_entry_matcher();
+ AtomMatcher* eventMatcher = config.add_atom_matcher();
eventMatcher->set_name("SCREEN_IS_ON");
- SimpleLogEntryMatcher* simpleLogEntryMatcher = eventMatcher->mutable_simple_log_entry_matcher();
- simpleLogEntryMatcher->set_tag(2 /*SCREEN_STATE_CHANGE*/);
- simpleLogEntryMatcher->add_key_value_matcher()->mutable_key_matcher()->set_key(
+ SimpleAtomMatcher* simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher();
+ simpleAtomMatcher->set_tag(2 /*SCREEN_STATE_CHANGE*/);
+ simpleAtomMatcher->add_key_value_matcher()->mutable_key_matcher()->set_key(
1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE*/);
- simpleLogEntryMatcher->mutable_key_value_matcher(0)->set_eq_int(
+ simpleAtomMatcher->mutable_key_value_matcher(0)->set_eq_int(
2 /*SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_ON*/);
- eventMatcher = config.add_log_entry_matcher();
+ eventMatcher = config.add_atom_matcher();
eventMatcher->set_name("SCREEN_ON_OR_OFF");
- LogEntryMatcher_Combination* combination = eventMatcher->mutable_combination();
+ AtomMatcher_Combination* combination = eventMatcher->mutable_combination();
combination->set_operation(LogicalOperation::OR);
combination->add_matcher("SCREEN_IS_ON");
// Circle dependency
@@ -120,7 +120,7 @@
StatsdConfig config;
config.set_name("12345");
- LogEntryMatcher* eventMatcher = config.add_log_entry_matcher();
+ AtomMatcher* eventMatcher = config.add_atom_matcher();
eventMatcher->set_name("SCREEN_IS_ON");
CountMetric* metric = config.add_count_metric();
@@ -143,20 +143,20 @@
StatsdConfig config;
config.set_name("12345");
- LogEntryMatcher* eventMatcher = config.add_log_entry_matcher();
+ AtomMatcher* eventMatcher = config.add_atom_matcher();
eventMatcher->set_name("SCREEN_IS_ON");
- SimpleLogEntryMatcher* simpleLogEntryMatcher = eventMatcher->mutable_simple_log_entry_matcher();
- simpleLogEntryMatcher->set_tag(2 /*SCREEN_STATE_CHANGE*/);
- simpleLogEntryMatcher->add_key_value_matcher()->mutable_key_matcher()->set_key(
+ SimpleAtomMatcher* simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher();
+ simpleAtomMatcher->set_tag(2 /*SCREEN_STATE_CHANGE*/);
+ simpleAtomMatcher->add_key_value_matcher()->mutable_key_matcher()->set_key(
1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE*/);
- simpleLogEntryMatcher->mutable_key_value_matcher(0)->set_eq_int(
+ simpleAtomMatcher->mutable_key_value_matcher(0)->set_eq_int(
2 /*SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_ON*/);
- eventMatcher = config.add_log_entry_matcher();
+ eventMatcher = config.add_atom_matcher();
eventMatcher->set_name("SCREEN_ON_OR_OFF");
- LogEntryMatcher_Combination* combination = eventMatcher->mutable_combination();
+ AtomMatcher_Combination* combination = eventMatcher->mutable_combination();
combination->set_operation(LogicalOperation::OR);
combination->add_matcher("SCREEN_IS_ON");
// undefined matcher
@@ -175,11 +175,11 @@
metric->mutable_bucket()->set_bucket_size_millis(30 * 1000L);
metric->set_condition("SOME_CONDITION");
- LogEntryMatcher* eventMatcher = config.add_log_entry_matcher();
+ AtomMatcher* eventMatcher = config.add_atom_matcher();
eventMatcher->set_name("SCREEN_EVENT");
- SimpleLogEntryMatcher* simpleLogEntryMatcher = eventMatcher->mutable_simple_log_entry_matcher();
- simpleLogEntryMatcher->set_tag(2);
+ SimpleAtomMatcher* simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher();
+ simpleAtomMatcher->set_tag(2);
return config;
}
@@ -188,20 +188,20 @@
StatsdConfig config;
config.set_name("12345");
- LogEntryMatcher* eventMatcher = config.add_log_entry_matcher();
+ AtomMatcher* eventMatcher = config.add_atom_matcher();
eventMatcher->set_name("BATTERY_VERY_LOW");
- SimpleLogEntryMatcher* simpleLogEntryMatcher = eventMatcher->mutable_simple_log_entry_matcher();
- simpleLogEntryMatcher->set_tag(2);
+ SimpleAtomMatcher* simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher();
+ simpleAtomMatcher->set_tag(2);
- eventMatcher = config.add_log_entry_matcher();
+ eventMatcher = config.add_atom_matcher();
eventMatcher->set_name("BATTERY_VERY_VERY_LOW");
- simpleLogEntryMatcher = eventMatcher->mutable_simple_log_entry_matcher();
- simpleLogEntryMatcher->set_tag(3);
+ simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher();
+ simpleAtomMatcher->set_tag(3);
- eventMatcher = config.add_log_entry_matcher();
+ eventMatcher = config.add_atom_matcher();
eventMatcher->set_name("BATTERY_LOW");
- LogEntryMatcher_Combination* combination = eventMatcher->mutable_combination();
+ AtomMatcher_Combination* combination = eventMatcher->mutable_combination();
combination->set_operation(LogicalOperation::OR);
combination->add_matcher("BATTERY_VERY_LOW");
combination->add_matcher("BATTERY_VERY_VERY_LOW");
@@ -227,24 +227,24 @@
StatsdConfig config;
config.set_name("12345");
- LogEntryMatcher* eventMatcher = config.add_log_entry_matcher();
+ AtomMatcher* eventMatcher = config.add_atom_matcher();
eventMatcher->set_name("SCREEN_IS_ON");
- SimpleLogEntryMatcher* simpleLogEntryMatcher = eventMatcher->mutable_simple_log_entry_matcher();
- simpleLogEntryMatcher->set_tag(2 /*SCREEN_STATE_CHANGE*/);
- simpleLogEntryMatcher->add_key_value_matcher()->mutable_key_matcher()->set_key(
+ SimpleAtomMatcher* simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher();
+ simpleAtomMatcher->set_tag(2 /*SCREEN_STATE_CHANGE*/);
+ simpleAtomMatcher->add_key_value_matcher()->mutable_key_matcher()->set_key(
1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE*/);
- simpleLogEntryMatcher->mutable_key_value_matcher(0)->set_eq_int(
+ simpleAtomMatcher->mutable_key_value_matcher(0)->set_eq_int(
2 /*SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_ON*/);
- eventMatcher = config.add_log_entry_matcher();
+ eventMatcher = config.add_atom_matcher();
eventMatcher->set_name("SCREEN_IS_OFF");
- simpleLogEntryMatcher = eventMatcher->mutable_simple_log_entry_matcher();
- simpleLogEntryMatcher->set_tag(2 /*SCREEN_STATE_CHANGE*/);
- simpleLogEntryMatcher->add_key_value_matcher()->mutable_key_matcher()->set_key(
+ simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher();
+ simpleAtomMatcher->set_tag(2 /*SCREEN_STATE_CHANGE*/);
+ simpleAtomMatcher->add_key_value_matcher()->mutable_key_matcher()->set_key(
1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE*/);
- simpleLogEntryMatcher->mutable_key_value_matcher(0)->set_eq_int(
+ simpleAtomMatcher->mutable_key_value_matcher(0)->set_eq_int(
1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_OFF*/);
auto condition = config.add_condition();
@@ -267,7 +267,7 @@
TEST(MetricsManagerTest, TestGoodConfig) {
StatsdConfig config = buildGoodConfig();
set<int> allTagIds;
- vector<sp<LogMatchingTracker>> allLogEntryMatchers;
+ vector<sp<LogMatchingTracker>> allAtomMatchers;
vector<sp<ConditionTracker>> allConditionTrackers;
vector<sp<MetricProducer>> allMetricProducers;
std::vector<sp<AnomalyTracker>> allAnomalyTrackers;
@@ -275,7 +275,7 @@
unordered_map<int, std::vector<int>> trackerToMetricMap;
unordered_map<int, std::vector<int>> trackerToConditionMap;
- EXPECT_TRUE(initStatsdConfig(kConfigKey, config, allTagIds, allLogEntryMatchers,
+ EXPECT_TRUE(initStatsdConfig(kConfigKey, config, allTagIds, allAtomMatchers,
allConditionTrackers, allMetricProducers, allAnomalyTrackers,
conditionToMetricMap, trackerToMetricMap, trackerToConditionMap));
EXPECT_EQ(1u, allMetricProducers.size());
@@ -285,7 +285,7 @@
TEST(MetricsManagerTest, TestDimensionMetricsWithMultiTags) {
StatsdConfig config = buildDimensionMetricsWithMultiTags();
set<int> allTagIds;
- vector<sp<LogMatchingTracker>> allLogEntryMatchers;
+ vector<sp<LogMatchingTracker>> allAtomMatchers;
vector<sp<ConditionTracker>> allConditionTrackers;
vector<sp<MetricProducer>> allMetricProducers;
std::vector<sp<AnomalyTracker>> allAnomalyTrackers;
@@ -293,7 +293,7 @@
unordered_map<int, std::vector<int>> trackerToMetricMap;
unordered_map<int, std::vector<int>> trackerToConditionMap;
- EXPECT_FALSE(initStatsdConfig(kConfigKey, config, allTagIds, allLogEntryMatchers,
+ EXPECT_FALSE(initStatsdConfig(kConfigKey, config, allTagIds, allAtomMatchers,
allConditionTrackers, allMetricProducers, allAnomalyTrackers,
conditionToMetricMap, trackerToMetricMap, trackerToConditionMap));
}
@@ -301,7 +301,7 @@
TEST(MetricsManagerTest, TestCircleLogMatcherDependency) {
StatsdConfig config = buildCircleMatchers();
set<int> allTagIds;
- vector<sp<LogMatchingTracker>> allLogEntryMatchers;
+ vector<sp<LogMatchingTracker>> allAtomMatchers;
vector<sp<ConditionTracker>> allConditionTrackers;
vector<sp<MetricProducer>> allMetricProducers;
std::vector<sp<AnomalyTracker>> allAnomalyTrackers;
@@ -309,7 +309,7 @@
unordered_map<int, std::vector<int>> trackerToMetricMap;
unordered_map<int, std::vector<int>> trackerToConditionMap;
- EXPECT_FALSE(initStatsdConfig(kConfigKey, config, allTagIds, allLogEntryMatchers,
+ EXPECT_FALSE(initStatsdConfig(kConfigKey, config, allTagIds, allAtomMatchers,
allConditionTrackers, allMetricProducers, allAnomalyTrackers,
conditionToMetricMap, trackerToMetricMap, trackerToConditionMap));
}
@@ -317,14 +317,14 @@
TEST(MetricsManagerTest, TestMissingMatchers) {
StatsdConfig config = buildMissingMatchers();
set<int> allTagIds;
- vector<sp<LogMatchingTracker>> allLogEntryMatchers;
+ vector<sp<LogMatchingTracker>> allAtomMatchers;
vector<sp<ConditionTracker>> allConditionTrackers;
vector<sp<MetricProducer>> allMetricProducers;
std::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;
- EXPECT_FALSE(initStatsdConfig(kConfigKey, config, allTagIds, allLogEntryMatchers,
+ EXPECT_FALSE(initStatsdConfig(kConfigKey, config, allTagIds, allAtomMatchers,
allConditionTrackers, allMetricProducers, allAnomalyTrackers,
conditionToMetricMap, trackerToMetricMap, trackerToConditionMap));
}
@@ -332,14 +332,14 @@
TEST(MetricsManagerTest, TestMissingCondition) {
StatsdConfig config = buildMissingCondition();
set<int> allTagIds;
- vector<sp<LogMatchingTracker>> allLogEntryMatchers;
+ vector<sp<LogMatchingTracker>> allAtomMatchers;
vector<sp<ConditionTracker>> allConditionTrackers;
vector<sp<MetricProducer>> allMetricProducers;
std::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;
- EXPECT_FALSE(initStatsdConfig(kConfigKey, config, allTagIds, allLogEntryMatchers,
+ EXPECT_FALSE(initStatsdConfig(kConfigKey, config, allTagIds, allAtomMatchers,
allConditionTrackers, allMetricProducers, allAnomalyTrackers,
conditionToMetricMap, trackerToMetricMap, trackerToConditionMap));
}
@@ -347,7 +347,7 @@
TEST(MetricsManagerTest, TestCircleConditionDependency) {
StatsdConfig config = buildCircleConditions();
set<int> allTagIds;
- vector<sp<LogMatchingTracker>> allLogEntryMatchers;
+ vector<sp<LogMatchingTracker>> allAtomMatchers;
vector<sp<ConditionTracker>> allConditionTrackers;
vector<sp<MetricProducer>> allMetricProducers;
std::vector<sp<AnomalyTracker>> allAnomalyTrackers;
@@ -355,7 +355,7 @@
unordered_map<int, std::vector<int>> trackerToMetricMap;
unordered_map<int, std::vector<int>> trackerToConditionMap;
- EXPECT_FALSE(initStatsdConfig(kConfigKey, config, allTagIds, allLogEntryMatchers,
+ EXPECT_FALSE(initStatsdConfig(kConfigKey, config, allTagIds, allAtomMatchers,
allConditionTrackers, allMetricProducers, allAnomalyTrackers,
conditionToMetricMap, trackerToMetricMap, trackerToConditionMap));
}
@@ -363,7 +363,7 @@
TEST(MetricsManagerTest, testAlertWithUnknownMetric) {
StatsdConfig config = buildAlertWithUnknownMetric();
set<int> allTagIds;
- vector<sp<LogMatchingTracker>> allLogEntryMatchers;
+ vector<sp<LogMatchingTracker>> allAtomMatchers;
vector<sp<ConditionTracker>> allConditionTrackers;
vector<sp<MetricProducer>> allMetricProducers;
std::vector<sp<AnomalyTracker>> allAnomalyTrackers;
@@ -371,7 +371,7 @@
unordered_map<int, std::vector<int>> trackerToMetricMap;
unordered_map<int, std::vector<int>> trackerToConditionMap;
- EXPECT_FALSE(initStatsdConfig(kConfigKey, config, allTagIds, allLogEntryMatchers,
+ EXPECT_FALSE(initStatsdConfig(kConfigKey, config, allTagIds, allAtomMatchers,
allConditionTrackers, allMetricProducers, allAnomalyTrackers,
conditionToMetricMap, trackerToMetricMap, trackerToConditionMap));
}
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 7ecc124..e7bb539 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
@@ -30,9 +30,8 @@
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.LogEntryMatcher;
+import com.android.internal.os.StatsdConfigProto.AtomMatcher;
import com.android.internal.os.StatsdConfigProto.SimpleCondition;
-import com.android.internal.os.StatsdConfigProto.SimpleLogEntryMatcher;
import com.android.internal.os.StatsdConfigProto.StatsdConfig;
import java.io.InputStream;
@@ -119,7 +118,7 @@
addCondition(condition, i, config);
}
// matchers
- for (LogEntryMatcher matcher : mTemplate.getLogEntryMatcherList()) {
+ for (AtomMatcher matcher : mTemplate.getAtomMatcherList()) {
addMatcher(matcher, i, config);
}
}
@@ -282,20 +281,20 @@
}
/**
- * Creates a {@link LogEntryMatcher} based on the template. Makes sure that all names
+ * Creates a {@link AtomMatcher} based on the template. Makes sure that all names
* are appended with the provided suffix. Then adds that matcher to the config.
*/
- private void addMatcher(LogEntryMatcher template, int suffix, StatsdConfig.Builder config) {
- LogEntryMatcher.Builder matcher = template.toBuilder()
+ private void addMatcher(AtomMatcher template, int suffix, StatsdConfig.Builder config) {
+ AtomMatcher.Builder matcher = template.toBuilder()
.setName(template.getName() + suffix);
if (template.hasCombination()) {
- LogEntryMatcher.Combination.Builder cb = template.getCombination().toBuilder()
+ AtomMatcher.Combination.Builder cb = template.getCombination().toBuilder()
.clearMatcher();
for (String child : template.getCombination().getMatcherList()) {
cb.addMatcher(child + suffix);
}
matcher.setCombination(cb);
}
- config.addLogEntryMatcher(matcher);
+ config.addAtomMatcher(matcher);
}
}
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 02b7f8c..9241378 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -1944,15 +1944,17 @@
* @param animate Whether we should play an animation for the moving the task
* @param initialBounds If the primary stack gets created, it will use these bounds for the
* docked stack. Pass {@code null} to use default bounds.
+ * @param showRecents If the recents activity should be shown on the other side of the task
+ * going into split-screen mode.
* @hide
*/
@TestApi
@RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS)
public void setTaskWindowingModeSplitScreenPrimary(int taskId, int createMode, boolean toTop,
- boolean animate, Rect initialBounds) throws SecurityException {
+ boolean animate, Rect initialBounds, boolean showRecents) throws SecurityException {
try {
getService().setTaskWindowingModeSplitScreenPrimary(taskId, createMode, toTop, animate,
- initialBounds);
+ initialBounds, showRecents);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 2d6308c..21e4227 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -502,7 +502,7 @@
void reportSizeConfigurations(in IBinder token, in int[] horizontalSizeConfiguration,
in int[] verticalSizeConfigurations, in int[] smallestWidthConfigurations);
boolean setTaskWindowingModeSplitScreenPrimary(int taskId, int createMode, boolean toTop,
- boolean animate, in Rect initialBounds);
+ boolean animate, in Rect initialBounds, boolean showRecents);
/**
* Dismisses split-screen multi-window mode.
* {@param toTop} If true the current primary split-screen stack will be placed or left on top.
diff --git a/core/java/android/app/IUiAutomationConnection.aidl b/core/java/android/app/IUiAutomationConnection.aidl
index b26117d..d01938b 100644
--- a/core/java/android/app/IUiAutomationConnection.aidl
+++ b/core/java/android/app/IUiAutomationConnection.aidl
@@ -18,6 +18,7 @@
import android.accessibilityservice.IAccessibilityServiceClient;
import android.graphics.Bitmap;
+import android.graphics.Rect;
import android.view.InputEvent;
import android.view.WindowContentFrameStats;
import android.view.WindowAnimationFrameStats;
@@ -37,7 +38,7 @@
void disconnect();
boolean injectInputEvent(in InputEvent event, boolean sync);
boolean setRotation(int rotation);
- Bitmap takeScreenshot(int width, int height);
+ Bitmap takeScreenshot(in Rect crop, int rotation);
boolean clearWindowContentFrameStats(int windowId);
WindowContentFrameStats getWindowContentFrameStats(int windowId);
void clearWindowAnimationFrameStats();
diff --git a/core/java/android/app/SharedPreferencesImpl.java b/core/java/android/app/SharedPreferencesImpl.java
index b186666..6dca400 100644
--- a/core/java/android/app/SharedPreferencesImpl.java
+++ b/core/java/android/app/SharedPreferencesImpl.java
@@ -217,10 +217,15 @@
}
private @GuardedBy("mLock") Map<String, Object> getLoaded() {
- try {
- return mMap.get();
- } catch (InterruptedException | ExecutionException e) {
- throw new IllegalStateException(e);
+ // For backwards compatibility, we need to ignore any interrupts. b/70122540.
+ for (;;) {
+ try {
+ return mMap.get();
+ } catch (ExecutionException e) {
+ throw new IllegalStateException(e);
+ } catch (InterruptedException e) {
+ // Ignore and try again.
+ }
}
}
private @GuardedBy("mLock") Map<String, Object> getLoadedWithBlockGuard() {
diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java
index c99de5d..8f01685 100644
--- a/core/java/android/app/UiAutomation.java
+++ b/core/java/android/app/UiAutomation.java
@@ -26,6 +26,7 @@
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Point;
+import android.graphics.Rect;
import android.graphics.Region;
import android.hardware.display.DisplayManagerGlobal;
import android.os.IBinder;
@@ -690,42 +691,15 @@
.getRealDisplay(Display.DEFAULT_DISPLAY);
Point displaySize = new Point();
display.getRealSize(displaySize);
- final int displayWidth = displaySize.x;
- final int displayHeight = displaySize.y;
- final float screenshotWidth;
- final float screenshotHeight;
-
- final int rotation = display.getRotation();
- switch (rotation) {
- case ROTATION_FREEZE_0: {
- screenshotWidth = displayWidth;
- screenshotHeight = displayHeight;
- } break;
- case ROTATION_FREEZE_90: {
- screenshotWidth = displayHeight;
- screenshotHeight = displayWidth;
- } break;
- case ROTATION_FREEZE_180: {
- screenshotWidth = displayWidth;
- screenshotHeight = displayHeight;
- } break;
- case ROTATION_FREEZE_270: {
- screenshotWidth = displayHeight;
- screenshotHeight = displayWidth;
- } break;
- default: {
- throw new IllegalArgumentException("Invalid rotation: "
- + rotation);
- }
- }
+ int rotation = display.getRotation();
// Take the screenshot
Bitmap screenShot = null;
try {
// Calling out without a lock held.
- screenShot = mUiAutomationConnection.takeScreenshot((int) screenshotWidth,
- (int) screenshotHeight);
+ screenShot = mUiAutomationConnection.takeScreenshot(
+ new Rect(0, 0, displaySize.x, displaySize.y), rotation);
if (screenShot == null) {
return null;
}
@@ -734,21 +708,6 @@
return null;
}
- // Rotate the screenshot to the current orientation
- if (rotation != ROTATION_FREEZE_0) {
- Bitmap unrotatedScreenShot = Bitmap.createBitmap(displayWidth, displayHeight,
- Bitmap.Config.ARGB_8888);
- Canvas canvas = new Canvas(unrotatedScreenShot);
- canvas.translate(unrotatedScreenShot.getWidth() / 2,
- unrotatedScreenShot.getHeight() / 2);
- canvas.rotate(getDegreesForRotation(rotation));
- canvas.translate(- screenshotWidth / 2, - screenshotHeight / 2);
- canvas.drawBitmap(screenShot, 0, 0, null);
- canvas.setBitmap(null);
- screenShot.recycle();
- screenShot = unrotatedScreenShot;
- }
-
// Optimization
screenShot.setHasAlpha(false);
diff --git a/core/java/android/app/UiAutomationConnection.java b/core/java/android/app/UiAutomationConnection.java
index 5e414b8..d3828ab 100644
--- a/core/java/android/app/UiAutomationConnection.java
+++ b/core/java/android/app/UiAutomationConnection.java
@@ -21,6 +21,7 @@
import android.content.Context;
import android.content.pm.IPackageManager;
import android.graphics.Bitmap;
+import android.graphics.Rect;
import android.hardware.input.InputManager;
import android.os.Binder;
import android.os.IBinder;
@@ -153,7 +154,7 @@
}
@Override
- public Bitmap takeScreenshot(int width, int height) {
+ public Bitmap takeScreenshot(Rect crop, int rotation) {
synchronized (mLock) {
throwIfCalledByNotTrustedUidLocked();
throwIfShutdownLocked();
@@ -161,7 +162,9 @@
}
final long identity = Binder.clearCallingIdentity();
try {
- return SurfaceControl.screenshot(width, height);
+ int width = crop.width();
+ int height = crop.height();
+ return SurfaceControl.screenshot(crop, width, height, rotation);
} finally {
Binder.restoreCallingIdentity(identity);
}
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 0bca969..f0117f2 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -6667,7 +6667,7 @@
}
/**
- * Called by device owners to update {@link android.provider.Settings.Global} settings.
+ * Called by device owner to update {@link android.provider.Settings.Global} settings.
* Validation that the value of the setting is in the correct form for the setting type should
* be performed by the caller.
* <p>
@@ -6716,6 +6716,37 @@
}
/**
+ * Called by device owner to update {@link android.provider.Settings.System} settings.
+ * Validation that the value of the setting is in the correct form for the setting type should
+ * be performed by the caller.
+ * <p>
+ * The settings that can be updated with this method are:
+ * <ul>
+ * <li>{@link android.provider.Settings.System#SCREEN_BRIGHTNESS}</li>
+ * <li>{@link android.provider.Settings.System#SCREEN_BRIGHTNESS_MODE}</li>
+ * <li>{@link android.provider.Settings.System#SCREEN_OFF_TIMEOUT}</li>
+ * </ul>
+ * <p>
+ *
+ * @see android.provider.Settings.System#SCREEN_OFF_TIMEOUT
+ * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+ * @param setting The name of the setting to update.
+ * @param value The value to update the setting to.
+ * @throws SecurityException if {@code admin} is not a device owner.
+ */
+ public void setSystemSetting(@NonNull ComponentName admin, @NonNull String setting,
+ String value) {
+ throwIfParentInstance("setSystemSetting");
+ if (mService != null) {
+ try {
+ mService.setSystemSetting(admin, setting, value);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
* Called by device owner to set the system wall clock time. This only takes effect if called
* when {@link android.provider.Settings.Global#AUTO_TIME} is 0, otherwise {@code false} will be
* returned.
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index b7740e9..802d42f 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -231,6 +231,7 @@
int getLockTaskFeatures(in ComponentName who);
void setGlobalSetting(in ComponentName who, in String setting, in String value);
+ void setSystemSetting(in ComponentName who, in String setting, in String value);
void setSecureSetting(in ComponentName who, in String setting, in String value);
boolean setTime(in ComponentName who, long millis);
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index bad452c..d1e12aa 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -4455,6 +4455,12 @@
public static final String EXTRA_EPHEMERAL_TOKEN = "android.intent.extra.EPHEMERAL_TOKEN";
/**
+ * The action that triggered an instant application resolution.
+ * @hide
+ */
+ public static final String EXTRA_INSTANT_APP_ACTION = "android.intent.extra.INSTANT_APP_ACTION";
+
+ /**
* The version code of the app to install components from.
* @hide
*/
diff --git a/core/java/android/net/IpSecAlgorithm.java b/core/java/android/net/IpSecAlgorithm.java
index d6e62cf..f82627b 100644
--- a/core/java/android/net/IpSecAlgorithm.java
+++ b/core/java/android/net/IpSecAlgorithm.java
@@ -21,6 +21,7 @@
import android.os.Parcel;
import android.os.Parcelable;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.HexDump;
import java.lang.annotation.Retention;
@@ -34,6 +35,8 @@
* Internet Protocol</a>
*/
public final class IpSecAlgorithm implements Parcelable {
+ private static final String TAG = "IpSecAlgorithm";
+
/**
* AES-CBC Encryption/Ciphering Algorithm.
*
@@ -45,6 +48,7 @@
* MD5 HMAC Authentication/Integrity Algorithm. <b>This algorithm is not recommended for use in
* new applications and is provided for legacy compatibility with 3gpp infrastructure.</b>
*
+ * <p>Keys for this algorithm must be 128 bits in length.
* <p>Valid truncation lengths are multiples of 8 bits from 96 to (default) 128.
*/
public static final String AUTH_HMAC_MD5 = "hmac(md5)";
@@ -53,6 +57,7 @@
* SHA1 HMAC Authentication/Integrity Algorithm. <b>This algorithm is not recommended for use in
* new applications and is provided for legacy compatibility with 3gpp infrastructure.</b>
*
+ * <p>Keys for this algorithm must be 160 bits in length.
* <p>Valid truncation lengths are multiples of 8 bits from 96 to (default) 160.
*/
public static final String AUTH_HMAC_SHA1 = "hmac(sha1)";
@@ -60,6 +65,7 @@
/**
* SHA256 HMAC Authentication/Integrity Algorithm.
*
+ * <p>Keys for this algorithm must be 256 bits in length.
* <p>Valid truncation lengths are multiples of 8 bits from 96 to (default) 256.
*/
public static final String AUTH_HMAC_SHA256 = "hmac(sha256)";
@@ -67,6 +73,7 @@
/**
* SHA384 HMAC Authentication/Integrity Algorithm.
*
+ * <p>Keys for this algorithm must be 384 bits in length.
* <p>Valid truncation lengths are multiples of 8 bits from 192 to (default) 384.
*/
public static final String AUTH_HMAC_SHA384 = "hmac(sha384)";
@@ -74,6 +81,7 @@
/**
* SHA512 HMAC Authentication/Integrity Algorithm.
*
+ * <p>Keys for this algorithm must be 512 bits in length.
* <p>Valid truncation lengths are multiples of 8 bits from 256 to (default) 512.
*/
public static final String AUTH_HMAC_SHA512 = "hmac(sha512)";
@@ -130,12 +138,10 @@
* @param truncLenBits number of bits of output hash to use.
*/
public IpSecAlgorithm(@AlgorithmName String algorithm, @NonNull byte[] key, int truncLenBits) {
- if (!isTruncationLengthValid(algorithm, truncLenBits)) {
- throw new IllegalArgumentException("Unknown algorithm or invalid length");
- }
mName = algorithm;
mKey = key.clone();
- mTruncLenBits = Math.min(truncLenBits, key.length * 8);
+ mTruncLenBits = truncLenBits;
+ checkValidOrThrow(mName, mKey.length * 8, mTruncLenBits);
}
/** Get the algorithm name */
@@ -169,7 +175,11 @@
public static final Parcelable.Creator<IpSecAlgorithm> CREATOR =
new Parcelable.Creator<IpSecAlgorithm>() {
public IpSecAlgorithm createFromParcel(Parcel in) {
- return new IpSecAlgorithm(in);
+ final String name = in.readString();
+ final byte[] key = in.createByteArray();
+ final int truncLenBits = in.readInt();
+
+ return new IpSecAlgorithm(name, key, truncLenBits);
}
public IpSecAlgorithm[] newArray(int size) {
@@ -177,30 +187,47 @@
}
};
- private IpSecAlgorithm(Parcel in) {
- mName = in.readString();
- mKey = in.createByteArray();
- mTruncLenBits = in.readInt();
- }
+ private static void checkValidOrThrow(String name, int keyLen, int truncLen) {
+ boolean isValidLen = true;
+ boolean isValidTruncLen = true;
- private static boolean isTruncationLengthValid(String algo, int truncLenBits) {
- switch (algo) {
+ switch(name) {
case CRYPT_AES_CBC:
- return (truncLenBits == 128 || truncLenBits == 192 || truncLenBits == 256);
+ isValidLen = keyLen == 128 || keyLen == 192 || keyLen == 256;
+ break;
case AUTH_HMAC_MD5:
- return (truncLenBits >= 96 && truncLenBits <= 128);
+ isValidLen = keyLen == 128;
+ isValidTruncLen = truncLen >= 96 && truncLen <= 128;
+ break;
case AUTH_HMAC_SHA1:
- return (truncLenBits >= 96 && truncLenBits <= 160);
+ isValidLen = keyLen == 160;
+ isValidTruncLen = truncLen >= 96 && truncLen <= 160;
+ break;
case AUTH_HMAC_SHA256:
- return (truncLenBits >= 96 && truncLenBits <= 256);
+ isValidLen = keyLen == 256;
+ isValidTruncLen = truncLen >= 96 && truncLen <= 256;
+ break;
case AUTH_HMAC_SHA384:
- return (truncLenBits >= 192 && truncLenBits <= 384);
+ isValidLen = keyLen == 384;
+ isValidTruncLen = truncLen >= 192 && truncLen <= 384;
+ break;
case AUTH_HMAC_SHA512:
- return (truncLenBits >= 256 && truncLenBits <= 512);
+ isValidLen = keyLen == 512;
+ isValidTruncLen = truncLen >= 256 && truncLen <= 512;
+ break;
case AUTH_CRYPT_AES_GCM:
- return (truncLenBits == 64 || truncLenBits == 96 || truncLenBits == 128);
+ // The keying material for GCM is a key plus a 32-bit salt
+ isValidLen = keyLen == 128 + 32 || keyLen == 192 + 32 || keyLen == 256 + 32;
+ break;
default:
- return false;
+ throw new IllegalArgumentException("Couldn't find an algorithm: " + name);
+ }
+
+ if (!isValidLen) {
+ throw new IllegalArgumentException("Invalid key material keyLength: " + keyLen);
+ }
+ if (!isValidTruncLen) {
+ throw new IllegalArgumentException("Invalid truncation keyLength: " + truncLen);
}
}
@@ -217,8 +244,9 @@
.toString();
}
- /** package */
- static boolean equals(IpSecAlgorithm lhs, IpSecAlgorithm rhs) {
+ /** @hide */
+ @VisibleForTesting
+ public static boolean equals(IpSecAlgorithm lhs, IpSecAlgorithm rhs) {
if (lhs == null || rhs == null) return (lhs == rhs);
return (lhs.mName.equals(rhs.mName)
&& Arrays.equals(lhs.mKey, rhs.mKey)
diff --git a/core/java/android/net/MacAddress.java b/core/java/android/net/MacAddress.java
index f6a69ba..5620a62 100644
--- a/core/java/android/net/MacAddress.java
+++ b/core/java/android/net/MacAddress.java
@@ -16,17 +16,23 @@
package android.net;
+import android.annotation.IntDef;
import android.os.Parcel;
import android.os.Parcelable;
import com.android.internal.util.BitUtils;
+import com.android.internal.util.Preconditions;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.Arrays;
import java.util.Random;
-import java.util.StringJoiner;
/**
- * Represents a mac address.
+ * Representation of a MAC address.
+ *
+ * This class only supports 48 bits long addresses and does not support 64 bits long addresses.
+ * Instances of this class are immutable.
*
* @hide
*/
@@ -35,76 +41,102 @@
private static final int ETHER_ADDR_LEN = 6;
private static final byte[] ETHER_ADDR_BROADCAST = addr(0xff, 0xff, 0xff, 0xff, 0xff, 0xff);
- /** The broadcast mac address. */
- public static final MacAddress BROADCAST_ADDRESS = new MacAddress(ETHER_ADDR_BROADCAST);
+ /**
+ * The MacAddress representing the unique broadcast MAC address.
+ */
+ public static final MacAddress BROADCAST_ADDRESS = MacAddress.fromBytes(ETHER_ADDR_BROADCAST);
- /** The zero mac address. */
+ /**
+ * The MacAddress zero MAC address.
+ * @hide
+ */
public static final MacAddress ALL_ZEROS_ADDRESS = new MacAddress(0);
- /** Represents categories of mac addresses. */
- public enum MacAddressType {
- UNICAST,
- MULTICAST,
- BROADCAST;
- }
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = { "TYPE_" }, value = {
+ TYPE_UNKNOWN,
+ TYPE_UNICAST,
+ TYPE_MULTICAST,
+ TYPE_BROADCAST,
+ })
+ public @interface MacAddressType { }
- private static final long VALID_LONG_MASK = BROADCAST_ADDRESS.mAddr;
- private static final long LOCALLY_ASSIGNED_MASK = new MacAddress("2:0:0:0:0:0").mAddr;
- private static final long MULTICAST_MASK = new MacAddress("1:0:0:0:0:0").mAddr;
- private static final long OUI_MASK = new MacAddress("ff:ff:ff:0:0:0").mAddr;
- private static final long NIC_MASK = new MacAddress("0:0:0:ff:ff:ff").mAddr;
- private static final MacAddress BASE_ANDROID_MAC = new MacAddress("da:a1:19:0:0:0");
+ /** Indicates a MAC address of unknown type. */
+ public static final int TYPE_UNKNOWN = 0;
+ /** Indicates a MAC address is a unicast address. */
+ public static final int TYPE_UNICAST = 1;
+ /** Indicates a MAC address is a multicast address. */
+ public static final int TYPE_MULTICAST = 2;
+ /** Indicates a MAC address is the broadcast address. */
+ public static final int TYPE_BROADCAST = 3;
- // Internal representation of the mac address as a single 8 byte long.
+ private static final long VALID_LONG_MASK = (1L << 48) - 1;
+ private static final long LOCALLY_ASSIGNED_MASK = MacAddress.fromString("2:0:0:0:0:0").mAddr;
+ private static final long MULTICAST_MASK = MacAddress.fromString("1:0:0:0:0:0").mAddr;
+ private static final long OUI_MASK = MacAddress.fromString("ff:ff:ff:0:0:0").mAddr;
+ private static final long NIC_MASK = MacAddress.fromString("0:0:0:ff:ff:ff").mAddr;
+ private static final MacAddress BASE_GOOGLE_MAC = MacAddress.fromString("da:a1:19:0:0:0");
+
+ // Internal representation of the MAC address as a single 8 byte long.
// The encoding scheme sets the two most significant bytes to 0. The 6 bytes of the
- // mac address are encoded in the 6 least significant bytes of the long, where the first
+ // MAC address are encoded in the 6 least significant bytes of the long, where the first
// byte of the array is mapped to the 3rd highest logical byte of the long, the second
// byte of the array is mapped to the 4th highest logical byte of the long, and so on.
private final long mAddr;
private MacAddress(long addr) {
- mAddr = addr;
+ mAddr = (VALID_LONG_MASK & addr);
}
- /** Creates a MacAddress for the given byte representation. */
- public MacAddress(byte[] addr) {
- this(longAddrFromByteAddr(addr));
- }
-
- /** Creates a MacAddress for the given string representation. */
- public MacAddress(String addr) {
- this(longAddrFromByteAddr(byteAddrFromStringAddr(addr)));
- }
-
- /** Returns the MacAddressType of this MacAddress. */
- public MacAddressType addressType() {
+ /**
+ * Returns the type of this address.
+ *
+ * @return the int constant representing the MAC address type of this MacAddress.
+ */
+ public @MacAddressType int addressType() {
if (equals(BROADCAST_ADDRESS)) {
- return MacAddressType.BROADCAST;
+ return TYPE_BROADCAST;
}
if (isMulticastAddress()) {
- return MacAddressType.MULTICAST;
+ return TYPE_MULTICAST;
}
- return MacAddressType.UNICAST;
+ return TYPE_UNICAST;
}
- /** Returns true if this MacAddress corresponds to a multicast address. */
+ /**
+ * @return true if this MacAddress is a multicast address.
+ * @hide
+ */
public boolean isMulticastAddress() {
return (mAddr & MULTICAST_MASK) != 0;
}
- /** Returns true if this MacAddress corresponds to a locally assigned address. */
+ /**
+ * @return true if this MacAddress is a locally assigned address.
+ */
public boolean isLocallyAssigned() {
return (mAddr & LOCALLY_ASSIGNED_MASK) != 0;
}
- /** Returns a byte array representation of this MacAddress. */
+ /**
+ * @return a byte array representation of this MacAddress.
+ */
public byte[] toByteArray() {
return byteAddrFromLongAddr(mAddr);
}
@Override
public String toString() {
- return stringAddrFromByteAddr(byteAddrFromLongAddr(mAddr));
+ return stringAddrFromLongAddr(mAddr);
+ }
+
+ /**
+ * @return a String representation of the OUI part of this MacAddres,
+ * with the lower 3 bytes constituting the NIC part replaced with 0.
+ */
+ public String toSafeString() {
+ return stringAddrFromLongAddr(mAddr & OUI_MASK);
}
@Override
@@ -138,27 +170,50 @@
}
};
- /** Return true if the given byte array is not null and has the length of a mac address. */
+ /**
+ * Returns true if the given byte array is an valid MAC address.
+ * A valid byte array representation for a MacAddress is a non-null array of length 6.
+ *
+ * @param addr a byte array.
+ * @return true if the given byte array is not null and has the length of a MAC address.
+ *
+ * @hide
+ */
public static boolean isMacAddress(byte[] addr) {
return addr != null && addr.length == ETHER_ADDR_LEN;
}
/**
- * Return the MacAddressType of the mac address represented by the given byte array,
- * or null if the given byte array does not represent an mac address.
+ * Returns the MAC address type of the MAC address represented by the given byte array,
+ * or null if the given byte array does not represent a MAC address.
+ * A valid byte array representation for a MacAddress is a non-null array of length 6.
+ *
+ * @param addr a byte array representing a MAC address.
+ * @return the int constant representing the MAC address type of the MAC address represented
+ * by the given byte array, or type UNKNOWN if the byte array is not a valid MAC address.
+ *
+ * @hide
*/
- public static MacAddressType macAddressType(byte[] addr) {
+ public static int macAddressType(byte[] addr) {
if (!isMacAddress(addr)) {
- return null;
+ return TYPE_UNKNOWN;
}
- return new MacAddress(addr).addressType();
+ return MacAddress.fromBytes(addr).addressType();
}
- /** DOCME */
+ /**
+ * Converts a String representation of a MAC address to a byte array representation.
+ * A valid String representation for a MacAddress is a series of 6 values in the
+ * range [0,ff] printed in hexadecimal and joined by ':' characters.
+ *
+ * @param addr a String representation of a MAC address.
+ * @return the byte representation of the MAC address.
+ * @throws IllegalArgumentException if the given String is not a valid representation.
+ *
+ * @hide
+ */
public static byte[] byteAddrFromStringAddr(String addr) {
- if (addr == null) {
- throw new IllegalArgumentException("cannot convert the null String");
- }
+ Preconditions.checkNotNull(addr);
String[] parts = addr.split(":");
if (parts.length != ETHER_ADDR_LEN) {
throw new IllegalArgumentException(addr + " was not a valid MAC address");
@@ -174,20 +229,26 @@
return bytes;
}
- /** DOCME */
+ /**
+ * Converts a byte array representation of a MAC address to a String representation made
+ * of 6 hexadecimal numbers in [0,ff] joined by ':' characters.
+ * A valid byte array representation for a MacAddress is a non-null array of length 6.
+ *
+ * @param addr a byte array representation of a MAC address.
+ * @return the String representation of the MAC address.
+ * @throws IllegalArgumentException if the given byte array is not a valid representation.
+ *
+ * @hide
+ */
public static String stringAddrFromByteAddr(byte[] addr) {
if (!isMacAddress(addr)) {
return null;
}
- StringJoiner j = new StringJoiner(":");
- for (byte b : addr) {
- j.add(Integer.toHexString(BitUtils.uint8(b)));
- }
- return j.toString();
+ return String.format("%02x:%02x:%02x:%02x:%02x:%02x",
+ addr[0], addr[1], addr[2], addr[3], addr[4], addr[5]);
}
- /** @hide */
- public static byte[] byteAddrFromLongAddr(long addr) {
+ private static byte[] byteAddrFromLongAddr(long addr) {
byte[] bytes = new byte[ETHER_ADDR_LEN];
int index = ETHER_ADDR_LEN;
while (index-- > 0) {
@@ -197,8 +258,8 @@
return bytes;
}
- /** @hide */
- public static long longAddrFromByteAddr(byte[] addr) {
+ private static long longAddrFromByteAddr(byte[] addr) {
+ Preconditions.checkNotNull(addr);
if (!isMacAddress(addr)) {
throw new IllegalArgumentException(
Arrays.toString(addr) + " was not a valid MAC address");
@@ -210,19 +271,17 @@
return longAddr;
}
- /** @hide */
- public static long longAddrFromStringAddr(String addr) {
- if (addr == null) {
- throw new IllegalArgumentException("cannot convert the null String");
- }
+ // Internal conversion function equivalent to longAddrFromByteAddr(byteAddrFromStringAddr(addr))
+ // that avoids the allocation of an intermediary byte[].
+ private static long longAddrFromStringAddr(String addr) {
+ Preconditions.checkNotNull(addr);
String[] parts = addr.split(":");
if (parts.length != ETHER_ADDR_LEN) {
throw new IllegalArgumentException(addr + " was not a valid MAC address");
}
long longAddr = 0;
- int index = ETHER_ADDR_LEN;
- while (index-- > 0) {
- int x = Integer.valueOf(parts[index], 16);
+ for (int i = 0; i < parts.length; i++) {
+ int x = Integer.valueOf(parts[i], 16);
if (x < 0 || 0xff < x) {
throw new IllegalArgumentException(addr + "was not a valid MAC address");
}
@@ -231,32 +290,74 @@
return longAddr;
}
- /** @hide */
- public static String stringAddrFromLongAddr(long addr) {
- addr = Long.reverseBytes(addr) >> 16;
- StringJoiner j = new StringJoiner(":");
- for (int i = 0; i < ETHER_ADDR_LEN; i++) {
- j.add(Integer.toHexString((byte) addr));
- addr = addr >> 8;
- }
- return j.toString();
+ // Internal conversion function equivalent to stringAddrFromByteAddr(byteAddrFromLongAddr(addr))
+ // that avoids the allocation of an intermediary byte[].
+ private static String stringAddrFromLongAddr(long addr) {
+ return String.format("%02x:%02x:%02x:%02x:%02x:%02x",
+ (addr >> 40) & 0xff,
+ (addr >> 32) & 0xff,
+ (addr >> 24) & 0xff,
+ (addr >> 16) & 0xff,
+ (addr >> 8) & 0xff,
+ addr & 0xff);
}
/**
- * Returns a randomely generated mac address with the Android OUI value "DA-A1-19".
- * The locally assigned bit is always set to 1.
+ * Creates a MacAddress from the given String representation. A valid String representation
+ * for a MacAddress is a series of 6 values in the range [0,ff] printed in hexadecimal
+ * and joined by ':' characters.
+ *
+ * @param addr a String representation of a MAC address.
+ * @return the MacAddress corresponding to the given String representation.
+ * @throws IllegalArgumentException if the given String is not a valid representation.
*/
- public static MacAddress getRandomAddress() {
- return getRandomAddress(BASE_ANDROID_MAC, new Random());
+ public static MacAddress fromString(String addr) {
+ return new MacAddress(longAddrFromStringAddr(addr));
}
/**
- * Returns a randomely generated mac address using the given Random object and the same
- * OUI values as the given MacAddress. The locally assigned bit is always set to 1.
+ * Creates a MacAddress from the given byte array representation.
+ * A valid byte array representation for a MacAddress is a non-null array of length 6.
+ *
+ * @param addr a byte array representation of a MAC address.
+ * @return the MacAddress corresponding to the given byte array representation.
+ * @throws IllegalArgumentException if the given byte array is not a valid representation.
*/
- public static MacAddress getRandomAddress(MacAddress base, Random r) {
- long longAddr = (base.mAddr & OUI_MASK) | (NIC_MASK & r.nextLong()) | LOCALLY_ASSIGNED_MASK;
- return new MacAddress(longAddr);
+ public static MacAddress fromBytes(byte[] addr) {
+ return new MacAddress(longAddrFromByteAddr(addr));
+ }
+
+ /**
+ * Returns a generated MAC address whose 24 least significant bits constituting the
+ * NIC part of the address are randomly selected.
+ *
+ * The locally assigned bit is always set to 1. The multicast bit is always set to 0.
+ *
+ * @return a random locally assigned MacAddress.
+ *
+ * @hide
+ */
+ public static MacAddress createRandomUnicastAddress() {
+ return createRandomUnicastAddress(BASE_GOOGLE_MAC, new Random());
+ }
+
+ /**
+ * Returns a randomly generated MAC address using the given Random object and the same
+ * OUI values as the given MacAddress.
+ *
+ * The locally assigned bit is always set to 1. The multicast bit is always set to 0.
+ *
+ * @param base a base MacAddress whose OUI is used for generating the random address.
+ * @param r a standard Java Random object used for generating the random address.
+ * @return a random locally assigned MacAddress.
+ *
+ * @hide
+ */
+ public static MacAddress createRandomUnicastAddress(MacAddress base, Random r) {
+ long addr = (base.mAddr & OUI_MASK) | (NIC_MASK & r.nextLong());
+ addr = addr | LOCALLY_ASSIGNED_MASK;
+ addr = addr & ~MULTICAST_MASK;
+ return new MacAddress(addr);
}
// Convenience function for working around the lack of byte literals.
diff --git a/core/java/android/net/TrafficStats.java b/core/java/android/net/TrafficStats.java
index c339856..954e59c 100644
--- a/core/java/android/net/TrafficStats.java
+++ b/core/java/android/net/TrafficStats.java
@@ -17,6 +17,7 @@
package android.net;
import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.app.DownloadManager;
import android.app.backup.BackupManager;
@@ -30,6 +31,8 @@
import dalvik.system.SocketTagger;
+import java.io.FileDescriptor;
+import java.io.IOException;
import java.net.DatagramSocket;
import java.net.Socket;
import java.net.SocketException;
@@ -264,14 +267,25 @@
}
/**
+ * Set specific UID to use when accounting {@link Socket} traffic
+ * originating from the current thread as the calling UID. Designed for use
+ * when another application is performing operations on your behalf.
+ * <p>
+ * Changes only take effect during subsequent calls to
+ * {@link #tagSocket(Socket)}.
+ */
+ public static void setThreadStatsUidSelf() {
+ setThreadStatsUid(android.os.Process.myUid());
+ }
+
+ /**
* Clear any active UID set to account {@link Socket} traffic originating
* from the current thread.
*
* @see #setThreadStatsUid(int)
- * @hide
*/
@SystemApi
- @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS)
+ @SuppressLint("Doclava125")
public static void clearThreadStatsUid() {
NetworkManagementSocketTagger.setThreadSocketStatsUid(-1);
}
@@ -316,6 +330,27 @@
}
/**
+ * Tag the given {@link FileDescriptor} socket with any statistics
+ * parameters active for the current thread. Subsequent calls always replace
+ * any existing parameters. When finished, call
+ * {@link #untagFileDescriptor(FileDescriptor)} to remove statistics
+ * parameters.
+ *
+ * @see #setThreadStatsTag(int)
+ */
+ public static void tagFileDescriptor(FileDescriptor fd) throws IOException {
+ SocketTagger.get().tag(fd);
+ }
+
+ /**
+ * Remove any statistics parameters from the given {@link FileDescriptor}
+ * socket.
+ */
+ public static void untagFileDescriptor(FileDescriptor fd) throws IOException {
+ SocketTagger.get().untag(fd);
+ }
+
+ /**
* Start profiling data usage for current UID. Only one profiling session
* can be active at a time.
*
diff --git a/core/java/android/net/metrics/WakeupStats.java b/core/java/android/net/metrics/WakeupStats.java
index 23c1f20..7277ba3 100644
--- a/core/java/android/net/metrics/WakeupStats.java
+++ b/core/java/android/net/metrics/WakeupStats.java
@@ -16,6 +16,7 @@
package android.net.metrics;
+import android.net.MacAddress;
import android.os.Process;
import android.os.SystemClock;
import android.util.SparseIntArray;
@@ -80,13 +81,13 @@
}
switch (ev.dstHwAddr.addressType()) {
- case UNICAST:
+ case MacAddress.TYPE_UNICAST:
l2UnicastCount++;
break;
- case MULTICAST:
+ case MacAddress.TYPE_MULTICAST:
l2MulticastCount++;
break;
- case BROADCAST:
+ case MacAddress.TYPE_BROADCAST:
l2BroadcastCount++;
break;
default:
diff --git a/core/java/android/os/IStatsManager.aidl b/core/java/android/os/IStatsManager.aidl
index b814b46..c8c428e 100644
--- a/core/java/android/os/IStatsManager.aidl
+++ b/core/java/android/os/IStatsManager.aidl
@@ -69,11 +69,16 @@
/**
* Fetches data for the specified configuration key. Returns a byte array representing proto
- * wire-encoded of ConfigMetricsReport.
+ * wire-encoded of ConfigMetricsReportList.
*/
byte[] getData(in String key);
/**
+ * Fetches metadata across statsd. Returns byte array representing wire-encoded proto.
+ */
+ byte[] getMetadata();
+
+ /**
* Sets a configuration with the specified config key and subscribes to updates for this
* configuration key. Broadcasts will be sent if this configuration needs to be collected.
* The configuration must be a wire-encoded StatsDConfig. The caller specifies the name of the
diff --git a/core/java/android/print/IPrintClient.aidl b/core/java/android/print/IPrintClient.aidl
deleted file mode 100644
index 3f39d08..0000000
--- a/core/java/android/print/IPrintClient.aidl
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.print;
-
-import android.content.IntentSender;
-
-/**
- * Interface for communication with a printing app.
- *
- * @see android.print.IPrintClientCallback
- *
- * @hide
- */
-oneway interface IPrintClient {
- void startPrintJobConfigActivity(in IntentSender intent);
-}
diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java
index cc1c067..ec5b1c6 100644
--- a/core/java/android/provider/ContactsContract.java
+++ b/core/java/android/provider/ContactsContract.java
@@ -22,6 +22,7 @@
import android.annotation.SystemApi;
import android.app.Activity;
import android.content.BroadcastReceiver;
+import android.content.ComponentName;
import android.content.ContentProviderClient;
import android.content.ContentProviderOperation;
import android.content.ContentResolver;
@@ -42,10 +43,12 @@
import android.graphics.Rect;
import android.net.Uri;
import android.os.RemoteException;
+import android.telecom.PhoneAccountHandle;
import android.text.TextUtils;
import android.util.DisplayMetrics;
import android.util.Pair;
import android.view.View;
+
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
@@ -4237,6 +4240,25 @@
* current carrier. An allowed bitmask of {@link #CARRIER_PRESENCE}.
*/
public static final int CARRIER_PRESENCE_VT_CAPABLE = 0x01;
+
+ /**
+ * The flattened {@link android.content.ComponentName} of a {@link
+ * android.telecom.PhoneAccountHandle} that is the preferred {@code PhoneAccountHandle} to
+ * call the contact with. Used by {@link CommonDataKinds.Phone}.
+ *
+ * @see PhoneAccountHandle#getComponentName()
+ * @see ComponentName#flattenToString()
+ */
+ String PREFERRED_PHONE_ACCOUNT_COMPONENT_NAME = "preferred_phone_account_component_name";
+
+ /**
+ * The ID of a {@link
+ * android.telecom.PhoneAccountHandle} that is the preferred {@code PhoneAccountHandle} to
+ * call the contact with. Used by {@link CommonDataKinds.Phone}.
+ *
+ * @see PhoneAccountHandle#getId() ()
+ */
+ String PREFERRED_PHONE_ACCOUNT_ID = "preferred_phone_account_id";
}
/**
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index c4c6798..5505f597 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -3123,6 +3123,10 @@
* to dream after a period of inactivity. This value is also known as the
* user activity timeout period since the screen isn't necessarily turned off
* when it expires.
+ *
+ * <p>
+ * This value is bounded by maximum timeout set by
+ * {@link android.app.admin.DevicePolicyManager#setMaximumTimeToLock(ComponentName, long)}.
*/
public static final String SCREEN_OFF_TIMEOUT = "screen_off_timeout";
@@ -5333,6 +5337,42 @@
public static final String AUTOFILL_FEATURE_FIELD_DETECTION = "autofill_field_detection";
/**
+ * Experimental autofill feature.
+ *
+ * <p>TODO(b/67867469): document (or remove) once feature is finished
+ * @hide
+ */
+ public static final String AUTOFILL_USER_DATA_MAX_USER_DATA_SIZE =
+ "autofill_user_data_max_user_data_size";
+
+ /**
+ * Experimental autofill feature.
+ *
+ * <p>TODO(b/67867469): document (or remove) once feature is finished
+ * @hide
+ */
+ public static final String AUTOFILL_USER_DATA_MAX_FIELD_CLASSIFICATION_IDS_SIZE =
+ "autofill_user_data_max_field_classification_size";
+
+ /**
+ * Experimental autofill feature.
+ *
+ * <p>TODO(b/67867469): document (or remove) once feature is finished
+ * @hide
+ */
+ public static final String AUTOFILL_USER_DATA_MAX_VALUE_LENGTH =
+ "autofill_user_data_max_value_length";
+
+ /**
+ * Experimental autofill feature.
+ *
+ * <p>TODO(b/67867469): document (or remove) once feature is finished
+ * @hide
+ */
+ public static final String AUTOFILL_USER_DATA_MIN_VALUE_LENGTH =
+ "autofill_user_data_min_value_length";
+
+ /**
* @deprecated Use {@link android.provider.Settings.Global#DEVICE_PROVISIONED} instead
*/
@Deprecated
diff --git a/core/java/android/provider/VoicemailContract.java b/core/java/android/provider/VoicemailContract.java
index 864a0fd..6be0e76 100644
--- a/core/java/android/provider/VoicemailContract.java
+++ b/core/java/android/provider/VoicemailContract.java
@@ -16,7 +16,6 @@
package android.provider;
-import android.Manifest;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.content.ComponentName;
@@ -50,7 +49,7 @@
* </ul>
*
* <P> The minimum permission needed to access this content provider is
- * {@link Manifest.permission#ADD_VOICEMAIL}
+ * {@link android.Manifest.permission#ADD_VOICEMAIL}
*
* <P>Voicemails are inserted by what is called as a "voicemail source"
* application, which is responsible for syncing voicemail data between a remote
@@ -293,11 +292,26 @@
* Flag used to indicate that local, unsynced changes are present.
* Currently, this is used to indicate that the voicemail was read or deleted.
* The value will be 1 if dirty is true, 0 if false.
+ *
+ * <p>When a caller updates a voicemail row (either with {@link ContentResolver#update} or
+ * {@link ContentResolver#applyBatch}), and if the {@link ContentValues} doesn't contain
+ * this column, the voicemail provider implicitly sets it to 0 if the calling package is
+ * the {@link #SOURCE_PACKAGE} or to 1 otherwise. To prevent this behavior, explicitly set
+ * {@link #DIRTY_RETAIN} to this column in the {@link ContentValues}.
+ *
* <P>Type: INTEGER (boolean)</P>
+ *
+ * @see #DIRTY_RETAIN
*/
public static final String DIRTY = "dirty";
/**
+ * Value of {@link #DIRTY} when updating to indicate that the value should not be updated
+ * during this operation.
+ */
+ public static final int DIRTY_RETAIN = -1;
+
+ /**
* Flag used to indicate that the voicemail was deleted but not synced to the server.
* A deleted row should be ignored.
* The value will be 1 if deleted is true, 0 if false.
diff --git a/core/java/android/service/autofill/AutofillService.java b/core/java/android/service/autofill/AutofillService.java
index cd362c7..1afa8b3 100644
--- a/core/java/android/service/autofill/AutofillService.java
+++ b/core/java/android/service/autofill/AutofillService.java
@@ -440,7 +440,6 @@
* save(username, password);
* </pre>
*
- *
* <a name="Privacy"></a>
* <h3>Privacy</h3>
*
@@ -453,6 +452,13 @@
* <p>Because this data could contain PII (Personally Identifiable Information, such as username or
* email address), the service should only use it locally (i.e., in the app's process) for
* heuristics purposes, but it should not be sent to external servers.
+ *
+ * <a name="FieldsClassification"></a>
+ * <h3>Metrics and fields classification</h3
+ *
+ * <p>TODO(b/67867469): document it or remove this section; in particular, document the relationship
+ * between set/getUserData(), FillResponse.setFieldClassificationIds(), and
+ * FillEventHistory.getFieldsClassification.
*/
public abstract class AutofillService extends Service {
private static final String TAG = "AutofillService";
diff --git a/core/java/android/service/autofill/FieldsDetection.java b/core/java/android/service/autofill/FieldsDetection.java
deleted file mode 100644
index 550ecf6..0000000
--- a/core/java/android/service/autofill/FieldsDetection.java
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * Copyright 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.service.autofill;
-
-import android.annotation.TestApi;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.view.autofill.AutofillId;
-
-/**
- * Class by service to improve autofillable fields detection by tracking the meaning of fields
- * manually edited by the user (when they match values provided by the service).
- *
- * TODO(b/67867469):
- * - proper javadoc
- * - unhide / remove testApi
- * - add FieldsDetection management so service can set it just once and reference it in further
- * calls to improve performance (and also API to refresh it)
- * - rename to FieldsDetectionInfo or FieldClassification? (same for CTS tests)
- * - add FieldsDetectionUnitTest once API is well-defined
- * @hide
- */
-@TestApi
-public final class FieldsDetection implements Parcelable {
-
- private final AutofillId mFieldId;
- private final String mRemoteId;
- private final String mValue;
-
- /**
- * Creates a field detection for just one field / value pair.
- *
- * @param fieldId autofill id of the field in the screen.
- * @param remoteId id used by the service to identify the field later.
- * @param value field value known to the service.
- *
- * TODO(b/67867469):
- * - proper javadoc
- * - change signature to allow more fields / values / match methods
- * - might also need to use a builder, where the constructor is the id for the fieldsdetector
- * - might need id for values as well
- * - add @NonNull / check it / add unit tests
- * - make 'value' input more generic so it can accept distance-based match and other matches
- * - throw exception if field value is less than X characters (somewhere between 7-10)
- * - make sure to limit total number of fields to around 10 or so
- * - use AutofillValue instead of String (so it can compare dates, for example)
- */
- public FieldsDetection(AutofillId fieldId, String remoteId, String value) {
- mFieldId = fieldId;
- mRemoteId = remoteId;
- mValue = value;
- }
-
- /** @hide */
- public AutofillId getFieldId() {
- return mFieldId;
- }
-
- /** @hide */
- public String getRemoteId() {
- return mRemoteId;
- }
-
- /** @hide */
- public String getValue() {
- return mValue;
- }
-
- /////////////////////////////////////
- // Object "contract" methods. //
- /////////////////////////////////////
- @Override
- public String toString() {
- // Cannot disclose remoteId or value because they could contain PII
- return new StringBuilder("FieldsDetection: [field=").append(mFieldId)
- .append(", remoteId_length=").append(mRemoteId.length())
- .append(", value_length=").append(mValue.length())
- .append("]").toString();
- }
-
- /////////////////////////////////////
- // Parcelable "contract" methods. //
- /////////////////////////////////////
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel parcel, int flags) {
- parcel.writeParcelable(mFieldId, flags);
- parcel.writeString(mRemoteId);
- parcel.writeString(mValue);
- }
-
- public static final Parcelable.Creator<FieldsDetection> CREATOR =
- new Parcelable.Creator<FieldsDetection>() {
- @Override
- public FieldsDetection createFromParcel(Parcel parcel) {
- // TODO(b/67867469): remove comment below if it does not use a builder at the end
- // Always go through the builder to ensure the data ingested by
- // the system obeys the contract of the builder to avoid attacks
- // using specially crafted parcels.
- return new FieldsDetection(parcel.readParcelable(null), parcel.readString(),
- parcel.readString());
- }
-
- @Override
- public FieldsDetection[] newArray(int size) {
- return new FieldsDetection[size];
- }
- };
-}
diff --git a/core/java/android/service/autofill/FillEventHistory.java b/core/java/android/service/autofill/FillEventHistory.java
index 736d9ef..eedb972 100644
--- a/core/java/android/service/autofill/FillEventHistory.java
+++ b/core/java/android/service/autofill/FillEventHistory.java
@@ -58,11 +58,6 @@
*/
public final class FillEventHistory implements Parcelable {
/**
- * Not in parcel. The UID of the {@link AutofillService} that created the {@link FillResponse}.
- */
- private final int mServiceUid;
-
- /**
* Not in parcel. The ID of the autofill session that created the {@link FillResponse}.
*/
private final int mSessionId;
@@ -70,17 +65,6 @@
@Nullable private final Bundle mClientState;
@Nullable List<Event> mEvents;
- /**
- * Gets the UID of the {@link AutofillService} that created the {@link FillResponse}.
- *
- * @return The UID of the {@link AutofillService}
- *
- * @hide
- */
- public int getServiceUid() {
- return mServiceUid;
- }
-
/** @hide */
public int getSessionId() {
return mSessionId;
@@ -123,9 +107,8 @@
/**
* @hide
*/
- public FillEventHistory(int serviceUid, int sessionId, @Nullable Bundle clientState) {
+ public FillEventHistory(int sessionId, @Nullable Bundle clientState) {
mClientState = clientState;
- mServiceUid = serviceUid;
mSessionId = sessionId;
}
@@ -364,16 +347,17 @@
}
/**
- * Gets the results of the last {@link FieldsDetection} request.
+ * Gets the results of the last fields classification request.
*
* @return map of edit-distance match ({@code 0} means full match,
- * {@code 1} means 1 character different, etc...) by remote id (as set in the
- * {@link FieldsDetection} constructor), or {@code null} if none of the user-input values
+ * {@code 1} means 1 character different, etc...) by remote id (as set on
+ * {@link UserData.Builder#add(String, android.view.autofill.AutofillValue)}),
+ * or {@code null} if none of the user-input values
* matched the requested detection.
*
* <p><b>Note: </b>Only set on events of type {@link #TYPE_CONTEXT_COMMITTED}, when the
- * service requested {@link FillResponse.Builder#setFieldsDetection(FieldsDetection) fields
- * detection}.
+ * service requested {@link FillResponse.Builder#setFieldClassificationIds(AutofillId...)
+ * fields detection}.
*
* TODO(b/67867469):
* - improve javadoc
@@ -382,11 +366,12 @@
* - unhide
* - unhide / remove testApi
* - add @NonNull / check it / add unit tests
+ * - add link to AutofillService #FieldsClassification anchor
*
* @hide
*/
@TestApi
- @NonNull public Map<String, Integer> getDetectedFields() {
+ @NonNull public Map<String, Integer> getFieldsClassification() {
if (mDetectedRemoteId == null || mDetectedFieldScore == -1) {
return Collections.emptyMap();
}
@@ -534,7 +519,7 @@
new Parcelable.Creator<FillEventHistory>() {
@Override
public FillEventHistory createFromParcel(Parcel parcel) {
- FillEventHistory selection = new FillEventHistory(0, 0, parcel.readBundle());
+ FillEventHistory selection = new FillEventHistory(0, parcel.readBundle());
final int numEvents = parcel.readInt();
for (int i = 0; i < numEvents; i++) {
diff --git a/core/java/android/service/autofill/FillResponse.java b/core/java/android/service/autofill/FillResponse.java
index 84a0974..06d2b07 100644
--- a/core/java/android/service/autofill/FillResponse.java
+++ b/core/java/android/service/autofill/FillResponse.java
@@ -72,11 +72,13 @@
private final @Nullable SaveInfo mSaveInfo;
private final @Nullable Bundle mClientState;
private final @Nullable RemoteViews mPresentation;
+ private final @Nullable RemoteViews mHeader;
+ private final @Nullable RemoteViews mFooter;
private final @Nullable IntentSender mAuthentication;
private final @Nullable AutofillId[] mAuthenticationIds;
private final @Nullable AutofillId[] mIgnoredIds;
private final long mDisableDuration;
- private final @Nullable FieldsDetection mFieldsDetection;
+ private final @Nullable AutofillId[] mFieldClassificationIds;
private final int mFlags;
private int mRequestId;
@@ -85,11 +87,13 @@
mSaveInfo = builder.mSaveInfo;
mClientState = builder.mClientState;
mPresentation = builder.mPresentation;
+ mHeader = builder.mHeader;
+ mFooter = builder.mFooter;
mAuthentication = builder.mAuthentication;
mAuthenticationIds = builder.mAuthenticationIds;
mIgnoredIds = builder.mIgnoredIds;
mDisableDuration = builder.mDisableDuration;
- mFieldsDetection = builder.mFieldsDetection;
+ mFieldClassificationIds = builder.mFieldClassificationIds;
mFlags = builder.mFlags;
mRequestId = INVALID_REQUEST_ID;
}
@@ -115,6 +119,16 @@
}
/** @hide */
+ public @Nullable RemoteViews getHeader() {
+ return mHeader;
+ }
+
+ /** @hide */
+ public @Nullable RemoteViews getFooter() {
+ return mFooter;
+ }
+
+ /** @hide */
public @Nullable IntentSender getAuthentication() {
return mAuthentication;
}
@@ -135,8 +149,8 @@
}
/** @hide */
- public @Nullable FieldsDetection getFieldsDetection() {
- return mFieldsDetection;
+ public @Nullable AutofillId[] getFieldClassificationIds() {
+ return mFieldClassificationIds;
}
/** @hide */
@@ -171,11 +185,13 @@
private SaveInfo mSaveInfo;
private Bundle mClientState;
private RemoteViews mPresentation;
+ private RemoteViews mHeader;
+ private RemoteViews mFooter;
private IntentSender mAuthentication;
private AutofillId[] mAuthenticationIds;
private AutofillId[] mIgnoredIds;
private long mDisableDuration;
- private FieldsDetection mFieldsDetection;
+ private AutofillId[] mFieldClassificationIds;
private int mFlags;
private boolean mDestroyed;
@@ -226,16 +242,24 @@
* @param ids id of Views that when focused will display the authentication UI.
*
* @return This builder.
+
* @throws IllegalArgumentException if {@code ids} is {@code null} or empty, or if
* both {@code authentication} and {@code presentation} are {@code null}, or if
* both {@code authentication} and {@code presentation} are non-{@code null}
*
+ * @throws IllegalStateException if a {@link #setHeader(RemoteViews) header} or a
+ * {@link #setFooter(RemoteViews) footer} are already set for this builder.
+ *
* @see android.app.PendingIntent#getIntentSender()
*/
public @NonNull Builder setAuthentication(@NonNull AutofillId[] ids,
@Nullable IntentSender authentication, @Nullable RemoteViews presentation) {
throwIfDestroyed();
throwIfDisableAutofillCalled();
+ if (mHeader != null || mFooter != null) {
+ throw new IllegalStateException("Already called #setHeader() or #setFooter()");
+ }
+
if (ids == null || ids.length == 0) {
throw new IllegalArgumentException("ids cannot be null or empry");
}
@@ -329,21 +353,29 @@
}
/**
+ * Sets which fields are used for <a href="#FieldsClassification">fields classification</a>
+ *
+ * @throws IllegalArgumentException is length of {@code ids} args is more than
+ * {@link UserData#getMaxFieldClassificationIdsSize()}.
+ * @throws IllegalStateException if {@link #build()} or {@link #disableAutofill(long)} was
+ * already called.
+ * @throws NullPointerException if {@code ids} or any element on it is {@code null}.
+ *
* TODO(b/67867469):
- * - javadoc it
- * - javadoc how to check results
- * - unhide
+ * - improve javadoc: explain relationship with UserData and how to check results
* - unhide / remove testApi
- * - throw exception (and document) if response has datasets or saveinfo
- * - throw exception (and document) if id on fieldsDetection is ignored
+ * - implement multiple ids
*
* @hide
*/
@TestApi
- public Builder setFieldsDetection(@NonNull FieldsDetection fieldsDetection) {
+ public Builder setFieldClassificationIds(@NonNull AutofillId... ids) {
throwIfDestroyed();
throwIfDisableAutofillCalled();
- mFieldsDetection = Preconditions.checkNotNull(fieldsDetection);
+ Preconditions.checkArrayElementsNotNull(ids, "ids");
+ Preconditions.checkArgumentInRange(ids.length, 1,
+ UserData.getMaxFieldClassificationIdsSize(), "ids length");
+ mFieldClassificationIds = ids;
return this;
}
@@ -391,16 +423,17 @@
* @throws IllegalArgumentException if {@code duration} is not a positive number.
* @throws IllegalStateException if either {@link #addDataset(Dataset)},
* {@link #setAuthentication(AutofillId[], IntentSender, RemoteViews)},
- * {@link #setSaveInfo(SaveInfo)}, or {@link #setClientState(Bundle)}
- * was already called.
+ * {@link #setSaveInfo(SaveInfo)}, {@link #setClientState(Bundle)}, or
+ * {link #setFieldClassificationIds(AutofillId...)} was already called.
*/
+ // TODO(b/67867469): add @ to {link setFieldClassificationIds} once it's public
public Builder disableAutofill(long duration) {
throwIfDestroyed();
if (duration <= 0) {
throw new IllegalArgumentException("duration must be greater than 0");
}
if (mAuthentication != null || mDatasets != null || mSaveInfo != null
- || mFieldsDetection != null || mClientState != null) {
+ || mFieldClassificationIds != null || mClientState != null) {
throw new IllegalStateException("disableAutofill() must be the only method called");
}
@@ -409,6 +442,62 @@
}
/**
+ * Sets a header to be shown as the first element in the list of datasets.
+ *
+ * <p>When this method is called, you must also {@link #addDataset(Dataset) add a dataset},
+ * otherwise {@link #build()} throws an {@link IllegalStateException}. Similarly, this
+ * method should only be used on {@link FillResponse FillResponses} that do not require
+ * authentication (as the header could have been set directly in the main presentation in
+ * these cases).
+ *
+ * @param header a presentation to represent the header. This presentation is not clickable
+ * —calling
+ * {@link RemoteViews#setOnClickPendingIntent(int, android.app.PendingIntent)} on it would
+ * have no effect.
+ *
+ * @return this builder
+ *
+ * @throws IllegalStateException if an
+ * {@link #setAuthentication(AutofillId[], IntentSender, RemoteViews) authentication} was
+ * already set for this builder.
+ */
+ // TODO(b/69796626): make it sticky / update javadoc
+ public Builder setHeader(@NonNull RemoteViews header) {
+ throwIfDestroyed();
+ throwIfAuthenticationCalled();
+ mHeader = Preconditions.checkNotNull(header);
+ return this;
+ }
+
+ /**
+ * Sets a footer to be shown as the last element in the list of datasets.
+ *
+ * <p>When this method is called, you must also {@link #addDataset(Dataset) add a dataset},
+ * otherwise {@link #build()} throws an {@link IllegalStateException}. Similarly, this
+ * method should only be used on {@link FillResponse FillResponses} that do not require
+ * authentication (as the footer could have been set directly in the main presentation in
+ * these cases).
+ *
+ * @param footer a presentation to represent the footer. This presentation is not clickable
+ * —calling
+ * {@link RemoteViews#setOnClickPendingIntent(int, android.app.PendingIntent)} on it would
+ * have no effect.
+ *
+ * @return this builder
+ *
+ * @throws IllegalStateException if the FillResponse
+ * {@link #setAuthentication(AutofillId[], IntentSender, RemoteViews)
+ * requires authentication}.
+ */
+ // TODO(b/69796626): make it sticky / update javadoc
+ public Builder setFooter(@NonNull RemoteViews footer) {
+ throwIfDestroyed();
+ throwIfAuthenticationCalled();
+ mFooter = Preconditions.checkNotNull(footer);
+ return this;
+ }
+
+ /**
* Builds a new {@link FillResponse} instance.
*
* @throws IllegalStateException if any of the following conditions occur:
@@ -417,19 +506,28 @@
* <li>No call was made to {@link #addDataset(Dataset)},
* {@link #setAuthentication(AutofillId[], IntentSender, RemoteViews)},
* {@link #setSaveInfo(SaveInfo)}, {@link #disableAutofill(long)},
- * or {@link #setClientState(Bundle)}.
+ * {@link #setClientState(Bundle)},
+ * or {link #setFieldClassificationIds(AutofillId...)}.
+ * <li>{@link #setHeader(RemoteViews)} or {@link #setFooter(RemoteViews)} is called
+ * without any previous calls to {@link #addDataset(Dataset)}.
* </ol>
*
* @return A built response.
*/
+ // TODO(b/67867469): add @ to {link setFieldClassificationIds} once it's public
public FillResponse build() {
throwIfDestroyed();
if (mAuthentication == null && mDatasets == null && mSaveInfo == null
- && mDisableDuration == 0 && mFieldsDetection == null && mClientState == null) {
+ && mDisableDuration == 0 && mFieldClassificationIds == null
+ && mClientState == null) {
throw new IllegalStateException("need to provide: at least one DataSet, or a "
+ "SaveInfo, or an authentication with a presentation, "
+ "or a FieldsDetection, or a client state, or disable autofill");
}
+ if (mDatasets == null && (mHeader != null || mFooter != null)) {
+ throw new IllegalStateException(
+ "must add at least 1 dataset when using header or footer");
+ }
mDestroyed = true;
return new FillResponse(this);
}
@@ -445,6 +543,12 @@
throw new IllegalStateException("Already called #disableAutofill()");
}
}
+
+ private void throwIfAuthenticationCalled() {
+ if (mAuthentication != null) {
+ throw new IllegalStateException("Already called #setAuthentication()");
+ }
+ }
}
/////////////////////////////////////
@@ -461,12 +565,15 @@
.append(", saveInfo=").append(mSaveInfo)
.append(", clientState=").append(mClientState != null)
.append(", hasPresentation=").append(mPresentation != null)
+ .append(", hasHeader=").append(mHeader != null)
+ .append(", hasFooter=").append(mFooter != null)
.append(", hasAuthentication=").append(mAuthentication != null)
.append(", authenticationIds=").append(Arrays.toString(mAuthenticationIds))
.append(", ignoredIds=").append(Arrays.toString(mIgnoredIds))
.append(", disableDuration=").append(mDisableDuration)
.append(", flags=").append(mFlags)
- .append(", fieldDetection=").append(mFieldsDetection)
+ .append(", fieldClassificationIds=")
+ .append(Arrays.toString(mFieldClassificationIds))
.append("]")
.toString();
}
@@ -488,9 +595,11 @@
parcel.writeParcelableArray(mAuthenticationIds, flags);
parcel.writeParcelable(mAuthentication, flags);
parcel.writeParcelable(mPresentation, flags);
+ parcel.writeParcelable(mHeader, flags);
+ parcel.writeParcelable(mFooter, flags);
parcel.writeParcelableArray(mIgnoredIds, flags);
parcel.writeLong(mDisableDuration);
- parcel.writeParcelable(mFieldsDetection, flags);
+ parcel.writeParcelableArray(mFieldClassificationIds, flags);
parcel.writeInt(mFlags);
parcel.writeInt(mRequestId);
}
@@ -520,15 +629,24 @@
if (authenticationIds != null) {
builder.setAuthentication(authenticationIds, authentication, presentation);
}
+ final RemoteViews header = parcel.readParcelable(null);
+ if (header != null) {
+ builder.setHeader(header);
+ }
+ final RemoteViews footer = parcel.readParcelable(null);
+ if (footer != null) {
+ builder.setFooter(footer);
+ }
builder.setIgnoredIds(parcel.readParcelableArray(null, AutofillId.class));
final long disableDuration = parcel.readLong();
if (disableDuration > 0) {
builder.disableAutofill(disableDuration);
}
- final FieldsDetection fieldsDetection = parcel.readParcelable(null);
- if (fieldsDetection != null) {
- builder.setFieldsDetection(fieldsDetection);
+ final AutofillId[] fieldClassifactionIds =
+ parcel.readParcelableArray(null, AutofillId.class);
+ if (fieldClassifactionIds != null) {
+ builder.setFieldClassificationIds(fieldClassifactionIds);
}
builder.setFlags(parcel.readInt());
diff --git a/core/java/android/service/autofill/IAuthenticationCallback.aidl b/core/java/android/service/autofill/IAuthenticationCallback.aidl
deleted file mode 100644
index 36b989d..0000000
--- a/core/java/android/service/autofill/IAuthenticationCallback.aidl
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.view.autofill;
-
-import android.view.autofill.Dataset;
-import android.service.autofill.FillResponse;
-
-/**
- * Callback for delivering authentication result.
- *
- * {@hide}
- */
-interface IAutoFillAuthCallback {
- void onSuccessForDataset(in Dataset dataset);
- void onSuccessForFillResponse(in FillResponse response);
- void onFailure(CharSequence message);
-}
diff --git a/telephony/java/com/android/internal/telephony/ISubscriptionListener.aidl b/core/java/android/service/autofill/UserData.aidl
similarity index 64%
rename from telephony/java/com/android/internal/telephony/ISubscriptionListener.aidl
rename to core/java/android/service/autofill/UserData.aidl
index 4ccdea5..76016de 100644
--- a/telephony/java/com/android/internal/telephony/ISubscriptionListener.aidl
+++ b/core/java/android/service/autofill/UserData.aidl
@@ -1,11 +1,11 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
+/**
+ * Copyright (c) 2017, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -14,11 +14,7 @@
* limitations under the License.
*/
-package com.android.internal.telephony;
+package android.service.autofill;
-import android.telephony.SubscriptionInfo;
-
-oneway interface ISubscriptionListener {
- void onSubscriptionInfoChanged();
-}
-
+parcelable UserData;
+parcelable UserData.Constraints;
diff --git a/core/java/android/service/autofill/UserData.java b/core/java/android/service/autofill/UserData.java
new file mode 100644
index 0000000..16d8d4a
--- /dev/null
+++ b/core/java/android/service/autofill/UserData.java
@@ -0,0 +1,288 @@
+/*
+ * 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.service.autofill;
+
+import static android.provider.Settings.Secure.AUTOFILL_USER_DATA_MAX_FIELD_CLASSIFICATION_IDS_SIZE;
+import static android.provider.Settings.Secure.AUTOFILL_USER_DATA_MAX_USER_DATA_SIZE;
+import static android.provider.Settings.Secure.AUTOFILL_USER_DATA_MAX_VALUE_LENGTH;
+import static android.provider.Settings.Secure.AUTOFILL_USER_DATA_MIN_VALUE_LENGTH;
+import static android.view.autofill.Helper.sDebug;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.TestApi;
+import android.app.ActivityThread;
+import android.content.ContentResolver;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.provider.Settings;
+import android.util.ArraySet;
+import android.util.Log;
+import android.view.autofill.Helper;
+
+import com.android.internal.util.Preconditions;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+
+/**
+ * Class used by service to improve autofillable fields detection by tracking the meaning of fields
+ * manually edited by the user (when they match values provided by the service).
+ *
+ * TODO(b/67867469):
+ * - improve javadoc / add link to section on AutofillService
+ * - unhide / remove testApi
+ * @hide
+ */
+@TestApi
+public final class UserData implements Parcelable {
+
+ private static final String TAG = "UserData";
+
+ private static final int DEFAULT_MAX_USER_DATA_SIZE = 10;
+ private static final int DEFAULT_MAX_FIELD_CLASSIFICATION_IDS_SIZE = 10;
+ private static final int DEFAULT_MIN_VALUE_LENGTH = 5;
+ private static final int DEFAULT_MAX_VALUE_LENGTH = 100;
+
+ private final String[] mRemoteIds;
+ private final String[] mValues;
+
+ private UserData(Builder builder) {
+ mRemoteIds = new String[builder.mRemoteIds.size()];
+ builder.mRemoteIds.toArray(mRemoteIds);
+ mValues = new String[builder.mValues.size()];
+ builder.mValues.toArray(mValues);
+ }
+
+ /** @hide */
+ public String[] getRemoteIds() {
+ return mRemoteIds;
+ }
+
+ /** @hide */
+ public String[] getValues() {
+ return mValues;
+ }
+
+ /** @hide */
+ public void dump(String prefix, PrintWriter pw) {
+ // Cannot disclose remote ids because they could contain PII
+ pw.print(prefix); pw.print("Remote ids size: "); pw.println(mRemoteIds.length);
+ for (int i = 0; i < mValues.length; i++) {
+ pw.print(prefix); pw.print(prefix); pw.print(i); pw.print(": "); pw.println(mValues[i]);
+ }
+ }
+
+ /** @hide */
+ public static void dumpConstraints(String prefix, PrintWriter pw) {
+ pw.print(prefix); pw.print("maxUserDataSize: "); pw.println(getMaxUserDataSize());
+ pw.print(prefix); pw.print("maxFieldClassificationIdsSize: ");
+ pw.println(getMaxFieldClassificationIdsSize());
+ pw.print(prefix); pw.print("minValueLength: "); pw.println(getMinValueLength());
+ pw.print(prefix); pw.print("maxValueLength: "); pw.println(getMaxValueLength());
+ }
+
+ /**
+ * A builder for {@link UserData} objects.
+ *
+ * TODO(b/67867469): unhide / remove testApi
+ *
+ * @hide
+ */
+ @TestApi
+ public static final class Builder {
+ private final ArraySet<String> mRemoteIds;
+ private final ArrayList<String> mValues;
+ private boolean mDestroyed;
+
+ /**
+ * Creates a new builder for the user data used for <a href="#FieldsClassification">fields
+ * classification</a>.
+ *
+ * @throws IllegalArgumentException if {@code remoteId} or {@code value} are empty or if the
+ * length of {@code value} is lower than {@link UserData#getMinValueLength()}
+ * or higher than {@link UserData#getMaxValueLength()}.
+ */
+ public Builder(@NonNull String remoteId, @NonNull String value) {
+ checkValidRemoteId(remoteId);
+ checkValidValue(value);
+ final int capacity = getMaxUserDataSize();
+ mRemoteIds = new ArraySet<>(capacity);
+ mValues = new ArrayList<>(capacity);
+ mRemoteIds.add(remoteId);
+ mValues.add(value);
+ }
+
+ /**
+ * Adds a new value for user data.
+ *
+ * @param remoteId unique string used to identify the user data.
+ * @param value value of the user data.
+ *
+ * @throws IllegalStateException if {@link #build()} or
+ * {@link #add(String, String)} with the same {@code remoteId} has already
+ * been called, or if the number of values add (i.e., calls made to this method plus
+ * constructor) is more than {@link UserData#getMaxUserDataSize()}.
+ *
+ * @throws IllegalArgumentException if {@code remoteId} or {@code value} are empty or if the
+ * length of {@code value} is lower than {@link UserData#getMinValueLength()}
+ * or higher than {@link UserData#getMaxValueLength()}.
+ */
+ public Builder add(@NonNull String remoteId, @NonNull String value) {
+ throwIfDestroyed();
+ checkValidRemoteId(remoteId);
+ checkValidValue(value);
+
+ Preconditions.checkState(!mRemoteIds.contains(remoteId),
+ // Don't include remoteId on message because it could contain PII
+ "already has entry with same remoteId");
+ Preconditions.checkState(mRemoteIds.size() < getMaxUserDataSize(),
+ "already added " + mRemoteIds.size() + " elements");
+ mRemoteIds.add(remoteId);
+ mValues.add(value);
+ return this;
+ }
+
+ private void checkValidRemoteId(@Nullable String remoteId) {
+ Preconditions.checkNotNull(remoteId);
+ Preconditions.checkArgument(!remoteId.isEmpty(), "remoteId cannot be empty");
+ }
+
+ private void checkValidValue(@Nullable String value) {
+ Preconditions.checkNotNull(value);
+ final int length = value.length();
+ Preconditions.checkArgumentInRange(length, getMinValueLength(),
+ getMaxValueLength(), "value length (" + length + ")");
+ }
+
+ /**
+ * Creates a new {@link UserData} instance.
+ *
+ * <p>You should not interact with this builder once this method is called.
+ *
+ * @throws IllegalStateException if {@link #build()} was already called.
+ *
+ * @return The built dataset.
+ */
+ public UserData build() {
+ throwIfDestroyed();
+ mDestroyed = true;
+ return new UserData(this);
+ }
+
+ private void throwIfDestroyed() {
+ if (mDestroyed) {
+ throw new IllegalStateException("Already called #build()");
+ }
+ }
+ }
+
+ /////////////////////////////////////
+ // Object "contract" methods. //
+ /////////////////////////////////////
+ @Override
+ public String toString() {
+ if (!sDebug) return super.toString();
+
+ // Cannot disclose keys or values because they could contain PII
+ final StringBuilder builder = new StringBuilder("UserData: [remoteIds=");
+ Helper.appendRedacted(builder, mRemoteIds);
+ builder.append(", values=");
+ Helper.appendRedacted(builder, mValues);
+ return builder.append("]").toString();
+ }
+
+ /////////////////////////////////////
+ // Parcelable "contract" methods. //
+ /////////////////////////////////////
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel parcel, int flags) {
+ parcel.writeStringArray(mRemoteIds);
+ parcel.writeStringArray(mValues);
+ }
+
+ public static final Parcelable.Creator<UserData> CREATOR =
+ new Parcelable.Creator<UserData>() {
+ @Override
+ public UserData createFromParcel(Parcel parcel) {
+ // Always go through the builder to ensure the data ingested by
+ // the system obeys the contract of the builder to avoid attacks
+ // using specially crafted parcels.
+ final String[] remoteIds = parcel.readStringArray();
+ final String[] values = parcel.readStringArray();
+ final Builder builder = new Builder(remoteIds[0], values[0]);
+ for (int i = 1; i < remoteIds.length; i++) {
+ builder.add(remoteIds[i], values[i]);
+ }
+ return builder.build();
+ }
+
+ @Override
+ public UserData[] newArray(int size) {
+ return new UserData[size];
+ }
+ };
+
+ /**
+ * Gets the maximum number of values that can be added to a {@link UserData}.
+ */
+ public static int getMaxUserDataSize() {
+ return getInt(AUTOFILL_USER_DATA_MAX_USER_DATA_SIZE, DEFAULT_MAX_USER_DATA_SIZE);
+ }
+
+ /**
+ * Gets the maximum number of ids that can be passed to {@link
+ * FillResponse.Builder#setFieldClassificationIds(android.view.autofill.AutofillId...)}.
+ */
+ public static int getMaxFieldClassificationIdsSize() {
+ return getInt(AUTOFILL_USER_DATA_MAX_FIELD_CLASSIFICATION_IDS_SIZE,
+ DEFAULT_MAX_FIELD_CLASSIFICATION_IDS_SIZE);
+ }
+
+ /**
+ * Gets the minimum length of values passed to {@link Builder#Builder(String, String)}.
+ */
+ public static int getMinValueLength() {
+ return getInt(AUTOFILL_USER_DATA_MIN_VALUE_LENGTH, DEFAULT_MIN_VALUE_LENGTH);
+ }
+
+ /**
+ * Gets the maximum length of values passed to {@link Builder#Builder(String, String)}.
+ */
+ public static int getMaxValueLength() {
+ return getInt(AUTOFILL_USER_DATA_MAX_VALUE_LENGTH, DEFAULT_MAX_VALUE_LENGTH);
+ }
+
+ private static int getInt(String settings, int defaultValue) {
+ ContentResolver cr = null;
+ final ActivityThread at = ActivityThread.currentActivityThread();
+ if (at != null) {
+ cr = at.getApplication().getContentResolver();
+ }
+
+ if (cr == null) {
+ Log.w(TAG, "Could not read from " + settings + "; hardcoding " + defaultValue);
+ return defaultValue;
+ }
+ return Settings.Secure.getInt(cr, settings, defaultValue);
+ }
+}
diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
index 735b822..1ec2406 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -890,7 +890,17 @@
}
public static boolean isValidScheduleConditionId(Uri conditionId) {
- return tryParseScheduleConditionId(conditionId) != null;
+ ScheduleInfo info;
+ try {
+ info = tryParseScheduleConditionId(conditionId);
+ } catch (NullPointerException | ArrayIndexOutOfBoundsException e) {
+ return false;
+ }
+
+ if (info == null || info.days == null || info.days.length == 0) {
+ return false;
+ }
+ return true;
}
public static ScheduleInfo tryParseScheduleConditionId(Uri conditionId) {
diff --git a/core/java/android/text/AutoGrowArray.java b/core/java/android/text/AutoGrowArray.java
deleted file mode 100644
index e428377..0000000
--- a/core/java/android/text/AutoGrowArray.java
+++ /dev/null
@@ -1,374 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.text;
-
-import android.annotation.IntRange;
-import android.annotation.NonNull;
-
-import com.android.internal.util.ArrayUtils;
-
-import libcore.util.EmptyArray;
-
-/**
- * Implements a growing array of int primitives.
- *
- * These arrays are NOT thread safe.
- *
- * @hide
- */
-public final class AutoGrowArray {
- private static final int MIN_CAPACITY_INCREMENT = 12;
- private static final int MAX_CAPACITY_TO_BE_KEPT = 10000;
-
- /**
- * Returns next capacity size.
- *
- * The returned capacity is larger than requested capacity.
- */
- private static int computeNewCapacity(int currentSize, int requested) {
- final int targetCapacity = currentSize + (currentSize < (MIN_CAPACITY_INCREMENT / 2)
- ? MIN_CAPACITY_INCREMENT : currentSize >> 1);
- return targetCapacity > requested ? targetCapacity : requested;
- }
-
- /**
- * An auto growing byte array.
- */
- public static class ByteArray {
-
- private @NonNull byte[] mValues;
- private @IntRange(from = 0) int mSize;
-
- /**
- * Creates an empty ByteArray with the default initial capacity.
- */
- public ByteArray() {
- this(10);
- }
-
- /**
- * Creates an empty ByteArray with the specified initial capacity.
- */
- public ByteArray(@IntRange(from = 0) int initialCapacity) {
- if (initialCapacity == 0) {
- mValues = EmptyArray.BYTE;
- } else {
- mValues = ArrayUtils.newUnpaddedByteArray(initialCapacity);
- }
- mSize = 0;
- }
-
- /**
- * Changes the size of this ByteArray. If this ByteArray is shrinked, the backing array
- * capacity is unchanged.
- */
- public void resize(@IntRange(from = 0) int newSize) {
- if (newSize > mValues.length) {
- ensureCapacity(newSize - mSize);
- }
- mSize = newSize;
- }
-
- /**
- * Appends the specified value to the end of this array.
- */
- public void append(byte value) {
- ensureCapacity(1);
- mValues[mSize++] = value;
- }
-
- /**
- * Ensures capacity to append at least <code>count</code> values.
- */
- private void ensureCapacity(@IntRange int count) {
- final int requestedSize = mSize + count;
- if (requestedSize >= mValues.length) {
- final int newCapacity = computeNewCapacity(mSize, requestedSize);
- final byte[] newValues = ArrayUtils.newUnpaddedByteArray(newCapacity);
- System.arraycopy(mValues, 0, newValues, 0, mSize);
- mValues = newValues;
- }
- }
-
- /**
- * Removes all values from this array.
- */
- public void clear() {
- mSize = 0;
- }
-
- /**
- * Removes all values from this array and release the internal array object if it is too
- * large.
- */
- public void clearWithReleasingLargeArray() {
- clear();
- if (mValues.length > MAX_CAPACITY_TO_BE_KEPT) {
- mValues = EmptyArray.BYTE;
- }
- }
-
- /**
- * Returns the value at the specified position in this array.
- */
- public byte get(@IntRange(from = 0) int index) {
- return mValues[index];
- }
-
- /**
- * Sets the value at the specified position in this array.
- */
- public void set(@IntRange(from = 0) int index, byte value) {
- mValues[index] = value;
- }
-
- /**
- * Returns the number of values in this array.
- */
- public @IntRange(from = 0) int size() {
- return mSize;
- }
-
- /**
- * Returns internal raw array.
- *
- * Note that this array may have larger size than you requested.
- * Use size() instead for getting the actual array size.
- */
- public @NonNull byte[] getRawArray() {
- return mValues;
- }
- }
-
- /**
- * An auto growing int array.
- */
- public static class IntArray {
-
- private @NonNull int[] mValues;
- private @IntRange(from = 0) int mSize;
-
- /**
- * Creates an empty IntArray with the default initial capacity.
- */
- public IntArray() {
- this(10);
- }
-
- /**
- * Creates an empty IntArray with the specified initial capacity.
- */
- public IntArray(@IntRange(from = 0) int initialCapacity) {
- if (initialCapacity == 0) {
- mValues = EmptyArray.INT;
- } else {
- mValues = ArrayUtils.newUnpaddedIntArray(initialCapacity);
- }
- mSize = 0;
- }
-
- /**
- * Changes the size of this IntArray. If this IntArray is shrinked, the backing array
- * capacity is unchanged.
- */
- public void resize(@IntRange(from = 0) int newSize) {
- if (newSize > mValues.length) {
- ensureCapacity(newSize - mSize);
- }
- mSize = newSize;
- }
-
- /**
- * Appends the specified value to the end of this array.
- */
- public void append(int value) {
- ensureCapacity(1);
- mValues[mSize++] = value;
- }
-
- /**
- * Ensures capacity to append at least <code>count</code> values.
- */
- private void ensureCapacity(@IntRange(from = 0) int count) {
- final int requestedSize = mSize + count;
- if (requestedSize >= mValues.length) {
- final int newCapacity = computeNewCapacity(mSize, requestedSize);
- final int[] newValues = ArrayUtils.newUnpaddedIntArray(newCapacity);
- System.arraycopy(mValues, 0, newValues, 0, mSize);
- mValues = newValues;
- }
- }
-
- /**
- * Removes all values from this array.
- */
- public void clear() {
- mSize = 0;
- }
-
- /**
- * Removes all values from this array and release the internal array object if it is too
- * large.
- */
- public void clearWithReleasingLargeArray() {
- clear();
- if (mValues.length > MAX_CAPACITY_TO_BE_KEPT) {
- mValues = EmptyArray.INT;
- }
- }
-
- /**
- * Returns the value at the specified position in this array.
- */
- public int get(@IntRange(from = 0) int index) {
- return mValues[index];
- }
-
- /**
- * Sets the value at the specified position in this array.
- */
- public void set(@IntRange(from = 0) int index, int value) {
- mValues[index] = value;
- }
-
- /**
- * Returns the number of values in this array.
- */
- public @IntRange(from = 0) int size() {
- return mSize;
- }
-
- /**
- * Returns internal raw array.
- *
- * Note that this array may have larger size than you requested.
- * Use size() instead for getting the actual array size.
- */
- public @NonNull int[] getRawArray() {
- return mValues;
- }
- }
-
- /**
- * An auto growing float array.
- */
- public static class FloatArray {
-
- private @NonNull float[] mValues;
- private @IntRange(from = 0) int mSize;
-
- /**
- * Creates an empty FloatArray with the default initial capacity.
- */
- public FloatArray() {
- this(10);
- }
-
- /**
- * Creates an empty FloatArray with the specified initial capacity.
- */
- public FloatArray(@IntRange(from = 0) int initialCapacity) {
- if (initialCapacity == 0) {
- mValues = EmptyArray.FLOAT;
- } else {
- mValues = ArrayUtils.newUnpaddedFloatArray(initialCapacity);
- }
- mSize = 0;
- }
-
- /**
- * Changes the size of this FloatArray. If this FloatArray is shrinked, the backing array
- * capacity is unchanged.
- */
- public void resize(@IntRange(from = 0) int newSize) {
- if (newSize > mValues.length) {
- ensureCapacity(newSize - mSize);
- }
- mSize = newSize;
- }
-
- /**
- * Appends the specified value to the end of this array.
- */
- public void append(float value) {
- ensureCapacity(1);
- mValues[mSize++] = value;
- }
-
- /**
- * Ensures capacity to append at least <code>count</code> values.
- */
- private void ensureCapacity(int count) {
- final int requestedSize = mSize + count;
- if (requestedSize >= mValues.length) {
- final int newCapacity = computeNewCapacity(mSize, requestedSize);
- final float[] newValues = ArrayUtils.newUnpaddedFloatArray(newCapacity);
- System.arraycopy(mValues, 0, newValues, 0, mSize);
- mValues = newValues;
- }
- }
-
- /**
- * Removes all values from this array.
- */
- public void clear() {
- mSize = 0;
- }
-
- /**
- * Removes all values from this array and release the internal array object if it is too
- * large.
- */
- public void clearWithReleasingLargeArray() {
- clear();
- if (mValues.length > MAX_CAPACITY_TO_BE_KEPT) {
- mValues = EmptyArray.FLOAT;
- }
- }
-
- /**
- * Returns the value at the specified position in this array.
- */
- public float get(@IntRange(from = 0) int index) {
- return mValues[index];
- }
-
- /**
- * Sets the value at the specified position in this array.
- */
- public void set(@IntRange(from = 0) int index, float value) {
- mValues[index] = value;
- }
-
- /**
- * Returns the number of values in this array.
- */
- public @IntRange(from = 0) int size() {
- return mSize;
- }
-
- /**
- * Returns internal raw array.
- *
- * Note that this array may have larger size than you requested.
- * Use size() instead for getting the actual array size.
- */
- public @NonNull float[] getRawArray() {
- return mValues;
- }
- }
-}
diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java
index 2a693a1..4d2a962 100644
--- a/core/java/android/text/Layout.java
+++ b/core/java/android/text/Layout.java
@@ -1907,14 +1907,22 @@
private static float measurePara(TextPaint paint, CharSequence text, int start, int end,
TextDirectionHeuristic textDir) {
- MeasuredText mt = null;
+ MeasuredText mt = MeasuredText.obtain();
TextLine tl = TextLine.obtain();
try {
- mt = MeasuredText.buildForBidi(text, start, end, textDir, mt);
- final char[] chars = mt.getChars();
- final int len = chars.length;
- final Directions directions = mt.getDirections(0, len);
- final int dir = mt.getParagraphDir();
+ mt.setPara(text, start, end, textDir);
+ Directions directions;
+ int dir;
+ if (mt.mEasy) {
+ directions = DIRS_ALL_LEFT_TO_RIGHT;
+ dir = Layout.DIR_LEFT_TO_RIGHT;
+ } else {
+ directions = AndroidBidi.directions(mt.mDir, mt.mLevels,
+ 0, mt.mChars, 0, mt.mLen);
+ dir = mt.mDir;
+ }
+ char[] chars = mt.mChars;
+ int len = mt.mLen;
boolean hasTabs = false;
TabStops tabStops = null;
// leading margins should be taken into account when measuring a paragraph
@@ -1947,9 +1955,7 @@
return margin + Math.abs(tl.metrics(null));
} finally {
TextLine.recycle(tl);
- if (mt != null) {
- mt.recycle();
- }
+ MeasuredText.recycle(mt);
}
}
@@ -2266,11 +2272,6 @@
private SpanSet<LineBackgroundSpan> mLineBackgroundSpans;
private int mJustificationMode;
- /** @hide */
- @IntDef({DIR_LEFT_TO_RIGHT, DIR_RIGHT_TO_LEFT})
- @Retention(RetentionPolicy.SOURCE)
- public @interface Direction {}
-
public static final int DIR_LEFT_TO_RIGHT = 1;
public static final int DIR_RIGHT_TO_LEFT = -1;
diff --git a/core/java/android/text/MeasuredText.java b/core/java/android/text/MeasuredText.java
index ca31176..3d9fba7 100644
--- a/core/java/android/text/MeasuredText.java
+++ b/core/java/android/text/MeasuredText.java
@@ -16,384 +16,125 @@
package android.text;
-import android.annotation.FloatRange;
-import android.annotation.IntRange;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.graphics.Paint;
-import android.text.AutoGrowArray.ByteArray;
-import android.text.AutoGrowArray.FloatArray;
-import android.text.AutoGrowArray.IntArray;
-import android.text.Layout.Directions;
import android.text.style.MetricAffectingSpan;
import android.text.style.ReplacementSpan;
-import android.util.Pools.SynchronizedPool;
+import android.util.Log;
-import java.util.Arrays;
+import com.android.internal.util.ArrayUtils;
/**
- * MeasuredText provides text information for rendering purpose.
- *
- * The first motivation of this class is identify the text directions and retrieving individual
- * character widths. However retrieving character widths is slower than identifying text directions.
- * Thus, this class provides several builder methods for specific purposes.
- *
- * - buildForBidi:
- * Compute only text directions.
- * - buildForMeasurement:
- * Compute text direction and all character widths.
- * - buildForStaticLayout:
- * This is bit special. StaticLayout also needs to know text direction and character widths for
- * line breaking, but all things are done in native code. Similarly, text measurement is done
- * in native code. So instead of storing result to Java array, this keeps the result in native
- * code since there is no good reason to move the results to Java layer.
- *
- * In addition to the character widths, some additional information is computed for each purposes,
- * e.g. whole text length for measurement or font metrics for static layout.
- *
- * MeasuredText is NOT a thread safe object.
* @hide
*/
class MeasuredText {
- private static final char OBJECT_REPLACEMENT_CHARACTER = '\uFFFC';
+ private static final boolean localLOGV = false;
+ CharSequence mText;
+ int mTextStart;
+ float[] mWidths;
+ char[] mChars;
+ byte[] mLevels;
+ int mDir;
+ boolean mEasy;
+ int mLen;
- private MeasuredText() {} // Use build static functions instead.
+ private int mPos;
+ private TextPaint mWorkPaint;
- private static final SynchronizedPool<MeasuredText> sPool = new SynchronizedPool<>(1);
-
- private static @NonNull MeasuredText obtain() { // Use build static functions instead.
- final MeasuredText mt = sPool.acquire();
- return mt != null ? mt : new MeasuredText();
+ private MeasuredText() {
+ mWorkPaint = new TextPaint();
}
- /**
- * Recycle the MeasuredText.
- *
- * Do not call any methods after you call this method.
- */
- public void recycle() {
- release();
- sPool.release(this);
- }
+ private static final Object[] sLock = new Object[0];
+ private static final MeasuredText[] sCached = new MeasuredText[3];
- // The casted original text.
- //
- // This may be null if the passed text is not a Spanned.
- private @Nullable Spanned mSpanned;
-
- // The start offset of the target range in the original text (mSpanned);
- private @IntRange(from = 0) int mTextStart;
-
- // The length of the target range in the original text.
- private @IntRange(from = 0) int mTextLength;
-
- // The copied character buffer for measuring text.
- //
- // The length of this array is mTextLength.
- private @Nullable char[] mCopiedBuffer;
-
- // The whole paragraph direction.
- private @Layout.Direction int mParaDir;
-
- // True if the text is LTR direction and doesn't contain any bidi characters.
- private boolean mLtrWithoutBidi;
-
- // The bidi level for individual characters.
- //
- // This is empty if mLtrWithoutBidi is true.
- private @NonNull ByteArray mLevels = new ByteArray();
-
- // The whole width of the text.
- // See getWholeWidth comments.
- private @FloatRange(from = 0.0f) float mWholeWidth;
-
- // Individual characters' widths.
- // See getWidths comments.
- private @Nullable FloatArray mWidths = new FloatArray();
-
- // The span end positions.
- // See getSpanEndCache comments.
- private @Nullable IntArray mSpanEndCache = new IntArray(4);
-
- // The font metrics.
- // See getFontMetrics comments.
- private @Nullable IntArray mFontMetrics = new IntArray(4 * 4);
-
- // Following two objects are for avoiding object allocation.
- private @NonNull TextPaint mCachedPaint = new TextPaint();
- private @Nullable Paint.FontMetricsInt mCachedFm;
-
- /**
- * Releases internal buffers.
- */
- public void release() {
- reset();
- mLevels.clearWithReleasingLargeArray();
- mWidths.clearWithReleasingLargeArray();
- mFontMetrics.clearWithReleasingLargeArray();
- mSpanEndCache.clearWithReleasingLargeArray();
- }
-
- /**
- * Resets the internal state for starting new text.
- */
- private void reset() {
- mSpanned = null;
- mCopiedBuffer = null;
- mWholeWidth = 0;
- mLevels.clear();
- mWidths.clear();
- mFontMetrics.clear();
- mSpanEndCache.clear();
- }
-
- /**
- * Returns the characters to be measured.
- *
- * This is always available.
- */
- public @NonNull char[] getChars() {
- return mCopiedBuffer;
- }
-
- /**
- * Returns the paragraph direction.
- *
- * This is always available.
- */
- public @Layout.Direction int getParagraphDir() {
- return mParaDir;
- }
-
- /**
- * Returns the directions.
- *
- * This is always available.
- */
- public Directions getDirections(@IntRange(from = 0) int start, // inclusive
- @IntRange(from = 0) int end) { // exclusive
- if (mLtrWithoutBidi) {
- return Layout.DIRS_ALL_LEFT_TO_RIGHT;
- }
-
- final int length = end - start;
- return AndroidBidi.directions(mParaDir, mLevels.getRawArray(), start, mCopiedBuffer, start,
- length);
- }
-
- /**
- * Returns the whole text width.
- *
- * This is available only if the MeasureText is computed with computeForMeasurement.
- * Returns 0 in other cases.
- */
- public @FloatRange(from = 0.0f) float getWholeWidth() {
- return mWholeWidth;
- }
-
- /**
- * Returns the individual character's width.
- *
- * This is available only if the MeasureText is computed with computeForMeasurement.
- * Returns empty array in other cases.
- */
- public @NonNull FloatArray getWidths() {
- return mWidths;
- }
-
- /**
- * Returns the MetricsAffectingSpan end indices.
- *
- * If the input text is not a spanned string, this has one value that is the length of the text.
- *
- * This is available only if the MeasureText is computed with computeForStaticLayout.
- * Returns empty array in other cases.
- */
- public @NonNull IntArray getSpanEndCache() {
- return mSpanEndCache;
- }
-
- /**
- * Returns the int array which holds FontMetrics.
- *
- * This array holds the repeat of top, bottom, ascent, descent of font metrics value.
- *
- * This is available only if the MeasureText is computed with computeForStaticLayout.
- * Returns empty array in other cases.
- */
- public @NonNull IntArray getFontMetrics() {
- return mFontMetrics;
- }
-
- /**
- * Generates new MeasuredText for Bidi computation.
- *
- * If recycle is null, this returns new instance. If recycle is not null, this fills computed
- * result to recycle and returns recycle.
- *
- * @param text the character sequence to be measured
- * @param start the inclusive start offset of the target region in the text
- * @param end the exclusive end offset of the target region in the text
- * @param textDir the text direction
- * @param recycle pass existing MeasuredText if you want to recycle it.
- *
- * @return measured text
- */
- public static @NonNull MeasuredText buildForBidi(@NonNull CharSequence text,
- @IntRange(from = 0) int start,
- @IntRange(from = 0) int end,
- @NonNull TextDirectionHeuristic textDir,
- @Nullable MeasuredText recycle) {
- final MeasuredText mt = recycle == null ? obtain() : recycle;
- mt.resetAndAnalyzeBidi(text, start, end, textDir);
- return mt;
- }
-
- /**
- * Generates new MeasuredText for measuring texts.
- *
- * If recycle is null, this returns new instance. If recycle is not null, this fills computed
- * result to recycle and returns recycle.
- *
- * @param paint the paint to be used for rendering the text.
- * @param text the character sequence to be measured
- * @param start the inclusive start offset of the target region in the text
- * @param end the exclusive end offset of the target region in the text
- * @param textDir the text direction
- * @param recycle pass existing MeasuredText if you want to recycle it.
- *
- * @return measured text
- */
- public static @NonNull MeasuredText buildForMeasurement(@NonNull TextPaint paint,
- @NonNull CharSequence text,
- @IntRange(from = 0) int start,
- @IntRange(from = 0) int end,
- @NonNull TextDirectionHeuristic textDir,
- @Nullable MeasuredText recycle) {
- final MeasuredText mt = recycle == null ? obtain() : recycle;
- mt.resetAndAnalyzeBidi(text, start, end, textDir);
-
- mt.mWidths.resize(mt.mTextLength);
- if (mt.mTextLength == 0) {
- return mt;
- }
-
- if (mt.mSpanned == null) {
- // No style change by MetricsAffectingSpan. Just measure all text.
- mt.applyMetricsAffectingSpan(
- paint, null /* spans */, start, end, 0 /* native static layout ptr */);
- } else {
- // There may be a MetricsAffectingSpan. Split into span transitions and apply styles.
- int spanEnd;
- for (int spanStart = start; spanStart < end; spanStart = spanEnd) {
- spanEnd = mt.mSpanned.nextSpanTransition(spanStart, end, MetricAffectingSpan.class);
- MetricAffectingSpan[] spans = mt.mSpanned.getSpans(spanStart, spanEnd,
- MetricAffectingSpan.class);
- spans = TextUtils.removeEmptySpans(spans, mt.mSpanned, MetricAffectingSpan.class);
- mt.applyMetricsAffectingSpan(
- paint, spans, spanStart, spanEnd, 0 /* native static layout ptr */);
+ static MeasuredText obtain() {
+ MeasuredText mt;
+ synchronized (sLock) {
+ for (int i = sCached.length; --i >= 0;) {
+ if (sCached[i] != null) {
+ mt = sCached[i];
+ sCached[i] = null;
+ return mt;
+ }
}
}
+ mt = new MeasuredText();
+ if (localLOGV) {
+ Log.v("MEAS", "new: " + mt);
+ }
return mt;
}
- /**
- * Generates new MeasuredText for StaticLayout.
- *
- * If recycle is null, this returns new instance. If recycle is not null, this fills computed
- * result to recycle and returns recycle.
- *
- * @param paint the paint to be used for rendering the text.
- * @param text the character sequence to be measured
- * @param start the inclusive start offset of the target region in the text
- * @param end the exclusive end offset of the target region in the text
- * @param textDir the text direction
- * @param nativeStaticLayoutPtr the pointer to the native static layout object
- * @param recycle pass existing MeasuredText if you want to recycle it.
- *
- * @return measured text
- */
- public static @NonNull MeasuredText buildForStaticLayout(
- @NonNull TextPaint paint,
- @NonNull CharSequence text,
- @IntRange(from = 0) int start,
- @IntRange(from = 0) int end,
- @NonNull TextDirectionHeuristic textDir,
- /* Non-Zero */ long nativeStaticLayoutPtr,
- @Nullable MeasuredText recycle) {
- final MeasuredText mt = recycle == null ? obtain() : recycle;
- mt.resetAndAnalyzeBidi(text, start, end, textDir);
- if (mt.mTextLength == 0) {
- return mt;
- }
-
- if (mt.mSpanned == null) {
- // No style change by MetricsAffectingSpan. Just measure all text.
- mt.applyMetricsAffectingSpan(paint, null /* spans */, start, end,
- nativeStaticLayoutPtr);
- mt.mSpanEndCache.append(end);
- } else {
- // There may be a MetricsAffectingSpan. Split into span transitions and apply styles.
- int spanEnd;
- for (int spanStart = start; spanStart < end; spanStart = spanEnd) {
- spanEnd = mt.mSpanned.nextSpanTransition(spanStart, end, MetricAffectingSpan.class);
- MetricAffectingSpan[] spans = mt.mSpanned.getSpans(spanStart, spanEnd,
- MetricAffectingSpan.class);
- spans = TextUtils.removeEmptySpans(spans, mt.mSpanned, MetricAffectingSpan.class);
- mt.applyMetricsAffectingSpan(paint, spans, spanStart, spanEnd,
- nativeStaticLayoutPtr);
- mt.mSpanEndCache.append(spanEnd);
+ static MeasuredText recycle(MeasuredText mt) {
+ mt.finish();
+ synchronized(sLock) {
+ for (int i = 0; i < sCached.length; ++i) {
+ if (sCached[i] == null) {
+ sCached[i] = mt;
+ mt.mText = null;
+ break;
+ }
}
}
+ return null;
+ }
- return mt;
+ void finish() {
+ mText = null;
+ if (mLen > 1000) {
+ mWidths = null;
+ mChars = null;
+ mLevels = null;
+ }
}
/**
- * Reset internal state and analyzes text for bidirectional runs.
- *
- * @param text the character sequence to be measured
- * @param start the inclusive start offset of the target region in the text
- * @param end the exclusive end offset of the target region in the text
- * @param textDir the text direction
+ * Analyzes text for bidirectional runs. Allocates working buffers.
*/
- private void resetAndAnalyzeBidi(@NonNull CharSequence text,
- @IntRange(from = 0) int start, // inclusive
- @IntRange(from = 0) int end, // exclusive
- @NonNull TextDirectionHeuristic textDir) {
- reset();
- mSpanned = text instanceof Spanned ? (Spanned) text : null;
+ void setPara(CharSequence text, int start, int end, TextDirectionHeuristic textDir) {
+ mText = text;
mTextStart = start;
- mTextLength = end - start;
- if (mCopiedBuffer == null || mCopiedBuffer.length != mTextLength) {
- mCopiedBuffer = new char[mTextLength];
+ int len = end - start;
+ mLen = len;
+ mPos = 0;
+
+ if (mWidths == null || mWidths.length < len) {
+ mWidths = ArrayUtils.newUnpaddedFloatArray(len);
}
- TextUtils.getChars(text, start, end, mCopiedBuffer, 0);
+ if (mChars == null || mChars.length != len) {
+ mChars = new char[len];
+ }
+ TextUtils.getChars(text, start, end, mChars, 0);
- // Replace characters associated with ReplacementSpan to U+FFFC.
- if (mSpanned != null) {
- ReplacementSpan[] spans = mSpanned.getSpans(start, end, ReplacementSpan.class);
+ if (text instanceof Spanned) {
+ Spanned spanned = (Spanned) text;
+ ReplacementSpan[] spans = spanned.getSpans(start, end,
+ ReplacementSpan.class);
for (int i = 0; i < spans.length; i++) {
- int startInPara = mSpanned.getSpanStart(spans[i]) - start;
- int endInPara = mSpanned.getSpanEnd(spans[i]) - start;
- // The span interval may be larger and must be restricted to [start, end)
+ int startInPara = spanned.getSpanStart(spans[i]) - start;
+ int endInPara = spanned.getSpanEnd(spans[i]) - start;
+ // The span interval may be larger and must be restricted to [start, end[
if (startInPara < 0) startInPara = 0;
- if (endInPara > mTextLength) endInPara = mTextLength;
- Arrays.fill(mCopiedBuffer, startInPara, endInPara, OBJECT_REPLACEMENT_CHARACTER);
+ if (endInPara > len) endInPara = len;
+ for (int j = startInPara; j < endInPara; j++) {
+ mChars[j] = '\uFFFC'; // object replacement character
+ }
}
}
if ((textDir == TextDirectionHeuristics.LTR ||
textDir == TextDirectionHeuristics.FIRSTSTRONG_LTR ||
textDir == TextDirectionHeuristics.ANYRTL_LTR) &&
- TextUtils.doesNotNeedBidi(mCopiedBuffer, 0, mTextLength)) {
- mLevels.clear();
- mParaDir = Layout.DIR_LEFT_TO_RIGHT;
- mLtrWithoutBidi = true;
+ TextUtils.doesNotNeedBidi(mChars, 0, len)) {
+ mDir = Layout.DIR_LEFT_TO_RIGHT;
+ mEasy = true;
} else {
- final int bidiRequest;
+ if (mLevels == null || mLevels.length < len) {
+ mLevels = ArrayUtils.newUnpaddedByteArray(len);
+ }
+ int bidiRequest;
if (textDir == TextDirectionHeuristics.LTR) {
bidiRequest = Layout.DIR_REQUEST_LTR;
} else if (textDir == TextDirectionHeuristics.RTL) {
@@ -403,146 +144,122 @@
} else if (textDir == TextDirectionHeuristics.FIRSTSTRONG_RTL) {
bidiRequest = Layout.DIR_REQUEST_DEFAULT_RTL;
} else {
- final boolean isRtl = textDir.isRtl(mCopiedBuffer, 0, mTextLength);
+ boolean isRtl = textDir.isRtl(mChars, 0, len);
bidiRequest = isRtl ? Layout.DIR_REQUEST_RTL : Layout.DIR_REQUEST_LTR;
}
- mLevels.resize(mTextLength);
- mParaDir = AndroidBidi.bidi(bidiRequest, mCopiedBuffer, mLevels.getRawArray());
- mLtrWithoutBidi = false;
- }
- }
-
- private void applyReplacementRun(@NonNull ReplacementSpan replacement,
- @IntRange(from = 0) int start, // inclusive, in copied buffer
- @IntRange(from = 0) int end, // exclusive, in copied buffer
- /* Maybe Zero */ long nativeStaticLayoutPtr) {
- // Use original text. Shouldn't matter.
- // TODO: passing uninitizlied FontMetrics to developers. Do we need to keep this for
- // backward compatibility? or Should we initialize them for getFontMetricsInt?
- final float width = replacement.getSize(
- mCachedPaint, mSpanned, start + mTextStart, end + mTextStart, mCachedFm);
- if (nativeStaticLayoutPtr == 0) {
- // Assigns all width to the first character. This is the same behavior as minikin.
- mWidths.set(start, width);
- if (end > start + 1) {
- Arrays.fill(mWidths.getRawArray(), start + 1, end, 0.0f);
- }
- mWholeWidth += width;
- } else {
- StaticLayout.addReplacementRun(nativeStaticLayoutPtr, mCachedPaint, start, end, width);
- }
- }
-
- private void applyStyleRun(@IntRange(from = 0) int start, // inclusive, in copied buffer
- @IntRange(from = 0) int end, // exclusive, in copied buffer
- /* Maybe Zero */ long nativeStaticLayoutPtr) {
- if (nativeStaticLayoutPtr != 0) {
- mCachedPaint.getFontMetricsInt(mCachedFm);
- }
-
- if (mLtrWithoutBidi) {
- // If the whole text is LTR direction, just apply whole region.
- if (nativeStaticLayoutPtr == 0) {
- mWholeWidth += mCachedPaint.getTextRunAdvances(
- mCopiedBuffer, start, end - start, start, end - start, false /* isRtl */,
- mWidths.getRawArray(), start);
- } else {
- StaticLayout.addStyleRun(nativeStaticLayoutPtr, mCachedPaint, start, end,
- false /* isRtl */);
- }
- } else {
- // If there is multiple bidi levels, split into individual bidi level and apply style.
- byte level = mLevels.get(start);
- // Note that the empty text or empty range won't reach this method.
- // Safe to search from start + 1.
- for (int levelStart = start, levelEnd = start + 1;; ++levelEnd) {
- if (levelEnd == end || mLevels.get(levelEnd) != level) { // transition point
- final boolean isRtl = (level & 0x1) != 0;
- if (nativeStaticLayoutPtr == 0) {
- final int levelLength = levelEnd - levelStart;
- mWholeWidth += mCachedPaint.getTextRunAdvances(
- mCopiedBuffer, levelStart, levelLength, levelStart, levelEnd, isRtl,
- mWidths.getRawArray(), levelStart);
- } else {
- StaticLayout.addStyleRun(
- nativeStaticLayoutPtr, mCachedPaint, levelStart, levelEnd, isRtl);
- }
- if (levelEnd == end) {
- break;
- }
- levelStart = levelEnd;
- level = mLevels.get(levelEnd);
- }
- }
- }
- }
-
- private void applyMetricsAffectingSpan(
- @NonNull TextPaint paint,
- @Nullable MetricAffectingSpan[] spans,
- @IntRange(from = 0) int start, // inclusive, in original text buffer
- @IntRange(from = 0) int end, // exclusive, in original text buffer
- /* Maybe Zero */ long nativeStaticLayoutPtr) {
- mCachedPaint.set(paint);
- // XXX paint should not have a baseline shift, but...
- mCachedPaint.baselineShift = 0;
-
- final boolean needFontMetrics = nativeStaticLayoutPtr != 0;
-
- if (needFontMetrics && mCachedFm == null) {
- mCachedFm = new Paint.FontMetricsInt();
- }
-
- ReplacementSpan replacement = null;
- if (spans != null) {
- for (int i = 0; i < spans.length; i++) {
- MetricAffectingSpan span = spans[i];
- if (span instanceof ReplacementSpan) {
- // The last ReplacementSpan is effective for backward compatibility reasons.
- replacement = (ReplacementSpan) span;
- } else {
- // TODO: No need to call updateMeasureState for ReplacementSpan as well?
- span.updateMeasureState(mCachedPaint);
- }
- }
- }
-
- final int startInCopiedBuffer = start - mTextStart;
- final int endInCopiedBuffer = end - mTextStart;
-
- if (replacement != null) {
- applyReplacementRun(replacement, startInCopiedBuffer, endInCopiedBuffer,
- nativeStaticLayoutPtr);
- } else {
- applyStyleRun(startInCopiedBuffer, endInCopiedBuffer, nativeStaticLayoutPtr);
- }
-
- if (needFontMetrics) {
- if (mCachedPaint.baselineShift < 0) {
- mCachedFm.ascent += mCachedPaint.baselineShift;
- mCachedFm.top += mCachedPaint.baselineShift;
- } else {
- mCachedFm.descent += mCachedPaint.baselineShift;
- mCachedFm.bottom += mCachedPaint.baselineShift;
- }
-
- mFontMetrics.append(mCachedFm.top);
- mFontMetrics.append(mCachedFm.bottom);
- mFontMetrics.append(mCachedFm.ascent);
- mFontMetrics.append(mCachedFm.descent);
+ mDir = AndroidBidi.bidi(bidiRequest, mChars, mLevels);
+ mEasy = false;
}
}
/**
- * Returns the maximum index that the accumulated width not exceeds the width.
+ * Apply the style.
*
- * If forward=false is passed, returns the minimum index from the end instead.
- *
- * This only works if the MeasuredText is computed with computeForMeasurement.
- * Undefined behavior in other case.
+ * If nativeStaticLayoutPtr is 0, this method measures the styled text width.
+ * If nativeStaticLayoutPtr is not 0, this method just passes the style information to native
+ * code by calling StaticLayout.addstyleRun() and returns 0.
*/
- @IntRange(from = 0) int breakText(int limit, boolean forwards, float width) {
- float[] w = mWidths.getRawArray();
+ float addStyleRun(TextPaint paint, int len, Paint.FontMetricsInt fm,
+ long nativeStaticLayoutPtr) {
+ if (fm != null) {
+ paint.getFontMetricsInt(fm);
+ }
+
+ final int p = mPos;
+ mPos = p + len;
+
+ if (mEasy) {
+ final boolean isRtl = mDir != Layout.DIR_LEFT_TO_RIGHT;
+ if (nativeStaticLayoutPtr == 0) {
+ return paint.getTextRunAdvances(mChars, p, len, p, len, isRtl, mWidths, p);
+ } else {
+ StaticLayout.addStyleRun(nativeStaticLayoutPtr, paint, p, p + len, isRtl);
+ return 0.0f; // Builder.addStyleRun doesn't return the width.
+ }
+ }
+
+ float totalAdvance = 0;
+ int level = mLevels[p];
+ for (int q = p, i = p + 1, e = p + len;; ++i) {
+ if (i == e || mLevels[i] != level) {
+ final boolean isRtl = (level & 0x1) != 0;
+ if (nativeStaticLayoutPtr == 0) {
+ totalAdvance +=
+ paint.getTextRunAdvances(mChars, q, i - q, q, i - q, isRtl, mWidths, q);
+ } else {
+ // Builder.addStyleRun doesn't return the width.
+ StaticLayout.addStyleRun(nativeStaticLayoutPtr, paint, q, i, isRtl);
+ }
+ if (i == e) {
+ break;
+ }
+ q = i;
+ level = mLevels[i];
+ }
+ }
+ return totalAdvance; // If nativeStaticLayoutPtr is 0, the result is zero.
+ }
+
+ float addStyleRun(TextPaint paint, int len, Paint.FontMetricsInt fm) {
+ return addStyleRun(paint, len, fm, 0 /* native ptr */);
+ }
+
+ float addStyleRun(TextPaint paint, MetricAffectingSpan[] spans, int len,
+ Paint.FontMetricsInt fm, long nativeStaticLayoutPtr) {
+
+ TextPaint workPaint = mWorkPaint;
+ workPaint.set(paint);
+ // XXX paint should not have a baseline shift, but...
+ workPaint.baselineShift = 0;
+
+ ReplacementSpan replacement = null;
+ for (int i = 0; i < spans.length; i++) {
+ MetricAffectingSpan span = spans[i];
+ if (span instanceof ReplacementSpan) {
+ replacement = (ReplacementSpan)span;
+ } else {
+ span.updateMeasureState(workPaint);
+ }
+ }
+
+ float wid;
+ if (replacement == null) {
+ wid = addStyleRun(workPaint, len, fm, nativeStaticLayoutPtr);
+ } else {
+ // Use original text. Shouldn't matter.
+ wid = replacement.getSize(workPaint, mText, mTextStart + mPos,
+ mTextStart + mPos + len, fm);
+ if (nativeStaticLayoutPtr == 0) {
+ float[] w = mWidths;
+ w[mPos] = wid;
+ for (int i = mPos + 1, e = mPos + len; i < e; i++)
+ w[i] = 0;
+ } else {
+ StaticLayout.addReplacementRun(nativeStaticLayoutPtr, paint, mPos, mPos + len, wid);
+ }
+ mPos += len;
+ }
+
+ if (fm != null) {
+ if (workPaint.baselineShift < 0) {
+ fm.ascent += workPaint.baselineShift;
+ fm.top += workPaint.baselineShift;
+ } else {
+ fm.descent += workPaint.baselineShift;
+ fm.bottom += workPaint.baselineShift;
+ }
+ }
+
+ return wid;
+ }
+
+ float addStyleRun(TextPaint paint, MetricAffectingSpan[] spans, int len,
+ Paint.FontMetricsInt fm) {
+ return addStyleRun(paint, spans, len, fm, 0 /* native ptr */);
+ }
+
+ int breakText(int limit, boolean forwards, float width) {
+ float[] w = mWidths;
if (forwards) {
int i = 0;
while (i < limit) {
@@ -550,7 +267,7 @@
if (width < 0.0f) break;
i++;
}
- while (i > 0 && mCopiedBuffer[i - 1] == ' ') i--;
+ while (i > 0 && mChars[i - 1] == ' ') i--;
return i;
} else {
int i = limit - 1;
@@ -559,22 +276,16 @@
if (width < 0.0f) break;
i--;
}
- while (i < limit - 1 && (mCopiedBuffer[i + 1] == ' ' || w[i + 1] == 0.0f)) {
+ while (i < limit - 1 && (mChars[i + 1] == ' ' || w[i + 1] == 0.0f)) {
i++;
}
return limit - i - 1;
}
}
- /**
- * Returns the length of the substring.
- *
- * This only works if the MeasuredText is computed with computeForMeasurement.
- * Undefined behavior in other case.
- */
- @FloatRange(from = 0.0f) float measure(int start, int limit) {
+ float measure(int start, int limit) {
float width = 0;
- float[] w = mWidths.getRawArray();
+ float[] w = mWidths;
for (int i = start; i < limit; ++i) {
width += w[i];
}
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index 81c82c9..c0fc44f 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -21,10 +21,10 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.graphics.Paint;
-import android.text.AutoGrowArray.FloatArray;
import android.text.style.LeadingMarginSpan;
import android.text.style.LeadingMarginSpan.LeadingMarginSpan2;
import android.text.style.LineHeightSpan;
+import android.text.style.MetricAffectingSpan;
import android.text.style.TabStopSpan;
import android.util.Log;
import android.util.Pools.SynchronizedPool;
@@ -99,6 +99,8 @@
b.mBreakStrategy = Layout.BREAK_STRATEGY_SIMPLE;
b.mHyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NONE;
b.mJustificationMode = Layout.JUSTIFICATION_MODE_NONE;
+
+ b.mMeasuredText = MeasuredText.obtain();
return b;
}
@@ -109,6 +111,8 @@
private static void recycle(@NonNull Builder b) {
b.mPaint = null;
b.mText = null;
+ MeasuredText.recycle(b.mMeasuredText);
+ b.mMeasuredText = null;
b.mLeftIndents = null;
b.mRightIndents = null;
b.mLeftPaddings = null;
@@ -124,6 +128,7 @@
mRightIndents = null;
mLeftPaddings = null;
mRightPaddings = null;
+ mMeasuredText.finish();
}
public Builder setText(CharSequence source) {
@@ -439,6 +444,9 @@
private final Paint.FontMetricsInt mFontMetricsInt = new Paint.FontMetricsInt();
+ // This will go away and be subsumed by native builder code
+ private MeasuredText mMeasuredText;
+
private static final SynchronizedPool<Builder> sPool = new SynchronizedPool<>(3);
}
@@ -610,7 +618,11 @@
TextUtils.TruncateAt ellipsize = b.mEllipsize;
final boolean addLastLineSpacing = b.mAddLastLineLineSpacing;
LineBreaks lineBreaks = new LineBreaks(); // TODO: move to builder to avoid allocation costs
- FloatArray widths = new FloatArray();
+ // store span end locations
+ int[] spanEndCache = new int[4];
+ // store fontMetrics per span range
+ // must be a multiple of 4 (and > 0) (store top, bottom, ascent, and descent per range)
+ int[] fmCache = new int[4 * 4];
mLineCount = 0;
mEllipsized = false;
@@ -622,6 +634,8 @@
Paint.FontMetricsInt fm = b.mFontMetricsInt;
int[] chooseHtv = null;
+ MeasuredText measured = b.mMeasuredText;
+
Spanned spanned = null;
if (source instanceof Spanned)
spanned = (Spanned) source;
@@ -648,7 +662,6 @@
b.mJustificationMode != Layout.JUSTIFICATION_MODE_NONE,
indents, mLeftPaddings, mRightPaddings);
- MeasuredText measured = null;
try {
int paraEnd;
for (int paraStart = bufStart; paraStart <= bufEnd; paraStart = paraEnd) {
@@ -708,6 +721,13 @@
}
}
+ measured.setPara(source, paraStart, paraEnd, textDir);
+ char[] chs = measured.mChars;
+ float[] widths = measured.mWidths;
+ byte[] chdirs = measured.mLevels;
+ int dir = measured.mDir;
+ boolean easy = measured.mEasy;
+
// tab stop locations
int[] variableTabStops = null;
if (spanned != null) {
@@ -723,16 +743,50 @@
}
}
- measured = MeasuredText.buildForStaticLayout(
- paint, source, paraStart, paraEnd, textDir, nativePtr, measured);
- final char[] chs = measured.getChars();
- final int[] spanEndCache = measured.getSpanEndCache().getRawArray();
- final int[] fmCache = measured.getFontMetrics().getRawArray();
- widths.resize(chs.length);
-
// measurement has to be done before performing line breaking
// but we don't want to recompute fontmetrics or span ranges the
// second time, so we cache those and then use those stored values
+ int fmCacheCount = 0;
+ int spanEndCacheCount = 0;
+ for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) {
+ if (fmCacheCount * 4 >= fmCache.length) {
+ int[] grow = new int[fmCacheCount * 4 * 2];
+ System.arraycopy(fmCache, 0, grow, 0, fmCacheCount * 4);
+ fmCache = grow;
+ }
+
+ if (spanEndCacheCount >= spanEndCache.length) {
+ int[] grow = new int[spanEndCacheCount * 2];
+ System.arraycopy(spanEndCache, 0, grow, 0, spanEndCacheCount);
+ spanEndCache = grow;
+ }
+
+ if (spanned == null) {
+ spanEnd = paraEnd;
+ int spanLen = spanEnd - spanStart;
+ measured.addStyleRun(paint, spanLen, fm, nativePtr);
+ } else {
+ spanEnd = spanned.nextSpanTransition(spanStart, paraEnd,
+ MetricAffectingSpan.class);
+ int spanLen = spanEnd - spanStart;
+ MetricAffectingSpan[] spans =
+ spanned.getSpans(spanStart, spanEnd, MetricAffectingSpan.class);
+ spans = TextUtils.removeEmptySpans(spans, spanned,
+ MetricAffectingSpan.class);
+ measured.addStyleRun(paint, spans, spanLen, fm, nativePtr);
+ }
+
+ // the order of storage here (top, bottom, ascent, descent) has to match the
+ // code below where these values are retrieved
+ fmCache[fmCacheCount * 4 + 0] = fm.top;
+ fmCache[fmCacheCount * 4 + 1] = fm.bottom;
+ fmCache[fmCacheCount * 4 + 2] = fm.ascent;
+ fmCache[fmCacheCount * 4 + 3] = fm.descent;
+ fmCacheCount++;
+
+ spanEndCache[spanEndCacheCount] = spanEnd;
+ spanEndCacheCount++;
+ }
int breakCount = nComputeLineBreaks(
nativePtr,
@@ -755,7 +809,7 @@
lineBreaks.ascents,
lineBreaks.descents,
lineBreaks.flags,
- widths.getRawArray());
+ widths);
final int[] breaks = lineBreaks.breaks;
final float[] lineWidths = lineBreaks.widths;
@@ -778,7 +832,7 @@
width += lineWidths[i];
} else {
for (int j = (i == 0 ? 0 : breaks[i - 1]); j < breaks[i]; j++) {
- width += widths.get(j);
+ width += widths[j];
}
}
flag |= flags[i] & TAB_MASK;
@@ -842,10 +896,10 @@
v = out(source, here, endPos,
ascent, descent, fmTop, fmBottom,
v, spacingmult, spacingadd, chooseHt, chooseHtv, fm,
- flags[breakIndex], needMultiply, measured, bufEnd,
- includepad, trackpad, addLastLineSpacing, chs, widths.getRawArray(),
- paraStart, ellipsize, ellipsizedWidth, lineWidths[breakIndex],
- paint, moreChars);
+ flags[breakIndex], needMultiply, chdirs, dir, easy, bufEnd,
+ includepad, trackpad, addLastLineSpacing, chs, widths, paraStart,
+ ellipsize, ellipsizedWidth, lineWidths[breakIndex], paint,
+ moreChars);
if (endPos < spanEnd) {
// preserve metrics for current span
@@ -873,8 +927,7 @@
if ((bufEnd == bufStart || source.charAt(bufEnd - 1) == CHAR_NEW_LINE)
&& mLineCount < mMaximumVisibleLineCount) {
- measured = MeasuredText.buildForStaticLayout(
- paint, source, bufEnd, bufEnd, textDir, nativePtr, measured);
+ measured.setPara(source, bufEnd, bufEnd, textDir);
paint.getFontMetricsInt(fm);
@@ -884,15 +937,12 @@
v,
spacingmult, spacingadd, null,
null, fm, 0,
- needMultiply, measured, bufEnd,
+ needMultiply, measured.mLevels, measured.mDir, measured.mEasy, bufEnd,
includepad, trackpad, addLastLineSpacing, null,
null, bufStart, ellipsize,
ellipsizedWidth, 0, paint, false);
}
} finally {
- if (measured != null) {
- measured.recycle();
- }
nFinish(nativePtr);
}
}
@@ -902,8 +952,8 @@
private int out(final CharSequence text, final int start, final int end, int above, int below,
int top, int bottom, int v, final float spacingmult, final float spacingadd,
final LineHeightSpan[] chooseHt, final int[] chooseHtv, final Paint.FontMetricsInt fm,
- final int flags, final boolean needMultiply, final MeasuredText measured,
- final int bufEnd, final boolean includePad, final boolean trackPad,
+ final int flags, final boolean needMultiply, final byte[] chdirs, final int dir,
+ final boolean easy, final int bufEnd, final boolean includePad, final boolean trackPad,
final boolean addLastLineLineSpacing, final char[] chs, final float[] widths,
final int widthStart, final TextUtils.TruncateAt ellipsize, final float ellipsisWidth,
final float textWidth, final TextPaint paint, final boolean moreChars) {
@@ -911,7 +961,6 @@
final int off = j * mColumns;
final int want = off + mColumns + TOP;
int[] lines = mLines;
- final int dir = measured.getParagraphDir();
if (want >= lines.length) {
final int[] grow = ArrayUtils.newUnpaddedIntArray(GrowingArrayUtils.growSize(want));
@@ -937,8 +986,17 @@
// one bit for start field
lines[off + TAB] |= flags & TAB_MASK;
lines[off + HYPHEN] = flags;
+
lines[off + DIR] |= dir << DIR_SHIFT;
- mLineDirections[j] = measured.getDirections(start - widthStart, end - widthStart);
+ // easy means all chars < the first RTL, so no emoji, no nothing
+ // XXX a run with no text or all spaces is easy but might be an empty
+ // RTL paragraph. Make sure easy is false if this is the case.
+ if (easy) {
+ mLineDirections[j] = DIRS_ALL_LEFT_TO_RIGHT;
+ } else {
+ mLineDirections[j] = AndroidBidi.directions(dir, chdirs, start - widthStart, chs,
+ start - widthStart, end - start);
+ }
final boolean firstLine = (j == 0);
final boolean currentLineIsTheLastVisibleOne = (j + 1 == mMaximumVisibleLineCount);
diff --git a/core/java/android/text/TextUtils.java b/core/java/android/text/TextUtils.java
index af66157..cbdaa69 100644
--- a/core/java/android/text/TextUtils.java
+++ b/core/java/android/text/TextUtils.java
@@ -42,6 +42,7 @@
import android.text.style.ForegroundColorSpan;
import android.text.style.LeadingMarginSpan;
import android.text.style.LocaleSpan;
+import android.text.style.MetricAffectingSpan;
import android.text.style.ParagraphStyle;
import android.text.style.QuoteSpan;
import android.text.style.RelativeSizeSpan;
@@ -1250,11 +1251,10 @@
@NonNull String ellipsis) {
final int len = text.length();
- MeasuredText mt = null;
+ final MeasuredText mt = MeasuredText.obtain();
MeasuredText resultMt = null;
try {
- mt = MeasuredText.buildForMeasurement(paint, text, 0, text.length(), textDir, mt);
- float width = mt.getWholeWidth();
+ float width = setPara(mt, paint, text, 0, text.length(), textDir);
if (width <= avail) {
if (callback != null) {
@@ -1263,6 +1263,7 @@
return text;
}
+ resultMt = MeasuredText.obtain();
// First estimate of effective width of ellipsis.
float ellipsisWidth = paint.measureText(ellipsis);
int numberOfTries = 0;
@@ -1289,7 +1290,7 @@
}
}
- final char[] buf = mt.getChars();
+ final char[] buf = mt.mChars;
final Spanned sp = text instanceof Spanned ? (Spanned) text : null;
final int removed = end - start;
@@ -1332,9 +1333,7 @@
if (remaining == 0) { // All text is gone.
textFits = true;
} else {
- resultMt = MeasuredText.buildForMeasurement(
- paint, result, 0, result.length(), textDir, resultMt);
- width = resultMt.getWholeWidth();
+ width = setPara(resultMt, paint, result, 0, result.length(), textDir);
if (width <= avail) {
textFits = true;
} else {
@@ -1358,11 +1357,9 @@
}
return result;
} finally {
- if (mt != null) {
- mt.recycle();
- }
+ MeasuredText.recycle(mt);
if (resultMt != null) {
- resultMt.recycle();
+ MeasuredText.recycle(resultMt);
}
}
}
@@ -1479,17 +1476,15 @@
public static CharSequence commaEllipsize(CharSequence text, TextPaint p,
float avail, String oneMore, String more, TextDirectionHeuristic textDir) {
- MeasuredText mt = null;
- MeasuredText tempMt = null;
+ MeasuredText mt = MeasuredText.obtain();
try {
int len = text.length();
- mt = MeasuredText.buildForMeasurement(p, text, 0, len, textDir, mt);
- float width = mt.getWholeWidth();
+ float width = setPara(mt, p, text, 0, len, textDir);
if (width <= avail) {
return text;
}
- char[] buf = mt.getChars();
+ char[] buf = mt.mChars;
int commaCount = 0;
for (int i = 0; i < len; i++) {
@@ -1505,8 +1500,9 @@
int w = 0;
int count = 0;
- float[] widths = mt.getWidths().getRawArray();
+ float[] widths = mt.mWidths;
+ MeasuredText tempMt = MeasuredText.obtain();
for (int i = 0; i < len; i++) {
w += widths[i];
@@ -1523,9 +1519,8 @@
}
// XXX this is probably ok, but need to look at it more
- tempMt = MeasuredText.buildForMeasurement(
- p, format, 0, format.length(), textDir, tempMt);
- float moreWid = tempMt.getWholeWidth();
+ tempMt.setPara(format, 0, format.length(), textDir);
+ float moreWid = tempMt.addStyleRun(p, tempMt.mLen, null);
if (w + moreWid <= avail) {
ok = i + 1;
@@ -1533,18 +1528,40 @@
}
}
}
+ MeasuredText.recycle(tempMt);
SpannableStringBuilder out = new SpannableStringBuilder(okFormat);
out.insert(0, text, 0, ok);
return out;
} finally {
- if (mt != null) {
- mt.recycle();
- }
- if (tempMt != null) {
- tempMt.recycle();
+ MeasuredText.recycle(mt);
+ }
+ }
+
+ private static float setPara(MeasuredText mt, TextPaint paint,
+ CharSequence text, int start, int end, TextDirectionHeuristic textDir) {
+
+ mt.setPara(text, start, end, textDir);
+
+ float width;
+ Spanned sp = text instanceof Spanned ? (Spanned) text : null;
+ int len = end - start;
+ if (sp == null) {
+ width = mt.addStyleRun(paint, len, null);
+ } else {
+ width = 0;
+ int spanEnd;
+ for (int spanStart = 0; spanStart < len; spanStart = spanEnd) {
+ spanEnd = sp.nextSpanTransition(spanStart, len,
+ MetricAffectingSpan.class);
+ MetricAffectingSpan[] spans = sp.getSpans(
+ spanStart, spanEnd, MetricAffectingSpan.class);
+ spans = TextUtils.removeEmptySpans(spans, sp, MetricAffectingSpan.class);
+ width += mt.addStyleRun(paint, spans, spanEnd - spanStart, null);
}
}
+
+ return width;
}
// Returns true if the character's presence could affect RTL layout.
diff --git a/core/java/android/util/StatsManager.java b/core/java/android/util/StatsManager.java
index 2bcd863..26a3c36 100644
--- a/core/java/android/util/StatsManager.java
+++ b/core/java/android/util/StatsManager.java
@@ -93,10 +93,11 @@
}
/**
- * Clients can request data with a binder call.
+ * Clients can request data with a binder call. This getter is destructive and also clears
+ * the retrieved metrics from statsd memory.
*
* @param configKey Configuration key to retrieve data from.
- * @return Serialized ConfigMetricsReport proto. Returns null on failure.
+ * @return Serialized ConfigMetricsReportList proto. Returns null on failure.
*/
@RequiresPermission(Manifest.permission.DUMP)
public byte[] getData(String configKey) {
@@ -115,6 +116,30 @@
}
}
+ /**
+ * Clients can request metadata for statsd. Will contain stats across all configurations but not
+ * the actual metrics themselves (metrics must be collected via {@link #getData(String)}.
+ * This getter is not destructive and will not reset any metrics/counters.
+ *
+ * @return Serialized StatsdStatsReport proto. Returns null on failure.
+ */
+ @RequiresPermission(Manifest.permission.DUMP)
+ public byte[] getMetadata() {
+ synchronized (this) {
+ try {
+ IStatsManager service = getIStatsManagerLocked();
+ if (service == null) {
+ Slog.d(TAG, "Failed to find statsd when getting metadata");
+ return null;
+ }
+ return service.getMetadata();
+ } catch (RemoteException e) {
+ Slog.d(TAG, "Failed to connecto statsd when getting metadata");
+ return null;
+ }
+ }
+ }
+
private class StatsdDeathRecipient implements IBinder.DeathRecipient {
@Override
public void binderDied() {
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 2e0ad1b..e3da757 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -16,6 +16,9 @@
package android.view;
+import static android.view.Surface.ROTATION_270;
+import static android.view.Surface.ROTATION_90;
+
import android.annotation.Size;
import android.graphics.Bitmap;
import android.graphics.GraphicBuffer;
@@ -59,8 +62,6 @@
private static native void nativeScreenshot(IBinder displayToken, Surface consumer,
Rect sourceCrop, int width, int height, int minLayer, int maxLayer,
boolean allLayers, boolean useIdentityTransform);
- private static native void nativeCaptureLayers(IBinder layerHandleToken, Surface consumer,
- Rect sourceCrop, float frameScale);
private static native GraphicBuffer nativeCaptureLayers(IBinder layerHandleToken,
Rect sourceCrop, float frameScale);
@@ -1179,22 +1180,35 @@
/**
* Like {@link SurfaceControl#screenshot(int, int, int, int, boolean)} but
- * includes all Surfaces in the screenshot.
+ * includes all Surfaces in the screenshot. This will also update the orientation so it
+ * sends the correct coordinates to SF based on the rotation value.
*
+ * @param sourceCrop The portion of the screen to capture into the Bitmap;
+ * caller may pass in 'new Rect()' if no cropping is desired.
* @param width The desired width of the returned bitmap; the raw
* screen will be scaled down to this size.
* @param height The desired height of the returned bitmap; the raw
* screen will be scaled down to this size.
+ * @param rotation Apply a custom clockwise rotation to the screenshot, i.e.
+ * Surface.ROTATION_0,90,180,270. Surfaceflinger will always take
+ * screenshots in its native portrait orientation by default, so this is
+ * useful for returning screenshots that are independent of device
+ * orientation.
* @return Returns a Bitmap containing the screen contents, or null
* if an error occurs. Make sure to call Bitmap.recycle() as soon as
* possible, once its content is not needed anymore.
*/
- public static Bitmap screenshot(int width, int height) {
+ public static Bitmap screenshot(Rect sourceCrop, int width, int height, int rotation) {
// TODO: should take the display as a parameter
IBinder displayToken = SurfaceControl.getBuiltInDisplay(
SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN);
- return nativeScreenshot(displayToken, new Rect(), width, height, 0, 0, true,
- false, Surface.ROTATION_0);
+ if (rotation == ROTATION_90 || rotation == ROTATION_270) {
+ rotation = (rotation == ROTATION_90) ? ROTATION_270 : ROTATION_90;
+ }
+
+ SurfaceControl.rotateCropForSF(sourceCrop, rotation);
+ return nativeScreenshot(displayToken, sourceCrop, width, height, 0, 0, true,
+ false, rotation);
}
private static void screenshot(IBinder display, Surface consumer, Rect sourceCrop,
@@ -1210,26 +1224,29 @@
minLayer, maxLayer, allLayers, useIdentityTransform);
}
+ private static void rotateCropForSF(Rect crop, int rot) {
+ if (rot == Surface.ROTATION_90 || rot == Surface.ROTATION_270) {
+ int tmp = crop.top;
+ crop.top = crop.left;
+ crop.left = tmp;
+ tmp = crop.right;
+ crop.right = crop.bottom;
+ crop.bottom = tmp;
+ }
+ }
+
/**
* Captures a layer and its children into the provided {@link Surface}.
*
* @param layerHandleToken The root layer to capture.
- * @param consumer The {@link Surface} to capture the layer into.
* @param sourceCrop The portion of the root surface to capture; caller may pass in 'new
* Rect()' or null if no cropping is desired.
* @param frameScale The desired scale of the returned buffer; the raw
* screen will be scaled up/down.
+ *
+ * @return Returns a GraphicBuffer that contains the layer capture.
*/
- public static void captureLayers(IBinder layerHandleToken, Surface consumer, Rect sourceCrop,
- float frameScale) {
- nativeCaptureLayers(layerHandleToken, consumer, sourceCrop, frameScale);
- }
-
- /**
- * Same as {@link #captureLayers(IBinder, Surface, Rect, float)} except this
- * captures to a {@link GraphicBuffer} instead of a {@link Surface}.
- */
- public static GraphicBuffer captureLayersToBuffer(IBinder layerHandleToken, Rect sourceCrop,
+ public static GraphicBuffer captureLayers(IBinder layerHandleToken, Rect sourceCrop,
float frameScale) {
return nativeCaptureLayers(layerHandleToken, sourceCrop, frameScale);
}
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index 547e0db..9a99e53 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -24,6 +24,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemService;
+import android.annotation.TestApi;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -36,6 +37,7 @@
import android.os.RemoteException;
import android.service.autofill.AutofillService;
import android.service.autofill.FillEventHistory;
+import android.service.autofill.UserData;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
@@ -1007,6 +1009,54 @@
}
/**
+ * Gets the user data used for <a href="#FieldsClassification">fields classification</a>.
+ *
+ * <p><b>Note:</b> This method should only be called by an app providing an autofill service.
+ *
+ * TODO(b/67867469):
+ * - proper javadoc
+ * - unhide / remove testApi
+ *
+ * @return value previously set by {@link #setUserData(UserData)} or {@code null} if it was
+ * reset or if the caller currently does not have an enabled autofill service for the user.
+ *
+ * @hide
+ */
+ @TestApi
+ @Nullable public UserData getUserData() {
+ try {
+ return mService.getUserData();
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ return null;
+ }
+ }
+
+ /**
+ * Sets the user data used for <a href="#FieldsClassification">fields classification</a>.
+ *
+ * <p><b>Note:</b> This method should only be called by an app providing an autofill service,
+ * and it's ignored if the caller currently doesn't have an enabled autofill service for
+ * the user.
+ *
+ * TODO(b/67867469):
+ * - proper javadoc
+ * - unhide / remove testApi
+ * - add unit tests:
+ * - call set / get / verify
+ *
+ * @hide
+ */
+ @TestApi
+ public void setUserData(@Nullable UserData userData) {
+ try {
+ mService.setUserData(userData);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Returns {@code true} if autofill is supported by the current device and
* is supported for this user.
*
diff --git a/core/java/android/view/autofill/AutofillValue.java b/core/java/android/view/autofill/AutofillValue.java
index 3beae11..8e649de 100644
--- a/core/java/android/view/autofill/AutofillValue.java
+++ b/core/java/android/view/autofill/AutofillValue.java
@@ -177,7 +177,7 @@
.append("[type=").append(mType)
.append(", value=");
if (isText()) {
- string.append(((CharSequence) mValue).length()).append("_chars");
+ Helper.appendRedacted(string, (CharSequence) mValue);
} else {
string.append(mValue);
}
diff --git a/core/java/android/view/autofill/Helper.java b/core/java/android/view/autofill/Helper.java
index 829e7f3..b95704a 100644
--- a/core/java/android/view/autofill/Helper.java
+++ b/core/java/android/view/autofill/Helper.java
@@ -16,6 +16,8 @@
package android.view.autofill;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.os.Bundle;
import java.util.Arrays;
@@ -50,6 +52,35 @@
return builder;
}
+ /**
+ * Appends {@code value} to the {@code builder} redacting its contents.
+ */
+ public static void appendRedacted(@NonNull StringBuilder builder,
+ @Nullable CharSequence value) {
+ if (value == null) {
+ builder.append("null");
+ } else {
+ builder.append(value.length()).append("_chars");
+ }
+ }
+
+ /**
+ * Appends {@code values} to the {@code builder} redacting its contents.
+ */
+ public static void appendRedacted(@NonNull StringBuilder builder, @Nullable String[] values) {
+ if (values == null) {
+ builder.append("N/A");
+ return;
+ }
+ builder.append("[");
+ for (String value : values) {
+ builder.append(" '");
+ appendRedacted(builder, value);
+ builder.append("'");
+ }
+ builder.append(" ]");
+ }
+
private Helper() {
throw new UnsupportedOperationException("contains static members only");
}
diff --git a/core/java/android/view/autofill/IAutoFillManager.aidl b/core/java/android/view/autofill/IAutoFillManager.aidl
index d6db3fe..7d6a19f 100644
--- a/core/java/android/view/autofill/IAutoFillManager.aidl
+++ b/core/java/android/view/autofill/IAutoFillManager.aidl
@@ -21,6 +21,7 @@
import android.os.Bundle;
import android.os.IBinder;
import android.service.autofill.FillEventHistory;
+import android.service.autofill.UserData;
import android.view.autofill.AutofillId;
import android.view.autofill.AutofillValue;
import android.view.autofill.IAutoFillManagerClient;
@@ -53,4 +54,6 @@
boolean isServiceSupported(int userId);
boolean isServiceEnabled(int userId, String packageName);
void onPendingSaveUi(int operation, IBinder token);
+ UserData getUserData();
+ void setUserData(in UserData userData);
}
diff --git a/core/java/android/view/textclassifier/TextClassification.java b/core/java/android/view/textclassifier/TextClassification.java
index 8916323..b2cab5b 100644
--- a/core/java/android/view/textclassifier/TextClassification.java
+++ b/core/java/android/view/textclassifier/TextClassification.java
@@ -39,7 +39,8 @@
* Information for generating a widget to handle classified text.
*
* <p>A TextClassification object contains icons, labels, onClickListeners and intents that may
- * be used to build a widget that can be used to act on classified text.
+ * be used to build a widget that can be used to act on classified text. There is the concept of a
+ * <i>primary action</i> and other <i>secondary actions</i>.
*
* <p>e.g. building a view that, when clicked, shares the classified text with the preferred app:
*
@@ -64,11 +65,18 @@
* view.startActionMode(new ActionMode.Callback() {
*
* public boolean onCreateActionMode(ActionMode mode, Menu menu) {
- * for (int i = 0; i < classification.getActionCount(); i++) {
- * if (thisAppHasPermissionToInvokeIntent(classification.getIntent(i))) {
- * menu.add(Menu.NONE, i, 20, classification.getLabel(i))
- * .setIcon(classification.getIcon(i))
- * .setIntent(classification.getIntent(i));
+ * // Add the "primary" action.
+ * if (thisAppHasPermissionToInvokeIntent(classification.getIntent())) {
+ * menu.add(Menu.NONE, 0, 20, classification.getLabel())
+ * .setIcon(classification.getIcon())
+ * .setIntent(classification.getIntent());
+ * }
+ * // Add the "secondary" actions.
+ * for (int i = 0; i < classification.getSecondaryActionsCount(); i++) {
+ * if (thisAppHasPermissionToInvokeIntent(classification.getSecondaryIntent(i))) {
+ * menu.add(Menu.NONE, i + 1, 20, classification.getSecondaryLabel(i))
+ * .setIcon(classification.getSecondaryIcon(i))
+ * .setIntent(classification.getSecondaryIntent(i));
* }
* }
* return true;
@@ -92,31 +100,43 @@
static final TextClassification EMPTY = new TextClassification.Builder().build();
@NonNull private final String mText;
- @NonNull private final List<Drawable> mIcons;
- @NonNull private final List<String> mLabels;
- @NonNull private final List<Intent> mIntents;
- @NonNull private final List<OnClickListener> mOnClickListeners;
+ @Nullable private final Drawable mPrimaryIcon;
+ @Nullable private final String mPrimaryLabel;
+ @Nullable private final Intent mPrimaryIntent;
+ @Nullable private final OnClickListener mPrimaryOnClickListener;
+ @NonNull private final List<Drawable> mSecondaryIcons;
+ @NonNull private final List<String> mSecondaryLabels;
+ @NonNull private final List<Intent> mSecondaryIntents;
+ @NonNull private final List<OnClickListener> mSecondaryOnClickListeners;
@NonNull private final EntityConfidence<String> mEntityConfidence;
private int mLogType;
@NonNull private final String mVersionInfo;
private TextClassification(
@Nullable String text,
- @NonNull List<Drawable> icons,
- @NonNull List<String> labels,
- @NonNull List<Intent> intents,
- @NonNull List<OnClickListener> onClickListeners,
+ @Nullable Drawable primaryIcon,
+ @Nullable String primaryLabel,
+ @Nullable Intent primaryIntent,
+ @Nullable OnClickListener primaryOnClickListener,
+ @NonNull List<Drawable> secondaryIcons,
+ @NonNull List<String> secondaryLabels,
+ @NonNull List<Intent> secondaryIntents,
+ @NonNull List<OnClickListener> secondaryOnClickListeners,
@NonNull Map<String, Float> entityConfidence,
int logType,
@NonNull String versionInfo) {
- Preconditions.checkArgument(labels.size() == intents.size());
- Preconditions.checkArgument(icons.size() == intents.size());
- Preconditions.checkArgument(onClickListeners.size() == intents.size());
+ Preconditions.checkArgument(secondaryLabels.size() == secondaryIntents.size());
+ Preconditions.checkArgument(secondaryIcons.size() == secondaryIntents.size());
+ Preconditions.checkArgument(secondaryOnClickListeners.size() == secondaryIntents.size());
mText = text;
- mIcons = icons;
- mLabels = labels;
- mIntents = intents;
- mOnClickListeners = onClickListeners;
+ mPrimaryIcon = primaryIcon;
+ mPrimaryLabel = primaryLabel;
+ mPrimaryIntent = primaryIntent;
+ mPrimaryOnClickListener = primaryOnClickListener;
+ mSecondaryIcons = secondaryIcons;
+ mSecondaryLabels = secondaryLabels;
+ mSecondaryIntents = secondaryIntents;
+ mSecondaryOnClickListeners = secondaryOnClickListeners;
mEntityConfidence = new EntityConfidence<>(entityConfidence);
mLogType = logType;
mVersionInfo = versionInfo;
@@ -161,106 +181,137 @@
}
/**
- * Returns the number of actions that are available to act on the classified text.
- * @see #getIntent(int)
- * @see #getLabel(int)
- * @see #getIcon(int)
- * @see #getOnClickListener(int)
+ * Returns the number of <i>secondary</i> actions that are available to act on the classified
+ * text.
+ *
+ * <p><strong>Note: </strong> that there may or may not be a <i>primary</i> action.
+ *
+ * @see #getSecondaryIntent(int)
+ * @see #getSecondaryLabel(int)
+ * @see #getSecondaryIcon(int)
+ * @see #getSecondaryOnClickListener(int)
*/
@IntRange(from = 0)
- public int getActionCount() {
- return mIntents.size();
+ public int getSecondaryActionsCount() {
+ return mSecondaryIntents.size();
}
/**
- * Returns one of the icons that maybe rendered on a widget used to act on the classified text.
+ * Returns one of the <i>secondary</i> icons that maybe rendered on a widget used to act on the
+ * classified text.
+ *
* @param index Index of the action to get the icon for.
+ *
* @throws IndexOutOfBoundsException if the specified index is out of range.
- * @see #getActionCount() for the number of entities available.
- * @see #getIntent(int)
- * @see #getLabel(int)
- * @see #getOnClickListener(int)
+ *
+ * @see #getSecondaryActionsCount() for the number of actions available.
+ * @see #getSecondaryIntent(int)
+ * @see #getSecondaryLabel(int)
+ * @see #getSecondaryOnClickListener(int)
+ * @see #getIcon()
*/
@Nullable
- public Drawable getIcon(int index) {
- return mIcons.get(index);
+ public Drawable getSecondaryIcon(int index) {
+ return mSecondaryIcons.get(index);
}
/**
- * Returns an icon for the default intent that may be rendered on a widget used to act on the
- * classified text.
+ * Returns an icon for the <i>primary</i> intent that may be rendered on a widget used to act
+ * on the classified text.
+ *
+ * @see #getSecondaryIcon(int)
*/
@Nullable
public Drawable getIcon() {
- return mIcons.isEmpty() ? null : mIcons.get(0);
+ return mPrimaryIcon;
}
/**
- * Returns one of the labels that may be rendered on a widget used to act on the classified
- * text.
+ * Returns one of the <i>secondary</i> labels that may be rendered on a widget used to act on
+ * the classified text.
+ *
* @param index Index of the action to get the label for.
+ *
* @throws IndexOutOfBoundsException if the specified index is out of range.
- * @see #getActionCount()
- * @see #getIntent(int)
- * @see #getIcon(int)
- * @see #getOnClickListener(int)
+ *
+ * @see #getSecondaryActionsCount()
+ * @see #getSecondaryIntent(int)
+ * @see #getSecondaryIcon(int)
+ * @see #getSecondaryOnClickListener(int)
+ * @see #getLabel()
*/
@Nullable
- public CharSequence getLabel(int index) {
- return mLabels.get(index);
+ public CharSequence getSecondaryLabel(int index) {
+ return mSecondaryLabels.get(index);
}
/**
- * Returns a label for the default intent that may be rendered on a widget used to act on the
- * classified text.
+ * Returns a label for the <i>primary</i> intent that may be rendered on a widget used to act
+ * on the classified text.
+ *
+ * @see #getSecondaryLabel(int)
*/
@Nullable
public CharSequence getLabel() {
- return mLabels.isEmpty() ? null : mLabels.get(0);
+ return mPrimaryLabel;
}
/**
- * Returns one of the intents that may be fired to act on the classified text.
+ * Returns one of the <i>secondary</i> intents that may be fired to act on the classified text.
+ *
* @param index Index of the action to get the intent for.
+ *
* @throws IndexOutOfBoundsException if the specified index is out of range.
- * @see #getActionCount()
- * @see #getLabel(int)
- * @see #getIcon(int)
- * @see #getOnClickListener(int)
+ *
+ * @see #getSecondaryActionsCount()
+ * @see #getSecondaryLabel(int)
+ * @see #getSecondaryIcon(int)
+ * @see #getSecondaryOnClickListener(int)
+ * @see #getIntent()
*/
@Nullable
- public Intent getIntent(int index) {
- return mIntents.get(index);
+ public Intent getSecondaryIntent(int index) {
+ return mSecondaryIntents.get(index);
}
/**
- * Returns the default intent that may be fired to act on the classified text.
+ * Returns the <i>primary</i> intent that may be fired to act on the classified text.
+ *
+ * @see #getSecondaryIntent(int)
*/
@Nullable
public Intent getIntent() {
- return mIntents.isEmpty() ? null : mIntents.get(0);
+ return mPrimaryIntent;
}
/**
- * Returns one of the OnClickListeners that may be triggered to act on the classified text.
+ * Returns one of the <i>secondary</i> OnClickListeners that may be triggered to act on the
+ * classified text.
+ *
* @param index Index of the action to get the click listener for.
+ *
* @throws IndexOutOfBoundsException if the specified index is out of range.
- * @see #getActionCount()
- * @see #getIntent(int)
- * @see #getLabel(int)
- * @see #getIcon(int)
+ *
+ * @see #getSecondaryActionsCount()
+ * @see #getSecondaryIntent(int)
+ * @see #getSecondaryLabel(int)
+ * @see #getSecondaryIcon(int)
+ * @see #getOnClickListener()
*/
@Nullable
- public OnClickListener getOnClickListener(int index) {
- return mOnClickListeners.get(index);
+ public OnClickListener getSecondaryOnClickListener(int index) {
+ return mSecondaryOnClickListeners.get(index);
}
/**
- * Returns the default OnClickListener that may be triggered to act on the classified text.
+ * Returns the <i>primary</i> OnClickListener that may be triggered to act on the classified
+ * text.
+ *
+ * @see #getSecondaryOnClickListener(int)
*/
@Nullable
public OnClickListener getOnClickListener() {
- return mOnClickListeners.isEmpty() ? null : mOnClickListeners.get(0);
+ return mPrimaryOnClickListener;
}
/**
@@ -282,9 +333,12 @@
@Override
public String toString() {
- return String.format(Locale.US,
- "TextClassification {text=%s, entities=%s, labels=%s, intents=%s}",
- mText, mEntityConfidence, mLabels, mIntents);
+ return String.format("TextClassification {"
+ + "text=%s, entities=%s, "
+ + "primaryLabel=%s, secondaryLabels=%s, "
+ + "primaryIntent=%s, secondaryIntents=%s}",
+ mText, mEntityConfidence,
+ mPrimaryLabel, mSecondaryLabels, mPrimaryIntent, mSecondaryIntents);
}
/**
@@ -303,15 +357,32 @@
/**
* Builder for building {@link TextClassification} objects.
+ *
+ * <p>e.g.
+ *
+ * <pre>{@code
+ * TextClassification classification = new TextClassification.Builder()
+ * .setText(classifiedText)
+ * .setEntityType(TextClassifier.TYPE_EMAIL, 0.9)
+ * .setEntityType(TextClassifier.TYPE_OTHER, 0.1)
+ * .setPrimaryAction(intent, label, icon, onClickListener)
+ * .addSecondaryAction(intent1, label1, icon1, onClickListener1)
+ * .addSecondaryAction(intent2, label2, icon2, onClickListener2)
+ * .build();
+ * }</pre>
*/
public static final class Builder {
@NonNull private String mText;
- @NonNull private final List<Drawable> mIcons = new ArrayList<>();
- @NonNull private final List<String> mLabels = new ArrayList<>();
- @NonNull private final List<Intent> mIntents = new ArrayList<>();
- @NonNull private final List<OnClickListener> mOnClickListeners = new ArrayList<>();
+ @NonNull private final List<Drawable> mSecondaryIcons = new ArrayList<>();
+ @NonNull private final List<String> mSecondaryLabels = new ArrayList<>();
+ @NonNull private final List<Intent> mSecondaryIntents = new ArrayList<>();
+ @NonNull private final List<OnClickListener> mSecondaryOnClickListeners = new ArrayList<>();
@NonNull private final Map<String, Float> mEntityConfidence = new ArrayMap<>();
+ @Nullable Drawable mPrimaryIcon;
+ @Nullable String mPrimaryLabel;
+ @Nullable Intent mPrimaryIntent;
+ @Nullable OnClickListener mPrimaryOnClickListener;
private int mLogType;
@NonNull private String mVersionInfo = "";
@@ -325,6 +396,8 @@
/**
* Sets an entity type for the classification result and assigns a confidence score.
+ * If a confidence score had already been set for the specified entity type, this will
+ * override that score.
*
* @param confidenceScore a value from 0 (low confidence) to 1 (high confidence).
* 0 implies the entity does not exist for the classified text.
@@ -338,57 +411,99 @@
}
/**
- * Adds an action that may be performed on the classified text. The label and icon are used
- * for rendering of widgets that offer the intent. Actions should be added in order of
- * priority and the first one will be treated as the default.
+ * Adds an <i>secondary</i> action that may be performed on the classified text.
+ * Secondary actions are in addition to the <i>primary</i> action which may or may not
+ * exist.
+ *
+ * <p>The label and icon are used for rendering of widgets that offer the intent.
+ * Actions should be added in order of priority.
+ *
+ * <p><stong>Note: </stong> If all input parameters are set to null, this method will be a
+ * no-op.
+ *
+ * @see #setPrimaryAction(Intent, String, Drawable, OnClickListener)
*/
- public Builder addAction(
- Intent intent, @Nullable String label, @Nullable Drawable icon,
+ public Builder addSecondaryAction(
+ @Nullable Intent intent, @Nullable String label, @Nullable Drawable icon,
@Nullable OnClickListener onClickListener) {
- mIntents.add(intent);
- mLabels.add(label);
- mIcons.add(icon);
- mOnClickListeners.add(onClickListener);
+ if (intent != null || label != null || icon != null || onClickListener != null) {
+ mSecondaryIntents.add(intent);
+ mSecondaryLabels.add(label);
+ mSecondaryIcons.add(icon);
+ mSecondaryOnClickListeners.add(onClickListener);
+ }
return this;
}
/**
- * Removes all actions.
+ * Removes all the <i>secondary</i> actions.
*/
- public Builder clearActions() {
- mIntents.clear();
- mOnClickListeners.clear();
- mLabels.clear();
- mIcons.clear();
+ public Builder clearSecondaryActions() {
+ mSecondaryIntents.clear();
+ mSecondaryOnClickListeners.clear();
+ mSecondaryLabels.clear();
+ mSecondaryIcons.clear();
return this;
}
/**
- * Sets the icon for the default action that may be rendered on a widget used to act on the
- * classified text.
+ * Sets the <i>primary</i> action that may be performed on the classified text. This is
+ * equivalent to calling {@code
+ * setIntent(intent).setLabel(label).setIcon(icon).setOnClickListener(onClickListener)}.
+ *
+ * <p><strong>Note: </strong>If all input parameters are null, there will be no
+ * <i>primary</i> action but there may still be <i>secondary</i> actions.
+ *
+ * @see #addSecondaryAction(Intent, String, Drawable, OnClickListener)
+ */
+ public Builder setPrimaryAction(
+ @Nullable Intent intent, @Nullable String label, @Nullable Drawable icon,
+ @Nullable OnClickListener onClickListener) {
+ return setIntent(intent).setLabel(label).setIcon(icon)
+ .setOnClickListener(onClickListener);
+ }
+
+ /**
+ * Sets the icon for the <i>primary</i> action that may be rendered on a widget used to act
+ * on the classified text.
+ *
+ * @see #setPrimaryAction(Intent, String, Drawable, OnClickListener)
*/
public Builder setIcon(@Nullable Drawable icon) {
- ensureDefaultActionAvailable();
- mIcons.set(0, icon);
+ mPrimaryIcon = icon;
return this;
}
/**
- * Sets the label for the default action that may be rendered on a widget used to act on the
- * classified text.
+ * Sets the label for the <i>primary</i> action that may be rendered on a widget used to
+ * act on the classified text.
+ *
+ * @see #setPrimaryAction(Intent, String, Drawable, OnClickListener)
*/
public Builder setLabel(@Nullable String label) {
- ensureDefaultActionAvailable();
- mLabels.set(0, label);
+ mPrimaryLabel = label;
return this;
}
/**
- * Sets the intent for the default action that may be fired to act on the classified text.
+ * Sets the intent for the <i>primary</i> action that may be fired to act on the classified
+ * text.
+ *
+ * @see #setPrimaryAction(Intent, String, Drawable, OnClickListener)
*/
public Builder setIntent(@Nullable Intent intent) {
- ensureDefaultActionAvailable();
- mIntents.set(0, intent);
+ mPrimaryIntent = intent;
+ return this;
+ }
+
+ /**
+ * Sets the OnClickListener for the <i>primary</i> action that may be triggered to act on
+ * the classified text.
+ *
+ * @see #setPrimaryAction(Intent, String, Drawable, OnClickListener)
+ */
+ public Builder setOnClickListener(@Nullable OnClickListener onClickListener) {
+ mPrimaryOnClickListener = onClickListener;
return this;
}
@@ -402,16 +517,6 @@
}
/**
- * Sets the OnClickListener for the default action that may be triggered to act on the
- * classified text.
- */
- public Builder setOnClickListener(@Nullable OnClickListener onClickListener) {
- ensureDefaultActionAvailable();
- mOnClickListeners.set(0, onClickListener);
- return this;
- }
-
- /**
* Sets information about the classifier model used to generate this TextClassification.
* @hide
*/
@@ -421,22 +526,16 @@
}
/**
- * Ensures that we have storage for the default action.
- */
- private void ensureDefaultActionAvailable() {
- if (mIntents.isEmpty()) mIntents.add(null);
- if (mLabels.isEmpty()) mLabels.add(null);
- if (mIcons.isEmpty()) mIcons.add(null);
- if (mOnClickListeners.isEmpty()) mOnClickListeners.add(null);
- }
-
- /**
* Builds and returns a {@link TextClassification} object.
*/
public TextClassification build() {
return new TextClassification(
- mText, mIcons, mLabels, mIntents, mOnClickListeners, mEntityConfidence,
- mLogType, mVersionInfo);
+ mText,
+ mPrimaryIcon, mPrimaryLabel,
+ mPrimaryIntent, mPrimaryOnClickListener,
+ mSecondaryIcons, mSecondaryLabels,
+ mSecondaryIntents, mSecondaryOnClickListeners,
+ mEntityConfidence, mLogType, mVersionInfo);
}
}
diff --git a/core/java/android/view/textclassifier/TextClassifierImpl.java b/core/java/android/view/textclassifier/TextClassifierImpl.java
index df5e35f..d8ea89a 100644
--- a/core/java/android/view/textclassifier/TextClassifierImpl.java
+++ b/core/java/android/view/textclassifier/TextClassifierImpl.java
@@ -32,6 +32,7 @@
import android.provider.Settings;
import android.text.util.Linkify;
import android.util.Patterns;
+import android.view.View.OnClickListener;
import android.widget.TextViewMetrics;
import com.android.internal.annotations.GuardedBy;
@@ -383,48 +384,54 @@
final String type = getHighestScoringType(classifications);
builder.setLogType(IntentFactory.getLogType(type));
- final List<Intent> intents = IntentFactory.create(mContext, type, text.toString());
- for (Intent intent : intents) {
- extendClassificationWithIntent(intent, builder);
- }
+ addActions(builder, IntentFactory.create(mContext, type, text.toString()));
return builder.setVersionInfo(getVersionInfo()).build();
}
- /** Extends the classification with the intent if it can be resolved. */
- private void extendClassificationWithIntent(Intent intent, TextClassification.Builder builder) {
- final PackageManager pm;
- final ResolveInfo resolveInfo;
- if (intent != null) {
- pm = mContext.getPackageManager();
- resolveInfo = pm.resolveActivity(intent, 0);
- } else {
- pm = null;
- resolveInfo = null;
- }
- if (resolveInfo != null && resolveInfo.activityInfo != null) {
- final String packageName = resolveInfo.activityInfo.packageName;
- CharSequence label;
- Drawable icon;
- if ("android".equals(packageName)) {
- // Requires the chooser to find an activity to handle the intent.
- label = IntentFactory.getLabel(mContext, intent);
- icon = null;
+ /** Extends the classification with the intents that can be resolved. */
+ private void addActions(
+ TextClassification.Builder builder, List<Intent> intents) {
+ final PackageManager pm = mContext.getPackageManager();
+ final int size = intents.size();
+ for (int i = 0; i < size; i++) {
+ final Intent intent = intents.get(i);
+ final ResolveInfo resolveInfo;
+ if (intent != null) {
+ resolveInfo = pm.resolveActivity(intent, 0);
} else {
- // A default activity will handle the intent.
- intent.setComponent(new ComponentName(packageName, resolveInfo.activityInfo.name));
- icon = resolveInfo.activityInfo.loadIcon(pm);
- if (icon == null) {
- icon = resolveInfo.loadIcon(pm);
+ resolveInfo = null;
+ }
+ if (resolveInfo != null && resolveInfo.activityInfo != null) {
+ final String packageName = resolveInfo.activityInfo.packageName;
+ CharSequence label;
+ Drawable icon;
+ if ("android".equals(packageName)) {
+ // Requires the chooser to find an activity to handle the intent.
+ label = IntentFactory.getLabel(mContext, intent);
+ icon = null;
+ } else {
+ // A default activity will handle the intent.
+ intent.setComponent(
+ new ComponentName(packageName, resolveInfo.activityInfo.name));
+ icon = resolveInfo.activityInfo.loadIcon(pm);
+ if (icon == null) {
+ icon = resolveInfo.loadIcon(pm);
+ }
+ label = resolveInfo.activityInfo.loadLabel(pm);
+ if (label == null) {
+ label = resolveInfo.loadLabel(pm);
+ }
}
- label = resolveInfo.activityInfo.loadLabel(pm);
- if (label == null) {
- label = resolveInfo.loadLabel(pm);
+ final String labelString = (label != null) ? label.toString() : null;
+ final OnClickListener onClickListener =
+ TextClassification.createStartActivityOnClickListener(mContext, intent);
+ if (i == 0) {
+ builder.setPrimaryAction(intent, labelString, icon, onClickListener);
+ } else {
+ builder.addSecondaryAction(intent, labelString, icon, onClickListener);
}
}
- builder.addAction(
- intent, label != null ? label.toString() : null, icon,
- TextClassification.createStartActivityOnClickListener(mContext, intent));
}
}
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 05cba1e..d4bac98 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -3982,31 +3982,43 @@
}
final TextClassification textClassification =
getSelectionActionModeHelper().getTextClassification();
- final int count = textClassification != null ? textClassification.getActionCount() : 0;
+ if (textClassification == null) {
+ return;
+ }
+ if (isValidAssistMenuItem(
+ textClassification.getIcon(),
+ textClassification.getLabel(),
+ textClassification.getOnClickListener(),
+ textClassification.getIntent())) {
+ final MenuItem item = menu.add(
+ TextView.ID_ASSIST, TextView.ID_ASSIST, MENU_ITEM_ORDER_ASSIST,
+ textClassification.getLabel())
+ .setIcon(textClassification.getIcon())
+ .setIntent(textClassification.getIntent());
+ item.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
+ mAssistClickHandlers.put(item, textClassification.getOnClickListener());
+ mMetricsLogger.write(
+ new LogMaker(MetricsEvent.TEXT_SELECTION_MENU_ITEM_ASSIST)
+ .setType(MetricsEvent.TYPE_OPEN)
+ .setSubtype(textClassification.getLogType()));
+ }
+ final int count = textClassification.getSecondaryActionsCount();
for (int i = 0; i < count; i++) {
- if (!isValidAssistMenuItem(i)) {
+ if (!isValidAssistMenuItem(
+ textClassification.getSecondaryIcon(i),
+ textClassification.getSecondaryLabel(i),
+ textClassification.getSecondaryOnClickListener(i),
+ textClassification.getSecondaryIntent(i))) {
continue;
}
- final int groupId = TextView.ID_ASSIST;
- final int order = (i == 0)
- ? MENU_ITEM_ORDER_ASSIST
- : MENU_ITEM_ORDER_SECONDARY_ASSIST_ACTIONS_START + i;
- final int id = (i == 0) ? TextView.ID_ASSIST : Menu.NONE;
- final int showAsFlag = (i == 0)
- ? MenuItem.SHOW_AS_ACTION_ALWAYS
- : MenuItem.SHOW_AS_ACTION_NEVER;
+ final int order = MENU_ITEM_ORDER_SECONDARY_ASSIST_ACTIONS_START + i;
final MenuItem item = menu.add(
- groupId, id, order, textClassification.getLabel(i))
- .setIcon(textClassification.getIcon(i))
- .setIntent(textClassification.getIntent(i));
- item.setShowAsAction(showAsFlag);
- mAssistClickHandlers.put(item, textClassification.getOnClickListener(i));
- if (id == TextView.ID_ASSIST) {
- mMetricsLogger.write(
- new LogMaker(MetricsEvent.TEXT_SELECTION_MENU_ITEM_ASSIST)
- .setType(MetricsEvent.TYPE_OPEN)
- .setSubtype(textClassification.getLogType()));
- }
+ TextView.ID_ASSIST, Menu.NONE, order,
+ textClassification.getSecondaryLabel(i))
+ .setIcon(textClassification.getSecondaryIcon(i))
+ .setIntent(textClassification.getSecondaryIntent(i));
+ item.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
+ mAssistClickHandlers.put(item, textClassification.getSecondaryOnClickListener(i));
}
}
@@ -4022,18 +4034,9 @@
}
}
- private boolean isValidAssistMenuItem(int index) {
- final TextClassification textClassification =
- getSelectionActionModeHelper().getTextClassification();
- if (!mTextView.isDeviceProvisioned() || textClassification == null
- || index < 0 || index >= textClassification.getActionCount()) {
- return false;
- }
- final Drawable icon = textClassification.getIcon(index);
- final CharSequence label = textClassification.getLabel(index);
+ private boolean isValidAssistMenuItem(
+ Drawable icon, CharSequence label, OnClickListener onClick, Intent intent) {
final boolean hasUi = icon != null || !TextUtils.isEmpty(label);
- final OnClickListener onClick = textClassification.getOnClickListener(index);
- final Intent intent = textClassification.getIntent(index);
final boolean hasAction = onClick != null || isSupportedIntent(intent);
return hasUi && hasAction;
}
@@ -6594,7 +6597,7 @@
Editor.MENU_ITEM_ORDER_PROCESS_TEXT_INTENT_ACTIONS_START + i,
getLabel(resolveInfo))
.setIntent(createProcessTextIntentForResolveInfo(resolveInfo))
- .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
+ .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
}
}
diff --git a/core/java/com/android/internal/widget/RecyclerView.java b/core/java/com/android/internal/widget/RecyclerView.java
index 408a4e9..7abc76a 100644
--- a/core/java/com/android/internal/widget/RecyclerView.java
+++ b/core/java/com/android/internal/widget/RecyclerView.java
@@ -9556,7 +9556,7 @@
if (vScroll == 0 && hScroll == 0) {
return false;
}
- mRecyclerView.smoothScrollBy(hScroll, vScroll);
+ mRecyclerView.scrollBy(hScroll, vScroll);
return true;
}
diff --git a/core/jni/android/graphics/YuvToJpegEncoder.cpp b/core/jni/android/graphics/YuvToJpegEncoder.cpp
index 31567f7..5eecd9c 100644
--- a/core/jni/android/graphics/YuvToJpegEncoder.cpp
+++ b/core/jni/android/graphics/YuvToJpegEncoder.cpp
@@ -26,12 +26,13 @@
bool YuvToJpegEncoder::encode(SkWStream* stream, void* inYuv, int width,
int height, int* offsets, int jpegQuality) {
jpeg_compress_struct cinfo;
- skjpeg_error_mgr sk_err;
+ jpeg_error_mgr err;
skjpeg_destination_mgr sk_wstream(stream);
- cinfo.err = jpeg_std_error(&sk_err);
- sk_err.error_exit = skjpeg_error_exit;
- if (setjmp(sk_err.fJmpBuf)) {
+ cinfo.err = jpeg_std_error(&err);
+ err.error_exit = skjpeg_error_exit;
+ jmp_buf jmp;
+ if (setjmp(jmp)) {
return false;
}
jpeg_create_compress(&cinfo);
diff --git a/core/jni/android_view_Surface.cpp b/core/jni/android_view_Surface.cpp
index 3ad4da6..421e0de 100644
--- a/core/jni/android_view_Surface.cpp
+++ b/core/jni/android_view_Surface.cpp
@@ -517,23 +517,7 @@
jobject graphicBuffer) {
Surface* surface = reinterpret_cast<Surface*>(nativeObject);
sp<GraphicBuffer> bp = graphicBufferForJavaObject(env, graphicBuffer);
- if (bp == nullptr) {
- return BAD_VALUE;
- }
- int err = ((ANativeWindow*)surface)->perform(surface, NATIVE_WINDOW_API_CONNECT,
- NATIVE_WINDOW_API_CPU);
- if (err != OK) {
- return err;
- }
- err = surface->attachBuffer(bp->getNativeBuffer());
- if (err != OK) {
- return err;
- }
- err = ((ANativeWindow*)surface)->queueBuffer(surface, bp->getNativeBuffer(), -1);
- if (err != OK) {
- return err;
- }
- err = surface->disconnect(NATIVE_WINDOW_API_CPU);
+ int err = Surface::attachAndQueueBuffer(surface, bp);
return err;
}
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 5e8eece..5e5d59b 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -167,7 +167,7 @@
maxLayer = INT32_MAX;
}
sp<GraphicBuffer> buffer;
- status_t res = ScreenshotClient::captureToBuffer(displayToken,
+ status_t res = ScreenshotClient::capture(displayToken,
sourceCrop, width, height, minLayer, maxLayer, useIdentityTransform,
rotation, &buffer);
if (res != NO_ERROR) {
@@ -201,15 +201,18 @@
maxLayer = INT32_MAX;
}
- res = screenshot->update(displayToken, sourceCrop, width, height,
- minLayer, maxLayer, useIdentityTransform, static_cast<uint32_t>(rotation));
+ sp<GraphicBuffer> buffer;
+ res = ScreenshotClient::capture(displayToken, sourceCrop, width, height,
+ minLayer, maxLayer, useIdentityTransform, static_cast<uint32_t>(rotation), &buffer);
if (res != NO_ERROR) {
return NULL;
}
SkColorType colorType;
SkAlphaType alphaType;
- switch (screenshot->getFormat()) {
+
+ PixelFormat format = buffer->getPixelFormat();
+ switch (format) {
case PIXEL_FORMAT_RGBX_8888: {
colorType = kRGBA_8888_SkColorType;
alphaType = kOpaque_SkAlphaType;
@@ -235,66 +238,20 @@
}
}
- sk_sp<SkColorSpace> colorSpace;
- if (screenshot->getDataSpace() == HAL_DATASPACE_DISPLAY_P3) {
- colorSpace = SkColorSpace::MakeRGB(
- SkColorSpace::kSRGB_RenderTargetGamma, SkColorSpace::kDCIP3_D65_Gamut);
- } else {
- colorSpace = SkColorSpace::MakeSRGB();
- }
+ SkImageInfo info = SkImageInfo::Make(buffer->getWidth(), buffer->getHeight(),
+ colorType, alphaType,
+ SkColorSpace::MakeSRGB());
- SkImageInfo screenshotInfo = SkImageInfo::Make(screenshot->getWidth(),
- screenshot->getHeight(),
- colorType,
- alphaType,
- colorSpace);
-
- const size_t rowBytes =
- screenshot->getStride() * android::bytesPerPixel(screenshot->getFormat());
-
- if (!screenshotInfo.width() || !screenshotInfo.height()) {
- return NULL;
- }
-
- auto bitmap = new Bitmap(
- (void*) screenshot->getPixels(), (void*) screenshot.get(), DeleteScreenshot,
- screenshotInfo, rowBytes);
- screenshot.release();
- bitmap->setImmutable();
- return bitmap::createBitmap(env, bitmap,
- android::bitmap::kBitmapCreateFlag_Premultiplied, NULL);
+ auto bitmap = sk_sp<Bitmap>(new Bitmap(buffer.get(), info));
+ return bitmap::createBitmap(env, bitmap.release(),
+ android::bitmap::kBitmapCreateFlag_Premultiplied, NULL);
}
static void nativeScreenshot(JNIEnv* env, jclass clazz, jobject displayTokenObj,
jobject surfaceObj, jobject sourceCropObj, jint width, jint height,
jint minLayer, jint maxLayer, bool allLayers, bool useIdentityTransform) {
sp<IBinder> displayToken = ibinderForJavaObject(env, displayTokenObj);
- if (displayToken != NULL) {
- sp<Surface> consumer = android_view_Surface_getSurface(env, surfaceObj);
- if (consumer != NULL) {
- int left = env->GetIntField(sourceCropObj, gRectClassInfo.left);
- int top = env->GetIntField(sourceCropObj, gRectClassInfo.top);
- int right = env->GetIntField(sourceCropObj, gRectClassInfo.right);
- int bottom = env->GetIntField(sourceCropObj, gRectClassInfo.bottom);
- Rect sourceCrop(left, top, right, bottom);
-
- if (allLayers) {
- minLayer = INT32_MIN;
- maxLayer = INT32_MAX;
- }
- ScreenshotClient::capture(displayToken,
- consumer->getIGraphicBufferProducer(), sourceCrop,
- width, height, minLayer, maxLayer,
- useIdentityTransform);
- }
- }
-}
-
-static void nativeCaptureLayers(JNIEnv* env, jclass clazz, jobject layerHandleToken,
- jobject surfaceObj, jobject sourceCropObj, jfloat frameScale) {
-
- sp<IBinder> layerHandle = ibinderForJavaObject(env, layerHandleToken);
- if (layerHandle == NULL) {
+ if (displayToken == NULL) {
return;
}
@@ -308,11 +265,19 @@
sourceCrop = rectFromObj(env, sourceCropObj);
}
- ScreenshotClient::captureLayers(layerHandle, consumer->getIGraphicBufferProducer(), sourceCrop,
- frameScale);
+ if (allLayers) {
+ minLayer = INT32_MIN;
+ maxLayer = INT32_MAX;
+ }
+
+ sp<GraphicBuffer> buffer;
+ ScreenshotClient::capture(displayToken, sourceCrop, width, height, minLayer, maxLayer,
+ useIdentityTransform, 0, &buffer);
+
+ Surface::attachAndQueueBuffer(consumer.get(), buffer);
}
-static jobject nativeCaptureLayersToBuffer(JNIEnv* env, jclass clazz, jobject layerHandleToken,
+static jobject nativeCaptureLayers(JNIEnv* env, jclass clazz, jobject layerHandleToken,
jobject sourceCropObj, jfloat frameScale) {
sp<IBinder> layerHandle = ibinderForJavaObject(env, layerHandleToken);
@@ -326,8 +291,7 @@
}
sp<GraphicBuffer> buffer;
- status_t res = ScreenshotClient::captureLayersToBuffer(layerHandle, sourceCrop, frameScale,
- &buffer);
+ status_t res = ScreenshotClient::captureLayers(layerHandle, sourceCrop, frameScale, &buffer);
if (res != NO_ERROR) {
return NULL;
}
@@ -1039,10 +1003,8 @@
{"nativeScreenshotToBuffer",
"(Landroid/os/IBinder;Landroid/graphics/Rect;IIIIZZI)Landroid/graphics/GraphicBuffer;",
(void*)nativeScreenshotToBuffer },
- {"nativeCaptureLayers", "(Landroid/os/IBinder;Landroid/view/Surface;Landroid/graphics/Rect;F)V",
- (void*)nativeCaptureLayers },
{"nativeCaptureLayers", "(Landroid/os/IBinder;Landroid/graphics/Rect;F)Landroid/graphics/GraphicBuffer;",
- (void*)nativeCaptureLayersToBuffer },
+ (void*)nativeCaptureLayers },
};
int register_android_view_SurfaceControl(JNIEnv* env)
diff --git a/core/jni/android_view_ThreadedRenderer.cpp b/core/jni/android_view_ThreadedRenderer.cpp
index 870a0c2..519a885 100644
--- a/core/jni/android_view_ThreadedRenderer.cpp
+++ b/core/jni/android_view_ThreadedRenderer.cpp
@@ -1043,7 +1043,22 @@
{ "nSetHighContrastText", "(Z)V", (void*)android_view_ThreadedRenderer_setHighContrastText },
};
+static JavaVM* mJvm = nullptr;
+
+static void attachRenderThreadToJvm() {
+ LOG_ALWAYS_FATAL_IF(!mJvm, "No jvm but we set the hook??");
+
+ JavaVMAttachArgs args;
+ args.version = JNI_VERSION_1_4;
+ args.name = (char*) "RenderThread";
+ args.group = NULL;
+ JNIEnv* env;
+ mJvm->AttachCurrentThreadAsDaemon(&env, (void*) &args);
+}
+
int register_android_view_ThreadedRenderer(JNIEnv* env) {
+ env->GetJavaVM(&mJvm);
+ RenderThread::setOnStartHook(&attachRenderThreadToJvm);
jclass observerClass = FindClassOrDie(env, "android/view/FrameMetricsObserver");
gFrameMetricsObserverClassInfo.frameMetrics = GetFieldIDOrDie(
env, observerClass, "mFrameMetrics", "Landroid/view/FrameMetrics;");
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index d944e4b..feb7800 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2932,8 +2932,8 @@
<!-- com.android.server.autofill -->
<java-symbol type="layout" name="autofill_save"/>
<java-symbol type="layout" name="autofill_dataset_picker"/>
- <java-symbol type="id" name="autofill_dataset_picker"/>
<java-symbol type="id" name="autofill_dataset_list"/>
+ <java-symbol type="id" name="autofill_dataset_picker"/>
<java-symbol type="id" name="autofill" />
<java-symbol type="id" name="autofill_save_custom_subtitle" />
<java-symbol type="id" name="autofill_save_icon" />
diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index 4e0e5ed..0367275 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -418,7 +418,13 @@
private static final Set<String> BACKUP_BLACKLISTED_SECURE_SETTINGS =
newHashSet(
Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE,
+ // TODO(b/67867469): Move autofill settings below to
+ // BACKUP_BLACKLISTED_SYSTEM_SETTINGS once feature is moved out of experimental
Settings.Secure.AUTOFILL_FEATURE_FIELD_DETECTION,
+ Settings.Secure.AUTOFILL_USER_DATA_MAX_FIELD_CLASSIFICATION_IDS_SIZE,
+ Settings.Secure.AUTOFILL_USER_DATA_MAX_USER_DATA_SIZE,
+ Settings.Secure.AUTOFILL_USER_DATA_MAX_VALUE_LENGTH,
+ Settings.Secure.AUTOFILL_USER_DATA_MIN_VALUE_LENGTH,
Settings.Secure.ALLOWED_GEOLOCATION_ORIGINS,
Settings.Secure.ALWAYS_ON_VPN_APP,
Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN,
diff --git a/core/tests/coretests/src/android/widget/TextViewTest.java b/core/tests/coretests/src/android/widget/TextViewTest.java
index d31da71..7875c17 100644
--- a/core/tests/coretests/src/android/widget/TextViewTest.java
+++ b/core/tests/coretests/src/android/widget/TextViewTest.java
@@ -204,7 +204,7 @@
int lineCount = layout.getLineCount();
boolean hyphenationHappend = false;
for (int i = 0; i < lineCount; ++i) {
- if (layout.getHyphen(i) != 1) {
+ if (layout.getHyphen(i) == 0) {
continue; // Hyphantion does not happen.
}
hyphenationHappend = true;
diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp
index 94a05b2..415d3e3 100644
--- a/libs/androidfw/AssetManager2.cpp
+++ b/libs/androidfw/AssetManager2.cpp
@@ -299,10 +299,9 @@
const PackageGroup& package_group = package_groups_[idx];
const size_t package_count = package_group.packages_.size();
+ FindEntryResult current_entry;
for (size_t i = 0; i < package_count; i++) {
const LoadedPackage* loaded_package = package_group.packages_[i];
-
- FindEntryResult current_entry;
if (!loaded_package->FindEntry(type_idx, entry_id, *desired_config, ¤t_entry)) {
continue;
}
@@ -394,7 +393,7 @@
return kInvalidCookie;
}
- if (dtohl(entry.entry->flags) & ResTable_entry::FLAG_COMPLEX) {
+ if (dtohs(entry.entry->flags) & ResTable_entry::FLAG_COMPLEX) {
if (!may_be_bag) {
LOG(ERROR) << base::StringPrintf("Resource %08x is a complex map type.", resid);
return kInvalidCookie;
diff --git a/libs/androidfw/LoadedArsc.cpp b/libs/androidfw/LoadedArsc.cpp
index c361ea2..28548e2 100644
--- a/libs/androidfw/LoadedArsc.cpp
+++ b/libs/androidfw/LoadedArsc.cpp
@@ -18,6 +18,7 @@
#include "androidfw/LoadedArsc.h"
+#include <algorithm>
#include <cstddef>
#include <limits>
@@ -128,9 +129,14 @@
// Precondition: The header passed in has already been verified, so reading any fields and trusting
// the ResChunk_header is safe.
static bool VerifyResTableType(const ResTable_type* header) {
+ if (header->id == 0) {
+ LOG(ERROR) << "RES_TABLE_TYPE_TYPE has invalid ID 0.";
+ return false;
+ }
+
const size_t entry_count = dtohl(header->entryCount);
if (entry_count > std::numeric_limits<uint16_t>::max()) {
- LOG(ERROR) << "Too many entries in RES_TABLE_TYPE_TYPE.";
+ LOG(ERROR) << "RES_TABLE_TYPE_TYPE has too many entries (" << entry_count << ").";
return false;
}
@@ -140,17 +146,17 @@
const size_t offsets_length = sizeof(uint32_t) * entry_count;
if (offsets_offset > entries_offset || entries_offset - offsets_offset < offsets_length) {
- LOG(ERROR) << "Entry offsets overlap actual entry data.";
+ LOG(ERROR) << "RES_TABLE_TYPE_TYPE entry offsets overlap actual entry data.";
return false;
}
if (entries_offset > dtohl(header->header.size)) {
- LOG(ERROR) << "Entry offsets extend beyond chunk.";
+ LOG(ERROR) << "RES_TABLE_TYPE_TYPE entry offsets extend beyond chunk.";
return false;
}
if (entries_offset & 0x03) {
- LOG(ERROR) << "Entries start at unaligned address.";
+ LOG(ERROR) << "RES_TABLE_TYPE_TYPE entries start at unaligned address.";
return false;
}
return true;
@@ -235,7 +241,6 @@
return true;
}
-template <bool Verified>
bool LoadedPackage::FindEntry(const TypeSpecPtr& type_spec_ptr, uint16_t entry_idx,
const ResTable_config& config, FindEntryResult* out_entry) const {
const ResTable_config* best_config = nullptr;
@@ -244,31 +249,56 @@
for (uint32_t i = 0; i < type_spec_ptr->type_count; i++) {
const Type* type = &type_spec_ptr->types[i];
+ const ResTable_type* type_chunk = type->type;
if (type->configuration.match(config) &&
(best_config == nullptr || type->configuration.isBetterThan(*best_config, &config))) {
// The configuration matches and is better than the previous selection.
// Find the entry value if it exists for this configuration.
- const size_t entry_count = dtohl(type->type->entryCount);
- const size_t offsets_offset = dtohs(type->type->header.headerSize);
- if (entry_idx < entry_count) {
- // If the package hasn't been verified, do bounds checking.
- if (!Verified) {
- if (!VerifyResTableType(type->type)) {
- continue;
- }
+ const size_t entry_count = dtohl(type_chunk->entryCount);
+ const size_t offsets_offset = dtohs(type_chunk->header.headerSize);
+
+ // Check if there is the desired entry in this type.
+
+ if (type_chunk->flags & ResTable_type::FLAG_SPARSE) {
+ // This is encoded as a sparse map, so perform a binary search.
+ const ResTable_sparseTypeEntry* sparse_indices =
+ reinterpret_cast<const ResTable_sparseTypeEntry*>(
+ reinterpret_cast<const uint8_t*>(type_chunk) + offsets_offset);
+ const ResTable_sparseTypeEntry* sparse_indices_end = sparse_indices + entry_count;
+ const ResTable_sparseTypeEntry* result =
+ std::lower_bound(sparse_indices, sparse_indices_end, entry_idx,
+ [](const ResTable_sparseTypeEntry& entry, uint16_t entry_idx) {
+ return dtohs(entry.idx) < entry_idx;
+ });
+
+ if (result == sparse_indices_end || dtohs(result->idx) != entry_idx) {
+ // No entry found.
+ continue;
+ }
+
+ // Extract the offset from the entry. Each offset must be a multiple of 4 so we store it as
+ // the real offset divided by 4.
+ best_offset = uint32_t{dtohs(result->offset)} * 4u;
+ } else {
+ if (entry_idx >= entry_count) {
+ // This entry cannot be here.
+ continue;
}
const uint32_t* entry_offsets = reinterpret_cast<const uint32_t*>(
- reinterpret_cast<const uint8_t*>(type->type) + offsets_offset);
+ reinterpret_cast<const uint8_t*>(type_chunk) + offsets_offset);
const uint32_t offset = dtohl(entry_offsets[entry_idx]);
- if (offset != ResTable_type::NO_ENTRY) {
- // There is an entry for this resource, record it.
- best_config = &type->configuration;
- best_type = type->type;
- best_offset = offset;
+ if (offset == ResTable_type::NO_ENTRY) {
+ continue;
}
+
+ // There is an entry for this resource, record it.
+ best_offset = offset;
}
+
+ best_config = &type->configuration;
+ best_type = type_chunk;
}
}
@@ -276,10 +306,8 @@
return false;
}
- if (!Verified) {
- if (!VerifyResTableEntry(best_type, best_offset, entry_idx)) {
- return false;
- }
+ if (UNLIKELY(!VerifyResTableEntry(best_type, best_offset, entry_idx))) {
+ return false;
}
const ResTable_entry* best_entry = reinterpret_cast<const ResTable_entry*>(
@@ -301,7 +329,7 @@
// If the type IDs are offset in this package, we need to take that into account when searching
// for a type.
const TypeSpecPtr& ptr = type_specs_[type_idx - type_id_offset_];
- if (ptr == nullptr) {
+ if (UNLIKELY(ptr == nullptr)) {
return false;
}
@@ -312,41 +340,7 @@
return false;
}
}
-
- // Don't bother checking if the entry ID is larger than the number of entries.
- if (entry_idx >= dtohl(ptr->type_spec->entryCount)) {
- return false;
- }
-
- if (verified_) {
- return FindEntry<true>(ptr, entry_idx, config, out_entry);
- }
- return FindEntry<false>(ptr, entry_idx, config, out_entry);
-}
-
-static bool VerifyType(const Chunk& chunk) {
- ATRACE_CALL();
- const ResTable_type* header = chunk.header<ResTable_type, kResTableTypeMinSize>();
-
- if (!VerifyResTableType(header)) {
- return false;
- }
-
- const size_t entry_count = dtohl(header->entryCount);
- const size_t offsets_offset = chunk.header_size();
-
- // Check each entry offset.
- const uint32_t* offsets =
- reinterpret_cast<const uint32_t*>(reinterpret_cast<const uint8_t*>(header) + offsets_offset);
- for (size_t i = 0; i < entry_count; i++) {
- uint32_t offset = dtohl(offsets[i]);
- if (offset != ResTable_type::NO_ENTRY) {
- if (!VerifyResTableEntry(header, offset, i)) {
- return false;
- }
- }
- }
- return true;
+ return FindEntry(ptr, entry_idx, config, out_entry);
}
void LoadedPackage::CollectConfigurations(bool exclude_mipmap,
@@ -461,7 +455,7 @@
sizeof(ResTable_package) - sizeof(ResTable_package::typeIdOffset);
const ResTable_package* header = chunk.header<ResTable_package, kMinPackageSize>();
if (header == nullptr) {
- LOG(ERROR) << "Chunk RES_TABLE_PACKAGE_TYPE is too small.";
+ LOG(ERROR) << "RES_TABLE_PACKAGE_TYPE too small.";
return {};
}
@@ -483,7 +477,7 @@
if (header->header.headerSize >= sizeof(ResTable_package)) {
uint32_t type_id_offset = dtohl(header->typeIdOffset);
if (type_id_offset > std::numeric_limits<uint8_t>::max()) {
- LOG(ERROR) << "Type ID offset in RES_TABLE_PACKAGE_TYPE is too large.";
+ LOG(ERROR) << "RES_TABLE_PACKAGE_TYPE type ID offset too large.";
return {};
}
loaded_package->type_id_offset_ = static_cast<int>(type_id_offset);
@@ -514,7 +508,7 @@
status_t err = loaded_package->type_string_pool_.setTo(
child_chunk.header<ResStringPool_header>(), child_chunk.size());
if (err != NO_ERROR) {
- LOG(ERROR) << "Corrupt package type string pool.";
+ LOG(ERROR) << "RES_STRING_POOL_TYPE for types corrupt.";
return {};
}
} else if (pool_address == header_address + dtohl(header->keyStrings)) {
@@ -522,11 +516,11 @@
status_t err = loaded_package->key_string_pool_.setTo(
child_chunk.header<ResStringPool_header>(), child_chunk.size());
if (err != NO_ERROR) {
- LOG(ERROR) << "Corrupt package key string pool.";
+ LOG(ERROR) << "RES_STRING_POOL_TYPE for keys corrupt.";
return {};
}
} else {
- LOG(WARNING) << "Too many string pool chunks found in package.";
+ LOG(WARNING) << "Too many RES_STRING_POOL_TYPEs found in RES_TABLE_PACKAGE_TYPE.";
}
} break;
@@ -557,18 +551,18 @@
const ResTable_typeSpec* type_spec = child_chunk.header<ResTable_typeSpec>();
if (type_spec == nullptr) {
- LOG(ERROR) << "Chunk RES_TABLE_TYPE_SPEC_TYPE is too small.";
+ LOG(ERROR) << "RES_TABLE_TYPE_SPEC_TYPE too small.";
return {};
}
if (type_spec->id == 0) {
- LOG(ERROR) << "Chunk RES_TABLE_TYPE_SPEC_TYPE has invalid ID 0.";
+ LOG(ERROR) << "RES_TABLE_TYPE_SPEC_TYPE has invalid ID 0.";
return {};
}
if (loaded_package->type_id_offset_ + static_cast<int>(type_spec->id) >
std::numeric_limits<uint8_t>::max()) {
- LOG(ERROR) << "Chunk RES_TABLE_TYPE_SPEC_TYPE has out of range ID.";
+ LOG(ERROR) << "RES_TABLE_TYPE_SPEC_TYPE has out of range ID.";
return {};
}
@@ -580,12 +574,12 @@
// There can only be 2^16 entries in a type, because that is the ID
// space for entries (EEEE) in the resource ID 0xPPTTEEEE.
if (entry_count > std::numeric_limits<uint16_t>::max()) {
- LOG(ERROR) << "Too many entries in RES_TABLE_TYPE_SPEC_TYPE: " << entry_count << ".";
+ LOG(ERROR) << "RES_TABLE_TYPE_SPEC_TYPE has too many entries (" << entry_count << ").";
return {};
}
if (entry_count * sizeof(uint32_t) > chunk.data_size()) {
- LOG(ERROR) << "Chunk too small to hold entries in RES_TABLE_TYPE_SPEC_TYPE.";
+ LOG(ERROR) << "RES_TABLE_TYPE_SPEC_TYPE too small to hold entries.";
return {};
}
@@ -604,41 +598,32 @@
case RES_TABLE_TYPE_TYPE: {
const ResTable_type* type = child_chunk.header<ResTable_type, kResTableTypeMinSize>();
if (type == nullptr) {
- LOG(ERROR) << "Chunk RES_TABLE_TYPE_TYPE is too small.";
+ LOG(ERROR) << "RES_TABLE_TYPE_TYPE too small.";
return {};
}
- if (type->id == 0) {
- LOG(ERROR) << "Chunk RES_TABLE_TYPE_TYPE has invalid ID 0.";
+ if (!VerifyResTableType(type)) {
return {};
}
// Type chunks must be preceded by their TypeSpec chunks.
if (!types_builder || type->id - 1 != last_type_idx) {
- LOG(ERROR) << "Found RES_TABLE_TYPE_TYPE chunk without RES_TABLE_TYPE_SPEC_TYPE.";
+ LOG(ERROR) << "RES_TABLE_TYPE_TYPE found without preceding RES_TABLE_TYPE_SPEC_TYPE.";
return {};
}
- // Only verify the type if we haven't already failed verification.
- if (loaded_package->verified_) {
- if (!VerifyType(child_chunk)) {
- LOG(WARNING) << "Package failed verification, resource retrieval may be slower";
- loaded_package->verified_ = false;
- }
- }
-
types_builder->AddType(type);
} break;
case RES_TABLE_LIBRARY_TYPE: {
const ResTable_lib_header* lib = child_chunk.header<ResTable_lib_header>();
if (lib == nullptr) {
- LOG(ERROR) << "Chunk RES_TABLE_LIBRARY_TYPE is too small.";
+ LOG(ERROR) << "RES_TABLE_LIBRARY_TYPE too small.";
return {};
}
if (child_chunk.data_size() / sizeof(ResTable_lib_entry) < dtohl(lib->count)) {
- LOG(ERROR) << "Chunk too small to hold entries in RES_TABLE_LIBRARY_TYPE.";
+ LOG(ERROR) << "RES_TABLE_LIBRARY_TYPE too small to hold entries.";
return {};
}
@@ -705,7 +690,7 @@
const uint8_t type_id = get_type_id(resid);
const uint16_t entry_id = get_entry_id(resid);
- if (type_id == 0) {
+ if (UNLIKELY(type_id == 0)) {
LOG(ERROR) << base::StringPrintf("Invalid ID 0x%08x.", resid);
return false;
}
@@ -723,7 +708,7 @@
ATRACE_CALL();
const ResTable_header* header = chunk.header<ResTable_header>();
if (header == nullptr) {
- LOG(ERROR) << "Chunk RES_TABLE_TYPE is too small.";
+ LOG(ERROR) << "RES_TABLE_TYPE too small.";
return false;
}
@@ -742,11 +727,11 @@
status_t err = global_string_pool_.setTo(child_chunk.header<ResStringPool_header>(),
child_chunk.size());
if (err != NO_ERROR) {
- LOG(ERROR) << "Corrupt string pool.";
+ LOG(ERROR) << "RES_STRING_POOL_TYPE corrupt.";
return false;
}
} else {
- LOG(WARNING) << "Multiple string pool chunks found in resource table.";
+ LOG(WARNING) << "Multiple RES_STRING_POOL_TYPEs found in RES_TABLE_TYPE.";
}
break;
diff --git a/libs/androidfw/include/androidfw/LoadedArsc.h b/libs/androidfw/include/androidfw/LoadedArsc.h
index 377735b..965e2db 100644
--- a/libs/androidfw/include/androidfw/LoadedArsc.h
+++ b/libs/androidfw/include/androidfw/LoadedArsc.h
@@ -119,11 +119,6 @@
return overlay_;
}
- // Returns true if this package is verified to be valid.
- inline bool IsVerified() const {
- return verified_;
- }
-
// Returns the map of package name to package ID used in this LoadedPackage. At runtime, a
// package could have been assigned a different package ID than what this LoadedPackage was
// compiled with. AssetManager rewrites the package IDs so that they are compatible at runtime.
@@ -145,7 +140,6 @@
LoadedPackage();
- template <bool Verified>
bool FindEntry(const util::unique_cptr<TypeSpec>& type_spec_ptr, uint16_t entry_idx,
const ResTable_config& config, FindEntryResult* out_entry) const;
@@ -157,7 +151,6 @@
bool dynamic_ = false;
bool system_ = false;
bool overlay_ = false;
- bool verified_ = true;
ByteBucketArray<util::unique_cptr<TypeSpec>> type_specs_;
std::vector<DynamicPackageEntry> dynamic_package_map_;
diff --git a/libs/androidfw/tests/ApkAssets_test.cpp b/libs/androidfw/tests/ApkAssets_test.cpp
index ba5844b..6c43a67 100644
--- a/libs/androidfw/tests/ApkAssets_test.cpp
+++ b/libs/androidfw/tests/ApkAssets_test.cpp
@@ -39,7 +39,6 @@
const LoadedPackage* loaded_package = loaded_arsc->GetPackageForId(0x7f010000);
ASSERT_NE(nullptr, loaded_package);
- EXPECT_TRUE(loaded_package->IsVerified());
std::unique_ptr<Asset> asset = loaded_apk->Open("res/layout/main.xml");
ASSERT_NE(nullptr, asset);
@@ -59,7 +58,6 @@
const LoadedPackage* loaded_package = loaded_arsc->GetPackageForId(0x7f010000);
ASSERT_NE(nullptr, loaded_package);
- EXPECT_TRUE(loaded_package->IsVerified());
std::unique_ptr<Asset> asset = loaded_apk->Open("res/layout/main.xml");
ASSERT_NE(nullptr, asset);
@@ -114,20 +112,6 @@
ASSERT_NE(nullptr, loaded_overlay_apk);
}
-TEST(ApkAssetsTest, LoadUnverifiableApk) {
- std::unique_ptr<const ApkAssets> loaded_apk =
- ApkAssets::Load(GetTestDataPath() + "/unverified/unverified.apk");
- ASSERT_NE(nullptr, loaded_apk);
-
- const LoadedArsc* loaded_arsc = loaded_apk->GetLoadedArsc();
- ASSERT_NE(nullptr, loaded_arsc);
-
- const LoadedPackage* loaded_package = loaded_arsc->GetPackageForId(0x7f010000);
- ASSERT_NE(nullptr, loaded_package);
-
- EXPECT_FALSE(loaded_package->IsVerified());
-}
-
TEST(ApkAssetsTest, CreateAndDestroyAssetKeepsApkAssetsOpen) {
std::unique_ptr<const ApkAssets> loaded_apk =
ApkAssets::Load(GetTestDataPath() + "/basic/basic.apk");
diff --git a/libs/androidfw/tests/AssetManager2_bench.cpp b/libs/androidfw/tests/AssetManager2_bench.cpp
index 739e733..85e8f25 100644
--- a/libs/androidfw/tests/AssetManager2_bench.cpp
+++ b/libs/androidfw/tests/AssetManager2_bench.cpp
@@ -26,12 +26,10 @@
#include "data/basic/R.h"
#include "data/libclient/R.h"
#include "data/styles/R.h"
-#include "data/unverified/R.h"
namespace app = com::android::app;
namespace basic = com::android::basic;
namespace libclient = com::android::libclient;
-namespace unverified = com::android::unverified;
namespace android {
@@ -83,37 +81,6 @@
}
BENCHMARK(BM_AssetManagerLoadFrameworkAssetsOld);
-static void GetResourceBenchmark(const std::vector<std::string>& paths,
- const ResTable_config* config, uint32_t resid,
- benchmark::State& state) {
- std::vector<std::unique_ptr<const ApkAssets>> apk_assets;
- std::vector<const ApkAssets*> apk_assets_ptrs;
- for (const std::string& path : paths) {
- std::unique_ptr<const ApkAssets> apk = ApkAssets::Load(path);
- if (apk == nullptr) {
- state.SkipWithError(base::StringPrintf("Failed to load assets %s", path.c_str()).c_str());
- return;
- }
- apk_assets_ptrs.push_back(apk.get());
- apk_assets.push_back(std::move(apk));
- }
-
- AssetManager2 assetmanager;
- assetmanager.SetApkAssets(apk_assets_ptrs);
- if (config != nullptr) {
- assetmanager.SetConfiguration(*config);
- }
-
- Res_value value;
- ResTable_config selected_config;
- uint32_t flags;
-
- while (state.KeepRunning()) {
- assetmanager.GetResource(resid, false /* may_be_bag */, 0u /* density_override */, &value,
- &selected_config, &flags);
- }
-}
-
static void BM_AssetManagerGetResource(benchmark::State& state) {
GetResourceBenchmark({GetTestDataPath() + "/basic/basic.apk"}, nullptr /*config*/,
basic::R::integer::number1, state);
@@ -126,12 +93,6 @@
}
BENCHMARK(BM_AssetManagerGetResourceOld);
-static void BM_AssetManagerGetResourceUnverified(benchmark::State& state) {
- GetResourceBenchmark({GetTestDataPath() + "/unverified/unverified.apk"}, nullptr /*config*/,
- unverified::R::integer::number1, state);
-}
-BENCHMARK(BM_AssetManagerGetResourceUnverified);
-
static void BM_AssetManagerGetLibraryResource(benchmark::State& state) {
GetResourceBenchmark(
{GetTestDataPath() + "/lib_two/lib_two.apk", GetTestDataPath() + "/lib_one/lib_one.apk",
@@ -214,30 +175,6 @@
}
BENCHMARK(BM_AssetManagerGetBagOld);
-static void BM_AssetManagerGetBagUnverified(benchmark::State& state) {
- std::unique_ptr<const ApkAssets> apk =
- ApkAssets::Load(GetTestDataPath() + "/unverified/unverified.apk");
- if (apk == nullptr) {
- state.SkipWithError("Failed to load assets");
- return;
- }
-
- AssetManager2 assets;
- assets.SetApkAssets({apk.get()});
-
- while (state.KeepRunning()) {
- const ResolvedBag* bag = assets.GetBag(unverified::R::array::integerArray1);
- const auto bag_end = end(bag);
- for (auto iter = begin(bag); iter != bag_end; ++iter) {
- uint32_t key = iter->key;
- Res_value value = iter->value;
- benchmark::DoNotOptimize(key);
- benchmark::DoNotOptimize(value);
- }
- }
-}
-BENCHMARK(BM_AssetManagerGetBagUnverified);
-
static void BM_AssetManagerGetResourceLocales(benchmark::State& state) {
std::unique_ptr<const ApkAssets> apk = ApkAssets::Load(kFrameworkPath);
if (apk == nullptr) {
diff --git a/libs/androidfw/tests/AssetManager2_test.cpp b/libs/androidfw/tests/AssetManager2_test.cpp
index 567adfe..92462a6 100644
--- a/libs/androidfw/tests/AssetManager2_test.cpp
+++ b/libs/androidfw/tests/AssetManager2_test.cpp
@@ -28,7 +28,6 @@
#include "data/libclient/R.h"
#include "data/styles/R.h"
#include "data/system/R.h"
-#include "data/unverified/R.h"
namespace app = com::android::app;
namespace appaslib = com::android::appaslib::app;
@@ -36,7 +35,6 @@
namespace lib_one = com::android::lib_one;
namespace lib_two = com::android::lib_two;
namespace libclient = com::android::libclient;
-namespace unverified = com::android::unverified;
namespace android {
@@ -452,30 +450,4 @@
TEST_F(AssetManager2Test, OpensFileFromMultipleApkAssets) {}
-TEST_F(AssetManager2Test, OperateOnUnverifiedApkAssets) {
- std::unique_ptr<const ApkAssets> unverified_assets =
- ApkAssets::Load(GetTestDataPath() + "/unverified/unverified.apk");
- ASSERT_NE(nullptr, unverified_assets);
-
- AssetManager2 assetmanager;
- assetmanager.SetApkAssets({unverified_assets.get()});
-
- Res_value value;
- ResTable_config config;
- uint32_t flags;
-
- EXPECT_EQ(kInvalidCookie,
- assetmanager.GetResource(unverified::R::string::test1, false /*may_be_bag*/, 0u, &value,
- &config, &flags));
- EXPECT_EQ(kInvalidCookie,
- assetmanager.GetResource(unverified::R::string::test2, false /*may_be_bag*/, 0u, &value,
- &config, &flags));
- EXPECT_NE(kInvalidCookie,
- assetmanager.GetResource(unverified::R::integer::number1, false /*may_be_bag*/, 0u,
- &value, &config, &flags));
-
- EXPECT_EQ(nullptr, assetmanager.GetBag(unverified::R::style::Theme1));
- EXPECT_NE(nullptr, assetmanager.GetBag(unverified::R::array::integerArray1));
-}
-
} // namespace android
diff --git a/libs/androidfw/tests/BenchmarkHelpers.cpp b/libs/androidfw/tests/BenchmarkHelpers.cpp
index 3619b7e..7149bee 100644
--- a/libs/androidfw/tests/BenchmarkHelpers.cpp
+++ b/libs/androidfw/tests/BenchmarkHelpers.cpp
@@ -18,6 +18,7 @@
#include "android-base/stringprintf.h"
#include "androidfw/AssetManager.h"
+#include "androidfw/AssetManager2.h"
namespace android {
@@ -48,4 +49,34 @@
}
}
+void GetResourceBenchmark(const std::vector<std::string>& paths, const ResTable_config* config,
+ uint32_t resid, benchmark::State& state) {
+ std::vector<std::unique_ptr<const ApkAssets>> apk_assets;
+ std::vector<const ApkAssets*> apk_assets_ptrs;
+ for (const std::string& path : paths) {
+ std::unique_ptr<const ApkAssets> apk = ApkAssets::Load(path);
+ if (apk == nullptr) {
+ state.SkipWithError(base::StringPrintf("Failed to load assets %s", path.c_str()).c_str());
+ return;
+ }
+ apk_assets_ptrs.push_back(apk.get());
+ apk_assets.push_back(std::move(apk));
+ }
+
+ AssetManager2 assetmanager;
+ assetmanager.SetApkAssets(apk_assets_ptrs);
+ if (config != nullptr) {
+ assetmanager.SetConfiguration(*config);
+ }
+
+ Res_value value;
+ ResTable_config selected_config;
+ uint32_t flags;
+
+ while (state.KeepRunning()) {
+ assetmanager.GetResource(resid, false /* may_be_bag */, 0u /* density_override */, &value,
+ &selected_config, &flags);
+ }
+}
+
} // namespace android
diff --git a/libs/androidfw/tests/BenchmarkHelpers.h b/libs/androidfw/tests/BenchmarkHelpers.h
index 0bb96b5..eb0939d 100644
--- a/libs/androidfw/tests/BenchmarkHelpers.h
+++ b/libs/androidfw/tests/BenchmarkHelpers.h
@@ -30,6 +30,9 @@
void GetResourceBenchmarkOld(const std::vector<std::string>& paths, const ResTable_config* config,
uint32_t resid, ::benchmark::State& state);
+void GetResourceBenchmark(const std::vector<std::string>& paths, const ResTable_config* config,
+ uint32_t resid, benchmark::State& state);
+
} // namespace android
#endif // ANDROIDFW_TESTS_BENCHMARKHELPERS_H
diff --git a/libs/androidfw/tests/LoadedArsc_test.cpp b/libs/androidfw/tests/LoadedArsc_test.cpp
index 954a54d..37ddafb 100644
--- a/libs/androidfw/tests/LoadedArsc_test.cpp
+++ b/libs/androidfw/tests/LoadedArsc_test.cpp
@@ -19,11 +19,13 @@
#include "TestHelpers.h"
#include "data/basic/R.h"
#include "data/libclient/R.h"
+#include "data/sparse/R.h"
#include "data/styles/R.h"
namespace app = com::android::app;
namespace basic = com::android::basic;
namespace libclient = com::android::libclient;
+namespace sparse = com::android::sparse;
namespace android {
@@ -68,6 +70,23 @@
ASSERT_NE(nullptr, entry.entry);
}
+TEST(LoadedArscTest, LoadSparseEntryApp) {
+ std::string contents;
+ ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/sparse/sparse.apk", "resources.arsc",
+ &contents));
+
+ std::unique_ptr<const LoadedArsc> loaded_arsc = LoadedArsc::Load(StringPiece(contents));
+ ASSERT_NE(nullptr, loaded_arsc);
+
+ ResTable_config config;
+ memset(&config, 0, sizeof(config));
+ config.sdkVersion = 26;
+
+ FindEntryResult entry;
+ ASSERT_TRUE(loaded_arsc->FindEntry(sparse::R::integer::foo_9, config, &entry));
+ ASSERT_NE(nullptr, entry.entry);
+}
+
TEST(LoadedArscTest, LoadSharedLibrary) {
std::string contents;
ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/lib_one/lib_one.apk", "resources.arsc",
diff --git a/libs/androidfw/tests/SparseEntry_bench.cpp b/libs/androidfw/tests/SparseEntry_bench.cpp
index d6dc07d..c9b4ad8 100644
--- a/libs/androidfw/tests/SparseEntry_bench.cpp
+++ b/libs/androidfw/tests/SparseEntry_bench.cpp
@@ -24,40 +24,40 @@
namespace android {
-static void BM_SparseEntryGetResourceSparseSmall(benchmark::State& state) {
+static void BM_SparseEntryGetResourceOldSparse(benchmark::State& state, uint32_t resid) {
ResTable_config config;
memset(&config, 0, sizeof(config));
config.sdkVersion = 26;
- GetResourceBenchmarkOld({GetTestDataPath() + "/sparse/sparse.apk"}, &config,
- sparse::R::integer::foo_9, state);
+ GetResourceBenchmarkOld({GetTestDataPath() + "/sparse/sparse.apk"}, &config, resid, state);
}
-BENCHMARK(BM_SparseEntryGetResourceSparseSmall);
+BENCHMARK_CAPTURE(BM_SparseEntryGetResourceOldSparse, Small, sparse::R::integer::foo_9);
+BENCHMARK_CAPTURE(BM_SparseEntryGetResourceOldSparse, Large, sparse::R::string::foo_999);
-static void BM_SparseEntryGetResourceNotSparseSmall(benchmark::State& state) {
+static void BM_SparseEntryGetResourceOldNotSparse(benchmark::State& state, uint32_t resid) {
ResTable_config config;
memset(&config, 0, sizeof(config));
config.sdkVersion = 26;
- GetResourceBenchmarkOld({GetTestDataPath() + "/sparse/not_sparse.apk"}, &config,
- sparse::R::integer::foo_9, state);
+ GetResourceBenchmarkOld({GetTestDataPath() + "/sparse/not_sparse.apk"}, &config, resid, state);
}
-BENCHMARK(BM_SparseEntryGetResourceNotSparseSmall);
+BENCHMARK_CAPTURE(BM_SparseEntryGetResourceOldNotSparse, Small, sparse::R::integer::foo_9);
+BENCHMARK_CAPTURE(BM_SparseEntryGetResourceOldNotSparse, Large, sparse::R::string::foo_999);
-static void BM_SparseEntryGetResourceSparseLarge(benchmark::State& state) {
+static void BM_SparseEntryGetResourceSparse(benchmark::State& state, uint32_t resid) {
ResTable_config config;
memset(&config, 0, sizeof(config));
config.sdkVersion = 26;
- GetResourceBenchmarkOld({GetTestDataPath() + "/sparse/sparse.apk"}, &config,
- sparse::R::string::foo_999, state);
+ GetResourceBenchmark({GetTestDataPath() + "/sparse/sparse.apk"}, &config, resid, state);
}
-BENCHMARK(BM_SparseEntryGetResourceSparseLarge);
+BENCHMARK_CAPTURE(BM_SparseEntryGetResourceSparse, Small, sparse::R::integer::foo_9);
+BENCHMARK_CAPTURE(BM_SparseEntryGetResourceSparse, Large, sparse::R::string::foo_999);
-static void BM_SparseEntryGetResourceNotSparseLarge(benchmark::State& state) {
+static void BM_SparseEntryGetResourceNotSparse(benchmark::State& state, uint32_t resid) {
ResTable_config config;
memset(&config, 0, sizeof(config));
config.sdkVersion = 26;
- GetResourceBenchmarkOld({GetTestDataPath() + "/sparse/not_sparse.apk"}, &config,
- sparse::R::string::foo_999, state);
+ GetResourceBenchmark({GetTestDataPath() + "/sparse/not_sparse.apk"}, &config, resid, state);
}
-BENCHMARK(BM_SparseEntryGetResourceNotSparseLarge);
+BENCHMARK_CAPTURE(BM_SparseEntryGetResourceNotSparse, Small, sparse::R::integer::foo_9);
+BENCHMARK_CAPTURE(BM_SparseEntryGetResourceNotSparse, Large, sparse::R::string::foo_999);
} // namespace android
diff --git a/libs/androidfw/tests/data/unverified/R.h b/libs/androidfw/tests/data/unverified/R.h
deleted file mode 100644
index b734b49..0000000
--- a/libs/androidfw/tests/data/unverified/R.h
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef TESTS_DATA_UNVERIFIED_R_H_
-#define TESTS_DATA_UNVERIFIED_R_H_
-
-#include <cstdint>
-
-#include "tests/data/basic/R.h"
-
-namespace com {
-namespace android {
-
-namespace unverified = basic;
-
-} // namespace android
-} // namespace com
-
-#endif /* TESTS_DATA_UNVERIFIED_R_H_ */
diff --git a/libs/androidfw/tests/data/unverified/unverified.apk b/libs/androidfw/tests/data/unverified/unverified.apk
deleted file mode 100644
index 234b390..0000000
--- a/libs/androidfw/tests/data/unverified/unverified.apk
+++ /dev/null
Binary files differ
diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp
index 05a9b75..20443ec 100644
--- a/libs/hwui/renderthread/RenderThread.cpp
+++ b/libs/hwui/renderthread/RenderThread.cpp
@@ -51,10 +51,17 @@
static bool gHasRenderThreadInstance = false;
+static void (*gOnStartHook)() = nullptr;
+
bool RenderThread::hasInstance() {
return gHasRenderThreadInstance;
}
+void RenderThread::setOnStartHook(void (*onStartHook)()) {
+ LOG_ALWAYS_FATAL_IF(hasInstance(), "can't set an onStartHook after we've started...");
+ gOnStartHook = onStartHook;
+}
+
RenderThread& RenderThread::getInstance() {
// This is a pointer because otherwise __cxa_finalize
// will try to delete it like a Good Citizen but that causes us to crash
@@ -256,6 +263,9 @@
bool RenderThread::threadLoop() {
setpriority(PRIO_PROCESS, 0, PRIORITY_DISPLAY);
+ if (gOnStartHook) {
+ gOnStartHook();
+ }
initThreadLocals();
while (true) {
diff --git a/libs/hwui/renderthread/RenderThread.h b/libs/hwui/renderthread/RenderThread.h
index d17a509..970537b 100644
--- a/libs/hwui/renderthread/RenderThread.h
+++ b/libs/hwui/renderthread/RenderThread.h
@@ -67,6 +67,9 @@
PREVENT_COPY_AND_ASSIGN(RenderThread);
public:
+ // Sets a callback that fires before any RenderThread setup has occured.
+ ANDROID_API static void setOnStartHook(void (*onStartHook)());
+
WorkQueue& queue() { return ThreadBase::queue(); }
// Mimics android.view.Choreographer
diff --git a/libs/hwui/thread/ThreadBase.h b/libs/hwui/thread/ThreadBase.h
index b3fec1f..8068121 100644
--- a/libs/hwui/thread/ThreadBase.h
+++ b/libs/hwui/thread/ThreadBase.h
@@ -31,7 +31,10 @@
PREVENT_COPY_AND_ASSIGN(ThreadBase);
public:
- ThreadBase() : mLooper(new Looper(false)), mQueue([this]() { mLooper->wake(); }, mLock) {}
+ ThreadBase()
+ : Thread(false)
+ , mLooper(new Looper(false))
+ , mQueue([this]() { mLooper->wake(); }, mLock) {}
WorkQueue& queue() { return mQueue; }
diff --git a/packages/SettingsLib/Android.mk b/packages/SettingsLib/Android.mk
index 99f7f71..894a1ab 100644
--- a/packages/SettingsLib/Android.mk
+++ b/packages/SettingsLib/Android.mk
@@ -13,11 +13,11 @@
android-support-v7-recyclerview \
android-support-v7-preference \
android-support-v7-appcompat \
- android-support-v14-preference
+ android-support-v14-preference \
+ apptoolkit-lifecycle-runtime
LOCAL_SHARED_JAVA_LIBRARIES := \
- apptoolkit-lifecycle-common \
- apptoolkit-lifecycle-runtime
+ apptoolkit-lifecycle-common
LOCAL_STATIC_JAVA_LIBRARY := legacy-android-test
diff --git a/packages/SettingsLib/common.mk b/packages/SettingsLib/common.mk
index b9abde2..49c5467 100644
--- a/packages/SettingsLib/common.mk
+++ b/packages/SettingsLib/common.mk
@@ -15,10 +15,12 @@
ifeq ($(LOCAL_USE_AAPT2),true)
LOCAL_STATIC_JAVA_LIBRARIES += \
- android-support-annotations
+ android-support-annotations \
+ apptoolkit-lifecycle-common
LOCAL_STATIC_ANDROID_LIBRARIES += \
android-support-v4 \
+ apptoolkit-lifecycle-runtime \
SettingsLib
else
LOCAL_RESOURCE_DIR += $(call my-dir)/res
@@ -59,5 +61,7 @@
LOCAL_STATIC_JAVA_LIBRARIES += \
android-support-annotations \
android-support-v4 \
+ apptoolkit-lifecycle-runtime \
+ apptoolkit-lifecycle-common \
SettingsLib
endif
diff --git a/packages/Shell/src/com/android/shell/Screenshooter.java b/packages/Shell/src/com/android/shell/Screenshooter.java
index 8e27edf..8e01619 100644
--- a/packages/Shell/src/com/android/shell/Screenshooter.java
+++ b/packages/Shell/src/com/android/shell/Screenshooter.java
@@ -17,12 +17,11 @@
package com.android.shell;
import android.graphics.Bitmap;
-import android.graphics.Canvas;
import android.graphics.Point;
+import android.graphics.Rect;
import android.hardware.display.DisplayManagerGlobal;
import android.util.Log;
import android.view.Display;
-import android.view.Surface;
import android.view.SurfaceControl;
/**
@@ -35,18 +34,6 @@
private static final String TAG = "Screenshooter";
- /** Rotation constant: Freeze rotation to 0 degrees (natural orientation) */
- public static final int ROTATION_FREEZE_0 = Surface.ROTATION_0;
-
- /** Rotation constant: Freeze rotation to 90 degrees . */
- public static final int ROTATION_FREEZE_90 = Surface.ROTATION_90;
-
- /** Rotation constant: Freeze rotation to 180 degrees . */
- public static final int ROTATION_FREEZE_180 = Surface.ROTATION_180;
-
- /** Rotation constant: Freeze rotation to 270 degrees . */
- public static final int ROTATION_FREEZE_270 = Surface.ROTATION_270;
-
/**
* Takes a screenshot.
*
@@ -60,78 +47,21 @@
final int displayWidth = displaySize.x;
final int displayHeight = displaySize.y;
- final float screenshotWidth;
- final float screenshotHeight;
-
- final int rotation = display.getRotation();
- switch (rotation) {
- case ROTATION_FREEZE_0: {
- screenshotWidth = displayWidth;
- screenshotHeight = displayHeight;
- } break;
- case ROTATION_FREEZE_90: {
- screenshotWidth = displayHeight;
- screenshotHeight = displayWidth;
- } break;
- case ROTATION_FREEZE_180: {
- screenshotWidth = displayWidth;
- screenshotHeight = displayHeight;
- } break;
- case ROTATION_FREEZE_270: {
- screenshotWidth = displayHeight;
- screenshotHeight = displayWidth;
- } break;
- default: {
- throw new IllegalArgumentException("Invalid rotation: "
- + rotation);
- }
- }
-
+ int rotation = display.getRotation();
+ Rect crop = new Rect(0, 0, displayWidth, displayHeight);
Log.d(TAG, "Taking screenshot of dimensions " + displayWidth + " x " + displayHeight);
// Take the screenshot
Bitmap screenShot =
- SurfaceControl.screenshot((int) screenshotWidth, (int) screenshotHeight);
+ SurfaceControl.screenshot(crop, displayWidth, displayHeight, rotation);
if (screenShot == null) {
- Log.e(TAG, "Failed to take screenshot of dimensions " + screenshotWidth + " x "
- + screenshotHeight);
+ Log.e(TAG, "Failed to take screenshot of dimensions " + displayWidth + " x "
+ + displayHeight);
return null;
}
- // Rotate the screenshot to the current orientation
- if (rotation != ROTATION_FREEZE_0) {
- Bitmap unrotatedScreenShot = Bitmap.createBitmap(displayWidth, displayHeight,
- Bitmap.Config.ARGB_8888, screenShot.hasAlpha(), screenShot.getColorSpace());
- Canvas canvas = new Canvas(unrotatedScreenShot);
- canvas.translate(unrotatedScreenShot.getWidth() / 2,
- unrotatedScreenShot.getHeight() / 2);
- canvas.rotate(getDegreesForRotation(rotation));
- canvas.translate(- screenshotWidth / 2, - screenshotHeight / 2);
- canvas.drawBitmap(screenShot, 0, 0, null);
- canvas.setBitmap(null);
- screenShot.recycle();
- screenShot = unrotatedScreenShot;
- }
-
// Optimization
screenShot.setHasAlpha(false);
return screenShot;
}
-
- private static float getDegreesForRotation(int value) {
- switch (value) {
- case Surface.ROTATION_90: {
- return 360f - 90f;
- }
- case Surface.ROTATION_180: {
- return 360f - 180f;
- }
- case Surface.ROTATION_270: {
- return 360f - 270f;
- } default: {
- return 0;
- }
- }
- }
-
}
diff --git a/packages/SystemUI/res/layout/volume_dialog.xml b/packages/SystemUI/res/layout/volume_dialog.xml
index fac254a..863f17b 100644
--- a/packages/SystemUI/res/layout/volume_dialog.xml
+++ b/packages/SystemUI/res/layout/volume_dialog.xml
@@ -13,7 +13,7 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<com.android.systemui.HardwareUiLayout
+<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
@@ -45,4 +45,4 @@
</LinearLayout>
</RelativeLayout>
-</com.android.systemui.HardwareUiLayout>
+</RelativeLayout>
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
index 2d3080b..130a5e3 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
@@ -267,7 +267,7 @@
try {
return mIam.setTaskWindowingModeSplitScreenPrimary(taskId, createMode, true /* onTop */,
- false /* animate */, initialBounds);
+ false /* animate */, initialBounds, true /* showRecents */);
} catch (RemoteException e) {
e.printStackTrace();
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
index 991c3c8..5fcd006 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
@@ -49,7 +49,6 @@
import android.media.MediaActionSound;
import android.net.Uri;
import android.os.AsyncTask;
-import android.os.Bundle;
import android.os.Environment;
import android.os.PowerManager;
import android.os.Process;
@@ -162,7 +161,7 @@
Matrix matrix = new Matrix();
int overlayColor = 0x40FFFFFF;
- Bitmap picture = Bitmap.createBitmap(previewWidth, previewHeight, data.image.getConfig());
+ Bitmap picture = Bitmap.createBitmap(previewWidth, previewHeight, Bitmap.Config.ARGB_8888);
matrix.setTranslate((previewWidth - mImageWidth) / 2, (previewHeight - mImageHeight) / 2);
c.setBitmap(picture);
c.drawBitmap(data.image, matrix, paint);
@@ -171,7 +170,7 @@
// Note, we can't use the preview for the small icon, since it is non-square
float scale = (float) iconSize / Math.min(mImageWidth, mImageHeight);
- Bitmap icon = Bitmap.createBitmap(iconSize, iconSize, data.image.getConfig());
+ Bitmap icon = Bitmap.createBitmap(iconSize, iconSize, Bitmap.Config.ARGB_8888);
matrix.setScale(scale, scale);
matrix.postTranslate((iconSize - (scale * mImageWidth)) / 2,
(iconSize - (scale * mImageHeight)) / 2);
@@ -557,25 +556,14 @@
/**
* Takes a screenshot of the current display and shows an animation.
*/
- void takeScreenshot(Runnable finisher, boolean statusBarVisible, boolean navBarVisible,
- int x, int y, int width, int height) {
- // We need to orient the screenshot correctly (and the Surface api seems to take screenshots
- // only in the natural orientation of the device :!)
- mDisplay.getRealMetrics(mDisplayMetrics);
- float[] dims = {mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels};
- float degrees = getDegreesForRotation(mDisplay.getRotation());
- boolean requiresRotation = (degrees > 0);
- if (requiresRotation) {
- // Get the dimensions of the device in its native orientation
- mDisplayMatrix.reset();
- mDisplayMatrix.preRotate(-degrees);
- mDisplayMatrix.mapPoints(dims);
- dims[0] = Math.abs(dims[0]);
- dims[1] = Math.abs(dims[1]);
- }
+ private void takeScreenshot(Runnable finisher, boolean statusBarVisible, boolean navBarVisible,
+ Rect crop) {
+ int rot = mDisplay.getRotation();
+ int width = crop.width();
+ int height = crop.height();
// Take the screenshot
- mScreenBitmap = SurfaceControl.screenshot((int) dims[0], (int) dims[1]);
+ mScreenBitmap = SurfaceControl.screenshot(crop, width, height, rot);
if (mScreenBitmap == null) {
notifyScreenshotError(mContext, mNotificationManager,
R.string.screenshot_failed_to_capture_text);
@@ -583,29 +571,6 @@
return;
}
- if (requiresRotation) {
- // Rotate the screenshot to the current orientation
- Bitmap ss = Bitmap.createBitmap(mDisplayMetrics.widthPixels,
- mDisplayMetrics.heightPixels, Bitmap.Config.ARGB_8888,
- mScreenBitmap.hasAlpha(), mScreenBitmap.getColorSpace());
- Canvas c = new Canvas(ss);
- c.translate(ss.getWidth() / 2, ss.getHeight() / 2);
- c.rotate(degrees);
- c.translate(-dims[0] / 2, -dims[1] / 2);
- c.drawBitmap(mScreenBitmap, 0, 0, null);
- c.setBitmap(null);
- // Recycle the previous bitmap
- mScreenBitmap.recycle();
- mScreenBitmap = ss;
- }
-
- if (width != mDisplayMetrics.widthPixels || height != mDisplayMetrics.heightPixels) {
- // Crop the screenshot to selected region
- Bitmap cropped = Bitmap.createBitmap(mScreenBitmap, x, y, width, height);
- mScreenBitmap.recycle();
- mScreenBitmap = cropped;
- }
-
// Optimizations
mScreenBitmap.setHasAlpha(false);
mScreenBitmap.prepareToDraw();
@@ -617,8 +582,8 @@
void takeScreenshot(Runnable finisher, boolean statusBarVisible, boolean navBarVisible) {
mDisplay.getRealMetrics(mDisplayMetrics);
- takeScreenshot(finisher, statusBarVisible, navBarVisible, 0, 0, mDisplayMetrics.widthPixels,
- mDisplayMetrics.heightPixels);
+ takeScreenshot(finisher, statusBarVisible, navBarVisible,
+ new Rect(0, 0, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels));
}
/**
@@ -648,7 +613,7 @@
mScreenshotLayout.post(new Runnable() {
public void run() {
takeScreenshot(finisher, statusBarVisible, navBarVisible,
- rect.left, rect.top, rect.width(), rect.height());
+ rect);
}
});
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
index 652f8bb..8516278 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
@@ -288,7 +288,7 @@
String description = null;
// Only send data sim callbacks to QS.
if (mCurrentState.dataSim) {
- qsTypeIcon = showDataIcon ? icons.mQsDataType : 0;
+ qsTypeIcon = (showDataIcon || mConfig.alwaysShowDataRatIcon) ? icons.mQsDataType : 0;
qsIcon = new IconState(mCurrentState.enabled
&& !mCurrentState.isEmergency, getQsCurrentIconId(), contentDescription);
description = mCurrentState.isEmergency ? null : mCurrentState.networkName;
@@ -300,7 +300,7 @@
&& !mCurrentState.carrierNetworkChangeMode
&& mCurrentState.activityOut;
showDataIcon &= mCurrentState.isDefault || dataDisabled;
- int typeIcon = showDataIcon ? icons.mDataType : 0;
+ int typeIcon = (showDataIcon || mConfig.alwaysShowDataRatIcon) ? icons.mDataType : 0;
callback.setMobileDataIndicators(statusIcon, qsIcon, typeIcon, qsTypeIcon,
activityIn, activityOut, dataContentDescription, description, icons.mIsWide,
mSubscriptionInfo.getSubscriptionId(), mCurrentState.roaming);
@@ -460,7 +460,7 @@
mCurrentState.roaming = isRoaming();
if (isCarrierNetworkChangeActive()) {
mCurrentState.iconGroup = TelephonyIcons.CARRIER_NETWORK_CHANGE;
- } else if (isDataDisabled()) {
+ } else if (isDataDisabled() && !mConfig.alwaysShowDataRatIcon) {
mCurrentState.iconGroup = TelephonyIcons.DATA_DISABLED;
}
if (isEmergencyOnly() != mCurrentState.isEmergency) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
index 40ee838..baf0ebf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
@@ -29,7 +29,9 @@
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
+import android.os.PersistableBundle;
import android.provider.Settings;
+import android.telephony.CarrierConfigManager;
import android.telephony.ServiceState;
import android.telephony.SignalStrength;
import android.telephony.SubscriptionInfo;
@@ -245,6 +247,7 @@
filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
filter.addAction(ConnectivityManager.INET_CONDITION_ACTION);
filter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+ filter.addAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
mContext.registerReceiver(this, filter, null, mReceiverHandler);
mListening = true;
@@ -426,6 +429,14 @@
// emergency state.
recalculateEmergency();
}
+ } else if (action.equals(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED)) {
+ mConfig = Config.readConfig(mContext);
+ mReceiverHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ handleConfigurationChanged();
+ }
+ });
} else {
int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY,
SubscriptionManager.INVALID_SUBSCRIPTION_ID);
@@ -969,6 +980,7 @@
boolean hideLtePlus = false;
boolean hspaDataDistinguishable;
boolean inflateSignalStrengths = false;
+ boolean alwaysShowDataRatIcon = false;
static Config readConfig(Context context) {
Config config = new Config();
@@ -982,6 +994,14 @@
res.getBoolean(R.bool.config_hspa_data_distinguishable);
config.hideLtePlus = res.getBoolean(R.bool.config_hideLtePlus);
config.inflateSignalStrengths = res.getBoolean(R.bool.config_inflateSignalStrength);
+
+ CarrierConfigManager configMgr = (CarrierConfigManager)
+ context.getSystemService(Context.CARRIER_CONFIG_SERVICE);
+ PersistableBundle b = configMgr.getConfig();
+ if (b != null) {
+ config.alwaysShowDataRatIcon = b.getBoolean(
+ CarrierConfigManager.KEY_ALWAYS_SHOW_DATA_RAT_ICON_BOOL);
+ }
return config;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
index b08b26d..2754026 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
@@ -49,7 +49,6 @@
import com.android.systemui.Dumpable;
import com.android.systemui.R;
import com.android.systemui.SysUiServiceProvider;
-import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.plugins.VolumeDialogController;
import com.android.systemui.qs.tiles.DndTile;
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 383d327..f16d7b8 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -19,6 +19,9 @@
import static android.accessibilityservice.AccessibilityServiceInfo.FEEDBACK_ALL_MASK;
import static android.accessibilityservice.AccessibilityServiceInfo.FEEDBACK_GENERIC;
+import static com.android.systemui.util.leak.RotationUtils.ROTATION_LANDSCAPE;
+import static com.android.systemui.util.leak.RotationUtils.ROTATION_NONE;
+import static com.android.systemui.util.leak.RotationUtils.ROTATION_SEASCAPE;
import static com.android.systemui.volume.Events.DISMISS_REASON_TOUCH_OUTSIDE;
import android.accessibilityservice.AccessibilityServiceInfo;
@@ -42,6 +45,7 @@
import android.os.Looper;
import android.os.Message;
import android.os.SystemClock;
+import android.provider.Settings;
import android.provider.Settings.Global;
import android.util.Log;
import android.util.Slog;
@@ -67,6 +71,7 @@
import com.android.settingslib.Utils;
import com.android.systemui.Dependency;
+import com.android.systemui.HardwareBgDrawable;
import com.android.systemui.HardwareUiLayout;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
@@ -74,6 +79,7 @@
import com.android.systemui.plugins.VolumeDialogController;
import com.android.systemui.plugins.VolumeDialogController.State;
import com.android.systemui.plugins.VolumeDialogController.StreamState;
+import com.android.systemui.util.leak.RotationUtils;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -97,9 +103,13 @@
private final VolumeDialogController mController;
private Window mWindow;
- private HardwareUiLayout mHardwareLayout;
+ //private HardwareUiLayout mHardwareLayout;
private CustomDialog mDialog;
private ViewGroup mDialogView;
+ private boolean mEdgeBleed;
+ private boolean mRoundedDivider;
+ private HardwareBgDrawable mBackground;
+ private int mRotation = ROTATION_NONE;
private ViewGroup mDialogRowsView;
private ViewGroup mDialogContentView;
private final List<VolumeRow> mRows = new ArrayList<>();
@@ -111,6 +121,8 @@
private final Accessibility mAccessibility = new Accessibility();
private final ColorStateList mActiveSliderTint;
private final ColorStateList mInactiveSliderTint;
+ private static final String EDGE_BLEED = "sysui_hwui_edge_bleed";
+ private static final String ROUNDED_DIVIDER = "sysui_hwui_rounded_divider";
private boolean mShowing;
private boolean mShowA11yStream;
@@ -181,8 +193,16 @@
return true;
}
});
- mHardwareLayout = HardwareUiLayout.get(mDialogView);
- mHardwareLayout.setOutsideTouchListener(view -> dismiss(DISMISS_REASON_TOUCH_OUTSIDE));
+
+ mEdgeBleed = Settings.Secure.getInt(mContext.getContentResolver(),
+ EDGE_BLEED, 0) != 0;
+ mRoundedDivider = Settings.Secure.getInt(mContext.getContentResolver(),
+ ROUNDED_DIVIDER, 1) != 0;
+ updateEdgeMargin(mEdgeBleed ? 0 : getEdgePadding());
+ mBackground = new HardwareBgDrawable(mRoundedDivider, !mEdgeBleed, mContext);
+ mDialogView.setBackground(mBackground);
+ //mHardwareLayout = HardwareUiLayout.get(mDialogView);
+ //mHardwareLayout.setOutsideTouchListener(view -> dismiss(DISMISS_REASON_TOUCH_OUTSIDE));
mDialogContentView = mDialog.findViewById(R.id.volume_dialog_content);
mDialogRowsView = mDialogContentView.findViewById(R.id.volume_dialog_rows);
@@ -210,6 +230,25 @@
updateRowsH(getActiveRow());
}
+ private int getEdgePadding() {
+ return mContext.getResources().getDimensionPixelSize(R.dimen.edge_margin);
+ }
+
+ private void updateEdgeMargin(int edge) {
+ if (mDialogView != null) {
+ mRotation = RotationUtils.getRotation(mContext);
+ ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) mDialogView.getLayoutParams();
+ if (mRotation == ROTATION_LANDSCAPE) {
+ params.topMargin = edge;
+ } else if (mRotation == ROTATION_SEASCAPE) {
+ params.bottomMargin = edge;
+ } else {
+ params.rightMargin = edge;
+ }
+ mDialogView.setLayoutParams(params);
+ }
+ }
+
private ColorStateList loadColorStateList(int colorResId) {
return ColorStateList.valueOf(mContext.getColor(colorResId));
}
@@ -389,11 +428,11 @@
rescheduleTimeoutH();
if (mShowing) return;
mShowing = true;
- mHardwareLayout.setTranslationX(getAnimTranslation());
- mHardwareLayout.setAlpha(0);
- mHardwareLayout.animate()
+ mDialogView.setTranslationY(getAnimTranslation());
+ mDialogView.setAlpha(0);
+ mDialogView.animate()
.alpha(1)
- .translationX(0)
+ .translationY(0)
.setDuration(300)
.setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
.withEndAction(() -> {
@@ -432,9 +471,9 @@
mHandler.removeMessages(H.SHOW);
if (!mShowing) return;
mShowing = false;
- mHardwareLayout.setTranslationX(0);
- mHardwareLayout.setAlpha(1);
- mHardwareLayout.animate()
+ mDialogView.setTranslationX(0);
+ mDialogView.setAlpha(1);
+ mDialogView.animate()
.alpha(0)
.translationX(getAnimTranslation())
.setDuration(300)
diff --git a/packages/SystemUI/src/com/android/systemui/volume/car/CarVolumeDialogController.java b/packages/SystemUI/src/com/android/systemui/volume/car/CarVolumeDialogController.java
index d28e42e..474085c 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/car/CarVolumeDialogController.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/car/CarVolumeDialogController.java
@@ -29,6 +29,8 @@
/**
* A volume dialog controller for the automotive use case.
+ * TODO(hwwang): consider removing this class since it's coupled with stream_type and we are
+ * moving to use AudioAttributes usage for volume control in a car.
*
* {@link android.car.media.CarAudioManager} is the source of truth to get the stream volumes.
* And volume changes should be sent to the car's audio module instead of the android's audio mixer.
@@ -70,7 +72,7 @@
return;
}
try {
- mCarAudioManager.setStreamVolume(stream, level, flag);
+ mCarAudioManager.setUsageVolume(stream, level, flag);
} catch (CarNotConnectedException e) {
Log.e(TAG, "Car is not connected", e);
}
@@ -84,7 +86,7 @@
}
try {
- return mCarAudioManager.getStreamVolume(stream);
+ return mCarAudioManager.getUsageVolume(stream);
} catch (CarNotConnectedException e) {
Log.e(TAG, "Car is not connected", e);
return 0;
@@ -99,7 +101,7 @@
}
try {
- return mCarAudioManager.getStreamMaxVolume(stream);
+ return mCarAudioManager.getUsageMaxVolume(stream);
} catch (CarNotConnectedException e) {
Log.e(TAG, "Car is not connected", e);
return 0;
@@ -114,7 +116,7 @@
}
try {
- return mCarAudioManager.getStreamMinVolume(stream);
+ return mCarAudioManager.getUsageMinVolume(stream);
} catch (CarNotConnectedException e) {
Log.e(TAG, "Car is not connected", e);
return 0;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java
index 1419e9a..3b796ca 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java
@@ -15,6 +15,7 @@
import com.android.settingslib.net.DataUsageController;
+import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -137,6 +138,22 @@
}
@Test
+ public void testAlwaysShowDataRatIcon() {
+ setupDefaultSignal();
+ when(mMockTm.getDataEnabled(mSubId)).thenReturn(false);
+ updateDataConnectionState(TelephonyManager.DATA_DISCONNECTED,
+ TelephonyManager.NETWORK_TYPE_GSM);
+
+ // Switch to showing data RAT icon when data is disconnected
+ // and re-initialize the NetworkController.
+ mConfig.alwaysShowDataRatIcon = true;
+ mNetworkController.handleConfigurationChanged();
+
+ verifyDataIndicators(TelephonyIcons.ICON_G,
+ TelephonyIcons.QS_DATA_G);
+ }
+
+ @Test
public void test4gDataIconConfigChange() {
setupDefaultSignal();
updateDataConnectionState(TelephonyManager.DATA_CONNECTED,
diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto
index bd6af01..4f1f4d7 100644
--- a/proto/src/metrics_constants.proto
+++ b/proto/src/metrics_constants.proto
@@ -5018,6 +5018,11 @@
// OS: P
FUELGAUGE_POWER_USAGE_SUMMARY_V2 = 1263;
+ // OPEN: Settings > Connected devices > Connection preferences
+ // CATEGORY: SETTINGS
+ // OS: P
+ CONNECTION_DEVICE_ADVANCED = 1264;
+
// ---- End P Constants, all P constants go above this line ----
// Add new aosp constants above this line.
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
index 23e4f50..0291276 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
@@ -52,6 +52,7 @@
import android.os.UserManagerInternal;
import android.provider.Settings;
import android.service.autofill.FillEventHistory;
+import android.service.autofill.UserData;
import android.util.LocalLog;
import android.util.Slog;
import android.util.SparseArray;
@@ -581,6 +582,34 @@
}
@Override
+ public UserData getUserData() throws RemoteException {
+ UserHandle user = getCallingUserHandle();
+ int uid = getCallingUid();
+
+ synchronized (mLock) {
+ AutofillManagerServiceImpl service = peekServiceForUserLocked(user.getIdentifier());
+ if (service != null) {
+ return service.getUserData(uid);
+ }
+ }
+
+ return null;
+ }
+
+ @Override
+ public void setUserData(UserData userData) throws RemoteException {
+ UserHandle user = getCallingUserHandle();
+ int uid = getCallingUid();
+
+ synchronized (mLock) {
+ AutofillManagerServiceImpl service = peekServiceForUserLocked(user.getIdentifier());
+ if (service != null) {
+ service.setUserData(uid, userData);
+ }
+ }
+ }
+
+ @Override
public boolean restoreSession(int sessionId, IBinder activityToken, IBinder appCallback)
throws RemoteException {
activityToken = Preconditions.checkNotNull(activityToken, "activityToken");
@@ -723,6 +752,7 @@
}
boolean oldDebug = sDebug;
+ final String prefix = " ";
try {
synchronized (mLock) {
oldDebug = sDebug;
@@ -731,6 +761,7 @@
pw.print("Verbose mode: "); pw.println(sVerbose);
pw.print("Disabled users: "); pw.println(mDisabledUsers);
pw.print("Max partitions per session: "); pw.println(sPartitionMaxCount);
+ pw.println("User data constraints: "); UserData.dumpConstraints(prefix, pw);
final int size = mServicesCache.size();
pw.print("Cached services: ");
if (size == 0) {
@@ -740,7 +771,7 @@
for (int i = 0; i < size; i++) {
pw.print("\nService at index "); pw.println(i);
final AutofillManagerServiceImpl impl = mServicesCache.valueAt(i);
- impl.dumpLocked(" ", pw);
+ impl.dumpLocked(prefix, pw);
}
}
mUi.dump(pw);
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
index 21e2722..8b6dc20 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -51,6 +51,7 @@
import android.service.autofill.FillEventHistory.Event;
import android.service.autofill.FillResponse;
import android.service.autofill.IAutoFillService;
+import android.service.autofill.UserData;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -121,6 +122,11 @@
private boolean mDisabled;
/**
+ * Data used for field classification.
+ */
+ private UserData mUserData;
+
+ /**
* Caches whether the setup completed for the current user.
*/
@GuardedBy("mLock")
@@ -183,6 +189,14 @@
}
}
+ private int getServiceUidLocked() {
+ if (mInfo == null) {
+ Slog.w(TAG, "getServiceUidLocked(): no mInfo");
+ return -1;
+ }
+ return mInfo.getServiceInfo().applicationInfo.uid;
+ }
+
@Nullable
String getServicePackageName() {
final ComponentName serviceComponent = getServiceComponentName();
@@ -574,9 +588,9 @@
* Initializes the last fill selection after an autofill service returned a new
* {@link FillResponse}.
*/
- void setLastResponse(int serviceUid, int sessionId, @NonNull FillResponse response) {
+ void setLastResponse(int sessionId, @NonNull FillResponse response) {
synchronized (mLock) {
- mEventHistory = new FillEventHistory(serviceUid, sessionId, response.getClientState());
+ mEventHistory = new FillEventHistory(sessionId, response.getClientState());
}
}
@@ -688,18 +702,54 @@
*/
FillEventHistory getFillEventHistory(int callingUid) {
synchronized (mLock) {
- if (mEventHistory != null && mEventHistory.getServiceUid() == callingUid) {
+ if (mEventHistory != null
+ && isCalledByServiceLocked("getFillEventHistory", callingUid)) {
return mEventHistory;
}
}
-
return null;
}
+ // Called by Session - does not need to check uid
+ UserData getUserData() {
+ synchronized (mLock) {
+ return mUserData;
+ }
+ }
+
+ // Called by AutofillManager
+ UserData getUserData(int callingUid) {
+ synchronized (mLock) {
+ if (isCalledByServiceLocked("getUserData", callingUid)) {
+ return mUserData;
+ }
+ }
+ return null;
+ }
+
+ // Called by AutofillManager
+ void setUserData(int callingUid, UserData userData) {
+ synchronized (mLock) {
+ if (isCalledByServiceLocked("setUserData", callingUid)) {
+ mUserData = userData;
+ }
+ }
+ }
+
+ private boolean isCalledByServiceLocked(String methodName, int callingUid) {
+ if (getServiceUidLocked() != callingUid) {
+ Slog.w(TAG, methodName + "() called by UID " + callingUid
+ + ", but service UID is " + getServiceUidLocked());
+ return false;
+ }
+ return true;
+ }
+
void dumpLocked(String prefix, PrintWriter pw) {
final String prefix2 = prefix + " ";
pw.print(prefix); pw.print("User: "); pw.println(mUserId);
+ pw.print(prefix); pw.print("UID: "); pw.println(getServiceUidLocked());
pw.print(prefix); pw.print("Component: "); pw.println(mInfo != null
? mInfo.getServiceInfo().getComponentName() : null);
pw.print(prefix); pw.print("Component from settings: ");
@@ -762,8 +812,13 @@
}
}
- pw.print(prefix); pw.println("Clients");
- mClients.dump(pw, prefix2);
+ pw.print(prefix); pw.print("Clients: ");
+ if (mClients == null) {
+ pw.println("N/A");
+ } else {
+ pw.println();
+ mClients.dump(pw, prefix2);
+ }
if (mEventHistory == null || mEventHistory.getEvents() == null
|| mEventHistory.getEvents().size() == 0) {
@@ -779,6 +834,14 @@
+ event.getDatasetId());
}
}
+
+ pw.print(prefix); pw.print("User data: ");
+ if (mUserData == null) {
+ pw.println("N/A");
+ } else {
+ pw.println();
+ mUserData.dump(prefix2, pw);
+ }
}
void destroySessionsLocked() {
diff --git a/services/autofill/java/com/android/server/autofill/RemoteFillService.java b/services/autofill/java/com/android/server/autofill/RemoteFillService.java
index 831c488..aea9ad0 100644
--- a/services/autofill/java/com/android/server/autofill/RemoteFillService.java
+++ b/services/autofill/java/com/android/server/autofill/RemoteFillService.java
@@ -97,7 +97,7 @@
private PendingRequest mPendingRequest;
public interface FillServiceCallbacks {
- void onFillRequestSuccess(int requestFlags, @Nullable FillResponse response, int serviceUid,
+ void onFillRequestSuccess(int requestFlags, @Nullable FillResponse response,
@NonNull String servicePackageName);
void onFillRequestFailure(@Nullable CharSequence message,
@NonNull String servicePackageName);
@@ -281,11 +281,11 @@
mContext.unbindService(mServiceConnection);
}
- private void dispatchOnFillRequestSuccess(PendingRequest pendingRequest,
- int callingUid, int requestFlags, FillResponse response) {
+ private void dispatchOnFillRequestSuccess(PendingRequest pendingRequest, int requestFlags,
+ FillResponse response) {
mHandler.getHandler().post(() -> {
if (handleResponseCallbackCommon(pendingRequest)) {
- mCallbacks.onFillRequestSuccess(requestFlags, response, callingUid,
+ mCallbacks.onFillRequestSuccess(requestFlags, response,
mComponentName.getPackageName());
}
});
@@ -546,7 +546,7 @@
final RemoteFillService remoteService = getService();
if (remoteService != null) {
remoteService.dispatchOnFillRequestSuccess(PendingFillRequest.this,
- getCallingUid(), request.getFlags(), response);
+ request.getFlags(), response);
}
}
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index e3db1b1..3615bca 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -55,7 +55,6 @@
import android.os.SystemClock;
import android.service.autofill.AutofillService;
import android.service.autofill.Dataset;
-import android.service.autofill.FieldsDetection;
import android.service.autofill.FillContext;
import android.service.autofill.FillRequest;
import android.service.autofill.FillResponse;
@@ -63,6 +62,7 @@
import android.service.autofill.InternalValidator;
import android.service.autofill.SaveInfo;
import android.service.autofill.SaveRequest;
+import android.service.autofill.UserData;
import android.service.autofill.ValueFinder;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -485,7 +485,7 @@
// FillServiceCallbacks
@Override
public void onFillRequestSuccess(int requestFlags, @Nullable FillResponse response,
- int serviceUid, @NonNull String servicePackageName) {
+ @NonNull String servicePackageName) {
synchronized (mLock) {
if (mDestroyed) {
Slog.w(TAG, "Call to Session#onFillRequestSuccess() rejected - session: "
@@ -499,13 +499,13 @@
}
// TODO(b/67867469): remove once feature is finished
- if (response.getFieldsDetection() != null && !mService.isFieldDetectionEnabled()) {
+ if (response.getFieldClassificationIds() != null && !mService.isFieldDetectionEnabled()) {
Slog.w(TAG, "Ignoring " + response + " because field detection is disabled");
processNullResponseLocked(requestFlags);
return;
}
- mService.setLastResponse(serviceUid, id, response);
+ mService.setLastResponse(id, response);
int sessionFinishedState = 0;
final long disableDuration = response.getDisableDuration();
@@ -908,7 +908,7 @@
final FillResponse response = mResponses.valueAt(i);
final List<Dataset> datasets = response.getDatasets();
if (datasets == null || datasets.isEmpty()) {
- if (sVerbose) Slog.v(TAG, "logContextCommitted() no datasets at " + i);
+ if (sVerbose) Slog.v(TAG, "logContextCommitted() no datasets at " + i);
} else {
for (int j = 0; j < datasets.size(); j++) {
final Dataset dataset = datasets.get(j);
@@ -931,25 +931,27 @@
}
}
}
- final FieldsDetection fieldsDetection = lastResponse.getFieldsDetection();
+ final AutofillId[] fieldClassificationIds = lastResponse.getFieldClassificationIds();
- if (!hasAtLeastOneDataset && fieldsDetection == null) {
+ if (!hasAtLeastOneDataset && fieldClassificationIds == null) {
if (sVerbose) {
Slog.v(TAG, "logContextCommittedLocked(): skipped (no datasets nor fields "
- + "detection)");
+ + "classification ids)");
}
return;
}
+ final UserData userData = mService.getUserData();
final AutofillId detectableFieldId;
final String detectableRemoteId;
String detectedRemoteId = null;
- if (fieldsDetection == null) {
+ if (userData == null) {
detectableFieldId = null;
detectableRemoteId = null;
} else {
- detectableFieldId = fieldsDetection.getFieldId();
- detectableRemoteId = fieldsDetection.getRemoteId();
+ // TODO(b/67867469): hardcoded to just first entry on initial refactoring.
+ detectableFieldId = fieldClassificationIds[0];
+ detectableRemoteId = userData.getRemoteIds()[0];
}
int detectedFieldScore = -1;
@@ -1062,7 +1064,8 @@
if (detectableFieldId != null && detectableFieldId.equals(viewState.id)
&& currentValue.isText() && currentValue.getTextValue() != null) {
final String actualValue = currentValue.getTextValue().toString();
- final String expectedValue = fieldsDetection.getValue();
+ // TODO(b/67867469): hardcoded to just first entry on initial refactoring.
+ final String expectedValue = userData.getValues()[0];
if (actualValue.equalsIgnoreCase(expectedValue)) {
detectedRemoteId = detectableRemoteId;
detectedFieldScore = 0;
diff --git a/services/autofill/java/com/android/server/autofill/ui/FillUi.java b/services/autofill/java/com/android/server/autofill/ui/FillUi.java
index dfc65a32b..5ee3cbf 100644
--- a/services/autofill/java/com/android/server/autofill/ui/FillUi.java
+++ b/services/autofill/java/com/android/server/autofill/ui/FillUi.java
@@ -108,9 +108,11 @@
mCallback = callback;
final LayoutInflater inflater = LayoutInflater.from(context);
+
final ViewGroup decor = (ViewGroup) inflater.inflate(
R.layout.autofill_dataset_picker, null);
+
final RemoteViews.OnClickHandler interceptionHandler = new RemoteViews.OnClickHandler() {
@Override
public boolean onClickHandler(View view, PendingIntent pendingIntent,
@@ -153,7 +155,38 @@
mCallback.requestShowFillUi(mContentWidth, mContentHeight, mWindowPresenter);
} else {
final int datasetCount = response.getDatasets().size();
- final ArrayList<ViewItem> items = new ArrayList<>(datasetCount);
+
+ // Total items include the (optional) header and footer - we cannot use listview's
+ // addHeader() and addFooter() because it would complicate the scrolling logic.
+ int totalItems = datasetCount;
+
+ RemoteViews.OnClickHandler clickBlocker = null;
+ final RemoteViews headerPresentation = response.getHeader();
+ View header = null;
+ if (headerPresentation != null) {
+ clickBlocker = newClickBlocker();
+ header = headerPresentation.apply(context, null, clickBlocker);
+ totalItems++;
+ }
+
+ final RemoteViews footerPresentation = response.getFooter();
+ View footer = null;
+ if (footerPresentation != null) {
+ if (clickBlocker == null) { // already set for header
+ clickBlocker = newClickBlocker();
+ }
+ footer = footerPresentation.apply(context, null, clickBlocker);
+ totalItems++;
+ }
+ if (sVerbose) {
+ Slog.v(TAG, "Number datasets: " + datasetCount + " Total items: " + totalItems);
+ }
+
+ final ArrayList<ViewItem> items = new ArrayList<>(totalItems);
+ if (header != null) {
+ if (sVerbose) Slog.v(TAG, "adding header");
+ items.add(new ViewItem(null, null, null, header));
+ }
for (int i = 0; i < datasetCount; i++) {
final Dataset dataset = response.getDatasets().get(i);
final int index = dataset.getFieldIds().indexOf(focusedViewId);
@@ -184,6 +217,10 @@
items.add(new ViewItem(dataset, filter, valueText, view));
}
}
+ if (footer != null) {
+ if (sVerbose) Slog.v(TAG, "adding footer");
+ items.add(new ViewItem(null, null, null, footer));
+ }
mAdapter = new ItemsAdapter(items);
@@ -192,7 +229,12 @@
mListView.setVisibility(View.VISIBLE);
mListView.setOnItemClickListener((adapter, view, position, id) -> {
final ViewItem vi = mAdapter.getItem(position);
- mCallback.onDatasetPicked(vi.getDataset());
+ if (vi.dataset == null) {
+ // Clicked on header or footer; ignore.
+ if (sDebug) Slog.d(TAG, "Ignoring click on item " + position + ": " + view);
+ return;
+ }
+ mCallback.onDatasetPicked(vi.dataset);
});
if (filterText == null) {
@@ -206,6 +248,20 @@
}
}
+ /**
+ * Creates a remoteview interceptor used to block clicks.
+ */
+ private RemoteViews.OnClickHandler newClickBlocker() {
+ return new RemoteViews.OnClickHandler() {
+ @Override
+ public boolean onClickHandler(View view, PendingIntent pendingIntent,
+ Intent fillInIntent) {
+ if (sVerbose) Slog.v(TAG, "Ignoring click on " + view);
+ return true;
+ }
+ };
+ }
+
private void applyNewFilterText() {
final int oldCount = mAdapter.getCount();
mAdapter.getFilter().filter(mFilterText, (count) -> {
@@ -298,7 +354,7 @@
MeasureSpec.AT_MOST);
final int itemCount = mAdapter.getCount();
for (int i = 0; i < itemCount; i++) {
- View view = mAdapter.getItem(i).getView();
+ View view = mAdapter.getItem(i).view;
view.measure(widthMeasureSpec, heightMeasureSpec);
final int clampedMeasuredWidth = Math.min(view.getMeasuredWidth(), maxSize.x);
final int newContentWidth = Math.max(mContentWidth, clampedMeasuredWidth);
@@ -336,33 +392,21 @@
outPoint.y = (int) typedValue.getFraction(outPoint.y, outPoint.y);
}
+ /**
+ * An item for the list view - either a (clickable) dataset or a (read-only) header / footer.
+ */
private static class ViewItem {
- private final String mValue;
- private final Dataset mDataset;
- private final View mView;
- private final Pattern mFilter;
+ public final @Nullable String value;
+ public final @Nullable Dataset dataset;
+ public final @NonNull View view;
+ public final @Nullable Pattern filter;
- ViewItem(Dataset dataset, Pattern filter, String value, View view) {
- mDataset = dataset;
- mValue = value;
- mView = view;
- mFilter = filter;
- }
-
- public Pattern getFilter() {
- return mFilter;
- }
-
- public View getView() {
- return mView;
- }
-
- public Dataset getDataset() {
- return mDataset;
- }
-
- public String getValue() {
- return mValue;
+ ViewItem(@Nullable Dataset dataset, @Nullable Pattern filter, @Nullable String value,
+ @NonNull View view) {
+ this.dataset = dataset;
+ this.value = value;
+ this.view = view;
+ this.filter = filter;
}
}
@@ -525,15 +569,13 @@
final int itemCount = mAllItems.size();
for (int i = 0; i < itemCount; i++) {
final ViewItem item = mAllItems.get(i);
- final String value = item.getValue();
- final Pattern filter = item.getFilter();
final boolean matches;
- if (filter != null) {
- matches = filter.matcher(constraintLowerCase).matches();
+ if (item.filter != null) {
+ matches = item.filter.matcher(constraintLowerCase).matches();
} else {
- matches = (value == null)
- ? (item.mDataset.getAuthentication() == null)
- : value.toLowerCase().startsWith(constraintLowerCase);
+ matches = (item.value == null)
+ ? (item.dataset.getAuthentication() == null)
+ : item.value.toLowerCase().startsWith(constraintLowerCase);
}
if (matches) {
filteredItems.add(item);
@@ -580,7 +622,7 @@
@Override
public View getView(int position, View convertView, ViewGroup parent) {
- return getItem(position).getView();
+ return getItem(position).view;
}
}
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index bccae06..86b0164 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -972,7 +972,10 @@
getNetworkTypeName(networkType), "");
info.setDetailedState(NetworkInfo.DetailedState.DISCONNECTED, null, null);
info.setIsAvailable(true);
- state = new NetworkState(info, new LinkProperties(), new NetworkCapabilities(),
+ final NetworkCapabilities capabilities = new NetworkCapabilities();
+ capabilities.setCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING,
+ !info.isRoaming());
+ state = new NetworkState(info, new LinkProperties(), capabilities,
null, null, null);
}
filterNetworkStateForUid(state, uid, ignoreBlocked);
diff --git a/services/core/java/com/android/server/DeviceIdleController.java b/services/core/java/com/android/server/DeviceIdleController.java
index d7aeb8c..4f1e335 100644
--- a/services/core/java/com/android/server/DeviceIdleController.java
+++ b/services/core/java/com/android/server/DeviceIdleController.java
@@ -76,6 +76,7 @@
import android.util.TimeUtils;
import android.util.Xml;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.app.IBatteryStats;
import com.android.internal.os.AtomicFile;
import com.android.internal.os.BackgroundThread;
@@ -1754,6 +1755,27 @@
}
}
+ void removePowerSaveTempWhitelistAppChecked(String packageName, int userId)
+ throws RemoteException {
+ getContext().enforceCallingPermission(
+ Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST,
+ "No permission to change device idle whitelist");
+ final int callingUid = Binder.getCallingUid();
+ userId = ActivityManager.getService().handleIncomingUser(
+ Binder.getCallingPid(),
+ callingUid,
+ userId,
+ /*allowAll=*/ false,
+ /*requireFull=*/ false,
+ "removePowerSaveTempWhitelistApp", null);
+ final long token = Binder.clearCallingIdentity();
+ try {
+ removePowerSaveTempWhitelistAppInternal(packageName, userId);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
/**
* Adds an app to the temporary whitelist and resets the endTime for granting the
* app an exemption to access network and acquire wakelocks.
@@ -1819,6 +1841,32 @@
}
}
+ /**
+ * Removes an app from the temporary whitelist and notifies the observers.
+ */
+ private void removePowerSaveTempWhitelistAppInternal(String packageName, int userId) {
+ try {
+ final int uid = getContext().getPackageManager().getPackageUidAsUser(
+ packageName, userId);
+ final int appId = UserHandle.getAppId(uid);
+ removePowerSaveTempWhitelistAppDirectInternal(appId);
+ } catch (NameNotFoundException e) {
+ }
+ }
+
+ private void removePowerSaveTempWhitelistAppDirectInternal(int appId) {
+ synchronized (this) {
+ final int idx = mTempWhitelistAppIdEndTimes.indexOfKey(appId);
+ if (idx < 0) {
+ // Nothing else to do
+ return;
+ }
+ final String reason = mTempWhitelistAppIdEndTimes.valueAt(idx).second;
+ mTempWhitelistAppIdEndTimes.removeAt(idx);
+ onAppRemovedFromTempWhitelistLocked(appId, reason);
+ }
+ }
+
private void postTempActiveTimeoutMessage(int uid, long delay) {
if (DEBUG) {
Slog.d(TAG, "postTempActiveTimeoutMessage: uid=" + uid + ", delay=" + delay);
@@ -1840,18 +1888,7 @@
}
if (timeNow >= entry.first.value) {
mTempWhitelistAppIdEndTimes.delete(uid);
- if (DEBUG) {
- Slog.d(TAG, "Removing UID " + uid + " from temp whitelist");
- }
- updateTempWhitelistAppIdsLocked(uid, false);
- mHandler.obtainMessage(MSG_REPORT_TEMP_APP_WHITELIST_CHANGED, uid, 0)
- .sendToTarget();
- reportTempWhitelistChangedLocked();
- try {
- mBatteryStats.noteEvent(BatteryStats.HistoryItem.EVENT_TEMP_WHITELIST_FINISH,
- entry.second, uid);
- } catch (RemoteException e) {
- }
+ onAppRemovedFromTempWhitelistLocked(uid, entry.second);
} else {
// Need more time
if (DEBUG) {
@@ -1862,6 +1899,22 @@
}
}
+ @GuardedBy("this")
+ private void onAppRemovedFromTempWhitelistLocked(int appId, String reason) {
+ if (DEBUG) {
+ Slog.d(TAG, "Removing appId " + appId + " from temp whitelist");
+ }
+ updateTempWhitelistAppIdsLocked(appId, false);
+ mHandler.obtainMessage(MSG_REPORT_TEMP_APP_WHITELIST_CHANGED, appId, 0)
+ .sendToTarget();
+ reportTempWhitelistChangedLocked();
+ try {
+ mBatteryStats.noteEvent(BatteryStats.HistoryItem.EVENT_TEMP_WHITELIST_FINISH,
+ reason, appId);
+ } catch (RemoteException e) {
+ }
+ }
+
public void exitIdleInternal(String reason) {
synchronized (this) {
becomeActiveLocked(reason, Binder.getCallingUid());
@@ -2699,9 +2752,11 @@
+ "changes made using this won't be persisted across boots");
pw.println(" tempwhitelist");
pw.println(" Print packages that are temporarily whitelisted.");
- pw.println(" tempwhitelist [-u USER] [-d DURATION] [package ..]");
- pw.println(" Temporarily place packages in whitelist for DURATION milliseconds.");
+ pw.println(" tempwhitelist [-u USER] [-d DURATION] [-r] [package]");
+ pw.println(" Temporarily place package in whitelist for DURATION milliseconds.");
pw.println(" If no DURATION is specified, 10 seconds is used");
+ pw.println(" If [-r] option is used, then the package is removed from temp whitelist "
+ + "and any [-d] is ignored");
}
class Shell extends ShellCommand {
@@ -2985,6 +3040,7 @@
}
} else if ("tempwhitelist".equals(cmd)) {
long duration = 10000;
+ boolean removePkg = false;
String opt;
while ((opt=shell.getNextOption()) != null) {
if ("-u".equals(opt)) {
@@ -3001,16 +3057,25 @@
return -1;
}
duration = Long.parseLong(opt);
+ } else if ("-r".equals(opt)) {
+ removePkg = true;
}
}
String arg = shell.getNextArg();
if (arg != null) {
try {
- addPowerSaveTempWhitelistAppChecked(arg, duration, shell.userId, "shell");
+ if (removePkg) {
+ removePowerSaveTempWhitelistAppChecked(arg, shell.userId);
+ } else {
+ addPowerSaveTempWhitelistAppChecked(arg, duration, shell.userId, "shell");
+ }
} catch (Exception e) {
pw.println("Failed: " + e);
return -1;
}
+ } else if (removePkg) {
+ pw.println("[-r] requires a package name");
+ return -1;
} else {
dumpTempWhitelistSchedule(pw, false);
}
diff --git a/services/core/java/com/android/server/EventLogTags.logtags b/services/core/java/com/android/server/EventLogTags.logtags
index d3ce306..6174aec 100644
--- a/services/core/java/com/android/server/EventLogTags.logtags
+++ b/services/core/java/com/android/server/EventLogTags.logtags
@@ -32,9 +32,11 @@
# The device is being asked to go into a soft sleep (typically by the ungaze gesture).
# It logs the time remaining before the device would've normally gone to sleep without the request.
2731 power_soft_sleep_requested (savedwaketimems|2)
+# Power save state has changed. See BatterySaverController.java for the details.
+2739 battery_saver_mode (prevOffOrOn|1|5),(nowOffOrOn|1|5),(interactive|1|5),(features|3|5)
#
-# Leave IDs through 2739 for more power logs (2730 used by battery_discharge above)
+# Leave IDs through 2740 for more power logs (2730 used by battery_discharge above)
#
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 43a4aef..5e67396 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -44,7 +44,9 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_TASK_ON_HOME;
import static android.content.pm.PackageManager.FEATURE_ACTIVITIES_ON_SECONDARY_DISPLAYS;
import static android.content.pm.PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT;
import static android.content.pm.PackageManager.FEATURE_LEANBACK_ONLY;
@@ -414,6 +416,7 @@
import com.android.server.ThreadPriorityBooster;
import com.android.server.Watchdog;
import com.android.server.am.ActivityStack.ActivityState;
+import com.android.server.am.EventLogTags;
import com.android.server.am.proto.ActivityManagerServiceProto;
import com.android.server.am.proto.BroadcastProto;
import com.android.server.am.proto.GrantUriProto;
@@ -632,7 +635,7 @@
final ActivityStackSupervisor mStackSupervisor;
private final KeyguardController mKeyguardController;
- final ActivityStarter mActivityStarter;
+ private final ActivityStartController mActivityStartController;
final ClientLifecycleManager mLifecycleManager;
@@ -1361,7 +1364,7 @@
@GuardedBy("this") boolean mCallFinishBooting = false;
@GuardedBy("this") boolean mBootAnimationComplete = false;
@GuardedBy("this") boolean mLaunchWarningShown = false;
- @GuardedBy("this") boolean mCheckedForSetup = false;
+ private @GuardedBy("this") boolean mCheckedForSetup = false;
final Context mContext;
@@ -1707,7 +1710,6 @@
static final int SHOW_UID_ERROR_UI_MSG = 14;
static final int SHOW_FINGERPRINT_ERROR_UI_MSG = 15;
static final int PROC_START_TIMEOUT_MSG = 20;
- static final int DO_PENDING_ACTIVITY_LAUNCHES_MSG = 21;
static final int KILL_APPLICATION_MSG = 22;
static final int FINALIZE_PENDING_INTENT_MSG = 23;
static final int POST_HEAVY_NOTIFICATION_MSG = 24;
@@ -2077,11 +2079,6 @@
processContentProviderPublishTimedOutLocked(app);
}
} break;
- case DO_PENDING_ACTIVITY_LAUNCHES_MSG: {
- synchronized (ActivityManagerService.this) {
- mActivityStarter.doPendingActivityLaunchesLocked(true);
- }
- } break;
case KILL_APPLICATION_MSG: {
synchronized (ActivityManagerService.this) {
final int appId = msg.arg1;
@@ -2677,7 +2674,7 @@
mContext = mInjector.getContext();
mUiContext = null;
GL_ES_VERSION = 0;
- mActivityStarter = null;
+ mActivityStartController = null;
mAppErrors = null;
mAppWarnings = null;
mAppOpsService = mInjector.getAppOpsService(null, null);
@@ -2801,7 +2798,7 @@
mIntentFirewall = new IntentFirewall(new IntentFirewallInterface(), mHandler);
mTaskChangeNotificationController =
new TaskChangeNotificationController(this, mStackSupervisor, mHandler);
- mActivityStarter = new ActivityStarter(this);
+ mActivityStartController = new ActivityStartController(this);
mRecentTasks = createRecentTasks();
mStackSupervisor.setRecentTasks(mRecentTasks);
mLockTaskController = new LockTaskController(mContext, mStackSupervisor, mHandler);
@@ -4121,7 +4118,7 @@
// For ANR debugging to verify if the user activity is the one that actually
// launched.
final String myReason = reason + ":" + userId + ":" + resolvedUserId;
- mActivityStarter.startHomeActivityLocked(intent, aInfo, myReason);
+ mActivityStartController.startHomeActivity(intent, aInfo, myReason);
}
} else {
Slog.wtf(TAG, "No home screen found for " + intent, new Throwable());
@@ -4154,49 +4151,12 @@
return ai;
}
- /**
- * Starts the "new version setup screen" if appropriate.
- */
- void startSetupActivityLocked() {
- // Only do this once per boot.
- if (mCheckedForSetup) {
- return;
- }
+ boolean getCheckedForSetup() {
+ return mCheckedForSetup;
+ }
- // We will show this screen if the current one is a different
- // version than the last one shown, and we are not running in
- // low-level factory test mode.
- final ContentResolver resolver = mContext.getContentResolver();
- if (mFactoryTest != FactoryTest.FACTORY_TEST_LOW_LEVEL &&
- Settings.Global.getInt(resolver,
- Settings.Global.DEVICE_PROVISIONED, 0) != 0) {
- mCheckedForSetup = true;
-
- // See if we should be showing the platform update setup UI.
- final Intent intent = new Intent(Intent.ACTION_UPGRADE_SETUP);
- final List<ResolveInfo> ris = mContext.getPackageManager().queryIntentActivities(intent,
- PackageManager.MATCH_SYSTEM_ONLY | PackageManager.GET_META_DATA);
- if (!ris.isEmpty()) {
- final ResolveInfo ri = ris.get(0);
- String vers = ri.activityInfo.metaData != null
- ? ri.activityInfo.metaData.getString(Intent.METADATA_SETUP_VERSION)
- : null;
- if (vers == null && ri.activityInfo.applicationInfo.metaData != null) {
- vers = ri.activityInfo.applicationInfo.metaData.getString(
- Intent.METADATA_SETUP_VERSION);
- }
- String lastVers = Settings.Secure.getString(
- resolver, Settings.Secure.LAST_SETUP_SHOWN);
- if (vers != null && !vers.equals(lastVers)) {
- intent.setFlags(FLAG_ACTIVITY_NEW_TASK);
- intent.setComponent(new ComponentName(
- ri.activityInfo.packageName, ri.activityInfo.name));
- mActivityStarter.startActivityLocked(null, intent, null /*ephemeralIntent*/,
- null, ri.activityInfo, null /*rInfo*/, null, null, null, null, 0, 0, 0,
- null, 0, 0, 0, null, false, false, null, null, "startSetupActivity");
- }
- }
- }
+ void setCheckedForSetup(boolean checked) {
+ mCheckedForSetup = checked;
}
CompatibilityInfo compatibilityInfoForPackageLocked(ApplicationInfo ai) {
@@ -4562,8 +4522,8 @@
userId = mUserController.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
userId, false, ALLOW_FULL_ONLY, "startActivity", null);
// TODO: Switch to user app stacks here.
- return mActivityStarter.startActivityMayWait(caller, -1, callingPackage, intent,
- resolvedType, null, null, resultTo, resultWho, requestCode, startFlags,
+ return mActivityStartController.startActivityMayWait(caller, -1, callingPackage,
+ intent, resolvedType, null, null, resultTo, resultWho, requestCode, startFlags,
profilerInfo, null, null, bOptions, false, userId, null, "startActivityAsUser");
}
@@ -4625,10 +4585,10 @@
// TODO: Switch to user app stacks here.
try {
- int ret = mActivityStarter.startActivityMayWait(null, targetUid, targetPackage, intent,
- resolvedType, null, null, resultTo, resultWho, requestCode, startFlags, null,
- null, null, bOptions, ignoreTargetSecurity, userId, null,
- "startActivityAsCaller");
+ int ret = mActivityStartController.startActivityMayWait(null, targetUid,
+ targetPackage, intent, resolvedType, null, null, resultTo, resultWho,
+ requestCode, startFlags, null, null, null, bOptions, ignoreTargetSecurity,
+ userId, null, "startActivityAsCaller");
return ret;
} catch (SecurityException e) {
// XXX need to figure out how to propagate to original app.
@@ -4655,9 +4615,9 @@
userId, false, ALLOW_FULL_ONLY, "startActivityAndWait", null);
WaitResult res = new WaitResult();
// TODO: Switch to user app stacks here.
- mActivityStarter.startActivityMayWait(caller, -1, callingPackage, intent, resolvedType,
- null, null, resultTo, resultWho, requestCode, startFlags, profilerInfo, res, null,
- bOptions, false, userId, null, "startActivityAndWait");
+ mActivityStartController.startActivityMayWait(caller, -1, callingPackage, intent,
+ resolvedType, null, null, resultTo, resultWho, requestCode, startFlags,
+ profilerInfo, res, null, bOptions, false, userId, null, "startActivityAndWait");
return res;
}
@@ -4669,9 +4629,9 @@
userId = mUserController.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
userId, false, ALLOW_FULL_ONLY, "startActivityWithConfig", null);
// TODO: Switch to user app stacks here.
- int ret = mActivityStarter.startActivityMayWait(caller, -1, callingPackage, intent,
- resolvedType, null, null, resultTo, resultWho, requestCode, startFlags,
- null, null, config, bOptions, false, userId, null, "startActivityWithConfig");
+ int ret = mActivityStartController.startActivityMayWait(caller, -1, callingPackage, intent,
+ resolvedType, null, null, resultTo, resultWho, requestCode, startFlags, null, null,
+ config, bOptions, false, userId, null, "startActivityWithConfig");
return ret;
}
@@ -4718,9 +4678,9 @@
userId = mUserController.handleIncomingUser(callingPid, callingUid, userId, false,
ALLOW_FULL_ONLY, "startVoiceActivity", null);
// TODO: Switch to user app stacks here.
- return mActivityStarter.startActivityMayWait(null, callingUid, callingPackage, intent,
- resolvedType, session, interactor, null, null, 0, startFlags, profilerInfo, null,
- null, bOptions, false, userId, null, "startVoiceActivity");
+ return mActivityStartController.startActivityMayWait(null, callingUid, callingPackage,
+ intent, resolvedType, session, interactor, null, null, 0, startFlags, profilerInfo,
+ null, null, bOptions, false, userId, null, "startVoiceActivity");
}
@Override
@@ -4729,9 +4689,9 @@
enforceCallingPermission(BIND_VOICE_INTERACTION, "startAssistantActivity()");
userId = mUserController.handleIncomingUser(callingPid, callingUid, userId, false,
ALLOW_FULL_ONLY, "startAssistantActivity", null);
- return mActivityStarter.startActivityMayWait(null, callingUid, callingPackage, intent,
- resolvedType, null, null, null, null, 0, 0, null, null, null, bOptions, false,
- userId, null, "startAssistantActivity");
+ return mActivityStartController.startActivityMayWait(null, callingUid, callingPackage,
+ intent, resolvedType, null, null, null, null, 0, 0, null, null, null, bOptions,
+ false, userId, null, "startAssistantActivity");
}
@Override
@@ -4771,9 +4731,9 @@
intent.setFlags(FLAG_ACTIVITY_NEW_TASK);
intent.setComponent(recentsComponent);
intent.putExtras(options);
- return mActivityStarter.startActivityMayWait(null, recentsUid, recentsPackage,
- intent, null, null, null, null, null, 0, 0, null, null, null, activityOptions,
- false, userId, null, "startRecentsActivity");
+ return mActivityStartController.startActivityMayWait(null, recentsUid,
+ recentsPackage, intent, null, null, null, null, null, 0, 0, null, null,
+ null, activityOptions, false, userId, null, "startRecentsActivity");
}
} finally {
Binder.restoreCallingIdentity(origId);
@@ -4946,7 +4906,7 @@
}
final long origId = Binder.clearCallingIdentity();
- int res = mActivityStarter.startActivityLocked(r.app.thread, intent,
+ int res = mActivityStartController.startActivity(r.app.thread, intent,
null /*ephemeralIntent*/, r.resolvedType, aInfo, null /*rInfo*/, null,
null, resultTo != null ? resultTo.appToken : null, resultWho, requestCode, -1,
r.launchedFromUid, r.launchedFromPackage, -1, r.launchedFromUid, 0, options,
@@ -4976,20 +4936,6 @@
}
}
- final int startActivityInPackage(int uid, String callingPackage,
- Intent intent, String resolvedType, IBinder resultTo,
- String resultWho, int requestCode, int startFlags, Bundle bOptions, int userId,
- TaskRecord inTask, String reason) {
-
- userId = mUserController.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
- userId, false, ALLOW_FULL_ONLY, "startActivityInPackage", null);
-
- // TODO: Switch to user app stacks here.
- return mActivityStarter.startActivityMayWait(null, uid, callingPackage, intent,
- resolvedType, null, null, resultTo, resultWho, requestCode, startFlags,
- null, null, null, bOptions, false, userId, inTask, reason);
- }
-
@Override
public final int startActivities(IApplicationThread caller, String callingPackage,
Intent[] intents, String[] resolvedTypes, IBinder resultTo, Bundle bOptions,
@@ -4999,21 +4945,8 @@
userId = mUserController.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
userId, false, ALLOW_FULL_ONLY, reason, null);
// TODO: Switch to user app stacks here.
- int ret = mActivityStarter.startActivities(caller, -1, callingPackage, intents,
- resolvedTypes, resultTo, bOptions, userId, reason);
- return ret;
- }
-
- final int startActivitiesInPackage(int uid, String callingPackage,
- Intent[] intents, String[] resolvedTypes, IBinder resultTo,
- Bundle bOptions, int userId) {
-
- final String reason = "startActivityInPackage";
- userId = mUserController.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
- userId, false, ALLOW_FULL_ONLY, reason, null);
- // TODO: Switch to user app stacks here.
- int ret = mActivityStarter.startActivities(null, uid, callingPackage, intents, resolvedTypes,
- resultTo, bOptions, userId, reason);
+ int ret = mActivityStartController.startActivities(caller, -1, callingPackage,
+ intents, resolvedTypes, resultTo, bOptions, userId, reason);
return ret;
}
@@ -6667,7 +6600,7 @@
ProcessList.INVALID_ADJ, callerWillRestart, true, doit, evenPersistent,
packageName == null ? ("stop user " + userId) : ("stop " + packageName));
- didSomething |= mActivityStarter.clearPendingActivityLaunchesLocked(packageName);
+ didSomething |= mActivityStartController.clearPendingActivityLaunches(packageName);
if (mStackSupervisor.finishDisabledPackageActivitiesLocked(
packageName, null, doit, evenPersistent, userId)) {
@@ -10501,7 +10434,7 @@
public void setTaskWindowingMode(int taskId, int windowingMode, boolean toTop) {
if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
setTaskWindowingModeSplitScreenPrimary(taskId, SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT,
- toTop, ANIMATE, null /* initialBounds */);
+ toTop, ANIMATE, null /* initialBounds */, true /* showRecents */);
return;
}
enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "setTaskWindowingMode()");
@@ -10547,10 +10480,12 @@
* @param animate Whether we should play an animation for the moving the task.
* @param initialBounds If the primary stack gets created, it will use these bounds for the
* stack. Pass {@code null} to use default bounds.
+ * @param showRecents If the recents activity should be shown on the other side of the task
+ * going into split-screen mode.
*/
@Override
public boolean setTaskWindowingModeSplitScreenPrimary(int taskId, int createMode, boolean toTop,
- boolean animate, Rect initialBounds) {
+ boolean animate, Rect initialBounds, boolean showRecents) {
enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS,
"setTaskWindowingModeSplitScreenPrimary()");
synchronized (this) {
@@ -10575,7 +10510,7 @@
if (toTop) {
stack.moveToFront("setTaskWindowingModeSplitScreenPrimary", task);
}
- stack.setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, animate);
+ stack.setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, animate, showRecents);
return windowingMode != task.getWindowingMode();
} finally {
Binder.restoreCallingIdentity(ident);
@@ -11755,6 +11690,10 @@
return AppGlobals.getPackageManager();
}
+ ActivityStartController getActivityStartController() {
+ return mActivityStartController;
+ }
+
PackageManagerInternal getPackageManagerInternalLocked() {
if (mPackageManagerInt == null) {
mPackageManagerInt = LocalServices.getService(PackageManagerInternal.class);
@@ -12604,7 +12543,16 @@
synchronized (this) {
final long ident = Binder.clearCallingIdentity();
try {
- mActivityStarter.startConfirmCredentialIntent(intent, options);
+ intent.addFlags(FLAG_ACTIVITY_NEW_TASK |
+ FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS |
+ FLAG_ACTIVITY_TASK_ON_HOME);
+ ActivityOptions activityOptions = options != null
+ ? new ActivityOptions(options)
+ : ActivityOptions.makeBasic();
+ activityOptions.setLaunchTaskId(
+ mStackSupervisor.getHomeActivity().getTask().taskId);
+ mContext.startActivityAsUser(intent, activityOptions.toBundle(),
+ UserHandle.CURRENT);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -12623,9 +12571,7 @@
mAppSwitchesAllowedTime = SystemClock.uptimeMillis()
+ APP_SWITCH_DELAY_TIME;
mDidAppSwitch = false;
- mHandler.removeMessages(DO_PENDING_ACTIVITY_LAUNCHES_MSG);
- Message msg = mHandler.obtainMessage(DO_PENDING_ACTIVITY_LAUNCHES_MSG);
- mHandler.sendMessageDelayed(msg, APP_SWITCH_DELAY_TIME);
+ mActivityStartController.schedulePendingActivityLaunches(APP_SWITCH_DELAY_TIME);
}
}
@@ -15491,7 +15437,7 @@
private void dumpActivityStarterLocked(PrintWriter pw, String dumpPackage) {
pw.println("ACTIVITY MANAGER STARTER (dumpsys activity starter)");
- mActivityStarter.dump(pw, "", dumpPackage);
+ mActivityStartController.dump(pw, "", dumpPackage);
}
void dumpActivitiesLocked(FileDescriptor fd, PrintWriter pw, String[] args,
@@ -24248,8 +24194,8 @@
}
synchronized (ActivityManagerService.this) {
- return startActivitiesInPackage(packageUid, packageName, intents, resolvedTypes,
- /*resultTo*/ null, bOptions, userId);
+ return mActivityStartController.startActivitiesInPackage(packageUid, packageName,
+ intents, resolvedTypes, /*resultTo*/ null, bOptions, userId);
}
}
@@ -24400,7 +24346,7 @@
pw.println(" Reason: " + reason);
}
pw.println();
- mActivityStarter.dump(pw, " ", null);
+ mActivityStartController.dump(pw, " ", null);
pw.println();
pw.println("-------------------------------------------------------------------------------");
dumpActivitiesLocked(null /* fd */, pw, null /* args */, 0 /* opti */,
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index bdfd82f..af4d3f8 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -144,6 +144,7 @@
import com.android.internal.os.BatteryStatsImpl;
import com.android.server.Watchdog;
import com.android.server.am.ActivityManagerService.ItemMatcher;
+import com.android.server.am.EventLogTags;
import com.android.server.wm.ConfigurationContainer;
import com.android.server.wm.StackWindowController;
import com.android.server.wm.StackWindowListener;
@@ -482,10 +483,10 @@
@Override
public void setWindowingMode(int windowingMode) {
- setWindowingMode(windowingMode, false /* animate */);
+ setWindowingMode(windowingMode, false /* animate */, true /* showRecents */);
}
- void setWindowingMode(int preferredWindowingMode, boolean animate) {
+ void setWindowingMode(int preferredWindowingMode, boolean animate, boolean showRecents) {
final int currentMode = getWindowingMode();
final ActivityDisplay display = getDisplay();
final TaskRecord topTask = topTask();
@@ -579,7 +580,7 @@
resize(mTmpRect2, null /* tempTaskBounds */, null /* tempTaskInsetBounds */);
}
} finally {
- if (!alreadyInSplitScreenMode && mDisplayId == DEFAULT_DISPLAY
+ if (showRecents && !alreadyInSplitScreenMode && mDisplayId == DEFAULT_DISPLAY
&& windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
// Make sure recents stack exist when creating a dock stack as it normally needs to
// be on the other side of the docked stack and we make visibility decisions based
@@ -1424,9 +1425,8 @@
if (prev.app != null && prev.app.thread != null) {
if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Enqueueing pending pause: " + prev);
try {
- EventLog.writeEvent(EventLogTags.AM_PAUSE_ACTIVITY,
- prev.userId, System.identityHashCode(prev),
- prev.shortComponentName);
+ EventLogTags.writeAmPauseActivity(prev.userId, System.identityHashCode(prev),
+ prev.shortComponentName, "userLeaving=" + userLeaving);
mService.updateUsageStats(prev, false);
mService.mLifecycleManager.scheduleTransaction(prev.app.thread, prev.appToken,
@@ -2260,7 +2260,7 @@
// Remember how we'll process this pause/resume situation, and ensure
// that the state is reset however we wind up proceeding.
- final boolean userLeaving = mStackSupervisor.mUserLeaving;
+ boolean userLeaving = mStackSupervisor.mUserLeaving;
mStackSupervisor.mUserLeaving = false;
if (!hasRunningActivity) {
@@ -2331,6 +2331,13 @@
// So, why aren't we using prev here??? See the param comment on the method. prev doesn't
// represent the last resumed activity. However, the last focus stack does if it isn't null.
final ActivityRecord lastResumed = lastFocusedStack.mResumedActivity;
+ if (userLeaving && inMultiWindowMode() && lastFocusedStack.shouldBeVisible(next)) {
+ // The user isn't leaving if this stack is the multi-window mode and the last
+ // focused stack should still be visible.
+ if(DEBUG_USER_LEAVING) Slog.i(TAG_USER_LEAVING, "Overriding userLeaving to false"
+ + " next=" + next + " lastResumed=" + lastResumed);
+ userLeaving = false;
+ }
lastResumedCanPip = lastResumed != null && lastResumed.checkEnterPictureInPictureState(
"resumeTopActivity", userLeaving /* beforeStopping */);
}
@@ -3892,11 +3899,12 @@
try {
ActivityInfo aInfo = AppGlobals.getPackageManager().getActivityInfo(
destIntent.getComponent(), 0, srec.userId);
- int res = mService.mActivityStarter.startActivityLocked(srec.app.thread,
- destIntent, null /*ephemeralIntent*/, null, aInfo, null /*rInfo*/, null,
- null, parent.appToken, null, 0, -1, parent.launchedFromUid,
- parent.launchedFromPackage, -1, parent.launchedFromUid, 0, null,
- false, true, null, null, "navigateUpTo");
+ int res = mService.getActivityStartController().startActivity(
+ srec.app.thread, destIntent, null /*ephemeralIntent*/, null, aInfo,
+ null /*rInfo*/, null, null, parent.appToken, null, 0, -1,
+ parent.launchedFromUid, parent.launchedFromPackage, -1,
+ parent.launchedFromUid, 0, null, false, true, null, null,
+ "navigateUpTo");
foundParentInTask = res == ActivityManager.START_SUCCESS;
} catch (RemoteException e) {
foundParentInTask = false;
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 445bf67..48c08a5 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -129,7 +129,7 @@
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManager.DisplayListener;
import android.hardware.display.DisplayManagerInternal;
-import android.hardware.input.InputManagerInternal;
+import android.hardware.power.V1_0.PowerHint;
import android.os.Binder;
import android.os.Bundle;
import android.os.Debug;
@@ -364,6 +364,9 @@
* is being brought in front of us. */
boolean mUserLeaving = false;
+ /** Set when a power hint has started, but not ended. */
+ private boolean mPowerHintSent;
+
/**
* We don't want to allow the device to go to sleep while in the process
* of launching an activity. This is primarily to allow alarm intent
@@ -978,7 +981,7 @@
}
}
// Send launch end powerhint when idle
- mService.mActivityStarter.sendPowerHintForLaunchEndIfNeeded();
+ sendPowerHintForLaunchEndIfNeeded();
return true;
}
@@ -1470,7 +1473,7 @@
// a chance to initialize itself while in the background, making the
// switch back to it faster and look better.
if (isFocusedStack(stack)) {
- mService.startSetupActivityLocked();
+ mService.getActivityStartController().startSetupActivity();
}
// Update any services we are bound to that might care about whether
@@ -1531,6 +1534,32 @@
"activity", r.intent.getComponent(), false, false, true);
}
+ void sendPowerHintForLaunchStartIfNeeded(boolean forceSend, ActivityRecord targetActivity) {
+ boolean sendHint = forceSend;
+
+ if (!sendHint) {
+ // If not forced, send power hint when the activity's process is different than the
+ // current resumed activity.
+ final ActivityRecord resumedActivity = getResumedActivityLocked();
+ sendHint = resumedActivity == null
+ || resumedActivity.app == null
+ || !resumedActivity.app.equals(targetActivity.app);
+ }
+
+ if (sendHint && mService.mLocalPowerManager != null) {
+ mService.mLocalPowerManager.powerHint(PowerHint.LAUNCH, 1);
+ mPowerHintSent = true;
+ }
+ }
+
+ void sendPowerHintForLaunchEndIfNeeded() {
+ // Trigger launch power hint if activity is launched
+ if (mPowerHintSent && mService.mLocalPowerManager != null) {
+ mService.mLocalPowerManager.powerHint(PowerHint.LAUNCH, 0);
+ mPowerHintSent = false;
+ }
+ }
+
boolean checkStartAnyActivityPermission(Intent intent, ActivityInfo aInfo,
String resultWho, int requestCode, int callingPid, int callingUid,
String callingPackage, boolean ignoreTargetSecurity, ProcessRecord callerApp,
@@ -2481,14 +2510,14 @@
}
}
- private void deferUpdateBounds(int activityType) {
+ void deferUpdateBounds(int activityType) {
final ActivityStack stack = getStack(WINDOWING_MODE_UNDEFINED, activityType);
if (stack != null) {
stack.deferUpdateBounds();
}
}
- private void continueUpdateBounds(int activityType) {
+ void continueUpdateBounds(int activityType) {
final ActivityStack stack = getStack(WINDOWING_MODE_UNDEFINED, activityType);
if (stack != null) {
stack.continueUpdateBounds();
@@ -3335,7 +3364,7 @@
}
// Send launch end powerhint before going sleep
- mService.mActivityStarter.sendPowerHintForLaunchEndIfNeeded();
+ sendPowerHintForLaunchEndIfNeeded();
removeSleepTimeouts();
@@ -4473,7 +4502,7 @@
*
* @param task The task to put into resizing mode
*/
- private void setResizingDuringAnimation(TaskRecord task) {
+ void setResizingDuringAnimation(TaskRecord task) {
mResizingTasksDuringAnimation.add(task.taskId);
task.setTaskDockedResizing(true);
}
@@ -4532,8 +4561,7 @@
&& task.getRootActivity() != null) {
final ActivityRecord targetActivity = task.getTopActivity();
- mService.mActivityStarter.sendPowerHintForLaunchStartIfNeeded(true /* forceSend */,
- targetActivity);
+ sendPowerHintForLaunchStartIfNeeded(true /* forceSend */, targetActivity);
mActivityMetricsLogger.notifyActivityLaunching();
try {
mService.moveTaskToFrontLocked(task.taskId, 0, bOptions,
@@ -4550,8 +4578,9 @@
setResizingDuringAnimation(task);
}
- mService.mActivityStarter.postStartActivityProcessing(task.getTopActivity(),
- ActivityManager.START_TASK_TO_FRONT, task.getStack());
+ mService.getActivityStartController().postStartActivityProcessingForLastStarter(
+ task.getTopActivity(), ActivityManager.START_TASK_TO_FRONT,
+ task.getStack());
return ActivityManager.START_TASK_TO_FRONT;
}
callingUid = task.mCallingUid;
@@ -4559,8 +4588,9 @@
intent = task.intent;
intent.addFlags(Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY);
userId = task.userId;
- int result = mService.startActivityInPackage(callingUid, callingPackage, intent, null,
- null, null, 0, 0, bOptions, userId, task, "startActivityFromRecents");
+ int result = mService.getActivityStartController().startActivityInPackage(callingUid,
+ callingPackage, intent, null, null, null, 0, 0, bOptions, userId, task,
+ "startActivityFromRecents");
if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
setResizingDuringAnimation(task);
}
diff --git a/services/core/java/com/android/server/am/ActivityStartController.java b/services/core/java/com/android/server/am/ActivityStartController.java
new file mode 100644
index 0000000..317a68f
--- /dev/null
+++ b/services/core/java/com/android/server/am/ActivityStartController.java
@@ -0,0 +1,434 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.am;
+
+import static android.app.ActivityManager.START_SUCCESS;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.am.ActivityManagerService.ALLOW_FULL_ONLY;
+
+import android.app.ActivityOptions;
+import android.app.IApplicationThread;
+import android.app.ProfilerInfo;
+import android.app.WaitResult;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.res.Configuration;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.FactoryTest;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.provider.Settings;
+import android.service.voice.IVoiceInteractionSession;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.am.ActivityStackSupervisor.PendingActivityLaunch;
+import com.android.internal.app.IVoiceInteractor;
+import com.android.server.am.ActivityStarter.DefaultFactory;
+import com.android.server.am.ActivityStarter.Factory;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Controller for delegating activity launches.
+ *
+ * This class' main objective is to take external activity start requests and prepare them into
+ * a series of discrete activity launches that can be handled by an {@link ActivityStarter}. It is
+ * also responsible for handling logic that happens around an activity launch, but doesn't
+ * necessarily influence the activity start. Examples include power hint management, processing
+ * through the pending activity list, and recording home activity launches.
+ */
+public class ActivityStartController {
+ private static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityStartController" : TAG_AM;
+
+ private static final int DO_PENDING_ACTIVITY_LAUNCHES_MSG = 1;
+
+ private final ActivityManagerService mService;
+ private final ActivityStackSupervisor mSupervisor;
+ private final ActivityStartInterceptor mInterceptor;
+
+ /** Last home activity record we attempted to start. */
+ private ActivityRecord mLastHomeActivityStartRecord;
+
+ /** Temporary array to capture start activity results */
+ private ActivityRecord[] tmpOutRecord = new ActivityRecord[1];
+
+ /**The result of the last home activity we attempted to start. */
+ private int mLastHomeActivityStartResult;
+
+ /** A list of activities that are waiting to launch. */
+ private final ArrayList<ActivityStackSupervisor.PendingActivityLaunch>
+ mPendingActivityLaunches = new ArrayList<>();
+
+ private final Factory mFactory;
+
+ private final Handler mHandler;
+
+ private final class StartHandler extends Handler {
+ public StartHandler(Looper looper) {
+ super(looper, null, true);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch(msg.what) {
+ case DO_PENDING_ACTIVITY_LAUNCHES_MSG:
+ synchronized (mService) {
+ doPendingActivityLaunches(true);
+ }
+ break;
+ }
+ }
+ }
+
+ /**
+ * TODO(b/64750076): Capture information necessary for dump and
+ * {@link #postStartActivityProcessingForLastStarter} rather than keeping the entire object
+ * around */
+ private ActivityStarter mLastStarter;
+
+ ActivityStartController(ActivityManagerService service) {
+ this(service, service.mStackSupervisor, new DefaultFactory());
+ }
+
+ @VisibleForTesting
+ ActivityStartController(ActivityManagerService service, ActivityStackSupervisor supervisor,
+ Factory factory) {
+ mService = service;
+ mSupervisor = supervisor;
+ mHandler = new StartHandler(mService.mHandlerThread.getLooper());
+ mInterceptor = new ActivityStartInterceptor(mService, mSupervisor);
+ mFactory = factory;
+ }
+
+ /**
+ * Retrieves a starter to be used for a new start request. The starter will be added to the
+ * active starters list.
+ *
+ * TODO(b/64750076): This should be removed when {@link #obtainStarter} is implemented. At that
+ * time, {@link ActivityStarter#execute} will be able to handle cleaning up the starter's
+ * internal references.
+ */
+ private ActivityStarter createStarter() {
+ mLastStarter = mFactory.getStarter(this, mService, mService.mStackSupervisor, mInterceptor);
+ return mLastStarter;
+ }
+
+ /**
+ * TODO(b/64750076): Remove once we directly expose starter interface to callers through
+ * {@link #obtainStarter}.
+ */
+ int startActivity(IApplicationThread caller, Intent intent, Intent ephemeralIntent,
+ String resolvedType, ActivityInfo aInfo, ResolveInfo rInfo,
+ IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
+ IBinder resultTo, String resultWho, int requestCode, int callingPid, int callingUid,
+ String callingPackage, int realCallingPid, int realCallingUid, int startFlags,
+ ActivityOptions options, boolean ignoreTargetSecurity, boolean componentSpecified,
+ ActivityRecord[] outActivity, TaskRecord inTask, String reason) {
+ return createStarter().startActivityLocked(caller, intent, ephemeralIntent, resolvedType,
+ aInfo, rInfo, voiceSession, voiceInteractor, resultTo, resultWho, requestCode,
+ callingPid, callingUid, callingPackage, realCallingPid, realCallingUid, startFlags,
+ options, ignoreTargetSecurity, componentSpecified, outActivity, inTask, reason);
+ }
+
+ /**
+ * TODO(b/64750076): usage of this doesn't seem right. We're making decisions based off the
+ * last starter for an arbitrary task record. Re-evaluate whether we can remove.
+ */
+ void postStartActivityProcessingForLastStarter(ActivityRecord r, int result,
+ ActivityStack targetStack) {
+ mLastStarter.postStartActivityProcessing(r, result, targetStack);
+ }
+
+ void startHomeActivity(Intent intent, ActivityInfo aInfo, String reason) {
+ mSupervisor.moveHomeStackTaskToTop(reason);
+
+ final ActivityStarter starter = createStarter();
+
+ mLastHomeActivityStartResult = starter.startActivityLocked(null /*caller*/, intent,
+ null /*ephemeralIntent*/, null /*resolvedType*/, aInfo, null /*rInfo*/,
+ null /*voiceSession*/, null /*voiceInteractor*/, null /*resultTo*/,
+ null /*resultWho*/, 0 /*requestCode*/, 0 /*callingPid*/, 0 /*callingUid*/,
+ null /*callingPackage*/, 0 /*realCallingPid*/, 0 /*realCallingUid*/,
+ 0 /*startFlags*/, null /*options*/, false /*ignoreTargetSecurity*/,
+ false /*componentSpecified*/, tmpOutRecord, null /*inTask*/,
+ "startHomeActivity: " + reason);
+ mLastHomeActivityStartRecord = tmpOutRecord[0];
+
+ if (mSupervisor.inResumeTopActivity) {
+ // If we are in resume section already, home activity will be initialized, but not
+ // resumed (to avoid recursive resume) and will stay that way until something pokes it
+ // again. We need to schedule another resume.
+ mSupervisor.scheduleResumeTopActivities();
+ }
+ }
+
+ /**
+ * Starts the "new version setup screen" if appropriate.
+ */
+ void startSetupActivity() {
+ // Only do this once per boot.
+ if (mService.getCheckedForSetup()) {
+ return;
+ }
+
+ // We will show this screen if the current one is a different
+ // version than the last one shown, and we are not running in
+ // low-level factory test mode.
+ final ContentResolver resolver = mService.mContext.getContentResolver();
+ if (mService.mFactoryTest != FactoryTest.FACTORY_TEST_LOW_LEVEL &&
+ Settings.Global.getInt(resolver,
+ Settings.Global.DEVICE_PROVISIONED, 0) != 0) {
+ mService.setCheckedForSetup(true);
+
+ // See if we should be showing the platform update setup UI.
+ final Intent intent = new Intent(Intent.ACTION_UPGRADE_SETUP);
+ final List<ResolveInfo> ris = mService.mContext.getPackageManager()
+ .queryIntentActivities(intent,
+ PackageManager.MATCH_SYSTEM_ONLY | PackageManager.GET_META_DATA);
+ if (!ris.isEmpty()) {
+ final ResolveInfo ri = ris.get(0);
+ String vers = ri.activityInfo.metaData != null
+ ? ri.activityInfo.metaData.getString(Intent.METADATA_SETUP_VERSION)
+ : null;
+ if (vers == null && ri.activityInfo.applicationInfo.metaData != null) {
+ vers = ri.activityInfo.applicationInfo.metaData.getString(
+ Intent.METADATA_SETUP_VERSION);
+ }
+ String lastVers = Settings.Secure.getString(
+ resolver, Settings.Secure.LAST_SETUP_SHOWN);
+ if (vers != null && !vers.equals(lastVers)) {
+ intent.setFlags(FLAG_ACTIVITY_NEW_TASK);
+ intent.setComponent(new ComponentName(
+ ri.activityInfo.packageName, ri.activityInfo.name));
+ startActivity(null, intent, null /*ephemeralIntent*/, null, ri.activityInfo,
+ null /*rInfo*/, null, null, null, null, 0, 0, 0, null, 0, 0, 0, null,
+ false, false, null, null, "startSetupActivity");
+ }
+ }
+ }
+ }
+
+ final int startActivityInPackage(int uid, String callingPackage,
+ Intent intent, String resolvedType, IBinder resultTo,
+ String resultWho, int requestCode, int startFlags, Bundle bOptions, int userId,
+ TaskRecord inTask, String reason) {
+
+ userId = mService.mUserController.handleIncomingUser(Binder.getCallingPid(),
+ Binder.getCallingUid(), userId, false, ALLOW_FULL_ONLY, "startActivityInPackage",
+ null);
+
+ // TODO: Switch to user app stacks here.
+ return startActivityMayWait(null, uid, callingPackage,
+ intent, resolvedType, null, null, resultTo, resultWho, requestCode, startFlags,
+ null, null, null, bOptions, false, userId, inTask, reason);
+ }
+
+ final int startActivitiesInPackage(int uid, String callingPackage, Intent[] intents,
+ String[] resolvedTypes, IBinder resultTo, Bundle bOptions, int userId) {
+ final String reason = "startActivityInPackage";
+ userId = mService.mUserController.handleIncomingUser(Binder.getCallingPid(),
+ Binder.getCallingUid(), userId, false, ALLOW_FULL_ONLY, reason, null);
+ // TODO: Switch to user app stacks here.
+ int ret = startActivities(null, uid, callingPackage, intents, resolvedTypes, resultTo,
+ bOptions, userId, reason);
+ return ret;
+ }
+
+ /**
+ * TODO(b/64750076): Remove once we directly expose starter interface to callers through
+ * {@link #obtainStarter}.
+ */
+ int startActivityMayWait(IApplicationThread caller, int callingUid,
+ String callingPackage, Intent intent, String resolvedType,
+ IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
+ IBinder resultTo, String resultWho, int requestCode, int startFlags,
+ ProfilerInfo profilerInfo, WaitResult outResult,
+ Configuration globalConfig, Bundle bOptions, boolean ignoreTargetSecurity, int userId,
+ TaskRecord inTask, String reason) {
+ return createStarter().startActivityMayWait(caller, callingUid, callingPackage, intent,
+ resolvedType, voiceSession, voiceInteractor, resultTo, resultWho, requestCode,
+ startFlags, profilerInfo, outResult, globalConfig, bOptions,
+ ignoreTargetSecurity,
+ userId, inTask, reason);
+ }
+
+ int startActivities(IApplicationThread caller, int callingUid, String callingPackage,
+ Intent[] intents, String[] resolvedTypes, IBinder resultTo, Bundle bOptions, int userId,
+ String reason) {
+ if (intents == null) {
+ throw new NullPointerException("intents is null");
+ }
+ if (resolvedTypes == null) {
+ throw new NullPointerException("resolvedTypes is null");
+ }
+ if (intents.length != resolvedTypes.length) {
+ throw new IllegalArgumentException("intents are length different than resolvedTypes");
+ }
+
+ final int realCallingPid = Binder.getCallingPid();
+ final int realCallingUid = Binder.getCallingUid();
+
+ int callingPid;
+ if (callingUid >= 0) {
+ callingPid = -1;
+ } else if (caller == null) {
+ callingPid = realCallingPid;
+ callingUid = realCallingUid;
+ } else {
+ callingPid = callingUid = -1;
+ }
+ final long origId = Binder.clearCallingIdentity();
+ try {
+ synchronized (mService) {
+ ActivityRecord[] outActivity = new ActivityRecord[1];
+ for (int i=0; i < intents.length; i++) {
+ Intent intent = intents[i];
+ if (intent == null) {
+ continue;
+ }
+
+ // Refuse possible leaked file descriptors
+ if (intent != null && intent.hasFileDescriptors()) {
+ throw new IllegalArgumentException("File descriptors passed in Intent");
+ }
+
+ boolean componentSpecified = intent.getComponent() != null;
+
+ // Don't modify the client's object!
+ intent = new Intent(intent);
+
+ // Collect information about the target of the Intent.
+ ActivityInfo aInfo = mSupervisor.resolveActivity(intent, resolvedTypes[i], 0,
+ null, userId);
+ // TODO: New, check if this is correct
+ aInfo = mService.getActivityInfoForUser(aInfo, userId);
+
+ if (aInfo != null &&
+ (aInfo.applicationInfo.privateFlags
+ & ApplicationInfo.PRIVATE_FLAG_CANT_SAVE_STATE) != 0) {
+ throw new IllegalArgumentException(
+ "FLAG_CANT_SAVE_STATE not supported here");
+ }
+
+ ActivityOptions options = ActivityOptions.fromBundle(
+ i == intents.length - 1 ? bOptions : null);
+ int res = startActivity(caller, intent, null /*ephemeralIntent*/,
+ resolvedTypes[i], aInfo, null /*rInfo*/, null, null, resultTo, null, -1,
+ callingPid, callingUid, callingPackage,
+ realCallingPid, realCallingUid, 0,
+ options, false, componentSpecified, outActivity, null, reason);
+ if (res < 0) {
+ return res;
+ }
+
+ resultTo = outActivity[0] != null ? outActivity[0].appToken : null;
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(origId);
+ }
+
+ return START_SUCCESS;
+ }
+
+ void schedulePendingActivityLaunches(long delayMs) {
+ mHandler.removeMessages(DO_PENDING_ACTIVITY_LAUNCHES_MSG);
+ Message msg = mHandler.obtainMessage(DO_PENDING_ACTIVITY_LAUNCHES_MSG);
+ mHandler.sendMessageDelayed(msg, delayMs);
+ }
+
+ void doPendingActivityLaunches(boolean doResume) {
+ while (!mPendingActivityLaunches.isEmpty()) {
+ final PendingActivityLaunch pal = mPendingActivityLaunches.remove(0);
+ final boolean resume = doResume && mPendingActivityLaunches.isEmpty();
+ final ActivityStarter starter = createStarter();
+ try {
+ starter.startActivity(pal.r, pal.sourceRecord, null, null, pal.startFlags, resume,
+ null, null, null /*outRecords*/);
+ } catch (Exception e) {
+ Slog.e(TAG, "Exception during pending activity launch pal=" + pal, e);
+ pal.sendErrorResult(e.getMessage());
+ }
+ }
+ }
+
+ void addPendingActivityLaunch(PendingActivityLaunch launch) {
+ mPendingActivityLaunches.add(launch);
+ }
+
+ boolean clearPendingActivityLaunches(String packageName) {
+ final int pendingLaunches = mPendingActivityLaunches.size();
+
+ for (int palNdx = pendingLaunches - 1; palNdx >= 0; --palNdx) {
+ final PendingActivityLaunch pal = mPendingActivityLaunches.get(palNdx);
+ final ActivityRecord r = pal.r;
+ if (r != null && r.packageName.equals(packageName)) {
+ mPendingActivityLaunches.remove(palNdx);
+ }
+ }
+ return mPendingActivityLaunches.size() < pendingLaunches;
+ }
+
+ void dump(PrintWriter pw, String prefix, String dumpPackage) {
+ pw.print(prefix);
+ pw.print("mLastHomeActivityStartResult=");
+ pw.println(mLastHomeActivityStartResult);
+
+ if (mLastHomeActivityStartRecord != null) {
+ pw.print(prefix);
+ pw.println("mLastHomeActivityStartRecord:");
+ mLastHomeActivityStartRecord.dump(pw, prefix + " ");
+ }
+
+ final boolean dumpPackagePresent = dumpPackage != null;
+
+ if (mLastStarter != null) {
+ final boolean dump = !dumpPackagePresent
+ || mLastStarter.relatedToPackage(dumpPackage)
+ || (mLastHomeActivityStartRecord != null
+ && dumpPackage.equals(mLastHomeActivityStartRecord.packageName));
+
+ if (dump) {
+ pw.print(prefix);
+ mLastStarter.dump(pw, prefix + " ");
+
+ if (dumpPackagePresent) {
+ return;
+ }
+ }
+ }
+
+ if (dumpPackagePresent) {
+ pw.print(prefix);
+ pw.println("(nothing)");
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java
index 2fc5dda..3bee4228 100644
--- a/services/core/java/com/android/server/am/ActivityStarter.java
+++ b/services/core/java/com/android/server/am/ActivityStarter.java
@@ -33,7 +33,6 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
-import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
import static android.content.Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT;
import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
@@ -45,7 +44,6 @@
import static android.content.Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED;
import static android.content.Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS;
import static android.content.Intent.FLAG_ACTIVITY_SINGLE_TOP;
-import static android.content.Intent.FLAG_ACTIVITY_TASK_ON_HOME;
import static android.content.pm.ActivityInfo.DOCUMENT_LAUNCH_ALWAYS;
import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_INSTANCE;
import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_TASK;
@@ -55,7 +53,6 @@
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_CONFIGURATION;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_FOCUS;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PERMISSIONS_REVIEW;
-import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_RECENTS;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_RESULTS;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_STACK;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_TASKS;
@@ -77,9 +74,9 @@
import static com.android.server.am.TaskRecord.REPARENT_MOVE_STACK_TO_FRONT;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityOptions;
-import android.app.AppGlobals;
import android.app.IApplicationThread;
import android.app.PendingIntent;
import android.app.ProfilerInfo;
@@ -96,7 +93,6 @@
import android.content.pm.UserInfo;
import android.content.res.Configuration;
import android.graphics.Rect;
-import android.hardware.power.V1_0.PowerHint;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
@@ -109,6 +105,7 @@
import android.util.EventLog;
import android.util.Slog;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.HeavyWeightSwitcherActivity;
import com.android.internal.app.IVoiceInteractor;
import com.android.server.am.ActivityStackSupervisor.PendingActivityLaunch;
@@ -116,11 +113,10 @@
import java.io.PrintWriter;
import java.text.DateFormat;
-import java.util.ArrayList;
import java.util.Date;
/**
- * Controller for interpreting how and then launching activities.
+ * Controller for interpreting how and then launching an activity.
*
* This class collects all the logic for determining how an intent and flags should be turned into
* an activity and associated task and stack.
@@ -137,7 +133,7 @@
private final ActivityStackSupervisor mSupervisor;
private final ActivityStartInterceptor mInterceptor;
- final ArrayList<PendingActivityLaunch> mPendingActivityLaunches = new ArrayList<>();
+ private final ActivityStartController mController;
// Share state variable among methods when starting an activity.
private ActivityRecord mStartActivity;
@@ -171,7 +167,6 @@
private boolean mNoAnimation;
private boolean mKeepCurTransition;
private boolean mAvoidMoveToFront;
- private boolean mPowerHintSent;
// We must track when we deliver the new intent since multiple code paths invoke
// {@link #deliverNewIntent}. This is due to early returns in the code path. This flag is used
@@ -182,10 +177,6 @@
private IVoiceInteractionSession mVoiceSession;
private IVoiceInteractor mVoiceInteractor;
- // Last home activity record we attempted to start
- private final ActivityRecord[] mLastHomeActivityStartRecord = new ActivityRecord[1];
- // The result of the last home activity we attempted to start.
- private int mLastHomeActivityStartResult;
// Last activity record we attempted to start
private final ActivityRecord[] mLastStartActivityRecord = new ActivityRecord[1];
// The result of the last activity we attempted to start.
@@ -195,48 +186,49 @@
// The reason we were trying to start the last activity
private String mLastStartReason;
- private void reset() {
- mStartActivity = null;
- mIntent = null;
- mCallingUid = -1;
- mOptions = null;
-
- mLaunchTaskBehind = false;
- mLaunchFlags = 0;
- mLaunchMode = INVALID_LAUNCH_MODE;
-
- mLaunchBounds.setEmpty();
-
- mNotTop = null;
- mDoResume = false;
- mStartFlags = 0;
- mSourceRecord = null;
- mPreferredDisplayId = INVALID_DISPLAY;
-
- mInTask = null;
- mAddingToTask = false;
- mReuseTask = null;
-
- mNewTaskInfo = null;
- mNewTaskIntent = null;
- mSourceStack = null;
-
- mTargetStack = null;
- mMovedToFront = false;
- mNoAnimation = false;
- mKeepCurTransition = false;
- mAvoidMoveToFront = false;
-
- mVoiceSession = null;
- mVoiceInteractor = null;
-
- mIntentDelivered = false;
+ /**
+ * An interface that to provide {@link ActivityStarter} instances to the controller. This is
+ * used by tests to inject their own starter implementations for verification purposes.
+ */
+ @VisibleForTesting
+ interface Factory {
+ /**
+ * Generates an {@link ActivityStarter} that is ready to handle a new start request.
+ * @param controller The {@link ActivityStartController} which the starter who will own
+ * this instance.
+ * @return an {@link ActivityStarter}
+ */
+ ActivityStarter getStarter(ActivityStartController controller,
+ ActivityManagerService service, ActivityStackSupervisor supervisor,
+ ActivityStartInterceptor interceptor);
}
- ActivityStarter(ActivityManagerService service) {
+ /**
+ * Default implementation of {@link StarterFactory}.
+ */
+ static class DefaultFactory implements Factory {
+ @Override
+ public ActivityStarter getStarter(ActivityStartController controller,
+ ActivityManagerService service, ActivityStackSupervisor supervisor,
+ ActivityStartInterceptor interceptor) {
+ // TODO(b/64750076): Investigate recycling instances to reduce object creation overhead.
+ return new ActivityStarter(controller, service, supervisor, interceptor);
+ }
+ }
+
+ ActivityStarter(ActivityStartController controller, ActivityManagerService service,
+ ActivityStackSupervisor supervisor, ActivityStartInterceptor interceptor) {
+ mController = controller;
mService = service;
- mSupervisor = mService.mStackSupervisor;
- mInterceptor = new ActivityStartInterceptor(mService, mSupervisor);
+ mSupervisor = supervisor;
+ mInterceptor = interceptor;
+ }
+
+ boolean relatedToPackage(String packageName) {
+ return (mLastStartActivityRecord[0] != null
+ && packageName.equals(mLastStartActivityRecord[0].packageName))
+ || (mStartActivity != null
+ && packageName.equals(mStartActivity.packageName));
}
int startActivityLocked(IApplicationThread caller, Intent intent, Intent ephemeralIntent,
@@ -268,7 +260,7 @@
return getExternalResult(mLastStartActivityResult);
}
- public static int getExternalResult(int result) {
+ static int getExternalResult(int result) {
// Aborted results are treated as successes externally, but we must track them internally.
return result != START_ABORTED ? result : START_SUCCESS;
}
@@ -536,9 +528,8 @@
|| stack.mResumedActivity.info.applicationInfo.uid != callingUid)) {
if (!mService.checkAppSwitchAllowedLocked(callingPid, callingUid,
realCallingPid, realCallingUid, "Activity start")) {
- PendingActivityLaunch pal = new PendingActivityLaunch(r,
- sourceRecord, startFlags, stack, callerApp);
- mPendingActivityLaunches.add(pal);
+ mController.addPendingActivityLaunch(new PendingActivityLaunch(r,
+ sourceRecord, startFlags, stack, callerApp));
ActivityOptions.abort(options);
return ActivityManager.START_SWITCHES_CANCELED;
}
@@ -555,7 +546,7 @@
mService.mDidAppSwitch = true;
}
- doPendingActivityLaunchesLocked(false);
+ mController.doPendingActivityLaunches(false);
return startActivity(r, sourceRecord, voiceSession, voiceInteractor, startFlags, true,
options, inTask, outActivity);
@@ -582,7 +573,6 @@
}
void postStartActivityProcessing(ActivityRecord r, int result, ActivityStack targetStack) {
-
if (ActivityManager.isStartResultFatalError(result)) {
return;
}
@@ -620,34 +610,6 @@
}
}
- void startHomeActivityLocked(Intent intent, ActivityInfo aInfo, String reason) {
- mSupervisor.moveHomeStackTaskToTop(reason);
- mLastHomeActivityStartResult = startActivityLocked(null /*caller*/, intent,
- null /*ephemeralIntent*/, null /*resolvedType*/, aInfo, null /*rInfo*/,
- null /*voiceSession*/, null /*voiceInteractor*/, null /*resultTo*/,
- null /*resultWho*/, 0 /*requestCode*/, 0 /*callingPid*/, 0 /*callingUid*/,
- null /*callingPackage*/, 0 /*realCallingPid*/, 0 /*realCallingUid*/,
- 0 /*startFlags*/, null /*options*/, false /*ignoreTargetSecurity*/,
- false /*componentSpecified*/, mLastHomeActivityStartRecord /*outActivity*/,
- null /*inTask*/, "startHomeActivity: " + reason);
- if (mSupervisor.inResumeTopActivity) {
- // If we are in resume section already, home activity will be initialized, but not
- // resumed (to avoid recursive resume) and will stay that way until something pokes it
- // again. We need to schedule another resume.
- mSupervisor.scheduleResumeTopActivities();
- }
- }
-
- void startConfirmCredentialIntent(Intent intent, Bundle optionsBundle) {
- intent.addFlags(FLAG_ACTIVITY_NEW_TASK |
- FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS |
- FLAG_ACTIVITY_TASK_ON_HOME);
- ActivityOptions options = (optionsBundle != null ? new ActivityOptions(optionsBundle)
- : ActivityOptions.makeBasic());
- options.setLaunchTaskId(mSupervisor.getHomeActivity().getTask().taskId);
- mService.mContext.startActivityAsUser(intent, options.toBundle(), UserHandle.CURRENT);
- }
-
final int startActivityMayWait(IApplicationThread caller, int callingUid,
String callingPackage, Intent intent, String resolvedType,
IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
@@ -858,112 +820,7 @@
}
}
- final int startActivities(IApplicationThread caller, int callingUid, String callingPackage,
- Intent[] intents, String[] resolvedTypes, IBinder resultTo,
- Bundle bOptions, int userId, String reason) {
- if (intents == null) {
- throw new NullPointerException("intents is null");
- }
- if (resolvedTypes == null) {
- throw new NullPointerException("resolvedTypes is null");
- }
- if (intents.length != resolvedTypes.length) {
- throw new IllegalArgumentException("intents are length different than resolvedTypes");
- }
-
- final int realCallingPid = Binder.getCallingPid();
- final int realCallingUid = Binder.getCallingUid();
-
- int callingPid;
- if (callingUid >= 0) {
- callingPid = -1;
- } else if (caller == null) {
- callingPid = realCallingPid;
- callingUid = realCallingUid;
- } else {
- callingPid = callingUid = -1;
- }
- final long origId = Binder.clearCallingIdentity();
- try {
- synchronized (mService) {
- ActivityRecord[] outActivity = new ActivityRecord[1];
- for (int i=0; i<intents.length; i++) {
- Intent intent = intents[i];
- if (intent == null) {
- continue;
- }
-
- // Refuse possible leaked file descriptors
- if (intent != null && intent.hasFileDescriptors()) {
- throw new IllegalArgumentException("File descriptors passed in Intent");
- }
-
- boolean componentSpecified = intent.getComponent() != null;
-
- // Don't modify the client's object!
- intent = new Intent(intent);
-
- // Collect information about the target of the Intent.
- ActivityInfo aInfo = mSupervisor.resolveActivity(intent, resolvedTypes[i], 0,
- null, userId);
- // TODO: New, check if this is correct
- aInfo = mService.getActivityInfoForUser(aInfo, userId);
-
- if (aInfo != null &&
- (aInfo.applicationInfo.privateFlags
- & ApplicationInfo.PRIVATE_FLAG_CANT_SAVE_STATE) != 0) {
- throw new IllegalArgumentException(
- "FLAG_CANT_SAVE_STATE not supported here");
- }
-
- ActivityOptions options = ActivityOptions.fromBundle(
- i == intents.length - 1 ? bOptions : null);
- int res = startActivityLocked(caller, intent, null /*ephemeralIntent*/,
- resolvedTypes[i], aInfo, null /*rInfo*/, null, null, resultTo, null, -1,
- callingPid, callingUid, callingPackage,
- realCallingPid, realCallingUid, 0,
- options, false, componentSpecified, outActivity, null, reason);
- if (res < 0) {
- return res;
- }
-
- resultTo = outActivity[0] != null ? outActivity[0].appToken : null;
- }
- }
- } finally {
- Binder.restoreCallingIdentity(origId);
- }
-
- return START_SUCCESS;
- }
-
- void sendPowerHintForLaunchStartIfNeeded(boolean forceSend, ActivityRecord targetActivity) {
- boolean sendHint = forceSend;
-
- if (!sendHint) {
- // If not forced, send power hint when the activity's process is different than the
- // current resumed activity.
- final ActivityRecord resumedActivity = mSupervisor.getResumedActivityLocked();
- sendHint = resumedActivity == null
- || resumedActivity.app == null
- || !resumedActivity.app.equals(targetActivity.app);
- }
-
- if (sendHint && mService.mLocalPowerManager != null) {
- mService.mLocalPowerManager.powerHint(PowerHint.LAUNCH, 1);
- mPowerHintSent = true;
- }
- }
-
- void sendPowerHintForLaunchEndIfNeeded() {
- // Trigger launch power hint if activity is launched
- if (mPowerHintSent && mService.mLocalPowerManager != null) {
- mService.mLocalPowerManager.powerHint(PowerHint.LAUNCH, 0);
- mPowerHintSent = false;
- }
- }
-
- private int startActivity(final ActivityRecord r, ActivityRecord sourceRecord,
+ int startActivity(final ActivityRecord r, ActivityRecord sourceRecord,
IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
int startFlags, boolean doResume, ActivityOptions options, TaskRecord inTask,
ActivityRecord[] outActivity) {
@@ -1064,7 +921,7 @@
}
}
- sendPowerHintForLaunchStartIfNeeded(false /* forceSend */, reusedActivity);
+ mSupervisor.sendPowerHintForLaunchStartIfNeeded(false /* forceSend */, reusedActivity);
reusedActivity = setTargetStackAndMoveToFrontIfNeeded(reusedActivity);
@@ -1179,7 +1036,7 @@
EventLogTags.AM_CREATE_ACTIVITY, mStartActivity, mStartActivity.getTask());
mTargetStack.mLastPausedActivity = null;
- sendPowerHintForLaunchStartIfNeeded(false /* forceSend */, mStartActivity);
+ mSupervisor.sendPowerHintForLaunchStartIfNeeded(false /* forceSend */, mStartActivity);
mTargetStack.startActivityLocked(mStartActivity, topFocused, newTask, mKeepCurTransition,
mOptions);
@@ -1225,8 +1082,6 @@
private void setInitialState(ActivityRecord r, ActivityOptions options, TaskRecord inTask,
boolean doResume, int startFlags, ActivityRecord sourceRecord,
IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor) {
- reset();
-
mStartActivity = r;
mIntent = r.intent;
mOptions = options;
@@ -1933,6 +1788,7 @@
return START_SUCCESS;
}
+ @VisibleForTesting
void updateBounds(TaskRecord task, Rect bounds) {
if (bounds.isEmpty()) {
return;
@@ -1996,20 +1852,6 @@
return launchFlags;
}
- final void doPendingActivityLaunchesLocked(boolean doResume) {
- while (!mPendingActivityLaunches.isEmpty()) {
- final PendingActivityLaunch pal = mPendingActivityLaunches.remove(0);
- final boolean resume = doResume && mPendingActivityLaunches.isEmpty();
- try {
- startActivity(pal.r, pal.sourceRecord, null, null, pal.startFlags, resume, null,
- null, null /*outRecords*/);
- } catch (Exception e) {
- Slog.e(TAG, "Exception during pending activity launch pal=" + pal, e);
- pal.sendErrorResult(e.getMessage());
- }
- }
- }
-
private ActivityStack computeStackFocus(ActivityRecord r, boolean newTask, int launchFlags,
ActivityOptions aOptions) {
final TaskRecord task = r.getTask();
@@ -2166,35 +2008,8 @@
(flags & Intent.FLAG_ACTIVITY_MULTIPLE_TASK) == 0;
}
- boolean clearPendingActivityLaunchesLocked(String packageName) {
- boolean didSomething = false;
-
- for (int palNdx = mPendingActivityLaunches.size() - 1; palNdx >= 0; --palNdx) {
- PendingActivityLaunch pal = mPendingActivityLaunches.get(palNdx);
- ActivityRecord r = pal.r;
- if (r != null && r.packageName.equals(packageName)) {
- mPendingActivityLaunches.remove(palNdx);
- didSomething = true;
- }
- }
- return didSomething;
- }
-
- void dump(PrintWriter pw, String prefix, String dumpPackage) {
+ void dump(PrintWriter pw, String prefix) {
prefix = prefix + " ";
-
- if (dumpPackage != null) {
- if ((mLastStartActivityRecord[0] == null ||
- !dumpPackage.equals(mLastHomeActivityStartRecord[0].packageName)) &&
- (mLastHomeActivityStartRecord[0] == null ||
- !dumpPackage.equals(mLastHomeActivityStartRecord[0].packageName)) &&
- (mStartActivity == null || !dumpPackage.equals(mStartActivity.packageName))) {
- pw.print(prefix);
- pw.println("(nothing)");
- return;
- }
- }
-
pw.print(prefix);
pw.print("mCurrentUser=");
pw.println(mSupervisor.mCurrentUser);
@@ -2213,15 +2028,6 @@
pw.println("mLastStartActivityRecord:");
r.dump(pw, prefix + " ");
}
- pw.print(prefix);
- pw.print("mLastHomeActivityStartResult=");
- pw.println(mLastHomeActivityStartResult);
- r = mLastHomeActivityStartRecord[0];
- if (r != null) {
- pw.print(prefix);
- pw.println("mLastHomeActivityStartRecord:");
- r.dump(pw, prefix + " ");
- }
if (mStartActivity != null) {
pw.print(prefix);
pw.println("mStartActivity:");
diff --git a/services/core/java/com/android/server/am/AppErrors.java b/services/core/java/com/android/server/am/AppErrors.java
index fe38097..d0bc33a 100644
--- a/services/core/java/com/android/server/am/AppErrors.java
+++ b/services/core/java/com/android/server/am/AppErrors.java
@@ -407,10 +407,10 @@
// recents entry. Let's see if we have a safe-to-restart intent.
final Set<String> cats = task.intent.getCategories();
if (cats != null && cats.contains(Intent.CATEGORY_LAUNCHER)) {
- mService.startActivityInPackage(task.mCallingUid,
- task.mCallingPackage, task.intent, null, null, null, 0, 0,
- ActivityOptions.makeBasic().toBundle(), task.userId, null,
- "AppErrors");
+ mService.getActivityStartController().startActivityInPackage(
+ task.mCallingUid, task.mCallingPackage, task.intent, null, null,
+ null, 0, 0, ActivityOptions.makeBasic().toBundle(), task.userId,
+ null, "AppErrors");
}
}
}
diff --git a/services/core/java/com/android/server/am/AppTaskImpl.java b/services/core/java/com/android/server/am/AppTaskImpl.java
index ab86dbdb..e5872c03 100644
--- a/services/core/java/com/android/server/am/AppTaskImpl.java
+++ b/services/core/java/com/android/server/am/AppTaskImpl.java
@@ -122,8 +122,8 @@
throw new IllegalArgumentException("Bad app thread " + appThread);
}
}
- return mService.mActivityStarter.startActivityMayWait(appThread, -1, callingPackage,
- intent, resolvedType, null, null, null, null, 0, 0, null, null,
+ return mService.getActivityStartController().startActivityMayWait(appThread, -1,
+ callingPackage, intent, resolvedType, null, null, null, null, 0, 0, null, null,
null, bOptions, false, callingUser, tr, "AppTaskImpl");
}
diff --git a/services/core/java/com/android/server/am/EventLogTags.logtags b/services/core/java/com/android/server/am/EventLogTags.logtags
index b2d3137..a131db5 100644
--- a/services/core/java/com/android/server/am/EventLogTags.logtags
+++ b/services/core/java/com/android/server/am/EventLogTags.logtags
@@ -38,7 +38,7 @@
# The Activity Manager failed to pause the given activity.
30012 am_failed_to_pause (User|1|5),(Token|1|5),(Wanting to pause|3),(Currently pausing|3)
# Attempting to pause the current activity
-30013 am_pause_activity (User|1|5),(Token|1|5),(Component Name|3)
+30013 am_pause_activity (User|1|5),(Token|1|5),(Component Name|3),(User Leaving|3)
# Application process has been started
30014 am_proc_start (User|1|5),(PID|1|5),(UID|1|5),(Process Name|3),(Type|3),(Component|3)
# An application process has been marked as bad
diff --git a/services/core/java/com/android/server/am/PendingIntentRecord.java b/services/core/java/com/android/server/am/PendingIntentRecord.java
index 7930f53..c26e770 100644
--- a/services/core/java/com/android/server/am/PendingIntentRecord.java
+++ b/services/core/java/com/android/server/am/PendingIntentRecord.java
@@ -332,12 +332,14 @@
}
allIntents[allIntents.length-1] = finalIntent;
allResolvedTypes[allResolvedTypes.length-1] = resolvedType;
- owner.startActivitiesInPackage(uid, key.packageName, allIntents,
- allResolvedTypes, resultTo, options, userId);
+ owner.getActivityStartController().startActivitiesInPackage(uid,
+ key.packageName, allIntents, allResolvedTypes, resultTo,
+ options, userId);
} else {
- owner.startActivityInPackage(uid, key.packageName, finalIntent,
- resolvedType, resultTo, resultWho, requestCode, 0,
- options, userId, null, "PendingIntentRecord");
+ owner.getActivityStartController().startActivityInPackage(uid,
+ key.packageName, finalIntent, resolvedType, resultTo,
+ resultWho, requestCode, 0, options, userId, null,
+ "PendingIntentRecord");
}
} catch (RuntimeException e) {
Slog.w(TAG, "Unable to send startActivity intent", e);
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 15a418dc..efa0bf8 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -1645,6 +1645,11 @@
};
private int getNewRingerMode(int stream, int index, int flags) {
+ // setRingerMode does nothing if the device is single volume,so the value would be unchanged
+ if (mIsSingleVolume) {
+ return getRingerModeExternal();
+ }
+
// setting volume on ui sounds stream type also controls silent mode
if (((flags & AudioManager.FLAG_ALLOW_RINGER_MODES) != 0) ||
(stream == getUiSoundsStreamType())) {
@@ -3691,7 +3696,7 @@
private int checkForRingerModeChange(int oldIndex, int direction, int step, boolean isMuted,
String caller, int flags) {
int result = FLAG_ADJUST_VOLUME;
- if (isPlatformTelevision()) {
+ if (isPlatformTelevision() || mIsSingleVolume) {
return result;
}
diff --git a/services/core/java/com/android/server/connectivity/NetdEventListenerService.java b/services/core/java/com/android/server/connectivity/NetdEventListenerService.java
index e243e56..979beed 100644
--- a/services/core/java/com/android/server/connectivity/NetdEventListenerService.java
+++ b/services/core/java/com/android/server/connectivity/NetdEventListenerService.java
@@ -244,7 +244,7 @@
event.timestampMs = timestampMs;
event.uid = uid;
event.ethertype = ethertype;
- event.dstHwAddr = new MacAddress(dstHw);
+ event.dstHwAddr = MacAddress.fromBytes(dstHw);
event.srcIp = srcIp;
event.dstIp = dstIp;
event.ipNextHeader = ipNextHeader;
diff --git a/services/core/java/com/android/server/notification/ScheduleCalendar.java b/services/core/java/com/android/server/notification/ScheduleCalendar.java
index 40230bd..5ff0e21 100644
--- a/services/core/java/com/android/server/notification/ScheduleCalendar.java
+++ b/services/core/java/com/android/server/notification/ScheduleCalendar.java
@@ -18,6 +18,7 @@
import android.service.notification.ZenModeConfig.ScheduleInfo;
import android.util.ArraySet;
+import android.util.Log;
import java.util.Calendar;
import java.util.Objects;
@@ -41,10 +42,25 @@
}
public void maybeSetNextAlarm(long now, long nextAlarm) {
- if (mSchedule != null) {
- if (mSchedule.exitAtAlarm
- && (now > mSchedule.nextAlarm || nextAlarm < mSchedule.nextAlarm)) {
- mSchedule.nextAlarm = nextAlarm;
+ if (mSchedule != null && mSchedule.exitAtAlarm) {
+ // alarm canceled
+ if (nextAlarm == 0) {
+ mSchedule.nextAlarm = 0;
+ }
+ // only allow alarms in the future
+ if (nextAlarm > now) {
+ // store earliest alarm
+ if (mSchedule.nextAlarm == 0) {
+ mSchedule.nextAlarm = nextAlarm;
+ } else {
+ mSchedule.nextAlarm = Math.min(mSchedule.nextAlarm, nextAlarm);
+ }
+ } else if (mSchedule.nextAlarm < now) {
+ if (ScheduleConditionProvider.DEBUG) {
+ Log.d(ScheduleConditionProvider.TAG,
+ "All alarms are in the past " + mSchedule.nextAlarm);
+ }
+ mSchedule.nextAlarm = 0;
}
}
}
@@ -87,6 +103,9 @@
}
public boolean shouldExitForAlarm(long time) {
+ if (mSchedule == null) {
+ return false;
+ }
return mSchedule.exitAtAlarm
&& mSchedule.nextAlarm != 0
&& time >= mSchedule.nextAlarm;
diff --git a/services/core/java/com/android/server/notification/ScheduleConditionProvider.java b/services/core/java/com/android/server/notification/ScheduleConditionProvider.java
index 50a51b2..c5f80bb 100644
--- a/services/core/java/com/android/server/notification/ScheduleConditionProvider.java
+++ b/services/core/java/com/android/server/notification/ScheduleConditionProvider.java
@@ -37,6 +37,8 @@
import android.util.Log;
import android.util.Slog;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.server.notification.NotificationManagerService.DumpFilter;
import java.io.PrintWriter;
@@ -62,10 +64,9 @@
private static final String SEPARATOR = ";";
private static final String SCP_SETTING = "snoozed_schedule_condition_provider";
-
private final Context mContext = this;
private final ArrayMap<Uri, ScheduleCalendar> mSubscriptions = new ArrayMap<>();
- private ArraySet<Uri> mSnoozed = new ArraySet<>();
+ private ArraySet<Uri> mSnoozedForAlarm = new ArraySet<>();
private AlarmManager mAlarmManager;
private boolean mConnected;
@@ -102,7 +103,7 @@
pw.println(mSubscriptions.get(conditionId).toString());
}
}
- pw.println(" snoozed due to alarm: " + TextUtils.join(SEPARATOR, mSnoozed));
+ pw.println(" snoozed due to alarm: " + TextUtils.join(SEPARATOR, mSnoozedForAlarm));
dumpUpcomingTime(pw, "mNextAlarmTime", mNextAlarmTime, now);
}
@@ -129,7 +130,7 @@
public void onSubscribe(Uri conditionId) {
if (DEBUG) Slog.d(TAG, "onSubscribe " + conditionId);
if (!ZenModeConfig.isValidScheduleConditionId(conditionId)) {
- notifyCondition(createCondition(conditionId, Condition.STATE_FALSE, "badCondition"));
+ notifyCondition(createCondition(conditionId, Condition.STATE_ERROR, "invalidId"));
return;
}
synchronized (mSubscriptions) {
@@ -169,32 +170,11 @@
synchronized (mSubscriptions) {
setRegistered(!mSubscriptions.isEmpty());
for (Uri conditionId : mSubscriptions.keySet()) {
- final ScheduleCalendar cal = mSubscriptions.get(conditionId);
- if (cal != null && cal.isInSchedule(now)) {
- if (conditionSnoozed(conditionId) || cal.shouldExitForAlarm(now)) {
- conditionsToNotify.add(createCondition(
- conditionId, Condition.STATE_FALSE, "alarmCanceled"));
- addSnoozed(conditionId);
- } else {
- conditionsToNotify.add(createCondition(
- conditionId, Condition.STATE_TRUE, "meetsSchedule"));
- }
- cal.maybeSetNextAlarm(now, nextUserAlarmTime);
- } else {
- conditionsToNotify.add(createCondition(
- conditionId, Condition.STATE_FALSE, "!meetsSchedule"));
- removeSnoozed(conditionId);
- if (cal != null && nextUserAlarmTime == 0) {
- cal.maybeSetNextAlarm(now, nextUserAlarmTime);
- }
- }
- if (cal != null) {
- final long nextChangeTime = cal.getNextChangeTime(now);
- if (nextChangeTime > 0 && nextChangeTime > now) {
- if (mNextAlarmTime == 0 || nextChangeTime < mNextAlarmTime) {
- mNextAlarmTime = nextChangeTime;
- }
- }
+ Condition condition =
+ evaluateSubscriptionLocked(conditionId, mSubscriptions.get(conditionId),
+ now, nextUserAlarmTime);
+ if (condition != null) {
+ conditionsToNotify.add(condition);
}
}
}
@@ -202,6 +182,39 @@
updateAlarm(now, mNextAlarmTime);
}
+ @VisibleForTesting
+ @GuardedBy("mSubscriptions")
+ Condition evaluateSubscriptionLocked(Uri conditionId, ScheduleCalendar cal,
+ long now, long nextUserAlarmTime) {
+ Condition condition;
+ if (cal == null) {
+ condition = createCondition(conditionId, Condition.STATE_ERROR, "!invalidId");
+ removeSnoozed(conditionId);
+ return condition;
+ }
+ if (cal.isInSchedule(now)) {
+ if (conditionSnoozed(conditionId)) {
+ condition = createCondition(conditionId, Condition.STATE_FALSE, "snoozed");
+ } else if (cal.shouldExitForAlarm(now)) {
+ condition = createCondition(conditionId, Condition.STATE_FALSE, "alarmCanceled");
+ addSnoozed(conditionId);
+ } else {
+ condition = createCondition(conditionId, Condition.STATE_TRUE, "meetsSchedule");
+ }
+ } else {
+ condition = createCondition(conditionId, Condition.STATE_FALSE, "!meetsSchedule");
+ removeSnoozed(conditionId);
+ }
+ cal.maybeSetNextAlarm(now, nextUserAlarmTime);
+ final long nextChangeTime = cal.getNextChangeTime(now);
+ if (nextChangeTime > 0 && nextChangeTime > now) {
+ if (mNextAlarmTime == 0 || nextChangeTime < mNextAlarmTime) {
+ mNextAlarmTime = nextChangeTime;
+ }
+ }
+ return condition;
+ }
+
private void updateAlarm(long now, long time) {
final AlarmManager alarms = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
final PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext,
@@ -266,27 +279,28 @@
}
private boolean conditionSnoozed(Uri conditionId) {
- synchronized (mSnoozed) {
- return mSnoozed.contains(conditionId);
+ synchronized (mSnoozedForAlarm) {
+ return mSnoozedForAlarm.contains(conditionId);
}
}
- private void addSnoozed(Uri conditionId) {
- synchronized (mSnoozed) {
- mSnoozed.add(conditionId);
+ @VisibleForTesting
+ void addSnoozed(Uri conditionId) {
+ synchronized (mSnoozedForAlarm) {
+ mSnoozedForAlarm.add(conditionId);
saveSnoozedLocked();
}
}
private void removeSnoozed(Uri conditionId) {
- synchronized (mSnoozed) {
- mSnoozed.remove(conditionId);
+ synchronized (mSnoozedForAlarm) {
+ mSnoozedForAlarm.remove(conditionId);
saveSnoozedLocked();
}
}
- public void saveSnoozedLocked() {
- final String setting = TextUtils.join(SEPARATOR, mSnoozed);
+ private void saveSnoozedLocked() {
+ final String setting = TextUtils.join(SEPARATOR, mSnoozedForAlarm);
final int currentUser = ActivityManager.getCurrentUser();
Settings.Secure.putStringForUser(mContext.getContentResolver(),
SCP_SETTING,
@@ -294,8 +308,8 @@
currentUser);
}
- public void readSnoozed() {
- synchronized (mSnoozed) {
+ private void readSnoozed() {
+ synchronized (mSnoozedForAlarm) {
long identity = Binder.clearCallingIdentity();
try {
final String setting = Settings.Secure.getStringForUser(
@@ -312,7 +326,7 @@
if (TextUtils.isEmpty(token)) {
continue;
}
- mSnoozed.add(Uri.parse(token));
+ mSnoozedForAlarm.add(Uri.parse(token));
}
}
} finally {
diff --git a/services/core/java/com/android/server/pm/InstantAppResolver.java b/services/core/java/com/android/server/pm/InstantAppResolver.java
index 5f54c67..e5e9a37 100644
--- a/services/core/java/com/android/server/pm/InstantAppResolver.java
+++ b/services/core/java/com/android/server/pm/InstantAppResolver.java
@@ -258,6 +258,7 @@
if (origIntent.getData() != null) {
intent.putExtra(Intent.EXTRA_EPHEMERAL_HOSTNAME, origIntent.getData().getHost());
}
+ intent.putExtra(Intent.EXTRA_INSTANT_APP_ACTION, origIntent.getAction());
// We have all of the data we need; just start the installer without a second phase
if (!needsPhaseTwo) {
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 45b94a4..6200444 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -1892,13 +1892,16 @@
final InstallParams params = new InstallParams();
params.sessionParams = sessionParams;
String opt;
+ boolean replaceExisting = true;
while ((opt = getNextOption()) != null) {
switch (opt) {
case "-l":
sessionParams.installFlags |= PackageManager.INSTALL_FORWARD_LOCK;
break;
- case "-r":
- sessionParams.installFlags |= PackageManager.INSTALL_REPLACE_EXISTING;
+ case "-r": // ignore
+ break;
+ case "-R":
+ replaceExisting = false;
break;
case "-i":
params.installerPackageName = getNextArg();
@@ -1983,6 +1986,9 @@
default:
throw new IllegalArgumentException("Unknown option " + opt);
}
+ if (replaceExisting) {
+ sessionParams.installFlags |= PackageManager.INSTALL_REPLACE_EXISTING;
+ }
}
return params;
}
@@ -2451,7 +2457,7 @@
pw.println(" Install an application. Must provide the apk data to install, either as a");
pw.println(" file path or '-' to read from stdin. Options are:");
pw.println(" -l: forward lock application");
- pw.println(" -r: allow replacement of existing application");
+ pw.println(" -R: disallow replacement of existing application");
pw.println(" -t: allow test packages");
pw.println(" -i: specify package name of installer owning the app");
pw.println(" -s: install application on sdcard");
diff --git a/services/core/java/com/android/server/power/BatterySaverPolicy.java b/services/core/java/com/android/server/power/BatterySaverPolicy.java
index 7c234f9..3810192 100644
--- a/services/core/java/com/android/server/power/BatterySaverPolicy.java
+++ b/services/core/java/com/android/server/power/BatterySaverPolicy.java
@@ -87,6 +87,12 @@
private String mDeviceSpecificSettingsSource; // For dump() only.
/**
+ * A short string describing which battery saver is now enabled, which we dump in the eventlog.
+ */
+ @GuardedBy("mLock")
+ private String mEventLogKeys;
+
+ /**
* {@code true} if vibration is disabled in battery saver mode.
*
* @see Settings.Global#BATTERY_SAVER_CONSTANTS
@@ -328,7 +334,7 @@
mFullBackupDeferred = parser.getBoolean(KEY_FULLBACKUP_DEFERRED, true);
mKeyValueBackupDeferred = parser.getBoolean(KEY_KEYVALUE_DEFERRED, true);
mFireWallDisabled = parser.getBoolean(KEY_FIREWALL_DISABLED, false);
- mAdjustBrightnessDisabled = parser.getBoolean(KEY_ADJUST_BRIGHTNESS_DISABLED, false);
+ mAdjustBrightnessDisabled = parser.getBoolean(KEY_ADJUST_BRIGHTNESS_DISABLED, true);
mAdjustBrightnessFactor = parser.getFloat(KEY_ADJUST_BRIGHTNESS_FACTOR, 0.5f);
mDataSaverDisabled = parser.getBoolean(KEY_DATASAVER_DISABLED, true);
mLaunchBoostDisabled = parser.getBoolean(KEY_LAUNCH_BOOST_DISABLED, true);
@@ -354,6 +360,27 @@
mFilesForNoninteractive = (new CpuFrequencies()).parseString(
parser.getString(KEY_CPU_FREQ_NONINTERACTIVE, "")).toSysFileMap();
+
+ final StringBuilder sb = new StringBuilder();
+
+ if (mForceAllAppsStandby) sb.append("A");
+ if (mForceBackgroundCheck) sb.append("B");
+
+ if (mVibrationDisabled) sb.append("v");
+ if (mAnimationDisabled) sb.append("a");
+ if (mSoundTriggerDisabled) sb.append("s");
+ if (mFullBackupDeferred) sb.append("F");
+ if (mKeyValueBackupDeferred) sb.append("K");
+ if (!mFireWallDisabled) sb.append("f");
+ if (!mDataSaverDisabled) sb.append("d");
+ if (!mAdjustBrightnessDisabled) sb.append("b");
+
+ if (mLaunchBoostDisabled) sb.append("l");
+ if (mOptionalSensorsDisabled) sb.append("S");
+
+ sb.append(mGpsMode);
+
+ mEventLogKeys = sb.toString();
}
/**
@@ -431,6 +458,12 @@
}
}
+ public String toEventLogString() {
+ synchronized (mLock) {
+ return mEventLogKeys;
+ }
+ }
+
public void dump(PrintWriter pw) {
synchronized (mLock) {
pw.println();
diff --git a/services/core/java/com/android/server/power/batterysaver/BatterySaverController.java b/services/core/java/com/android/server/power/batterysaver/BatterySaverController.java
index 1b19c33..a6bca0b 100644
--- a/services/core/java/com/android/server/power/batterysaver/BatterySaverController.java
+++ b/services/core/java/com/android/server/power/batterysaver/BatterySaverController.java
@@ -45,6 +45,7 @@
import com.android.internal.notification.SystemNotificationChannels;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.Preconditions;
+import com.android.server.EventLogTags;
import com.android.server.LocalServices;
import com.android.server.power.BatterySaverPolicy;
import com.android.server.power.BatterySaverPolicy.BatterySaverPolicyListener;
@@ -77,6 +78,12 @@
@GuardedBy("mLock")
private boolean mEnabled;
+ /**
+ * Previously enabled or not; only for the event logging. Only use it from
+ * {@link #handleBatterySaverStateChanged}.
+ */
+ private boolean mPreviouslyEnabled;
+
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -213,12 +220,18 @@
final ArrayMap<String, String> fileValues;
synchronized (mLock) {
- Slog.i(TAG, "Battery saver " + (mEnabled ? "enabled" : "disabled")
- + ": isInteractive=" + isInteractive);
+ EventLogTags.writeBatterySaverMode(
+ mPreviouslyEnabled ? 1 : 0, // Previously off or on.
+ mEnabled ? 1 : 0, // Now off or on.
+ isInteractive ? 1 : 0, // Device interactive state.
+ mEnabled ? mBatterySaverPolicy.toEventLogString() : "");
+ mPreviouslyEnabled = mEnabled;
listeners = mListeners.toArray(new LowPowerModeListener[mListeners.size()]);
+
enabled = mEnabled;
+
if (enabled) {
fileValues = mBatterySaverPolicy.getFileValues(isInteractive);
} else {
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java
index 84e475a..43a0893 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotController.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java
@@ -226,7 +226,7 @@
final Rect taskFrame = new Rect();
task.getBounds(taskFrame);
- final GraphicBuffer buffer = SurfaceControl.captureLayersToBuffer(
+ final GraphicBuffer buffer = SurfaceControl.captureLayers(
task.getSurfaceControl().getHandle(), taskFrame, scaleFraction);
if (buffer == null || buffer.getWidth() <= 1 || buffer.getHeight() <= 1) {
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index c9d7b70..a3d4b71 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -125,6 +125,11 @@
}
void setExiting() {
+ if (mChildren.size() == 0) {
+ super.removeImmediately();
+ return;
+ }
+
// This token is exiting, so allow it to be removed when it no longer contains any windows.
mPersistOnEmpty = false;
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
index dddff8f..84cfabe 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
@@ -16,6 +16,7 @@
package com.android.server.devicepolicy;
import android.app.admin.IDevicePolicyManager;
+import android.content.ComponentName;
import com.android.internal.R;
import com.android.server.SystemService;
@@ -54,4 +55,6 @@
* @see {@link SystemService#onStopUser}
*/
abstract void handleStopUser(int userId);
+
+ public void setSystemSetting(ComponentName who, String setting, String value){}
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index d7e4a62..663083c 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -315,6 +315,7 @@
private static final Set<String> SECURE_SETTINGS_DEVICEOWNER_WHITELIST;
private static final Set<String> GLOBAL_SETTINGS_WHITELIST;
private static final Set<String> GLOBAL_SETTINGS_DEPRECATED;
+ private static final Set<String> SYSTEM_SETTINGS_WHITELIST;
static {
SECURE_SETTINGS_WHITELIST = new ArraySet<>();
SECURE_SETTINGS_WHITELIST.add(Settings.Secure.DEFAULT_INPUT_METHOD);
@@ -341,6 +342,11 @@
GLOBAL_SETTINGS_DEPRECATED.add(Settings.Global.MODE_RINGER);
GLOBAL_SETTINGS_DEPRECATED.add(Settings.Global.NETWORK_PREFERENCE);
GLOBAL_SETTINGS_DEPRECATED.add(Settings.Global.WIFI_ON);
+
+ SYSTEM_SETTINGS_WHITELIST = new ArraySet<>();
+ SYSTEM_SETTINGS_WHITELIST.add(Settings.System.SCREEN_BRIGHTNESS);
+ SYSTEM_SETTINGS_WHITELIST.add(Settings.System.SCREEN_BRIGHTNESS_MODE);
+ SYSTEM_SETTINGS_WHITELIST.add(Settings.System.SCREEN_OFF_TIMEOUT);
}
/**
@@ -1822,6 +1828,10 @@
Settings.Global.putString(mContext.getContentResolver(), name, value);
}
+ void settingsSystemPutString(String name, String value) {
+ Settings.System.putString(mContext.getContentResolver(), name, value);
+ }
+
void securityLogSetLoggingEnabledProperty(boolean enabled) {
SecurityLog.setLoggingEnabledProperty(enabled);
}
@@ -9137,6 +9147,24 @@
}
@Override
+ public void setSystemSetting(ComponentName who, String setting, String value) {
+ Preconditions.checkNotNull(who, "ComponentName is null");
+ Preconditions.checkStringNotEmpty(setting, "String setting is null or empty");
+
+ synchronized (this) {
+ getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
+
+ if (!SYSTEM_SETTINGS_WHITELIST.contains(setting)) {
+ throw new SecurityException(String.format(
+ "Permission denial: device owners cannot update %1$s", setting));
+ }
+
+ mInjector.binderWithCleanCallingIdentity(() -> mInjector.settingsSystemPutString(
+ setting, value));
+ }
+ }
+
+ @Override
public boolean setTime(ComponentName who, long millis) {
Preconditions.checkNotNull(who, "ComponentName is null in setTime");
getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
diff --git a/services/tests/notification/src/com/android/server/notification/ScheduleCalendarTest.java b/services/tests/notification/src/com/android/server/notification/ScheduleCalendarTest.java
new file mode 100644
index 0000000..cbda12d
--- /dev/null
+++ b/services/tests/notification/src/com/android/server/notification/ScheduleCalendarTest.java
@@ -0,0 +1,327 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.notification;
+
+import static junit.framework.Assert.assertFalse;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.service.notification.ZenModeConfig;
+import android.support.test.runner.AndroidJUnit4;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Slog;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class ScheduleCalendarTest extends NotificationTestCase {
+
+ private ScheduleCalendar mScheduleCalendar;
+ private ZenModeConfig.ScheduleInfo mScheduleInfo;
+
+ @Before
+ public void setUp() throws Exception {
+ mScheduleCalendar = new ScheduleCalendar();
+ mScheduleInfo = new ZenModeConfig.ScheduleInfo();
+ mScheduleInfo.days = new int[] {1, 2, 3, 4, 5};
+ mScheduleCalendar.setSchedule(mScheduleInfo);
+ }
+
+ @Test
+ public void testNullScheduleInfo() throws Exception {
+ mScheduleCalendar.setSchedule(null);
+
+ mScheduleCalendar.maybeSetNextAlarm(1000, 1999);
+ assertEquals(0, mScheduleCalendar.getNextChangeTime(1000));
+ assertFalse(mScheduleCalendar.isInSchedule(100));
+ assertFalse(mScheduleCalendar.shouldExitForAlarm(100));
+ }
+
+ @Test
+ public void testGetNextChangeTime_startToday() throws Exception {
+ Calendar cal = new GregorianCalendar();
+ cal.set(Calendar.HOUR_OF_DAY, 1);
+ cal.set(Calendar.MINUTE, 15);
+ cal.set(Calendar.SECOND, 0);
+ cal.set(Calendar.MILLISECOND, 0);
+ mScheduleInfo.days = new int[] {getTodayDay()};
+ mScheduleInfo.startHour = cal.get(Calendar.HOUR_OF_DAY) + 1;
+ mScheduleInfo.endHour = cal.get(Calendar.HOUR_OF_DAY) + 3;
+ mScheduleInfo.startMinute = 15;
+ mScheduleInfo.endMinute = 15;
+ mScheduleInfo.exitAtAlarm = false;
+ mScheduleCalendar.setSchedule(mScheduleInfo);
+
+ Calendar expected = new GregorianCalendar();
+ expected.setTimeInMillis(cal.getTimeInMillis());
+ expected.set(Calendar.HOUR_OF_DAY, mScheduleInfo.startHour);
+
+ long actualMs = mScheduleCalendar.getNextChangeTime(cal.getTimeInMillis());
+ GregorianCalendar actual = new GregorianCalendar();
+ actual.setTimeInMillis(actualMs);
+ assertEquals("Expected " + expected + " was " + actual, expected.getTimeInMillis(),
+ actualMs);
+ }
+
+ @Test
+ public void testGetNextChangeTime_endToday() throws Exception {
+ Calendar cal = new GregorianCalendar();
+ cal.set(Calendar.HOUR_OF_DAY, 2);
+ cal.set(Calendar.MINUTE, 15);
+ cal.set(Calendar.SECOND, 0);
+ cal.set(Calendar.MILLISECOND, 0);
+ mScheduleInfo.days = new int[] {getTodayDay()};
+ mScheduleInfo.startHour = cal.get(Calendar.HOUR_OF_DAY) - 1;
+ mScheduleInfo.endHour = cal.get(Calendar.HOUR_OF_DAY) + 3;
+ mScheduleInfo.startMinute = 15;
+ mScheduleInfo.endMinute = 15;
+ mScheduleInfo.exitAtAlarm = false;
+
+ Calendar expected = new GregorianCalendar();
+ expected.setTimeInMillis(cal.getTimeInMillis());
+ expected.set(Calendar.HOUR_OF_DAY, mScheduleInfo.endHour);
+ expected.set(Calendar.MINUTE, mScheduleInfo.endMinute);
+
+ long actualMs = mScheduleCalendar.getNextChangeTime(cal.getTimeInMillis());
+ GregorianCalendar actual = new GregorianCalendar();
+ actual.setTimeInMillis(actualMs);
+ assertEquals("Expected " + expected + " was " + actual, expected.getTimeInMillis(),
+ actualMs);
+ }
+
+ @Test
+ public void testGetNextChangeTime_startTomorrow() throws Exception {
+ Calendar cal = new GregorianCalendar();
+ cal.set(Calendar.HOUR_OF_DAY, 23);
+ cal.set(Calendar.MINUTE, 15);
+ cal.set(Calendar.SECOND, 0);
+ cal.set(Calendar.MILLISECOND, 0);
+ mScheduleInfo.days = new int[] {getTodayDay(), getTodayDay() + 1};
+ mScheduleInfo.startHour = 1;
+ mScheduleInfo.endHour = 3;
+ mScheduleInfo.startMinute = 15;
+ mScheduleInfo.endMinute = 15;
+ mScheduleInfo.exitAtAlarm = false;
+
+ Calendar expected = new GregorianCalendar();
+ expected.setTimeInMillis(cal.getTimeInMillis());
+ expected.add(Calendar.DATE, 1);
+ expected.set(Calendar.HOUR_OF_DAY, mScheduleInfo.startHour);
+ expected.set(Calendar.MINUTE, mScheduleInfo.startMinute);
+
+ long actualMs = mScheduleCalendar.getNextChangeTime(cal.getTimeInMillis());
+ GregorianCalendar actual = new GregorianCalendar();
+ actual.setTimeInMillis(actualMs);
+ assertEquals("Expected " + expected + " was " + actual, expected.getTimeInMillis(),
+ actualMs);
+ }
+
+ @Test
+ public void testGetNextChangeTime_endTomorrow() throws Exception {
+ Calendar cal = new GregorianCalendar();
+ cal.set(Calendar.HOUR_OF_DAY, 23);
+ cal.set(Calendar.MINUTE, 15);
+ cal.set(Calendar.SECOND, 0);
+ cal.set(Calendar.MILLISECOND, 0);
+ mScheduleInfo.days = new int[] {getTodayDay(), getTodayDay() + 1};
+ mScheduleInfo.startHour = 22;
+ mScheduleInfo.endHour = 3;
+ mScheduleInfo.startMinute = 15;
+ mScheduleInfo.endMinute = 15;
+ mScheduleInfo.exitAtAlarm = false;
+
+ Calendar expected = new GregorianCalendar();
+ expected.setTimeInMillis(cal.getTimeInMillis());
+ expected.add(Calendar.DATE, 1);
+ expected.set(Calendar.HOUR_OF_DAY, mScheduleInfo.endHour);
+ expected.set(Calendar.MINUTE, mScheduleInfo.endMinute);
+
+ long actualMs = mScheduleCalendar.getNextChangeTime(cal.getTimeInMillis());
+ GregorianCalendar actual = new GregorianCalendar();
+ actual.setTimeInMillis(actualMs);
+ assertEquals("Expected " + expected + " was " + actual, expected.getTimeInMillis(),
+ actualMs);
+ }
+
+ @Test
+ public void testShouldExitForAlarm_settingOff() {
+ mScheduleInfo.exitAtAlarm = false;
+ mScheduleInfo.nextAlarm = 1000;
+
+ assertFalse(mScheduleCalendar.shouldExitForAlarm(1000));
+ }
+
+ @Test
+ public void testShouldExitForAlarm_beforeAlarm() {
+ mScheduleInfo.exitAtAlarm = true;
+ mScheduleInfo.nextAlarm = 1000;
+
+ assertFalse(mScheduleCalendar.shouldExitForAlarm(999));
+ }
+
+ @Test
+ public void testShouldExitForAlarm_noAlarm() {
+ mScheduleInfo.exitAtAlarm = true;
+ mScheduleInfo.nextAlarm = 0;
+
+ assertFalse(mScheduleCalendar.shouldExitForAlarm(999));
+ }
+
+ @Test
+ public void testShouldExitForAlarm() {
+ mScheduleInfo.exitAtAlarm = true;
+ mScheduleInfo.nextAlarm = 1000;
+
+ assertTrue(mScheduleCalendar.shouldExitForAlarm(1000));
+ }
+
+ @Test
+ public void testMaybeSetNextAlarm_settingOff() {
+ mScheduleInfo.exitAtAlarm = false;
+ mScheduleInfo.nextAlarm = 0;
+
+ mScheduleCalendar.maybeSetNextAlarm(1000, 2000);
+
+ assertEquals(0, mScheduleInfo.nextAlarm);
+ }
+
+ @Test
+ public void testMaybeSetNextAlarm_settingOn() {
+ mScheduleInfo.exitAtAlarm = true;
+ mScheduleInfo.nextAlarm = 0;
+
+ mScheduleCalendar.maybeSetNextAlarm(1000, 2000);
+
+ assertEquals(2000, mScheduleInfo.nextAlarm);
+ }
+
+ @Test
+ public void testMaybeSetNextAlarm_alarmCanceled() {
+ mScheduleInfo.exitAtAlarm = true;
+ mScheduleInfo.nextAlarm = 10000;
+
+ mScheduleCalendar.maybeSetNextAlarm(1000, 0);
+
+ assertEquals(0, mScheduleInfo.nextAlarm);
+ }
+
+ @Test
+ public void testMaybeSetNextAlarm_earlierAlarm() {
+ mScheduleInfo.exitAtAlarm = true;
+ mScheduleInfo.nextAlarm = 2000;
+
+ mScheduleCalendar.maybeSetNextAlarm(1000, 1500);
+
+ assertEquals(1500, mScheduleInfo.nextAlarm);
+ }
+
+ @Test
+ public void testMaybeSetNextAlarm_laterAlarm() {
+ mScheduleInfo.exitAtAlarm = true;
+ mScheduleInfo.nextAlarm = 2000;
+
+ mScheduleCalendar.maybeSetNextAlarm(1000, 3000);
+
+ assertEquals(2000, mScheduleInfo.nextAlarm);
+ }
+
+ @Test
+ public void testMaybeSetNextAlarm_expiredAlarm() {
+ mScheduleInfo.exitAtAlarm = true;
+ mScheduleInfo.nextAlarm = 998;
+
+ mScheduleCalendar.maybeSetNextAlarm(1000, 999);
+
+ assertEquals(0, mScheduleInfo.nextAlarm);
+ }
+
+ @Test
+ public void testIsInSchedule_inScheduleOvernight() {
+ Calendar cal = new GregorianCalendar();
+ cal.set(Calendar.HOUR_OF_DAY, 23);
+ cal.set(Calendar.MINUTE, 15);
+ cal.set(Calendar.SECOND, 0);
+ cal.set(Calendar.MILLISECOND, 0);
+ mScheduleInfo.days = new int[] {getTodayDay()};
+ mScheduleInfo.startHour = 22;
+ mScheduleInfo.endHour = 3;
+ mScheduleInfo.startMinute = 15;
+ mScheduleInfo.endMinute = 15;
+
+ assertTrue(mScheduleCalendar.isInSchedule(cal.getTimeInMillis()));
+ }
+
+ @Test
+ public void testIsInSchedule_inScheduleSingleDay() {
+ Calendar cal = new GregorianCalendar();
+ cal.set(Calendar.HOUR_OF_DAY, 14);
+ cal.set(Calendar.MINUTE, 15);
+ cal.set(Calendar.SECOND, 0);
+ cal.set(Calendar.MILLISECOND, 0);
+ mScheduleInfo.days = new int[] {getTodayDay()};
+ mScheduleInfo.startHour = 12;
+ mScheduleInfo.endHour = 3;
+ mScheduleInfo.startMinute = 16;
+ mScheduleInfo.endMinute = 15;
+
+ assertTrue(mScheduleCalendar.isInSchedule(cal.getTimeInMillis()));
+ }
+
+ @Test
+ public void testIsInSchedule_notToday() {
+ Calendar cal = new GregorianCalendar();
+ cal.set(Calendar.HOUR_OF_DAY, 14);
+ cal.set(Calendar.MINUTE, 15);
+ cal.set(Calendar.SECOND, 0);
+ cal.set(Calendar.MILLISECOND, 0);
+ cal.set(Calendar.DAY_OF_WEEK, Calendar.SATURDAY);
+ mScheduleInfo.days = new int[] {Calendar.FRIDAY, Calendar.SUNDAY};
+ mScheduleInfo.startHour = 12;
+ mScheduleInfo.startMinute = 16;
+ mScheduleInfo.endHour = 15;
+ mScheduleInfo.endMinute = 15;
+
+ assertFalse(mScheduleCalendar.isInSchedule(cal.getTimeInMillis()));
+ }
+
+ @Test
+ public void testIsInSchedule_startingSoon() {
+ Calendar cal = new GregorianCalendar();
+ cal.set(Calendar.HOUR_OF_DAY, 14);
+ cal.set(Calendar.MINUTE, 15);
+ cal.set(Calendar.SECOND, 59);
+ cal.set(Calendar.MILLISECOND, 0);
+ mScheduleInfo.days = new int[] {getTodayDay()};
+ mScheduleInfo.startHour = 14;
+ mScheduleInfo.endHour = 3;
+ mScheduleInfo.startMinute = 16;
+ mScheduleInfo.endMinute = 15;
+
+ assertFalse(mScheduleCalendar.isInSchedule(cal.getTimeInMillis()));
+ }
+
+ private int getTodayDay() {
+ return new GregorianCalendar().get(Calendar.DAY_OF_WEEK);
+ }
+}
diff --git a/services/tests/notification/src/com/android/server/notification/ScheduleConditionProviderTest.java b/services/tests/notification/src/com/android/server/notification/ScheduleConditionProviderTest.java
new file mode 100644
index 0000000..ddf46a0
--- /dev/null
+++ b/services/tests/notification/src/com/android/server/notification/ScheduleConditionProviderTest.java
@@ -0,0 +1,337 @@
+package com.android.server.notification;
+
+import static org.mockito.Mockito.spy;
+
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Looper;
+import android.service.notification.Condition;
+import android.service.notification.ZenModeConfig;
+import android.support.test.InstrumentationRegistry;
+import android.test.ServiceTestCase;
+import android.testing.TestableContext;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+
+public class ScheduleConditionProviderTest extends ServiceTestCase<ScheduleConditionProvider> {
+
+ ScheduleConditionProvider mService;
+
+ @Rule
+ public final TestableContext mContext =
+ new TestableContext(InstrumentationRegistry.getContext(), null);
+
+ public ScheduleConditionProviderTest() {
+ super(ScheduleConditionProvider.class);
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ Looper.prepare();
+
+ Intent startIntent =
+ new Intent("com.android.server.notification.ScheduleConditionProvider");
+ startIntent.setPackage("android");
+ bindService(startIntent);
+ mService = spy(getService());
+ }
+
+ @Test
+ public void testIsValidConditionId_incomplete() throws Exception {
+ Uri badConditionId = Uri.EMPTY;
+ assertFalse(mService.isValidConditionId(badConditionId));
+ assertEquals(Condition.STATE_ERROR,
+ mService.evaluateSubscriptionLocked(badConditionId, null, 0, 1000).state);
+ }
+
+ @Test
+ public void testIsValidConditionId() throws Exception {
+ ZenModeConfig.ScheduleInfo info = new ZenModeConfig.ScheduleInfo();
+ info.days = new int[] {1, 2, 4};
+ info.startHour = 8;
+ info.startMinute = 56;
+ info.nextAlarm = 1000;
+ info.exitAtAlarm = true;
+ info.endHour = 12;
+ info.endMinute = 9;
+ Uri conditionId = ZenModeConfig.toScheduleConditionId(info);
+ assertTrue(mService.isValidConditionId(conditionId));
+ }
+
+ @Test
+ public void testEvaluateSubscription_noAlarmExit_InSchedule() {
+ Calendar now = getNow();
+
+ // Schedule - 1 hour long; starts now
+ ZenModeConfig.ScheduleInfo info = new ZenModeConfig.ScheduleInfo();
+ info.days = new int[] {Calendar.FRIDAY};
+ info.startHour = now.get(Calendar.HOUR_OF_DAY);
+ info.startMinute = now.get(Calendar.MINUTE);
+ info.nextAlarm = 0;
+ info.exitAtAlarm = false;
+ info.endHour = now.get(Calendar.HOUR_OF_DAY) + 1;
+ info.endMinute = info.startMinute;
+ Uri conditionId = ZenModeConfig.toScheduleConditionId(info);
+ ScheduleCalendar cal = new ScheduleCalendar();
+ cal.setSchedule(info);
+ assertTrue(cal.isInSchedule(now.getTimeInMillis()));
+
+ Condition condition = mService.evaluateSubscriptionLocked(
+ conditionId, cal, now.getTimeInMillis(), now.getTimeInMillis() + 1000);
+
+ assertEquals(Condition.STATE_TRUE, condition.state);
+ }
+
+ @Test
+ public void testEvaluateSubscription_noAlarmExit_InScheduleSnoozed() {
+ Calendar now = getNow();
+
+ // Schedule - 1 hour long; starts now
+ ZenModeConfig.ScheduleInfo info = new ZenModeConfig.ScheduleInfo();
+ info.days = new int[] {Calendar.FRIDAY};
+ info.startHour = now.get(Calendar.HOUR_OF_DAY);
+ info.startMinute = now.get(Calendar.MINUTE);
+ info.nextAlarm = 0;
+ info.exitAtAlarm = false;
+ info.endHour = now.get(Calendar.HOUR_OF_DAY) + 1;
+ info.endMinute = info.startMinute;
+ Uri conditionId = ZenModeConfig.toScheduleConditionId(info);
+ ScheduleCalendar cal = new ScheduleCalendar();
+ cal.setSchedule(info);
+ assertTrue(cal.isInSchedule(now.getTimeInMillis()));
+
+ mService.addSnoozed(conditionId);
+
+ Condition condition = mService.evaluateSubscriptionLocked(
+ conditionId, cal, now.getTimeInMillis(), now.getTimeInMillis() + 1000);
+
+ assertEquals(Condition.STATE_FALSE, condition.state);
+ }
+
+ @Test
+ public void testEvaluateSubscription_noAlarmExit_beforeSchedule() {
+ Calendar now = new GregorianCalendar();
+ now.set(Calendar.HOUR_OF_DAY, 14);
+ now.set(Calendar.MINUTE, 15);
+ now.set(Calendar.SECOND, 59);
+ now.set(Calendar.MILLISECOND, 0);
+ now.set(Calendar.DAY_OF_WEEK, Calendar.FRIDAY);
+
+ // Schedule - 1 hour long; starts in 1 second
+ ZenModeConfig.ScheduleInfo info = new ZenModeConfig.ScheduleInfo();
+ info.days = new int[] {Calendar.FRIDAY};
+ info.startHour = now.get(Calendar.HOUR_OF_DAY);
+ info.startMinute = now.get(Calendar.MINUTE) + 1;
+ info.nextAlarm = 0;
+ info.exitAtAlarm = false;
+ info.endHour = now.get(Calendar.HOUR_OF_DAY) + 1;
+ info.endMinute = info.startMinute;
+ Uri conditionId = ZenModeConfig.toScheduleConditionId(info);
+ ScheduleCalendar cal = new ScheduleCalendar();
+ cal.setSchedule(info);
+
+ Condition condition = mService.evaluateSubscriptionLocked(
+ conditionId, cal, now.getTimeInMillis(), now.getTimeInMillis() + 1000);
+
+ assertEquals(Condition.STATE_FALSE, condition.state);
+ }
+
+ @Test
+ public void testEvaluateSubscription_noAlarmExit_endSchedule() {
+ Calendar now = getNow();
+
+ // Schedule - 1 hour long; ends now
+ ZenModeConfig.ScheduleInfo info = new ZenModeConfig.ScheduleInfo();
+ info.days = new int[] {Calendar.FRIDAY};
+ info.startHour = now.get(Calendar.HOUR_OF_DAY) - 1;
+ info.startMinute = now.get(Calendar.MINUTE);
+ info.nextAlarm = 0;
+ info.exitAtAlarm = false;
+ info.endHour = now.get(Calendar.HOUR_OF_DAY);
+ info.endMinute = now.get(Calendar.MINUTE);
+ Uri conditionId = ZenModeConfig.toScheduleConditionId(info);
+ ScheduleCalendar cal = new ScheduleCalendar();
+ cal.setSchedule(info);
+
+ Condition condition = mService.evaluateSubscriptionLocked(
+ conditionId, cal, now.getTimeInMillis(), now.getTimeInMillis() + 1000);
+
+ assertEquals(Condition.STATE_FALSE, condition.state);
+ }
+
+ @Test
+ public void testEvaluateSubscription_alarmSetBeforeInSchedule() {
+ Calendar now = getNow();
+
+ // Schedule - 1 hour long; starts now, ends with alarm
+ ZenModeConfig.ScheduleInfo info = getScheduleEndsInHour(now);
+ Uri conditionId = ZenModeConfig.toScheduleConditionId(info);
+ ScheduleCalendar cal = new ScheduleCalendar();
+ cal.setSchedule(info);
+
+ // an hour before start, update with an alarm that will fire during the schedule
+ mService.evaluateSubscriptionLocked(
+ conditionId, cal, now.getTimeInMillis() - 1000, now.getTimeInMillis() + 1000);
+
+ // at start, should be in dnd
+ Condition condition = mService.evaluateSubscriptionLocked(
+ conditionId, cal, now.getTimeInMillis(), now.getTimeInMillis() + 1000);
+ assertEquals(Condition.STATE_TRUE, condition.state);
+
+ // at alarm fire time, should exit dnd
+ assertTrue(cal.isInSchedule(now.getTimeInMillis() + 1000));
+ assertTrue("" + info.nextAlarm + " " + now.getTimeInMillis(),
+ cal.shouldExitForAlarm(now.getTimeInMillis() + 1000));
+ condition = mService.evaluateSubscriptionLocked(
+ conditionId, cal, now.getTimeInMillis() + 1000, 0);
+ assertEquals(Condition.STATE_FALSE, condition.state);
+ }
+
+ @Test
+ public void testEvaluateSubscription_alarmSetInSchedule() {
+ Calendar now = getNow();
+
+ // Schedule - 1 hour long; starts now, ends with alarm
+ ZenModeConfig.ScheduleInfo info = getScheduleEndsInHour(now);
+ Uri conditionId = ZenModeConfig.toScheduleConditionId(info);
+ ScheduleCalendar cal = new ScheduleCalendar();
+ cal.setSchedule(info);
+
+ // at start, should be in dnd
+ Condition condition = mService.evaluateSubscriptionLocked(
+ conditionId, cal, now.getTimeInMillis(), 0);
+ assertEquals(Condition.STATE_TRUE, condition.state);
+
+ // in schedule, update with alarm time, should be in dnd
+ condition = mService.evaluateSubscriptionLocked(
+ conditionId, cal, now.getTimeInMillis() + 500, now.getTimeInMillis() + 1000);
+ assertEquals(Condition.STATE_TRUE, condition.state);
+
+ // at alarm fire time, should exit dnd
+ assertTrue(cal.isInSchedule(now.getTimeInMillis() + 1000));
+ assertTrue("" + info.nextAlarm + " " + now.getTimeInMillis(),
+ cal.shouldExitForAlarm(now.getTimeInMillis() + 1000));
+ condition = mService.evaluateSubscriptionLocked(
+ conditionId, cal, now.getTimeInMillis() + 1000, 0);
+ assertEquals(Condition.STATE_FALSE, condition.state);
+ }
+
+ @Test
+ public void testEvaluateSubscription_earlierAlarmSet() {
+ Calendar now = getNow();
+
+ // Schedule - 1 hour long; starts now, ends with alarm
+ ZenModeConfig.ScheduleInfo info = getScheduleEndsInHour(now);
+ Uri conditionId = ZenModeConfig.toScheduleConditionId(info);
+ ScheduleCalendar cal = new ScheduleCalendar();
+ cal.setSchedule(info);
+
+ // at start, should be in dnd, alarm in 2000 ms
+ Condition condition = mService.evaluateSubscriptionLocked(
+ conditionId, cal, now.getTimeInMillis(), now.getTimeInMillis() + 2000);
+ assertEquals(Condition.STATE_TRUE, condition.state);
+
+ // in schedule, update with earlier alarm time, should be in dnd
+ condition = mService.evaluateSubscriptionLocked(
+ conditionId, cal, now.getTimeInMillis() + 500, now.getTimeInMillis() + 1000);
+ assertEquals(Condition.STATE_TRUE, condition.state);
+
+ // at earliest alarm fire time, should exit dnd
+ assertTrue(cal.isInSchedule(now.getTimeInMillis() + 1000));
+ assertTrue("" + info.nextAlarm + " " + now.getTimeInMillis(),
+ cal.shouldExitForAlarm(now.getTimeInMillis() + 1000));
+ condition = mService.evaluateSubscriptionLocked(
+ conditionId, cal, now.getTimeInMillis() + 1000, 0);
+ assertEquals(Condition.STATE_FALSE, condition.state);
+ }
+
+ @Test
+ public void testEvaluateSubscription_laterAlarmSet() {
+ Calendar now = getNow();
+
+ // Schedule - 1 hour long; starts now, ends with alarm
+ ZenModeConfig.ScheduleInfo info = getScheduleEndsInHour(now);
+ Uri conditionId = ZenModeConfig.toScheduleConditionId(info);
+ ScheduleCalendar cal = new ScheduleCalendar();
+ cal.setSchedule(info);
+
+ // at start, should be in dnd, alarm in 500 ms
+ Condition condition = mService.evaluateSubscriptionLocked(
+ conditionId, cal, now.getTimeInMillis(), now.getTimeInMillis() + 500);
+ assertEquals(Condition.STATE_TRUE, condition.state);
+
+ // in schedule, update with later alarm time, should be in dnd
+ condition = mService.evaluateSubscriptionLocked(
+ conditionId, cal, now.getTimeInMillis() + 250, now.getTimeInMillis() + 1000);
+ assertEquals(Condition.STATE_TRUE, condition.state);
+
+ // at earliest alarm fire time, should exit dnd
+ assertTrue(cal.isInSchedule(now.getTimeInMillis() + 500));
+ assertTrue("" + info.nextAlarm + " " + now.getTimeInMillis(),
+ cal.shouldExitForAlarm(now.getTimeInMillis() + 500));
+ condition = mService.evaluateSubscriptionLocked(
+ conditionId, cal, now.getTimeInMillis() + 500, 0);
+ assertEquals(Condition.STATE_FALSE, condition.state);
+ }
+
+ @Test
+ public void testEvaluateSubscription_alarmCanceled() {
+ Calendar now = getNow();
+
+ // Schedule - 1 hour long; starts now, ends with alarm
+ ZenModeConfig.ScheduleInfo info = getScheduleEndsInHour(now);
+ Uri conditionId = ZenModeConfig.toScheduleConditionId(info);
+ ScheduleCalendar cal = new ScheduleCalendar();
+ cal.setSchedule(info);
+
+ // at start, should be in dnd, alarm in 500 ms
+ Condition condition = mService.evaluateSubscriptionLocked(
+ conditionId, cal, now.getTimeInMillis(), now.getTimeInMillis() + 500);
+ assertEquals(Condition.STATE_TRUE, condition.state);
+
+ // in schedule, cancel alarm
+ condition = mService.evaluateSubscriptionLocked(
+ conditionId, cal, now.getTimeInMillis() + 250, 0);
+ assertEquals(Condition.STATE_TRUE, condition.state);
+
+ // at previous alarm time, should not exit DND
+ assertTrue(cal.isInSchedule(now.getTimeInMillis() + 500));
+ assertFalse(cal.shouldExitForAlarm(now.getTimeInMillis() + 500));
+ condition = mService.evaluateSubscriptionLocked(
+ conditionId, cal, now.getTimeInMillis() + 500, 0);
+ assertEquals(Condition.STATE_TRUE, condition.state);
+
+ // end of schedule, exit DND
+ now.add(Calendar.HOUR_OF_DAY, 1);
+ condition = mService.evaluateSubscriptionLocked(conditionId, cal, now.getTimeInMillis(), 0);
+ assertEquals(Condition.STATE_FALSE, condition.state);
+ }
+
+ private Calendar getNow() {
+ Calendar now = new GregorianCalendar();
+ now.set(Calendar.HOUR_OF_DAY, 14);
+ now.set(Calendar.MINUTE, 16);
+ now.set(Calendar.SECOND, 0);
+ now.set(Calendar.MILLISECOND, 0);
+ now.set(Calendar.DAY_OF_WEEK, Calendar.FRIDAY);
+ return now;
+ }
+
+ private ZenModeConfig.ScheduleInfo getScheduleEndsInHour(Calendar now) {
+ ZenModeConfig.ScheduleInfo info = new ZenModeConfig.ScheduleInfo();
+ info.days = new int[] {Calendar.FRIDAY};
+ info.startHour = now.get(Calendar.HOUR_OF_DAY);
+ info.startMinute = now.get(Calendar.MINUTE);
+ info.exitAtAlarm = true;
+ info.endHour = now.get(Calendar.HOUR_OF_DAY) + 1;
+ info.endMinute = now.get(Calendar.MINUTE);
+ return info;
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityStartControllerTests.java b/services/tests/servicestests/src/com/android/server/am/ActivityStartControllerTests.java
new file mode 100644
index 0000000..5676510
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityStartControllerTests.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.am;
+
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+
+import android.app.ActivityOptions;
+import android.app.IApplicationThread;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ResolveInfo;
+import android.os.IBinder;
+import android.platform.test.annotations.Presubmit;
+import android.service.voice.IVoiceInteractionSession;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.android.internal.app.IVoiceInteractor;
+import com.android.server.am.ActivityStackSupervisor.PendingActivityLaunch;
+import com.android.server.am.ActivityStarter.Factory;
+
+import org.junit.runner.RunWith;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyBoolean;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.anyObject;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.times;
+
+import java.util.Random;
+
+/**
+ * Tests for the {@link ActivityStartController} class.
+ *
+ * Build/Install/Run:
+ * atest FrameworksServicesTests:ActivityStartControllerTests
+ */
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class ActivityStartControllerTests extends ActivityTestsBase {
+ private ActivityManagerService mService;
+ private ActivityStartController mController;
+ private Factory mFactory;
+ private ActivityStarter mStarter;
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ mService = createActivityManagerService();
+ mFactory = mock(Factory.class);
+ mStarter = mock(ActivityStarter.class);
+ doReturn(mStarter).when(mFactory).getStarter(any(), any(), any(), any());
+ mController = new ActivityStartController(mService, mService.mStackSupervisor, mFactory);
+ }
+
+ /**
+ * Ensures that the starter is correctly invoked on
+ * {@link ActivityStartController#startActivity}
+ */
+ @Test
+ @Presubmit
+ public void testStartActivity() {
+ final Random random = new Random();
+
+ final IApplicationThread applicationThread = mock(IApplicationThread.class);
+ final Intent intent = mock(Intent.class);
+ final Intent ephemeralIntent = mock(Intent.class);
+ final String resolvedType = "TestType";
+ final ActivityInfo aInfo = mock(ActivityInfo.class);
+ final ResolveInfo rInfo = mock(ResolveInfo.class);
+ final IVoiceInteractionSession voiceInteractionSession =
+ mock(IVoiceInteractionSession.class);
+ final IVoiceInteractor voiceInteractor = mock(IVoiceInteractor.class);
+ final IBinder resultTo = mock(IBinder.class);
+ final String resultWho = "resultWho";
+ final int requestCode = random.nextInt();
+ final int callingPid = random.nextInt();
+ final int callingUid = random.nextInt();
+ final String callingPackage = "callingPackage";
+ final int realCallingPid = random.nextInt();
+ final int realCallingUid = random.nextInt();
+ final int startFlags = random.nextInt();
+ final ActivityOptions options = mock(ActivityOptions.class);
+ final boolean ignoreTargetSecurity = random.nextBoolean();
+ final boolean componentSpecified = random.nextBoolean();
+ final ActivityRecord[] outActivity = new ActivityRecord[1];
+ final TaskRecord inTask = mock(TaskRecord.class);
+ final String reason ="reason";
+
+ mController.startActivity(applicationThread, intent, ephemeralIntent, resolvedType,
+ aInfo, rInfo, voiceInteractionSession, voiceInteractor, resultTo, resultWho,
+ requestCode, callingPid, callingUid, callingPackage, realCallingPid, realCallingUid,
+ startFlags, options, ignoreTargetSecurity, componentSpecified, outActivity, inTask,
+ reason);
+
+ // The starter should receive a start command with the originally provided parameters
+ verify(mStarter, times(1)).startActivityLocked(eq(applicationThread), eq(intent),
+ eq(ephemeralIntent), eq(resolvedType), eq(aInfo), eq(rInfo),
+ eq(voiceInteractionSession), eq(voiceInteractor), eq(resultTo), eq(resultWho),
+ eq(requestCode), eq(callingPid), eq(callingUid), eq(callingPackage),
+ eq(realCallingPid), eq(realCallingUid), eq(startFlags), eq(options),
+ eq(ignoreTargetSecurity), eq(componentSpecified), eq(outActivity), eq(inTask),
+ eq(reason));
+ }
+
+ /**
+ * Ensures that pending launches are processed.
+ */
+ @Test
+ @Presubmit
+ public void testPendingActivityLaunches() {
+ final Random random = new Random();
+
+ final ActivityRecord activity = new ActivityBuilder(mService).build();
+ final ActivityRecord source = new ActivityBuilder(mService).build();
+ final int startFlags = random.nextInt();
+ final ActivityStack stack = mService.mStackSupervisor.getDefaultDisplay().createStack(
+ WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+ final ProcessRecord process= new ProcessRecord(null, mService.mContext.getApplicationInfo(),
+ "name", 12345);
+
+ mController.addPendingActivityLaunch(
+ new PendingActivityLaunch(activity, source, startFlags, stack, process));
+ final boolean resume = random.nextBoolean();
+ mController.doPendingActivityLaunches(resume);
+
+ verify(mStarter, times(1)).startActivity(eq(activity), eq(source), eq(null),
+ eq(null), eq(startFlags), eq(resume), eq(null), eq(null), eq(null));
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityStarterTests.java b/services/tests/servicestests/src/com/android/server/am/ActivityStarterTests.java
index 1cec0d9..471726b 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityStarterTests.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityStarterTests.java
@@ -65,10 +65,10 @@
import com.android.internal.os.BatteryStatsImpl;
/**
- * Tests for the {@link ActivityStack} class.
+ * Tests for the {@link ActivityStarter} class.
*
* Build/Install/Run:
- * bit FrameworksServicesTests:com.android.server.am.ActivityStarterTests
+ * atest FrameworksServicesTests:ActivityStarterTests
*/
@SmallTest
@Presubmit
@@ -76,7 +76,7 @@
public class ActivityStarterTests extends ActivityTestsBase {
private ActivityManagerService mService;
private ActivityStarter mStarter;
- private IPackageManager mPackageManager;
+ private ActivityStartController mController;
private static final int PRECONDITION_NO_CALLER_APP = 1;
private static final int PRECONDITION_NO_INTENT_COMPONENT = 1 << 1;
@@ -94,7 +94,9 @@
public void setUp() throws Exception {
super.setUp();
mService = createActivityManagerService();
- mStarter = new ActivityStarter(mService);
+ mController = mock(ActivityStartController.class);
+ mStarter = new ActivityStarter(mController, mService, mService.mStackSupervisor,
+ mock(ActivityStartInterceptor.class));
}
@Test
@@ -176,8 +178,10 @@
int expectedResult) {
final ActivityManagerService service = createActivityManagerService();
final IPackageManager packageManager = mock(IPackageManager.class);
- final ActivityStarter starter = new ActivityStarter(service);
+ final ActivityStartController controller = mock(ActivityStartController.class);
+ final ActivityStarter starter = new ActivityStarter(controller, service,
+ service.mStackSupervisor, mock(ActivityStartInterceptor.class));
final IApplicationThread caller = mock(IApplicationThread.class);
// If no caller app, return {@code null} {@link ProcessRecord}.
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 13ca10c..0d03863 100644
--- a/services/tests/servicestests/src/com/android/server/am/RecentTasksTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/RecentTasksTest.java
@@ -479,7 +479,7 @@
() -> mService.moveTaskToStack(0, INVALID_STACK_ID, true));
assertSecurityException(expectCallable,
() -> mService.setTaskWindowingModeSplitScreenPrimary(0,
- SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT, true, true, new Rect()));
+ SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT, true, true, new Rect(), true));
assertSecurityException(expectCallable, () -> mService.dismissSplitScreenMode(true));
assertSecurityException(expectCallable, () -> mService.dismissPip(true, 0));
assertSecurityException(expectCallable,
@@ -781,4 +781,4 @@
callingUid, allowed);
}
}
-}
\ No newline at end of file
+}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
index ae4b569..d168479 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
@@ -373,6 +373,11 @@
}
@Override
+ void settingsSystemPutString(String name, String value) {
+ services.settings.settingsSystemPutString(name, value);
+ }
+
+ @Override
int settingsGlobalGetInt(String name, int def) {
return services.settings.settingsGlobalGetInt(name, def);
}
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 6de3395..ca918c6 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -3090,6 +3090,27 @@
assertEquals(-1, dpm.getLastSecurityLogRetrievalTime());
}
+ public void testSetSystemSettingFailWithNonWhitelistedSettings() throws Exception {
+ mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
+ setupDeviceOwner();
+ assertExpectException(SecurityException.class, null, () ->
+ dpm.setSystemSetting(admin1, Settings.System.SCREEN_BRIGHTNESS_FOR_VR, "0"));
+ }
+
+ public void testSetSystemSettingFailWithPO() throws Exception {
+ setupProfileOwner();
+ assertExpectException(SecurityException.class, null, () ->
+ dpm.setSystemSetting(admin1, Settings.System.SCREEN_BRIGHTNESS, "0"));
+ }
+
+ public void testSetSystemSetting() throws Exception {
+ mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
+ setupDeviceOwner();
+ dpm.setSystemSetting(admin1, Settings.System.SCREEN_BRIGHTNESS, "0");
+ verify(getServices().settings).settingsSystemPutString(
+ Settings.System.SCREEN_BRIGHTNESS, "0");
+ }
+
public void testSetTime() throws Exception {
mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
setupDeviceOwner();
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
index 99f54ba..4ee5ba6 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
@@ -379,6 +379,9 @@
public void settingsGlobalPutString(String name, String value) {
}
+ public void settingsSystemPutString(String name, String value) {
+ }
+
public int settingsGlobalGetInt(String name, int value) {
return 0;
}
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 878fbed..0572771 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -683,7 +683,7 @@
final int callingUid = Binder.getCallingUid();
try {
userId = ActivityManager.getService().handleIncomingUser(
- Binder.getCallingPid(), callingUid, userId, false, true,
+ Binder.getCallingPid(), callingUid, userId, false, false,
"getAppStandbyBucket", null);
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java
index a07f2bb..16150ba 100644
--- a/telecomm/java/android/telecom/Call.java
+++ b/telecomm/java/android/telecom/Call.java
@@ -858,7 +858,8 @@
* @hide
*/
@IntDef({HANDOVER_FAILURE_DEST_APP_REJECTED, HANDOVER_FAILURE_DEST_NOT_SUPPORTED,
- HANDOVER_FAILURE_DEST_INVALID_PERM, HANDOVER_FAILURE_DEST_USER_REJECTED})
+ HANDOVER_FAILURE_DEST_INVALID_PERM, HANDOVER_FAILURE_DEST_USER_REJECTED,
+ HANDOVER_FAILURE_ONGOING_EMERG_CALL})
@Retention(RetentionPolicy.SOURCE)
public @interface HandoverFailureErrors {}
@@ -886,6 +887,12 @@
*/
public static final int HANDOVER_FAILURE_DEST_USER_REJECTED = 4;
+ /**
+ * Handover failure reason returned via {@link #onHandoverFailed(Call, int)} when there
+ * is ongoing emergency call.
+ */
+ public static final int HANDOVER_FAILURE_ONGOING_EMERG_CALL = 5;
+
/**
* Invoked when the state of this {@code Call} has changed. See {@link #getState()}.
@@ -1935,6 +1942,15 @@
}
}
+ /** {@hide} */
+ final void internalOnHandoverFailed(int error) {
+ for (CallbackRecord<Callback> record : mCallbackRecords) {
+ final Call call = this;
+ final Callback callback = record.getCallback();
+ record.getHandler().post(() -> callback.onHandoverFailed(call, error));
+ }
+ }
+
private void fireStateChanged(final int newState) {
for (CallbackRecord<Callback> record : mCallbackRecords) {
final Call call = this;
diff --git a/telecomm/java/android/telecom/ConnectionService.java b/telecomm/java/android/telecom/ConnectionService.java
index 7e83306..2834201 100644
--- a/telecomm/java/android/telecom/ConnectionService.java
+++ b/telecomm/java/android/telecom/ConnectionService.java
@@ -1371,9 +1371,19 @@
isIncoming,
isUnknown);
- Connection connection = isUnknown ? onCreateUnknownConnection(callManagerAccount, request)
- : isIncoming ? onCreateIncomingConnection(callManagerAccount, request)
- : onCreateOutgoingConnection(callManagerAccount, request);
+ Connection connection = null;
+ if (request.getExtras() != null && request.getExtras().getBoolean(
+ TelecomManager.EXTRA_IS_HANDOVER,false)) {
+ if (!isIncoming) {
+ connection = onCreateOutgoingHandoverConnection(callManagerAccount, request);
+ } else {
+ // Todo: Call onCreateIncommingHandoverConnection()
+ }
+ } else {
+ connection = isUnknown ? onCreateUnknownConnection(callManagerAccount, request)
+ : isIncoming ? onCreateIncomingConnection(callManagerAccount, request)
+ : onCreateOutgoingConnection(callManagerAccount, request);
+ }
Log.d(this, "createConnection, connection: %s", connection);
if (connection == null) {
connection = Connection.createFailedConnection(
diff --git a/telecomm/java/android/telecom/InCallService.java b/telecomm/java/android/telecom/InCallService.java
index d558bba..74fa62d 100644
--- a/telecomm/java/android/telecom/InCallService.java
+++ b/telecomm/java/android/telecom/InCallService.java
@@ -80,6 +80,7 @@
private static final int MSG_ON_CONNECTION_EVENT = 9;
private static final int MSG_ON_RTT_UPGRADE_REQUEST = 10;
private static final int MSG_ON_RTT_INITIATION_FAILURE = 11;
+ private static final int MSG_ON_HANDOVER_FAILED = 12;
/** Default Handler used to consolidate binder method calls onto a single thread. */
private final Handler mHandler = new Handler(Looper.getMainLooper()) {
@@ -150,6 +151,12 @@
mPhone.internalOnRttInitiationFailure(callId, reason);
break;
}
+ case MSG_ON_HANDOVER_FAILED: {
+ String callId = (String) msg.obj;
+ int error = msg.arg1;
+ mPhone.internalOnHandoverFailed(callId, error);
+ break;
+ }
default:
break;
}
@@ -225,6 +232,11 @@
public void onRttInitiationFailure(String callId, int reason) {
mHandler.obtainMessage(MSG_ON_RTT_INITIATION_FAILURE, reason, 0, callId).sendToTarget();
}
+
+ @Override
+ public void onHandoverFailed(String callId, int error) {
+ mHandler.obtainMessage(MSG_ON_HANDOVER_FAILED, error, 0, callId).sendToTarget();
+ }
}
private Phone.Listener mPhoneListener = new Phone.Listener() {
diff --git a/telecomm/java/android/telecom/Phone.java b/telecomm/java/android/telecom/Phone.java
index 421b1a4..b5394b9 100644
--- a/telecomm/java/android/telecom/Phone.java
+++ b/telecomm/java/android/telecom/Phone.java
@@ -223,6 +223,13 @@
}
}
+ final void internalOnHandoverFailed(String callId, int error) {
+ Call call = mCallByTelecomCallId.get(callId);
+ if (call != null) {
+ call.internalOnHandoverFailed(error);
+ }
+ }
+
/**
* Called to destroy the phone and cleanup any lingering calls.
*/
diff --git a/telecomm/java/com/android/internal/telecom/IInCallService.aidl b/telecomm/java/com/android/internal/telecom/IInCallService.aidl
index e8cf8e9..110109e 100644
--- a/telecomm/java/com/android/internal/telecom/IInCallService.aidl
+++ b/telecomm/java/com/android/internal/telecom/IInCallService.aidl
@@ -54,4 +54,6 @@
void onRttUpgradeRequest(String callId, int id);
void onRttInitiationFailure(String callId, int reason);
+
+ void onHandoverFailed(String callId, int error);
}
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 69371a1..e9feb89 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -735,6 +735,14 @@
public static final String KEY_SHOW_ICCID_IN_SIM_STATUS_BOOL = "show_iccid_in_sim_status_bool";
/**
+ * Flag specifying whether signal strength is hidden in SIM Status screen,
+ * default to false.
+ * @hide
+ */
+ public static final String KEY_HIDE_SIGNAL_STRENGTH_IN_SIM_STATUS_BOOL =
+ "hide_signal_strength_in_sim_status_bool";
+
+ /**
* Flag specifying whether an additional (client initiated) intent needs to be sent on System
* update
*/
@@ -989,6 +997,12 @@
public static final String KEY_STK_DISABLE_LAUNCH_BROWSER_BOOL =
"stk_disable_launch_browser_bool";
+ /**
+ * Boolean indicating if show data RAT icon on status bar even when data is disabled
+ * @hide
+ */
+ public static final String KEY_ALWAYS_SHOW_DATA_RAT_ICON_BOOL =
+ "always_show_data_rat_icon_bool";
// These variables are used by the MMS service and exposed through another API, {@link
// SmsManager}. The variable names and string values are copied from there.
@@ -1768,6 +1782,7 @@
sDefaults.putString(KEY_CARRIER_VVM_PACKAGE_NAME_STRING, "");
sDefaults.putStringArray(KEY_CARRIER_VVM_PACKAGE_NAME_STRING_ARRAY, null);
sDefaults.putBoolean(KEY_SHOW_ICCID_IN_SIM_STATUS_BOOL, false);
+ sDefaults.putBoolean(KEY_HIDE_SIGNAL_STRENGTH_IN_SIM_STATUS_BOOL, false);
sDefaults.putBoolean(KEY_CI_ACTION_ON_SYS_UPDATE_BOOL, false);
sDefaults.putString(KEY_CI_ACTION_ON_SYS_UPDATE_INTENT_STRING, "");
sDefaults.putString(KEY_CI_ACTION_ON_SYS_UPDATE_EXTRA_STRING, "");
@@ -1967,6 +1982,7 @@
sDefaults.putStringArray(KEY_FEATURE_ACCESS_CODES_STRING_ARRAY, null);
sDefaults.putBoolean(KEY_IDENTIFY_HIGH_DEFINITION_CALLS_IN_CALL_LOG_BOOL, false);
sDefaults.putBoolean(KEY_SPN_DISPLAY_RULE_USE_ROAMING_FROM_SERVICE_STATE_BOOL, false);
+ sDefaults.putBoolean(KEY_ALWAYS_SHOW_DATA_RAT_ICON_BOOL, false);
}
/**
diff --git a/telephony/java/com/android/internal/telephony/ICallService.aidl b/telephony/java/com/android/internal/telephony/ICallService.aidl
deleted file mode 100644
index cb9b2e8..0000000
--- a/telephony/java/com/android/internal/telephony/ICallService.aidl
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.telephony;
-
-import com.android.internal.telephony.ICallServiceAdapter;
-
-/**
- * Service interface for services which would like to provide calls to be
- * managed by the system in-call UI.
- *
- * This interface provides methods that the android framework can use to deliver commands
- * for calls provided by this call service including making new calls and disconnecting
- * existing ones. A binding to ICallService implementations exists for two conditions:
- * 1) There exists one or more live calls for that call service,
- * 2) Prior to an outbound call to test if this call service is compatible with the outgoing call.
- */
-oneway interface ICallService {
-
- /**
- * Determines if the CallService can make calls to the handle.
- * TODO(santoscordon): Move this method into its own service interface long term.
- * TODO(santoscordon): Add response callback parameter.
- */
- void isCompatibleWith(String handle);
-
- /**
- * Attempts to call the relevant party using the specified handle, be it a phone number,
- * SIP address, or some other kind of user ID. Note that the set of handle types is
- * dynamically extensible since call providers should be able to implement arbitrary
- * handle-calling systems. See {@link #isCompatibleWith}.
- * TODO(santoscordon): Should this have a response attached to it to ensure that the call
- * service actually plans to make the call?
- */
- void call(String handle);
-
- /**
- * Disconnects the call identified by callId.
- */
- void disconnect(String callId);
-
- /**
- * Sets an implementation of ICallServiceAdapter which the call service can use to add new calls
- * and communicate state changes of existing calls. This is the first method that is called
- * after a the framework binds to the call service.
- */
- void setCallServiceAdapter(ICallServiceAdapter callServiceAdapter);
-}
diff --git a/telephony/java/com/android/internal/telephony/ICallServiceAdapter.aidl b/telephony/java/com/android/internal/telephony/ICallServiceAdapter.aidl
deleted file mode 100644
index bc900f0..0000000
--- a/telephony/java/com/android/internal/telephony/ICallServiceAdapter.aidl
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.telephony;
-
-import com.android.internal.telephony.CallInfo;
-
-/**
- * Provides methods for ICallService implementations to interact with the system phone app.
- */
-oneway interface ICallServiceAdapter {
-
- /**
- * Retrieves a new unique call id for use with newOutgoingCall and newIncomingCall.
- */
- void getNextCallId(/* TODO(santoscordon): Needs response object */);
-
- /**
- * Tells CallsManager of a new incoming call.
- */
- void newIncomingCall(String callId, in CallInfo info);
-
- /**
- * Tells CallsManager of a new outgoing call.
- */
- void newOutgoingCall(String callId, in CallInfo info);
-
- /**
- * Sets a call's state to active (e.g., an ongoing call where two parties can actively
- * communicate).
- */
- void setActive(String callId);
-
- /**
- * Sets a call's state to ringing (e.g., an inbound ringing call).
- */
- void setRinging(String callId);
-
- /**
- * Sets a call's state to dialing (e.g., dialing an outbound call).
- */
- void setDialing(String callId);
-
- /**
- * Sets a call's state to disconnected.
- */
- void setDisconnected(String callId);
-}
diff --git a/telephony/java/com/android/internal/telephony/ISub.aidl b/telephony/java/com/android/internal/telephony/ISub.aidl
index 644ad49..5e015e0 100755
--- a/telephony/java/com/android/internal/telephony/ISub.aidl
+++ b/telephony/java/com/android/internal/telephony/ISub.aidl
@@ -18,7 +18,6 @@
import android.app.PendingIntent;
import android.telephony.SubscriptionInfo;
-import com.android.internal.telephony.ISubscriptionListener;
interface ISub {
/**
diff --git a/tests/BackgroundDexOptServiceIntegrationTests/Android.mk b/tests/BackgroundDexOptServiceIntegrationTests/Android.mk
new file mode 100644
index 0000000..da1a08b
--- /dev/null
+++ b/tests/BackgroundDexOptServiceIntegrationTests/Android.mk
@@ -0,0 +1,34 @@
+#
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+# We only want this apk build for tests.
+LOCAL_MODULE_TAGS := tests
+
+# Include all test java files.
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ android-support-test \
+
+LOCAL_PACKAGE_NAME := BackgroundDexOptServiceIntegrationTests
+LOCAL_COMPATIBILITY_SUITE := device-tests
+
+LOCAL_CERTIFICATE := platform
+
+include $(BUILD_PACKAGE)
diff --git a/tests/BackgroundDexOptServiceIntegrationTests/AndroidManifest.xml b/tests/BackgroundDexOptServiceIntegrationTests/AndroidManifest.xml
new file mode 100644
index 0000000..afae155
--- /dev/null
+++ b/tests/BackgroundDexOptServiceIntegrationTests/AndroidManifest.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.frameworks.bgdexopttest">
+
+
+ <!-- Uses API introduced in O (26) -->
+ <uses-sdk
+ android:minSdkVersion="1"
+ android:targetSdkVersion="26" />
+
+ <uses-permission android:name="android.permission.DUMP" />
+ <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />
+ <uses-permission android:name="android.permission.SET_TIME" />
+ <uses-permission android:name="android.permission.UPDATE_DEVICE_STATS" />
+ <uses-permission android:name="android.permission.WAKE_LOCK" />
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation
+ android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.frameworks.bgdexopttest"
+ android:label="Integration test for BackgroundDexOptService" />
+</manifest>
diff --git a/tests/BackgroundDexOptServiceIntegrationTests/AndroidTest.xml b/tests/BackgroundDexOptServiceIntegrationTests/AndroidTest.xml
new file mode 100644
index 0000000..9bb1e28
--- /dev/null
+++ b/tests/BackgroundDexOptServiceIntegrationTests/AndroidTest.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration description="Runs BackgroundDexOptService Integration Tests">
+ <!--DeviceSetup should go before TimeSetter because it stops automatic update of time-->
+ <target_preparer
+ class="com.android.tradefed.targetprep.DeviceSetup">
+ <option name="auto-update-time" value="OFF"/>
+ <option name="auto-update-timezone" value="OFF"/>
+ <option name="set-property" key="pm.dexopt.downgrade_after_inactive_days" value="2"/>
+ <option name="set-property" key="pm.dexopt.disable_bg_dexopt" value="true"/>
+ <option name="set-property" key="pm.dexopt.inactive" value="verify"/>
+ <option name="set-property" key="pm.dexopt.bg-dexopt" value="speed"/>
+ <option name="restore-settings" value="true"/>
+ <option name="restore-properties" value="true"/>
+ </target_preparer>
+
+ <!--Test app needs to be installed when we change its settings below-->
+ <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+ <option name="test-file-name" value="BackgroundDexOptServiceIntegrationTests.apk"/>
+ <option name="cleanup-apks" value="true"/>
+ </target_preparer>
+
+ <target_preparer class="com.android.tradefed.targetprep.SetPackagesRecentlyUsed">
+ <option name="package-recently-used-time" value="0d"/>
+ <option name="package-recently-used-name" value="com.android.frameworks.bgdexopttest"/>
+ </target_preparer>
+
+ <target_preparer class="com.android.tradefed.targetprep.RestartSystemServerTargetPreparer"/>
+
+ <target_preparer class="com.android.tradefed.targetprep.DeviceStorageFiller">
+ <!--32GB-->
+ <!--necessary because a package cannot create a file larger than 100GB-->
+ <option name="free-bytes" value="34359738368"/>
+ </target_preparer>
+
+ <option name="test-suite-tag" value="apct"/>
+ <option name="test-tag" value="BackgroundDexOptServiceIntegrationTests"/>
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+ <option name="package" value="com.android.frameworks.bgdexopttest"/>
+ <option name="runner" value="android.support.test.runner.AndroidJUnitRunner"/>
+ </test>
+</configuration>
diff --git a/tests/BackgroundDexOptServiceIntegrationTests/src/com/android/server/pm/BackgroundDexOptServiceIntegrationTests.java b/tests/BackgroundDexOptServiceIntegrationTests/src/com/android/server/pm/BackgroundDexOptServiceIntegrationTests.java
new file mode 100644
index 0000000..3734412
--- /dev/null
+++ b/tests/BackgroundDexOptServiceIntegrationTests/src/com/android/server/pm/BackgroundDexOptServiceIntegrationTests.java
@@ -0,0 +1,313 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import android.app.AlarmManager;
+import android.content.Context;
+import android.os.Environment;
+import android.os.SystemProperties;
+import android.os.storage.StorageManager;
+import android.support.test.InstrumentationRegistry;
+import android.util.Log;
+
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Assert;
+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.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Integration tests for {@link BackgroundDexOptService}.
+ *
+ * Tests various scenarios around BackgroundDexOptService.
+ * 1. Under normal conditions, check that dexopt upgrades test app to
+ * $(getprop pm.dexopt.bg-dexopt).
+ * 2. Under low storage conditions and package is unused, check
+ * that dexopt downgrades test app to $(getprop pm.dexopt.inactive).
+ * 3. Under low storage conditions and package is recently used, check
+ * that dexopt upgrades test app to $(getprop pm.dexopt.bg-dexopt).
+ *
+ * Each test case runs "cmd package bg-dexopt-job com.android.frameworks.bgdexopttest".
+ *
+ * The setup for these tests make sure this package has been configured to have been recently used
+ * plus installed far enough in the past. If a test case requires that this package has not been
+ * recently used, it sets the time forward more than
+ * `getprop pm.dexopt.downgrade_after_inactive_days` days.
+ *
+ * For tests that require low storage, the phone is filled up.
+ *
+ * Run with "atest BackgroundDexOptServiceIntegrationTests".
+ */
+@RunWith(JUnit4.class)
+public final class BackgroundDexOptServiceIntegrationTests {
+
+ private static final String TAG = BackgroundDexOptServiceIntegrationTests.class.getSimpleName();
+
+ // Name of package to test on.
+ private static final String PACKAGE_NAME = "com.android.frameworks.bgdexopttest";
+ // Name of file used to fill up storage.
+ private static final String BIG_FILE = "bigfile";
+ private static final String BG_DEXOPT_COMPILER_FILTER = SystemProperties.get(
+ "pm.dexopt.bg-dexopt");
+ private static final String DOWNGRADE_COMPILER_FILTER = SystemProperties.get(
+ "pm.dexopt.inactive");
+ private static final long DOWNGRADE_AFTER_DAYS = SystemProperties.getLong(
+ "pm.dexopt.downgrade_after_inactive_days", 0);
+ // Needs to be between 1.0 and 2.0.
+ private static final double LOW_STORAGE_MULTIPLIER = 1.5;
+
+ // The file used to fill up storage.
+ private File mBigFile;
+
+ // Remember start time.
+ @BeforeClass
+ public static void setUpAll() {
+ if (!SystemProperties.getBoolean("pm.dexopt.disable_bg_dexopt", false)) {
+ throw new RuntimeException(
+ "bg-dexopt is not disabled (set pm.dexopt.disable_bg_dexopt to true)");
+ }
+ if (DOWNGRADE_AFTER_DAYS < 1) {
+ throw new RuntimeException(
+ "pm.dexopt.downgrade_after_inactive_days must be at least 1");
+ }
+ if ("quicken".equals(BG_DEXOPT_COMPILER_FILTER)) {
+ throw new RuntimeException("pm.dexopt.bg-dexopt should not be \"quicken\"");
+ }
+ if ("quicken".equals(DOWNGRADE_COMPILER_FILTER)) {
+ throw new RuntimeException("pm.dexopt.inactive should not be \"quicken\"");
+ }
+ }
+
+
+ private static Context getContext() {
+ return InstrumentationRegistry.getTargetContext();
+ }
+
+ @Before
+ public void setUp() throws IOException {
+ File dataDir = getContext().getDataDir();
+ mBigFile = new File(dataDir, BIG_FILE);
+ }
+
+ @After
+ public void tearDown() {
+ if (mBigFile.exists()) {
+ boolean result = mBigFile.delete();
+ if (!result) {
+ throw new RuntimeException("Couldn't delete big file");
+ }
+ }
+ }
+
+ // Return the content of the InputStream as a String.
+ private static String inputStreamToString(InputStream is) throws IOException {
+ char[] buffer = new char[1024];
+ StringBuilder builder = new StringBuilder();
+ try (InputStreamReader reader = new InputStreamReader(is)) {
+ for (; ; ) {
+ int count = reader.read(buffer, 0, buffer.length);
+ if (count < 0) {
+ break;
+ }
+ builder.append(buffer, 0, count);
+ }
+ }
+ return builder.toString();
+ }
+
+ // Run the command and return the stdout.
+ private static String runShellCommand(String cmd) throws IOException {
+ Log.i(TAG, String.format("running command: '%s'", cmd));
+ long startTime = System.nanoTime();
+ Process p = Runtime.getRuntime().exec(cmd);
+ int res;
+ try {
+ res = p.waitFor();
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ String stdout = inputStreamToString(p.getInputStream());
+ String stderr = inputStreamToString(p.getErrorStream());
+ long elapsedTime = System.nanoTime() - startTime;
+ Log.i(TAG, String.format("ran command: '%s' in %d ms with return code %d", cmd,
+ TimeUnit.NANOSECONDS.toMillis(elapsedTime), res));
+ Log.i(TAG, "stdout");
+ Log.i(TAG, stdout);
+ Log.i(TAG, "stderr");
+ Log.i(TAG, stderr);
+ if (res != 0) {
+ throw new RuntimeException(String.format("failed command: '%s'", cmd));
+ }
+ return stdout;
+ }
+
+ // Run the command and return the stdout split by lines.
+ private static String[] runShellCommandSplitLines(String cmd) throws IOException {
+ return runShellCommand(cmd).split("\n");
+ }
+
+ // Return the compiler filter of a package.
+ private static String getCompilerFilter(String pkg) throws IOException {
+ String cmd = String.format("dumpsys package %s", pkg);
+ String[] lines = runShellCommandSplitLines(cmd);
+ final String substr = "compilation_filter=";
+ for (String line : lines) {
+ int startIndex = line.indexOf(substr);
+ if (startIndex < 0) {
+ continue;
+ }
+ startIndex += substr.length();
+ int endIndex = line.indexOf(']', startIndex);
+ return line.substring(startIndex, endIndex);
+ }
+ throw new RuntimeException("Couldn't find compiler filter in dumpsys package");
+ }
+
+ // Return the number of bytes available in the data partition.
+ private static long getDataDirUsableSpace() {
+ return Environment.getDataDirectory().getUsableSpace();
+ }
+
+ // Fill up the storage until there are bytesRemaining number of bytes available in the data
+ // partition. Writes to the current package's data directory.
+ private void fillUpStorage(long bytesRemaining) throws IOException {
+ Log.i(TAG, String.format("Filling up storage with %d bytes remaining", bytesRemaining));
+ logSpaceRemaining();
+ long numBytesToAdd = getDataDirUsableSpace() - bytesRemaining;
+ String cmd = String.format("fallocate -l %d %s", numBytesToAdd, mBigFile.getAbsolutePath());
+ runShellCommand(cmd);
+ logSpaceRemaining();
+ }
+
+ // Fill up storage so that device is in low storage condition.
+ private void fillUpToLowStorage() throws IOException {
+ fillUpStorage((long) (getStorageLowBytes() * LOW_STORAGE_MULTIPLIER));
+ }
+
+ // TODO(aeubanks): figure out how to get scheduled bg-dexopt to run
+ private static void runBackgroundDexOpt() throws IOException {
+ runShellCommand("cmd package bg-dexopt-job " + PACKAGE_NAME);
+ }
+
+ // Set the time ahead of the last use time of the test app in days.
+ private static void setTimeFutureDays(long futureDays) {
+ setTimeFutureMillis(TimeUnit.DAYS.toMillis(futureDays));
+ }
+
+ // Set the time ahead of the last use time of the test app in milliseconds.
+ private static void setTimeFutureMillis(long futureMillis) {
+ long currentTime = System.currentTimeMillis();
+ setTime(currentTime + futureMillis);
+ }
+
+ private static void setTime(long time) {
+ AlarmManager am = (AlarmManager) getContext().getSystemService(Context.ALARM_SERVICE);
+ am.setTime(time);
+ }
+
+ // Return the number of free bytes when the data partition is considered low on storage.
+ private static long getStorageLowBytes() {
+ StorageManager storageManager = (StorageManager) getContext().getSystemService(
+ Context.STORAGE_SERVICE);
+ return storageManager.getStorageLowBytes(Environment.getDataDirectory());
+ }
+
+ // Log the amount of space remaining in the data directory.
+ private static void logSpaceRemaining() throws IOException {
+ runShellCommand("df -h /data");
+ }
+
+ // Compile the given package with the given compiler filter.
+ private static void compilePackageWithFilter(String pkg, String filter) throws IOException {
+ runShellCommand(String.format("cmd package compile -f -m %s %s", filter, pkg));
+ }
+
+ // Test that background dexopt under normal conditions succeeds.
+ @Test
+ public void testBackgroundDexOpt() throws IOException {
+ // Set filter to quicken.
+ compilePackageWithFilter(PACKAGE_NAME, "verify");
+ Assert.assertEquals("verify", getCompilerFilter(PACKAGE_NAME));
+
+ runBackgroundDexOpt();
+
+ // Verify that bg-dexopt is successful.
+ Assert.assertEquals(BG_DEXOPT_COMPILER_FILTER, getCompilerFilter(PACKAGE_NAME));
+ }
+
+ // Test that background dexopt under low storage conditions upgrades used packages.
+ @Test
+ public void testBackgroundDexOptDowngradeSkipRecentlyUsedPackage() throws IOException {
+ // Should be less than DOWNGRADE_AFTER_DAYS.
+ long deltaDays = DOWNGRADE_AFTER_DAYS - 1;
+ try {
+ // Set time to future.
+ setTimeFutureDays(deltaDays);
+
+ // Set filter to quicken.
+ compilePackageWithFilter(PACKAGE_NAME, "quicken");
+ Assert.assertEquals("quicken", getCompilerFilter(PACKAGE_NAME));
+
+ // Fill up storage to trigger low storage threshold.
+ fillUpToLowStorage();
+
+ runBackgroundDexOpt();
+
+ // Verify that downgrade did not happen.
+ Assert.assertEquals(BG_DEXOPT_COMPILER_FILTER, getCompilerFilter(PACKAGE_NAME));
+ } finally {
+ // Reset time.
+ setTimeFutureDays(-deltaDays);
+ }
+ }
+
+ // Test that background dexopt under low storage conditions downgrades unused packages.
+ @Test
+ public void testBackgroundDexOptDowngradeSuccessful() throws IOException {
+ // Should be more than DOWNGRADE_AFTER_DAYS.
+ long deltaDays = DOWNGRADE_AFTER_DAYS + 1;
+ try {
+ // Set time to future.
+ setTimeFutureDays(deltaDays);
+
+ // Set filter to quicken.
+ compilePackageWithFilter(PACKAGE_NAME, "quicken");
+ Assert.assertEquals("quicken", getCompilerFilter(PACKAGE_NAME));
+
+ // Fill up storage to trigger low storage threshold.
+ fillUpToLowStorage();
+
+ runBackgroundDexOpt();
+
+ // Verify that downgrade is successful.
+ Assert.assertEquals(DOWNGRADE_COMPILER_FILTER, getCompilerFilter(PACKAGE_NAME));
+ } finally {
+ // Reset time.
+ setTimeFutureDays(-deltaDays);
+ }
+ }
+
+}
diff --git a/tests/net/java/android/net/IpSecAlgorithmTest.java b/tests/net/java/android/net/IpSecAlgorithmTest.java
new file mode 100644
index 0000000..6bdfdc6
--- /dev/null
+++ b/tests/net/java/android/net/IpSecAlgorithmTest.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.os.Parcel;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import java.util.Arrays;
+import java.util.Random;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Unit tests for {@link IpSecAlgorithm}. */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class IpSecAlgorithmTest {
+
+ private static final byte[] KEY_MATERIAL;
+
+ static {
+ KEY_MATERIAL = new byte[128];
+ new Random().nextBytes(KEY_MATERIAL);
+ };
+
+ @Test
+ public void testDefaultTruncLen() throws Exception {
+ IpSecAlgorithm explicit =
+ new IpSecAlgorithm(
+ IpSecAlgorithm.AUTH_HMAC_SHA256, Arrays.copyOf(KEY_MATERIAL, 256 / 8), 256);
+ IpSecAlgorithm implicit =
+ new IpSecAlgorithm(
+ IpSecAlgorithm.AUTH_HMAC_SHA256, Arrays.copyOf(KEY_MATERIAL, 256 / 8));
+ assertTrue(
+ "Default Truncation Length Incorrect, Explicit: "
+ + explicit
+ + "implicit: "
+ + implicit,
+ IpSecAlgorithm.equals(explicit, implicit));
+ }
+
+ @Test
+ public void testTruncLenValidation() throws Exception {
+ for (int truncLen : new int[] {256, 512}) {
+ new IpSecAlgorithm(
+ IpSecAlgorithm.AUTH_HMAC_SHA512,
+ Arrays.copyOf(KEY_MATERIAL, 512 / 8),
+ truncLen);
+ }
+
+ for (int truncLen : new int[] {255, 513}) {
+ try {
+ new IpSecAlgorithm(
+ IpSecAlgorithm.AUTH_HMAC_SHA512,
+ Arrays.copyOf(KEY_MATERIAL, 512 / 8),
+ truncLen);
+ fail("Invalid truncation length not validated");
+ } catch (IllegalArgumentException pass) {
+ }
+ }
+ }
+
+ @Test
+ public void testLenValidation() throws Exception {
+ for (int len : new int[] {128, 192, 256}) {
+ new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, Arrays.copyOf(KEY_MATERIAL, len / 8));
+ }
+ try {
+ new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, Arrays.copyOf(KEY_MATERIAL, 384 / 8));
+ fail("Invalid key length not validated");
+ } catch (IllegalArgumentException pass) {
+ }
+ }
+
+ @Test
+ public void testAlgoNameValidation() throws Exception {
+ try {
+ new IpSecAlgorithm("rot13", Arrays.copyOf(KEY_MATERIAL, 128 / 8));
+ fail("Invalid algorithm name not validated");
+ } catch (IllegalArgumentException pass) {
+ }
+ }
+
+ @Test
+ public void testParcelUnparcel() throws Exception {
+ IpSecAlgorithm init =
+ new IpSecAlgorithm(
+ IpSecAlgorithm.AUTH_HMAC_SHA512, Arrays.copyOf(KEY_MATERIAL, 512 / 8), 256);
+
+ Parcel p = Parcel.obtain();
+ p.setDataPosition(0);
+ init.writeToParcel(p, 0);
+
+ p.setDataPosition(0);
+ IpSecAlgorithm fin = IpSecAlgorithm.CREATOR.createFromParcel(p);
+ assertTrue("Parcel/Unparcel failed!", IpSecAlgorithm.equals(init, fin));
+ p.recycle();
+ }
+}
diff --git a/tests/net/java/android/net/IpSecConfigTest.java b/tests/net/java/android/net/IpSecConfigTest.java
index 1b4bef5..efc01f2a 100644
--- a/tests/net/java/android/net/IpSecConfigTest.java
+++ b/tests/net/java/android/net/IpSecConfigTest.java
@@ -71,7 +71,7 @@
c.setAuthentication(
IpSecTransform.DIRECTION_OUT,
new IpSecAlgorithm(
- IpSecAlgorithm.AUTH_HMAC_SHA1,
+ IpSecAlgorithm.AUTH_HMAC_MD5,
new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF, 0}));
c.setSpiResourceId(IpSecTransform.DIRECTION_OUT, 1984);
c.setEncryption(
@@ -82,7 +82,7 @@
c.setAuthentication(
IpSecTransform.DIRECTION_IN,
new IpSecAlgorithm(
- IpSecAlgorithm.AUTH_HMAC_SHA1,
+ IpSecAlgorithm.AUTH_HMAC_MD5,
new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF, 1}));
c.setSpiResourceId(IpSecTransform.DIRECTION_IN, 99);
assertParcelingIsLossless(c);
diff --git a/tests/net/java/android/net/MacAddressTest.java b/tests/net/java/android/net/MacAddressTest.java
index fcbb9da..558dbb6 100644
--- a/tests/net/java/android/net/MacAddressTest.java
+++ b/tests/net/java/android/net/MacAddressTest.java
@@ -21,7 +21,6 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.fail;
-import android.net.MacAddress.MacAddressType;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
@@ -37,11 +36,11 @@
static class AddrTypeTestCase {
byte[] addr;
- MacAddressType expected;
+ int expectedType;
- static AddrTypeTestCase of(MacAddressType expected, int... addr) {
+ static AddrTypeTestCase of(int expectedType, int... addr) {
AddrTypeTestCase t = new AddrTypeTestCase();
- t.expected = expected;
+ t.expectedType = expectedType;
t.addr = toByteArray(addr);
return t;
}
@@ -50,41 +49,73 @@
@Test
public void testMacAddrTypes() {
AddrTypeTestCase[] testcases = {
- AddrTypeTestCase.of(null),
- AddrTypeTestCase.of(null, 0),
- AddrTypeTestCase.of(null, 1, 2, 3, 4, 5),
- AddrTypeTestCase.of(null, 1, 2, 3, 4, 5, 6, 7),
- AddrTypeTestCase.of(MacAddressType.UNICAST, 0xa0, 0xb0, 0xc0, 0xd0, 0xe0, 0xf0),
- AddrTypeTestCase.of(MacAddressType.BROADCAST, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff),
- AddrTypeTestCase.of(MacAddressType.MULTICAST, 1, 2, 3, 4, 5, 6),
- AddrTypeTestCase.of(MacAddressType.MULTICAST, 11, 22, 33, 44, 55, 66),
- AddrTypeTestCase.of(MacAddressType.MULTICAST, 33, 33, 0xaa, 0xbb, 0xcc, 0xdd)
+ AddrTypeTestCase.of(MacAddress.TYPE_UNKNOWN),
+ AddrTypeTestCase.of(MacAddress.TYPE_UNKNOWN, 0),
+ AddrTypeTestCase.of(MacAddress.TYPE_UNKNOWN, 1, 2, 3, 4, 5),
+ AddrTypeTestCase.of(MacAddress.TYPE_UNKNOWN, 1, 2, 3, 4, 5, 6, 7),
+ AddrTypeTestCase.of(MacAddress.TYPE_UNICAST, 0xa0, 0xb0, 0xc0, 0xd0, 0xe0, 0xf0),
+ AddrTypeTestCase.of(MacAddress.TYPE_BROADCAST, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff),
+ AddrTypeTestCase.of(MacAddress.TYPE_MULTICAST, 1, 2, 3, 4, 5, 6),
+ AddrTypeTestCase.of(MacAddress.TYPE_MULTICAST, 11, 22, 33, 44, 55, 66),
+ AddrTypeTestCase.of(MacAddress.TYPE_MULTICAST, 33, 33, 0xaa, 0xbb, 0xcc, 0xdd)
};
for (AddrTypeTestCase t : testcases) {
- MacAddressType got = MacAddress.macAddressType(t.addr);
+ int got = MacAddress.macAddressType(t.addr);
String msg = String.format("expected type of %s to be %s, but got %s",
- Arrays.toString(t.addr), t.expected, got);
- assertEquals(msg, t.expected, got);
+ Arrays.toString(t.addr), t.expectedType, got);
+ assertEquals(msg, t.expectedType, got);
- if (got != null) {
- assertEquals(got, new MacAddress(t.addr).addressType());
+ if (got != MacAddress.TYPE_UNKNOWN) {
+ assertEquals(got, MacAddress.fromBytes(t.addr).addressType());
}
}
}
@Test
+ public void testToSafeString() {
+ String[][] macs = {
+ {"07:00:d3:56:8a:c4", "07:00:d3:00:00:00"},
+ {"33:33:aa:bb:cc:dd", "33:33:aa:00:00:00"},
+ {"06:00:00:00:00:00", "06:00:00:00:00:00"},
+ {"07:00:d3:56:8a:c4", "07:00:d3:00:00:00"}
+ };
+
+ for (String[] pair : macs) {
+ String mac = pair[0];
+ String expected = pair[1];
+ assertEquals(expected, MacAddress.fromString(mac).toSafeString());
+ }
+ }
+
+ @Test
+ public void testHexPaddingWhenPrinting() {
+ String[] macs = {
+ "07:00:d3:56:8a:c4",
+ "33:33:aa:bb:cc:dd",
+ "06:00:00:00:00:00",
+ "07:00:d3:56:8a:c4"
+ };
+
+ for (String mac : macs) {
+ assertEquals(mac, MacAddress.fromString(mac).toString());
+ assertEquals(mac,
+ MacAddress.stringAddrFromByteAddr(MacAddress.byteAddrFromStringAddr(mac)));
+ }
+ }
+
+ @Test
public void testIsMulticastAddress() {
MacAddress[] multicastAddresses = {
MacAddress.BROADCAST_ADDRESS,
- new MacAddress("07:00:d3:56:8a:c4"),
- new MacAddress("33:33:aa:bb:cc:dd"),
+ MacAddress.fromString("07:00:d3:56:8a:c4"),
+ MacAddress.fromString("33:33:aa:bb:cc:dd"),
};
MacAddress[] unicastAddresses = {
MacAddress.ALL_ZEROS_ADDRESS,
- new MacAddress("00:01:44:55:66:77"),
- new MacAddress("08:00:22:33:44:55"),
- new MacAddress("06:00:00:00:00:00"),
+ MacAddress.fromString("00:01:44:55:66:77"),
+ MacAddress.fromString("08:00:22:33:44:55"),
+ MacAddress.fromString("06:00:00:00:00:00"),
};
for (MacAddress mac : multicastAddresses) {
@@ -100,13 +131,13 @@
@Test
public void testIsLocallyAssignedAddress() {
MacAddress[] localAddresses = {
- new MacAddress("06:00:00:00:00:00"),
- new MacAddress("07:00:d3:56:8a:c4"),
- new MacAddress("33:33:aa:bb:cc:dd"),
+ MacAddress.fromString("06:00:00:00:00:00"),
+ MacAddress.fromString("07:00:d3:56:8a:c4"),
+ MacAddress.fromString("33:33:aa:bb:cc:dd"),
};
MacAddress[] universalAddresses = {
- new MacAddress("00:01:44:55:66:77"),
- new MacAddress("08:00:22:33:44:55"),
+ MacAddress.fromString("00:01:44:55:66:77"),
+ MacAddress.fromString("08:00:22:33:44:55"),
};
for (MacAddress mac : localAddresses) {
@@ -123,13 +154,16 @@
public void testMacAddressConversions() {
final int iterations = 10000;
for (int i = 0; i < iterations; i++) {
- MacAddress mac = MacAddress.getRandomAddress();
+ MacAddress mac = MacAddress.createRandomUnicastAddress();
String stringRepr = mac.toString();
byte[] bytesRepr = mac.toByteArray();
- assertEquals(mac, new MacAddress(stringRepr));
- assertEquals(mac, new MacAddress(bytesRepr));
+ assertEquals(mac, MacAddress.fromString(stringRepr));
+ assertEquals(mac, MacAddress.fromBytes(bytesRepr));
+
+ assertEquals(mac, MacAddress.fromString(MacAddress.stringAddrFromByteAddr(bytesRepr)));
+ assertEquals(mac, MacAddress.fromBytes(MacAddress.byteAddrFromStringAddr(stringRepr)));
}
}
@@ -138,7 +172,7 @@
final int iterations = 1000;
final String expectedAndroidOui = "da:a1:19";
for (int i = 0; i < iterations; i++) {
- MacAddress mac = MacAddress.getRandomAddress();
+ MacAddress mac = MacAddress.createRandomUnicastAddress();
String stringRepr = mac.toString();
assertTrue(stringRepr + " expected to be a locally assigned address",
@@ -150,13 +184,14 @@
final Random r = new Random();
final String anotherOui = "24:5f:78";
final String expectedLocalOui = "26:5f:78";
- final MacAddress base = new MacAddress(anotherOui + ":0:0:0");
+ final MacAddress base = MacAddress.fromString(anotherOui + ":0:0:0");
for (int i = 0; i < iterations; i++) {
- MacAddress mac = MacAddress.getRandomAddress(base, r);
+ MacAddress mac = MacAddress.createRandomUnicastAddress(base, r);
String stringRepr = mac.toString();
assertTrue(stringRepr + " expected to be a locally assigned address",
mac.isLocallyAssigned());
+ assertEquals(MacAddress.TYPE_UNICAST, mac.addressType());
assertTrue(stringRepr + " expected to begin with " + expectedLocalOui,
stringRepr.startsWith(expectedLocalOui));
}
@@ -165,7 +200,6 @@
@Test
public void testConstructorInputValidation() {
String[] invalidStringAddresses = {
- null,
"",
"abcd",
"1:2:3:4:5",
@@ -175,14 +209,19 @@
for (String s : invalidStringAddresses) {
try {
- MacAddress mac = new MacAddress(s);
- fail("new MacAddress(" + s + ") should have failed, but returned " + mac);
+ MacAddress mac = MacAddress.fromString(s);
+ fail("MacAddress.fromString(" + s + ") should have failed, but returned " + mac);
} catch (IllegalArgumentException excepted) {
}
}
+ try {
+ MacAddress mac = MacAddress.fromString(null);
+ fail("MacAddress.fromString(null) should have failed, but returned " + mac);
+ } catch (NullPointerException excepted) {
+ }
+
byte[][] invalidBytesAddresses = {
- null,
{},
{1,2,3,4,5},
{1,2,3,4,5,6,7},
@@ -190,12 +229,18 @@
for (byte[] b : invalidBytesAddresses) {
try {
- MacAddress mac = new MacAddress(b);
- fail("new MacAddress(" + Arrays.toString(b)
+ MacAddress mac = MacAddress.fromBytes(b);
+ fail("MacAddress.fromBytes(" + Arrays.toString(b)
+ ") should have failed, but returned " + mac);
} catch (IllegalArgumentException excepted) {
}
}
+
+ try {
+ MacAddress mac = MacAddress.fromBytes(null);
+ fail("MacAddress.fromBytes(null) should have failed, but returned " + mac);
+ } catch (NullPointerException excepted) {
+ }
}
static byte[] toByteArray(int... in) {
diff --git a/tools/aapt2/cmd/Optimize.cpp b/tools/aapt2/cmd/Optimize.cpp
index 2bf91a5..3b90637 100644
--- a/tools/aapt2/cmd/Optimize.cpp
+++ b/tools/aapt2/cmd/Optimize.cpp
@@ -17,6 +17,7 @@
#include <memory>
#include <vector>
+#include "android-base/file.h"
#include "android-base/stringprintf.h"
#include "androidfw/ResourceTypes.h"
@@ -47,6 +48,7 @@
using ::aapt::configuration::PostProcessingConfiguration;
using ::android::ResTable_config;
using ::android::StringPiece;
+using ::android::base::ReadFileToString;
using ::android::base::StringAppendF;
using ::android::base::StringPrintf;
@@ -279,6 +281,20 @@
OptimizeContext* context_;
};
+bool ExtractWhitelistFromConfig(const std::string& path, OptimizeContext* context,
+ OptimizeOptions* options) {
+ std::string contents;
+ if (!ReadFileToString(path, &contents, true)) {
+ context->GetDiagnostics()->Error(DiagMessage()
+ << "failed to parse whitelist from config file: " << path);
+ return false;
+ }
+ for (const StringPiece& resource_name : util::Tokenize(contents, ',')) {
+ options->table_flattener_options.whitelisted_resources.insert(resource_name.to_string());
+ }
+ return true;
+}
+
bool ExtractAppDataFromManifest(OptimizeContext* context, const LoadedApk* apk,
OptimizeOptions* out_options) {
const xml::XmlResource* manifest = apk->GetManifest();
@@ -302,6 +318,7 @@
OptimizeContext context;
OptimizeOptions options;
Maybe<std::string> config_path;
+ Maybe<std::string> whitelist_path;
Maybe<std::string> target_densities;
Maybe<std::string> target_abis;
std::vector<std::string> configs;
@@ -320,6 +337,10 @@
"All the resources that would be unused on devices of the given densities will be \n"
"removed from the APK.",
&target_densities)
+ .OptionalFlag("--whitelist-config-path",
+ "Path to the whitelist.cfg file containing whitelisted resources \n"
+ "whose names should not be altered in final resource tables.",
+ &whitelist_path)
.OptionalFlag(
"--target-abis",
"Comma separated list of the CPU ABIs that the APK will be optimized for.\n"
@@ -339,6 +360,9 @@
"Enables encoding sparse entries using a binary search tree.\n"
"This decreases APK size at the cost of resource retrieval performance.",
&options.table_flattener_options.use_sparse_entries)
+ .OptionalSwitch("--enable-resource-obfuscation",
+ "Enables obfuscation of key string pool to single value",
+ &options.table_flattener_options.collapse_key_stringpool)
.OptionalSwitch("-v", "Enables verbose logging", &verbose);
if (!flags.Parse("aapt2 optimize", args, &std::cerr)) {
@@ -425,6 +449,15 @@
return 1;
}
+ if (options.table_flattener_options.collapse_key_stringpool) {
+ if (whitelist_path) {
+ std::string& path = whitelist_path.value();
+ if (!ExtractWhitelistFromConfig(path, &context, &options)) {
+ return 1;
+ }
+ }
+ }
+
if (!ExtractAppDataFromManifest(&context, apk.get(), &options)) {
return 1;
}
diff --git a/tools/aapt2/format/binary/TableFlattener.cpp b/tools/aapt2/format/binary/TableFlattener.cpp
index 2a51df3..a3034df 100644
--- a/tools/aapt2/format/binary/TableFlattener.cpp
+++ b/tools/aapt2/format/binary/TableFlattener.cpp
@@ -220,12 +220,15 @@
class PackageFlattener {
public:
PackageFlattener(IAaptContext* context, ResourceTablePackage* package,
- const std::map<size_t, std::string>* shared_libs, bool use_sparse_entries)
+ const std::map<size_t, std::string>* shared_libs, bool use_sparse_entries,
+ bool collapse_key_stringpool, const std::set<std::string>& whitelisted_resources)
: context_(context),
diag_(context->GetDiagnostics()),
package_(package),
shared_libs_(shared_libs),
- use_sparse_entries_(use_sparse_entries) {
+ use_sparse_entries_(use_sparse_entries),
+ collapse_key_stringpool_(collapse_key_stringpool),
+ whitelisted_resources_(whitelisted_resources) {
}
bool FlattenPackage(BigBuffer* buffer) {
@@ -494,13 +497,23 @@
// configuration available. Here we reverse this to match the binary
// table.
std::map<ConfigDescription, std::vector<FlatEntry>> config_to_entry_list_map;
- for (ResourceEntry* entry : sorted_entries) {
- const uint32_t key_index = (uint32_t)key_pool_.MakeRef(entry->name).index();
+ // hardcoded string uses characters which make it an invalid resource name
+ const std::string obfuscated_resource_name = "0_resource_name_obfuscated";
+
+ for (ResourceEntry* entry : sorted_entries) {
+ uint32_t local_key_index;
+ if (!collapse_key_stringpool_ ||
+ whitelisted_resources_.find(entry->name) != whitelisted_resources_.end()) {
+ local_key_index = (uint32_t)key_pool_.MakeRef(entry->name).index();
+ } else {
+ // resource isn't whitelisted, add it as obfuscated value
+ local_key_index = (uint32_t)key_pool_.MakeRef(obfuscated_resource_name).index();
+ }
// Group values by configuration.
for (auto& config_value : entry->values) {
config_to_entry_list_map[config_value->config].push_back(
- FlatEntry{entry, config_value->value.get(), key_index});
+ FlatEntry{entry, config_value->value.get(), local_key_index});
}
}
@@ -549,6 +562,8 @@
bool use_sparse_entries_;
StringPool type_pool_;
StringPool key_pool_;
+ bool collapse_key_stringpool_;
+ const std::set<std::string>& whitelisted_resources_;
};
} // namespace
@@ -593,7 +608,8 @@
}
PackageFlattener flattener(context, package.get(), &table->included_packages_,
- options_.use_sparse_entries);
+ options_.use_sparse_entries, options_.collapse_key_stringpool,
+ options_.whitelisted_resources);
if (!flattener.FlattenPackage(&package_buffer)) {
return false;
}
diff --git a/tools/aapt2/format/binary/TableFlattener.h b/tools/aapt2/format/binary/TableFlattener.h
index 88cbddf..c2e1d4b 100644
--- a/tools/aapt2/format/binary/TableFlattener.h
+++ b/tools/aapt2/format/binary/TableFlattener.h
@@ -35,6 +35,14 @@
// This is only available on platforms O+ and will only be respected when
// minSdk is O+.
bool use_sparse_entries = false;
+
+ // When true, the key string pool in the final ResTable
+ // is collapsed to a single entry. All resource entries
+ // have name indices that point to this single value
+ bool collapse_key_stringpool = false;
+
+ // Set of whitelisted resource names to avoid altering in key stringpool
+ std::set<std::string> whitelisted_resources;
};
class TableFlattener : public IResourceTableConsumer {
diff --git a/tools/aapt2/format/binary/TableFlattener_test.cpp b/tools/aapt2/format/binary/TableFlattener_test.cpp
index e11890b..f0b80d2 100644
--- a/tools/aapt2/format/binary/TableFlattener_test.cpp
+++ b/tools/aapt2/format/binary/TableFlattener_test.cpp
@@ -127,6 +127,15 @@
<< StringPiece16(actual_name.name, actual_name.nameLen) << "'";
}
+ ResourceName actual_res_name(resName.value());
+
+ if (expected_res_name.entry != actual_res_name.entry ||
+ expected_res_name.package != actual_res_name.package ||
+ expected_res_name.type != actual_res_name.type) {
+ return ::testing::AssertionFailure() << "expected resource '" << expected_res_name.to_string()
+ << "' but got '" << actual_res_name.to_string() << "'";
+ }
+
if (expected_config != config) {
return ::testing::AssertionFailure() << "expected config '" << expected_config
<< "' but got '" << ConfigDescription(config) << "'";
@@ -450,4 +459,113 @@
ASSERT_FALSE(Flatten(context.get(), {}, table.get(), &result));
}
+TEST_F(TableFlattenerTest, ObfuscatingResourceNamesNoWhitelistSucceeds) {
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .SetPackageId("com.app.test", 0x7f)
+ .AddSimple("com.app.test:id/one", ResourceId(0x7f020000))
+ .AddSimple("com.app.test:id/two", ResourceId(0x7f020001))
+ .AddValue("com.app.test:id/three", ResourceId(0x7f020002),
+ test::BuildReference("com.app.test:id/one", ResourceId(0x7f020000)))
+ .AddValue("com.app.test:integer/one", ResourceId(0x7f030000),
+ util::make_unique<BinaryPrimitive>(uint8_t(Res_value::TYPE_INT_DEC), 1u))
+ .AddValue("com.app.test:integer/one", test::ParseConfigOrDie("v1"),
+ ResourceId(0x7f030000),
+ util::make_unique<BinaryPrimitive>(uint8_t(Res_value::TYPE_INT_DEC), 2u))
+ .AddString("com.app.test:string/test", ResourceId(0x7f040000), "foo")
+ .AddString("com.app.test:layout/bar", ResourceId(0x7f050000), "res/layout/bar.xml")
+ .Build();
+
+ TableFlattenerOptions options;
+ options.collapse_key_stringpool = true;
+
+ ResTable res_table;
+
+ ASSERT_TRUE(Flatten(context_.get(), options, table.get(), &res_table));
+
+ EXPECT_TRUE(Exists(&res_table, "com.app.test:id/0_resource_name_obfuscated",
+ ResourceId(0x7f020000), {}, Res_value::TYPE_INT_BOOLEAN, 0u, 0u));
+
+ EXPECT_TRUE(Exists(&res_table, "com.app.test:id/0_resource_name_obfuscated",
+ ResourceId(0x7f020001), {}, Res_value::TYPE_INT_BOOLEAN, 0u, 0u));
+
+ EXPECT_TRUE(Exists(&res_table, "com.app.test:id/0_resource_name_obfuscated",
+ ResourceId(0x7f020002), {}, Res_value::TYPE_REFERENCE, 0x7f020000u, 0u));
+
+ EXPECT_TRUE(Exists(&res_table, "com.app.test:integer/0_resource_name_obfuscated",
+ ResourceId(0x7f030000), {}, Res_value::TYPE_INT_DEC, 1u,
+ ResTable_config::CONFIG_VERSION));
+
+ EXPECT_TRUE(Exists(&res_table, "com.app.test:integer/0_resource_name_obfuscated",
+ ResourceId(0x7f030000), test::ParseConfigOrDie("v1"), Res_value::TYPE_INT_DEC,
+ 2u, ResTable_config::CONFIG_VERSION));
+
+ std::u16string foo_str = u"foo";
+ ssize_t idx = res_table.getTableStringBlock(0)->indexOfString(foo_str.data(), foo_str.size());
+ ASSERT_GE(idx, 0);
+ EXPECT_TRUE(Exists(&res_table, "com.app.test:string/0_resource_name_obfuscated",
+ ResourceId(0x7f040000), {}, Res_value::TYPE_STRING, (uint32_t)idx, 0u));
+
+ std::u16string bar_path = u"res/layout/bar.xml";
+ idx = res_table.getTableStringBlock(0)->indexOfString(bar_path.data(), bar_path.size());
+ ASSERT_GE(idx, 0);
+ EXPECT_TRUE(Exists(&res_table, "com.app.test:layout/0_resource_name_obfuscated",
+ ResourceId(0x7f050000), {}, Res_value::TYPE_STRING, (uint32_t)idx, 0u));
+}
+
+TEST_F(TableFlattenerTest, ObfuscatingResourceNamesWithWhitelistSucceeds) {
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .SetPackageId("com.app.test", 0x7f)
+ .AddSimple("com.app.test:id/one", ResourceId(0x7f020000))
+ .AddSimple("com.app.test:id/two", ResourceId(0x7f020001))
+ .AddValue("com.app.test:id/three", ResourceId(0x7f020002),
+ test::BuildReference("com.app.test:id/one", ResourceId(0x7f020000)))
+ .AddValue("com.app.test:integer/one", ResourceId(0x7f030000),
+ util::make_unique<BinaryPrimitive>(uint8_t(Res_value::TYPE_INT_DEC), 1u))
+ .AddValue("com.app.test:integer/one", test::ParseConfigOrDie("v1"),
+ ResourceId(0x7f030000),
+ util::make_unique<BinaryPrimitive>(uint8_t(Res_value::TYPE_INT_DEC), 2u))
+ .AddString("com.app.test:string/test", ResourceId(0x7f040000), "foo")
+ .AddString("com.app.test:layout/bar", ResourceId(0x7f050000), "res/layout/bar.xml")
+ .Build();
+
+ TableFlattenerOptions options;
+ options.collapse_key_stringpool = true;
+ options.whitelisted_resources.insert("test");
+ options.whitelisted_resources.insert("three");
+ ResTable res_table;
+
+ ASSERT_TRUE(Flatten(context_.get(), options, table.get(), &res_table));
+
+ EXPECT_TRUE(Exists(&res_table, "com.app.test:id/0_resource_name_obfuscated",
+ ResourceId(0x7f020000), {}, Res_value::TYPE_INT_BOOLEAN, 0u, 0u));
+
+ EXPECT_TRUE(Exists(&res_table, "com.app.test:id/0_resource_name_obfuscated",
+ ResourceId(0x7f020001), {}, Res_value::TYPE_INT_BOOLEAN, 0u, 0u));
+
+ EXPECT_TRUE(Exists(&res_table, "com.app.test:id/three", ResourceId(0x7f020002), {},
+ Res_value::TYPE_REFERENCE, 0x7f020000u, 0u));
+
+ EXPECT_TRUE(Exists(&res_table, "com.app.test:integer/0_resource_name_obfuscated",
+ ResourceId(0x7f030000), {}, Res_value::TYPE_INT_DEC, 1u,
+ ResTable_config::CONFIG_VERSION));
+
+ EXPECT_TRUE(Exists(&res_table, "com.app.test:integer/0_resource_name_obfuscated",
+ ResourceId(0x7f030000), test::ParseConfigOrDie("v1"), Res_value::TYPE_INT_DEC,
+ 2u, ResTable_config::CONFIG_VERSION));
+
+ std::u16string foo_str = u"foo";
+ ssize_t idx = res_table.getTableStringBlock(0)->indexOfString(foo_str.data(), foo_str.size());
+ ASSERT_GE(idx, 0);
+ EXPECT_TRUE(Exists(&res_table, "com.app.test:string/test", ResourceId(0x7f040000), {},
+ Res_value::TYPE_STRING, (uint32_t)idx, 0u));
+
+ std::u16string bar_path = u"res/layout/bar.xml";
+ idx = res_table.getTableStringBlock(0)->indexOfString(bar_path.data(), bar_path.size());
+ ASSERT_GE(idx, 0);
+ EXPECT_TRUE(Exists(&res_table, "com.app.test:layout/0_resource_name_obfuscated",
+ ResourceId(0x7f050000), {}, Res_value::TYPE_STRING, (uint32_t)idx, 0u));
+}
+
} // namespace aapt
diff --git a/tools/aapt2/text/Unicode_test.cpp b/tools/aapt2/text/Unicode_test.cpp
index a8e797c..16bc2e8 100644
--- a/tools/aapt2/text/Unicode_test.cpp
+++ b/tools/aapt2/text/Unicode_test.cpp
@@ -63,6 +63,7 @@
EXPECT_FALSE(IsValidResourceEntryName("Føø/Bar"));
EXPECT_FALSE(IsValidResourceEntryName("Føø:Bar"));
EXPECT_FALSE(IsValidResourceEntryName("Føø;Bar"));
+ EXPECT_FALSE(IsValidResourceEntryName("0_resource_name_obfuscated"));
}
} // namespace text
diff --git a/wifi/java/android/net/wifi/WifiInfo.java b/wifi/java/android/net/wifi/WifiInfo.java
index bf8fed1..3eb13ce 100644
--- a/wifi/java/android/net/wifi/WifiInfo.java
+++ b/wifi/java/android/net/wifi/WifiInfo.java
@@ -22,7 +22,6 @@
import android.net.NetworkUtils;
import android.text.TextUtils;
-import java.lang.Math;
import java.net.InetAddress;
import java.net.Inet4Address;
import java.net.UnknownHostException;
@@ -126,143 +125,35 @@
public long rxSuccess;
/**
- * Average rate of lost transmitted packets, in units of packets per 5 seconds.
+ * Average rate of lost transmitted packets, in units of packets per second.
* @hide
*/
public double txBadRate;
/**
- * Average rate of transmitted retry packets, in units of packets per 5 seconds.
+ * Average rate of transmitted retry packets, in units of packets per second.
* @hide
*/
public double txRetriesRate;
/**
- * Average rate of successfully transmitted unicast packets, in units of packets per 5 seconds.
+ * Average rate of successfully transmitted unicast packets, in units of packets per second.
* @hide
*/
public double txSuccessRate;
/**
- * Average rate of received unicast data packets, in units of packets per 5 seconds.
+ * Average rate of received unicast data packets, in units of packets per second.
* @hide
*/
public double rxSuccessRate;
- private static final long RESET_TIME_STAMP = Long.MIN_VALUE;
- private static final long FILTER_TIME_CONSTANT = 3000;
- /**
- * This factor is used to adjust the rate output under the new algorithm
- * such that the result is comparable to the previous algorithm.
- * This actually converts from unit 'packets per second' to 'packets per 5 seconds'.
- */
- private static final long OUTPUT_SCALE_FACTOR = 5;
- private long mLastPacketCountUpdateTimeStamp;
-
- /**
- * @hide
- */
- public int badRssiCount;
-
- /**
- * @hide
- */
- public int linkStuckCount;
-
- /**
- * @hide
- */
- public int lowRssiCount;
-
/**
* @hide
*/
public int score;
/**
- * @hide
+ * Flag indicating that AP has hinted that upstream connection is metered,
+ * and sensitive to heavy data transfers.
*/
- public void updatePacketRates(WifiLinkLayerStats stats, long timeStamp) {
- if (stats != null) {
- long txgood = stats.txmpdu_be + stats.txmpdu_bk + stats.txmpdu_vi + stats.txmpdu_vo;
- long txretries = stats.retries_be + stats.retries_bk
- + stats.retries_vi + stats.retries_vo;
- long rxgood = stats.rxmpdu_be + stats.rxmpdu_bk + stats.rxmpdu_vi + stats.rxmpdu_vo;
- long txbad = stats.lostmpdu_be + stats.lostmpdu_bk
- + stats.lostmpdu_vi + stats.lostmpdu_vo;
-
- if (mLastPacketCountUpdateTimeStamp != RESET_TIME_STAMP
- && mLastPacketCountUpdateTimeStamp < timeStamp
- && txBad <= txbad
- && txSuccess <= txgood
- && rxSuccess <= rxgood
- && txRetries <= txretries) {
- long timeDelta = timeStamp - mLastPacketCountUpdateTimeStamp;
- double lastSampleWeight = Math.exp(-1.0 * timeDelta / FILTER_TIME_CONSTANT);
- double currentSampleWeight = 1.0 - lastSampleWeight;
-
- txBadRate = txBadRate * lastSampleWeight
- + (txbad - txBad) * OUTPUT_SCALE_FACTOR * 1000 / timeDelta
- * currentSampleWeight;
- txSuccessRate = txSuccessRate * lastSampleWeight
- + (txgood - txSuccess) * OUTPUT_SCALE_FACTOR * 1000 / timeDelta
- * currentSampleWeight;
- rxSuccessRate = rxSuccessRate * lastSampleWeight
- + (rxgood - rxSuccess) * OUTPUT_SCALE_FACTOR * 1000 / timeDelta
- * currentSampleWeight;
- txRetriesRate = txRetriesRate * lastSampleWeight
- + (txretries - txRetries) * OUTPUT_SCALE_FACTOR * 1000/ timeDelta
- * currentSampleWeight;
- } else {
- txBadRate = 0;
- txSuccessRate = 0;
- rxSuccessRate = 0;
- txRetriesRate = 0;
- }
- txBad = txbad;
- txSuccess = txgood;
- rxSuccess = rxgood;
- txRetries = txretries;
- mLastPacketCountUpdateTimeStamp = timeStamp;
- } else {
- txBad = 0;
- txSuccess = 0;
- rxSuccess = 0;
- txRetries = 0;
- txBadRate = 0;
- txSuccessRate = 0;
- rxSuccessRate = 0;
- txRetriesRate = 0;
- mLastPacketCountUpdateTimeStamp = RESET_TIME_STAMP;
- }
- }
-
-
- /**
- * This function is less powerful and used if the WifiLinkLayerStats API is not implemented
- * at the Wifi HAL
- * @hide
- */
- public void updatePacketRates(long txPackets, long rxPackets) {
- //paranoia
- txBad = 0;
- txRetries = 0;
- txBadRate = 0;
- txRetriesRate = 0;
- if (txSuccess <= txPackets && rxSuccess <= rxPackets) {
- txSuccessRate = (txSuccessRate * 0.5)
- + ((double) (txPackets - txSuccess) * 0.5);
- rxSuccessRate = (rxSuccessRate * 0.5)
- + ((double) (rxPackets - rxSuccess) * 0.5);
- } else {
- txBadRate = 0;
- txRetriesRate = 0;
- }
- txSuccess = txPackets;
- rxSuccess = rxPackets;
- }
-
- /**
- * Flag indicating that AP has hinted that upstream connection is metered,
- * and sensitive to heavy data transfers.
- */
private boolean mMeteredHint;
/** @hide */
@@ -274,7 +165,6 @@
mRssi = INVALID_RSSI;
mLinkSpeed = -1;
mFrequency = -1;
- mLastPacketCountUpdateTimeStamp = RESET_TIME_STAMP;
}
/** @hide */
@@ -296,11 +186,7 @@
txSuccessRate = 0;
rxSuccessRate = 0;
txRetriesRate = 0;
- lowRssiCount = 0;
- badRssiCount = 0;
- linkStuckCount = 0;
score = 0;
- mLastPacketCountUpdateTimeStamp = RESET_TIME_STAMP;
}
/**
@@ -328,12 +214,7 @@
txRetriesRate = source.txRetriesRate;
txSuccessRate = source.txSuccessRate;
rxSuccessRate = source.rxSuccessRate;
- mLastPacketCountUpdateTimeStamp =
- source.mLastPacketCountUpdateTimeStamp;
score = source.score;
- badRssiCount = source.badRssiCount;
- lowRssiCount = source.lowRssiCount;
- linkStuckCount = source.linkStuckCount;
}
}
@@ -452,22 +333,6 @@
}
/**
- * @hide
- * This returns txSuccessRate in packets per second.
- */
- public double getTxSuccessRatePps() {
- return txSuccessRate / OUTPUT_SCALE_FACTOR;
- }
-
- /**
- * @hide
- * This returns rxSuccessRate in packets per second.
- */
- public double getRxSuccessRatePps() {
- return rxSuccessRate / OUTPUT_SCALE_FACTOR;
- }
-
- /**
* Record the MAC address of the WLAN interface
* @param macAddress the MAC address in {@code XX:XX:XX:XX:XX:XX} form
* @hide
@@ -658,8 +523,6 @@
dest.writeDouble(txRetriesRate);
dest.writeDouble(txBadRate);
dest.writeDouble(rxSuccessRate);
- dest.writeInt(badRssiCount);
- dest.writeInt(lowRssiCount);
mSupplicantState.writeToParcel(dest, flags);
}
@@ -689,8 +552,6 @@
info.txRetriesRate = in.readDouble();
info.txBadRate = in.readDouble();
info.rxSuccessRate = in.readDouble();
- info.badRssiCount = in.readInt();
- info.lowRssiCount = in.readInt();
info.mSupplicantState = SupplicantState.CREATOR.createFromParcel(in);
return info;
}
diff --git a/wifi/java/android/net/wifi/WifiLinkLayerStats.java b/wifi/java/android/net/wifi/WifiLinkLayerStats.java
deleted file mode 100644
index edd400b..0000000
--- a/wifi/java/android/net/wifi/WifiLinkLayerStats.java
+++ /dev/null
@@ -1,211 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.wifi;
-
-import android.os.Parcelable;
-import android.os.Parcel;
-
-import java.util.Arrays;
-
-/**
- * A class representing link layer statistics collected over a Wifi Interface.
- */
-/** {@hide} */
-public class WifiLinkLayerStats implements Parcelable {
- private static final String TAG = "WifiLinkLayerStats";
-
- /**
- * The current status of this network configuration entry.
- * @see Status
- */
- /** {@hide} */
- public int status;
-
- /**
- * The network's SSID. Can either be an ASCII string,
- * which must be enclosed in double quotation marks
- * (e.g., {@code "MyNetwork"}, or a string of
- * hex digits,which are not enclosed in quotes
- * (e.g., {@code 01a243f405}).
- */
- /** {@hide} */
- public String SSID;
- /**
- * When set. this is the BSSID the radio is currently associated with.
- * The value is a string in the format of an Ethernet MAC address, e.g.,
- * <code>XX:XX:XX:XX:XX:XX</code> where each <code>X</code> is a hex digit.
- */
- /** {@hide} */
- public String BSSID;
-
- /* number beacons received from our own AP */
- /** {@hide} */
- public int beacon_rx;
-
- /* RSSI taken on management frames */
- /** {@hide} */
- public int rssi_mgmt;
-
- /* packets counters */
- /** {@hide} */
- /* WME Best Effort Access Category (receive mpdu, transmit mpdu, lost mpdu, number of retries)*/
- public long rxmpdu_be;
- /** {@hide} */
- public long txmpdu_be;
- /** {@hide} */
- public long lostmpdu_be;
- /** {@hide} */
- public long retries_be;
- /** {@hide} */
- /* WME Background Access Category (receive mpdu, transmit mpdu, lost mpdu, number of retries) */
- public long rxmpdu_bk;
- /** {@hide} */
- public long txmpdu_bk;
- /** {@hide} */
- public long lostmpdu_bk;
- /** {@hide} */
- public long retries_bk;
- /** {@hide} */
- /* WME Video Access Category (receive mpdu, transmit mpdu, lost mpdu, number of retries) */
- public long rxmpdu_vi;
- /** {@hide} */
- public long txmpdu_vi;
- /** {@hide} */
- public long lostmpdu_vi;
- /** {@hide} */
- public long retries_vi;
- /** {@hide} */
- /* WME Voice Access Category (receive mpdu, transmit mpdu, lost mpdu, number of retries) */
- public long rxmpdu_vo;
- /** {@hide} */
- public long txmpdu_vo;
- /** {@hide} */
- public long lostmpdu_vo;
- /** {@hide} */
- public long retries_vo;
-
- /** {@hide} */
- public int on_time;
- /** {@hide} */
- public int tx_time;
- /** {@hide} */
- public int[] tx_time_per_level;
- /** {@hide} */
- public int rx_time;
- /** {@hide} */
- public int on_time_scan;
-
- /** {@hide} */
- public WifiLinkLayerStats() {
- }
-
- @Override
- /** {@hide} */
- public String toString() {
- StringBuilder sbuf = new StringBuilder();
- sbuf.append(" WifiLinkLayerStats: ").append('\n');
-
- if (this.SSID != null) {
- sbuf.append(" SSID: ").append(this.SSID).append('\n');
- }
- if (this.BSSID != null) {
- sbuf.append(" BSSID: ").append(this.BSSID).append('\n');
- }
-
- sbuf.append(" my bss beacon rx: ").append(Integer.toString(this.beacon_rx)).append('\n');
- sbuf.append(" RSSI mgmt: ").append(Integer.toString(this.rssi_mgmt)).append('\n');
- sbuf.append(" BE : ").append(" rx=").append(Long.toString(this.rxmpdu_be))
- .append(" tx=").append(Long.toString(this.txmpdu_be))
- .append(" lost=").append(Long.toString(this.lostmpdu_be))
- .append(" retries=").append(Long.toString(this.retries_be)).append('\n');
- sbuf.append(" BK : ").append(" rx=").append(Long.toString(this.rxmpdu_bk))
- .append(" tx=").append(Long.toString(this.txmpdu_bk))
- .append(" lost=").append(Long.toString(this.lostmpdu_bk))
- .append(" retries=").append(Long.toString(this.retries_bk)).append('\n');
- sbuf.append(" VI : ").append(" rx=").append(Long.toString(this.rxmpdu_vi))
- .append(" tx=").append(Long.toString(this.txmpdu_vi))
- .append(" lost=").append(Long.toString(this.lostmpdu_vi))
- .append(" retries=").append(Long.toString(this.retries_vi)).append('\n');
- sbuf.append(" VO : ").append(" rx=").append(Long.toString(this.rxmpdu_vo))
- .append(" tx=").append(Long.toString(this.txmpdu_vo))
- .append(" lost=").append(Long.toString(this.lostmpdu_vo))
- .append(" retries=").append(Long.toString(this.retries_vo)).append('\n');
- sbuf.append(" on_time : ").append(Integer.toString(this.on_time))
- .append(" rx_time=").append(Integer.toString(this.rx_time))
- .append(" scan_time=").append(Integer.toString(this.on_time_scan)).append('\n')
- .append(" tx_time=").append(Integer.toString(this.tx_time))
- .append(" tx_time_per_level=" + Arrays.toString(tx_time_per_level));
- return sbuf.toString();
- }
-
- /** Implement the Parcelable interface {@hide} */
- public int describeContents() {
- return 0;
- }
-
- /** {@hide} */
- public String getPrintableSsid() {
- if (SSID == null) return "";
- final int length = SSID.length();
- if (length > 2 && (SSID.charAt(0) == '"') && SSID.charAt(length - 1) == '"') {
- return SSID.substring(1, length - 1);
- }
-
- /** The ascii-encoded string format is P"<ascii-encoded-string>"
- * The decoding is implemented in the supplicant for a newly configured
- * network.
- */
- if (length > 3 && (SSID.charAt(0) == 'P') && (SSID.charAt(1) == '"') &&
- (SSID.charAt(length-1) == '"')) {
- WifiSsid wifiSsid = WifiSsid.createFromAsciiEncoded(
- SSID.substring(2, length - 1));
- return wifiSsid.toString();
- }
- return SSID;
- }
-
- /** Implement the Parcelable interface {@hide} */
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeString(SSID);
- dest.writeString(BSSID);
- dest.writeInt(on_time);
- dest.writeInt(tx_time);
- dest.writeIntArray(tx_time_per_level);
- dest.writeInt(rx_time);
- dest.writeInt(on_time_scan);
- }
-
- /** Implement the Parcelable interface {@hide} */
- public static final Creator<WifiLinkLayerStats> CREATOR =
- new Creator<WifiLinkLayerStats>() {
- public WifiLinkLayerStats createFromParcel(Parcel in) {
- WifiLinkLayerStats stats = new WifiLinkLayerStats();
- stats.SSID = in.readString();
- stats.BSSID = in.readString();
- stats.on_time = in.readInt();
- stats.tx_time = in.readInt();
- stats.tx_time_per_level = in.createIntArray();
- stats.rx_time = in.readInt();
- stats.on_time_scan = in.readInt();
- return stats;
- };
- public WifiLinkLayerStats[] newArray(int size) {
- return new WifiLinkLayerStats[size];
- }
-
- };
-}
diff --git a/wifi/java/android/net/wifi/rtt/ResponderConfig.java b/wifi/java/android/net/wifi/rtt/ResponderConfig.java
index b503769..1090bfa 100644
--- a/wifi/java/android/net/wifi/rtt/ResponderConfig.java
+++ b/wifi/java/android/net/wifi/rtt/ResponderConfig.java
@@ -248,7 +248,10 @@
* Point (AP), which can be obtained from {@link android.net.wifi.WifiManager#getScanResults()}.
*/
public static ResponderConfig fromScanResult(ScanResult scanResult) {
- byte[] macAddress = new MacAddress(scanResult.BSSID).toByteArray();
+ byte[] macAddress = null;
+ if (scanResult.BSSID != null) {
+ macAddress = MacAddress.byteAddrFromStringAddr(scanResult.BSSID);
+ }
int responderType = RESPONDER_AP;
boolean supports80211mc = scanResult.is80211mcResponder();
int channelWidth = translcateScanResultChannelWidth(scanResult.channelWidth);