diff options
283 files changed, 8833 insertions, 1949 deletions
diff --git a/Android.mk b/Android.mk index 58e21ffd1617..470714b184ae 100644 --- a/Android.mk +++ b/Android.mk @@ -143,7 +143,6 @@ framework_docs_LOCAL_API_CHECK_JAVA_LIBRARIES := \ bouncycastle \ okhttp \ ext \ - icu4j \ framework \ voip-common \ @@ -195,6 +194,7 @@ framework_docs_LOCAL_DROIDDOC_OPTIONS := \ -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 c13eee6c9068..d56d1e209096 100644 --- a/api/current.txt +++ b/api/current.txt @@ -1378,6 +1378,7 @@ package android { 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 @@ package android.app.admin { 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 @@ package android.content { 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 @@ package android.media.audiofx { 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 @@ package android.media.audiofx { 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 @@ package android.os { 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 @@ package android.provider { 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 3aca59a5724d..375f840ae98a 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -617,6 +617,7 @@ package android.app.backup { 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 @@ package android.provider { 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 @@ package android.telephony.ims.feature { 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 6bdd9becff37..3a47fe1946c2 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_TEST_DATA := $(call find-test-data-in-subdirs, $(LOCAL_PATH), *, testdata) 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 1bd146850ea9..6dd811452e9e 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 db60794a7225..64da6773686a 100644 --- a/cmds/incidentd/src/FdBuffer.cpp +++ b/cmds/incidentd/src/FdBuffer.cpp @@ -76,6 +76,7 @@ status_t FdBuffer::read(int fd, int64_t timeout) { return -errno; } } else if (amt == 0) { + VLOG("Reached EOF of fd=%d", fd); break; } mBuffer.wp()->move(amt); @@ -156,10 +157,10 @@ status_t FdBuffer::readProcessedDataInStream(int fd, int toFd, int fromFd, int64 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 @@ status_t FdBuffer::readProcessedDataInStream(int fd, int toFd, int fromFd, int64 // 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 @@ status_t FdBuffer::readProcessedDataInStream(int fd, int toFd, int fromFd, int64 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 5bfa0938f5e8..66a3de154c51 100644 --- a/cmds/incidentd/src/FdBuffer.h +++ b/cmds/incidentd/src/FdBuffer.h @@ -26,7 +26,7 @@ using namespace android::util; 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 @@ public: */ 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 64eae3acd34c..334d77c1494e 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 FIELD_ID_INCIDENT_METADATA = 2; 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 @@ status_t MetadataSection::Execute(ReportRequestSet* requests) const { 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 @@ status_t FileSection::Execute(ReportRequestSet* requests) const { 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 @@ status_t FileSection::Execute(ReportRequestSet* requests) const { // 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 @@ status_t FileSection::Execute(ReportRequestSet* requests) const { 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 @@ status_t WorkerThreadSection::Execute(ReportRequestSet* requests) const { } // ================================================================================ -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 @@ status_t CommandSection::Execute(ReportRequestSet* requests) const { 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 @@ status_t CommandSection::Execute(ReportRequestSet* requests) const { } // 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 d6446810c40f..8294be133bcb 100644 --- a/cmds/incidentd/src/Section.h +++ b/cmds/incidentd/src/Section.h @@ -84,6 +84,21 @@ private: }; /** + * 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 @@ public: 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 2415860572fb..fc7cec9dbb40 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 @@ bool Fpipe::init() { return Pipe(&mRead, &mWrite); } 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 09aa0404277a..db7ec82d83f4 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 @@ private: 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 000000000000..a8e3c02faab5 --- /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 Binary files differnew file mode 100644 index 000000000000..fba449f8922c --- /dev/null +++ b/cmds/incidentd/testdata/kmsg.txt.gz diff --git a/cmds/incidentd/tests/Section_test.cpp b/cmds/incidentd/tests/Section_test.cpp index 026bf7419eb1..1528224447af 100644 --- a/cmds/incidentd/tests/Section_test.cpp +++ b/cmds/incidentd/tests/Section_test.cpp @@ -39,10 +39,22 @@ using namespace android::binder; 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 @@ protected: 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 @@ TEST(SectionTest, HeaderSection) { 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 @@ TEST(SectionTest, HeaderSection) { "\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 @@ TEST(SectionTest, FileSection) { 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 @@ TEST(SectionTest, LogSectionBinary) { 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 @@ TEST(SectionTest, LogSectionSystem) { 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 @@ TEST(SectionTest, TestFilterPiiTaggedFields) { 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 @@ TEST(SectionTest, TestBadFdRequest) { 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 @@ TEST(SectionTest, TestBadRequests) { 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 @@ TEST(SectionTest, TestMultipleRequests) { 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 @@ TEST(SectionTest, TestMultipleRequestsBySpec) { 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 d7ce352d4b78..7f76ab14a171 100644 --- a/cmds/statsd/Android.mk +++ b/cmds/statsd/Android.mk @@ -21,9 +21,11 @@ statsd_common_src := \ 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 @@ LOCAL_SRC_FILES := \ 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 21f30e288c25..b0e2c43e8cdd 100644 --- a/cmds/statsd/src/FieldValue.h +++ b/cmds/statsd/src/FieldValue.h @@ -217,6 +217,14 @@ struct Matcher { 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 @@ struct Matcher { } 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 1502a00abee9..cc7063131f18 100644 --- a/cmds/statsd/src/HashableDimensionKey.cpp +++ b/cmds/statsd/src/HashableDimensionKey.cpp @@ -166,10 +166,11 @@ void filterGaugeValues(const std::vector<Matcher>& matcherFields, } } -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 89fe317834d8..57bdf68091d0 100644 --- a/cmds/statsd/src/HashableDimensionKey.h +++ b/cmds/statsd/src/HashableDimensionKey.h @@ -40,7 +40,7 @@ public: mValues = values; } - HashableDimensionKey(){}; + HashableDimensionKey() {}; HashableDimensionKey(const HashableDimensionKey& that) : mValues(that.getValues()){}; @@ -144,7 +144,8 @@ bool filterValues(const std::vector<Matcher>& matcherFields, const std::vector<F 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 3c9dd68c1cc2..087e59657247 100644 --- a/cmds/statsd/src/StatsLogProcessor.cpp +++ b/cmds/statsd/src/StatsLogProcessor.cpp @@ -66,11 +66,13 @@ const int FIELD_ID_CURRENT_REPORT_ELAPSED_NANOS = 4; #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 @@ StatsLogProcessor::~StatsLogProcessor() { 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::OnLogEvent(LogEvent* event) { 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 @@ void StatsLogProcessor::OnConfigUpdated(const ConfigKey& key, const StatsdConfig 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 144430639d9f..4d9f18509ddb 100644 --- a/cmds/statsd/src/StatsLogProcessor.h +++ b/cmds/statsd/src/StatsLogProcessor.h @@ -34,7 +34,8 @@ namespace statsd { 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 @@ public: 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 @@ private: 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 c27b130678cd..ee4f434aa6a2 100644 --- a/cmds/statsd/src/StatsService.cpp +++ b/cmds/statsd/src/StatsService.cpp @@ -50,49 +50,73 @@ namespace statsd { 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 @@ status_t StatsService::cmd_config(FILE* in, FILE* out, FILE* err, Vector<String8 } 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 @@ status_t StatsService::cmd_config(FILE* in, FILE* out, FILE* err, Vector<String8 } // 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_dump_memory_info(FILE* out) { 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 @@ Status StatsService::informAnomalyAlarmFired() { 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 @@ Status StatsService::statsCompanionReady() { "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 9690de702c24..e0a1299a9c38 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 @@ public: 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 @@ private: sp<StatsLogProcessor> mProcessor; /** - * The anomaly detector. + * The alarm monitor for anomaly detection. + */ + const sp<AlarmMonitor> mAnomalyAlarmMonitor; + + /** + * The alarm monitor for alarms to directly trigger subscriber. */ - const sp<AnomalyMonitor> mAnomalyMonitor; + 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 index ca34dc6d87fd..78f0c2b09537 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 @@ void AnomalyMonitor::setStatsCompanionService(sp<IStatsCompanionService> statsCo 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::add(sp<const AnomalyAlarm> alarm) { } } -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 @@ void AnomalyMonitor::remove(sp<const AnomalyAlarm> alarm) { // 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 @@ unordered_set<sp<const AnomalyAlarm>, SpHash<AnomalyAlarm>> AnomalyMonitor::popS 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 index 7acc7904bb57..3badb1faece6 100644 --- a/cmds/statsd/src/anomaly/AnomalyMonitor.h +++ b/cmds/statsd/src/anomaly/AlarmMonitor.h @@ -41,33 +41,34 @@ namespace statsd { * 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 @@ public: /** * 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 @@ private: /** * 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 @@ private: /** 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 000000000000..eb283838afd7 --- /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 000000000000..d59dacaa1b69 --- /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 c40eb812f949..642604e17bf5 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 @@ bool AnomalyTracker::isInRefractoryPeriod(const uint64_t& timestampNs, } 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 3be959d14109..e3f493cfe7cf 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 @@ public: 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 @@ public: } // 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 3ba943c310bb..31d50be7ec26 100644 --- a/cmds/statsd/src/anomaly/DurationAnomalyTracker.cpp +++ b/cmds/statsd/src/anomaly/DurationAnomalyTracker.cpp @@ -24,8 +24,9 @@ namespace android { 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 @@ void DurationAnomalyTracker::startAlarm(const MetricDimensionKey& dimensionKey, 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 @@ void DurationAnomalyTracker::stopAlarm(const MetricDimensionKey& dimensionKey) { 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::stopAllAlarms() { } } -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 15aef29bc644..51186df3e93d 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 @@ using std::unordered_map; 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 @@ public: // 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 @@ public: // 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 000000000000..e796d19efd93 --- /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 000000000000..dba8981a72aa --- /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 7159b9b24a6a..42ae0226228e 100644 --- a/cmds/statsd/src/atoms.proto +++ b/cmds/statsd/src/atoms.proto @@ -101,6 +101,9 @@ message Atom { 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 @@ message Atom { 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 @@ message CallStateChanged { } /** + * 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 @@ message Temperature { // 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 e2e02533f17f..2a5679cc79fd 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 66cb1d04a4e1..0881d44c96d4 100644 --- a/cmds/statsd/src/guardrail/StatsdStats.cpp +++ b/cmds/statsd/src/guardrail/StatsdStats.cpp @@ -47,11 +47,13 @@ const int FIELD_ID_UIDMAP_STATS = 8; 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 @@ void StatsdStats::noteRegisteredAnomalyAlarmChanged() { 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 @@ void StatsdStats::resetInternalLocked() { 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 @@ void StatsdStats::dumpStats(FILE* out) const { 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 @@ void StatsdStats::dumpStats(FILE* out) const { 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 @@ void StatsdStats::dumpStats(std::vector<uint8_t>* output, bool reset) { 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 @@ void StatsdStats::dumpStats(std::vector<uint8_t>* output, bool reset) { } // 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 8c16e4e9c2eb..24ac6883f444 100644 --- a/cmds/statsd/src/guardrail/StatsdStats.h +++ b/cmds/statsd/src/guardrail/StatsdStats.h @@ -170,6 +170,11 @@ public: 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 @@ private: // 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 461200905a25..5d5e64e48e54 100644 --- a/cmds/statsd/src/matchers/matcher_util.cpp +++ b/cmds/statsd/src/matchers/matcher_util.cpp @@ -87,6 +87,10 @@ bool tryMatchString(const UidMap& uidMap, const Field& field, const Value& value 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 @@ bool matchesSimple(const UidMap& uidMap, const FieldValueMatcher& matcher, } 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 @@ bool matchesSimple(const UidMap& uidMap, const FieldValueMatcher& matcher, return true; } } + return false; } + 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::kEqInt: + } + 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 @@ bool matchesSimple(const UidMap& uidMap, const FieldValueMatcher& matcher, } } 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 @@ bool matchesSimple(const UidMap& uidMap, const FieldValueMatcher& matcher, } } 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 @@ bool matchesSimple(const UidMap& uidMap, const FieldValueMatcher& matcher, } } 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 @@ bool matchesSimple(const UidMap& uidMap, const FieldValueMatcher& matcher, } } 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 @@ bool matchesSimple(const UidMap& uidMap, const FieldValueMatcher& matcher, } } 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 @@ bool matchesSimple(const UidMap& uidMap, const FieldValueMatcher& matcher, } } 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 @@ bool matchesSimple(const UidMap& uidMap, const FieldValueMatcher& matcher, } } return false; + } default: return false; } diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.cpp b/cmds/statsd/src/metrics/DurationMetricProducer.cpp index 9c65371ba958..80329c3a8a42 100644 --- a/cmds/statsd/src/metrics/DurationMetricProducer.cpp +++ b/cmds/statsd/src/metrics/DurationMetricProducer.cpp @@ -101,6 +101,18 @@ DurationMetricProducer::DurationMetricProducer(const ConfigKey& key, const Durat } 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 @@ DurationMetricProducer::~DurationMetricProducer() { 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 @@ void DurationMetricProducer::onSlicedConditionMayChangeLocked(const uint64_t eve 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); + } + } + 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); + } } - newKeys.insert(newKey); } } } @@ -173,8 +214,10 @@ void DurationMetricProducer::onConditionChangedLocked(const bool conditionMet, 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 @@ void DurationMetricProducer::flushIfNeededLocked(const uint64_t& eventTimeNs) { 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::flushIfNeededLocked(const uint64_t& eventTimeNs) { } 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 @@ void DurationMetricProducer::dumpStatesLocked(FILE* out, bool verbose) const { 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 @@ bool DurationMetricProducer::hitGuardRailLocked(const MetricDimensionKey& newKey return false; } +void DurationMetricProducer::handleStartEvent(const MetricDimensionKey& eventKey, + const ConditionKey& conditionKeys, + bool condition, const LogEvent& event) { + const auto& whatKey = eventKey.getDimensionKeyInWhat(); + const auto& condKey = eventKey.getDimensionKeyInCondition(); + + auto whatIt = mCurrentSlicedDurationTrackerMap.find(whatKey); + if (whatIt == mCurrentSlicedDurationTrackerMap.end()) { + if (hitGuardRailLocked(eventKey)) { + return; + } + 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(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()) { + it->second->noteStart(DEFAULT_DIMENSION_KEY, condition, + event.GetElapsedTimestampNs(), conditionKeys); + } else { + for (const auto& value : values) { + 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& pair : mCurrentSlicedDurationTrackerMap) { - pair.second->noteStopAll(event.GetElapsedTimestampNs()); + for (auto& whatIt : mCurrentSlicedDurationTrackerMap) { + for (auto& pair : whatIt.second) { + pair.second->noteStopAll(event.GetElapsedTimestampNs()); + } } return; } - if (mCurrentSlicedDurationTrackerMap.find(eventKey) == mCurrentSlicedDurationTrackerMap.end()) { - if (hitGuardRailLocked(eventKey)) { + 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; } - mCurrentSlicedDurationTrackerMap[eventKey] = createDurationTracker(eventKey); + + 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; } - auto it = mCurrentSlicedDurationTrackerMap.find(eventKey); + bool condition; + ConditionKey conditionKey; + std::unordered_set<HashableDimensionKey> dimensionKeysInCondition; + if (mConditionSliced) { + for (const auto& link : mMetric2ConditionLinks) { + getDimensionForCondition(event.getValues(), link, &conditionKey[link.conditionId]); + } - 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); + auto conditionState = + mWizard->query(mConditionTrackerIndex, conditionKey, mDimensionsInCondition, + &dimensionKeysInCondition); + condition = (conditionState == ConditionState::kTrue); + if (mDimensionsInCondition.empty() && condition) { + dimensionKeysInCondition.insert(DEFAULT_DIMENSION_KEY); } } 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); - } + 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 5f29281a8a3c..73d074f1a2ce 100644 --- a/cmds/statsd/src/metrics/DurationMetricProducer.h +++ b/cmds/statsd/src/metrics/DurationMetricProducer.h @@ -46,15 +46,20 @@ public: 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 @@ private: // 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 f3307dc1d1e3..18694a1cadcd 100644 --- a/cmds/statsd/src/metrics/MetricProducer.cpp +++ b/cmds/statsd/src/metrics/MetricProducer.cpp @@ -37,7 +37,7 @@ void MetricProducer::onMatchedLogEventLocked(const size_t matcherIndex, const Lo 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 @@ void MetricProducer::onMatchedLogEventLocked(const size_t matcherIndex, const Lo condition = mCondition; } + if (mDimensionsInCondition.empty() && condition) { + dimensionKeysInCondition.insert(DEFAULT_DIMENSION_KEY); + } + vector<HashableDimensionKey> dimensionInWhatValues; - if (mDimensionsInWhat.size() > 0) { + if (!mDimensionsInWhat.empty()) { filterValues(mDimensionsInWhat, event.getValues(), &dimensionInWhatValues); + } else { + dimensionInWhatValues.push_back(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()) { + 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 8663e5eed7a7..2bf624160475 100644 --- a/cmds/statsd/src/metrics/MetricProducer.h +++ b/cmds/statsd/src/metrics/MetricProducer.h @@ -124,7 +124,8 @@ public: } /* 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 @@ protected: 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 e75b710cc9db..4c8a7d8a92d5 100644 --- a/cmds/statsd/src/metrics/MetricsManager.cpp +++ b/cmds/statsd/src/metrics/MetricsManager.cpp @@ -49,13 +49,17 @@ namespace statsd { 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 @@ void MetricsManager::onLogEvent(const LogEvent& event) { // 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::onLogEvent(const LogEvent& event) { } } -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 d4f844fe386c..b50ef4a0c5a2 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 @@ namespace statsd { 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 @@ public: 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 @@ private: // 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 71e5c33b1b88..9912afa01c3d 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 initMetrics(const ConfigKey& key, const StatsdConfig& config, const long ti 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 @@ bool initAlerts(const StatsdConfig& config, } 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 @@ bool initAlerts(const StatsdConfig& config, } 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 @@ bool initAlerts(const StatsdConfig& config, 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 @@ bool initStatsdConfig(const ConfigKey& key, const StatsdConfig& config, 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 4f19ada5b022..edda53d5e0cf 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 @@ bool initMetrics( // 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 @@ bool initStatsdConfig(const ConfigKey& key, const StatsdConfig& config, } // 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 272e90be662d..269f25b30c84 100644 --- a/cmds/statsd/src/stats_log.proto +++ b/cmds/statsd/src/stats_log.proto @@ -164,4 +164,4 @@ message ConfigMetricsReportList { 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 a31385470c9f..1e8aa12112bc 100644 --- a/cmds/statsd/src/statsd_config.proto +++ b/cmds/statsd/src/statsd_config.proto @@ -74,6 +74,9 @@ message FieldValueMatcher { int64 gte_int = 11; MessageMatcher matches_tuple = 12; + + StringListMatcher eq_any_string = 13; + StringListMatcher neq_all_string = 14; } } @@ -81,6 +84,10 @@ message MessageMatcher { 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 6a1db72b3911..781ecede1700 100644 --- a/cmds/statsd/src/storage/StorageManager.cpp +++ b/cmds/statsd/src/storage/StorageManager.cpp @@ -195,6 +195,20 @@ void StorageManager::appendConfigMetricsReport(ProtoOutputStream& proto) { } } +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 d319674b8987..6c8ed0a96704 100644 --- a/cmds/statsd/src/storage/StorageManager.h +++ b/cmds/statsd/src/storage/StorageManager.h @@ -37,6 +37,11 @@ public: 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 d9a8fc894804..1c18f673aeb9 100644 --- a/cmds/statsd/src/subscriber/IncidentdReporter.cpp +++ b/cmds/statsd/src/subscriber/IncidentdReporter.cpp @@ -28,10 +28,10 @@ namespace android { 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 @@ bool GenerateIncidentReport(const IncidentdDetails& config, const Alert& alert, 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 229ed778af3a..1b83fe23de8f 100644 --- a/cmds/statsd/src/subscriber/IncidentdReporter.h +++ b/cmds/statsd/src/subscriber/IncidentdReporter.h @@ -26,7 +26,7 @@ namespace statsd { /** * 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 index 920ca08ef955..1fccb35eccb5 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 62bdba406de2..90c3a2f1a539 100644 --- a/cmds/statsd/tests/ConfigManager_test.cpp +++ b/cmds/statsd/tests/ConfigManager_test.cpp @@ -65,8 +65,12 @@ MATCHER_P(StatsdConfigEq, id, 0) { 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 2320a9d89f6b..36c6e0c964bd 100644 --- a/cmds/statsd/tests/LogEntryMatcher_test.cpp +++ b/cmds/statsd/tests/LogEntryMatcher_test.cpp @@ -294,6 +294,159 @@ TEST(AtomMatcherTest, TestAttributionMatcher) { 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 f90ca409e84c..5d8c3f72551e 100644 --- a/cmds/statsd/tests/MetricsManager_test.cpp +++ b/cmds/statsd/tests/MetricsManager_test.cpp @@ -271,19 +271,25 @@ StatsdConfig buildCirclePredicates() { 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, TestGoodConfig) { 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 cb72697941e0..3238b74fb635 100644 --- a/cmds/statsd/tests/StatsLogProcessor_test.cpp +++ b/cmds/statsd/tests/StatsLogProcessor_test.cpp @@ -41,7 +41,13 @@ using android::util::ProtoOutputStream; */ 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 @@ public: 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, TestRateLimitByteSize) { 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, TestRateLimitBroadcast) { 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 f26c10d33e67..ca656ed9ab35 100644 --- a/cmds/statsd/tests/UidMap_test.cpp +++ b/cmds/statsd/tests/UidMap_test.cpp @@ -36,9 +36,11 @@ const string kApp2 = "app2.sharing.1"; 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 000000000000..3330ee93eddf --- /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 20ddbe9f0e38..9a0de0d81802 100644 --- a/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp +++ b/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp @@ -201,6 +201,7 @@ TEST(CountMetricProducerTest, TestEventsWithSlicedCondition) { } 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 @@ TEST(CountMetricProducerTest, TestEventWithAppUpgrade) { 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, TestEventWithAppUpgradeInNextBucket) { } TEST(CountMetricProducerTest, TestAnomalyDetectionUnSliced) { + sp<AlarmMonitor> alarmMonitor; Alert alert; alert.set_id(11); alert.set_metric_id(1); @@ -337,7 +339,7 @@ TEST(CountMetricProducerTest, TestAnomalyDetectionUnSliced) { 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 79695967a6dd..1b22d75da7b4 100644 --- a/cmds/statsd/tests/metrics/DurationMetricProducer_test.cpp +++ b/cmds/statsd/tests/metrics/DurationMetricProducer_test.cpp @@ -239,6 +239,7 @@ TEST(DurationMetricTrackerTest, TestSumDurationWithUpgradeInFollowingBucket) { } 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 @@ TEST(DurationMetricTrackerTest, TestSumDurationAnomalyWithUpgrade) { 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 0eb8ce2603bd..77b3ace90aff 100644 --- a/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp +++ b/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp @@ -129,6 +129,7 @@ TEST(GaugeMetricProducerTest, TestNoCondition) { } TEST(GaugeMetricProducerTest, TestPushedEventsWithUpgrade) { + sp<AlarmMonitor> alarmMonitor; GaugeMetric metric; metric.set_id(metricId); metric.set_bucket(ONE_MINUTE); @@ -145,8 +146,9 @@ TEST(GaugeMetricProducerTest, TestPushedEventsWithUpgrade) { 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, TestWithCondition) { } TEST(GaugeMetricProducerTest, TestAnomalyDetection) { + sp<AlarmMonitor> alarmMonitor; sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); shared_ptr<MockStatsPullerManager> pullerManager = @@ -363,7 +366,7 @@ TEST(GaugeMetricProducerTest, TestAnomalyDetection) { 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 a164c12134b5..83b1cbfb6fb3 100644 --- a/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp +++ b/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp @@ -276,13 +276,15 @@ TEST(MaxDurationTrackerTest, TestAnomalyDetection) { 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 @@ TEST(MaxDurationTrackerTest, TestAnomalyPredictedTimestamp) { 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 @@ TEST(MaxDurationTrackerTest, TestAnomalyPredictedTimestamp_UpdatedOnStop) { 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 cb731c555e90..aa41038b9acb 100644 --- a/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp +++ b/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp @@ -334,7 +334,9 @@ TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp) { 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 @@ TEST(OringDurationTrackerTest, TestAnomalyDetectionExpiredAlarm) { 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 @@ TEST(OringDurationTrackerTest, TestAnomalyDetectionFiredAlarm) { 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 @@ TEST(OringDurationTrackerTest, TestAnomalyDetectionFiredAlarm) { 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 ce4fa3278493..a0addcccb7a1 100644 --- a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp +++ b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp @@ -346,6 +346,7 @@ TEST(ValueMetricProducerTest, TestPushedEventsWithoutCondition) { } TEST(ValueMetricProducerTest, TestAnomalyDetection) { + sp<AlarmMonitor> alarmMonitor; Alert alert; alert.set_id(101); alert.set_metric_id(metricId); @@ -365,7 +366,7 @@ TEST(ValueMetricProducerTest, TestAnomalyDetection) { -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 7568348108c4..242b6ebe5ec1 100644 --- a/cmds/statsd/tests/statsd_test_util.cpp +++ b/cmds/statsd/tests/statsd_test_util.cpp @@ -391,9 +391,10 @@ std::unique_ptr<LogEvent> CreateIsolatedUidChangedEvent( 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 feeb0f23e751..cab6744e3c81 100644 --- a/core/java/android/app/ActivityManagerInternal.java +++ b/core/java/android/app/ActivityManagerInternal.java @@ -27,6 +27,7 @@ import android.os.IBinder; 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 class ActivityManagerInternal { 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 class ActivityManagerInternal { 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 21d146a5500a..872370e0d0ed 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -3900,8 +3900,7 @@ public final class ActivityThread extends ClientTransactionHandler { @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 @@ public final class ActivityThread extends ClientTransactionHandler { 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 f8f50a278511..21fb18a212a8 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -1354,11 +1354,10 @@ public class ApplicationPackageManager extends PackageManager { 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 cb52a855e7ca..6bc66ecdb261 100644 --- a/core/java/android/app/ClientTransactionHandler.java +++ b/core/java/android/app/ClientTransactionHandler.java @@ -65,8 +65,7 @@ public abstract class ClientTransactionHandler { /** 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 16e36bc54ef2..7b6a28810f3b 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -56,8 +56,11 @@ import android.os.PersistableBundle; 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 @@ public class DevicePolicyManager { } /** + * 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 @@ public class DevicePolicyManager { * <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 @@ public class DevicePolicyManager { * @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 @@ public class DevicePolicyManager { 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 @@ public class DevicePolicyManager { } /** - * 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 @@ public class DevicePolicyManager { * @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 @@ public class DevicePolicyManager { * * @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 5218a7340ec9..c29369fe96a8 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -192,6 +192,8 @@ interface IDevicePolicyManager { 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 f456395aa2b5..0963594bc00e 100644 --- a/core/java/android/app/backup/BackupTransport.java +++ b/core/java/android/app/backup/BackupTransport.java @@ -83,6 +83,13 @@ public class BackupTransport { */ 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 578f0e3d24cb..65e429126ac2 100644 --- a/core/java/android/app/servertransaction/PauseActivityItem.java +++ b/core/java/android/app/servertransaction/PauseActivityItem.java @@ -42,8 +42,8 @@ public class PauseActivityItem extends ActivityLifecycleItem { 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 b66d61b76d5f..059e0af27a33 100644 --- a/core/java/android/app/servertransaction/TransactionExecutor.java +++ b/core/java/android/app/servertransaction/TransactionExecutor.java @@ -185,8 +185,8 @@ public class TransactionExecutor { 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 718e465bf0de..73b6eb27bed3 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 @@ import java.util.ArrayList; @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 @@ public class ClipboardManager extends android.text.ClipboardManager { 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 @@ public class ClipboardManager extends android.text.ClipboardManager { /** {@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 @@ public class ClipboardManager extends android.text.ClipboardManager { * 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 @@ public class ClipboardManager extends android.text.ClipboardManager { } /** + * Clears any current primary clip on the clipboard. + * + * @see #setPrimaryClip(ClipData) + */ + 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 ClipData getPrimaryClip() { + public @Nullable ClipData getPrimaryClip() { try { return mService.getPrimaryClip(mContext.getOpPackageName()); } catch (RemoteException e) { @@ -124,8 +136,10 @@ public class ClipboardManager extends android.text.ClipboardManager { /** * 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 af0b8f0593a6..135a4363ef21 100644 --- a/core/java/android/content/IClipboard.aidl +++ b/core/java/android/content/IClipboard.aidl @@ -27,6 +27,7 @@ import android.content.IOnPrimaryClipChangedListener; */ 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 41aa9c374978..514112f1e81f 100644 --- a/core/java/android/content/pm/PackageManagerInternal.java +++ b/core/java/android/content/pm/PackageManagerInternal.java @@ -236,6 +236,11 @@ public abstract class PackageManagerInternal { 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 @@ public abstract class PackageManagerInternal { /** 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 93690bf3aef9..2baf539317e9 100644 --- a/core/java/android/content/res/Configuration.java +++ b/core/java/android/content/res/Configuration.java @@ -1615,12 +1615,7 @@ public final class Configuration implements Parcelable, Comparable<Configuration 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 @@ public final class Configuration implements Parcelable, Comparable<Configuration 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 bae373d7564b..9d94ecce1b8e 100644 --- a/core/java/android/net/NetworkCapabilities.java +++ b/core/java/android/net/NetworkCapabilities.java @@ -117,6 +117,7 @@ public final class NetworkCapabilities implements Parcelable { NET_CAPABILITY_FOREGROUND, NET_CAPABILITY_NOT_CONGESTED, NET_CAPABILITY_NOT_SUSPENDED, + NET_CAPABILITY_OEM_PAID, }) public @interface NetCapability { } @@ -264,8 +265,15 @@ public final class NetworkCapabilities implements Parcelable { */ 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 @@ public final class NetworkCapabilities implements Parcelable { (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 @@ public final class NetworkCapabilities implements Parcelable { 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 eae52171ee48..29c298e31ae9 100644 --- a/core/java/android/os/IStatsCompanionService.aidl +++ b/core/java/android/os/IStatsCompanionService.aidl @@ -41,7 +41,7 @@ interface IStatsCompanionService { 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 @@ interface IStatsCompanionService { */ 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 682a24f17648..2a68714431d4 100644 --- a/core/java/android/os/IStatsManager.aidl +++ b/core/java/android/os/IStatsManager.aidl @@ -47,6 +47,13 @@ interface IStatsManager { 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 811cc5ed472c..97d415e18a73 100644 --- a/core/java/android/os/PowerManager.java +++ b/core/java/android/os/PowerManager.java @@ -538,6 +538,7 @@ public final class PowerManager { ServiceType.DATA_SAVER, ServiceType.FORCE_ALL_APPS_STANDBY, ServiceType.OPTIONAL_SENSORS, + ServiceType.AOD, }) public @interface ServiceType { int NULL = 0; @@ -551,6 +552,7 @@ public final class PowerManager { 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 185620066454..2693bab5203e 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -913,6 +913,9 @@ public class UserManager { * 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 class UserManager { */ 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 57418c8b9879..ca4c796ab62d 100644 --- a/core/java/android/os/ZygoteProcess.java +++ b/core/java/android/os/ZygoteProcess.java @@ -493,11 +493,12 @@ public class ZygoteProcess { * 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 @@ public class ZygoteProcess { 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 60df467bc20f..70de09ebe85f 100644 --- a/core/java/android/provider/CallLog.java +++ b/core/java/android/provider/CallLog.java @@ -223,14 +223,14 @@ public class CallLog { /** 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 cb38c0fc55a3..02994079d6ec 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -8581,6 +8581,7 @@ public final class Settings { * (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 e9df3705db6e..f6127e184139 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 @@ public class ConfirmationDialog { private byte[] mExtraData; private ConfirmationCallback mCallback; private Executor mExecutor; + private Context mContext; private final KeyStore mKeyStore = KeyStore.getInstance(); @@ -190,15 +194,39 @@ public class ConfirmationDialog { 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 @@ public class ConfirmationDialog { 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 @@ public class ConfirmationDialog { * @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 ddc7bd2366ac..c51b7737e823 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 945fcf6f88f2..c0b52f714d0b 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 85829bed9191..40076f732b98 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 b19cee2d31a4..e475dc36e1c3 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.Retention; 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 a940fdc778a9..19a087d5d1d4 100644 --- a/core/java/android/security/keystore/KeychainProtectionParams.java +++ b/core/java/android/security/keystore/KeychainProtectionParams.java @@ -28,23 +28,7 @@ import java.lang.annotation.RetentionPolicy; 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 23aec25eb128..cf18fd1c6a0b 100644 --- a/core/java/android/security/keystore/KeychainSnapshot.java +++ b/core/java/android/security/keystore/KeychainSnapshot.java @@ -25,21 +25,7 @@ import com.android.internal.util.Preconditions; 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 b07fb9cdd002..097028457c9e 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 6f566af1dc7d..12be607a23d4 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 4a0de5f2c7f0..145261e3b71d 100644 --- a/core/java/android/security/keystore/RecoveryController.java +++ b/core/java/android/security/keystore/RecoveryController.java @@ -31,22 +31,6 @@ import java.util.List; 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 5b806b75ebab..f990c236c9d3 100644 --- a/core/java/android/security/keystore/RecoveryControllerException.java +++ b/core/java/android/security/keystore/RecoveryControllerException.java @@ -19,8 +19,7 @@ package android.security.keystore; 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 ae8d91af3230..8a3e06b7deb1 100644 --- a/core/java/android/security/keystore/RecoverySession.java +++ b/core/java/android/security/keystore/RecoverySession.java @@ -19,9 +19,7 @@ package android.security.keystore; 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 f13e20602625..7c8d5e4f52f9 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 522bb9557b8d..2ce8c7d395d5 100644 --- a/core/java/android/security/keystore/WrappedApplicationKey.java +++ b/core/java/android/security/keystore/WrappedApplicationKey.java @@ -23,16 +23,7 @@ import android.os.Parcelable; 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 45c6b4ff6758..000000000000 --- 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 0683e02b66a7..426ca5c472b9 100644 --- a/core/java/android/security/keystore/recovery/RecoveryController.java +++ b/core/java/android/security/keystore/recovery/RecoveryController.java @@ -113,6 +113,14 @@ public class RecoveryController { */ 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 @@ public class RecoveryController { } } + // TODO: Unhide the following APIs, generateKey(), importKey(), and getKey() /** * @deprecated Use {@link #generateKey(String)}. * @removed @@ -503,6 +512,40 @@ public class RecoveryController { } /** + * 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 422e36baee70..eebd22ae64bb 100644 --- a/core/java/android/service/notification/NotificationListenerService.java +++ b/core/java/android/service/notification/NotificationListenerService.java @@ -85,7 +85,10 @@ import java.util.List; * 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 @@ public abstract class NotificationListenerService extends Service { // 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 1ead0b49bbed..a5a7cbcb4bec 100644 --- a/core/java/android/util/FeatureFlagUtils.java +++ b/core/java/android/util/FeatureFlagUtils.java @@ -37,7 +37,6 @@ public class FeatureFlagUtils { 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 402bef914421..cc9991a9be20 100644 --- a/core/java/android/util/LauncherIcons.java +++ b/core/java/android/util/LauncherIcons.java @@ -110,9 +110,9 @@ public final class LauncherIcons { 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 d597e597b119..a864e550c256 100644 --- a/core/java/android/view/RemoteAnimationAdapter.java +++ b/core/java/android/view/RemoteAnimationAdapter.java @@ -52,6 +52,9 @@ public class RemoteAnimationAdapter implements Parcelable { 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 @@ public class RemoteAnimationAdapter implements Parcelable { 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 381f6926a1e8..8def43512e51 100644 --- a/core/java/android/view/RemoteAnimationDefinition.java +++ b/core/java/android/view/RemoteAnimationDefinition.java @@ -70,6 +70,16 @@ public class RemoteAnimationDefinition implements Parcelable { 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 e9fe481112a2..e0ccda98bf1d 100644 --- a/core/java/android/webkit/WebViewFactory.java +++ b/core/java/android/webkit/WebViewFactory.java @@ -205,25 +205,21 @@ public final class WebViewFactory { } 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 @@ public final class WebViewFactory { 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 eb2b6bccf4cc..cabba06bdff5 100644 --- a/core/java/android/webkit/WebViewLibraryLoader.java +++ b/core/java/android/webkit/WebViewLibraryLoader.java @@ -234,17 +234,14 @@ public class WebViewLibraryLoader { * <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 63fbef3f4bc4..4167ad42cad6 100644 --- a/core/java/android/webkit/WebViewZygote.java +++ b/core/java/android/webkit/WebViewZygote.java @@ -169,6 +169,8 @@ public class WebViewZygote { 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 @@ public class WebViewZygote { 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 6e87e2356f65..85f68d7c29f4 100644 --- a/core/java/android/widget/Magnifier.java +++ b/core/java/android/widget/Magnifier.java @@ -24,6 +24,7 @@ import android.annotation.UiThread; 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 @@ public final class Magnifier { * 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 @@ public final class Magnifier { 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 2cfdb7693a38..50e6393d2579 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -319,6 +319,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener // 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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } } - 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); - } - if (tf != null) { - setTypeface(tf); - return; + /** + * 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; + } } - switch (typefaceIndex) { - case SANS: - tf = Typeface.SANS_SERIF; - break; - - case SERIF: - tf = Typeface.SERIF; - break; + } - case MONOSPACE: - tf = Typeface.MONOSPACE; - break; + 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)); } - - setTypeface(tf, styleIndex); } private void setRelativeDrawablesIfNeeded(Drawable start, Drawable end) { @@ -3392,6 +3416,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener 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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener + " 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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener 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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener 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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener 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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener 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 7fc36bc8f63f..8ee31f7c5149 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -13436,6 +13436,7 @@ public class BatteryStatsImpl extends BatteryStats { 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 2519412f3246..ce45f3c988cb 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 @@ import java.nio.IntBuffer; * 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 @@ public class KernelUidCpuActiveTimeReader { 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 @@ public class KernelUidCpuActiveTimeReader { } } } - // 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 41ef8f05f0d1..c21b7665c7ae 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 @@ import java.nio.IntBuffer; * 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 @@ public class KernelUidCpuClusterTimeReader { 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 @@ public class KernelUidCpuClusterTimeReader { 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 @@ public class KernelUidCpuClusterTimeReader { int numUids = buf.remaining() / (mNumCores + 1); for (int i = 0; i < numUids; i++) { - processUidLocked(buf, cb); + processUid(buf, cb); + } + if (DEBUG) { + Slog.d(TAG, "Read uids: " + numUids); } - // Slog.i(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 a21a70e1d2c9..a0787a039dbe 100644 --- a/core/java/com/android/internal/os/KernelUidCpuFreqTimeReader.java +++ b/core/java/com/android/internal/os/KernelUidCpuFreqTimeReader.java @@ -59,24 +59,21 @@ import java.nio.IntBuffer; * 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 @@ public class KernelUidCpuFreqTimeReader { 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 @@ public class KernelUidCpuFreqTimeReader { } finally { StrictMode.setThreadPolicyMask(oldMask); } + mLastTimeReadMs = mNowTimeMs; } @VisibleForTesting @@ -232,7 +220,9 @@ public class KernelUidCpuFreqTimeReader { } } } - // 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 444049e7e415..4263b832bd4f 100644 --- a/core/java/com/android/internal/os/KernelUidCpuTimeReader.java +++ b/core/java/com/android/internal/os/KernelUidCpuTimeReader.java @@ -38,18 +38,19 @@ import java.io.IOException; * 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 @@ public class KernelUidCpuTimeReader { /** * 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 @@ public class KernelUidCpuTimeReader { } /** - * 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 @@ public class KernelUidCpuTimeReader { } /** - * 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 000000000000..11e50e1ecb95 --- /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 32b580c2d277..9f2434e97d7a 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.text.TextUtils; 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 @@ class WebViewZygoteInit { } @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 @@ class WebViewZygoteInit { 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 a32fb4316d12..cd83c57f60f9 100644 --- a/core/java/com/android/internal/os/ZygoteConnection.java +++ b/core/java/com/android/internal/os/ZygoteConnection.java @@ -155,7 +155,7 @@ class ZygoteConnection { if (parsedArgs.preloadPackage != null) { handlePreloadPackage(parsedArgs.preloadPackage, parsedArgs.preloadPackageLibs, - parsedArgs.preloadPackageCacheKey); + parsedArgs.preloadPackageLibFileName, parsedArgs.preloadPackageCacheKey); return null; } @@ -290,7 +290,8 @@ class ZygoteConnection { 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 @@ class ZygoteConnection { 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 @@ class ZygoteConnection { } 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 d3fc644c2341..7c9cf7a183cd 100644 --- a/core/java/com/android/internal/widget/ILockSettings.aidl +++ b/core/java/com/android/internal/widget/ILockSettings.aidl @@ -68,6 +68,7 @@ interface ILockSettings { 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 61a22c144f1b..6456fe622f98 100644 --- a/core/jni/android_media_AudioTrack.cpp +++ b/core/jni/android_media_AudioTrack.cpp @@ -282,7 +282,7 @@ android_media_AudioTrack_setup(JNIEnv *env, jobject thiz, jobject weak_this, job // 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 000000000000..c06f318a7312 --- /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 9a53b89ffe30..be155977a1ea 100644 --- a/core/proto/android/os/incident.proto +++ b/core/proto/android/os/incident.proto @@ -20,6 +20,7 @@ option java_multiple_files = true; 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 @@ import "frameworks/base/libs/incident/proto/android/section.proto"; 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 @@ message IncidentProto { (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 788d90127c3d..5042ede77d2d 100644 --- a/core/proto/android/server/activitymanagerservice.proto +++ b/core/proto/android/server/activitymanagerservice.proto @@ -58,6 +58,9 @@ message ActivityStackSupervisorProto { 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 d1c5db66a841..b288c1149731 100644 --- a/core/proto/android/server/alarmmanagerservice.proto +++ b/core/proto/android/server/alarmmanagerservice.proto @@ -220,6 +220,8 @@ message ConstantsProto { 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 78cce586ac18..915f5fb96c01 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 2d11ee61eabb..1cd995eea171 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 b6c79696c022..4aef7d007590 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 @@ Copyright (C) 2016 The Android Open Source Project 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 dd653c6ff2d3..50551d401120 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 3bc4e67ec8df..6dba2779bc53 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 @@ Copyright (C) 2016 The Android Open Source Project 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="#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="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: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 a546cdd29a8e..f33ed1f010be 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 @@ Copyright (C) 2016 The Android Open Source Project 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="#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: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="#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,50.75a12,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: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 96a83f837fb3..22ab9c913540 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 a078d8bca29b..722102e99aa2 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 3b963d1ea079..d6f346391e2f 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 2ce08ebb882f..291826025cc1 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 a5ba4c63d0fb..c4006b324569 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 5c9f86396772..c3ae5fab7d8d 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 1b00c670b555..1babd707c781 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 3cca47b47a59..b6ffe12b0fe7 100644 --- a/graphics/java/android/graphics/ImageDecoder.java +++ b/graphics/java/android/graphics/ImageDecoder.java @@ -758,8 +758,9 @@ public final class ImageDecoder implements AutoCloseable { /** @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 8595165aab27..38beebde4b14 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.FontFamilyFilesResourceEnt 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.FileInputStream; 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 class Typeface { */ 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 class Typeface { 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 @@ public class Typeface { } /** Returns the typeface's intrinsic style attributes */ - public int getStyle() { + public @Style int getStyle() { return mStyle; } @@ -659,7 +674,7 @@ public class Typeface { * 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 @@ public class Typeface { * 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 @@ public class Typeface { * * @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 ded427eb244a..1924bbe764c7 100644 --- a/keystore/java/android/security/KeyStore.java +++ b/keystore/java/android/security/KeyStore.java @@ -784,6 +784,20 @@ public class KeyStore { } /** + * 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 ab27a0d00246..cf29e434a351 100644 --- a/libs/hwui/JankTracker.cpp +++ b/libs/hwui/JankTracker.cpp @@ -165,7 +165,7 @@ void JankTracker::finishFrame(const FrameInfo& frame) { 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 49bfe1e8a598..ef6a8ff6bcea 100644 --- a/libs/incident/proto/android/section.proto +++ b/libs/incident/proto/android/section.proto @@ -40,6 +40,9 @@ enum SectionType { // 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 0a8a5aa347ba..bf698d4a8f1d 100644 --- a/libs/protoutil/include/android/util/EncodedBuffer.h +++ b/libs/protoutil/include/android/util/EncodedBuffer.h @@ -154,7 +154,7 @@ public: 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 7dbca3b9d7e1..21d687377b47 100644 --- a/media/java/android/media/audiofx/AudioEffect.java +++ b/media/java/android/media/audiofx/AudioEffect.java @@ -39,6 +39,7 @@ import java.util.UUID; * <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 @@ public class AudioEffect { .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 @@ public class AudioEffect { * {@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 @@ public class AudioEffect { * {@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 @@ public class AudioEffect { * {@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 @@ public class AudioEffect { /** * @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 000000000000..d09c9a895e0c --- /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 022198beae45..62030bba44f5 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 1b3c24fcde49..2d9051f5230d 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 3518392d30b6..4c20f050087d 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 d14b53b12fcd..566e03756c4c 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 @@ public abstract class AbstractPreferenceController { /** - * @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 0c3a5e9f8e1a..e6935516f401 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 @@ import com.android.settingslib.R; 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 8a48e7bd9354..02d0d702e133 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 cf0659aef31c..a444ff9eb20d 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 920dd98b400a..9245ac18a6b2 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 b8319a8e8822..846aaddf74de 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 @@ interface ISystemUiProxy { * 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 68400fc977df..5b49e67f5492 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 @@ public class WindowManagerWrapper { 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 c3413d9d76bb..cb5a0507815b 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.support.annotation.VisibleForTesting; 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 class KeyguardSecurityContainer extends FrameLayout implements KeyguardSe 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 b54d09a66535..4c2aa6355813 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java @@ -162,12 +162,13 @@ public class KeyguardSliceView extends LinearLayout implements View.OnClickListe 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 1185f45469df..3c666e4b11cc 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.util.Log; 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 class OverviewProxyService implements CallbackController<OverviewProxyLis } } + 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 ee573fbeaf33..396d317e8954 100644 --- a/packages/SystemUI/src/com/android/systemui/Prefs.java +++ b/packages/SystemUI/src/com/android/systemui/Prefs.java @@ -24,6 +24,7 @@ import android.content.SharedPreferences.OnSharedPreferenceChangeListener; 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 @@ public final class Prefs { 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 @@ public final class Prefs { 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 c7d276c1b7a3..26618bff4db7 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 @@ package com.android.systemui.keyguard; 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.DateFormat; 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 com.android.systemui.statusbar.policy.NextAlarmControllerImpl; 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 class KeyguardSliceProvider extends SliceProvider implements 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 @@ public class KeyguardSliceProvider extends SliceProvider implements 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 class KeyguardSliceProvider extends SliceProvider implements 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 @@ public class KeyguardSliceProvider extends SliceProvider implements 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 @@ public class KeyguardSliceProvider extends SliceProvider implements 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 @@ public class KeyguardSliceProvider extends SliceProvider implements @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 b22ea4c81f17..2629f30f40e2 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 @@ public class PageIndicator extends ViewGroup { // 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 @@ public class PageIndicator extends ViewGroup { 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 f3417dc078c2..ea3a60b93246 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.support.v4.view.ViewPager; 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 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { 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 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { } } + @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 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { 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 5758762b06a0..29f3c43a1fa4 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 @@ public class QSFragment extends Fragment implements QS { // 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 143ad21c998c..61e3065fd4a3 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 class QSPanel extends LinearLayout implements Tunable, Callback, Brightne 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 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne addView(mPageIndicator); ((PagedTileLayout) mTileLayout).setPageIndicator((PageIndicator) mPageIndicator); + mQsTileRevealController = new QSTileRevealController(mContext, this, + ((PagedTileLayout) mTileLayout)); addDivider(); @@ -136,6 +139,10 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne return mPageIndicator; } + public QSTileRevealController getQsTileRevealController() { + return mQsTileRevealController; + } + public boolean isShowingCustomize() { return mCustomizePanel != null && mCustomizePanel.isCustomizing(); } @@ -352,6 +359,9 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne } 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 000000000000..2f012e6e608e --- /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 78481d3b07e0..2151436e9dd6 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; 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 @@ public class QuickStatusBarHeader extends RelativeLayout implements CommandQueue @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 class QuickStatusBarHeader extends RelativeLayout implements CommandQueue 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 409c753c147c..47b0de94f133 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 android.widget.Toast; 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 @@ public class Recents extends SystemUI private static RecentsTaskLoader sTaskLoader; private static RecentsConfiguration sConfiguration; + private OverviewProxyService mOverviewProxyService; + private Handler mHandler; private RecentsImpl mImpl; private int mDraggingInRecentsCurrentUser; @@ -208,6 +212,7 @@ public class Recents extends SystemUI 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 @@ public class Recents extends SystemUI 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 @@ public class Recents extends SystemUI 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 @@ public class Recents extends SystemUI 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 @@ public class Recents extends SystemUI return; } + if (mOverviewProxyService.getProxy() != null) { + // TODO: Proxy to Launcher + return; + } + int currentUser = sSystemServicesProxy.getCurrentUser(); if (sSystemServicesProxy.isSystemUser(currentUser)) { mImpl.preloadRecents(); @@ -373,6 +402,11 @@ public class Recents extends SystemUI 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 b0a2fadf1f84..95b311ff8e3e 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.RecentsView; 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 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD 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 26fac6c762e0..127361a8bdd3 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.R; 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 @@ public class RecentsOnboarding { 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 class RecentsOnboarding { 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 @@ public class RecentsOnboarding { 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 93fd34aa0519..544d95c7b62f 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 class SystemServicesProxy { } } - 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 22e8909b4812..bc14203fef13 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 @@ public class KeyguardIndicationController { 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 cad956cd602a..4f09133303de 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 @@ public class NotificationShelf extends ActivatableNotificationView implements 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 b220686b3056..446a1d4c3c7b 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; 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 class AutoTileManager { 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 @@ public class AutoTileManager { && 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 @@ public class AutoTileManager { 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 @@ public class AutoTileManager { } }; - 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 f7f791ebaf63..f42473d4cddf 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 class CollapsedStatusBarFragment extends Fragment implements CommandQueue 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 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue 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 c4996193dd2a..edfd02bdfb26 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.view.ViewGroup; 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 @@ public class DemoStatusIcons extends LinearLayout implements DemoMode { 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 @@ public class DemoStatusIcons extends LinearLayout implements DemoMode { 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 @@ public class DemoStatusIcons extends LinearLayout implements DemoMode { 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 380c08eb1738..edfbd3f8ec72 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.Handler; 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 @@ public class KeyguardBouncer { mKeyguardView.requestLayout(); } mShowingSoon = false; + StatsLog.write(StatsLog.KEYGUARD_BOUNCER_STATE_CHANGED, + StatsLog.KEYGUARD_BOUNCER_STATE_CHANGED__STATE__SHOWN); } }; @@ -183,6 +186,8 @@ public class KeyguardBouncer { 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 5cf4c4c70974..5479dd80fded 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 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { 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 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { 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 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { 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 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { 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 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { } /** - * 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 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { 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 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { 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 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { 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 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { 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 class NotificationIconContainer extends AlphaOptimizedFrameLayout { } 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 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { * 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 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { 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 933c952903cd..a31727e67078 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.StatusBarState; 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.FileDescriptor; 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 @@ public class StatusBar extends SystemUI implements DemoMode, @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 07610ceff7b0..956bebb6ca41 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.DISABLE2_SYSTEM_ICONS; 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; 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 @@ public interface StatusBarIconController { 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 @@ public interface StatusBarIconController { } } } + + @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 interface StatusBarIconController { } } + 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 @@ public interface StatusBarIconController { 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 1c3ee758a3ce..8f5e705ff2a4 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.FileDescriptor; 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 @@ import java.util.ArrayList; */ 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 @@ public class StatusBarIconControllerImpl extends StatusBarIconList implements Tu private Context mContext; private DemoStatusIcons mDemoStatusIcons; + private IconManager mStatusBarIconManager; public StatusBarIconControllerImpl(Context context) { super(context.getResources().getStringArray( @@ -197,26 +201,28 @@ public class StatusBarIconControllerImpl extends StatusBarIconList implements Tu @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 f6009082b88a..1aa3a4312f8d 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 class StatusBarIconList { } 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 47ea3a76c221..49cffc090a51 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.ComponentCallbacks2; 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 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb 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 class StatusBarKeyguardViewManager implements RemoteInputController.Callb 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 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb }); 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 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb 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 503a1b40bb0c..dba89479cf98 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.R; 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 @@ public class StatusIconContainer extends AlphaOptimizedLinearLayout { 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 641fe69f2f78..6f4026db5633 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 @@ public interface BatteryController extends DemoMode, Dumpable, 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 e8d5af6121ed..49f880ce3320 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.BatteryManager; 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 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC 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 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC return mPowerSave; } + @Override + public boolean isAodPowerSave() { + return mAodPowerSave; + } + private void updatePowerSave() { setPowerSave(mPowerManager.isPowerSaveMode()); } @@ -173,6 +186,11 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC 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 cd409d86a3dc..b6116e00bac1 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.Assert; 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 @@ public class KeyguardSliceProviderTest extends SysuiTestCase { @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 @@ public class KeyguardSliceProviderTest extends SysuiTestCase { @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 @@ public class KeyguardSliceProviderTest extends SysuiTestCase { } @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 2d2db1bba735..a80b04576715 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 @@ package com.android.systemui.statusbar.phone; 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 @@ import org.mockito.MockitoAnnotations; 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 @@ public class AutoTileManagerTest extends SysuiTestCase { 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 000000000000..d54c29582247 --- /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 c56002e88406..b897c7cc8873 100644 --- a/proto/src/metrics_constants.proto +++ b/proto/src/metrics_constants.proto @@ -5328,6 +5328,21 @@ message MetricsEvent { // 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 d4ecc28ba9f3..f7a4b73e85cb 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 @@ final class RemoteFillService implements DeathRecipient { @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 3cf374faada4..4443130005dc 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.ComponentName; 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.Build; 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 @@ public class PackageManagerBackupAgent extends BackupAgent { 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 @@ public class PackageManagerBackupAgent extends BackupAgent { // 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 501ff293b535..c87d2987d0b2 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 @@ package com.android.server.backup; 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 @@ public class TransportManager { @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 @@ public class TransportManager { 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 3df6e47a0244..136fada43b1f 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 @@ public class BackupHandler extends Handler { 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 e500d6e1b5ca..5125b0d5234e 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 class RestoreParams { 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 @@ public class RestoreParams { 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 238f7a05877d..140dded1cb74 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_RESTORE_SESSI 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 @@ public class ActiveRestoreSession extends IRestoreSession.Stub { 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 class ActiveRestoreSession extends IRestoreSession.Stub { } } + 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 0ca4f25093ce..c1a1c1dc10e7 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.app.backup.IFullBackupRestoreObserver; 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 @@ public class FullRestoreEngine extends RestoreEngine { 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 e576b3c32859..dacde0b9af68 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.app.backup.IFullBackupRestoreObserver; 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.os.RemoteException; 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 @@ public class PerformAdbRestoreTask implements Runnable { 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 6eb9619b8844..4b467e5a0399 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_BACKUP_RESTOR 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.app.backup.RestoreDescription; 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 android.util.Slog; 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 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask { 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 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask { * * [ state change => FINAL ] * - * 7. t.finishRestore(), release wakelock, etc. + * 7. t.finishRestore(), call listeners, etc. * * */ @@ -497,7 +506,7 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask { 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 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask { } 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 6780563120e3..90c1387fd176 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.app.backup.BackupTransport; 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 @@ import com.android.server.backup.transport.TransportClient; * 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 class AppBackupUtils { 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 @@ public class AppBackupUtils { * * <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 @@ public class AppBackupUtils { 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)) { + return false; } - // Don't allow unsigned apps on either end - if (ArrayUtils.isEmpty(storedSigs) || ArrayUtils.isEmpty(deviceSigs)) { + 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; } - // 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; - - 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 10f06954f17f..df7e6d45ba0f 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; 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.os.Process; 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 @@ public class RestoreUtils { 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 @@ public class RestoreUtils { } 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 cc26ff8b5090..6dd5284879f0 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.app.backup.IBackupManagerMonitor; 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 @@ public class TarBackupReader { * @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 @@ public class TarBackupReader { // 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 @@ public class TarBackupReader { // 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 c93f405012ca..62a7b8feb19e 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.provider.Settings; 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 @@ class AlarmManagerService extends SystemService { // 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 @@ class AlarmManagerService extends SystemService { 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 @@ class AlarmManagerService extends SystemService { // 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 @@ class AlarmManagerService extends SystemService { 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 @@ class AlarmManagerService extends SystemService { 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 @@ class AlarmManagerService extends SystemService { 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 @@ class AlarmManagerService extends SystemService { 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 @@ class AlarmManagerService extends SystemService { 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 @@ class AlarmManagerService extends SystemService { // 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 @@ class AlarmManagerService extends SystemService { 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 @@ class AlarmManagerService extends SystemService { } // 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 @@ class AlarmManagerService extends SystemService { 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 56ed6c8a3f56..bac81e7cd4a2 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 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> } 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 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> 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 d4307d72ac15..f1e3bfd463b7 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 class ActivityManagerService extends IActivityManager.Stub 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 @@ public class ActivityManagerService extends IActivityManager.Stub // 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 @@ public class ActivityManagerService extends IActivityManager.Stub 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 @@ public class ActivityManagerService extends IActivityManager.Stub } } + 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 @@ public class ActivityManagerService extends IActivityManager.Stub 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 @@ public class ActivityManagerService extends IActivityManager.Stub 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 @@ public class ActivityManagerService extends IActivityManager.Stub 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 @@ public class ActivityManagerService extends IActivityManager.Stub } } + @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 @@ public class ActivityManagerService extends IActivityManager.Stub 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 @@ public class ActivityManagerService extends IActivityManager.Stub 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 8cc927346500..274a4b068fa5 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 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo 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 4987b336651e..2f6afd2318e5 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 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai // 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 55771867302c..0157c7c3fd9a 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.TaskRecord.REPARENT_MOVE_STACK_TO_FRONT; 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.SparseIntArray; 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 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D } } + 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 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D } } + 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 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D } 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 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D 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 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D } else { proto.write(FOCUSED_STACK_ID, INVALID_STACK_ID); } + proto.write(IS_HOME_RECENTS_COMPONENT, + mRecentTasks.isRecentsComponentHomeActivity(mCurrentUser)); } /** @@ -4546,14 +4560,14 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D // 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 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D // 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 1f6075530412..0bf269151f24 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 @@ final class ProcessRecord { // 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 @@ final class ProcessRecord { 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 2de84ab265ff..5fd300c034e3 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 @@ class RecentTasks { } /** + * @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 6dcf04193c8e..0ef8bffc861d 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 @@ class RecentsAnimation implements RecentsAnimationCallbacks { private final WindowManagerService mWindowManager; private final UserController mUserController; private final Handler mHandler; + private final int mCallingPid; private final Runnable mCancelAnimationRunnable; @@ -58,13 +59,14 @@ class RecentsAnimation implements RecentsAnimationCallbacks { 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 @@ class RecentsAnimation implements RecentsAnimationCallbacks { } } + 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 @@ class RecentsAnimation implements RecentsAnimationCallbacks { 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 d08111ec0aa5..ac6f01fa855f 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 @@ class SafeActivityOptions { 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 8afa54008baa..ca228201f15d 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 @@ public class AudioService extends IAudioService.Stub 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 @@ public class AudioService extends IAudioService.Stub 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 e5d564dec459..f44afe458c4a 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 @@ package com.android.server.backup; 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 @@ import java.util.List; 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 @@ public class BackupUtils { 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; + 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; + } - // hash each on-device signature - ArrayList<byte[]> deviceHashes = new ArrayList<byte[]>(nDevice); - for (int i = 0; i < nDevice; i++) { - deviceHashes.add(hashSignature(deviceSigs[i])); + if (DEBUG) { + Slog.v(TAG, "signaturesMatch(): stored=" + storedSigHashes + + " device=" + deviceHistorySigs); } - // 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; + 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 3133a51a5fb5..ca8823f61ee2 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 @@ public class CameraServiceProxy extends SystemService 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 @@ public class CameraServiceProxy extends SystemService @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 @@ public class CameraServiceProxy extends SystemService .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 @@ public class CameraServiceProxy extends SystemService 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 @@ public class CameraServiceProxy extends SystemService 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 0c9d70a95ab9..776e93dd053f 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.app.KeyguardManager; 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.Binder; 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 android.util.SparseArray; 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 @@ public class ClipboardService extends SystemService { 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 @@ public class ClipboardService extends SystemService { 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 @@ public class ClipboardService extends SystemService { @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 @@ public class ClipboardService extends SystemService { 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 @@ public class ClipboardService extends SystemService { } } - 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 @@ public class ClipboardService extends SystemService { } } - 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 @@ public class ClipboardService extends SystemService { 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 9e00819d4eee..752ab8f4128d 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 @@ public class LockSettingsService extends ILockSettings.Stub { } @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 2fe3f4e943b3..7ebe8bf20d62 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 java.util.Locale; 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 @@ public class RecoverableKeyGenerator { 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 @@ public class RecoverableKeyGenerator { 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 22e99c43f950..da0b0d03b54d 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.NoSuchAlgorithmException; 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 @@ public class RecoverableKeyStoreManager { * * <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 @@ public class RecoverableKeyStoreManager { } /** + * 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 @@ public class RecoverableKeyStoreManager { } } - 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 d85e89e08386..0077242b412a 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 @@ public class WrappedKey { * @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 600a534facf9..3d9762337312 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 1cb5d91be3ba..8983ec369f55 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 @@ class RecoverableKeyStoreDbContract { } /** + * 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 896480ffb560..c0c66b248ea5 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.Set; 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 @@ public class ValidateNotificationPeople implements NotificationSignalExtractor { 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 @@ public class ValidateNotificationPeople implements NotificationSignalExtractor { 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 @@ public class ValidateNotificationPeople implements NotificationSignalExtractor { // 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 @@ public class ValidateNotificationPeople implements NotificationSignalExtractor { @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 256fb42d3fdc..7a4cd5f41e65 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.PackageLite; 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 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName()); } @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 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName()); } @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 3d37229642d1..f5edae0966b3 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 @@ package com.android.server.pm; 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 @@ class ShortcutPackageInfo { //@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 076f81f87340..ca6f53a76922 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 @@ public class ShortcutService extends IShortcutService.Stub { 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 08dc97e7dd63..16336b308dbc 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 @@ public class BatterySaverPolicy extends ContentObserver { 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 @@ public class BatterySaverPolicy extends ContentObserver { 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 @@ public class BatterySaverPolicy extends ContentObserver { 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 @@ public class BatterySaverPolicy extends ContentObserver { if (mLaunchBoostDisabled) sb.append("l"); if (mOptionalSensorsDisabled) sb.append("S"); + if (mAodDisabled) sb.append("o"); sb.append(mGpsMode); @@ -437,6 +446,9 @@ public class BatterySaverPolicy extends ContentObserver { 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 @@ public class BatterySaverPolicy extends ContentObserver { 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 95c30d10341f..6e017cd0a3d1 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 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { 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 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { 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 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { } } - 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 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { } @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 277a04b6b201..c2cc7c983620 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 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree @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 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree 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 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree @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 75a633816f03..19c634a55d5a 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 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo 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 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo 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 0e1283899eea..e3e4a4673ea3 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 @@ public class DisplayWindowController } 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 46c59c5e7b59..1f1efc4ed77a 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 @@ public class DockedStackDividerController { 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 @@ public class DockedStackDividerController { return mMinimizedDock; } - private void checkMinimizeChanged(boolean animate) { + void checkMinimizeChanged(boolean animate) { if (mDisplayContent.getSplitScreenPrimaryStackIgnoringVisibility() == null) { return; } @@ -693,7 +698,7 @@ public class DockedStackDividerController { 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 ed6e606b0c75..e4bb0436e1e7 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 @@ class RemoteAnimationController { onAnimationFinished(); } }); + sendRunningRemoteAnimation(true); } private RemoteAnimationTarget[] createAnimations() { @@ -131,6 +132,7 @@ class RemoteAnimationController { mService.closeSurfaceTransaction("RemoteAnimationController#finished"); } } + sendRunningRemoteAnimation(false); } private void invokeAnimationCancelled() { @@ -148,6 +150,14 @@ class RemoteAnimationController { } } + 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 @@ class RemoteAnimationController { 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 1f7caffd1916..42f606531cc2 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 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< } 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 0d9a37a846d0..8b8a6d382596 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 @@ public class WindowManagerService extends IWindowManager.Stub 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 class WindowManagerService extends IWindowManager.Stub 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 @@ public class WindowManagerService extends IWindowManager.Stub 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 @@ public class WindowManagerService extends IWindowManager.Stub 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 66c729342441..286cc499e69c 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_SNAPSHOT; 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 @@ class WindowSurfacePlacer { 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 3557dc90a503..4020a5243d23 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 @@ abstract class BaseIDevicePolicyManager extends IDevicePolicyManager.Stub { 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 0a6ff6da6e7a..6a468b146252 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.RecoverySystem; 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.messages.nano.SystemMessageProto.SystemMessage; 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.SystemService; 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 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } @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 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { 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 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } } 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 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { 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 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { 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 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { 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 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { 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 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { 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 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { 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 02514b82ffba..6abd30c5ebf0 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 static java.util.stream.Stream.concat; 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 @@ public class TransportManagerTest { 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 @@ public class TransportManagerTest { 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 4ac00f0de9be..c6a4f57b2a91 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 com.google.common.truth.Truth.assertThat; 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.IRestoreSession; 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.backup.testing.TransportTestUtils.TransportMock; 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.annotation.Config; 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 @@ public class ActiveRestoreSessionTest { 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 @@ public class ActiveRestoreSessionTest { 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 @@ public class ActiveRestoreSessionTest { application.getPackageManager(), backupHandler, mWakeLock); + when(mBackupManagerService.getPendingRestores()).thenReturn(new ArrayDeque<>()); + } + + @After + public void tearDown() throws Exception { + ShadowPerformUnifiedRestoreTask.reset(); } @Test @@ -193,12 +214,244 @@ public class ActiveRestoreSessionTest { 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 565c7e638aac..c00a61dde90a 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 @@ public class TransportTestUtils { .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 @@ public class TransportTestUtils { .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 000000000000..0f93c7a1d0b3 --- /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 bfc31337134e..fb1595e1eb49 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 @@ public class ActivityRecordTests extends ActivityTestsBase { 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 ce3528b57d27..c62820e8e3c3 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 @@ public class ActivityStackTests extends ActivityTestsBase { 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 @@ public class ActivityStackTests extends ActivityTestsBase { 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 86c83d6707b6..8ccacb8d5d91 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 @@ package com.android.server.backup.utils; 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 android.support.test.runner.AndroidJUnit4; 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 @@ public class AppBackupUtilsTest { 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 @@ public class AppBackupUtilsTest { @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 @@ public class AppBackupUtilsTest { 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 class AppBackupUtilsTest { 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 class AppBackupUtilsTest { 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 @@ public class AppBackupUtilsTest { 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 @@ public class AppBackupUtilsTest { 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 class AppBackupUtilsTest { 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 class AppBackupUtilsTest { 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 @@ public class AppBackupUtilsTest { 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 @@ public class AppBackupUtilsTest { 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 @@ public class AppBackupUtilsTest { 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 @@ public class AppBackupUtilsTest { 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 0cdf04bda2d0..5f052ceb2e26 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 android.app.backup.BackupManagerMonitor.LOG_EVENT_ID_VERSION_OF_BA 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.app.backup.IBackupManagerMonitor; 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 @@ public class TarBackupReaderTest { @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 @@ public class TarBackupReaderTest { 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 @@ public class TarBackupReaderTest { 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 @@ public class TarBackupReaderTest { 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 @@ public class TarBackupReaderTest { 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 @@ public class TarBackupReaderTest { 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 @@ public class TarBackupReaderTest { 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 @@ public class TarBackupReaderTest { 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 @@ public class TarBackupReaderTest { 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 @@ public class TarBackupReaderTest { 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 @@ public class TarBackupReaderTest { 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 @@ public class TarBackupReaderTest { 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 @@ public class TarBackupReaderTest { 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 @@ public class TarBackupReaderTest { 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 @@ public class TarBackupReaderTest { 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 8a461ac508fa..fd8b319b74ca 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 org.junit.runner.RunWith; 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 @@ public class RecoverableKeyGeneratorTest { 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 @@ public class RecoverableKeyGeneratorTest { 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 @@ public class RecoverableKeyGeneratorTest { 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 @@ public class RecoverableKeyGeneratorTest { 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 b67659debee1..199410c42b0e 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 @@ public class RecoverableKeyStoreManagerTest { 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 @@ public class RecoverableKeyStoreManagerTest { } @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 609faa49affa..dfb2dbf884f0 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.Test; 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 c016e6104755..a0cefbfc58e9 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 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 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 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; + + ArrayList<byte[]> storedSigHashes = new ArrayList<>(); + storedSigHashes.add(SIGNATURE_HASH_1); + boolean result = BackupUtils.signaturesMatch(storedSigHashes, packageInfo, + mMockPackageManagerInternal); + + 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(); - PackageInfo pi; + 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); - // False for null package. - assertFalse(BackupUtils.signaturesMatch(stored1, null)); + assertThat(result).isTrue(); + } - // If it's a system app, signatures don't matter. - pi = genPackage("xyz"); - pi.applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM; - assertTrue(BackupUtils.signaturesMatch(stored1, pi)); + @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(); - // Non system apps. - assertTrue(BackupUtils.signaturesMatch(stored1, genPackage("abc"))); + ArrayList<byte[]> storedSigHashes = new ArrayList<>(); + storedSigHashes.add(SIGNATURE_HASH_1); + storedSigHashes.add(SIGNATURE_HASH_2); + boolean result = BackupUtils.signaturesMatch(storedSigHashes, packageInfo, + mMockPackageManagerInternal); - // Superset is okay. - assertTrue(BackupUtils.signaturesMatch(stored1, genPackage("abc", "xyz"))); - assertTrue(BackupUtils.signaturesMatch(stored1, genPackage("xyz", "abc"))); + assertThat(result).isTrue(); + } - assertFalse(BackupUtils.signaturesMatch(stored1, genPackage("xyz"))); - assertFalse(BackupUtils.signaturesMatch(stored1, genPackage("xyz", "def"))); + @Test + public void signaturesMatch_extraSignatureInStored_returnsFalse() throws Exception { + PackageInfo packageInfo = new PackageInfo(); + packageInfo.signingCertificateHistory = new Signature[][] {{SIGNATURE_1, SIGNATURE_2}}; + packageInfo.applicationInfo = new ApplicationInfo(); - assertTrue(BackupUtils.signaturesMatch(stored2, genPackage("def", "abc"))); - assertTrue(BackupUtils.signaturesMatch(stored2, genPackage("x", "def", "abc", "y"))); + 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); - // Subset is not okay. - assertFalse(BackupUtils.signaturesMatch(stored2, genPackage("abc"))); - assertFalse(BackupUtils.signaturesMatch(stored2, genPackage("def"))); + 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 @@ public class BackupUtilsTest extends AndroidTestCase { 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 3fbcd8116d9d..7f060b3093e9 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 @@ import java.util.List; 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 @@ class UserUsageStatsService { 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 ddb4f04200b8..33da4033174a 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 class UsbPortManager { } public void systemReady() { + mSystemReady = true; if (mProxy != null) { try { mProxy.queryPortStatus(); @@ -146,7 +147,6 @@ public class UsbPortManager { "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 297a6eab4a78..956efc075d0a 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 @@ public final class UsbDescriptorParser { 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 @@ public final class UsbDescriptorParser { 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 9bd6cb942888..cbb899ea4c32 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 @@ public final class UsbTerminalTypes { 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 e7f0cc2423f4..cb87d1fb1d56 100644 --- a/telephony/java/android/provider/Telephony.java +++ b/telephony/java/android/provider/Telephony.java @@ -1103,10 +1103,15 @@ public final class Telephony { "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 6798a83142dc..96eb23d88b1f 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -1815,7 +1815,14 @@ public class CarrierConfigManager { "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 @@ public class CarrierConfigManager { 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 778ca77662ab..47a7d5f35388 100644 --- a/telephony/java/android/telephony/SignalStrength.java +++ b/telephony/java/android/telephony/SignalStrength.java @@ -62,7 +62,9 @@ public class SignalStrength implements Parcelable { */ 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 @@ public class SignalStrength implements Parcelable { // 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 @@ public class SignalStrength implements Parcelable { // 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 class SignalStrength implements Parcelable { */ 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 cdc1ba99b6f4..af3a0bb781cc 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -5281,6 +5281,24 @@ public class TelephonyManager { } /** + * 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 @@ public class TelephonyManager { * * @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 @@ public class TelephonyManager { * * @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 @@ public class TelephonyManager { 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 1fdbae9186b7..d53769907342 100644 --- a/telephony/java/android/telephony/ims/feature/ImsFeature.java +++ b/telephony/java/android/telephony/ims/feature/ImsFeature.java @@ -87,7 +87,9 @@ public abstract class ImsFeature { // 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 09267fc2554c..2fffd36a1a4f 100644 --- a/telephony/java/android/telephony/ims/feature/MmTelFeature.java +++ b/telephony/java/android/telephony/ims/feature/MmTelFeature.java @@ -332,20 +332,14 @@ public class MmTelFeature extends ImsFeature { 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 @@ public class MmTelFeature extends ImsFeature { /** * 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 9e2b519e3dd6..a941a5671f13 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -823,6 +823,12 @@ interface ITelephony { 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 @@ interface ITelephony { * @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 000000000000..62b673c2f079 --- /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 Binary files differnew file mode 100644 index 000000000000..e8f2932d7b5b --- /dev/null +++ b/tests/UsbTests/res/raw/usbdescriptors_headphones.bin diff --git a/tests/UsbTests/res/raw/usbdescriptors_headset.bin b/tests/UsbTests/res/raw/usbdescriptors_headset.bin Binary files differnew file mode 100644 index 000000000000..30eef2aae787 --- /dev/null +++ b/tests/UsbTests/res/raw/usbdescriptors_headset.bin diff --git a/tests/UsbTests/res/raw/usbdescriptors_lineout.bin b/tests/UsbTests/res/raw/usbdescriptors_lineout.bin Binary files differnew file mode 100644 index 000000000000..d540d33d15f3 --- /dev/null +++ b/tests/UsbTests/res/raw/usbdescriptors_lineout.bin diff --git a/tests/UsbTests/res/raw/usbdescriptors_nothing.bin b/tests/UsbTests/res/raw/usbdescriptors_nothing.bin Binary files differnew file mode 100644 index 000000000000..c318abf93afb --- /dev/null +++ b/tests/UsbTests/res/raw/usbdescriptors_nothing.bin 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 000000000000..f32395226f4a --- /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 2e1519b8717b..4227da6c4780 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_EIMS; 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 @@ public class NetworkCapabilitiesTest { 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 66e0955b04c3..3e1ff6dd5f32 100644 --- a/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java +++ b/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java @@ -281,6 +281,7 @@ public class IpSecServiceParameterizedTest { 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 7cffeea6fe2c..1b6f8827291b 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 @@ ResourceParser::ResourceParser(IDiagnostics* diag, ResourceTable* table, 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; + + 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; + } - 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; + 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 { - // 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}); + // 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; + } + } + + // 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 (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 = {}; + 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(); } - } 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(); + // 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 618c8ed4afd1..c98c0b95b69b 100644 --- a/tools/aapt2/ResourceParser_test.cpp +++ b/tools/aapt2/ResourceParser_test.cpp @@ -95,6 +95,16 @@ TEST_F(ResourceParserTest, ParseQuotedString) { 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 @@ TEST_F(ResourceParserTest, ParseStyledString) { 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 @@ TEST_F(ResourceParserTest, ParseStringWithWhitespace) { 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 @@ TEST_F(ResourceParserTest, ParseStringWithWhitespace) { 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 @@ TEST_F(ResourceParserTest, RecordUntranslateableXliffSectionsInString) { 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 @@ TEST_F(ResourceParserTest, RecordUntranslateableXliffSectionsInStyledString) { StyledString* str = test::GetValue<StyledString>(&table_, "string/foo"); ASSERT_THAT(str, NotNull()); - EXPECT_THAT(str->value->value, Eq("There are %1$d apples")); + EXPECT_THAT(str->value->value, Eq(" There are %1$d apples")); + 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)); - // 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->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 628466d0a281..8fc3d6580165 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 @@ std::unique_ptr<Item> ParseBinaryResValue(const ResourceType& type, const Config 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 f83d49ee5591..7af2fe06b908 100644 --- a/tools/aapt2/ResourceUtils.h +++ b/tools/aapt2/ResourceUtils.h @@ -224,6 +224,95 @@ std::unique_ptr<Item> ParseBinaryResValue(const ResourceType& type, const Config 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 cb786d3794c2..11f3fa3bc6cd 100644 --- a/tools/aapt2/ResourceUtils_test.cpp +++ b/tools/aapt2/ResourceUtils_test.cpp @@ -212,4 +212,48 @@ TEST(ResourceUtilsTest, ItemsWithWhitespaceAreParsedCorrectly) { 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 345cc95cfb29..781b9fe8bc29 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 @@ class XmlFlattenerVisitor : public xml::ConstVisitor { 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 @@ class XmlFlattenerVisitor : public xml::ConstVisitor { 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 @@ class XmlFlattenerVisitor : public xml::ConstVisitor { } } + // 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 @@ class XmlFlattenerVisitor : public xml::ConstVisitor { 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); + 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 { + // 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); } - if (xml_attr->compiled_value) { - CHECK(xml_attr->compiled_value->Flatten(&flat_attr->typedValue)); - } else { - // Flatten as a regular string type. + if (compiled_text) { + // Write out the compiled text and raw_text. flat_attr->typedValue.dataType = android::Res_value::TYPE_STRING; - - AddString(str_builder.ToString(), kLowPriority, - (ResStringPool_ref*)&flat_attr->typedValue.data); + 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 0450f6c16de5..08243feb3769 100644 --- a/tools/aapt2/format/binary/XmlFlattener_test.cpp +++ b/tools/aapt2/format/binary/XmlFlattener_test.cpp @@ -286,4 +286,92 @@ TEST_F(XmlFlattenerTest, ProcessEscapedStrings) { 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 b8f880427c71..9aaaa69f8994 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 @@ class ReferenceLinkerVisitor : public DescendingValueVisitor { // 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 e42145dff47e..d1c9ca1644d9 100644 --- a/tools/aapt2/util/Util.cpp +++ b/tools/aapt2/util/Util.cpp @@ -76,6 +76,34 @@ bool EndsWith(const StringPiece& str, const StringPiece& suffix) { 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 @@ bool VerifyJavaStringFormat(const StringPiece& str) { 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 7c949b90c10a..0eb35d18c06e 100644 --- a/tools/aapt2/util/Util.h +++ b/tools/aapt2/util/Util.h @@ -59,7 +59,15 @@ bool StartsWith(const android::StringPiece& str, const android::StringPiece& pre // 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 @@ std::string GetString(const android::ResStringPool& pool, size_t idx); // 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 @@ class StringBuilder { 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 2d1242ada949..d4e3bec24bd1 100644 --- a/tools/aapt2/util/Util_test.cpp +++ b/tools/aapt2/util/Util_test.cpp @@ -41,45 +41,6 @@ TEST(UtilTest, StringStartsWith) { 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 7b748ce78cbc..b6cd08697545 100644 --- a/tools/aapt2/xml/XmlDom.cpp +++ b/tools/aapt2/xml/XmlDom.cpp @@ -248,8 +248,14 @@ static void CopyAttributes(Element* el, android::ResXMLParser* parser, StringPoo 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 @@ std::unique_ptr<XmlResource> Inflate(const void* data, size_t len, std::string* // 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 @@ std::unique_ptr<XmlResource> Inflate(const void* data, size_t len, std::string* } 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 @@ std::unique_ptr<XmlResource> Inflate(const void* data, size_t len, std::string* 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 e7b269aaa9a5..9183918fcc63 100644 --- a/tools/incident_section_gen/main.cpp +++ b/tools/incident_section_gen/main.cpp @@ -411,6 +411,11 @@ static bool generateSectionListCpp(Descriptor const* descriptor) { 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"); |