Merge "Unhide VideoView2 APIs"
diff --git a/Android.mk b/Android.mk
index 58e21ff..470714b 100644
--- a/Android.mk
+++ b/Android.mk
@@ -143,7 +143,6 @@
bouncycastle \
okhttp \
ext \
- icu4j \
framework \
voip-common \
@@ -195,6 +194,7 @@
-since $(SRC_API_DIR)/25.txt 25 \
-since $(SRC_API_DIR)/26.txt 26 \
-since $(SRC_API_DIR)/27.txt 27 \
+ -since ./frameworks/base/api/current.txt P \
-werror -lerror -hide 111 -hide 113 -hide 125 -hide 126 -hide 127 -hide 128 \
-overview $(LOCAL_PATH)/core/java/overview.html \
diff --git a/api/current.txt b/api/current.txt
index c13eee6..d56d1e2 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -1378,6 +1378,7 @@
field public static final int textEditSidePasteWindowLayout = 16843614; // 0x101035e
field public static final int textEditSuggestionItemLayout = 16843636; // 0x1010374
field public static final int textFilterEnabled = 16843007; // 0x10100ff
+ field public static final int textFontWeight = 16844166; // 0x1010586
field public static final int textIsSelectable = 16843542; // 0x1010316
field public static final int textOff = 16843045; // 0x1010125
field public static final int textOn = 16843044; // 0x1010124
@@ -6702,11 +6703,6 @@
field public static final int RESET_PASSWORD_DO_NOT_ASK_CREDENTIALS_ON_BOOT = 2; // 0x2
field public static final int RESET_PASSWORD_REQUIRE_ENTRY = 1; // 0x1
field public static final int SKIP_SETUP_WIZARD = 1; // 0x1
- field public static final int USER_OPERATION_ERROR_CURRENT_USER = 4; // 0x4
- field public static final int USER_OPERATION_ERROR_MANAGED_PROFILE = 2; // 0x2
- field public static final int USER_OPERATION_ERROR_MAX_RUNNING_USERS = 3; // 0x3
- field public static final int USER_OPERATION_ERROR_UNKNOWN = 1; // 0x1
- field public static final int USER_OPERATION_SUCCESS = 0; // 0x0
field public static final int WIPE_EUICC = 4; // 0x4
field public static final int WIPE_EXTERNAL_STORAGE = 1; // 0x1
field public static final int WIPE_RESET_PROTECTION_DATA = 2; // 0x2
@@ -8972,6 +8968,7 @@
public class ClipboardManager extends android.text.ClipboardManager {
method public void addPrimaryClipChangedListener(android.content.ClipboardManager.OnPrimaryClipChangedListener);
+ method public void clearPrimaryClip();
method public android.content.ClipData getPrimaryClip();
method public android.content.ClipDescription getPrimaryClipDescription();
method public deprecated java.lang.CharSequence getText();
@@ -25519,6 +25516,7 @@
field public static final java.util.UUID EFFECT_TYPE_AEC;
field public static final java.util.UUID EFFECT_TYPE_AGC;
field public static final java.util.UUID EFFECT_TYPE_BASS_BOOST;
+ field public static final java.util.UUID EFFECT_TYPE_DYNAMICS_PROCESSING;
field public static final java.util.UUID EFFECT_TYPE_ENV_REVERB;
field public static final java.util.UUID EFFECT_TYPE_EQUALIZER;
field public static final java.util.UUID EFFECT_TYPE_LOUDNESS_ENHANCER;
@@ -25582,6 +25580,201 @@
field public short strength;
}
+ public final class DynamicsProcessing extends android.media.audiofx.AudioEffect {
+ ctor public DynamicsProcessing(int);
+ ctor public DynamicsProcessing(int, int, android.media.audiofx.DynamicsProcessing.Config);
+ method public android.media.audiofx.DynamicsProcessing.Channel getChannelByChannelIndex(int);
+ method public int getChannelCount();
+ method public android.media.audiofx.DynamicsProcessing.Config getConfig();
+ method public float getInputGainByChannelIndex(int);
+ method public android.media.audiofx.DynamicsProcessing.Limiter getLimiterByChannelIndex(int);
+ method public android.media.audiofx.DynamicsProcessing.MbcBand getMbcBandByChannelIndex(int, int);
+ method public android.media.audiofx.DynamicsProcessing.Mbc getMbcByChannelIndex(int);
+ method public android.media.audiofx.DynamicsProcessing.EqBand getPostEqBandByChannelIndex(int, int);
+ method public android.media.audiofx.DynamicsProcessing.Eq getPostEqByChannelIndex(int);
+ method public android.media.audiofx.DynamicsProcessing.EqBand getPreEqBandByChannelIndex(int, int);
+ method public android.media.audiofx.DynamicsProcessing.Eq getPreEqByChannelIndex(int);
+ method public void setAllChannelsTo(android.media.audiofx.DynamicsProcessing.Channel);
+ method public void setChannelTo(int, android.media.audiofx.DynamicsProcessing.Channel);
+ method public void setInputGainAllChannelsTo(float);
+ method public void setInputGainbyChannel(int, float);
+ method public void setLimiterAllChannelsTo(android.media.audiofx.DynamicsProcessing.Limiter);
+ method public void setLimiterByChannelIndex(int, android.media.audiofx.DynamicsProcessing.Limiter);
+ method public void setMbcAllChannelsTo(android.media.audiofx.DynamicsProcessing.Mbc);
+ method public void setMbcBandAllChannelsTo(int, android.media.audiofx.DynamicsProcessing.MbcBand);
+ method public void setMbcBandByChannelIndex(int, int, android.media.audiofx.DynamicsProcessing.MbcBand);
+ method public void setMbcByChannelIndex(int, android.media.audiofx.DynamicsProcessing.Mbc);
+ method public void setPostEqAllChannelsTo(android.media.audiofx.DynamicsProcessing.Eq);
+ method public void setPostEqBandAllChannelsTo(int, android.media.audiofx.DynamicsProcessing.EqBand);
+ method public void setPostEqBandByChannelIndex(int, int, android.media.audiofx.DynamicsProcessing.EqBand);
+ method public void setPostEqByChannelIndex(int, android.media.audiofx.DynamicsProcessing.Eq);
+ method public void setPreEqAllChannelsTo(android.media.audiofx.DynamicsProcessing.Eq);
+ method public void setPreEqBandAllChannelsTo(int, android.media.audiofx.DynamicsProcessing.EqBand);
+ method public void setPreEqBandByChannelIndex(int, int, android.media.audiofx.DynamicsProcessing.EqBand);
+ method public void setPreEqByChannelIndex(int, android.media.audiofx.DynamicsProcessing.Eq);
+ field public static final int VARIANT_FAVOR_FREQUENCY_RESOLUTION = 0; // 0x0
+ field public static final int VARIANT_FAVOR_TIME_RESOLUTION = 1; // 0x1
+ }
+
+ public static class DynamicsProcessing.BandBase {
+ ctor public DynamicsProcessing.BandBase(boolean, float);
+ method public float getCutoffFrequency();
+ method public boolean isEnabled();
+ method public void setCutoffFrequency(float);
+ method public void setEnabled(boolean);
+ }
+
+ public static class DynamicsProcessing.BandStage extends android.media.audiofx.DynamicsProcessing.Stage {
+ ctor public DynamicsProcessing.BandStage(boolean, boolean, int);
+ method public int getBandCount();
+ }
+
+ public static final class DynamicsProcessing.Channel {
+ ctor public DynamicsProcessing.Channel(float, boolean, int, boolean, int, boolean, int, boolean);
+ ctor public DynamicsProcessing.Channel(android.media.audiofx.DynamicsProcessing.Channel);
+ method public float getInputGain();
+ method public android.media.audiofx.DynamicsProcessing.Limiter getLimiter();
+ method public android.media.audiofx.DynamicsProcessing.Mbc getMbc();
+ method public android.media.audiofx.DynamicsProcessing.MbcBand getMbcBand(int);
+ method public android.media.audiofx.DynamicsProcessing.Eq getPostEq();
+ method public android.media.audiofx.DynamicsProcessing.EqBand getPostEqBand(int);
+ method public android.media.audiofx.DynamicsProcessing.Eq getPreEq();
+ method public android.media.audiofx.DynamicsProcessing.EqBand getPreEqBand(int);
+ method public void setInputGain(float);
+ method public void setLimiter(android.media.audiofx.DynamicsProcessing.Limiter);
+ method public void setMbc(android.media.audiofx.DynamicsProcessing.Mbc);
+ method public void setMbcBand(int, android.media.audiofx.DynamicsProcessing.MbcBand);
+ method public void setPostEq(android.media.audiofx.DynamicsProcessing.Eq);
+ method public void setPostEqBand(int, android.media.audiofx.DynamicsProcessing.EqBand);
+ method public void setPreEq(android.media.audiofx.DynamicsProcessing.Eq);
+ method public void setPreEqBand(int, android.media.audiofx.DynamicsProcessing.EqBand);
+ }
+
+ public static final class DynamicsProcessing.Config {
+ method public android.media.audiofx.DynamicsProcessing.Channel getChannelByChannelIndex(int);
+ method public float getInputGainByChannelIndex(int);
+ method public android.media.audiofx.DynamicsProcessing.Limiter getLimiterByChannelIndex(int);
+ method public android.media.audiofx.DynamicsProcessing.MbcBand getMbcBandByChannelIndex(int, int);
+ method public int getMbcBandCount();
+ method public android.media.audiofx.DynamicsProcessing.Mbc getMbcByChannelIndex(int);
+ method public android.media.audiofx.DynamicsProcessing.EqBand getPostEqBandByChannelIndex(int, int);
+ method public int getPostEqBandCount();
+ method public android.media.audiofx.DynamicsProcessing.Eq getPostEqByChannelIndex(int);
+ method public android.media.audiofx.DynamicsProcessing.EqBand getPreEqBandByChannelIndex(int, int);
+ method public int getPreEqBandCount();
+ method public android.media.audiofx.DynamicsProcessing.Eq getPreEqByChannelIndex(int);
+ method public float getPreferredFrameDuration();
+ method public int getVariant();
+ method public boolean isLimiterInUse();
+ method public boolean isMbcInUse();
+ method public boolean isPostEqInUse();
+ method public boolean isPreEqInUse();
+ method public void setAllChannelsTo(android.media.audiofx.DynamicsProcessing.Channel);
+ method public void setChannelTo(int, android.media.audiofx.DynamicsProcessing.Channel);
+ method public void setInputGainAllChannelsTo(float);
+ method public void setInputGainByChannelIndex(int, float);
+ method public void setLimiterAllChannelsTo(android.media.audiofx.DynamicsProcessing.Limiter);
+ method public void setLimiterByChannelIndex(int, android.media.audiofx.DynamicsProcessing.Limiter);
+ method public void setMbcAllChannelsTo(android.media.audiofx.DynamicsProcessing.Mbc);
+ method public void setMbcBandAllChannelsTo(int, android.media.audiofx.DynamicsProcessing.MbcBand);
+ method public void setMbcBandByChannelIndex(int, int, android.media.audiofx.DynamicsProcessing.MbcBand);
+ method public void setMbcByChannelIndex(int, android.media.audiofx.DynamicsProcessing.Mbc);
+ method public void setPostEqAllChannelsTo(android.media.audiofx.DynamicsProcessing.Eq);
+ method public void setPostEqBandAllChannelsTo(int, android.media.audiofx.DynamicsProcessing.EqBand);
+ method public void setPostEqBandByChannelIndex(int, int, android.media.audiofx.DynamicsProcessing.EqBand);
+ method public void setPostEqByChannelIndex(int, android.media.audiofx.DynamicsProcessing.Eq);
+ method public void setPreEqAllChannelsTo(android.media.audiofx.DynamicsProcessing.Eq);
+ method public void setPreEqBandAllChannelsTo(int, android.media.audiofx.DynamicsProcessing.EqBand);
+ method public void setPreEqBandByChannelIndex(int, int, android.media.audiofx.DynamicsProcessing.EqBand);
+ method public void setPreEqByChannelIndex(int, android.media.audiofx.DynamicsProcessing.Eq);
+ }
+
+ public static final class DynamicsProcessing.Config.Builder {
+ ctor public DynamicsProcessing.Config.Builder(int, int, boolean, int, boolean, int, boolean, int, boolean);
+ method public android.media.audiofx.DynamicsProcessing.Config build();
+ method public android.media.audiofx.DynamicsProcessing.Config.Builder setAllChannelsTo(android.media.audiofx.DynamicsProcessing.Channel);
+ method public android.media.audiofx.DynamicsProcessing.Config.Builder setChannelTo(int, android.media.audiofx.DynamicsProcessing.Channel);
+ method public android.media.audiofx.DynamicsProcessing.Config.Builder setInputGainAllChannelsTo(float);
+ method public android.media.audiofx.DynamicsProcessing.Config.Builder setInputGainByChannelIndex(int, float);
+ method public android.media.audiofx.DynamicsProcessing.Config.Builder setLimiterAllChannelsTo(android.media.audiofx.DynamicsProcessing.Limiter);
+ method public android.media.audiofx.DynamicsProcessing.Config.Builder setLimiterByChannelIndex(int, android.media.audiofx.DynamicsProcessing.Limiter);
+ method public android.media.audiofx.DynamicsProcessing.Config.Builder setMbcAllChannelsTo(android.media.audiofx.DynamicsProcessing.Mbc);
+ method public android.media.audiofx.DynamicsProcessing.Config.Builder setMbcByChannelIndex(int, android.media.audiofx.DynamicsProcessing.Mbc);
+ method public android.media.audiofx.DynamicsProcessing.Config.Builder setPostEqAllChannelsTo(android.media.audiofx.DynamicsProcessing.Eq);
+ method public android.media.audiofx.DynamicsProcessing.Config.Builder setPostEqByChannelIndex(int, android.media.audiofx.DynamicsProcessing.Eq);
+ method public android.media.audiofx.DynamicsProcessing.Config.Builder setPreEqAllChannelsTo(android.media.audiofx.DynamicsProcessing.Eq);
+ method public android.media.audiofx.DynamicsProcessing.Config.Builder setPreEqByChannelIndex(int, android.media.audiofx.DynamicsProcessing.Eq);
+ method public android.media.audiofx.DynamicsProcessing.Config.Builder setPreferredFrameDuration(float);
+ }
+
+ public static final class DynamicsProcessing.Eq extends android.media.audiofx.DynamicsProcessing.BandStage {
+ ctor public DynamicsProcessing.Eq(boolean, boolean, int);
+ ctor public DynamicsProcessing.Eq(android.media.audiofx.DynamicsProcessing.Eq);
+ method public android.media.audiofx.DynamicsProcessing.EqBand getBand(int);
+ method public void setBand(int, android.media.audiofx.DynamicsProcessing.EqBand);
+ }
+
+ public static final class DynamicsProcessing.EqBand extends android.media.audiofx.DynamicsProcessing.BandBase {
+ ctor public DynamicsProcessing.EqBand(boolean, float, float);
+ ctor public DynamicsProcessing.EqBand(android.media.audiofx.DynamicsProcessing.EqBand);
+ method public float getGain();
+ method public void setGain(float);
+ }
+
+ public static final class DynamicsProcessing.Limiter extends android.media.audiofx.DynamicsProcessing.Stage {
+ ctor public DynamicsProcessing.Limiter(boolean, boolean, int, float, float, float, float, float);
+ ctor public DynamicsProcessing.Limiter(android.media.audiofx.DynamicsProcessing.Limiter);
+ method public float getAttackTime();
+ method public int getLinkGroup();
+ method public float getPostGain();
+ method public float getRatio();
+ method public float getReleaseTime();
+ method public float getThreshold();
+ method public void setAttackTime(float);
+ method public void setLinkGroup(int);
+ method public void setPostGain(float);
+ method public void setRatio(float);
+ method public void setReleaseTime(float);
+ method public void setThreshold(float);
+ }
+
+ public static final class DynamicsProcessing.Mbc extends android.media.audiofx.DynamicsProcessing.BandStage {
+ ctor public DynamicsProcessing.Mbc(boolean, boolean, int);
+ ctor public DynamicsProcessing.Mbc(android.media.audiofx.DynamicsProcessing.Mbc);
+ method public android.media.audiofx.DynamicsProcessing.MbcBand getBand(int);
+ method public void setBand(int, android.media.audiofx.DynamicsProcessing.MbcBand);
+ }
+
+ public static final class DynamicsProcessing.MbcBand extends android.media.audiofx.DynamicsProcessing.BandBase {
+ ctor public DynamicsProcessing.MbcBand(boolean, float, float, float, float, float, float, float, float, float, float);
+ ctor public DynamicsProcessing.MbcBand(android.media.audiofx.DynamicsProcessing.MbcBand);
+ method public float getAttackTime();
+ method public float getExpanderRatio();
+ method public float getKneeWidth();
+ method public float getNoiseGateThreshold();
+ method public float getPostGain();
+ method public float getPreGain();
+ method public float getRatio();
+ method public float getReleaseTime();
+ method public float getThreshold();
+ method public void setAttackTime(float);
+ method public void setExpanderRatio(float);
+ method public void setKneeWidth(float);
+ method public void setNoiseGateThreshold(float);
+ method public void setPostGain(float);
+ method public void setPreGain(float);
+ method public void setRatio(float);
+ method public void setReleaseTime(float);
+ method public void setThreshold(float);
+ }
+
+ public static class DynamicsProcessing.Stage {
+ ctor public DynamicsProcessing.Stage(boolean, boolean);
+ method public boolean isEnabled();
+ method public boolean isInUse();
+ method public void setEnabled(boolean);
+ }
+
public class EnvironmentalReverb extends android.media.audiofx.AudioEffect {
ctor public EnvironmentalReverb(int, int) throws java.lang.IllegalArgumentException, java.lang.RuntimeException, java.lang.UnsupportedOperationException;
method public short getDecayHFRatio() throws java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.UnsupportedOperationException;
@@ -33629,6 +33822,17 @@
field public static final java.lang.String KEY_RESTRICTIONS_PENDING = "restrictions_pending";
field public static final int USER_CREATION_FAILED_NOT_PERMITTED = 1; // 0x1
field public static final int USER_CREATION_FAILED_NO_MORE_USERS = 2; // 0x2
+ field public static final int USER_OPERATION_ERROR_CURRENT_USER = 4; // 0x4
+ field public static final int USER_OPERATION_ERROR_LOW_STORAGE = 5; // 0x5
+ field public static final int USER_OPERATION_ERROR_MANAGED_PROFILE = 2; // 0x2
+ field public static final int USER_OPERATION_ERROR_MAX_RUNNING_USERS = 3; // 0x3
+ field public static final int USER_OPERATION_ERROR_MAX_USERS = 6; // 0x6
+ field public static final int USER_OPERATION_ERROR_UNKNOWN = 1; // 0x1
+ field public static final int USER_OPERATION_SUCCESS = 0; // 0x0
+ }
+
+ public static class UserManager.UserOperationException extends java.lang.RuntimeException {
+ method public int getUserOperationResult();
}
public abstract class VibrationEffect implements android.os.Parcelable {
@@ -35103,7 +35307,7 @@
field public static final java.lang.String FEATURES = "features";
field public static final int FEATURES_HD_CALL = 4; // 0x4
field public static final int FEATURES_PULLED_EXTERNALLY = 2; // 0x2
- field public static final int FEATURES_RTT = 16; // 0x10
+ field public static final int FEATURES_RTT = 32; // 0x20
field public static final int FEATURES_VIDEO = 1; // 0x1
field public static final int FEATURES_WIFI = 8; // 0x8
field public static final java.lang.String GEOCODED_LOCATION = "geocoded_location";
diff --git a/api/system-current.txt b/api/system-current.txt
index 3aca59a..375f840 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -617,6 +617,7 @@
method public java.lang.String transportDirName();
field public static final int AGENT_ERROR = -1003; // 0xfffffc15
field public static final int AGENT_UNKNOWN = -1004; // 0xfffffc14
+ field public static final java.lang.String EXTRA_TRANSPORT_REGISTRATION = "android.app.backup.extra.TRANSPORT_REGISTRATION";
field public static final int FLAG_INCREMENTAL = 2; // 0x2
field public static final int FLAG_NON_INCREMENTAL = 4; // 0x4
field public static final int FLAG_USER_INITIATED = 1; // 0x1
@@ -4180,6 +4181,7 @@
field public static final java.lang.String CARRIER_APP_NAMES = "carrier_app_names";
field public static final java.lang.String CARRIER_APP_WHITELIST = "carrier_app_whitelist";
field public static final java.lang.String DEFAULT_SM_DP_PLUS = "default_sm_dp_plus";
+ field public static final java.lang.String EUICC_PROVISIONED = "euicc_provisioned";
field public static final java.lang.String INSTALL_CARRIER_APP_NOTIFICATION_PERSISTENT = "install_carrier_app_notification_persistent";
field public static final java.lang.String INSTALL_CARRIER_APP_NOTIFICATION_SLEEP_MILLIS = "install_carrier_app_notification_sleep_millis";
field public static final java.lang.String OTA_DISABLE_AUTOMATIC_UPDATE = "ota_disable_automatic_update";
@@ -5983,7 +5985,6 @@
method public void setUiTtyMode(int, android.os.Message);
method public int shouldProcessCall(java.lang.String[]);
field public static final int PROCESS_CALL_CSFB = 1; // 0x1
- field public static final int PROCESS_CALL_EMERGENCY_CSFB = 2; // 0x2
field public static final int PROCESS_CALL_IMS = 0; // 0x0
}
diff --git a/cmds/incidentd/Android.mk b/cmds/incidentd/Android.mk
index 6bdd9be..3a47fe1 100644
--- a/cmds/incidentd/Android.mk
+++ b/cmds/incidentd/Android.mk
@@ -15,7 +15,8 @@
LOCAL_PATH:= $(call my-dir)
# proto files used in incidentd to generate cppstream proto headers.
-PROTO_FILES:= frameworks/base/core/proto/android/util/log.proto
+PROTO_FILES:= frameworks/base/core/proto/android/util/log.proto \
+ frameworks/base/core/proto/android/os/data.proto
# ========= #
# incidentd #
@@ -131,7 +132,7 @@
LOCAL_MODULE_CLASS := NATIVE_TESTS
gen_src_dir := $(local-generated-sources-dir)
# generate cppstream proto for testing
-GEN_PROTO := $(gen_src_dir)/log.proto.timestamp
+GEN_PROTO := $(gen_src_dir)/test.proto.timestamp
$(GEN_PROTO): $(HOST_OUT_EXECUTABLES)/aprotoc $(HOST_OUT_EXECUTABLES)/protoc-gen-cppstream $(PROTO_FILES)
$(GEN_PROTO): PRIVATE_GEN_SRC_DIR := $(gen_src_dir)
$(GEN_PROTO): PRIVATE_CUSTOM_TOOL = \
diff --git a/cmds/incidentd/incidentd.rc b/cmds/incidentd/incidentd.rc
index 1bd1468..6dd8114 100644
--- a/cmds/incidentd/incidentd.rc
+++ b/cmds/incidentd/incidentd.rc
@@ -15,7 +15,7 @@
service incidentd /system/bin/incidentd
class main
user incidentd
- group incidentd log
+ group incidentd log readproc
on post-fs-data
# Create directory for incidentd
diff --git a/cmds/incidentd/src/FdBuffer.cpp b/cmds/incidentd/src/FdBuffer.cpp
index db60794..64da677 100644
--- a/cmds/incidentd/src/FdBuffer.cpp
+++ b/cmds/incidentd/src/FdBuffer.cpp
@@ -76,6 +76,7 @@
return -errno;
}
} else if (amt == 0) {
+ VLOG("Reached EOF of fd=%d", fd);
break;
}
mBuffer.wp()->move(amt);
@@ -156,10 +157,10 @@
if (!(errno == EAGAIN || errno == EWOULDBLOCK)) {
VLOG("Fail to read fd %d: %s", fd, strerror(errno));
return -errno;
- } // otherwise just continue
- } else if (amt == 0) { // reach EOF so don't have to poll pfds[0].
- ::close(pfds[0].fd);
- pfds[0].fd = -1;
+ } // otherwise just continue
+ } else if (amt == 0) {
+ VLOG("Reached EOF of input file %d", fd);
+ pfds[0].fd = -1; // reach EOF so don't have to poll pfds[0].
} else {
rpos += amt;
cirSize += amt;
@@ -187,6 +188,7 @@
// if buffer is empty and fd is closed, close write fd.
if (cirSize == 0 && pfds[0].fd == -1 && pfds[1].fd != -1) {
+ VLOG("Close write pipe %d", toFd);
::close(pfds[1].fd);
pfds[1].fd = -1;
}
@@ -207,6 +209,7 @@
return -errno;
} // otherwise just continue
} else if (amt == 0) {
+ VLOG("Reached EOF of fromFd %d", fromFd);
break;
} else {
mBuffer.wp()->move(amt);
diff --git a/cmds/incidentd/src/FdBuffer.h b/cmds/incidentd/src/FdBuffer.h
index 5bfa093..66a3de1 100644
--- a/cmds/incidentd/src/FdBuffer.h
+++ b/cmds/incidentd/src/FdBuffer.h
@@ -26,7 +26,7 @@
using namespace std;
/**
- * Reads a file into a buffer, and then writes that data to an FdSet.
+ * Reads data from fd into a buffer, fd must be closed explicitly.
*/
class FdBuffer {
public:
@@ -83,6 +83,11 @@
*/
EncodedBuffer::iterator data() const;
+ /**
+ * Return the internal buffer, don't call unless you are familiar with EncodedBuffer.
+ */
+ EncodedBuffer* getInternalBuffer() { return &mBuffer; }
+
private:
EncodedBuffer mBuffer;
int64_t mStartTime;
diff --git a/cmds/incidentd/src/Section.cpp b/cmds/incidentd/src/Section.cpp
index 64eae3a..334d77c 100644
--- a/cmds/incidentd/src/Section.cpp
+++ b/cmds/incidentd/src/Section.cpp
@@ -18,12 +18,8 @@
#include "Section.h"
-#include <errno.h>
-#include <sys/prctl.h>
-#include <unistd.h>
#include <wait.h>
-#include <memory>
#include <mutex>
#include <android-base/file.h>
@@ -37,6 +33,7 @@
#include "FdBuffer.h"
#include "Privacy.h"
#include "PrivacyBuffer.h"
+#include "frameworks/base/core/proto/android/os/data.proto.h"
#include "frameworks/base/core/proto/android/util/log.proto.h"
#include "incidentd_util.h"
@@ -52,31 +49,11 @@
const int WAIT_MAX = 5;
const struct timespec WAIT_INTERVAL_NS = {0, 200 * 1000 * 1000};
const char INCIDENT_HELPER[] = "/system/bin/incident_helper";
+const char GZIP[] = "/system/bin/gzip";
-static pid_t fork_execute_incident_helper(const int id, const char* name, Fpipe& p2cPipe,
- Fpipe& c2pPipe) {
+static pid_t fork_execute_incident_helper(const int id, Fpipe* p2cPipe, Fpipe* c2pPipe) {
const char* ihArgs[]{INCIDENT_HELPER, "-s", String8::format("%d", id).string(), NULL};
- // fork used in multithreaded environment, avoid adding unnecessary code in child process
- pid_t pid = fork();
- if (pid == 0) {
- if (TEMP_FAILURE_RETRY(dup2(p2cPipe.readFd(), STDIN_FILENO)) != 0 || !p2cPipe.close() ||
- TEMP_FAILURE_RETRY(dup2(c2pPipe.writeFd(), STDOUT_FILENO)) != 1 || !c2pPipe.close()) {
- ALOGW("%s can't setup stdin and stdout for incident helper", name);
- _exit(EXIT_FAILURE);
- }
-
- /* make sure the child dies when incidentd dies */
- prctl(PR_SET_PDEATHSIG, SIGKILL);
-
- execv(INCIDENT_HELPER, const_cast<char**>(ihArgs));
-
- ALOGW("%s failed in incident helper process: %s", name, strerror(errno));
- _exit(EXIT_FAILURE); // always exits with failure if any
- }
- // close the fds used in incident helper
- close(p2cPipe.readFd());
- close(c2pPipe.writeFd());
- return pid;
+ return fork_execute_cmd(INCIDENT_HELPER, const_cast<char**>(ihArgs), p2cPipe, c2pPipe);
}
// ================================================================================
@@ -254,10 +231,12 @@
return NO_ERROR;
}
// ================================================================================
+static inline bool isSysfs(const char* filename) { return strncmp(filename, "/sys/", 5) == 0; }
+
FileSection::FileSection(int id, const char* filename, const int64_t timeoutMs)
: Section(id, timeoutMs), mFilename(filename) {
name = filename;
- mIsSysfs = strncmp(filename, "/sys/", 5) == 0;
+ mIsSysfs = isSysfs(filename);
}
FileSection::~FileSection() {}
@@ -280,7 +259,7 @@
return -errno;
}
- pid_t pid = fork_execute_incident_helper(this->id, this->name.string(), p2cPipe, c2pPipe);
+ pid_t pid = fork_execute_incident_helper(this->id, &p2cPipe, &c2pPipe);
if (pid == -1) {
ALOGW("FileSection '%s' failed to fork", this->name.string());
return -errno;
@@ -289,6 +268,8 @@
// parent process
status_t readStatus = buffer.readProcessedDataInStream(fd, p2cPipe.writeFd(), c2pPipe.readFd(),
this->timeoutMs, mIsSysfs);
+ close(fd); // close the fd anyway.
+
if (readStatus != NO_ERROR || buffer.timedOut()) {
ALOGW("FileSection '%s' failed to read data from incident helper: %s, timedout: %s",
this->name.string(), strerror(-readStatus), buffer.timedOut() ? "true" : "false");
@@ -313,7 +294,99 @@
return NO_ERROR;
}
+// ================================================================================
+GZipSection::GZipSection(int id, const char* filename, ...) : Section(id) {
+ name = "gzip ";
+ name += filename;
+ va_list args;
+ va_start(args, filename);
+ mFilenames = varargs(filename, args);
+ va_end(args);
+}
+GZipSection::~GZipSection() {}
+
+status_t GZipSection::Execute(ReportRequestSet* requests) const {
+ // Reads the files in order, use the first available one.
+ int index = 0;
+ int fd = -1;
+ while (mFilenames[index] != NULL) {
+ fd = open(mFilenames[index], O_RDONLY | O_CLOEXEC);
+ if (fd != -1) {
+ break;
+ }
+ ALOGW("GZipSection failed to open file %s", mFilenames[index]);
+ index++; // look at the next file.
+ }
+ VLOG("GZipSection is using file %s, fd=%d", mFilenames[index], fd);
+ if (fd == -1) return -1;
+
+ FdBuffer buffer;
+ Fpipe p2cPipe;
+ Fpipe c2pPipe;
+ // initiate pipes to pass data to/from gzip
+ if (!p2cPipe.init() || !c2pPipe.init()) {
+ ALOGW("GZipSection '%s' failed to setup pipes", this->name.string());
+ return -errno;
+ }
+
+ const char* gzipArgs[]{GZIP, NULL};
+ pid_t pid = fork_execute_cmd(GZIP, const_cast<char**>(gzipArgs), &p2cPipe, &c2pPipe);
+ if (pid == -1) {
+ ALOGW("GZipSection '%s' failed to fork", this->name.string());
+ return -errno;
+ }
+ // parent process
+
+ // construct Fdbuffer to output GZippedfileProto, the reason to do this instead of using
+ // ProtoOutputStream is to avoid allocation of another buffer inside ProtoOutputStream.
+ EncodedBuffer* internalBuffer = buffer.getInternalBuffer();
+ internalBuffer->writeHeader((uint32_t)GZippedFileProto::FILENAME, WIRE_TYPE_LENGTH_DELIMITED);
+ String8 usedFile(mFilenames[index]);
+ internalBuffer->writeRawVarint32(usedFile.size());
+ for (size_t i = 0; i < usedFile.size(); i++) {
+ internalBuffer->writeRawByte(mFilenames[index][i]);
+ }
+ internalBuffer->writeHeader((uint32_t)GZippedFileProto::GZIPPED_DATA,
+ WIRE_TYPE_LENGTH_DELIMITED);
+ size_t editPos = internalBuffer->wp()->pos();
+ internalBuffer->wp()->move(8); // reserve 8 bytes for the varint of the data size.
+ size_t dataBeginAt = internalBuffer->wp()->pos();
+ VLOG("GZipSection '%s' editPos=%zd, dataBeginAt=%zd", this->name.string(), editPos,
+ dataBeginAt);
+
+ status_t readStatus = buffer.readProcessedDataInStream(
+ fd, p2cPipe.writeFd(), c2pPipe.readFd(), this->timeoutMs, isSysfs(mFilenames[index]));
+ close(fd); // close the fd anyway.
+
+ if (readStatus != NO_ERROR || buffer.timedOut()) {
+ ALOGW("GZipSection '%s' failed to read data from gzip: %s, timedout: %s",
+ this->name.string(), strerror(-readStatus), buffer.timedOut() ? "true" : "false");
+ kill_child(pid);
+ return readStatus;
+ }
+
+ status_t gzipStatus = wait_child(pid);
+ if (gzipStatus != NO_ERROR) {
+ ALOGW("GZipSection '%s' abnormal child process: %s", this->name.string(),
+ strerror(-gzipStatus));
+ return gzipStatus;
+ }
+ // Revisit the actual size from gzip result and edit the internal buffer accordingly.
+ size_t dataSize = buffer.size() - dataBeginAt;
+ internalBuffer->wp()->rewind()->move(editPos);
+ internalBuffer->writeRawVarint32(dataSize);
+ internalBuffer->copy(dataBeginAt, dataSize);
+ VLOG("GZipSection '%s' wrote %zd bytes in %d ms, dataSize=%zd", this->name.string(),
+ buffer.size(), (int)buffer.durationMs(), dataSize);
+ status_t err = write_report_requests(this->id, buffer, requests);
+ if (err != NO_ERROR) {
+ ALOGW("GZipSection '%s' failed writing: %s", this->name.string(), strerror(-err));
+ return err;
+ }
+
+ return NO_ERROR;
+}
// ================================================================================
struct WorkerThreadData : public virtual RefBase {
const WorkerThreadSection* section;
@@ -457,42 +530,20 @@
}
// ================================================================================
-void CommandSection::init(const char* command, va_list args) {
- va_list copied_args;
- int numOfArgs = 0;
-
- va_copy(copied_args, args);
- while (va_arg(copied_args, const char*) != NULL) {
- numOfArgs++;
- }
- va_end(copied_args);
-
- // allocate extra 1 for command and 1 for NULL terminator
- mCommand = (const char**)malloc(sizeof(const char*) * (numOfArgs + 2));
-
- mCommand[0] = command;
- name = command;
- for (int i = 0; i < numOfArgs; i++) {
- const char* arg = va_arg(args, const char*);
- mCommand[i + 1] = arg;
- name += " ";
- name += arg;
- }
- mCommand[numOfArgs + 1] = NULL;
-}
-
CommandSection::CommandSection(int id, const int64_t timeoutMs, const char* command, ...)
: Section(id, timeoutMs) {
+ name = command;
va_list args;
va_start(args, command);
- init(command, args);
+ mCommand = varargs(command, args);
va_end(args);
}
CommandSection::CommandSection(int id, const char* command, ...) : Section(id) {
+ name = command;
va_list args;
va_start(args, command);
- init(command, args);
+ mCommand = varargs(command, args);
va_end(args);
}
@@ -527,7 +578,7 @@
strerror(errno));
_exit(err); // exit with command error code
}
- pid_t ihPid = fork_execute_incident_helper(this->id, this->name.string(), cmdPipe, ihPipe);
+ pid_t ihPid = fork_execute_incident_helper(this->id, &cmdPipe, &ihPipe);
if (ihPid == -1) {
ALOGW("CommandSection '%s' failed to fork", this->name.string());
return -errno;
@@ -544,8 +595,7 @@
}
// TODO: wait for command here has one trade-off: the failed status of command won't be detected
- // until
- // buffer timeout, but it has advatage on starting the data stream earlier.
+ // until buffer timeout, but it has advatage on starting the data stream earlier.
status_t cmdStatus = wait_child(cmdPid);
status_t ihStatus = wait_child(ihPid);
if (cmdStatus != NO_ERROR || ihStatus != NO_ERROR) {
diff --git a/cmds/incidentd/src/Section.h b/cmds/incidentd/src/Section.h
index d644681..8294be1 100644
--- a/cmds/incidentd/src/Section.h
+++ b/cmds/incidentd/src/Section.h
@@ -84,6 +84,21 @@
};
/**
+ * Section that reads in a file and gzips the content.
+ */
+class GZipSection : public Section {
+public:
+ GZipSection(int id, const char* filename, ...);
+ virtual ~GZipSection();
+
+ virtual status_t Execute(ReportRequestSet* requests) const;
+
+private:
+ // It looks up the content from multiple files and stops when the first one is available.
+ const char** mFilenames;
+};
+
+/**
* Base class for sections that call a command that might need a timeout.
*/
class WorkerThreadSection : public Section {
@@ -111,8 +126,6 @@
private:
const char** mCommand;
-
- void init(const char* command, va_list args);
};
/**
diff --git a/cmds/incidentd/src/incidentd_util.cpp b/cmds/incidentd/src/incidentd_util.cpp
index 2415860..fc7cec9 100644
--- a/cmds/incidentd/src/incidentd_util.cpp
+++ b/cmds/incidentd/src/incidentd_util.cpp
@@ -13,8 +13,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+#define DEBUG false
+#include "Log.h"
+
#include "incidentd_util.h"
+#include <sys/prctl.h>
+
#include "section_list.h"
const Privacy* get_privacy_of_section(int id) {
@@ -50,4 +55,49 @@
int Fpipe::readFd() const { return mRead.get(); }
-int Fpipe::writeFd() const { return mWrite.get(); }
\ No newline at end of file
+int Fpipe::writeFd() const { return mWrite.get(); }
+
+pid_t fork_execute_cmd(const char* cmd, char* const argv[], Fpipe* input, Fpipe* output) {
+ // fork used in multithreaded environment, avoid adding unnecessary code in child process
+ pid_t pid = fork();
+ if (pid == 0) {
+ if (TEMP_FAILURE_RETRY(dup2(input->readFd(), STDIN_FILENO)) < 0 || !input->close() ||
+ TEMP_FAILURE_RETRY(dup2(output->writeFd(), STDOUT_FILENO)) < 0 || !output->close()) {
+ ALOGW("Can't setup stdin and stdout for command %s", cmd);
+ _exit(EXIT_FAILURE);
+ }
+
+ /* make sure the child dies when incidentd dies */
+ prctl(PR_SET_PDEATHSIG, SIGKILL);
+
+ execv(cmd, argv);
+
+ ALOGW("%s failed in the child process: %s", cmd, strerror(errno));
+ _exit(EXIT_FAILURE); // always exits with failure if any
+ }
+ // close the fds used in child process.
+ close(input->readFd());
+ close(output->writeFd());
+ return pid;
+}
+// ================================================================================
+const char** varargs(const char* first, va_list rest) {
+ va_list copied_rest;
+ int numOfArgs = 1; // first is already count.
+
+ va_copy(copied_rest, rest);
+ while (va_arg(copied_rest, const char*) != NULL) {
+ numOfArgs++;
+ }
+ va_end(copied_rest);
+
+ // allocate extra 1 for NULL terminator
+ const char** ret = (const char**)malloc(sizeof(const char*) * (numOfArgs + 1));
+ ret[0] = first;
+ for (int i = 0; i < numOfArgs; i++) {
+ const char* arg = va_arg(rest, const char*);
+ ret[i + 1] = arg;
+ }
+ ret[numOfArgs + 1] = NULL;
+ return ret;
+}
diff --git a/cmds/incidentd/src/incidentd_util.h b/cmds/incidentd/src/incidentd_util.h
index 09aa040..db7ec82 100644
--- a/cmds/incidentd/src/incidentd_util.h
+++ b/cmds/incidentd/src/incidentd_util.h
@@ -20,12 +20,20 @@
#include <android-base/unique_fd.h>
+#include <stdarg.h>
+
#include "Privacy.h"
using namespace android::base;
+/**
+ * Looks up Privacy of a section in the auto-gen PRIVACY_POLICY_LIST;
+ */
const Privacy* get_privacy_of_section(int id);
+/**
+ * This class wraps android::base::Pipe.
+ */
class Fpipe {
public:
Fpipe();
@@ -41,4 +49,15 @@
unique_fd mWrite;
};
+/**
+ * Forks and exec a command with two pipes, one connects stdin for input,
+ * one connects stdout for output. It returns the pid of the child.
+ */
+pid_t fork_execute_cmd(const char* cmd, char* const argv[], Fpipe* input, Fpipe* output);
+
+/**
+ * Grabs varargs from stack and stores them in heap with NULL-terminated array.
+ */
+const char** varargs(const char* first, va_list rest);
+
#endif // INCIDENTD_UTIL_H
\ No newline at end of file
diff --git a/cmds/incidentd/testdata/kmsg.txt b/cmds/incidentd/testdata/kmsg.txt
new file mode 100644
index 0000000..a8e3c02
--- /dev/null
+++ b/cmds/incidentd/testdata/kmsg.txt
@@ -0,0 +1,47 @@
+[0] bldr_log_init: bldr_log_base=0x83600000, bldr_log_size=458752
+B - 626409 - [INFO][XBL]: Bypass appsbl verification on DEVELOPMENT device
+B - 729255 - [INFO][XBL]: Bypass appsbl verification on DEVELOPMENT device
+B - 729285 - boot_elf_load_and_verify_image: boot_auth_compute_verify_hash for 9:0
+D - 104829 - APPSBL Image Loaded, Delta - (2498816 Bytes)
+B - 729468 - SBL1, End
+D - 643611 - SBL1, Delta
+S - Flash Throughput, 129000 KB/s (4729638 Bytes, 36613 us)
+S - DDR Frequency, 1017 MHz
+0x400, 0x400
+B - 482296 - Basic DDR tests done
+B - 544638 - clock_init, Start
+D - 244 - clock_init, Delta
+B - 544913 - HTC RPM DATARAM UPDATE info: Done
+B - 545004 - Image Load, Start
+B - 548359 - boot_elf_load_and_verify_image: boot_auth_compute_verify_hash for 5:0
+D - 3386 - QSEE Dev Config Image Loaded, Delta - (46232 Bytes)
+B - 548725 - Image Load, Start
+B - 550860 - boot_elf_load_and_verify_image: boot_auth_compute_verify_hash for 512:0
+D - 2166 - APDP Image Loaded, Delta - (7696 Bytes)
+B - 550891 - Image Load, Start
+B - 601612 - boot_elf_load_and_verify_image: boot_auth_compute_verify_hash for 7:0
+D - 50782 - QSEE Image Loaded, Delta - (1648640 Bytes)
+B - 601704 - Image Load, Start
+D - 244 - SEC Image Loaded, Delta - (4096 Bytes)
+B - 602344 - 0x1310 = 0x24
+B - 602375 - is_above_vbat_weak = 1, pon_reasons (with usb_in checked) = 0x31
+B - 602466 - sbl1_efs_handle_cookies, Start
+D - 91 - sbl1_efs_handle_cookies, Delta
+B - 602558 - Image Load, Start
+B - 613446 - boot_elf_load_and_verify_image: boot_auth_compute_verify_hash for 21:0
+D - 11010 - QHEE Image Loaded, Delta - (258280 Bytes)
+B - 613568 - Image Load, Start
+B - 624274 - boot_elf_load_and_verify_image: boot_auth_compute_verify_hash for 10:0
+D - 10736 - RPM Image Loaded, Delta - (224104 Bytes)
+B - 624335 - Image Load, Start
+D - 0 - STI Image Loaded, Delta - (0 Bytes)
+B - 624548 - Image Load, Start
+m_driver_init, Delta
+B - 471804 - pm_sbl_chg
+ ******************** [ START SECOND] ********************
+^@B - 736605 - [INFO][XBL]: Bypass appsbl verification on DEVELOPMENT device
+B - 839451 - [INFO][XBL]: Bypass appsbl verification on DEVELOPMENT device
+B - 839482 - boot_elf_load_and_verify_image: boot_auth_compute_verify_hash for 9:0
+D - 104828 - APPSBL Image Loaded, Delta - (2498816 Bytes)
+B - 839665 - SBL1, End
+D - 753838 - SBL1, Delta
diff --git a/cmds/incidentd/testdata/kmsg.txt.gz b/cmds/incidentd/testdata/kmsg.txt.gz
new file mode 100644
index 0000000..fba449f
--- /dev/null
+++ b/cmds/incidentd/testdata/kmsg.txt.gz
Binary files differ
diff --git a/cmds/incidentd/tests/Section_test.cpp b/cmds/incidentd/tests/Section_test.cpp
index 026bf74..1528224 100644
--- a/cmds/incidentd/tests/Section_test.cpp
+++ b/cmds/incidentd/tests/Section_test.cpp
@@ -39,10 +39,22 @@
using namespace android::os;
using namespace std;
using ::testing::StrEq;
+using ::testing::Test;
using ::testing::internal::CaptureStdout;
using ::testing::internal::GetCapturedStdout;
// NOTICE: this test requires /system/bin/incident_helper is installed.
+class SectionTest : public Test {
+public:
+ virtual void SetUp() override { ASSERT_NE(tf.fd, -1); }
+
+protected:
+ TemporaryFile tf;
+ ReportRequestSet requests;
+
+ const std::string kTestPath = GetExecutableDirectory();
+ const std::string kTestDataPath = kTestPath + "/testdata/";
+};
class SimpleListener : public IIncidentReportStatusListener {
public:
@@ -58,10 +70,8 @@
virtual IBinder* onAsBinder() override { return nullptr; };
};
-TEST(SectionTest, HeaderSection) {
- TemporaryFile output2;
+TEST_F(SectionTest, HeaderSection) {
HeaderSection hs;
- ReportRequestSet requests;
IncidentReportArgs args1, args2;
args1.addSection(1);
@@ -77,7 +87,7 @@
args2.addHeader(head2);
requests.add(new ReportRequest(args1, new SimpleListener(), -1));
- requests.add(new ReportRequest(args2, new SimpleListener(), output2.fd));
+ requests.add(new ReportRequest(args2, new SimpleListener(), tf.fd));
requests.setMainFd(STDOUT_FILENO);
string content;
@@ -87,28 +97,25 @@
"\x12\x3"
"axe\n\x05\x12\x03pup"));
- EXPECT_TRUE(ReadFileToString(output2.path, &content));
+ EXPECT_TRUE(ReadFileToString(tf.path, &content));
EXPECT_THAT(content, StrEq("\n\x05\x12\x03pup"));
}
-TEST(SectionTest, MetadataSection) {
+TEST_F(SectionTest, MetadataSection) {
MetadataSection ms;
- ReportRequestSet requests;
requests.setMainFd(STDOUT_FILENO);
requests.sectionStats(1)->set_success(true);
CaptureStdout();
ASSERT_EQ(NO_ERROR, ms.Execute(&requests));
- EXPECT_THAT(GetCapturedStdout(), StrEq("\x12\b\x18\x1\"\x4\b\x1\x10\x1"));
+ EXPECT_THAT(GetCapturedStdout(), StrEq("\x12\b(\x1"
+ "2\x4\b\x1\x10\x1"));
}
-TEST(SectionTest, FileSection) {
- TemporaryFile tf;
+TEST_F(SectionTest, FileSection) {
FileSection fs(REVERSE_PARSER, tf.path);
- ReportRequestSet requests;
- ASSERT_TRUE(tf.fd != -1);
ASSERT_TRUE(WriteStringToFile("iamtestdata", tf.path));
requests.setMainFd(STDOUT_FILENO);
@@ -120,66 +127,79 @@
EXPECT_THAT(GetCapturedStdout(), StrEq("\xa\vatadtsetmai"));
}
-TEST(SectionTest, FileSectionTimeout) {
- TemporaryFile tf;
- // id -1 is timeout parser
+TEST_F(SectionTest, FileSectionTimeout) {
FileSection fs(TIMEOUT_PARSER, tf.path, QUICK_TIMEOUT_MS);
- ReportRequestSet requests;
ASSERT_EQ(NO_ERROR, fs.Execute(&requests));
}
-TEST(SectionTest, CommandSectionConstructor) {
+TEST_F(SectionTest, GZipSection) {
+ const std::string testFile = kTestDataPath + "kmsg.txt";
+ const std::string testGzFile = testFile + ".gz";
+ GZipSection gs(NOOP_PARSER, "/tmp/nonexist", testFile.c_str(), NULL);
+
+ requests.setMainFd(tf.fd);
+ requests.setMainDest(android::os::DEST_LOCAL);
+
+ ASSERT_EQ(NO_ERROR, gs.Execute(&requests));
+ std::string expect, gzFile, actual;
+ ASSERT_TRUE(ReadFileToString(testGzFile, &gzFile));
+ ASSERT_TRUE(ReadFileToString(tf.path, &actual));
+ expect = "\x2\xC6\x6\n\"" + testFile + "\x12\x9F\x6" + gzFile;
+ EXPECT_THAT(actual, StrEq(expect));
+}
+
+TEST_F(SectionTest, GZipSectionNoFileFound) {
+ GZipSection gs(NOOP_PARSER, "/tmp/nonexist1", "/tmp/nonexist2", NULL);
+ requests.setMainFd(STDOUT_FILENO);
+ ASSERT_EQ(-1, gs.Execute(&requests));
+}
+
+TEST_F(SectionTest, CommandSectionConstructor) {
CommandSection cs1(1, "echo", "\"this is a test\"", "ooo", NULL);
CommandSection cs2(2, "single_command", NULL);
CommandSection cs3(1, 3123, "echo", "\"this is a test\"", "ooo", NULL);
CommandSection cs4(2, 43214, "single_command", NULL);
- EXPECT_THAT(cs1.name.string(), StrEq("echo \"this is a test\" ooo"));
+ EXPECT_THAT(cs1.name.string(), StrEq("echo"));
EXPECT_THAT(cs2.name.string(), StrEq("single_command"));
EXPECT_EQ(3123, cs3.timeoutMs);
EXPECT_EQ(43214, cs4.timeoutMs);
- EXPECT_THAT(cs3.name.string(), StrEq("echo \"this is a test\" ooo"));
+ EXPECT_THAT(cs3.name.string(), StrEq("echo"));
EXPECT_THAT(cs4.name.string(), StrEq("single_command"));
}
-TEST(SectionTest, CommandSectionEcho) {
+TEST_F(SectionTest, CommandSectionEcho) {
CommandSection cs(REVERSE_PARSER, "/system/bin/echo", "about", NULL);
- ReportRequestSet requests;
requests.setMainFd(STDOUT_FILENO);
CaptureStdout();
ASSERT_EQ(NO_ERROR, cs.Execute(&requests));
EXPECT_THAT(GetCapturedStdout(), StrEq("\xa\x06\ntuoba"));
}
-TEST(SectionTest, CommandSectionCommandTimeout) {
+TEST_F(SectionTest, CommandSectionCommandTimeout) {
CommandSection cs(NOOP_PARSER, QUICK_TIMEOUT_MS, "/system/bin/yes", NULL);
- ReportRequestSet requests;
ASSERT_EQ(NO_ERROR, cs.Execute(&requests));
}
-TEST(SectionTest, CommandSectionIncidentHelperTimeout) {
+TEST_F(SectionTest, CommandSectionIncidentHelperTimeout) {
CommandSection cs(TIMEOUT_PARSER, QUICK_TIMEOUT_MS, "/system/bin/echo", "about", NULL);
- ReportRequestSet requests;
requests.setMainFd(STDOUT_FILENO);
ASSERT_EQ(NO_ERROR, cs.Execute(&requests));
}
-TEST(SectionTest, CommandSectionBadCommand) {
+TEST_F(SectionTest, CommandSectionBadCommand) {
CommandSection cs(NOOP_PARSER, "echoo", "about", NULL);
- ReportRequestSet requests;
ASSERT_EQ(NAME_NOT_FOUND, cs.Execute(&requests));
}
-TEST(SectionTest, CommandSectionBadCommandAndTimeout) {
+TEST_F(SectionTest, CommandSectionBadCommandAndTimeout) {
CommandSection cs(TIMEOUT_PARSER, QUICK_TIMEOUT_MS, "nonexistcommand", "-opt", NULL);
- ReportRequestSet requests;
// timeout will return first
ASSERT_EQ(NO_ERROR, cs.Execute(&requests));
}
-TEST(SectionTest, LogSectionBinary) {
+TEST_F(SectionTest, LogSectionBinary) {
LogSection ls(1, LOG_ID_EVENTS);
- ReportRequestSet requests;
requests.setMainFd(STDOUT_FILENO);
CaptureStdout();
ASSERT_EQ(NO_ERROR, ls.Execute(&requests));
@@ -187,9 +207,8 @@
EXPECT_FALSE(results.empty());
}
-TEST(SectionTest, LogSectionSystem) {
+TEST_F(SectionTest, LogSectionSystem) {
LogSection ls(1, LOG_ID_SYSTEM);
- ReportRequestSet requests;
requests.setMainFd(STDOUT_FILENO);
CaptureStdout();
ASSERT_EQ(NO_ERROR, ls.Execute(&requests));
@@ -197,12 +216,9 @@
EXPECT_FALSE(results.empty());
}
-TEST(SectionTest, TestFilterPiiTaggedFields) {
- TemporaryFile tf;
+TEST_F(SectionTest, TestFilterPiiTaggedFields) {
FileSection fs(NOOP_PARSER, tf.path);
- ReportRequestSet requests;
- ASSERT_TRUE(tf.fd != -1);
ASSERT_TRUE(WriteStringToFile(VARINT_FIELD_1 + STRING_FIELD_2 + FIX64_FIELD_3, tf.path));
requests.setMainFd(STDOUT_FILENO);
@@ -212,11 +228,9 @@
EXPECT_THAT(GetCapturedStdout(), StrEq("\x02\r" + STRING_FIELD_2));
}
-TEST(SectionTest, TestBadFdRequest) {
- TemporaryFile input;
- FileSection fs(NOOP_PARSER, input.path);
- ReportRequestSet requests;
- ASSERT_TRUE(WriteStringToFile(VARINT_FIELD_1 + STRING_FIELD_2 + FIX64_FIELD_3, input.path));
+TEST_F(SectionTest, TestBadFdRequest) {
+ FileSection fs(NOOP_PARSER, tf.path);
+ ASSERT_TRUE(WriteStringToFile(VARINT_FIELD_1 + STRING_FIELD_2 + FIX64_FIELD_3, tf.path));
IncidentReportArgs args;
args.setAll(true);
@@ -231,11 +245,9 @@
EXPECT_EQ(badFdRequest->err, -EBADF);
}
-TEST(SectionTest, TestBadRequests) {
- TemporaryFile input;
- FileSection fs(NOOP_PARSER, input.path);
- ReportRequestSet requests;
- ASSERT_TRUE(WriteStringToFile(VARINT_FIELD_1 + STRING_FIELD_2 + FIX64_FIELD_3, input.path));
+TEST_F(SectionTest, TestBadRequests) {
+ FileSection fs(NOOP_PARSER, tf.path);
+ ASSERT_TRUE(WriteStringToFile(VARINT_FIELD_1 + STRING_FIELD_2 + FIX64_FIELD_3, tf.path));
IncidentReportArgs args;
args.setAll(true);
@@ -244,16 +256,14 @@
EXPECT_EQ(fs.Execute(&requests), -EBADF);
}
-TEST(SectionTest, TestMultipleRequests) {
- TemporaryFile input, output1, output2, output3;
- FileSection fs(NOOP_PARSER, input.path);
- ReportRequestSet requests;
+TEST_F(SectionTest, TestMultipleRequests) {
+ TemporaryFile output1, output2, output3;
+ FileSection fs(NOOP_PARSER, tf.path);
- ASSERT_TRUE(input.fd != -1);
ASSERT_TRUE(output1.fd != -1);
ASSERT_TRUE(output2.fd != -1);
ASSERT_TRUE(output3.fd != -1);
- ASSERT_TRUE(WriteStringToFile(VARINT_FIELD_1 + STRING_FIELD_2 + FIX64_FIELD_3, input.path));
+ ASSERT_TRUE(WriteStringToFile(VARINT_FIELD_1 + STRING_FIELD_2 + FIX64_FIELD_3, tf.path));
IncidentReportArgs args1, args2, args3;
args1.setAll(true);
@@ -286,17 +296,15 @@
EXPECT_THAT(content, StrEq(""));
}
-TEST(SectionTest, TestMultipleRequestsBySpec) {
- TemporaryFile input, output1, output2, output3;
- FileSection fs(NOOP_PARSER, input.path);
- ReportRequestSet requests;
+TEST_F(SectionTest, TestMultipleRequestsBySpec) {
+ TemporaryFile output1, output2, output3;
+ FileSection fs(NOOP_PARSER, tf.path);
- ASSERT_TRUE(input.fd != -1);
ASSERT_TRUE(output1.fd != -1);
ASSERT_TRUE(output2.fd != -1);
ASSERT_TRUE(output3.fd != -1);
- ASSERT_TRUE(WriteStringToFile(VARINT_FIELD_1 + STRING_FIELD_2 + FIX64_FIELD_3, input.path));
+ ASSERT_TRUE(WriteStringToFile(VARINT_FIELD_1 + STRING_FIELD_2 + FIX64_FIELD_3, tf.path));
IncidentReportArgs args1, args2, args3;
args1.setAll(true);
@@ -328,4 +336,4 @@
c = (char)STRING_FIELD_2.size();
EXPECT_TRUE(ReadFileToString(output3.path, &content));
EXPECT_THAT(content, StrEq(string("\x02") + c + STRING_FIELD_2));
-}
\ No newline at end of file
+}
diff --git a/cmds/statsd/Android.mk b/cmds/statsd/Android.mk
index d7ce352..7f76ab1 100644
--- a/cmds/statsd/Android.mk
+++ b/cmds/statsd/Android.mk
@@ -21,9 +21,11 @@
src/statsd_config.proto \
src/FieldValue.cpp \
src/stats_log_util.cpp \
- src/anomaly/AnomalyMonitor.cpp \
+ src/anomaly/AlarmMonitor.cpp \
+ src/anomaly/AlarmTracker.cpp \
src/anomaly/AnomalyTracker.cpp \
src/anomaly/DurationAnomalyTracker.cpp \
+ src/anomaly/subscriber_util.cpp \
src/condition/CombinationConditionTracker.cpp \
src/condition/condition_util.cpp \
src/condition/SimpleConditionTracker.cpp \
@@ -172,7 +174,8 @@
src/atom_field_options.proto \
src/atoms.proto \
src/stats_log.proto \
- tests/AnomalyMonitor_test.cpp \
+ tests/AlarmMonitor_test.cpp \
+ tests/anomaly/AlarmTracker_test.cpp \
tests/anomaly/AnomalyTracker_test.cpp \
tests/ConfigManager_test.cpp \
tests/external/puller_util_test.cpp \
diff --git a/cmds/statsd/src/FieldValue.h b/cmds/statsd/src/FieldValue.h
index 21f30e2..b0e2c43 100644
--- a/cmds/statsd/src/FieldValue.h
+++ b/cmds/statsd/src/FieldValue.h
@@ -217,6 +217,14 @@
const Field mMatcher;
const int32_t mMask;
+ inline const Field& getMatcher() const {
+ return mMatcher;
+ }
+
+ inline int32_t getMask() const {
+ return mMask;
+ }
+
bool hasAnyPositionMatcher(int* prefix) const {
if (mMatcher.getDepth() == 2 && mMatcher.getRawPosAtDepth(2) == 0) {
(*prefix) = mMatcher.getPrefix(2);
@@ -224,6 +232,10 @@
}
return false;
}
+
+ inline bool operator!=(const Matcher& that) const {
+ return mMatcher != that.getMatcher() || mMask != that.getMask();
+ };
};
/**
diff --git a/cmds/statsd/src/HashableDimensionKey.cpp b/cmds/statsd/src/HashableDimensionKey.cpp
index 1502a00..cc706313 100644
--- a/cmds/statsd/src/HashableDimensionKey.cpp
+++ b/cmds/statsd/src/HashableDimensionKey.cpp
@@ -166,10 +166,11 @@
}
}
-void getDimensionForCondition(const LogEvent& event, Metric2Condition links,
+void getDimensionForCondition(const std::vector<FieldValue>& eventValues,
+ const Metric2Condition& links,
vector<HashableDimensionKey>* conditionDimension) {
// Get the dimension first by using dimension from what.
- filterValues(links.metricFields, event.getValues(), conditionDimension);
+ filterValues(links.metricFields, eventValues, conditionDimension);
// Then replace the field with the dimension from condition.
for (auto& dim : *conditionDimension) {
diff --git a/cmds/statsd/src/HashableDimensionKey.h b/cmds/statsd/src/HashableDimensionKey.h
index 89fe317..57bdf68 100644
--- a/cmds/statsd/src/HashableDimensionKey.h
+++ b/cmds/statsd/src/HashableDimensionKey.h
@@ -40,7 +40,7 @@
mValues = values;
}
- HashableDimensionKey(){};
+ HashableDimensionKey() {};
HashableDimensionKey(const HashableDimensionKey& that) : mValues(that.getValues()){};
@@ -144,7 +144,8 @@
void filterGaugeValues(const std::vector<Matcher>& matchers, const std::vector<FieldValue>& values,
std::vector<FieldValue>* output);
-void getDimensionForCondition(const LogEvent& event, Metric2Condition links,
+void getDimensionForCondition(const std::vector<FieldValue>& eventValues,
+ const Metric2Condition& links,
std::vector<HashableDimensionKey>* conditionDimension);
} // namespace statsd
diff --git a/cmds/statsd/src/StatsLogProcessor.cpp b/cmds/statsd/src/StatsLogProcessor.cpp
index 3c9dd68..087e596 100644
--- a/cmds/statsd/src/StatsLogProcessor.cpp
+++ b/cmds/statsd/src/StatsLogProcessor.cpp
@@ -66,11 +66,13 @@
#define STATS_DATA_DIR "/data/misc/stats-data"
StatsLogProcessor::StatsLogProcessor(const sp<UidMap>& uidMap,
- const sp<AnomalyMonitor>& anomalyMonitor,
+ const sp<AlarmMonitor>& anomalyAlarmMonitor,
+ const sp<AlarmMonitor>& periodicAlarmMonitor,
const long timeBaseSec,
const std::function<void(const ConfigKey&)>& sendBroadcast)
: mUidMap(uidMap),
- mAnomalyMonitor(anomalyMonitor),
+ mAnomalyAlarmMonitor(anomalyAlarmMonitor),
+ mPeriodicAlarmMonitor(periodicAlarmMonitor),
mSendBroadcast(sendBroadcast),
mTimeBaseSec(timeBaseSec) {
StatsPullerManager statsPullerManager;
@@ -82,10 +84,19 @@
void StatsLogProcessor::onAnomalyAlarmFired(
const uint64_t timestampNs,
- unordered_set<sp<const AnomalyAlarm>, SpHash<AnomalyAlarm>> anomalySet) {
+ unordered_set<sp<const InternalAlarm>, SpHash<InternalAlarm>> alarmSet) {
std::lock_guard<std::mutex> lock(mMetricsMutex);
for (const auto& itr : mMetricsManagers) {
- itr.second->onAnomalyAlarmFired(timestampNs, anomalySet);
+ itr.second->onAnomalyAlarmFired(timestampNs, alarmSet);
+ }
+}
+void StatsLogProcessor::onPeriodicAlarmFired(
+ const uint64_t timestampNs,
+ unordered_set<sp<const InternalAlarm>, SpHash<InternalAlarm>> alarmSet) {
+
+ std::lock_guard<std::mutex> lock(mMetricsMutex);
+ for (const auto& itr : mMetricsManagers) {
+ itr.second->onPeriodicAlarmFired(timestampNs, alarmSet);
}
}
@@ -170,7 +181,9 @@
void StatsLogProcessor::OnConfigUpdated(const ConfigKey& key, const StatsdConfig& config) {
std::lock_guard<std::mutex> lock(mMetricsMutex);
VLOG("Updated configuration for key %s", key.ToString().c_str());
- sp<MetricsManager> newMetricsManager = new MetricsManager(key, config, mTimeBaseSec, mUidMap);
+ sp<MetricsManager> newMetricsManager =
+ new MetricsManager(key, config, mTimeBaseSec, mUidMap,
+ mAnomalyAlarmMonitor, mPeriodicAlarmMonitor);
auto it = mMetricsManagers.find(key);
if (it == mMetricsManagers.end() && mMetricsManagers.size() > StatsdStats::kMaxConfigCount) {
ALOGE("Can't accept more configs!");
@@ -179,7 +192,6 @@
if (newMetricsManager->isConfigValid()) {
mUidMap->OnConfigUpdated(key);
- newMetricsManager->setAnomalyMonitor(mAnomalyMonitor);
if (newMetricsManager->shouldAddUidMapListener()) {
// We have to add listener after the MetricsManager is constructed because it's
// not safe to create wp or sp from this pointer inside its constructor.
diff --git a/cmds/statsd/src/StatsLogProcessor.h b/cmds/statsd/src/StatsLogProcessor.h
index 1444306..4d9f185 100644
--- a/cmds/statsd/src/StatsLogProcessor.h
+++ b/cmds/statsd/src/StatsLogProcessor.h
@@ -34,7 +34,8 @@
class StatsLogProcessor : public ConfigListener {
public:
- StatsLogProcessor(const sp<UidMap>& uidMap, const sp<AnomalyMonitor>& anomalyMonitor,
+ StatsLogProcessor(const sp<UidMap>& uidMap, const sp<AlarmMonitor>& anomalyAlarmMonitor,
+ const sp<AlarmMonitor>& subscriberTriggerAlarmMonitor,
const long timeBaseSec,
const std::function<void(const ConfigKey&)>& sendBroadcast);
virtual ~StatsLogProcessor();
@@ -48,10 +49,15 @@
void onDumpReport(const ConfigKey& key, const uint64_t dumpTimeNs, vector<uint8_t>* outData);
- /* Tells MetricsManager that the alarms in anomalySet have fired. Modifies anomalySet. */
+ /* Tells MetricsManager that the alarms in alarmSet have fired. Modifies anomaly alarmSet. */
void onAnomalyAlarmFired(
const uint64_t timestampNs,
- unordered_set<sp<const AnomalyAlarm>, SpHash<AnomalyAlarm>> anomalySet);
+ unordered_set<sp<const InternalAlarm>, SpHash<InternalAlarm>> alarmSet);
+
+ /* Tells MetricsManager that the alarms in alarmSet have fired. Modifies periodic alarmSet. */
+ void onPeriodicAlarmFired(
+ const uint64_t timestampNs,
+ unordered_set<sp<const InternalAlarm>, SpHash<InternalAlarm>> alarmSet);
/* Flushes data to disk. Data on memory will be gone after written to disk. */
void WriteDataToDisk();
@@ -76,7 +82,9 @@
StatsPullerManager mStatsPullerManager;
- sp<AnomalyMonitor> mAnomalyMonitor;
+ sp<AlarmMonitor> mAnomalyAlarmMonitor;
+
+ sp<AlarmMonitor> mPeriodicAlarmMonitor;
void onDumpReportLocked(const ConfigKey& key, const uint64_t dumpTimeNs,
vector<uint8_t>* outData);
diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp
index c27b130..ee4f434 100644
--- a/cmds/statsd/src/StatsService.cpp
+++ b/cmds/statsd/src/StatsService.cpp
@@ -50,49 +50,73 @@
constexpr const char* kPermissionDump = "android.permission.DUMP";
#define STATS_SERVICE_DIR "/data/misc/stats-service"
-// ======================================================================
/**
* Watches for the death of the stats companion (system process).
*/
class CompanionDeathRecipient : public IBinder::DeathRecipient {
public:
- CompanionDeathRecipient(const sp<AnomalyMonitor>& anomalyMonitor);
+ CompanionDeathRecipient(const sp<AlarmMonitor>& anomalyAlarmMonitor,
+ const sp<AlarmMonitor>& periodicAlarmMonitor) :
+ mAnomalyAlarmMonitor(anomalyAlarmMonitor),
+ mPeriodicAlarmMonitor(periodicAlarmMonitor) {}
virtual void binderDied(const wp<IBinder>& who);
private:
- const sp<AnomalyMonitor> mAnomalyMonitor;
+ sp<AlarmMonitor> mAnomalyAlarmMonitor;
+ sp<AlarmMonitor> mPeriodicAlarmMonitor;
};
-CompanionDeathRecipient::CompanionDeathRecipient(const sp<AnomalyMonitor>& anomalyMonitor)
- : mAnomalyMonitor(anomalyMonitor) {
-}
-
void CompanionDeathRecipient::binderDied(const wp<IBinder>& who) {
ALOGW("statscompanion service died");
- mAnomalyMonitor->setStatsCompanionService(nullptr);
+ mAnomalyAlarmMonitor->setStatsCompanionService(nullptr);
+ mPeriodicAlarmMonitor->setStatsCompanionService(nullptr);
SubscriberReporter::getInstance().setStatsCompanionService(nullptr);
}
-// ======================================================================
StatsService::StatsService(const sp<Looper>& handlerLooper)
- : mAnomalyMonitor(new AnomalyMonitor(MIN_DIFF_TO_UPDATE_REGISTERED_ALARM_SECS))
-{
+ : mAnomalyAlarmMonitor(new AlarmMonitor(MIN_DIFF_TO_UPDATE_REGISTERED_ALARM_SECS,
+ [](const sp<IStatsCompanionService>& sc, int64_t timeMillis) {
+ if (sc != nullptr) {
+ sc->setAnomalyAlarm(timeMillis);
+ StatsdStats::getInstance().noteRegisteredAnomalyAlarmChanged();
+ }
+ },
+ [](const sp<IStatsCompanionService>& sc) {
+ if (sc != nullptr) {
+ sc->cancelAnomalyAlarm();
+ StatsdStats::getInstance().noteRegisteredAnomalyAlarmChanged();
+ }
+ })),
+ mPeriodicAlarmMonitor(new AlarmMonitor(MIN_DIFF_TO_UPDATE_REGISTERED_ALARM_SECS,
+ [](const sp<IStatsCompanionService>& sc, int64_t timeMillis) {
+ if (sc != nullptr) {
+ sc->setAlarmForSubscriberTriggering(timeMillis);
+ StatsdStats::getInstance().noteRegisteredPeriodicAlarmChanged();
+ }
+ },
+ [](const sp<IStatsCompanionService>& sc) {
+ if (sc != nullptr) {
+ sc->cancelAlarmForSubscriberTriggering();
+ StatsdStats::getInstance().noteRegisteredPeriodicAlarmChanged();
+ }
+
+ })) {
mUidMap = new UidMap();
StatsPuller::SetUidMap(mUidMap);
mConfigManager = new ConfigManager();
- mProcessor = new StatsLogProcessor(mUidMap, mAnomalyMonitor, getElapsedRealtimeSec(),
- [this](const ConfigKey& key) {
- sp<IStatsCompanionService> sc = getStatsCompanionService();
- auto receiver = mConfigManager->GetConfigReceiver(key);
- if (sc == nullptr) {
- VLOG("Could not find StatsCompanionService");
- } else if (receiver == nullptr) {
- VLOG("Statscompanion could not find a broadcast receiver for %s",
- key.ToString().c_str());
- } else {
- sc->sendDataBroadcast(receiver);
- }
+ mProcessor = new StatsLogProcessor(mUidMap, mAnomalyAlarmMonitor, mPeriodicAlarmMonitor,
+ getElapsedRealtimeSec(), [this](const ConfigKey& key) {
+ sp<IStatsCompanionService> sc = getStatsCompanionService();
+ auto receiver = mConfigManager->GetConfigReceiver(key);
+ if (sc == nullptr) {
+ VLOG("Could not find StatsCompanionService");
+ } else if (receiver == nullptr) {
+ VLOG("Statscompanion could not find a broadcast receiver for %s",
+ key.ToString().c_str());
+ } else {
+ sc->sendDataBroadcast(receiver);
}
+ }
);
mConfigManager->AddListener(mProcessor);
@@ -423,6 +447,13 @@
}
if (args[1] == "update") {
+ char* endp;
+ int64_t configID = strtoll(name.c_str(), &endp, 10);
+ if (endp == name.c_str() || *endp != '\0') {
+ fprintf(err, "Error parsing config ID.\n");
+ return UNKNOWN_ERROR;
+ }
+
// Read stream into buffer.
string buffer;
if (!android::base::ReadFdToString(fileno(in), &buffer)) {
@@ -438,7 +469,7 @@
}
// Add / update the config.
- mConfigManager->UpdateConfig(ConfigKey(uid, StrToInt64(name)), config);
+ mConfigManager->UpdateConfig(ConfigKey(uid, configID), config);
} else {
if (argCount == 2) {
cmd_remove_all_configs(out);
@@ -615,7 +646,8 @@
status_t StatsService::cmd_clear_puller_cache(FILE* out) {
IPCThreadState* ipc = IPCThreadState::self();
- VLOG("StatsService::cmd_clear_puller_cache with Pid %i, Uid %i", ipc->getCallingPid(), ipc->getCallingUid());
+ VLOG("StatsService::cmd_clear_puller_cache with Pid %i, Uid %i",
+ ipc->getCallingPid(), ipc->getCallingUid());
if (checkCallingPermission(String16(kPermissionDump))) {
int cleared = mStatsPullerManager.ForceClearPullerCache();
fprintf(out, "Puller removed %d cached data!\n", cleared);
@@ -670,18 +702,40 @@
return Status::fromExceptionCode(Status::EX_SECURITY,
"Only system uid can call informAnomalyAlarmFired");
}
+
uint64_t currentTimeSec = getElapsedRealtimeSec();
- std::unordered_set<sp<const AnomalyAlarm>, SpHash<AnomalyAlarm>> anomalySet =
- mAnomalyMonitor->popSoonerThan(static_cast<uint32_t>(currentTimeSec));
- if (anomalySet.size() > 0) {
+ std::unordered_set<sp<const InternalAlarm>, SpHash<InternalAlarm>> alarmSet =
+ mAnomalyAlarmMonitor->popSoonerThan(static_cast<uint32_t>(currentTimeSec));
+ if (alarmSet.size() > 0) {
VLOG("Found an anomaly alarm that fired.");
- mProcessor->onAnomalyAlarmFired(currentTimeSec * NS_PER_SEC, anomalySet);
+ mProcessor->onAnomalyAlarmFired(currentTimeSec * NS_PER_SEC, alarmSet);
} else {
VLOG("Cannot find an anomaly alarm that fired. Perhaps it was recently cancelled.");
}
return Status::ok();
}
+Status StatsService::informAlarmForSubscriberTriggeringFired() {
+ VLOG("StatsService::informAlarmForSubscriberTriggeringFired was called");
+
+ if (IPCThreadState::self()->getCallingUid() != AID_SYSTEM) {
+ return Status::fromExceptionCode(
+ Status::EX_SECURITY,
+ "Only system uid can call informAlarmForSubscriberTriggeringFired");
+ }
+
+ uint64_t currentTimeSec = time(nullptr);
+ std::unordered_set<sp<const InternalAlarm>, SpHash<InternalAlarm>> alarmSet =
+ mPeriodicAlarmMonitor->popSoonerThan(static_cast<uint32_t>(currentTimeSec));
+ if (alarmSet.size() > 0) {
+ VLOG("Found periodic alarm fired.");
+ mProcessor->onPeriodicAlarmFired(currentTimeSec * NS_PER_SEC, alarmSet);
+ } else {
+ ALOGW("Cannot find an periodic alarm that fired. Perhaps it was recently cancelled.");
+ }
+ return Status::ok();
+}
+
Status StatsService::informPollAlarmFired() {
VLOG("StatsService::informPollAlarmFired was called");
@@ -766,10 +820,11 @@
"statscompanion unavailable despite it contacting statsd!");
}
VLOG("StatsService::statsCompanionReady linking to statsCompanion.");
- IInterface::asBinder(statsCompanion)->linkToDeath(new CompanionDeathRecipient(mAnomalyMonitor));
- mAnomalyMonitor->setStatsCompanionService(statsCompanion);
+ IInterface::asBinder(statsCompanion)->linkToDeath(
+ new CompanionDeathRecipient(mAnomalyAlarmMonitor, mPeriodicAlarmMonitor));
+ mAnomalyAlarmMonitor->setStatsCompanionService(statsCompanion);
+ mPeriodicAlarmMonitor->setStatsCompanionService(statsCompanion);
SubscriberReporter::getInstance().setStatsCompanionService(statsCompanion);
-
return Status::ok();
}
diff --git a/cmds/statsd/src/StatsService.h b/cmds/statsd/src/StatsService.h
index 9690de7..e0a1299 100644
--- a/cmds/statsd/src/StatsService.h
+++ b/cmds/statsd/src/StatsService.h
@@ -18,7 +18,7 @@
#define STATS_SERVICE_H
#include "StatsLogProcessor.h"
-#include "anomaly/AnomalyMonitor.h"
+#include "anomaly/AlarmMonitor.h"
#include "config/ConfigManager.h"
#include "external/StatsPullerManager.h"
#include "packages/UidMap.h"
@@ -58,6 +58,8 @@
virtual Status statsCompanionReady();
virtual Status informAnomalyAlarmFired();
virtual Status informPollAlarmFired();
+ virtual Status informAlarmForSubscriberTriggeringFired();
+
virtual Status informAllUidData(const vector<int32_t>& uid, const vector<int64_t>& version,
const vector<String16>& app);
virtual Status informOnePackage(const String16& app, int32_t uid, int64_t version);
@@ -244,9 +246,14 @@
sp<StatsLogProcessor> mProcessor;
/**
- * The anomaly detector.
+ * The alarm monitor for anomaly detection.
*/
- const sp<AnomalyMonitor> mAnomalyMonitor;
+ const sp<AlarmMonitor> mAnomalyAlarmMonitor;
+
+ /**
+ * The alarm monitor for alarms to directly trigger subscriber.
+ */
+ const sp<AlarmMonitor> mPeriodicAlarmMonitor;
/**
* Whether this is an eng build.
diff --git a/cmds/statsd/src/anomaly/AnomalyMonitor.cpp b/cmds/statsd/src/anomaly/AlarmMonitor.cpp
similarity index 74%
rename from cmds/statsd/src/anomaly/AnomalyMonitor.cpp
rename to cmds/statsd/src/anomaly/AlarmMonitor.cpp
index ca34dc6..78f0c2b 100644
--- a/cmds/statsd/src/anomaly/AnomalyMonitor.cpp
+++ b/cmds/statsd/src/anomaly/AlarmMonitor.cpp
@@ -17,21 +17,24 @@
#define DEBUG false
#include "Log.h"
-#include "anomaly/AnomalyMonitor.h"
+#include "anomaly/AlarmMonitor.h"
#include "guardrail/StatsdStats.h"
namespace android {
namespace os {
namespace statsd {
-AnomalyMonitor::AnomalyMonitor(uint32_t minDiffToUpdateRegisteredAlarmTimeSec)
- : mRegisteredAlarmTimeSec(0), mMinUpdateTimeSec(minDiffToUpdateRegisteredAlarmTimeSec) {
-}
+AlarmMonitor::AlarmMonitor(
+ uint32_t minDiffToUpdateRegisteredAlarmTimeSec,
+ const std::function<void(const sp<IStatsCompanionService>&, int64_t)>& updateAlarm,
+ const std::function<void(const sp<IStatsCompanionService>&)>& cancelAlarm)
+ : mRegisteredAlarmTimeSec(0), mMinUpdateTimeSec(minDiffToUpdateRegisteredAlarmTimeSec),
+ mUpdateAlarm(updateAlarm),
+ mCancelAlarm(cancelAlarm) {}
-AnomalyMonitor::~AnomalyMonitor() {
-}
+AlarmMonitor::~AlarmMonitor() {}
-void AnomalyMonitor::setStatsCompanionService(sp<IStatsCompanionService> statsCompanionService) {
+void AlarmMonitor::setStatsCompanionService(sp<IStatsCompanionService> statsCompanionService) {
std::lock_guard<std::mutex> lock(mLock);
sp<IStatsCompanionService> tmpForLock = mStatsCompanionService;
mStatsCompanionService = statsCompanionService;
@@ -40,13 +43,13 @@
return;
}
VLOG("Creating link to statsCompanionService");
- const sp<const AnomalyAlarm> top = mPq.top();
+ const sp<const InternalAlarm> top = mPq.top();
if (top != nullptr) {
updateRegisteredAlarmTime_l(top->timestampSec);
}
}
-void AnomalyMonitor::add(sp<const AnomalyAlarm> alarm) {
+void AlarmMonitor::add(sp<const InternalAlarm> alarm) {
std::lock_guard<std::mutex> lock(mLock);
if (alarm == nullptr) {
ALOGW("Asked to add a null alarm.");
@@ -66,7 +69,7 @@
}
}
-void AnomalyMonitor::remove(sp<const AnomalyAlarm> alarm) {
+void AlarmMonitor::remove(sp<const InternalAlarm> alarm) {
std::lock_guard<std::mutex> lock(mLock);
if (alarm == nullptr) {
ALOGW("Asked to remove a null alarm.");
@@ -89,13 +92,13 @@
// More efficient than repeatedly calling remove(mPq.top()) since it batches the
// updates to the registered alarm.
-unordered_set<sp<const AnomalyAlarm>, SpHash<AnomalyAlarm>> AnomalyMonitor::popSoonerThan(
+unordered_set<sp<const InternalAlarm>, SpHash<InternalAlarm>> AlarmMonitor::popSoonerThan(
uint32_t timestampSec) {
VLOG("Removing alarms with time <= %u", timestampSec);
- unordered_set<sp<const AnomalyAlarm>, SpHash<AnomalyAlarm>> oldAlarms;
+ unordered_set<sp<const InternalAlarm>, SpHash<InternalAlarm>> oldAlarms;
std::lock_guard<std::mutex> lock(mLock);
- for (sp<const AnomalyAlarm> t = mPq.top(); t != nullptr && t->timestampSec <= timestampSec;
+ for (sp<const InternalAlarm> t = mPq.top(); t != nullptr && t->timestampSec <= timestampSec;
t = mPq.top()) {
oldAlarms.insert(t);
mPq.pop(); // remove t
@@ -113,25 +116,19 @@
return oldAlarms;
}
-void AnomalyMonitor::updateRegisteredAlarmTime_l(uint32_t timestampSec) {
+void AlarmMonitor::updateRegisteredAlarmTime_l(uint32_t timestampSec) {
VLOG("Updating reg alarm time to %u", timestampSec);
mRegisteredAlarmTimeSec = timestampSec;
- if (mStatsCompanionService != nullptr) {
- mStatsCompanionService->setAnomalyAlarm(secToMs(mRegisteredAlarmTimeSec));
- StatsdStats::getInstance().noteRegisteredAnomalyAlarmChanged();
- }
+ mUpdateAlarm(mStatsCompanionService, secToMs(mRegisteredAlarmTimeSec));
}
-void AnomalyMonitor::cancelRegisteredAlarmTime_l() {
+void AlarmMonitor::cancelRegisteredAlarmTime_l() {
VLOG("Cancelling reg alarm.");
mRegisteredAlarmTimeSec = 0;
- if (mStatsCompanionService != nullptr) {
- mStatsCompanionService->cancelAnomalyAlarm();
- StatsdStats::getInstance().noteRegisteredAnomalyAlarmChanged();
- }
+ mCancelAlarm(mStatsCompanionService);
}
-int64_t AnomalyMonitor::secToMs(uint32_t timeSec) {
+int64_t AlarmMonitor::secToMs(uint32_t timeSec) {
return ((int64_t)timeSec) * 1000;
}
diff --git a/cmds/statsd/src/anomaly/AnomalyMonitor.h b/cmds/statsd/src/anomaly/AlarmMonitor.h
similarity index 77%
rename from cmds/statsd/src/anomaly/AnomalyMonitor.h
rename to cmds/statsd/src/anomaly/AlarmMonitor.h
index 7acc7904..3badb1f 100644
--- a/cmds/statsd/src/anomaly/AnomalyMonitor.h
+++ b/cmds/statsd/src/anomaly/AlarmMonitor.h
@@ -41,33 +41,34 @@
* threshold.
* Timestamps are in seconds since epoch in a uint32, so will fail in year 2106.
*/
-struct AnomalyAlarm : public RefBase {
- AnomalyAlarm(uint32_t timestampSec) : timestampSec(timestampSec) {
+struct InternalAlarm : public RefBase {
+ InternalAlarm(uint32_t timestampSec) : timestampSec(timestampSec) {
}
const uint32_t timestampSec;
- /** AnomalyAlarm a is smaller (higher priority) than b if its timestamp is sooner. */
+ /** InternalAlarm a is smaller (higher priority) than b if its timestamp is sooner. */
struct SmallerTimestamp {
- bool operator()(sp<const AnomalyAlarm> a, sp<const AnomalyAlarm> b) const {
+ bool operator()(sp<const InternalAlarm> a, sp<const InternalAlarm> b) const {
return (a->timestampSec < b->timestampSec);
}
};
};
-// TODO: Rename this file to AnomalyAlarmMonitor.
/**
- * Manages alarms for Anomaly Detection.
+ * Manages internal alarms that may get registered with the AlarmManager.
*/
-class AnomalyMonitor : public RefBase {
+class AlarmMonitor : public RefBase {
public:
/**
* @param minDiffToUpdateRegisteredAlarmTimeSec If the soonest alarm differs
* from the registered alarm by more than this amount, update the registered
* alarm.
*/
- AnomalyMonitor(uint32_t minDiffToUpdateRegisteredAlarmTimeSec);
- ~AnomalyMonitor();
+ AlarmMonitor(uint32_t minDiffToUpdateRegisteredAlarmTimeSec,
+ const std::function<void(const sp<IStatsCompanionService>&, int64_t)>& updateAlarm,
+ const std::function<void(const sp<IStatsCompanionService>&)>& cancelAlarm);
+ ~AlarmMonitor();
/**
* Tells AnomalyMonitor what IStatsCompanionService to use and, if
@@ -80,20 +81,20 @@
/**
* Adds the given alarm (reference) to the queue.
*/
- void add(sp<const AnomalyAlarm> alarm);
+ void add(sp<const InternalAlarm> alarm);
/**
* Removes the given alarm (reference) from the queue.
* Note that alarm comparison is reference-based; if another alarm exists
* with the same timestampSec, that alarm will still remain in the queue.
*/
- void remove(sp<const AnomalyAlarm> alarm);
+ void remove(sp<const InternalAlarm> alarm);
/**
* Returns and removes all alarms whose timestamp <= the given timestampSec.
* Always updates the registered alarm if return is non-empty.
*/
- unordered_set<sp<const AnomalyAlarm>, SpHash<AnomalyAlarm>> popSoonerThan(
+ unordered_set<sp<const InternalAlarm>, SpHash<InternalAlarm>> popSoonerThan(
uint32_t timestampSec);
/**
@@ -119,7 +120,7 @@
/**
* Priority queue of alarms, prioritized by soonest alarm.timestampSec.
*/
- indexed_priority_queue<AnomalyAlarm, AnomalyAlarm::SmallerTimestamp> mPq;
+ indexed_priority_queue<InternalAlarm, InternalAlarm::SmallerTimestamp> mPq;
/**
* Binder interface for communicating with StatsCompanionService.
@@ -146,6 +147,13 @@
/** Converts uint32 timestamp in seconds to a Java long in msec. */
int64_t secToMs(uint32_t timeSec);
+
+ // Callback function to update the alarm via StatsCompanionService.
+ std::function<void(const sp<IStatsCompanionService>, int64_t)> mUpdateAlarm;
+
+ // Callback function to cancel the alarm via StatsCompanionService.
+ std::function<void(const sp<IStatsCompanionService>)> mCancelAlarm;
+
};
} // namespace statsd
diff --git a/cmds/statsd/src/anomaly/AlarmTracker.cpp b/cmds/statsd/src/anomaly/AlarmTracker.cpp
new file mode 100644
index 0000000..eb28383
--- /dev/null
+++ b/cmds/statsd/src/anomaly/AlarmTracker.cpp
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define DEBUG true // STOPSHIP if true
+#include "Log.h"
+
+#include "anomaly/AlarmTracker.h"
+#include "anomaly/subscriber_util.h"
+#include "HashableDimensionKey.h"
+#include "stats_util.h"
+#include "storage/StorageManager.h"
+
+#include <statslog.h>
+#include <time.h>
+
+namespace android {
+namespace os {
+namespace statsd {
+
+AlarmTracker::AlarmTracker(uint64_t startMillis,
+ const Alarm& alarm, const ConfigKey& configKey,
+ const sp<AlarmMonitor>& alarmMonitor)
+ : mAlarmConfig(alarm),
+ mConfigKey(configKey),
+ mAlarmMonitor(alarmMonitor) {
+ VLOG("AlarmTracker() called");
+ mAlarmSec = (startMillis + mAlarmConfig.offset_millis()) / MS_PER_SEC;
+ mInternalAlarm = new InternalAlarm{static_cast<uint32_t>(mAlarmSec)};
+ mAlarmMonitor->add(mInternalAlarm);
+}
+
+AlarmTracker::~AlarmTracker() {
+ VLOG("~AlarmTracker() called");
+ if (mInternalAlarm != nullptr) {
+ mAlarmMonitor->remove(mInternalAlarm);
+ }
+}
+
+void AlarmTracker::addSubscription(const Subscription& subscription) {
+ mSubscriptions.push_back(subscription);
+}
+
+uint64_t AlarmTracker::findNextAlarmSec(uint64_t currentTimeSec) {
+ int periodsForward = (currentTimeSec - mAlarmSec) * MS_PER_SEC / mAlarmConfig.period_millis();
+ return mAlarmSec + (periodsForward + 1) * mAlarmConfig.period_millis() / MS_PER_SEC;
+}
+
+void AlarmTracker::informAlarmsFired(
+ const uint64_t& timestampNs,
+ unordered_set<sp<const InternalAlarm>, SpHash<InternalAlarm>>& firedAlarms) {
+ if (firedAlarms.empty() || firedAlarms.find(mInternalAlarm) == firedAlarms.end()) {
+ return;
+ }
+ if (!mSubscriptions.empty()) {
+ triggerSubscribers(mAlarmConfig.id(), DEFAULT_METRIC_DIMENSION_KEY, mConfigKey,
+ mSubscriptions);
+ }
+ firedAlarms.erase(mInternalAlarm);
+ mAlarmSec = findNextAlarmSec(timestampNs / NS_PER_SEC);
+ mInternalAlarm = new InternalAlarm{static_cast<uint32_t>(mAlarmSec)};
+ mAlarmMonitor->add(mInternalAlarm);
+}
+
+} // namespace statsd
+} // namespace os
+} // namespace android
diff --git a/cmds/statsd/src/anomaly/AlarmTracker.h b/cmds/statsd/src/anomaly/AlarmTracker.h
new file mode 100644
index 0000000..d59dacaa
--- /dev/null
+++ b/cmds/statsd/src/anomaly/AlarmTracker.h
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <gtest/gtest_prod.h>
+
+#include "AlarmMonitor.h"
+#include "config/ConfigKey.h"
+#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h" // Alarm
+
+#include <android/os/IStatsCompanionService.h>
+#include <stdlib.h>
+#include <utils/RefBase.h>
+
+using android::os::IStatsCompanionService;
+
+namespace android {
+namespace os {
+namespace statsd {
+
+class AlarmTracker : public virtual RefBase {
+public:
+ AlarmTracker(uint64_t startMillis,
+ const Alarm& alarm, const ConfigKey& configKey,
+ const sp<AlarmMonitor>& subscriberAlarmMonitor);
+
+ virtual ~AlarmTracker();
+
+ void onAlarmFired();
+
+ void addSubscription(const Subscription& subscription);
+
+ void informAlarmsFired(const uint64_t& timestampNs,
+ unordered_set<sp<const InternalAlarm>, SpHash<InternalAlarm>>& firedAlarms);
+
+protected:
+ uint64_t findNextAlarmSec(uint64_t currentTimeMillis);
+
+ // statsd_config.proto Alarm message that defines this tracker.
+ const Alarm mAlarmConfig;
+
+ // A reference to the Alarm's config key.
+ const ConfigKey& mConfigKey;
+
+ // The subscriptions that depend on this alarm.
+ std::vector<Subscription> mSubscriptions;
+
+ // Alarm monitor.
+ sp<AlarmMonitor> mAlarmMonitor;
+
+ // The current expected alarm time in seconds.
+ uint64_t mAlarmSec;
+
+ // The current alarm.
+ sp<const InternalAlarm> mInternalAlarm;
+
+ FRIEND_TEST(AlarmTrackerTest, TestTriggerTimestamp);
+};
+
+} // namespace statsd
+} // namespace os
+} // namespace android
diff --git a/cmds/statsd/src/anomaly/AnomalyTracker.cpp b/cmds/statsd/src/anomaly/AnomalyTracker.cpp
index c40eb81..642604e 100644
--- a/cmds/statsd/src/anomaly/AnomalyTracker.cpp
+++ b/cmds/statsd/src/anomaly/AnomalyTracker.cpp
@@ -18,6 +18,7 @@
#include "Log.h"
#include "AnomalyTracker.h"
+#include "subscriber_util.h"
#include "external/Perfetto.h"
#include "guardrail/StatsdStats.h"
#include "subscriber/IncidentdReporter.h"
@@ -231,40 +232,7 @@
}
void AnomalyTracker::informSubscribers(const MetricDimensionKey& key) {
- VLOG("informSubscribers called.");
- if (mSubscriptions.empty()) {
- // The config just wanted to log the anomaly. That's fine.
- VLOG("No Subscriptions were associated with the alert.");
- return;
- }
-
- for (const Subscription& subscription : mSubscriptions) {
- if (subscription.probability_of_informing() < 1
- && ((float)rand() / RAND_MAX) >= subscription.probability_of_informing()) {
- // Note that due to float imprecision, 0.0 and 1.0 might not truly mean never/always.
- // The config writer was advised to use -0.1 and 1.1 for never/always.
- ALOGI("Fate decided that a subscriber would not be informed.");
- continue;
- }
- switch (subscription.subscriber_information_case()) {
- case Subscription::SubscriberInformationCase::kIncidentdDetails:
- if (!GenerateIncidentReport(subscription.incidentd_details(), mAlert, mConfigKey)) {
- ALOGW("Failed to generate incident report.");
- }
- break;
- case Subscription::SubscriberInformationCase::kPerfettoDetails:
- if (!CollectPerfettoTraceAndUploadToDropbox(subscription.perfetto_details())) {
- ALOGW("Failed to generate prefetto traces.");
- }
- break;
- case Subscription::SubscriberInformationCase::kBroadcastSubscriberDetails:
- SubscriberReporter::getInstance().alertBroadcastSubscriber(mConfigKey, subscription,
- key);
- break;
- default:
- break;
- }
- }
+ triggerSubscribers(mAlert.id(), key, mConfigKey, mSubscriptions);
}
} // namespace statsd
diff --git a/cmds/statsd/src/anomaly/AnomalyTracker.h b/cmds/statsd/src/anomaly/AnomalyTracker.h
index 3be959d..e3f493c 100644
--- a/cmds/statsd/src/anomaly/AnomalyTracker.h
+++ b/cmds/statsd/src/anomaly/AnomalyTracker.h
@@ -23,7 +23,7 @@
#include <gtest/gtest_prod.h>
#include <utils/RefBase.h>
-#include "AnomalyMonitor.h"
+#include "AlarmMonitor.h"
#include "config/ConfigKey.h"
#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h" // Alert
#include "stats_util.h" // HashableDimensionKey and DimToValMap
@@ -64,9 +64,9 @@
void detectAndDeclareAnomaly(const uint64_t& timestampNs, const int64_t& currBucketNum,
const MetricDimensionKey& key, const int64_t& currentBucketValue);
- // Init the AnomalyMonitor which is shared across anomaly trackers.
- virtual void setAnomalyMonitor(const sp<AnomalyMonitor>& anomalyMonitor) {
- return; // Base AnomalyTracker class has no need for the AnomalyMonitor.
+ // Init the AlarmMonitor which is shared across anomaly trackers.
+ virtual void setAlarmMonitor(const sp<AlarmMonitor>& alarmMonitor) {
+ return; // Base AnomalyTracker class has no need for the AlarmMonitor.
}
// Helper function to return the sum value of past buckets at given dimension.
@@ -92,11 +92,10 @@
}
// Declares an anomaly for each alarm in firedAlarms that belongs to this AnomalyTracker,
- // and removes it from firedAlarms. Does NOT remove the alarm from the AnomalyMonitor.
- virtual void informAlarmsFired(
- const uint64_t& timestampNs,
- unordered_set<sp<const AnomalyAlarm>, SpHash<AnomalyAlarm>>& firedAlarms) {
- return; // The base AnomalyTracker class doesn't have alarms.
+ // and removes it from firedAlarms. Does NOT remove the alarm from the AlarmMonitor.
+ virtual void informAlarmsFired(const uint64_t& timestampNs,
+ unordered_set<sp<const InternalAlarm>, SpHash<InternalAlarm>>& firedAlarms) {
+ return; // The base AnomalyTracker class doesn't have alarms.
}
protected:
diff --git a/cmds/statsd/src/anomaly/DurationAnomalyTracker.cpp b/cmds/statsd/src/anomaly/DurationAnomalyTracker.cpp
index 3ba943c..31d50be 100644
--- a/cmds/statsd/src/anomaly/DurationAnomalyTracker.cpp
+++ b/cmds/statsd/src/anomaly/DurationAnomalyTracker.cpp
@@ -24,8 +24,9 @@
namespace os {
namespace statsd {
-DurationAnomalyTracker::DurationAnomalyTracker(const Alert& alert, const ConfigKey& configKey)
- : AnomalyTracker(alert, configKey) {
+DurationAnomalyTracker::DurationAnomalyTracker(const Alert& alert, const ConfigKey& configKey,
+ const sp<AlarmMonitor>& alarmMonitor)
+ : AnomalyTracker(alert, configKey), mAlarmMonitor(alarmMonitor) {
}
DurationAnomalyTracker::~DurationAnomalyTracker() {
@@ -59,10 +60,10 @@
VLOG("Setting a delayed anomaly alarm lest it fall in the refractory period");
timestampSec = getRefractoryPeriodEndsSec(dimensionKey) + 1;
}
- sp<const AnomalyAlarm> alarm = new AnomalyAlarm{timestampSec};
+ sp<const InternalAlarm> alarm = new InternalAlarm{timestampSec};
mAlarms.insert({dimensionKey, alarm});
- if (mAnomalyMonitor != nullptr) {
- mAnomalyMonitor->add(alarm);
+ if (mAlarmMonitor != nullptr) {
+ mAlarmMonitor->add(alarm);
}
}
@@ -70,8 +71,8 @@
auto itr = mAlarms.find(dimensionKey);
if (itr != mAlarms.end()) {
mAlarms.erase(dimensionKey);
- if (mAnomalyMonitor != nullptr) {
- mAnomalyMonitor->remove(itr->second);
+ if (mAlarmMonitor != nullptr) {
+ mAlarmMonitor->remove(itr->second);
}
}
}
@@ -86,16 +87,16 @@
}
}
-void DurationAnomalyTracker::informAlarmsFired(
- const uint64_t& timestampNs,
- unordered_set<sp<const AnomalyAlarm>, SpHash<AnomalyAlarm>>& firedAlarms) {
+void DurationAnomalyTracker::informAlarmsFired(const uint64_t& timestampNs,
+ unordered_set<sp<const InternalAlarm>, SpHash<InternalAlarm>>& firedAlarms) {
+
if (firedAlarms.empty() || mAlarms.empty()) return;
// Find the intersection of firedAlarms and mAlarms.
// The for loop is inefficient, since it loops over all keys, but that's okay since it is very
- // seldomly called. The alternative would be having AnomalyAlarms store information about the
+ // seldomly called. The alternative would be having InternalAlarms store information about the
// DurationAnomalyTracker and key, but that's a lot of data overhead to speed up something that
// is rarely ever called.
- unordered_map<MetricDimensionKey, sp<const AnomalyAlarm>> matchedAlarms;
+ unordered_map<MetricDimensionKey, sp<const InternalAlarm>> matchedAlarms;
for (const auto& kv : mAlarms) {
if (firedAlarms.count(kv.second) > 0) {
matchedAlarms.insert({kv.first, kv.second});
diff --git a/cmds/statsd/src/anomaly/DurationAnomalyTracker.h b/cmds/statsd/src/anomaly/DurationAnomalyTracker.h
index 15aef29..51186df 100644
--- a/cmds/statsd/src/anomaly/DurationAnomalyTracker.h
+++ b/cmds/statsd/src/anomaly/DurationAnomalyTracker.h
@@ -16,7 +16,7 @@
#pragma once
-#include "AnomalyMonitor.h"
+#include "AlarmMonitor.h"
#include "AnomalyTracker.h"
namespace android {
@@ -27,7 +27,8 @@
class DurationAnomalyTracker : public virtual AnomalyTracker {
public:
- DurationAnomalyTracker(const Alert& alert, const ConfigKey& configKey);
+ DurationAnomalyTracker(const Alert& alert, const ConfigKey& configKey,
+ const sp<AlarmMonitor>& alarmMonitor);
virtual ~DurationAnomalyTracker();
@@ -40,11 +41,6 @@
// Stop all the alarms owned by this tracker.
void stopAllAlarms();
- // Init the AnomalyMonitor which is shared across anomaly trackers.
- void setAnomalyMonitor(const sp<AnomalyMonitor>& anomalyMonitor) override {
- mAnomalyMonitor = anomalyMonitor;
- }
-
// Declares the anomaly when the alarm expired given the current timestamp.
void declareAnomalyIfAlarmExpired(const MetricDimensionKey& dimensionKey,
const uint64_t& timestampNs);
@@ -53,17 +49,16 @@
// and removes it from firedAlarms.
// Note that this will generally be called from a different thread from the other functions;
// the caller is responsible for thread safety.
- void informAlarmsFired(
- const uint64_t& timestampNs,
- unordered_set<sp<const AnomalyAlarm>, SpHash<AnomalyAlarm>>& firedAlarms) override;
+ void informAlarmsFired(const uint64_t& timestampNs,
+ unordered_set<sp<const InternalAlarm>, SpHash<InternalAlarm>>& firedAlarms) override;
protected:
// The alarms owned by this tracker. The alarm monitor also shares the alarm pointers when they
// are still active.
- std::unordered_map<MetricDimensionKey, sp<const AnomalyAlarm>> mAlarms;
+ std::unordered_map<MetricDimensionKey, sp<const InternalAlarm>> mAlarms;
// Anomaly alarm monitor.
- sp<AnomalyMonitor> mAnomalyMonitor;
+ sp<AlarmMonitor> mAlarmMonitor;
// Resets all bucket data. For use when all the data gets stale.
void resetStorage() override;
diff --git a/cmds/statsd/src/anomaly/subscriber_util.cpp b/cmds/statsd/src/anomaly/subscriber_util.cpp
new file mode 100644
index 0000000..e796d19
--- /dev/null
+++ b/cmds/statsd/src/anomaly/subscriber_util.cpp
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define DEBUG true // STOPSHIP if true
+#include "Log.h"
+
+#include <android/os/IIncidentManager.h>
+#include <android/os/IncidentReportArgs.h>
+#include <binder/IServiceManager.h>
+
+#include "external/Perfetto.h"
+#include "frameworks/base/libs/incident/proto/android/os/header.pb.h"
+#include "subscriber/IncidentdReporter.h"
+#include "subscriber/SubscriberReporter.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+void triggerSubscribers(const int64_t rule_id,
+ const MetricDimensionKey& dimensionKey,
+ const ConfigKey& configKey,
+ const std::vector<Subscription>& subscriptions) {
+ VLOG("informSubscribers called.");
+ if (subscriptions.empty()) {
+ VLOG("No Subscriptions were associated.");
+ return;
+ }
+
+ for (const Subscription& subscription : subscriptions) {
+ if (subscription.probability_of_informing() < 1
+ && ((float)rand() / RAND_MAX) >= subscription.probability_of_informing()) {
+ // Note that due to float imprecision, 0.0 and 1.0 might not truly mean never/always.
+ // The config writer was advised to use -0.1 and 1.1 for never/always.
+ ALOGI("Fate decided that a subscriber would not be informed.");
+ continue;
+ }
+ switch (subscription.subscriber_information_case()) {
+ case Subscription::SubscriberInformationCase::kIncidentdDetails:
+ if (!GenerateIncidentReport(subscription.incidentd_details(), rule_id, configKey)) {
+ ALOGW("Failed to generate incident report.");
+ }
+ break;
+ case Subscription::SubscriberInformationCase::kPerfettoDetails:
+ if (!CollectPerfettoTraceAndUploadToDropbox(subscription.perfetto_details())) {
+ ALOGW("Failed to generate prefetto traces.");
+ }
+ break;
+ case Subscription::SubscriberInformationCase::kBroadcastSubscriberDetails:
+ SubscriberReporter::getInstance().alertBroadcastSubscriber(configKey, subscription,
+ dimensionKey);
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+
+} // namespace statsd
+} // namespace os
+} // namespace android
diff --git a/cmds/statsd/src/anomaly/subscriber_util.h b/cmds/statsd/src/anomaly/subscriber_util.h
new file mode 100644
index 0000000..dba8981
--- /dev/null
+++ b/cmds/statsd/src/anomaly/subscriber_util.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include "config/ConfigKey.h"
+#include "HashableDimensionKey.h"
+#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+void triggerSubscribers(const int64_t rule_id,
+ const MetricDimensionKey& dimensionKey,
+ const ConfigKey& configKey,
+ const std::vector<Subscription>& subscriptions);
+
+} // namespace statsd
+} // namespace os
+} // namespace android
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index 7159b9b..42ae022 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -101,6 +101,9 @@
OverlayStateChanged overlay_state_changed = 59;
ForegroundServiceStateChanged foreground_service_state_changed = 60;
CallStateChanged call_state_changed = 61;
+ KeyguardStateChanged keyguard_state_changed = 62;
+ KeyguardBouncerStateChanged keyguard_bouncer_state_changed = 63;
+ KeyguardBouncerPasswordEntered keyguard_bouncer_password_entered = 64;
// TODO: Reorder the numbering so that the most frequent occur events occur in the first 15.
}
@@ -130,6 +133,9 @@
FullBatteryCapacity full_battery_capacity = 10020;
Temperature temperature = 10021;
}
+
+ // DO NOT USE field numbers above 100,000 in AOSP. Field numbers above
+ // 100,000 are reserved for non-AOSP (e.g. OEMs) to use.
}
/**
@@ -747,14 +753,74 @@
}
/**
+ * Logs keyguard state. The keyguard is the lock screen.
+ *
+ * Logged from:
+ * frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+ */
+message KeyguardStateChanged {
+ enum State {
+ UNKNOWN = 0;
+ // The keyguard is hidden when the phone is unlocked.
+ HIDDEN = 1;
+ // The keyguard is shown when the phone is locked (screen turns off).
+ SHOWN= 2;
+ // The keyguard is occluded when something is overlaying the keyguard.
+ // Eg. Opening the camera while on the lock screen.
+ OCCLUDED = 3;
+ }
+ optional State state = 1;
+}
+
+/**
+ * Logs keyguard bouncer state. The bouncer is a part of the keyguard, and
+ * prompts the user to enter a password (pattern, pin, etc).
+ *
+ * Logged from:
+ * frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
+ */
+
+message KeyguardBouncerStateChanged {
+ enum State {
+ UNKNOWN = 0;
+ // Bouncer is hidden, either as a result of successfully entering the
+ // password, screen timing out, or user going back to lock screen.
+ HIDDEN = 1;
+ // This is when the user is being prompted to enter the password.
+ SHOWN = 2;
+ }
+ optional State state = 1;
+}
+
+/**
+ * Logs the result of entering a password into the keyguard bouncer.
+ *
+ * Logged from:
+ * frameworks/base/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+ */
+message KeyguardBouncerPasswordEntered {
+ enum BouncerResult {
+ UNKNOWN = 0;
+ // The password entered was incorrect.
+ FAILURE = 1;
+ // The password entered was correct.
+ SUCCESS = 2;
+ }
+ optional BouncerResult result = 1;
+}
+
+/**
* Logs the duration of a davey (jank of >=700ms) when it occurs
*
* Logged from:
* frameworks/base/libs/hwui/JankTracker.cpp
*/
message DaveyOccurred {
+ // The UID that logged this atom.
+ optional int32 uid = 1;
+
// Amount of time it took to render the frame. Should be >=700ms.
- optional int64 jank_duration_millis = 1;
+ optional int64 jank_duration_millis = 2;
}
/**
@@ -1529,4 +1595,4 @@
// Temperature in degrees C.
optional float temperature_C = 3;
-}
\ No newline at end of file
+}
diff --git a/cmds/statsd/src/external/Perfetto.h b/cmds/statsd/src/external/Perfetto.h
index e2e0253..2a5679c 100644
--- a/cmds/statsd/src/external/Perfetto.h
+++ b/cmds/statsd/src/external/Perfetto.h
@@ -16,6 +16,8 @@
#pragma once
+#include <android/os/StatsLogEventWrapper.h>
+
using android::os::StatsLogEventWrapper;
namespace android {
diff --git a/cmds/statsd/src/guardrail/StatsdStats.cpp b/cmds/statsd/src/guardrail/StatsdStats.cpp
index 66cb1d0..0881d44 100644
--- a/cmds/statsd/src/guardrail/StatsdStats.cpp
+++ b/cmds/statsd/src/guardrail/StatsdStats.cpp
@@ -47,11 +47,13 @@
const int FIELD_ID_ANOMALY_ALARM_STATS = 9;
// const int FIELD_ID_PULLED_ATOM_STATS = 10; // The proto is written in stats_log_util.cpp
const int FIELD_ID_LOGGER_ERROR_STATS = 11;
+const int FIELD_ID_SUBSCRIBER_ALARM_STATS = 12;
const int FIELD_ID_ATOM_STATS_TAG = 1;
const int FIELD_ID_ATOM_STATS_COUNT = 2;
const int FIELD_ID_ANOMALY_ALARMS_REGISTERED = 1;
+const int FIELD_ID_SUBSCRIBER_ALARMS_REGISTERED = 1;
const int FIELD_ID_LOGGER_STATS_TIME = 1;
const int FIELD_ID_LOGGER_STATS_ERROR_CODE = 2;
@@ -248,6 +250,11 @@
mAnomalyAlarmRegisteredStats++;
}
+void StatsdStats::noteRegisteredPeriodicAlarmChanged() {
+ lock_guard<std::mutex> lock(mLock);
+ mPeriodicAlarmRegisteredStats++;
+}
+
void StatsdStats::updateMinPullIntervalSec(int pullAtomId, long intervalSec) {
lock_guard<std::mutex> lock(mLock);
mPulledAtomStats[pullAtomId].minPullIntervalSec = intervalSec;
@@ -297,6 +304,7 @@
std::fill(mPushedAtomStats.begin(), mPushedAtomStats.end(), 0);
mAlertStats.clear();
mAnomalyAlarmRegisteredStats = 0;
+ mPeriodicAlarmRegisteredStats = 0;
mMatcherStats.clear();
mLoggerErrors.clear();
for (auto& config : mConfigStats) {
@@ -367,7 +375,7 @@
fprintf(out, "%lu Config in icebox: \n", (unsigned long)mIceBox.size());
for (const auto& configStats : mIceBox) {
fprintf(out,
- "Config {%d-%lld}: creation=%d, deletion=%d, #metric=%d, #condition=%d, "
+ "Config {%d_%lld}: creation=%d, deletion=%d, #metric=%d, #condition=%d, "
"#matcher=%d, #alert=%d, valid=%d\n",
configStats.uid(), (long long)configStats.id(), configStats.creation_time_sec(),
configStats.deletion_time_sec(), configStats.metric_count(),
@@ -462,6 +470,11 @@
fprintf(out, "Anomaly alarm registrations: %d\n", mAnomalyAlarmRegisteredStats);
}
+ if (mPeriodicAlarmRegisteredStats > 0) {
+ fprintf(out, "********SubscriberAlarmStats stats***********\n");
+ fprintf(out, "Subscriber alarm registrations: %d\n", mPeriodicAlarmRegisteredStats);
+ }
+
fprintf(out,
"UID map stats: bytes=%d, snapshots=%d, changes=%d, snapshots lost=%d, changes "
"lost=%d\n",
@@ -531,6 +544,13 @@
proto.end(token);
}
+ if (mPeriodicAlarmRegisteredStats > 0) {
+ long long token = proto.start(FIELD_TYPE_MESSAGE | FIELD_ID_SUBSCRIBER_ALARM_STATS);
+ proto.write(FIELD_TYPE_INT32 | FIELD_ID_SUBSCRIBER_ALARMS_REGISTERED,
+ mPeriodicAlarmRegisteredStats);
+ proto.end(token);
+ }
+
const int numBytes = mUidMapStats.ByteSize();
vector<char> buffer(numBytes);
mUidMapStats.SerializeToArray(&buffer[0], numBytes);
@@ -566,4 +586,4 @@
} // namespace statsd
} // namespace os
-} // namespace android
\ No newline at end of file
+} // namespace android
diff --git a/cmds/statsd/src/guardrail/StatsdStats.h b/cmds/statsd/src/guardrail/StatsdStats.h
index 8c16e4e..24ac688 100644
--- a/cmds/statsd/src/guardrail/StatsdStats.h
+++ b/cmds/statsd/src/guardrail/StatsdStats.h
@@ -170,6 +170,11 @@
void noteRegisteredAnomalyAlarmChanged();
/**
+ * Report that statsd modified the periodic alarm registered with StatsCompanionService.
+ */
+ void noteRegisteredPeriodicAlarmChanged();
+
+ /**
* Records the number of snapshot and delta entries that are being dropped from the uid map.
*/
void noteUidMapDropped(int snapshots, int deltas);
@@ -264,6 +269,9 @@
// StatsCompanionService.
int mAnomalyAlarmRegisteredStats = 0;
+ // Stores the number of times statsd registers the periodic alarm changes
+ int mPeriodicAlarmRegisteredStats = 0;
+
// Stores the number of times an anomaly detection alert has been declared
// (per config, per alert name). The map size is capped by kMaxConfigCount.
std::map<const ConfigKey, std::map<const int64_t, int>> mAlertStats;
diff --git a/cmds/statsd/src/matchers/matcher_util.cpp b/cmds/statsd/src/matchers/matcher_util.cpp
index 4612009..5d5e64e 100644
--- a/cmds/statsd/src/matchers/matcher_util.cpp
+++ b/cmds/statsd/src/matchers/matcher_util.cpp
@@ -87,6 +87,10 @@
const string& str_match) {
if (isAttributionUidField(field, value)) {
int uid = value.int_value;
+ auto aidIt = UidMap::sAidToUidMapping.find(str_match);
+ if (aidIt != UidMap::sAidToUidMapping.end()) {
+ return ((int)aidIt->second) == uid;
+ }
std::set<string> packageNames = uidMap.getAppNamesFromUid(uid, true /* normalize*/);
return packageNames.find(str_match) != packageNames.end();
} else if (value.getType() == STRING) {
@@ -207,6 +211,9 @@
}
return false;
}
+ // Finally, we get to the point of real value matching.
+ // If the field matcher ends with ANY, then we have [start, end) range > 1.
+ // In the following, we should return true, when ANY of the values matches.
case FieldValueMatcher::ValueMatcherCase::kEqBool: {
for (int i = start; i < end; i++) {
if ((values[i].mValue.getType() == INT &&
@@ -225,9 +232,36 @@
return true;
}
}
- }
return false;
- case FieldValueMatcher::ValueMatcherCase::kEqInt:
+ }
+ case FieldValueMatcher::ValueMatcherCase::kNeqAllString: {
+ const auto& str_list = matcher.neq_all_string();
+ for (int i = start; i < end; i++) {
+ bool notEqAll = true;
+ for (const auto& str : str_list.str_value()) {
+ if (tryMatchString(uidMap, values[i].mField, values[i].mValue, str)) {
+ notEqAll = false;
+ break;
+ }
+ }
+ if (notEqAll) {
+ return true;
+ }
+ }
+ return false;
+ }
+ case FieldValueMatcher::ValueMatcherCase::kEqAnyString: {
+ const auto& str_list = matcher.eq_any_string();
+ for (int i = start; i < end; i++) {
+ for (const auto& str : str_list.str_value()) {
+ if (tryMatchString(uidMap, values[i].mField, values[i].mValue, str)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+ case FieldValueMatcher::ValueMatcherCase::kEqInt: {
for (int i = start; i < end; i++) {
if (values[i].mValue.getType() == INT &&
(matcher.eq_int() == values[i].mValue.int_value)) {
@@ -240,7 +274,8 @@
}
}
return false;
- case FieldValueMatcher::ValueMatcherCase::kLtInt:
+ }
+ case FieldValueMatcher::ValueMatcherCase::kLtInt: {
for (int i = start; i < end; i++) {
if (values[i].mValue.getType() == INT &&
(values[i].mValue.int_value < matcher.lt_int())) {
@@ -253,7 +288,8 @@
}
}
return false;
- case FieldValueMatcher::ValueMatcherCase::kGtInt:
+ }
+ case FieldValueMatcher::ValueMatcherCase::kGtInt: {
for (int i = start; i < end; i++) {
if (values[i].mValue.getType() == INT &&
(values[i].mValue.int_value > matcher.gt_int())) {
@@ -266,7 +302,8 @@
}
}
return false;
- case FieldValueMatcher::ValueMatcherCase::kLtFloat:
+ }
+ case FieldValueMatcher::ValueMatcherCase::kLtFloat: {
for (int i = start; i < end; i++) {
if (values[i].mValue.getType() == FLOAT &&
(values[i].mValue.float_value < matcher.lt_float())) {
@@ -274,7 +311,8 @@
}
}
return false;
- case FieldValueMatcher::ValueMatcherCase::kGtFloat:
+ }
+ case FieldValueMatcher::ValueMatcherCase::kGtFloat: {
for (int i = start; i < end; i++) {
if (values[i].mValue.getType() == FLOAT &&
(values[i].mValue.float_value > matcher.gt_float())) {
@@ -282,7 +320,8 @@
}
}
return false;
- case FieldValueMatcher::ValueMatcherCase::kLteInt:
+ }
+ case FieldValueMatcher::ValueMatcherCase::kLteInt: {
for (int i = start; i < end; i++) {
if (values[i].mValue.getType() == INT &&
(values[i].mValue.int_value <= matcher.lte_int())) {
@@ -295,7 +334,8 @@
}
}
return false;
- case FieldValueMatcher::ValueMatcherCase::kGteInt:
+ }
+ case FieldValueMatcher::ValueMatcherCase::kGteInt: {
for (int i = start; i < end; i++) {
if (values[i].mValue.getType() == INT &&
(values[i].mValue.int_value >= matcher.gte_int())) {
@@ -308,6 +348,7 @@
}
}
return false;
+ }
default:
return false;
}
diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.cpp b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
index 9c65371..80329c3 100644
--- a/cmds/statsd/src/metrics/DurationMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
@@ -101,6 +101,18 @@
}
mConditionSliced = (metric.links().size() > 0) || (mDimensionsInCondition.size() > 0);
+ if (mDimensionsInWhat.size() == mInternalDimensions.size()) {
+ bool mUseWhatDimensionAsInternalDimension = true;
+ for (size_t i = 0; mUseWhatDimensionAsInternalDimension &&
+ i < mDimensionsInWhat.size(); ++i) {
+ if (mDimensionsInWhat[i] != mInternalDimensions[i]) {
+ mUseWhatDimensionAsInternalDimension = false;
+ }
+ }
+ } else {
+ mUseWhatDimensionAsInternalDimension = false;
+ }
+
VLOG("metric %lld created. bucket size %lld start_time: %lld", (long long)metric.id(),
(long long)mBucketSizeNs, (long long)mStartTimeNs);
}
@@ -109,9 +121,11 @@
VLOG("~DurationMetric() called");
}
-sp<AnomalyTracker> DurationMetricProducer::addAnomalyTracker(const Alert &alert) {
+sp<AnomalyTracker> DurationMetricProducer::addAnomalyTracker(
+ const Alert &alert, const sp<AlarmMonitor>& anomalyAlarmMonitor) {
std::lock_guard<std::mutex> lock(mMutex);
- sp<DurationAnomalyTracker> anomalyTracker = new DurationAnomalyTracker(alert, mConfigKey);
+ sp<DurationAnomalyTracker> anomalyTracker =
+ new DurationAnomalyTracker(alert, mConfigKey, anomalyAlarmMonitor);
if (anomalyTracker != nullptr) {
mAnomalyTrackers.push_back(anomalyTracker);
}
@@ -139,29 +153,56 @@
flushIfNeededLocked(eventTime);
// Now for each of the on-going event, check if the condition has changed for them.
- for (auto& pair : mCurrentSlicedDurationTrackerMap) {
- pair.second->onSlicedConditionMayChange(eventTime);
+ for (auto& whatIt : mCurrentSlicedDurationTrackerMap) {
+ for (auto& pair : whatIt.second) {
+ pair.second->onSlicedConditionMayChange(eventTime);
+ }
}
-
- std::unordered_set<HashableDimensionKey> conditionDimensionsKeySet;
- mWizard->getMetConditionDimension(mConditionTrackerIndex, mDimensionsInCondition,
- &conditionDimensionsKeySet);
-
- for (auto& pair : mCurrentSlicedDurationTrackerMap) {
- conditionDimensionsKeySet.erase(pair.first.getDimensionKeyInCondition());
+ if (mDimensionsInCondition.empty()) {
+ return;
}
- std::unordered_set<MetricDimensionKey> newKeys;
- for (const auto& conditionDimensionsKey : conditionDimensionsKeySet) {
- for (auto& pair : mCurrentSlicedDurationTrackerMap) {
- auto newKey =
- MetricDimensionKey(pair.first.getDimensionKeyInWhat(), conditionDimensionsKey);
- if (newKeys.find(newKey) == newKeys.end()) {
- mCurrentSlicedDurationTrackerMap[newKey] = pair.second->clone(eventTime);
- mCurrentSlicedDurationTrackerMap[newKey]->setEventKey(newKey);
- mCurrentSlicedDurationTrackerMap[newKey]->onSlicedConditionMayChange(eventTime);
+
+ if (mMetric2ConditionLinks.empty()) {
+ std::unordered_set<HashableDimensionKey> conditionDimensionsKeySet;
+ mWizard->getMetConditionDimension(mConditionTrackerIndex, mDimensionsInCondition,
+ &conditionDimensionsKeySet);
+ for (const auto& whatIt : mCurrentSlicedDurationTrackerMap) {
+ for (const auto& pair : whatIt.second) {
+ conditionDimensionsKeySet.erase(pair.first);
}
- newKeys.insert(newKey);
+ }
+ for (const auto& conditionDimension : conditionDimensionsKeySet) {
+ for (auto& whatIt : mCurrentSlicedDurationTrackerMap) {
+ if (!whatIt.second.empty()) {
+ unique_ptr<DurationTracker> newTracker =
+ whatIt.second.begin()->second->clone(eventTime);
+ newTracker->setEventKey(MetricDimensionKey(whatIt.first, conditionDimension));
+ newTracker->onSlicedConditionMayChange(eventTime);
+ whatIt.second[conditionDimension] = std::move(newTracker);
+ }
+ }
+ }
+ } else {
+ for (auto& whatIt : mCurrentSlicedDurationTrackerMap) {
+ ConditionKey conditionKey;
+ for (const auto& link : mMetric2ConditionLinks) {
+ getDimensionForCondition(whatIt.first.getValues(), link,
+ &conditionKey[link.conditionId]);
+ }
+ std::unordered_set<HashableDimensionKey> conditionDimensionsKeys;
+ mWizard->query(mConditionTrackerIndex, conditionKey, mDimensionsInCondition,
+ &conditionDimensionsKeys);
+
+ for (const auto& conditionDimension : conditionDimensionsKeys) {
+ if (!whatIt.second.empty() &&
+ whatIt.second.find(conditionDimension) == whatIt.second.end()) {
+ auto newTracker = whatIt.second.begin()->second->clone(eventTime);
+ newTracker->setEventKey(MetricDimensionKey(whatIt.first, conditionDimension));
+ newTracker->onSlicedConditionMayChange(eventTime);
+ whatIt.second[conditionDimension] = std::move(newTracker);
+ }
+ }
}
}
}
@@ -173,8 +214,10 @@
flushIfNeededLocked(eventTime);
// TODO: need to populate the condition change time from the event which triggers the condition
// change, instead of using current time.
- for (auto& pair : mCurrentSlicedDurationTrackerMap) {
- pair.second->onConditionChanged(conditionMet, eventTime);
+ for (auto& whatIt : mCurrentSlicedDurationTrackerMap) {
+ for (auto& pair : whatIt.second) {
+ pair.second->onConditionChanged(conditionMet, eventTime);
+ }
}
}
@@ -239,13 +282,20 @@
return;
}
VLOG("flushing...........");
- for (auto it = mCurrentSlicedDurationTrackerMap.begin();
- it != mCurrentSlicedDurationTrackerMap.end();) {
- if (it->second->flushIfNeeded(eventTimeNs, &mPastBuckets)) {
- VLOG("erase bucket for key %s", it->first.c_str());
- it = mCurrentSlicedDurationTrackerMap.erase(it);
+ for (auto whatIt = mCurrentSlicedDurationTrackerMap.begin();
+ whatIt != mCurrentSlicedDurationTrackerMap.end();) {
+ for (auto it = whatIt->second.begin(); it != whatIt->second.end();) {
+ if (it->second->flushIfNeeded(eventTimeNs, &mPastBuckets)) {
+ VLOG("erase bucket for key %s %s", whatIt->first.c_str(), it->first.c_str());
+ it = whatIt->second.erase(it);
+ } else {
+ ++it;
+ }
+ }
+ if (whatIt->second.empty()) {
+ whatIt = mCurrentSlicedDurationTrackerMap.erase(whatIt);
} else {
- ++it;
+ whatIt++;
}
}
@@ -255,13 +305,20 @@
}
void DurationMetricProducer::flushCurrentBucketLocked(const uint64_t& eventTimeNs) {
- for (auto it = mCurrentSlicedDurationTrackerMap.begin();
- it != mCurrentSlicedDurationTrackerMap.end();) {
- if (it->second->flushCurrentBucket(eventTimeNs, &mPastBuckets)) {
- VLOG("erase bucket for key %s", it->first.c_str());
- it = mCurrentSlicedDurationTrackerMap.erase(it);
+ for (auto whatIt = mCurrentSlicedDurationTrackerMap.begin();
+ whatIt != mCurrentSlicedDurationTrackerMap.end();) {
+ for (auto it = whatIt->second.begin(); it != whatIt->second.end();) {
+ if (it->second->flushCurrentBucket(eventTimeNs, &mPastBuckets)) {
+ VLOG("erase bucket for key %s %s", whatIt->first.c_str(), it->first.c_str());
+ it = whatIt->second.erase(it);
+ } else {
+ ++it;
+ }
+ }
+ if (whatIt->second.empty()) {
+ whatIt = mCurrentSlicedDurationTrackerMap.erase(whatIt);
} else {
- ++it;
+ whatIt++;
}
}
}
@@ -274,18 +331,16 @@
fprintf(out, "DurationMetric %lld dimension size %lu\n", (long long)mMetricId,
(unsigned long)mCurrentSlicedDurationTrackerMap.size());
if (verbose) {
- for (const auto& slice : mCurrentSlicedDurationTrackerMap) {
- fprintf(out, "\t%s\n", slice.first.c_str());
- slice.second->dumpStates(out, verbose);
+ for (const auto& whatIt : mCurrentSlicedDurationTrackerMap) {
+ for (const auto& slice : whatIt.second) {
+ fprintf(out, "\t%s\t%s\n", whatIt.first.c_str(), slice.first.c_str());
+ slice.second->dumpStates(out, verbose);
+ }
}
}
}
bool DurationMetricProducer::hitGuardRailLocked(const MetricDimensionKey& newKey) {
- // the key is not new, we are good.
- if (mCurrentSlicedDurationTrackerMap.find(newKey) != mCurrentSlicedDurationTrackerMap.end()) {
- return false;
- }
// 1. Report the tuple count if the tuple count > soft limit
if (mCurrentSlicedDurationTrackerMap.size() > StatsdStats::kDimensionKeySizeSoftLimit - 1) {
size_t newTupleCount = mCurrentSlicedDurationTrackerMap.size() + 1;
@@ -300,49 +355,162 @@
return false;
}
-void DurationMetricProducer::onMatchedLogEventInternalLocked(
- const size_t matcherIndex, const MetricDimensionKey& eventKey,
- const ConditionKey& conditionKeys, bool condition,
- const LogEvent& event) {
- flushIfNeededLocked(event.GetElapsedTimestampNs());
+void DurationMetricProducer::handleStartEvent(const MetricDimensionKey& eventKey,
+ const ConditionKey& conditionKeys,
+ bool condition, const LogEvent& event) {
+ const auto& whatKey = eventKey.getDimensionKeyInWhat();
+ const auto& condKey = eventKey.getDimensionKeyInCondition();
- if (matcherIndex == mStopAllIndex) {
- for (auto& pair : mCurrentSlicedDurationTrackerMap) {
- pair.second->noteStopAll(event.GetElapsedTimestampNs());
- }
- return;
- }
-
- if (mCurrentSlicedDurationTrackerMap.find(eventKey) == mCurrentSlicedDurationTrackerMap.end()) {
+ auto whatIt = mCurrentSlicedDurationTrackerMap.find(whatKey);
+ if (whatIt == mCurrentSlicedDurationTrackerMap.end()) {
if (hitGuardRailLocked(eventKey)) {
return;
}
- mCurrentSlicedDurationTrackerMap[eventKey] = createDurationTracker(eventKey);
+ mCurrentSlicedDurationTrackerMap[whatKey][condKey] = createDurationTracker(eventKey);
+ } else {
+ if (whatIt->second.find(condKey) == whatIt->second.end()) {
+ if (hitGuardRailLocked(eventKey)) {
+ return;
+ }
+ mCurrentSlicedDurationTrackerMap[whatKey][condKey] = createDurationTracker(eventKey);
+ }
}
- auto it = mCurrentSlicedDurationTrackerMap.find(eventKey);
+ auto it = mCurrentSlicedDurationTrackerMap.find(whatKey)->second.find(condKey);
+ if (mUseWhatDimensionAsInternalDimension) {
+ it->second->noteStart(whatKey, condition,
+ event.GetElapsedTimestampNs(), conditionKeys);
+ return;
+ }
std::vector<HashableDimensionKey> values;
filterValues(mInternalDimensions, event.getValues(), &values);
if (values.empty()) {
- if (matcherIndex == mStartIndex) {
- it->second->noteStart(DEFAULT_DIMENSION_KEY, condition,
- event.GetElapsedTimestampNs(), conditionKeys);
- } else if (matcherIndex == mStopIndex) {
- it->second->noteStop(DEFAULT_DIMENSION_KEY, event.GetElapsedTimestampNs(), false);
- }
+ it->second->noteStart(DEFAULT_DIMENSION_KEY, condition,
+ event.GetElapsedTimestampNs(), conditionKeys);
} else {
for (const auto& value : values) {
- if (matcherIndex == mStartIndex) {
- it->second->noteStart(value, condition, event.GetElapsedTimestampNs(), conditionKeys);
- } else if (matcherIndex == mStopIndex) {
- it->second->noteStop(value, event.GetElapsedTimestampNs(), false);
- }
+ it->second->noteStart(value, condition, event.GetElapsedTimestampNs(), conditionKeys);
}
}
}
+void DurationMetricProducer::onMatchedLogEventInternalLocked(
+ const size_t matcherIndex, const MetricDimensionKey& eventKey,
+ const ConditionKey& conditionKeys, bool condition,
+ const LogEvent& event) {
+ ALOGW("Not used in duration tracker.");
+}
+
+void DurationMetricProducer::onMatchedLogEventLocked(const size_t matcherIndex,
+ const LogEvent& event) {
+ uint64_t eventTimeNs = event.GetElapsedTimestampNs();
+ if (eventTimeNs < mStartTimeNs) {
+ return;
+ }
+
+ flushIfNeededLocked(event.GetElapsedTimestampNs());
+
+ // Handles Stopall events.
+ if (matcherIndex == mStopAllIndex) {
+ for (auto& whatIt : mCurrentSlicedDurationTrackerMap) {
+ for (auto& pair : whatIt.second) {
+ pair.second->noteStopAll(event.GetElapsedTimestampNs());
+ }
+ }
+ return;
+ }
+
+ vector<HashableDimensionKey> dimensionInWhatValues;
+ if (!mDimensionsInWhat.empty()) {
+ filterValues(mDimensionsInWhat, event.getValues(), &dimensionInWhatValues);
+ } else {
+ dimensionInWhatValues.push_back(DEFAULT_DIMENSION_KEY);
+ }
+
+ // Handles Stop events.
+ if (matcherIndex == mStopIndex) {
+ if (mUseWhatDimensionAsInternalDimension) {
+ for (const HashableDimensionKey& whatKey : dimensionInWhatValues) {
+ auto whatIt = mCurrentSlicedDurationTrackerMap.find(whatKey);
+ if (whatIt != mCurrentSlicedDurationTrackerMap.end()) {
+ for (const auto& condIt : whatIt->second) {
+ condIt.second->noteStop(whatKey, event.GetElapsedTimestampNs(), false);
+ }
+ }
+ }
+ return;
+ }
+
+ std::vector<HashableDimensionKey> internalDimensionKeys;
+ filterValues(mInternalDimensions, event.getValues(), &internalDimensionKeys);
+ if (internalDimensionKeys.empty()) {
+ internalDimensionKeys.push_back(DEFAULT_DIMENSION_KEY);
+ }
+ for (const HashableDimensionKey& whatDimension : dimensionInWhatValues) {
+ auto whatIt = mCurrentSlicedDurationTrackerMap.find(whatDimension);
+ if (whatIt != mCurrentSlicedDurationTrackerMap.end()) {
+ for (const auto& condIt : whatIt->second) {
+ for (const auto& internalDimensionKey : internalDimensionKeys) {
+ condIt.second->noteStop(
+ internalDimensionKey, event.GetElapsedTimestampNs(), false);
+ }
+ }
+ }
+ }
+ return;
+ }
+
+ bool condition;
+ ConditionKey conditionKey;
+ std::unordered_set<HashableDimensionKey> dimensionKeysInCondition;
+ if (mConditionSliced) {
+ for (const auto& link : mMetric2ConditionLinks) {
+ getDimensionForCondition(event.getValues(), link, &conditionKey[link.conditionId]);
+ }
+
+ auto conditionState =
+ mWizard->query(mConditionTrackerIndex, conditionKey, mDimensionsInCondition,
+ &dimensionKeysInCondition);
+ condition = (conditionState == ConditionState::kTrue);
+ if (mDimensionsInCondition.empty() && condition) {
+ dimensionKeysInCondition.insert(DEFAULT_DIMENSION_KEY);
+ }
+ } else {
+ condition = mCondition;
+ if (condition) {
+ dimensionKeysInCondition.insert(DEFAULT_DIMENSION_KEY);
+ }
+ }
+
+ for (const auto& whatDimension : dimensionInWhatValues) {
+ auto whatIt = mCurrentSlicedDurationTrackerMap.find(whatDimension);
+ // If the what dimension is already there, we should update all the trackers even
+ // the condition is false.
+ if (whatIt != mCurrentSlicedDurationTrackerMap.end()) {
+ for (const auto& condIt : whatIt->second) {
+ const bool cond = dimensionKeysInCondition.find(condIt.first) !=
+ dimensionKeysInCondition.end();
+ handleStartEvent(MetricDimensionKey(whatDimension, condIt.first),
+ conditionKey, cond, event);
+ }
+ } else {
+ // If it is a new what dimension key, we need to handle the start events for all current
+ // condition dimensions.
+ for (const auto& conditionDimension : dimensionKeysInCondition) {
+ handleStartEvent(MetricDimensionKey(whatDimension, conditionDimension),
+ conditionKey, condition, event);
+ }
+ }
+ if (dimensionKeysInCondition.empty()) {
+ handleStartEvent(MetricDimensionKey(whatDimension, DEFAULT_DIMENSION_KEY),
+ conditionKey, condition, event);
+ }
+ }
+}
+
+
size_t DurationMetricProducer::byteSizeLocked() const {
size_t totalSize = 0;
for (const auto& pair : mPastBuckets) {
diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.h b/cmds/statsd/src/metrics/DurationMetricProducer.h
index 5f29281..73d074f 100644
--- a/cmds/statsd/src/metrics/DurationMetricProducer.h
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.h
@@ -46,15 +46,20 @@
virtual ~DurationMetricProducer();
- sp<AnomalyTracker> addAnomalyTracker(const Alert &alert) override;
+ sp<AnomalyTracker> addAnomalyTracker(const Alert &alert,
+ const sp<AlarmMonitor>& anomalyAlarmMonitor) override;
protected:
+ void onMatchedLogEventLocked(const size_t matcherIndex, const LogEvent& event) override;
void onMatchedLogEventInternalLocked(
const size_t matcherIndex, const MetricDimensionKey& eventKey,
const ConditionKey& conditionKeys, bool condition,
const LogEvent& event) override;
private:
+ void handleStartEvent(const MetricDimensionKey& eventKey, const ConditionKey& conditionKeys,
+ bool condition, const LogEvent& event);
+
void onDumpReportLocked(const uint64_t dumpTimeNs,
android::util::ProtoOutputStream* protoOutput) override;
@@ -91,12 +96,16 @@
// The dimension from the atom predicate. e.g., uid, wakelock name.
vector<Matcher> mInternalDimensions;
+ // This boolean is true iff When mInternalDimensions == mDimensionsInWhat
+ bool mUseWhatDimensionAsInternalDimension;
+
// Save the past buckets and we can clear when the StatsLogReport is dumped.
// TODO: Add a lock to mPastBuckets.
std::unordered_map<MetricDimensionKey, std::vector<DurationBucket>> mPastBuckets;
- // The current bucket.
- std::unordered_map<MetricDimensionKey, std::unique_ptr<DurationTracker>>
+ // The duration trackers in the current bucket.
+ std::unordered_map<HashableDimensionKey,
+ std::unordered_map<HashableDimensionKey, std::unique_ptr<DurationTracker>>>
mCurrentSlicedDurationTrackerMap;
// Helper function to create a duration tracker given the metric aggregation type.
diff --git a/cmds/statsd/src/metrics/MetricProducer.cpp b/cmds/statsd/src/metrics/MetricProducer.cpp
index f3307dc..18694a1 100644
--- a/cmds/statsd/src/metrics/MetricProducer.cpp
+++ b/cmds/statsd/src/metrics/MetricProducer.cpp
@@ -37,7 +37,7 @@
std::unordered_set<HashableDimensionKey> dimensionKeysInCondition;
if (mConditionSliced) {
for (const auto& link : mMetric2ConditionLinks) {
- getDimensionForCondition(event, link, &conditionKey[link.conditionId]);
+ getDimensionForCondition(event.getValues(), link, &conditionKey[link.conditionId]);
}
auto conditionState =
@@ -48,37 +48,30 @@
condition = mCondition;
}
- vector<HashableDimensionKey> dimensionInWhatValues;
- if (mDimensionsInWhat.size() > 0) {
- filterValues(mDimensionsInWhat, event.getValues(), &dimensionInWhatValues);
+ if (mDimensionsInCondition.empty() && condition) {
+ dimensionKeysInCondition.insert(DEFAULT_DIMENSION_KEY);
}
- if (dimensionInWhatValues.empty() && dimensionKeysInCondition.empty()) {
- onMatchedLogEventInternalLocked(
- matcherIndex, DEFAULT_METRIC_DIMENSION_KEY, conditionKey, condition, event);
- } else if (dimensionKeysInCondition.empty()) {
- for (const HashableDimensionKey& whatValue : dimensionInWhatValues) {
- onMatchedLogEventInternalLocked(matcherIndex,
- MetricDimensionKey(whatValue, DEFAULT_DIMENSION_KEY),
- conditionKey, condition, event);
- }
- } else if (dimensionInWhatValues.empty()) {
+ vector<HashableDimensionKey> dimensionInWhatValues;
+ if (!mDimensionsInWhat.empty()) {
+ filterValues(mDimensionsInWhat, event.getValues(), &dimensionInWhatValues);
+ } else {
+ dimensionInWhatValues.push_back(DEFAULT_DIMENSION_KEY);
+ }
+
+ for (const auto& whatDimension : dimensionInWhatValues) {
for (const auto& conditionDimensionKey : dimensionKeysInCondition) {
onMatchedLogEventInternalLocked(
- matcherIndex,
- MetricDimensionKey(DEFAULT_DIMENSION_KEY, conditionDimensionKey),
- conditionKey, condition, event);
+ matcherIndex, MetricDimensionKey(whatDimension, conditionDimensionKey),
+ conditionKey, condition, event);
}
- } else {
- for (const auto& whatValue : dimensionInWhatValues) {
- for (const auto& conditionDimensionKey : dimensionKeysInCondition) {
- onMatchedLogEventInternalLocked(
- matcherIndex, MetricDimensionKey(whatValue, conditionDimensionKey),
- conditionKey, condition, event);
- }
+ if (dimensionKeysInCondition.empty()) {
+ onMatchedLogEventInternalLocked(
+ matcherIndex, MetricDimensionKey(whatDimension, DEFAULT_DIMENSION_KEY),
+ conditionKey, condition, event);
}
}
-}
+ }
} // namespace statsd
} // namespace os
diff --git a/cmds/statsd/src/metrics/MetricProducer.h b/cmds/statsd/src/metrics/MetricProducer.h
index 8663e5e..2bf6241 100644
--- a/cmds/statsd/src/metrics/MetricProducer.h
+++ b/cmds/statsd/src/metrics/MetricProducer.h
@@ -124,7 +124,8 @@
}
/* If alert is valid, adds an AnomalyTracker and returns it. If invalid, returns nullptr. */
- virtual sp<AnomalyTracker> addAnomalyTracker(const Alert &alert) {
+ virtual sp<AnomalyTracker> addAnomalyTracker(const Alert &alert,
+ const sp<AlarmMonitor>& anomalyAlarmMonitor) {
std::lock_guard<std::mutex> lock(mMutex);
sp<AnomalyTracker> anomalyTracker = new AnomalyTracker(alert, mConfigKey);
if (anomalyTracker != nullptr) {
@@ -232,7 +233,7 @@
const LogEvent& event) = 0;
// Consume the parsed stats log entry that already matched the "what" of the metric.
- void onMatchedLogEventLocked(const size_t matcherIndex, const LogEvent& event);
+ virtual void onMatchedLogEventLocked(const size_t matcherIndex, const LogEvent& event);
mutable std::mutex mMutex;
};
diff --git a/cmds/statsd/src/metrics/MetricsManager.cpp b/cmds/statsd/src/metrics/MetricsManager.cpp
index e75b710..4c8a7d8 100644
--- a/cmds/statsd/src/metrics/MetricsManager.cpp
+++ b/cmds/statsd/src/metrics/MetricsManager.cpp
@@ -49,13 +49,17 @@
const int FIELD_ID_METRICS = 1;
MetricsManager::MetricsManager(const ConfigKey& key, const StatsdConfig& config,
- const long timeBaseSec, sp<UidMap> uidMap)
+ const long timeBaseSec,
+ const sp<UidMap> &uidMap,
+ const sp<AlarmMonitor>& anomalyAlarmMonitor,
+ const sp<AlarmMonitor>& periodicAlarmMonitor)
: mConfigKey(key), mUidMap(uidMap), mLastReportTimeNs(timeBaseSec * NS_PER_SEC) {
mConfigValid =
- initStatsdConfig(key, config, *uidMap, timeBaseSec, mTagIds, mAllAtomMatchers,
- mAllConditionTrackers,
- mAllMetricProducers, mAllAnomalyTrackers, mConditionToMetricMap,
- mTrackerToMetricMap, mTrackerToConditionMap, mNoReportMetricIds);
+ initStatsdConfig(key, config, *uidMap, anomalyAlarmMonitor, periodicAlarmMonitor,
+ timeBaseSec, mTagIds, mAllAtomMatchers,
+ mAllConditionTrackers, mAllMetricProducers, mAllAnomalyTrackers,
+ mAllPeriodicAlarmTrackers, mConditionToMetricMap, mTrackerToMetricMap,
+ mTrackerToConditionMap, mNoReportMetricIds);
if (config.allowed_log_source_size() == 0) {
// TODO(b/70794411): uncomment the following line and remove the hard coded log source
@@ -198,31 +202,59 @@
// Uid is 3rd from last field and must match the caller's uid,
// unless that caller is statsd itself (statsd is allowed to spoof uids).
long appHookUid = event.GetLong(event.size()-2, &err);
+ if (err != NO_ERROR ) {
+ VLOG("APP_BREADCRUMB_REPORTED had error when parsing the uid");
+ return;
+ }
int32_t loggerUid = event.GetUid();
- if (err != NO_ERROR || (loggerUid != appHookUid && loggerUid != AID_STATSD)) {
- VLOG("AppHook has invalid uid: claimed %ld but caller is %d", appHookUid, loggerUid);
+ if (loggerUid != appHookUid && loggerUid != AID_STATSD) {
+ VLOG("APP_BREADCRUMB_REPORTED has invalid uid: claimed %ld but caller is %d",
+ appHookUid, loggerUid);
return;
}
// Label is 2nd from last field and must be from [0, 15].
long appHookLabel = event.GetLong(event.size()-1, &err);
- if (err != NO_ERROR || appHookLabel < 0 || appHookLabel > 15) {
- VLOG("AppHook does not have valid label %ld", appHookLabel);
+ if (err != NO_ERROR ) {
+ VLOG("APP_BREADCRUMB_REPORTED had error when parsing the label field");
+ return;
+ } else if (appHookLabel < 0 || appHookLabel > 15) {
+ VLOG("APP_BREADCRUMB_REPORTED does not have valid label %ld", appHookLabel);
return;
}
// The state must be from 0,3. This part of code must be manually updated.
long appHookState = event.GetLong(event.size(), &err);
- if (err != NO_ERROR || appHookState < 0 || appHookState > 3) {
- VLOG("AppHook does not have valid state %ld", appHookState);
+ if (err != NO_ERROR ) {
+ VLOG("APP_BREADCRUMB_REPORTED had error when parsing the state field");
+ return;
+ } else if (appHookState < 0 || appHookState > 3) {
+ VLOG("APP_BREADCRUMB_REPORTED does not have valid state %ld", appHookState);
return;
}
} else if (event.GetTagId() == android::util::DAVEY_OCCURRED) {
// Daveys can be logged from any app since they are logged in libs/hwui/JankTracker.cpp.
// Check that the davey duration is reasonable. Max length check is for privacy.
status_t err = NO_ERROR;
+
+ // Uid is the first field provided.
+ long jankUid = event.GetLong(1, &err);
+ if (err != NO_ERROR ) {
+ VLOG("Davey occurred had error when parsing the uid");
+ return;
+ }
+ int32_t loggerUid = event.GetUid();
+ if (loggerUid != jankUid && loggerUid != AID_STATSD) {
+ VLOG("DAVEY_OCCURRED has invalid uid: claimed %ld but caller is %d", jankUid,
+ loggerUid);
+ return;
+ }
+
long duration = event.GetLong(event.size(), &err);
- if (err != NO_ERROR || duration > 100000) {
+ if (err != NO_ERROR ) {
+ VLOG("Davey occurred had error when parsing the duration");
+ return;
+ } else if (duration > 100000) {
VLOG("Davey duration is unreasonably long: %ld", duration);
return;
}
@@ -312,16 +344,19 @@
}
}
-void MetricsManager::onAnomalyAlarmFired(const uint64_t timestampNs,
- unordered_set<sp<const AnomalyAlarm>, SpHash<AnomalyAlarm>>& anomalySet) {
+void MetricsManager::onAnomalyAlarmFired(
+ const uint64_t timestampNs,
+ unordered_set<sp<const InternalAlarm>, SpHash<InternalAlarm>>& alarmSet) {
for (const auto& itr : mAllAnomalyTrackers) {
- itr->informAlarmsFired(timestampNs, anomalySet);
+ itr->informAlarmsFired(timestampNs, alarmSet);
}
}
-void MetricsManager::setAnomalyMonitor(const sp<AnomalyMonitor>& anomalyMonitor) {
- for (auto& itr : mAllAnomalyTrackers) {
- itr->setAnomalyMonitor(anomalyMonitor);
+void MetricsManager::onPeriodicAlarmFired(
+ const uint64_t timestampNs,
+ unordered_set<sp<const InternalAlarm>, SpHash<InternalAlarm>>& alarmSet) {
+ for (const auto& itr : mAllPeriodicAlarmTrackers) {
+ itr->informAlarmsFired(timestampNs, alarmSet);
}
}
diff --git a/cmds/statsd/src/metrics/MetricsManager.h b/cmds/statsd/src/metrics/MetricsManager.h
index d4f844f..b50ef4a 100644
--- a/cmds/statsd/src/metrics/MetricsManager.h
+++ b/cmds/statsd/src/metrics/MetricsManager.h
@@ -16,7 +16,8 @@
#pragma once
-#include "anomaly/AnomalyMonitor.h"
+#include "anomaly/AlarmMonitor.h"
+#include "anomaly/AlarmTracker.h"
#include "anomaly/AnomalyTracker.h"
#include "condition/ConditionTracker.h"
#include "config/ConfigKey.h"
@@ -36,7 +37,8 @@
class MetricsManager : public PackageInfoListener {
public:
MetricsManager(const ConfigKey& configKey, const StatsdConfig& config, const long timeBaseSec,
- sp<UidMap> uidMap);
+ const sp<UidMap>& uidMap, const sp<AlarmMonitor>& anomalyAlarmMonitor,
+ const sp<AlarmMonitor>& periodicAlarmMonitor);
virtual ~MetricsManager();
@@ -47,9 +49,11 @@
void onAnomalyAlarmFired(
const uint64_t timestampNs,
- unordered_set<sp<const AnomalyAlarm>, SpHash<AnomalyAlarm>>& anomalySet);
+ unordered_set<sp<const InternalAlarm>, SpHash<InternalAlarm>>& alarmSet);
- void setAnomalyMonitor(const sp<AnomalyMonitor>& anomalyMonitor);
+ void onPeriodicAlarmFired(
+ const uint64_t timestampNs,
+ unordered_set<sp<const InternalAlarm>, SpHash<InternalAlarm>>& alarmSet);
void notifyAppUpgrade(const uint64_t& eventTimeNs, const string& apk, const int uid,
const int64_t version) override;
@@ -120,6 +124,9 @@
// Hold all alert trackers.
std::vector<sp<AnomalyTracker>> mAllAnomalyTrackers;
+ // Hold all periodic alarm trackers.
+ std::vector<sp<AlarmTracker>> mAllPeriodicAlarmTrackers;
+
// To make the log processing more efficient, we want to do as much filtering as possible
// before we go into individual trackers and conditions to match.
diff --git a/cmds/statsd/src/metrics/metrics_manager_util.cpp b/cmds/statsd/src/metrics/metrics_manager_util.cpp
index 71e5c33..9912afa 100644
--- a/cmds/statsd/src/metrics/metrics_manager_util.cpp
+++ b/cmds/statsd/src/metrics/metrics_manager_util.cpp
@@ -17,16 +17,19 @@
#define DEBUG false // STOPSHIP if true
#include "Log.h"
+#include "metrics_manager_util.h"
+
#include "../condition/CombinationConditionTracker.h"
#include "../condition/SimpleConditionTracker.h"
#include "../external/StatsPullerManager.h"
#include "../matchers/CombinationLogMatchingTracker.h"
#include "../matchers/SimpleLogMatchingTracker.h"
-#include "CountMetricProducer.h"
-#include "DurationMetricProducer.h"
-#include "EventMetricProducer.h"
-#include "GaugeMetricProducer.h"
-#include "ValueMetricProducer.h"
+#include "../metrics/CountMetricProducer.h"
+#include "../metrics/DurationMetricProducer.h"
+#include "../metrics/EventMetricProducer.h"
+#include "../metrics/GaugeMetricProducer.h"
+#include "../metrics/ValueMetricProducer.h"
+
#include "stats_util.h"
using std::set;
@@ -494,6 +497,7 @@
bool initAlerts(const StatsdConfig& config,
const unordered_map<int64_t, int>& metricProducerMap,
+ const sp<AlarmMonitor>& anomalyAlarmMonitor,
vector<sp<MetricProducer>>& allMetricProducers,
vector<sp<AnomalyTracker>>& allAnomalyTrackers) {
unordered_map<int64_t, int> anomalyTrackerMap;
@@ -512,7 +516,7 @@
}
const int metricIndex = itr->second;
sp<MetricProducer> metric = allMetricProducers[metricIndex];
- sp<AnomalyTracker> anomalyTracker = metric->addAnomalyTracker(alert);
+ sp<AnomalyTracker> anomalyTracker = metric->addAnomalyTracker(alert, anomalyAlarmMonitor);
if (anomalyTracker == nullptr) {
// The ALOGW for this invalid alert was already displayed in addAnomalyTracker().
return false;
@@ -522,6 +526,9 @@
}
for (int i = 0; i < config.subscription_size(); ++i) {
const Subscription& subscription = config.subscription(i);
+ if (subscription.rule_type() != Subscription::ALERT) {
+ continue;
+ }
if (subscription.subscriber_information_case() ==
Subscription::SubscriberInformationCase::SUBSCRIBER_INFORMATION_NOT_SET) {
ALOGW("subscription \"%lld\" has no subscriber info.\"",
@@ -540,13 +547,60 @@
return true;
}
+bool initAlarms(const StatsdConfig& config, const ConfigKey& key,
+ const sp<AlarmMonitor>& periodicAlarmMonitor,
+ const long timeBaseSec,
+ vector<sp<AlarmTracker>>& allAlarmTrackers) {
+ unordered_map<int64_t, int> alarmTrackerMap;
+ uint64_t startMillis = (uint64_t)timeBaseSec * MS_PER_SEC;
+ for (int i = 0; i < config.alarm_size(); i++) {
+ const Alarm& alarm = config.alarm(i);
+ if (alarm.offset_millis() <= 0) {
+ ALOGW("Alarm offset_millis should be larger than 0.");
+ return false;
+ }
+ if (alarm.period_millis() <= 0) {
+ ALOGW("Alarm period_millis should be larger than 0.");
+ return false;
+ }
+ alarmTrackerMap.insert(std::make_pair(alarm.id(), allAlarmTrackers.size()));
+ allAlarmTrackers.push_back(
+ new AlarmTracker(startMillis, alarm, key, periodicAlarmMonitor));
+ }
+ for (int i = 0; i < config.subscription_size(); ++i) {
+ const Subscription& subscription = config.subscription(i);
+ if (subscription.rule_type() != Subscription::ALARM) {
+ continue;
+ }
+ if (subscription.subscriber_information_case() ==
+ Subscription::SubscriberInformationCase::SUBSCRIBER_INFORMATION_NOT_SET) {
+ ALOGW("subscription \"%lld\" has no subscriber info.\"",
+ (long long)subscription.id());
+ return false;
+ }
+ const auto& itr = alarmTrackerMap.find(subscription.rule_id());
+ if (itr == alarmTrackerMap.end()) {
+ ALOGW("subscription \"%lld\" has unknown rule id: \"%lld\"",
+ (long long)subscription.id(), (long long)subscription.rule_id());
+ return false;
+ }
+ const int trackerIndex = itr->second;
+ allAlarmTrackers[trackerIndex]->addSubscription(subscription);
+ }
+ return true;
+}
+
bool initStatsdConfig(const ConfigKey& key, const StatsdConfig& config,
const UidMap& uidMap,
- const long timeBaseSec, set<int>& allTagIds,
+ const sp<AlarmMonitor>& anomalyAlarmMonitor,
+ const sp<AlarmMonitor>& periodicAlarmMonitor,
+ const long timeBaseSec,
+ set<int>& allTagIds,
vector<sp<LogMatchingTracker>>& allAtomMatchers,
vector<sp<ConditionTracker>>& allConditionTrackers,
vector<sp<MetricProducer>>& allMetricProducers,
vector<sp<AnomalyTracker>>& allAnomalyTrackers,
+ vector<sp<AlarmTracker>>& allPeriodicAlarmTrackers,
unordered_map<int, std::vector<int>>& conditionToMetricMap,
unordered_map<int, std::vector<int>>& trackerToMetricMap,
unordered_map<int, std::vector<int>>& trackerToConditionMap,
@@ -573,10 +627,16 @@
ALOGE("initMetricProducers failed");
return false;
}
- if (!initAlerts(config, metricProducerMap, allMetricProducers, allAnomalyTrackers)) {
+ if (!initAlerts(config, metricProducerMap, anomalyAlarmMonitor, allMetricProducers,
+ allAnomalyTrackers)) {
ALOGE("initAlerts failed");
return false;
}
+ if (!initAlarms(config, key, periodicAlarmMonitor, timeBaseSec, allPeriodicAlarmTrackers)) {
+ ALOGE("initAlarms failed");
+ return false;
+ }
+
return true;
}
diff --git a/cmds/statsd/src/metrics/metrics_manager_util.h b/cmds/statsd/src/metrics/metrics_manager_util.h
index 4f19ada..edda53d 100644
--- a/cmds/statsd/src/metrics/metrics_manager_util.h
+++ b/cmds/statsd/src/metrics/metrics_manager_util.h
@@ -13,16 +13,19 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-#ifndef METRIC_UTIL_H
-#define METRIC_UTIL_H
+
+#pragma once
+
#include <memory>
#include <set>
#include <unordered_map>
#include <vector>
+#include "../anomaly/AlarmTracker.h"
#include "../condition/ConditionTracker.h"
#include "../external/StatsPullerManagerImpl.h"
#include "../matchers/LogMatchingTracker.h"
+#include "../metrics/MetricProducer.h"
namespace android {
namespace os {
@@ -93,11 +96,15 @@
// Parameters are the members of MetricsManager. See MetricsManager for declaration.
bool initStatsdConfig(const ConfigKey& key, const StatsdConfig& config,
const UidMap& uidMap,
- const long timeBaseSec, std::set<int>& allTagIds,
+ const sp<AlarmMonitor>& anomalyAlarmMonitor,
+ const sp<AlarmMonitor>& periodicAlarmMonitor,
+ const long timeBaseSec,
+ std::set<int>& allTagIds,
std::vector<sp<LogMatchingTracker>>& allAtomMatchers,
std::vector<sp<ConditionTracker>>& allConditionTrackers,
std::vector<sp<MetricProducer>>& allMetricProducers,
vector<sp<AnomalyTracker>>& allAnomalyTrackers,
+ vector<sp<AlarmTracker>>& allPeriodicAlarmTrackers,
std::unordered_map<int, std::vector<int>>& conditionToMetricMap,
std::unordered_map<int, std::vector<int>>& trackerToMetricMap,
std::unordered_map<int, std::vector<int>>& trackerToConditionMap,
@@ -106,4 +113,3 @@
} // namespace statsd
} // namespace os
} // namespace android
-#endif // METRIC_UTIL_H
diff --git a/cmds/statsd/src/stats_log.proto b/cmds/statsd/src/stats_log.proto
index 272e90b..269f25b 100644
--- a/cmds/statsd/src/stats_log.proto
+++ b/cmds/statsd/src/stats_log.proto
@@ -164,4 +164,4 @@
optional ConfigKey config_key = 1;
repeated ConfigMetricsReport reports = 2;
-}
\ No newline at end of file
+}
diff --git a/cmds/statsd/src/statsd_config.proto b/cmds/statsd/src/statsd_config.proto
index a313854..1e8aa12 100644
--- a/cmds/statsd/src/statsd_config.proto
+++ b/cmds/statsd/src/statsd_config.proto
@@ -74,6 +74,9 @@
int64 gte_int = 11;
MessageMatcher matches_tuple = 12;
+
+ StringListMatcher eq_any_string = 13;
+ StringListMatcher neq_all_string = 14;
}
}
@@ -81,6 +84,10 @@
repeated FieldValueMatcher field_value_matcher = 1;
}
+message StringListMatcher {
+ repeated string str_value = 1;
+}
+
enum LogicalOperation {
LOGICAL_OPERATION_UNSPECIFIED = 0;
AND = 1;
diff --git a/cmds/statsd/src/storage/StorageManager.cpp b/cmds/statsd/src/storage/StorageManager.cpp
index 6a1db72..781eced 100644
--- a/cmds/statsd/src/storage/StorageManager.cpp
+++ b/cmds/statsd/src/storage/StorageManager.cpp
@@ -195,6 +195,20 @@
}
}
+bool StorageManager::readFileToString(const char* file, string* content) {
+ int fd = open(file, O_RDONLY | O_CLOEXEC);
+ bool res = false;
+ if (fd != -1) {
+ if (android::base::ReadFdToString(fd, content)) {
+ res = true;
+ } else {
+ VLOG("Failed to read file %s\n", file);
+ }
+ close(fd);
+ }
+ return res;
+}
+
void StorageManager::readConfigFromDisk(map<ConfigKey, StatsdConfig>& configsMap) {
unique_ptr<DIR, decltype(&closedir)> dir(opendir(STATS_SERVICE_DIR), closedir);
if (dir == NULL) {
diff --git a/cmds/statsd/src/storage/StorageManager.h b/cmds/statsd/src/storage/StorageManager.h
index d319674..6c8ed0a 100644
--- a/cmds/statsd/src/storage/StorageManager.h
+++ b/cmds/statsd/src/storage/StorageManager.h
@@ -37,6 +37,11 @@
static void writeFile(const char* file, const void* buffer, int numBytes);
/**
+ * Reads the file content to the buffer.
+ */
+ static bool readFileToString(const char* file, string* content);
+
+ /**
* Deletes a single file given a file name.
*/
static void deleteFile(const char* file);
diff --git a/cmds/statsd/src/subscriber/IncidentdReporter.cpp b/cmds/statsd/src/subscriber/IncidentdReporter.cpp
index d9a8fc8..1c18f67 100644
--- a/cmds/statsd/src/subscriber/IncidentdReporter.cpp
+++ b/cmds/statsd/src/subscriber/IncidentdReporter.cpp
@@ -28,10 +28,10 @@
namespace os {
namespace statsd {
-bool GenerateIncidentReport(const IncidentdDetails& config, const Alert& alert,
+bool GenerateIncidentReport(const IncidentdDetails& config, const int64_t& rule_id,
const ConfigKey& configKey) {
if (config.section_size() == 0) {
- VLOG("The alert %lld contains zero section in config(%d,%lld)", alert.id(),
+ VLOG("The alert %lld contains zero section in config(%d,%lld)", (unsigned long long)rule_id,
configKey.GetUid(), (long long) configKey.GetId());
return false;
}
@@ -39,7 +39,7 @@
IncidentReportArgs incidentReport;
android::os::IncidentHeaderProto header;
- header.set_alert_id(alert.id());
+ header.set_alert_id(rule_id);
header.mutable_config_key()->set_uid(configKey.GetUid());
header.mutable_config_key()->set_id(configKey.GetId());
incidentReport.addHeader(header);
diff --git a/cmds/statsd/src/subscriber/IncidentdReporter.h b/cmds/statsd/src/subscriber/IncidentdReporter.h
index 229ed77..1b83fe2 100644
--- a/cmds/statsd/src/subscriber/IncidentdReporter.h
+++ b/cmds/statsd/src/subscriber/IncidentdReporter.h
@@ -26,7 +26,7 @@
/**
* Calls incidentd to trigger an incident report and put in dropbox for uploading.
*/
-bool GenerateIncidentReport(const IncidentdDetails& config, const Alert& alert,
+bool GenerateIncidentReport(const IncidentdDetails& config, const int64_t& rule_id,
const ConfigKey& configKey);
} // namespace statsd
diff --git a/cmds/statsd/tests/AnomalyMonitor_test.cpp b/cmds/statsd/tests/AlarmMonitor_test.cpp
similarity index 70%
rename from cmds/statsd/tests/AnomalyMonitor_test.cpp
rename to cmds/statsd/tests/AlarmMonitor_test.cpp
index 920ca08..1fccb35 100644
--- a/cmds/statsd/tests/AnomalyMonitor_test.cpp
+++ b/cmds/statsd/tests/AlarmMonitor_test.cpp
@@ -12,28 +12,29 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-#include "anomaly/AnomalyMonitor.h"
+#include "anomaly/AlarmMonitor.h"
#include <gtest/gtest.h>
using namespace android::os::statsd;
#ifdef __ANDROID__
-TEST(AnomalyMonitor, popSoonerThan) {
+TEST(AlarmMonitor, popSoonerThan) {
std::string emptyMetricId;
std::string emptyDimensionId;
- unordered_set<sp<const AnomalyAlarm>, SpHash<AnomalyAlarm>> set;
- AnomalyMonitor am(2);
+ unordered_set<sp<const InternalAlarm>, SpHash<InternalAlarm>> set;
+ AlarmMonitor am(2, [](const sp<IStatsCompanionService>&, int64_t){},
+ [](const sp<IStatsCompanionService>&){});
set = am.popSoonerThan(5);
EXPECT_TRUE(set.empty());
- sp<const AnomalyAlarm> a = new AnomalyAlarm{10};
- sp<const AnomalyAlarm> b = new AnomalyAlarm{20};
- sp<const AnomalyAlarm> c = new AnomalyAlarm{20};
- sp<const AnomalyAlarm> d = new AnomalyAlarm{30};
- sp<const AnomalyAlarm> e = new AnomalyAlarm{40};
- sp<const AnomalyAlarm> f = new AnomalyAlarm{50};
+ sp<const InternalAlarm> a = new InternalAlarm{10};
+ sp<const InternalAlarm> b = new InternalAlarm{20};
+ sp<const InternalAlarm> c = new InternalAlarm{20};
+ sp<const InternalAlarm> d = new InternalAlarm{30};
+ sp<const InternalAlarm> e = new InternalAlarm{40};
+ sp<const InternalAlarm> f = new InternalAlarm{50};
am.add(a);
am.add(b);
diff --git a/cmds/statsd/tests/ConfigManager_test.cpp b/cmds/statsd/tests/ConfigManager_test.cpp
index 62bdba4..90c3a2f 100644
--- a/cmds/statsd/tests/ConfigManager_test.cpp
+++ b/cmds/statsd/tests/ConfigManager_test.cpp
@@ -65,8 +65,12 @@
const int64_t testConfigId = 12345;
TEST(ConfigManagerTest, TestFakeConfig) {
- auto metricsManager = std::make_unique<MetricsManager>(ConfigKey(0, testConfigId),
- build_fake_config(), 1000, new UidMap());
+ auto metricsManager = std::make_unique<MetricsManager>(
+ ConfigKey(0, testConfigId), build_fake_config(), 1000, new UidMap(),
+ new AlarmMonitor(10, [](const sp<IStatsCompanionService>&, int64_t){},
+ [](const sp<IStatsCompanionService>&){}),
+ new AlarmMonitor(10, [](const sp<IStatsCompanionService>&, int64_t){},
+ [](const sp<IStatsCompanionService>&){}));
EXPECT_TRUE(metricsManager->isConfigValid());
}
diff --git a/cmds/statsd/tests/LogEntryMatcher_test.cpp b/cmds/statsd/tests/LogEntryMatcher_test.cpp
index 2320a9d..36c6e0c 100644
--- a/cmds/statsd/tests/LogEntryMatcher_test.cpp
+++ b/cmds/statsd/tests/LogEntryMatcher_test.cpp
@@ -294,6 +294,159 @@
EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
}
+TEST(AtomMatcherTest, TestNeqAnyStringMatcher) {
+ UidMap uidMap;
+ uidMap.updateMap(
+ {1111, 1111, 2222, 3333, 3333} /* uid list */, {1, 1, 2, 1, 2} /* version list */,
+ {android::String16("pkg0"), android::String16("pkg1"), android::String16("pkg1"),
+ android::String16("Pkg2"), android::String16("PkG3")} /* package name list */);
+
+ AttributionNodeInternal attribution_node1;
+ attribution_node1.set_uid(1111);
+ attribution_node1.set_tag("location1");
+
+ AttributionNodeInternal attribution_node2;
+ attribution_node2.set_uid(2222);
+ attribution_node2.set_tag("location2");
+
+ AttributionNodeInternal attribution_node3;
+ attribution_node3.set_uid(3333);
+ attribution_node3.set_tag("location3");
+
+ AttributionNodeInternal attribution_node4;
+ attribution_node4.set_uid(1066);
+ attribution_node4.set_tag("location3");
+ std::vector<AttributionNodeInternal> attribution_nodes = {attribution_node1, attribution_node2,
+ attribution_node3, attribution_node4};
+
+ // Set up the event
+ LogEvent event(TAG_ID, 0);
+ event.write(attribution_nodes);
+ event.write("some value");
+ // Convert to a LogEvent
+ event.init();
+
+ // Set up the matcher
+ AtomMatcher matcher;
+ auto simpleMatcher = matcher.mutable_simple_atom_matcher();
+ simpleMatcher->set_atom_id(TAG_ID);
+
+ // Match first node.
+ auto attributionMatcher = simpleMatcher->add_field_value_matcher();
+ attributionMatcher->set_field(FIELD_ID_1);
+ attributionMatcher->set_position(Position::FIRST);
+ attributionMatcher->mutable_matches_tuple()->add_field_value_matcher()->set_field(
+ ATTRIBUTION_UID_FIELD_ID);
+ auto neqStringList = attributionMatcher->mutable_matches_tuple()
+ ->mutable_field_value_matcher(0)
+ ->mutable_neq_all_string();
+ neqStringList->add_str_value("pkg2");
+ neqStringList->add_str_value("pkg3");
+
+ auto fieldMatcher = simpleMatcher->add_field_value_matcher();
+ fieldMatcher->set_field(FIELD_ID_2);
+ fieldMatcher->set_eq_string("some value");
+
+ EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+
+ neqStringList->Clear();
+ neqStringList->add_str_value("pkg1");
+ neqStringList->add_str_value("pkg3");
+ EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+
+ attributionMatcher->set_position(Position::ANY);
+ neqStringList->Clear();
+ neqStringList->add_str_value("maps.com");
+ EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+
+ neqStringList->Clear();
+ neqStringList->add_str_value("PkG3");
+ EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+
+ attributionMatcher->set_position(Position::LAST);
+ neqStringList->Clear();
+ neqStringList->add_str_value("AID_STATSD");
+ EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+}
+
+TEST(AtomMatcherTest, TestEqAnyStringMatcher) {
+ UidMap uidMap;
+ uidMap.updateMap(
+ {1111, 1111, 2222, 3333, 3333} /* uid list */, {1, 1, 2, 1, 2} /* version list */,
+ {android::String16("pkg0"), android::String16("pkg1"), android::String16("pkg1"),
+ android::String16("Pkg2"), android::String16("PkG3")} /* package name list */);
+
+ AttributionNodeInternal attribution_node1;
+ attribution_node1.set_uid(1067);
+ attribution_node1.set_tag("location1");
+
+ AttributionNodeInternal attribution_node2;
+ attribution_node2.set_uid(2222);
+ attribution_node2.set_tag("location2");
+
+ AttributionNodeInternal attribution_node3;
+ attribution_node3.set_uid(3333);
+ attribution_node3.set_tag("location3");
+
+ AttributionNodeInternal attribution_node4;
+ attribution_node4.set_uid(1066);
+ attribution_node4.set_tag("location3");
+ std::vector<AttributionNodeInternal> attribution_nodes = {attribution_node1, attribution_node2,
+ attribution_node3, attribution_node4};
+
+ // Set up the event
+ LogEvent event(TAG_ID, 0);
+ event.write(attribution_nodes);
+ event.write("some value");
+ // Convert to a LogEvent
+ event.init();
+
+ // Set up the matcher
+ AtomMatcher matcher;
+ auto simpleMatcher = matcher.mutable_simple_atom_matcher();
+ simpleMatcher->set_atom_id(TAG_ID);
+
+ // Match first node.
+ auto attributionMatcher = simpleMatcher->add_field_value_matcher();
+ attributionMatcher->set_field(FIELD_ID_1);
+ attributionMatcher->set_position(Position::FIRST);
+ attributionMatcher->mutable_matches_tuple()->add_field_value_matcher()->set_field(
+ ATTRIBUTION_UID_FIELD_ID);
+ auto eqStringList = attributionMatcher->mutable_matches_tuple()
+ ->mutable_field_value_matcher(0)
+ ->mutable_eq_any_string();
+ eqStringList->add_str_value("AID_ROOT");
+ eqStringList->add_str_value("AID_INCIDENTD");
+
+ auto fieldMatcher = simpleMatcher->add_field_value_matcher();
+ fieldMatcher->set_field(FIELD_ID_2);
+ fieldMatcher->set_eq_string("some value");
+
+ EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+
+ attributionMatcher->set_position(Position::ANY);
+ eqStringList->Clear();
+ eqStringList->add_str_value("AID_STATSD");
+ EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+
+ eqStringList->Clear();
+ eqStringList->add_str_value("pkg1");
+ EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+
+ auto normalStringField = fieldMatcher->mutable_eq_any_string();
+ normalStringField->add_str_value("some value123");
+ normalStringField->add_str_value("some value");
+ EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+
+ normalStringField->Clear();
+ normalStringField->add_str_value("AID_STATSD");
+ EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+
+ eqStringList->Clear();
+ eqStringList->add_str_value("maps.com");
+ EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+}
+
TEST(AtomMatcherTest, TestBoolMatcher) {
UidMap uidMap;
// Set up the matcher
diff --git a/cmds/statsd/tests/MetricsManager_test.cpp b/cmds/statsd/tests/MetricsManager_test.cpp
index f90ca40..5d8c3f7 100644
--- a/cmds/statsd/tests/MetricsManager_test.cpp
+++ b/cmds/statsd/tests/MetricsManager_test.cpp
@@ -271,19 +271,25 @@
TEST(MetricsManagerTest, TestGoodConfig) {
UidMap uidMap;
+ sp<AlarmMonitor> anomalyAlarmMonitor;
+ sp<AlarmMonitor> periodicAlarmMonitor;
StatsdConfig config = buildGoodConfig();
set<int> allTagIds;
vector<sp<LogMatchingTracker>> allAtomMatchers;
vector<sp<ConditionTracker>> allConditionTrackers;
vector<sp<MetricProducer>> allMetricProducers;
std::vector<sp<AnomalyTracker>> allAnomalyTrackers;
+ std::vector<sp<AlarmTracker>> allAlarmTrackers;
unordered_map<int, std::vector<int>> conditionToMetricMap;
unordered_map<int, std::vector<int>> trackerToMetricMap;
unordered_map<int, std::vector<int>> trackerToConditionMap;
std::set<int64_t> noReportMetricIds;
- EXPECT_TRUE(initStatsdConfig(kConfigKey, config, uidMap, timeBaseSec, allTagIds, allAtomMatchers,
+ EXPECT_TRUE(initStatsdConfig(kConfigKey, config, uidMap,
+ anomalyAlarmMonitor, periodicAlarmMonitor,
+ timeBaseSec, allTagIds, allAtomMatchers,
allConditionTrackers, allMetricProducers, allAnomalyTrackers,
+ allAlarmTrackers,
conditionToMetricMap, trackerToMetricMap, trackerToConditionMap,
noReportMetricIds));
EXPECT_EQ(1u, allMetricProducers.size());
@@ -293,112 +299,148 @@
TEST(MetricsManagerTest, TestDimensionMetricsWithMultiTags) {
UidMap uidMap;
+ sp<AlarmMonitor> anomalyAlarmMonitor;
+ sp<AlarmMonitor> periodicAlarmMonitor;
StatsdConfig config = buildDimensionMetricsWithMultiTags();
set<int> allTagIds;
vector<sp<LogMatchingTracker>> allAtomMatchers;
vector<sp<ConditionTracker>> allConditionTrackers;
vector<sp<MetricProducer>> allMetricProducers;
std::vector<sp<AnomalyTracker>> allAnomalyTrackers;
+ std::vector<sp<AlarmTracker>> allAlarmTrackers;
unordered_map<int, std::vector<int>> conditionToMetricMap;
unordered_map<int, std::vector<int>> trackerToMetricMap;
unordered_map<int, std::vector<int>> trackerToConditionMap;
std::set<int64_t> noReportMetricIds;
- EXPECT_FALSE(initStatsdConfig(kConfigKey, config, uidMap, timeBaseSec, allTagIds, allAtomMatchers,
+ EXPECT_FALSE(initStatsdConfig(kConfigKey, config, uidMap,
+ anomalyAlarmMonitor, periodicAlarmMonitor,
+ timeBaseSec, allTagIds, allAtomMatchers,
allConditionTrackers, allMetricProducers, allAnomalyTrackers,
+ allAlarmTrackers,
conditionToMetricMap, trackerToMetricMap, trackerToConditionMap,
noReportMetricIds));
}
TEST(MetricsManagerTest, TestCircleLogMatcherDependency) {
UidMap uidMap;
+ sp<AlarmMonitor> anomalyAlarmMonitor;
+ sp<AlarmMonitor> periodicAlarmMonitor;
StatsdConfig config = buildCircleMatchers();
set<int> allTagIds;
vector<sp<LogMatchingTracker>> allAtomMatchers;
vector<sp<ConditionTracker>> allConditionTrackers;
vector<sp<MetricProducer>> allMetricProducers;
std::vector<sp<AnomalyTracker>> allAnomalyTrackers;
+ std::vector<sp<AlarmTracker>> allAlarmTrackers;
unordered_map<int, std::vector<int>> conditionToMetricMap;
unordered_map<int, std::vector<int>> trackerToMetricMap;
unordered_map<int, std::vector<int>> trackerToConditionMap;
std::set<int64_t> noReportMetricIds;
- EXPECT_FALSE(initStatsdConfig(kConfigKey, config, uidMap, timeBaseSec, allTagIds, allAtomMatchers,
+ EXPECT_FALSE(initStatsdConfig(kConfigKey, config, uidMap,
+ anomalyAlarmMonitor, periodicAlarmMonitor,
+ timeBaseSec, allTagIds, allAtomMatchers,
allConditionTrackers, allMetricProducers, allAnomalyTrackers,
+ allAlarmTrackers,
conditionToMetricMap, trackerToMetricMap, trackerToConditionMap,
noReportMetricIds));
}
TEST(MetricsManagerTest, TestMissingMatchers) {
UidMap uidMap;
+ sp<AlarmMonitor> anomalyAlarmMonitor;
+ sp<AlarmMonitor> periodicAlarmMonitor;
StatsdConfig config = buildMissingMatchers();
set<int> allTagIds;
vector<sp<LogMatchingTracker>> allAtomMatchers;
vector<sp<ConditionTracker>> allConditionTrackers;
vector<sp<MetricProducer>> allMetricProducers;
std::vector<sp<AnomalyTracker>> allAnomalyTrackers;
+ std::vector<sp<AlarmTracker>> allAlarmTrackers;
unordered_map<int, std::vector<int>> conditionToMetricMap;
unordered_map<int, std::vector<int>> trackerToMetricMap;
unordered_map<int, std::vector<int>> trackerToConditionMap;
std::set<int64_t> noReportMetricIds;
- EXPECT_FALSE(initStatsdConfig(kConfigKey, config, uidMap, timeBaseSec, allTagIds, allAtomMatchers,
+ EXPECT_FALSE(initStatsdConfig(kConfigKey, config, uidMap,
+ anomalyAlarmMonitor, periodicAlarmMonitor,
+ timeBaseSec, allTagIds, allAtomMatchers,
allConditionTrackers, allMetricProducers, allAnomalyTrackers,
+ allAlarmTrackers,
conditionToMetricMap, trackerToMetricMap, trackerToConditionMap,
noReportMetricIds));
}
TEST(MetricsManagerTest, TestMissingPredicate) {
UidMap uidMap;
+ sp<AlarmMonitor> anomalyAlarmMonitor;
+ sp<AlarmMonitor> periodicAlarmMonitor;
StatsdConfig config = buildMissingPredicate();
set<int> allTagIds;
vector<sp<LogMatchingTracker>> allAtomMatchers;
vector<sp<ConditionTracker>> allConditionTrackers;
vector<sp<MetricProducer>> allMetricProducers;
std::vector<sp<AnomalyTracker>> allAnomalyTrackers;
+ std::vector<sp<AlarmTracker>> allAlarmTrackers;
unordered_map<int, std::vector<int>> conditionToMetricMap;
unordered_map<int, std::vector<int>> trackerToMetricMap;
unordered_map<int, std::vector<int>> trackerToConditionMap;
std::set<int64_t> noReportMetricIds;
- EXPECT_FALSE(initStatsdConfig(kConfigKey, config, uidMap, timeBaseSec, allTagIds, allAtomMatchers,
+ EXPECT_FALSE(initStatsdConfig(kConfigKey, config, uidMap,
+ anomalyAlarmMonitor, periodicAlarmMonitor,
+ timeBaseSec, allTagIds, allAtomMatchers,
allConditionTrackers, allMetricProducers, allAnomalyTrackers,
+ allAlarmTrackers,
conditionToMetricMap, trackerToMetricMap, trackerToConditionMap,
noReportMetricIds));
}
TEST(MetricsManagerTest, TestCirclePredicateDependency) {
UidMap uidMap;
+ sp<AlarmMonitor> anomalyAlarmMonitor;
+ sp<AlarmMonitor> periodicAlarmMonitor;
StatsdConfig config = buildCirclePredicates();
set<int> allTagIds;
vector<sp<LogMatchingTracker>> allAtomMatchers;
vector<sp<ConditionTracker>> allConditionTrackers;
vector<sp<MetricProducer>> allMetricProducers;
std::vector<sp<AnomalyTracker>> allAnomalyTrackers;
+ std::vector<sp<AlarmTracker>> allAlarmTrackers;
unordered_map<int, std::vector<int>> conditionToMetricMap;
unordered_map<int, std::vector<int>> trackerToMetricMap;
unordered_map<int, std::vector<int>> trackerToConditionMap;
std::set<int64_t> noReportMetricIds;
- EXPECT_FALSE(initStatsdConfig(kConfigKey, config, uidMap, timeBaseSec, allTagIds, allAtomMatchers,
+ EXPECT_FALSE(initStatsdConfig(kConfigKey, config, uidMap,
+ anomalyAlarmMonitor, periodicAlarmMonitor,
+ timeBaseSec, allTagIds, allAtomMatchers,
allConditionTrackers, allMetricProducers, allAnomalyTrackers,
+ allAlarmTrackers,
conditionToMetricMap, trackerToMetricMap, trackerToConditionMap,
noReportMetricIds));
}
TEST(MetricsManagerTest, testAlertWithUnknownMetric) {
UidMap uidMap;
+ sp<AlarmMonitor> anomalyAlarmMonitor;
+ sp<AlarmMonitor> periodicAlarmMonitor;
StatsdConfig config = buildAlertWithUnknownMetric();
set<int> allTagIds;
vector<sp<LogMatchingTracker>> allAtomMatchers;
vector<sp<ConditionTracker>> allConditionTrackers;
vector<sp<MetricProducer>> allMetricProducers;
std::vector<sp<AnomalyTracker>> allAnomalyTrackers;
+ std::vector<sp<AlarmTracker>> allAlarmTrackers;
unordered_map<int, std::vector<int>> conditionToMetricMap;
unordered_map<int, std::vector<int>> trackerToMetricMap;
unordered_map<int, std::vector<int>> trackerToConditionMap;
std::set<int64_t> noReportMetricIds;
- EXPECT_FALSE(initStatsdConfig(kConfigKey, config, uidMap, timeBaseSec, allTagIds, allAtomMatchers,
+ EXPECT_FALSE(initStatsdConfig(kConfigKey, config, uidMap,
+ anomalyAlarmMonitor, periodicAlarmMonitor,
+ timeBaseSec, allTagIds, allAtomMatchers,
allConditionTrackers, allMetricProducers, allAnomalyTrackers,
+ allAlarmTrackers,
conditionToMetricMap, trackerToMetricMap, trackerToConditionMap,
noReportMetricIds));
}
diff --git a/cmds/statsd/tests/StatsLogProcessor_test.cpp b/cmds/statsd/tests/StatsLogProcessor_test.cpp
index cb72697..3238b74 100644
--- a/cmds/statsd/tests/StatsLogProcessor_test.cpp
+++ b/cmds/statsd/tests/StatsLogProcessor_test.cpp
@@ -41,7 +41,13 @@
*/
class MockMetricsManager : public MetricsManager {
public:
- MockMetricsManager() : MetricsManager(ConfigKey(1, 12345), StatsdConfig(), 1000, new UidMap()) {
+ MockMetricsManager() : MetricsManager(
+ ConfigKey(1, 12345), StatsdConfig(), 1000,
+ new UidMap(),
+ new AlarmMonitor(10, [](const sp<IStatsCompanionService>&, int64_t){},
+ [](const sp<IStatsCompanionService>&){}),
+ new AlarmMonitor(10, [](const sp<IStatsCompanionService>&, int64_t){},
+ [](const sp<IStatsCompanionService>&){})) {
}
MOCK_METHOD0(byteSize, size_t());
@@ -50,9 +56,11 @@
TEST(StatsLogProcessorTest, TestRateLimitByteSize) {
sp<UidMap> m = new UidMap();
- sp<AnomalyMonitor> anomalyMonitor;
+ sp<AlarmMonitor> anomalyAlarmMonitor;
+ sp<AlarmMonitor> periodicAlarmMonitor;
// Construct the processor with a dummy sendBroadcast function that does nothing.
- StatsLogProcessor p(m, anomalyMonitor, 0, [](const ConfigKey& key) {});
+ StatsLogProcessor p(m, anomalyAlarmMonitor, periodicAlarmMonitor, 0,
+ [](const ConfigKey& key) {});
MockMetricsManager mockMetricsManager;
@@ -67,11 +75,11 @@
TEST(StatsLogProcessorTest, TestRateLimitBroadcast) {
sp<UidMap> m = new UidMap();
- sp<AnomalyMonitor> anomalyMonitor;
+ sp<AlarmMonitor> anomalyAlarmMonitor;
+ sp<AlarmMonitor> subscriberAlarmMonitor;
int broadcastCount = 0;
- StatsLogProcessor p(m, anomalyMonitor, 0, [&broadcastCount](const ConfigKey& key) {
- broadcastCount++;
- });
+ StatsLogProcessor p(m, anomalyAlarmMonitor, subscriberAlarmMonitor, 0,
+ [&broadcastCount](const ConfigKey& key) { broadcastCount++; });
MockMetricsManager mockMetricsManager;
@@ -93,9 +101,10 @@
TEST(StatsLogProcessorTest, TestDropWhenByteSizeTooLarge) {
sp<UidMap> m = new UidMap();
- sp<AnomalyMonitor> anomalyMonitor;
+ sp<AlarmMonitor> anomalyAlarmMonitor;
+ sp<AlarmMonitor> subscriberAlarmMonitor;
int broadcastCount = 0;
- StatsLogProcessor p(m, anomalyMonitor, 0,
+ StatsLogProcessor p(m, anomalyAlarmMonitor, subscriberAlarmMonitor, 0,
[&broadcastCount](const ConfigKey& key) { broadcastCount++; });
MockMetricsManager mockMetricsManager;
diff --git a/cmds/statsd/tests/UidMap_test.cpp b/cmds/statsd/tests/UidMap_test.cpp
index f26c10d..ca656ed 100644
--- a/cmds/statsd/tests/UidMap_test.cpp
+++ b/cmds/statsd/tests/UidMap_test.cpp
@@ -36,9 +36,11 @@
TEST(UidMapTest, TestIsolatedUID) {
sp<UidMap> m = new UidMap();
- sp<AnomalyMonitor> anomalyMonitor;
+ sp<AlarmMonitor> anomalyAlarmMonitor;
+ sp<AlarmMonitor> subscriberAlarmMonitor;
// Construct the processor with a dummy sendBroadcast function that does nothing.
- StatsLogProcessor p(m, anomalyMonitor, 0, [](const ConfigKey& key) {});
+ StatsLogProcessor p(m, anomalyAlarmMonitor, subscriberAlarmMonitor, 0,
+ [](const ConfigKey& key) {});
LogEvent addEvent(android::util::ISOLATED_UID_CHANGED, 1);
addEvent.write(100); // parent UID
addEvent.write(101); // isolated UID
diff --git a/cmds/statsd/tests/anomaly/AlarmTracker_test.cpp b/cmds/statsd/tests/anomaly/AlarmTracker_test.cpp
new file mode 100644
index 0000000..3330ee9
--- /dev/null
+++ b/cmds/statsd/tests/anomaly/AlarmTracker_test.cpp
@@ -0,0 +1,68 @@
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/anomaly/AlarmTracker.h"
+
+#include <gtest/gtest.h>
+#include <stdio.h>
+#include <vector>
+
+using namespace testing;
+using android::sp;
+using std::set;
+using std::unordered_map;
+using std::vector;
+
+#ifdef __ANDROID__
+
+namespace android {
+namespace os {
+namespace statsd {
+
+const ConfigKey kConfigKey(0, 12345);
+
+TEST(AlarmTrackerTest, TestTriggerTimestamp) {
+ sp<AlarmMonitor> subscriberAlarmMonitor =
+ new AlarmMonitor(100, [](const sp<IStatsCompanionService>&, int64_t){},
+ [](const sp<IStatsCompanionService>&){});
+ Alarm alarm;
+ alarm.set_offset_millis(15 * MS_PER_SEC);
+ alarm.set_period_millis(60 * 60 * MS_PER_SEC); // 1hr
+ uint64_t startMillis = 100000000 * MS_PER_SEC;
+ AlarmTracker tracker(startMillis, alarm, kConfigKey,
+ subscriberAlarmMonitor);
+
+ EXPECT_EQ(tracker.mAlarmSec, startMillis / MS_PER_SEC + 15);
+
+ uint64_t currentTimeSec = startMillis / MS_PER_SEC + 10;
+ std::unordered_set<sp<const InternalAlarm>, SpHash<InternalAlarm>> firedAlarmSet =
+ subscriberAlarmMonitor->popSoonerThan(static_cast<uint32_t>(currentTimeSec));
+ EXPECT_TRUE(firedAlarmSet.empty());
+ tracker.informAlarmsFired(currentTimeSec * NS_PER_SEC, firedAlarmSet);
+ EXPECT_EQ(tracker.mAlarmSec, startMillis / MS_PER_SEC + 15);
+
+ currentTimeSec = startMillis / MS_PER_SEC + 7000;
+ firedAlarmSet = subscriberAlarmMonitor->popSoonerThan(static_cast<uint32_t>(currentTimeSec));
+ EXPECT_EQ(firedAlarmSet.size(), 1u);
+ tracker.informAlarmsFired(currentTimeSec * NS_PER_SEC, firedAlarmSet);
+ EXPECT_TRUE(firedAlarmSet.empty());
+ EXPECT_EQ(tracker.mAlarmSec, startMillis / MS_PER_SEC + 15 + 2 * 60 * 60);
+}
+
+} // namespace statsd
+} // namespace os
+} // namespace android
+#else
+GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
diff --git a/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp b/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp
index 20ddbe9..9a0de0d 100644
--- a/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp
@@ -201,6 +201,7 @@
}
TEST(CountMetricProducerTest, TestEventWithAppUpgrade) {
+ sp<AlarmMonitor> alarmMonitor;
uint64_t bucketStartTimeNs = 10000000000;
uint64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL;
uint64_t eventUpgradeTimeNs = bucketStartTimeNs + 15 * NS_PER_SEC;
@@ -222,7 +223,7 @@
bucketStartTimeNs);
countProducer.setBucketSize(60 * NS_PER_SEC);
- sp<AnomalyTracker> anomalyTracker = countProducer.addAnomalyTracker(alert);
+ sp<AnomalyTracker> anomalyTracker = countProducer.addAnomalyTracker(alert, alarmMonitor);
EXPECT_TRUE(anomalyTracker != nullptr);
// Bucket is flushed yet.
@@ -315,6 +316,7 @@
}
TEST(CountMetricProducerTest, TestAnomalyDetectionUnSliced) {
+ sp<AlarmMonitor> alarmMonitor;
Alert alert;
alert.set_id(11);
alert.set_metric_id(1);
@@ -337,7 +339,7 @@
bucketStartTimeNs);
countProducer.setBucketSize(60 * NS_PER_SEC);
- sp<AnomalyTracker> anomalyTracker = countProducer.addAnomalyTracker(alert);
+ sp<AnomalyTracker> anomalyTracker = countProducer.addAnomalyTracker(alert, alarmMonitor);
int tagId = 1;
LogEvent event1(tagId, bucketStartTimeNs + 1);
diff --git a/cmds/statsd/tests/metrics/DurationMetricProducer_test.cpp b/cmds/statsd/tests/metrics/DurationMetricProducer_test.cpp
index 7969596..1b22d75 100644
--- a/cmds/statsd/tests/metrics/DurationMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/DurationMetricProducer_test.cpp
@@ -239,6 +239,7 @@
}
TEST(DurationMetricTrackerTest, TestSumDurationAnomalyWithUpgrade) {
+ sp<AlarmMonitor> alarmMonitor;
uint64_t bucketStartTimeNs = 10000000000;
uint64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL;
uint64_t eventUpgradeTimeNs = bucketStartTimeNs + 15 * NS_PER_SEC;
@@ -263,7 +264,7 @@
3 /* stop_all index */, false /*nesting*/, wizard, dimensions, bucketStartTimeNs);
durationProducer.setBucketSize(60 * NS_PER_SEC);
- sp<AnomalyTracker> anomalyTracker = durationProducer.addAnomalyTracker(alert);
+ sp<AnomalyTracker> anomalyTracker = durationProducer.addAnomalyTracker(alert, alarmMonitor);
EXPECT_TRUE(anomalyTracker != nullptr);
LogEvent start_event(tagId, startTimeNs);
diff --git a/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp b/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp
index 0eb8ce2..77b3ace 100644
--- a/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp
@@ -129,6 +129,7 @@
}
TEST(GaugeMetricProducerTest, TestPushedEventsWithUpgrade) {
+ sp<AlarmMonitor> alarmMonitor;
GaugeMetric metric;
metric.set_id(metricId);
metric.set_bucket(ONE_MINUTE);
@@ -145,8 +146,9 @@
GaugeMetricProducer gaugeProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard,
-1 /* -1 means no pulling */, bucketStartTimeNs,
pullerManager);
+
gaugeProducer.setBucketSize(60 * NS_PER_SEC);
- sp<AnomalyTracker> anomalyTracker = gaugeProducer.addAnomalyTracker(alert);
+ sp<AnomalyTracker> anomalyTracker = gaugeProducer.addAnomalyTracker(alert, alarmMonitor);
EXPECT_TRUE(anomalyTracker != nullptr);
shared_ptr<LogEvent> event1 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 10);
@@ -339,6 +341,7 @@
}
TEST(GaugeMetricProducerTest, TestAnomalyDetection) {
+ sp<AlarmMonitor> alarmMonitor;
sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
shared_ptr<MockStatsPullerManager> pullerManager =
@@ -363,7 +366,7 @@
alert.set_num_buckets(2);
const int32_t refPeriodSec = 60;
alert.set_refractory_period_secs(refPeriodSec);
- sp<AnomalyTracker> anomalyTracker = gaugeProducer.addAnomalyTracker(alert);
+ sp<AnomalyTracker> anomalyTracker = gaugeProducer.addAnomalyTracker(alert, alarmMonitor);
int tagId = 1;
std::shared_ptr<LogEvent> event1 = std::make_shared<LogEvent>(tagId, bucketStartTimeNs + 1);
diff --git a/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp b/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp
index a164c12..83b1cbf 100644
--- a/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp
+++ b/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp
@@ -276,13 +276,15 @@
alert.set_num_buckets(2);
const int32_t refPeriodSec = 45;
alert.set_refractory_period_secs(refPeriodSec);
- sp<DurationAnomalyTracker> anomalyTracker = new DurationAnomalyTracker(alert, kConfigKey);
+ sp<AlarmMonitor> alarmMonitor;
+ sp<DurationAnomalyTracker> anomalyTracker =
+ new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor);
MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition,
false, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs,
true, {anomalyTracker});
tracker.noteStart(key1, true, eventStartTimeNs, conditionKey1);
- sp<const AnomalyAlarm> alarm = anomalyTracker->mAlarms.begin()->second;
+ sp<const InternalAlarm> alarm = anomalyTracker->mAlarms.begin()->second;
EXPECT_EQ((long long)(53ULL * NS_PER_SEC), (long long)(alarm->timestampSec * NS_PER_SEC));
// Remove the anomaly alarm when the duration is no longer fully met.
@@ -336,7 +338,9 @@
alert.set_num_buckets(2);
const int32_t refPeriodSec = 45;
alert.set_refractory_period_secs(refPeriodSec);
- sp<DurationAnomalyTracker> anomalyTracker = new DurationAnomalyTracker(alert, kConfigKey);
+ sp<AlarmMonitor> alarmMonitor;
+ sp<DurationAnomalyTracker> anomalyTracker =
+ new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor);
MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition,
false, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs,
true, {anomalyTracker});
@@ -390,7 +394,9 @@
alert.set_num_buckets(2);
const int32_t refPeriodSec = 45;
alert.set_refractory_period_secs(refPeriodSec);
- sp<DurationAnomalyTracker> anomalyTracker = new DurationAnomalyTracker(alert, kConfigKey);
+ sp<AlarmMonitor> alarmMonitor;
+ sp<DurationAnomalyTracker> anomalyTracker =
+ new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor);
MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition,
false, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs,
true, {anomalyTracker});
diff --git a/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp b/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp
index cb731c5..aa41038 100644
--- a/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp
+++ b/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp
@@ -334,7 +334,9 @@
uint64_t bucketNum = 0;
uint64_t eventStartTimeNs = bucketStartTimeNs + NS_PER_SEC + 1;
- sp<DurationAnomalyTracker> anomalyTracker = new DurationAnomalyTracker(alert, kConfigKey);
+ sp<AlarmMonitor> alarmMonitor;
+ sp<DurationAnomalyTracker> anomalyTracker =
+ new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor);
OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition,
true, bucketStartTimeNs, bucketNum, bucketStartTimeNs,
bucketSizeNs, true, {anomalyTracker});
@@ -403,7 +405,9 @@
uint64_t bucketNum = 0;
uint64_t eventStartTimeNs = bucketStartTimeNs + NS_PER_SEC + 1;
- sp<DurationAnomalyTracker> anomalyTracker = new DurationAnomalyTracker(alert, kConfigKey);
+ sp<AlarmMonitor> alarmMonitor;
+ sp<DurationAnomalyTracker> anomalyTracker =
+ new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor);
OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition,
true /*nesting*/, bucketStartTimeNs, bucketNum, bucketStartTimeNs,
bucketSizeNs, false, {anomalyTracker});
@@ -453,14 +457,16 @@
uint64_t bucketStartTimeNs = 10 * NS_PER_SEC;
uint64_t bucketSizeNs = 30 * NS_PER_SEC;
- sp<DurationAnomalyTracker> anomalyTracker = new DurationAnomalyTracker(alert, kConfigKey);
+ sp<AlarmMonitor> alarmMonitor;
+ sp<DurationAnomalyTracker> anomalyTracker =
+ new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor);
OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition,
true /*nesting*/, bucketStartTimeNs, 0, bucketStartTimeNs,
bucketSizeNs, false, {anomalyTracker});
tracker.noteStart(kEventKey1, true, 15 * NS_PER_SEC, conkey); // start key1
EXPECT_EQ(1u, anomalyTracker->mAlarms.size());
- sp<const AnomalyAlarm> alarm = anomalyTracker->mAlarms.begin()->second;
+ sp<const InternalAlarm> alarm = anomalyTracker->mAlarms.begin()->second;
EXPECT_EQ((long long)(55ULL * NS_PER_SEC), (long long)(alarm->timestampSec * NS_PER_SEC));
EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(eventKey), 0U);
@@ -487,7 +493,7 @@
EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(eventKey), 0U);
// Now, at 60s, which is 38s after key1 started again, we have reached 40s of 'on' time.
- std::unordered_set<sp<const AnomalyAlarm>, SpHash<AnomalyAlarm>> firedAlarms({alarm});
+ std::unordered_set<sp<const InternalAlarm>, SpHash<InternalAlarm>> firedAlarms({alarm});
anomalyTracker->informAlarmsFired(62 * NS_PER_SEC, firedAlarms);
EXPECT_EQ(0u, anomalyTracker->mAlarms.size());
EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(eventKey), 62U + refPeriodSec);
diff --git a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp
index ce4fa32..a0addcc 100644
--- a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp
@@ -346,6 +346,7 @@
}
TEST(ValueMetricProducerTest, TestAnomalyDetection) {
+ sp<AlarmMonitor> alarmMonitor;
Alert alert;
alert.set_id(101);
alert.set_metric_id(metricId);
@@ -365,7 +366,7 @@
-1 /*not pulled*/, bucketStartTimeNs);
valueProducer.setBucketSize(60 * NS_PER_SEC);
- sp<AnomalyTracker> anomalyTracker = valueProducer.addAnomalyTracker(alert);
+ sp<AnomalyTracker> anomalyTracker = valueProducer.addAnomalyTracker(alert, alarmMonitor);
shared_ptr<LogEvent> event1
diff --git a/cmds/statsd/tests/statsd_test_util.cpp b/cmds/statsd/tests/statsd_test_util.cpp
index 7568348..242b6eb 100644
--- a/cmds/statsd/tests/statsd_test_util.cpp
+++ b/cmds/statsd/tests/statsd_test_util.cpp
@@ -391,9 +391,10 @@
sp<StatsLogProcessor> CreateStatsLogProcessor(const long timeBaseSec, const StatsdConfig& config,
const ConfigKey& key) {
sp<UidMap> uidMap = new UidMap();
- sp<AnomalyMonitor> anomalyMonitor = new AnomalyMonitor(10); // 10 seconds
+ sp<AlarmMonitor> anomalyAlarmMonitor;
+ sp<AlarmMonitor> periodicAlarmMonitor;
sp<StatsLogProcessor> processor = new StatsLogProcessor(
- uidMap, anomalyMonitor, timeBaseSec, [](const ConfigKey&){});
+ uidMap, anomalyAlarmMonitor, periodicAlarmMonitor, timeBaseSec, [](const ConfigKey&){});
processor->OnConfigUpdated(key, config);
return processor;
}
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index feeb0f2..cab6744 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -27,6 +27,7 @@
import android.os.SystemClock;
import android.service.voice.IVoiceInteractionSession;
import android.util.SparseIntArray;
+import android.view.RemoteAnimationAdapter;
import com.android.internal.app.IVoiceInteractor;
@@ -264,6 +265,17 @@
public abstract void setHasOverlayUi(int pid, boolean hasOverlayUi);
/**
+ * Sets if the given pid is currently running a remote animation, which is taken a signal for
+ * determining oom adjustment and scheduling behavior.
+ *
+ * @param pid The pid we are setting overlay UI for.
+ * @param runningRemoteAnimation True if the process is running a remote animation, false
+ * otherwise.
+ * @see RemoteAnimationAdapter
+ */
+ public abstract void setRunningRemoteAnimation(int pid, boolean runningRemoteAnimation);
+
+ /**
* Called after the network policy rules are updated by
* {@link com.android.server.net.NetworkPolicyManagerService} for a specific {@param uid} and
* {@param procStateSeq}.
@@ -353,6 +365,11 @@
public abstract boolean isCallerRecents(int callingUid);
/**
+ * Returns whether the recents component is the home activity for the given user.
+ */
+ public abstract boolean isRecentsComponentHomeActivity(int userId);
+
+ /**
* Whether an UID is active or idle.
*/
public abstract boolean isUidActive(int uid);
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 21d146a..872370e 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -3900,8 +3900,7 @@
@Override
public void handlePauseActivity(IBinder token, boolean finished, boolean userLeaving,
- int configChanges, boolean dontReport, PendingTransactionActions pendingActions,
- String reason) {
+ int configChanges, PendingTransactionActions pendingActions, String reason) {
ActivityClientRecord r = mActivities.get(token);
if (r != null) {
if (userLeaving) {
@@ -3915,15 +3914,6 @@
if (r.isPreHoneycomb()) {
QueuedWork.waitToFinish();
}
-
- // Tell the activity manager we have paused.
- if (!dontReport) {
- try {
- ActivityManager.getService().activityPaused(token);
- } catch (RemoteException ex) {
- throw ex.rethrowFromSystemServer();
- }
- }
mSomeActivitiesChanged = true;
}
}
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index f8f50a2..21fb18a 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -1354,11 +1354,10 @@
if (badgeColor == null) {
return null;
}
- badgeColor.setTint(getUserBadgeColor(user));
Drawable badgeForeground = getDrawableForDensity(
com.android.internal.R.drawable.ic_corp_badge_case, density);
- Drawable badge = new LayerDrawable(
- new Drawable[] {badgeColor, badgeForeground });
+ badgeForeground.setTint(getUserBadgeColor(user));
+ Drawable badge = new LayerDrawable(new Drawable[] {badgeColor, badgeForeground });
return badge;
}
diff --git a/core/java/android/app/ClientTransactionHandler.java b/core/java/android/app/ClientTransactionHandler.java
index cb52a85..6bc66ec 100644
--- a/core/java/android/app/ClientTransactionHandler.java
+++ b/core/java/android/app/ClientTransactionHandler.java
@@ -65,8 +65,7 @@
/** Pause the activity. */
public abstract void handlePauseActivity(IBinder token, boolean finished, boolean userLeaving,
- int configChanges, boolean dontReport, PendingTransactionActions pendingActions,
- String reason);
+ int configChanges, PendingTransactionActions pendingActions, String reason);
/** Resume the activity. */
public abstract void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward,
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 16e36bc..7b6a288 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -56,8 +56,11 @@
import android.os.Process;
import android.os.RemoteCallback;
import android.os.RemoteException;
+import android.os.ServiceSpecificException;
import android.os.UserHandle;
import android.os.UserManager;
+import android.os.UserManager.UserOperationException;
+import android.os.UserManager.UserOperationResult;
import android.provider.ContactsContract.Directory;
import android.provider.Settings;
import android.security.AttestedKeyPair;
@@ -5627,6 +5630,29 @@
}
/**
+ * Called by a device owner to set the default SMS application.
+ * <p>
+ * The calling device admin must be a device owner. If it is not, a security exception will be
+ * thrown.
+ *
+ * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+ * @param packageName The name of the package to set as the default SMS application.
+ * @throws SecurityException if {@code admin} is not a device owner.
+ *
+ * @hide
+ */
+ public void setDefaultSmsApplication(@NonNull ComponentName admin, String packageName) {
+ throwIfParentInstance("setDefaultSmsApplication");
+ if (mService != null) {
+ try {
+ mService.setDefaultSmsApplication(admin, packageName);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
* Called by a profile owner or device owner to grant permission to a package to manage
* application restrictions for the calling user via {@link #setApplicationRestrictions} and
* {@link #getApplicationRestrictions}.
@@ -6549,6 +6575,9 @@
* <p>
* If the adminExtras are not null, they will be stored on the device until the user is started
* for the first time. Then the extras will be passed to the admin when onEnable is called.
+ * <p>From {@link android.os.Build.VERSION_CODES#P} onwards, if targeting
+ * {@link android.os.Build.VERSION_CODES#P}, throws {@link UserOperationException} instead of
+ * returning {@code null} on failure.
*
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
* @param name The user's name.
@@ -6563,6 +6592,9 @@
* @return the {@link android.os.UserHandle} object for the created user, or {@code null} if the
* user could not be created.
* @throws SecurityException if {@code admin} is not a device owner.
+ * @throws UserOperationException if the user could not be created and the calling app is
+ * targeting {@link android.os.Build.VERSION_CODES#P} and running on
+ * {@link android.os.Build.VERSION_CODES#P}.
*/
public @Nullable UserHandle createAndManageUser(@NonNull ComponentName admin,
@NonNull String name,
@@ -6571,6 +6603,8 @@
throwIfParentInstance("createAndManageUser");
try {
return mService.createAndManageUser(admin, name, profileOwner, adminExtras, flags);
+ } catch (ServiceSpecificException e) {
+ throw new UserOperationException(e.getMessage(), e.errorCode);
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
@@ -6614,77 +6648,15 @@
}
/**
- * Indicates user operation is successful.
- *
- * @see #startUserInBackground(ComponentName, UserHandle)
- * @see #stopUser(ComponentName, UserHandle)
- * @see #logoutUser(ComponentName)
- */
- public static final int USER_OPERATION_SUCCESS = 0;
-
- /**
- * Indicates user operation failed for unknown reason.
- *
- * @see #startUserInBackground(ComponentName, UserHandle)
- * @see #stopUser(ComponentName, UserHandle)
- * @see #logoutUser(ComponentName)
- */
- public static final int USER_OPERATION_ERROR_UNKNOWN = 1;
-
- /**
- * Indicates user operation failed because target user is a managed profile.
- *
- * @see #startUserInBackground(ComponentName, UserHandle)
- * @see #stopUser(ComponentName, UserHandle)
- * @see #logoutUser(ComponentName)
- */
- public static final int USER_OPERATION_ERROR_MANAGED_PROFILE = 2;
-
- /**
- * Indicates user operation failed because maximum running user limit has reached.
- *
- * @see #startUserInBackground(ComponentName, UserHandle)
- */
- public static final int USER_OPERATION_ERROR_MAX_RUNNING_USERS = 3;
-
- /**
- * Indicates user operation failed because the target user is in foreground.
- *
- * @see #stopUser(ComponentName, UserHandle)
- * @see #logoutUser(ComponentName)
- */
- public static final int USER_OPERATION_ERROR_CURRENT_USER = 4;
-
- /**
- * Result returned from
- * <ul>
- * <li>{@link #startUserInBackground(ComponentName, UserHandle)}</li>
- * <li>{@link #stopUser(ComponentName, UserHandle)}</li>
- * <li>{@link #logoutUser(ComponentName)}</li>
- * </ul>
- *
- * @hide
- */
- @Retention(RetentionPolicy.SOURCE)
- @IntDef(prefix = { "USER_OPERATION_" }, value = {
- USER_OPERATION_SUCCESS,
- USER_OPERATION_ERROR_UNKNOWN,
- USER_OPERATION_ERROR_MANAGED_PROFILE,
- USER_OPERATION_ERROR_MAX_RUNNING_USERS,
- USER_OPERATION_ERROR_CURRENT_USER
- })
- public @interface UserOperationResult {}
-
- /**
* Called by a device owner to start the specified secondary user in background.
*
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
* @param userHandle the user to be started in background.
* @return one of the following result codes:
- * {@link #USER_OPERATION_ERROR_UNKNOWN},
- * {@link #USER_OPERATION_SUCCESS},
- * {@link #USER_OPERATION_ERROR_MANAGED_PROFILE},
- * {@link #USER_OPERATION_ERROR_MAX_RUNNING_USERS},
+ * {@link UserManager#USER_OPERATION_ERROR_UNKNOWN},
+ * {@link UserManager#USER_OPERATION_SUCCESS},
+ * {@link UserManager#USER_OPERATION_ERROR_MANAGED_PROFILE},
+ * {@link UserManager#USER_OPERATION_ERROR_MAX_RUNNING_USERS},
* @throws SecurityException if {@code admin} is not a device owner.
* @see #getSecondaryUsers(ComponentName)
*/
@@ -6704,10 +6676,10 @@
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
* @param userHandle the user to be stopped.
* @return one of the following result codes:
- * {@link #USER_OPERATION_ERROR_UNKNOWN},
- * {@link #USER_OPERATION_SUCCESS},
- * {@link #USER_OPERATION_ERROR_MANAGED_PROFILE},
- * {@link #USER_OPERATION_ERROR_CURRENT_USER}
+ * {@link UserManager#USER_OPERATION_ERROR_UNKNOWN},
+ * {@link UserManager#USER_OPERATION_SUCCESS},
+ * {@link UserManager#USER_OPERATION_ERROR_MANAGED_PROFILE},
+ * {@link UserManager#USER_OPERATION_ERROR_CURRENT_USER}
* @throws SecurityException if {@code admin} is not a device owner.
* @see #getSecondaryUsers(ComponentName)
*/
@@ -6727,10 +6699,10 @@
*
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
* @return one of the following result codes:
- * {@link #USER_OPERATION_ERROR_UNKNOWN},
- * {@link #USER_OPERATION_SUCCESS},
- * {@link #USER_OPERATION_ERROR_MANAGED_PROFILE},
- * {@link #USER_OPERATION_ERROR_CURRENT_USER}
+ * {@link UserManager#USER_OPERATION_ERROR_UNKNOWN},
+ * {@link UserManager#USER_OPERATION_SUCCESS},
+ * {@link UserManager#USER_OPERATION_ERROR_MANAGED_PROFILE},
+ * {@link UserManager#USER_OPERATION_ERROR_CURRENT_USER}
* @throws SecurityException if {@code admin} is not a profile owner affiliated with the device.
* @see #getSecondaryUsers(ComponentName)
*/
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 5218a73..c29369f 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -192,6 +192,8 @@
void addPersistentPreferredActivity(in ComponentName admin, in IntentFilter filter, in ComponentName activity);
void clearPackagePersistentPreferredActivities(in ComponentName admin, String packageName);
+ void setDefaultSmsApplication(in ComponentName admin, String packageName);
+
void setApplicationRestrictions(in ComponentName who, in String callerPackage, in String packageName, in Bundle settings);
Bundle getApplicationRestrictions(in ComponentName who, in String callerPackage, in String packageName);
boolean setApplicationRestrictionsManagingPackage(in ComponentName admin, in String packageName);
diff --git a/core/java/android/app/backup/BackupTransport.java b/core/java/android/app/backup/BackupTransport.java
index f456395..0963594 100644
--- a/core/java/android/app/backup/BackupTransport.java
+++ b/core/java/android/app/backup/BackupTransport.java
@@ -83,6 +83,13 @@
*/
public static final int FLAG_NON_INCREMENTAL = 1 << 2;
+ /**
+ * Used as a boolean extra in the binding intent of transports. We pass {@code true} to
+ * notify transports that the current connection is used for registering the transport.
+ */
+ public static final String EXTRA_TRANSPORT_REGISTRATION =
+ "android.app.backup.extra.TRANSPORT_REGISTRATION";
+
IBackupTransport mBinderImpl = new TransportImpl();
public IBinder getBinder() {
diff --git a/core/java/android/app/servertransaction/PauseActivityItem.java b/core/java/android/app/servertransaction/PauseActivityItem.java
index 578f0e3..65e4291 100644
--- a/core/java/android/app/servertransaction/PauseActivityItem.java
+++ b/core/java/android/app/servertransaction/PauseActivityItem.java
@@ -42,8 +42,8 @@
public void execute(ClientTransactionHandler client, IBinder token,
PendingTransactionActions pendingActions) {
Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityPause");
- client.handlePauseActivity(token, mFinished, mUserLeaving, mConfigChanges, mDontReport,
- pendingActions, "PAUSE_ACTIVITY_ITEM");
+ client.handlePauseActivity(token, mFinished, mUserLeaving, mConfigChanges, pendingActions,
+ "PAUSE_ACTIVITY_ITEM");
Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
}
diff --git a/core/java/android/app/servertransaction/TransactionExecutor.java b/core/java/android/app/servertransaction/TransactionExecutor.java
index b66d61b..059e0af 100644
--- a/core/java/android/app/servertransaction/TransactionExecutor.java
+++ b/core/java/android/app/servertransaction/TransactionExecutor.java
@@ -185,8 +185,8 @@
break;
case ON_PAUSE:
mTransactionHandler.handlePauseActivity(r.token, false /* finished */,
- false /* userLeaving */, 0 /* configChanges */,
- true /* dontReport */, mPendingActions, "LIFECYCLER_PAUSE_ACTIVITY");
+ false /* userLeaving */, 0 /* configChanges */, mPendingActions,
+ "LIFECYCLER_PAUSE_ACTIVITY");
break;
case ON_STOP:
mTransactionHandler.handleStopActivity(r.token, false /* show */,
diff --git a/core/java/android/content/ClipboardManager.java b/core/java/android/content/ClipboardManager.java
index 718e465..73b6eb2 100644
--- a/core/java/android/content/ClipboardManager.java
+++ b/core/java/android/content/ClipboardManager.java
@@ -16,13 +16,16 @@
package android.content;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.SystemService;
import android.os.Handler;
-import android.os.Message;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.ServiceManager.ServiceNotFoundException;
+import com.android.internal.util.Preconditions;
+
import java.util.ArrayList;
/**
@@ -45,6 +48,7 @@
@SystemService(Context.CLIPBOARD_SERVICE)
public class ClipboardManager extends android.text.ClipboardManager {
private final Context mContext;
+ private final Handler mHandler;
private final IClipboard mService;
private final ArrayList<OnPrimaryClipChangedListener> mPrimaryClipChangedListeners
@@ -52,20 +56,11 @@
private final IOnPrimaryClipChangedListener.Stub mPrimaryClipChangedServiceListener
= new IOnPrimaryClipChangedListener.Stub() {
- public void dispatchPrimaryClipChanged() {
- mHandler.sendEmptyMessage(MSG_REPORT_PRIMARY_CLIP_CHANGED);
- }
- };
-
- static final int MSG_REPORT_PRIMARY_CLIP_CHANGED = 1;
-
- private final Handler mHandler = new Handler() {
@Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case MSG_REPORT_PRIMARY_CLIP_CHANGED:
- reportPrimaryClipChanged();
- }
+ public void dispatchPrimaryClipChanged() {
+ mHandler.post(() -> {
+ reportPrimaryClipChanged();
+ });
}
};
@@ -89,6 +84,7 @@
/** {@hide} */
public ClipboardManager(Context context, Handler handler) throws ServiceNotFoundException {
mContext = context;
+ mHandler = handler;
mService = IClipboard.Stub.asInterface(
ServiceManager.getServiceOrThrow(Context.CLIPBOARD_SERVICE));
}
@@ -98,12 +94,13 @@
* is involved in normal cut and paste operations.
*
* @param clip The clipped data item to set.
+ * @see #getPrimaryClip()
+ * @see #clearPrimaryClip()
*/
- public void setPrimaryClip(ClipData clip) {
+ public void setPrimaryClip(@NonNull ClipData clip) {
try {
- if (clip != null) {
- clip.prepareToLeaveProcess(true);
- }
+ Preconditions.checkNotNull(clip);
+ clip.prepareToLeaveProcess(true);
mService.setPrimaryClip(clip, mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -111,9 +108,24 @@
}
/**
- * Returns the current primary clip on the clipboard.
+ * Clears any current primary clip on the clipboard.
+ *
+ * @see #setPrimaryClip(ClipData)
*/
- public ClipData getPrimaryClip() {
+ public void clearPrimaryClip() {
+ try {
+ mService.clearPrimaryClip(mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns the current primary clip on the clipboard.
+ *
+ * @see #setPrimaryClip(ClipData)
+ */
+ public @Nullable ClipData getPrimaryClip() {
try {
return mService.getPrimaryClip(mContext.getOpPackageName());
} catch (RemoteException e) {
@@ -124,8 +136,10 @@
/**
* Returns a description of the current primary clip on the clipboard
* but not a copy of its data.
+ *
+ * @see #setPrimaryClip(ClipData)
*/
- public ClipDescription getPrimaryClipDescription() {
+ public @Nullable ClipDescription getPrimaryClipDescription() {
try {
return mService.getPrimaryClipDescription(mContext.getOpPackageName());
} catch (RemoteException e) {
diff --git a/core/java/android/content/IClipboard.aidl b/core/java/android/content/IClipboard.aidl
index af0b8f0..135a436 100644
--- a/core/java/android/content/IClipboard.aidl
+++ b/core/java/android/content/IClipboard.aidl
@@ -27,6 +27,7 @@
*/
interface IClipboard {
void setPrimaryClip(in ClipData clip, String callingPackage);
+ void clearPrimaryClip(String callingPackage);
ClipData getPrimaryClip(String pkg);
ClipDescription getPrimaryClipDescription(String callingPackage);
boolean hasPrimaryClip(String callingPackage);
diff --git a/core/java/android/content/pm/PackageManagerInternal.java b/core/java/android/content/pm/PackageManagerInternal.java
index 41aa9c3..514112f 100644
--- a/core/java/android/content/pm/PackageManagerInternal.java
+++ b/core/java/android/content/pm/PackageManagerInternal.java
@@ -236,6 +236,11 @@
int userId);
/**
+ * @return The default home activity component name.
+ */
+ public abstract ComponentName getDefaultHomeActivity(int userId);
+
+ /**
* Called by DeviceOwnerManagerService to set the package names of device owner and profile
* owners.
*/
@@ -539,4 +544,16 @@
/** Updates the flags for the given permission. */
public abstract void updatePermissionFlagsTEMP(@NonNull String permName,
@NonNull String packageName, int flagMask, int flagValues, int userId);
+
+ /**
+ * Returns true if it's still safe to restore data backed up from this app's version
+ * that was signed with restoringFromSigHash.
+ */
+ public abstract boolean isDataRestoreSafe(byte[] restoringFromSigHash, String packageName);
+
+ /**
+ * Returns true if it's still safe to restore data backed up from this app's version
+ * that was signed with restoringFromSig.
+ */
+ public abstract boolean isDataRestoreSafe(Signature restoringFromSig, String packageName);
}
diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java
index 93690bf..2baf539 100644
--- a/core/java/android/content/res/Configuration.java
+++ b/core/java/android/content/res/Configuration.java
@@ -1615,12 +1615,7 @@
dest.writeInt(mnc);
fixUpLocaleList();
- final int localeListSize = mLocaleList.size();
- dest.writeInt(localeListSize);
- for (int i = 0; i < localeListSize; ++i) {
- final Locale l = mLocaleList.get(i);
- dest.writeString(l.toLanguageTag());
- }
+ dest.writeParcelable(mLocaleList, flags);
if(userSetLocale) {
dest.writeInt(1);
@@ -1654,12 +1649,7 @@
mcc = source.readInt();
mnc = source.readInt();
- final int localeListSize = source.readInt();
- final Locale[] localeArray = new Locale[localeListSize];
- for (int i = 0; i < localeListSize; ++i) {
- localeArray[i] = Locale.forLanguageTag(source.readString());
- }
- mLocaleList = new LocaleList(localeArray);
+ mLocaleList = source.readParcelable(LocaleList.class.getClassLoader());
locale = mLocaleList.get(0);
userSetLocale = (source.readInt()==1);
diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java
index bae373d..9d94ecc 100644
--- a/core/java/android/net/NetworkCapabilities.java
+++ b/core/java/android/net/NetworkCapabilities.java
@@ -117,6 +117,7 @@
NET_CAPABILITY_FOREGROUND,
NET_CAPABILITY_NOT_CONGESTED,
NET_CAPABILITY_NOT_SUSPENDED,
+ NET_CAPABILITY_OEM_PAID,
})
public @interface NetCapability { }
@@ -264,8 +265,15 @@
*/
public static final int NET_CAPABILITY_NOT_SUSPENDED = 21;
+ /**
+ * Indicates that traffic that goes through this network is paid by oem. For example,
+ * this network can be used by system apps to upload telemetry data.
+ * @hide
+ */
+ public static final int NET_CAPABILITY_OEM_PAID = 22;
+
private static final int MIN_NET_CAPABILITY = NET_CAPABILITY_MMS;
- private static final int MAX_NET_CAPABILITY = NET_CAPABILITY_NOT_SUSPENDED;
+ private static final int MAX_NET_CAPABILITY = NET_CAPABILITY_OEM_PAID;
/**
* Network capabilities that are expected to be mutable, i.e., can change while a particular
@@ -313,7 +321,8 @@
(1 << NET_CAPABILITY_IA) |
(1 << NET_CAPABILITY_IMS) |
(1 << NET_CAPABILITY_RCS) |
- (1 << NET_CAPABILITY_XCAP);
+ (1 << NET_CAPABILITY_XCAP) |
+ (1 << NET_CAPABILITY_OEM_PAID);
/**
* Capabilities that suggest that a network is unrestricted.
@@ -1313,6 +1322,7 @@
case NET_CAPABILITY_FOREGROUND: return "FOREGROUND";
case NET_CAPABILITY_NOT_CONGESTED: return "NOT_CONGESTED";
case NET_CAPABILITY_NOT_SUSPENDED: return "NOT_SUSPENDED";
+ case NET_CAPABILITY_OEM_PAID: return "OEM_PAID";
default: return Integer.toString(capability);
}
}
diff --git a/core/java/android/os/IStatsCompanionService.aidl b/core/java/android/os/IStatsCompanionService.aidl
index eae5217..29c298e 100644
--- a/core/java/android/os/IStatsCompanionService.aidl
+++ b/core/java/android/os/IStatsCompanionService.aidl
@@ -41,7 +41,7 @@
oneway void cancelAnomalyAlarm();
/**
- * Register a repeating alarm for polling to fire at the given timestamp and every
+ * Register a repeating alarm for pulling to fire at the given timestamp and every
* intervalMs thereafter (in ms since epoch).
* If polling alarm had already been registered, it will be replaced by new one.
* Uses AlarmManager.setRepeating API, so if the timestamp is in past, alarm fires immediately,
@@ -49,9 +49,19 @@
*/
oneway void setPullingAlarms(long timestampMs, long intervalMs);
- /** Cancel any repeating polling alarm. */
+ /** Cancel any repeating pulling alarm. */
oneway void cancelPullingAlarms();
+ /**
+ * Register an alarm when we want to trigger subscribers at the given
+ * timestamp (in ms since epoch).
+ * If an alarm had already been registered, it will be replaced by new one.
+ */
+ oneway void setAlarmForSubscriberTriggering(long timestampMs);
+
+ /** Cancel any alarm for the purpose of subscriber triggering. */
+ oneway void cancelAlarmForSubscriberTriggering();
+
/** Pull the specified data. Results will be sent to statsd when complete. */
StatsLogEventWrapper[] pullData(int pullCode);
diff --git a/core/java/android/os/IStatsManager.aidl b/core/java/android/os/IStatsManager.aidl
index 682a24f1..2a68714 100644
--- a/core/java/android/os/IStatsManager.aidl
+++ b/core/java/android/os/IStatsManager.aidl
@@ -47,6 +47,13 @@
void informPollAlarmFired();
/**
+ * Tells statsd that it is time to handle periodic alarms. Statsd will be responsible for
+ * determing what alarm subscriber to trigger.
+ * Two-way binder call so that caller's method (and corresponding wakelocks) will linger.
+ */
+ void informAlarmForSubscriberTriggeringFired();
+
+ /**
* Tells statsd to store data to disk.
*/
void writeDataToDisk();
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index 811cc5e..97d415e 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -538,6 +538,7 @@
ServiceType.DATA_SAVER,
ServiceType.FORCE_ALL_APPS_STANDBY,
ServiceType.OPTIONAL_SENSORS,
+ ServiceType.AOD,
})
public @interface ServiceType {
int NULL = 0;
@@ -551,6 +552,7 @@
int SOUND = 8;
int BATTERY_STATS = 9;
int DATA_SAVER = 10;
+ int AOD = 14;
/**
* Whether to enable force-app-standby on all apps or not.
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 1856200..2693bab 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -913,6 +913,9 @@
* Specifies if user switching is blocked on the current user.
*
* <p> This restriction can only be set by the device owner, it will be applied to all users.
+ * Device owner can still switch user via
+ * {@link DevicePolicyManager#switchUser(ComponentName, UserHandle)} when this restriction is
+ * set.
*
* <p>The default value is <code>false</code>.
*
@@ -1039,6 +1042,85 @@
*/
public static final int USER_CREATION_FAILED_NO_MORE_USERS = Activity.RESULT_FIRST_USER + 1;
+ /**
+ * Indicates user operation is successful.
+ */
+ public static final int USER_OPERATION_SUCCESS = 0;
+
+ /**
+ * Indicates user operation failed for unknown reason.
+ */
+ public static final int USER_OPERATION_ERROR_UNKNOWN = 1;
+
+ /**
+ * Indicates user operation failed because target user is a managed profile.
+ */
+ public static final int USER_OPERATION_ERROR_MANAGED_PROFILE = 2;
+
+ /**
+ * Indicates user operation failed because maximum running user limit has been reached.
+ */
+ public static final int USER_OPERATION_ERROR_MAX_RUNNING_USERS = 3;
+
+ /**
+ * Indicates user operation failed because the target user is in the foreground.
+ */
+ public static final int USER_OPERATION_ERROR_CURRENT_USER = 4;
+
+ /**
+ * Indicates user operation failed because device has low data storage.
+ */
+ public static final int USER_OPERATION_ERROR_LOW_STORAGE = 5;
+
+ /**
+ * Indicates user operation failed because maximum user limit has been reached.
+ */
+ public static final int USER_OPERATION_ERROR_MAX_USERS = 6;
+
+ /**
+ * Result returned from various user operations.
+ *
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = { "USER_OPERATION_" }, value = {
+ USER_OPERATION_SUCCESS,
+ USER_OPERATION_ERROR_UNKNOWN,
+ USER_OPERATION_ERROR_MANAGED_PROFILE,
+ USER_OPERATION_ERROR_MAX_RUNNING_USERS,
+ USER_OPERATION_ERROR_CURRENT_USER,
+ USER_OPERATION_ERROR_LOW_STORAGE,
+ USER_OPERATION_ERROR_MAX_USERS
+ })
+ public @interface UserOperationResult {}
+
+ /**
+ * Thrown to indicate user operation failed.
+ */
+ public static class UserOperationException extends RuntimeException {
+ private final @UserOperationResult int mUserOperationResult;
+
+ /**
+ * Constructs a UserOperationException with specific result code.
+ *
+ * @param message the detail message
+ * @param userOperationResult the result code
+ * @hide
+ */
+ public UserOperationException(String message,
+ @UserOperationResult int userOperationResult) {
+ super(message);
+ mUserOperationResult = userOperationResult;
+ }
+
+ /**
+ * Returns the operation result code.
+ */
+ public @UserOperationResult int getUserOperationResult() {
+ return mUserOperationResult;
+ }
+ }
+
/** @hide */
public static UserManager get(Context context) {
return (UserManager) context.getSystemService(Context.USER_SERVICE);
diff --git a/core/java/android/os/ZygoteProcess.java b/core/java/android/os/ZygoteProcess.java
index 57418c8..ca4c796 100644
--- a/core/java/android/os/ZygoteProcess.java
+++ b/core/java/android/os/ZygoteProcess.java
@@ -493,11 +493,12 @@
* Instructs the zygote to pre-load the classes and native libraries at the given paths
* for the specified abi. Not all zygotes support this function.
*/
- public boolean preloadPackageForAbi(String packagePath, String libsPath, String cacheKey,
- String abi) throws ZygoteStartFailedEx, IOException {
+ public boolean preloadPackageForAbi(String packagePath, String libsPath, String libFileName,
+ String cacheKey, String abi) throws ZygoteStartFailedEx,
+ IOException {
synchronized(mLock) {
ZygoteState state = openZygoteSocketIfNeeded(abi);
- state.writer.write("4");
+ state.writer.write("5");
state.writer.newLine();
state.writer.write("--preload-package");
@@ -509,6 +510,9 @@
state.writer.write(libsPath);
state.writer.newLine();
+ state.writer.write(libFileName);
+ state.writer.newLine();
+
state.writer.write(cacheKey);
state.writer.newLine();
diff --git a/core/java/android/provider/CallLog.java b/core/java/android/provider/CallLog.java
index 60df467..70de09e 100644
--- a/core/java/android/provider/CallLog.java
+++ b/core/java/android/provider/CallLog.java
@@ -223,14 +223,14 @@
/** Call was WIFI call. */
public static final int FEATURES_WIFI = 1 << 3;
- /** Call was on RTT at some point */
- public static final int FEATURES_RTT = 1 << 4;
-
/**
* Indicates the call underwent Assisted Dialing.
* @hide
*/
- public static final Integer FEATURES_ASSISTED_DIALING_USED = 0x10;
+ public static final int FEATURES_ASSISTED_DIALING_USED = 1 << 4;
+
+ /** Call was on RTT at some point */
+ public static final int FEATURES_RTT = 1 << 5;
/**
* The phone number as the user entered it.
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index cb38c0f..0299407 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -8581,6 +8581,7 @@
* (0 = false, 1 = true)
* @hide
*/
+ @SystemApi
public static final String EUICC_PROVISIONED = "euicc_provisioned";
/**
diff --git a/core/java/android/security/ConfirmationDialog.java b/core/java/android/security/ConfirmationDialog.java
index e9df370..f6127e1 100644
--- a/core/java/android/security/ConfirmationDialog.java
+++ b/core/java/android/security/ConfirmationDialog.java
@@ -17,7 +17,10 @@
package android.security;
import android.annotation.NonNull;
+import android.content.ContentResolver;
import android.content.Context;
+import android.provider.Settings;
+import android.provider.Settings.SettingNotFoundException;
import android.text.TextUtils;
import android.util.Log;
@@ -86,6 +89,7 @@
private byte[] mExtraData;
private ConfirmationCallback mCallback;
private Executor mExecutor;
+ private Context mContext;
private final KeyStore mKeyStore = KeyStore.getInstance();
@@ -190,15 +194,39 @@
if (mExtraData == null) {
throw new IllegalArgumentException("extraData must be set");
}
- return new ConfirmationDialog(mPromptText, mExtraData);
+ return new ConfirmationDialog(context, mPromptText, mExtraData);
}
}
- private ConfirmationDialog(CharSequence promptText, byte[] extraData) {
+ private ConfirmationDialog(Context context, CharSequence promptText, byte[] extraData) {
+ mContext = context;
mPromptText = promptText;
mExtraData = extraData;
}
+ private static final int UI_OPTION_ACCESSIBILITY_INVERTED_FLAG = 1 << 0;
+ private static final int UI_OPTION_ACCESSIBILITY_MAGNIFIED_FLAG = 1 << 1;
+
+ private int getUiOptionsAsFlags() {
+ int uiOptionsAsFlags = 0;
+ try {
+ ContentResolver contentResolver = mContext.getContentResolver();
+ int inversionEnabled = Settings.Secure.getInt(contentResolver,
+ Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED);
+ if (inversionEnabled == 1) {
+ uiOptionsAsFlags |= UI_OPTION_ACCESSIBILITY_INVERTED_FLAG;
+ }
+ float fontScale = Settings.System.getFloat(contentResolver,
+ Settings.System.FONT_SCALE);
+ if (fontScale > 1.0) {
+ uiOptionsAsFlags |= UI_OPTION_ACCESSIBILITY_MAGNIFIED_FLAG;
+ }
+ } catch (SettingNotFoundException e) {
+ Log.w(TAG, "Unexpected SettingNotFoundException");
+ }
+ return uiOptionsAsFlags;
+ }
+
/**
* Requests a confirmation prompt to be presented to the user.
*
@@ -220,8 +248,7 @@
mCallback = callback;
mExecutor = executor;
- int uiOptionsAsFlags = 0;
- // TODO: set AccessibilityInverted, AccessibilityMagnified in uiOptionsAsFlags as needed.
+ int uiOptionsAsFlags = getUiOptionsAsFlags();
String locale = Locale.getDefault().toLanguageTag();
int responseCode = mKeyStore.presentConfirmationPrompt(
mCallbackBinder, mPromptText.toString(), mExtraData, locale, uiOptionsAsFlags);
@@ -277,7 +304,6 @@
* @return true if confirmation prompts are supported by the device.
*/
public static boolean isSupported() {
- // TODO: read and return system property.
- return true;
+ return KeyStore.getInstance().isConfirmationPromptSupported();
}
}
diff --git a/core/java/android/security/keystore/BadCertificateFormatException.java b/core/java/android/security/keystore/BadCertificateFormatException.java
index ddc7bd2..c51b773 100644
--- a/core/java/android/security/keystore/BadCertificateFormatException.java
+++ b/core/java/android/security/keystore/BadCertificateFormatException.java
@@ -17,8 +17,7 @@
package android.security.keystore;
/**
- * Error thrown when the recovery agent supplies an invalid X509 certificate.
- *
+ * @deprecated Use {@link android.security.keystore.recovery.BadCertificateFormatException}.
* @hide
*/
public class BadCertificateFormatException extends RecoveryControllerException {
diff --git a/core/java/android/security/keystore/DecryptionFailedException.java b/core/java/android/security/keystore/DecryptionFailedException.java
index 945fcf6..c0b52f7 100644
--- a/core/java/android/security/keystore/DecryptionFailedException.java
+++ b/core/java/android/security/keystore/DecryptionFailedException.java
@@ -17,9 +17,7 @@
package android.security.keystore;
/**
- * Error thrown when decryption failed, due to an agent error. i.e., using the incorrect key,
- * trying to decrypt garbage data, trying to decrypt data that has somehow been corrupted, etc.
- *
+ * @deprecated Use {@link android.security.keystore.recovery.DecryptionFailedException}.
* @hide
*/
public class DecryptionFailedException extends RecoveryControllerException {
diff --git a/core/java/android/security/keystore/InternalRecoveryServiceException.java b/core/java/android/security/keystore/InternalRecoveryServiceException.java
index 85829be..40076f7 100644
--- a/core/java/android/security/keystore/InternalRecoveryServiceException.java
+++ b/core/java/android/security/keystore/InternalRecoveryServiceException.java
@@ -17,11 +17,7 @@
package android.security.keystore;
/**
- * An error thrown when something went wrong internally in the recovery service.
- *
- * <p>This is an unexpected error, and indicates a problem with the service itself, rather than the
- * caller having performed some kind of illegal action.
- *
+ * @deprecated Use {@link android.security.keystore.recovery.InternalRecoveryServiceException}.
* @hide
*/
public class InternalRecoveryServiceException extends RecoveryControllerException {
diff --git a/core/java/android/security/keystore/KeyDerivationParams.java b/core/java/android/security/keystore/KeyDerivationParams.java
index b19cee2..e475dc3 100644
--- a/core/java/android/security/keystore/KeyDerivationParams.java
+++ b/core/java/android/security/keystore/KeyDerivationParams.java
@@ -27,9 +27,7 @@
import java.lang.annotation.RetentionPolicy;
/**
- * Collection of parameters which define a key derivation function.
- * Currently only supports salted SHA-256
- *
+ * @deprecated Use {@link android.security.keystore.recovery.KeyDerivationParams}.
* @hide
*/
public final class KeyDerivationParams implements Parcelable {
diff --git a/core/java/android/security/keystore/KeychainProtectionParams.java b/core/java/android/security/keystore/KeychainProtectionParams.java
index a940fdc..19a087d 100644
--- a/core/java/android/security/keystore/KeychainProtectionParams.java
+++ b/core/java/android/security/keystore/KeychainProtectionParams.java
@@ -28,23 +28,7 @@
import java.util.Arrays;
/**
- * A {@link KeychainSnapshot} is protected with a key derived from the user's lock screen. This
- * class wraps all the data necessary to derive the same key on a recovering device:
- *
- * <ul>
- * <li>UI parameters for the user's lock screen - so that if e.g., the user was using a pattern,
- * the recovering device can display the pattern UI to the user when asking them to enter
- * the lock screen from their previous device.
- * <li>The algorithm used to derive a key from the user's lock screen, e.g. SHA-256 with a salt.
- * </ul>
- *
- * <p>As such, this data is sent along with the {@link KeychainSnapshot} when syncing the current
- * version of the keychain.
- *
- * <p>For now, the recoverable keychain only supports a single layer of protection, which is the
- * user's lock screen. In the future, the keychain will support multiple layers of protection
- * (e.g. an additional keychain password, along with the lock screen).
- *
+ * @deprecated Use {@link android.security.keystore.recovery.KeyChainProtectionParams}.
* @hide
*/
public final class KeychainProtectionParams implements Parcelable {
diff --git a/core/java/android/security/keystore/KeychainSnapshot.java b/core/java/android/security/keystore/KeychainSnapshot.java
index 23aec25..cf18fd1 100644
--- a/core/java/android/security/keystore/KeychainSnapshot.java
+++ b/core/java/android/security/keystore/KeychainSnapshot.java
@@ -25,21 +25,7 @@
import java.util.List;
/**
- * A snapshot of a version of the keystore. Two events can trigger the generation of a new snapshot:
- *
- * <ul>
- * <li>The user's lock screen changes. (A key derived from the user's lock screen is used to
- * protected the keychain, which is why this forces a new snapshot.)
- * <li>A key is added to or removed from the recoverable keychain.
- * </ul>
- *
- * <p>The snapshot data is also encrypted with the remote trusted hardware's public key, so even
- * the recovery agent itself should not be able to decipher the data. The recovery agent sends an
- * instance of this to the remote trusted hardware whenever a new snapshot is generated. During a
- * recovery flow, the recovery agent retrieves a snapshot from the remote trusted hardware. It then
- * sends it to the framework, where it is decrypted using the user's lock screen from their previous
- * device.
- *
+ * @deprecated Use {@link android.security.keystore.recovery.KeyChainSnapshot}.
* @hide
*/
public final class KeychainSnapshot implements Parcelable {
diff --git a/core/java/android/security/keystore/LockScreenRequiredException.java b/core/java/android/security/keystore/LockScreenRequiredException.java
index b07fb9c..0970284 100644
--- a/core/java/android/security/keystore/LockScreenRequiredException.java
+++ b/core/java/android/security/keystore/LockScreenRequiredException.java
@@ -17,10 +17,7 @@
package android.security.keystore;
/**
- * Error thrown when trying to generate keys for a profile that has no lock screen set.
- *
- * <p>A lock screen must be set, as the lock screen is used to encrypt the snapshot.
- *
+ * @deprecated Use {@link android.security.keystore.recovery.LockScreenRequiredException}.
* @hide
*/
public class LockScreenRequiredException extends RecoveryControllerException {
diff --git a/core/java/android/security/keystore/RecoveryClaim.java b/core/java/android/security/keystore/RecoveryClaim.java
index 6f566af..12be607 100644
--- a/core/java/android/security/keystore/RecoveryClaim.java
+++ b/core/java/android/security/keystore/RecoveryClaim.java
@@ -17,8 +17,7 @@
package android.security.keystore;
/**
- * An attempt to recover a keychain protected by remote secure hardware.
- *
+ * @deprecated Use {@link android.security.keystore.recovery.RecoverySession}.
* @hide
*/
public class RecoveryClaim {
diff --git a/core/java/android/security/keystore/RecoveryController.java b/core/java/android/security/keystore/RecoveryController.java
index 4a0de5f..145261e 100644
--- a/core/java/android/security/keystore/RecoveryController.java
+++ b/core/java/android/security/keystore/RecoveryController.java
@@ -31,22 +31,6 @@
import java.util.Map;
/**
- * An assistant for generating {@link javax.crypto.SecretKey} instances that can be recovered by
- * other Android devices belonging to the user. The exported keychain is protected by the user's
- * lock screen.
- *
- * <p>The RecoveryController must be paired with a recovery agent. The recovery agent is responsible
- * for transporting the keychain to remote trusted hardware. This hardware must prevent brute force
- * attempts against the user's lock screen by limiting the number of allowed guesses (to, e.g., 10).
- * After that number of incorrect guesses, the trusted hardware no longer allows access to the
- * key chain.
- *
- * <p>For now only the recovery agent itself is able to create keys, so it is expected that the
- * recovery agent is itself the system app.
- *
- * <p>A recovery agent requires the privileged permission
- * {@code android.Manifest.permission#RECOVER_KEYSTORE}.
- *
* @deprecated Use {@link android.security.keystore.recovery.RecoveryController}.
* @hide
*/
diff --git a/core/java/android/security/keystore/RecoveryControllerException.java b/core/java/android/security/keystore/RecoveryControllerException.java
index 5b806b7..f990c23 100644
--- a/core/java/android/security/keystore/RecoveryControllerException.java
+++ b/core/java/android/security/keystore/RecoveryControllerException.java
@@ -19,8 +19,7 @@
import java.security.GeneralSecurityException;
/**
- * Base exception for errors thrown by {@link RecoveryController}.
- *
+ * @deprecated Use {@link android.security.keystore.recovery.RecoveryController}.
* @hide
*/
public abstract class RecoveryControllerException extends GeneralSecurityException {
diff --git a/core/java/android/security/keystore/RecoverySession.java b/core/java/android/security/keystore/RecoverySession.java
index ae8d91a..8a3e06b 100644
--- a/core/java/android/security/keystore/RecoverySession.java
+++ b/core/java/android/security/keystore/RecoverySession.java
@@ -19,9 +19,7 @@
import java.security.SecureRandom;
/**
- * Session to recover a {@link KeychainSnapshot} from the remote trusted hardware, initiated by a
- * recovery agent.
- *
+ * @deprecated Use {@link android.security.keystore.recovery.RecoverySession}.
* @hide
*/
public class RecoverySession implements AutoCloseable {
diff --git a/core/java/android/security/keystore/SessionExpiredException.java b/core/java/android/security/keystore/SessionExpiredException.java
index f13e206..7c8d5e4 100644
--- a/core/java/android/security/keystore/SessionExpiredException.java
+++ b/core/java/android/security/keystore/SessionExpiredException.java
@@ -17,8 +17,7 @@
package android.security.keystore;
/**
- * Error thrown when attempting to use a {@link RecoverySession} that has since expired.
- *
+ * @deprecated Use {@link android.security.keystore.recovery.SessionExpiredException}.
* @hide
*/
public class SessionExpiredException extends RecoveryControllerException {
diff --git a/core/java/android/security/keystore/WrappedApplicationKey.java b/core/java/android/security/keystore/WrappedApplicationKey.java
index 522bb95..2ce8c7d 100644
--- a/core/java/android/security/keystore/WrappedApplicationKey.java
+++ b/core/java/android/security/keystore/WrappedApplicationKey.java
@@ -23,16 +23,7 @@
import com.android.internal.util.Preconditions;
/**
- * Helper class with data necessary recover a single application key, given a recovery key.
- *
- * <ul>
- * <li>Alias - Keystore alias of the key.
- * <li>Encrypted key material.
- * </ul>
- *
- * Note that Application info is not included. Recovery Agent can only make its own keys
- * recoverable.
- *
+ * @deprecated Use {@link android.security.keystore.recovery.WrappedApplicationKey}.
* @hide
*/
public final class WrappedApplicationKey implements Parcelable {
diff --git a/core/java/android/security/keystore/recovery/RecoveryClaim.java b/core/java/android/security/keystore/recovery/RecoveryClaim.java
deleted file mode 100644
index 45c6b4ff..0000000
--- a/core/java/android/security/keystore/recovery/RecoveryClaim.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.security.keystore.recovery;
-
-/**
- * An attempt to recover a keychain protected by remote secure hardware.
- *
- * @hide
- * Deprecated
- */
-public class RecoveryClaim {
-
- private final RecoverySession mRecoverySession;
- private final byte[] mClaimBytes;
-
- RecoveryClaim(RecoverySession recoverySession, byte[] claimBytes) {
- mRecoverySession = recoverySession;
- mClaimBytes = claimBytes;
- }
-
- /**
- * Returns the session associated with the recovery attempt. This is used to match the symmetric
- * key, which remains internal to the framework, for decrypting the claim response.
- *
- * @return The session data.
- */
- public RecoverySession getRecoverySession() {
- return mRecoverySession;
- }
-
- /**
- * Returns the encrypted claim's bytes.
- *
- * <p>This should be sent by the recovery agent to the remote secure hardware, which will use
- * it to decrypt the keychain, before sending it re-encrypted with the session's symmetric key
- * to the device.
- */
- public byte[] getClaimBytes() {
- return mClaimBytes;
- }
-}
diff --git a/core/java/android/security/keystore/recovery/RecoveryController.java b/core/java/android/security/keystore/recovery/RecoveryController.java
index 0683e02..426ca5c 100644
--- a/core/java/android/security/keystore/recovery/RecoveryController.java
+++ b/core/java/android/security/keystore/recovery/RecoveryController.java
@@ -113,6 +113,14 @@
*/
public static final int ERROR_DECRYPTION_FAILED = 26;
+ /**
+ * Error thrown if the format of a given key is invalid. This might be because the key has a
+ * wrong length, invalid content, etc.
+ *
+ * @hide
+ */
+ public static final int ERROR_INVALID_KEY_FORMAT = 27;
+
private final ILockSettings mBinder;
private final KeyStore mKeyStore;
@@ -461,6 +469,7 @@
}
}
+ // TODO: Unhide the following APIs, generateKey(), importKey(), and getKey()
/**
* @deprecated Use {@link #generateKey(String)}.
* @removed
@@ -503,6 +512,40 @@
}
/**
+ * Imports a 256-bit recoverable AES key with the given {@code alias} and the raw bytes {@code
+ * keyBytes}.
+ *
+ * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery
+ * service.
+ * @throws LockScreenRequiredException if the user does not have a lock screen set. A lock
+ * screen is required to generate recoverable keys.
+ *
+ * @hide
+ */
+ public Key importKey(@NonNull String alias, byte[] keyBytes)
+ throws InternalRecoveryServiceException, LockScreenRequiredException {
+ try {
+ String grantAlias = mBinder.importKey(alias, keyBytes);
+ if (grantAlias == null) {
+ throw new InternalRecoveryServiceException("Null grant alias");
+ }
+ return AndroidKeyStoreProvider.loadAndroidKeyStoreKeyFromKeystore(
+ mKeyStore,
+ grantAlias,
+ KeyStore.UID_SELF);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ } catch (UnrecoverableKeyException e) {
+ throw new InternalRecoveryServiceException("Failed to get key from keystore", e);
+ } catch (ServiceSpecificException e) {
+ if (e.errorCode == ERROR_INSECURE_USER) {
+ throw new LockScreenRequiredException(e.getMessage());
+ }
+ throw wrapUnexpectedServiceSpecificException(e);
+ }
+ }
+
+ /**
* Gets a key called {@code alias} from the recoverable key store.
*
* @param alias The key alias.
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index 422e36b..eebd22a 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -85,7 +85,10 @@
* or after {@link #onListenerDisconnected()}.
* </p>
* <p> Notification listeners cannot get notification access or be bound by the system on
- * {@link ActivityManager#isLowRamDevice() low ram} devices</p>
+ * {@linkplain ActivityManager#isLowRamDevice() low-RAM} devices. The system also ignores
+ * notification listeners running in a work profile. A
+ * {@link android.app.admin.DevicePolicyManager} might block notifications originating from a work
+ * profile.</p>
*/
public abstract class NotificationListenerService extends Service {
@@ -1217,6 +1220,7 @@
// convert icon metadata to legacy format for older clients
createLegacyIconExtras(sbn.getNotification());
maybePopulateRemoteViews(sbn.getNotification());
+ maybePopulatePeople(sbn.getNotification());
} catch (IllegalArgumentException e) {
// warn and drop corrupt notification
Log.w(TAG, "onNotificationPosted: can't rebuild notification from " +
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index 1ead0b4..a5a7cbc 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -37,7 +37,6 @@
private static final Map<String, String> DEFAULT_FLAGS;
static {
DEFAULT_FLAGS = new HashMap<>();
- DEFAULT_FLAGS.put("settings_connected_device_v2", "true");
DEFAULT_FLAGS.put("settings_battery_v2", "true");
DEFAULT_FLAGS.put("settings_battery_display_app_list", "false");
DEFAULT_FLAGS.put("settings_zone_picker_v2", "true");
diff --git a/core/java/android/util/LauncherIcons.java b/core/java/android/util/LauncherIcons.java
index 402bef9..cc9991a 100644
--- a/core/java/android/util/LauncherIcons.java
+++ b/core/java/android/util/LauncherIcons.java
@@ -110,9 +110,9 @@
Drawable badgeColor = sysRes.getDrawable(
com.android.internal.R.drawable.ic_corp_icon_badge_color)
.getConstantState().newDrawable().mutate();
- badgeColor.setTint(backgroundColor);
Drawable badgeForeground = sysRes.getDrawable(foregroundRes);
+ badgeForeground.setTint(backgroundColor);
Drawable[] drawables = base == null
? new Drawable[] {badgeShadow, badgeColor, badgeForeground }
diff --git a/core/java/android/view/RemoteAnimationAdapter.java b/core/java/android/view/RemoteAnimationAdapter.java
index d597e59..a864e55 100644
--- a/core/java/android/view/RemoteAnimationAdapter.java
+++ b/core/java/android/view/RemoteAnimationAdapter.java
@@ -52,6 +52,9 @@
private final long mDuration;
private final long mStatusBarTransitionDelay;
+ /** @see #getCallingPid */
+ private int mCallingPid;
+
/**
* @param runner The interface that gets notified when we actually need to start the animation.
* @param duration The duration of the animation.
@@ -83,6 +86,20 @@
return mStatusBarTransitionDelay;
}
+ /**
+ * To be called by system_server to keep track which pid is running this animation.
+ */
+ public void setCallingPid(int pid) {
+ mCallingPid = pid;
+ }
+
+ /**
+ * @return The pid of the process running the animation.
+ */
+ public int getCallingPid() {
+ return mCallingPid;
+ }
+
@Override
public int describeContents() {
return 0;
diff --git a/core/java/android/view/RemoteAnimationDefinition.java b/core/java/android/view/RemoteAnimationDefinition.java
index 381f692..8def435 100644
--- a/core/java/android/view/RemoteAnimationDefinition.java
+++ b/core/java/android/view/RemoteAnimationDefinition.java
@@ -70,6 +70,16 @@
mTransitionAnimationMap = in.readSparseArray(null /* loader */);
}
+ /**
+ * To be called by system_server to keep track which pid is running the remote animations inside
+ * this definition.
+ */
+ public void setCallingPid(int pid) {
+ for (int i = mTransitionAnimationMap.size() - 1; i >= 0; i--) {
+ mTransitionAnimationMap.valueAt(i).setCallingPid(pid);
+ }
+ }
+
@Override
public int describeContents() {
return 0;
diff --git a/core/java/android/webkit/WebViewFactory.java b/core/java/android/webkit/WebViewFactory.java
index e9fe481..e0ccda9 100644
--- a/core/java/android/webkit/WebViewFactory.java
+++ b/core/java/android/webkit/WebViewFactory.java
@@ -205,25 +205,21 @@
}
PackageManager packageManager = AppGlobals.getInitialApplication().getPackageManager();
- PackageInfo packageInfo;
+ String libraryFileName;
try {
- packageInfo = packageManager.getPackageInfo(packageName,
+ PackageInfo packageInfo = packageManager.getPackageInfo(packageName,
PackageManager.GET_META_DATA | PackageManager.MATCH_DEBUG_TRIAGED_MISSING);
+ libraryFileName = getWebViewLibrary(packageInfo.applicationInfo);
} catch (PackageManager.NameNotFoundException e) {
Log.e(LOGTAG, "Couldn't find package " + packageName);
return LIBLOAD_WRONG_PACKAGE_NAME;
}
- try {
- int loadNativeRet = WebViewLibraryLoader.loadNativeLibrary(clazzLoader, packageInfo);
- // If we failed waiting for relro we want to return that fact even if we successfully
- // load the relro file.
- if (loadNativeRet == LIBLOAD_SUCCESS) return response.status;
- return loadNativeRet;
- } catch (MissingWebViewPackageException e) {
- Log.e(LOGTAG, "Couldn't load native library: " + e);
- return LIBLOAD_FAILED_TO_LOAD_LIBRARY;
- }
+ int loadNativeRet = WebViewLibraryLoader.loadNativeLibrary(clazzLoader, libraryFileName);
+ // If we failed waiting for relro we want to return that fact even if we successfully
+ // load the relro file.
+ if (loadNativeRet == LIBLOAD_SUCCESS) return response.status;
+ return loadNativeRet;
}
static WebViewFactoryProvider getProvider() {
@@ -454,7 +450,8 @@
ClassLoader clazzLoader = webViewContext.getClassLoader();
Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewFactory.loadNativeLibrary()");
- WebViewLibraryLoader.loadNativeLibrary(clazzLoader, sPackageInfo);
+ WebViewLibraryLoader.loadNativeLibrary(clazzLoader,
+ getWebViewLibrary(sPackageInfo.applicationInfo));
Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "Class.forName()");
diff --git a/core/java/android/webkit/WebViewLibraryLoader.java b/core/java/android/webkit/WebViewLibraryLoader.java
index eb2b6bc..cabba06 100644
--- a/core/java/android/webkit/WebViewLibraryLoader.java
+++ b/core/java/android/webkit/WebViewLibraryLoader.java
@@ -234,17 +234,14 @@
* <p class="note"><b>Note:</b> Assumes that we have waited for relro creation.
*
* @param clazzLoader class loader used to find the linker namespace to load the library into.
- * @param packageInfo the package from which WebView is loaded.
+ * @param libraryFileName the filename of the library to load.
*/
- static int loadNativeLibrary(ClassLoader clazzLoader, PackageInfo packageInfo)
- throws WebViewFactory.MissingWebViewPackageException {
+ public static int loadNativeLibrary(ClassLoader clazzLoader, String libraryFileName) {
if (!sAddressSpaceReserved) {
Log.e(LOGTAG, "can't load with relro file; address space not reserved");
return WebViewFactory.LIBLOAD_ADDRESS_SPACE_NOT_RESERVED;
}
- final String libraryFileName =
- WebViewFactory.getWebViewLibrary(packageInfo.applicationInfo);
String relroPath = VMRuntime.getRuntime().is64Bit() ? CHROMIUM_WEBVIEW_NATIVE_RELRO_64 :
CHROMIUM_WEBVIEW_NATIVE_RELRO_32;
int result = nativeLoadWithRelroFile(libraryFileName, relroPath, clazzLoader);
diff --git a/core/java/android/webkit/WebViewZygote.java b/core/java/android/webkit/WebViewZygote.java
index 63fbef3..4167ad4 100644
--- a/core/java/android/webkit/WebViewZygote.java
+++ b/core/java/android/webkit/WebViewZygote.java
@@ -169,6 +169,8 @@
final String zip = (zipPaths.size() == 1) ? zipPaths.get(0) :
TextUtils.join(File.pathSeparator, zipPaths);
+ String libFileName = WebViewFactory.getWebViewLibrary(sPackage.applicationInfo);
+
// In the case where the ApplicationInfo has been modified by the stub WebView,
// we need to use the original ApplicationInfo to determine what the original classpath
// would have been to use as a cache key.
@@ -179,7 +181,7 @@
ZygoteProcess.waitForConnectionToZygote(sZygote.getPrimarySocketAddress());
Log.d(LOGTAG, "Preloading package " + zip + " " + librarySearchPath);
- sZygote.preloadPackageForAbi(zip, librarySearchPath, cacheKey,
+ sZygote.preloadPackageForAbi(zip, librarySearchPath, libFileName, cacheKey,
Build.SUPPORTED_ABIS[0]);
} catch (Exception e) {
Log.e(LOGTAG, "Error connecting to webview zygote", e);
diff --git a/core/java/android/widget/Magnifier.java b/core/java/android/widget/Magnifier.java
index 6e87e23..85f68d7 100644
--- a/core/java/android/widget/Magnifier.java
+++ b/core/java/android/widget/Magnifier.java
@@ -24,6 +24,7 @@
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
+import android.graphics.Color;
import android.graphics.Outline;
import android.graphics.Paint;
import android.graphics.PixelFormat;
@@ -319,6 +320,10 @@
* producing a shakiness effect for the magnifier content.
*/
private static class InternalPopupWindow {
+ // The alpha set on the magnifier's content, which defines how
+ // prominent the white background is.
+ private static final int CONTENT_BITMAP_ALPHA = 242;
+
// Display associated to the view the magnifier is attached to.
private final Display mDisplay;
// The size of the content of the magnifier.
@@ -511,10 +516,13 @@
final DisplayListCanvas canvas =
mBitmapRenderNode.start(mContentWidth, mContentHeight);
try {
+ canvas.drawColor(Color.WHITE);
+
final Rect srcRect = new Rect(0, 0, mBitmap.getWidth(), mBitmap.getHeight());
final Rect dstRect = new Rect(0, 0, mContentWidth, mContentHeight);
final Paint paint = new Paint();
paint.setFilterBitmap(true);
+ paint.setAlpha(CONTENT_BITMAP_ALPHA);
canvas.drawBitmap(mBitmap, srcRect, dstRect, paint);
} finally {
mBitmapRenderNode.end(canvas);
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 2cfdb76..50e6393 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -319,6 +319,11 @@
// Enum for the "typeface" XML parameter.
// TODO: How can we get this from the XML instead of hardcoding it here?
+ /** @hide */
+ @IntDef(value = {DEFAULT_TYPEFACE, SANS, SERIF, MONOSPACE})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface XMLTypefaceAttr{}
+ private static final int DEFAULT_TYPEFACE = -1;
private static final int SANS = 1;
private static final int SERIF = 2;
private static final int MONOSPACE = 3;
@@ -1976,33 +1981,52 @@
}
}
- private void setTypefaceFromAttrs(Typeface fontTypeface, String familyName, int typefaceIndex,
- int styleIndex) {
- Typeface tf = fontTypeface;
- if (tf == null && familyName != null) {
- tf = Typeface.create(familyName, styleIndex);
- } else if (tf != null && tf.getStyle() != styleIndex) {
- tf = Typeface.create(tf, styleIndex);
+ /**
+ * Sets the Typeface taking into account the given attributes.
+ *
+ * @param typeface a typeface
+ * @param familyName family name string, e.g. "serif"
+ * @param typefaceIndex an index of the typeface enum, e.g. SANS, SERIF.
+ * @param style a typeface style
+ * @param weight a weight value for the Typeface or -1 if not specified.
+ */
+ private void setTypefaceFromAttrs(@Nullable Typeface typeface, @Nullable String familyName,
+ @XMLTypefaceAttr int typefaceIndex, @Typeface.Style int style,
+ @IntRange(from = -1, to = Typeface.MAX_WEIGHT) int weight) {
+ if (typeface == null && familyName != null) {
+ // Lookup normal Typeface from system font map.
+ final Typeface normalTypeface = Typeface.create(familyName, Typeface.NORMAL);
+ resolveStyleAndSetTypeface(normalTypeface, style, weight);
+ } else if (typeface != null) {
+ resolveStyleAndSetTypeface(typeface, style, weight);
+ } else { // both typeface and familyName is null.
+ switch (typefaceIndex) {
+ case SANS:
+ resolveStyleAndSetTypeface(Typeface.SANS_SERIF, style, weight);
+ break;
+ case SERIF:
+ resolveStyleAndSetTypeface(Typeface.SERIF, style, weight);
+ break;
+ case MONOSPACE:
+ resolveStyleAndSetTypeface(Typeface.MONOSPACE, style, weight);
+ break;
+ case DEFAULT_TYPEFACE:
+ default:
+ resolveStyleAndSetTypeface(null, style, weight);
+ break;
+ }
}
- if (tf != null) {
- setTypeface(tf);
- return;
+ }
+
+ private void resolveStyleAndSetTypeface(@NonNull Typeface typeface, @Typeface.Style int style,
+ @IntRange(from = -1, to = Typeface.MAX_WEIGHT) int weight) {
+ if (weight >= 0) {
+ weight = Math.min(Typeface.MAX_WEIGHT, weight);
+ final boolean italic = (style & Typeface.ITALIC) != 0;
+ setTypeface(Typeface.create(typeface, weight, italic));
+ } else {
+ setTypeface(Typeface.create(typeface, style));
}
- switch (typefaceIndex) {
- case SANS:
- tf = Typeface.SANS_SERIF;
- break;
-
- case SERIF:
- tf = Typeface.SERIF;
- break;
-
- case MONOSPACE:
- tf = Typeface.MONOSPACE;
- break;
- }
-
- setTypeface(tf, styleIndex);
}
private void setRelativeDrawablesIfNeeded(Drawable start, Drawable end) {
@@ -3392,6 +3416,7 @@
boolean mFontFamilyExplicit = false;
int mTypefaceIndex = -1;
int mStyleIndex = -1;
+ int mFontWeight = -1;
boolean mAllCaps = false;
int mShadowColor = 0;
float mShadowDx = 0, mShadowDy = 0, mShadowRadius = 0;
@@ -3416,6 +3441,7 @@
+ " mFontFamilyExplicit:" + mFontFamilyExplicit + "\n"
+ " mTypefaceIndex:" + mTypefaceIndex + "\n"
+ " mStyleIndex:" + mStyleIndex + "\n"
+ + " mFontWeight:" + mFontWeight + "\n"
+ " mAllCaps:" + mAllCaps + "\n"
+ " mShadowColor:" + mShadowColor + "\n"
+ " mShadowDx:" + mShadowDx + "\n"
@@ -3451,6 +3477,8 @@
com.android.internal.R.styleable.TextAppearance_fontFamily);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_textStyle,
com.android.internal.R.styleable.TextAppearance_textStyle);
+ sAppearanceValues.put(com.android.internal.R.styleable.TextView_textFontWeight,
+ com.android.internal.R.styleable.TextAppearance_textFontWeight);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_textAllCaps,
com.android.internal.R.styleable.TextAppearance_textAllCaps);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowColor,
@@ -3536,6 +3564,9 @@
case com.android.internal.R.styleable.TextAppearance_textStyle:
attributes.mStyleIndex = appearance.getInt(attr, attributes.mStyleIndex);
break;
+ case com.android.internal.R.styleable.TextAppearance_textFontWeight:
+ attributes.mFontWeight = appearance.getInt(attr, attributes.mFontWeight);
+ break;
case com.android.internal.R.styleable.TextAppearance_textAllCaps:
attributes.mAllCaps = appearance.getBoolean(attr, attributes.mAllCaps);
break;
@@ -3598,7 +3629,7 @@
attributes.mFontFamily = null;
}
setTypefaceFromAttrs(attributes.mFontTypeface, attributes.mFontFamily,
- attributes.mTypefaceIndex, attributes.mStyleIndex);
+ attributes.mTypefaceIndex, attributes.mStyleIndex, attributes.mFontWeight);
if (attributes.mShadowColor != 0) {
setShadowLayer(attributes.mShadowRadius, attributes.mShadowDx, attributes.mShadowDy,
@@ -5938,15 +5969,19 @@
boolean forceUpdate = false;
if (isPassword) {
setTransformationMethod(PasswordTransformationMethod.getInstance());
- setTypefaceFromAttrs(null/* fontTypeface */, null /* fontFamily */, MONOSPACE, 0);
+ setTypefaceFromAttrs(null/* fontTypeface */, null /* fontFamily */, MONOSPACE,
+ Typeface.NORMAL, -1 /* weight, not specifeid */);
} else if (isVisiblePassword) {
if (mTransformation == PasswordTransformationMethod.getInstance()) {
forceUpdate = true;
}
- setTypefaceFromAttrs(null/* fontTypeface */, null /* fontFamily */, MONOSPACE, 0);
+ setTypefaceFromAttrs(null/* fontTypeface */, null /* fontFamily */, MONOSPACE,
+ Typeface.NORMAL, -1 /* weight, not specified */);
} else if (wasPassword || wasVisiblePassword) {
// not in password mode, clean up typeface and transformation
- setTypefaceFromAttrs(null/* fontTypeface */, null /* fontFamily */, -1, -1);
+ setTypefaceFromAttrs(null/* fontTypeface */, null /* fontFamily */,
+ DEFAULT_TYPEFACE /* typeface index */, Typeface.NORMAL,
+ -1 /* weight, not specified */);
if (mTransformation == PasswordTransformationMethod.getInstance()) {
forceUpdate = true;
}
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 7fc36bc..8ee31f7 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -13436,6 +13436,7 @@
private void updateKernelUidReadersThrottleTime(long oldTimeMs, long newTimeMs) {
KERNEL_UID_READERS_THROTTLE_TIME = newTimeMs;
if (oldTimeMs != newTimeMs) {
+ mKernelUidCpuTimeReader.setThrottleInterval(KERNEL_UID_READERS_THROTTLE_TIME);
mKernelUidCpuFreqTimeReader.setThrottleInterval(KERNEL_UID_READERS_THROTTLE_TIME);
mKernelUidCpuActiveTimeReader.setThrottleInterval(KERNEL_UID_READERS_THROTTLE_TIME);
mKernelUidCpuClusterTimeReader
diff --git a/core/java/com/android/internal/os/KernelUidCpuActiveTimeReader.java b/core/java/com/android/internal/os/KernelUidCpuActiveTimeReader.java
index 2519412..ce45f3c 100644
--- a/core/java/com/android/internal/os/KernelUidCpuActiveTimeReader.java
+++ b/core/java/com/android/internal/os/KernelUidCpuActiveTimeReader.java
@@ -17,7 +17,6 @@
package com.android.internal.os;
import android.annotation.Nullable;
-import android.os.SystemClock;
import android.util.Slog;
import android.util.SparseArray;
@@ -46,20 +45,17 @@
* which has a shorter throttle interval and returns cached result from last read when the request
* is throttled.
*
- * This class is NOT thread-safe and NOT designed to be accessed by more than one caller (due to
- * the nature of {@link #readDelta(Callback)}).
+ * This class is NOT thread-safe and NOT designed to be accessed by more than one caller since each
+ * caller has its own view of delta.
*/
-public class KernelUidCpuActiveTimeReader {
- private static final String TAG = "KernelUidCpuActiveTimeReader";
- // Throttle interval in milliseconds
- private static final long DEFAULT_THROTTLE_INTERVAL = 10_000L;
+public class KernelUidCpuActiveTimeReader extends
+ KernelUidCpuTimeReaderBase<KernelUidCpuActiveTimeReader.Callback> {
+ private static final String TAG = KernelUidCpuActiveTimeReader.class.getSimpleName();
private final KernelCpuProcReader mProcReader;
- private long mLastTimeReadMs = Long.MIN_VALUE;
- private long mThrottleInterval = DEFAULT_THROTTLE_INTERVAL;
private SparseArray<Double> mLastUidCpuActiveTimeMs = new SparseArray<>();
- public interface Callback {
+ public interface Callback extends KernelUidCpuTimeReaderBase.Callback {
/**
* Notifies when new data is available.
*
@@ -78,11 +74,8 @@
mProcReader = procReader;
}
- public void readDelta(@Nullable Callback cb) {
- if (SystemClock.elapsedRealtime() < mLastTimeReadMs + mThrottleInterval) {
- Slog.w(TAG, "Throttle");
- return;
- }
+ @Override
+ protected void readDeltaImpl(@Nullable Callback cb) {
synchronized (mProcReader) {
final ByteBuffer bytes = mProcReader.readBytes();
if (bytes == null || bytes.remaining() <= 4) {
@@ -124,14 +117,9 @@
}
}
}
- // Slog.i(TAG, "Read uids: " + numUids);
- }
- mLastTimeReadMs = SystemClock.elapsedRealtime();
- }
-
- public void setThrottleInterval(long throttleInterval) {
- if (throttleInterval >= 0) {
- mThrottleInterval = throttleInterval;
+ if (DEBUG) {
+ Slog.d(TAG, "Read uids: " + numUids);
+ }
}
}
diff --git a/core/java/com/android/internal/os/KernelUidCpuClusterTimeReader.java b/core/java/com/android/internal/os/KernelUidCpuClusterTimeReader.java
index 41ef8f0..c21b766 100644
--- a/core/java/com/android/internal/os/KernelUidCpuClusterTimeReader.java
+++ b/core/java/com/android/internal/os/KernelUidCpuClusterTimeReader.java
@@ -17,7 +17,6 @@
package com.android.internal.os;
import android.annotation.Nullable;
-import android.os.SystemClock;
import android.util.Slog;
import android.util.SparseArray;
@@ -50,17 +49,14 @@
* which has a shorter throttle interval and returns cached result from last read when the request
* is throttled.
*
- * This class is NOT thread-safe and NOT designed to be accessed by more than one caller (due to
- * the nature of {@link #readDelta(Callback)}).
+ * This class is NOT thread-safe and NOT designed to be accessed by more than one caller since each
+ * caller has its own view of delta.
*/
-public class KernelUidCpuClusterTimeReader {
- private static final String TAG = "KernelUidCpuClusterTimeReader";
- // Throttle interval in milliseconds
- private static final long DEFAULT_THROTTLE_INTERVAL = 10_000L;
+public class KernelUidCpuClusterTimeReader extends
+ KernelUidCpuTimeReaderBase<KernelUidCpuClusterTimeReader.Callback> {
+ private static final String TAG = KernelUidCpuClusterTimeReader.class.getSimpleName();
private final KernelCpuProcReader mProcReader;
- private long mLastTimeReadMs = Long.MIN_VALUE;
- private long mThrottleInterval = DEFAULT_THROTTLE_INTERVAL;
private SparseArray<double[]> mLastUidPolicyTimeMs = new SparseArray<>();
private int mNumClusters = -1;
@@ -70,7 +66,7 @@
private double[] mCurTime; // Reuse to avoid GC.
private long[] mDeltaTime; // Reuse to avoid GC.
- public interface Callback {
+ public interface Callback extends KernelUidCpuTimeReaderBase.Callback {
/**
* Notifies when new data is available.
*
@@ -90,17 +86,8 @@
mProcReader = procReader;
}
- public void setThrottleInterval(long throttleInterval) {
- if (throttleInterval >= 0) {
- mThrottleInterval = throttleInterval;
- }
- }
-
- public void readDelta(@Nullable Callback cb) {
- if (SystemClock.elapsedRealtime() < mLastTimeReadMs + mThrottleInterval) {
- Slog.w(TAG, "Throttle");
- return;
- }
+ @Override
+ protected void readDeltaImpl(@Nullable Callback cb) {
synchronized (mProcReader) {
ByteBuffer bytes = mProcReader.readBytes();
if (bytes == null || bytes.remaining() <= 4) {
@@ -142,14 +129,15 @@
int numUids = buf.remaining() / (mNumCores + 1);
for (int i = 0; i < numUids; i++) {
- processUidLocked(buf, cb);
+ processUid(buf, cb);
}
- // Slog.i(TAG, "Read uids: " + numUids);
+ if (DEBUG) {
+ Slog.d(TAG, "Read uids: " + numUids);
+ }
}
- mLastTimeReadMs = SystemClock.elapsedRealtime();
}
- private void processUidLocked(IntBuffer buf, @Nullable Callback cb) {
+ private void processUid(IntBuffer buf, @Nullable Callback cb) {
int uid = buf.get();
double[] lastTimes = mLastUidPolicyTimeMs.get(uid);
if (lastTimes == null) {
diff --git a/core/java/com/android/internal/os/KernelUidCpuFreqTimeReader.java b/core/java/com/android/internal/os/KernelUidCpuFreqTimeReader.java
index a21a70e..a0787a0 100644
--- a/core/java/com/android/internal/os/KernelUidCpuFreqTimeReader.java
+++ b/core/java/com/android/internal/os/KernelUidCpuFreqTimeReader.java
@@ -59,24 +59,21 @@
* which has a shorter throttle interval and returns cached result from last read when the request
* is throttled.
*
- * This class is NOT thread-safe and NOT designed to be accessed by more than one caller (due to
- * the nature of {@link #readDelta(Callback)}).
+ * This class is NOT thread-safe and NOT designed to be accessed by more than one caller since each
+ * caller has its own view of delta.
*/
-public class KernelUidCpuFreqTimeReader {
- private static final boolean DEBUG = false;
- private static final String TAG = "KernelUidCpuFreqTimeReader";
+public class KernelUidCpuFreqTimeReader extends
+ KernelUidCpuTimeReaderBase<KernelUidCpuFreqTimeReader.Callback> {
+ private static final String TAG = KernelUidCpuFreqTimeReader.class.getSimpleName();
static final String UID_TIMES_PROC_FILE = "/proc/uid_time_in_state";
- // Throttle interval in milliseconds
- private static final long DEFAULT_THROTTLE_INTERVAL = 10_000L;
- public interface Callback {
+ public interface Callback extends KernelUidCpuTimeReaderBase.Callback {
void onUidCpuFreqTime(int uid, long[] cpuFreqTimeMs);
}
private long[] mCpuFreqs;
private long[] mCurTimes; // Reuse to prevent GC.
private long[] mDeltaTimes; // Reuse to prevent GC.
- private long mThrottleInterval = DEFAULT_THROTTLE_INTERVAL;
private int mCpuFreqsCount;
private long mLastTimeReadMs = Long.MIN_VALUE;
private long mNowTimeMs;
@@ -150,30 +147,20 @@
mReadBinary = readBinary;
}
- public void setThrottleInterval(long throttleInterval) {
- if (throttleInterval >= 0) {
- mThrottleInterval = throttleInterval;
- }
- }
-
- public void readDelta(@Nullable Callback callback) {
+ @Override
+ protected void readDeltaImpl(@Nullable Callback callback) {
if (mCpuFreqs == null) {
return;
}
- if (SystemClock.elapsedRealtime() < mLastTimeReadMs + mThrottleInterval) {
- Slog.w(TAG, "Throttle");
- return;
- }
- mNowTimeMs = SystemClock.elapsedRealtime();
if (mReadBinary) {
readDeltaBinary(callback);
} else {
readDeltaString(callback);
}
- mLastTimeReadMs = mNowTimeMs;
}
private void readDeltaString(@Nullable Callback callback) {
+ mNowTimeMs = SystemClock.elapsedRealtime();
final int oldMask = StrictMode.allowThreadDiskReadsMask();
try (BufferedReader reader = new BufferedReader(new FileReader(UID_TIMES_PROC_FILE))) {
readDelta(reader, callback);
@@ -182,6 +169,7 @@
} finally {
StrictMode.setThreadPolicyMask(oldMask);
}
+ mLastTimeReadMs = mNowTimeMs;
}
@VisibleForTesting
@@ -232,7 +220,9 @@
}
}
}
- // Slog.i(TAG, "Read uids: "+numUids);
+ if (DEBUG) {
+ Slog.d(TAG, "Read uids: " + numUids);
+ }
}
}
diff --git a/core/java/com/android/internal/os/KernelUidCpuTimeReader.java b/core/java/com/android/internal/os/KernelUidCpuTimeReader.java
index 444049e..4263b83 100644
--- a/core/java/com/android/internal/os/KernelUidCpuTimeReader.java
+++ b/core/java/com/android/internal/os/KernelUidCpuTimeReader.java
@@ -38,18 +38,19 @@
* maintains the previous results of a call to {@link #readDelta} in order to provide a proper
* delta.
*/
-public class KernelUidCpuTimeReader {
- private static final String TAG = "KernelUidCpuTimeReader";
+public class KernelUidCpuTimeReader extends
+ KernelUidCpuTimeReaderBase<KernelUidCpuTimeReader.Callback> {
+ private static final String TAG = KernelUidCpuTimeReader.class.getSimpleName();
private static final String sProcFile = "/proc/uid_cputime/show_uid_stat";
private static final String sRemoveUidProcFile = "/proc/uid_cputime/remove_uid_range";
/**
* Callback interface for processing each line of the proc file.
*/
- public interface Callback {
+ public interface Callback extends KernelUidCpuTimeReaderBase.Callback {
/**
- * @param uid UID of the app
- * @param userTimeUs time spent executing in user space in microseconds
+ * @param uid UID of the app
+ * @param userTimeUs time spent executing in user space in microseconds
* @param systemTimeUs time spent executing in kernel space in microseconds
*/
void onUidCpuTime(int uid, long userTimeUs, long systemTimeUs);
@@ -61,11 +62,13 @@
/**
* Reads the proc file, calling into the callback with a delta of time for each UID.
+ *
* @param callback The callback to invoke for each line of the proc file. If null,
* the data is consumed and subsequent calls to readDelta will provide
* a fresh delta.
*/
- public void readDelta(@Nullable Callback callback) {
+ @Override
+ protected void readDeltaImpl(@Nullable Callback callback) {
final int oldMask = StrictMode.allowThreadDiskReadsMask();
long nowUs = SystemClock.elapsedRealtime() * 1000;
try (BufferedReader reader = new BufferedReader(new FileReader(sProcFile))) {
@@ -132,7 +135,10 @@
}
/**
- * Removes the UID from the kernel module and from internal accounting data.
+ * Removes the UID from the kernel module and from internal accounting data. Only
+ * {@link BatteryStatsImpl} and its child processes should call this, as the change on Kernel is
+ * visible system wide.
+ *
* @param uid The UID to remove.
*/
public void removeUid(int uid) {
@@ -145,9 +151,12 @@
}
/**
- * Removes UIDs in a given range from the kernel module and internal accounting data.
+ * Removes UIDs in a given range from the kernel module and internal accounting data. Only
+ * {@link BatteryStatsImpl} and its child processes should call this, as the change on Kernel is
+ * visible system wide.
+ *
* @param startUid the first uid to remove
- * @param endUid the last uid to remove
+ * @param endUid the last uid to remove
*/
public void removeUidsInRange(int startUid, int endUid) {
if (endUid < startUid) {
diff --git a/core/java/com/android/internal/os/KernelUidCpuTimeReaderBase.java b/core/java/com/android/internal/os/KernelUidCpuTimeReaderBase.java
new file mode 100644
index 0000000..11e50e1
--- /dev/null
+++ b/core/java/com/android/internal/os/KernelUidCpuTimeReaderBase.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.os;
+
+import android.annotation.Nullable;
+import android.os.SystemClock;
+import android.util.Slog;
+
+/**
+ * The base class of all KernelUidCpuTimeReaders.
+ *
+ * This class is NOT designed to be thread-safe or accessed by more than one caller (due to
+ * the nature of {@link #readDelta(Callback)}).
+ */
+public abstract class KernelUidCpuTimeReaderBase<T extends KernelUidCpuTimeReaderBase.Callback> {
+ protected static final boolean DEBUG = false;
+ // Throttle interval in milliseconds
+ private static final long DEFAULT_THROTTLE_INTERVAL = 10_000L;
+
+ private final String TAG = this.getClass().getSimpleName();
+ private long mLastTimeReadMs = Long.MIN_VALUE;
+ private long mThrottleInterval = DEFAULT_THROTTLE_INTERVAL;
+
+ // A generic Callback interface (used by readDelta) to be extended by subclasses.
+ public interface Callback {
+ }
+
+ public void readDelta(@Nullable T cb) {
+ if (SystemClock.elapsedRealtime() < mLastTimeReadMs + mThrottleInterval) {
+ if (DEBUG) {
+ Slog.d(TAG, "Throttle");
+ }
+ return;
+ }
+ readDeltaImpl(cb);
+ mLastTimeReadMs = SystemClock.elapsedRealtime();
+ }
+
+ protected abstract void readDeltaImpl(@Nullable T cb);
+
+ public void setThrottleInterval(long throttleInterval) {
+ if (throttleInterval >= 0) {
+ mThrottleInterval = throttleInterval;
+ }
+ }
+}
diff --git a/core/java/com/android/internal/os/WebViewZygoteInit.java b/core/java/com/android/internal/os/WebViewZygoteInit.java
index 32b580c..9f2434e 100644
--- a/core/java/com/android/internal/os/WebViewZygoteInit.java
+++ b/core/java/com/android/internal/os/WebViewZygoteInit.java
@@ -27,6 +27,7 @@
import android.util.Log;
import android.webkit.WebViewFactory;
import android.webkit.WebViewFactoryProvider;
+import android.webkit.WebViewLibraryLoader;
import java.io.DataOutputStream;
import java.io.File;
@@ -71,7 +72,8 @@
}
@Override
- protected void handlePreloadPackage(String packagePath, String libsPath, String cacheKey) {
+ protected void handlePreloadPackage(String packagePath, String libsPath, String libFileName,
+ String cacheKey) {
Log.i(TAG, "Beginning package preload");
// Ask ApplicationLoaders to create and cache a classloader for the WebView APK so that
// our children will reuse the same classloader instead of creating their own.
@@ -80,6 +82,10 @@
ClassLoader loader = ApplicationLoaders.getDefault().createAndCacheWebViewClassLoader(
packagePath, libsPath, cacheKey);
+ // Load the native library using WebViewLibraryLoader to share the RELRO data with other
+ // processes.
+ WebViewLibraryLoader.loadNativeLibrary(loader, libFileName);
+
// Add the APK to the Zygote's list of allowed files for children.
String[] packageList = TextUtils.split(packagePath, File.pathSeparator);
for (String packageEntry : packageList) {
diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java
index a32fb43..cd83c57 100644
--- a/core/java/com/android/internal/os/ZygoteConnection.java
+++ b/core/java/com/android/internal/os/ZygoteConnection.java
@@ -155,7 +155,7 @@
if (parsedArgs.preloadPackage != null) {
handlePreloadPackage(parsedArgs.preloadPackage, parsedArgs.preloadPackageLibs,
- parsedArgs.preloadPackageCacheKey);
+ parsedArgs.preloadPackageLibFileName, parsedArgs.preloadPackageCacheKey);
return null;
}
@@ -290,7 +290,8 @@
return mSocketOutStream;
}
- protected void handlePreloadPackage(String packagePath, String libsPath, String cacheKey) {
+ protected void handlePreloadPackage(String packagePath, String libsPath, String libFileName,
+ String cacheKey) {
throw new RuntimeException("Zyogte does not support package preloading");
}
@@ -402,10 +403,24 @@
String appDataDir;
/**
- * Whether to preload a package, with the package path in the remainingArgs.
+ * The APK path of the package to preload, when using --preload-package.
*/
String preloadPackage;
+
+ /**
+ * The native library path of the package to preload, when using --preload-package.
+ */
String preloadPackageLibs;
+
+ /**
+ * The filename of the native library to preload, when using --preload-package.
+ */
+ String preloadPackageLibFileName;
+
+ /**
+ * The cache key under which to enter the preloaded package into the classloader cache,
+ * when using --preload-package.
+ */
String preloadPackageCacheKey;
/**
@@ -571,6 +586,7 @@
} else if (arg.equals("--preload-package")) {
preloadPackage = args[++curArg];
preloadPackageLibs = args[++curArg];
+ preloadPackageLibFileName = args[++curArg];
preloadPackageCacheKey = args[++curArg];
} else if (arg.equals("--preload-default")) {
preloadDefault = true;
diff --git a/core/java/com/android/internal/widget/ILockSettings.aidl b/core/java/com/android/internal/widget/ILockSettings.aidl
index d3fc644..7c9cf7a 100644
--- a/core/java/com/android/internal/widget/ILockSettings.aidl
+++ b/core/java/com/android/internal/widget/ILockSettings.aidl
@@ -68,6 +68,7 @@
KeyChainSnapshot getKeyChainSnapshot();
byte[] generateAndStoreKey(String alias);
String generateKey(String alias);
+ String importKey(String alias, in byte[] keyBytes);
String getKey(String alias);
void removeKey(String alias);
void setSnapshotCreatedPendingIntent(in PendingIntent intent);
diff --git a/core/jni/android_media_AudioTrack.cpp b/core/jni/android_media_AudioTrack.cpp
index 61a22c1..6456fe6 100644
--- a/core/jni/android_media_AudioTrack.cpp
+++ b/core/jni/android_media_AudioTrack.cpp
@@ -282,7 +282,7 @@
// compute the frame count
size_t frameCount;
- if (audio_is_linear_pcm(format)) {
+ if (audio_has_proportional_frames(format)) {
const size_t bytesPerSample = audio_bytes_per_sample(format);
frameCount = buffSizeInBytes / (channelCount * bytesPerSample);
} else {
diff --git a/core/proto/android/os/data.proto b/core/proto/android/os/data.proto
new file mode 100644
index 0000000..c06f318
--- /dev/null
+++ b/core/proto/android/os/data.proto
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+syntax = "proto2";
+option java_multiple_files = true;
+
+package android.os;
+
+// This file contains protobuf definitions used in incidentd directly.
+// The top level proto message must be used as a new SectionType in
+// incidentd.
+
+// Output of SECTION_GZIP section type, which reads a file, gzip it and attached
+// in incident report as a proto field, example is LAST_KMSG.
+// NOTE the content in the file must not contain sensitive PII otherwise
+// implement it with fine-grained proto definition.
+message GZippedFileProto {
+ optional string filename = 1;
+
+ optional bytes gzipped_data = 2;
+}
diff --git a/core/proto/android/os/incident.proto b/core/proto/android/os/incident.proto
index 9a53b89..be15597 100644
--- a/core/proto/android/os/incident.proto
+++ b/core/proto/android/os/incident.proto
@@ -20,6 +20,7 @@
import "frameworks/base/core/proto/android/os/batterytype.proto";
import "frameworks/base/core/proto/android/os/cpufreq.proto";
import "frameworks/base/core/proto/android/os/cpuinfo.proto";
+import "frameworks/base/core/proto/android/os/data.proto";
import "frameworks/base/core/proto/android/os/kernelwake.proto";
import "frameworks/base/core/proto/android/os/pagetypeinfo.proto";
import "frameworks/base/core/proto/android/os/procrank.proto";
@@ -52,9 +53,8 @@
package android.os;
-// privacy field options must not be set at this level because all
-// the sections are able to be controlled and configured by section ids.
-// Instead privacy field options need to be configured in each section proto message.
+// Privacy tag can be marked to override UNSET messages so generic
+// message type can be handled case by case, e.g. GZippedFileProto.
message IncidentProto {
reserved 1001;
@@ -151,6 +151,12 @@
(section).args = "/sys/class/power_supply/bms/battery_type"
];
+ optional GZippedFileProto last_kmsg = 2007 [
+ (section).type = SECTION_GZIP,
+ (section).args = "/sys/fs/pstore/console-ramoops /sys/fs/pstore/console-ramoops-0 /proc/last_kmsg",
+ (privacy).dest = DEST_AUTOMATIC
+ ];
+
// System Services
optional com.android.server.fingerprint.FingerprintServiceDumpProto fingerprint = 3000 [
(section).type = SECTION_DUMPSYS,
diff --git a/core/proto/android/server/activitymanagerservice.proto b/core/proto/android/server/activitymanagerservice.proto
index 788d901..5042ede 100644
--- a/core/proto/android/server/activitymanagerservice.proto
+++ b/core/proto/android/server/activitymanagerservice.proto
@@ -58,6 +58,9 @@
optional KeyguardControllerProto keyguard_controller = 3;
optional int32 focused_stack_id = 4;
optional .com.android.server.wm.proto.IdentifierProto resumed_activity = 5;
+ // Whether or not the home activity is the recents activity. This is needed for the CTS tests to
+ // know what activity types to check for when invoking splitscreen multi-window.
+ optional bool is_home_recents_component = 6;
}
/* represents ActivityStackSupervisor.ActivityDisplay */
diff --git a/core/proto/android/server/alarmmanagerservice.proto b/core/proto/android/server/alarmmanagerservice.proto
index d1c5db6..b288c11 100644
--- a/core/proto/android/server/alarmmanagerservice.proto
+++ b/core/proto/android/server/alarmmanagerservice.proto
@@ -220,6 +220,8 @@
optional int64 allow_while_idle_long_duration_ms = 5;
// BroadcastOptions.setTemporaryAppWhitelistDuration() to use for FLAG_ALLOW_WHILE_IDLE.
optional int64 allow_while_idle_whitelist_duration_ms = 6;
+ // Maximum alarm recurrence interval.
+ optional int64 max_interval_duration_ms = 7;
}
// A com.android.server.AlarmManagerService.FilterStats object.
diff --git a/core/res/res/drawable/ic_corp_badge.xml b/core/res/res/drawable/ic_corp_badge.xml
index 78cce58..915f5fb 100644
--- a/core/res/res/drawable/ic_corp_badge.xml
+++ b/core/res/res/drawable/ic_corp_badge.xml
@@ -1,12 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright (C) 2018 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="20dp"
android:height="20dp"
- android:viewportWidth="20.0"
- android:viewportHeight="20.0">
+ android:viewportWidth="20"
+ android:viewportHeight="20">
+
<path
- android:pathData="M10,10m-10,0a10,10 0,1 1,20 0a10,10 0,1 1,-20 0"
- android:fillColor="#FF6D00"/>
+ android:fillColor="#fcfcfc"
+ android:pathData="M 10 0 C 15.5228474983 0 20 4.47715250169 20 10 C 20 15.5228474983 15.5228474983 20 10 20 C 4.47715250169 20 0 15.5228474983 0 10 C 0 4.47715250169 4.47715250169 0 10 0 Z" />
<path
- android:pathData="M14.67,6.5h-2.33V5.33c0,-0.65 -0.52,-1.17 -1.17,-1.17H8.83c-0.65,0 -1.17,0.52 -1.17,1.17V6.5H5.33c-0.65,0 -1.16,0.52 -1.16,1.17l-0.01,6.42c0,0.65 0.52,1.17 1.17,1.17h9.33c0.65,0 1.17,-0.52 1.17,-1.17V7.67C15.83,7.02 15.31,6.5 14.67,6.5zM10,11.75c-0.64,0 -1.17,-0.52 -1.17,-1.17c0,-0.64 0.52,-1.17 1.17,-1.17c0.64,0 1.17,0.52 1.17,1.17C11.17,11.22 10.64,11.75 10,11.75zM11.17,6.5H8.83V5.33h2.33V6.5z"
- android:fillColor="#FFFFFF"/>
-</vector>
+ android:strokeColor="#e8eaed"
+ android:strokeWidth="0.25"
+ android:pathData="M 10 0.12 C 15.4565733283 0.12 19.88 4.54342667167 19.88 10 C 19.88 15.4565733283 15.4565733283 19.88 10 19.88 C 4.54342667167 19.88 0.12 15.4565733283 0.12 10 C 0.12 4.54342667167 4.54342667167 0.12 10 0.12 Z" />
+ <path
+ android:pathData="M 3.5 3.5 L 16.5 3.5 L 16.5 16.5 L 3.5 16.5 L 3.5 3.5 Z" />
+ <path
+ android:fillColor="#1a73e8"
+ android:pathData="M14.46,6.58H12.23V5.5a1.09,1.09,0,0,0-1.11-1.08H8.89A1.09,1.09,0,0,0,7.77,5.5V6.58H5.54A1.09,1.09,0,0,0,4.43,7.65v5.91a1.09,1.09,0,0,0,1.11,1.08h8.91a1.09,1.09,0,0,0,1.11-1.08V7.65A1.09,1.09,0,0,0,14.46,6.58ZM10,11.42a1.08,1.08,0,1,1,1.11-1.08A1.1,1.1,0,0,1,10,11.42Zm1.11-4.84H8.89V5.5h2.23Z" />
+ <path
+ android:pathData="M 0 0 H 20 V 20 H 0 V 0 Z" />
+</vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/ic_corp_badge_case.xml b/core/res/res/drawable/ic_corp_badge_case.xml
index 2d11ee6..1cd995e 100644
--- a/core/res/res/drawable/ic_corp_badge_case.xml
+++ b/core/res/res/drawable/ic_corp_badge_case.xml
@@ -5,5 +5,5 @@
android:viewportHeight="20.0">
<path
android:pathData="M14.67,6.5h-2.33V5.33c0,-0.65 -0.52,-1.17 -1.17,-1.17H8.83c-0.65,0 -1.17,0.52 -1.17,1.17V6.5H5.33c-0.65,0 -1.16,0.52 -1.16,1.17l-0.01,6.42c0,0.65 0.52,1.17 1.17,1.17h9.33c0.65,0 1.17,-0.52 1.17,-1.17V7.67C15.83,7.02 15.31,6.5 14.67,6.5zM10,11.75c-0.64,0 -1.17,-0.52 -1.17,-1.17c0,-0.64 0.52,-1.17 1.17,-1.17c0.64,0 1.17,0.52 1.17,1.17C11.17,11.22 10.64,11.75 10,11.75zM11.17,6.5H8.83V5.33h2.33V6.5z"
- android:fillColor="#FFFFFF"/>
+ android:fillColor="#1A73E8"/>
</vector>
diff --git a/core/res/res/drawable/ic_corp_badge_color.xml b/core/res/res/drawable/ic_corp_badge_color.xml
index b6c7969..4aef7d0 100644
--- a/core/res/res/drawable/ic_corp_badge_color.xml
+++ b/core/res/res/drawable/ic_corp_badge_color.xml
@@ -1,3 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2016 The Android Open Source Project
@@ -20,5 +21,5 @@
android:viewportHeight="20.0">
<path
android:pathData="M10.0,10.0m-10.0,0.0a10.0,10.0 0.0,1.0 1.0,20.0 0.0a10.0,10.0 0.0,1.0 1.0,-20.0 0.0"
- android:fillColor="#FFFFFF"/>
-</vector>
+ android:fillColor="#fcfcfc"/>
+</vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/ic_corp_icon_badge_case.xml b/core/res/res/drawable/ic_corp_icon_badge_case.xml
index dd653c6..50551d40 100644
--- a/core/res/res/drawable/ic_corp_icon_badge_case.xml
+++ b/core/res/res/drawable/ic_corp_icon_badge_case.xml
@@ -1,9 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="64dp"
android:height="64dp"
- android:viewportWidth="64.0"
- android:viewportHeight="64.0">
+ android:viewportWidth="64"
+ android:viewportHeight="64">
+
<path
- android:pathData="M55.67,44h-3.33v-1.67c0,-0.92 -0.74,-1.67 -1.67,-1.67h-3.33c-0.92,0 -1.67,0.74 -1.67,1.67V44h-3.33c-0.92,0 -1.66,0.74 -1.66,1.67l-0.01,9.17c0,0.93 0.74,1.67 1.67,1.67h13.33c0.92,0 1.67,-0.74 1.67,-1.67v-9.17C57.33,44.74 56.59,44 55.67,44zM49,51.5c-0.92,0 -1.67,-0.75 -1.67,-1.67c0,-0.92 0.75,-1.67 1.67,-1.67s1.67,0.75 1.67,1.67C50.67,50.75 49.92,51.5 49,51.5zM50.67,44h-3.33v-1.67h3.33V44z"
- android:fillColor="#FFFFFF"/>
-</vector>
+ android:pathData="M 42 42 L 58 42 L 58 58 L 42 58 L 42 42 Z" />
+ <path
+ android:fillColor="#1A73E8"
+ android:pathData="M55.33,46H52.67V44.67a1.33,1.33,0,0,0-1.33-1.33H48.67a1.33,1.33,0,0,0-1.33,1.33V46H44.67a1.32,1.32,0,0,0-1.33,1.33v7.33A1.33,1.33,0,0,0,44.67,56H55.33a1.33,1.33,0,0,0,1.33-1.33V47.33A1.33,1.33,0,0,0,55.33,46ZM50,52a1.33,1.33,0,1,1,1.33-1.33A1.34,1.34,0,0,1,50,52Zm1.33-6H48.67V44.67h2.67Z" />
+ <path
+ android:pathData="M 0 0 H 64 V 64 H 0 V 0 Z" />
+</vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/ic_corp_icon_badge_color.xml b/core/res/res/drawable/ic_corp_icon_badge_color.xml
index 3bc4e67..6dba277 100644
--- a/core/res/res/drawable/ic_corp_icon_badge_color.xml
+++ b/core/res/res/drawable/ic_corp_icon_badge_color.xml
@@ -1,3 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2016 The Android Open Source Project
@@ -14,11 +15,16 @@
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="64.0dp"
- android:height="64.0dp"
- android:viewportWidth="64.0"
- android:viewportHeight="64.0">
+ android:width="64dp"
+ android:height="64dp"
+ android:viewportWidth="64"
+ android:viewportHeight="64">
+
<path
- android:pathData="M49.1,48.8m-13.9,0.0a13.9,13.9 0.0,1.0 1.0,27.8 0.0a13.9,13.9 0.0,1.0 1.0,-27.8 0.0"
- android:fillColor="#FFFFFF"/>
-</vector>
+ android:fillColor="#fcfcfc"
+ android:strokeColor="#e8eaed"
+ android:strokeWidth="0.25"
+ android:pathData="M62,50A12,12,0,1,1,50,38,12,12,0,0,1,62,50" />
+ <path
+ android:pathData="M 0 0 H 64 V 64 H 0 V 0 Z" />
+</vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/ic_corp_icon_badge_shadow.xml b/core/res/res/drawable/ic_corp_icon_badge_shadow.xml
index a546cdd..f33ed1f 100644
--- a/core/res/res/drawable/ic_corp_icon_badge_shadow.xml
+++ b/core/res/res/drawable/ic_corp_icon_badge_shadow.xml
@@ -1,3 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2016 The Android Open Source Project
@@ -14,16 +15,35 @@
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="64.0dp"
- android:height="64.0dp"
- android:viewportWidth="64.0"
- android:viewportHeight="64.0">
+ android:width="64dp"
+ android:height="64dp"
+ android:viewportWidth="64"
+ android:viewportHeight="64">
+
<path
- android:fillColor="#FF000000"
- android:pathData="M49.1,50.1m-13.9,0.0a13.9,13.9 0.0,1.0 1.0,27.8 0.0a13.9,13.9 0.0,1.0 1.0,-27.8 0.0"
- android:fillAlpha="0.2"/>
+ android:fillColor="#000000"
+ android:fillAlpha="0.06"
+ android:strokeAlpha="0.06"
+ android:strokeWidth="1"
+ android:pathData="M62,51.25a12,12,0,1,1-12-12,12,12,0,0,1,12,12" />
<path
- android:fillColor="#FF000000"
- android:pathData="M49.1,49.4m-13.9,0.0a13.9,13.9 0.0,1.0 1.0,27.8 0.0a13.9,13.9 0.0,1.0 1.0,-27.8 0.0"
- android:fillAlpha="0.2"/>
-</vector>
+ android:pathData="M 0 0 H 64 V 64 H 0 V 0 Z" />
+ <path
+ android:fillColor="#000000"
+ android:fillAlpha="0.06"
+ android:strokeAlpha="0.06"
+ android:strokeWidth="1"
+ android:pathData="M62,52.28A12,12,0,1,1,50.53,39.76,12,12,0,0,1,62,52.28" />
+ <path
+ android:fillColor="#000000"
+ android:fillAlpha="0.06"
+ android:strokeAlpha="0.06"
+ android:strokeWidth="1"
+ android:pathData="M62,50.75a12,12,0,1,1-12-12,12,12,0,0,1,12,12" />
+ <path
+ android:fillColor="#000000"
+ android:fillAlpha="0.06"
+ android:strokeAlpha="0.06"
+ android:strokeWidth="1"
+ android:pathData="M62,50.25a12,12,0,1,1-12-12,12,12,0,0,1,12,12" />
+</vector>
\ No newline at end of file
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 96a83f8..22ab9c9 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -4472,6 +4472,8 @@
<attr name="textSize" />
<!-- Style (normal, bold, italic, bold|italic) for the text. -->
<attr name="textStyle" />
+ <!-- Weight for the font used in the TextView. -->
+ <attr name="textFontWeight" />
<!-- Typeface (normal, sans, serif, monospace) for the text. -->
<attr name="typeface" />
<!-- Font family (named by string or as a font resource reference) for the text. -->
@@ -4561,6 +4563,8 @@
<attr name="typeface" />
<!-- Style (normal, bold, italic, bold|italic) for the text. -->
<attr name="textStyle" />
+ <!-- Weight for the font used in the TextView. -->
+ <attr name="textFontWeight" />
<!-- Font family (named by string or as a font resource reference) for the text. -->
<attr name="fontFamily" />
<!-- Text color for links. -->
diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml
index a078d8b..722102e 100644
--- a/core/res/res/values/colors.xml
+++ b/core/res/res/values/colors.xml
@@ -167,8 +167,8 @@
<color name="user_icon_default_white">#ffffffff</color><!-- white -->
<!-- Default profile badge colors -->
- <color name="profile_badge_1">#ffff6d00</color><!-- Orange -->
- <color name="profile_badge_2">#ff000000</color><!-- Black -->
+ <color name="profile_badge_1">#ff1A73E8</color><!-- Blue -->
+ <color name="profile_badge_2">#ffff6d00</color><!-- Orange -->
<color name="profile_badge_3">#ff22f033</color><!-- Green -->
<!-- Default instant app badge color -->
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 3b963d1..d6f3463 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1636,6 +1636,9 @@
<!-- Operating volatage for bluetooth controller. 0 by default-->
<integer translatable="false" name="config_bluetooth_operating_voltage_mv">0</integer>
+ <!-- Max number of connected audio devices supported by Bluetooth stack -->
+ <integer name="config_bluetooth_max_connected_audio_devices">1</integer>
+
<!-- Whether supported profiles should be reloaded upon enabling bluetooth -->
<bool name="config_bluetooth_reload_supported_profiles_when_enabled">false</bool>
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 2ce08eb..2918260 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -541,7 +541,7 @@
<!-- Magnifier dimensions -->
<dimen name="magnifier_width">100dp</dimen>
<dimen name="magnifier_height">48dp</dimen>
- <dimen name="magnifier_elevation">2dp</dimen>
+ <dimen name="magnifier_elevation">4dp</dimen>
<dimen name="magnifier_offset">42dp</dimen>
<item type="dimen" format="float" name="magnifier_zoom_scale">1.25</item>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index a5ba4c6..c4006b3 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2872,6 +2872,7 @@
<public name="urlBarResourceId" />
<!-- @hide @SystemApi -->
<public name="userRestriction" />
+ <public name="textFontWeight" />
</public-group>
<public-group type="style" first-id="0x010302e0">
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 5c9f863..c3ae5fa 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -379,7 +379,7 @@
<!-- Text message in the factory reset warning dialog. This says that the the device admin app
is missing or corrupted. As a result the device will be erased. [CHAR LIMIT=NONE]-->
<string name="factory_reset_message">The admin app can\'t be used. Your device will now be
- erased.\n\nIf you have questions, contact your organization's admin.</string>
+ erased.\n\nIf you have questions, contact your organization\'s admin.</string>
<!-- A toast message displayed when printing is attempted but disabled by policy. -->
<string name="printing_disabled_by">Printing disabled by <xliff:g id="owner_app">%s</xliff:g>.</string>
@@ -764,7 +764,7 @@
<string name="capability_title_canCaptureFingerprintGestures">Fingerprint gestures</string>
<!-- Description for the capability of an accessibility service to perform gestures. -->
<string name="capability_desc_canCaptureFingerprintGestures">Can capture gestures performed on
- the device's fingerprint sensor.</string>
+ the device\'s fingerprint sensor.</string>
<!-- Permissions -->
@@ -3775,7 +3775,7 @@
<!-- Notification title when data usage has exceeded warning threshold. [CHAR LIMIT=50] -->
<string name="data_usage_warning_title">Data warning</string>
<!-- Notification body when data usage has exceeded warning threshold. [CHAR LIMIT=32] -->
- <string name="data_usage_warning_body">You've used <xliff:g id="app" example="3.8GB">%s</xliff:g> of data</string>
+ <string name="data_usage_warning_body">You\'ve used <xliff:g id="app" example="3.8GB">%s</xliff:g> of data</string>
<!-- Notification title when mobile data usage has exceeded limit threshold, and has been disabled. [CHAR LIMIT=50] -->
<string name="data_usage_mobile_limit_title">Mobile data limit reached</string>
@@ -3789,7 +3789,7 @@
<!-- Notification title when Wi-Fi data usage has exceeded limit threshold. [CHAR LIMIT=32] -->
<string name="data_usage_wifi_limit_snoozed_title">Over your Wi-Fi data limit</string>
<!-- Notification body when data usage has exceeded limit threshold. -->
- <string name="data_usage_limit_snoozed_body">You've gone <xliff:g id="size" example="3.8GB">%s</xliff:g> over your set limit</string>
+ <string name="data_usage_limit_snoozed_body">You\'ve gone <xliff:g id="size" example="3.8GB">%s</xliff:g> over your set limit</string>
<!-- Notification title when background data usage is limited. [CHAR LIMIT=32] -->
<string name="data_usage_restricted_title">Background data restricted</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 1b00c67..1babd70 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -405,6 +405,7 @@
<java-symbol type="integer" name="config_wifi_framework_current_network_boost" />
<java-symbol type="integer" name="config_bluetooth_max_advertisers" />
<java-symbol type="integer" name="config_bluetooth_max_scan_filters" />
+ <java-symbol type="integer" name="config_bluetooth_max_connected_audio_devices" />
<java-symbol type="integer" name="config_burnInProtectionMinHorizontalOffset" />
<java-symbol type="integer" name="config_burnInProtectionMaxHorizontalOffset" />
<java-symbol type="integer" name="config_burnInProtectionMinVerticalOffset" />
diff --git a/graphics/java/android/graphics/ImageDecoder.java b/graphics/java/android/graphics/ImageDecoder.java
index 3cca47b..b6ffe12 100644
--- a/graphics/java/android/graphics/ImageDecoder.java
+++ b/graphics/java/android/graphics/ImageDecoder.java
@@ -758,8 +758,9 @@
/** @hide **/
@Retention(SOURCE)
- @IntDef({ ALLOCATOR_DEFAULT, ALLOCATOR_SOFTWARE, ALLOCATOR_SHARED_MEMORY,
- ALLOCATOR_HARDWARE })
+ @IntDef(value = { ALLOCATOR_DEFAULT, ALLOCATOR_SOFTWARE,
+ ALLOCATOR_SHARED_MEMORY, ALLOCATOR_HARDWARE },
+ prefix = {"ALLOCATOR_"})
public @interface Allocator {};
/**
diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java
index 8595165..38beebd 100644
--- a/graphics/java/android/graphics/Typeface.java
+++ b/graphics/java/android/graphics/Typeface.java
@@ -21,6 +21,7 @@
import static android.content.res.FontResourcesParser.FontFileResourceEntry;
import static android.content.res.FontResourcesParser.ProviderResourceEntry;
+import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -49,6 +50,8 @@
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
@@ -117,6 +120,11 @@
*/
public long native_instance;
+ /** @hide */
+ @IntDef(value = {NORMAL, BOLD, ITALIC, BOLD_ITALIC})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Style {}
+
// Style
public static final int NORMAL = 0;
public static final int BOLD = 1;
@@ -124,8 +132,15 @@
public static final int BOLD_ITALIC = 3;
/** @hide */ public static final int STYLE_MASK = 0x03;
- private int mStyle = 0;
- private int mWeight = 0;
+ private @Style int mStyle = 0;
+
+ /**
+ * A maximum value for the weight value.
+ * @hide
+ */
+ public static final int MAX_WEIGHT = 1000;
+
+ private @IntRange(from = 0, to = MAX_WEIGHT) int mWeight = 0;
// Value for weight and italic. Indicates the value is resolved by font metadata.
// Must be the same as the C++ constant in core/jni/android/graphics/FontFamily.cpp
@@ -153,7 +168,7 @@
}
/** Returns the typeface's intrinsic style attributes */
- public int getStyle() {
+ public @Style int getStyle() {
return mStyle;
}
@@ -659,7 +674,7 @@
* e.g. NORMAL, BOLD, ITALIC, BOLD_ITALIC
* @return The best matching typeface.
*/
- public static Typeface create(String familyName, int style) {
+ public static Typeface create(String familyName, @Style int style) {
return create(sSystemFontMap.get(familyName), style);
}
@@ -680,7 +695,7 @@
* e.g. NORMAL, BOLD, ITALIC, BOLD_ITALIC
* @return The best matching typeface.
*/
- public static Typeface create(Typeface family, int style) {
+ public static Typeface create(Typeface family, @Style int style) {
if ((style & ~STYLE_MASK) != 0) {
style = NORMAL;
}
@@ -776,7 +791,7 @@
*
* @return the default typeface that corresponds to the style
*/
- public static Typeface defaultFromStyle(int style) {
+ public static Typeface defaultFromStyle(@Style int style) {
return sDefaults[style];
}
diff --git a/keystore/java/android/security/KeyStore.java b/keystore/java/android/security/KeyStore.java
index ded427e..1924bbe 100644
--- a/keystore/java/android/security/KeyStore.java
+++ b/keystore/java/android/security/KeyStore.java
@@ -784,6 +784,20 @@
}
/**
+ * Requests keystore to check if the confirmationui HAL is available.
+ *
+ * @return whether the confirmationUI HAL is available.
+ */
+ public boolean isConfirmationPromptSupported() {
+ try {
+ return mBinder.isConfirmationPromptSupported();
+ } catch (RemoteException e) {
+ Log.w(TAG, "Cannot connect to keystore", e);
+ return false;
+ }
+ }
+
+ /**
* Returns a {@link KeyStoreException} corresponding to the provided keystore/keymaster error
* code.
*/
diff --git a/libs/hwui/JankTracker.cpp b/libs/hwui/JankTracker.cpp
index ab27a0d..cf29e43 100644
--- a/libs/hwui/JankTracker.cpp
+++ b/libs/hwui/JankTracker.cpp
@@ -165,7 +165,7 @@
ALOGI("%s", ss.str().c_str());
// Just so we have something that counts up, the value is largely irrelevant
ATRACE_INT(ss.str().c_str(), ++sDaveyCount);
- android::util::stats_write(android::util::DAVEY_OCCURRED, ns2ms(totalDuration));
+ android::util::stats_write(android::util::DAVEY_OCCURRED, getuid(), ns2ms(totalDuration));
}
}
diff --git a/libs/incident/proto/android/section.proto b/libs/incident/proto/android/section.proto
index 49bfe1e..ef6a8ff 100644
--- a/libs/incident/proto/android/section.proto
+++ b/libs/incident/proto/android/section.proto
@@ -40,6 +40,9 @@
// incidentd calls logs for annotated field
SECTION_LOG = 4;
+
+ // incidentd read file and gzip the data in bytes field
+ SECTION_GZIP = 5;
}
message SectionFlags {
diff --git a/libs/protoutil/include/android/util/EncodedBuffer.h b/libs/protoutil/include/android/util/EncodedBuffer.h
index 0a8a5aa..bf698d4 100644
--- a/libs/protoutil/include/android/util/EncodedBuffer.h
+++ b/libs/protoutil/include/android/util/EncodedBuffer.h
@@ -154,7 +154,7 @@
void editRawFixed32(size_t pos, uint32_t val);
/**
- * Copy _size_ bytes of data starting at __srcPos__ to wp.
+ * Copy _size_ bytes of data starting at __srcPos__ to wp, srcPos must be larger than wp.pos().
*/
void copy(size_t srcPos, size_t size);
diff --git a/media/java/android/media/audiofx/AudioEffect.java b/media/java/android/media/audiofx/AudioEffect.java
index 7dbca3b..21d6873 100644
--- a/media/java/android/media/audiofx/AudioEffect.java
+++ b/media/java/android/media/audiofx/AudioEffect.java
@@ -39,6 +39,7 @@
* <li> {@link android.media.audiofx.BassBoost}</li>
* <li> {@link android.media.audiofx.PresetReverb}</li>
* <li> {@link android.media.audiofx.EnvironmentalReverb}</li>
+ * <li> {@link android.media.audiofx.DynamicsProcessing}</li>
* </ul>
* <p>To apply the audio effect to a specific AudioTrack or MediaPlayer instance,
* the application must specify the audio session ID of that instance when creating the AudioEffect.
@@ -126,6 +127,12 @@
.fromString("fe3199be-aed0-413f-87bb-11260eb63cf1");
/**
+ * UUID for Dynamics Processing
+ */
+ public static final UUID EFFECT_TYPE_DYNAMICS_PROCESSING = UUID
+ .fromString("7261676f-6d75-7369-6364-28e2fd3ac39e");
+
+ /**
* Null effect UUID. Used when the UUID for effect type of
* @hide
*/
@@ -203,7 +210,8 @@
* {@link AudioEffect#EFFECT_TYPE_AEC}, {@link AudioEffect#EFFECT_TYPE_AGC},
* {@link AudioEffect#EFFECT_TYPE_BASS_BOOST}, {@link AudioEffect#EFFECT_TYPE_ENV_REVERB},
* {@link AudioEffect#EFFECT_TYPE_EQUALIZER}, {@link AudioEffect#EFFECT_TYPE_NS},
- * {@link AudioEffect#EFFECT_TYPE_PRESET_REVERB}, {@link AudioEffect#EFFECT_TYPE_VIRTUALIZER}.
+ * {@link AudioEffect#EFFECT_TYPE_PRESET_REVERB}, {@link AudioEffect#EFFECT_TYPE_VIRTUALIZER},
+ * {@link AudioEffect#EFFECT_TYPE_DYNAMICS_PROCESSING}.
* </li>
* <li>uuid: UUID for this particular implementation</li>
* <li>connectMode: {@link #EFFECT_INSERT} or {@link #EFFECT_AUXILIARY}</li>
@@ -224,7 +232,8 @@
* {@link AudioEffect#EFFECT_TYPE_BASS_BOOST}, {@link AudioEffect#EFFECT_TYPE_ENV_REVERB},
* {@link AudioEffect#EFFECT_TYPE_EQUALIZER}, {@link AudioEffect#EFFECT_TYPE_NS},
* {@link AudioEffect#EFFECT_TYPE_PRESET_REVERB},
- * {@link AudioEffect#EFFECT_TYPE_VIRTUALIZER}.
+ * {@link AudioEffect#EFFECT_TYPE_VIRTUALIZER},
+ * {@link AudioEffect#EFFECT_TYPE_DYNAMICS_PROCESSING}.
* @param uuid UUID for this particular implementation
* @param connectMode {@link #EFFECT_INSERT} or {@link #EFFECT_AUXILIARY}
* @param name human readable effect name
@@ -246,7 +255,8 @@
* {@link AudioEffect#EFFECT_TYPE_AGC}, {@link AudioEffect#EFFECT_TYPE_BASS_BOOST},
* {@link AudioEffect#EFFECT_TYPE_ENV_REVERB}, {@link AudioEffect#EFFECT_TYPE_EQUALIZER},
* {@link AudioEffect#EFFECT_TYPE_NS}, {@link AudioEffect#EFFECT_TYPE_PRESET_REVERB}
- * or {@link AudioEffect#EFFECT_TYPE_VIRTUALIZER}.<br>
+ * {@link AudioEffect#EFFECT_TYPE_VIRTUALIZER}
+ * or {@link AudioEffect#EFFECT_TYPE_DYNAMICS_PROCESSING}.<br>
* For reverberation, bass boost, EQ and virtualizer, the UUID
* corresponds to the OpenSL ES Interface ID.
*/
@@ -1344,6 +1354,34 @@
/**
* @hide
*/
+ public static float byteArrayToFloat(byte[] valueBuf) {
+ return byteArrayToFloat(valueBuf, 0);
+
+ }
+
+ /**
+ * @hide
+ */
+ public static float byteArrayToFloat(byte[] valueBuf, int offset) {
+ ByteBuffer converter = ByteBuffer.wrap(valueBuf);
+ converter.order(ByteOrder.nativeOrder());
+ return converter.getFloat(offset);
+
+ }
+
+ /**
+ * @hide
+ */
+ public static byte[] floatToByteArray(float value) {
+ ByteBuffer converter = ByteBuffer.allocate(4);
+ converter.order(ByteOrder.nativeOrder());
+ converter.putFloat(value);
+ return converter.array();
+ }
+
+ /**
+ * @hide
+ */
public static byte[] concatArrays(byte[]... arrays) {
int len = 0;
for (byte[] a : arrays) {
diff --git a/media/java/android/media/audiofx/DynamicsProcessing.java b/media/java/android/media/audiofx/DynamicsProcessing.java
new file mode 100644
index 0000000..d09c9a8
--- /dev/null
+++ b/media/java/android/media/audiofx/DynamicsProcessing.java
@@ -0,0 +1,2257 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.audiofx;
+
+import android.media.AudioTrack;
+import android.media.MediaPlayer;
+import android.media.audiofx.AudioEffect;
+import android.media.audiofx.DynamicsProcessing.Settings;
+import android.util.Log;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.StringTokenizer;
+
+/**
+ * DynamicsProcessing is an audio effect for equalizing and changing dynamic range properties of the
+ * sound. It is composed of multiple stages including equalization, multi-band compression and
+ * limiter.
+ * <p>The number of bands and active stages is configurable, and most parameters can be controlled
+ * in realtime, such as gains, attack/release times, thresholds, etc.
+ * <p>The effect is instantiated and controlled by channels. Each channel has the same basic
+ * architecture, but all of their parameters are independent from other channels.
+ * <p>The basic channel configuration is:
+ * <pre>
+ *
+ * Channel 0 Channel 1 .... Channel N-1
+ * Input Input Input
+ * | | |
+ * +----v----+ +----v----+ +----v----+
+ * |inputGain| |inputGain| |inputGain|
+ * +---------+ +---------+ +---------+
+ * | | |
+ * +-----v-----+ +-----v-----+ +-----v-----+
+ * | PreEQ | | PreEQ | | PreEQ |
+ * +-----------+ +-----------+ +-----------+
+ * | | |
+ * +-----v-----+ +-----v-----+ +-----v-----+
+ * | MBC | | MBC | | MBC |
+ * +-----------+ +-----------+ +-----------+
+ * | | |
+ * +-----v-----+ +-----v-----+ +-----v-----+
+ * | PostEQ | | PostEQ | | PostEQ |
+ * +-----------+ +-----------+ +-----------+
+ * | | |
+ * +-----v-----+ +-----v-----+ +-----v-----+
+ * | Limiter | | Limiter | | Limiter |
+ * +-----------+ +-----------+ +-----------+
+ * | | |
+ * Output Output Output
+ * </pre>
+ *
+ * <p>Where the stages are:
+ * inputGain: input gain factor in decibels (dB). 0 dB means no change in level.
+ * PreEQ: Multi-band Equalizer.
+ * MBC: Multi-band Compressor
+ * PostEQ: Multi-band Equalizer
+ * Limiter: Single band compressor/limiter.
+ *
+ * <p>An application creates a DynamicsProcessing object to instantiate and control this audio
+ * effect in the audio framework. A DynamicsProcessor.Config and DynamicsProcessor.Config.Builder
+ * are available to help configure the multiple stages and each band parameters if desired.
+ * <p>See each stage documentation for further details.
+ * <p>If no Config is specified during creation, a default configuration is chosen.
+ * <p>To attach the DynamicsProcessing to a particular AudioTrack or MediaPlayer,
+ * specify the audio session ID of this AudioTrack or MediaPlayer when constructing the effect
+ * (see {@link AudioTrack#getAudioSessionId()} and {@link MediaPlayer#getAudioSessionId()}).
+ *
+ * <p>To attach the DynamicsProcessing to a particular AudioTrack or MediaPlayer, specify the audio
+ * session ID of this AudioTrack or MediaPlayer when constructing the DynamicsProcessing.
+ * <p>See {@link android.media.MediaPlayer#getAudioSessionId()} for details on audio sessions.
+ * <p>See {@link android.media.audiofx.AudioEffect} class for more details on controlling audio
+ * effects.
+ */
+
+public final class DynamicsProcessing extends AudioEffect {
+
+ private final static String TAG = "DynamicsProcessing";
+
+ /**
+ * Config object used to initialize and change effect parameters at runtime.
+ */
+ private Config mConfig = null;
+
+
+ // These parameter constants must be synchronized with those in
+ // /system/media/audio_effects/include/audio_effects/effect_dynamicsprocessing.h
+
+ private static final int PARAM_GET_CHANNEL_COUNT = 0x0;
+ private static final int PARAM_EQ_BAND_COUNT = 0x1;
+ private static final int PARAM_MBC_BAND_COUNT = 0x2;
+ private static final int PARAM_INPUT_GAIN = 0x3;
+ private static final int PARAM_PRE_EQ_ENABLED = 0x10;
+ private static final int PARAM_PRE_EQ_BAND_ENABLED = 0x11;
+ private static final int PARAM_PRE_EQ_BAND_FREQUENCY = 0x12;
+ private static final int PARAM_PRE_EQ_BAND_GAIN = 0x13;
+ private static final int PARAM_EQ_FREQUENCY_RANGE = 0x22;
+ private static final int PARAM_EQ_GAIN_RANGE = 0x23;
+ private static final int PARAM_MBC_ENABLED = 0x30;
+ private static final int PARAM_MBC_BAND_ENABLED = 0x31;
+ private static final int PARAM_MBC_BAND_FREQUENCY = 0x32;
+ private static final int PARAM_MBC_BAND_ATTACK_TIME = 0x33;
+ private static final int PARAM_MBC_BAND_RELEASE_TIME = 0x34;
+ private static final int PARAM_MBC_BAND_RATIO = 0x35;
+ private static final int PARAM_MBC_BAND_THRESHOLD = 0x36;
+ private static final int PARAM_MBC_BAND_KNEE_WIDTH = 0x37;
+ private static final int PARAM_MBC_BAND_NOISE_GATE_THRESHOLD = 0x38;
+ private static final int PARAM_MBC_BAND_EXPANDER_RATIO = 0x39;
+ private static final int PARAM_MBC_BAND_GAIN_PRE = 0x3A;
+ private static final int PARAM_MBC_BAND_GAIN_POST = 0x3B;
+ private static final int PARAM_MBC_FREQUENCY_RANGE = 0x42;
+ private static final int PARAM_MBC_ATTACK_TIME_RANGE = 0x43;
+ private static final int PARAM_MBC_RELEASE_TIME_RANGE = 0x44;
+ private static final int PARAM_MBC_RATIO_RANGE = 0x45;
+ private static final int PARAM_MBC_THRESHOLD_RANGE = 0x46;
+ private static final int PARAM_MBC_KNEE_WIDTH_RANGE = 0x47;
+ private static final int PARAM_MBC_NOISE_GATE_THRESHOLD_RANGE = 0x48;
+ private static final int PARAM_MBC_EXPANDER_RATIO_RANGE = 0x49;
+ private static final int PARAM_MBC_GAIN_RANGE = 0x4A;
+ private static final int PARAM_POST_EQ_ENABLED = 0x50;
+ private static final int PARAM_POST_EQ_BAND_ENABLED = 0x51;
+ private static final int PARAM_POST_EQ_BAND_FREQUENCY = 0x52;
+ private static final int PARAM_POST_EQ_BAND_GAIN = 0x53;
+ private static final int PARAM_LIMITER_ENABLED = 0x60;
+ private static final int PARAM_LIMITER_LINK_GROUP = 0x61;
+ private static final int PARAM_LIMITER_ATTACK_TIME = 0x62;
+ private static final int PARAM_LIMITER_RELEASE_TIME = 0x63;
+ private static final int PARAM_LIMITER_RATIO = 0x64;
+ private static final int PARAM_LIMITER_THRESHOLD = 0x65;
+ private static final int PARAM_LIMITER_GAIN_POST = 0x66;
+ private static final int PARAM_LIMITER_ATTACK_TIME_RANGE = 0x72;
+ private static final int PARAM_LIMITER_RELEASE_TIME_RANGE = 0x73;
+ private static final int PARAM_LIMITER_RATIO_RANGE = 0x74;
+ private static final int PARAM_LIMITER_THRESHOLD_RANGE = 0x75;
+ private static final int PARAM_LIMITER_GAIN_RANGE = 0x76;
+ private static final int PARAM_VARIANT = 0x100;
+ private static final int PARAM_VARIANT_DESCRIPTION = 0x101;
+ private static final int PARAM_VARIANT_COUNT = 0x102;
+ private static final int PARAM_SET_ENGINE_ARCHITECTURE = 0x200;
+
+ /**
+ * Index of variant that favors frequency resolution. Frequency domain based implementation.
+ */
+ public static final int VARIANT_FAVOR_FREQUENCY_RESOLUTION = 0;
+
+ /**
+ * Index of variant that favors time resolution resolution. Time domain based implementation.
+ */
+ public static final int VARIANT_FAVOR_TIME_RESOLUTION = 1;
+
+ /**
+ * Maximum expected channels to be reported by effect
+ */
+ private static final int CHANNEL_COUNT_MAX = 32;
+
+ /**
+ * Number of channels in effect architecture
+ */
+ private int mChannelCount = 0;
+
+ /**
+ * Registered listener for parameter changes.
+ */
+ private OnParameterChangeListener mParamListener = null;
+
+ /**
+ * Listener used internally to to receive raw parameter change events
+ * from AudioEffect super class
+ */
+ private BaseParameterListener mBaseParamListener = null;
+
+ /**
+ * Lock for access to mParamListener
+ */
+ private final Object mParamListenerLock = new Object();
+
+ /**
+ * Class constructor.
+ * @param audioSession system-wide unique audio session identifier. The DynamicsProcessing
+ * will be attached to the MediaPlayer or AudioTrack in the same audio session.
+ */
+ public DynamicsProcessing(int audioSession) {
+ this(0 /*priority*/, audioSession);
+ }
+
+ /**
+ * @hide
+ * Class constructor for the DynamicsProcessing audio effect.
+ * @param priority the priority level requested by the application for controlling the
+ * DynamicsProcessing engine. As the same engine can be shared by several applications,
+ * this parameter indicates how much the requesting application needs control of effect
+ * parameters. The normal priority is 0, above normal is a positive number, below normal a
+ * negative number.
+ * @param audioSession system-wide unique audio session identifier. The DynamicsProcessing
+ * will be attached to the MediaPlayer or AudioTrack in the same audio session.
+ */
+ public DynamicsProcessing(int priority, int audioSession) {
+ this(priority, audioSession, null);
+ }
+
+ /**
+ * Class constructor for the DynamicsProcessing audio effect
+ * @param priority the priority level requested by the application for controlling the
+ * DynamicsProcessing engine. As the same engine can be shared by several applications,
+ * this parameter indicates how much the requesting application needs control of effect
+ * parameters. The normal priority is 0, above normal is a positive number, below normal a
+ * negative number.
+ * @param audioSession system-wide unique audio session identifier. The DynamicsProcessing
+ * will be attached to the MediaPlayer or AudioTrack in the same audio session.
+ * @param cfg Config object used to setup the audio effect, including bands per stage, and
+ * specific parameters for each stage/band. Use
+ * {@link android.media.audiofx.DynamicsProcessing.Config.Builder} to create a
+ * Config object that suits your needs. A null cfg parameter will create and use a default
+ * configuration for the effect
+ */
+ public DynamicsProcessing(int priority, int audioSession, Config cfg) {
+ super(EFFECT_TYPE_DYNAMICS_PROCESSING, EFFECT_TYPE_NULL, priority, audioSession);
+ if (audioSession == 0) {
+ Log.w(TAG, "WARNING: attaching a DynamicsProcessing to global output mix is"
+ + "deprecated!");
+ }
+ mChannelCount = getChannelCount();
+ if (cfg == null) {
+ //create a default configuration and effect, with the number of channels this effect has
+ DynamicsProcessing.Config.Builder builder =
+ new DynamicsProcessing.Config.Builder(
+ CONFIG_DEFAULT_VARIANT,
+ mChannelCount,
+ true /*use preEQ*/, 6 /*pre eq bands*/,
+ true /*use mbc*/, 6 /*mbc bands*/,
+ true /*use postEQ*/, 6 /*postEq bands*/,
+ true /*use Limiter*/);
+ mConfig = builder.build();
+ } else {
+ //validate channels are ok. decide what to do: replicate channels if more, or fail, or
+ mConfig = new DynamicsProcessing.Config(mChannelCount, cfg);
+ }
+
+ setEngineArchitecture(mConfig.getVariant(),
+ mConfig.isPreEqInUse(), mConfig.getPreEqBandCount(),
+ mConfig.isMbcInUse(), mConfig.getMbcBandCount(),
+ mConfig.isPostEqInUse(), mConfig.getPostEqBandCount(),
+ mConfig.isLimiterInUse());
+ }
+
+ /**
+ * Returns the Config object used to setup this effect.
+ * @return Config Current Config object used to setup this DynamicsProcessing effect.
+ */
+ public Config getConfig() {
+ return mConfig;
+ }
+
+
+ private static final int CONFIG_DEFAULT_VARIANT = 0; //favor frequency
+ private static final float CHANNEL_DEFAULT_INPUT_GAIN = 0; // dB
+ private static final float CONFIG_PREFERRED_FRAME_DURATION_MS = 10.0f; //milliseconds
+
+ private static final float EQ_DEFAULT_GAIN = 0; // dB
+ private static final boolean PREEQ_DEFAULT_ENABLED = true;
+ private static final boolean POSTEQ_DEFAULT_ENABLED = true;
+
+
+ private static final boolean MBC_DEFAULT_ENABLED = true;
+ private static final float MBC_DEFAULT_ATTACK_TIME = 50; // ms
+ private static final float MBC_DEFAULT_RELEASE_TIME = 120; // ms
+ private static final float MBC_DEFAULT_RATIO = 2; // 1:N
+ private static final float MBC_DEFAULT_THRESHOLD = -30; // dB
+ private static final float MBC_DEFAULT_KNEE_WIDTH = 0; // dB
+ private static final float MBC_DEFAULT_NOISE_GATE_THRESHOLD = -80; // dB
+ private static final float MBC_DEFAULT_EXPANDER_RATIO = 1.5f; // N:1
+ private static final float MBC_DEFAULT_PRE_GAIN = 0; // dB
+ private static final float MBC_DEFAULT_POST_GAIN = 10; // dB
+
+ private static final boolean LIMITER_DEFAULT_ENABLED = true;
+ private static final int LIMITER_DEFAULT_LINK_GROUP = 0;//;
+ private static final float LIMITER_DEFAULT_ATTACK_TIME = 50; // ms
+ private static final float LIMITER_DEFAULT_RELEASE_TIME = 120; // ms
+ private static final float LIMITER_DEFAULT_RATIO = 2; // 1:N
+ private static final float LIMITER_DEFAULT_THRESHOLD = -30; // dB
+ private static final float LIMITER_DEFAULT_POST_GAIN = 10; // dB
+
+ private static final float DEFAULT_MIN_FREQUENCY = 220; // Hz
+ private static final float DEFAULT_MAX_FREQUENCY = 20000; // Hz
+ private static final float mMinFreqLog = (float)Math.log10(DEFAULT_MIN_FREQUENCY);
+ private static final float mMaxFreqLog = (float)Math.log10(DEFAULT_MAX_FREQUENCY);
+
+ /**
+ * base class for the different stages.
+ */
+ public static class Stage {
+ private boolean mInUse;
+ private boolean mEnabled;
+ /**
+ * Class constructor for stage
+ * @param inUse true if this stage is set to be used. False otherwise. Stages that are not
+ * set "inUse" at initialization time are not available to be used at any time.
+ * @param enabled true if this stage is currently used to process sound. When disabled,
+ * the stage is bypassed and the sound is copied unaltered from input to output.
+ */
+ public Stage(boolean inUse, boolean enabled) {
+ mInUse = inUse;
+ mEnabled = enabled;
+ }
+
+ /**
+ * returns enabled state of the stage
+ * @return true if stage is enabled for processing, false otherwise
+ */
+ public boolean isEnabled() {
+ return mEnabled;
+ }
+ /**
+ * sets enabled state of the stage
+ * @param enabled true for enabled, false otherwise
+ */
+ public void setEnabled(boolean enabled) {
+ mEnabled = enabled;
+ }
+
+ /**
+ * returns inUse state of the stage.
+ * @return inUse state of the stage. True if this stage is currently used to process sound.
+ * When false, the stage is bypassed and the sound is copied unaltered from input to output.
+ */
+ public boolean isInUse() {
+ return mInUse;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append(String.format(" Stage InUse: %b\n", isInUse()));
+ if (isInUse()) {
+ sb.append(String.format(" Stage Enabled: %b\n", mEnabled));
+ }
+ return sb.toString();
+ }
+ }
+
+ /**
+ * Base class for stages that hold bands
+ */
+ public static class BandStage extends Stage{
+ private int mBandCount;
+ /**
+ * Class constructor for BandStage
+ * @param inUse true if this stage is set to be used. False otherwise. Stages that are not
+ * set "inUse" at initialization time are not available to be used at any time.
+ * @param enabled true if this stage is currently used to process sound. When disabled,
+ * the stage is bypassed and the sound is copied unaltered from input to output.
+ * @param bandCount number of bands this stage will handle. If stage is not inUse, bandcount
+ * is set to 0
+ */
+ public BandStage(boolean inUse, boolean enabled, int bandCount) {
+ super(inUse, enabled);
+ mBandCount = isInUse() ? bandCount : 0;
+ }
+
+ /**
+ * gets number of bands held in this stage
+ * @return number of bands held in this stage
+ */
+ public int getBandCount() {
+ return mBandCount;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append(super.toString());
+ if (isInUse()) {
+ sb.append(String.format(" Band Count: %d\n", mBandCount));
+ }
+ return sb.toString();
+ }
+ }
+
+ /**
+ * Base class for bands
+ */
+ public static class BandBase {
+ private boolean mEnabled;
+ private float mCutoffFrequency;
+ /**
+ * Class constructor for BandBase
+ * @param enabled true if this band is currently used to process sound. When false,
+ * the band is effectively muted and sound set to zero.
+ * @param cutoffFrequency topmost frequency number (in Hz) this band will process. The
+ * effective bandwidth for the band is then computed using this and the previous band
+ * topmost frequency (or 0 Hz for band number 0). Frequencies are expected to increase with
+ * band number, thus band 0 cutoffFrequency <= band 1 cutoffFrequency, and so on.
+ */
+ public BandBase(boolean enabled, float cutoffFrequency) {
+ mEnabled = enabled;
+ mCutoffFrequency = cutoffFrequency;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append(String.format(" Enabled: %b\n", mEnabled));
+ sb.append(String.format(" CutoffFrequency: %f\n", mCutoffFrequency));
+ return sb.toString();
+ }
+
+ /**
+ * returns enabled state of the band
+ * @return true if bands is enabled for processing, false otherwise
+ */
+ public boolean isEnabled() {
+ return mEnabled;
+ }
+ /**
+ * sets enabled state of the band
+ * @param enabled true for enabled, false otherwise
+ */
+ public void setEnabled(boolean enabled) {
+ mEnabled = enabled;
+ }
+
+ /**
+ * gets cutoffFrequency for this band in Hertz (Hz)
+ * @return cutoffFrequency for this band in Hertz (Hz)
+ */
+ public float getCutoffFrequency() {
+ return mCutoffFrequency;
+ }
+
+ /**
+ * sets topmost frequency number (in Hz) this band will process. The
+ * effective bandwidth for the band is then computed using this and the previous band
+ * topmost frequency (or 0 Hz for band number 0). Frequencies are expected to increase with
+ * band number, thus band 0 cutoffFrequency <= band 1 cutoffFrequency, and so on.
+ * @param frequency
+ */
+ public void setCutoffFrequency(float frequency) {
+ mCutoffFrequency = frequency;
+ }
+ }
+
+ /**
+ * Class for Equalizer Bands
+ * Equalizer bands have three controllable parameters: enabled/disabled, cutoffFrequency and
+ * gain
+ */
+ public final static class EqBand extends BandBase {
+ private float mGain;
+ /**
+ * Class constructor for EqBand
+ * @param enabled true if this band is currently used to process sound. When false,
+ * the band is effectively muted and sound set to zero.
+ * @param cutoffFrequency topmost frequency number (in Hz) this band will process. The
+ * effective bandwidth for the band is then computed using this and the previous band
+ * topmost frequency (or 0 Hz for band number 0). Frequencies are expected to increase with
+ * band number, thus band 0 cutoffFrequency <= band 1 cutoffFrequency, and so on.
+ * @param gain of equalizer band in decibels (dB). A gain of 0 dB means no change in level.
+ */
+ public EqBand(boolean enabled, float cutoffFrequency, float gain) {
+ super(enabled, cutoffFrequency);
+ mGain = gain;
+ }
+
+ /**
+ * Class constructor for EqBand
+ * @param cfg copy constructor
+ */
+ public EqBand(EqBand cfg) {
+ super(cfg.isEnabled(), cfg.getCutoffFrequency());
+ mGain = cfg.mGain;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append(super.toString());
+ sb.append(String.format(" Gain: %f\n", mGain));
+ return sb.toString();
+ }
+
+ /**
+ * gets current gain of band in decibels (dB)
+ * @return current gain of band in decibels (dB)
+ */
+ public float getGain() {
+ return mGain;
+ }
+
+ /**
+ * sets current gain of band in decibels (dB)
+ * @param gain desired in decibels (db)
+ */
+ public void setGain(float gain) {
+ mGain = gain;
+ }
+ }
+
+ /**
+ * Class for Multi-Band compressor bands
+ * MBC bands have multiple controllable parameters: enabled/disabled, cutoffFrequency,
+ * attackTime, releaseTime, ratio, threshold, kneeWidth, noiseGateThreshold, expanderRatio,
+ * preGain and postGain.
+ */
+ public final static class MbcBand extends BandBase{
+ private float mAttackTime;
+ private float mReleaseTime;
+ private float mRatio;
+ private float mThreshold;
+ private float mKneeWidth;
+ private float mNoiseGateThreshold;
+ private float mExpanderRatio;
+ private float mPreGain;
+ private float mPostGain;
+ /**
+ * Class constructor for MbcBand
+ * @param enabled true if this band is currently used to process sound. When false,
+ * the band is effectively muted and sound set to zero.
+ * @param cutoffFrequency topmost frequency number (in Hz) this band will process. The
+ * effective bandwidth for the band is then computed using this and the previous band
+ * topmost frequency (or 0 Hz for band number 0). Frequencies are expected to increase with
+ * band number, thus band 0 cutoffFrequency <= band 1 cutoffFrequency, and so on.
+ * @param attackTime Attack Time for compressor in milliseconds (ms)
+ * @param releaseTime Release Time for compressor in milliseconds (ms)
+ * @param ratio Compressor ratio (1:N)
+ * @param threshold Compressor threshold measured in decibels (dB) from 0 dB Full Scale
+ * (dBFS).
+ * @param kneeWidth Width in decibels (dB) around compressor threshold point.
+ * @param noiseGateThreshold Noise gate threshold in decibels (dB) from 0 dB Full Scale
+ * (dBFS).
+ * @param expanderRatio Expander ratio (N:1) for signals below the Noise Gate Threshold.
+ * @param preGain Gain applied to the signal BEFORE the compression.
+ * @param postGain Gain applied to the signal AFTER compression.
+ */
+ public MbcBand(boolean enabled, float cutoffFrequency, float attackTime, float releaseTime,
+ float ratio, float threshold, float kneeWidth, float noiseGateThreshold,
+ float expanderRatio, float preGain, float postGain) {
+ super(enabled, cutoffFrequency);
+ mAttackTime = attackTime;
+ mReleaseTime = releaseTime;
+ mRatio = ratio;
+ mThreshold = threshold;
+ mKneeWidth = kneeWidth;
+ mNoiseGateThreshold = noiseGateThreshold;
+ mExpanderRatio = expanderRatio;
+ mPreGain = preGain;
+ mPostGain = postGain;
+ }
+
+ /**
+ * Class constructor for MbcBand
+ * @param cfg copy constructor
+ */
+ public MbcBand(MbcBand cfg) {
+ super(cfg.isEnabled(), cfg.getCutoffFrequency());
+ mAttackTime = cfg.mAttackTime;
+ mReleaseTime = cfg.mReleaseTime;
+ mRatio = cfg.mRatio;
+ mThreshold = cfg.mThreshold;
+ mKneeWidth = cfg.mKneeWidth;
+ mNoiseGateThreshold = cfg.mNoiseGateThreshold;
+ mExpanderRatio = cfg.mExpanderRatio;
+ mPreGain = cfg.mPreGain;
+ mPostGain = cfg.mPostGain;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append(super.toString());
+ sb.append(String.format(" AttackTime: %f (ms)\n", mAttackTime));
+ sb.append(String.format(" ReleaseTime: %f (ms)\n", mReleaseTime));
+ sb.append(String.format(" Ratio: 1:%f\n", mRatio));
+ sb.append(String.format(" Threshold: %f (dB)\n", mThreshold));
+ sb.append(String.format(" NoiseGateThreshold: %f(dB)\n", mNoiseGateThreshold));
+ sb.append(String.format(" ExpanderRatio: %f:1\n", mExpanderRatio));
+ sb.append(String.format(" PreGain: %f (dB)\n", mPreGain));
+ sb.append(String.format(" PostGain: %f (dB)\n", mPostGain));
+ return sb.toString();
+ }
+
+ /**
+ * gets attack time for compressor in milliseconds (ms)
+ * @return attack time for compressor in milliseconds (ms)
+ */
+ public float getAttackTime() { return mAttackTime; }
+ /**
+ * sets attack time for compressor in milliseconds (ms)
+ * @param attackTime desired for compressor in milliseconds (ms)
+ */
+ public void setAttackTime(float attackTime) { mAttackTime = attackTime; }
+ /**
+ * gets release time for compressor in milliseconds (ms)
+ * @return release time for compressor in milliseconds (ms)
+ */
+ public float getReleaseTime() { return mReleaseTime; }
+ /**
+ * sets release time for compressor in milliseconds (ms)
+ * @param releaseTime desired for compressor in milliseconds (ms)
+ */
+ public void setReleaseTime(float releaseTime) { mReleaseTime = releaseTime; }
+ /**
+ * gets the compressor ratio (1:N)
+ * @return compressor ratio (1:N)
+ */
+ public float getRatio() { return mRatio; }
+ /**
+ * sets compressor ratio (1:N)
+ * @param ratio desired for the compressor (1:N)
+ */
+ public void setRatio(float ratio) { mRatio = ratio; }
+ /**
+ * gets the compressor threshold measured in decibels (dB) from 0 dB Full Scale (dBFS).
+ * Thresholds are negative. A threshold of 0 dB means no compression will take place.
+ * @return compressor threshold in decibels (dB)
+ */
+ public float getThreshold() { return mThreshold; }
+ /**
+ * sets the compressor threshold measured in decibels (dB) from 0 dB Full Scale (dBFS).
+ * Thresholds are negative. A threshold of 0 dB means no compression will take place.
+ * @param threshold desired for compressor in decibels(dB)
+ */
+ public void setThreshold(float threshold) { mThreshold = threshold; }
+ /**
+ * get Knee Width in decibels (dB) around compressor threshold point. Widths are always
+ * positive, with higher values representing a wider area of transition from the linear zone
+ * to the compression zone. A knee of 0 dB means a more abrupt transition.
+ * @return Knee Width in decibels (dB)
+ */
+ public float getKneeWidth() { return mKneeWidth; }
+ /**
+ * sets knee width in decibels (dB). See
+ * {@link android.media.audiofx.DynamicsProcessing.MbcBand#getKneeWidth} for more
+ * information.
+ * @param kneeWidth desired in decibels (dB)
+ */
+ public void setKneeWidth(float kneeWidth) { mKneeWidth = kneeWidth; }
+ /**
+ * gets the noise gate threshold in decibels (dB) from 0 dB Full Scale (dBFS). Noise gate
+ * thresholds are negative. Signals below this level will be expanded according the
+ * expanderRatio parameter. A Noise Gate Threshold of -75 dB means very quiet signals might
+ * be effectively removed from the signal.
+ * @return Noise Gate Threshold in decibels (dB)
+ */
+ public float getNoiseGateThreshold() { return mNoiseGateThreshold; }
+ /**
+ * sets noise gate threshod in decibels (dB). See
+ * {@link android.media.audiofx.DynamicsProcessing.MbcBand#getNoiseGateThreshold} for more
+ * information.
+ * @param noiseGateThreshold desired in decibels (dB)
+ */
+ public void setNoiseGateThreshold(float noiseGateThreshold) {
+ mNoiseGateThreshold = noiseGateThreshold; }
+ /**
+ * gets Expander ratio (N:1) for signals below the Noise Gate Threshold.
+ * @return Expander ratio (N:1)
+ */
+ public float getExpanderRatio() { return mExpanderRatio; }
+ /**
+ * sets Expander ratio (N:1) for signals below the Noise Gate Threshold.
+ * @param expanderRatio desired expander ratio (N:1)
+ */
+ public void setExpanderRatio(float expanderRatio) { mExpanderRatio = expanderRatio; }
+ /**
+ * gets the gain applied to the signal BEFORE the compression. Measured in decibels (dB)
+ * where 0 dB means no level change.
+ * @return preGain value in decibels (dB)
+ */
+ public float getPreGain() { return mPreGain; }
+ /**
+ * sets the gain to be applied to the signal BEFORE the compression, measured in decibels
+ * (dB), where 0 dB means no level change.
+ * @param preGain desired in decibels (dB)
+ */
+ public void setPreGain(float preGain) { mPreGain = preGain; }
+ /**
+ * gets the gain applied to the signal AFTER compression. Measured in decibels (dB) where 0
+ * dB means no level change
+ * @return postGain value in decibels (dB)
+ */
+ public float getPostGain() { return mPostGain; }
+ /**
+ * sets the gain to be applied to the siganl AFTER the compression. Measured in decibels
+ * (dB), where 0 dB means no level change.
+ * @param postGain desired value in decibels (dB)
+ */
+ public void setPostGain(float postGain) { mPostGain = postGain; }
+ }
+
+ /**
+ * Class for Equalizer stage
+ */
+ public final static class Eq extends BandStage {
+ private final EqBand[] mBands;
+ /**
+ * Class constructor for Equalizer (Eq) stage
+ * @param inUse true if Eq stage will be used, false otherwise.
+ * @param enabled true if Eq stage is enabled/disabled. This can be changed while effect is
+ * running
+ * @param bandCount number of bands for this Equalizer stage. Can't be changed while effect
+ * is running
+ */
+ public Eq(boolean inUse, boolean enabled, int bandCount) {
+ super(inUse, enabled, bandCount);
+ if (isInUse()) {
+ mBands = new EqBand[bandCount];
+ for (int b = 0; b < bandCount; b++) {
+ float freq = DEFAULT_MAX_FREQUENCY;
+ if (bandCount > 1) {
+ freq = (float)Math.pow(10, mMinFreqLog +
+ b * (mMaxFreqLog - mMinFreqLog)/(bandCount -1));
+ }
+ mBands[b] = new EqBand(true, freq, EQ_DEFAULT_GAIN);
+ }
+ } else {
+ mBands = null;
+ }
+ }
+ /**
+ * Class constructor for Eq stage
+ * @param cfg copy constructor
+ */
+ public Eq(Eq cfg) {
+ super(cfg.isInUse(), cfg.isEnabled(), cfg.getBandCount());
+ if (isInUse()) {
+ mBands = new EqBand[cfg.mBands.length];
+ for (int b = 0; b < mBands.length; b++) {
+ mBands[b] = new EqBand(cfg.mBands[b]);
+ }
+ } else {
+ mBands = null;
+ }
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append(super.toString());
+ if (isInUse()) {
+ sb.append("--->EqBands: " + mBands.length + "\n");
+ for (int b = 0; b < mBands.length; b++) {
+ sb.append(String.format(" Band %d\n", b));
+ sb.append(mBands[b].toString());
+ }
+ }
+ return sb.toString();
+ }
+ /**
+ * Helper function to check if band index is within range
+ * @param band index to check
+ */
+ private void checkBand(int band) {
+ if (mBands == null || band < 0 || band >= mBands.length) {
+ throw new IllegalArgumentException("band index " + band +" out of bounds");
+ }
+ }
+ /**
+ * Sets EqBand object for given band index
+ * @param band index of band to be modified
+ * @param bandCfg EqBand object.
+ */
+ public void setBand(int band, EqBand bandCfg) {
+ checkBand(band);
+ mBands[band] = new EqBand(bandCfg);
+ }
+ /**
+ * Gets EqBand object for band of interest.
+ * @param band index of band of interest
+ * @return EqBand Object
+ */
+ public EqBand getBand(int band) {
+ checkBand(band);
+ return mBands[band];
+ }
+ }
+
+ /**
+ * Class for Multi-Band Compressor (MBC) stage
+ */
+ public final static class Mbc extends BandStage {
+ private final MbcBand[] mBands;
+ /**
+ * Constructor for Multi-Band Compressor (MBC) stage
+ * @param inUse true if MBC stage will be used, false otherwise.
+ * @param enabled true if MBC stage is enabled/disabled. This can be changed while effect
+ * is running
+ * @param bandCount number of bands for this MBC stage. Can't be changed while effect is
+ * running
+ */
+ public Mbc(boolean inUse, boolean enabled, int bandCount) {
+ super(inUse, enabled, bandCount);
+ if (isInUse()) {
+ mBands = new MbcBand[bandCount];
+ for (int b = 0; b < bandCount; b++) {
+ float freq = DEFAULT_MAX_FREQUENCY;
+ if (bandCount > 1) {
+ freq = (float)Math.pow(10, mMinFreqLog +
+ b * (mMaxFreqLog - mMinFreqLog)/(bandCount -1));
+ }
+ mBands[b] = new MbcBand(true, freq, MBC_DEFAULT_ATTACK_TIME,
+ MBC_DEFAULT_RELEASE_TIME, MBC_DEFAULT_RATIO,
+ MBC_DEFAULT_THRESHOLD, MBC_DEFAULT_KNEE_WIDTH,
+ MBC_DEFAULT_NOISE_GATE_THRESHOLD, MBC_DEFAULT_EXPANDER_RATIO,
+ MBC_DEFAULT_PRE_GAIN, MBC_DEFAULT_POST_GAIN);
+ }
+ } else {
+ mBands = null;
+ }
+ }
+ /**
+ * Class constructor for MBC stage
+ * @param cfg copy constructor
+ */
+ public Mbc(Mbc cfg) {
+ super(cfg.isInUse(), cfg.isEnabled(), cfg.getBandCount());
+ if (isInUse()) {
+ mBands = new MbcBand[cfg.mBands.length];
+ for (int b = 0; b < mBands.length; b++) {
+ mBands[b] = new MbcBand(cfg.mBands[b]);
+ }
+ } else {
+ mBands = null;
+ }
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append(super.toString());
+ if (isInUse()) {
+ sb.append("--->MbcBands: " + mBands.length + "\n");
+ for (int b = 0; b < mBands.length; b++) {
+ sb.append(String.format(" Band %d\n", b));
+ sb.append(mBands[b].toString());
+ }
+ }
+ return sb.toString();
+ }
+ /**
+ * Helper function to check if band index is within range
+ * @param band index to check
+ */
+ private void checkBand(int band) {
+ if (mBands == null || band < 0 || band >= mBands.length) {
+ throw new IllegalArgumentException("band index " + band +" out of bounds");
+ }
+ }
+ /**
+ * Sets MbcBand object for given band index
+ * @param band index of band to be modified
+ * @param bandCfg MbcBand object.
+ */
+ public void setBand(int band, MbcBand bandCfg) {
+ checkBand(band);
+ mBands[band] = new MbcBand(bandCfg);
+ }
+ /**
+ * Gets MbcBand object for band of interest.
+ * @param band index of band of interest
+ * @return MbcBand Object
+ */
+ public MbcBand getBand(int band) {
+ checkBand(band);
+ return mBands[band];
+ }
+ }
+
+ /**
+ * Class for Limiter Stage
+ * Limiter is a single band compressor at the end of the processing chain, commonly used to
+ * protect the signal from overloading and distortion. Limiters have multiple controllable
+ * parameters: enabled/disabled, linkGroup, attackTime, releaseTime, ratio, threshold, and
+ * postGain.
+ * <p>Limiters can be linked in groups across multiple channels. Linked limiters will trigger
+ * the same limiting if any of the linked limiters starts compressing.
+ */
+ public final static class Limiter extends Stage {
+ private int mLinkGroup;
+ private float mAttackTime;
+ private float mReleaseTime;
+ private float mRatio;
+ private float mThreshold;
+ private float mPostGain;
+
+ /**
+ * Class constructor for Limiter Stage
+ * @param inUse true if MBC stage will be used, false otherwise.
+ * @param enabled true if MBC stage is enabled/disabled. This can be changed while effect
+ * is running
+ * @param linkGroup index of group assigned to this Limiter. Only limiters that share the
+ * same linkGroup index will react together.
+ * @param attackTime Attack Time for limiter compressor in milliseconds (ms)
+ * @param releaseTime Release Time for limiter compressor in milliseconds (ms)
+ * @param ratio Limiter Compressor ratio (1:N)
+ * @param threshold Limiter Compressor threshold measured in decibels (dB) from 0 dB Full
+ * Scale (dBFS).
+ * @param postGain Gain applied to the signal AFTER compression.
+ */
+ public Limiter(boolean inUse, boolean enabled, int linkGroup, float attackTime,
+ float releaseTime, float ratio, float threshold, float postGain) {
+ super(inUse, enabled);
+ mLinkGroup = linkGroup;
+ mAttackTime = attackTime;
+ mReleaseTime = releaseTime;
+ mRatio = ratio;
+ mThreshold = threshold;
+ mPostGain = postGain;
+ }
+
+ /**
+ * Class Constructor for Limiter
+ * @param cfg copy constructor
+ */
+ public Limiter(Limiter cfg) {
+ super(cfg.isInUse(), cfg.isEnabled());
+ mLinkGroup = cfg.mLinkGroup;
+ mAttackTime = cfg.mAttackTime;
+ mReleaseTime = cfg.mReleaseTime;
+ mRatio = cfg.mRatio;
+ mThreshold = cfg.mThreshold;
+ mPostGain = cfg.mPostGain;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append(super.toString());
+ if (isInUse()) {
+ sb.append(String.format(" LinkGroup: %d (group)\n", mLinkGroup));
+ sb.append(String.format(" AttackTime: %f (ms)\n", mAttackTime));
+ sb.append(String.format(" ReleaseTime: %f (ms)\n", mReleaseTime));
+ sb.append(String.format(" Ratio: 1:%f\n", mRatio));
+ sb.append(String.format(" Threshold: %f (dB)\n", mThreshold));
+ sb.append(String.format(" PostGain: %f (dB)\n", mPostGain));
+ }
+ return sb.toString();
+ }
+ /**
+ * Gets the linkGroup index for this Limiter Stage. Only limiters that share the same
+ * linkGroup index will react together.
+ * @return linkGroup index.
+ */
+ public int getLinkGroup() { return mLinkGroup; }
+ /**
+ * Sets the linkGroup index for this limiter Stage.
+ * @param linkGroup desired linkGroup index
+ */
+ public void setLinkGroup(int linkGroup) { mLinkGroup = linkGroup; }
+ /**
+ * gets attack time for limiter compressor in milliseconds (ms)
+ * @return attack time for limiter compressor in milliseconds (ms)
+ */
+ public float getAttackTime() { return mAttackTime; }
+ /**
+ * sets attack time for limiter compressor in milliseconds (ms)
+ * @param attackTime desired for limiter compressor in milliseconds (ms)
+ */
+ public void setAttackTime(float attackTime) { mAttackTime = attackTime; }
+ /**
+ * gets release time for limiter compressor in milliseconds (ms)
+ * @return release time for limiter compressor in milliseconds (ms)
+ */
+ public float getReleaseTime() { return mReleaseTime; }
+ /**
+ * sets release time for limiter compressor in milliseconds (ms)
+ * @param releaseTime desired for limiter compressor in milliseconds (ms)
+ */
+ public void setReleaseTime(float releaseTime) { mReleaseTime = releaseTime; }
+ /**
+ * gets the limiter compressor ratio (1:N)
+ * @return limiter compressor ratio (1:N)
+ */
+ public float getRatio() { return mRatio; }
+ /**
+ * sets limiter compressor ratio (1:N)
+ * @param ratio desired for the limiter compressor (1:N)
+ */
+ public void setRatio(float ratio) { mRatio = ratio; }
+ /**
+ * gets the limiter compressor threshold measured in decibels (dB) from 0 dB Full Scale
+ * (dBFS). Thresholds are negative. A threshold of 0 dB means no limiting will take place.
+ * @return limiter compressor threshold in decibels (dB)
+ */
+ public float getThreshold() { return mThreshold; }
+ /**
+ * sets the limiter compressor threshold measured in decibels (dB) from 0 dB Full Scale
+ * (dBFS). Thresholds are negative. A threshold of 0 dB means no limiting will take place.
+ * @param threshold desired for limiter compressor in decibels(dB)
+ */
+ public void setThreshold(float threshold) { mThreshold = threshold; }
+ /**
+ * gets the gain applied to the signal AFTER limiting. Measured in decibels (dB) where 0
+ * dB means no level change
+ * @return postGain value in decibels (dB)
+ */
+ public float getPostGain() { return mPostGain; }
+ /**
+ * sets the gain to be applied to the siganl AFTER the limiter. Measured in decibels
+ * (dB), where 0 dB means no level change.
+ * @param postGain desired value in decibels (dB)
+ */
+ public void setPostGain(float postGain) { mPostGain = postGain; }
+ }
+
+ /**
+ * Class for Channel configuration parameters. It is composed of multiple stages, which can be
+ * used/enabled independently. Stages not used or disabled will be bypassed and the sound would
+ * be unaffected by them.
+ */
+ public final static class Channel {
+ private float mInputGain;
+ private Eq mPreEq;
+ private Mbc mMbc;
+ private Eq mPostEq;
+ private Limiter mLimiter;
+
+ /**
+ * Class constructor for Channel configuration.
+ * @param inputGain value in decibels (dB) of level change applied to the audio before
+ * processing. A value of 0 dB means no change.
+ * @param preEqInUse true if PreEq stage will be used, false otherwise. This can't be
+ * changed later.
+ * @param preEqBandCount number of bands for PreEq stage. This can't be changed later.
+ * @param mbcInUse true if Mbc stage will be used, false otherwise. This can't be changed
+ * later.
+ * @param mbcBandCount number of bands for Mbc stage. This can't be changed later.
+ * @param postEqInUse true if PostEq stage will be used, false otherwise. This can't be
+ * changed later.
+ * @param postEqBandCount number of bands for PostEq stage. This can't be changed later.
+ * @param limiterInUse true if Limiter stage will be used, false otherwise. This can't be
+ * changed later.
+ */
+ public Channel (float inputGain,
+ boolean preEqInUse, int preEqBandCount,
+ boolean mbcInUse, int mbcBandCount,
+ boolean postEqInUse, int postEqBandCount,
+ boolean limiterInUse) {
+ mInputGain = inputGain;
+ mPreEq = new Eq(preEqInUse, PREEQ_DEFAULT_ENABLED, preEqBandCount);
+ mMbc = new Mbc(mbcInUse, MBC_DEFAULT_ENABLED, mbcBandCount);
+ mPostEq = new Eq(postEqInUse, POSTEQ_DEFAULT_ENABLED,
+ postEqBandCount);
+ mLimiter = new Limiter(limiterInUse,
+ LIMITER_DEFAULT_ENABLED, LIMITER_DEFAULT_LINK_GROUP,
+ LIMITER_DEFAULT_ATTACK_TIME, LIMITER_DEFAULT_RELEASE_TIME,
+ LIMITER_DEFAULT_RATIO, LIMITER_DEFAULT_THRESHOLD, LIMITER_DEFAULT_POST_GAIN);
+ }
+
+ /**
+ * Class constructor for Channel configuration
+ * @param cfg copy constructor
+ */
+ public Channel(Channel cfg) {
+ mInputGain = cfg.mInputGain;
+ mPreEq = new Eq(cfg.mPreEq);
+ mMbc = new Mbc(cfg.mMbc);
+ mPostEq = new Eq(cfg.mPostEq);
+ mLimiter = new Limiter(cfg.mLimiter);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append(String.format(" InputGain: %f\n", mInputGain));
+ sb.append("-->PreEq\n");
+ sb.append(mPreEq.toString());
+ sb.append("-->MBC\n");
+ sb.append(mMbc.toString());
+ sb.append("-->PostEq\n");
+ sb.append(mPostEq.toString());
+ sb.append("-->Limiter\n");
+ sb.append(mLimiter.toString());
+ return sb.toString();
+ }
+ /**
+ * Gets inputGain value in decibels (dB). 0 dB means no change;
+ * @return gain value in decibels (dB)
+ */
+ public float getInputGain() {
+ return mInputGain;
+ }
+ /**
+ * Sets inputGain value in decibels (dB). 0 dB means no change;
+ * @param inputGain desired gain value in decibels (dB)
+ */
+ public void setInputGain(float inputGain) {
+ mInputGain = inputGain;
+ }
+
+ /**
+ * Gets PreEq configuration stage
+ * @return PreEq configuration stage
+ */
+ public Eq getPreEq() {
+ return mPreEq;
+ }
+ /**
+ * Sets PreEq configuration stage. New PreEq stage must have the same number of bands than
+ * original PreEq stage.
+ * @param preEq configuration
+ */
+ public void setPreEq(Eq preEq) {
+ if (preEq.getBandCount() != mPreEq.getBandCount()) {
+ throw new IllegalArgumentException("PreEqBandCount changed from " +
+ mPreEq.getBandCount() + " to " + preEq.getBandCount());
+ }
+ mPreEq = new Eq(preEq);
+ }
+ /**
+ * Gets EqBand for PreEq stage for given band index.
+ * @param band index of band of interest from PreEq stage
+ * @return EqBand configuration
+ */
+ public EqBand getPreEqBand(int band) {
+ return mPreEq.getBand(band);
+ }
+ /**
+ * Sets EqBand for PreEq stage for given band index
+ * @param band index of band of interest from PreEq stage
+ * @param preEqBand configuration to be set.
+ */
+ public void setPreEqBand(int band, EqBand preEqBand) {
+ mPreEq.setBand(band, preEqBand);
+ }
+
+ /**
+ * Gets Mbc configuration stage
+ * @return Mbc configuration stage
+ */
+ public Mbc getMbc() {
+ return mMbc;
+ }
+ /**
+ * Sets Mbc configuration stage. New Mbc stage must have the same number of bands than
+ * original Mbc stage.
+ * @param mbc
+ */
+ public void setMbc(Mbc mbc) {
+ if (mbc.getBandCount() != mMbc.getBandCount()) {
+ throw new IllegalArgumentException("MbcBandCount changed from " +
+ mMbc.getBandCount() + " to " + mbc.getBandCount());
+ }
+ mMbc = new Mbc(mbc);
+ }
+ /**
+ * Gets MbcBand configuration for Mbc stage, for given band index.
+ * @param band index of band of interest from Mbc stage
+ * @return MbcBand configuration
+ */
+ public MbcBand getMbcBand(int band) {
+ return mMbc.getBand(band);
+ }
+ /**
+ * Sets MbcBand for Mbc stage for given band index
+ * @param band index of band of interest from Mbc Stage
+ * @param mbcBand configuration to be set
+ */
+ public void setMbcBand(int band, MbcBand mbcBand) {
+ mMbc.setBand(band, mbcBand);
+ }
+
+ /**
+ * Gets PostEq configuration stage
+ * @return PostEq configuration stage
+ */
+ public Eq getPostEq() {
+ return mPostEq;
+ }
+ /**
+ * Sets PostEq configuration stage. New PostEq stage must have the same number of bands than
+ * original PostEq stage.
+ * @param postEq configuration
+ */
+ public void setPostEq(Eq postEq) {
+ if (postEq.getBandCount() != mPostEq.getBandCount()) {
+ throw new IllegalArgumentException("PostEqBandCount changed from " +
+ mPostEq.getBandCount() + " to " + postEq.getBandCount());
+ }
+ mPostEq = new Eq(postEq);
+ }
+ /**
+ * Gets EqBand for PostEq stage for given band index.
+ * @param band index of band of interest from PostEq stage
+ * @return EqBand configuration
+ */
+ public EqBand getPostEqBand(int band) {
+ return mPostEq.getBand(band);
+ }
+ /**
+ * Sets EqBand for PostEq stage for given band index
+ * @param band index of band of interest from PostEq stage
+ * @param postEqBand configuration to be set.
+ */
+ public void setPostEqBand(int band, EqBand postEqBand) {
+ mPostEq.setBand(band, postEqBand);
+ }
+
+ /**
+ * Gets Limiter configuration stage
+ * @return Limiter configuration stage
+ */
+ public Limiter getLimiter() {
+ return mLimiter;
+ }
+ /**
+ * Sets Limiter configuration stage.
+ * @param limiter configuration stage.
+ */
+ public void setLimiter(Limiter limiter) {
+ mLimiter = new Limiter(limiter);
+ }
+ }
+
+ /**
+ * Class for Config object, used by DynamicsProcessing to configure and update the audio effect.
+ * use Builder to instantiate objects of this type.
+ */
+ public final static class Config {
+ private final int mVariant;
+ private final int mChannelCount;
+ private final boolean mPreEqInUse;
+ private final int mPreEqBandCount;
+ private final boolean mMbcInUse;
+ private final int mMbcBandCount;
+ private final boolean mPostEqInUse;
+ private final int mPostEqBandCount;
+ private final boolean mLimiterInUse;
+ private final float mPreferredFrameDuration;
+ private final Channel[] mChannel;
+
+ /**
+ * @hide
+ * Class constructor for config. None of these parameters can be changed later.
+ * @param variant index of variant used for effect engine. See
+ * {@link #VARIANT_FAVOR_FREQUENCY_RESOLUTION} and {@link #VARIANT_FAVOR_TIME_RESOLUTION}.
+ * @param frameDurationMs preferred frame duration in milliseconds (ms).
+ * @param channelCount Number of channels to be configured.
+ * @param preEqInUse true if PreEq stage will be used, false otherwise.
+ * @param preEqBandCount number of bands for PreEq stage.
+ * @param mbcInUse true if Mbc stage will be used, false otherwise.
+ * @param mbcBandCount number of bands for Mbc stage.
+ * @param postEqInUse true if PostEq stage will be used, false otherwise.
+ * @param postEqBandCount number of bands for PostEq stage.
+ * @param limiterInUse true if Limiter stage will be used, false otherwise.
+ * @param channel array of Channel objects to be used for this configuration.
+ */
+ public Config(int variant, float frameDurationMs, int channelCount,
+ boolean preEqInUse, int preEqBandCount,
+ boolean mbcInUse, int mbcBandCount,
+ boolean postEqInUse, int postEqBandCount,
+ boolean limiterInUse,
+ Channel[] channel) {
+ mVariant = variant;
+ mPreferredFrameDuration = frameDurationMs;
+ mChannelCount = channelCount;
+ mPreEqInUse = preEqInUse;
+ mPreEqBandCount = preEqBandCount;
+ mMbcInUse = mbcInUse;
+ mMbcBandCount = mbcBandCount;
+ mPostEqInUse = postEqInUse;
+ mPostEqBandCount = postEqBandCount;
+ mLimiterInUse = limiterInUse;
+
+ mChannel = new Channel[mChannelCount];
+ //check if channelconfig is null or has less channels than channel count.
+ //options: fill the missing with default options.
+ // or fail?
+ for (int ch = 0; ch < mChannelCount; ch++) {
+ if (ch < channel.length) {
+ mChannel[ch] = new Channel(channel[ch]); //copy create
+ } else {
+ //create a new one from scratch? //fail?
+ }
+ }
+ }
+ //a version that will scale to necessary number of channels
+ /**
+ * @hide
+ * Class constructor for Configuration.
+ * @param channelCount limit configuration to this number of channels. if channelCount is
+ * greater than number of channels in cfg, the constructor will duplicate the last channel
+ * found as many times as necessary to create a Config with channelCount number of channels.
+ * If channelCount is less than channels in cfg, the extra channels in cfg will be ignored.
+ * @param cfg copy constructor paremter.
+ */
+ public Config(int channelCount, Config cfg) {
+ mVariant = cfg.mVariant;
+ mPreferredFrameDuration = cfg.mPreferredFrameDuration;
+ mChannelCount = cfg.mChannelCount;
+ mPreEqInUse = cfg.mPreEqInUse;
+ mPreEqBandCount = cfg.mPreEqBandCount;
+ mMbcInUse = cfg.mMbcInUse;
+ mMbcBandCount = cfg.mMbcBandCount;
+ mPostEqInUse = cfg.mPostEqInUse;
+ mPostEqBandCount = cfg.mPostEqBandCount;
+ mLimiterInUse = cfg.mLimiterInUse;
+
+ if (mChannelCount != cfg.mChannel.length) {
+ throw new IllegalArgumentException("configuration channel counts differ " +
+ mChannelCount + " !=" + cfg.mChannel.length);
+ }
+ if (channelCount < 1) {
+ throw new IllegalArgumentException("channel resizing less than 1 not allowed");
+ }
+
+ mChannel = new Channel[channelCount];
+ for (int ch = 0; ch < channelCount; ch++) {
+ if (ch < mChannelCount) {
+ mChannel[ch] = new Channel(cfg.mChannel[ch]);
+ } else {
+ //duplicate last
+ mChannel[ch] = new Channel(cfg.mChannel[mChannelCount-1]);
+ }
+ }
+ }
+
+ /**
+ * @hide
+ * Class constructor for Config
+ * @param cfg Configuration object copy constructor
+ */
+ public Config(Config cfg) {
+ this(cfg.mChannelCount, cfg);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append(String.format("Variant: %d\n", mVariant));
+ sb.append(String.format("PreferredFrameDuration: %f\n", mPreferredFrameDuration));
+ sb.append(String.format("ChannelCount: %d\n", mChannelCount));
+ sb.append(String.format("PreEq inUse: %b, bandCount:%d\n",mPreEqInUse,
+ mPreEqBandCount));
+ sb.append(String.format("Mbc inUse: %b, bandCount: %d\n",mMbcInUse, mMbcBandCount));
+ sb.append(String.format("PostEq inUse: %b, bandCount: %d\n", mPostEqInUse,
+ mPostEqBandCount));
+ sb.append(String.format("Limiter inUse: %b\n", mLimiterInUse));
+ for (int ch = 0; ch < mChannel.length; ch++) {
+ sb.append(String.format("==Channel %d\n", ch));
+ sb.append(mChannel[ch].toString());
+ }
+ return sb.toString();
+ }
+ private void checkChannel(int channelIndex) {
+ if (channelIndex < 0 || channelIndex >= mChannel.length) {
+ throw new IllegalArgumentException("ChannelIndex out of bounds");
+ }
+ }
+
+ //getters and setters
+ /**
+ * Gets variant for effect engine See {@link #VARIANT_FAVOR_FREQUENCY_RESOLUTION} and
+ * {@link #VARIANT_FAVOR_TIME_RESOLUTION}.
+ * @return variant of effect engine
+ */
+ public int getVariant() {
+ return mVariant;
+ }
+ /**
+ * Gets preferred frame duration in milliseconds (ms).
+ * @return preferred frame duration in milliseconds (ms)
+ */
+ public float getPreferredFrameDuration() {
+ return mPreferredFrameDuration;
+ }
+ /**
+ * Gets if preEq stage is in use
+ * @return true if preEq stage is in use;
+ */
+ public boolean isPreEqInUse() {
+ return mPreEqInUse;
+ }
+ /**
+ * Gets number of bands configured for the PreEq stage.
+ * @return number of bands configured for the PreEq stage.
+ */
+ public int getPreEqBandCount() {
+ return mPreEqBandCount;
+ }
+ /**
+ * Gets if Mbc stage is in use
+ * @return true if Mbc stage is in use;
+ */
+ public boolean isMbcInUse() {
+ return mMbcInUse;
+ }
+ /**
+ * Gets number of bands configured for the Mbc stage.
+ * @return number of bands configured for the Mbc stage.
+ */
+ public int getMbcBandCount() {
+ return mMbcBandCount;
+ }
+ /**
+ * Gets if PostEq stage is in use
+ * @return true if PostEq stage is in use;
+ */
+ public boolean isPostEqInUse() {
+ return mPostEqInUse;
+ }
+ /**
+ * Gets number of bands configured for the PostEq stage.
+ * @return number of bands configured for the PostEq stage.
+ */
+ public int getPostEqBandCount() {
+ return mPostEqBandCount;
+ }
+ /**
+ * Gets if Limiter stage is in use
+ * @return true if Limiter stage is in use;
+ */
+ public boolean isLimiterInUse() {
+ return mLimiterInUse;
+ }
+
+ //channel
+ /**
+ * Gets the Channel configuration object by using the channel index
+ * @param channelIndex of desired Channel object
+ * @return Channel configuration object
+ */
+ public Channel getChannelByChannelIndex(int channelIndex) {
+ checkChannel(channelIndex);
+ return mChannel[channelIndex];
+ }
+
+ /**
+ * Sets the chosen Channel object in the selected channelIndex
+ * Note that all the stages should have the same number of bands than the existing Channel
+ * object.
+ * @param channelIndex index of channel to be replaced
+ * @param channel Channel configuration object to be set
+ */
+ public void setChannelTo(int channelIndex, Channel channel) {
+ checkChannel(channelIndex);
+ //check all things are compatible
+ if (mMbcBandCount != channel.getMbc().getBandCount()) {
+ throw new IllegalArgumentException("MbcBandCount changed from " +
+ mMbcBandCount + " to " + channel.getPreEq().getBandCount());
+ }
+ if (mPreEqBandCount != channel.getPreEq().getBandCount()) {
+ throw new IllegalArgumentException("PreEqBandCount changed from " +
+ mPreEqBandCount + " to " + channel.getPreEq().getBandCount());
+ }
+ if (mPostEqBandCount != channel.getPostEq().getBandCount()) {
+ throw new IllegalArgumentException("PostEqBandCount changed from " +
+ mPostEqBandCount + " to " + channel.getPostEq().getBandCount());
+ }
+ mChannel[channelIndex] = new Channel(channel);
+ }
+
+ /**
+ * Sets ALL channels to the chosen Channel object. Note that all the stages should have the
+ * same number of bands than the existing ones.
+ * @param channel Channel configuration object to be set.
+ */
+ public void setAllChannelsTo(Channel channel) {
+ for (int ch = 0; ch < mChannel.length; ch++) {
+ setChannelTo(ch, channel);
+ }
+ }
+
+ //===channel params
+ /**
+ * Gets inputGain value in decibels (dB) for channel indicated by channelIndex
+ * @param channelIndex index of channel of interest
+ * @return inputGain value in decibels (dB). 0 dB means no change.
+ */
+ public float getInputGainByChannelIndex(int channelIndex) {
+ checkChannel(channelIndex);
+ return mChannel[channelIndex].getInputGain();
+ }
+ /**
+ * Sets the inputGain value in decibels (dB) for the channel indicated by channelIndex.
+ * @param channelIndex index of channel of interest
+ * @param inputGain desired value in decibels (dB).
+ */
+ public void setInputGainByChannelIndex(int channelIndex, float inputGain) {
+ checkChannel(channelIndex);
+ mChannel[channelIndex].setInputGain(inputGain);
+ }
+ /**
+ * Sets the inputGain value in decibels (dB) for ALL channels
+ * @param inputGain desired value in decibels (dB)
+ */
+ public void setInputGainAllChannelsTo(float inputGain) {
+ for (int ch = 0; ch < mChannel.length; ch++) {
+ mChannel[ch].setInputGain(inputGain);
+ }
+ }
+
+ //=== PreEQ
+ /**
+ * Gets PreEq stage from channel indicated by channelIndex
+ * @param channelIndex index of channel of interest
+ * @return PreEq stage configuration object
+ */
+ public Eq getPreEqByChannelIndex(int channelIndex) {
+ checkChannel(channelIndex);
+ return mChannel[channelIndex].getPreEq();
+ }
+ /**
+ * Sets the PreEq stage configuration for the channel indicated by channelIndex. Note that
+ * new preEq stage must have the same number of bands than original preEq stage
+ * @param channelIndex index of channel to be set
+ * @param preEq desired PreEq configuration to be set
+ */
+ public void setPreEqByChannelIndex(int channelIndex, Eq preEq) {
+ checkChannel(channelIndex);
+ mChannel[channelIndex].setPreEq(preEq);
+ }
+ /**
+ * Sets the PreEq stage configuration for ALL channels. Note that new preEq stage must have
+ * the same number of bands than original preEq stages.
+ * @param preEq desired PreEq configuration to be set
+ */
+ public void setPreEqAllChannelsTo(Eq preEq) {
+ for (int ch = 0; ch < mChannel.length; ch++) {
+ mChannel[ch].setPreEq(preEq);
+ }
+ }
+ public EqBand getPreEqBandByChannelIndex(int channelIndex, int band) {
+ checkChannel(channelIndex);
+ return mChannel[channelIndex].getPreEqBand(band);
+ }
+ public void setPreEqBandByChannelIndex(int channelIndex, int band, EqBand preEqBand) {
+ checkChannel(channelIndex);
+ mChannel[channelIndex].setPreEqBand(band, preEqBand);
+ }
+ public void setPreEqBandAllChannelsTo(int band, EqBand preEqBand) {
+ for (int ch = 0; ch < mChannel.length; ch++) {
+ mChannel[ch].setPreEqBand(band, preEqBand);
+ }
+ }
+
+ //=== MBC
+ public Mbc getMbcByChannelIndex(int channelIndex) {
+ checkChannel(channelIndex);
+ return mChannel[channelIndex].getMbc();
+ }
+ public void setMbcByChannelIndex(int channelIndex, Mbc mbc) {
+ checkChannel(channelIndex);
+ mChannel[channelIndex].setMbc(mbc);
+ }
+ public void setMbcAllChannelsTo(Mbc mbc) {
+ for (int ch = 0; ch < mChannel.length; ch++) {
+ mChannel[ch].setMbc(mbc);
+ }
+ }
+ public MbcBand getMbcBandByChannelIndex(int channelIndex, int band) {
+ checkChannel(channelIndex);
+ return mChannel[channelIndex].getMbcBand(band);
+ }
+ public void setMbcBandByChannelIndex(int channelIndex, int band, MbcBand mbcBand) {
+ checkChannel(channelIndex);
+ mChannel[channelIndex].setMbcBand(band, mbcBand);
+ }
+ public void setMbcBandAllChannelsTo(int band, MbcBand mbcBand) {
+ for (int ch = 0; ch < mChannel.length; ch++) {
+ mChannel[ch].setMbcBand(band, mbcBand);
+ }
+ }
+
+ //=== PostEQ
+ public Eq getPostEqByChannelIndex(int channelIndex) {
+ checkChannel(channelIndex);
+ return mChannel[channelIndex].getPostEq();
+ }
+ public void setPostEqByChannelIndex(int channelIndex, Eq postEq) {
+ checkChannel(channelIndex);
+ mChannel[channelIndex].setPostEq(postEq);
+ }
+ public void setPostEqAllChannelsTo(Eq postEq) {
+ for (int ch = 0; ch < mChannel.length; ch++) {
+ mChannel[ch].setPostEq(postEq);
+ }
+ }
+ public EqBand getPostEqBandByChannelIndex(int channelIndex, int band) {
+ checkChannel(channelIndex);
+ return mChannel[channelIndex].getPostEqBand(band);
+ }
+ public void setPostEqBandByChannelIndex(int channelIndex, int band, EqBand postEqBand) {
+ checkChannel(channelIndex);
+ mChannel[channelIndex].setPostEqBand(band, postEqBand);
+ }
+ public void setPostEqBandAllChannelsTo(int band, EqBand postEqBand) {
+ for (int ch = 0; ch < mChannel.length; ch++) {
+ mChannel[ch].setPostEqBand(band, postEqBand);
+ }
+ }
+
+ //Limiter
+ public Limiter getLimiterByChannelIndex(int channelIndex) {
+ checkChannel(channelIndex);
+ return mChannel[channelIndex].getLimiter();
+ }
+ public void setLimiterByChannelIndex(int channelIndex, Limiter limiter) {
+ checkChannel(channelIndex);
+ mChannel[channelIndex].setLimiter(limiter);
+ }
+ public void setLimiterAllChannelsTo(Limiter limiter) {
+ for (int ch = 0; ch < mChannel.length; ch++) {
+ mChannel[ch].setLimiter(limiter);
+ }
+ }
+
+ public final static class Builder {
+ private int mVariant;
+ private int mChannelCount;
+ private boolean mPreEqInUse;
+ private int mPreEqBandCount;
+ private boolean mMbcInUse;
+ private int mMbcBandCount;
+ private boolean mPostEqInUse;
+ private int mPostEqBandCount;
+ private boolean mLimiterInUse;
+ private float mPreferredFrameDuration = CONFIG_PREFERRED_FRAME_DURATION_MS;
+ private Channel[] mChannel;
+
+ public Builder(int variant, int channelCount,
+ boolean preEqInUse, int preEqBandCount,
+ boolean mbcInUse, int mbcBandCount,
+ boolean postEqInUse, int postEqBandCount,
+ boolean limiterInUse) {
+ mVariant = variant;
+ mChannelCount = channelCount;
+ mPreEqInUse = preEqInUse;
+ mPreEqBandCount = preEqBandCount;
+ mMbcInUse = mbcInUse;
+ mMbcBandCount = mbcBandCount;
+ mPostEqInUse = postEqInUse;
+ mPostEqBandCount = postEqBandCount;
+ mLimiterInUse = limiterInUse;
+ mChannel = new Channel[mChannelCount];
+ for (int ch = 0; ch < mChannelCount; ch++) {
+ this.mChannel[ch] = new Channel(CHANNEL_DEFAULT_INPUT_GAIN,
+ this.mPreEqInUse, this.mPreEqBandCount,
+ this.mMbcInUse, this.mMbcBandCount,
+ this.mPostEqInUse, this.mPostEqBandCount,
+ this.mLimiterInUse);
+ }
+ }
+
+ private void checkChannel(int channelIndex) {
+ if (channelIndex < 0 || channelIndex >= mChannel.length) {
+ throw new IllegalArgumentException("ChannelIndex out of bounds");
+ }
+ }
+
+ public Builder setPreferredFrameDuration(float frameDuration) {
+ if (frameDuration < 0) {
+ throw new IllegalArgumentException("Expected positive frameDuration");
+ }
+ mPreferredFrameDuration = frameDuration;
+ return this;
+ }
+
+ public Builder setInputGainByChannelIndex(int channelIndex, float inputGain) {
+ checkChannel(channelIndex);
+ mChannel[channelIndex].setInputGain(inputGain);
+ return this;
+ }
+ public Builder setInputGainAllChannelsTo(float inputGain) {
+ for (int ch = 0; ch < mChannel.length; ch++) {
+ mChannel[ch].setInputGain(inputGain);
+ }
+ return this;
+ }
+
+ public Builder setChannelTo(int channelIndex, Channel channel) {
+ checkChannel(channelIndex);
+ //check all things are compatible
+ if (mMbcBandCount != channel.getMbc().getBandCount()) {
+ throw new IllegalArgumentException("MbcBandCount changed from " +
+ mMbcBandCount + " to " + channel.getPreEq().getBandCount());
+ }
+ if (mPreEqBandCount != channel.getPreEq().getBandCount()) {
+ throw new IllegalArgumentException("PreEqBandCount changed from " +
+ mPreEqBandCount + " to " + channel.getPreEq().getBandCount());
+ }
+ if (mPostEqBandCount != channel.getPostEq().getBandCount()) {
+ throw new IllegalArgumentException("PostEqBandCount changed from " +
+ mPostEqBandCount + " to " + channel.getPostEq().getBandCount());
+ }
+ mChannel[channelIndex] = new Channel(channel);
+ return this;
+ }
+ public Builder setAllChannelsTo(Channel channel) {
+ for (int ch = 0; ch < mChannel.length; ch++) {
+ setChannelTo(ch, channel);
+ }
+ return this;
+ }
+
+ public Builder setPreEqByChannelIndex(int channelIndex, Eq preEq) {
+ checkChannel(channelIndex);
+ mChannel[channelIndex].setPreEq(preEq);
+ return this;
+ }
+ public Builder setPreEqAllChannelsTo(Eq preEq) {
+ for (int ch = 0; ch < mChannel.length; ch++) {
+ setPreEqByChannelIndex(ch, preEq);
+ }
+ return this;
+ }
+
+ public Builder setMbcByChannelIndex(int channelIndex, Mbc mbc) {
+ checkChannel(channelIndex);
+ mChannel[channelIndex].setMbc(mbc);
+ return this;
+ }
+ public Builder setMbcAllChannelsTo(Mbc mbc) {
+ for (int ch = 0; ch < mChannel.length; ch++) {
+ setMbcByChannelIndex(ch, mbc);
+ }
+ return this;
+ }
+
+ public Builder setPostEqByChannelIndex(int channelIndex, Eq postEq) {
+ checkChannel(channelIndex);
+ mChannel[channelIndex].setPostEq(postEq);
+ return this;
+ }
+ public Builder setPostEqAllChannelsTo(Eq postEq) {
+ for (int ch = 0; ch < mChannel.length; ch++) {
+ setPostEqByChannelIndex(ch, postEq);
+ }
+ return this;
+ }
+
+ public Builder setLimiterByChannelIndex(int channelIndex, Limiter limiter) {
+ checkChannel(channelIndex);
+ mChannel[channelIndex].setLimiter(limiter);
+ return this;
+ }
+ public Builder setLimiterAllChannelsTo(Limiter limiter) {
+ for (int ch = 0; ch < mChannel.length; ch++) {
+ setLimiterByChannelIndex(ch, limiter);
+ }
+ return this;
+ }
+
+ public Config build() {
+ return new Config(mVariant, mPreferredFrameDuration, mChannelCount,
+ mPreEqInUse, mPreEqBandCount,
+ mMbcInUse, mMbcBandCount,
+ mPostEqInUse, mPostEqBandCount,
+ mLimiterInUse, mChannel);
+ }
+ }
+ }
+ //=== CHANNEL
+ public Channel getChannelByChannelIndex(int channelIndex) {
+ return mConfig.getChannelByChannelIndex(channelIndex);
+ }
+
+ public void setChannelTo(int channelIndex, Channel channel) {
+ mConfig.setChannelTo(channelIndex, channel);
+ }
+
+ public void setAllChannelsTo(Channel channel) {
+ mConfig.setAllChannelsTo(channel);
+ }
+
+ //=== channel params
+ public float getInputGainByChannelIndex(int channelIndex) {
+ //TODO: return info from engine instead of cached config
+ return mConfig.getInputGainByChannelIndex(channelIndex);
+ }
+ public void setInputGainbyChannel(int channelIndex, float inputGain) {
+ mConfig.setInputGainByChannelIndex(channelIndex, inputGain);
+ //TODO: communicate change to engine
+ }
+ public void setInputGainAllChannelsTo(float inputGain) {
+ mConfig.setInputGainAllChannelsTo(inputGain);
+ //TODO: communicate change to engine
+ }
+
+ //=== PreEQ
+ public Eq getPreEqByChannelIndex(int channelIndex) {
+ //TODO: return info from engine instead of cached config
+ return mConfig.getPreEqByChannelIndex(channelIndex);
+ }
+
+ public void setPreEqByChannelIndex(int channelIndex, Eq preEq) {
+ mConfig.setPreEqByChannelIndex(channelIndex, preEq);
+ //TODO: communicate change to engine
+ }
+
+ public void setPreEqAllChannelsTo(Eq preEq) {
+ mConfig.setPreEqAllChannelsTo(preEq);
+ //TODO: communicate change to engine
+ }
+
+ public EqBand getPreEqBandByChannelIndex(int channelIndex, int band) {
+ //TODO: return info from engine instead of cached config
+ return mConfig.getPreEqBandByChannelIndex(channelIndex, band);
+ }
+
+ public void setPreEqBandByChannelIndex(int channelIndex, int band, EqBand preEqBand) {
+ mConfig.setPreEqBandByChannelIndex(channelIndex, band, preEqBand);
+ //TODO: communicate change to engine
+ }
+
+ public void setPreEqBandAllChannelsTo(int band, EqBand preEqBand) {
+ mConfig.setPreEqBandAllChannelsTo(band, preEqBand);
+ //TODO: communicate change to engine
+ }
+
+ //=== MBC
+ public Mbc getMbcByChannelIndex(int channelIndex) {
+ //TODO: return info from engine instead of cached config
+ return mConfig.getMbcByChannelIndex(channelIndex);
+ }
+
+ public void setMbcByChannelIndex(int channelIndex, Mbc mbc) {
+ mConfig.setMbcByChannelIndex(channelIndex, mbc);
+ //TODO: communicate change to engine
+ }
+
+ public void setMbcAllChannelsTo(Mbc mbc) {
+ mConfig.setMbcAllChannelsTo(mbc);
+ //TODO: communicate change to engine
+ }
+
+ public MbcBand getMbcBandByChannelIndex(int channelIndex, int band) {
+ //TODO: return info from engine instead of cached config
+ return mConfig.getMbcBandByChannelIndex(channelIndex, band);
+ }
+
+ public void setMbcBandByChannelIndex(int channelIndex, int band, MbcBand mbcBand) {
+ mConfig.setMbcBandByChannelIndex(channelIndex, band, mbcBand);
+ //TODO: communicate change to engine
+ }
+
+ public void setMbcBandAllChannelsTo(int band, MbcBand mbcBand) {
+ mConfig.setMbcBandAllChannelsTo(band, mbcBand);
+ //TODO: communicate change to engine
+ }
+
+ //== PostEq
+ public Eq getPostEqByChannelIndex(int channelIndex) {
+ //TODO: return info from engine instead of cached config
+ return mConfig.getPostEqByChannelIndex(channelIndex);
+ }
+
+ public void setPostEqByChannelIndex(int channelIndex, Eq postEq) {
+ mConfig.setPostEqByChannelIndex(channelIndex, postEq);
+ //TODO: communicate change to engine
+ }
+
+ public void setPostEqAllChannelsTo(Eq postEq) {
+ mConfig.setPostEqAllChannelsTo(postEq);
+ //TODO: communicate change to engine
+ }
+
+ public EqBand getPostEqBandByChannelIndex(int channelIndex, int band) {
+ //TODO: return info from engine instead of cached config
+ return mConfig.getPostEqBandByChannelIndex(channelIndex, band);
+ }
+
+ public void setPostEqBandByChannelIndex(int channelIndex, int band, EqBand postEqBand) {
+ mConfig.setPostEqBandByChannelIndex(channelIndex, band, postEqBand);
+ //TODO: communicate change to engine
+ }
+
+ public void setPostEqBandAllChannelsTo(int band, EqBand postEqBand) {
+ mConfig.setPostEqBandAllChannelsTo(band, postEqBand);
+ //TODO: communicate change to engine
+ }
+
+ //==== Limiter
+ public Limiter getLimiterByChannelIndex(int channelIndex) {
+ //TODO: return info from engine instead of cached config
+ return mConfig.getLimiterByChannelIndex(channelIndex);
+ }
+
+ public void setLimiterByChannelIndex(int channelIndex, Limiter limiter) {
+ mConfig.setLimiterByChannelIndex(channelIndex, limiter);
+ //TODO: communicate change to engine
+ }
+
+ public void setLimiterAllChannelsTo(Limiter limiter) {
+ mConfig.setLimiterAllChannelsTo(limiter);
+ //TODO: communicate change to engine
+ }
+
+ /**
+ * Gets the number of channels in the effect engine
+ * @return number of channels currently in use by the effect engine
+ */
+ public int getChannelCount() {
+ return getOneInt(PARAM_GET_CHANNEL_COUNT);
+ }
+
+ private void setEngineArchitecture(int variant, boolean preEqInUse, int preEqBandCount,
+ boolean mbcInUse, int mbcBandCount, boolean postEqInUse, int postEqBandCount,
+ boolean limiterInUse) {
+ int[] values = { variant, (preEqInUse ? 1 : 0), preEqBandCount,
+ (mbcInUse ? 1 : 0), mbcBandCount, (postEqInUse ? 1 : 0), postEqBandCount,
+ (limiterInUse ? 1 : 0)};
+ //TODO: enable later setIntArray(PARAM_SET_ENGINE_ARCHITECTURE, values);
+ }
+
+ //****** convenience methods:
+ //
+ private int getOneInt(int paramGet) {
+ int[] param = new int[1];
+ int[] result = new int[1];
+
+ param[0] = paramGet;
+ checkStatus(getParameter(param, result));
+ return result[0];
+ }
+
+ private int getTwoInt(int paramGet, int paramA) {
+ int[] param = new int[2];
+ int[] result = new int[1];
+
+ param[0] = paramGet;
+ param[1] = paramA;
+ checkStatus(getParameter(param, result));
+ return result[0];
+ }
+
+ private int getThreeInt(int paramGet, int paramA, int paramB) {
+ //have to use bytearrays, with more than 2 parameters.
+ byte[] paramBytes = concatArrays(intToByteArray(paramGet),
+ intToByteArray(paramA),
+ intToByteArray(paramB));
+ byte[] resultBytes = new byte[4]; //single int
+
+ checkStatus(getParameter(paramBytes, resultBytes));
+
+ return byteArrayToInt(resultBytes);
+ }
+
+ private void setOneInt(int paramSet, int valueSet) {
+ int[] param = new int[1];
+ int[] value = new int[1];
+
+ param[0] = paramSet;
+ value[0] = valueSet;
+ checkStatus(setParameter(param, value));
+ }
+
+ private void setTwoInt(int paramSet, int paramA, int valueSet) {
+ int[] param = new int[2];
+ int[] value = new int[1];
+
+ param[0] = paramSet;
+ param[1] = paramA;
+ value[0] = valueSet;
+ checkStatus(setParameter(param, value));
+ }
+
+ private void setThreeInt(int paramSet, int paramA, int paramB, int valueSet) {
+ //have to use bytearrays, with more than 2 parameters.
+ byte[] paramBytes = concatArrays(intToByteArray(paramSet),
+ intToByteArray(paramA),
+ intToByteArray(paramB));
+ byte[] valueBytes = intToByteArray(valueSet);
+
+ checkStatus(setParameter(paramBytes, valueBytes));
+ }
+
+ private void setOneFloat(int paramSet, float valueSet) {
+ int[] param = new int[1];
+ byte[] value;
+
+ param[0] = paramSet;
+ value = floatToByteArray(valueSet);
+ checkStatus(setParameter(param, value));
+ }
+
+ private void setTwoFloat(int paramSet, int paramA, float valueSet) {
+ int[] param = new int[2];
+ byte[] value;
+
+ param[0] = paramSet;
+ param[1] = paramA;
+ value = floatToByteArray(valueSet);
+ checkStatus(setParameter(param, value));
+ }
+
+ private void setThreeFloat(int paramSet, int paramA, int paramB, float valueSet) {
+ //have to use bytearrays, with more than 2 parameters.
+ byte[] paramBytes = concatArrays(intToByteArray(paramSet),
+ intToByteArray(paramA),
+ intToByteArray(paramB));
+ byte[] valueBytes = floatToByteArray(valueSet);
+
+ checkStatus(setParameter(paramBytes, valueBytes));
+ }
+ private byte[] intArrayToByteArray(int[] values) {
+ int expectedBytes = values.length * 4;
+ ByteBuffer converter = ByteBuffer.allocate(expectedBytes);
+ converter.order(ByteOrder.nativeOrder());
+ for (int k = 0; k < values.length; k++) {
+ converter.putFloat(values[k]);
+ }
+ return converter.array();
+ }
+ private void setIntArray(int paramSet, int[] paramArray) {
+ //have to use bytearrays, with more than 2 parameters.
+ byte[] paramBytes = intToByteArray(paramSet);
+ byte[] valueBytes = intArrayToByteArray(paramArray);
+
+ checkStatus(setParameter(paramBytes, valueBytes));
+ }
+
+ private float getOneFloat(int paramGet) {
+ int[] param = new int[1];
+ byte[] result = new byte[4];
+
+ param[0] = paramGet;
+ checkStatus(getParameter(param, result));
+ return byteArrayToFloat(result);
+ }
+
+ private float getTwoFloat(int paramGet, int paramA) {
+ int[] param = new int[2];
+ byte[] result = new byte[4];
+
+ param[0] = paramGet;
+ param[1] = paramA;
+ checkStatus(getParameter(param, result));
+ return byteArrayToFloat(result);
+ }
+
+ private float getThreeFloat(int paramGet, int paramA, int paramB) {
+ //have to use bytearrays, with more than 2 parameters.
+ byte[] paramBytes = concatArrays(intToByteArray(paramGet),
+ intToByteArray(paramA),
+ intToByteArray(paramB));
+ byte[] resultBytes = new byte[4]; //single float
+
+ checkStatus(getParameter(paramBytes, resultBytes));
+
+ return byteArrayToFloat(resultBytes);
+ }
+
+ private float[] getOneFloatArray(int paramGet, int expectedSize) {
+ int[] param = new int[1];
+ byte[] result = new byte[4 * expectedSize];
+
+ param[0] = paramGet;
+ checkStatus(getParameter(param, result));
+ float[] returnArray = new float[expectedSize];
+ for (int k = 0; k < expectedSize; k++) {
+ returnArray[k] = byteArrayToFloat(result, 4 * k);
+ }
+ return returnArray;
+ }
+ /**
+ * @hide
+ * The OnParameterChangeListener interface defines a method called by the DynamicsProcessing
+ * when a parameter value has changed.
+ */
+ public interface OnParameterChangeListener {
+ /**
+ * Method called when a parameter value has changed. The method is called only if the
+ * parameter was changed by another application having the control of the same
+ * DynamicsProcessing engine.
+ * @param effect the DynamicsProcessing on which the interface is registered.
+ * @param param ID of the modified parameter. See {@link #PARAM_GENERIC_PARAM1} ...
+ * @param value the new parameter value.
+ */
+ void onParameterChange(DynamicsProcessing effect, int param, int value);
+ }
+
+ /**
+ * helper method to update effect architecture parameters
+ */
+ private void updateEffectArchitecture() {
+ mChannelCount = getChannelCount();
+ }
+
+ /**
+ * Listener used internally to receive unformatted parameter change events from AudioEffect
+ * super class.
+ */
+ private class BaseParameterListener implements AudioEffect.OnParameterChangeListener {
+ private BaseParameterListener() {
+
+ }
+ public void onParameterChange(AudioEffect effect, int status, byte[] param, byte[] value) {
+ // only notify when the parameter was successfully change
+ if (status != AudioEffect.SUCCESS) {
+ return;
+ }
+ OnParameterChangeListener l = null;
+ synchronized (mParamListenerLock) {
+ if (mParamListener != null) {
+ l = mParamListener;
+ }
+ }
+ if (l != null) {
+ int p = -1;
+ int v = Integer.MIN_VALUE;
+
+ if (param.length == 4) {
+ p = byteArrayToInt(param, 0);
+ }
+ if (value.length == 4) {
+ v = byteArrayToInt(value, 0);
+ }
+ if (p != -1 && v != Integer.MIN_VALUE) {
+ l.onParameterChange(DynamicsProcessing.this, p, v);
+ }
+ }
+ }
+ }
+
+ /**
+ * @hide
+ * Registers an OnParameterChangeListener interface.
+ * @param listener OnParameterChangeListener interface registered
+ */
+ public void setParameterListener(OnParameterChangeListener listener) {
+ synchronized (mParamListenerLock) {
+ if (mParamListener == null) {
+ mBaseParamListener = new BaseParameterListener();
+ super.setParameterListener(mBaseParamListener);
+ }
+ mParamListener = listener;
+ }
+ }
+
+ /**
+ * @hide
+ * The Settings class regroups the DynamicsProcessing parameters. It is used in
+ * conjunction with the getProperties() and setProperties() methods to backup and restore
+ * all parameters in a single call.
+ */
+
+ public static class Settings {
+ public int channelCount;
+ public float[] inputGain;
+
+ public Settings() {
+ }
+
+ /**
+ * Settings class constructor from a key=value; pairs formatted string. The string is
+ * typically returned by Settings.toString() method.
+ * @throws IllegalArgumentException if the string is not correctly formatted.
+ */
+ public Settings(String settings) {
+ StringTokenizer st = new StringTokenizer(settings, "=;");
+ //int tokens = st.countTokens();
+ if (st.countTokens() != 3) {
+ throw new IllegalArgumentException("settings: " + settings);
+ }
+ String key = st.nextToken();
+ if (!key.equals("DynamicsProcessing")) {
+ throw new IllegalArgumentException(
+ "invalid settings for DynamicsProcessing: " + key);
+ }
+ try {
+ key = st.nextToken();
+ if (!key.equals("channelCount")) {
+ throw new IllegalArgumentException("invalid key name: " + key);
+ }
+ channelCount = Short.parseShort(st.nextToken());
+ if (channelCount > CHANNEL_COUNT_MAX) {
+ throw new IllegalArgumentException("too many channels Settings:" + settings);
+ }
+ if (st.countTokens() != channelCount*1) { //check expected parameters.
+ throw new IllegalArgumentException("settings: " + settings);
+ }
+ //check to see it is ok the size
+ inputGain = new float[channelCount];
+ for (int ch = 0; ch < channelCount; ch++) {
+ key = st.nextToken();
+ if (!key.equals(ch +"_inputGain")) {
+ throw new IllegalArgumentException("invalid key name: " + key);
+ }
+ inputGain[ch] = Float.parseFloat(st.nextToken());
+ }
+ } catch (NumberFormatException nfe) {
+ throw new IllegalArgumentException("invalid value for key: " + key);
+ }
+ }
+
+ @Override
+ public String toString() {
+ String str = new String (
+ "DynamicsProcessing"+
+ ";channelCount="+Integer.toString(channelCount));
+ for (int ch = 0; ch < channelCount; ch++) {
+ str = str.concat(";"+ch+"_inputGain="+Float.toString(inputGain[ch]));
+ }
+ return str;
+ }
+ };
+
+
+ /**
+ * @hide
+ * Gets the DynamicsProcessing properties. This method is useful when a snapshot of current
+ * effect settings must be saved by the application.
+ * @return a DynamicsProcessing.Settings object containing all current parameters values
+ */
+ public DynamicsProcessing.Settings getProperties() {
+ Settings settings = new Settings();
+
+ //TODO: just for testing, we are calling the getters one by one, this is
+ // supposed to be done in a single (or few calls) and get all the parameters at once.
+
+ settings.channelCount = getChannelCount();
+
+ if (settings.channelCount > CHANNEL_COUNT_MAX) {
+ throw new IllegalArgumentException("too many channels Settings:" + settings);
+ }
+
+ { // get inputGainmB per channel
+ settings.inputGain = new float [settings.channelCount];
+ for (int ch = 0; ch < settings.channelCount; ch++) {
+//TODO:with config settings.inputGain[ch] = getInputGain(ch);
+ }
+ }
+ return settings;
+ }
+
+ /**
+ * @hide
+ * Sets the DynamicsProcessing properties. This method is useful when bass boost settings
+ * have to be applied from a previous backup.
+ * @param settings a DynamicsProcessing.Settings object containing the properties to apply
+ */
+ public void setProperties(DynamicsProcessing.Settings settings) {
+
+ if (settings.channelCount != settings.inputGain.length ||
+ settings.channelCount != mChannelCount) {
+ throw new IllegalArgumentException("settings invalid channel count: "
+ + settings.channelCount);
+ }
+
+ //TODO: for now calling multiple times.
+ for (int ch = 0; ch < mChannelCount; ch++) {
+//TODO: use config setInputGain(ch, settings.inputGain[ch]);
+ }
+ }
+}
diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp
index 022198b..62030bb 100644
--- a/media/jni/android_media_MediaCodec.cpp
+++ b/media/jni/android_media_MediaCodec.cpp
@@ -36,7 +36,6 @@
#include <gui/Surface.h>
-#include <media/ICrypto.h>
#include <media/MediaCodecBuffer.h>
#include <media/stagefright/MediaCodec.h>
#include <media/stagefright/foundation/ABuffer.h>
@@ -46,6 +45,7 @@
#include <media/stagefright/foundation/AString.h>
#include <media/stagefright/MediaErrors.h>
#include <media/stagefright/PersistentSurface.h>
+#include <mediadrm/ICrypto.h>
#include <nativehelper/ScopedLocalRef.h>
#include <system/window.h>
diff --git a/media/jni/android_media_MediaCrypto.cpp b/media/jni/android_media_MediaCrypto.cpp
index 1b3c24f..2d9051f 100644
--- a/media/jni/android_media_MediaCrypto.cpp
+++ b/media/jni/android_media_MediaCrypto.cpp
@@ -26,9 +26,9 @@
#include <binder/IServiceManager.h>
#include <cutils/properties.h>
-#include <media/ICrypto.h>
-#include <media/IMediaDrmService.h>
#include <media/stagefright/foundation/ADebug.h>
+#include <mediadrm/ICrypto.h>
+#include <mediadrm/IMediaDrmService.h>
namespace android {
diff --git a/media/jni/android_media_MediaDrm.cpp b/media/jni/android_media_MediaDrm.cpp
index 3518392..4c20f05 100644
--- a/media/jni/android_media_MediaDrm.cpp
+++ b/media/jni/android_media_MediaDrm.cpp
@@ -31,10 +31,10 @@
#include <binder/Parcel.h>
#include <binder/PersistableBundle.h>
#include <cutils/properties.h>
-#include <media/IDrm.h>
-#include <media/IMediaDrmService.h>
#include <media/stagefright/foundation/ADebug.h>
#include <media/stagefright/MediaErrors.h>
+#include <mediadrm/IDrm.h>
+#include <mediadrm/IMediaDrmService.h>
using ::android::os::PersistableBundle;
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/AbstractPreferenceController.java b/packages/SettingsLib/src/com/android/settingslib/core/AbstractPreferenceController.java
index d14b53b..566e037 100644
--- a/packages/SettingsLib/src/com/android/settingslib/core/AbstractPreferenceController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/core/AbstractPreferenceController.java
@@ -72,9 +72,9 @@
/**
- * @return a String for the summary of the preference.
+ * @return a {@link CharSequence} for the summary of the preference.
*/
- public String getSummary() {
+ public CharSequence getSummary() {
return null;
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/widget/FooterPreference.java b/packages/SettingsLib/src/com/android/settingslib/widget/FooterPreference.java
index 0c3a5e9..e693551 100644
--- a/packages/SettingsLib/src/com/android/settingslib/widget/FooterPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/widget/FooterPreference.java
@@ -33,7 +33,7 @@
public class FooterPreference extends Preference {
static final int ORDER_FOOTER = Integer.MAX_VALUE - 1;
- static final String KEY_FOOTER = "footer_preference";
+ public static final String KEY_FOOTER = "footer_preference";
public FooterPreference(Context context, AttributeSet attrs) {
super(context, attrs, TypedArrayUtils.getAttr(
diff --git a/packages/SystemUI/res-keyguard/values/strings.xml b/packages/SystemUI/res-keyguard/values/strings.xml
index 8a48e7b..02d0d70 100644
--- a/packages/SystemUI/res-keyguard/values/strings.xml
+++ b/packages/SystemUI/res-keyguard/values/strings.xml
@@ -57,15 +57,15 @@
<!-- When the lock screen is showing and the phone plugged in, and the battery
is not fully charged, say that it's charging. -->
- <string name="keyguard_plugged_in">Charging</string>
+ <string name="keyguard_plugged_in"><xliff:g id="percentage">%s</xliff:g> • Charging</string>
<!-- When the lock screen is showing and the phone plugged in, and the battery
is not fully charged, and it's plugged into a fast charger, say that it's charging fast. -->
- <string name="keyguard_plugged_in_charging_fast">Charging rapidly</string>
+ <string name="keyguard_plugged_in_charging_fast"><xliff:g id="percentage">%s</xliff:g> • Charging rapidly</string>
<!-- When the lock screen is showing and the phone plugged in, and the battery
is not fully charged, and it's plugged into a slow charger, say that it's charging slowly. -->
- <string name="keyguard_plugged_in_charging_slowly">Charging slowly</string>
+ <string name="keyguard_plugged_in_charging_slowly"><xliff:g id="percentage">%s</xliff:g> • Charging slowly</string>
<!-- When the lock screen is showing and the battery is low, warn user to plug
in the phone soon. -->
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index cf0659a..a444ff9 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -124,7 +124,7 @@
<!-- Tiles native to System UI. Order should match "quick_settings_tiles_default" -->
<string name="quick_settings_tiles_stock" translatable="false">
- wifi,cell,battery,dnd,flashlight,rotation,bt,airplane,location,hotspot,inversion,saver,work,cast,night,alarm
+ wifi,cell,battery,dnd,flashlight,rotation,bt,airplane,location,hotspot,inversion,saver,work,cast,night
</string>
<!-- The tiles to display in QuickSettings -->
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 920dd98..9245ac1 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -743,8 +743,6 @@
<string name="quick_settings_wifi_on_label">Wi-Fi On</string>
<!-- QuickSettings: Wifi detail panel, text when there are no items [CHAR LIMIT=NONE] -->
<string name="quick_settings_wifi_detail_empty_text">No Wi-Fi networks available</string>
- <!-- QuickSettings: Alarm title [CHAR LIMIT=NONE] -->
- <string name="quick_settings_alarm_title">Alarm</string>
<!-- QuickSettings: Cast title [CHAR LIMIT=NONE] -->
<string name="quick_settings_cast_title">Cast</string>
<!-- QuickSettings: Cast detail panel, status text when casting [CHAR LIMIT=NONE] -->
@@ -949,13 +947,13 @@
<string name="interruption_level_alarms_twoline">Alarms\nonly</string>
<!-- Indication on the keyguard that is shown when the device is charging. [CHAR LIMIT=40]-->
- <string name="keyguard_indication_charging_time">Charging (<xliff:g id="charging_time_left" example="4 hours and 2 minutes">%s</xliff:g> until full)</string>
+ <string name="keyguard_indication_charging_time"><xliff:g id="percentage">%2$s</xliff:g> • Charging (<xliff:g id="charging_time_left" example="4 hours and 2 minutes">%s</xliff:g> until full)</string>
<!-- Indication on the keyguard that is shown when the device is charging rapidly. Should match keyguard_plugged_in_charging_fast [CHAR LIMIT=40]-->
- <string name="keyguard_indication_charging_time_fast">Charging rapidly (<xliff:g id="charging_time_left" example="4 hours and 2 minutes">%s</xliff:g> until full)</string>
+ <string name="keyguard_indication_charging_time_fast"><xliff:g id="percentage">%2$s</xliff:g> • Charging rapidly (<xliff:g id="charging_time_left" example="4 hours and 2 minutes">%s</xliff:g> until full)</string>
<!-- Indication on the keyguard that is shown when the device is charging slowly. Should match keyguard_plugged_in_charging_slowly [CHAR LIMIT=40]-->
- <string name="keyguard_indication_charging_time_slowly">Charging slowly (<xliff:g id="charging_time_left" example="4 hours and 2 minutes">%s</xliff:g> until full)</string>
+ <string name="keyguard_indication_charging_time_slowly"><xliff:g id="percentage">%2$s</xliff:g> • Charging slowly (<xliff:g id="charging_time_left" example="4 hours and 2 minutes">%s</xliff:g> until full)</string>
<!-- Related to user switcher --><skip/>
@@ -2140,4 +2138,8 @@
<!-- Option to grant the slice permission request on the screen [CHAR LIMIT=15] -->
<string name="slice_permission_deny">Deny</string>
+ <!-- List of packages for which we don't want to show recents onboarding, add into overlay as needed. -->
+ <string-array name="recents_onboarding_blacklisted_packages" translatable="false">
+ </string-array>
+
</resources>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
index b8319a8e..846aadd 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
@@ -28,25 +28,30 @@
* Proxies SurfaceControl.screenshotToBuffer().
*/
GraphicBufferCompat screenshot(in Rect sourceCrop, int width, int height, int minLayer,
- int maxLayer, boolean useIdentityTransform, int rotation);
+ int maxLayer, boolean useIdentityTransform, int rotation) = 0;
/**
* Begins screen pinning on the provided {@param taskId}.
*/
- void startScreenPinning(int taskId);
+ void startScreenPinning(int taskId) = 1;
/**
* Called when the overview service has started the recents animation.
*/
- void onRecentsAnimationStarted();
+ void onRecentsAnimationStarted() = 2;
/**
* Specifies the text to be shown for onboarding the new swipe-up gesture to access recents.
*/
- void setRecentsOnboardingText(CharSequence text);
+ void setRecentsOnboardingText(CharSequence text) = 3;
/**
* Enables/disables launcher/overview interaction features {@link InteractionType}.
*/
- void setInteractionState(int flags);
+ void setInteractionState(int flags) = 4;
+
+ /**
+ * Notifies SystemUI that split screen has been invoked.
+ */
+ void onSplitScreenInvoked() = 5;
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/WindowManagerWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/WindowManagerWrapper.java
index 68400fc..5b49e67 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/WindowManagerWrapper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/WindowManagerWrapper.java
@@ -101,4 +101,12 @@
Log.w(TAG, "Failed to override pending app transition (remote): ", e);
}
}
+
+ public void endProlongedAnimations() {
+ try {
+ WindowManagerGlobal.getWindowManagerService().endProlongedAnimations();
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to end prolonged animations: ", e);
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index c3413d9..cb5a050 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -25,6 +25,7 @@
import android.util.AttributeSet;
import android.util.Log;
import android.util.Slog;
+import android.util.StatsLog;
import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
import android.view.View;
@@ -430,9 +431,13 @@
public void reportUnlockAttempt(int userId, boolean success, int timeoutMs) {
KeyguardUpdateMonitor monitor = KeyguardUpdateMonitor.getInstance(mContext);
if (success) {
+ StatsLog.write(StatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED,
+ StatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED__RESULT__SUCCESS);
monitor.clearFailedUnlockAttempts();
mLockPatternUtils.reportSuccessfulPasswordAttempt(userId);
} else {
+ StatsLog.write(StatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED,
+ StatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED__RESULT__FAILURE);
KeyguardSecurityContainer.this.reportFailedUnlockAttempt(userId, timeoutMs);
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
index b54d09a..4c2aa63 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
@@ -162,12 +162,13 @@
mRow.addView(button);
PendingIntent pendingIntent = null;
- if (rc.getPrimaryAction() != null) {
- pendingIntent = rc.getPrimaryAction().getAction();
+ if (rc.getContentIntent() != null) {
+ pendingIntent = rc.getContentIntent().getAction();
}
mClickActions.put(button, pendingIntent);
- button.setText(rc.getTitleItem().getText());
+ final SliceItem titleItem = rc.getTitleItem();
+ button.setText(titleItem == null ? null : titleItem.getText());
Drawable iconDrawable = null;
SliceItem icon = SliceQuery.find(item.getSlice(),
diff --git a/packages/SystemUI/src/com/android/systemui/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/OverviewProxyService.java
index 1185f45..3c666e4 100644
--- a/packages/SystemUI/src/com/android/systemui/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/OverviewProxyService.java
@@ -34,6 +34,8 @@
import android.view.SurfaceControl;
import com.android.systemui.OverviewProxyService.OverviewProxyListener;
+import com.android.systemui.recents.events.EventBus;
+import com.android.systemui.recents.events.activity.DockedFirstAnimationFrameEvent;
import com.android.systemui.shared.recents.IOverviewProxy;
import com.android.systemui.shared.recents.ISystemUiProxy;
import com.android.systemui.shared.system.GraphicBufferCompat;
@@ -108,6 +110,15 @@
}
}
+ public void onSplitScreenInvoked() {
+ long token = Binder.clearCallingIdentity();
+ try {
+ EventBus.getDefault().post(new DockedFirstAnimationFrameEvent());
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
public void setRecentsOnboardingText(CharSequence text) {
mOnboardingText = text;
}
diff --git a/packages/SystemUI/src/com/android/systemui/Prefs.java b/packages/SystemUI/src/com/android/systemui/Prefs.java
index ee573fb..396d317 100644
--- a/packages/SystemUI/src/com/android/systemui/Prefs.java
+++ b/packages/SystemUI/src/com/android/systemui/Prefs.java
@@ -24,6 +24,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Map;
+import java.util.Set;
public final class Prefs {
private Prefs() {} // no instantation
@@ -87,6 +88,7 @@
String NUM_APPS_LAUNCHED = "NumAppsLaunched";
String HAS_SEEN_RECENTS_ONBOARDING = "HasSeenRecentsOnboarding";
String SEEN_RINGER_GUIDANCE_COUNT = "RingerGuidanceCount";
+ String QS_TILE_SPECS_REVEALED = "QsTileSpecsRevealed";
}
public static boolean getBoolean(Context context, @Key String key, boolean defaultValue) {
@@ -121,6 +123,15 @@
get(context).edit().putString(key, value).apply();
}
+ public static void putStringSet(Context context, @Key String key, Set<String> value) {
+ get(context).edit().putStringSet(key, value).apply();
+ }
+
+ public static Set<String> getStringSet(
+ Context context, @Key String key, Set<String> defaultValue) {
+ return get(context).getStringSet(key, defaultValue);
+ }
+
public static Map<String, ?> getAll(Context context) {
return get(context).getAll();
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
index c7d276c..26618bf 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
@@ -19,6 +19,7 @@
import android.app.ActivityManager;
import android.app.AlarmManager;
import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -27,6 +28,7 @@
import android.icu.text.DisplayContext;
import android.net.Uri;
import android.os.Handler;
+import android.os.SystemClock;
import android.text.TextUtils;
import com.android.internal.annotations.VisibleForTesting;
@@ -36,6 +38,7 @@
import java.util.Date;
import java.util.Locale;
+import java.util.concurrent.TimeUnit;
import androidx.app.slice.Slice;
import androidx.app.slice.SliceProvider;
@@ -53,6 +56,12 @@
public static final String KEYGUARD_NEXT_ALARM_URI =
"content://com.android.systemui.keyguard/alarm";
+ /**
+ * Only show alarms that will ring within N hours.
+ */
+ @VisibleForTesting
+ static final int ALARM_VISIBILITY_HOURS = 12;
+
private final Date mCurrentTime = new Date();
protected final Uri mSliceUri;
protected final Uri mDateUri;
@@ -65,6 +74,10 @@
private boolean mRegisteredEveryMinute;
private String mNextAlarm;
private NextAlarmController mNextAlarmController;
+ protected AlarmManager mAlarmManager;
+ protected ContentResolver mContentResolver;
+ private AlarmManager.AlarmClockInfo mNextAlarmInfo;
+ private final AlarmManager.OnAlarmListener mUpdateNextAlarm = this::updateNextAlarm;
/**
* Receiver responsible for time ticking and updating the date format.
@@ -105,17 +118,26 @@
public Slice onBindSlice(Uri sliceUri) {
ListBuilder builder = new ListBuilder(getContext(), mSliceUri);
builder.addRow(new RowBuilder(builder, mDateUri).setTitle(mLastText));
- if (!TextUtils.isEmpty(mNextAlarm)) {
- Icon icon = Icon.createWithResource(getContext(), R.drawable.ic_access_alarms_big);
- builder.addRow(new RowBuilder(builder, mAlarmUri)
- .setTitle(mNextAlarm).addEndItem(icon));
+ addNextAlarm(builder);
+ return builder.build();
+ }
+
+ protected void addNextAlarm(ListBuilder builder) {
+ if (TextUtils.isEmpty(mNextAlarm)) {
+ return;
}
- return builder.build();
+ Icon alarmIcon = Icon.createWithResource(getContext(), R.drawable.ic_access_alarms_big);
+ RowBuilder alarmRowBuilder = new RowBuilder(builder, mAlarmUri)
+ .setTitle(mNextAlarm)
+ .addEndItem(alarmIcon);
+ builder.addRow(alarmRowBuilder);
}
@Override
public boolean onCreateSliceProvider() {
+ mAlarmManager = getContext().getSystemService(AlarmManager.class);
+ mContentResolver = getContext().getContentResolver();
mNextAlarmController = new NextAlarmControllerImpl(getContext());
mNextAlarmController.addCallback(this);
mDatePattern = getContext().getString(R.string.system_ui_aod_date_pattern);
@@ -124,15 +146,25 @@
return true;
}
- public static String formatNextAlarm(Context context, AlarmManager.AlarmClockInfo info) {
- if (info == null) {
- return "";
+ private void updateNextAlarm() {
+ if (withinNHours(mNextAlarmInfo, ALARM_VISIBILITY_HOURS)) {
+ String pattern = android.text.format.DateFormat.is24HourFormat(getContext(),
+ ActivityManager.getCurrentUser()) ? "H:mm" : "h:mm";
+ mNextAlarm = android.text.format.DateFormat.format(pattern,
+ mNextAlarmInfo.getTriggerTime()).toString();
+ } else {
+ mNextAlarm = "";
}
- String skeleton = android.text.format.DateFormat
- .is24HourFormat(context, ActivityManager.getCurrentUser()) ? "EHm" : "Ehma";
- String pattern = android.text.format.DateFormat
- .getBestDateTimePattern(Locale.getDefault(), skeleton);
- return android.text.format.DateFormat.format(pattern, info.getTriggerTime()).toString();
+ mContentResolver.notifyChange(mSliceUri, null /* observer */);
+ }
+
+ private boolean withinNHours(AlarmManager.AlarmClockInfo alarmClockInfo, int hours) {
+ if (alarmClockInfo == null) {
+ return false;
+ }
+
+ long limit = System.currentTimeMillis() + TimeUnit.HOURS.toMillis(hours);
+ return mNextAlarmInfo.getTriggerTime() <= limit;
}
/**
@@ -181,7 +213,7 @@
final String text = getFormattedDate();
if (!text.equals(mLastText)) {
mLastText = text;
- getContext().getContentResolver().notifyChange(mSliceUri, null /* observer */);
+ mContentResolver.notifyChange(mSliceUri, null /* observer */);
}
}
@@ -203,7 +235,15 @@
@Override
public void onNextAlarmChanged(AlarmManager.AlarmClockInfo nextAlarm) {
- mNextAlarm = formatNextAlarm(getContext(), nextAlarm);
- getContext().getContentResolver().notifyChange(mSliceUri, null /* observer */);
+ mNextAlarmInfo = nextAlarm;
+ mAlarmManager.cancel(mUpdateNextAlarm);
+
+ long triggerAt = mNextAlarmInfo == null ? -1 : mNextAlarmInfo.getTriggerTime()
+ - TimeUnit.HOURS.toMillis(ALARM_VISIBILITY_HOURS);
+ if (triggerAt > 0) {
+ mAlarmManager.setExact(AlarmManager.RTC, triggerAt, "lock_screen_next_alarm",
+ mUpdateNextAlarm, mHandler);
+ }
+ updateNextAlarm();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/PageIndicator.java b/packages/SystemUI/src/com/android/systemui/qs/PageIndicator.java
index b22ea4c..2629f30 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/PageIndicator.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/PageIndicator.java
@@ -24,7 +24,7 @@
// The size of a single dot in relation to the whole animation.
private static final float SINGLE_SCALE = .4f;
- private static final float MINOR_ALPHA = .3f;
+ private static final float MINOR_ALPHA = .42f;
private final ArrayList<Integer> mQueuedPositions = new ArrayList<>();
@@ -53,7 +53,7 @@
removeViewAt(getChildCount() - 1);
}
TypedArray array = getContext().obtainStyledAttributes(
- new int[]{android.R.attr.colorForeground});
+ new int[]{android.R.attr.colorControlActivated});
int color = array.getColor(0, 0);
array.recycle();
while (numPages > getChildCount()) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
index f3417dc..ea3a60b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
@@ -1,5 +1,10 @@
package com.android.systemui.qs;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
@@ -8,20 +13,34 @@
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
+import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
+import android.view.animation.Interpolator;
+import android.view.animation.OvershootInterpolator;
+import android.widget.Scroller;
import com.android.systemui.R;
import com.android.systemui.qs.QSPanel.QSTileLayout;
import com.android.systemui.qs.QSPanel.TileRecord;
import java.util.ArrayList;
+import java.util.Set;
public class PagedTileLayout extends ViewPager implements QSTileLayout {
private static final boolean DEBUG = false;
private static final String TAG = "PagedTileLayout";
+ private static final int REVEAL_SCROLL_DURATION_MILLIS = 750;
+ private static final float BOUNCE_ANIMATION_TENSION = 1.3f;
+ private static final long BOUNCE_ANIMATION_DURATION = 450L;
+ private static final int TILE_ANIMATION_STAGGER_DELAY = 85;
+ private static final Interpolator SCROLL_CUBIC = (t) -> {
+ t -= 1.0f;
+ return t * t * t + 1.0f;
+ };
+
private final ArrayList<TileRecord> mTiles = new ArrayList<TileRecord>();
private final ArrayList<TilePage> mPages = new ArrayList<TilePage>();
@@ -34,37 +53,17 @@
private int mPosition;
private boolean mOffPage;
private boolean mListening;
+ private Scroller mScroller;
+
+ private AnimatorSet mBounceAnimatorSet;
+ private int mAnimatingToPage = -1;
public PagedTileLayout(Context context, AttributeSet attrs) {
super(context, attrs);
+ mScroller = new Scroller(context, SCROLL_CUBIC);
setAdapter(mAdapter);
- setOnPageChangeListener(new OnPageChangeListener() {
- @Override
- public void onPageSelected(int position) {
- if (mPageIndicator == null) return;
- if (mPageListener != null) {
- mPageListener.onPageChanged(isLayoutRtl() ? position == mPages.size() - 1
- : position == 0);
- }
- }
-
- @Override
- public void onPageScrolled(int position, float positionOffset,
- int positionOffsetPixels) {
- if (mPageIndicator == null) return;
- setCurrentPage(position, positionOffset != 0);
- mPageIndicator.setLocation(position + positionOffset);
- if (mPageListener != null) {
- mPageListener.onPageChanged(positionOffsetPixels == 0 &&
- (isLayoutRtl() ? position == mPages.size() - 1 : position == 0));
- }
- }
-
- @Override
- public void onPageScrollStateChanged(int state) {
- }
- });
- setCurrentItem(0);
+ setOnPageChangeListener(mOnPageChangeListener);
+ setCurrentItem(0, false);
}
@Override
@@ -99,6 +98,45 @@
}
}
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ // Suppress all touch event during reveal animation.
+ if (mAnimatingToPage != -1) {
+ return true;
+ }
+ return super.onInterceptTouchEvent(ev);
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent ev) {
+ // Suppress all touch event during reveal animation.
+ if (mAnimatingToPage != -1) {
+ return true;
+ }
+ return super.onTouchEvent(ev);
+ }
+
+ @Override
+ public void computeScroll() {
+ if (!mScroller.isFinished() && mScroller.computeScrollOffset()) {
+ scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
+ float pageFraction = (float) getScrollX() / getWidth();
+ int position = (int) pageFraction;
+ float positionOffset = pageFraction - position;
+ mOnPageChangeListener.onPageScrolled(position, positionOffset, getScrollX());
+ // Keep on drawing until the animation has finished.
+ postInvalidateOnAnimation();
+ return;
+ }
+ if (mAnimatingToPage != -1) {
+ setCurrentItem(mAnimatingToPage, true);
+ mBounceAnimatorSet.start();
+ setOffscreenPageLimit(1);
+ mAnimatingToPage = -1;
+ }
+ super.computeScroll();
+ }
+
/**
* Sets individual pages to listening or not. If offPage it will set
* the next page after position to listening as well since we are in between
@@ -257,9 +295,84 @@
return mPages.get(0).mColumns;
}
+ public void startTileReveal(Set<String> tileSpecs, final Runnable postAnimation) {
+ if (tileSpecs.isEmpty() || mPages.size() < 2 || getScrollX() != 0) {
+ // Do not start the reveal animation unless there are tiles to animate, multiple
+ // TilePages available and the user has not already started dragging.
+ return;
+ }
+
+ final int lastPageNumber = mPages.size() - 1;
+ final TilePage lastPage = mPages.get(lastPageNumber);
+ final ArrayList<Animator> bounceAnims = new ArrayList<>();
+ for (TileRecord tr : lastPage.mRecords) {
+ if (tileSpecs.contains(tr.tile.getTileSpec())) {
+ bounceAnims.add(setupBounceAnimator(tr.tileView, bounceAnims.size()));
+ }
+ }
+
+ if (bounceAnims.isEmpty()) {
+ // All tileSpecs are on the first page. Nothing to do.
+ // TODO: potentially show a bounce animation for first page QS tiles
+ return;
+ }
+
+ mBounceAnimatorSet = new AnimatorSet();
+ mBounceAnimatorSet.playTogether(bounceAnims);
+ mBounceAnimatorSet.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mBounceAnimatorSet = null;
+ postAnimation.run();
+ }
+ });
+ mAnimatingToPage = lastPageNumber;
+ setOffscreenPageLimit(mAnimatingToPage); // Ensure the page to reveal has been inflated.
+ mScroller.startScroll(getScrollX(), getScrollY(), getWidth() * mAnimatingToPage, 0,
+ REVEAL_SCROLL_DURATION_MILLIS);
+ postInvalidateOnAnimation();
+ }
+
+ private static Animator setupBounceAnimator(View view, int ordinal) {
+ view.setAlpha(0f);
+ view.setScaleX(0f);
+ view.setScaleY(0f);
+ ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(view,
+ PropertyValuesHolder.ofFloat(View.ALPHA, 1),
+ PropertyValuesHolder.ofFloat(View.SCALE_X, 1),
+ PropertyValuesHolder.ofFloat(View.SCALE_Y, 1));
+ animator.setDuration(BOUNCE_ANIMATION_DURATION);
+ animator.setStartDelay(ordinal * TILE_ANIMATION_STAGGER_DELAY);
+ animator.setInterpolator(new OvershootInterpolator(BOUNCE_ANIMATION_TENSION));
+ return animator;
+ }
+
+ private final ViewPager.OnPageChangeListener mOnPageChangeListener =
+ new ViewPager.SimpleOnPageChangeListener() {
+ @Override
+ public void onPageSelected(int position) {
+ if (mPageIndicator == null) return;
+ if (mPageListener != null) {
+ mPageListener.onPageChanged(isLayoutRtl() ? position == mPages.size() - 1
+ : position == 0);
+ }
+ }
+
+ @Override
+ public void onPageScrolled(int position, float positionOffset,
+ int positionOffsetPixels) {
+ if (mPageIndicator == null) return;
+ setCurrentPage(position, positionOffset != 0);
+ mPageIndicator.setLocation(position + positionOffset);
+ if (mPageListener != null) {
+ mPageListener.onPageChanged(positionOffsetPixels == 0 &&
+ (isLayoutRtl() ? position == mPages.size() - 1 : position == 0));
+ }
+ }
+ };
+
public static class TilePage extends TileLayout {
private int mMaxRows = 3;
-
public TilePage(Context context, AttributeSet attrs) {
super(context, attrs);
updateResources();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
index 5758762..29f3c43 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
@@ -290,6 +290,7 @@
// Let the views animate their contents correctly by giving them the necessary context.
mHeader.setExpansion(mKeyguardShowing, expansion, panelTranslationY);
mFooter.setExpansion(mKeyguardShowing ? 1 : expansion);
+ mQSPanel.getQsTileRevealController().setExpansion(expansion);
mQSPanel.setTranslationY(translationScaleY * heightDiff);
mQSDetail.setFullyExpanded(fullyExpanded);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 143ad21..61e3065 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -60,11 +60,12 @@
public static final String QS_SHOW_HEADER = "qs_show_header";
protected final Context mContext;
- protected final ArrayList<TileRecord> mRecords = new ArrayList<TileRecord>();
+ protected final ArrayList<TileRecord> mRecords = new ArrayList<>();
protected final View mBrightnessView;
private final H mHandler = new H();
private final View mPageIndicator;
private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
+ private final QSTileRevealController mQsTileRevealController;
protected boolean mExpanded;
protected boolean mListening;
@@ -108,6 +109,8 @@
addView(mPageIndicator);
((PagedTileLayout) mTileLayout).setPageIndicator((PageIndicator) mPageIndicator);
+ mQsTileRevealController = new QSTileRevealController(mContext, this,
+ ((PagedTileLayout) mTileLayout));
addDivider();
@@ -136,6 +139,10 @@
return mPageIndicator;
}
+ public QSTileRevealController getQsTileRevealController() {
+ return mQsTileRevealController;
+ }
+
public boolean isShowingCustomize() {
return mCustomizePanel != null && mCustomizePanel.isCustomizing();
}
@@ -352,6 +359,9 @@
}
public void setTiles(Collection<QSTile> tiles, boolean collapsedView) {
+ if (!collapsedView) {
+ mQsTileRevealController.updateRevealedTiles(tiles);
+ }
for (TileRecord record : mRecords) {
mTileLayout.removeTile(record);
record.tile.removeCallback(record.callback);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileRevealController.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileRevealController.java
new file mode 100644
index 0000000..2f012e6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileRevealController.java
@@ -0,0 +1,76 @@
+package com.android.systemui.qs;
+
+import static com.android.systemui.Prefs.Key.QS_TILE_SPECS_REVEALED;
+
+import android.content.Context;
+import android.os.Handler;
+import android.util.ArraySet;
+
+import com.android.systemui.Prefs;
+import com.android.systemui.plugins.qs.QSTile;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Set;
+
+public class QSTileRevealController {
+ private static final long QS_REVEAL_TILES_DELAY = 500L;
+
+ private final Context mContext;
+ private final QSPanel mQSPanel;
+ private final PagedTileLayout mPagedTileLayout;
+ private final ArraySet<String> mTilesToReveal = new ArraySet<>();
+ private final Handler mHandler = new Handler();
+
+ private final Runnable mRevealQsTiles = new Runnable() {
+ @Override
+ public void run() {
+ mPagedTileLayout.startTileReveal(mTilesToReveal, () -> {
+ if (mQSPanel.isExpanded()) {
+ addTileSpecsToRevealed(mTilesToReveal);
+ mTilesToReveal.clear();
+ }
+ });
+ }
+ };
+
+ QSTileRevealController(Context context, QSPanel qsPanel, PagedTileLayout pagedTileLayout) {
+ mContext = context;
+ mQSPanel = qsPanel;
+ mPagedTileLayout = pagedTileLayout;
+ }
+
+ public void setExpansion(float expansion) {
+ if (expansion == 1f) {
+ mHandler.postDelayed(mRevealQsTiles, QS_REVEAL_TILES_DELAY);
+ } else {
+ mHandler.removeCallbacks(mRevealQsTiles);
+ }
+ }
+
+ public void updateRevealedTiles(Collection<QSTile> tiles) {
+ ArraySet<String> tileSpecs = new ArraySet<>();
+ for (QSTile tile : tiles) {
+ tileSpecs.add(tile.getTileSpec());
+ }
+
+ final Set<String> revealedTiles = Prefs.getStringSet(
+ mContext, QS_TILE_SPECS_REVEALED, Collections.EMPTY_SET);
+ if (revealedTiles.isEmpty() || mQSPanel.isShowingCustomize()) {
+ // Do not reveal QS tiles the user has upon first load or those that they directly
+ // added through customization.
+ addTileSpecsToRevealed(tileSpecs);
+ } else {
+ // Animate all tiles that the user has not directly added themselves.
+ tileSpecs.removeAll(revealedTiles);
+ mTilesToReveal.addAll(tileSpecs);
+ }
+ }
+
+ private void addTileSpecsToRevealed(ArraySet<String> specs) {
+ final ArraySet<String> revealedTiles = new ArraySet<>(
+ Prefs.getStringSet(mContext, QS_TILE_SPECS_REVEALED, Collections.EMPTY_SET));
+ revealedTiles.addAll(specs);
+ Prefs.putStringSet(mContext, QS_TILE_SPECS_REVEALED, revealedTiles);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
index 78481d3..2151436 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
@@ -15,10 +15,10 @@
package com.android.systemui.qs;
import static android.app.StatusBarManager.DISABLE2_QUICK_SETTINGS;
-import static com.android.systemui.keyguard.KeyguardSliceProvider.formatNextAlarm;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
+import android.app.ActivityManager;
import android.app.AlarmManager;
import android.content.Context;
import android.content.Intent;
@@ -51,6 +51,8 @@
import com.android.systemui.statusbar.policy.DarkIconDispatcher.DarkReceiver;
import com.android.systemui.statusbar.policy.NextAlarmController;
+import java.util.Locale;
+
/**
* View that contains the top-most bits of the screen (primarily the status bar with date, time, and
* battery) and also contains the {@link QuickQSPanel} along with some of the panel's inner
@@ -289,7 +291,7 @@
@Override
public void onNextAlarmChanged(AlarmManager.AlarmClockInfo nextAlarm) {
- mNextAlarmText = nextAlarm != null ? formatNextAlarm(mContext, nextAlarm) : null;
+ mNextAlarmText = nextAlarm != null ? formatNextAlarm(nextAlarm) : null;
if (mNextAlarmText != null) {
hideLongPressTooltip(true /* shouldFadeInAlarmText */);
} else {
@@ -430,4 +432,15 @@
public void setCallback(Callback qsPanelCallback) {
mHeaderQsPanel.setCallback(qsPanelCallback);
}
+
+ private String formatNextAlarm(AlarmManager.AlarmClockInfo info) {
+ if (info == null) {
+ return "";
+ }
+ String skeleton = android.text.format.DateFormat
+ .is24HourFormat(mContext, ActivityManager.getCurrentUser()) ? "EHm" : "Ehma";
+ String pattern = android.text.format.DateFormat
+ .getBestDateTimePattern(Locale.getDefault(), skeleton);
+ return android.text.format.DateFormat.format(pattern, info.getTriggerTime()).toString();
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Recents.java b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
index 409c753..47b0de9 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Recents.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
@@ -45,8 +45,10 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.systemui.Dependency;
import com.android.systemui.EventLogConstants;
import com.android.systemui.EventLogTags;
+import com.android.systemui.OverviewProxyService;
import com.android.systemui.R;
import com.android.systemui.RecentsComponent;
import com.android.systemui.SystemUI;
@@ -100,6 +102,8 @@
private static RecentsTaskLoader sTaskLoader;
private static RecentsConfiguration sConfiguration;
+ private OverviewProxyService mOverviewProxyService;
+
private Handler mHandler;
private RecentsImpl mImpl;
private int mDraggingInRecentsCurrentUser;
@@ -208,6 +212,7 @@
sTaskLoader.setDefaultColors(defaultTaskBarBackgroundColor, defaultTaskViewBackgroundColor);
mHandler = new Handler();
mImpl = new RecentsImpl(mContext);
+ mOverviewProxyService = Dependency.get(OverviewProxyService.class);
// Register with the event bus
EventBus.getDefault().register(this, EVENT_BUS_PRIORITY);
@@ -247,6 +252,13 @@
return;
}
+ if (mOverviewProxyService.getProxy() != null) {
+ // TODO: Proxy to Launcher
+ if (!triggeredFromAltTab) {
+ return;
+ }
+ }
+
ActivityManagerWrapper.getInstance().closeSystemWindows(SYSTEM_DIALOG_REASON_RECENT_APPS);
int recentsGrowTarget = getComponent(Divider.class).getView().growsRecents();
int currentUser = sSystemServicesProxy.getCurrentUser();
@@ -282,6 +294,13 @@
return;
}
+ if (mOverviewProxyService.getProxy() != null) {
+ // TODO: Proxy to Launcher
+ if (!triggeredFromAltTab) {
+ return;
+ }
+ }
+
int currentUser = sSystemServicesProxy.getCurrentUser();
if (sSystemServicesProxy.isSystemUser(currentUser)) {
mImpl.hideRecents(triggeredFromAltTab, triggeredFromHomeKey);
@@ -313,6 +332,11 @@
return;
}
+ if (mOverviewProxyService.getProxy() != null) {
+ // TODO: Proxy to Launcher
+ return;
+ }
+
int growTarget = getComponent(Divider.class).getView().growsRecents();
int currentUser = sSystemServicesProxy.getCurrentUser();
if (sSystemServicesProxy.isSystemUser(currentUser)) {
@@ -345,6 +369,11 @@
return;
}
+ if (mOverviewProxyService.getProxy() != null) {
+ // TODO: Proxy to Launcher
+ return;
+ }
+
int currentUser = sSystemServicesProxy.getCurrentUser();
if (sSystemServicesProxy.isSystemUser(currentUser)) {
mImpl.preloadRecents();
@@ -373,6 +402,11 @@
return;
}
+ if (mOverviewProxyService.getProxy() != null) {
+ // TODO: Proxy to Launcher
+ return;
+ }
+
int currentUser = sSystemServicesProxy.getCurrentUser();
if (sSystemServicesProxy.isSystemUser(currentUser)) {
mImpl.cancelPreloadingRecents();
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index b0a2fad..95b311f 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -96,6 +96,7 @@
import com.android.systemui.recents.views.SystemBarScrimViews;
import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.shared.system.WindowManagerWrapper;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -836,12 +837,7 @@
mRecentsView.getViewTreeObserver().removeOnPreDrawListener(this);
// We post to make sure that this information is delivered after this traversals is
// finished.
- mRecentsView.post(new Runnable() {
- @Override
- public void run() {
- Recents.getSystemServices().endProlongedAnimations();
- }
- });
+ mRecentsView.post(() -> WindowManagerWrapper.getInstance().endProlongedAnimations());
return true;
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsOnboarding.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsOnboarding.java
index 26fac6c..127361a 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsOnboarding.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsOnboarding.java
@@ -49,6 +49,10 @@
import com.android.systemui.recents.misc.SysUiTaskStackChangeListener;
import com.android.systemui.shared.system.ActivityManagerWrapper;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
/**
* Shows onboarding for the new recents interaction in P (codenamed quickstep).
*/
@@ -65,6 +69,7 @@
private final Context mContext;
private final WindowManager mWindowManager;
private final OverviewProxyService mOverviewProxyService;
+ private Set<String> mBlacklistedPackages;
private final View mLayout;
private final TextView mTextView;
private final ImageView mDismissView;
@@ -85,6 +90,10 @@
public void onTaskStackChanged() {
ActivityManager.RunningTaskInfo info = ActivityManagerWrapper.getInstance()
.getRunningTask(ACTIVITY_TYPE_UNDEFINED /* ignoreActivityType */);
+ if (mBlacklistedPackages.contains(info.baseActivity.getPackageName())) {
+ hide(true);
+ return;
+ }
int activityType = info.configuration.windowConfiguration.getActivityType();
int numAppsLaunched = Prefs.getInt(mContext, Prefs.Key.NUM_APPS_LAUNCHED, 0);
if (activityType == ACTIVITY_TYPE_STANDARD) {
@@ -122,6 +131,9 @@
mOverviewProxyService = overviewProxyService;
final Resources res = context.getResources();
mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
+ mBlacklistedPackages = new HashSet<>();
+ Collections.addAll(mBlacklistedPackages, res.getStringArray(
+ R.array.recents_onboarding_blacklisted_packages));
mLayout = LayoutInflater.from(mContext).inflate(R.layout.recents_onboarding, null);
mTextView = mLayout.findViewById(R.id.onboarding_text);
mDismissView = mLayout.findViewById(R.id.dismiss);
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 93fd34a..544d95c 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
@@ -444,17 +444,6 @@
}
}
- public void endProlongedAnimations() {
- if (mWm == null) {
- return;
- }
- try {
- mIwm.endProlongedAnimations();
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
-
public void registerDockedStackListener(IDockedStackListener listener) {
if (mWm == null) return;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 22e8909..bc14203 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -411,12 +411,14 @@
break;
}
+ String percentage = NumberFormat.getPercentInstance()
+ .format(mBatteryLevel / 100f);
if (hasChargingTime) {
String chargingTimeFormatted = Formatter.formatShortElapsedTimeRoundingUpToMinutes(
mContext, chargingTimeRemaining);
- return mContext.getResources().getString(chargingId, chargingTimeFormatted);
+ return mContext.getResources().getString(chargingId, chargingTimeFormatted, percentage);
} else {
- return mContext.getResources().getString(chargingId);
+ return mContext.getResources().getString(chargingId, percentage);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index cad956c..4f09133 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -698,7 +698,7 @@
if (!hasOverflow) {
// we have to ensure that adding the low priority notification won't lead to an
// overflow
- collapsedPadding -= (1.0f + OVERFLOW_EARLY_AMOUNT) * mCollapsedIcons.getIconSize();
+ collapsedPadding -= mCollapsedIcons.getNoOverflowExtraPadding();
} else {
// Partial overflow padding will fill enough space to add extra dots
collapsedPadding -= mCollapsedIcons.getPartialOverflowExtraPadding();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
index b220686..446a1d4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
@@ -14,7 +14,6 @@
package com.android.systemui.statusbar.phone;
-import android.app.AlarmManager.AlarmClockInfo;
import android.content.Context;
import android.os.Handler;
import android.provider.Settings.Secure;
@@ -28,8 +27,6 @@
import com.android.systemui.statusbar.policy.DataSaverController.Listener;
import com.android.systemui.statusbar.policy.HotspotController;
import com.android.systemui.statusbar.policy.HotspotController.Callback;
-import com.android.systemui.statusbar.policy.NextAlarmController;
-import com.android.systemui.statusbar.policy.NextAlarmController.NextAlarmChangeCallback;
/**
* Manages which tiles should be automatically added to QS.
@@ -40,7 +37,6 @@
public static final String INVERSION = "inversion";
public static final String WORK = "work";
public static final String NIGHT = "night";
- public static final String ALARM = "alarm";
private final Context mContext;
private final QSTileHost mHost;
@@ -87,9 +83,6 @@
&& ColorDisplayController.isAvailable(mContext)) {
Dependency.get(ColorDisplayController.class).setListener(mColorDisplayCallback);
}
- if (!mAutoTracker.isAdded(ALARM)) {
- Dependency.get(NextAlarmController.class).addCallback(mNextAlarmChangeCallback);
- }
}
public void destroy() {
@@ -101,7 +94,6 @@
Dependency.get(DataSaverController.class).removeCallback(mDataSaverListener);
Dependency.get(ManagedProfileController.class).removeCallback(mProfileCallback);
Dependency.get(ColorDisplayController.class).setListener(null);
- Dependency.get(NextAlarmController.class).removeCallback(mNextAlarmChangeCallback);
}
private final ManagedProfileController.Callback mProfileCallback =
@@ -150,19 +142,6 @@
}
};
- private final NextAlarmChangeCallback mNextAlarmChangeCallback = new NextAlarmChangeCallback() {
- @Override
- public void onNextAlarmChanged(AlarmClockInfo nextAlarm) {
- if (mAutoTracker.isAdded(ALARM)) return;
- if (nextAlarm != null) {
- mHost.addTile(ALARM);
- mAutoTracker.setTileAdded(ALARM);
- mHandler.post(() -> Dependency.get(NextAlarmController.class)
- .removeCallback(mNextAlarmChangeCallback));
- }
- }
- };
-
@VisibleForTesting
final ColorDisplayController.Callback mColorDisplayCallback =
new ColorDisplayController.Callback() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
index f7f791e..f42473d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
@@ -51,6 +51,7 @@
public static final String TAG = "CollapsedStatusBarFragment";
private static final String EXTRA_PANEL_STATE = "panel_state";
+ public static final String STATUS_BAR_ICON_MANAGER_TAG = "status_bar_icon_manager";
public static final int FADE_IN_DURATION = 320;
public static final int FADE_IN_DELAY = 50;
private PhoneStatusBarView mStatusBar;
@@ -94,6 +95,7 @@
mStatusBar.go(savedInstanceState.getInt(EXTRA_PANEL_STATE));
}
mDarkIconManager = new DarkIconManager(view.findViewById(R.id.statusIcons));
+ mDarkIconManager.setShouldLog(true);
Dependency.get(StatusBarIconController.class).addIconGroup(mDarkIconManager);
mSystemIconArea = mStatusBar.findViewById(R.id.system_icon_area);
mClockView = mStatusBar.findViewById(R.id.clock);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java
index c499619..edfd02b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.phone;
+import android.graphics.Rect;
import android.graphics.drawable.Icon;
import android.os.Bundle;
import android.os.UserHandle;
@@ -25,21 +26,27 @@
import android.widget.LinearLayout;
import com.android.internal.statusbar.StatusBarIcon;
+import com.android.settingslib.Utils;
import com.android.systemui.DemoMode;
import com.android.systemui.R;
import com.android.systemui.statusbar.StatusBarIconView;
+import com.android.systemui.statusbar.policy.DarkIconDispatcher;
+import com.android.systemui.statusbar.policy.DarkIconDispatcher.DarkReceiver;
import com.android.systemui.statusbar.policy.LocationControllerImpl;
+import com.android.systemui.util.leak.LeakDetector;
-public class DemoStatusIcons extends LinearLayout implements DemoMode {
+public class DemoStatusIcons extends StatusIconContainer implements DemoMode, DarkReceiver {
private final LinearLayout mStatusIcons;
private final int mIconSize;
private boolean mDemoMode;
+ private int mColor;
public DemoStatusIcons(LinearLayout statusIcons, int iconSize) {
super(statusIcons.getContext());
mStatusIcons = statusIcons;
mIconSize = iconSize;
+ mColor = DarkIconDispatcher.DEFAULT_ICON_TINT;
setLayoutParams(mStatusIcons.getLayoutParams());
setOrientation(mStatusIcons.getOrientation());
@@ -48,6 +55,22 @@
p.addView(this, p.indexOfChild(mStatusIcons));
}
+ public void remove() {
+ ((ViewGroup) getParent()).removeView(this);
+ }
+
+ public void setColor(int color) {
+ mColor = color;
+ updateColors();
+ }
+
+ private void updateColors() {
+ for (int i = 0; i < getChildCount(); i++) {
+ StatusBarIconView child = (StatusBarIconView) getChildAt(i);
+ child.setStaticDrawableColor(mColor);
+ }
+ }
+
@Override
public void dispatchDemoCommand(String command, Bundle args) {
if (!mDemoMode && command.equals(COMMAND_ENTER)) {
@@ -136,6 +159,7 @@
break;
} else {
StatusBarIcon icon = v.getStatusBarIcon();
+ icon.visible = true;
icon.icon = Icon.createWithResource(icon.icon.getResPackage(), iconId);
v.set(icon);
v.updateDrawable();
@@ -150,9 +174,16 @@
return;
}
StatusBarIcon icon = new StatusBarIcon(iconPkg, UserHandle.SYSTEM, iconId, 0, 0, "Demo");
+ icon.visible = true;
StatusBarIconView v = new StatusBarIconView(getContext(), null, null);
v.setTag(slot);
v.set(icon);
+ v.setStaticDrawableColor(mColor);
addView(v, 0, new LinearLayout.LayoutParams(mIconSize, mIconSize));
}
+
+ @Override
+ public void onDarkChanged(Rect area, float darkIntensity, int tint) {
+ setColor(DarkIconDispatcher.getTint(area, mStatusIcons, tint));
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
index 380c08e..edfbd3f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
@@ -21,6 +21,7 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.util.Slog;
+import android.util.StatsLog;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
@@ -152,6 +153,8 @@
mKeyguardView.requestLayout();
}
mShowingSoon = false;
+ StatsLog.write(StatsLog.KEYGUARD_BOUNCER_STATE_CHANGED,
+ StatsLog.KEYGUARD_BOUNCER_STATE_CHANGED__STATE__SHOWN);
}
};
@@ -183,6 +186,8 @@
public void hide(boolean destroyView) {
if (isShowing()) {
+ StatsLog.write(StatsLog.KEYGUARD_BOUNCER_STATE_CHANGED,
+ StatsLog.KEYGUARD_BOUNCER_STATE_CHANGED__STATE__HIDDEN);
mDismissCallbackRegistry.notifyDismissCancelled();
}
mFalsingManager.onBouncerHidden();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
index 5cf4c4c..5479dd8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
@@ -56,6 +56,7 @@
private static final int NO_VALUE = Integer.MIN_VALUE;
private static final String TAG = "NotificationIconContainer";
private static final boolean DEBUG = false;
+ private static final boolean DEBUG_OVERFLOW = false;
private static final int CANNED_ANIMATION_DURATION = 100;
private static final AnimationProperties DOT_ANIMATION_PROPERTIES = new AnimationProperties() {
private AnimationFilter mAnimationFilter = new AnimationFilter().animateX();
@@ -107,6 +108,7 @@
private final HashMap<View, IconState> mIconStates = new HashMap<>();
private int mDotPadding;
private int mStaticDotRadius;
+ private int mStaticDotDiameter;
private int mActualLayoutWidth = NO_VALUE;
private float mActualPaddingEnd = NO_VALUE;
private float mActualPaddingStart = NO_VALUE;
@@ -122,17 +124,21 @@
private ArrayMap<String, ArrayList<StatusBarIcon>> mReplacingIcons;
// Keep track of the last visible icon so collapsed container can report on its location
private IconState mLastVisibleIconState;
+ private float mVisualOverflowStart;
+ // Keep track of overflow in range [0, 3]
+ private int mNumDots;
public NotificationIconContainer(Context context, AttributeSet attrs) {
super(context, attrs);
initDimens();
- setWillNotDraw(!DEBUG);
+ setWillNotDraw(!(DEBUG || DEBUG_OVERFLOW));
}
private void initDimens() {
mDotPadding = getResources().getDimensionPixelSize(R.dimen.overflow_icon_dot_padding);
mStaticDotRadius = getResources().getDimensionPixelSize(R.dimen.overflow_dot_radius);
+ mStaticDotDiameter = 2 * mStaticDotRadius;
}
@Override
@@ -142,6 +148,30 @@
paint.setColor(Color.RED);
paint.setStyle(Paint.Style.STROKE);
canvas.drawRect(getActualPaddingStart(), 0, getLayoutEnd(), getHeight(), paint);
+
+ if (DEBUG_OVERFLOW) {
+ if (mLastVisibleIconState == null) {
+ return;
+ }
+
+ int height = getHeight();
+ int end = getFinalTranslationX();
+
+ // Visualize the "end" of the layout
+ paint.setColor(Color.BLUE);
+ canvas.drawLine(end, 0, end, height, paint);
+
+ paint.setColor(Color.BLACK);
+ int lastIcon = (int) mLastVisibleIconState.xTranslation;
+ canvas.drawLine(lastIcon, 0, lastIcon, height, paint);
+
+ paint.setColor(Color.RED);
+ canvas.drawLine(mVisualOverflowStart, 0, mVisualOverflowStart, height, paint);
+
+ paint.setColor(Color.YELLOW);
+ float overflow = getMaxOverflowStart();
+ canvas.drawLine(overflow, 0, overflow, height, paint);
+ }
}
@Override
@@ -282,7 +312,7 @@
}
/**
- * Calulate the horizontal translations for each notification based on how much the icons
+ * Calculate the horizontal translations for each notification based on how much the icons
* are inserted into the notification container.
* If this is not a whole number, the fraction means by how much the icon is appearing.
*/
@@ -293,9 +323,9 @@
int maxVisibleIcons = mDark ? MAX_VISIBLE_ICONS_WHEN_DARK :
mIsStaticLayout ? MAX_STATIC_ICONS : childCount;
float layoutEnd = getLayoutEnd();
- float overflowStart = layoutEnd - mIconSize * (2 + OVERFLOW_EARLY_AMOUNT);
+ float overflowStart = getMaxOverflowStart();
+ mVisualOverflowStart = 0;
boolean hasAmbient = mSpeedBumpIndex != -1 && mSpeedBumpIndex < getChildCount();
- float visualOverflowStart = 0;
for (int i = 0; i < childCount; i++) {
View view = getChildAt(i);
IconState iconState = mIconStates.get(view);
@@ -310,45 +340,40 @@
noOverflowAfter = noOverflowAfter && !hasAmbient && !forceOverflow;
}
iconState.visibleState = StatusBarIconView.STATE_ICON;
- if (firstOverflowIndex == -1 && (forceOverflow
- || (translationX >= (noOverflowAfter ? layoutEnd - mIconSize : overflowStart)))) {
+
+ boolean isOverflowing =
+ (translationX >= (noOverflowAfter ? layoutEnd - mIconSize : overflowStart));
+ if (firstOverflowIndex == -1 && (forceOverflow || isOverflowing)) {
firstOverflowIndex = noOverflowAfter && !forceOverflow ? i - 1 : i;
- int totalDotLength = mStaticDotRadius * 6 + 2 * mDotPadding;
- visualOverflowStart = overflowStart + mIconSize * (1 + OVERFLOW_EARLY_AMOUNT)
- - totalDotLength / 2
- - mIconSize * 0.5f + mStaticDotRadius;
+ mVisualOverflowStart = layoutEnd - mIconSize
+ - 2 * (mStaticDotDiameter + mDotPadding);
if (forceOverflow) {
- visualOverflowStart = Math.min(translationX, visualOverflowStart
- + mStaticDotRadius * 2 + mDotPadding);
- } else {
- visualOverflowStart += (translationX - overflowStart) / mIconSize
- * (mStaticDotRadius * 2 + mDotPadding);
+ mVisualOverflowStart = Math.min(translationX, mVisualOverflowStart);
}
}
translationX += iconState.iconAppearAmount * view.getWidth() * drawingScale;
}
+ mNumDots = 0;
if (firstOverflowIndex != -1) {
- int numDots = 1;
- translationX = visualOverflowStart;
+ translationX = mVisualOverflowStart;
for (int i = firstOverflowIndex; i < childCount; i++) {
View view = getChildAt(i);
IconState iconState = mIconStates.get(view);
int dotWidth = mStaticDotRadius * 2 + mDotPadding;
iconState.xTranslation = translationX;
- if (numDots <= MAX_DOTS) {
- if (numDots == 1 && iconState.iconAppearAmount < 0.8f) {
+ if (mNumDots < MAX_DOTS) {
+ if (mNumDots == 0 && iconState.iconAppearAmount < 0.8f) {
iconState.visibleState = StatusBarIconView.STATE_ICON;
- numDots--;
} else {
iconState.visibleState = StatusBarIconView.STATE_DOT;
+ mNumDots++;
}
- translationX += (numDots == MAX_DOTS ? MAX_DOTS * dotWidth : dotWidth)
+ translationX += (mNumDots == MAX_DOTS ? MAX_DOTS * dotWidth : dotWidth)
* iconState.iconAppearAmount;
mLastVisibleIconState = iconState;
} else {
iconState.visibleState = StatusBarIconView.STATE_HIDDEN;
}
- numDots++;
}
} else if (childCount > 0) {
View lastChild = getChildAt(childCount - 1);
@@ -360,7 +385,7 @@
if (firstOverflowIndex != -1) {
// If we have an overflow, only count those half for centering because the dots
// don't have a lot of visual weight.
- float deltaIgnoringOverflow = (getLayoutEnd() - visualOverflowStart) / 2;
+ float deltaIgnoringOverflow = (getLayoutEnd() - mVisualOverflowStart) / 2;
delta = (deltaIgnoringOverflow + delta) / 2;
}
for (int i = 0; i < childCount; i++) {
@@ -440,7 +465,13 @@
return 0;
}
- return (int) (mLastVisibleIconState.xTranslation + mIconSize * (1 + OVERFLOW_EARLY_AMOUNT));
+ int translation = (int) (mLastVisibleIconState.xTranslation + mIconSize);
+ // There's a chance that last translation goes beyond the edge maybe
+ return Math.min(getWidth(), translation);
+ }
+
+ private float getMaxOverflowStart() {
+ return getLayoutEnd() - mIconSize * (2 + OVERFLOW_EARLY_AMOUNT);
}
public void setChangingViewPositions(boolean changingViewPositions) {
@@ -471,12 +502,7 @@
}
public boolean hasOverflow() {
- if (mIsStaticLayout) {
- return getChildCount() > MAX_STATIC_ICONS;
- }
-
- float width = (getChildCount() + OVERFLOW_EARLY_AMOUNT) * mIconSize;
- return width - (getWidth() - getActualPaddingStart() - getActualPaddingEnd()) > 0;
+ return mNumDots > 0;
}
/**
@@ -486,12 +512,7 @@
* This method has no meaning for non-static containers
*/
public boolean hasPartialOverflow() {
- if (mIsStaticLayout) {
- int count = getChildCount();
- return count > MAX_STATIC_ICONS && count <= MAX_STATIC_ICONS + MAX_DOTS;
- }
-
- return false;
+ return mNumDots > 0 && mNumDots < MAX_DOTS;
}
/**
@@ -504,7 +525,30 @@
return 0;
}
- return (MAX_STATIC_ICONS + MAX_DOTS - getChildCount()) * (mStaticDotRadius + mDotPadding);
+ int partialOverflowAmount = (MAX_DOTS - mNumDots) * (mStaticDotRadius * 2 + mDotPadding);
+
+ int adjustedWidth = getFinalTranslationX() + partialOverflowAmount;
+ // In case we actually give too much padding...
+ if (adjustedWidth > getWidth()) {
+ partialOverflowAmount = getWidth() - getFinalTranslationX();
+ }
+
+ return partialOverflowAmount;
+ }
+
+ // Give some extra room for btw notifications if we can
+ public int getNoOverflowExtraPadding() {
+ if (mNumDots != 0) {
+ return 0;
+ }
+
+ int collapsedPadding = (int) ((1.0f + OVERFLOW_EARLY_AMOUNT) * getIconSize());
+
+ if (collapsedPadding + getFinalTranslationX() > getWidth()) {
+ collapsedPadding = getWidth() - getFinalTranslationX();
+ }
+
+ return collapsedPadding;
}
public int getIconSize() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 933c952..a31727e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -210,7 +210,6 @@
import com.android.systemui.statusbar.notification.AboveShelfObserver;
import com.android.systemui.statusbar.notification.ActivityLaunchAnimator;
import com.android.systemui.statusbar.notification.VisualStabilityManager;
-import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
import com.android.systemui.statusbar.phone.UnlockMethodCache.OnUnlockMethodChangedListener;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback;
@@ -240,7 +239,6 @@
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
-import java.util.Collection;
import java.util.List;
import java.util.Map;
@@ -4727,7 +4725,7 @@
@Override
public boolean isPowerSaveActive() {
- return mBatteryController.isPowerSave();
+ return mBatteryController.isAodPowerSave();
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
index 07610ce..956bebb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
@@ -18,6 +18,7 @@
import static android.app.StatusBarManager.DISABLE_NONE;
import android.content.Context;
+import android.os.Bundle;
import android.support.annotation.VisibleForTesting;
import android.text.TextUtils;
import android.util.ArraySet;
@@ -29,6 +30,7 @@
import android.widget.LinearLayout.LayoutParams;
import com.android.internal.statusbar.StatusBarIcon;
+import com.android.systemui.DemoMode;
import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.statusbar.StatusBarIconView;
@@ -109,6 +111,20 @@
super.onSetIcon(viewIndex, icon);
mDarkIconDispatcher.applyDark((ImageView) mGroup.getChildAt(viewIndex));
}
+
+ @Override
+ protected DemoStatusIcons createDemoStatusIcons() {
+ DemoStatusIcons icons = super.createDemoStatusIcons();
+ mDarkIconDispatcher.addDarkReceiver(icons);
+
+ return icons;
+ }
+
+ @Override
+ protected void exitDemoMode() {
+ mDarkIconDispatcher.removeDarkReceiver(mDemoStatusIcons);
+ super.exitDemoMode();
+ }
}
public static class TintedIconManager extends IconManager {
@@ -134,15 +150,28 @@
}
}
}
+
+ @Override
+ protected DemoStatusIcons createDemoStatusIcons() {
+ DemoStatusIcons icons = super.createDemoStatusIcons();
+ icons.setColor(mColor);
+ return icons;
+ }
}
/**
* Turns info from StatusBarIconController into ImageViews in a ViewGroup.
*/
- public static class IconManager {
+ public static class IconManager implements DemoMode {
protected final ViewGroup mGroup;
protected final Context mContext;
protected final int mIconSize;
+ // Whether or not these icons show up in dumpsys
+ protected boolean mShouldLog = false;
+
+ // Enables SystemUI demo mode to take effect in this group
+ protected boolean mDemoable = true;
+ protected DemoStatusIcons mDemoStatusIcons;
public IconManager(ViewGroup group) {
mGroup = group;
@@ -159,6 +188,22 @@
}
}
+ public boolean isDemoable() {
+ return mDemoable;
+ }
+
+ public void setIsDemoable(boolean demoable) {
+ mDemoable = demoable;
+ }
+
+ public void setShouldLog(boolean should) {
+ mShouldLog = should;
+ }
+
+ public boolean shouldLog() {
+ return mShouldLog;
+ }
+
protected void onIconAdded(int index, String slot, boolean blocked,
StatusBarIcon icon) {
addIcon(index, slot, blocked, icon);
@@ -218,5 +263,31 @@
StatusBarIconView view = (StatusBarIconView) mGroup.getChildAt(viewIndex);
view.set(icon);
}
+
+ @Override
+ public void dispatchDemoCommand(String command, Bundle args) {
+ if (!mDemoable) {
+ return;
+ }
+
+ if (mDemoStatusIcons != null && command.equals(COMMAND_EXIT)) {
+ mDemoStatusIcons.dispatchDemoCommand(command, args);
+ exitDemoMode();
+ } else {
+ if (mDemoStatusIcons == null) {
+ mDemoStatusIcons = createDemoStatusIcons();
+ }
+ mDemoStatusIcons.dispatchDemoCommand(command, args);
+ }
+ }
+
+ protected void exitDemoMode() {
+ mDemoStatusIcons.remove();
+ mDemoStatusIcons = null;
+ }
+
+ protected DemoStatusIcons createDemoStatusIcons() {
+ return new DemoStatusIcons((LinearLayout) mGroup, mIconSize);
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
index 1c3ee75..8f5e705 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
@@ -41,6 +41,8 @@
import java.io.PrintWriter;
import java.util.ArrayList;
+import static com.android.systemui.statusbar.phone.CollapsedStatusBarFragment.STATUS_BAR_ICON_MANAGER_TAG;
+
/**
* Receives the callbacks from CommandQueue related to icons and tracks the state of
* all the icons. Dispatches this state to any IconManagers that are currently
@@ -48,6 +50,7 @@
*/
public class StatusBarIconControllerImpl extends StatusBarIconList implements Tunable,
ConfigurationListener, Dumpable, CommandQueue.Callbacks, StatusBarIconController {
+ private static final String TAG = "StatusBarIconController";
private final ArrayList<IconManager> mIconGroups = new ArrayList<>();
private final ArraySet<String> mIconBlacklist = new ArraySet<>();
@@ -55,6 +58,7 @@
private Context mContext;
private DemoStatusIcons mDemoStatusIcons;
+ private IconManager mStatusBarIconManager;
public StatusBarIconControllerImpl(Context context) {
super(context.getResources().getStringArray(
@@ -197,26 +201,28 @@
@Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- // TODO: Dump info about all icon groups?
- ViewGroup statusIcons = mIconGroups.get(0).mGroup;
- int N = statusIcons.getChildCount();
- pw.println(" icon views: " + N);
- for (int i = 0; i < N; i++) {
- StatusBarIconView ic = (StatusBarIconView) statusIcons.getChildAt(i);
- pw.println(" [" + i + "] icon=" + ic);
+ pw.println(TAG + " state:");
+ for (IconManager manager : mIconGroups) {
+ if (manager.shouldLog()) {
+ ViewGroup group = manager.mGroup;
+ int N = group.getChildCount();
+ pw.println(" icon views: " + N);
+ for (int i = 0; i < N; i++) {
+ StatusBarIconView ic = (StatusBarIconView) group.getChildAt(i);
+ pw.println(" [" + i + "] icon=" + ic);
+ }
+ }
}
+
super.dump(pw);
}
public void dispatchDemoCommand(String command, Bundle args) {
- if (mDemoStatusIcons == null) {
- // TODO: Rework how we handle demo mode.
- int iconSize = mContext.getResources().getDimensionPixelSize(
- com.android.internal.R.dimen.status_bar_icon_size);
- mDemoStatusIcons = new DemoStatusIcons((LinearLayout) mIconGroups.get(0).mGroup,
- iconSize);
+ for (IconManager manager : mIconGroups) {
+ if (manager.isDemoable()) {
+ manager.dispatchDemoCommand(command, args);
+ }
}
- mDemoStatusIcons.dispatchDemoCommand(command, args);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconList.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconList.java
index f600908..1aa3a43 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconList.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconList.java
@@ -77,6 +77,7 @@
}
public void dump(PrintWriter pw) {
+ pw.println("StatusBarIconList state:");
final int N = mSlots.size();
pw.println(" icon slots: " + N);
for (int i=0; i<N; i++) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 47ea3a7..49cffc0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -24,6 +24,7 @@
import android.content.Context;
import android.os.Bundle;
import android.os.SystemClock;
+import android.util.StatsLog;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup;
@@ -140,6 +141,8 @@
mShowing = true;
mStatusBarWindowManager.setKeyguardShowing(true);
reset(true /* hideBouncerWhenShowing */);
+ StatsLog.write(StatsLog.KEYGUARD_STATE_CHANGED,
+ StatsLog.KEYGUARD_STATE_CHANGED__STATE__SHOWN);
}
/**
@@ -289,6 +292,8 @@
public void setOccluded(boolean occluded, boolean animate) {
mStatusBar.setOccluded(occluded);
if (occluded && !mOccluded && mShowing) {
+ StatsLog.write(StatsLog.KEYGUARD_STATE_CHANGED,
+ StatsLog.KEYGUARD_STATE_CHANGED__STATE__OCCLUDED);
if (mStatusBar.isInLaunchTransition()) {
mOccluded = true;
mStatusBar.fadeKeyguardAfterLaunchTransition(null /* beforeFading */,
@@ -301,6 +306,9 @@
});
return;
}
+ } else if (!occluded && mOccluded && mShowing) {
+ StatsLog.write(StatsLog.KEYGUARD_STATE_CHANGED,
+ StatsLog.KEYGUARD_STATE_CHANGED__STATE__SHOWN);
}
boolean isOccluding = !mOccluded && occluded;
mOccluded = occluded;
@@ -398,6 +406,8 @@
mStatusBarWindowManager.setKeyguardShowing(false);
mViewMediatorCallback.keyguardGone();
}
+ StatsLog.write(StatsLog.KEYGUARD_STATE_CHANGED,
+ StatsLog.KEYGUARD_STATE_CHANGED__STATE__HIDDEN);
}
public void onDensityOrFontScaleChanged() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusIconContainer.java
index 503a1b4..dba89479 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusIconContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusIconContainer.java
@@ -14,12 +14,6 @@
* limitations under the License.
*/
-/**
- * A container for Status bar system icons. Limits the number of system icons and handles overflow
- * similar to NotificationIconController. Can be used to layout nested StatusIconContainers
- *
- * Children are expected to be of type StatusBarIconView.
- */
package com.android.systemui.statusbar.phone;
import android.annotation.Nullable;
@@ -33,6 +27,12 @@
import com.android.systemui.statusbar.StatusBarIconView;
import com.android.systemui.statusbar.stack.ViewState;
+/**
+ * A container for Status bar system icons. Limits the number of system icons and handles overflow
+ * similar to NotificationIconController. Can be used to layout nested StatusIconContainers
+ *
+ * Children are expected to be of type StatusBarIconView.
+ */
public class StatusIconContainer extends AlphaOptimizedLinearLayout {
private static final String TAG = "StatusIconContainer";
@@ -40,6 +40,10 @@
private static final int MAX_ICONS = 5;
private static final int MAX_DOTS = 3;
+ public StatusIconContainer(Context context) {
+ this(context, null);
+ }
+
public StatusIconContainer(Context context, AttributeSet attrs) {
super(context, attrs);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
index 641fe69..6f4026d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
@@ -41,6 +41,13 @@
boolean isPowerSave();
/**
+ * Returns {@code true} if AOD was disabled by power saving policies.
+ */
+ default boolean isAodPowerSave() {
+ return isPowerSave();
+ }
+
+ /**
* A listener that will be notified whenever a change in battery level or power save mode
* has occurred.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
index e8d5af6..49f880c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
@@ -24,8 +24,10 @@
import android.os.Bundle;
import android.os.Handler;
import android.os.PowerManager;
+import android.os.PowerSaveState;
import android.util.Log;
-import com.android.systemui.DemoMode;
+
+import com.android.internal.annotations.VisibleForTesting;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -52,13 +54,19 @@
protected boolean mCharging;
protected boolean mCharged;
protected boolean mPowerSave;
+ protected boolean mAodPowerSave;
private boolean mTestmode = false;
private boolean mHasReceivedBattery = false;
public BatteryControllerImpl(Context context) {
+ this(context, context.getSystemService(PowerManager.class));
+ }
+
+ @VisibleForTesting
+ BatteryControllerImpl(Context context, PowerManager powerManager) {
mContext = context;
mHandler = new Handler();
- mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
+ mPowerManager = powerManager;
registerReceiver();
updatePowerSave();
@@ -166,6 +174,11 @@
return mPowerSave;
}
+ @Override
+ public boolean isAodPowerSave() {
+ return mAodPowerSave;
+ }
+
private void updatePowerSave() {
setPowerSave(mPowerManager.isPowerSaveMode());
}
@@ -173,6 +186,11 @@
private void setPowerSave(boolean powerSave) {
if (powerSave == mPowerSave) return;
mPowerSave = powerSave;
+
+ // AOD power saving setting might be different from PowerManager power saving mode.
+ PowerSaveState state = mPowerManager.getPowerSaveState(PowerManager.ServiceType.AOD);
+ mAodPowerSave = state.batterySaverEnabled;
+
if (DEBUG) Log.d(TAG, "Power save is " + (mPowerSave ? "on" : "off"));
firePowerSaveChanged();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java
index cd409d8..b6116e0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java
@@ -16,10 +16,17 @@
package com.android.systemui.keyguard;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+
import androidx.app.slice.Slice;
+
+import android.app.AlarmManager;
+import android.content.ContentResolver;
import android.content.Intent;
import android.net.Uri;
-import android.os.Debug;
import android.os.Handler;
import android.support.test.filters.SmallTest;
import android.testing.AndroidTestingRunner;
@@ -32,24 +39,31 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
import java.util.Arrays;
+import java.util.concurrent.TimeUnit;
import androidx.app.slice.SliceItem;
import androidx.app.slice.SliceProvider;
import androidx.app.slice.SliceSpecs;
import androidx.app.slice.core.SliceQuery;
-import androidx.app.slice.widget.SliceLiveData;
@SmallTest
@RunWith(AndroidTestingRunner.class)
@RunWithLooper(setAsMainLooper = true)
public class KeyguardSliceProviderTest extends SysuiTestCase {
+ @Mock
+ private ContentResolver mContentResolver;
+ @Mock
+ private AlarmManager mAlarmManager;
private TestableKeyguardSliceProvider mProvider;
@Before
public void setup() {
+ MockitoAnnotations.initMocks(this);
mProvider = new TestableKeyguardSliceProvider();
mProvider.attachInfo(getContext(), null);
SliceProvider.setSpecs(Arrays.asList(SliceSpecs.LIST));
@@ -70,7 +84,7 @@
@Test
public void returnsValidSlice() {
- Slice slice = mProvider.onBindSlice(Uri.parse(KeyguardSliceProvider.KEYGUARD_SLICE_URI));
+ Slice slice = mProvider.onBindSlice(mProvider.getUri());
SliceItem text = SliceQuery.find(slice, android.app.slice.SliceItem.FORMAT_TEXT,
android.app.slice.Slice.HINT_TITLE,
null /* nonHints */);
@@ -87,21 +101,52 @@
@Test
public void updatesClock() {
- mProvider.mUpdateClockInvokations = 0;
mProvider.mIntentReceiver.onReceive(getContext(), new Intent(Intent.ACTION_TIME_TICK));
TestableLooper.get(this).processAllMessages();
- Assert.assertEquals("Clock should have been updated.", 1 /* expected */,
- mProvider.mUpdateClockInvokations);
+ verify(mContentResolver).notifyChange(eq(mProvider.getUri()), eq(null));
+ }
+
+ @Test
+ public void schedulesAlarm12hBefore() {
+ long in16Hours = System.currentTimeMillis() + TimeUnit.HOURS.toHours(16);
+ AlarmManager.AlarmClockInfo alarmClockInfo = new AlarmManager.AlarmClockInfo(in16Hours, null);
+ mProvider.onNextAlarmChanged(alarmClockInfo);
+
+ long twelveHours = TimeUnit.HOURS.toMillis(KeyguardSliceProvider.ALARM_VISIBILITY_HOURS);
+ long triggerAt = in16Hours - twelveHours;
+ verify(mAlarmManager).setExact(eq(AlarmManager.RTC), eq(triggerAt), anyString(), any(),
+ any());
+ }
+
+ @Test
+ public void updatingNextAlarmInvalidatesSlice() {
+ long in16Hours = System.currentTimeMillis() + TimeUnit.HOURS.toHours(8);
+ AlarmManager.AlarmClockInfo alarmClockInfo = new AlarmManager.AlarmClockInfo(in16Hours, null);
+ mProvider.onNextAlarmChanged(alarmClockInfo);
+
+ verify(mContentResolver).notifyChange(eq(mProvider.getUri()), eq(null));
}
private class TestableKeyguardSliceProvider extends KeyguardSliceProvider {
int mCleanDateFormatInvokations;
- int mUpdateClockInvokations;
+ private int mCounter;
TestableKeyguardSliceProvider() {
super(new Handler(TestableLooper.get(KeyguardSliceProviderTest.this).getLooper()));
}
+ Uri getUri() {
+ return mSliceUri;
+ }
+
+ @Override
+ public boolean onCreateSliceProvider() {
+ super.onCreateSliceProvider();
+ mAlarmManager = KeyguardSliceProviderTest.this.mAlarmManager;
+ mContentResolver = KeyguardSliceProviderTest.this.mContentResolver;
+ return true;
+ }
+
@Override
void cleanDateFormat() {
super.cleanDateFormat();
@@ -109,9 +154,8 @@
}
@Override
- protected void updateClock() {
- super.updateClock();
- mUpdateClockInvokations++;
+ protected String getFormattedDate() {
+ return super.getFormattedDate() + mCounter++;
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
index 2d2db1b..a80b045 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
@@ -18,28 +18,21 @@
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-import android.app.AlarmManager.AlarmClockInfo;
-import android.os.Handler;
import android.support.test.filters.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
import com.android.internal.app.ColorDisplayController;
import com.android.systemui.Dependency;
+import com.android.systemui.Prefs;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.qs.AutoAddTracker;
import com.android.systemui.qs.QSTileHost;
-import com.android.systemui.statusbar.policy.NextAlarmController;
-import com.android.systemui.statusbar.policy.NextAlarmController.NextAlarmChangeCallback;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
+import org.mockito.Mockito;
@RunWith(AndroidTestingRunner.class)
@RunWithLooper
@@ -47,19 +40,16 @@
public class AutoTileManagerTest extends SysuiTestCase {
@Mock private QSTileHost mQsTileHost;
- @Mock private AutoAddTracker mAutoAddTracker;
- @Captor private ArgumentCaptor<NextAlarmChangeCallback> mAlarmCallback;
private AutoTileManager mAutoTileManager;
@Before
public void setUp() throws Exception {
- MockitoAnnotations.initMocks(this);
- mDependency.injectMockDependency(NextAlarmController.class);
- mAutoTileManager = new AutoTileManager(mContext, mAutoAddTracker,
- mQsTileHost, new Handler(TestableLooper.get(this).getLooper()));
- verify(Dependency.get(NextAlarmController.class))
- .addCallback(mAlarmCallback.capture());
+ mDependency.injectTestDependency(Dependency.BG_LOOPER,
+ TestableLooper.get(this).getLooper());
+ Prefs.putBoolean(mContext, Prefs.Key.QS_NIGHTDISPLAY_ADDED, false);
+ mQsTileHost = Mockito.mock(QSTileHost.class);
+ mAutoTileManager = new AutoTileManager(mContext, mQsTileHost);
}
@Test
@@ -109,30 +99,4 @@
ColorDisplayController.AUTO_MODE_DISABLED);
verify(mQsTileHost, never()).addTile("night");
}
-
- @Test
- public void alarmTileAdded_whenAlarmSet() {
- mAlarmCallback.getValue().onNextAlarmChanged(new AlarmClockInfo(0, null));
-
- verify(mQsTileHost).addTile("alarm");
- verify(mAutoAddTracker).setTileAdded("alarm");
- }
-
- @Test
- public void alarmTileNotAdded_whenAlarmNotSet() {
- mAlarmCallback.getValue().onNextAlarmChanged(null);
-
- verify(mQsTileHost, never()).addTile("alarm");
- verify(mAutoAddTracker, never()).setTileAdded("alarm");
- }
-
- @Test
- public void alarmTileNotAdded_whenAlreadyAdded() {
- when(mAutoAddTracker.isAdded("alarm")).thenReturn(true);
-
- mAlarmCallback.getValue().onNextAlarmChanged(new AlarmClockInfo(0, null));
-
- verify(mQsTileHost, never()).addTile("alarm");
- verify(mAutoAddTracker, never()).setTileAdded("alarm");
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
new file mode 100644
index 0000000..d54c295
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar.policy;
+
+import static org.mockito.Mockito.when;
+
+import android.content.Intent;
+import android.os.PowerManager;
+import android.os.PowerSaveState;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class BatteryControllerTest extends SysuiTestCase {
+
+ @Mock
+ private PowerManager mPowerManager;
+ private BatteryControllerImpl mBatteryController;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mBatteryController = new BatteryControllerImpl(getContext(), mPowerManager);
+ }
+
+ @Test
+ public void testIndependentAODBatterySaver_true() {
+ PowerSaveState state = new PowerSaveState.Builder()
+ .setBatterySaverEnabled(true)
+ .build();
+ Intent intent = new Intent(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED);
+ when(mPowerManager.getPowerSaveState(PowerManager.ServiceType.AOD)).thenReturn(state);
+ when(mPowerManager.isPowerSaveMode()).thenReturn(true);
+
+ mBatteryController.onReceive(getContext(), intent);
+
+ Assert.assertTrue(mBatteryController.isPowerSave());
+ Assert.assertTrue(mBatteryController.isAodPowerSave());
+ }
+
+ @Test
+ public void testIndependentAODBatterySaver_false() {
+ PowerSaveState state = new PowerSaveState.Builder()
+ .setBatterySaverEnabled(false)
+ .build();
+ Intent intent = new Intent(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED);
+ when(mPowerManager.getPowerSaveState(PowerManager.ServiceType.AOD)).thenReturn(state);
+ when(mPowerManager.isPowerSaveMode()).thenReturn(true);
+
+ mBatteryController.onReceive(getContext(), intent);
+
+ Assert.assertTrue(mBatteryController.isPowerSave());
+ Assert.assertFalse(mBatteryController.isAodPowerSave());
+ }
+
+}
diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto
index c56002e..b897c7c 100644
--- a/proto/src/metrics_constants.proto
+++ b/proto/src/metrics_constants.proto
@@ -5328,6 +5328,21 @@
// OS: P
PACKAGE_OPTIMIZATION_COMPILATION_REASON = 1321;
+ // FIELD: The camera API level used.
+ // CATEGORY: CAMERA
+ // OS: P
+ FIELD_CAMERA_API_LEVEL = 1322;
+
+ // OPEN: Settings > Battery > Battery tip > Battery tip Dialog
+ // CATEGORY: SETTINGS
+ // OS: P
+ FUELGAUGE_BATTERY_TIP_DIALOG = 1323;
+
+ // OPEN: Settings > Battery > Battery tip
+ // CATEGORY: SETTINGS
+ // OS: P
+ ACTION_BATTERY_TIP_SHOWN = 1324;
+
// ---- End P Constants, all P constants go above this line ----
// Add new aosp constants above this line.
// END OF AOSP CONSTANTS
diff --git a/services/autofill/java/com/android/server/autofill/RemoteFillService.java b/services/autofill/java/com/android/server/autofill/RemoteFillService.java
index d4ecc28..f7a4b73 100644
--- a/services/autofill/java/com/android/server/autofill/RemoteFillService.java
+++ b/services/autofill/java/com/android/server/autofill/RemoteFillService.java
@@ -356,7 +356,8 @@
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
if (mDestroyed || !mBinding) {
- mContext.unbindService(mServiceConnection);
+ // This is abnormal. Unbinding the connection has been requested already.
+ Slog.wtf(LOG_TAG, "onServiceConnected was dispatched after unbindService.");
return;
}
mBinding = false;
diff --git a/services/backup/java/com/android/server/backup/PackageManagerBackupAgent.java b/services/backup/java/com/android/server/backup/PackageManagerBackupAgent.java
index 3cf374f..4443130 100644
--- a/services/backup/java/com/android/server/backup/PackageManagerBackupAgent.java
+++ b/services/backup/java/com/android/server/backup/PackageManagerBackupAgent.java
@@ -23,6 +23,7 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
import android.content.pm.ResolveInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.Signature;
@@ -30,6 +31,7 @@
import android.os.ParcelFileDescriptor;
import android.util.Slog;
+import com.android.server.LocalServices;
import com.android.server.backup.utils.AppBackupUtils;
import java.io.BufferedInputStream;
@@ -235,7 +237,7 @@
if (home != null) {
try {
homeInfo = mPackageManager.getPackageInfo(home.getPackageName(),
- PackageManager.GET_SIGNATURES);
+ PackageManager.GET_SIGNING_CERTIFICATES);
homeInstaller = mPackageManager.getInstallerPackageName(home.getPackageName());
homeVersion = homeInfo.getLongVersionCode();
homeSigHashes = BackupUtils.hashSignatureArray(homeInfo.signatures);
@@ -252,10 +254,11 @@
// 2. the home app [or absence] we now use differs from the prior state,
// OR 3. it looks like we use the same home app + version as before, but
// the signatures don't match so we treat them as different apps.
+ PackageManagerInternal pmi = LocalServices.getService(PackageManagerInternal.class);
final boolean needHomeBackup = (homeVersion != mStoredHomeVersion)
|| !Objects.equals(home, mStoredHomeComponent)
|| (home != null
- && !BackupUtils.signaturesMatch(mStoredHomeSigHashes, homeInfo));
+ && !BackupUtils.signaturesMatch(mStoredHomeSigHashes, homeInfo, pmi));
if (needHomeBackup) {
if (DEBUG) {
Slog.i(TAG, "Home preference changed; backing up new state " + home);
diff --git a/services/backup/java/com/android/server/backup/TransportManager.java b/services/backup/java/com/android/server/backup/TransportManager.java
index 501ff29..c87d298 100644
--- a/services/backup/java/com/android/server/backup/TransportManager.java
+++ b/services/backup/java/com/android/server/backup/TransportManager.java
@@ -19,6 +19,7 @@
import android.annotation.Nullable;
import android.annotation.WorkerThread;
import android.app.backup.BackupManager;
+import android.app.backup.BackupTransport;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -58,8 +59,6 @@
@VisibleForTesting
public static final String SERVICE_ACTION_TRANSPORT_HOST = "android.backup.TRANSPORT_HOST";
- private static final String EXTRA_TRANSPORT_REGISTRATION = "transport_registration";
-
private final Intent mTransportServiceIntent = new Intent(SERVICE_ACTION_TRANSPORT_HOST);
private final Context mContext;
private final PackageManager mPackageManager;
@@ -587,7 +586,7 @@
String callerLogString = "TransportManager.registerTransport()";
Bundle extras = new Bundle();
- extras.putBoolean(EXTRA_TRANSPORT_REGISTRATION, true);
+ extras.putBoolean(BackupTransport.EXTRA_TRANSPORT_REGISTRATION, true);
TransportClient transportClient = mTransportClientManager.getTransportClient(
transportComponent, extras, callerLogString);
diff --git a/services/backup/java/com/android/server/backup/internal/BackupHandler.java b/services/backup/java/com/android/server/backup/internal/BackupHandler.java
index 3df6e47..136fada 100644
--- a/services/backup/java/com/android/server/backup/internal/BackupHandler.java
+++ b/services/backup/java/com/android/server/backup/internal/BackupHandler.java
@@ -302,7 +302,7 @@
sets = transport.getAvailableRestoreSets();
// cache the result in the active session
synchronized (params.session) {
- params.session.mRestoreSets = sets;
+ params.session.setRestoreSets(sets);
}
if (sets == null) {
EventLog.writeEvent(EventLogTags.RESTORE_TRANSPORT_FAILURE);
diff --git a/services/backup/java/com/android/server/backup/params/RestoreParams.java b/services/backup/java/com/android/server/backup/params/RestoreParams.java
index e500d6e..5125b0d 100644
--- a/services/backup/java/com/android/server/backup/params/RestoreParams.java
+++ b/services/backup/java/com/android/server/backup/params/RestoreParams.java
@@ -16,6 +16,7 @@
package com.android.server.backup.params;
+import android.annotation.Nullable;
import android.app.backup.IBackupManagerMonitor;
import android.app.backup.IRestoreObserver;
import android.content.pm.PackageInfo;
@@ -28,10 +29,10 @@
public final IRestoreObserver observer;
public final IBackupManagerMonitor monitor;
public final long token;
- public final PackageInfo packageInfo;
+ @Nullable public final PackageInfo packageInfo;
public final int pmToken; // in post-install restore, the PM's token for this transaction
public final boolean isSystemRestore;
- public final String[] filterSet;
+ @Nullable public final String[] filterSet;
public final OnTaskFinishedListener listener;
/**
@@ -129,10 +130,10 @@
IRestoreObserver observer,
IBackupManagerMonitor monitor,
long token,
- PackageInfo packageInfo,
+ @Nullable PackageInfo packageInfo,
int pmToken,
boolean isSystemRestore,
- String[] filterSet,
+ @Nullable String[] filterSet,
OnTaskFinishedListener listener) {
this.transportClient = transportClient;
this.observer = observer;
diff --git a/services/backup/java/com/android/server/backup/restore/ActiveRestoreSession.java b/services/backup/java/com/android/server/backup/restore/ActiveRestoreSession.java
index 238f7a0..140dded 100644
--- a/services/backup/java/com/android/server/backup/restore/ActiveRestoreSession.java
+++ b/services/backup/java/com/android/server/backup/restore/ActiveRestoreSession.java
@@ -22,6 +22,7 @@
import static com.android.server.backup.internal.BackupHandler.MSG_RUN_GET_RESTORE_SETS;
import static com.android.server.backup.internal.BackupHandler.MSG_RUN_RESTORE;
+import android.annotation.Nullable;
import android.app.backup.IBackupManagerMonitor;
import android.app.backup.IRestoreObserver;
import android.app.backup.IRestoreSession;
@@ -53,13 +54,15 @@
private final TransportManager mTransportManager;
private final String mTransportName;
private final BackupManagerService mBackupManagerService;
- private final String mPackageName;
+ @Nullable private final String mPackageName;
public RestoreSet[] mRestoreSets = null;
boolean mEnded = false;
boolean mTimedOut = false;
- public ActiveRestoreSession(BackupManagerService backupManagerService,
- String packageName, String transportName) {
+ public ActiveRestoreSession(
+ BackupManagerService backupManagerService,
+ @Nullable String packageName,
+ String transportName) {
mBackupManagerService = backupManagerService;
mPackageName = packageName;
mTransportManager = backupManagerService.getTransportManager();
@@ -360,6 +363,10 @@
}
}
+ public void setRestoreSets(RestoreSet[] restoreSets) {
+ mRestoreSets = restoreSets;
+ }
+
/**
* Returns 0 if operation sent or -1 otherwise.
*/
diff --git a/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java b/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java
index 0ca4f25..c1a1c1d 100644
--- a/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java
+++ b/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java
@@ -36,11 +36,13 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.PackageManagerInternal;
import android.content.pm.Signature;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.util.Slog;
+import com.android.server.LocalServices;
import com.android.server.backup.BackupRestoreTask;
import com.android.server.backup.FileMetadata;
import com.android.server.backup.KeyValueAdbRestoreEngine;
@@ -207,8 +209,11 @@
if (info.path.equals(BACKUP_MANIFEST_FILENAME)) {
Signature[] signatures = tarBackupReader.readAppManifestAndReturnSignatures(
info);
+ PackageManagerInternal pmi = LocalServices.getService(
+ PackageManagerInternal.class);
RestorePolicy restorePolicy = tarBackupReader.chooseRestorePolicy(
- mBackupManagerService.getPackageManager(), allowApks, info, signatures);
+ mBackupManagerService.getPackageManager(), allowApks, info, signatures,
+ pmi);
mManifestSignatures.put(info.packageName, signatures);
mPackagePolicies.put(pkg, restorePolicy);
mPackageInstallers.put(pkg, info.installerPackageName);
diff --git a/services/backup/java/com/android/server/backup/restore/PerformAdbRestoreTask.java b/services/backup/java/com/android/server/backup/restore/PerformAdbRestoreTask.java
index e576b3c..dacde0b 100644
--- a/services/backup/java/com/android/server/backup/restore/PerformAdbRestoreTask.java
+++ b/services/backup/java/com/android/server/backup/restore/PerformAdbRestoreTask.java
@@ -40,6 +40,7 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.PackageManagerInternal;
import android.content.pm.Signature;
import android.os.Environment;
import android.os.ParcelFileDescriptor;
@@ -47,6 +48,7 @@
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.LocalServices;
import com.android.server.backup.BackupManagerService;
import com.android.server.backup.FileMetadata;
import com.android.server.backup.KeyValueAdbRestoreEngine;
@@ -470,9 +472,11 @@
if (info.path.equals(BACKUP_MANIFEST_FILENAME)) {
Signature[] signatures = tarBackupReader.readAppManifestAndReturnSignatures(
info);
+ PackageManagerInternal pmi = LocalServices.getService(
+ PackageManagerInternal.class);
RestorePolicy restorePolicy = tarBackupReader.chooseRestorePolicy(
mBackupManagerService.getPackageManager(), allowApks,
- info, signatures);
+ info, signatures, pmi);
mManifestSignatures.put(info.packageName, signatures);
mPackagePolicies.put(pkg, restorePolicy);
mPackageInstallers.put(pkg, info.installerPackageName);
diff --git a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
index 6eb9619..4b467e5 100644
--- a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
+++ b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
@@ -30,6 +30,7 @@
import static com.android.server.backup.internal.BackupHandler.MSG_RESTORE_OPERATION_TIMEOUT;
import static com.android.server.backup.internal.BackupHandler.MSG_RESTORE_SESSION_TIMEOUT;
+import android.annotation.Nullable;
import android.app.ApplicationThreadConstants;
import android.app.IBackupAgent;
import android.app.backup.BackupDataInput;
@@ -42,6 +43,7 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Bundle;
import android.os.Message;
@@ -56,6 +58,7 @@
import com.android.internal.backup.IBackupTransport;
import com.android.server.AppWidgetBackupBridge;
import com.android.server.EventLogTags;
+import com.android.server.LocalServices;
import com.android.server.backup.BackupRestoreTask;
import com.android.server.backup.BackupUtils;
import com.android.server.backup.PackageManagerBackupAgent;
@@ -158,12 +161,18 @@
private final int mEphemeralOpToken;
- // Invariant: mWakelock is already held, and this task is responsible for
- // releasing it at the end of the restore operation.
- public PerformUnifiedRestoreTask(BackupManagerService backupManagerService,
- TransportClient transportClient, IRestoreObserver observer,
- IBackupManagerMonitor monitor, long restoreSetToken, PackageInfo targetPackage,
- int pmToken, boolean isFullSystemRestore, String[] filterSet,
+ // This task can assume that the wakelock is properly held for it and doesn't have to worry
+ // about releasing it.
+ public PerformUnifiedRestoreTask(
+ BackupManagerService backupManagerService,
+ TransportClient transportClient,
+ IRestoreObserver observer,
+ IBackupManagerMonitor monitor,
+ long restoreSetToken,
+ @Nullable PackageInfo targetPackage,
+ int pmToken,
+ boolean isFullSystemRestore,
+ @Nullable String[] filterSet,
OnTaskFinishedListener listener) {
this.backupManagerService = backupManagerService;
mTransportManager = backupManagerService.getTransportManager();
@@ -336,7 +345,7 @@
*
* [ state change => FINAL ]
*
- * 7. t.finishRestore(), release wakelock, etc.
+ * 7. t.finishRestore(), call listeners, etc.
*
*
*/
@@ -497,7 +506,7 @@
try {
mCurrentPackage = backupManagerService.getPackageManager().getPackageInfo(
- pkgName, PackageManager.GET_SIGNATURES);
+ pkgName, PackageManager.GET_SIGNING_CERTIFICATES);
} catch (NameNotFoundException e) {
// Whoops, we thought we could restore this package but it
// turns out not to be present. Skip it.
@@ -612,7 +621,8 @@
}
Metadata metaInfo = mPmAgent.getRestoredMetadata(packageName);
- if (!BackupUtils.signaturesMatch(metaInfo.sigHashes, mCurrentPackage)) {
+ PackageManagerInternal pmi = LocalServices.getService(PackageManagerInternal.class);
+ if (!BackupUtils.signaturesMatch(metaInfo.sigHashes, mCurrentPackage, pmi)) {
Slog.w(TAG, "Signature mismatch restoring " + packageName);
mMonitor = BackupManagerMonitorUtils.monitorEvent(mMonitor,
BackupManagerMonitor.LOG_EVENT_ID_SIGNATURE_MISMATCH, mCurrentPackage,
diff --git a/services/backup/java/com/android/server/backup/utils/AppBackupUtils.java b/services/backup/java/com/android/server/backup/utils/AppBackupUtils.java
index 6780563..90c1387 100644
--- a/services/backup/java/com/android/server/backup/utils/AppBackupUtils.java
+++ b/services/backup/java/com/android/server/backup/utils/AppBackupUtils.java
@@ -25,6 +25,7 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
import android.content.pm.Signature;
import android.os.Process;
import android.util.Slog;
@@ -37,6 +38,9 @@
* Utility methods wrapping operations on ApplicationInfo and PackageInfo.
*/
public class AppBackupUtils {
+
+ private static final boolean DEBUG = false;
+
/**
* Returns whether app is eligible for backup.
*
@@ -88,7 +92,8 @@
public static boolean appIsRunningAndEligibleForBackupWithTransport(
@Nullable TransportClient transportClient, String packageName, PackageManager pm) {
try {
- PackageInfo packageInfo = pm.getPackageInfo(packageName, PackageManager.GET_SIGNATURES);
+ PackageInfo packageInfo = pm.getPackageInfo(packageName,
+ PackageManager.GET_SIGNING_CERTIFICATES);
ApplicationInfo applicationInfo = packageInfo.applicationInfo;
if (!appIsEligibleForBackup(applicationInfo, pm)
|| appIsStopped(applicationInfo)
@@ -165,12 +170,18 @@
*
* <ul>
* <li>Source and target have at least one signature each
- * <li>Target contains all signatures in source
+ * <li>Target contains all signatures in source, and nothing more
* </ul>
*
+ * or if both source and target have exactly one signature, and they don't match, we check
+ * if the app was ever signed with source signature (i.e. app has rotated key)
+ * Note: key rotation is only supported for apps ever signed with one key, and those apps will
+ * not be allowed to be signed by more certificates in the future
+ *
* Note that if {@param target} is null we return false.
*/
- public static boolean signaturesMatch(Signature[] storedSigs, PackageInfo target) {
+ public static boolean signaturesMatch(Signature[] storedSigs, PackageInfo target,
+ PackageManagerInternal pmi) {
if (target == null) {
return false;
}
@@ -187,33 +198,52 @@
return true;
}
- Signature[] deviceSigs = target.signatures;
- if (MORE_DEBUG) {
- Slog.v(TAG, "signaturesMatch(): stored=" + storedSigs + " device=" + deviceSigs);
- }
-
// Don't allow unsigned apps on either end
- if (ArrayUtils.isEmpty(storedSigs) || ArrayUtils.isEmpty(deviceSigs)) {
+ if (ArrayUtils.isEmpty(storedSigs)) {
return false;
}
- // Signatures can be added over time, so the target-device apk needs to contain all the
- // source-device apk signatures, but not necessarily the other way around.
- int nStored = storedSigs.length;
- int nDevice = deviceSigs.length;
+ Signature[][] deviceHistorySigs = target.signingCertificateHistory;
+ if (ArrayUtils.isEmpty(deviceHistorySigs)) {
+ Slog.w(TAG, "signingCertificateHistory is empty, app was either unsigned or the flag" +
+ " PackageManager#GET_SIGNING_CERTIFICATES was not specified");
+ return false;
+ }
- for (int i = 0; i < nStored; i++) {
- boolean match = false;
- for (int j = 0; j < nDevice; j++) {
- if (storedSigs[i].equals(deviceSigs[j])) {
- match = true;
- break;
+ if (DEBUG) {
+ Slog.v(TAG, "signaturesMatch(): stored=" + storedSigs + " device=" + deviceHistorySigs);
+ }
+
+ final int nStored = storedSigs.length;
+ if (nStored == 1) {
+ // if the app is only signed with one sig, it's possible it has rotated its key
+ // (the checks with signing history are delegated to PackageManager)
+ // TODO: address the case that app has declared restoreAnyVersion and is restoring
+ // from higher version to lower after having rotated the key (i.e. higher version has
+ // different sig than lower version that we want to restore to)
+ return pmi.isDataRestoreSafe(storedSigs[0], target.packageName);
+ } else {
+ // the app couldn't have rotated keys, since it was signed with multiple sigs - do
+ // a comprehensive 1-to-1 signatures check
+ // since app hasn't rotated key, we only need to check with deviceHistorySigs[0]
+ Signature[] deviceSigs = deviceHistorySigs[0];
+ int nDevice = deviceSigs.length;
+
+ // ensure that each stored sig matches an on-device sig
+ for (int i = 0; i < nStored; i++) {
+ boolean match = false;
+ for (int j = 0; j < nDevice; j++) {
+ if (storedSigs[i].equals(deviceSigs[j])) {
+ match = true;
+ break;
+ }
+ }
+ if (!match) {
+ return false;
}
}
- if (!match) {
- return false;
- }
+ // we have found a match for all stored sigs
+ return true;
}
- return true;
}
}
diff --git a/services/backup/java/com/android/server/backup/utils/RestoreUtils.java b/services/backup/java/com/android/server/backup/utils/RestoreUtils.java
index 10f0695..df7e6d45 100644
--- a/services/backup/java/com/android/server/backup/utils/RestoreUtils.java
+++ b/services/backup/java/com/android/server/backup/utils/RestoreUtils.java
@@ -30,6 +30,7 @@
import android.content.pm.PackageInstaller.Session;
import android.content.pm.PackageInstaller.SessionParams;
import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
import android.content.pm.Signature;
import android.os.Bundle;
import android.os.IBinder;
@@ -37,6 +38,7 @@
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
+import com.android.server.LocalServices;
import com.android.server.backup.FileMetadata;
import com.android.server.backup.restore.RestoreDeleteObserver;
import com.android.server.backup.restore.RestorePolicy;
@@ -142,9 +144,8 @@
uninstall = true;
} else {
try {
- PackageInfo pkg = packageManager.getPackageInfo(
- info.packageName,
- PackageManager.GET_SIGNATURES);
+ PackageInfo pkg = packageManager.getPackageInfo(info.packageName,
+ PackageManager.GET_SIGNING_CERTIFICATES);
if ((pkg.applicationInfo.flags & ApplicationInfo.FLAG_ALLOW_BACKUP)
== 0) {
Slog.w(TAG, "Restore stream contains apk of package "
@@ -154,7 +155,9 @@
} else {
// So far so good -- do the signatures match the manifest?
Signature[] sigs = manifestSignatures.get(info.packageName);
- if (AppBackupUtils.signaturesMatch(sigs, pkg)) {
+ PackageManagerInternal pmi = LocalServices.getService(
+ PackageManagerInternal.class);
+ if (AppBackupUtils.signaturesMatch(sigs, pkg, pmi)) {
// If this is a system-uid app without a declared backup agent,
// don't restore any of the file data.
if ((pkg.applicationInfo.uid < Process.FIRST_APPLICATION_UID)
diff --git a/services/backup/java/com/android/server/backup/utils/TarBackupReader.java b/services/backup/java/com/android/server/backup/utils/TarBackupReader.java
index cc26ff8..6dd5284 100644
--- a/services/backup/java/com/android/server/backup/utils/TarBackupReader.java
+++ b/services/backup/java/com/android/server/backup/utils/TarBackupReader.java
@@ -50,6 +50,7 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
import android.content.pm.Signature;
import android.os.Bundle;
import android.os.Process;
@@ -385,7 +386,8 @@
* @return a restore policy constant.
*/
public RestorePolicy chooseRestorePolicy(PackageManager packageManager,
- boolean allowApks, FileMetadata info, Signature[] signatures) {
+ boolean allowApks, FileMetadata info, Signature[] signatures,
+ PackageManagerInternal pmi) {
if (signatures == null) {
return RestorePolicy.IGNORE;
}
@@ -395,7 +397,7 @@
// Okay, got the manifest info we need...
try {
PackageInfo pkgInfo = packageManager.getPackageInfo(
- info.packageName, PackageManager.GET_SIGNATURES);
+ info.packageName, PackageManager.GET_SIGNING_CERTIFICATES);
// Fall through to IGNORE if the app explicitly disallows backup
final int flags = pkgInfo.applicationInfo.flags;
if ((flags & ApplicationInfo.FLAG_ALLOW_BACKUP) != 0) {
@@ -411,7 +413,7 @@
// such packages are signed with the platform cert instead of
// the app developer's cert, so they're different on every
// device.
- if (AppBackupUtils.signaturesMatch(signatures, pkgInfo)) {
+ if (AppBackupUtils.signaturesMatch(signatures, pkgInfo, pmi)) {
if ((pkgInfo.applicationInfo.flags
& ApplicationInfo.FLAG_RESTORE_ANY_VERSION) != 0) {
Slog.i(TAG, "Package has restoreAnyVersion; taking data");
diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java
index c93f405..62a7b8f 100644
--- a/services/core/java/com/android/server/AlarmManagerService.java
+++ b/services/core/java/com/android/server/AlarmManagerService.java
@@ -66,6 +66,7 @@
import android.system.Os;
import android.text.TextUtils;
import android.text.format.DateFormat;
+import android.text.format.DateUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.KeyValueListParser;
@@ -268,6 +269,7 @@
// Key names stored in the settings value.
private static final String KEY_MIN_FUTURITY = "min_futurity";
private static final String KEY_MIN_INTERVAL = "min_interval";
+ private static final String KEY_MAX_INTERVAL = "max_interval";
private static final String KEY_ALLOW_WHILE_IDLE_SHORT_TIME = "allow_while_idle_short_time";
private static final String KEY_ALLOW_WHILE_IDLE_LONG_TIME = "allow_while_idle_long_time";
private static final String KEY_ALLOW_WHILE_IDLE_WHITELIST_DURATION
@@ -285,6 +287,7 @@
private static final long DEFAULT_MIN_FUTURITY = 5 * 1000;
private static final long DEFAULT_MIN_INTERVAL = 60 * 1000;
+ private static final long DEFAULT_MAX_INTERVAL = 365 * DateUtils.DAY_IN_MILLIS;
private static final long DEFAULT_ALLOW_WHILE_IDLE_SHORT_TIME = DEFAULT_MIN_FUTURITY;
private static final long DEFAULT_ALLOW_WHILE_IDLE_LONG_TIME = 9*60*1000;
private static final long DEFAULT_ALLOW_WHILE_IDLE_WHITELIST_DURATION = 10*1000;
@@ -303,6 +306,9 @@
// Minimum alarm recurrence interval
public long MIN_INTERVAL = DEFAULT_MIN_INTERVAL;
+ // Maximum alarm recurrence interval
+ public long MAX_INTERVAL = DEFAULT_MAX_INTERVAL;
+
// Minimum time between ALLOW_WHILE_IDLE alarms when system is not idle.
public long ALLOW_WHILE_IDLE_SHORT_TIME = DEFAULT_ALLOW_WHILE_IDLE_SHORT_TIME;
@@ -361,6 +367,7 @@
MIN_FUTURITY = mParser.getLong(KEY_MIN_FUTURITY, DEFAULT_MIN_FUTURITY);
MIN_INTERVAL = mParser.getLong(KEY_MIN_INTERVAL, DEFAULT_MIN_INTERVAL);
+ MAX_INTERVAL = mParser.getLong(KEY_MAX_INTERVAL, DEFAULT_MAX_INTERVAL);
ALLOW_WHILE_IDLE_SHORT_TIME = mParser.getLong(KEY_ALLOW_WHILE_IDLE_SHORT_TIME,
DEFAULT_ALLOW_WHILE_IDLE_SHORT_TIME);
ALLOW_WHILE_IDLE_LONG_TIME = mParser.getLong(KEY_ALLOW_WHILE_IDLE_LONG_TIME,
@@ -391,6 +398,10 @@
TimeUtils.formatDuration(MIN_INTERVAL, pw);
pw.println();
+ pw.print(" "); pw.print(KEY_MAX_INTERVAL); pw.print("=");
+ TimeUtils.formatDuration(MAX_INTERVAL, pw);
+ pw.println();
+
pw.print(" "); pw.print(KEY_LISTENER_TIMEOUT); pw.print("=");
TimeUtils.formatDuration(LISTENER_TIMEOUT, pw);
pw.println();
@@ -419,6 +430,7 @@
proto.write(ConstantsProto.MIN_FUTURITY_DURATION_MS, MIN_FUTURITY);
proto.write(ConstantsProto.MIN_INTERVAL_DURATION_MS, MIN_INTERVAL);
+ proto.write(ConstantsProto.MAX_INTERVAL_DURATION_MS, MAX_INTERVAL);
proto.write(ConstantsProto.LISTENER_TIMEOUT_DURATION_MS, LISTENER_TIMEOUT);
proto.write(ConstantsProto.ALLOW_WHILE_IDLE_SHORT_DURATION_MS,
ALLOW_WHILE_IDLE_SHORT_TIME);
@@ -481,7 +493,7 @@
Batch(Alarm seed) {
start = seed.whenElapsed;
- end = seed.maxWhenElapsed;
+ end = clampPositive(seed.maxWhenElapsed);
flags = seed.flags;
alarms.add(seed);
if (seed.operation == mTimeTickSender) {
@@ -737,7 +749,7 @@
if (futurity < MIN_FUZZABLE_INTERVAL) {
futurity = 0;
}
- return triggerAtTime + (long)(.75 * futurity);
+ return clampPositive(triggerAtTime + (long)(.75 * futurity));
}
// returns true if the batch was added at the head
@@ -913,7 +925,7 @@
// the window based on the alarm's new futurity. Note that this
// reflects a policy of preferring timely to deferred delivery.
maxElapsed = (a.windowLength > 0)
- ? (whenElapsed + a.windowLength)
+ ? clampPositive(whenElapsed + a.windowLength)
: maxTriggerTime(nowElapsed, whenElapsed, a.repeatInterval);
}
a.whenElapsed = whenElapsed;
@@ -921,6 +933,10 @@
setImplLocked(a, true, doValidate);
}
+ static long clampPositive(long val) {
+ return (val >= 0) ? val : Long.MAX_VALUE;
+ }
+
/**
* Sends alarms that were blocked due to user applied background restrictions - either because
* the user lifted those or the uid came to foreground.
@@ -1421,13 +1437,18 @@
}
// Sanity check the recurrence interval. This will catch people who supply
- // seconds when the API expects milliseconds.
+ // seconds when the API expects milliseconds, or apps trying shenanigans
+ // around intentional period overflow, etc.
final long minInterval = mConstants.MIN_INTERVAL;
if (interval > 0 && interval < minInterval) {
Slog.w(TAG, "Suspiciously short interval " + interval
+ " millis; expanding to " + (minInterval/1000)
+ " seconds");
interval = minInterval;
+ } else if (interval > mConstants.MAX_INTERVAL) {
+ Slog.w(TAG, "Suspiciously long interval " + interval
+ + " millis; clamping");
+ interval = mConstants.MAX_INTERVAL;
}
if (type < RTC_WAKEUP || type > ELAPSED_REALTIME) {
@@ -3175,8 +3196,7 @@
whenElapsed = _whenElapsed;
expectedWhenElapsed = _whenElapsed;
windowLength = _windowLength;
- maxWhenElapsed = _maxWhen;
- expectedMaxWhenElapsed = _maxWhen;
+ maxWhenElapsed = expectedMaxWhenElapsed = clampPositive(_maxWhen);
repeatInterval = _interval;
operation = _op;
listener = _rec;
diff --git a/services/core/java/com/android/server/am/ActivityDisplay.java b/services/core/java/com/android/server/am/ActivityDisplay.java
index 56ed6c8..bac81e7 100644
--- a/services/core/java/com/android/server/am/ActivityDisplay.java
+++ b/services/core/java/com/android/server/am/ActivityDisplay.java
@@ -158,6 +158,8 @@
}
private void positionChildAt(ActivityStack stack, int position) {
+ // TODO: Keep in sync with WindowContainer.positionChildAt(), once we change that to adjust
+ // the position internally, also update the logic here
mStacks.remove(stack);
final int insertPosition = getTopInsertPosition(stack, position);
mStacks.add(insertPosition, stack);
@@ -750,7 +752,15 @@
return;
}
- positionChildAt(mHomeStack, Math.max(0, mStacks.indexOf(behindStack) - 1));
+ // Note that positionChildAt will first remove the given stack before inserting into the
+ // list, so we need to adjust the insertion index to account for the removed index
+ // TODO: Remove this logic when WindowContainer.positionChildAt() is updated to adjust the
+ // position internally
+ final int homeStackIndex = mStacks.indexOf(mHomeStack);
+ final int behindStackIndex = mStacks.indexOf(behindStack);
+ final int insertIndex = homeStackIndex <= behindStackIndex
+ ? behindStackIndex - 1 : behindStackIndex;
+ positionChildAt(mHomeStack, Math.max(0, insertIndex));
}
boolean isSleeping() {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index d4307d7..f1e3bfd 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -5140,6 +5140,7 @@
public void startRecentsActivity(Intent intent, IAssistDataReceiver assistDataReceiver,
IRecentsAnimationRunner recentsAnimationRunner) {
enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "startRecentsActivity()");
+ final int callingPid = Binder.getCallingPid();
final long origId = Binder.clearCallingIdentity();
try {
synchronized (this) {
@@ -5165,7 +5166,7 @@
// Start a new recents animation
final RecentsAnimation anim = new RecentsAnimation(this, mStackSupervisor,
- mActivityStartController, mWindowManager, mUserController);
+ mActivityStartController, mWindowManager, mUserController, callingPid);
anim.startRecentsActivity(intent, recentsAnimationRunner, recentsComponent,
recentsUid);
}
@@ -9409,6 +9410,25 @@
allowed = false;
}
}
+ if (pi.pathPermissions != null) {
+ final int N = pi.pathPermissions.length;
+ for (int i=0; i<N; i++) {
+ if (pi.pathPermissions[i] != null
+ && pi.pathPermissions[i].match(grantUri.uri.getPath())) {
+ if ((modeFlags&Intent.FLAG_GRANT_READ_URI_PERMISSION) != 0) {
+ if (pi.pathPermissions[i].getReadPermission() != null) {
+ allowed = false;
+ }
+ }
+ if ((modeFlags&Intent.FLAG_GRANT_WRITE_URI_PERMISSION) != 0) {
+ if (pi.pathPermissions[i].getWritePermission() != null) {
+ allowed = false;
+ }
+ }
+ break;
+ }
+ }
+ }
if (allowed) {
return -1;
}
@@ -14309,6 +14329,28 @@
}
}
+ void setRunningRemoteAnimation(int pid, boolean runningRemoteAnimation) {
+ synchronized (ActivityManagerService.this) {
+ final ProcessRecord pr;
+ synchronized (mPidsSelfLocked) {
+ pr = mPidsSelfLocked.get(pid);
+ if (pr == null) {
+ Slog.w(TAG, "setRunningRemoteAnimation called on unknown pid: " + pid);
+ return;
+ }
+ }
+ if (pr.runningRemoteAnimation == runningRemoteAnimation) {
+ return;
+ }
+ pr.runningRemoteAnimation = runningRemoteAnimation;
+ if (DEBUG_OOM_ADJ) {
+ Slog.i(TAG, "Setting runningRemoteAnimation=" + pr.runningRemoteAnimation
+ + " for pid=" + pid);
+ }
+ updateOomAdjLocked(pr, true);
+ }
+ }
+
public final void enterSafeMode() {
synchronized(this) {
// It only makes sense to do this before the system is ready
@@ -22686,6 +22728,12 @@
foregroundActivities = true;
procState = PROCESS_STATE_CUR_TOP;
if (DEBUG_OOM_ADJ_REASON) Slog.d(TAG, "Making top: " + app);
+ } else if (app.runningRemoteAnimation) {
+ adj = ProcessList.VISIBLE_APP_ADJ;
+ schedGroup = ProcessList.SCHED_GROUP_TOP_APP;
+ app.adjType = "running-remote-anim";
+ procState = PROCESS_STATE_CUR_TOP;
+ if (DEBUG_OOM_ADJ_REASON) Slog.d(TAG, "Making running remote anim: " + app);
} else if (app.instr != null) {
// Don't want to kill running instrumentation.
adj = ProcessList.FOREGROUND_APP_ADJ;
@@ -22761,7 +22809,9 @@
app.adjType = "vis-activity";
if (DEBUG_OOM_ADJ_REASON) Slog.d(TAG, "Raise to vis-activity: " + app);
}
- schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
+ if (schedGroup < ProcessList.SCHED_GROUP_DEFAULT) {
+ schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
+ }
app.cached = false;
app.empty = false;
foregroundActivities = true;
@@ -22784,7 +22834,9 @@
app.adjType = "pause-activity";
if (DEBUG_OOM_ADJ_REASON) Slog.d(TAG, "Raise to pause-activity: " + app);
}
- schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
+ if (schedGroup < ProcessList.SCHED_GROUP_DEFAULT) {
+ schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
+ }
app.cached = false;
app.empty = false;
foregroundActivities = true;
@@ -25925,6 +25977,11 @@
}
}
+ @Override
+ public void setRunningRemoteAnimation(int pid, boolean runningRemoteAnimation) {
+ ActivityManagerService.this.setRunningRemoteAnimation(pid, runningRemoteAnimation);
+ }
+
/**
* Called after the network policy rules are updated by
* {@link com.android.server.net.NetworkPolicyManagerService} for a specific {@param uid}
@@ -26117,6 +26174,10 @@
return getRecentTasks().isCallerRecents(callingUid);
}
+ public boolean isRecentsComponentHomeActivity(int userId) {
+ return getRecentTasks().isRecentsComponentHomeActivity(userId);
+ }
+
@Override
public boolean isUidActive(int uid) {
synchronized (ActivityManagerService.this) {
@@ -26470,6 +26531,7 @@
throws RemoteException {
enforceCallingPermission(CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS,
"registerRemoteAnimations");
+ definition.setCallingPid(Binder.getCallingPid());
synchronized (this) {
final ActivityRecord r = ActivityRecord.isInStackLocked(token);
if (r == null) {
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index 8cc9273..274a4b0 100644
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -1581,25 +1581,7 @@
void setState(ActivityState state, String reason) {
if (DEBUG_STATES) Slog.v(TAG_STATES, "State movement: " + this + " from:" + getState()
+ " to:" + state + " reason:" + reason);
- final boolean stateChanged = mState != state;
mState = state;
-
- if (stateChanged && isState(DESTROYING, DESTROYED)) {
- makeFinishingLocked();
-
- // When moving to the destroyed state, immediately destroy the activity in the
- // associated stack. Most paths for finishing an activity will handle an activity's path
- // to destroy through mechanisms such as ActivityStackSupervisor#mFinishingActivities.
- // However, moving to the destroyed state directly (as in the case of an app dying) and
- // marking it as finished will lead to cleanup steps that will prevent later handling
- // from happening.
- if (isState(DESTROYED)) {
- final ActivityStack stack = getStack();
- if (stack != null) {
- stack.activityDestroyedLocked(this, reason);
- }
- }
- }
}
ActivityState getState() {
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 4987b33..2f6afd2 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -603,6 +603,8 @@
// the one where the home stack is visible since recents isn't visible yet, but the
// divider will be off. I think we should just make the initial bounds that of home
// so that the divider matches and remove this logic.
+ // TODO: This is currently only called when entering split-screen while in another
+ // task, and from the tests
final ActivityStack recentStack = display.getOrCreateStack(
WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_RECENTS,
true /* onTop */);
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 5577186..0157c7c 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -93,6 +93,7 @@
import static com.android.server.am.proto.ActivityStackSupervisorProto.CONFIGURATION_CONTAINER;
import static com.android.server.am.proto.ActivityStackSupervisorProto.DISPLAYS;
import static com.android.server.am.proto.ActivityStackSupervisorProto.FOCUSED_STACK_ID;
+import static com.android.server.am.proto.ActivityStackSupervisorProto.IS_HOME_RECENTS_COMPONENT;
import static com.android.server.am.proto.ActivityStackSupervisorProto.KEYGUARD_CONTROLLER;
import static com.android.server.am.proto.ActivityStackSupervisorProto.RESUMED_ACTIVITY;
import static android.view.WindowManager.TRANSIT_DOCK_TASK_FROM_RECENTS;
@@ -164,7 +165,6 @@
import android.util.TimeUtils;
import android.util.proto.ProtoOutputStream;
import android.view.Display;
-import android.view.RemoteAnimationAdapter;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -2540,6 +2540,11 @@
}
}
+ void deferUpdateRecentsHomeStackBounds() {
+ deferUpdateBounds(ACTIVITY_TYPE_RECENTS);
+ deferUpdateBounds(ACTIVITY_TYPE_HOME);
+ }
+
void deferUpdateBounds(int activityType) {
final ActivityStack stack = getStack(WINDOWING_MODE_UNDEFINED, activityType);
if (stack != null) {
@@ -2547,6 +2552,11 @@
}
}
+ void continueUpdateRecentsHomeStackBounds() {
+ continueUpdateBounds(ACTIVITY_TYPE_RECENTS);
+ continueUpdateBounds(ACTIVITY_TYPE_HOME);
+ }
+
void continueUpdateBounds(int activityType) {
final ActivityStack stack = getStack(WINDOWING_MODE_UNDEFINED, activityType);
if (stack != null) {
@@ -2555,7 +2565,7 @@
}
void notifyAppTransitionDone() {
- continueUpdateBounds(ACTIVITY_TYPE_RECENTS);
+ continueUpdateRecentsHomeStackBounds();
for (int i = mResizingTasksDuringAnimation.size() - 1; i >= 0; i--) {
final int taskId = mResizingTasksDuringAnimation.valueAt(i);
final TaskRecord task = anyTaskForIdLocked(taskId, MATCH_TASK_IN_STACKS_ONLY);
@@ -3760,6 +3770,8 @@
pw.print(prefix); pw.print(prefix); mWaitingForActivityVisible.get(i).dump(pw, prefix);
}
}
+ pw.print(prefix); pw.print("isHomeRecentsComponent=");
+ pw.print(mRecentTasks.isRecentsComponentHomeActivity(mCurrentUser));
mKeyguardController.dump(pw, prefix);
mService.mLockTaskController.dump(pw, prefix);
@@ -3781,6 +3793,8 @@
} else {
proto.write(FOCUSED_STACK_ID, INVALID_STACK_ID);
}
+ proto.write(IS_HOME_RECENTS_COMPONENT,
+ mRecentTasks.isRecentsComponentHomeActivity(mCurrentUser));
}
/**
@@ -4546,14 +4560,14 @@
// Defer updating the stack in which recents is until the app transition is done, to
// not run into issues where we still need to draw the task in recents but the
// docked stack is already created.
- deferUpdateBounds(ACTIVITY_TYPE_RECENTS);
+ deferUpdateRecentsHomeStackBounds();
mWindowManager.prepareAppTransition(TRANSIT_DOCK_TASK_FROM_RECENTS, false);
}
task = anyTaskForIdLocked(taskId, MATCH_TASK_IN_STACKS_OR_RECENT_TASKS_AND_RESTORE,
activityOptions, ON_TOP);
if (task == null) {
- continueUpdateBounds(ACTIVITY_TYPE_RECENTS);
+ continueUpdateRecentsHomeStackBounds();
mWindowManager.executeAppTransition();
throw new IllegalArgumentException(
"startActivityFromRecents: Task " + taskId + " not found.");
@@ -4611,6 +4625,11 @@
// window manager can correctly calculate the focus window that can receive
// input keys.
moveHomeStackToFront("startActivityFromRecents: homeVisibleInSplitScreen");
+
+ // Immediately update the minimized docked stack mode, the upcoming animation
+ // for the docked activity (WMS.overridePendingAppTransitionMultiThumbFuture)
+ // will do the animation to the target bounds
+ mWindowManager.checkSplitScreenMinimizedChanged(false /* animate */);
}
}
mWindowManager.continueSurfaceLayout();
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index 1f60755..0bf2691 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -129,6 +129,12 @@
// When true the process will oom adj score will be set to
// ProcessList#PERCEPTIBLE_APP_ADJ at minimum to reduce the chance
// of the process getting killed.
+ boolean runningRemoteAnimation; // Is the process currently running a RemoteAnimation? When true
+ // the process will be set to use the
+ // ProcessList#SCHED_GROUP_TOP_APP scheduling group to boost
+ // performance, as well as oom adj score will be set to
+ // ProcessList#VISIBLE_APP_ADJ at minimum to reduce the chance
+ // of the process getting killed.
boolean pendingUiClean; // Want to clean up resources from showing UI?
boolean hasAboveClient; // Bound using BIND_ABOVE_CLIENT, so want to be lower
boolean treatLikeActivity; // Bound using BIND_TREAT_LIKE_ACTIVITY
@@ -336,9 +342,10 @@
pw.print(" hasAboveClient="); pw.print(hasAboveClient);
pw.print(" treatLikeActivity="); pw.println(treatLikeActivity);
}
- if (hasTopUi || hasOverlayUi) {
+ if (hasTopUi || hasOverlayUi || runningRemoteAnimation) {
pw.print(prefix); pw.print("hasTopUi="); pw.print(hasTopUi);
- pw.print(" hasOverlayUi="); pw.println(hasOverlayUi);
+ pw.print(" hasOverlayUi="); pw.print(hasOverlayUi);
+ pw.print(" runningRemoteAnimation="); pw.println(runningRemoteAnimation);
}
if (foregroundServices || forcingToImportant != null) {
pw.print(prefix); pw.print("foregroundServices="); pw.print(foregroundServices);
diff --git a/services/core/java/com/android/server/am/RecentTasks.java b/services/core/java/com/android/server/am/RecentTasks.java
index 2de84ab..5fd300c 100644
--- a/services/core/java/com/android/server/am/RecentTasks.java
+++ b/services/core/java/com/android/server/am/RecentTasks.java
@@ -279,6 +279,16 @@
}
/**
+ * @return whether the home app is also the active handler of recent tasks.
+ */
+ boolean isRecentsComponentHomeActivity(int userId) {
+ final ComponentName defaultHomeActivity = mService.getPackageManagerInternalLocked()
+ .getDefaultHomeActivity(userId);
+ return defaultHomeActivity != null &&
+ defaultHomeActivity.getPackageName().equals(mRecentsComponent.getPackageName());
+ }
+
+ /**
* @return the recents component.
*/
ComponentName getRecentsComponent() {
diff --git a/services/core/java/com/android/server/am/RecentsAnimation.java b/services/core/java/com/android/server/am/RecentsAnimation.java
index 6dcf041..0ef8bff 100644
--- a/services/core/java/com/android/server/am/RecentsAnimation.java
+++ b/services/core/java/com/android/server/am/RecentsAnimation.java
@@ -50,6 +50,7 @@
private final WindowManagerService mWindowManager;
private final UserController mUserController;
private final Handler mHandler;
+ private final int mCallingPid;
private final Runnable mCancelAnimationRunnable;
@@ -58,13 +59,14 @@
RecentsAnimation(ActivityManagerService am, ActivityStackSupervisor stackSupervisor,
ActivityStartController activityStartController, WindowManagerService wm,
- UserController userController) {
+ UserController userController, int callingPid) {
mService = am;
mStackSupervisor = stackSupervisor;
mActivityStartController = activityStartController;
mHandler = new Handler(mStackSupervisor.mLooper);
mWindowManager = wm;
mUserController = userController;
+ mCallingPid = callingPid;
mCancelAnimationRunnable = () -> {
// The caller has not finished the animation in a predefined amount of time, so
@@ -94,9 +96,10 @@
}
}
+ mService.setRunningRemoteAnimation(mCallingPid, true);
+
mWindowManager.deferSurfaceLayout();
try {
-
final ActivityDisplay display;
if (hasExistingHomeActivity) {
// Move the home activity into place for the animation if it is not already top most
@@ -152,6 +155,8 @@
synchronized (mService) {
if (mWindowManager.getRecentsAnimationController() == null) return;
+ mService.setRunningRemoteAnimation(mCallingPid, false);
+
mWindowManager.inSurfaceTransaction(() -> {
Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER,
"RecentsAnimation#onAnimationFinished_inSurfaceTransaction");
diff --git a/services/core/java/com/android/server/am/SafeActivityOptions.java b/services/core/java/com/android/server/am/SafeActivityOptions.java
index d08111e..ac6f01f 100644
--- a/services/core/java/com/android/server/am/SafeActivityOptions.java
+++ b/services/core/java/com/android/server/am/SafeActivityOptions.java
@@ -121,10 +121,16 @@
if (mOriginalOptions != null) {
checkPermissions(intent, aInfo, callerApp, supervisor, mOriginalOptions,
mOriginalCallingPid, mOriginalCallingUid);
+ if (mOriginalOptions.getRemoteAnimationAdapter() != null) {
+ mOriginalOptions.getRemoteAnimationAdapter().setCallingPid(mOriginalCallingPid);
+ }
}
if (mCallerOptions != null) {
checkPermissions(intent, aInfo, callerApp, supervisor, mCallerOptions,
mRealCallingPid, mRealCallingUid);
+ if (mCallerOptions.getRemoteAnimationAdapter() != null) {
+ mCallerOptions.getRemoteAnimationAdapter().setCallingPid(mRealCallingPid);
+ }
}
return mergeActivityOptions(mOriginalOptions, mCallerOptions);
}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 8afa540..ca22820 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -1538,17 +1538,6 @@
if (adjustVolume && (direction != AudioManager.ADJUST_SAME)) {
mAudioHandler.removeMessages(MSG_UNMUTE_STREAM);
- // Check if volume update should be send to AVRCP
- if (streamTypeAlias == AudioSystem.STREAM_MUSIC &&
- (device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0 &&
- (flags & AudioManager.FLAG_BLUETOOTH_ABS_VOLUME) == 0) {
- synchronized (mA2dpAvrcpLock) {
- if (mA2dp != null && mAvrcpAbsVolSupported) {
- mA2dp.adjustAvrcpAbsoluteVolume(direction);
- }
- }
- }
-
if (isMuteAdjust) {
boolean state;
if (direction == AudioManager.ADJUST_TOGGLE_MUTE) {
@@ -1597,8 +1586,20 @@
0);
}
- // Check if volume update should be sent to Hdmi system audio.
int newIndex = mStreamStates[streamType].getIndex(device);
+
+ // Check if volume update should be send to AVRCP
+ if (streamTypeAlias == AudioSystem.STREAM_MUSIC &&
+ (device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0 &&
+ (flags & AudioManager.FLAG_BLUETOOTH_ABS_VOLUME) == 0) {
+ synchronized (mA2dpAvrcpLock) {
+ if (mA2dp != null && mAvrcpAbsVolSupported) {
+ mA2dp.setAvrcpAbsoluteVolume(newIndex / 10);
+ }
+ }
+ }
+
+ // Check if volume update should be sent to Hdmi system audio.
if (streamTypeAlias == AudioSystem.STREAM_MUSIC) {
setSystemAudioVolume(oldIndex, newIndex, getStreamMaxVolume(streamType), flags);
}
diff --git a/services/core/java/com/android/server/backup/BackupUtils.java b/services/core/java/com/android/server/backup/BackupUtils.java
index e5d564d..f44afe4 100644
--- a/services/core/java/com/android/server/backup/BackupUtils.java
+++ b/services/core/java/com/android/server/backup/BackupUtils.java
@@ -18,9 +18,12 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
+import android.content.pm.PackageManagerInternal;
import android.content.pm.Signature;
import android.util.Slog;
+import com.android.internal.util.ArrayUtils;
+
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
@@ -30,9 +33,10 @@
public class BackupUtils {
private static final String TAG = "BackupUtils";
- private static final boolean DEBUG = false; // STOPSHIP if true
+ private static final boolean DEBUG = false;
- public static boolean signaturesMatch(ArrayList<byte[]> storedSigHashes, PackageInfo target) {
+ public static boolean signaturesMatch(ArrayList<byte[]> storedSigHashes, PackageInfo target,
+ PackageManagerInternal pmi) {
if (target == null) {
return false;
}
@@ -47,48 +51,54 @@
return true;
}
- // Allow unsigned apps, but not signed on one device and unsigned on the other
- // !!! TODO: is this the right policy?
- Signature[] deviceSigs = target.signatures;
- if (DEBUG) Slog.v(TAG, "signaturesMatch(): stored=" + storedSigHashes
- + " device=" + deviceSigs);
- if ((storedSigHashes == null || storedSigHashes.size() == 0)
- && (deviceSigs == null || deviceSigs.length == 0)) {
- return true;
- }
- if (storedSigHashes == null || deviceSigs == null) {
+ // Don't allow unsigned apps on either end
+ if (ArrayUtils.isEmpty(storedSigHashes)) {
return false;
}
- // !!! TODO: this demands that every stored signature match one
- // that is present on device, and does not demand the converse.
- // Is this this right policy?
- final int nStored = storedSigHashes.size();
- final int nDevice = deviceSigs.length;
-
- // hash each on-device signature
- ArrayList<byte[]> deviceHashes = new ArrayList<byte[]>(nDevice);
- for (int i = 0; i < nDevice; i++) {
- deviceHashes.add(hashSignature(deviceSigs[i]));
+ Signature[][] deviceHistorySigs = target.signingCertificateHistory;
+ if (ArrayUtils.isEmpty(deviceHistorySigs)) {
+ Slog.w(TAG, "signingCertificateHistory is empty, app was either unsigned or the flag" +
+ " PackageManager#GET_SIGNING_CERTIFICATES was not specified");
+ return false;
}
- // now ensure that each stored sig (hash) matches an on-device sig (hash)
- for (int n = 0; n < nStored; n++) {
- boolean match = false;
- final byte[] storedHash = storedSigHashes.get(n);
- for (int i = 0; i < nDevice; i++) {
- if (Arrays.equals(storedHash, deviceHashes.get(i))) {
- match = true;
- break;
+ if (DEBUG) {
+ Slog.v(TAG, "signaturesMatch(): stored=" + storedSigHashes
+ + " device=" + deviceHistorySigs);
+ }
+
+ final int nStored = storedSigHashes.size();
+ if (nStored == 1) {
+ // if the app is only signed with one sig, it's possible it has rotated its key
+ // the checks with signing history are delegated to PackageManager
+ // TODO: address the case that app has declared restoreAnyVersion and is restoring
+ // from higher version to lower after having rotated the key (i.e. higher version has
+ // different sig than lower version that we want to restore to)
+ return pmi.isDataRestoreSafe(storedSigHashes.get(0), target.packageName);
+ } else {
+ // the app couldn't have rotated keys, since it was signed with multiple sigs - do
+ // a comprehensive 1-to-1 signatures check
+ // since app hasn't rotated key, we only need to check with deviceHistorySigs[0]
+ ArrayList<byte[]> deviceHashes = hashSignatureArray(deviceHistorySigs[0]);
+ int nDevice = deviceHashes.size();
+
+ // ensure that each stored sig matches an on-device sig
+ for (int i = 0; i < nStored; i++) {
+ boolean match = false;
+ for (int j = 0; j < nDevice; j++) {
+ if (Arrays.equals(storedSigHashes.get(i), deviceHashes.get(j))) {
+ match = true;
+ break;
+ }
+ }
+ if (!match) {
+ return false;
}
}
- // match is false when no on-device sig matched one of the stored ones
- if (!match) {
- return false;
- }
+ // we have found a match for all stored sigs
+ return true;
}
-
- return true;
}
public static byte[] hashSignature(byte[] signature) {
diff --git a/services/core/java/com/android/server/camera/CameraServiceProxy.java b/services/core/java/com/android/server/camera/CameraServiceProxy.java
index 3133a51..ca8823f 100644
--- a/services/core/java/com/android/server/camera/CameraServiceProxy.java
+++ b/services/core/java/com/android/server/camera/CameraServiceProxy.java
@@ -103,13 +103,15 @@
private static class CameraUsageEvent {
public final int mCameraFacing;
public final String mClientName;
+ public final int mAPILevel;
private boolean mCompleted;
private long mDurationOrStartTimeMs; // Either start time, or duration once completed
- public CameraUsageEvent(int facing, String clientName) {
+ public CameraUsageEvent(int facing, String clientName, int apiLevel) {
mCameraFacing = facing;
mClientName = clientName;
+ mAPILevel = apiLevel;
mDurationOrStartTimeMs = SystemClock.elapsedRealtime();
mCompleted = false;
}
@@ -168,13 +170,13 @@
@Override
public void notifyCameraState(String cameraId, int newCameraState, int facing,
- String clientName) {
+ String clientName, int apiLevel) {
String state = cameraStateToString(newCameraState);
String facingStr = cameraFacingToString(facing);
if (DEBUG) Slog.v(TAG, "Camera " + cameraId + " facing " + facingStr + " state now " +
- state + " for client " + clientName);
+ state + " for client " + clientName + " API Level " + apiLevel);
- updateActivityCount(cameraId, newCameraState, facing, clientName);
+ updateActivityCount(cameraId, newCameraState, facing, clientName, apiLevel);
}
};
@@ -293,6 +295,7 @@
.setType(MetricsEvent.TYPE_ACTION)
.setSubtype(subtype)
.setLatency(e.getDuration())
+ .addTaggedData(MetricsEvent.FIELD_CAMERA_API_LEVEL, e.mAPILevel)
.setPackageName(e.mClientName);
mLogger.write(l);
}
@@ -368,7 +371,8 @@
return true;
}
- private void updateActivityCount(String cameraId, int newCameraState, int facing, String clientName) {
+ private void updateActivityCount(String cameraId, int newCameraState, int facing,
+ String clientName, int apiLevel) {
synchronized(mLock) {
// Update active camera list and notify NFC if necessary
boolean wasEmpty = mActiveCameraUsage.isEmpty();
@@ -376,7 +380,7 @@
case ICameraServiceProxy.CAMERA_STATE_OPEN:
break;
case ICameraServiceProxy.CAMERA_STATE_ACTIVE:
- CameraUsageEvent newEvent = new CameraUsageEvent(facing, clientName);
+ CameraUsageEvent newEvent = new CameraUsageEvent(facing, clientName, apiLevel);
CameraUsageEvent oldEvent = mActiveCameraUsage.put(cameraId, newEvent);
if (oldEvent != null) {
Slog.w(TAG, "Camera " + cameraId + " was already marked as active");
diff --git a/services/core/java/com/android/server/clipboard/ClipboardService.java b/services/core/java/com/android/server/clipboard/ClipboardService.java
index 0c9d70a..776e93d 100644
--- a/services/core/java/com/android/server/clipboard/ClipboardService.java
+++ b/services/core/java/com/android/server/clipboard/ClipboardService.java
@@ -16,6 +16,7 @@
package com.android.server.clipboard;
+import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.AppGlobals;
import android.app.AppOpsManager;
@@ -24,9 +25,9 @@
import android.content.ClipData;
import android.content.ClipDescription;
import android.content.ContentProvider;
+import android.content.Context;
import android.content.IClipboard;
import android.content.IOnPrimaryClipChangedListener;
-import android.content.Context;
import android.content.Intent;
import android.content.pm.IPackageManager;
import android.content.pm.PackageInfo;
@@ -37,7 +38,6 @@
import android.os.IBinder;
import android.os.IUserManager;
import android.os.Parcel;
-import android.os.Process;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -49,14 +49,10 @@
import com.android.server.SystemService;
-import java.util.HashSet;
-import java.util.List;
-
-import java.lang.Thread;
-import java.lang.Runnable;
-import java.lang.InterruptedException;
import java.io.IOException;
import java.io.RandomAccessFile;
+import java.util.HashSet;
+import java.util.List;
// The following class is Android Emulator specific. It is used to read and
// write contents of the host system's clipboard.
@@ -182,7 +178,8 @@
new String[]{"text/plain"},
new ClipData.Item(contents));
synchronized(mClipboards) {
- setPrimaryClipInternal(getClipboard(0), clip);
+ setPrimaryClipInternal(getClipboard(0), clip,
+ android.os.Process.SYSTEM_UID);
}
}
});
@@ -218,7 +215,10 @@
final RemoteCallbackList<IOnPrimaryClipChangedListener> primaryClipListeners
= new RemoteCallbackList<IOnPrimaryClipChangedListener>();
+ /** Current primary clip. */
ClipData primaryClip;
+ /** UID that set {@link #primaryClip}. */
+ int primaryClipUid = android.os.Process.NOBODY_UID;
final HashSet<String> activePermissionOwners
= new HashSet<String>();
@@ -246,58 +246,28 @@
@Override
public void setPrimaryClip(ClipData clip, String callingPackage) {
synchronized (this) {
- if (clip != null && clip.getItemCount() <= 0) {
+ if (clip == null || clip.getItemCount() <= 0) {
throw new IllegalArgumentException("No items");
}
- if (clip.getItemAt(0).getText() != null &&
- mHostClipboardMonitor != null) {
- mHostClipboardMonitor.setHostClipboard(
- clip.getItemAt(0).getText().toString());
- }
final int callingUid = Binder.getCallingUid();
if (!clipboardAccessAllowed(AppOpsManager.OP_WRITE_CLIPBOARD, callingPackage,
callingUid)) {
return;
}
checkDataOwnerLocked(clip, callingUid);
- final int userId = UserHandle.getUserId(callingUid);
- PerUserClipboard clipboard = getClipboard(userId);
- revokeUris(clipboard);
- setPrimaryClipInternal(clipboard, clip);
- List<UserInfo> related = getRelatedProfiles(userId);
- if (related != null) {
- int size = related.size();
- if (size > 1) { // Related profiles list include the current profile.
- boolean canCopy = false;
- try {
- canCopy = !mUm.getUserRestrictions(userId).getBoolean(
- UserManager.DISALLOW_CROSS_PROFILE_COPY_PASTE);
- } catch (RemoteException e) {
- Slog.e(TAG, "Remote Exception calling UserManager: " + e);
- }
- // Copy clip data to related users if allowed. If disallowed, then remove
- // primary clip in related users to prevent pasting stale content.
- if (!canCopy) {
- clip = null;
- } else {
- // We want to fix the uris of the related user's clip without changing the
- // uris of the current user's clip.
- // So, copy the ClipData, and then copy all the items, so that nothing
- // is shared in memmory.
- clip = new ClipData(clip);
- for (int i = clip.getItemCount() - 1; i >= 0; i--) {
- clip.setItemAt(i, new ClipData.Item(clip.getItemAt(i)));
- }
- clip.fixUrisLight(userId);
- }
- for (int i = 0; i < size; i++) {
- int id = related.get(i).id;
- if (id != userId) {
- setPrimaryClipInternal(getClipboard(id), clip);
- }
- }
- }
+ setPrimaryClipInternal(clip, callingUid);
+ }
+ }
+
+ @Override
+ public void clearPrimaryClip(String callingPackage) {
+ synchronized (this) {
+ final int callingUid = Binder.getCallingUid();
+ if (!clipboardAccessAllowed(AppOpsManager.OP_WRITE_CLIPBOARD, callingPackage,
+ callingUid)) {
+ return;
}
+ setPrimaryClipInternal(null, callingUid);
}
}
@@ -398,13 +368,75 @@
return related;
}
- void setPrimaryClipInternal(PerUserClipboard clipboard, ClipData clip) {
+ void setPrimaryClipInternal(@Nullable ClipData clip, int callingUid) {
+ // Push clipboard to host, if any
+ if (mHostClipboardMonitor != null) {
+ if (clip == null) {
+ // Someone really wants the clipboard cleared, so push empty
+ mHostClipboardMonitor.setHostClipboard("");
+ } else if (clip.getItemCount() > 0) {
+ final CharSequence text = clip.getItemAt(0).getText();
+ if (text != null) {
+ mHostClipboardMonitor.setHostClipboard(text.toString());
+ }
+ }
+ }
+
+ // Update this user
+ final int userId = UserHandle.getUserId(callingUid);
+ setPrimaryClipInternal(getClipboard(userId), clip, callingUid);
+
+ // Update related users
+ List<UserInfo> related = getRelatedProfiles(userId);
+ if (related != null) {
+ int size = related.size();
+ if (size > 1) { // Related profiles list include the current profile.
+ boolean canCopy = false;
+ try {
+ canCopy = !mUm.getUserRestrictions(userId).getBoolean(
+ UserManager.DISALLOW_CROSS_PROFILE_COPY_PASTE);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote Exception calling UserManager: " + e);
+ }
+ // Copy clip data to related users if allowed. If disallowed, then remove
+ // primary clip in related users to prevent pasting stale content.
+ if (!canCopy) {
+ clip = null;
+ } else {
+ // We want to fix the uris of the related user's clip without changing the
+ // uris of the current user's clip.
+ // So, copy the ClipData, and then copy all the items, so that nothing
+ // is shared in memmory.
+ clip = new ClipData(clip);
+ for (int i = clip.getItemCount() - 1; i >= 0; i--) {
+ clip.setItemAt(i, new ClipData.Item(clip.getItemAt(i)));
+ }
+ clip.fixUrisLight(userId);
+ }
+ for (int i = 0; i < size; i++) {
+ int id = related.get(i).id;
+ if (id != userId) {
+ setPrimaryClipInternal(getClipboard(id), clip, callingUid);
+ }
+ }
+ }
+ }
+ }
+
+ void setPrimaryClipInternal(PerUserClipboard clipboard, @Nullable ClipData clip,
+ int callingUid) {
+ revokeUris(clipboard);
clipboard.activePermissionOwners.clear();
if (clip == null && clipboard.primaryClip == null) {
return;
}
clipboard.primaryClip = clip;
if (clip != null) {
+ clipboard.primaryClipUid = callingUid;
+ } else {
+ clipboard.primaryClipUid = android.os.Process.NOBODY_UID;
+ }
+ if (clip != null) {
final ClipDescription description = clip.getDescription();
if (description != null) {
description.setTimestamp(System.currentTimeMillis());
@@ -479,12 +511,12 @@
}
}
- private final void grantUriLocked(Uri uri, String pkg, int userId) {
+ private final void grantUriLocked(Uri uri, int primaryClipUid, String pkg, int userId) {
long ident = Binder.clearCallingIdentity();
try {
int sourceUserId = ContentProvider.getUserIdFromUri(uri, userId);
uri = ContentProvider.getUriWithoutUserId(uri);
- mAm.grantUriPermissionFromOwner(mPermissionOwner, Process.myUid(), pkg,
+ mAm.grantUriPermissionFromOwner(mPermissionOwner, primaryClipUid, pkg,
uri, Intent.FLAG_GRANT_READ_URI_PERMISSION, sourceUserId, userId);
} catch (RemoteException e) {
} finally {
@@ -492,13 +524,14 @@
}
}
- private final void grantItemLocked(ClipData.Item item, String pkg, int userId) {
+ private final void grantItemLocked(ClipData.Item item, int primaryClipUid, String pkg,
+ int userId) {
if (item.getUri() != null) {
- grantUriLocked(item.getUri(), pkg, userId);
+ grantUriLocked(item.getUri(), primaryClipUid, pkg, userId);
}
Intent intent = item.getIntent();
if (intent != null && intent.getData() != null) {
- grantUriLocked(intent.getData(), pkg, userId);
+ grantUriLocked(intent.getData(), primaryClipUid, pkg, userId);
}
}
@@ -524,7 +557,8 @@
if (clipboard.primaryClip != null && !clipboard.activePermissionOwners.contains(pkg)) {
final int N = clipboard.primaryClip.getItemCount();
for (int i=0; i<N; i++) {
- grantItemLocked(clipboard.primaryClip.getItemAt(i), pkg, UserHandle.getUserId(uid));
+ grantItemLocked(clipboard.primaryClip.getItemAt(i), clipboard.primaryClipUid, pkg,
+ UserHandle.getUserId(uid));
}
clipboard.activePermissionOwners.add(pkg);
}
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 9e00819..752ab8f 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -2079,6 +2079,11 @@
}
@Override
+ public String importKey(@NonNull String alias, byte[] keyBytes) throws RemoteException {
+ return mRecoverableKeyStoreManager.importKey(alias, keyBytes);
+ }
+
+ @Override
public String getKey(@NonNull String alias) throws RemoteException {
return mRecoverableKeyStoreManager.getKey(alias);
}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGenerator.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGenerator.java
index 2fe3f4e..7ebe8bf 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGenerator.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGenerator.java
@@ -16,6 +16,8 @@
package com.android.server.locksettings.recoverablekeystore;
+import android.annotation.NonNull;
+
import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb;
import java.security.InvalidKeyException;
@@ -25,20 +27,24 @@
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
+import javax.crypto.spec.SecretKeySpec;
+// TODO: Rename RecoverableKeyGenerator to RecoverableKeyManager as it can import a key too now
/**
- * Generates keys and stores them both in AndroidKeyStore and on disk, in wrapped form.
+ * Generates/imports keys and stores them both in AndroidKeyStore and on disk, in wrapped form.
*
- * <p>Generates 256-bit AES keys, which can be used for encrypt / decrypt with AES/GCM/NoPadding.
+ * <p>Generates/imports 256-bit AES keys, which can be used for encrypt and decrypt with AES-GCM.
* They are synced to disk wrapped by a platform key. This allows them to be exported to a remote
* service.
*
* @hide
*/
public class RecoverableKeyGenerator {
+
private static final int RESULT_CANNOT_INSERT_ROW = -1;
- private static final String KEY_GENERATOR_ALGORITHM = "AES";
- private static final int KEY_SIZE_BITS = 256;
+ private static final String SECRET_KEY_ALGORITHM = "AES";
+
+ static final int KEY_SIZE_BITS = 256;
/**
* A new {@link RecoverableKeyGenerator} instance.
@@ -52,7 +58,7 @@
throws NoSuchAlgorithmException {
// NB: This cannot use AndroidKeyStore as the provider, as we need access to the raw key
// material, so that it can be synced to disk in encrypted form.
- KeyGenerator keyGenerator = KeyGenerator.getInstance(KEY_GENERATOR_ALGORITHM);
+ KeyGenerator keyGenerator = KeyGenerator.getInstance(SECRET_KEY_ALGORITHM);
return new RecoverableKeyGenerator(keyGenerator, database);
}
@@ -102,4 +108,41 @@
mDatabase.setShouldCreateSnapshot(userId, uid, true);
return key.getEncoded();
}
+
+ /**
+ * Imports an AES key with the given alias.
+ *
+ * <p>Stores in the AndroidKeyStore, as well as persisting in wrapped form to disk. It is
+ * persisted to disk so that it can be synced remotely, and then recovered on another device.
+ * The generated key allows encrypt/decrypt only using AES/GCM/NoPadding.
+ *
+ * @param platformKey The user's platform key, with which to wrap the generated key.
+ * @param userId The user ID of the profile to which the calling app belongs.
+ * @param uid The uid of the application that will own the key.
+ * @param alias The alias by which the key will be known in the recoverable key store.
+ * @param keyBytes The raw bytes of the AES key to be imported.
+ * @throws RecoverableKeyStorageException if there is some error persisting the key either to
+ * the database.
+ * @throws KeyStoreException if there is a KeyStore error wrapping the generated key.
+ * @throws InvalidKeyException if the platform key cannot be used to wrap keys.
+ *
+ * @hide
+ */
+ public void importKey(
+ @NonNull PlatformEncryptionKey platformKey, int userId, int uid, @NonNull String alias,
+ @NonNull byte[] keyBytes)
+ throws RecoverableKeyStorageException, KeyStoreException, InvalidKeyException {
+ SecretKey key = new SecretKeySpec(keyBytes, SECRET_KEY_ALGORITHM);
+
+ WrappedKey wrappedKey = WrappedKey.fromSecretKey(platformKey, key);
+ long result = mDatabase.insertKey(userId, uid, alias, wrappedKey);
+
+ if (result == RESULT_CANNOT_INSERT_ROW) {
+ throw new RecoverableKeyStorageException(
+ String.format(
+ Locale.US, "Failed writing (%d, %s) to database.", uid, alias));
+ }
+
+ mDatabase.setShouldCreateSnapshot(userId, uid, true);
+ }
}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
index 22e99c4..da0b0d0 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
@@ -16,12 +16,13 @@
package com.android.server.locksettings.recoverablekeystore;
-import static android.security.keystore.RecoveryController.ERROR_BAD_CERTIFICATE_FORMAT;
-import static android.security.keystore.RecoveryController.ERROR_DECRYPTION_FAILED;
-import static android.security.keystore.RecoveryController.ERROR_INSECURE_USER;
-import static android.security.keystore.RecoveryController.ERROR_NO_SNAPSHOT_PENDING;
-import static android.security.keystore.RecoveryController.ERROR_SERVICE_INTERNAL_ERROR;
-import static android.security.keystore.RecoveryController.ERROR_SESSION_EXPIRED;
+import static android.security.keystore.recovery.RecoveryController.ERROR_BAD_CERTIFICATE_FORMAT;
+import static android.security.keystore.recovery.RecoveryController.ERROR_DECRYPTION_FAILED;
+import static android.security.keystore.recovery.RecoveryController.ERROR_INSECURE_USER;
+import static android.security.keystore.recovery.RecoveryController.ERROR_INVALID_KEY_FORMAT;
+import static android.security.keystore.recovery.RecoveryController.ERROR_NO_SNAPSHOT_PENDING;
+import static android.security.keystore.recovery.RecoveryController.ERROR_SERVICE_INTERNAL_ERROR;
+import static android.security.keystore.recovery.RecoveryController.ERROR_SESSION_EXPIRED;
import android.Manifest;
import android.annotation.NonNull;
@@ -58,10 +59,8 @@
import java.security.PublicKey;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertPath;
-import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
-import java.security.cert.X509Certificate;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
import java.util.Arrays;
@@ -507,6 +506,7 @@
*
* <p>TODO: Once AndroidKeyStore has added move api, do not return raw bytes.
*
+ * @deprecated
* @hide
*/
public byte[] generateAndStoreKey(@NonNull String alias) throws RemoteException {
@@ -583,6 +583,57 @@
}
/**
+ * Imports a 256-bit AES-GCM key named {@code alias}. The key is stored in system service
+ * keystore namespace.
+ *
+ * @param alias the alias provided by caller as a reference to the key.
+ * @param keyBytes the raw bytes of the 256-bit AES key.
+ * @return grant alias, which caller can use to access the key.
+ * @throws RemoteException if the given key is invalid or some internal errors occur.
+ *
+ * @hide
+ */
+ public String importKey(@NonNull String alias, @NonNull byte[] keyBytes)
+ throws RemoteException {
+ if (keyBytes == null ||
+ keyBytes.length != RecoverableKeyGenerator.KEY_SIZE_BITS / Byte.SIZE) {
+ Log.e(TAG, "The given key for import doesn't have the required length "
+ + RecoverableKeyGenerator.KEY_SIZE_BITS);
+ throw new ServiceSpecificException(ERROR_INVALID_KEY_FORMAT,
+ "The given key does not contain " + RecoverableKeyGenerator.KEY_SIZE_BITS
+ + " bits.");
+ }
+
+ int uid = Binder.getCallingUid();
+ int userId = UserHandle.getCallingUserId();
+
+ // TODO: Refactor RecoverableKeyGenerator to wrap the PlatformKey logic
+
+ PlatformEncryptionKey encryptionKey;
+ try {
+ encryptionKey = mPlatformKeyManager.getEncryptKey(userId);
+ } catch (NoSuchAlgorithmException e) {
+ // Impossible: all algorithms must be supported by AOSP
+ throw new RuntimeException(e);
+ } catch (KeyStoreException | UnrecoverableKeyException e) {
+ throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
+ } catch (InsecureUserException e) {
+ throw new ServiceSpecificException(ERROR_INSECURE_USER, e.getMessage());
+ }
+
+ try {
+ // Wrap the key by the platform key and store the wrapped key locally
+ mRecoverableKeyGenerator.importKey(encryptionKey, userId, uid, alias, keyBytes);
+
+ // Import the key to Android KeyStore and get grant
+ mApplicationKeyStorage.setSymmetricKeyEntry(userId, uid, alias, keyBytes);
+ return mApplicationKeyStorage.getGrantAlias(userId, uid, alias);
+ } catch (KeyStoreException | InvalidKeyException | RecoverableKeyStorageException e) {
+ throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
+ }
+ }
+
+ /**
* Gets a key named {@code alias} in caller's namespace.
*
* @return grant alias, which caller can use to access the key.
@@ -632,14 +683,6 @@
}
}
- private String constructLoggingMessage(String key, byte[] value) {
- if (value == null) {
- return key + " is null";
- } else {
- return key + ": " + HexDump.toHexString(value);
- }
- }
-
/**
* Uses {@code recoveryKey} to decrypt {@code applicationKeys}.
*
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/WrappedKey.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/WrappedKey.java
index d85e89e..0077242 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/WrappedKey.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/WrappedKey.java
@@ -16,7 +16,7 @@
package com.android.server.locksettings.recoverablekeystore;
-import android.security.keystore.RecoveryController;
+import android.security.keystore.recovery.RecoveryController;
import android.util.Log;
import java.security.InvalidAlgorithmParameterException;
@@ -107,7 +107,7 @@
* @param keyMaterial The encrypted bytes of the key material.
* @param platformKeyGenerationId The generation ID of the key used to wrap this key.
*
- * @see RecoveryController.RECOVERY_STATUS_SYNC_IN_PROGRESS
+ * @see RecoveryController#RECOVERY_STATUS_SYNC_IN_PROGRESS
* @hide
*/
public WrappedKey(byte[] nonce, byte[] keyMaterial, int platformKeyGenerationId) {
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/ApplicationKeyStorage.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/ApplicationKeyStorage.java
index 600a534..3d97623 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/ApplicationKeyStorage.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/ApplicationKeyStorage.java
@@ -16,15 +16,13 @@
package com.android.server.locksettings.recoverablekeystore.storage;
-import static android.security.keystore.RecoveryController.ERROR_SERVICE_INTERNAL_ERROR;
+import static android.security.keystore.recovery.RecoveryController.ERROR_SERVICE_INTERNAL_ERROR;
import android.annotation.Nullable;
import android.os.ServiceSpecificException;
import android.security.Credentials;
-import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties;
import android.security.keystore.KeyProtection;
-import android.security.keystore.recovery.KeyChainSnapshot;
import android.security.KeyStore;
import com.android.internal.annotations.VisibleForTesting;
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbContract.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbContract.java
index 1cb5d91..8983ec3 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbContract.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbContract.java
@@ -70,6 +70,122 @@
}
/**
+ * Table holding encrypted snapshots of the recoverable key store.
+ */
+ static class SnapshotsEntry implements BaseColumns {
+ static final String TABLE_NAME = "snapshots";
+
+ /**
+ * The version number of the snapshot.
+ */
+ static final String COLUMN_NAME_VERSION = "version";
+
+ /**
+ * The ID of the user whose keystore was snapshotted.
+ */
+ static final String COLUMN_NAME_USER_ID = "user_id";
+
+ /**
+ * The UID of the app that owns the snapshot (i.e., the recovery agent).
+ */
+ static final String COLUMN_NAME_UID = "uid";
+
+ /**
+ * The maximum number of attempts allowed to attempt to decrypt the recovery key.
+ */
+ static final String COLUMN_NAME_MAX_ATTEMPTS = "max_attempts";
+
+ /**
+ * The ID of the counter in the trusted hardware module.
+ */
+ static final String COLUMN_NAME_COUNTER_ID = "counter_id";
+
+ /**
+ * Server parameters used to help identify the device (during recovery).
+ */
+ static final String SERVER_PARAMS = "server_params";
+
+ /**
+ * The public key of the trusted hardware module. This key has been used to encrypt the
+ * snapshot, to ensure that it can only be read by the trusted module.
+ */
+ static final String TRUSTED_HARDWARE_PUBLIC_KEY = "thm_public_key";
+
+ /**
+ * {@link java.security.cert.CertPath} signing the trusted hardware module to whose public
+ * key this snapshot is encrypted.
+ */
+ static final String CERT_PATH = "cert_path";
+
+ /**
+ * The recovery key, encrypted with the user's lock screen and the trusted hardware module's
+ * public key.
+ */
+ static final String ENCRYPTED_RECOVERY_KEY = "encrypted_recovery_key";
+ }
+
+ /**
+ * Table holding encrypted keys belonging to a particular snapshot.
+ */
+ static class SnapshotKeysEntry implements BaseColumns {
+ static final String TABLE_NAME = "snapshot_keys";
+
+ /**
+ * ID of the associated snapshot entry in {@link SnapshotsEntry}.
+ */
+ static final String COLUMN_NAME_SNAPSHOT_ID = "snapshot_id";
+
+ /**
+ * Alias of the key.
+ */
+ static final String COLUMN_NAME_ALIAS = "alias";
+
+ /**
+ * Key material, encrypted with the recovery key from the snapshot.
+ */
+ static final String COLUMN_NAME_ENCRYPTED_BYTES = "encrypted_key_bytes";
+ }
+
+ /**
+ * A layer of protection associated with a snapshot.
+ */
+ static class SnapshotProtectionParams implements BaseColumns {
+ static final String TABLE_NAME = "snapshot_protection_params";
+
+ /**
+ * ID of the associated snapshot entry in {@link SnapshotsEntry}.
+ */
+ static final String COLUMN_NAME_SNAPSHOT_ID = "snapshot_id";
+
+ /**
+ * Type of secret used to generate recovery key. One of
+ * {@link android.security.keystore.recovery.KeyChainProtectionParams#TYPE_LOCKSCREEN} or
+ * {@link android.security.keystore.recovery.KeyChainProtectionParams#TYPE_CUSTOM_PASSWORD}.
+ */
+ static final String COLUMN_NAME_SECRET_TYPE = "secret_type";
+
+ /**
+ * If a lock screen, the type of UI used. One of
+ * {@link android.security.keystore.recovery.KeyChainProtectionParams#UI_FORMAT_PATTERN},
+ * {@link android.security.keystore.recovery.KeyChainProtectionParams#UI_FORMAT_PIN}, or
+ * {@link android.security.keystore.recovery.KeyChainProtectionParams#UI_FORMAT_PASSWORD}.
+ */
+ static final String COLUMN_NAME_LOCKSCREEN_UI_TYPE = "lock_screen_ui_type";
+
+ /**
+ * The algorithm used to derive cryptographic material from the key and salt. One of
+ * {@link android.security.keystore.recovery.KeyDerivationParams#ALGORITHM_SHA256} or
+ * {@link android.security.keystore.recovery.KeyDerivationParams#ALGORITHM_ARGON2ID}.
+ */
+ static final String COLUMN_NAME_KEY_DERIVATION_ALGORITHM = "key_derivation_algorithm";
+
+ /**
+ * The salt used along with the secret to generate cryptographic material.
+ */
+ static final String COLUMN_NAME_KEY_DERIVATION_SALT = "key_derivation_salt";
+ }
+
+ /**
* Recoverable KeyStore metadata for a specific user profile.
*/
static class UserMetadataEntry implements BaseColumns {
diff --git a/services/core/java/com/android/server/notification/ValidateNotificationPeople.java b/services/core/java/com/android/server/notification/ValidateNotificationPeople.java
index 896480f..c0c66b2 100644
--- a/services/core/java/com/android/server/notification/ValidateNotificationPeople.java
+++ b/services/core/java/com/android/server/notification/ValidateNotificationPeople.java
@@ -16,6 +16,7 @@
package com.android.server.notification;
+import android.annotation.Nullable;
import android.app.Notification;
import android.content.Context;
import android.content.pm.PackageManager;
@@ -45,8 +46,6 @@
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
-import android.os.SystemClock;
-
/**
* This {@link NotificationSignalExtractor} attempts to validate
* people references. Also elevates the priority of real people.
@@ -231,7 +230,6 @@
private PeopleRankingReconsideration validatePeople(Context context, String key, Bundle extras,
List<String> peopleOverride, float[] affinityOut) {
- long start = SystemClock.elapsedRealtime();
float affinity = NONE;
if (extras == null) {
return null;
@@ -239,7 +237,7 @@
final Set<String> people = new ArraySet<>(peopleOverride);
final String[] notificationPeople = getExtraPeople(extras);
if (notificationPeople != null ) {
- people.addAll(Arrays.asList(getExtraPeople(extras)));
+ people.addAll(Arrays.asList(notificationPeople));
}
if (VERBOSE) Slog.i(TAG, "Validating: " + key + " for " + context.getUserId());
@@ -283,7 +281,31 @@
// VisibleForTesting
public static String[] getExtraPeople(Bundle extras) {
- Object people = extras.get(Notification.EXTRA_PEOPLE_LIST);
+ String[] peopleList = getExtraPeopleForKey(extras, Notification.EXTRA_PEOPLE_LIST);
+ String[] legacyPeople = getExtraPeopleForKey(extras, Notification.EXTRA_PEOPLE);
+ return combineLists(legacyPeople, peopleList);
+ }
+
+ private static String[] combineLists(String[] first, String[] second) {
+ if (first == null) {
+ return second;
+ }
+ if (second == null) {
+ return first;
+ }
+ ArraySet<String> people = new ArraySet<>(first.length + second.length);
+ for (String person: first) {
+ people.add(person);
+ }
+ for (String person: second) {
+ people.add(person);
+ }
+ return (String[]) people.toArray();
+ }
+
+ @Nullable
+ private static String[] getExtraPeopleForKey(Bundle extras, String key) {
+ Object people = extras.get(key);
if (people instanceof String[]) {
return (String[]) people;
}
@@ -458,7 +480,6 @@
@Override
public void work() {
- long start = SystemClock.elapsedRealtime();
if (VERBOSE) Slog.i(TAG, "Executing: validation for: " + mKey);
long timeStartMs = System.currentTimeMillis();
for (final String handle: mPendingLookups) {
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 256fb42..7a4cd5f 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -176,6 +176,7 @@
import android.content.pm.PackageParser.PackageParserException;
import android.content.pm.PackageParser.ParseFlags;
import android.content.pm.PackageParser.ServiceIntentInfo;
+import android.content.pm.PackageParser.SigningDetails;
import android.content.pm.PackageParser.SigningDetails.SignatureSchemeVersion;
import android.content.pm.PackageStats;
import android.content.pm.PackageUserState;
@@ -23320,6 +23321,39 @@
}
@Override
+ public boolean isDataRestoreSafe(byte[] restoringFromSigHash, String packageName) {
+ SigningDetails sd = getSigningDetails(packageName);
+ if (sd == null) {
+ return false;
+ }
+ return sd.hasSha256Certificate(restoringFromSigHash,
+ SigningDetails.CertCapabilities.INSTALLED_DATA);
+ }
+
+ @Override
+ public boolean isDataRestoreSafe(Signature restoringFromSig, String packageName) {
+ SigningDetails sd = getSigningDetails(packageName);
+ if (sd == null) {
+ return false;
+ }
+ return sd.hasCertificate(restoringFromSig,
+ SigningDetails.CertCapabilities.INSTALLED_DATA);
+ }
+
+ private SigningDetails getSigningDetails(String packageName) {
+ synchronized (mPackages) {
+ if (packageName == null) {
+ return null;
+ }
+ PackageParser.Package p = mPackages.get(packageName);
+ if (p == null) {
+ return null;
+ }
+ return p.mSigningDetails;
+ }
+ }
+
+ @Override
public int getPermissionFlagsTEMP(String permName, String packageName, int userId) {
return PackageManagerService.this.getPermissionFlags(permName, packageName, userId);
}
@@ -23556,6 +23590,11 @@
}
@Override
+ public ComponentName getDefaultHomeActivity(int userId) {
+ return PackageManagerService.this.getDefaultHomeActivity(userId);
+ }
+
+ @Override
public void setDeviceAndProfileOwnerPackages(
int deviceOwnerUserId, String deviceOwnerPackage,
SparseArray<String> profileOwnerPackages) {
diff --git a/services/core/java/com/android/server/pm/ShortcutPackageInfo.java b/services/core/java/com/android/server/pm/ShortcutPackageInfo.java
index 3d37229..f5edae0 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackageInfo.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackageInfo.java
@@ -18,10 +18,12 @@
import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.content.pm.PackageInfo;
+import android.content.pm.PackageManagerInternal;
import android.content.pm.ShortcutInfo;
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.LocalServices;
import com.android.server.backup.BackupUtils;
import libcore.util.HexEncoding;
@@ -136,7 +138,8 @@
//@DisabledReason
public int canRestoreTo(ShortcutService s, PackageInfo currentPackage, boolean anyVersionOkay) {
- if (!BackupUtils.signaturesMatch(mSigHashes, currentPackage)) {
+ PackageManagerInternal pmi = LocalServices.getService(PackageManagerInternal.class);
+ if (!BackupUtils.signaturesMatch(mSigHashes, currentPackage, pmi)) {
Slog.w(TAG, "Can't restore: Package signature mismatch");
return ShortcutInfo.DISABLED_REASON_SIGNATURE_MISMATCH;
}
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 076f81f..ca6f53a 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -3125,7 +3125,8 @@
try {
return mIPackageManager.getPackageInfo(
packageName, PACKAGE_MATCH_FLAGS
- | (getSignatures ? PackageManager.GET_SIGNATURES : 0), userId);
+ | (getSignatures ? PackageManager.GET_SIGNING_CERTIFICATES : 0),
+ userId);
} catch (RemoteException e) {
// Shouldn't happen.
Slog.wtf(TAG, "RemoteException", e);
diff --git a/services/core/java/com/android/server/power/BatterySaverPolicy.java b/services/core/java/com/android/server/power/BatterySaverPolicy.java
index 08dc97e..16336b3 100644
--- a/services/core/java/com/android/server/power/BatterySaverPolicy.java
+++ b/services/core/java/com/android/server/power/BatterySaverPolicy.java
@@ -68,6 +68,7 @@
private static final String KEY_FORCE_ALL_APPS_STANDBY = "force_all_apps_standby";
private static final String KEY_FORCE_BACKGROUND_CHECK = "force_background_check";
private static final String KEY_OPTIONAL_SENSORS_DISABLED = "optional_sensors_disabled";
+ private static final String KEY_AOD_DISABLED = "aod_disabled";
private static final String KEY_CPU_FREQ_INTERACTIVE = "cpufreq-i";
private static final String KEY_CPU_FREQ_NONINTERACTIVE = "cpufreq-n";
@@ -200,11 +201,17 @@
private boolean mForceBackgroundCheck;
/**
- * Weather to show non-essential sensors (e.g. edge sensors) or not.
+ * Whether to show non-essential sensors (e.g. edge sensors) or not.
*/
@GuardedBy("mLock")
private boolean mOptionalSensorsDisabled;
+ /**
+ * Whether AOD is enabled or not.
+ */
+ @GuardedBy("mLock")
+ private boolean mAodDisabled;
+
@GuardedBy("mLock")
private Context mContext;
@@ -339,6 +346,7 @@
mForceAllAppsStandby = parser.getBoolean(KEY_FORCE_ALL_APPS_STANDBY, true);
mForceBackgroundCheck = parser.getBoolean(KEY_FORCE_BACKGROUND_CHECK, true);
mOptionalSensorsDisabled = parser.getBoolean(KEY_OPTIONAL_SENSORS_DISABLED, true);
+ mAodDisabled = parser.getBoolean(KEY_AOD_DISABLED, true);
// Get default value from Settings.Secure
final int defaultGpsMode = Settings.Secure.getInt(mContentResolver, SECURE_KEY_GPS_MODE,
@@ -375,6 +383,7 @@
if (mLaunchBoostDisabled) sb.append("l");
if (mOptionalSensorsDisabled) sb.append("S");
+ if (mAodDisabled) sb.append("o");
sb.append(mGpsMode);
@@ -437,6 +446,9 @@
case ServiceType.OPTIONAL_SENSORS:
return builder.setBatterySaverEnabled(mOptionalSensorsDisabled)
.build();
+ case ServiceType.AOD:
+ return builder.setBatterySaverEnabled(mAodDisabled)
+ .build();
default:
return builder.setBatterySaverEnabled(realMode)
.build();
@@ -491,6 +503,7 @@
pw.println(" " + KEY_FORCE_ALL_APPS_STANDBY + "=" + mForceAllAppsStandby);
pw.println(" " + KEY_FORCE_BACKGROUND_CHECK + "=" + mForceBackgroundCheck);
pw.println(" " + KEY_OPTIONAL_SENSORS_DISABLED + "=" + mOptionalSensorsDisabled);
+ pw.println(" " + KEY_AOD_DISABLED + "=" + mAodDisabled);
pw.println();
pw.print(" Interactive File values:\n");
diff --git a/services/core/java/com/android/server/stats/StatsCompanionService.java b/services/core/java/com/android/server/stats/StatsCompanionService.java
index 95c30d1..6e017cd 100644
--- a/services/core/java/com/android/server/stats/StatsCompanionService.java
+++ b/services/core/java/com/android/server/stats/StatsCompanionService.java
@@ -100,6 +100,7 @@
private final PendingIntent mAnomalyAlarmIntent;
private final PendingIntent mPullingAlarmIntent;
+ private final PendingIntent mPeriodicAlarmIntent;
private final BroadcastReceiver mAppUpdateReceiver;
private final BroadcastReceiver mUserUpdateReceiver;
private final ShutdownEventReceiver mShutdownEventReceiver;
@@ -123,6 +124,8 @@
new Intent(mContext, AnomalyAlarmReceiver.class), 0);
mPullingAlarmIntent = PendingIntent.getBroadcast(
mContext, 0, new Intent(mContext, PullingAlarmReceiver.class), 0);
+ mPeriodicAlarmIntent = PendingIntent.getBroadcast(
+ mContext, 0, new Intent(mContext, PeriodicAlarmReceiver.class), 0);
mAppUpdateReceiver = new AppUpdateReceiver();
mUserUpdateReceiver = new BroadcastReceiver() {
@Override
@@ -329,7 +332,28 @@
}
}
- private final static class ShutdownEventReceiver extends BroadcastReceiver {
+ public final static class PeriodicAlarmReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (DEBUG)
+ Slog.d(TAG, "Time to poll something.");
+ synchronized (sStatsdLock) {
+ if (sStatsd == null) {
+ Slog.w(TAG, "Could not access statsd to inform it of periodic alarm firing.");
+ return;
+ }
+ try {
+ // Two-way call to statsd to retain AlarmManager wakelock
+ sStatsd.informAlarmForSubscriberTriggeringFired();
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to inform statsd of periodic alarm firing.", e);
+ }
+ }
+ // AlarmManager releases its own wakelock here.
+ }
+ }
+
+ public final static class ShutdownEventReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
/**
@@ -385,6 +409,35 @@
}
@Override // Binder call
+ public void setAlarmForSubscriberTriggering(long timestampMs) {
+ enforceCallingPermission();
+ if (DEBUG)
+ Slog.d(TAG, "Setting periodic alarm at " + timestampMs);
+ final long callingToken = Binder.clearCallingIdentity();
+ try {
+ // using ELAPSED_REALTIME, not ELAPSED_REALTIME_WAKEUP, so if device is asleep, will
+ // only fire when it awakens.
+ // This alarm is inexact, leaving its exactness completely up to the OS optimizations.
+ mAlarmManager.set(AlarmManager.ELAPSED_REALTIME, timestampMs, mPeriodicAlarmIntent);
+ } finally {
+ Binder.restoreCallingIdentity(callingToken);
+ }
+ }
+
+ @Override // Binder call
+ public void cancelAlarmForSubscriberTriggering() {
+ enforceCallingPermission();
+ if (DEBUG)
+ Slog.d(TAG, "Cancelling periodic alarm");
+ final long callingToken = Binder.clearCallingIdentity();
+ try {
+ mAlarmManager.cancel(mPeriodicAlarmIntent);
+ } finally {
+ Binder.restoreCallingIdentity(callingToken);
+ }
+ }
+
+ @Override // Binder call
public void setPullingAlarms(long timestampMs, long intervalMs) {
enforceCallingPermission();
if (DEBUG)
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index 277a04b..c2cc7c9 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -1620,7 +1620,15 @@
@Override
public SurfaceControl getAnimationLeashParent() {
- return getAppAnimationLayer();
+ // All normal app transitions take place in an animation layer which is below the pinned
+ // stack but may be above the parent stacks of the given animating apps.
+ // For transitions in the pinned stack (menu activity) we just let them occur as a child
+ // of the pinned stack.
+ if (!inPinnedWindowingMode()) {
+ return getAppAnimationLayer();
+ } else {
+ return getStack().getSurfaceControl();
+ }
}
boolean applyAnimationLocked(WindowManager.LayoutParams lp, int transit, boolean enter,
@@ -1709,6 +1717,10 @@
frame.set(win.mFrame);
} else if (win.isLetterboxedAppWindow()) {
frame.set(getTask().getBounds());
+ } else if (win.isDockedResizing()) {
+ // If we are animating while docked resizing, then use the stack bounds as the
+ // animation target (which will be different than the task bounds)
+ frame.set(getTask().getParent().getBounds());
} else {
frame.set(win.mContainingFrame);
}
@@ -1763,10 +1775,18 @@
@Override
public void onAnimationLeashCreated(Transaction t, SurfaceControl leash) {
-
// The leash is parented to the animation layer. We need to preserve the z-order by using
// the prefix order index, but we boost if necessary.
- int layer = getPrefixOrderIndex();
+ int layer = 0;
+ if (!inPinnedWindowingMode()) {
+ layer = getPrefixOrderIndex();
+ } else {
+ // Pinned stacks have animations take place within themselves rather than an animation
+ // layer so we need to preserve the order relative to the stack (e.g. the order of our
+ // task/parent).
+ layer = getParent().getPrefixOrderIndex();
+ }
+
if (mNeedsZBoost) {
layer += Z_BOOST_BASE;
}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 75a6338..19c634a 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -3583,7 +3583,7 @@
if (s.inSplitScreenWindowingMode() && mSplitScreenDividerAnchor != null) {
t.setLayer(mSplitScreenDividerAnchor, layer++);
}
- if (s.isSelfOrChildAnimating()) {
+ if (s.isAppAnimating() && state != ALWAYS_ON_TOP_STATE) {
// Ensure the animation layer ends up above the
// highest animating stack and no higher.
layerForAnimationLayer = layer++;
@@ -3632,6 +3632,11 @@
super(name, service);
}
+ @Override
+ void assignChildLayers(SurfaceControl.Transaction t) {
+ assignChildLayers(t, null /* imeContainer */);
+ }
+
void assignChildLayers(SurfaceControl.Transaction t, WindowContainer imeContainer) {
boolean needAssignIme = imeContainer != null
&& imeContainer.getSurfaceControl() != null;
diff --git a/services/core/java/com/android/server/wm/DisplayWindowController.java b/services/core/java/com/android/server/wm/DisplayWindowController.java
index 0e12838..e3e4a46 100644
--- a/services/core/java/com/android/server/wm/DisplayWindowController.java
+++ b/services/core/java/com/android/server/wm/DisplayWindowController.java
@@ -50,7 +50,9 @@
}
if (mContainer == null) {
- throw new IllegalArgumentException("Trying to add displayId=" + displayId);
+ throw new IllegalArgumentException("Trying to add displayId=" + displayId
+ + " display=" + display
+ + " dc=" + mRoot.getDisplayContent(displayId));
}
}
}
diff --git a/services/core/java/com/android/server/wm/DockedStackDividerController.java b/services/core/java/com/android/server/wm/DockedStackDividerController.java
index 46c59c5..1f1efc4 100644
--- a/services/core/java/com/android/server/wm/DockedStackDividerController.java
+++ b/services/core/java/com/android/server/wm/DockedStackDividerController.java
@@ -620,7 +620,12 @@
if (wasMinimized && mMinimizedDock && containsAppInDockedStack(openingApps)
&& appTransition != TRANSIT_NONE &&
!AppTransition.isKeyguardGoingAwayTransit(appTransition)) {
- mService.showRecentApps();
+ if (mService.mAmInternal.isRecentsComponentHomeActivity(mService.mCurrentUserId)) {
+ // When the home activity is the recents component and we are already minimized,
+ // then there is nothing to do here since home is already visible
+ } else {
+ mService.showRecentApps();
+ }
}
}
@@ -641,7 +646,7 @@
return mMinimizedDock;
}
- private void checkMinimizeChanged(boolean animate) {
+ void checkMinimizeChanged(boolean animate) {
if (mDisplayContent.getSplitScreenPrimaryStackIgnoringVisibility() == null) {
return;
}
@@ -693,7 +698,7 @@
final boolean imeChanged = clearImeAdjustAnimation();
boolean minimizedChange = false;
if (isHomeStackResizable()) {
- notifyDockedStackMinimizedChanged(minimizedDock, true /* animate */,
+ notifyDockedStackMinimizedChanged(minimizedDock, animate,
true /* isHomeStackResizable */);
minimizedChange = true;
} else {
diff --git a/services/core/java/com/android/server/wm/RemoteAnimationController.java b/services/core/java/com/android/server/wm/RemoteAnimationController.java
index ed6e606..e4bb043 100644
--- a/services/core/java/com/android/server/wm/RemoteAnimationController.java
+++ b/services/core/java/com/android/server/wm/RemoteAnimationController.java
@@ -103,6 +103,7 @@
onAnimationFinished();
}
});
+ sendRunningRemoteAnimation(true);
}
private RemoteAnimationTarget[] createAnimations() {
@@ -131,6 +132,7 @@
mService.closeSurfaceTransaction("RemoteAnimationController#finished");
}
}
+ sendRunningRemoteAnimation(false);
}
private void invokeAnimationCancelled() {
@@ -148,6 +150,14 @@
}
}
+ private void sendRunningRemoteAnimation(boolean running) {
+ final int pid = mRemoteAnimationAdapter.getCallingPid();
+ if (pid == 0) {
+ throw new RuntimeException("Calling pid of remote animation was null");
+ }
+ mService.sendSetRunningRemoteAnimation(pid, running);
+ }
+
private static final class FinishedCallback extends IRemoteAnimationFinishedCallback.Stub {
RemoteAnimationController mOuter;
@@ -251,6 +261,7 @@
mHandler.removeCallbacks(mTimeoutRunnable);
releaseFinishedCallback();
invokeAnimationCancelled();
+ sendRunningRemoteAnimation(false);
}
}
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 1f7caff..42f6065 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -406,6 +406,10 @@
}
break;
default:
+ // TODO: Removing the child before reinserting requires the caller to provide a
+ // position that takes into account the removed child (if the index of the
+ // child < position, then the position should be adjusted). We should consider
+ // doing this adjustment here and remove any adjustments in the callers.
mChildren.remove(child);
mChildren.add(position, child);
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 0d9a37a..8b8a6d3 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -2782,6 +2782,13 @@
mDockedStackCreateBounds = bounds;
}
+ public void checkSplitScreenMinimizedChanged(boolean animate) {
+ synchronized (mWindowMap) {
+ final DisplayContent displayContent = getDefaultDisplayContentLocked();
+ displayContent.getDockedDividerController().checkMinimizeChanged(animate);
+ }
+ }
+
public boolean isValidPictureInPictureAspectRatio(int displayId, float aspectRatio) {
final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
return displayContent.getPinnedStackController().isValidPictureInPictureAspectRatio(
@@ -4599,6 +4606,7 @@
public static final int NOTIFY_KEYGUARD_FLAGS_CHANGED = 56;
public static final int NOTIFY_KEYGUARD_TRUSTED_CHANGED = 57;
public static final int SET_HAS_OVERLAY_UI = 58;
+ public static final int SET_RUNNING_REMOTE_ANIMATION = 59;
/**
* Used to denote that an integer field in a message will not be used.
@@ -5013,6 +5021,10 @@
mAmInternal.setHasOverlayUi(msg.arg1, msg.arg2 == 1);
}
break;
+ case SET_RUNNING_REMOTE_ANIMATION: {
+ mAmInternal.setRunningRemoteAnimation(msg.arg1, msg.arg2 == 1);
+ }
+ break;
}
if (DEBUG_WINDOW_TRACE) {
Slog.v(TAG_WM, "handleMessage: exit");
@@ -7441,5 +7453,10 @@
SurfaceControl.Builder makeSurfaceBuilder(SurfaceSession s) {
return mSurfaceBuilderFactory.make(s);
}
+
+ void sendSetRunningRemoteAnimation(int pid, boolean runningRemoteAnimation) {
+ mH.obtainMessage(H.SET_RUNNING_REMOTE_ANIMATION, pid, runningRemoteAnimation ? 1 : 0)
+ .sendToTarget();
+ }
}
diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
index 66c7293..286cc49 100644
--- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
+++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
@@ -21,6 +21,7 @@
import static android.app.ActivityManagerInternal.APP_TRANSITION_SPLASH_SCREEN;
import static android.app.ActivityManagerInternal.APP_TRANSITION_WINDOWS_DRAWN;
+import static android.view.WindowManager.TRANSIT_DOCK_TASK_FROM_RECENTS;
import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_CONFIG;
import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT;
import static android.view.WindowManager.TRANSIT_ACTIVITY_CLOSE;
@@ -608,6 +609,10 @@
if (transit == TRANSIT_NONE) {
return TRANSIT_NONE;
}
+ // Never update the transition for the wallpaper if we are just docking from recents
+ if (transit == TRANSIT_DOCK_TASK_FROM_RECENTS) {
+ return TRANSIT_DOCK_TASK_FROM_RECENTS;
+ }
// if wallpaper is animating in or out set oldWallpaper to null else to wallpaper
final WindowState wallpaperTarget = mWallpaperControllerLocked.getWallpaperTarget();
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
index 3557dc9..4020a52 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
@@ -172,4 +172,8 @@
public long forceSecurityLogs() {
return 0;
}
+
+ @Override
+ public void setDefaultSmsApplication(ComponentName admin, String packageName) {
+ }
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 0a6ff6d..6a468b1 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -160,6 +160,7 @@
import android.os.RemoteCallback;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.ServiceSpecificException;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
@@ -206,6 +207,7 @@
import com.android.internal.notification.SystemNotificationChannels;
import com.android.internal.os.BackgroundThread;
import com.android.internal.statusbar.IStatusBarService;
+import com.android.internal.telephony.SmsApplication;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.FastXmlSerializer;
import com.android.internal.util.FunctionalUtils.ThrowingRunnable;
@@ -219,6 +221,7 @@
import com.android.server.devicepolicy.DevicePolicyManagerService.ActiveAdmin.TrustAgentInfo;
import com.android.server.net.NetworkPolicyManagerInternal;
import com.android.server.pm.UserRestrictionsUtils;
+import com.android.server.storage.DeviceStorageMonitorInternal;
import com.google.android.collect.Sets;
@@ -8217,6 +8220,16 @@
}
@Override
+ public void setDefaultSmsApplication(ComponentName admin, String packageName) {
+ Preconditions.checkNotNull(admin, "ComponentName is null");
+ synchronized (this) {
+ getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
+ }
+ mInjector.binderWithCleanCallingIdentity(() ->
+ SmsApplication.setDefaultApplication(packageName, mContext));
+ }
+
+ @Override
public boolean setApplicationRestrictionsManagingPackage(ComponentName admin,
String packageName) {
try {
@@ -8873,13 +8886,40 @@
final boolean demo = (flags & DevicePolicyManager.MAKE_USER_DEMO) != 0
&& UserManager.isDeviceInDemoMode(mContext);
final boolean leaveAllSystemAppsEnabled = (flags & LEAVE_ALL_SYSTEM_APPS_ENABLED) != 0;
+ final int targetSdkVersion;
+
// Create user.
UserHandle user = null;
synchronized (this) {
getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
+ final int callingUid = mInjector.binderGetCallingUid();
final long id = mInjector.binderClearCallingIdentity();
try {
+ targetSdkVersion = mInjector.getPackageManagerInternal().getUidTargetSdkVersion(
+ callingUid);
+
+ // Return detail error code for checks inside
+ // UserManagerService.createUserInternalUnchecked.
+ DeviceStorageMonitorInternal deviceStorageMonitorInternal =
+ LocalServices.getService(DeviceStorageMonitorInternal.class);
+ if (deviceStorageMonitorInternal.isMemoryLow()) {
+ if (targetSdkVersion >= Build.VERSION_CODES.P) {
+ throw new ServiceSpecificException(
+ UserManager.USER_OPERATION_ERROR_LOW_STORAGE, "low device storage");
+ } else {
+ return null;
+ }
+ }
+ if (!mUserManager.canAddMoreUsers()) {
+ if (targetSdkVersion >= Build.VERSION_CODES.P) {
+ throw new ServiceSpecificException(
+ UserManager.USER_OPERATION_ERROR_MAX_USERS, "user limit reached");
+ } else {
+ return null;
+ }
+ }
+
int userInfoFlags = 0;
if (ephemeral) {
userInfoFlags |= UserInfo.FLAG_EPHEMERAL;
@@ -8903,7 +8943,12 @@
}
}
if (user == null) {
- return null;
+ if (targetSdkVersion >= Build.VERSION_CODES.P) {
+ throw new ServiceSpecificException(UserManager.USER_OPERATION_ERROR_UNKNOWN,
+ "failed to create user");
+ } else {
+ return null;
+ }
}
final int userHandle = user.getIdentifier();
@@ -8949,7 +8994,12 @@
return user;
} catch (Throwable re) {
mUserManager.removeUser(userHandle);
- return null;
+ if (targetSdkVersion >= Build.VERSION_CODES.P) {
+ throw new ServiceSpecificException(UserManager.USER_OPERATION_ERROR_UNKNOWN,
+ re.getMessage());
+ } else {
+ return null;
+ }
} finally {
mInjector.binderRestoreCallingIdentity(id);
}
@@ -9030,24 +9080,24 @@
final int userId = userHandle.getIdentifier();
if (isManagedProfile(userId)) {
Log.w(LOG_TAG, "Managed profile cannot be started in background");
- return DevicePolicyManager.USER_OPERATION_ERROR_MANAGED_PROFILE;
+ return UserManager.USER_OPERATION_ERROR_MANAGED_PROFILE;
}
final long id = mInjector.binderClearCallingIdentity();
try {
if (!mInjector.getActivityManagerInternal().canStartMoreUsers()) {
Log.w(LOG_TAG, "Cannot start more users in background");
- return DevicePolicyManager.USER_OPERATION_ERROR_MAX_RUNNING_USERS;
+ return UserManager.USER_OPERATION_ERROR_MAX_RUNNING_USERS;
}
if (mInjector.getIActivityManager().startUserInBackground(userId)) {
- return DevicePolicyManager.USER_OPERATION_SUCCESS;
+ return UserManager.USER_OPERATION_SUCCESS;
} else {
- return DevicePolicyManager.USER_OPERATION_ERROR_UNKNOWN;
+ return UserManager.USER_OPERATION_ERROR_UNKNOWN;
}
} catch (RemoteException e) {
// Same process, should not happen.
- return DevicePolicyManager.USER_OPERATION_ERROR_UNKNOWN;
+ return UserManager.USER_OPERATION_ERROR_UNKNOWN;
} finally {
mInjector.binderRestoreCallingIdentity(id);
}
@@ -9065,7 +9115,7 @@
final int userId = userHandle.getIdentifier();
if (isManagedProfile(userId)) {
Log.w(LOG_TAG, "Managed profile cannot be stopped");
- return DevicePolicyManager.USER_OPERATION_ERROR_MANAGED_PROFILE;
+ return UserManager.USER_OPERATION_ERROR_MANAGED_PROFILE;
}
return stopUserUnchecked(userId);
@@ -9086,7 +9136,7 @@
if (isManagedProfile(callingUserId)) {
Log.w(LOG_TAG, "Managed profile cannot be logout");
- return DevicePolicyManager.USER_OPERATION_ERROR_MANAGED_PROFILE;
+ return UserManager.USER_OPERATION_ERROR_MANAGED_PROFILE;
}
final long id = mInjector.binderClearCallingIdentity();
@@ -9094,11 +9144,11 @@
if (!mInjector.getIActivityManager().switchUser(UserHandle.USER_SYSTEM)) {
Log.w(LOG_TAG, "Failed to switch to primary user");
// This should never happen as target user is UserHandle.USER_SYSTEM
- return DevicePolicyManager.USER_OPERATION_ERROR_UNKNOWN;
+ return UserManager.USER_OPERATION_ERROR_UNKNOWN;
}
} catch (RemoteException e) {
// Same process, should not happen.
- return DevicePolicyManager.USER_OPERATION_ERROR_UNKNOWN;
+ return UserManager.USER_OPERATION_ERROR_UNKNOWN;
} finally {
mInjector.binderRestoreCallingIdentity(id);
}
@@ -9111,15 +9161,15 @@
try {
switch (mInjector.getIActivityManager().stopUser(userId, true /*force*/, null)) {
case ActivityManager.USER_OP_SUCCESS:
- return DevicePolicyManager.USER_OPERATION_SUCCESS;
+ return UserManager.USER_OPERATION_SUCCESS;
case ActivityManager.USER_OP_IS_CURRENT:
- return DevicePolicyManager.USER_OPERATION_ERROR_CURRENT_USER;
+ return UserManager.USER_OPERATION_ERROR_CURRENT_USER;
default:
- return DevicePolicyManager.USER_OPERATION_ERROR_UNKNOWN;
+ return UserManager.USER_OPERATION_ERROR_UNKNOWN;
}
} catch (RemoteException e) {
// Same process, should not happen.
- return DevicePolicyManager.USER_OPERATION_ERROR_UNKNOWN;
+ return UserManager.USER_OPERATION_ERROR_UNKNOWN;
} finally {
mInjector.binderRestoreCallingIdentity(id);
}
diff --git a/services/robotests/src/com/android/server/backup/TransportManagerTest.java b/services/robotests/src/com/android/server/backup/TransportManagerTest.java
index 02514b8..6abd30c 100644
--- a/services/robotests/src/com/android/server/backup/TransportManagerTest.java
+++ b/services/robotests/src/com/android/server/backup/TransportManagerTest.java
@@ -42,6 +42,7 @@
import android.annotation.Nullable;
import android.app.backup.BackupManager;
+import android.app.backup.BackupTransport;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -86,12 +87,6 @@
private static final String PACKAGE_A = "some.package.a";
private static final String PACKAGE_B = "some.package.b";
- /**
- * GMSCore depends on this constant so we define it here on top of the definition in {@link
- * TransportManager} to verify this extra is passed
- */
- private static final String EXTRA_TRANSPORT_REGISTRATION = "transport_registration";
-
@Mock private OnTransportRegisteredListener mListener;
@Mock private TransportClientManager mTransportClientManager;
private TransportData mTransportA1;
@@ -210,7 +205,8 @@
verify(mTransportClientManager)
.getTransportClient(
eq(mTransportA1.getTransportComponent()),
- argThat(bundle -> bundle.getBoolean(EXTRA_TRANSPORT_REGISTRATION)),
+ argThat(bundle ->
+ bundle.getBoolean(BackupTransport.EXTRA_TRANSPORT_REGISTRATION)),
anyString());
}
diff --git a/services/robotests/src/com/android/server/backup/restore/ActiveRestoreSessionTest.java b/services/robotests/src/com/android/server/backup/restore/ActiveRestoreSessionTest.java
index 4ac00f0..c6a4f57 100644
--- a/services/robotests/src/com/android/server/backup/restore/ActiveRestoreSessionTest.java
+++ b/services/robotests/src/com/android/server/backup/restore/ActiveRestoreSessionTest.java
@@ -25,8 +25,10 @@
import static org.mockito.AdditionalMatchers.aryEq;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.doCallRealMethod;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.robolectric.Shadows.shadowOf;
@@ -39,6 +41,7 @@
import android.app.backup.RestoreSet;
import android.os.Looper;
import android.os.PowerManager;
+import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
import com.android.server.EventLogTags;
@@ -51,7 +54,9 @@
import com.android.server.testing.FrameworkRobolectricTestRunner;
import com.android.server.testing.SystemLoaderPackages;
import com.android.server.testing.shadows.ShadowEventLog;
+import com.android.server.testing.shadows.ShadowPerformUnifiedRestoreTask;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -62,8 +67,14 @@
import org.robolectric.shadows.ShadowApplication;
import org.robolectric.shadows.ShadowLooper;
+import java.util.ArrayDeque;
+
@RunWith(FrameworkRobolectricTestRunner.class)
-@Config(manifest = Config.NONE, sdk = 26, shadows = ShadowEventLog.class)
+@Config(
+ manifest = Config.NONE,
+ sdk = 26,
+ shadows = {ShadowEventLog.class, ShadowPerformUnifiedRestoreTask.class}
+)
@SystemLoaderPackages({"com.android.server.backup"})
@Presubmit
public class ActiveRestoreSessionTest {
@@ -78,6 +89,8 @@
private ShadowApplication mShadowApplication;
private PowerManager.WakeLock mWakeLock;
private TransportData mTransport;
+ private long mToken1;
+ private long mToken2;
private RestoreSet mRestoreSet1;
private RestoreSet mRestoreSet2;
@@ -87,8 +100,10 @@
mTransport = backupTransport();
- mRestoreSet1 = new RestoreSet("name1", "device1", 1L);
- mRestoreSet2 = new RestoreSet("name2", "device2", 2L);
+ mToken1 = 1L;
+ mRestoreSet1 = new RestoreSet("name1", "device1", mToken1);
+ mToken2 = 2L;
+ mRestoreSet2 = new RestoreSet("name2", "device2", mToken2);
Application application = RuntimeEnvironment.application;
mShadowApplication = shadowOf(application);
@@ -106,6 +121,12 @@
application.getPackageManager(),
backupHandler,
mWakeLock);
+ when(mBackupManagerService.getPendingRestores()).thenReturn(new ArrayDeque<>());
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ ShadowPerformUnifiedRestoreTask.reset();
}
@Test
@@ -193,12 +214,244 @@
assertThat(mWakeLock.isHeld()).isFalse();
}
+ @Test
+ public void testRestoreAll() throws Exception {
+ mShadowApplication.grantPermissions(android.Manifest.permission.BACKUP);
+ doCallRealMethod().when(mBackupManagerService).setRestoreInProgress(anyBoolean());
+ when(mBackupManagerService.isRestoreInProgress()).thenCallRealMethod();
+ TransportMock transportMock = setUpTransport(mTransport);
+ IRestoreSession restoreSession =
+ createActiveRestoreSessionWithRestoreSets(null, mTransport, mRestoreSet1);
+
+ int result = restoreSession.restoreAll(mToken1, mObserver, mMonitor);
+
+ mShadowBackupLooper.runToEndOfTasks();
+ assertThat(result).isEqualTo(0);
+ verify(mTransportManager)
+ .disposeOfTransportClient(eq(transportMock.transportClient), any());
+ assertThat(mWakeLock.isHeld()).isFalse();
+ assertThat(mBackupManagerService.isRestoreInProgress()).isFalse();
+ // Verify it created the task properly
+ ShadowPerformUnifiedRestoreTask shadowTask =
+ ShadowPerformUnifiedRestoreTask.getLastCreated();
+ assertThat(shadowTask.isFullSystemRestore()).isTrue();
+ assertThat(shadowTask.getFilterSet()).isNull();
+ assertThat(shadowTask.getPackage()).isNull();
+ }
+
+ @Test
+ public void testRestoreAll_whenNoRestoreSets() throws Exception {
+ mShadowApplication.grantPermissions(android.Manifest.permission.BACKUP);
+ setUpTransport(mTransport);
+ IRestoreSession restoreSession = createActiveRestoreSession(null, mTransport);
+
+ int result = restoreSession.restoreAll(mToken1, mObserver, mMonitor);
+
+ mShadowBackupLooper.runToEndOfTasks();
+ assertThat(result).isEqualTo(-1);
+ assertThat(ShadowPerformUnifiedRestoreTask.getLastCreated()).isNull();
+ }
+
+ @Test
+ public void testRestoreAll_whenSinglePackageSession() throws Exception {
+ mShadowApplication.grantPermissions(android.Manifest.permission.BACKUP);
+ setUpTransport(mTransport);
+ IRestoreSession restoreSession =
+ createActiveRestoreSessionWithRestoreSets(PACKAGE_1, mTransport, mRestoreSet1);
+
+ int result = restoreSession.restoreAll(mToken1, mObserver, mMonitor);
+
+ mShadowBackupLooper.runToEndOfTasks();
+ assertThat(result).isEqualTo(-1);
+ assertThat(ShadowPerformUnifiedRestoreTask.getLastCreated()).isNull();
+ }
+
+ @Test
+ public void testRestoreAll_whenSessionEnded() throws Exception {
+ mShadowApplication.grantPermissions(android.Manifest.permission.BACKUP);
+ setUpTransport(mTransport);
+ IRestoreSession restoreSession =
+ createActiveRestoreSessionWithRestoreSets(null, mTransport, mRestoreSet1);
+ restoreSession.endRestoreSession();
+ mShadowBackupLooper.runToEndOfTasks();
+
+ expectThrows(
+ IllegalStateException.class,
+ () -> restoreSession.restoreAll(mToken1, mObserver, mMonitor));
+ }
+
+ @Test
+ public void testRestoreAll_whenTransportNotRegistered() throws Exception {
+ mShadowApplication.grantPermissions(android.Manifest.permission.BACKUP);
+ setUpTransport(mTransport.unregistered());
+ IRestoreSession restoreSession =
+ createActiveRestoreSessionWithRestoreSets(null, mTransport, mRestoreSet1);
+
+ int result = restoreSession.restoreAll(mToken1, mObserver, mMonitor);
+
+ mShadowBackupLooper.runToEndOfTasks();
+ assertThat(result).isEqualTo(-1);
+ assertThat(ShadowPerformUnifiedRestoreTask.getLastCreated()).isNull();
+ }
+
+ @Test
+ public void testRestoreAll_whenRestoreInProgress_addsToPendingRestores() throws Exception {
+ mShadowApplication.grantPermissions(android.Manifest.permission.BACKUP);
+ setUpTransport(mTransport);
+ when(mBackupManagerService.isRestoreInProgress()).thenReturn(true);
+ IRestoreSession restoreSession =
+ createActiveRestoreSessionWithRestoreSets(null, mTransport, mRestoreSet1);
+
+ int result = restoreSession.restoreAll(mToken1, mObserver, mMonitor);
+
+ mShadowBackupLooper.runToEndOfTasks();
+ assertThat(result).isEqualTo(0);
+ assertThat(mBackupManagerService.getPendingRestores()).hasSize(1);
+ }
+
+ @Test
+ public void testRestoreSome_for2Packages() throws Exception {
+ mShadowApplication.grantPermissions(android.Manifest.permission.BACKUP);
+ TransportMock transportMock = setUpTransport(mTransport);
+ IRestoreSession restoreSession =
+ createActiveRestoreSessionWithRestoreSets(null, mTransport, mRestoreSet1);
+
+ int result =
+ restoreSession.restoreSome(
+ mToken1, mObserver, mMonitor, new String[] {PACKAGE_1, PACKAGE_2});
+
+ mShadowBackupLooper.runToEndOfTasks();
+ assertThat(result).isEqualTo(0);
+ verify(mTransportManager)
+ .disposeOfTransportClient(eq(transportMock.transportClient), any());
+ assertThat(mWakeLock.isHeld()).isFalse();
+ assertThat(mBackupManagerService.isRestoreInProgress()).isFalse();
+ ShadowPerformUnifiedRestoreTask shadowTask =
+ ShadowPerformUnifiedRestoreTask.getLastCreated();
+ assertThat(shadowTask.getFilterSet()).asList().containsExactly(PACKAGE_1, PACKAGE_2);
+ assertThat(shadowTask.getPackage()).isNull();
+ }
+
+ @Test
+ public void testRestoreSome_for2Packages_createsSystemRestoreTask() throws Exception {
+ mShadowApplication.grantPermissions(android.Manifest.permission.BACKUP);
+ setUpTransport(mTransport);
+ IRestoreSession restoreSession =
+ createActiveRestoreSessionWithRestoreSets(null, mTransport, mRestoreSet1);
+
+ restoreSession.restoreSome(
+ mToken1, mObserver, mMonitor, new String[] {PACKAGE_1, PACKAGE_2});
+
+ mShadowBackupLooper.runToEndOfTasks();
+ assertThat(ShadowPerformUnifiedRestoreTask.getLastCreated().isFullSystemRestore()).isTrue();
+ }
+
+ @Test
+ public void testRestoreSome_for1Package() throws Exception {
+ mShadowApplication.grantPermissions(android.Manifest.permission.BACKUP);
+ setUpTransport(mTransport);
+ IRestoreSession restoreSession =
+ createActiveRestoreSessionWithRestoreSets(null, mTransport, mRestoreSet1);
+
+ restoreSession.restoreSome(mToken1, mObserver, mMonitor, new String[] {PACKAGE_1});
+
+ mShadowBackupLooper.runToEndOfTasks();
+ ShadowPerformUnifiedRestoreTask shadowTask =
+ ShadowPerformUnifiedRestoreTask.getLastCreated();
+ assertThat(shadowTask.getFilterSet()).asList().containsExactly(PACKAGE_1);
+ assertThat(shadowTask.getPackage()).isNull();
+ }
+
+ @Test
+ public void testRestoreSome_for1Package_createsNonSystemRestoreTask() throws Exception {
+ mShadowApplication.grantPermissions(android.Manifest.permission.BACKUP);
+ setUpTransport(mTransport);
+ IRestoreSession restoreSession =
+ createActiveRestoreSessionWithRestoreSets(null, mTransport, mRestoreSet1);
+
+ restoreSession.restoreSome(mToken1, mObserver, mMonitor, new String[] {PACKAGE_1});
+
+ mShadowBackupLooper.runToEndOfTasks();
+ assertThat(ShadowPerformUnifiedRestoreTask.getLastCreated().isFullSystemRestore())
+ .isFalse();
+ }
+
+ @Test
+ public void testRestoreSome_whenNoRestoreSets() throws Exception {
+ mShadowApplication.grantPermissions(android.Manifest.permission.BACKUP);
+ setUpTransport(mTransport);
+ IRestoreSession restoreSession = createActiveRestoreSession(null, mTransport);
+
+ int result =
+ restoreSession.restoreSome(mToken1, mObserver, mMonitor, new String[] {PACKAGE_1});
+
+ mShadowBackupLooper.runToEndOfTasks();
+ assertThat(result).isEqualTo(-1);
+ assertThat(ShadowPerformUnifiedRestoreTask.getLastCreated()).isNull();
+ }
+
+ @Test
+ public void testRestoreSome_whenSinglePackageSession() throws Exception {
+ mShadowApplication.grantPermissions(android.Manifest.permission.BACKUP);
+ setUpTransport(mTransport);
+ IRestoreSession restoreSession =
+ createActiveRestoreSessionWithRestoreSets(PACKAGE_1, mTransport, mRestoreSet1);
+
+ int result =
+ restoreSession.restoreSome(mToken1, mObserver, mMonitor, new String[] {PACKAGE_2});
+
+ mShadowBackupLooper.runToEndOfTasks();
+ assertThat(result).isEqualTo(-1);
+ assertThat(ShadowPerformUnifiedRestoreTask.getLastCreated()).isNull();
+ }
+
+ @Test
+ public void testRestoreSome_whenSessionEnded() throws Exception {
+ mShadowApplication.grantPermissions(android.Manifest.permission.BACKUP);
+ setUpTransport(mTransport);
+ IRestoreSession restoreSession =
+ createActiveRestoreSessionWithRestoreSets(null, mTransport, mRestoreSet1);
+ restoreSession.endRestoreSession();
+ mShadowBackupLooper.runToEndOfTasks();
+
+ expectThrows(
+ IllegalStateException.class,
+ () ->
+ restoreSession.restoreSome(
+ mToken1, mObserver, mMonitor, new String[] {PACKAGE_1}));
+ }
+
+ @Test
+ public void testRestoreSome_whenTransportNotRegistered() throws Exception {
+ mShadowApplication.grantPermissions(android.Manifest.permission.BACKUP);
+ setUpTransport(mTransport.unregistered());
+ IRestoreSession restoreSession =
+ createActiveRestoreSessionWithRestoreSets(null, mTransport, mRestoreSet1);
+
+ int result =
+ restoreSession.restoreSome(mToken1, mObserver, mMonitor, new String[] {PACKAGE_1});
+
+ mShadowBackupLooper.runToEndOfTasks();
+ assertThat(result).isEqualTo(-1);
+ assertThat(ShadowPerformUnifiedRestoreTask.getLastCreated()).isNull();
+ }
+
private IRestoreSession createActiveRestoreSession(
String packageName, TransportData transport) {
return new ActiveRestoreSession(
mBackupManagerService, packageName, transport.transportName);
}
+ private IRestoreSession createActiveRestoreSessionWithRestoreSets(
+ String packageName, TransportData transport, RestoreSet... restoreSets)
+ throws RemoteException {
+ ActiveRestoreSession restoreSession =
+ new ActiveRestoreSession(
+ mBackupManagerService, packageName, transport.transportName);
+ restoreSession.setRestoreSets(restoreSets);
+ return restoreSession;
+ }
+
private TransportMock setUpTransport(TransportData transport) throws Exception {
return TransportTestUtils.setUpTransport(mTransportManager, transport);
}
diff --git a/services/robotests/src/com/android/server/backup/testing/TransportTestUtils.java b/services/robotests/src/com/android/server/backup/testing/TransportTestUtils.java
index 565c7e6..c00a61d 100644
--- a/services/robotests/src/com/android/server/backup/testing/TransportTestUtils.java
+++ b/services/robotests/src/com/android/server/backup/testing/TransportTestUtils.java
@@ -115,6 +115,7 @@
.thenReturn(transportDirName);
when(transportManager.getTransportDirName(eq(transportComponent)))
.thenReturn(transportDirName);
+ when(transportManager.isTransportRegistered(eq(transportName))).thenReturn(true);
// TODO: Mock rest of description methods
} else {
// Transport not registered
@@ -127,6 +128,7 @@
.thenThrow(TransportNotRegisteredException.class);
when(transportManager.getTransportDirName(eq(transportComponent)))
.thenThrow(TransportNotRegisteredException.class);
+ when(transportManager.isTransportRegistered(eq(transportName))).thenReturn(false);
}
return transportMock;
}
diff --git a/services/robotests/src/com/android/server/testing/shadows/ShadowPerformUnifiedRestoreTask.java b/services/robotests/src/com/android/server/testing/shadows/ShadowPerformUnifiedRestoreTask.java
new file mode 100644
index 0000000..0f93c7a
--- /dev/null
+++ b/services/robotests/src/com/android/server/testing/shadows/ShadowPerformUnifiedRestoreTask.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.testing.shadows;
+
+import android.annotation.Nullable;
+import android.app.backup.IBackupManagerMonitor;
+import android.app.backup.IRestoreObserver;
+import android.content.pm.PackageInfo;
+
+import com.android.server.backup.BackupManagerService;
+import com.android.server.backup.internal.OnTaskFinishedListener;
+import com.android.server.backup.restore.PerformUnifiedRestoreTask;
+import com.android.server.backup.transport.TransportClient;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+
+@Implements(PerformUnifiedRestoreTask.class)
+public class ShadowPerformUnifiedRestoreTask {
+ @Nullable private static ShadowPerformUnifiedRestoreTask sLastShadow;
+
+ /**
+ * Retrieves the shadow for the last {@link PerformUnifiedRestoreTask} object created.
+ *
+ * @return The shadow or {@code null} if no object created since last {@link #reset()}.
+ */
+ @Nullable
+ public static ShadowPerformUnifiedRestoreTask getLastCreated() {
+ return sLastShadow;
+ }
+
+ public static void reset() {
+ sLastShadow = null;
+ }
+
+ private BackupManagerService mBackupManagerService;
+ @Nullable private PackageInfo mPackage;
+ private boolean mIsFullSystemRestore;
+ @Nullable private String[] mFilterSet;
+ private OnTaskFinishedListener mListener;
+
+ @Implementation
+ public void __constructor__(
+ BackupManagerService backupManagerService,
+ TransportClient transportClient,
+ IRestoreObserver observer,
+ IBackupManagerMonitor monitor,
+ long restoreSetToken,
+ @Nullable PackageInfo targetPackage,
+ int pmToken,
+ boolean isFullSystemRestore,
+ @Nullable String[] filterSet,
+ OnTaskFinishedListener listener) {
+ mBackupManagerService = backupManagerService;
+ mPackage = targetPackage;
+ mIsFullSystemRestore = isFullSystemRestore;
+ mFilterSet = filterSet;
+ mListener = listener;
+ sLastShadow = this;
+ }
+
+ @Implementation
+ public void execute() {
+ mBackupManagerService.setRestoreInProgress(false);
+ mListener.onFinished("ShadowPerformUnifiedRestoreTask.execute()");
+ }
+
+ public PackageInfo getPackage() {
+ return mPackage;
+ }
+
+ public String[] getFilterSet() {
+ return mFilterSet;
+ }
+
+ public boolean isFullSystemRestore() {
+ return mIsFullSystemRestore;
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityRecordTests.java b/services/tests/servicestests/src/com/android/server/am/ActivityRecordTests.java
index bfc3133..fb1595e 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityRecordTests.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityRecordTests.java
@@ -204,20 +204,4 @@
verify(mService.mStackSupervisor, times(1)).canPlaceEntityOnDisplay(anyInt(), eq(expected),
anyInt(), anyInt(), eq(record.info));
}
-
- @Test
- public void testFinishingAfterDestroying() throws Exception {
- assertFalse(mActivity.finishing);
- mActivity.setState(DESTROYING, "testFinishingAfterDestroying");
- assertTrue(mActivity.isState(DESTROYING));
- assertTrue(mActivity.finishing);
- }
-
- @Test
- public void testFinishingAfterDestroyed() throws Exception {
- assertFalse(mActivity.finishing);
- mActivity.setState(DESTROYED, "testFinishingAfterDestroyed");
- assertTrue(mActivity.isState(DESTROYED));
- assertTrue(mActivity.finishing);
- }
}
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityStackTests.java b/services/tests/servicestests/src/com/android/server/am/ActivityStackTests.java
index ce3528b..c62820e 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityStackTests.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityStackTests.java
@@ -408,6 +408,10 @@
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
final TestActivityStack fullscreenStack2 = createStackForShouldBeVisibleTest(display,
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+ final TestActivityStack fullscreenStack3 = createStackForShouldBeVisibleTest(display,
+ WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+ final TestActivityStack fullscreenStack4 = createStackForShouldBeVisibleTest(display,
+ WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
final TestActivityStack homeStack = createStackForShouldBeVisibleTest(display,
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */);
@@ -415,6 +419,10 @@
assertTrue(display.getStackAboveHome() == fullscreenStack1);
display.moveHomeStackBehindStack(fullscreenStack2);
assertTrue(display.getStackAboveHome() == fullscreenStack2);
+ display.moveHomeStackBehindStack(fullscreenStack4);
+ assertTrue(display.getStackAboveHome() == fullscreenStack4);
+ display.moveHomeStackBehindStack(fullscreenStack2);
+ assertTrue(display.getStackAboveHome() == fullscreenStack2);
}
private <T extends ActivityStack> T createStackForShouldBeVisibleTest(
diff --git a/services/tests/servicestests/src/com/android/server/backup/utils/AppBackupUtilsTest.java b/services/tests/servicestests/src/com/android/server/backup/utils/AppBackupUtilsTest.java
index 86c83d6..8ccacb8 100644
--- a/services/tests/servicestests/src/com/android/server/backup/utils/AppBackupUtilsTest.java
+++ b/services/tests/servicestests/src/com/android/server/backup/utils/AppBackupUtilsTest.java
@@ -18,9 +18,14 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
import android.content.pm.Signature;
import android.os.Process;
import android.platform.test.annotations.Presubmit;
@@ -30,6 +35,7 @@
import com.android.server.backup.BackupManagerService;
import com.android.server.backup.testutils.PackageManagerStub;
+import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -45,7 +51,14 @@
private static final Signature SIGNATURE_3 = generateSignature((byte) 3);
private static final Signature SIGNATURE_4 = generateSignature((byte) 4);
- private final PackageManagerStub mPackageManagerStub = new PackageManagerStub();
+ private PackageManagerStub mPackageManagerStub;
+ private PackageManagerInternal mMockPackageManagerInternal;
+
+ @Before
+ public void setUp() throws Exception {
+ mPackageManagerStub = new PackageManagerStub();
+ mMockPackageManagerInternal = mock(PackageManagerInternal.class);
+ }
@Test
public void appIsEligibleForBackup_backupNotAllowed_returnsFalse() throws Exception {
@@ -358,7 +371,8 @@
@Test
public void signaturesMatch_targetIsNull_returnsFalse() throws Exception {
- boolean result = AppBackupUtils.signaturesMatch(new Signature[] {SIGNATURE_1}, null);
+ boolean result = AppBackupUtils.signaturesMatch(new Signature[] {SIGNATURE_1}, null,
+ mMockPackageManagerInternal);
assertThat(result).isFalse();
}
@@ -369,7 +383,8 @@
packageInfo.applicationInfo = new ApplicationInfo();
packageInfo.applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM;
- boolean result = AppBackupUtils.signaturesMatch(new Signature[0], packageInfo);
+ boolean result = AppBackupUtils.signaturesMatch(new Signature[0], packageInfo,
+ mMockPackageManagerInternal);
assertThat(result).isTrue();
}
@@ -378,10 +393,11 @@
public void signaturesMatch_disallowsUnsignedApps_storedSignatureNull_returnsFalse()
throws Exception {
PackageInfo packageInfo = new PackageInfo();
- packageInfo.signatures = new Signature[] {SIGNATURE_1};
+ packageInfo.signingCertificateHistory = new Signature[][] {{SIGNATURE_1}};
packageInfo.applicationInfo = new ApplicationInfo();
- boolean result = AppBackupUtils.signaturesMatch(null, packageInfo);
+ boolean result = AppBackupUtils.signaturesMatch(null, packageInfo,
+ mMockPackageManagerInternal);
assertThat(result).isFalse();
}
@@ -390,10 +406,11 @@
public void signaturesMatch_disallowsUnsignedApps_storedSignatureEmpty_returnsFalse()
throws Exception {
PackageInfo packageInfo = new PackageInfo();
- packageInfo.signatures = new Signature[] {SIGNATURE_1};
+ packageInfo.signingCertificateHistory = new Signature[][] {{SIGNATURE_1}};
packageInfo.applicationInfo = new ApplicationInfo();
- boolean result = AppBackupUtils.signaturesMatch(new Signature[0], packageInfo);
+ boolean result = AppBackupUtils.signaturesMatch(new Signature[0], packageInfo,
+ mMockPackageManagerInternal);
assertThat(result).isFalse();
}
@@ -404,11 +421,11 @@
signaturesMatch_disallowsUnsignedApps_targetSignatureEmpty_returnsFalse()
throws Exception {
PackageInfo packageInfo = new PackageInfo();
- packageInfo.signatures = new Signature[0];
+ packageInfo.signingCertificateHistory = new Signature[0][0];
packageInfo.applicationInfo = new ApplicationInfo();
- boolean result = AppBackupUtils.signaturesMatch(new Signature[] {SIGNATURE_1},
- packageInfo);
+ boolean result = AppBackupUtils.signaturesMatch(new Signature[] {SIGNATURE_1}, packageInfo,
+ mMockPackageManagerInternal);
assertThat(result).isFalse();
}
@@ -418,11 +435,11 @@
signaturesMatch_disallowsUnsignedApps_targetSignatureNull_returnsFalse()
throws Exception {
PackageInfo packageInfo = new PackageInfo();
- packageInfo.signatures = null;
+ packageInfo.signingCertificateHistory = null;
packageInfo.applicationInfo = new ApplicationInfo();
- boolean result = AppBackupUtils.signaturesMatch(new Signature[] {SIGNATURE_1},
- packageInfo);
+ boolean result = AppBackupUtils.signaturesMatch(new Signature[] {SIGNATURE_1}, packageInfo,
+ mMockPackageManagerInternal);
assertThat(result).isFalse();
}
@@ -431,10 +448,11 @@
public void signaturesMatch_disallowsUnsignedApps_bothSignaturesNull_returnsFalse()
throws Exception {
PackageInfo packageInfo = new PackageInfo();
- packageInfo.signatures = null;
+ packageInfo.signingCertificateHistory = null;
packageInfo.applicationInfo = new ApplicationInfo();
- boolean result = AppBackupUtils.signaturesMatch(null, packageInfo);
+ boolean result = AppBackupUtils.signaturesMatch(null, packageInfo,
+ mMockPackageManagerInternal);
assertThat(result).isFalse();
}
@@ -443,10 +461,11 @@
public void signaturesMatch_disallowsUnsignedApps_bothSignaturesEmpty_returnsFalse()
throws Exception {
PackageInfo packageInfo = new PackageInfo();
- packageInfo.signatures = new Signature[0];
+ packageInfo.signingCertificateHistory = new Signature[0][0];
packageInfo.applicationInfo = new ApplicationInfo();
- boolean result = AppBackupUtils.signaturesMatch(new Signature[0], packageInfo);
+ boolean result = AppBackupUtils.signaturesMatch(new Signature[0], packageInfo,
+ mMockPackageManagerInternal);
assertThat(result).isFalse();
}
@@ -458,11 +477,14 @@
Signature signature3Copy = new Signature(SIGNATURE_3.toByteArray());
PackageInfo packageInfo = new PackageInfo();
- packageInfo.signatures = new Signature[]{SIGNATURE_1, SIGNATURE_2, SIGNATURE_3};
+ packageInfo.signingCertificateHistory = new Signature[][] {
+ {SIGNATURE_1, SIGNATURE_2, SIGNATURE_3}
+ };
packageInfo.applicationInfo = new ApplicationInfo();
boolean result = AppBackupUtils.signaturesMatch(
- new Signature[]{signature3Copy, signature1Copy, signature2Copy}, packageInfo);
+ new Signature[] {signature3Copy, signature1Copy, signature2Copy}, packageInfo,
+ mMockPackageManagerInternal);
assertThat(result).isTrue();
}
@@ -473,11 +495,14 @@
Signature signature2Copy = new Signature(SIGNATURE_2.toByteArray());
PackageInfo packageInfo = new PackageInfo();
- packageInfo.signatures = new Signature[]{SIGNATURE_1, SIGNATURE_2, SIGNATURE_3};
+ packageInfo.signingCertificateHistory = new Signature[][] {
+ {SIGNATURE_1, SIGNATURE_2, SIGNATURE_3}
+ };
packageInfo.applicationInfo = new ApplicationInfo();
boolean result = AppBackupUtils.signaturesMatch(
- new Signature[]{signature2Copy, signature1Copy}, packageInfo);
+ new Signature[]{signature2Copy, signature1Copy}, packageInfo,
+ mMockPackageManagerInternal);
assertThat(result).isTrue();
}
@@ -488,11 +513,14 @@
Signature signature2Copy = new Signature(SIGNATURE_2.toByteArray());
PackageInfo packageInfo = new PackageInfo();
- packageInfo.signatures = new Signature[]{signature1Copy, signature2Copy};
+ packageInfo.signingCertificateHistory = new Signature[][] {
+ {signature1Copy, signature2Copy}
+ };
packageInfo.applicationInfo = new ApplicationInfo();
boolean result = AppBackupUtils.signaturesMatch(
- new Signature[]{SIGNATURE_1, SIGNATURE_2, SIGNATURE_3}, packageInfo);
+ new Signature[]{SIGNATURE_1, SIGNATURE_2, SIGNATURE_3}, packageInfo,
+ mMockPackageManagerInternal);
assertThat(result).isFalse();
}
@@ -503,11 +531,76 @@
Signature signature2Copy = new Signature(SIGNATURE_2.toByteArray());
PackageInfo packageInfo = new PackageInfo();
- packageInfo.signatures = new Signature[]{SIGNATURE_1, SIGNATURE_2, SIGNATURE_3};
+ packageInfo.signingCertificateHistory = new Signature[][] {
+ {SIGNATURE_1, SIGNATURE_2, SIGNATURE_3}
+ };
packageInfo.applicationInfo = new ApplicationInfo();
boolean result = AppBackupUtils.signaturesMatch(
- new Signature[]{signature1Copy, signature2Copy, SIGNATURE_4}, packageInfo);
+ new Signature[]{signature1Copy, signature2Copy, SIGNATURE_4}, packageInfo,
+ mMockPackageManagerInternal);
+
+ assertThat(result).isFalse();
+ }
+
+ @Test
+ public void signaturesMatch_singleStoredSignatureNoRotation_returnsTrue()
+ throws Exception {
+ Signature signature1Copy = new Signature(SIGNATURE_1.toByteArray());
+
+ PackageInfo packageInfo = new PackageInfo();
+ packageInfo.packageName = "test";
+ packageInfo.signingCertificateHistory = new Signature[][] {{SIGNATURE_1}};
+ packageInfo.applicationInfo = new ApplicationInfo();
+
+ doReturn(true).when(mMockPackageManagerInternal).isDataRestoreSafe(signature1Copy,
+ packageInfo.packageName);
+
+ boolean result = AppBackupUtils.signaturesMatch(new Signature[] {signature1Copy},
+ packageInfo, mMockPackageManagerInternal);
+
+ assertThat(result).isTrue();
+ }
+
+ @Test
+ public void signaturesMatch_singleStoredSignatureWithRotationAssumeDataCapability_returnsTrue()
+ throws Exception {
+ Signature signature1Copy = new Signature(SIGNATURE_1.toByteArray());
+
+ PackageInfo packageInfo = new PackageInfo();
+ packageInfo.packageName = "test";
+ packageInfo.signingCertificateHistory = new Signature[][] {{SIGNATURE_1}, {SIGNATURE_2}};
+ packageInfo.applicationInfo = new ApplicationInfo();
+
+ // we know signature1Copy is in history, and we want to assume it has
+ // SigningDetails.CertCapabilities.INSTALLED_DATA capability
+ doReturn(true).when(mMockPackageManagerInternal).isDataRestoreSafe(signature1Copy,
+ packageInfo.packageName);
+
+ boolean result = AppBackupUtils.signaturesMatch(new Signature[] {signature1Copy},
+ packageInfo, mMockPackageManagerInternal);
+
+ assertThat(result).isTrue();
+ }
+
+ @Test
+ public void
+ signaturesMatch_singleStoredSignatureWithRotationAssumeNoDataCapability_returnsFalse()
+ throws Exception {
+ Signature signature1Copy = new Signature(SIGNATURE_1.toByteArray());
+
+ PackageInfo packageInfo = new PackageInfo();
+ packageInfo.packageName = "test";
+ packageInfo.signingCertificateHistory = new Signature[][] {{SIGNATURE_1}, {SIGNATURE_2}};
+ packageInfo.applicationInfo = new ApplicationInfo();
+
+ // we know signature1Copy is in history, but we want to assume it does not have
+ // SigningDetails.CertCapabilities.INSTALLED_DATA capability
+ doReturn(false).when(mMockPackageManagerInternal).isDataRestoreSafe(signature1Copy,
+ packageInfo.packageName);
+
+ boolean result = AppBackupUtils.signaturesMatch(new Signature[] {signature1Copy},
+ packageInfo, mMockPackageManagerInternal);
assertThat(result).isFalse();
}
diff --git a/services/tests/servicestests/src/com/android/server/backup/utils/TarBackupReaderTest.java b/services/tests/servicestests/src/com/android/server/backup/utils/TarBackupReaderTest.java
index 0cdf04b..5f052ce 100644
--- a/services/tests/servicestests/src/com/android/server/backup/utils/TarBackupReaderTest.java
+++ b/services/tests/servicestests/src/com/android/server/backup/utils/TarBackupReaderTest.java
@@ -28,6 +28,9 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.verifyZeroInteractions;
@@ -37,6 +40,7 @@
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
+import android.content.pm.PackageManagerInternal;
import android.content.pm.Signature;
import android.os.Bundle;
import android.os.Process;
@@ -79,6 +83,7 @@
@Mock private BytesReadListener mBytesReadListenerMock;
@Mock private IBackupManagerMonitor mBackupManagerMonitorMock;
+ @Mock private PackageManagerInternal mMockPackageManagerInternal;
private final PackageManagerStub mPackageManagerStub = new PackageManagerStub();
private Context mContext;
@@ -139,7 +144,8 @@
Signature[] signatures = tarBackupReader.readAppManifestAndReturnSignatures(
fileMetadata);
RestorePolicy restorePolicy = tarBackupReader.chooseRestorePolicy(
- mPackageManagerStub, false /* allowApks */, fileMetadata, signatures);
+ mPackageManagerStub, false /* allowApks */, fileMetadata, signatures,
+ mMockPackageManagerInternal);
assertThat(restorePolicy).isEqualTo(RestorePolicy.IGNORE);
assertThat(fileMetadata.packageName).isEqualTo(TEST_PACKAGE_NAME);
@@ -152,7 +158,8 @@
signatures = tarBackupReader.readAppManifestAndReturnSignatures(
fileMetadata);
restorePolicy = tarBackupReader.chooseRestorePolicy(
- mPackageManagerStub, false /* allowApks */, fileMetadata, signatures);
+ mPackageManagerStub, false /* allowApks */, fileMetadata, signatures,
+ mMockPackageManagerInternal);
assertThat(restorePolicy).isEqualTo(RestorePolicy.IGNORE);
assertThat(fileMetadata.packageName).isEqualTo(TEST_PACKAGE_NAME);
@@ -214,7 +221,8 @@
mBytesReadListenerMock, mBackupManagerMonitorMock);
RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub,
- true /* allowApks */, new FileMetadata(), null /* signatures */);
+ true /* allowApks */, new FileMetadata(), null /* signatures */,
+ mMockPackageManagerInternal);
assertThat(policy).isEqualTo(RestorePolicy.IGNORE);
verifyZeroInteractions(mBackupManagerMonitorMock);
@@ -234,7 +242,8 @@
PackageManagerStub.sPackageInfo = null;
RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub,
- true /* allowApks */, info, new Signature[0] /* signatures */);
+ true /* allowApks */, info, new Signature[0] /* signatures */,
+ mMockPackageManagerInternal);
assertThat(policy).isEqualTo(RestorePolicy.ACCEPT_IF_APK);
ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
@@ -258,7 +267,8 @@
PackageManagerStub.sPackageInfo = null;
RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub,
- true /* allowApks */, info, new Signature[0] /* signatures */);
+ true /* allowApks */, info, new Signature[0] /* signatures */,
+ mMockPackageManagerInternal);
assertThat(policy).isEqualTo(RestorePolicy.ACCEPT_IF_APK);
ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
@@ -283,7 +293,8 @@
PackageManagerStub.sPackageInfo = null;
RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub,
- false /* allowApks */, new FileMetadata(), new Signature[0] /* signatures */);
+ false /* allowApks */, new FileMetadata(), new Signature[0] /* signatures */,
+ mMockPackageManagerInternal);
assertThat(policy).isEqualTo(RestorePolicy.IGNORE);
ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
@@ -307,7 +318,8 @@
PackageManagerStub.sPackageInfo = packageInfo;
RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub,
- false /* allowApks */, new FileMetadata(), new Signature[0] /* signatures */);
+ false /* allowApks */, new FileMetadata(), new Signature[0] /* signatures */,
+ mMockPackageManagerInternal);
assertThat(policy).isEqualTo(RestorePolicy.IGNORE);
ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
@@ -333,7 +345,8 @@
PackageManagerStub.sPackageInfo = packageInfo;
RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub,
- false /* allowApks */, new FileMetadata(), new Signature[0] /* signatures */);
+ false /* allowApks */, new FileMetadata(), new Signature[0] /* signatures */,
+ mMockPackageManagerInternal);
assertThat(policy).isEqualTo(RestorePolicy.IGNORE);
ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
@@ -358,11 +371,11 @@
packageInfo.applicationInfo.flags |= ApplicationInfo.FLAG_ALLOW_BACKUP;
packageInfo.applicationInfo.uid = Process.FIRST_APPLICATION_UID;
packageInfo.applicationInfo.backupAgentName = null;
- packageInfo.signatures = new Signature[]{FAKE_SIGNATURE_2};
+ packageInfo.signingCertificateHistory = new Signature[][] {{FAKE_SIGNATURE_2}};
PackageManagerStub.sPackageInfo = packageInfo;
RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub,
- false /* allowApks */, new FileMetadata(), signatures);
+ false /* allowApks */, new FileMetadata(), signatures, mMockPackageManagerInternal);
assertThat(policy).isEqualTo(RestorePolicy.IGNORE);
ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
@@ -383,16 +396,19 @@
Signature[] signatures = new Signature[]{FAKE_SIGNATURE_1};
PackageInfo packageInfo = new PackageInfo();
+ packageInfo.packageName = "test";
packageInfo.applicationInfo = new ApplicationInfo();
packageInfo.applicationInfo.flags |=
ApplicationInfo.FLAG_ALLOW_BACKUP | ApplicationInfo.FLAG_RESTORE_ANY_VERSION;
packageInfo.applicationInfo.uid = Process.SYSTEM_UID;
packageInfo.applicationInfo.backupAgentName = "backup.agent";
- packageInfo.signatures = new Signature[]{FAKE_SIGNATURE_1};
+ packageInfo.signingCertificateHistory = new Signature[][] {{FAKE_SIGNATURE_1}};
PackageManagerStub.sPackageInfo = packageInfo;
+ doReturn(true).when(mMockPackageManagerInternal).isDataRestoreSafe(FAKE_SIGNATURE_1,
+ packageInfo.packageName);
RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub,
- false /* allowApks */, new FileMetadata(), signatures);
+ false /* allowApks */, new FileMetadata(), signatures, mMockPackageManagerInternal);
assertThat(policy).isEqualTo(RestorePolicy.ACCEPT);
ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
@@ -412,16 +428,19 @@
Signature[] signatures = new Signature[]{FAKE_SIGNATURE_1};
PackageInfo packageInfo = new PackageInfo();
+ packageInfo.packageName = "test";
packageInfo.applicationInfo = new ApplicationInfo();
packageInfo.applicationInfo.flags |=
ApplicationInfo.FLAG_ALLOW_BACKUP | ApplicationInfo.FLAG_RESTORE_ANY_VERSION;
packageInfo.applicationInfo.uid = Process.FIRST_APPLICATION_UID;
packageInfo.applicationInfo.backupAgentName = null;
- packageInfo.signatures = new Signature[]{FAKE_SIGNATURE_1};
+ packageInfo.signingCertificateHistory = new Signature[][] {{FAKE_SIGNATURE_1}};
PackageManagerStub.sPackageInfo = packageInfo;
+ doReturn(true).when(mMockPackageManagerInternal).isDataRestoreSafe(FAKE_SIGNATURE_1,
+ packageInfo.packageName);
RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub,
- false /* allowApks */, new FileMetadata(), signatures);
+ false /* allowApks */, new FileMetadata(), signatures, mMockPackageManagerInternal);
assertThat(policy).isEqualTo(RestorePolicy.ACCEPT);
ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
@@ -444,17 +463,20 @@
info.version = 1;
PackageInfo packageInfo = new PackageInfo();
+ packageInfo.packageName = "test";
packageInfo.applicationInfo = new ApplicationInfo();
packageInfo.applicationInfo.flags |= ApplicationInfo.FLAG_ALLOW_BACKUP;
packageInfo.applicationInfo.flags &= ~ApplicationInfo.FLAG_RESTORE_ANY_VERSION;
packageInfo.applicationInfo.uid = Process.FIRST_APPLICATION_UID;
packageInfo.applicationInfo.backupAgentName = null;
- packageInfo.signatures = new Signature[]{FAKE_SIGNATURE_1};
+ packageInfo.signingCertificateHistory = new Signature[][] {{FAKE_SIGNATURE_1}};
packageInfo.versionCode = 2;
PackageManagerStub.sPackageInfo = packageInfo;
+ doReturn(true).when(mMockPackageManagerInternal).isDataRestoreSafe(FAKE_SIGNATURE_1,
+ packageInfo.packageName);
RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub,
- false /* allowApks */, info, signatures);
+ false /* allowApks */, info, signatures, mMockPackageManagerInternal);
assertThat(policy).isEqualTo(RestorePolicy.ACCEPT);
ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
@@ -479,17 +501,20 @@
info.hasApk = true;
PackageInfo packageInfo = new PackageInfo();
+ packageInfo.packageName = "test";
packageInfo.applicationInfo = new ApplicationInfo();
packageInfo.applicationInfo.flags |= ApplicationInfo.FLAG_ALLOW_BACKUP;
packageInfo.applicationInfo.flags &= ~ApplicationInfo.FLAG_RESTORE_ANY_VERSION;
packageInfo.applicationInfo.uid = Process.FIRST_APPLICATION_UID;
packageInfo.applicationInfo.backupAgentName = null;
- packageInfo.signatures = new Signature[]{FAKE_SIGNATURE_1};
+ packageInfo.signingCertificateHistory = new Signature[][] {{FAKE_SIGNATURE_1}};
packageInfo.versionCode = 1;
PackageManagerStub.sPackageInfo = packageInfo;
+ doReturn(true).when(mMockPackageManagerInternal).isDataRestoreSafe(FAKE_SIGNATURE_1,
+ packageInfo.packageName);
RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub,
- true /* allowApks */, info, signatures);
+ true /* allowApks */, info, signatures, mMockPackageManagerInternal);
assertThat(policy).isEqualTo(RestorePolicy.ACCEPT_IF_APK);
verifyNoMoreInteractions(mBackupManagerMonitorMock);
@@ -510,17 +535,20 @@
info.version = 2;
PackageInfo packageInfo = new PackageInfo();
+ packageInfo.packageName = "test";
packageInfo.applicationInfo = new ApplicationInfo();
packageInfo.applicationInfo.flags |= ApplicationInfo.FLAG_ALLOW_BACKUP;
packageInfo.applicationInfo.flags &= ~ApplicationInfo.FLAG_RESTORE_ANY_VERSION;
packageInfo.applicationInfo.uid = Process.FIRST_APPLICATION_UID;
packageInfo.applicationInfo.backupAgentName = null;
- packageInfo.signatures = new Signature[]{FAKE_SIGNATURE_1};
+ packageInfo.signingCertificateHistory = new Signature[][] {{FAKE_SIGNATURE_1}};
packageInfo.versionCode = 1;
PackageManagerStub.sPackageInfo = packageInfo;
+ doReturn(true).when(mMockPackageManagerInternal).isDataRestoreSafe(FAKE_SIGNATURE_1,
+ packageInfo.packageName);
RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub,
- false /* allowApks */, info, signatures);
+ false /* allowApks */, info, signatures, mMockPackageManagerInternal);
assertThat(policy).isEqualTo(RestorePolicy.IGNORE);
ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGeneratorTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGeneratorTest.java
index 8a461ac..fd8b319 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGeneratorTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGeneratorTest.java
@@ -39,6 +39,7 @@
import java.io.File;
import java.nio.charset.StandardCharsets;
import java.security.KeyStore;
+import java.util.Random;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
@@ -51,7 +52,7 @@
private static final int TEST_GENERATION_ID = 3;
private static final String ANDROID_KEY_STORE_PROVIDER = "AndroidKeyStore";
private static final String KEY_ALGORITHM = "AES";
- private static final int KEY_SIZE_BYTES = 32;
+ private static final int KEY_SIZE_BYTES = RecoverableKeyGenerator.KEY_SIZE_BITS / Byte.SIZE;
private static final String KEY_WRAP_ALGORITHM = "AES/GCM/NoPadding";
private static final String TEST_ALIAS = "karlin";
private static final String WRAPPING_KEY_ALIAS = "RecoverableKeyGeneratorTestWrappingKey";
@@ -71,7 +72,7 @@
mDatabaseFile = context.getDatabasePath(DATABASE_FILE_NAME);
mRecoverableKeyStoreDb = RecoverableKeyStoreDb.newInstance(context);
- AndroidKeyStoreSecretKey platformKey = generateAndroidKeyStoreKey();
+ AndroidKeyStoreSecretKey platformKey = generatePlatformKey();
mPlatformKey = new PlatformEncryptionKey(TEST_GENERATION_ID, platformKey);
mDecryptKey = new PlatformDecryptionKey(TEST_GENERATION_ID, platformKey);
mRecoverableKeyGenerator = RecoverableKeyGenerator.newInstance(mRecoverableKeyStoreDb);
@@ -117,7 +118,21 @@
assertArrayEquals(rawMaterial, unwrappedMaterial);
}
- private AndroidKeyStoreSecretKey generateAndroidKeyStoreKey() throws Exception {
+ @Test
+ public void importKey_storesTheWrappedVersionOfTheRawMaterial() throws Exception {
+ byte[] rawMaterial = randomBytes(KEY_SIZE_BYTES);
+ mRecoverableKeyGenerator.importKey(
+ mPlatformKey, TEST_USER_ID, KEYSTORE_UID_SELF, TEST_ALIAS, rawMaterial);
+
+ WrappedKey wrappedKey = mRecoverableKeyStoreDb.getKey(KEYSTORE_UID_SELF, TEST_ALIAS);
+ Cipher cipher = Cipher.getInstance(KEY_WRAP_ALGORITHM);
+ cipher.init(Cipher.DECRYPT_MODE, mDecryptKey.getKey(),
+ new GCMParameterSpec(GCM_TAG_LENGTH_BITS, wrappedKey.getNonce()));
+ byte[] unwrappedMaterial = cipher.doFinal(wrappedKey.getKeyMaterial());
+ assertArrayEquals(rawMaterial, unwrappedMaterial);
+ }
+
+ private AndroidKeyStoreSecretKey generatePlatformKey() throws Exception {
KeyGenerator keyGenerator = KeyGenerator.getInstance(
KEY_ALGORITHM,
ANDROID_KEY_STORE_PROVIDER);
@@ -132,4 +147,10 @@
private static byte[] getUtf8Bytes(String s) {
return s.getBytes(StandardCharsets.UTF_8);
}
+
+ private static byte[] randomBytes(int n) {
+ byte[] bytes = new byte[n];
+ new Random().nextBytes(bytes);
+ return bytes;
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java
index b67659d..199410c 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java
@@ -132,6 +132,7 @@
private static final String TEST_ALIAS = "nick";
private static final String TEST_ALIAS2 = "bob";
private static final int RECOVERABLE_KEY_SIZE_BYTES = 32;
+ private static final int APPLICATION_KEY_SIZE_BYTES = 32;
private static final int GENERATION_ID = 1;
private static final byte[] NONCE = getUtf8Bytes("nonce");
private static final byte[] KEY_MATERIAL = getUtf8Bytes("keymaterial");
@@ -209,6 +210,39 @@
}
@Test
+ public void importKey_storesTheKey() throws Exception {
+ int uid = Binder.getCallingUid();
+ int userId = UserHandle.getCallingUserId();
+ byte[] keyMaterial = randomBytes(APPLICATION_KEY_SIZE_BYTES);
+
+ mRecoverableKeyStoreManager.importKey(TEST_ALIAS, keyMaterial);
+
+ assertThat(mRecoverableKeyStoreDb.getKey(uid, TEST_ALIAS)).isNotNull();
+ assertThat(mRecoverableKeyStoreDb.getShouldCreateSnapshot(userId, uid)).isTrue();
+ }
+
+ @Test
+ public void importKey_throwsIfInvalidLength() throws Exception {
+ byte[] keyMaterial = randomBytes(APPLICATION_KEY_SIZE_BYTES - 1);
+ try {
+ mRecoverableKeyStoreManager.importKey(TEST_ALIAS, keyMaterial);
+ fail("should have thrown");
+ } catch (ServiceSpecificException e) {
+ assertThat(e.getMessage()).contains("not contain 256 bits");
+ }
+ }
+
+ @Test
+ public void importKey_throwsIfNullKey() throws Exception {
+ try {
+ mRecoverableKeyStoreManager.importKey(TEST_ALIAS, /*keyBytes=*/ null);
+ fail("should have thrown");
+ } catch (ServiceSpecificException e) {
+ assertThat(e.getMessage()).contains("not contain 256 bits");
+ }
+ }
+
+ @Test
public void removeKey_removesAKey() throws Exception {
int uid = Binder.getCallingUid();
mRecoverableKeyStoreManager.generateAndStoreKey(TEST_ALIAS);
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbTest.java
index 609faa4..dfb2dbf 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbTest.java
@@ -28,7 +28,7 @@
import org.junit.runner.RunWith;
import android.content.Context;
-import android.security.keystore.RecoveryController;
+import android.security.keystore.recovery.RecoveryController;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
diff --git a/services/tests/servicestests/src/com/android/server/pm/backup/BackupUtilsTest.java b/services/tests/servicestests/src/com/android/server/pm/backup/BackupUtilsTest.java
index c016e61..a0cefbf 100644
--- a/services/tests/servicestests/src/com/android/server/pm/backup/BackupUtilsTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/backup/BackupUtilsTest.java
@@ -15,73 +15,298 @@
*/
package com.android.server.pm.backup;
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
+import android.content.pm.PackageManagerInternal;
import android.content.pm.PackageParser.Package;
import android.content.pm.Signature;
-import android.test.AndroidTestCase;
import android.test.MoreAsserts;
-import android.test.suitebuilder.annotation.SmallTest;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
import com.android.server.backup.BackupUtils;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
import java.util.ArrayList;
import java.util.Arrays;
@SmallTest
-public class BackupUtilsTest extends AndroidTestCase {
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class BackupUtilsTest {
- private Signature[] genSignatures(String... signatures) {
- final Signature[] sigs = new Signature[signatures.length];
- for (int i = 0; i < signatures.length; i++){
- sigs[i] = new Signature(signatures[i].getBytes());
- }
- return sigs;
+ private static final Signature SIGNATURE_1 = generateSignature((byte) 1);
+ private static final Signature SIGNATURE_2 = generateSignature((byte) 2);
+ private static final Signature SIGNATURE_3 = generateSignature((byte) 3);
+ private static final Signature SIGNATURE_4 = generateSignature((byte) 4);
+ private static final byte[] SIGNATURE_HASH_1 = BackupUtils.hashSignature(SIGNATURE_1);
+ private static final byte[] SIGNATURE_HASH_2 = BackupUtils.hashSignature(SIGNATURE_2);
+ private static final byte[] SIGNATURE_HASH_3 = BackupUtils.hashSignature(SIGNATURE_3);
+ private static final byte[] SIGNATURE_HASH_4 = BackupUtils.hashSignature(SIGNATURE_4);
+
+ private PackageManagerInternal mMockPackageManagerInternal;
+
+ @Before
+ public void setUp() throws Exception {
+ mMockPackageManagerInternal = mock(PackageManagerInternal.class);
}
- private PackageInfo genPackage(String... signatures) {
- final PackageInfo pi = new PackageInfo();
- pi.packageName = "package";
- pi.applicationInfo = new ApplicationInfo();
- pi.signatures = genSignatures(signatures);
+ @Test
+ public void signaturesMatch_targetIsNull_returnsFalse() throws Exception {
+ ArrayList<byte[]> storedSigHashes = new ArrayList<>();
+ storedSigHashes.add(SIGNATURE_HASH_1);
+ boolean result = BackupUtils.signaturesMatch(storedSigHashes, null,
+ mMockPackageManagerInternal);
- return pi;
+ assertThat(result).isFalse();
}
- public void testSignaturesMatch() {
- final ArrayList<byte[]> stored1 = BackupUtils.hashSignatureArray(Arrays.asList(
- "abc".getBytes()));
- final ArrayList<byte[]> stored2 = BackupUtils.hashSignatureArray(Arrays.asList(
- "abc".getBytes(), "def".getBytes()));
+ @Test
+ public void signaturesMatch_systemApplication_returnsTrue() throws Exception {
+ PackageInfo packageInfo = new PackageInfo();
+ packageInfo.applicationInfo = new ApplicationInfo();
+ packageInfo.applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM;
- PackageInfo pi;
+ ArrayList<byte[]> storedSigHashes = new ArrayList<>();
+ storedSigHashes.add(SIGNATURE_HASH_1);
+ boolean result = BackupUtils.signaturesMatch(storedSigHashes, packageInfo,
+ mMockPackageManagerInternal);
- // False for null package.
- assertFalse(BackupUtils.signaturesMatch(stored1, null));
-
- // If it's a system app, signatures don't matter.
- pi = genPackage("xyz");
- pi.applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM;
- assertTrue(BackupUtils.signaturesMatch(stored1, pi));
-
- // Non system apps.
- assertTrue(BackupUtils.signaturesMatch(stored1, genPackage("abc")));
-
- // Superset is okay.
- assertTrue(BackupUtils.signaturesMatch(stored1, genPackage("abc", "xyz")));
- assertTrue(BackupUtils.signaturesMatch(stored1, genPackage("xyz", "abc")));
-
- assertFalse(BackupUtils.signaturesMatch(stored1, genPackage("xyz")));
- assertFalse(BackupUtils.signaturesMatch(stored1, genPackage("xyz", "def")));
-
- assertTrue(BackupUtils.signaturesMatch(stored2, genPackage("def", "abc")));
- assertTrue(BackupUtils.signaturesMatch(stored2, genPackage("x", "def", "abc", "y")));
-
- // Subset is not okay.
- assertFalse(BackupUtils.signaturesMatch(stored2, genPackage("abc")));
- assertFalse(BackupUtils.signaturesMatch(stored2, genPackage("def")));
+ assertThat(result).isTrue();
}
+ @Test
+ public void signaturesMatch_disallowsUnsignedApps_storedSignatureNull_returnsFalse()
+ throws Exception {
+ PackageInfo packageInfo = new PackageInfo();
+ packageInfo.signingCertificateHistory = new Signature[][] {{SIGNATURE_1}};
+ packageInfo.applicationInfo = new ApplicationInfo();
+
+ boolean result = BackupUtils.signaturesMatch(null, packageInfo,
+ mMockPackageManagerInternal);
+
+ assertThat(result).isFalse();
+ }
+
+ @Test
+ public void signaturesMatch_disallowsUnsignedApps_storedSignatureEmpty_returnsFalse()
+ throws Exception {
+ PackageInfo packageInfo = new PackageInfo();
+ packageInfo.signingCertificateHistory = new Signature[][] {{SIGNATURE_1}};
+ packageInfo.applicationInfo = new ApplicationInfo();
+
+ ArrayList<byte[]> storedSigHashes = new ArrayList<>();
+ boolean result = BackupUtils.signaturesMatch(storedSigHashes, packageInfo,
+ mMockPackageManagerInternal);
+
+ assertThat(result).isFalse();
+ }
+
+
+ @Test
+ public void
+ signaturesMatch_disallowsUnsignedApps_targetSignatureEmpty_returnsFalse()
+ throws Exception {
+ PackageInfo packageInfo = new PackageInfo();
+ packageInfo.signingCertificateHistory = new Signature[0][0];
+ packageInfo.applicationInfo = new ApplicationInfo();
+
+ ArrayList<byte[]> storedSigHashes = new ArrayList<>();
+ storedSigHashes.add(SIGNATURE_HASH_1);
+ boolean result = BackupUtils.signaturesMatch(storedSigHashes, packageInfo,
+ mMockPackageManagerInternal);
+
+ assertThat(result).isFalse();
+ }
+
+ @Test
+ public void
+ signaturesMatch_disallowsUnsignedApps_targetSignatureNull_returnsFalse()
+ throws Exception {
+ PackageInfo packageInfo = new PackageInfo();
+ packageInfo.signingCertificateHistory = null;
+ packageInfo.applicationInfo = new ApplicationInfo();
+
+ ArrayList<byte[]> storedSigHashes = new ArrayList<>();
+ storedSigHashes.add(SIGNATURE_HASH_1);
+ boolean result = BackupUtils.signaturesMatch(storedSigHashes, packageInfo,
+ mMockPackageManagerInternal);
+
+ assertThat(result).isFalse();
+ }
+
+ @Test
+ public void signaturesMatch_disallowsUnsignedApps_bothSignaturesNull_returnsFalse()
+ throws Exception {
+ PackageInfo packageInfo = new PackageInfo();
+ packageInfo.signingCertificateHistory = null;
+ packageInfo.applicationInfo = new ApplicationInfo();
+
+ boolean result = BackupUtils.signaturesMatch(null, packageInfo,
+ mMockPackageManagerInternal);
+
+ assertThat(result).isFalse();
+ }
+
+ @Test
+ public void signaturesMatch_disallowsUnsignedApps_bothSignaturesEmpty_returnsFalse()
+ throws Exception {
+ PackageInfo packageInfo = new PackageInfo();
+ packageInfo.signingCertificateHistory = new Signature[0][0];
+ packageInfo.applicationInfo = new ApplicationInfo();
+
+ ArrayList<byte[]> storedSigHashes = new ArrayList<>();
+ boolean result = BackupUtils.signaturesMatch(storedSigHashes, packageInfo,
+ mMockPackageManagerInternal);
+
+ assertThat(result).isFalse();
+ }
+
+ @Test
+ public void signaturesMatch_equalSignatures_returnsTrue() throws Exception {
+ PackageInfo packageInfo = new PackageInfo();
+ packageInfo.signingCertificateHistory = new Signature[][] {
+ {SIGNATURE_1, SIGNATURE_2, SIGNATURE_3}
+ };
+ packageInfo.applicationInfo = new ApplicationInfo();
+
+ ArrayList<byte[]> storedSigHashes = new ArrayList<>();
+ storedSigHashes.add(SIGNATURE_HASH_1);
+ storedSigHashes.add(SIGNATURE_HASH_2);
+ storedSigHashes.add(SIGNATURE_HASH_3);
+ boolean result = BackupUtils.signaturesMatch(storedSigHashes, packageInfo,
+ mMockPackageManagerInternal);
+
+ assertThat(result).isTrue();
+ }
+
+ @Test
+ public void signaturesMatch_extraSignatureInTarget_returnsTrue() throws Exception {
+ PackageInfo packageInfo = new PackageInfo();
+ packageInfo.signingCertificateHistory = new Signature[][] {
+ {SIGNATURE_1, SIGNATURE_2, SIGNATURE_3}
+ };
+ packageInfo.applicationInfo = new ApplicationInfo();
+
+ ArrayList<byte[]> storedSigHashes = new ArrayList<>();
+ storedSigHashes.add(SIGNATURE_HASH_1);
+ storedSigHashes.add(SIGNATURE_HASH_2);
+ boolean result = BackupUtils.signaturesMatch(storedSigHashes, packageInfo,
+ mMockPackageManagerInternal);
+
+ assertThat(result).isTrue();
+ }
+
+ @Test
+ public void signaturesMatch_extraSignatureInStored_returnsFalse() throws Exception {
+ PackageInfo packageInfo = new PackageInfo();
+ packageInfo.signingCertificateHistory = new Signature[][] {{SIGNATURE_1, SIGNATURE_2}};
+ packageInfo.applicationInfo = new ApplicationInfo();
+
+ ArrayList<byte[]> storedSigHashes = new ArrayList<>();
+ storedSigHashes.add(SIGNATURE_HASH_1);
+ storedSigHashes.add(SIGNATURE_HASH_2);
+ storedSigHashes.add(SIGNATURE_HASH_3);
+ boolean result = BackupUtils.signaturesMatch(storedSigHashes, packageInfo,
+ mMockPackageManagerInternal);
+
+ assertThat(result).isFalse();
+ }
+
+ @Test
+ public void signaturesMatch_oneNonMatchingSignature_returnsFalse() throws Exception {
+ PackageInfo packageInfo = new PackageInfo();
+ packageInfo.signingCertificateHistory = new Signature[][] {
+ {SIGNATURE_1, SIGNATURE_2, SIGNATURE_3}
+ };
+ packageInfo.applicationInfo = new ApplicationInfo();
+
+ ArrayList<byte[]> storedSigHashes = new ArrayList<>();
+ storedSigHashes.add(SIGNATURE_HASH_1);
+ storedSigHashes.add(SIGNATURE_HASH_2);
+ storedSigHashes.add(SIGNATURE_HASH_4);
+ boolean result = BackupUtils.signaturesMatch(storedSigHashes, packageInfo,
+ mMockPackageManagerInternal);
+
+ assertThat(result).isFalse();
+ }
+
+ @Test
+ public void signaturesMatch_singleStoredSignatureNoRotation_returnsTrue()
+ throws Exception {
+ PackageInfo packageInfo = new PackageInfo();
+ packageInfo.packageName = "test";
+ packageInfo.signingCertificateHistory = new Signature[][] {{SIGNATURE_1}};
+ packageInfo.applicationInfo = new ApplicationInfo();
+
+ doReturn(true).when(mMockPackageManagerInternal).isDataRestoreSafe(SIGNATURE_HASH_1,
+ packageInfo.packageName);
+
+ ArrayList<byte[]> storedSigHashes = new ArrayList<>();
+ storedSigHashes.add(SIGNATURE_HASH_1);
+ boolean result = BackupUtils.signaturesMatch(storedSigHashes, packageInfo,
+ mMockPackageManagerInternal);
+
+ assertThat(result).isTrue();
+ }
+
+ @Test
+ public void signaturesMatch_singleStoredSignatureWithRotationAssumeDataCapability_returnsTrue()
+ throws Exception {
+ PackageInfo packageInfo = new PackageInfo();
+ packageInfo.packageName = "test";
+ packageInfo.signingCertificateHistory = new Signature[][] {{SIGNATURE_1}, {SIGNATURE_2}};
+ packageInfo.applicationInfo = new ApplicationInfo();
+
+ // we know SIGNATURE_1 is in history, and we want to assume it has
+ // SigningDetails.CertCapabilities.INSTALLED_DATA capability
+ doReturn(true).when(mMockPackageManagerInternal).isDataRestoreSafe(SIGNATURE_HASH_1,
+ packageInfo.packageName);
+
+ ArrayList<byte[]> storedSigHashes = new ArrayList<>();
+ storedSigHashes.add(SIGNATURE_HASH_1);
+ boolean result = BackupUtils.signaturesMatch(storedSigHashes, packageInfo,
+ mMockPackageManagerInternal);
+
+ assertThat(result).isTrue();
+ }
+
+ @Test
+ public void
+ signaturesMatch_singleStoredSignatureWithRotationAssumeNoDataCapability_returnsFalse()
+ throws Exception {
+ PackageInfo packageInfo = new PackageInfo();
+ packageInfo.packageName = "test";
+ packageInfo.signingCertificateHistory = new Signature[][] {{SIGNATURE_1}, {SIGNATURE_2}};
+ packageInfo.applicationInfo = new ApplicationInfo();
+
+ // we know SIGNATURE_1 is in history, but we want to assume it does not have
+ // SigningDetails.CertCapabilities.INSTALLED_DATA capability
+ doReturn(false).when(mMockPackageManagerInternal).isDataRestoreSafe(SIGNATURE_HASH_1,
+ packageInfo.packageName);
+
+ ArrayList<byte[]> storedSigHashes = new ArrayList<>();
+ storedSigHashes.add(SIGNATURE_HASH_1);
+ boolean result = BackupUtils.signaturesMatch(storedSigHashes, packageInfo,
+ mMockPackageManagerInternal);
+
+ assertThat(result).isFalse();
+ }
+
+ @Test
public void testHashSignature() {
final byte[] sig1 = "abc".getBytes();
final byte[] sig2 = "def".getBytes();
@@ -115,4 +340,10 @@
MoreAsserts.assertEquals(hash2a, listA.get(1));
MoreAsserts.assertEquals(hash2a, listB.get(1));
}
+
+ private static Signature generateSignature(byte i) {
+ byte[] signatureBytes = new byte[256];
+ signatureBytes[0] = i;
+ return new Signature(signatureBytes);
+ }
}
diff --git a/services/usage/java/com/android/server/usage/UserUsageStatsService.java b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
index 3fbcd81..7f060b3 100644
--- a/services/usage/java/com/android/server/usage/UserUsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
@@ -47,7 +47,7 @@
class UserUsageStatsService {
private static final String TAG = "UsageStatsService";
private static final boolean DEBUG = UsageStatsService.DEBUG;
- private static final SimpleDateFormat sDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+ private static final SimpleDateFormat sDateFormat = new SimpleDateFormat("yyyy-MM-dd/HH:mm:ss");
private static final int sDateFormatFlags =
DateUtils.FORMAT_SHOW_DATE
| DateUtils.FORMAT_SHOW_TIME
@@ -572,11 +572,13 @@
pw.printPair("endTime", endTime);
}
pw.println(")");
- pw.increaseIndent();
- for (UsageEvents.Event event : events) {
- printEvent(pw, event, prettyDates);
+ if (events != null) {
+ pw.increaseIndent();
+ for (UsageEvents.Event event : events) {
+ printEvent(pw, event, prettyDates);
+ }
+ pw.decreaseIndent();
}
- pw.decreaseIndent();
}
void printIntervalStats(IndentingPrintWriter pw, IntervalStats stats,
diff --git a/services/usb/java/com/android/server/usb/UsbPortManager.java b/services/usb/java/com/android/server/usb/UsbPortManager.java
index ddb4f04..33da403 100644
--- a/services/usb/java/com/android/server/usb/UsbPortManager.java
+++ b/services/usb/java/com/android/server/usb/UsbPortManager.java
@@ -138,6 +138,7 @@
}
public void systemReady() {
+ mSystemReady = true;
if (mProxy != null) {
try {
mProxy.queryPortStatus();
@@ -146,7 +147,6 @@
"ServiceStart: Failed to query port status", e);
}
}
- mSystemReady = true;
}
public UsbPort[] getPorts() {
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java b/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java
index 297a6ea..956efc0 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java
@@ -380,11 +380,10 @@
if (DEBUG) {
Log.d(TAG, " type:0x" + Integer.toHexString(type));
}
- if ((type >= UsbTerminalTypes.TERMINAL_IN_UNDEFINED
- && type <= UsbTerminalTypes.TERMINAL_IN_PROC_MIC_ARRAY)
- || (type >= UsbTerminalTypes.TERMINAL_BIDIR_UNDEFINED
- && type <= UsbTerminalTypes.TERMINAL_BIDIR_SKRPHONE_CANCEL)
- || (type == UsbTerminalTypes.TERMINAL_USB_STREAMING)) {
+ int terminalCategory = type & ~0xFF;
+ if (terminalCategory != UsbTerminalTypes.TERMINAL_USB_UNDEFINED
+ && terminalCategory != UsbTerminalTypes.TERMINAL_OUT_UNDEFINED) {
+ // If not explicitly a USB connection or output, it could be an input.
hasInput = true;
break;
}
@@ -419,10 +418,10 @@
if (DEBUG) {
Log.d(TAG, " type:0x" + Integer.toHexString(type));
}
- if ((type >= UsbTerminalTypes.TERMINAL_OUT_UNDEFINED
- && type <= UsbTerminalTypes.TERMINAL_OUT_LFSPEAKER)
- || (type >= UsbTerminalTypes.TERMINAL_BIDIR_UNDEFINED
- && type <= UsbTerminalTypes.TERMINAL_BIDIR_SKRPHONE_CANCEL)) {
+ int terminalCategory = type & ~0xFF;
+ if (terminalCategory != UsbTerminalTypes.TERMINAL_USB_UNDEFINED
+ && terminalCategory != UsbTerminalTypes.TERMINAL_IN_UNDEFINED) {
+ // If not explicitly a USB connection or input, it could be an output.
hasOutput = true;
break;
}
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbTerminalTypes.java b/services/usb/java/com/android/server/usb/descriptors/UsbTerminalTypes.java
index 9bd6cb9..cbb899e 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbTerminalTypes.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbTerminalTypes.java
@@ -24,6 +24,7 @@
private static final String TAG = "UsbTerminalTypes";
// USB
+ public static final int TERMINAL_USB_UNDEFINED = 0x0100;
public static final int TERMINAL_USB_STREAMING = 0x0101;
// Inputs
diff --git a/telephony/java/android/provider/Telephony.java b/telephony/java/android/provider/Telephony.java
index e7f0cc2..cb87d1f 100644
--- a/telephony/java/android/provider/Telephony.java
+++ b/telephony/java/android/provider/Telephony.java
@@ -1103,10 +1103,15 @@
"android.provider.Telephony.MMS_DOWNLOADED";
/**
- * Broadcast Action: A debug code has been entered in the dialer. These "secret codes"
- * are used to activate developer menus by dialing certain codes. And they are of the
- * form {@code *#*#<code>#*#*}. The intent will have the data URI:
- * {@code android_secret_code://<code>}.
+ * Broadcast Action: A debug code has been entered in the dialer. This intent is
+ * broadcast by the system and OEM telephony apps may need to receive these broadcasts.
+ * These "secret codes" are used to activate developer menus by dialing certain codes.
+ * And they are of the form {@code *#*#<code>#*#*}. The intent will have the data
+ * URI: {@code android_secret_code://<code>}. It is possible that a manifest
+ * receiver would be woken up even if it is not currently running.
+ *
+ * <p>Requires {@code android.Manifest.permission#CONTROL_INCALL_EXPERIENCE} to
+ * send and receive.</p>
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String SECRET_CODE_ACTION =
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 6798a83..96eb23d 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -1815,7 +1815,14 @@
"check_pricing_with_carrier_data_roaming_bool";
/**
- * List of thresholds of RSRP for determining the display level of LTE signal bar.
+ * A list of 4 LTE RSRP thresholds above which a signal level is considered POOR,
+ * MODERATE, GOOD, or EXCELLENT, to be used in SignalStrength reporting.
+ *
+ * Note that the min and max thresholds are fixed at -140 and -44, as explained in
+ * TS 136.133 9.1.4 - RSRP Measurement Report Mapping.
+ * <p>
+ * See SignalStrength#MAX_LTE_RSRP and SignalStrength#MIN_LTE_RSRP. Any signal level outside
+ * these boundaries is considered invalid.
* @hide
*/
public static final String KEY_LTE_RSRP_THRESHOLDS_INT_ARRAY =
@@ -2136,12 +2143,10 @@
sDefaults.putBoolean(KEY_CHECK_PRICING_WITH_CARRIER_FOR_DATA_ROAMING_BOOL, false);
sDefaults.putIntArray(KEY_LTE_RSRP_THRESHOLDS_INT_ARRAY,
new int[] {
- -140, /* SIGNAL_STRENGTH_NONE_OR_UNKNOWN */
-128, /* SIGNAL_STRENGTH_POOR */
-118, /* SIGNAL_STRENGTH_MODERATE */
-108, /* SIGNAL_STRENGTH_GOOD */
-98, /* SIGNAL_STRENGTH_GREAT */
- -44
});
}
diff --git a/telephony/java/android/telephony/SignalStrength.java b/telephony/java/android/telephony/SignalStrength.java
index 778ca77..47a7d5f 100644
--- a/telephony/java/android/telephony/SignalStrength.java
+++ b/telephony/java/android/telephony/SignalStrength.java
@@ -62,7 +62,9 @@
*/
public static final int INVALID = Integer.MAX_VALUE;
- private static final int LTE_RSRP_THRESHOLDS_NUM = 6;
+ private static final int LTE_RSRP_THRESHOLDS_NUM = 4;
+ private static final int MAX_LTE_RSRP = -44;
+ private static final int MIN_LTE_RSRP = -140;
/** Parameters reported by the Radio */
private int mGsmSignalStrength; // Valid values are (0-31, 99) as defined in TS 27.007 8.5
@@ -86,7 +88,8 @@
// onSignalStrengthResult.
private boolean mUseOnlyRsrpForLteLevel; // Use only RSRP for the number of LTE signal bar.
- // The threshold of LTE RSRP for determining the display level of LTE signal bar.
+ // The threshold of LTE RSRP for determining the display level of LTE signal bar. Note that the
+ // min and max are fixed at MIN_LTE_RSRP (-140) and MAX_LTE_RSRP (-44).
private int mLteRsrpThresholds[] = new int[LTE_RSRP_THRESHOLDS_NUM];
/**
@@ -324,7 +327,8 @@
// TS 36.214 Physical Layer Section 5.1.3, TS 36.331 RRC
mLteSignalStrength = (mLteSignalStrength >= 0) ? mLteSignalStrength : 99;
- mLteRsrp = ((mLteRsrp >= 44) && (mLteRsrp <= 140)) ? -mLteRsrp : SignalStrength.INVALID;
+ mLteRsrp = ((-mLteRsrp >= MIN_LTE_RSRP) && (-mLteRsrp <= MAX_LTE_RSRP)) ? -mLteRsrp
+ : SignalStrength.INVALID;
mLteRsrq = ((mLteRsrq >= 3) && (mLteRsrq <= 20)) ? -mLteRsrq : SignalStrength.INVALID;
mLteRssnr = ((mLteRssnr >= -200) && (mLteRssnr <= 300)) ? mLteRssnr
: SignalStrength.INVALID;
@@ -740,24 +744,29 @@
*/
public int getLteLevel() {
/*
- * TS 36.214 Physical Layer Section 5.1.3 TS 36.331 RRC RSSI = received
- * signal + noise RSRP = reference signal dBm RSRQ = quality of signal
- * dB= Number of Resource blocksxRSRP/RSSI SNR = gain=signal/noise ratio
- * = -10log P1/P2 dB
+ * TS 36.214 Physical Layer Section 5.1.3
+ * TS 36.331 RRC
+ *
+ * RSSI = received signal + noise
+ * RSRP = reference signal dBm
+ * RSRQ = quality of signal dB = Number of Resource blocks*RSRP/RSSI
+ * SNR = gain = signal/noise ratio = -10log P1/P2 dB
*/
int rssiIconLevel = SIGNAL_STRENGTH_NONE_OR_UNKNOWN, rsrpIconLevel = -1, snrIconLevel = -1;
- if (mLteRsrp > mLteRsrpThresholds[5]) {
- rsrpIconLevel = -1;
- } else if (mLteRsrp >= (mLteRsrpThresholds[4] - mLteRsrpBoost)) {
- rsrpIconLevel = SIGNAL_STRENGTH_GREAT;
+ if (mLteRsrp > MAX_LTE_RSRP || mLteRsrp < MIN_LTE_RSRP) {
+ if (mLteRsrp != INVALID) {
+ Log.wtf(LOG_TAG, "getLteLevel - invalid lte rsrp: mLteRsrp=" + mLteRsrp);
+ }
} else if (mLteRsrp >= (mLteRsrpThresholds[3] - mLteRsrpBoost)) {
- rsrpIconLevel = SIGNAL_STRENGTH_GOOD;
+ rsrpIconLevel = SIGNAL_STRENGTH_GREAT;
} else if (mLteRsrp >= (mLteRsrpThresholds[2] - mLteRsrpBoost)) {
- rsrpIconLevel = SIGNAL_STRENGTH_MODERATE;
+ rsrpIconLevel = SIGNAL_STRENGTH_GOOD;
} else if (mLteRsrp >= (mLteRsrpThresholds[1] - mLteRsrpBoost)) {
+ rsrpIconLevel = SIGNAL_STRENGTH_MODERATE;
+ } else if (mLteRsrp >= (mLteRsrpThresholds[0] - mLteRsrpBoost)) {
rsrpIconLevel = SIGNAL_STRENGTH_POOR;
- } else if (mLteRsrp >= mLteRsrpThresholds[0]) {
+ } else {
rsrpIconLevel = SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
}
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index cdc1ba9..af3a0bb 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -5281,6 +5281,24 @@
}
/**
+ * Determines if emergency calling is allowed for the MMTEL feature on the slot provided.
+ * @param slotIndex The SIM slot of the MMTEL feature
+ * @return true if emergency calling is allowed, false otherwise.
+ * @hide
+ */
+ public boolean isEmergencyMmTelAvailable(int slotIndex) {
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony != null) {
+ return telephony.isEmergencyMmTelAvailable(slotIndex);
+ }
+ } catch (RemoteException e) {
+ Rlog.e(TAG, "isEmergencyMmTelAvailable, RemoteException: " + e.getMessage());
+ }
+ return false;
+ }
+
+ /**
* Set IMS registration state
*
* @param Registration state
@@ -7183,18 +7201,16 @@
*
* @return Carrier id of the current subscription. Return {@link #UNKNOWN_CARRIER_ID} if the
* subscription is unavailable or the carrier cannot be identified.
- * @throws IllegalStateException if telephony service is unavailable.
*/
public int getAndroidCarrierIdForSubscription() {
try {
ITelephony service = getITelephony();
- return service.getSubscriptionCarrierId(getSubId());
+ if (service != null) {
+ return service.getSubscriptionCarrierId(getSubId());
+ }
} catch (RemoteException ex) {
// This could happen if binder process crashes.
ex.rethrowAsRuntimeException();
- } catch (NullPointerException ex) {
- // This could happen before phone restarts due to crashing.
- throw new IllegalStateException("Telephony service unavailable");
}
return UNKNOWN_CARRIER_ID;
}
@@ -7210,18 +7226,16 @@
*
* @return Carrier name of the current subscription. Return {@code null} if the subscription is
* unavailable or the carrier cannot be identified.
- * @throws IllegalStateException if telephony service is unavailable.
*/
public CharSequence getAndroidCarrierNameForSubscription() {
try {
ITelephony service = getITelephony();
- return service.getSubscriptionCarrierName(getSubId());
+ if (service != null) {
+ return service.getSubscriptionCarrierName(getSubId());
+ }
} catch (RemoteException ex) {
// This could happen if binder process crashes.
ex.rethrowAsRuntimeException();
- } catch (NullPointerException ex) {
- // This could happen before phone restarts due to crashing.
- throw new IllegalStateException("Telephony service unavailable");
}
return null;
}
@@ -7669,4 +7683,83 @@
Log.e(TAG, "Error calling ITelephony#setUserDataEnabled", e);
}
}
+
+ /**
+ * In this mode, modem will not send specified indications when screen is off.
+ * @hide
+ */
+ public static final int INDICATION_UPDATE_MODE_NORMAL = 1;
+
+ /**
+ * In this mode, modem will still send specified indications when screen is off.
+ * @hide
+ */
+ public static final int INDICATION_UPDATE_MODE_IGNORE_SCREEN_OFF = 2;
+
+ /** @hide */
+ @IntDef(prefix = { "INDICATION_UPDATE_MODE_" }, value = {
+ INDICATION_UPDATE_MODE_NORMAL,
+ INDICATION_UPDATE_MODE_IGNORE_SCREEN_OFF
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface IndicationUpdateMode{}
+
+ /**
+ * The indication for signal strength update.
+ * @hide
+ */
+ public static final int INDICATION_FILTER_SIGNAL_STRENGTH = 0x1;
+
+ /**
+ * The indication for full network state update.
+ * @hide
+ */
+ public static final int INDICATION_FILTER_FULL_NETWORK_STATE = 0x2;
+
+ /**
+ * The indication for data call dormancy changed update.
+ * @hide
+ */
+ public static final int INDICATION_FILTER_DATA_CALL_DORMANCY_CHANGED = 0x4;
+
+ /** @hide */
+ @IntDef(flag = true, prefix = { "INDICATION_FILTER_" }, value = {
+ INDICATION_FILTER_SIGNAL_STRENGTH,
+ INDICATION_FILTER_FULL_NETWORK_STATE,
+ INDICATION_FILTER_DATA_CALL_DORMANCY_CHANGED
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface IndicationFilters{}
+
+ /**
+ * Sets radio indication update mode. This can be used to control the behavior of indication
+ * update from modem to Android frameworks. For example, by default several indication updates
+ * are turned off when screen is off, but in some special cases (e.g. carkit is connected but
+ * screen is off) we want to turn on those indications even when the screen is off.
+ *
+ * <p>Requires Permission:
+ * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}
+ *
+ * @param filters Indication filters. Should be a bitmask of INDICATION_FILTER_XXX.
+ * @see #INDICATION_FILTER_SIGNAL_STRENGTH
+ * @see #INDICATION_FILTER_FULL_NETWORK_STATE
+ * @see #INDICATION_FILTER_DATA_CALL_DORMANCY_CHANGED
+ * @param updateMode The voice activation state
+ * @see #INDICATION_UPDATE_MODE_NORMAL
+ * @see #INDICATION_UPDATE_MODE_IGNORE_SCREEN_OFF
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+ public void setRadioIndicationUpdateMode(@IndicationFilters int filters,
+ @IndicationUpdateMode int updateMode) {
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony != null) {
+ telephony.setRadioIndicationUpdateMode(getSubId(), filters, updateMode);
+ }
+ } catch (RemoteException ex) {
+ // This could happen if binder process crashes.
+ ex.rethrowAsRuntimeException();
+ }
+ }
}
diff --git a/telephony/java/android/telephony/ims/feature/ImsFeature.java b/telephony/java/android/telephony/ims/feature/ImsFeature.java
index 1fdbae9..d537699 100644
--- a/telephony/java/android/telephony/ims/feature/ImsFeature.java
+++ b/telephony/java/android/telephony/ims/feature/ImsFeature.java
@@ -87,7 +87,9 @@
// ImsFeatures that are defined in the Manifests. Ensure that these values match the previously
// defined values in ImsServiceClass for compatibility purposes.
/**
- * This feature supports emergency calling over MMTEL.
+ * This feature supports emergency calling over MMTEL. If defined, the framework will try to
+ * place an emergency call over IMS first. If it is not defined, the framework will only use
+ * CSFB for emergency calling.
*/
public static final int FEATURE_EMERGENCY_MMTEL = 0;
/**
diff --git a/telephony/java/android/telephony/ims/feature/MmTelFeature.java b/telephony/java/android/telephony/ims/feature/MmTelFeature.java
index 09267fc..2fffd36 100644
--- a/telephony/java/android/telephony/ims/feature/MmTelFeature.java
+++ b/telephony/java/android/telephony/ims/feature/MmTelFeature.java
@@ -332,20 +332,14 @@
public static final int PROCESS_CALL_IMS = 0;
/**
* To be returned by {@link #shouldProcessCall(String[])} when the telephony framework should
- * not process the outgoing NON_EMERGENCY call as IMS and should instead use circuit switch.
+ * not process the outgoing call as IMS and should instead use circuit switch.
*/
public static final int PROCESS_CALL_CSFB = 1;
- /**
- * To be returned by {@link #shouldProcessCall(String[])} when the telephony framework should
- * not process the outgoing EMERGENCY call as IMS and should instead use circuit switch.
- */
- public static final int PROCESS_CALL_EMERGENCY_CSFB = 2;
@IntDef(flag = true,
value = {
PROCESS_CALL_IMS,
- PROCESS_CALL_CSFB,
- PROCESS_CALL_EMERGENCY_CSFB
+ PROCESS_CALL_CSFB
})
@Retention(RetentionPolicy.SOURCE)
public @interface ProcessCallResult {}
@@ -536,12 +530,15 @@
/**
* Called by the framework to determine if the outgoing call, designated by the outgoing
- * {@link Uri}s, should be processed as an IMS call or CSFB call.
+ * {@link String}s, should be processed as an IMS call or CSFB call. If this method's
+ * functionality is not overridden, the platform will process every call as IMS as long as the
+ * MmTelFeature reports that the {@link MmTelCapabilities#CAPABILITY_TYPE_VOICE} capability is
+ * available.
* @param numbers An array of {@link String}s that will be used for placing the call. There can
* be multiple {@link String}s listed in the case when we want to place an outgoing
* call as a conference.
* @return a {@link ProcessCallResult} to the framework, which will be used to determine if the
- * call wil lbe placed over IMS or via CSFB.
+ * call will be placed over IMS or via CSFB.
*/
public @ProcessCallResult int shouldProcessCall(String[] numbers) {
return PROCESS_CALL_IMS;
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 9e2b519..a941a56 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -823,6 +823,12 @@
IImsConfig getImsConfig(int slotId, int feature);
/**
+ * Returns true if emergency calling is available for the MMTEL feature associated with the
+ * slot specified.
+ */
+ boolean isEmergencyMmTelAvailable(int slotId);
+
+ /**
* Set the network selection mode to automatic.
*
* @param subId the id of the subscription to update.
@@ -1473,4 +1479,12 @@
* @return boolean Return true if the switch succeeds, false if the switch fails.
*/
boolean switchSlots(in int[] physicalSlots);
+
+ /**
+ * Sets radio indication update mode. This can be used to control the behavior of indication
+ * update from modem to Android frameworks. For example, by default several indication updates
+ * are turned off when screen is off, but in some special cases (e.g. carkit is connected but
+ * screen is off) we want to turn on those indications even when the screen is off.
+ */
+ void setRadioIndicationUpdateMode(int subId, int filters, int mode);
}
diff --git a/tests/UsbTests/res/raw/readme.txt b/tests/UsbTests/res/raw/readme.txt
new file mode 100644
index 0000000..62b673c
--- /dev/null
+++ b/tests/UsbTests/res/raw/readme.txt
@@ -0,0 +1,35 @@
+The usbdescriptors_ files contain raw USB descriptors from the Google
+USB-C to 3.5mm adapter, with different loads connected to the 3.5mm
+jack.
+
+usbdescriptors_nothing.bin:
+ - The descriptors when the jack is disconnected.
+
+usbdescriptors_headphones.bin:
+ - The descriptors when the jack is connected to 32-ohm headphones,
+ no microphone.
+ The relevant output terminal is:
+ bDescriptorSubtype 3 (OUTPUT_TERMINAL)
+ bTerminalID 15
+ wTerminalType 0x0302 Headphones
+
+usbdescriptors_lineout.bin:
+ - The descriptors when the jack is connected to a PC line-in jack.
+ The relevant output terminal is:
+ bDescriptorSubtype 3 (OUTPUT_TERMINAL)
+ bTerminalID 15
+ wTerminalType 0x0603 Line Connector
+
+usbdescriptors_headset.bin:
+ - The descriptors when a headset with microphone and low-impedance
+ headphones are connected.
+ The relevant input terminal is:
+ bDescriptorSubtype 2 (INPUT_TERMINAL)
+ bTerminalID 1
+ wTerminalType 0x0201 Microphone
+ The relevant output terminal is:
+ bDescriptorSubtype 3 (OUTPUT_TERMINAL)
+ bTerminalID 15
+ wTerminalType 0x0302 Headphones
+
+
diff --git a/tests/UsbTests/res/raw/usbdescriptors_headphones.bin b/tests/UsbTests/res/raw/usbdescriptors_headphones.bin
new file mode 100644
index 0000000..e8f2932
--- /dev/null
+++ b/tests/UsbTests/res/raw/usbdescriptors_headphones.bin
Binary files differ
diff --git a/tests/UsbTests/res/raw/usbdescriptors_headset.bin b/tests/UsbTests/res/raw/usbdescriptors_headset.bin
new file mode 100644
index 0000000..30eef2a
--- /dev/null
+++ b/tests/UsbTests/res/raw/usbdescriptors_headset.bin
Binary files differ
diff --git a/tests/UsbTests/res/raw/usbdescriptors_lineout.bin b/tests/UsbTests/res/raw/usbdescriptors_lineout.bin
new file mode 100644
index 0000000..d540d33
--- /dev/null
+++ b/tests/UsbTests/res/raw/usbdescriptors_lineout.bin
Binary files differ
diff --git a/tests/UsbTests/res/raw/usbdescriptors_nothing.bin b/tests/UsbTests/res/raw/usbdescriptors_nothing.bin
new file mode 100644
index 0000000..c318abf
--- /dev/null
+++ b/tests/UsbTests/res/raw/usbdescriptors_nothing.bin
Binary files differ
diff --git a/tests/UsbTests/src/com/android/server/usb/UsbDescriptorParserTests.java b/tests/UsbTests/src/com/android/server/usb/UsbDescriptorParserTests.java
new file mode 100644
index 0000000..f323952
--- /dev/null
+++ b/tests/UsbTests/src/com/android/server/usb/UsbDescriptorParserTests.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.usb;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.fail;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.Resources.NotFoundException;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.android.server.usb.descriptors.UsbDescriptorParser;
+import com.google.common.io.ByteStreams;
+
+import java.io.InputStream;
+import java.io.IOException;
+import java.lang.Exception;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests for {@link com.android.server.usb.descriptors.UsbDescriptorParser}
+ */
+@RunWith(AndroidJUnit4.class)
+public class UsbDescriptorParserTests {
+
+ public UsbDescriptorParser loadParser(int resource) {
+ Context c = InstrumentationRegistry.getContext();
+ Resources res = c.getResources();
+ InputStream is = null;
+ try {
+ is = res.openRawResource(resource);
+ } catch (NotFoundException e) {
+ fail("Failed to load resource.");
+ }
+
+ byte[] descriptors = null;
+ try {
+ descriptors = ByteStreams.toByteArray(is);
+ } catch (IOException e) {
+ fail("Failed to convert descriptor strema to bytearray.");
+ }
+
+ // Testing same codepath as UsbHostManager.java:usbDeviceAdded
+ UsbDescriptorParser parser = new UsbDescriptorParser("test-usb-addr");
+ if (!parser.parseDescriptors(descriptors)) {
+ fail("failed to parse descriptors.");
+ }
+ return parser;
+ }
+
+ // A Headset has a microphone and a speaker and is a headset.
+ @Test
+ @SmallTest
+ public void testHeadsetDescriptorParser() {
+ UsbDescriptorParser parser = loadParser(R.raw.usbdescriptors_headset);
+ assertTrue(parser.hasInput());
+ assertTrue(parser.hasOutput());
+ assertTrue(parser.isInputHeadset());
+ assertTrue(parser.isOutputHeadset());
+ }
+
+ // Headphones have no microphones but are considered a headset.
+ @Test
+ @SmallTest
+ public void testHeadphoneDescriptorParser() {
+ UsbDescriptorParser parser = loadParser(R.raw.usbdescriptors_headphones);
+ assertFalse(parser.hasInput());
+ assertTrue(parser.hasOutput());
+ assertFalse(parser.isInputHeadset());
+ assertTrue(parser.isOutputHeadset());
+ }
+
+ // Line out has no microphones and aren't considered a headset.
+ @Test
+ @SmallTest
+ public void testLineoutDescriptorParser() {
+ UsbDescriptorParser parser = loadParser(R.raw.usbdescriptors_lineout);
+ assertFalse(parser.hasInput());
+ assertTrue(parser.hasOutput());
+ assertFalse(parser.isInputHeadset());
+ assertFalse(parser.isOutputHeadset());
+ }
+
+ // An HID-only device shouldn't be considered anything at all.
+ @Test
+ @SmallTest
+ public void testNothingDescriptorParser() {
+ UsbDescriptorParser parser = loadParser(R.raw.usbdescriptors_nothing);
+ assertFalse(parser.hasInput());
+ assertFalse(parser.hasOutput());
+ assertFalse(parser.isInputHeadset());
+ assertFalse(parser.isOutputHeadset());
+ }
+
+}
diff --git a/tests/net/java/android/net/NetworkCapabilitiesTest.java b/tests/net/java/android/net/NetworkCapabilitiesTest.java
index 2e1519b..4227da6 100644
--- a/tests/net/java/android/net/NetworkCapabilitiesTest.java
+++ b/tests/net/java/android/net/NetworkCapabilitiesTest.java
@@ -22,6 +22,7 @@
import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PAID;
import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
import static android.net.NetworkCapabilities.RESTRICTED_CAPABILITIES;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
@@ -252,6 +253,19 @@
assertEqualsThroughMarshalling(netCap);
}
+ @Test
+ public void testOemPaid() {
+ NetworkCapabilities nc = new NetworkCapabilities();
+ nc.maybeMarkCapabilitiesRestricted();
+ assertFalse(nc.hasCapability(NET_CAPABILITY_OEM_PAID));
+ assertTrue(nc.hasCapability(NET_CAPABILITY_NOT_RESTRICTED));
+
+ nc.addCapability(NET_CAPABILITY_OEM_PAID);
+ nc.maybeMarkCapabilitiesRestricted();
+ assertTrue(nc.hasCapability(NET_CAPABILITY_OEM_PAID));
+ assertFalse(nc.hasCapability(NET_CAPABILITY_NOT_RESTRICTED));
+ }
+
private void assertEqualsThroughMarshalling(NetworkCapabilities netCap) {
Parcel p = Parcel.obtain();
netCap.writeToParcel(p, /* flags */ 0);
diff --git a/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java b/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java
index 66e0955..3e1ff6d 100644
--- a/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java
+++ b/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java
@@ -281,6 +281,7 @@
anyInt());
}
+ @Test
public void testCreateTwoTransformsWithSameSpis() throws Exception {
IpSecConfig ipSecConfig = new IpSecConfig();
addDefaultSpisAndRemoteAddrToIpSecConfig(ipSecConfig);
diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp
index 7cffeea..1b6f882 100644
--- a/tools/aapt2/ResourceParser.cpp
+++ b/tools/aapt2/ResourceParser.cpp
@@ -26,11 +26,14 @@
#include "ResourceUtils.h"
#include "ResourceValues.h"
#include "ValueVisitor.h"
+#include "text/Utf8Iterator.h"
#include "util/ImmutableMap.h"
#include "util/Maybe.h"
#include "util/Util.h"
#include "xml/XmlPullParser.h"
+using ::aapt::ResourceUtils::StringBuilder;
+using ::aapt::text::Utf8Iterator;
using ::android::StringPiece;
namespace aapt {
@@ -169,114 +172,212 @@
config_(config),
options_(options) {}
-/**
- * Build a string from XML that converts nested elements into Span objects.
- */
+// Base class Node for representing the various Spans and UntranslatableSections of an XML string.
+// This will be used to traverse and flatten the XML string into a single std::string, with all
+// Span and Untranslatable data maintained in parallel, as indices into the string.
+class Node {
+ public:
+ virtual ~Node() = default;
+
+ // Adds the given child node to this parent node's set of child nodes, moving ownership to the
+ // parent node as well.
+ // Returns a pointer to the child node that was added as a convenience.
+ template <typename T>
+ T* AddChild(std::unique_ptr<T> node) {
+ T* raw_ptr = node.get();
+ children.push_back(std::move(node));
+ return raw_ptr;
+ }
+
+ virtual void Build(StringBuilder* builder) const {
+ for (const auto& child : children) {
+ child->Build(builder);
+ }
+ }
+
+ std::vector<std::unique_ptr<Node>> children;
+};
+
+// A chunk of text in the XML string. This lives between other tags, such as XLIFF tags and Spans.
+class SegmentNode : public Node {
+ public:
+ std::string data;
+
+ void Build(StringBuilder* builder) const override {
+ builder->AppendText(data);
+ }
+};
+
+// A tag that will be encoded into the final flattened string. Tags like <b> or <i>.
+class SpanNode : public Node {
+ public:
+ std::string name;
+
+ void Build(StringBuilder* builder) const override {
+ StringBuilder::SpanHandle span_handle = builder->StartSpan(name);
+ Node::Build(builder);
+ builder->EndSpan(span_handle);
+ }
+};
+
+// An XLIFF 'g' tag, which marks a section of the string as untranslatable.
+class UntranslatableNode : public Node {
+ public:
+ void Build(StringBuilder* builder) const override {
+ StringBuilder::UntranslatableHandle handle = builder->StartUntranslatable();
+ Node::Build(builder);
+ builder->EndUntranslatable(handle);
+ }
+};
+
+// Build a string from XML that converts nested elements into Span objects.
bool ResourceParser::FlattenXmlSubtree(
xml::XmlPullParser* parser, std::string* out_raw_string, StyleString* out_style_string,
std::vector<UntranslatableSection>* out_untranslatable_sections) {
- // Keeps track of formatting tags (<b>, <i>) and the range of characters for which they apply.
- // The stack elements refer to the indices in out_style_string->spans.
- // By first adding to the out_style_string->spans vector, and then using the stack to refer
- // to this vector, the original order of tags is preserved in cases such as <b><i>hello</b></i>.
- std::vector<size_t> span_stack;
-
- // Clear the output variables.
- out_raw_string->clear();
- out_style_string->spans.clear();
- out_untranslatable_sections->clear();
-
- // The StringBuilder will concatenate the various segments of text which are initially
- // separated by tags. It also handles unicode escape codes and quotations.
- util::StringBuilder builder;
+ std::string raw_string;
+ std::string current_text;
// The first occurrence of a <xliff:g> tag. Nested <xliff:g> tags are illegal.
Maybe<size_t> untranslatable_start_depth;
+ Node root;
+ std::vector<Node*> node_stack;
+ node_stack.push_back(&root);
+
+ bool saw_span_node = false;
+ SegmentNode* first_segment = nullptr;
+ SegmentNode* last_segment = nullptr;
+
size_t depth = 1;
- while (xml::XmlPullParser::IsGoodEvent(parser->Next())) {
+ while (depth > 0 && xml::XmlPullParser::IsGoodEvent(parser->Next())) {
const xml::XmlPullParser::Event event = parser->event();
- if (event == xml::XmlPullParser::Event::kStartElement) {
- if (parser->element_namespace().empty()) {
- // This is an HTML tag which we encode as a span. Add it to the span stack.
- std::string span_name = parser->element_name();
- const auto end_attr_iter = parser->end_attributes();
- for (auto attr_iter = parser->begin_attributes(); attr_iter != end_attr_iter; ++attr_iter) {
- span_name += ";";
- span_name += attr_iter->name;
- span_name += "=";
- span_name += attr_iter->value;
+ // First take care of any SegmentNodes that should be created.
+ if (event == xml::XmlPullParser::Event::kStartElement ||
+ event == xml::XmlPullParser::Event::kEndElement) {
+ if (!current_text.empty()) {
+ std::unique_ptr<SegmentNode> segment_node = util::make_unique<SegmentNode>();
+ segment_node->data = std::move(current_text);
+ last_segment = node_stack.back()->AddChild(std::move(segment_node));
+ if (first_segment == nullptr) {
+ first_segment = last_segment;
}
+ current_text = {};
+ }
+ }
- // Make sure the string is representable in our binary format.
- if (builder.Utf16Len() > std::numeric_limits<uint32_t>::max()) {
- diag_->Error(DiagMessage(source_.WithLine(parser->line_number()))
- << "style string '" << builder.ToString() << "' is too long");
- return false;
- }
+ switch (event) {
+ case xml::XmlPullParser::Event::kText: {
+ current_text += parser->text();
+ raw_string += parser->text();
+ } break;
- out_style_string->spans.push_back(
- Span{std::move(span_name), static_cast<uint32_t>(builder.Utf16Len())});
- span_stack.push_back(out_style_string->spans.size() - 1);
- } else if (parser->element_namespace() == sXliffNamespaceUri) {
- if (parser->element_name() == "g") {
- if (untranslatable_start_depth) {
- // We've already encountered an <xliff:g> tag, and nested <xliff:g> tags are illegal.
- diag_->Error(DiagMessage(source_.WithLine(parser->line_number()))
- << "illegal nested XLIFF 'g' tag");
- return false;
- } else {
- // Mark the start of an untranslatable section. Use UTF8 indices/lengths.
- untranslatable_start_depth = depth;
- const size_t current_idx = builder.ToString().size();
- out_untranslatable_sections->push_back(UntranslatableSection{current_idx, current_idx});
+ case xml::XmlPullParser::Event::kStartElement: {
+ if (parser->element_namespace().empty()) {
+ // This is an HTML tag which we encode as a span. Add it to the span stack.
+ std::unique_ptr<SpanNode> span_node = util::make_unique<SpanNode>();
+ span_node->name = parser->element_name();
+ const auto end_attr_iter = parser->end_attributes();
+ for (auto attr_iter = parser->begin_attributes(); attr_iter != end_attr_iter;
+ ++attr_iter) {
+ span_node->name += ";";
+ span_node->name += attr_iter->name;
+ span_node->name += "=";
+ span_node->name += attr_iter->value;
}
+
+ node_stack.push_back(node_stack.back()->AddChild(std::move(span_node)));
+ saw_span_node = true;
+ } else if (parser->element_namespace() == sXliffNamespaceUri) {
+ // This is an XLIFF tag, which is not encoded as a span.
+ if (parser->element_name() == "g") {
+ // Check that an 'untranslatable' tag is not already being processed. Nested
+ // <xliff:g> tags are illegal.
+ if (untranslatable_start_depth) {
+ diag_->Error(DiagMessage(source_.WithLine(parser->line_number()))
+ << "illegal nested XLIFF 'g' tag");
+ return false;
+ } else {
+ // Mark the beginning of an 'untranslatable' section.
+ untranslatable_start_depth = depth;
+ node_stack.push_back(
+ node_stack.back()->AddChild(util::make_unique<UntranslatableNode>()));
+ }
+ } else {
+ // Ignore unknown XLIFF tags, but don't warn.
+ node_stack.push_back(node_stack.back()->AddChild(util::make_unique<Node>()));
+ }
+ } else {
+ // Besides XLIFF, any other namespaced tag is unsupported and ignored.
+ diag_->Warn(DiagMessage(source_.WithLine(parser->line_number()))
+ << "ignoring element '" << parser->element_name()
+ << "' with unknown namespace '" << parser->element_namespace() << "'");
+ node_stack.push_back(node_stack.back()->AddChild(util::make_unique<Node>()));
}
- // Ignore other xliff tags, they get handled by other tools.
- } else {
- // Besides XLIFF, any other namespaced tag is unsupported and ignored.
- diag_->Warn(DiagMessage(source_.WithLine(parser->line_number()))
- << "ignoring element '" << parser->element_name()
- << "' with unknown namespace '" << parser->element_namespace() << "'");
- }
+ // Enter one level inside the element.
+ depth++;
+ } break;
- // Enter one level inside the element.
- depth++;
- } else if (event == xml::XmlPullParser::Event::kText) {
- // Record both the raw text and append to the builder to deal with escape sequences
- // and quotations.
- out_raw_string->append(parser->text());
- builder.Append(parser->text());
- } else if (event == xml::XmlPullParser::Event::kEndElement) {
- // Return one level from within the element.
- depth--;
- if (depth == 0) {
+ case xml::XmlPullParser::Event::kEndElement: {
+ // Return one level from within the element.
+ depth--;
+ if (depth == 0) {
+ break;
+ }
+
+ node_stack.pop_back();
+ if (untranslatable_start_depth == make_value(depth)) {
+ // This is the end of an untranslatable section.
+ untranslatable_start_depth = {};
+ }
+ } break;
+
+ default:
+ // ignore.
break;
- }
-
- if (parser->element_namespace().empty()) {
- // This is an HTML tag which we encode as a span. Update the span
- // stack and pop the top entry.
- Span& top_span = out_style_string->spans[span_stack.back()];
- top_span.last_char = builder.Utf16Len() - 1;
- span_stack.pop_back();
- } else if (untranslatable_start_depth == make_value(depth)) {
- // This is the end of an untranslatable section. Use UTF8 indices/lengths.
- UntranslatableSection& untranslatable_section = out_untranslatable_sections->back();
- untranslatable_section.end = builder.ToString().size();
- untranslatable_start_depth = {};
- }
- } else if (event == xml::XmlPullParser::Event::kComment) {
- // Ignore.
- } else {
- LOG(FATAL) << "unhandled XML event";
}
}
- CHECK(span_stack.empty()) << "spans haven't been fully processed";
- out_style_string->str = builder.ToString();
+ // Sanity check to make sure we processed all the nodes.
+ CHECK(node_stack.size() == 1u);
+ CHECK(node_stack.back() == &root);
+
+ if (!saw_span_node) {
+ // If there were no spans, we must treat this string a little differently (according to AAPT).
+ // Find and strip the leading whitespace from the first segment, and the trailing whitespace
+ // from the last segment.
+ if (first_segment != nullptr) {
+ // Trim leading whitespace.
+ StringPiece trimmed = util::TrimLeadingWhitespace(first_segment->data);
+ if (trimmed.size() != first_segment->data.size()) {
+ first_segment->data = trimmed.to_string();
+ }
+ }
+
+ if (last_segment != nullptr) {
+ // Trim trailing whitespace.
+ StringPiece trimmed = util::TrimTrailingWhitespace(last_segment->data);
+ if (trimmed.size() != last_segment->data.size()) {
+ last_segment->data = trimmed.to_string();
+ }
+ }
+ }
+
+ // Have the XML structure flatten itself into the StringBuilder. The StringBuilder will take
+ // care of recording the correctly adjusted Spans and UntranslatableSections.
+ StringBuilder builder;
+ root.Build(&builder);
+ if (!builder) {
+ diag_->Error(DiagMessage(source_.WithLine(parser->line_number())) << builder.GetError());
+ return false;
+ }
+
+ ResourceUtils::FlattenedXmlString flattened_string = builder.GetFlattenedString();
+ *out_raw_string = std::move(raw_string);
+ *out_untranslatable_sections = std::move(flattened_string.untranslatable_sections);
+ out_style_string->str = std::move(flattened_string.text);
+ out_style_string->spans = std::move(flattened_string.spans);
return true;
}
diff --git a/tools/aapt2/ResourceParser_test.cpp b/tools/aapt2/ResourceParser_test.cpp
index 618c8ed..c98c0b9 100644
--- a/tools/aapt2/ResourceParser_test.cpp
+++ b/tools/aapt2/ResourceParser_test.cpp
@@ -95,6 +95,16 @@
ASSERT_THAT(str, NotNull());
EXPECT_THAT(*str, StrValueEq(" hey there "));
EXPECT_THAT(str->untranslatable_sections, IsEmpty());
+
+ ASSERT_TRUE(TestParse(R"(<string name="bar">Isn\'t it cool?</string>)"));
+ str = test::GetValue<String>(&table_, "string/bar");
+ ASSERT_THAT(str, NotNull());
+ EXPECT_THAT(*str, StrValueEq("Isn't it cool?"));
+
+ ASSERT_TRUE(TestParse(R"(<string name="baz">"Isn't it cool?"</string>)"));
+ str = test::GetValue<String>(&table_, "string/baz");
+ ASSERT_THAT(str, NotNull());
+ EXPECT_THAT(*str, StrValueEq("Isn't it cool?"));
}
TEST_F(ResourceParserTest, ParseEscapedString) {
@@ -126,16 +136,16 @@
StyledString* str = test::GetValue<StyledString>(&table_, "string/foo");
ASSERT_THAT(str, NotNull());
- EXPECT_THAT(str->value->value, Eq("This is my aunt\u2019s fickle string"));
+ EXPECT_THAT(str->value->value, StrEq("This is my aunt\u2019s fickle string"));
EXPECT_THAT(str->value->spans, SizeIs(2));
EXPECT_THAT(str->untranslatable_sections, IsEmpty());
- EXPECT_THAT(*str->value->spans[0].name, Eq("b"));
- EXPECT_THAT(str->value->spans[0].first_char, Eq(17u));
+ EXPECT_THAT(*str->value->spans[0].name, StrEq("b"));
+ EXPECT_THAT(str->value->spans[0].first_char, Eq(18u));
EXPECT_THAT(str->value->spans[0].last_char, Eq(30u));
- EXPECT_THAT(*str->value->spans[1].name, Eq("small"));
- EXPECT_THAT(str->value->spans[1].first_char, Eq(24u));
+ EXPECT_THAT(*str->value->spans[1].name, StrEq("small"));
+ EXPECT_THAT(str->value->spans[1].first_char, Eq(25u));
EXPECT_THAT(str->value->spans[1].last_char, Eq(30u));
}
@@ -144,7 +154,7 @@
String* str = test::GetValue<String>(&table_, "string/foo");
ASSERT_THAT(str, NotNull());
- EXPECT_THAT(*str->value, Eq("This is what I think"));
+ EXPECT_THAT(*str->value, StrEq("This is what I think"));
EXPECT_THAT(str->untranslatable_sections, IsEmpty());
ASSERT_TRUE(TestParse(R"(<string name="foo2">" This is what I think "</string>)"));
@@ -154,6 +164,25 @@
EXPECT_THAT(*str, StrValueEq(" This is what I think "));
}
+TEST_F(ResourceParserTest, ParseStyledStringWithWhitespace) {
+ std::string input = R"(<string name="foo"> <b> My <i> favorite</i> string </b> </string>)";
+ ASSERT_TRUE(TestParse(input));
+
+ StyledString* str = test::GetValue<StyledString>(&table_, "string/foo");
+ ASSERT_THAT(str, NotNull());
+ EXPECT_THAT(str->value->value, StrEq(" My favorite string "));
+ EXPECT_THAT(str->untranslatable_sections, IsEmpty());
+
+ ASSERT_THAT(str->value->spans, SizeIs(2u));
+ EXPECT_THAT(*str->value->spans[0].name, StrEq("b"));
+ EXPECT_THAT(str->value->spans[0].first_char, Eq(1u));
+ EXPECT_THAT(str->value->spans[0].last_char, Eq(21u));
+
+ EXPECT_THAT(*str->value->spans[1].name, StrEq("i"));
+ EXPECT_THAT(str->value->spans[1].first_char, Eq(5u));
+ EXPECT_THAT(str->value->spans[1].last_char, Eq(13u));
+}
+
TEST_F(ResourceParserTest, IgnoreXliffTagsOtherThanG) {
std::string input = R"(
<string name="foo" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
@@ -182,12 +211,9 @@
String* str = test::GetValue<String>(&table_, "string/foo");
ASSERT_THAT(str, NotNull());
EXPECT_THAT(*str, StrValueEq("There are %1$d apples"));
- ASSERT_THAT(str->untranslatable_sections, SizeIs(1));
- // We expect indices and lengths that span to include the whitespace
- // before %1$d. This is due to how the StringBuilder withholds whitespace unless
- // needed (to deal with line breaks, etc.).
- EXPECT_THAT(str->untranslatable_sections[0].start, Eq(9u));
+ ASSERT_THAT(str->untranslatable_sections, SizeIs(1));
+ EXPECT_THAT(str->untranslatable_sections[0].start, Eq(10u));
EXPECT_THAT(str->untranslatable_sections[0].end, Eq(14u));
}
@@ -199,14 +225,16 @@
StyledString* str = test::GetValue<StyledString>(&table_, "string/foo");
ASSERT_THAT(str, NotNull());
- EXPECT_THAT(str->value->value, Eq("There are %1$d apples"));
- ASSERT_THAT(str->untranslatable_sections, SizeIs(1));
+ EXPECT_THAT(str->value->value, Eq(" There are %1$d apples"));
- // We expect indices and lengths that span to include the whitespace
- // before %1$d. This is due to how the StringBuilder withholds whitespace unless
- // needed (to deal with line breaks, etc.).
- EXPECT_THAT(str->untranslatable_sections[0].start, Eq(9u));
- EXPECT_THAT(str->untranslatable_sections[0].end, Eq(14u));
+ ASSERT_THAT(str->untranslatable_sections, SizeIs(1));
+ EXPECT_THAT(str->untranslatable_sections[0].start, Eq(11u));
+ EXPECT_THAT(str->untranslatable_sections[0].end, Eq(15u));
+
+ ASSERT_THAT(str->value->spans, SizeIs(1u));
+ EXPECT_THAT(*str->value->spans[0].name, StrEq("b"));
+ EXPECT_THAT(str->value->spans[0].first_char, Eq(11u));
+ EXPECT_THAT(str->value->spans[0].last_char, Eq(14u));
}
TEST_F(ResourceParserTest, ParseNull) {
diff --git a/tools/aapt2/ResourceUtils.cpp b/tools/aapt2/ResourceUtils.cpp
index 628466d..8fc3d65 100644
--- a/tools/aapt2/ResourceUtils.cpp
+++ b/tools/aapt2/ResourceUtils.cpp
@@ -18,17 +18,23 @@
#include <sstream>
+#include "android-base/stringprintf.h"
#include "androidfw/ResourceTypes.h"
#include "androidfw/ResourceUtils.h"
#include "NameMangler.h"
#include "SdkConstants.h"
#include "format/binary/ResourceTypeExtensions.h"
+#include "text/Unicode.h"
+#include "text/Utf8Iterator.h"
#include "util/Files.h"
#include "util/Util.h"
+using ::aapt::text::IsWhitespace;
+using ::aapt::text::Utf8Iterator;
using ::android::StringPiece;
using ::android::StringPiece16;
+using ::android::base::StringPrintf;
namespace aapt {
namespace ResourceUtils {
@@ -750,5 +756,195 @@
return util::make_unique<BinaryPrimitive>(res_value);
}
+// Converts the codepoint to UTF-8 and appends it to the string.
+static bool AppendCodepointToUtf8String(char32_t codepoint, std::string* output) {
+ ssize_t len = utf32_to_utf8_length(&codepoint, 1);
+ if (len < 0) {
+ return false;
+ }
+
+ const size_t start_append_pos = output->size();
+
+ // Make room for the next character.
+ output->resize(output->size() + len);
+
+ char* dst = &*(output->begin() + start_append_pos);
+ utf32_to_utf8(&codepoint, 1, dst, len + 1);
+ return true;
+}
+
+// Reads up to 4 UTF-8 characters that represent a Unicode escape sequence, and appends the
+// Unicode codepoint represented by the escape sequence to the string.
+static bool AppendUnicodeEscapeSequence(Utf8Iterator* iter, std::string* output) {
+ char32_t code = 0;
+ for (size_t i = 0; i < 4 && iter->HasNext(); i++) {
+ char32_t codepoint = iter->Next();
+ char32_t a;
+ if (codepoint >= U'0' && codepoint <= U'9') {
+ a = codepoint - U'0';
+ } else if (codepoint >= U'a' && codepoint <= U'f') {
+ a = codepoint - U'a' + 10;
+ } else if (codepoint >= U'A' && codepoint <= U'F') {
+ a = codepoint - U'A' + 10;
+ } else {
+ return {};
+ }
+ code = (code << 4) | a;
+ }
+ return AppendCodepointToUtf8String(code, output);
+}
+
+StringBuilder::StringBuilder(bool preserve_spaces)
+ : preserve_spaces_(preserve_spaces), quote_(preserve_spaces) {
+}
+
+StringBuilder& StringBuilder::AppendText(const std::string& text) {
+ if (!error_.empty()) {
+ return *this;
+ }
+
+ const size_t previous_len = xml_string_.text.size();
+ Utf8Iterator iter(text);
+ while (iter.HasNext()) {
+ char32_t codepoint = iter.Next();
+ if (!quote_ && text::IsWhitespace(codepoint)) {
+ if (!last_codepoint_was_space_) {
+ // Emit a space if it's the first.
+ xml_string_.text += ' ';
+ last_codepoint_was_space_ = true;
+ }
+
+ // Keep eating spaces.
+ continue;
+ }
+
+ // This is not a space.
+ last_codepoint_was_space_ = false;
+
+ if (codepoint == U'\\') {
+ if (iter.HasNext()) {
+ codepoint = iter.Next();
+ switch (codepoint) {
+ case U't':
+ xml_string_.text += '\t';
+ break;
+
+ case U'n':
+ xml_string_.text += '\n';
+ break;
+
+ case U'#':
+ case U'@':
+ case U'?':
+ case U'"':
+ case U'\'':
+ case U'\\':
+ xml_string_.text += static_cast<char>(codepoint);
+ break;
+
+ case U'u':
+ if (!AppendUnicodeEscapeSequence(&iter, &xml_string_.text)) {
+ error_ =
+ StringPrintf("invalid unicode escape sequence in string\n\"%s\"", text.c_str());
+ return *this;
+ }
+ break;
+
+ default:
+ // Ignore the escape character and just include the codepoint.
+ AppendCodepointToUtf8String(codepoint, &xml_string_.text);
+ break;
+ }
+ }
+ } else if (!preserve_spaces_ && codepoint == U'"') {
+ // Only toggle the quote state when we are not preserving spaces.
+ quote_ = !quote_;
+
+ } else if (!quote_ && codepoint == U'\'') {
+ // This should be escaped.
+ error_ = StringPrintf("unescaped apostrophe in string\n\"%s\"", text.c_str());
+ return *this;
+
+ } else {
+ AppendCodepointToUtf8String(codepoint, &xml_string_.text);
+ }
+ }
+
+ // Accumulate the added string's UTF-16 length.
+ const uint8_t* utf8_data = reinterpret_cast<const uint8_t*>(xml_string_.text.c_str());
+ const size_t utf8_length = xml_string_.text.size();
+ ssize_t len = utf8_to_utf16_length(utf8_data + previous_len, utf8_length - previous_len);
+ if (len < 0) {
+ error_ = StringPrintf("invalid unicode code point in string\n\"%s\"", utf8_data + previous_len);
+ return *this;
+ }
+
+ utf16_len_ += static_cast<uint32_t>(len);
+ return *this;
+}
+
+StringBuilder::SpanHandle StringBuilder::StartSpan(const std::string& name) {
+ if (!error_.empty()) {
+ return 0u;
+ }
+
+ // When we start a span, all state associated with whitespace truncation and quotation is ended.
+ ResetTextState();
+ Span span;
+ span.name = name;
+ span.first_char = span.last_char = utf16_len_;
+ xml_string_.spans.push_back(std::move(span));
+ return xml_string_.spans.size() - 1;
+}
+
+void StringBuilder::EndSpan(SpanHandle handle) {
+ if (!error_.empty()) {
+ return;
+ }
+
+ // When we end a span, all state associated with whitespace truncation and quotation is ended.
+ ResetTextState();
+ xml_string_.spans[handle].last_char = utf16_len_ - 1u;
+}
+
+StringBuilder::UntranslatableHandle StringBuilder::StartUntranslatable() {
+ if (!error_.empty()) {
+ return 0u;
+ }
+
+ UntranslatableSection section;
+ section.start = section.end = xml_string_.text.size();
+ xml_string_.untranslatable_sections.push_back(section);
+ return xml_string_.untranslatable_sections.size() - 1;
+}
+
+void StringBuilder::EndUntranslatable(UntranslatableHandle handle) {
+ if (!error_.empty()) {
+ return;
+ }
+ xml_string_.untranslatable_sections[handle].end = xml_string_.text.size();
+}
+
+FlattenedXmlString StringBuilder::GetFlattenedString() const {
+ return xml_string_;
+}
+
+std::string StringBuilder::to_string() const {
+ return xml_string_.text;
+}
+
+StringBuilder::operator bool() const {
+ return error_.empty();
+}
+
+std::string StringBuilder::GetError() const {
+ return error_;
+}
+
+void StringBuilder::ResetTextState() {
+ quote_ = preserve_spaces_;
+ last_codepoint_was_space_ = false;
+}
+
} // namespace ResourceUtils
} // namespace aapt
diff --git a/tools/aapt2/ResourceUtils.h b/tools/aapt2/ResourceUtils.h
index f83d49e..7af2fe0 100644
--- a/tools/aapt2/ResourceUtils.h
+++ b/tools/aapt2/ResourceUtils.h
@@ -224,6 +224,95 @@
const android::Res_value& res_value,
StringPool* dst_pool);
+// A string flattened from an XML hierarchy, which maintains tags and untranslatable sections
+// in parallel data structures.
+struct FlattenedXmlString {
+ std::string text;
+ std::vector<UntranslatableSection> untranslatable_sections;
+ std::vector<Span> spans;
+};
+
+// Flattens an XML hierarchy into a FlattenedXmlString, formatting the text, escaping characters,
+// and removing whitespace, all while keeping the untranslatable sections and spans in sync with the
+// transformations.
+//
+// Specifically, the StringBuilder will handle escaped characters like \t, \n, \\, \', etc.
+// Single quotes *must* be escaped, unless within a pair of double-quotes.
+// Pairs of double-quotes disable whitespace stripping of the enclosed text.
+// Unicode escape codes (\u0049) are interpreted and the represented Unicode character is inserted.
+//
+// A NOTE ON WHITESPACE:
+//
+// When preserve_spaces is false, and when text is not enclosed within double-quotes,
+// StringBuilder replaces a series of whitespace with a single space character. This happens at the
+// start and end of the string as well, so leading and trailing whitespace is possible.
+//
+// When a Span is started or stopped, the whitespace counter is reset, meaning if whitespace
+// is encountered directly after the span, it will be emitted. This leads to situations like the
+// following: "This <b> is </b> spaced" -> "This is spaced". Without spans, this would be properly
+// compressed: "This is spaced" -> "This is spaced".
+//
+// Untranslatable sections do not have the same problem:
+// "This <xliff:g> is </xliff:g> not spaced" -> "This is not spaced".
+//
+// NOTE: This is all the way it is because AAPT1 did it this way. Maintaining backwards
+// compatibility is important.
+//
+class StringBuilder {
+ public:
+ using SpanHandle = size_t;
+ using UntranslatableHandle = size_t;
+
+ // Creates a StringBuilder. If preserve_spaces is true, whitespace removal is not performed, and
+ // single quotations can be used without escaping them.
+ explicit StringBuilder(bool preserve_spaces = false);
+
+ // Appends a chunk of text.
+ StringBuilder& AppendText(const std::string& text);
+
+ // Starts a Span (tag) with the given name. The name is expected to be of the form:
+ // "tag_name;attr1=value;attr2=value;"
+ // Which is how Spans are encoded in the ResStringPool.
+ // To end the span, pass back the SpanHandle received from this method to the EndSpan() method.
+ SpanHandle StartSpan(const std::string& name);
+
+ // Ends a Span (tag). Pass in the matching SpanHandle previously obtained from StartSpan().
+ void EndSpan(SpanHandle handle);
+
+ // Starts an Untranslatable section.
+ // To end the section, pass back the UntranslatableHandle received from this method to
+ // the EndUntranslatable() method.
+ UntranslatableHandle StartUntranslatable();
+
+ // Ends an Untranslatable section. Pass in the matching UntranslatableHandle previously obtained
+ // from StartUntranslatable().
+ void EndUntranslatable(UntranslatableHandle handle);
+
+ // Returns the flattened XML string, with all spans and untranslatable sections encoded as
+ // parallel data structures.
+ FlattenedXmlString GetFlattenedString() const;
+
+ // Returns just the flattened XML text, with no spans or untranslatable sections.
+ std::string to_string() const;
+
+ // Returns true if there was no error.
+ explicit operator bool() const;
+
+ std::string GetError() const;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(StringBuilder);
+
+ void ResetTextState();
+
+ std::string error_;
+ FlattenedXmlString xml_string_;
+ uint32_t utf16_len_ = 0u;
+ bool preserve_spaces_;
+ bool quote_;
+ bool last_codepoint_was_space_ = false;
+};
+
} // namespace ResourceUtils
} // namespace aapt
diff --git a/tools/aapt2/ResourceUtils_test.cpp b/tools/aapt2/ResourceUtils_test.cpp
index cb786d3..11f3fa3 100644
--- a/tools/aapt2/ResourceUtils_test.cpp
+++ b/tools/aapt2/ResourceUtils_test.cpp
@@ -212,4 +212,48 @@
Pointee(ValueEq(BinaryPrimitive(Res_value::TYPE_FLOAT, expected_float_flattened))));
}
+TEST(ResourceUtilsTest, StringBuilderWhitespaceRemoval) {
+ EXPECT_THAT(ResourceUtils::StringBuilder()
+ .AppendText(" hey guys ")
+ .AppendText(" this is so cool ")
+ .to_string(),
+ Eq(" hey guys this is so cool "));
+ EXPECT_THAT(ResourceUtils::StringBuilder()
+ .AppendText(" \" wow, so many \t ")
+ .AppendText("spaces. \"what? ")
+ .to_string(),
+ Eq(" wow, so many \t spaces. what? "));
+ EXPECT_THAT(ResourceUtils::StringBuilder()
+ .AppendText(" where \t ")
+ .AppendText(" \nis the pie?")
+ .to_string(),
+ Eq(" where is the pie?"));
+}
+
+TEST(ResourceUtilsTest, StringBuilderEscaping) {
+ EXPECT_THAT(ResourceUtils::StringBuilder()
+ .AppendText("hey guys\\n ")
+ .AppendText(" this \\t is so\\\\ cool")
+ .to_string(),
+ Eq("hey guys\n this \t is so\\ cool"));
+ EXPECT_THAT(ResourceUtils::StringBuilder().AppendText("\\@\\?\\#\\\\\\'").to_string(),
+ Eq("@?#\\\'"));
+}
+
+TEST(ResourceUtilsTest, StringBuilderMisplacedQuote) {
+ ResourceUtils::StringBuilder builder;
+ EXPECT_FALSE(builder.AppendText("they're coming!"));
+}
+
+TEST(ResourceUtilsTest, StringBuilderUnicodeCodes) {
+ EXPECT_THAT(ResourceUtils::StringBuilder().AppendText("\\u00AF\\u0AF0 woah").to_string(),
+ Eq("\u00AF\u0AF0 woah"));
+ EXPECT_FALSE(ResourceUtils::StringBuilder().AppendText("\\u00 yo"));
+}
+
+TEST(ResourceUtilsTest, StringBuilderPreserveSpaces) {
+ EXPECT_THAT(ResourceUtils::StringBuilder(true /*preserve_spaces*/).AppendText("\"").to_string(),
+ Eq("\""));
+}
+
} // namespace aapt
diff --git a/tools/aapt2/format/binary/XmlFlattener.cpp b/tools/aapt2/format/binary/XmlFlattener.cpp
index 345cc95..781b9fe 100644
--- a/tools/aapt2/format/binary/XmlFlattener.cpp
+++ b/tools/aapt2/format/binary/XmlFlattener.cpp
@@ -25,13 +25,17 @@
#include "androidfw/ResourceTypes.h"
#include "utils/misc.h"
+#include "ResourceUtils.h"
#include "SdkConstants.h"
+#include "ValueVisitor.h"
#include "format/binary/ChunkWriter.h"
#include "format/binary/ResourceTypeExtensions.h"
#include "xml/XmlDom.h"
using namespace android;
+using ::aapt::ResourceUtils::StringBuilder;
+
namespace aapt {
namespace {
@@ -88,9 +92,9 @@
ResXMLTree_cdataExt* flat_text = writer.NextBlock<ResXMLTree_cdataExt>();
// Process plain strings to make sure they get properly escaped.
- util::StringBuilder builder;
- builder.Append(node->text);
- AddString(builder.ToString(), kLowPriority, &flat_text->data);
+ StringBuilder builder;
+ builder.AppendText(node->text);
+ AddString(builder.to_string(), kLowPriority, &flat_text->data);
writer.Finish();
}
@@ -153,6 +157,9 @@
private:
DISALLOW_COPY_AND_ASSIGN(XmlFlattenerVisitor);
+ // We are adding strings to a StringPool whose strings will be sorted and merged with other
+ // string pools. That means we can't encode the ID of a string directly. Instead, we defer the
+ // writing of the ID here, until after the StringPool is merged and sorted.
void AddString(const StringPiece& str, uint32_t priority, android::ResStringPool_ref* dest,
bool treat_empty_string_as_null = false) {
if (str.empty() && treat_empty_string_as_null) {
@@ -164,6 +171,9 @@
}
}
+ // We are adding strings to a StringPool whose strings will be sorted and merged with other
+ // string pools. That means we can't encode the ID of a string directly. Instead, we defer the
+ // writing of the ID here, until after the StringPool is merged and sorted.
void AddString(const StringPool::Ref& ref, android::ResStringPool_ref* dest) {
string_refs.push_back(StringFlattenDest{ref, dest});
}
@@ -248,30 +258,39 @@
AddString(name_ref, &flat_attr->name);
}
- // Process plain strings to make sure they get properly escaped.
- StringPiece raw_value = xml_attr->value;
-
- util::StringBuilder str_builder(true /*preserve_spaces*/);
- str_builder.Append(xml_attr->value);
-
- if (!options_.keep_raw_values) {
- raw_value = str_builder.ToString();
- }
-
- if (options_.keep_raw_values || !xml_attr->compiled_value) {
- // Keep raw values if the value is not compiled or
- // if we're building a static library (need symbols).
- AddString(raw_value, kLowPriority, &flat_attr->rawValue);
- }
-
- if (xml_attr->compiled_value) {
- CHECK(xml_attr->compiled_value->Flatten(&flat_attr->typedValue));
+ std::string processed_str;
+ Maybe<StringPiece> compiled_text;
+ if (xml_attr->compiled_value != nullptr) {
+ // Make sure we're not flattening a String. A String can be referencing a string from
+ // a different StringPool than we're using here to build the binary XML.
+ String* string_value = ValueCast<String>(xml_attr->compiled_value.get());
+ if (string_value != nullptr) {
+ // Mark the String's text as needing to be serialized.
+ compiled_text = StringPiece(*string_value->value);
+ } else {
+ // Serialize this compiled value safely.
+ CHECK(xml_attr->compiled_value->Flatten(&flat_attr->typedValue));
+ }
} else {
- // Flatten as a regular string type.
- flat_attr->typedValue.dataType = android::Res_value::TYPE_STRING;
+ // There is no compiled value, so treat the raw string as compiled, once it is processed to
+ // make sure escape sequences are properly interpreted.
+ processed_str =
+ StringBuilder(true /*preserve_spaces*/).AppendText(xml_attr->value).to_string();
+ compiled_text = StringPiece(processed_str);
+ }
- AddString(str_builder.ToString(), kLowPriority,
- (ResStringPool_ref*)&flat_attr->typedValue.data);
+ if (compiled_text) {
+ // Write out the compiled text and raw_text.
+ flat_attr->typedValue.dataType = android::Res_value::TYPE_STRING;
+ AddString(compiled_text.value(), kLowPriority,
+ reinterpret_cast<ResStringPool_ref*>(&flat_attr->typedValue.data));
+ if (options_.keep_raw_values) {
+ AddString(xml_attr->value, kLowPriority, &flat_attr->rawValue);
+ } else {
+ AddString(compiled_text.value(), kLowPriority, &flat_attr->rawValue);
+ }
+ } else if (options_.keep_raw_values && !xml_attr->value.empty()) {
+ AddString(xml_attr->value, kLowPriority, &flat_attr->rawValue);
}
flat_attr->typedValue.size = util::HostToDevice16(sizeof(flat_attr->typedValue));
diff --git a/tools/aapt2/format/binary/XmlFlattener_test.cpp b/tools/aapt2/format/binary/XmlFlattener_test.cpp
index 0450f6c..08243fe 100644
--- a/tools/aapt2/format/binary/XmlFlattener_test.cpp
+++ b/tools/aapt2/format/binary/XmlFlattener_test.cpp
@@ -286,4 +286,92 @@
EXPECT_THAT(tree.getText(&len), StrEq(u"\\d{5}"));
}
+TEST_F(XmlFlattenerTest, FlattenRawValueOnlyMakesCompiledValueToo) {
+ std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"(<element foo="bar" />)");
+
+ // Raw values are kept when encoding an attribute with no compiled value, regardless of option.
+ XmlFlattenerOptions options;
+ options.keep_raw_values = false;
+
+ android::ResXMLTree tree;
+ ASSERT_TRUE(Flatten(doc.get(), &tree, options));
+
+ while (tree.next() != android::ResXMLTree::START_TAG) {
+ ASSERT_THAT(tree.getEventType(), Ne(android::ResXMLTree::BAD_DOCUMENT));
+ ASSERT_THAT(tree.getEventType(), Ne(android::ResXMLTree::END_DOCUMENT));
+ }
+
+ ASSERT_THAT(tree.getAttributeCount(), Eq(1u));
+ EXPECT_THAT(tree.getAttributeValueStringID(0), Ge(0));
+ EXPECT_THAT(tree.getAttributeDataType(0), Eq(android::Res_value::TYPE_STRING));
+ EXPECT_THAT(tree.getAttributeValueStringID(0), Eq(tree.getAttributeData(0)));
+}
+
+TEST_F(XmlFlattenerTest, FlattenCompiledStringValuePreservesRawValue) {
+ std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"(<element foo="bar" />)");
+ doc->root->attributes[0].compiled_value =
+ util::make_unique<String>(doc->string_pool.MakeRef("bar"));
+
+ // Raw values are kept when encoding a string anyways.
+ XmlFlattenerOptions options;
+ options.keep_raw_values = false;
+
+ android::ResXMLTree tree;
+ ASSERT_TRUE(Flatten(doc.get(), &tree, options));
+
+ while (tree.next() != android::ResXMLTree::START_TAG) {
+ ASSERT_THAT(tree.getEventType(), Ne(android::ResXMLTree::BAD_DOCUMENT));
+ ASSERT_THAT(tree.getEventType(), Ne(android::ResXMLTree::END_DOCUMENT));
+ }
+
+ ASSERT_THAT(tree.getAttributeCount(), Eq(1u));
+ EXPECT_THAT(tree.getAttributeValueStringID(0), Ge(0));
+ EXPECT_THAT(tree.getAttributeDataType(0), Eq(android::Res_value::TYPE_STRING));
+ EXPECT_THAT(tree.getAttributeValueStringID(0), Eq(tree.getAttributeData(0)));
+}
+
+TEST_F(XmlFlattenerTest, FlattenCompiledValueExcludesRawValueWithKeepRawOptionFalse) {
+ std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"(<element foo="true" />)");
+ doc->root->attributes[0].compiled_value = ResourceUtils::MakeBool(true);
+
+ XmlFlattenerOptions options;
+ options.keep_raw_values = false;
+
+ android::ResXMLTree tree;
+ ASSERT_TRUE(Flatten(doc.get(), &tree, options));
+
+ while (tree.next() != android::ResXMLTree::START_TAG) {
+ ASSERT_THAT(tree.getEventType(), Ne(android::ResXMLTree::BAD_DOCUMENT));
+ ASSERT_THAT(tree.getEventType(), Ne(android::ResXMLTree::END_DOCUMENT));
+ }
+
+ ASSERT_THAT(tree.getAttributeCount(), Eq(1u));
+ EXPECT_THAT(tree.getAttributeValueStringID(0), Eq(-1));
+ EXPECT_THAT(tree.getAttributeDataType(0), Eq(android::Res_value::TYPE_INT_BOOLEAN));
+}
+
+TEST_F(XmlFlattenerTest, FlattenCompiledValueExcludesRawValueWithKeepRawOptionTrue) {
+ std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"(<element foo="true" />)");
+ doc->root->attributes[0].compiled_value = ResourceUtils::MakeBool(true);
+
+ XmlFlattenerOptions options;
+ options.keep_raw_values = true;
+
+ android::ResXMLTree tree;
+ ASSERT_TRUE(Flatten(doc.get(), &tree, options));
+
+ while (tree.next() != android::ResXMLTree::START_TAG) {
+ ASSERT_THAT(tree.getEventType(), Ne(android::ResXMLTree::BAD_DOCUMENT));
+ ASSERT_THAT(tree.getEventType(), Ne(android::ResXMLTree::END_DOCUMENT));
+ }
+
+ ASSERT_THAT(tree.getAttributeCount(), Eq(1u));
+ EXPECT_THAT(tree.getAttributeValueStringID(0), Ge(0));
+
+ size_t len;
+ EXPECT_THAT(tree.getAttributeStringValue(0, &len), StrEq(u"true"));
+
+ EXPECT_THAT(tree.getAttributeDataType(0), Eq(android::Res_value::TYPE_INT_BOOLEAN));
+}
+
} // namespace aapt
diff --git a/tools/aapt2/link/ReferenceLinker.cpp b/tools/aapt2/link/ReferenceLinker.cpp
index b8f8804..9aaaa69 100644
--- a/tools/aapt2/link/ReferenceLinker.cpp
+++ b/tools/aapt2/link/ReferenceLinker.cpp
@@ -30,6 +30,7 @@
#include "util/Util.h"
#include "xml/XmlUtil.h"
+using ::aapt::ResourceUtils::StringBuilder;
using ::android::StringPiece;
namespace aapt {
@@ -133,10 +134,11 @@
// If we could not parse as any specific type, try a basic STRING.
if (!transformed && (attr->type_mask & android::ResTable_map::TYPE_STRING)) {
- util::StringBuilder string_builder;
- string_builder.Append(*raw_string->value);
+ StringBuilder string_builder;
+ string_builder.AppendText(*raw_string->value);
if (string_builder) {
- transformed = util::make_unique<String>(string_pool_->MakeRef(string_builder.ToString()));
+ transformed =
+ util::make_unique<String>(string_pool_->MakeRef(string_builder.to_string()));
}
}
diff --git a/tools/aapt2/util/Util.cpp b/tools/aapt2/util/Util.cpp
index e42145d..d1c9ca1 100644
--- a/tools/aapt2/util/Util.cpp
+++ b/tools/aapt2/util/Util.cpp
@@ -76,6 +76,34 @@
return str.substr(str.size() - suffix.size(), suffix.size()) == suffix;
}
+StringPiece TrimLeadingWhitespace(const StringPiece& str) {
+ if (str.size() == 0 || str.data() == nullptr) {
+ return str;
+ }
+
+ const char* start = str.data();
+ const char* end = start + str.length();
+
+ while (start != end && isspace(*start)) {
+ start++;
+ }
+ return StringPiece(start, end - start);
+}
+
+StringPiece TrimTrailingWhitespace(const StringPiece& str) {
+ if (str.size() == 0 || str.data() == nullptr) {
+ return str;
+ }
+
+ const char* start = str.data();
+ const char* end = start + str.length();
+
+ while (end != start && isspace(*(end - 1))) {
+ end--;
+ }
+ return StringPiece(start, end - start);
+}
+
StringPiece TrimWhitespace(const StringPiece& str) {
if (str.size() == 0 || str.data() == nullptr) {
return str;
@@ -269,162 +297,6 @@
return true;
}
-static bool AppendCodepointToUtf8String(char32_t codepoint, std::string* output) {
- ssize_t len = utf32_to_utf8_length(&codepoint, 1);
- if (len < 0) {
- return false;
- }
-
- const size_t start_append_pos = output->size();
-
- // Make room for the next character.
- output->resize(output->size() + len);
-
- char* dst = &*(output->begin() + start_append_pos);
- utf32_to_utf8(&codepoint, 1, dst, len + 1);
- return true;
-}
-
-static bool AppendUnicodeCodepoint(Utf8Iterator* iter, std::string* output) {
- char32_t code = 0;
- for (size_t i = 0; i < 4 && iter->HasNext(); i++) {
- char32_t codepoint = iter->Next();
- char32_t a;
- if (codepoint >= U'0' && codepoint <= U'9') {
- a = codepoint - U'0';
- } else if (codepoint >= U'a' && codepoint <= U'f') {
- a = codepoint - U'a' + 10;
- } else if (codepoint >= U'A' && codepoint <= U'F') {
- a = codepoint - U'A' + 10;
- } else {
- return {};
- }
- code = (code << 4) | a;
- }
- return AppendCodepointToUtf8String(code, output);
-}
-
-static bool IsCodepointSpace(char32_t codepoint) {
- if (static_cast<uint32_t>(codepoint) & 0xffffff00u) {
- return false;
- }
- return isspace(static_cast<char>(codepoint));
-}
-
-StringBuilder::StringBuilder(bool preserve_spaces) : preserve_spaces_(preserve_spaces) {
-}
-
-StringBuilder& StringBuilder::Append(const StringPiece& str) {
- if (!error_.empty()) {
- return *this;
- }
-
- // Where the new data will be appended to.
- const size_t new_data_index = str_.size();
-
- Utf8Iterator iter(str);
- while (iter.HasNext()) {
- const char32_t codepoint = iter.Next();
-
- if (last_char_was_escape_) {
- switch (codepoint) {
- case U't':
- str_ += '\t';
- break;
-
- case U'n':
- str_ += '\n';
- break;
-
- case U'#':
- case U'@':
- case U'?':
- case U'"':
- case U'\'':
- case U'\\':
- str_ += static_cast<char>(codepoint);
- break;
-
- case U'u':
- if (!AppendUnicodeCodepoint(&iter, &str_)) {
- error_ = "invalid unicode escape sequence";
- return *this;
- }
- break;
-
- default:
- // Ignore the escape character and just include the codepoint.
- AppendCodepointToUtf8String(codepoint, &str_);
- break;
- }
- last_char_was_escape_ = false;
-
- } else if (!preserve_spaces_ && codepoint == U'"') {
- if (!quote_ && trailing_space_) {
- // We found an opening quote, and we have trailing space, so we should append that
- // space now.
- if (trailing_space_) {
- // We had trailing whitespace, so replace with a single space.
- if (!str_.empty()) {
- str_ += ' ';
- }
- trailing_space_ = false;
- }
- }
- quote_ = !quote_;
-
- } else if (!preserve_spaces_ && codepoint == U'\'' && !quote_) {
- // This should be escaped.
- error_ = "unescaped apostrophe";
- return *this;
-
- } else if (codepoint == U'\\') {
- // This is an escape sequence, convert to the real value.
- if (!quote_ && trailing_space_) {
- // We had trailing whitespace, so
- // replace with a single space.
- if (!str_.empty()) {
- str_ += ' ';
- }
- trailing_space_ = false;
- }
- last_char_was_escape_ = true;
- } else {
- if (preserve_spaces_ || quote_) {
- // Quotes mean everything is taken, including whitespace.
- AppendCodepointToUtf8String(codepoint, &str_);
- } else {
- // This is not quoted text, so we will accumulate whitespace and only emit a single
- // character of whitespace if it is followed by a non-whitespace character.
- if (IsCodepointSpace(codepoint)) {
- // We found whitespace.
- trailing_space_ = true;
- } else {
- if (trailing_space_) {
- // We saw trailing space before, so replace all
- // that trailing space with one space.
- if (!str_.empty()) {
- str_ += ' ';
- }
- trailing_space_ = false;
- }
- AppendCodepointToUtf8String(codepoint, &str_);
- }
- }
- }
- }
-
- // Accumulate the added string's UTF-16 length.
- ssize_t len = utf8_to_utf16_length(reinterpret_cast<const uint8_t*>(str_.data()) + new_data_index,
- str_.size() - new_data_index);
- if (len < 0) {
- error_ = "invalid unicode code point";
- return *this;
- }
- utf16_len_ += len;
- return *this;
-}
-
std::u16string Utf8ToUtf16(const StringPiece& utf8) {
ssize_t utf16_length = utf8_to_utf16_length(
reinterpret_cast<const uint8_t*>(utf8.data()), utf8.length());
diff --git a/tools/aapt2/util/Util.h b/tools/aapt2/util/Util.h
index 7c949b90..0eb35d1 100644
--- a/tools/aapt2/util/Util.h
+++ b/tools/aapt2/util/Util.h
@@ -59,7 +59,15 @@
// Returns true if the string ends with suffix.
bool EndsWith(const android::StringPiece& str, const android::StringPiece& suffix);
-// Creates a new StringPiece16 that points to a substring of the original string without leading or
+// Creates a new StringPiece that points to a substring of the original string without leading
+// whitespace.
+android::StringPiece TrimLeadingWhitespace(const android::StringPiece& str);
+
+// Creates a new StringPiece that points to a substring of the original string without trailing
+// whitespace.
+android::StringPiece TrimTrailingWhitespace(const android::StringPiece& str);
+
+// Creates a new StringPiece that points to a substring of the original string without leading or
// trailing whitespace.
android::StringPiece TrimWhitespace(const android::StringPiece& str);
@@ -141,9 +149,12 @@
// break the string interpolation.
bool VerifyJavaStringFormat(const android::StringPiece& str);
+bool AppendStyledString(const android::StringPiece& input, bool preserve_spaces,
+ std::string* out_str, std::string* out_error);
+
class StringBuilder {
public:
- explicit StringBuilder(bool preserve_spaces = false);
+ StringBuilder() = default;
StringBuilder& Append(const android::StringPiece& str);
const std::string& ToString() const;
@@ -158,7 +169,6 @@
explicit operator bool() const;
private:
- bool preserve_spaces_;
std::string str_;
size_t utf16_len_ = 0;
bool quote_ = false;
diff --git a/tools/aapt2/util/Util_test.cpp b/tools/aapt2/util/Util_test.cpp
index 2d1242a..d4e3bec 100644
--- a/tools/aapt2/util/Util_test.cpp
+++ b/tools/aapt2/util/Util_test.cpp
@@ -41,45 +41,6 @@
EXPECT_TRUE(util::StartsWith("hello.xml", "he"));
}
-TEST(UtilTest, StringBuilderSplitEscapeSequence) {
- EXPECT_THAT(util::StringBuilder().Append("this is a new\\").Append("nline.").ToString(),
- Eq("this is a new\nline."));
-}
-
-TEST(UtilTest, StringBuilderWhitespaceRemoval) {
- EXPECT_THAT(util::StringBuilder().Append(" hey guys ").Append(" this is so cool ").ToString(),
- Eq("hey guys this is so cool"));
- EXPECT_THAT(
- util::StringBuilder().Append(" \" wow, so many \t ").Append("spaces. \"what? ").ToString(),
- Eq(" wow, so many \t spaces. what?"));
- EXPECT_THAT(util::StringBuilder().Append(" where \t ").Append(" \nis the pie?").ToString(),
- Eq("where is the pie?"));
-}
-
-TEST(UtilTest, StringBuilderEscaping) {
- EXPECT_THAT(util::StringBuilder()
- .Append(" hey guys\\n ")
- .Append(" this \\t is so\\\\ cool ")
- .ToString(),
- Eq("hey guys\n this \t is so\\ cool"));
- EXPECT_THAT(util::StringBuilder().Append("\\@\\?\\#\\\\\\'").ToString(), Eq("@?#\\\'"));
-}
-
-TEST(UtilTest, StringBuilderMisplacedQuote) {
- util::StringBuilder builder;
- EXPECT_FALSE(builder.Append("they're coming!"));
-}
-
-TEST(UtilTest, StringBuilderUnicodeCodes) {
- EXPECT_THAT(util::StringBuilder().Append("\\u00AF\\u0AF0 woah").ToString(),
- Eq("\u00AF\u0AF0 woah"));
- EXPECT_FALSE(util::StringBuilder().Append("\\u00 yo"));
-}
-
-TEST(UtilTest, StringBuilderPreserveSpaces) {
- EXPECT_THAT(util::StringBuilder(true /*preserve_spaces*/).Append("\"").ToString(), Eq("\""));
-}
-
TEST(UtilTest, TokenizeInput) {
auto tokenizer = util::Tokenize(StringPiece("this| is|the|end"), '|');
auto iter = tokenizer.begin();
diff --git a/tools/aapt2/xml/XmlDom.cpp b/tools/aapt2/xml/XmlDom.cpp
index 7b748ce..b6cd086 100644
--- a/tools/aapt2/xml/XmlDom.cpp
+++ b/tools/aapt2/xml/XmlDom.cpp
@@ -248,8 +248,14 @@
android::Res_value res_value;
if (parser->getAttributeValue(i, &res_value) > 0) {
- attr.compiled_value = ResourceUtils::ParseBinaryResValue(
- ResourceType::kAnim, {}, parser->getStrings(), res_value, out_pool);
+ // Only compile the value if it is not a string, or it is a string that differs from
+ // the raw attribute value.
+ int32_t raw_value_idx = parser->getAttributeValueStringID(i);
+ if (res_value.dataType != android::Res_value::TYPE_STRING || raw_value_idx < 0 ||
+ static_cast<uint32_t>(raw_value_idx) != res_value.data) {
+ attr.compiled_value = ResourceUtils::ParseBinaryResValue(
+ ResourceType::kAnim, {}, parser->getStrings(), res_value, out_pool);
+ }
}
el->attributes.push_back(std::move(attr));
@@ -262,8 +268,8 @@
// an enum, which causes errors when qualifying it with android::
using namespace android;
- StringPool string_pool;
- std::unique_ptr<Element> root;
+ std::unique_ptr<XmlResource> xml_resource = util::make_unique<XmlResource>();
+
std::stack<Element*> node_stack;
std::unique_ptr<Element> pending_element;
@@ -322,12 +328,12 @@
}
Element* this_el = el.get();
- CopyAttributes(el.get(), &tree, &string_pool);
+ CopyAttributes(el.get(), &tree, &xml_resource->string_pool);
if (!node_stack.empty()) {
node_stack.top()->AppendChild(std::move(el));
} else {
- root = std::move(el);
+ xml_resource->root = std::move(el);
}
node_stack.push(this_el);
break;
@@ -359,7 +365,7 @@
break;
}
}
- return util::make_unique<XmlResource>(ResourceFile{}, std::move(string_pool), std::move(root));
+ return xml_resource;
}
std::unique_ptr<XmlResource> XmlResource::Clone() const {
diff --git a/tools/incident_section_gen/main.cpp b/tools/incident_section_gen/main.cpp
index e7b269a..9183918 100644
--- a/tools/incident_section_gen/main.cpp
+++ b/tools/incident_section_gen/main.cpp
@@ -411,6 +411,11 @@
case SECTION_LOG:
printf(" new LogSection(%d, %s),\n", field->number(), s.args().c_str());
break;
+ case SECTION_GZIP:
+ printf(" new GZipSection(%d,", field->number());
+ splitAndPrint(s.args());
+ printf(" NULL),\n");
+ break;
}
}
printf(" NULL };\n");