diff options
544 files changed, 16757 insertions, 4393 deletions
diff --git a/Android.bp b/Android.bp index faad6f32dec4..9f93d399f074 100644 --- a/Android.bp +++ b/Android.bp @@ -152,9 +152,10 @@ java_defaults { ":libcamera_client_framework_aidl", "core/java/android/hardware/IConsumerIrService.aidl", "core/java/android/hardware/ISerialManager.aidl", + "core/java/android/hardware/biometrics/IBiometricEnabledOnKeyguardCallback.aidl", + "core/java/android/hardware/biometrics/IBiometricPromptReceiver.aidl", "core/java/android/hardware/biometrics/IBiometricService.aidl", "core/java/android/hardware/biometrics/IBiometricServiceReceiver.aidl", - "core/java/android/hardware/biometrics/IBiometricPromptReceiver.aidl", "core/java/android/hardware/biometrics/IBiometricServiceLockoutResetCallback.aidl", "core/java/android/hardware/display/IDisplayManager.aidl", "core/java/android/hardware/display/IDisplayManagerCallback.aidl", diff --git a/CleanSpec.mk b/CleanSpec.mk index 2247e43758d7..6deda0caa9aa 100644 --- a/CleanSpec.mk +++ b/CleanSpec.mk @@ -247,6 +247,7 @@ $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/statsd $(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/framework/com.android.mediadrm.signer.jar) $(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/framework/com.android.location.provider.jar) $(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/framework/com.android.future.usb.accessory.jar) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/framework/com.android.media.remotedisplay.jar) # ****************************************************************** # NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST ABOVE THIS BANNER # ****************************************************************** diff --git a/api/current.txt b/api/current.txt index 87aaaa671da3..9752d92ddecc 100755 --- a/api/current.txt +++ b/api/current.txt @@ -7,6 +7,7 @@ package android { public static final class Manifest.permission { ctor public Manifest.permission(); field public static final java.lang.String ACCEPT_HANDOVER = "android.permission.ACCEPT_HANDOVER"; + field public static final java.lang.String ACCESS_BACKGROUND_LOCATION = "android.permission.ACCESS_BACKGROUND_LOCATION"; field public static final java.lang.String ACCESS_CHECKIN_PROPERTIES = "android.permission.ACCESS_CHECKIN_PROPERTIES"; field public static final java.lang.String ACCESS_COARSE_LOCATION = "android.permission.ACCESS_COARSE_LOCATION"; field public static final java.lang.String ACCESS_FINE_LOCATION = "android.permission.ACCESS_FINE_LOCATION"; @@ -22,6 +23,7 @@ package android { field public static final java.lang.String BIND_ACCESSIBILITY_SERVICE = "android.permission.BIND_ACCESSIBILITY_SERVICE"; field public static final java.lang.String BIND_APPWIDGET = "android.permission.BIND_APPWIDGET"; field public static final java.lang.String BIND_AUTOFILL_SERVICE = "android.permission.BIND_AUTOFILL_SERVICE"; + field public static final java.lang.String BIND_CALL_REDIRECTION_SERVICE = "android.permission.BIND_CALL_REDIRECTION_SERVICE"; field public static final deprecated java.lang.String BIND_CARRIER_MESSAGING_SERVICE = "android.permission.BIND_CARRIER_MESSAGING_SERVICE"; field public static final java.lang.String BIND_CARRIER_SERVICES = "android.permission.BIND_CARRIER_SERVICES"; field public static final java.lang.String BIND_CHOOSER_TARGET_SERVICE = "android.permission.BIND_CHOOSER_TARGET_SERVICE"; @@ -777,6 +779,7 @@ package android { field public static final int isModifier = 16843334; // 0x1010246 field public static final int isRepeatable = 16843336; // 0x1010248 field public static final int isScrollContainer = 16843342; // 0x101024e + field public static final int isSplitRequired = 16844176; // 0x1010590 field public static final int isStatic = 16844122; // 0x101055a field public static final int isSticky = 16843335; // 0x1010247 field public static final int isolatedProcess = 16843689; // 0x10103a9 @@ -6733,14 +6736,21 @@ package android.app.admin { field public static final java.lang.String EXTRA_PROVISIONING_SKIP_ENCRYPTION = "android.app.extra.PROVISIONING_SKIP_ENCRYPTION"; field public static final java.lang.String EXTRA_PROVISIONING_SKIP_USER_CONSENT = "android.app.extra.PROVISIONING_SKIP_USER_CONSENT"; field public static final java.lang.String EXTRA_PROVISIONING_TIME_ZONE = "android.app.extra.PROVISIONING_TIME_ZONE"; + field public static final java.lang.String EXTRA_PROVISIONING_WIFI_ANONYMOUS_IDENTITY = "android.app.extra.PROVISIONING_WIFI_ANONYMOUS_IDENTITY"; + field public static final java.lang.String EXTRA_PROVISIONING_WIFI_CA_CERTIFICATE = "android.app.extra.PROVISIONING_WIFI_CA_CERTIFICATE"; + field public static final java.lang.String EXTRA_PROVISIONING_WIFI_DOMAIN = "android.app.extra.PROVISIONING_WIFI_DOMAIN"; + field public static final java.lang.String EXTRA_PROVISIONING_WIFI_EAP_METHOD = "android.app.extra.PROVISIONING_WIFI_EAP_METHOD"; field public static final java.lang.String EXTRA_PROVISIONING_WIFI_HIDDEN = "android.app.extra.PROVISIONING_WIFI_HIDDEN"; + field public static final java.lang.String EXTRA_PROVISIONING_WIFI_IDENTITY = "android.app.extra.PROVISIONING_WIFI_IDENTITY"; field public static final java.lang.String EXTRA_PROVISIONING_WIFI_PAC_URL = "android.app.extra.PROVISIONING_WIFI_PAC_URL"; field public static final java.lang.String EXTRA_PROVISIONING_WIFI_PASSWORD = "android.app.extra.PROVISIONING_WIFI_PASSWORD"; + field public static final java.lang.String EXTRA_PROVISIONING_WIFI_PHASE2_AUTH = "android.app.extra.PROVISIONING_WIFI_PHASE2_AUTH"; field public static final java.lang.String EXTRA_PROVISIONING_WIFI_PROXY_BYPASS = "android.app.extra.PROVISIONING_WIFI_PROXY_BYPASS"; field public static final java.lang.String EXTRA_PROVISIONING_WIFI_PROXY_HOST = "android.app.extra.PROVISIONING_WIFI_PROXY_HOST"; field public static final java.lang.String EXTRA_PROVISIONING_WIFI_PROXY_PORT = "android.app.extra.PROVISIONING_WIFI_PROXY_PORT"; field public static final java.lang.String EXTRA_PROVISIONING_WIFI_SECURITY_TYPE = "android.app.extra.PROVISIONING_WIFI_SECURITY_TYPE"; field public static final java.lang.String EXTRA_PROVISIONING_WIFI_SSID = "android.app.extra.PROVISIONING_WIFI_SSID"; + field public static final java.lang.String EXTRA_PROVISIONING_WIFI_USER_CERTIFICATE = "android.app.extra.PROVISIONING_WIFI_USER_CERTIFICATE"; field public static final int FLAG_EVICT_CREDENTIAL_ENCRYPTION_KEY = 1; // 0x1 field public static final int FLAG_MANAGED_CAN_ACCESS_PARENT = 2; // 0x2 field public static final int FLAG_PARENT_CAN_ACCESS_MANAGED = 1; // 0x1 @@ -13395,6 +13405,8 @@ package android.graphics { method public void drawCircle(float, float, float, android.graphics.Paint); method public void drawColor(int); method public void drawColor(int, android.graphics.PorterDuff.Mode); + method public void drawDoubleRoundRect(android.graphics.RectF, float, float, android.graphics.RectF, float, float, android.graphics.Paint); + method public void drawDoubleRoundRect(android.graphics.RectF, float[], android.graphics.RectF, float[], android.graphics.Paint); method public void drawLine(float, float, float, float, android.graphics.Paint); method public void drawLines(float[], int, int, android.graphics.Paint); method public void drawLines(float[], android.graphics.Paint); @@ -13746,6 +13758,7 @@ package android.graphics { method public static android.graphics.ImageDecoder.Source createSource(android.content.res.AssetManager, java.lang.String); method public static android.graphics.ImageDecoder.Source createSource(java.nio.ByteBuffer); method public static android.graphics.ImageDecoder.Source createSource(java.io.File); + method public static android.graphics.ImageDecoder.Source createSource(java.util.concurrent.Callable<android.content.res.AssetFileDescriptor>); method public static android.graphics.Bitmap decodeBitmap(android.graphics.ImageDecoder.Source, android.graphics.ImageDecoder.OnHeaderDecodedListener) throws java.io.IOException; method public static android.graphics.Bitmap decodeBitmap(android.graphics.ImageDecoder.Source) throws java.io.IOException; method public static android.graphics.drawable.Drawable decodeDrawable(android.graphics.ImageDecoder.Source, android.graphics.ImageDecoder.OnHeaderDecodedListener) throws java.io.IOException; @@ -15961,7 +15974,10 @@ package android.hardware { package android.hardware.biometrics { public class BiometricManager { - method public boolean hasEnrolledBiometrics(); + method public int canAuthenticate(); + field public static final int ERROR_NONE = 0; // 0x0 + field public static final int ERROR_NO_BIOMETRICS = 11; // 0xb + field public static final int ERROR_UNAVAILABLE = 1; // 0x1 } public class BiometricPrompt { @@ -21648,7 +21664,7 @@ package android.inputmethodservice { method public android.view.inputmethod.InputConnection getCurrentInputConnection(); method public android.view.inputmethod.EditorInfo getCurrentInputEditorInfo(); method public boolean getCurrentInputStarted(); - method public int getInputMethodWindowRecommendedHeight(); + method public deprecated int getInputMethodWindowRecommendedHeight(); method public android.view.LayoutInflater getLayoutInflater(); method public int getMaxWidth(); method public java.lang.CharSequence getTextForImeAction(int); @@ -32521,6 +32537,7 @@ package android.os { public class Build { ctor public Build(); + method public static java.util.List<android.os.Build.Partition> getPartitions(); method public static java.lang.String getRadioVersion(); method public static java.lang.String getSerial(); field public static final java.lang.String BOARD; @@ -32549,6 +32566,14 @@ package android.os { field public static final java.lang.String USER; } + public static class Build.Partition { + ctor public Build.Partition(); + method public java.lang.String getFingerprint(); + method public java.lang.String getName(); + method public long getTimeMillis(); + field public static final java.lang.String PARTITION_NAME_SYSTEM = "system"; + } + public static class Build.VERSION { ctor public Build.VERSION(); field public static final java.lang.String BASE_OS; @@ -33723,6 +33748,7 @@ package android.os { field public static final java.lang.String DISALLOW_FUN = "no_fun"; field public static final java.lang.String DISALLOW_INSTALL_APPS = "no_install_apps"; field public static final java.lang.String DISALLOW_INSTALL_UNKNOWN_SOURCES = "no_install_unknown_sources"; + field public static final java.lang.String DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY = "no_install_unknown_sources_globally"; field public static final java.lang.String DISALLOW_MODIFY_ACCOUNTS = "no_modify_accounts"; field public static final java.lang.String DISALLOW_MOUNT_PHYSICAL_MEDIA = "no_physical_media"; field public static final java.lang.String DISALLOW_NETWORK_RESET = "no_network_reset"; @@ -36943,6 +36969,7 @@ package android.provider { field public static final java.lang.String ACTION_APPLICATION_DEVELOPMENT_SETTINGS = "android.settings.APPLICATION_DEVELOPMENT_SETTINGS"; field public static final java.lang.String ACTION_APPLICATION_SETTINGS = "android.settings.APPLICATION_SETTINGS"; field public static final java.lang.String ACTION_APP_NOTIFICATION_SETTINGS = "android.settings.APP_NOTIFICATION_SETTINGS"; + field public static final java.lang.String ACTION_APP_SEARCH_SETTINGS = "android.settings.APP_SEARCH_SETTINGS"; field public static final java.lang.String ACTION_BATTERY_SAVER_SETTINGS = "android.settings.BATTERY_SAVER_SETTINGS"; field public static final java.lang.String ACTION_BLUETOOTH_SETTINGS = "android.settings.BLUETOOTH_SETTINGS"; field public static final java.lang.String ACTION_CAPTIONING_SETTINGS = "android.settings.CAPTIONING_SETTINGS"; @@ -42568,7 +42595,7 @@ package android.telephony { method public android.telephony.mbms.StreamingService startStreaming(android.telephony.mbms.StreamingServiceInfo, java.util.concurrent.Executor, android.telephony.mbms.StreamingServiceCallback); } - public class NeighboringCellInfo implements android.os.Parcelable { + public deprecated class NeighboringCellInfo implements android.os.Parcelable { ctor public deprecated NeighboringCellInfo(); ctor public deprecated NeighboringCellInfo(int, int); ctor public NeighboringCellInfo(int, java.lang.String, int); @@ -43029,7 +43056,6 @@ package android.telephony { method public java.lang.String getMmsUAProfUrl(); method public java.lang.String getMmsUserAgent(); method public java.lang.String getNai(); - method public deprecated java.util.List<android.telephony.NeighboringCellInfo> getNeighboringCellInfo(); method public java.lang.String getNetworkCountryIso(); method public java.lang.String getNetworkOperator(); method public java.lang.String getNetworkOperatorName(); @@ -45696,6 +45722,7 @@ package android.util { method public java.util.Set<java.util.Map.Entry<K, V>> entrySet(); method public V get(java.lang.Object); method public int indexOfKey(java.lang.Object); + method public int indexOfValue(java.lang.Object); method public boolean isEmpty(); method public K keyAt(int); method public java.util.Set<K> keySet(); @@ -45716,6 +45743,7 @@ package android.util { ctor public ArraySet(); ctor public ArraySet(int); ctor public ArraySet(android.util.ArraySet<E>); + ctor public ArraySet(java.util.Collection<? extends E>); method public boolean add(E); method public void addAll(android.util.ArraySet<? extends E>); method public boolean addAll(java.util.Collection<? extends E>); @@ -46287,6 +46315,7 @@ package android.util { method public int keyAt(int); method public void put(int, boolean); method public void removeAt(int); + method public void setValueAt(int, boolean); method public int size(); method public boolean valueAt(int); } @@ -46305,6 +46334,7 @@ package android.util { method public int keyAt(int); method public void put(int, int); method public void removeAt(int); + method public void setValueAt(int, int); method public int size(); method public int valueAt(int); } @@ -46626,7 +46656,12 @@ package android.view { } public final class DisplayCutout { - ctor public DisplayCutout(android.graphics.Rect, java.util.List<android.graphics.Rect>); + ctor public DisplayCutout(android.graphics.Insets, android.graphics.Rect, android.graphics.Rect, android.graphics.Rect, android.graphics.Rect); + ctor public deprecated DisplayCutout(android.graphics.Rect, java.util.List<android.graphics.Rect>); + method public android.graphics.Rect getBoundingRectBottom(); + method public android.graphics.Rect getBoundingRectLeft(); + method public android.graphics.Rect getBoundingRectRight(); + method public android.graphics.Rect getBoundingRectTop(); method public java.util.List<android.graphics.Rect> getBoundingRects(); method public int getSafeInsetBottom(); method public int getSafeInsetLeft(); @@ -50322,7 +50357,7 @@ package android.view.animation { method public long computeDurationHint(); method protected void ensureInterpolator(); method public int getBackgroundColor(); - method public boolean getDetachWallpaper(); + method public deprecated boolean getDetachWallpaper(); method public long getDuration(); method public boolean getFillAfter(); method public boolean getFillBefore(); @@ -50346,7 +50381,7 @@ package android.view.animation { method public void scaleCurrentDuration(float); method public void setAnimationListener(android.view.animation.Animation.AnimationListener); method public void setBackgroundColor(int); - method public void setDetachWallpaper(boolean); + method public deprecated void setDetachWallpaper(boolean); method public void setDuration(long); method public void setFillAfter(boolean); method public void setFillBefore(boolean); diff --git a/api/removed.txt b/api/removed.txt index b6dabcd8614b..f7106d2207ec 100644 --- a/api/removed.txt +++ b/api/removed.txt @@ -545,6 +545,7 @@ package android.telephony { } public class TelephonyManager { + method public deprecated java.util.List<android.telephony.NeighboringCellInfo> getNeighboringCellInfo(); method public deprecated android.telephony.NetworkScan requestNetworkScan(android.telephony.NetworkScanRequest, android.telephony.TelephonyScanManager.NetworkScanCallback); } diff --git a/api/system-current.txt b/api/system-current.txt index 5785e4aa3969..e134554c5257 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -128,6 +128,7 @@ package android { field public static final java.lang.String PEERS_MAC_ADDRESS = "android.permission.PEERS_MAC_ADDRESS"; field public static final java.lang.String PERFORM_CDMA_PROVISIONING = "android.permission.PERFORM_CDMA_PROVISIONING"; field public static final java.lang.String PERFORM_SIM_ACTIVATION = "android.permission.PERFORM_SIM_ACTIVATION"; + field public static final java.lang.String POWER_SAVER = "android.permission.POWER_SAVER"; field public static final java.lang.String PROVIDE_RESOLVER_RANKER_SERVICE = "android.permission.PROVIDE_RESOLVER_RANKER_SERVICE"; field public static final java.lang.String PROVIDE_TRUST_AGENT = "android.permission.PROVIDE_TRUST_AGENT"; field public static final java.lang.String QUERY_TIME_ZONE_RULES = "android.permission.QUERY_TIME_ZONE_RULES"; @@ -3995,6 +3996,7 @@ package android.os { } public final class PowerManager { + method public boolean setPowerSaveMode(boolean); method public void userActivity(long, int, int); field public static final int USER_ACTIVITY_EVENT_ACCESSIBILITY = 3; // 0x3 field public static final int USER_ACTIVITY_EVENT_BUTTON = 1; // 0x1 @@ -4320,13 +4322,6 @@ package android.provider { field public static final java.lang.String STATE = "state"; } - public static final class ContactsContract.RawContacts implements android.provider.BaseColumns android.provider.ContactsContract.ContactNameColumns android.provider.ContactsContract.ContactOptionsColumns android.provider.ContactsContract.RawContactsColumns android.provider.ContactsContract.SyncColumns { - field public static final android.net.Uri RAW_CONTACTS_NOTIFICATION_DELETE_URI; - field public static final android.net.Uri RAW_CONTACTS_NOTIFICATION_INSERT_URI; - field public static final android.net.Uri RAW_CONTACTS_NOTIFICATION_UPDATE_URI; - field public static final android.net.Uri RAW_CONTACTS_NOTIFICATION_URI; - } - public abstract class SearchIndexableData { ctor public SearchIndexableData(); ctor public SearchIndexableData(android.content.Context); @@ -5335,9 +5330,12 @@ package android.telephony { } public class ServiceState implements android.os.Parcelable { + method public android.telephony.NetworkRegistrationState getNetworkRegistrationState(int, int); method public java.util.List<android.telephony.NetworkRegistrationState> getNetworkRegistrationStates(); - method public java.util.List<android.telephony.NetworkRegistrationState> getNetworkRegistrationStates(int); - method public android.telephony.NetworkRegistrationState getNetworkRegistrationStates(int, int); + method public deprecated java.util.List<android.telephony.NetworkRegistrationState> getNetworkRegistrationStates(int); + method public deprecated android.telephony.NetworkRegistrationState getNetworkRegistrationStates(int, int); + method public java.util.List<android.telephony.NetworkRegistrationState> getNetworkRegistrationStatesForDomain(int); + method public java.util.List<android.telephony.NetworkRegistrationState> getNetworkRegistrationStatesForTransportType(int); } public final class SmsManager { @@ -5854,7 +5852,7 @@ package android.telephony.ims { field public static final java.lang.String EXTRA_CODEC = "Codec"; field public static final java.lang.String EXTRA_DIALSTRING = "dialstring"; field public static final java.lang.String EXTRA_DISPLAY_TEXT = "DisplayText"; - field public static final java.lang.String EXTRA_E_CALL = "e_call"; + field public static final java.lang.String EXTRA_EMERGENCY_CALL = "e_call"; field public static final java.lang.String EXTRA_IS_CALL_PULL = "CallPull"; field public static final java.lang.String EXTRA_OI = "oi"; field public static final java.lang.String EXTRA_OIR = "oir"; diff --git a/api/test-current.txt b/api/test-current.txt index 956761615254..dd02504e37b1 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -708,6 +708,10 @@ package android.os { method public void removeSyncBarrier(int); } + public final class PowerManager { + method public boolean setPowerSaveMode(boolean); + } + public class Process { method public static final int getThreadScheduler(int) throws java.lang.IllegalArgumentException; } @@ -932,13 +936,6 @@ package android.provider { field public static final android.net.Uri ENTERPRISE_CONTENT_URI; } - public static final class ContactsContract.RawContacts implements android.provider.BaseColumns android.provider.ContactsContract.ContactNameColumns android.provider.ContactsContract.ContactOptionsColumns android.provider.ContactsContract.RawContactsColumns android.provider.ContactsContract.SyncColumns { - field public static final android.net.Uri RAW_CONTACTS_NOTIFICATION_DELETE_URI; - field public static final android.net.Uri RAW_CONTACTS_NOTIFICATION_INSERT_URI; - field public static final android.net.Uri RAW_CONTACTS_NOTIFICATION_UPDATE_URI; - field public static final android.net.Uri RAW_CONTACTS_NOTIFICATION_URI; - } - public static final class ContactsContract.RawContactsEntity implements android.provider.BaseColumns android.provider.ContactsContract.DataColumns android.provider.ContactsContract.RawContactsColumns { field public static final android.net.Uri CORP_CONTENT_URI; } diff --git a/cmds/statsd/Android.mk b/cmds/statsd/Android.mk index e090ed14320c..f6b0db80f3ad 100644 --- a/cmds/statsd/Android.mk +++ b/cmds/statsd/Android.mk @@ -228,7 +228,8 @@ LOCAL_SRC_FILES := \ tests/e2e/Anomaly_count_e2e_test.cpp \ tests/e2e/Anomaly_duration_sum_e2e_test.cpp \ tests/e2e/ConfigTtl_e2e_test.cpp \ - tests/e2e/PartialBucket_e2e_test.cpp + tests/e2e/PartialBucket_e2e_test.cpp \ + tests/shell/ShellSubscriber_test.cpp LOCAL_STATIC_LIBRARIES := \ $(statsd_common_static_libraries) \ diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto index 30d8bfce5d2a..8ab67e32cd59 100644 --- a/cmds/statsd/src/atoms.proto +++ b/cmds/statsd/src/atoms.proto @@ -31,6 +31,7 @@ import "frameworks/base/core/proto/android/telephony/enums.proto"; import "frameworks/base/core/proto/android/view/enums.proto"; import "frameworks/base/proto/src/stats_enums.proto"; import "frameworks/base/core/proto/android/service/procstats.proto"; +import "frameworks/base/core/proto/android/internal/powerprofile.proto"; /** * The master atom class. This message defines all of the available @@ -175,9 +176,11 @@ message Atom { DirectoryUsage directory_usage = 10026; AppSize app_size = 10027; CategorySize category_size = 10028; + ProcStats proc_stats = 10029; BatteryVoltage battery_voltage = 10030; NumFingerprints num_fingerprints = 10031; - ProcStats proc_stats = 10029; + DiskIo disk_io = 10032; + PowerProfile power_profile = 10033; } // DO NOT USE field numbers above 100,000 in AOSP. @@ -2622,6 +2625,30 @@ message CategorySize { } /** + * Pulls per uid I/O stats. The stats are cumulative since boot. + * + * Read/write bytes are I/O events from a storage device + * Read/write chars are data requested by read/write syscalls, and can be + * satisfied by caching. + * + * Pulled from StatsCompanionService, which reads proc/uid_io/stats. + */ +message DiskIo { + optional int32 uid = 1 [(is_uid) = true]; + optional int64 fg_chars_read = 2; + optional int64 fg_chars_write = 3; + optional int64 fg_bytes_read = 4; + optional int64 fg_bytes_write = 5; + optional int64 bg_chars_read = 6; + optional int64 bg_chars_write = 7; + optional int64 bg_bytes_read = 8; + optional int64 bg_bytes_write = 9; + optional int64 fg_fsync = 10; + optional int64 bg_fsync= 11; +} + + +/** * Pulls the number of fingerprints for each user. * * Pulled from StatsCompanionService, which queries FingerprintManager. @@ -2640,3 +2667,11 @@ message NumFingerprints { message ProcStats { optional android.service.procstats.ProcessStatsSectionProto proc_stats_section = 1; } + +/** + * power_profile.xml and other constants for power model calculations. + * Pulled from PowerProfile.java + */ +message PowerProfile { + optional com.android.internal.os.PowerProfileProto power_profile_proto = 1; +}
\ No newline at end of file diff --git a/cmds/statsd/src/external/StatsCompanionServicePuller.cpp b/cmds/statsd/src/external/StatsCompanionServicePuller.cpp index 6d7bba028974..3eb05a90e3b4 100644 --- a/cmds/statsd/src/external/StatsCompanionServicePuller.cpp +++ b/cmds/statsd/src/external/StatsCompanionServicePuller.cpp @@ -54,7 +54,7 @@ bool StatsCompanionServicePuller::PullInternal(vector<shared_ptr<LogEvent> >* da vector<StatsLogEventWrapper> returned_value; Status status = statsCompanionServiceCopy->pullData(mTagId, &returned_value); if (!status.isOk()) { - ALOGW("StatsCompanionServicePuller::pull failed to pull for %d", mTagId); + ALOGW("StatsCompanionServicePuller::pull failed for %d", mTagId); return false; } data->clear(); diff --git a/cmds/statsd/src/external/StatsPullerManager.cpp b/cmds/statsd/src/external/StatsPullerManager.cpp index 66392f80f1fe..cd215b4bd705 100644 --- a/cmds/statsd/src/external/StatsPullerManager.cpp +++ b/cmds/statsd/src/external/StatsPullerManager.cpp @@ -163,10 +163,7 @@ const std::map<int, PullAtomInfo> StatsPullerManager::kAllPullAtomInfo = { new ResourceHealthManagerPuller(android::util::FULL_BATTERY_CAPACITY)}}, // battery_voltage {android::util::BATTERY_VOLTAGE, - {{}, - {}, - 1 * NS_PER_SEC, - new ResourceHealthManagerPuller(android::util::BATTERY_VOLTAGE)}}, + {{}, {}, 1 * NS_PER_SEC, new ResourceHealthManagerPuller(android::util::BATTERY_VOLTAGE)}}, // process_memory_state {android::util::PROCESS_MEMORY_STATE, {{4, 5, 6, 7, 8, 9}, @@ -204,17 +201,26 @@ const std::map<int, PullAtomInfo> StatsPullerManager::kAllPullAtomInfo = { {{}, {}, 1 * NS_PER_SEC, new StatsCompanionServicePuller(android::util::APP_SIZE)}}, // Size of specific categories of files. Eg. Music. {android::util::CATEGORY_SIZE, - {{}, - {}, - 1 * NS_PER_SEC, - new StatsCompanionServicePuller(android::util::CATEGORY_SIZE)}}, + {{}, {}, 1 * NS_PER_SEC, new StatsCompanionServicePuller(android::util::CATEGORY_SIZE)}}, // Number of fingerprints registered to each user. {android::util::NUM_FINGERPRINTS, {{}, {}, 1 * NS_PER_SEC, new StatsCompanionServicePuller(android::util::NUM_FINGERPRINTS)}}, - }; + // ProcStats. + {android::util::PROC_STATS, + {{}, {}, 1 * NS_PER_SEC, new StatsCompanionServicePuller(android::util::PROC_STATS)}}, + // Disk I/O stats per uid. + {android::util::DISK_IO, + {{2,3,4,5,6,7,8,9,10,11}, + {}, + 3 * NS_PER_SEC, + new StatsCompanionServicePuller(android::util::DISK_IO)}}, + // PowerProfile constants for power model calculations. + {android::util::POWER_PROFILE, + {{}, {}, 1 * NS_PER_SEC, new StatsCompanionServicePuller(android::util::POWER_PROFILE)}}, +}; StatsPullerManager::StatsPullerManager() : mNextPullTimeNs(NO_ALARM_UPDATE) { } diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.cpp b/cmds/statsd/src/metrics/DurationMetricProducer.cpp index 9d9e5be9e165..dd3402dae2f8 100644 --- a/cmds/statsd/src/metrics/DurationMetricProducer.cpp +++ b/cmds/statsd/src/metrics/DurationMetricProducer.cpp @@ -781,7 +781,7 @@ void DurationMetricProducer::onMatchedLogEventLocked(const size_t matcherIndex, if (whatIt != mCurrentSlicedDurationTrackerMap.end()) { for (const auto& condIt : whatIt->second) { const bool cond = dimensionKeysInCondition.find(condIt.first) != - dimensionKeysInCondition.end(); + dimensionKeysInCondition.end() && condition; handleStartEvent(MetricDimensionKey(dimensionInWhat, condIt.first), conditionKey, cond, event); dimensionKeysInCondition.erase(condIt.first); diff --git a/cmds/statsd/src/shell/ShellSubscriber.cpp b/cmds/statsd/src/shell/ShellSubscriber.cpp index 3cd49d722fea..1306a467e5c4 100644 --- a/cmds/statsd/src/shell/ShellSubscriber.cpp +++ b/cmds/statsd/src/shell/ShellSubscriber.cpp @@ -113,12 +113,12 @@ void ShellSubscriber::onLogEvent(const LogEvent& event) { for (const auto& matcher : mPushedMatchers) { if (matchesSimple(*mUidMap, matcher, event)) { + event.ToProto(mProto); // First write the payload size. size_t bufferSize = mProto.size(); write(mOutput, &bufferSize, sizeof(bufferSize)); // Then write the payload. - event.ToProto(mProto); mProto.flush(mOutput); mProto.clear(); break; @@ -137,4 +137,4 @@ void ShellSubscriber::binderDied(const wp<IBinder>& who) { } // namespace statsd } // namespace os -} // namespace android
\ No newline at end of file +} // namespace android diff --git a/cmds/statsd/tests/e2e/DimensionInCondition_e2e_combination_AND_cond_test.cpp b/cmds/statsd/tests/e2e/DimensionInCondition_e2e_combination_AND_cond_test.cpp index f03821432cc1..75bd40f67946 100644 --- a/cmds/statsd/tests/e2e/DimensionInCondition_e2e_combination_AND_cond_test.cpp +++ b/cmds/statsd/tests/e2e/DimensionInCondition_e2e_combination_AND_cond_test.cpp @@ -81,6 +81,34 @@ StatsdConfig CreateDurationMetricConfig_NoLink_AND_CombinationCondition( } // namespace +/* + The following test has the following input. + +{ 10000000002 10000000002 (8)9999[I], [S], job0[S], 1[I], } +{ 10000000010 10000000010 (7)111[I], App1[S], 222[I], GMSCoreModule1[S], 222[I], GMSCoreModule2[S], ReadEmail[S], 1[I], } +{ 10000000011 10000000011 (29)1[I], } +{ 10000000040 10000000040 (29)2[I], } +{ 10000000050 10000000050 (7)111[I], App1[S], 222[I], GMSCoreModule1[S], 222[I], GMSCoreModule2[S], ReadEmail[S], 0[I], } +{ 10000000101 10000000101 (8)9999[I], [S], job0[S], 0[I], } +{ 10000000102 10000000102 (29)1[I], } +{ 10000000200 10000000200 (7)111[I], App1[S], 222[I], GMSCoreModule1[S], 222[I], GMSCoreModule2[S], ReadEmail[S], 1[I], } +{ 10000000201 10000000201 (8)9999[I], [S], job2[S], 1[I], } +{ 10000000400 10000000400 (7)111[I], App1[S], 222[I], GMSCoreModule1[S], 222[I], GMSCoreModule2[S], ReadDoc[S], 1[I], } +{ 10000000401 10000000401 (7)333[I], App2[S], 222[I], GMSCoreModule1[S], 555[I], GMSCoreModule2[S], ReadEmail[S], 1[I], } +{ 10000000450 10000000450 (29)2[I], } +{ 10000000500 10000000500 (8)9999[I], [S], job2[S], 0[I], } +{ 10000000600 10000000600 (8)8888[I], [S], job2[S], 1[I], } +{ 10000000650 10000000650 (29)1[I], } +{ 309999999999 309999999999 (7)111[I], App1[S], 222[I], GMSCoreModule1[S], 222[I], GMSCoreModule2[S], ReadDoc[S], 0[I], } +{ 310000000100 310000000100 (29)2[I], } +{ 310000000300 310000000300 (7)111[I], App1[S], 222[I], GMSCoreModule1[S], 222[I], GMSCoreModule2[S], ReadEmail[S], 0[I], } +{ 310000000600 310000000600 (8)8888[I], [S], job1[S], 1[I], } +{ 310000000640 310000000640 (29)1[I], } +{ 310000000650 310000000650 (29)2[I], } +{ 310000000700 310000000700 (7)333[I], App2[S], 222[I], GMSCoreModule1[S], 555[I], GMSCoreModule2[S], ReadEmail[S], 0[I], } +{ 310000000850 310000000850 (8)8888[I], [S], job2[S], 0[I], } +{ 310000000900 310000000900 (8)8888[I], [S], job1[S], 0[I], } +*/ TEST(DimensionInConditionE2eTest, TestDurationMetric_NoLink_AND_CombinationCondition) { for (const bool hashStringInReport : { true, false }) { for (bool isDimensionInConditionSubSetOfConditionTrackerDimension : { true, false }) { @@ -250,7 +278,7 @@ TEST(DimensionInConditionE2eTest, TestDurationMetric_NoLink_AND_CombinationCondi EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs); EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(0).duration_nanos(), 450 - 201 + bucketSizeNs - 600); + EXPECT_EQ(data.bucket_info(0).duration_nanos(), 450 - 201 + bucketSizeNs - 650); EXPECT_EQ(data.bucket_info(1).duration_nanos(), 100); EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(), bucketStartTimeNs + bucketSizeNs); @@ -269,7 +297,7 @@ TEST(DimensionInConditionE2eTest, TestDurationMetric_NoLink_AND_CombinationCondi data.dimensions_in_condition(), android::util::SYNC_STATE_CHANGED, 333, "App2"); EXPECT_EQ(data.bucket_info_size(), 2); - EXPECT_EQ(data.bucket_info(0).duration_nanos(), 450 - 401 + bucketSizeNs - 600); + EXPECT_EQ(data.bucket_info(0).duration_nanos(), 450 - 401 + bucketSizeNs - 650); EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs); EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), bucketStartTimeNs + bucketSizeNs); @@ -331,7 +359,7 @@ TEST(DimensionInConditionE2eTest, TestDurationMetric_NoLink_AND_CombinationCondi EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), bucketStartTimeNs + bucketSizeNs); EXPECT_EQ(data.bucket_info(0).duration_nanos(), 450 - 201); - EXPECT_EQ(data.bucket_info(1).duration_nanos(), bucketSizeNs - 600 + 100); + EXPECT_EQ(data.bucket_info(1).duration_nanos(), bucketSizeNs - 650 + 100); EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(), bucketStartTimeNs + bucketSizeNs); EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(), @@ -353,7 +381,7 @@ TEST(DimensionInConditionE2eTest, TestDurationMetric_NoLink_AND_CombinationCondi EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs); EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(1).duration_nanos(), bucketSizeNs - 600 + 110); + EXPECT_EQ(data.bucket_info(1).duration_nanos(), bucketSizeNs - 650 + 110); EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(), bucketStartTimeNs + bucketSizeNs); EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(), diff --git a/cmds/statsd/tests/shell/ShellSubscriber_test.cpp b/cmds/statsd/tests/shell/ShellSubscriber_test.cpp new file mode 100644 index 000000000000..b380b03e28d0 --- /dev/null +++ b/cmds/statsd/tests/shell/ShellSubscriber_test.cpp @@ -0,0 +1,136 @@ +// 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 <gtest/gtest.h> + +#include <unistd.h> +#include "frameworks/base/cmds/statsd/src/atoms.pb.h" +#include "frameworks/base/cmds/statsd/src/shell/shell_config.pb.h" +#include "src/shell/ShellSubscriber.h" +#include "tests/metrics/metrics_test_helper.h" + +#include <stdio.h> +#include <vector> + +using namespace android::os::statsd; +using android::sp; +using std::vector; +using testing::NaggyMock; + +#ifdef __ANDROID__ + +class MyResultReceiver : public BnResultReceiver { +public: + Mutex mMutex; + Condition mCondition; + bool mHaveResult = false; + int32_t mResult = 0; + + virtual void send(int32_t resultCode) { + AutoMutex _l(mMutex); + mResult = resultCode; + mHaveResult = true; + mCondition.signal(); + } + + int32_t waitForResult() { + AutoMutex _l(mMutex); + mCondition.waitRelative(mMutex, 1000000000); + return mResult; + } +}; + +TEST(ShellSubscriberTest, testPushedSubscription) { + // set up 2 pipes for read/write config and data + int fds_config[2]; + ASSERT_EQ(0, pipe(fds_config)); + + int fds_data[2]; + ASSERT_EQ(0, pipe(fds_data)); + + // create a simple config to get screen events + ShellSubscription config; + config.add_pushed()->set_atom_id(29); + + size_t bufferSize = config.ByteSize(); + + // write the config to pipe, first write size of the config + vector<uint8_t> size_buffer(sizeof(bufferSize)); + std::memcpy(size_buffer.data(), &bufferSize, sizeof(bufferSize)); + write(fds_config[1], &bufferSize, sizeof(bufferSize)); + // then write config itself + vector<uint8_t> buffer(bufferSize); + config.SerializeToArray(&buffer[0], bufferSize); + write(fds_config[1], buffer.data(), bufferSize); + close(fds_config[1]); + + // create a shell subscriber. + sp<MockUidMap> uidMap = new NaggyMock<MockUidMap>(); + sp<ShellSubscriber> shellClient = new ShellSubscriber(uidMap); + sp<MyResultReceiver> resultReceiver = new MyResultReceiver(); + + LogEvent event1(29, 1000); + event1.write(2); + event1.init(); + + // mimic a binder thread that a shell subscriber runs on. it would block. + std::thread reader([&resultReceiver, &fds_config, &fds_data, &shellClient] { + shellClient->startNewSubscription(fds_config[0], fds_data[1], resultReceiver); + }); + reader.detach(); + + // let the shell subscriber to receive the config from pipe. + std::this_thread::sleep_for(100ms); + + // send a log event that matches the config. + std::thread log_reader([&shellClient, &event1] { shellClient->onLogEvent(event1); }); + log_reader.detach(); + + if (log_reader.joinable()) { + log_reader.join(); + } + + // wait for the data to be written. + std::this_thread::sleep_for(100ms); + + // this is the expected screen event atom. + Atom atom; + atom.mutable_screen_state_changed()->set_state( + ::android::view::DisplayStateEnum::DISPLAY_STATE_ON); + + int atom_size = atom.ByteSize(); + + // now read from the pipe. firstly read the atom size. + size_t dataSize = 0; + EXPECT_EQ((int)sizeof(dataSize), read(fds_data[0], &dataSize, sizeof(dataSize))); + EXPECT_EQ(atom_size, (int)dataSize); + + // then read that much data which is the atom in proto binary format + vector<uint8_t> dataBuffer(dataSize); + EXPECT_EQ((int)dataSize, read(fds_data[0], dataBuffer.data(), dataSize)); + + // make sure the received bytes can be parsed to an atom + Atom receivedAtom; + EXPECT_TRUE(receivedAtom.ParseFromArray(dataBuffer.data(), dataSize) != 0); + + // serialze the expected atom to bytes. and compare. to make sure they are the same. + vector<uint8_t> atomBuffer(atom_size); + atom.SerializeToArray(&atomBuffer[0], atom_size); + EXPECT_EQ(atomBuffer, dataBuffer); + close(fds_data[0]); +} + +#else +GTEST_LOG_(INFO) << "This test does nothing.\n"; +#endif diff --git a/config/hiddenapi-light-greylist.txt b/config/hiddenapi-light-greylist.txt index ac16fd311c32..6af34f9151b9 100644 --- a/config/hiddenapi-light-greylist.txt +++ b/config/hiddenapi-light-greylist.txt @@ -867,7 +867,6 @@ Landroid/os/PowerManager;->GO_TO_SLEEP_REASON_TIMEOUT:I Landroid/os/PowerManager;->isLightDeviceIdleMode()Z Landroid/os/PowerManager;->mHandler:Landroid/os/Handler; Landroid/os/PowerManager;->mService:Landroid/os/IPowerManager; -Landroid/os/PowerManager;->setPowerSaveMode(Z)Z Landroid/os/PowerManager;->validateWakeLockParameters(ILjava/lang/String;)V Landroid/os/PowerManager;->wakeUp(JLjava/lang/String;)V Landroid/os/Process;->BLUETOOTH_UID:I diff --git a/core/java/android/accounts/AbstractAccountAuthenticator.java b/core/java/android/accounts/AbstractAccountAuthenticator.java index a3b3a9f21954..25cd342cb5e9 100644 --- a/core/java/android/accounts/AbstractAccountAuthenticator.java +++ b/core/java/android/accounts/AbstractAccountAuthenticator.java @@ -17,7 +17,6 @@ package android.accounts; import android.Manifest; -import android.annotation.SystemApi; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; @@ -32,8 +31,8 @@ import java.util.Arrays; /** * Abstract base class for creating AccountAuthenticators. - * In order to be an authenticator one must extend this class, provider implementations for the - * abstract methods and write a service that returns the result of {@link #getIBinder()} + * In order to be an authenticator one must extend this class, provide implementations for the + * abstract methods, and write a service that returns the result of {@link #getIBinder()} * in the service's {@link android.app.Service#onBind(android.content.Intent)} when invoked * with an intent with action {@link AccountManager#ACTION_AUTHENTICATOR_INTENT}. This service * must specify the following intent filter and metadata tags in its AndroidManifest.xml file @@ -974,7 +973,8 @@ public abstract class AbstractAccountAuthenticator { * * @param response to send the result back to the AccountManager, will never be null. * @param account the account to check, will never be null - * @param statusToken a String of token to check if update of credentials is suggested. + * @param statusToken a String of token which can be used to check the status of locally + * stored credentials and if update of credentials is suggested * @return a Bundle result or null if the result is to be returned via the response. The result * will contain either: * <ul> diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 2acae1cf8bac..482ef2d1a6fd 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -394,7 +394,7 @@ import java.util.List; * <td>The final call you receive before your * activity is destroyed. This can happen either because the * activity is finishing (someone called {@link Activity#finish} on - * it, or because the system is temporarily destroying this + * it), or because the system is temporarily destroying this * instance of the activity to save space. You can distinguish * between these two scenarios with the {@link * Activity#isFinishing} method.</td> @@ -1985,7 +1985,7 @@ public class Activity extends ContextThemeWrapper /** * Perform any final cleanup before an activity is destroyed. This can * happen either because the activity is finishing (someone called - * {@link #finish} on it, or because the system is temporarily destroying + * {@link #finish} on it), or because the system is temporarily destroying * this instance of the activity to save space. You can distinguish * between these two scenarios with the {@link #isFinishing} method. * diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java index 294a3ec73efd..76b90f5354b3 100644 --- a/core/java/android/app/ActivityManagerInternal.java +++ b/core/java/android/app/ActivityManagerInternal.java @@ -245,4 +245,6 @@ public abstract class ActivityManagerInternal { public abstract ComponentName startServiceInPackage(int uid, Intent service, String resolvedType, boolean fgRequired, String callingPackage, int userId) throws TransactionTooLargeException; + + public abstract void disconnectActivityFromServices(Object connectionHolder); } diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java index 34c22822403e..1144e26717fe 100644 --- a/core/java/android/app/Instrumentation.java +++ b/core/java/android/app/Instrumentation.java @@ -1760,7 +1760,7 @@ public class Instrumentation { /** * Like {@link #execStartActivity(android.content.Context, android.os.IBinder, * android.os.IBinder, String, android.content.Intent, int, android.os.Bundle)}, - * but for calls from a {#link Fragment}. + * but for calls from a {@link Fragment}. * * @param who The Context from which the activity is being started. * @param contextThread The main thread of the Context from which the activity diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java index 07a8504e3b0b..3f07024d1e5a 100644 --- a/core/java/android/app/NotificationManager.java +++ b/core/java/android/app/NotificationManager.java @@ -48,6 +48,7 @@ import android.util.proto.ProtoOutputStream; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; @@ -722,10 +723,15 @@ public class NotificationManager { public List<NotificationChannelGroup> getNotificationChannelGroups() { INotificationManager service = getService(); try { - return service.getNotificationChannelGroups(mContext.getPackageName()).getList(); + final ParceledListSlice<NotificationChannelGroup> parceledList = + service.getNotificationChannelGroups(mContext.getPackageName()); + if (parceledList != null) { + return parceledList.getList(); + } } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } + return new ArrayList<>(); } /** diff --git a/core/java/android/app/Service.java b/core/java/android/app/Service.java index 67acfe906915..16f6bdaa4313 100644 --- a/core/java/android/app/Service.java +++ b/core/java/android/app/Service.java @@ -21,13 +21,13 @@ import android.annotation.Nullable; import android.annotation.UnsupportedAppUsage; import android.content.ComponentCallbacks2; import android.content.ComponentName; -import android.content.Intent; -import android.content.ContextWrapper; import android.content.Context; +import android.content.ContextWrapper; +import android.content.Intent; import android.content.res.Configuration; import android.os.Build; -import android.os.RemoteException; import android.os.IBinder; +import android.os.RemoteException; import android.util.Log; import java.io.FileDescriptor; @@ -391,7 +391,7 @@ public abstract class Service extends ContextWrapper implements ComponentCallbac * don't recreate until a future explicit call to * {@link Context#startService Context.startService(Intent)}. The * service will not receive a {@link #onStartCommand(Intent, int, int)} - * call with a null Intent because it will not be re-started if there + * call with a null Intent because it will not be restarted if there * are no pending Intents to deliver. * * <p>This mode makes sense for things that want to do some work as a @@ -416,7 +416,7 @@ public abstract class Service extends ContextWrapper implements ComponentCallbac * redelivery until the service calls {@link #stopSelf(int)} with the * start ID provided to {@link #onStartCommand}. The * service will not receive a {@link #onStartCommand(Intent, int, int)} - * call with a null Intent because it will will only be re-started if + * call with a null Intent because it will only be restarted if * it is not finished processing all Intents sent to it (and any such * pending events will be delivered at the point of restart). */ diff --git a/core/java/android/app/SmsAppService.java b/core/java/android/app/SmsAppService.java index 3f2b025016df..3829d7103b07 100644 --- a/core/java/android/app/SmsAppService.java +++ b/core/java/android/app/SmsAppService.java @@ -24,21 +24,42 @@ import android.os.IBinder; * it so that the process is always running, which allows the app to have a persistent connection * to the server. * - * <p>The service must have {@link android.telephony.TelephonyManager#ACTION_SMS_APP_SERVICE} + * <p>The service must have an {@link android.telephony.TelephonyManager#ACTION_SMS_APP_SERVICE} * action in the intent handler, and be protected with * {@link android.Manifest.permission#BIND_SMS_APP_SERVICE}. However the service does not have to * be exported. * - * <p>Apps can use + * <p>The service must be associated with a non-main process, meaning it must have an + * {@code android:process} tag in its manifest entry. + * + * <p>An app can use * {@link android.content.pm.PackageManager#setComponentEnabledSetting(ComponentName, int, int)} - * to disable/enable the service. Apps should use it to disable the service when it no longer needs - * to be running. + * to disable or enable the service. An app should use it to disable the service when it no longer + * needs to be running. * * <p>When the owner process crashes, the service will be re-bound automatically after a * back-off. * * <p>Note the process may still be killed if the system is under heavy memory pressure, in which * case the process will be re-started later. + * + * <p>Example: First, define a subclass in the application: + * <pre> + * public class MySmsAppService extends SmsAppService { + * } + * </pre> + * Then, declare it in its {@code AndroidManifest.xml}: + * <pre> + * <service + * android:name=".MySmsAppService" + * android:exported="false" + * android:process=":persistent" + * android:permission="android.permission.BIND_SMS_APP_SERVICE"> + * <intent-filter> + * <action android:name="android.telephony.action.SMS_APP_SERVICE" /> + * </intent-filter> + * </service> + * </pre> */ public class SmsAppService extends Service { private final ISmsAppService mImpl; diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java index 2718bfacb618..bf3d885cd9c9 100644 --- a/core/java/android/app/StatusBarManager.java +++ b/core/java/android/app/StatusBarManager.java @@ -17,6 +17,7 @@ package android.app; import android.annotation.IntDef; +import android.annotation.Nullable; import android.annotation.SystemService; import android.annotation.UnsupportedAppUsage; import android.content.Context; @@ -208,10 +209,11 @@ public class StatusBarManager { } /** - * Expand the settings panel and open a subPanel, pass null to just open the settings panel. + * Expand the settings panel and open a subPanel. If the subpanel is null or does not have a + * corresponding tile, the QS panel is simply expanded */ @UnsupportedAppUsage - public void expandSettingsPanel(String subPanel) { + public void expandSettingsPanel(@Nullable String subPanel) { try { final IStatusBarService svc = getService(); if (svc != null) { diff --git a/core/java/android/app/WaitResult.java b/core/java/android/app/WaitResult.java index 898d0cabee3e..5baf2e22bc31 100644 --- a/core/java/android/app/WaitResult.java +++ b/core/java/android/app/WaitResult.java @@ -28,10 +28,10 @@ import java.io.PrintWriter; * @hide */ public class WaitResult implements Parcelable { + public static final int INVALID_DELAY = -1; public int result; public boolean timeout; public ComponentName who; - public long thisTime; public long totalTime; public WaitResult() { @@ -47,7 +47,6 @@ public class WaitResult implements Parcelable { dest.writeInt(result); dest.writeInt(timeout ? 1 : 0); ComponentName.writeToParcel(who, dest); - dest.writeLong(thisTime); dest.writeLong(totalTime); } @@ -68,7 +67,6 @@ public class WaitResult implements Parcelable { result = source.readInt(); timeout = source.readInt() != 0; who = ComponentName.readFromParcel(source); - thisTime = source.readLong(); totalTime = source.readLong(); } @@ -77,7 +75,6 @@ public class WaitResult implements Parcelable { pw.println(prefix + " result=" + result); pw.println(prefix + " timeout=" + timeout); pw.println(prefix + " who=" + who); - pw.println(prefix + " thisTime=" + thisTime); pw.println(prefix + " totalTime=" + totalTime); } }
\ No newline at end of file diff --git a/core/java/android/app/WindowConfiguration.java b/core/java/android/app/WindowConfiguration.java index e6fb5dc02ce3..096c7aa44446 100644 --- a/core/java/android/app/WindowConfiguration.java +++ b/core/java/android/app/WindowConfiguration.java @@ -28,9 +28,13 @@ import android.content.res.Configuration; import android.graphics.Rect; import android.os.Parcel; import android.os.Parcelable; +import android.util.proto.ProtoInputStream; import android.util.proto.ProtoOutputStream; +import android.util.proto.WireTypeMismatchException; import android.view.DisplayInfo; +import java.io.IOException; + /** * Class that contains windowing configuration/state for other objects that contain windows directly * or indirectly. E.g. Activities, Task, Displays, ... @@ -511,6 +515,38 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu } /** + * Read from a protocol buffer input stream. + * Protocol buffer message definition at {@link android.app.WindowConfigurationProto} + * + * @param proto Stream to read the WindowConfiguration object from. + * @param fieldId Field Id of the WindowConfiguration as defined in the parent message + * @hide + */ + public void readFromProto(ProtoInputStream proto, long fieldId) + throws IOException, WireTypeMismatchException { + final long token = proto.start(fieldId); + try { + while (proto.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (proto.getFieldNumber()) { + case (int) APP_BOUNDS: + mAppBounds = new Rect(); + mAppBounds.readFromProto(proto, APP_BOUNDS); + break; + case (int) WINDOWING_MODE: + mWindowingMode = proto.readInt(WINDOWING_MODE); + break; + case (int) ACTIVITY_TYPE: + mActivityType = proto.readInt(ACTIVITY_TYPE); + break; + } + } + } finally { + // Let caller handle any exceptions + proto.end(token); + } + } + + /** * Returns true if the activities associated with this window configuration display a shadow * around their border. * @hide diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index fc67c10e7e5e..09ab67186266 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -664,8 +664,8 @@ public class DevicePolicyManager { /** * A String extra indicating the security type of the wifi network in - * {@link #EXTRA_PROVISIONING_WIFI_SSID} and could be one of {@code NONE}, {@code WPA} or - * {@code WEP}. + * {@link #EXTRA_PROVISIONING_WIFI_SSID} and could be one of {@code NONE}, {@code WPA}, + * {@code WEP} or {@code EAP}. * * <p>Use in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner * provisioning via an NFC bump. @@ -680,8 +680,89 @@ public class DevicePolicyManager { * <p>Use in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner * provisioning via an NFC bump. */ - public static final String EXTRA_PROVISIONING_WIFI_PASSWORD - = "android.app.extra.PROVISIONING_WIFI_PASSWORD"; + public static final String EXTRA_PROVISIONING_WIFI_PASSWORD = + "android.app.extra.PROVISIONING_WIFI_PASSWORD"; + + /** + * The EAP method of the wifi network in {@link #EXTRA_PROVISIONING_WIFI_SSID} + * and could be one of {@code PEAP}, {@code TLS}, {@code TTLS}, {@code PWD}, {@code SIM}, + * {@code AKA} or {@code AKA_PRIME}. This is only used if the + * {@link #EXTRA_PROVISIONING_WIFI_SECURITY_TYPE} is {@code EAP}. + * + * <p>Use in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner + * provisioning via an NFC bump. It can also be used for QR code provisioning. + */ + public static final String EXTRA_PROVISIONING_WIFI_EAP_METHOD = + "android.app.extra.PROVISIONING_WIFI_EAP_METHOD"; + + /** + * The phase 2 authentication of the wifi network in {@link #EXTRA_PROVISIONING_WIFI_SSID} + * and could be one of {@code NONE}, {@code PAP}, {@code MSCHAP}, {@code MSCHAPV2}, {@code GTC}, + * {@code SIM}, {@code AKA} or {@code AKA_PRIME}. This is only used if the + * {@link #EXTRA_PROVISIONING_WIFI_SECURITY_TYPE} is {@code EAP}. + * + * <p>Use in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner + * provisioning via an NFC bump. It can also be used for QR code provisioning. + */ + public static final String EXTRA_PROVISIONING_WIFI_PHASE2_AUTH = + "android.app.extra.PROVISIONING_WIFI_PHASE2_AUTH"; + + /** + * The CA certificate of the wifi network in {@link #EXTRA_PROVISIONING_WIFI_SSID}. This should + * be an X.509 certificate Base64 encoded DER format, ie. PEM representation of a certificate + * without header, footer and line breaks. <a href= + * "https://tools.ietf.org/html/rfc7468"> More information</a> This is only + * used if the {@link + * #EXTRA_PROVISIONING_WIFI_SECURITY_TYPE} is {@code EAP}. + * + * <p>Use in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner + * provisioning via an NFC bump. It can also be used for QR code provisioning. + */ + public static final String EXTRA_PROVISIONING_WIFI_CA_CERTIFICATE = + "android.app.extra.PROVISIONING_WIFI_CA_CERTIFICATE"; + + /** + * The user certificate of the wifi network in {@link #EXTRA_PROVISIONING_WIFI_SSID}. This + * should be an X.509 certificate and private key Base64 encoded DER format, ie. PEM + * representation of a certificate and key without header, footer and line breaks. <a href= + * "https://tools.ietf.org/html/rfc7468"> More information</a> This is only + * used if the {@link #EXTRA_PROVISIONING_WIFI_SECURITY_TYPE} is {@code EAP}. + * + * <p>Use in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner + * provisioning via an NFC bump. It can also be used for QR code provisioning. + */ + public static final String EXTRA_PROVISIONING_WIFI_USER_CERTIFICATE = + "android.app.extra.PROVISIONING_WIFI_USER_CERTIFICATE"; + + /** + * The identity of the wifi network in {@link #EXTRA_PROVISIONING_WIFI_SSID}. This is only used + * if the {@link #EXTRA_PROVISIONING_WIFI_SECURITY_TYPE} is {@code EAP}. + * + * <p>Use in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner + * provisioning via an NFC bump. It can also be used for QR code provisioning. + */ + public static final String EXTRA_PROVISIONING_WIFI_IDENTITY = + "android.app.extra.PROVISIONING_WIFI_IDENTITY"; + + /** + * The anonymous identity of the wifi network in {@link #EXTRA_PROVISIONING_WIFI_SSID}. This is + * only used if the {@link #EXTRA_PROVISIONING_WIFI_SECURITY_TYPE} is {@code EAP}. + * + * <p>Use in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner + * provisioning via an NFC bump. It can also be used for QR code provisioning. + */ + + public static final String EXTRA_PROVISIONING_WIFI_ANONYMOUS_IDENTITY = + "android.app.extra.PROVISIONING_WIFI_ANONYMOUS_IDENTITY"; + /** + * The domain of the wifi network in {@link #EXTRA_PROVISIONING_WIFI_SSID}. This is only used if + * the {@link #EXTRA_PROVISIONING_WIFI_SECURITY_TYPE} is {@code EAP}. + * + * <p>Use in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner + * provisioning via an NFC bump. It can also be used for QR code provisioning. + */ + public static final String EXTRA_PROVISIONING_WIFI_DOMAIN = + "android.app.extra.PROVISIONING_WIFI_DOMAIN"; /** * A String extra holding the proxy host for the wifi network in @@ -1067,8 +1148,22 @@ public class DevicePolicyManager { * <li>{@link #EXTRA_PROVISIONING_WIFI_PROXY_PORT} (convert to String), optional</li> * <li>{@link #EXTRA_PROVISIONING_WIFI_PROXY_BYPASS}, optional</li> * <li>{@link #EXTRA_PROVISIONING_WIFI_PAC_URL}, optional</li> - * <li>{@link #EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE}, optional, supported from - * {@link android.os.Build.VERSION_CODES#M} </li></ul> + * <li>{@link #EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE}, optional, supported from {@link + * android.os.Build.VERSION_CODES#M} </li> + * <li>{@link #EXTRA_PROVISIONING_WIFI_EAP_METHOD}, optional, supported from {@link + * android.os.Build.VERSION_CODES#Q}</li> + * <li>{@link #EXTRA_PROVISIONING_WIFI_PHASE2_AUTH}, optional, supported from {@link + * android.os.Build.VERSION_CODES#Q}</li> + * <li>{@link #EXTRA_PROVISIONING_WIFI_CA_CERTIFICATE}, optional, supported from {@link + * android.os.Build.VERSION_CODES#Q}</li> + * <li>{@link #EXTRA_PROVISIONING_WIFI_USER_CERTIFICATE}, optional, supported from {@link + * android.os.Build.VERSION_CODES#Q}</li> + * <li>{@link #EXTRA_PROVISIONING_WIFI_IDENTITY}, optional, supported from {@link + * android.os.Build.VERSION_CODES#Q}</li> + * <li>{@link #EXTRA_PROVISIONING_WIFI_ANONYMOUS_IDENTITY}, optional, supported from {@link + * android.os.Build.VERSION_CODES#Q}</li> + * <li>{@link #EXTRA_PROVISIONING_WIFI_DOMAIN}, optional, supported from {@link + * android.os.Build.VERSION_CODES#Q}</li></ul> * * <p> * As of {@link android.os.Build.VERSION_CODES#M}, the properties should contain @@ -7404,6 +7499,10 @@ public class DevicePolicyManager { * If any app targeting {@link android.os.Build.VERSION_CODES#O} or higher calls this method * with {@link android.provider.Settings.Secure#INSTALL_NON_MARKET_APPS}, * an {@link UnsupportedOperationException} is thrown. + * + * Starting from Android Q, the device and profile owner can also call + * {@link UserManager#DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY} to restrict unknown sources for + * all users. * </strong> * * @param admin Which {@link DeviceAdminReceiver} this request is associated with. diff --git a/core/java/android/app/usage/UsageEvents.java b/core/java/android/app/usage/UsageEvents.java index 9f22ad193e42..308b39efc3b1 100644 --- a/core/java/android/app/usage/UsageEvents.java +++ b/core/java/android/app/usage/UsageEvents.java @@ -165,6 +165,12 @@ public final class UsageEvents implements Parcelable { */ public static final int KEYGUARD_HIDDEN = 18; + /** + * Keep in sync with the greatest event type value. + * @hide + */ + public static final int MAX_EVENT_TYPE = 18; + /** @hide */ public static final int FLAG_IS_PACKAGE_INSTANT_APP = 1 << 0; @@ -176,6 +182,12 @@ public final class UsageEvents implements Parcelable { public @interface EventFlags {} /** + * Bitwise OR all valid flag constants to create this constant. + * @hide + */ + public static final int VALID_FLAG_BITS = FLAG_IS_PACKAGE_INSTANT_APP; + + /** * {@hide} */ @UnsupportedAppUsage diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java index 654bfaf293b8..8e6a3856d51b 100644 --- a/core/java/android/bluetooth/BluetoothAdapter.java +++ b/core/java/android/bluetooth/BluetoothAdapter.java @@ -2973,7 +2973,7 @@ public final class BluetoothAdapter { * socket will be encrypted. * <p>Use {@link BluetoothServerSocket#accept} to retrieve incoming connections from a listening * {@link BluetoothServerSocket}. - * <p>The system will assign a dynamic PSM value. This PSM value can be read from the {#link + * <p>The system will assign a dynamic PSM value. This PSM value can be read from the {@link * BluetoothServerSocket#getPsm()} and this value will be released when this server socket is * closed, Bluetooth is turned off, or the application exits unexpectedly. * <p>The mechanism of disclosing the assigned dynamic PSM value to the initiating peer is @@ -3031,7 +3031,7 @@ public final class BluetoothAdapter { * <p>Use {@link BluetoothServerSocket#accept} to retrieve incoming connections from a listening * {@link BluetoothServerSocket}. * <p>The system will assign a dynamic protocol/service multiplexer (PSM) value. This PSM value - * can be read from the {#link BluetoothServerSocket#getPsm()} and this value will be released + * can be read from the {@link BluetoothServerSocket#getPsm()} and this value will be released * when this server socket is closed, Bluetooth is turned off, or the application exits * unexpectedly. * <p>The mechanism of disclosing the assigned dynamic PSM value to the initiating peer is diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java index 73e98cd99f8f..30d5fbc7fe99 100644 --- a/core/java/android/bluetooth/BluetoothDevice.java +++ b/core/java/android/bluetooth/BluetoothDevice.java @@ -1596,7 +1596,7 @@ public final class BluetoothDevice implements Parcelable { * For example, for Bluetooth 2.1 devices, if any of the devices does not * have an input and output capability or just has the ability to * display a numeric key, a secure socket connection is not possible. - * In such a case, use {#link createInsecureRfcommSocket}. + * In such a case, use {@link createInsecureRfcommSocket}. * For more details, refer to the Security Model section 5.2 (vol 3) of * Bluetooth Core Specification version 2.1 + EDR. * <p>Use {@link BluetoothSocket#connect} to initiate the outgoing @@ -1631,7 +1631,7 @@ public final class BluetoothDevice implements Parcelable { * For example, for Bluetooth 2.1 devices, if any of the devices does not * have an input and output capability or just has the ability to * display a numeric key, a secure socket connection is not possible. - * In such a case, use {#link createInsecureRfcommSocket}. + * In such a case, use {@link createInsecureRfcommSocket}. * For more details, refer to the Security Model section 5.2 (vol 3) of * Bluetooth Core Specification version 2.1 + EDR. * <p>Use {@link BluetoothSocket#connect} to initiate the outgoing @@ -1688,7 +1688,7 @@ public final class BluetoothDevice implements Parcelable { * For example, for Bluetooth 2.1 devices, if any of the devices does not * have an input and output capability or just has the ability to * display a numeric key, a secure socket connection is not possible. - * In such a case, use {#link createInsecureRfcommSocketToServiceRecord}. + * In such a case, use {@link #createInsecureRfcommSocketToServiceRecord}. * For more details, refer to the Security Model section 5.2 (vol 3) of * Bluetooth Core Specification version 2.1 + EDR. * <p>Hint: If you are connecting to a Bluetooth serial board then try @@ -1972,7 +1972,7 @@ public final class BluetoothDevice implements Parcelable { * encrypted. * <p> Use this socket if an authenticated socket link is possible. Authentication refers * to the authentication of the link key to prevent man-in-the-middle type of attacks. When a - * secure socket connection is not possible, use {#link createInsecureLeL2capCocSocket(int, + * secure socket connection is not possible, use {@link createInsecureLeL2capCocSocket(int, * int)}. * * @param psm dynamic PSM value from remote device diff --git a/core/java/android/bluetooth/BluetoothServerSocket.java b/core/java/android/bluetooth/BluetoothServerSocket.java index 5fc344a14f99..758c68db1c5f 100644 --- a/core/java/android/bluetooth/BluetoothServerSocket.java +++ b/core/java/android/bluetooth/BluetoothServerSocket.java @@ -203,7 +203,7 @@ public final class BluetoothServerSocket implements Closeable { /** * Returns the assigned dynamic protocol/service multiplexer (PSM) value for the listening L2CAP * Connection-oriented Channel (CoC) server socket. This server socket must be returned by the - * {#link BluetoothAdapter.listenUsingL2capChannel()} or {#link + * {@link BluetoothAdapter.listenUsingL2capChannel()} or {@link * BluetoothAdapter.listenUsingInsecureL2capChannel()}. The returned value is undefined if this * method is called on non-L2CAP server sockets. * diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java index a64eead04c6f..a13a438cd7f7 100644 --- a/core/java/android/content/ContentProvider.java +++ b/core/java/android/content/ContentProvider.java @@ -2104,7 +2104,11 @@ public abstract class ContentProvider implements ComponentCallbacks2 { // a source of security issues. final String encodedPath = uri.getEncodedPath(); if (encodedPath != null && encodedPath.indexOf("//") != -1) { - return uri.buildUpon().encodedPath(encodedPath.replaceAll("//+", "/")).build(); + final Uri normalized = uri.buildUpon() + .encodedPath(encodedPath.replaceAll("//+", "/")).build(); + Log.w(TAG, "Normalized " + uri + " to " + normalized + + " to avoid possible security issues"); + return normalized; } else { return uri; } diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index a15711f5da50..3032d164ef46 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -1170,6 +1170,14 @@ public abstract class PackageManager { public static final int INSTALL_FAILED_SANDBOX_VERSION_DOWNGRADE = -27; /** + * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS} + * if the new package requires at least one split and it was not provided. + * + * @hide + */ + public static final int INSTALL_FAILED_MISSING_SPLIT = -28; + + /** * Installation parse return code: this is passed in the * {@link PackageInstaller#EXTRA_LEGACY_STATUS} if the parser was given a path that is not a * file, or does not end with the expected '.apk' extension. @@ -5927,8 +5935,8 @@ public abstract class PackageManager { case INSTALL_FAILED_DUPLICATE_PERMISSION: return "INSTALL_FAILED_DUPLICATE_PERMISSION"; case INSTALL_FAILED_NO_MATCHING_ABIS: return "INSTALL_FAILED_NO_MATCHING_ABIS"; case INSTALL_FAILED_ABORTED: return "INSTALL_FAILED_ABORTED"; - case INSTALL_FAILED_BAD_DEX_METADATA: - return "INSTALL_FAILED_BAD_DEX_METADATA"; + case INSTALL_FAILED_BAD_DEX_METADATA: return "INSTALL_FAILED_BAD_DEX_METADATA"; + case INSTALL_FAILED_MISSING_SPLIT: return "INSTALL_FAILED_MISSING_SPLIT"; default: return Integer.toString(status); } } @@ -5979,6 +5987,7 @@ public abstract class PackageManager { case INSTALL_FAILED_DUPLICATE_PERMISSION: return PackageInstaller.STATUS_FAILURE_CONFLICT; case INSTALL_FAILED_NO_MATCHING_ABIS: return PackageInstaller.STATUS_FAILURE_INCOMPATIBLE; case INSTALL_FAILED_ABORTED: return PackageInstaller.STATUS_FAILURE_ABORTED; + case INSTALL_FAILED_MISSING_SPLIT: return PackageInstaller.STATUS_FAILURE_INCOMPATIBLE; default: return PackageInstaller.STATUS_FAILURE; } } diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index 1fa5190ef8df..24675d301f4a 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -452,10 +452,12 @@ public class PackageParser { public final boolean use32bitAbi; public final boolean extractNativeLibs; public final boolean isolatedSplits; + public final boolean isSplitRequired; public ApkLite(String codePath, String packageName, String splitName, boolean isFeatureSplit, - String configForSplit, String usesSplitName, int versionCode, int versionCodeMajor, + String configForSplit, String usesSplitName, boolean isSplitRequired, + int versionCode, int versionCodeMajor, int revisionCode, int installLocation, List<VerifierInfo> verifiers, SigningDetails signingDetails, boolean coreApp, boolean debuggable, boolean multiArch, boolean use32bitAbi, @@ -478,6 +480,7 @@ public class PackageParser { this.use32bitAbi = use32bitAbi; this.extractNativeLibs = extractNativeLibs; this.isolatedSplits = isolatedSplits; + this.isSplitRequired = isSplitRequired; } public long getLongVersionCode() { @@ -1695,6 +1698,7 @@ public class PackageParser { boolean extractNativeLibs = true; boolean isolatedSplits = false; boolean isFeatureSplit = false; + boolean isSplitRequired = false; String configForSplit = null; String usesSplitName = null; @@ -1717,6 +1721,8 @@ public class PackageParser { configForSplit = attrs.getAttributeValue(i); } else if (attr.equals("isFeatureSplit")) { isFeatureSplit = attrs.getAttributeBooleanValue(i, false); + } else if (attr.equals("isSplitRequired")) { + isSplitRequired = attrs.getAttributeBooleanValue(i, false); } } @@ -1772,8 +1778,8 @@ public class PackageParser { } return new ApkLite(codePath, packageSplit.first, packageSplit.second, isFeatureSplit, - configForSplit, usesSplitName, versionCode, versionCodeMajor, revisionCode, - installLocation, verifiers, signingDetails, coreApp, debuggable, + configForSplit, usesSplitName, isSplitRequired, versionCode, versionCodeMajor, + revisionCode, installLocation, verifiers, signingDetails, coreApp, debuggable, multiArch, use32bitAbi, extractNativeLibs, isolatedSplits); } @@ -5779,52 +5785,32 @@ public class PackageParser { int AUTH = 16; } - /** - * APK Signature Scheme v3 includes support for adding a proof-of-rotation record that - * contains two pieces of information: - * 1) the past signing certificates - * 2) the flags that APK wants to assign to each of the past signing certificates. - * - * These flags, which have a one-to-one relationship for the {@code pastSigningCertificates} - * collection, represent the second piece of information and are viewed as capabilities. - * They are an APK's way of telling the platform: "this is how I want to trust my old certs, - * please enforce that." This is useful for situation where this app itself is using its - * signing certificate as an authorization mechanism, like whether or not to allow another - * app to have its SIGNATURE permission. An app could specify whether to allow other apps - * signed by its old cert 'X' to still get a signature permission it defines, for example. - */ - @Nullable - public final int[] pastSigningCertificatesFlags; - /** A representation of unknown signing details. Use instead of null. */ public static final SigningDetails UNKNOWN = - new SigningDetails(null, SignatureSchemeVersion.UNKNOWN, null, null, null); + new SigningDetails(null, SignatureSchemeVersion.UNKNOWN, null, null); @VisibleForTesting public SigningDetails(Signature[] signatures, @SignatureSchemeVersion int signatureSchemeVersion, - ArraySet<PublicKey> keys, Signature[] pastSigningCertificates, - int[] pastSigningCertificatesFlags) { + ArraySet<PublicKey> keys, Signature[] pastSigningCertificates) { this.signatures = signatures; this.signatureSchemeVersion = signatureSchemeVersion; this.publicKeys = keys; this.pastSigningCertificates = pastSigningCertificates; - this.pastSigningCertificatesFlags = pastSigningCertificatesFlags; } public SigningDetails(Signature[] signatures, @SignatureSchemeVersion int signatureSchemeVersion, - Signature[] pastSigningCertificates, int[] pastSigningCertificatesFlags) + Signature[] pastSigningCertificates) throws CertificateException { this(signatures, signatureSchemeVersion, toSigningKeys(signatures), - pastSigningCertificates, pastSigningCertificatesFlags); + pastSigningCertificates); } public SigningDetails(Signature[] signatures, @SignatureSchemeVersion int signatureSchemeVersion) throws CertificateException { - this(signatures, signatureSchemeVersion, - null, null); + this(signatures, signatureSchemeVersion, null); } public SigningDetails(SigningDetails orig) { @@ -5838,17 +5824,14 @@ public class PackageParser { this.publicKeys = new ArraySet<>(orig.publicKeys); if (orig.pastSigningCertificates != null) { this.pastSigningCertificates = orig.pastSigningCertificates.clone(); - this.pastSigningCertificatesFlags = orig.pastSigningCertificatesFlags.clone(); } else { this.pastSigningCertificates = null; - this.pastSigningCertificatesFlags = null; } } else { this.signatures = null; this.signatureSchemeVersion = SignatureSchemeVersion.UNKNOWN; this.publicKeys = null; this.pastSigningCertificates = null; - this.pastSigningCertificatesFlags = null; } } @@ -5950,7 +5933,7 @@ public class PackageParser { if (Signature.areEffectiveMatch( oldDetails.signatures[0], pastSigningCertificates[i]) - && pastSigningCertificatesFlags[i] == flags) { + && pastSigningCertificates[i].getFlags() == flags) { return true; } } @@ -6000,7 +5983,7 @@ public class PackageParser { for (int i = 0; i < pastSigningCertificates.length - 1; i++) { if (pastSigningCertificates[i].equals(signature)) { if (flags == PAST_CERT_EXISTS - || (flags & pastSigningCertificatesFlags[i]) == flags) { + || (flags & pastSigningCertificates[i].getFlags()) == flags) { return true; } } @@ -6084,7 +6067,7 @@ public class PackageParser { pastSigningCertificates[i].toByteArray()); if (Arrays.equals(sha256Certificate, digest)) { if (flags == PAST_CERT_EXISTS - || (flags & pastSigningCertificatesFlags[i]) == flags) { + || (flags & pastSigningCertificates[i].getFlags()) == flags) { return true; } } @@ -6121,7 +6104,6 @@ public class PackageParser { dest.writeInt(this.signatureSchemeVersion); dest.writeArraySet(this.publicKeys); dest.writeTypedArray(this.pastSigningCertificates, flags); - dest.writeIntArray(this.pastSigningCertificatesFlags); } protected SigningDetails(Parcel in) { @@ -6130,7 +6112,6 @@ public class PackageParser { this.signatureSchemeVersion = in.readInt(); this.publicKeys = (ArraySet<PublicKey>) in.readArraySet(boot); this.pastSigningCertificates = in.createTypedArray(Signature.CREATOR); - this.pastSigningCertificatesFlags = in.createIntArray(); } public static final Creator<SigningDetails> CREATOR = new Creator<SigningDetails>() { @@ -6169,9 +6150,6 @@ public class PackageParser { if (!Arrays.equals(pastSigningCertificates, that.pastSigningCertificates)) { return false; } - if (!Arrays.equals(pastSigningCertificatesFlags, that.pastSigningCertificatesFlags)) { - return false; - } return true; } @@ -6182,7 +6160,6 @@ public class PackageParser { result = 31 * result + signatureSchemeVersion; result = 31 * result + (publicKeys != null ? publicKeys.hashCode() : 0); result = 31 * result + Arrays.hashCode(pastSigningCertificates); - result = 31 * result + Arrays.hashCode(pastSigningCertificatesFlags); return result; } @@ -6193,7 +6170,6 @@ public class PackageParser { private Signature[] mSignatures; private int mSignatureSchemeVersion = SignatureSchemeVersion.UNKNOWN; private Signature[] mPastSigningCertificates; - private int[] mPastSigningCertificatesFlags; @UnsupportedAppUsage public Builder() { @@ -6220,34 +6196,12 @@ public class PackageParser { return this; } - /** set the flags for the {@code pastSigningCertificates} */ - @UnsupportedAppUsage - public Builder setPastSigningCertificatesFlags(int[] pastSigningCertificatesFlags) { - mPastSigningCertificatesFlags = pastSigningCertificatesFlags; - return this; - } - private void checkInvariants() { // must have signatures and scheme version set if (mSignatures == null) { throw new IllegalStateException("SigningDetails requires the current signing" + " certificates."); } - - // pastSigningCerts and flags must match up - boolean pastMismatch = false; - if (mPastSigningCertificates != null && mPastSigningCertificatesFlags != null) { - if (mPastSigningCertificates.length != mPastSigningCertificatesFlags.length) { - pastMismatch = true; - } - } else if (!(mPastSigningCertificates == null - && mPastSigningCertificatesFlags == null)) { - pastMismatch = true; - } - if (pastMismatch) { - throw new IllegalStateException("SigningDetails must have a one to one mapping " - + "between pastSigningCertificates and pastSigningCertificatesFlags"); - } } /** build a {@code SigningDetails} object */ @UnsupportedAppUsage @@ -6255,7 +6209,7 @@ public class PackageParser { throws CertificateException { checkInvariants(); return new SigningDetails(mSignatures, mSignatureSchemeVersion, - mPastSigningCertificates, mPastSigningCertificatesFlags); + mPastSigningCertificates); } } } diff --git a/core/java/android/content/pm/Signature.java b/core/java/android/content/pm/Signature.java index e58ca609f1a8..349bb693bd9c 100644 --- a/core/java/android/content/pm/Signature.java +++ b/core/java/android/content/pm/Signature.java @@ -45,6 +45,20 @@ public class Signature implements Parcelable { private boolean mHaveHashCode; private SoftReference<String> mStringRef; private Certificate[] mCertificateChain; + /** + * APK Signature Scheme v3 includes support for adding a proof-of-rotation record that + * contains two pieces of information: + * 1) the past signing certificates + * 2) the flags that APK wants to assign to each of the past signing certificates. + * + * These flags represent the second piece of information and are viewed as capabilities. + * They are an APK's way of telling the platform: "this is how I want to trust my old certs, + * please enforce that." This is useful for situation where this app itself is using its + * signing certificate as an authorization mechanism, like whether or not to allow another + * app to have its SIGNATURE permission. An app could specify whether to allow other apps + * signed by its old cert 'X' to still get a signature permission it defines, for example. + */ + private int mFlags; /** * Create Signature from an existing raw byte array. @@ -109,6 +123,22 @@ public class Signature implements Parcelable { } /** + * Sets the flags representing the capabilities of the past signing certificate. + * @hide + */ + public void setFlags(int flags) { + this.mFlags = flags; + } + + /** + * Returns the flags representing the capabilities of the past signing certificate. + * @hide + */ + public int getFlags() { + return mFlags; + } + + /** * Encode the Signature as ASCII text. */ public char[] toChars() { @@ -328,4 +358,4 @@ public class Signature implements Parcelable { return sPrime; } -} +}
\ No newline at end of file diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java index 121b43275257..799f8e55cd18 100644 --- a/core/java/android/content/res/Configuration.java +++ b/core/java/android/content/res/Configuration.java @@ -46,6 +46,7 @@ import android.annotation.Nullable; import android.annotation.TestApi; import android.annotation.UnsupportedAppUsage; import android.app.WindowConfiguration; +import android.content.LocaleProto; import android.content.pm.ActivityInfo; import android.content.pm.ActivityInfo.Config; import android.os.Build; @@ -54,7 +55,9 @@ import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; import android.util.DisplayMetrics; +import android.util.proto.ProtoInputStream; import android.util.proto.ProtoOutputStream; +import android.util.proto.WireTypeMismatchException; import android.view.View; import com.android.internal.util.XmlUtils; @@ -67,6 +70,7 @@ import java.io.IOException; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; +import java.util.List; import java.util.Locale; /** @@ -1086,12 +1090,14 @@ public final class Configuration implements Parcelable, Comparable<Configuration /** * Write to a protocol buffer output stream. * Protocol buffer message definition at {@link android.content.ConfigurationProto} + * Has the option to ignore fields that don't need to be persisted to disk. * * @param protoOutputStream Stream to write the Configuration object to. * @param fieldId Field Id of the Configuration as defined in the parent message + * @param persisted Note if this proto will be persisted to disk * @hide */ - public void writeToProto(ProtoOutputStream protoOutputStream, long fieldId) { + public void writeToProto(ProtoOutputStream protoOutputStream, long fieldId, boolean persisted) { final long token = protoOutputStream.start(fieldId); protoOutputStream.write(FONT_SCALE, fontScale); protoOutputStream.write(MCC, mcc); @@ -1113,13 +1119,137 @@ public final class Configuration implements Parcelable, Comparable<Configuration protoOutputStream.write(SCREEN_HEIGHT_DP, screenHeightDp); protoOutputStream.write(SMALLEST_SCREEN_WIDTH_DP, smallestScreenWidthDp); protoOutputStream.write(DENSITY_DPI, densityDpi); - if (windowConfiguration != null) { + // For persistence, we do not care about window configuration + if (!persisted && windowConfiguration != null) { windowConfiguration.writeToProto(protoOutputStream, WINDOW_CONFIGURATION); } protoOutputStream.end(token); } /** + * Write to a protocol buffer output stream. + * Protocol buffer message definition at {@link android.content.ConfigurationProto} + * + * @param protoOutputStream Stream to write the Configuration object to. + * @param fieldId Field Id of the Configuration as defined in the parent message + * @hide + */ + public void writeToProto(ProtoOutputStream protoOutputStream, long fieldId) { + writeToProto(protoOutputStream, fieldId, false); + } + + /** + * Read from a protocol buffer output stream. + * Protocol buffer message definition at {@link android.content.ConfigurationProto} + * + * @param protoInputStream Stream to read the Configuration object from. + * @param fieldId Field Id of the Configuration as defined in the parent message + * @hide + */ + public void readFromProto(ProtoInputStream protoInputStream, long fieldId) throws IOException { + final long token = protoInputStream.start(fieldId); + final List<Locale> list = new ArrayList(); + try { + while (protoInputStream.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (protoInputStream.getFieldNumber()) { + case (int) FONT_SCALE: + fontScale = protoInputStream.readFloat(FONT_SCALE); + break; + case (int) MCC: + mcc = protoInputStream.readInt(MCC); + break; + case (int) MNC: + mnc = protoInputStream.readInt(MNC); + break; + case (int) LOCALES: + // Parse the Locale here to handle all the repeated Locales + // The LocaleList will be created when the message is completed + final long localeToken = protoInputStream.start(LOCALES); + String language = ""; + String country = ""; + String variant = ""; + try { + while (protoInputStream.nextField() + != ProtoInputStream.NO_MORE_FIELDS) { + switch (protoInputStream.getFieldNumber()) { + case (int) LocaleProto.LANGUAGE: + language = protoInputStream.readString( + LocaleProto.LANGUAGE); + break; + case (int) LocaleProto.COUNTRY: + country = protoInputStream.readString(LocaleProto.COUNTRY); + break; + case (int) LocaleProto.VARIANT: + variant = protoInputStream.readString(LocaleProto.VARIANT); + break; + } + } + } catch (WireTypeMismatchException wtme) { + // rethrow for caller deal with + throw wtme; + } finally { + protoInputStream.end(localeToken); + list.add(new Locale(language, country, variant)); + } + break; + case (int) SCREEN_LAYOUT: + screenLayout = protoInputStream.readInt(SCREEN_LAYOUT); + break; + case (int) COLOR_MODE: + colorMode = protoInputStream.readInt(COLOR_MODE); + break; + case (int) TOUCHSCREEN: + touchscreen = protoInputStream.readInt(TOUCHSCREEN); + break; + case (int) KEYBOARD: + keyboard = protoInputStream.readInt(KEYBOARD); + break; + case (int) KEYBOARD_HIDDEN: + keyboardHidden = protoInputStream.readInt(KEYBOARD_HIDDEN); + break; + case (int) HARD_KEYBOARD_HIDDEN: + hardKeyboardHidden = protoInputStream.readInt(HARD_KEYBOARD_HIDDEN); + break; + case (int) NAVIGATION: + navigation = protoInputStream.readInt(NAVIGATION); + break; + case (int) NAVIGATION_HIDDEN: + navigationHidden = protoInputStream.readInt(NAVIGATION_HIDDEN); + break; + case (int) ORIENTATION: + orientation = protoInputStream.readInt(ORIENTATION); + break; + case (int) UI_MODE: + uiMode = protoInputStream.readInt(UI_MODE); + break; + case (int) SCREEN_WIDTH_DP: + screenWidthDp = protoInputStream.readInt(SCREEN_WIDTH_DP); + break; + case (int) SCREEN_HEIGHT_DP: + screenHeightDp = protoInputStream.readInt(SCREEN_HEIGHT_DP); + break; + case (int) SMALLEST_SCREEN_WIDTH_DP: + smallestScreenWidthDp = protoInputStream.readInt(SMALLEST_SCREEN_WIDTH_DP); + break; + case (int) DENSITY_DPI: + densityDpi = protoInputStream.readInt(DENSITY_DPI); + break; + case (int) WINDOW_CONFIGURATION: + windowConfiguration.readFromProto(protoInputStream, WINDOW_CONFIGURATION); + break; + } + } + } finally { + // Let caller handle any exceptions + if (list.size() > 0) { + //Create the LocaleList from the collected Locales + setLocales(new LocaleList(list.toArray(new Locale[list.size()]))); + } + protoInputStream.end(token); + } + } + + /** * Write full {@link android.content.ResourcesConfigurationProto} to protocol buffer output * stream. * diff --git a/core/java/android/hardware/GeomagneticField.java b/core/java/android/hardware/GeomagneticField.java index 94f2ac085965..0d7b695d7f1d 100644 --- a/core/java/android/hardware/GeomagneticField.java +++ b/core/java/android/hardware/GeomagneticField.java @@ -31,7 +31,7 @@ import java.util.GregorianCalendar; * Android may use a newer version of the model. */ public class GeomagneticField { - // The magnetic field at a given point, in nonoteslas in geodetic + // The magnetic field at a given point, in nanoteslas in geodetic // coordinates. private float mX; private float mY; @@ -278,7 +278,7 @@ public class GeomagneticField { } /** - * @return Horizontal component of the field strength in nonoteslas. + * @return Horizontal component of the field strength in nanoteslas. */ public float getHorizontalStrength() { return (float) Math.hypot(mX, mY); diff --git a/core/java/android/hardware/biometrics/BiometricConstants.java b/core/java/android/hardware/biometrics/BiometricConstants.java index 6150be361ef2..2a64c2e1e074 100644 --- a/core/java/android/hardware/biometrics/BiometricConstants.java +++ b/core/java/android/hardware/biometrics/BiometricConstants.java @@ -34,6 +34,13 @@ public interface BiometricConstants { // /** + * This was not added here since it would update BiometricPrompt API. But, is used in + * BiometricManager. + * @hide + */ + int BIOMETRIC_ERROR_NONE = 0; + + /** * The hardware is unavailable. Try again later. */ int BIOMETRIC_ERROR_HW_UNAVAILABLE = 1; diff --git a/core/java/android/hardware/biometrics/BiometricManager.java b/core/java/android/hardware/biometrics/BiometricManager.java index eea5f9ba9835..b8739b9fb2f9 100644 --- a/core/java/android/hardware/biometrics/BiometricManager.java +++ b/core/java/android/hardware/biometrics/BiometricManager.java @@ -17,16 +17,35 @@ package android.hardware.biometrics; import static android.Manifest.permission.USE_BIOMETRIC; +import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL; import android.annotation.RequiresPermission; import android.content.Context; import android.os.RemoteException; +import android.util.Slog; /** * A class that contains biometric utilities. For authentication, see {@link BiometricPrompt}. */ public class BiometricManager { + private static final String TAG = "BiometricManager"; + + /** + * No error detected. + */ + public static final int ERROR_NONE = BiometricConstants.BIOMETRIC_ERROR_NONE; + + /** + * The hardware is unavailable. Try again later. + */ + public static final int ERROR_UNAVAILABLE = BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE; + + /** + * The user does not have any biometrics enrolled. + */ + public static final int ERROR_NO_BIOMETRICS = BiometricConstants.BIOMETRIC_ERROR_NO_BIOMETRICS; + private final Context mContext; private final IBiometricService mService; @@ -41,16 +60,42 @@ public class BiometricManager { } /** - * Determine if there is at least one biometric enrolled. + * Determine if biometrics can be used. In other words, determine if {@link BiometricPrompt} + * can be expected to be shown (hardware available, templates enrolled, user-enabled). * - * @return true if at least one biometric is enrolled, false otherwise + * @return Returns {@link #ERROR_NO_BIOMETRICS} if the user does not have any enrolled, or + * {@link #ERROR_UNAVAILABLE} if none are currently supported/enabled. Returns + * {@link #ERROR_NONE} if a biometric can currently be used (enrolled and available). */ @RequiresPermission(USE_BIOMETRIC) - public boolean hasEnrolledBiometrics() { - try { - return mService.hasEnrolledBiometrics(mContext.getOpPackageName()); - } catch (RemoteException e) { - return false; + public int canAuthenticate() { + if (mService != null) { + try { + return mService.canAuthenticate(mContext.getOpPackageName()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } else { + Slog.w(TAG, "hasEnrolledBiometrics(): Service not connected"); + return ERROR_UNAVAILABLE; + } + } + + /** + * Listens for changes to biometric eligibility on keyguard from user settings. + * @param callback + * @hide + */ + @RequiresPermission(USE_BIOMETRIC_INTERNAL) + public void registerEnabledOnKeyguardCallback(IBiometricEnabledOnKeyguardCallback callback) { + if (mService != null) { + try { + mService.registerEnabledOnKeyguardCallback(callback); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } else { + Slog.w(TAG, "registerEnabledOnKeyguardCallback(): Service not connected"); } } } diff --git a/core/java/android/hardware/biometrics/IBiometricEnabledOnKeyguardCallback.aidl b/core/java/android/hardware/biometrics/IBiometricEnabledOnKeyguardCallback.aidl new file mode 100644 index 000000000000..d22e7e295b77 --- /dev/null +++ b/core/java/android/hardware/biometrics/IBiometricEnabledOnKeyguardCallback.aidl @@ -0,0 +1,26 @@ +/* + * 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.hardware.biometrics; + +import android.hardware.biometrics.BiometricSourceType; + +/** + * @hide + */ +oneway interface IBiometricEnabledOnKeyguardCallback { + void onChanged(in BiometricSourceType type, boolean enabled); +}
\ No newline at end of file diff --git a/core/java/android/hardware/biometrics/IBiometricPromptReceiver.aidl b/core/java/android/hardware/biometrics/IBiometricPromptReceiver.aidl index 67c9346da265..27d25b86b859 100644 --- a/core/java/android/hardware/biometrics/IBiometricPromptReceiver.aidl +++ b/core/java/android/hardware/biometrics/IBiometricPromptReceiver.aidl @@ -15,9 +15,6 @@ */ package android.hardware.biometrics; -import android.os.Bundle; -import android.os.UserHandle; - /** * Communication channel from the BiometricPrompt (SysUI) back to AuthenticationClient. * @hide diff --git a/core/java/android/hardware/biometrics/IBiometricService.aidl b/core/java/android/hardware/biometrics/IBiometricService.aidl index fd9d5725f921..51e4ecbf56b0 100644 --- a/core/java/android/hardware/biometrics/IBiometricService.aidl +++ b/core/java/android/hardware/biometrics/IBiometricService.aidl @@ -17,6 +17,7 @@ package android.hardware.biometrics; import android.os.Bundle; +import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback; import android.hardware.biometrics.IBiometricPromptReceiver; import android.hardware.biometrics.IBiometricServiceReceiver; @@ -37,6 +38,9 @@ interface IBiometricService { // Cancel authentication for the given sessionId void cancelAuthentication(IBinder token, String opPackageName); - // Returns true if the user has at least one enrolled biometric. - boolean hasEnrolledBiometrics(String opPackageName); + // Checks if biometrics can be used. + int canAuthenticate(String opPackageName); + + // Register callback for when keyguard biometric eligibility changes. + void registerEnabledOnKeyguardCallback(IBiometricEnabledOnKeyguardCallback callback); }
\ No newline at end of file diff --git a/core/java/android/hardware/biometrics/IBiometricServiceReceiver.aidl b/core/java/android/hardware/biometrics/IBiometricServiceReceiver.aidl index 71abdd246097..a6e3696eeb10 100644 --- a/core/java/android/hardware/biometrics/IBiometricServiceReceiver.aidl +++ b/core/java/android/hardware/biometrics/IBiometricServiceReceiver.aidl @@ -15,10 +15,6 @@ */ package android.hardware.biometrics; -import android.hardware.biometrics.BiometricSourceType; -import android.os.Bundle; -import android.os.UserHandle; - /** * Communication channel from the BiometricService back to BiometricPrompt. * @hide diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java index e8fb28779563..09113e5d175d 100644 --- a/core/java/android/hardware/display/DisplayManager.java +++ b/core/java/android/hardware/display/DisplayManager.java @@ -297,6 +297,15 @@ public final class DisplayManager { */ public static final int VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL = 1 << 8; + /** + * Virtual display flag: Indicates that the display should support system decorations. Virtual + * displays without this flag shouldn't show home, IME or any other system decorations. + * + * @see #createVirtualDisplay + * @hide + */ + public static final int VIRTUAL_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS = 1 << 9; + /** @hide */ public DisplayManager(Context context) { mContext = context; diff --git a/core/java/android/hardware/display/DisplayViewport.java b/core/java/android/hardware/display/DisplayViewport.java index 496f34c0348a..df0d46be1354 100644 --- a/core/java/android/hardware/display/DisplayViewport.java +++ b/core/java/android/hardware/display/DisplayViewport.java @@ -16,9 +16,14 @@ package android.hardware.display; +import static java.lang.annotation.RetentionPolicy.SOURCE; + +import android.annotation.IntDef; import android.graphics.Rect; import android.text.TextUtils; +import java.lang.annotation.Retention; + /** * Describes how the pixels of physical display device reflects the content of * a logical display. @@ -35,6 +40,10 @@ public final class DisplayViewport { public static final int VIEWPORT_INTERNAL = 1; public static final int VIEWPORT_EXTERNAL = 2; public static final int VIEWPORT_VIRTUAL = 3; + @IntDef(prefix = { "VIEWPORT_" }, value = { + VIEWPORT_INTERNAL, VIEWPORT_EXTERNAL, VIEWPORT_VIRTUAL}) + @Retention(SOURCE) + public @interface ViewportType {}; // True if this viewport is valid. public boolean valid; @@ -62,6 +71,8 @@ public final class DisplayViewport { // The ID used to uniquely identify this display. public String uniqueId; + public @ViewportType int type; + public void copyFrom(DisplayViewport viewport) { valid = viewport.valid; displayId = viewport.displayId; @@ -71,6 +82,7 @@ public final class DisplayViewport { deviceWidth = viewport.deviceWidth; deviceHeight = viewport.deviceHeight; uniqueId = viewport.uniqueId; + type = viewport.type; } /** @@ -100,7 +112,8 @@ public final class DisplayViewport { && physicalFrame.equals(other.physicalFrame) && deviceWidth == other.deviceWidth && deviceHeight == other.deviceHeight - && TextUtils.equals(uniqueId, other.uniqueId); + && TextUtils.equals(uniqueId, other.uniqueId) + && type == other.type; } @Override @@ -115,13 +128,15 @@ public final class DisplayViewport { result += prime * result + deviceWidth; result += prime * result + deviceHeight; result += prime * result + uniqueId.hashCode(); + result += prime * result + type; return result; } // For debugging purposes. @Override public String toString() { - return "DisplayViewport{valid=" + valid + return "DisplayViewport{type=" + typeToString(type) + + ", valid=" + valid + ", displayId=" + displayId + ", uniqueId='" + uniqueId + "'" + ", orientation=" + orientation @@ -131,4 +146,20 @@ public final class DisplayViewport { + ", deviceHeight=" + deviceHeight + "}"; } + + /** + * Human-readable viewport type. + */ + public static String typeToString(@ViewportType int viewportType) { + switch (viewportType) { + case VIEWPORT_INTERNAL: + return "INTERNAL"; + case VIEWPORT_EXTERNAL: + return "EXTERNAL"; + case VIEWPORT_VIRTUAL: + return "VIRTUAL"; + default: + return "UNKNOWN (" + viewportType + ")"; + } + } } diff --git a/core/java/android/hardware/face/IFaceServiceReceiver.aidl b/core/java/android/hardware/face/IFaceServiceReceiver.aidl index 16fb69021467..b88574b74c36 100644 --- a/core/java/android/hardware/face/IFaceServiceReceiver.aidl +++ b/core/java/android/hardware/face/IFaceServiceReceiver.aidl @@ -16,8 +16,6 @@ package android.hardware.face; import android.hardware.face.Face; -import android.os.Bundle; -import android.os.UserHandle; /** * Communication channel from the FaceService back to FaceAuthenticationManager. diff --git a/core/java/android/hardware/fingerprint/IFingerprintServiceReceiver.aidl b/core/java/android/hardware/fingerprint/IFingerprintServiceReceiver.aidl index 370383f4a909..cf1c94ea5346 100644 --- a/core/java/android/hardware/fingerprint/IFingerprintServiceReceiver.aidl +++ b/core/java/android/hardware/fingerprint/IFingerprintServiceReceiver.aidl @@ -16,8 +16,6 @@ package android.hardware.fingerprint; import android.hardware.fingerprint.Fingerprint; -import android.os.Bundle; -import android.os.UserHandle; /** * Communication channel from the FingerprintService back to FingerprintManager. diff --git a/core/java/android/hardware/input/InputManagerInternal.java b/core/java/android/hardware/input/InputManagerInternal.java index c4d7e400795c..d8da548d9bd3 100644 --- a/core/java/android/hardware/input/InputManagerInternal.java +++ b/core/java/android/hardware/input/InputManagerInternal.java @@ -40,8 +40,7 @@ public abstract class InputManagerInternal { * Called by the display manager to set information about the displays as needed * by the input system. The input system must copy this information to retain it. */ - public abstract void setDisplayViewports(DisplayViewport defaultViewport, - DisplayViewport externalTouchViewport, List<DisplayViewport> virtualTouchViewports); + public abstract void setDisplayViewports(List<DisplayViewport> viewports); /** * Called by the power manager to tell the input manager whether it should start diff --git a/core/java/android/hardware/location/ContextHubBroadcastReceiver.java b/core/java/android/hardware/location/ContextHubBroadcastReceiver.java new file mode 100644 index 000000000000..e0cc8b7de634 --- /dev/null +++ b/core/java/android/hardware/location/ContextHubBroadcastReceiver.java @@ -0,0 +1,102 @@ +/* + * Copyright 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.hardware.location; + +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.Handler; + +/** + * A BroadcastReceiver that can be used with the Context Hub Service notifications. + * + * @hide + */ +public class ContextHubBroadcastReceiver extends BroadcastReceiver { + // The context at which this receiver operates in + private Context mContext; + + // The handler to post callbacks to when receiving Context Hub Service intents + private Handler mHandler; + + // The callback to be invoked when receiving Context Hub Service intents + private ContextHubClientCallback mCallback; + + // The string to use as the broadcast action for this receiver + private String mAction; + + // True when this receiver is registered to receive Intents, false otherwise + private boolean mRegistered = false; + + public ContextHubBroadcastReceiver(Context context, Handler handler, + ContextHubClientCallback callback, String tag) { + mContext = context; + mHandler = handler; + mCallback = callback; + mAction = tag; + } + + /** + * Registers this receiver to receive Intents from the Context Hub Service. This method must + * only be invoked when the receiver is not registered. + * + * @throws IllegalStateException if the receiver is already registered + */ + public void register() throws IllegalStateException { + if (mRegistered) { + throw new IllegalStateException( + "Cannot register ContextHubBroadcastReceiver multiple times"); + } + IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(mAction); + mContext.registerReceiver(this, intentFilter, null /* broadcastPermission */, mHandler); + mRegistered = true; + } + + /** + * Unregisters this receiver. This method must only be invoked if {@link #register()} is + * previously invoked. + * + * @throws IllegalStateException if the receiver is not yet registered + */ + public void unregister() throws IllegalStateException { + if (!mRegistered) { + throw new IllegalStateException( + "Cannot unregister ContextHubBroadcastReceiver when not registered"); + } + mContext.unregisterReceiver(this); + mRegistered = false; + } + + /** + * Creates a new PendingIntent associated with this receiver. + * + * @param flags the flags {@link PendingIntent.Flags} to use for the PendingIntent + * + * @return a PendingIntent to receive notifications for this receiver + */ + public PendingIntent getPendingIntent(@PendingIntent.Flags int flags) { + return PendingIntent.getBroadcast( + mContext, 0 /* requestCode */, new Intent(mAction), flags); + } + + @Override + public void onReceive(Context context, Intent intent) { + // TODO: Implement this + } +} diff --git a/core/java/android/hardware/location/ContextHubClient.java b/core/java/android/hardware/location/ContextHubClient.java index 2335203eb100..917644db4202 100644 --- a/core/java/android/hardware/location/ContextHubClient.java +++ b/core/java/android/hardware/location/ContextHubClient.java @@ -18,6 +18,7 @@ package android.hardware.location; import android.annotation.NonNull; import android.annotation.RequiresPermission; import android.annotation.SystemApi; +import android.app.PendingIntent; import android.os.RemoteException; import com.android.internal.util.Preconditions; @@ -100,6 +101,57 @@ public class ContextHubClient implements Closeable { } /** + * Registers to receive persistent intents for a given nanoapp. + * + * This method should be used if the caller wants to receive notifications even after the + * process exits. The client must have an open connection with the Context Hub Service (i.e. it + * cannot have been closed through the {@link #close()} method). If registered successfully, + * intents will be delivered regarding events for the specified nanoapp from the attached + * Context Hub. Any unicast messages for this client will also be delivered. The intent will + * have an extra {@link #EXTRA_EVENT_TYPE} of type {@link ContextHubManager.Event}, which will + * contain the type of the event. See {@link ContextHubManager.Event} for description of each + * event type. + * + * When the intent is received, this client can be recreated through + * {@link ContextHubManager.createClient(PendingIntent, ContextHubInfo, + * ContextHubClientCallback, Exectutor)}. When recreated, the client can be treated as the + * same endpoint entity from a nanoapp's perspective, and can be continued to be used to send + * messages even if the original process has exited. + * + * Intents will be delivered until it is unregistered through + * {@link #unregisterIntent(PendingIntent)}. Note that the registration of this client will + * continued to be maintained at the Context Hub Service until + * {@link #unregisterIntent(PendingIntent)} is called for registered intents. + * + * See {@link ContextHubBroadcastReceiver} for a helper class to generate the + * {@link PendingIntent} through a {@link BroadcastReceiver}, and maps an {@link Intent} to a + * {@link ContextHubClientCallback}. + * + * @param intent The PendingIntent to register for this client + * @param nanoAppId the unique ID of the nanoapp to receive events for + * @return true on success, false otherwise + * + * @hide + */ + public boolean registerIntent(@NonNull PendingIntent intent, long nanoAppId) { + // TODO: Implement this + return false; + } + + /** + * Unregisters an intent previously registered via {@link #registerIntent(PendingIntent, long)}. + * If this intent has not been registered for this client, this method returns false. + * + * @return true on success, false otherwise + * + * @hide + */ + public boolean unregisterIntent(@NonNull PendingIntent intent) { + // TODO: Implement this + return false; + } + + /** * Sends a message to a nanoapp through the Context Hub Service. * * This function returns RESULT_SUCCESS if the message has reached the HAL, but diff --git a/core/java/android/hardware/location/ContextHubManager.java b/core/java/android/hardware/location/ContextHubManager.java index 12d0531bbf2b..36f3586aec2a 100644 --- a/core/java/android/hardware/location/ContextHubManager.java +++ b/core/java/android/hardware/location/ContextHubManager.java @@ -16,12 +16,14 @@ package android.hardware.location; import android.annotation.CallbackExecutor; +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.annotation.SystemService; +import android.app.PendingIntent; import android.content.Context; import android.os.Handler; import android.os.HandlerExecutor; @@ -33,6 +35,8 @@ import android.util.Log; import com.android.internal.util.Preconditions; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.List; import java.util.concurrent.Executor; @@ -49,6 +53,111 @@ import java.util.concurrent.Executor; public final class ContextHubManager { private static final String TAG = "ContextHubManager"; + /** + * An extra of type {@link ContextHubInfo} describing the source of the event. + * + * @hide + */ + public static final String EXTRA_CONTEXT_HUB_INFO = + "android.hardware.location.extra.CONTEXT_HUB_INFO"; + + /** + * An extra of type {@link ContextHubManager.Event} describing the event type. + * + * @hide + */ + public static final String EXTRA_EVENT_TYPE = "android.hardware.location.extra.EVENT_TYPE"; + + /** + * An extra of type long describing the ID of the nanoapp an event is for. + * + * @hide + */ + public static final String EXTRA_NANOAPP_ID = "android.location.hardware.extra.NANOAPP_ID"; + + /** + * An extra of type int describing the nanoapp-specific abort code. + * + * @hide + */ + public static final String EXTRA_NANOAPP_ABORT_CODE = + "android.location.hardware.extra.NANOAPP_ABORT_CODE"; + + /** + * An extra of type {@link NanoAppMessage} describing contents of a message from a nanoapp. + * + * @hide + */ + public static final String EXTRA_MESSAGE = "android.location.hardware.extra.MESSAGE"; + + /** + * Constants describing the type of events from a Context Hub. + * {@hide} + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = { "EVENT_" }, value = { + EVENT_NANOAPP_LOADED, + EVENT_NANOAPP_UNLOADED, + EVENT_NANOAPP_ENABLED, + EVENT_NANOAPP_DISABLED, + EVENT_NANOAPP_ABORTED, + EVENT_NANOAPP_MESSAGE, + EVENT_HUB_RESET, + }) + public @interface Event { } + + /** + * An event describing that a nanoapp has been loaded. Contains the EXTRA_NANOAPP_ID extra. + * + * @hide + */ + public static final int EVENT_NANOAPP_LOADED = 0; + + /** + * An event describing that a nanoapp has been unloaded. Contains the EXTRA_NANOAPP_ID extra. + * + * @hide + */ + public static final int EVENT_NANOAPP_UNLOADED = 1; + + /** + * An event describing that a nanoapp has been enabled. Contains the EXTRA_NANOAPP_ID extra. + * + * @hide + */ + public static final int EVENT_NANOAPP_ENABLED = 2; + + /** + * An event describing that a nanoapp has been disabled. Contains the EXTRA_NANOAPP_ID extra. + * + * @hide + */ + public static final int EVENT_NANOAPP_DISABLED = 3; + + /** + * An event describing that a nanoapp has aborted. Contains the EXTRA_NANOAPP_ID and + * EXTRA_NANOAPP_ABORT_CODE extras. + * + * @hide + */ + public static final int EVENT_NANOAPP_ABORTED = 4; + + /** + * An event containing a message sent from a nanoapp. Contains the EXTRA_NANOAPP_ID and + * EXTRA_NANOAPP_MESSAGE extras. + * + * @hide + */ + public static final int EVENT_NANOAPP_MESSAGE = 5; + + /** + * An event describing that the Context Hub has reset. + * + * @hide + */ + public static final int EVENT_HUB_RESET = 6; + + private final Looper mMainLooper; private final IContextHubService mService; private Callback mCallback; @@ -682,6 +791,57 @@ public final class ContextHubManager { } /** + * Creates a ContextHubClient based on an Intent received by the Context Hub Service. + * + * This method is intended to be used after receiving an Intent received as a result of + * {@link ContextHubClient.registerIntent(PendingIntent, long)}, and must have been created + * through {@link #createClient(ContextHubInfo, ContextHubClientCallback, Executor)} or + * equivalent at an earlier time. + * + * @param intent the intent that is associated with a client + * @param hubInfo the hub to attach this client to + * @param callback the notification callback to register + * @param executor the executor to invoke the callback + * @return the registered client object + * + * @throws IllegalArgumentException if hubInfo does not represent a valid hub, or the intent + * was not associated with a client + * @throws IllegalStateException if there were too many registered clients at the service + * @throws NullPointerException if intent, hubInfo, callback, or executor is null + * + * @hide + */ + @NonNull public ContextHubClient createClient( + @NonNull PendingIntent intent, @NonNull ContextHubInfo hubInfo, + @NonNull ContextHubClientCallback callback, + @NonNull @CallbackExecutor Executor executor) { + // TODO: Implement this + throw new UnsupportedOperationException("Not implemented yet"); + } + + /** + * Equivalent to {@link #createClient(Intent, ContextHubInfo, ContextHubClientCallback, + * Executor)} with the executor using the main thread's Looper. + * + * @param intent the intent that is associated with a client + * @param hubInfo the hub to attach this client to + * @param callback the notification callback to register + * @return the registered client object + * + * @throws IllegalArgumentException if hubInfo does not represent a valid hub, or the intent + * was not associated with a client + * @throws IllegalStateException if there were too many registered clients at the service + * @throws NullPointerException if intent, hubInfo, or callback is null + * + * @hide + */ + @NonNull public ContextHubClient createClient( + @NonNull PendingIntent intent, @NonNull ContextHubInfo hubInfo, + @NonNull ContextHubClientCallback callback) { + return createClient(intent, hubInfo, callback, new HandlerExecutor(Handler.getMain())); + } + + /** * Unregister a callback for receive messages from the context hub. * * @see Callback diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java index ae12f93285a8..097a3e3a270a 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -2095,7 +2095,7 @@ public class InputMethodService extends AbstractInputMethodService { * Called when the application has reported a new location of its text * cursor. This is only called if explicitly requested by the input method. * The default implementation does nothing. - * @deprecated Use {#link onUpdateCursorAnchorInfo(CursorAnchorInfo)} instead. + * @deprecated Use {@link #onUpdateCursorAnchorInfo(CursorAnchorInfo)} instead. */ @Deprecated public void onUpdateCursor(Rect newCursor) { @@ -2162,7 +2162,7 @@ public class InputMethodService extends AbstractInputMethodService { } /** - * @return {#link ExtractEditText} if it is considered to be visible and active. Otherwise + * @return {@link ExtractEditText} if it is considered to be visible and active. Otherwise * {@code null} is returned. */ private ExtractEditText getExtractEditTextIfVisible() { @@ -2803,18 +2803,22 @@ public class InputMethodService extends AbstractInputMethodService { } /** - * @return The recommended height of the input method window. - * An IME author can get the last input method's height as the recommended height - * by calling this in - * {@link android.inputmethodservice.InputMethodService#onStartInputView(EditorInfo, boolean)}. - * If you don't need to use a predefined fixed height, you can avoid the window-resizing of IME - * switching by using this value as a visible inset height. It's efficient for the smooth - * transition between different IMEs. However, note that this may return 0 (or possibly - * unexpectedly low height). You should thus avoid relying on the return value of this method - * all the time. Please make sure to use a reasonable height for the IME. + * Aimed to return the previous input method's {@link Insets#contentTopInsets}, but its actual + * semantics has never been well defined. + * + * <p>Note that the previous document clearly mentioned that this method could return {@code 0} + * at any time for whatever reason. Now this method is just always returning {@code 0}.</p> + * + * @return on Android {@link android.os.Build.VERSION_CODES#Q} and later devices this method + * always returns {@code 0} + * @deprecated the actual behavior of this method has never been well defined. You cannot use + * this method in a reliable and predictable way */ + @Deprecated public int getInputMethodWindowRecommendedHeight() { - return mImm.getInputMethodWindowVisibleHeight(); + Log.w(TAG, "getInputMethodWindowRecommendedHeight() is deprecated and now always returns 0." + + " Do not use this method."); + return 0; } /** diff --git a/core/java/android/inputmethodservice/SoftInputWindow.java b/core/java/android/inputmethodservice/SoftInputWindow.java index b4b88871d251..0513feef801f 100644 --- a/core/java/android/inputmethodservice/SoftInputWindow.java +++ b/core/java/android/inputmethodservice/SoftInputWindow.java @@ -162,9 +162,9 @@ public class SoftInputWindow extends Dialog { /** * Set which boundary of the screen the DockWindow sticks to. * - * @param gravity The boundary of the screen to stick. See {#link - * android.view.Gravity.LEFT}, {#link android.view.Gravity.TOP}, - * {#link android.view.Gravity.BOTTOM}, {#link + * @param gravity The boundary of the screen to stick. See {@link + * android.view.Gravity.LEFT}, {@link android.view.Gravity.TOP}, + * {@link android.view.Gravity.BOTTOM}, {@link * android.view.Gravity.RIGHT}. */ public void setGravity(int gravity) { diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java index f2e907833612..8333b817add0 100644 --- a/core/java/android/net/ConnectivityManager.java +++ b/core/java/android/net/ConnectivityManager.java @@ -26,7 +26,6 @@ import android.annotation.UnsupportedAppUsage; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; -import android.content.pm.PackageManager; import android.os.Binder; import android.os.Build.VERSION_CODES; import android.os.Bundle; @@ -3801,8 +3800,9 @@ public class ConnectivityManager { private void unsupportedStartingFrom(int version) { if (Process.myUid() == Process.SYSTEM_UID) { - // The getApplicationInfo() call we make below is not supported in system context, and - // we want to allow the system to use these APIs anyway. + // The getApplicationInfo() call we make below is not supported in system context. Let + // the call through here, and rely on the fact that ConnectivityService will refuse to + // allow the system to use these APIs anyway. return; } @@ -3819,11 +3819,6 @@ public class ConnectivityManager { // functions by accessing ConnectivityService directly. However, it should be clear that doing // so is unsupported and may break in the future. http://b/22728205 private void checkLegacyRoutingApiAccess() { - if (mContext.checkCallingOrSelfPermission("com.android.permission.INJECT_OMADM_SETTINGS") - == PackageManager.PERMISSION_GRANTED) { - return; - } - unsupportedStartingFrom(VERSION_CODES.M); } diff --git a/core/java/android/net/NetworkMisc.java b/core/java/android/net/NetworkMisc.java index 69f50a272af0..a2da6eaa4922 100644 --- a/core/java/android/net/NetworkMisc.java +++ b/core/java/android/net/NetworkMisc.java @@ -46,7 +46,7 @@ public class NetworkMisc implements Parcelable { /** * Set if the user desires to use this network even if it is unvalidated. This field has meaning - * only if {#link explicitlySelected} is true. If it is, this field must also be set to the + * only if {@link explicitlySelected} is true. If it is, this field must also be set to the * appropriate value based on previous user choice. */ public boolean acceptUnvalidated; diff --git a/core/java/android/net/UrlQuerySanitizer.java b/core/java/android/net/UrlQuerySanitizer.java index d2073b4dfd3a..5b674067192e 100644 --- a/core/java/android/net/UrlQuerySanitizer.java +++ b/core/java/android/net/UrlQuerySanitizer.java @@ -287,7 +287,7 @@ public class UrlQuerySanitizer { /** * Sanitize a value. * <ol> - * <li>If script URLs are not OK, the will be removed. + * <li>If script URLs are not OK, they will be removed. * <li>If neither spaces nor other white space is OK, then * white space will be trimmed from the beginning and end of * the URL. (Just the actual white space characters are trimmed, not @@ -563,7 +563,7 @@ public class UrlQuerySanitizer { } /** - * Constructs a UrlQuerySanitizer and parse a URL. + * Constructs a UrlQuerySanitizer and parses a URL. * This constructor is provided for convenience when the * default parsing behavior is acceptable. * <p> @@ -644,7 +644,7 @@ public class UrlQuerySanitizer { } /** - * An array list of all of the parameter value pairs in the sanitized + * An array list of all of the parameter-value pairs in the sanitized * query, in the order they appeared in the query. May contain duplicate * parameters. * <p class="note"><b>Note:</b> Do not modify this list. Treat it as a read-only list.</p> @@ -656,7 +656,7 @@ public class UrlQuerySanitizer { /** * Check if a parameter exists in the current sanitized query. * @param parameter the unencoded name of a parameter. - * @return true if the paramater exists in the current sanitized queary. + * @return true if the parameter exists in the current sanitized queary. */ public boolean hasParameter(String parameter) { return mEntries.containsKey(parameter); @@ -766,7 +766,7 @@ public class UrlQuerySanitizer { * the value. If all goes well then addSanitizedValue is called with * the unescaped parameter and the sanitized unescaped value. * @param parameter an escaped parameter - * @param value an unsanitzied escaped value + * @param value an unsanitized escaped value */ protected void parseEntry(String parameter, String value) { String unescapedParameter = unescape(parameter); @@ -812,7 +812,7 @@ public class UrlQuerySanitizer { /** * Get the effective value sanitizer for a parameter. Like getValueSanitizer, * except if there is no value sanitizer registered for a parameter, and - * unregistered paramaters are allowed, then the default value sanitizer is + * unregistered parameters are allowed, then the default value sanitizer is * returned. * @param parameter an unescaped parameter * @return the effective value sanitizer for a parameter. diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java index 6bd2e76cdf35..8681893702b4 100644 --- a/core/java/android/os/Build.java +++ b/core/java/android/os/Build.java @@ -30,6 +30,8 @@ import com.android.internal.telephony.TelephonyProperties; import dalvik.system.VMRuntime; +import java.util.ArrayList; +import java.util.List; import java.util.Objects; /** @@ -1083,7 +1085,67 @@ public class Build { return true; } + /** Build information for a particular device partition. */ + public static class Partition { + /** The name identifying the system partition. */ + public static final String PARTITION_NAME_SYSTEM = "system"; + + private String mName; + private String mFingerprint; + private long mTimeMs; + + public Partition() {} + + private Partition(String name, String fingerprint, long timeMs) { + mName = name; + mFingerprint = fingerprint; + mTimeMs = timeMs; + } + + /** The name of this partition, e.g. "system", or "vendor" */ + public String getName() { + return mName; + } + + /** The build fingerprint of this partition, see {@link Build#FINGERPRINT}. */ + public String getFingerprint() { + return mFingerprint; + } + + /** The time (ms since epoch), at which this partition was built, see {@link Build#TIME}. */ + public long getTimeMillis() { + return mTimeMs; + } + } + + /** + * Get build information about partitions that have a separate fingerprint defined. + * + * The list includes partitions that are suitable candidates for over-the-air updates. This is + * not an exhaustive list of partitions on the device. + */ + public static List<Partition> getPartitions() { + ArrayList<Partition> partitions = new ArrayList(); + + String[] names = new String[] { + "bootimage", "odm", "product", "product_services", Partition.PARTITION_NAME_SYSTEM, + "vendor" + }; + for (String name : names) { + String fingerprint = SystemProperties.get("ro." + name + ".build.fingerprint"); + if (TextUtils.isEmpty(fingerprint)) { + continue; + } + long time = getLong("ro." + name + ".build.date.utc") * 1000; + partitions.add(new Partition(name, fingerprint, time)); + } + + return partitions; + } + // The following properties only make sense for internal engineering builds. + + /** The time at which the build was produced, given in milliseconds since the UNIX epoch. */ public static final long TIME = getLong("ro.build.date.utc") * 1000; public static final String USER = getString("ro.build.user"); public static final String HOST = getString("ro.build.host"); diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java index 3c43fd189337..483b7644f4d6 100644 --- a/core/java/android/os/Environment.java +++ b/core/java/android/os/Environment.java @@ -219,12 +219,11 @@ public class Environment { * services to store files relating to the user. This directory will be * automatically deleted when the user is removed. * - * @deprecated This directory is valid and still exists, but callers should - * <em>strongly</em> consider switching to - * {@link #getDataSystemCeDirectory(int)} which is protected - * with user credentials or - * {@link #getDataSystemDeDirectory(int)} which supports fast - * user wipe. + * @deprecated This directory is valid and still exists, but but callers + * should <em>strongly</em> consider switching to using either + * {@link #getDataSystemCeDirectory(int)} or + * {@link #getDataSystemDeDirectory(int)}, both of which support + * fast user wipe. * @hide */ @Deprecated @@ -292,12 +291,42 @@ public class Environment { return buildPath(getDataDirectory(), "system_ce"); } - /** {@hide} */ + /** + * Return the "credential encrypted" system directory for a user. This is + * for use by system services to store files relating to the user. This + * directory supports fast user wipe, and will be automatically deleted when + * the user is removed. + * <p> + * Data stored under this path is "credential encrypted", which uses an + * encryption key that is entangled with user credentials, such as a PIN or + * password. The contents will only be available once the user has been + * unlocked, as reported by {@code SystemService.onUnlockUser()}. + * <p> + * New code should <em>strongly</em> prefer storing sensitive data in these + * credential encrypted areas. + * + * @hide + */ public static File getDataSystemCeDirectory(int userId) { return buildPath(getDataDirectory(), "system_ce", String.valueOf(userId)); } - /** {@hide} */ + /** + * Return the "device encrypted" system directory for a user. This is for + * use by system services to store files relating to the user. This + * directory supports fast user wipe, and will be automatically deleted when + * the user is removed. + * <p> + * Data stored under this path is "device encrypted", which uses an + * encryption key that is tied to the physical device. The contents will + * only be available once the device has finished a {@code dm-verity} + * protected boot. + * <p> + * New code should <em>strongly</em> avoid storing sensitive data in these + * device encrypted areas. + * + * @hide + */ public static File getDataSystemDeDirectory(int userId) { return buildPath(getDataDirectory(), "system_de", String.valueOf(userId)); } diff --git a/core/java/android/os/GraphicsEnvironment.java b/core/java/android/os/GraphicsEnvironment.java index f83acb6b7972..54be6393e651 100644 --- a/core/java/android/os/GraphicsEnvironment.java +++ b/core/java/android/os/GraphicsEnvironment.java @@ -45,6 +45,7 @@ public class GraphicsEnvironment { private static final String TAG = "GraphicsEnvironment"; private static final String PROPERTY_GFX_DRIVER = "ro.gfx.driver.0"; private static final String ANGLE_PACKAGE_NAME = "com.android.angle"; + private static final String GLES_MODE_METADATA_KEY = "com.android.angle.GLES_MODE"; private ClassLoader mClassLoader; private String mLayerPath; @@ -123,7 +124,6 @@ public class GraphicsEnvironment { } } } - } // Include the app's lib directory in all cases @@ -133,7 +133,7 @@ public class GraphicsEnvironment { } /** - * Selectively enable ANGLE for applications + * Pass ANGLE details down to trigger enable logic */ private static void setupAngle(Context context) { @@ -143,39 +143,67 @@ public class GraphicsEnvironment { String packageName = context.getPackageName(); - // Only provide an ANGLE namespace if the package name matches setting + boolean devOptIn = false; if ((angleEnabledApp != null && packageName != null) && (!angleEnabledApp.isEmpty() && !packageName.isEmpty()) && angleEnabledApp.equals(packageName)) { - if (DEBUG) Log.v(TAG, "ANGLE enabled for " + packageName); + if (DEBUG) Log.v(TAG, packageName + " opted in for ANGLE via Developer Setting"); - ApplicationInfo angleInfo; + devOptIn = true; + } - try { - angleInfo = context.getPackageManager().getApplicationInfo(ANGLE_PACKAGE_NAME, - PackageManager.MATCH_SYSTEM_ONLY); - } catch (PackageManager.NameNotFoundException e) { - Log.w(TAG, "ANGLE package '" + ANGLE_PACKAGE_NAME + "' not installed"); - return; + ApplicationInfo appInfo; + try { + appInfo = context.getPackageManager().getApplicationInfo(packageName, + PackageManager.GET_META_DATA); + } catch (PackageManager.NameNotFoundException e) { + Log.w(TAG, "Failed to get info about current application: " + packageName); + return; + } + + String appPref = "dontcare"; + final BaseBundle metadata = appInfo.metaData; + if (metadata != null) { + final String glesMode = metadata.getString(GLES_MODE_METADATA_KEY); + if (glesMode != null) { + if (glesMode.equals("angle")) { + appPref = "angle"; + if (DEBUG) Log.v(TAG, packageName + " opted for ANGLE via AndroidManifest"); + } else if (glesMode.equals("native")) { + appPref = "native"; + if (DEBUG) Log.v(TAG, packageName + " opted for NATIVE via AndroidManifest"); + } else { + Log.w(TAG, "Unrecognized GLES_MODE (\"" + glesMode + "\") for " + packageName + + ". Supported values are \"angle\" or \"native\""); + } } + } - String abi = chooseAbi(angleInfo); + ApplicationInfo angleInfo; + try { + angleInfo = context.getPackageManager().getApplicationInfo(ANGLE_PACKAGE_NAME, + PackageManager.MATCH_SYSTEM_ONLY); + } catch (PackageManager.NameNotFoundException e) { + Log.w(TAG, "ANGLE package '" + ANGLE_PACKAGE_NAME + "' not installed"); + return; + } - // Build a path that includes installed native libs and APK - StringBuilder sb = new StringBuilder(); - sb.append(angleInfo.nativeLibraryDir) - .append(File.pathSeparator) - .append(angleInfo.sourceDir) - .append("!/lib/") - .append(abi); - String paths = sb.toString(); + String abi = chooseAbi(angleInfo); - if (DEBUG) Log.v(TAG, "ANGLE package libs: " + paths); + // Build a path that includes installed native libs and APK + StringBuilder sb = new StringBuilder(); + sb.append(angleInfo.nativeLibraryDir) + .append(File.pathSeparator) + .append(angleInfo.sourceDir) + .append("!/lib/") + .append(abi); + String paths = sb.toString(); - // Providing any path will trigger namespace creation - setAnglePath(paths); - } + if (DEBUG) Log.v(TAG, "ANGLE package libs: " + paths); + + // Further opt-in logic is handled in native, so pass relevant info down + setAngleInfo(paths, packageName, appPref, devOptIn); } /** @@ -266,5 +294,6 @@ public class GraphicsEnvironment { private static native void setLayerPaths(ClassLoader classLoader, String layerPaths); private static native void setDebugLayers(String layers); private static native void setDriverPath(String path); - private static native void setAnglePath(String path); + private static native void setAngleInfo(String path, String appPackage, String appPref, + boolean devOptIn); } diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java index 89a5defd221d..8ea061e4a8d0 100644 --- a/core/java/android/os/PowerManager.java +++ b/core/java/android/os/PowerManager.java @@ -1154,10 +1154,15 @@ public final class PowerManager { * * @return True if the set was allowed. * - * @see #isPowerSaveMode() - * * @hide + * @see #isPowerSaveMode() */ + @SystemApi + @TestApi + @RequiresPermission(anyOf = { + android.Manifest.permission.DEVICE_POWER, + android.Manifest.permission.POWER_SAVER + }) public boolean setPowerSaveMode(boolean mode) { try { return mService.setPowerSaveMode(mode); diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java index 6fab3c412ae5..0f64c4531bc3 100644 --- a/core/java/android/os/Process.java +++ b/core/java/android/os/Process.java @@ -483,6 +483,8 @@ public class Process { * @param appDataDir null-ok the data directory of the app. * @param invokeWith null-ok the command to invoke with. * @param packageName null-ok the name of the package this process belongs to. + * @param packagesForUid null-ok all the packages with the same uid as this process. + * @param visibleVols null-ok storage volumes that can be accessed by this process. * @param zygoteArgs Additional arguments to supply to the zygote process. * * @return An object that describes the result of the attempt to start the process. @@ -501,10 +503,13 @@ public class Process { @Nullable String appDataDir, @Nullable String invokeWith, @Nullable String packageName, + @Nullable String[] packagesForUid, + @Nullable String[] visibleVols, @Nullable String[] zygoteArgs) { return zygoteProcess.start(processClass, niceName, uid, gid, gids, runtimeFlags, mountExternal, targetSdkVersion, seInfo, - abi, instructionSet, appDataDir, invokeWith, packageName, zygoteArgs); + abi, instructionSet, appDataDir, invokeWith, packageName, + packagesForUid, visibleVols, zygoteArgs); } /** @hide */ @@ -519,10 +524,13 @@ public class Process { @Nullable String appDataDir, @Nullable String invokeWith, @Nullable String packageName, + @Nullable String[] packagesForUid, + @Nullable String[] visibleVols, @Nullable String[] zygoteArgs) { return WebViewZygote.getProcess().start(processClass, niceName, uid, gid, gids, runtimeFlags, mountExternal, targetSdkVersion, seInfo, - abi, instructionSet, appDataDir, invokeWith, packageName, zygoteArgs); + abi, instructionSet, appDataDir, invokeWith, packageName, + packagesForUid, visibleVols, zygoteArgs); } /** diff --git a/core/java/android/os/StatsLogEventWrapper.java b/core/java/android/os/StatsLogEventWrapper.java index 051ab75c857c..72e1ab972846 100644 --- a/core/java/android/os/StatsLogEventWrapper.java +++ b/core/java/android/os/StatsLogEventWrapper.java @@ -70,11 +70,17 @@ public final class StatsLogEventWrapper implements Parcelable { } }; + /** + * Write a int value. + */ public void writeInt(int val) { mTypes.add(EVENT_TYPE_INT); mValues.add(val); } + /** + * Write a long value. + */ public void writeLong(long val) { mTypes.add(EVENT_TYPE_LONG); mValues.add(val); @@ -89,12 +95,23 @@ public final class StatsLogEventWrapper implements Parcelable { mValues.add(val == null ? "" : val); } + /** + * Write a float value. + */ public void writeFloat(float val) { mTypes.add(EVENT_TYPE_FLOAT); mValues.add(val); } /** + * Write a double value. + */ + public void writeDouble(double val) { + mTypes.add(EVENT_TYPE_DOUBLE); + mValues.add(val); + } + + /** * Write a storage value. */ public void writeStorage(byte[] val) { @@ -102,6 +119,9 @@ public final class StatsLogEventWrapper implements Parcelable { mValues.add(val); } + /** + * Write a boolean value. + */ public void writeBoolean(boolean val) { mTypes.add(EVENT_TYPE_INT); mValues.add(val ? 1 : 0); @@ -134,6 +154,9 @@ public final class StatsLogEventWrapper implements Parcelable { case EVENT_TYPE_FLOAT: out.writeFloat((float) mValues.get(i)); break; + case EVENT_TYPE_DOUBLE: + out.writeDouble((double) mValues.get(i)); + break; case EVENT_TYPE_STRING: out.writeString((String) mValues.get(i)); break; diff --git a/core/java/android/os/SystemProperties.java b/core/java/android/os/SystemProperties.java index fb34a524f625..bdc776dd3702 100644 --- a/core/java/android/os/SystemProperties.java +++ b/core/java/android/os/SystemProperties.java @@ -208,13 +208,18 @@ public class SystemProperties { return; } ArrayList<Runnable> callbacks = new ArrayList<Runnable>(sChangeCallbacks); - for (int i=0; i<callbacks.size(); i++) { - try { - callbacks.get(i).run(); - } catch (Throwable t) { - Log.wtf(TAG, "Exception in SystemProperties change callback", t); - // Ignore and try to go on. + final long token = Binder.clearCallingIdentity(); + try { + for (int i = 0; i < callbacks.size(); i++) { + try { + callbacks.get(i).run(); + } catch (Throwable t) { + Log.wtf(TAG, "Exception in SystemProperties change callback", t); + // Ignore and try to go on. + } } + } finally { + Binder.restoreCallingIdentity(token); } } } diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index b0891050634c..128217001b17 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -256,6 +256,7 @@ public class UserManager { /** * Specifies if a user is disallowed from enabling the * "Unknown Sources" setting, that allows installation of apps from unknown sources. + * Unknown sources exclude adb and special apps such as trusted app stores. * The default value is <code>false</code>. * * <p>Key for user restrictions. @@ -267,6 +268,22 @@ public class UserManager { public static final String DISALLOW_INSTALL_UNKNOWN_SOURCES = "no_install_unknown_sources"; /** + * This restriction is a device-wide version of {@link DISALLOW_INSTALL_UNKNOWN_SOURCES}. + * + * Specifies if all users on the device are disallowed from enabling the + * "Unknown Sources" setting, that allows installation of apps from unknown sources. + * The default value is <code>false</code>. + * + * <p>Key for user restrictions. + * <p>Type: Boolean + * @see DevicePolicyManager#addUserRestriction(ComponentName, String) + * @see DevicePolicyManager#clearUserRestriction(ComponentName, String) + * @see #getUserRestrictions() + */ + public static final String DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY = + "no_install_unknown_sources_globally"; + + /** * Specifies if a user is disallowed from configuring bluetooth. * This does <em>not</em> restrict the user from turning bluetooth on or off. * The default value is <code>false</code>. @@ -1669,8 +1686,9 @@ public class UserManager { /** * @hide * Returns whether the given user has been disallowed from performing certain actions - * or setting certain settings through UserManager. This method disregards restrictions - * set by device policy. + * or setting certain settings through UserManager (e.g. this type of restriction would prevent + * the guest user from doing certain things, such as making calls). This method disregards + * restrictions set by device policy. * @param restrictionKey the string key representing the restriction * @param userHandle the UserHandle of the user for whom to retrieve the restrictions. */ diff --git a/core/java/android/os/ZygoteProcess.java b/core/java/android/os/ZygoteProcess.java index 99181acb03c7..7fd0a4b66d66 100644 --- a/core/java/android/os/ZygoteProcess.java +++ b/core/java/android/os/ZygoteProcess.java @@ -215,6 +215,8 @@ public class ZygoteProcess { * @param appDataDir null-ok the data directory of the app. * @param invokeWith null-ok the command to invoke with. * @param packageName null-ok the name of the package this process belongs to. + * @param packagesForUid null-ok all the packages with the same uid as this process. + * @param visibleVols null-ok storage volumes that can be accessed by this process. * @param zygoteArgs Additional arguments to supply to the zygote process. * * @return An object that describes the result of the attempt to start the process. @@ -231,12 +233,14 @@ public class ZygoteProcess { @Nullable String appDataDir, @Nullable String invokeWith, @Nullable String packageName, + @Nullable String[] packagesForUid, + @Nullable String[] visibleVols, @Nullable String[] zygoteArgs) { try { return startViaZygote(processClass, niceName, uid, gid, gids, runtimeFlags, mountExternal, targetSdkVersion, seInfo, abi, instructionSet, appDataDir, invokeWith, false /* startChildZygote */, - packageName, zygoteArgs); + packageName, packagesForUid, visibleVols, zygoteArgs); } catch (ZygoteStartFailedEx ex) { Log.e(LOG_TAG, "Starting VM process through Zygote failed"); @@ -355,6 +359,8 @@ public class ZygoteProcess { * @param startChildZygote Start a sub-zygote. This creates a new zygote process * that has its state cloned from this zygote process. * @param packageName null-ok the name of the package this process belongs to. + * @param packagesForUid null-ok all the packages with the same uid as this process. + * @param visibleVols null-ok storage volumes that can be accessed by this process. * @param extraArgs Additional arguments to supply to the zygote process. * @return An object that describes the result of the attempt to start the process. * @throws ZygoteStartFailedEx if process start failed for any reason @@ -372,6 +378,8 @@ public class ZygoteProcess { @Nullable String invokeWith, boolean startChildZygote, @Nullable String packageName, + @Nullable String[] packagesForUid, + @Nullable String[] visibleVols, @Nullable String[] extraArgs) throws ZygoteStartFailedEx { ArrayList<String> argsForZygote = new ArrayList<String>(); @@ -439,6 +447,32 @@ public class ZygoteProcess { argsForZygote.add("--package-name=" + packageName); } + if (packagesForUid != null && packagesForUid.length > 0) { + final StringBuilder sb = new StringBuilder(); + sb.append("--packages-for-uid="); + + for (int i = 0; i < packagesForUid.length; ++i) { + if (i != 0) { + sb.append(','); + } + sb.append(packagesForUid[i]); + } + argsForZygote.add(sb.toString()); + } + + if (visibleVols != null && visibleVols.length > 0) { + final StringBuilder sb = new StringBuilder(); + sb.append("--visible-vols="); + + for (int i = 0; i < visibleVols.length; ++i) { + if (i != 0) { + sb.append(','); + } + sb.append(visibleVols[i]); + } + argsForZygote.add(sb.toString()); + } + argsForZygote.add(processClass); if (extraArgs != null) { @@ -746,7 +780,8 @@ public class ZygoteProcess { result = startViaZygote(processClass, niceName, uid, gid, gids, runtimeFlags, 0 /* mountExternal */, 0 /* targetSdkVersion */, seInfo, abi, instructionSet, null /* appDataDir */, null /* invokeWith */, - true /* startChildZygote */, null /* packageName */, extraArgs); + true /* startChildZygote */, null /* packageName */, + null /* packagesForUid */, null /* visibleVolumes */, extraArgs); } catch (ZygoteStartFailedEx ex) { throw new RuntimeException("Starting child-zygote through Zygote failed", ex); } diff --git a/core/java/android/os/storage/StorageManagerInternal.java b/core/java/android/os/storage/StorageManagerInternal.java index d850e27e913f..1f54ea53facc 100644 --- a/core/java/android/os/storage/StorageManagerInternal.java +++ b/core/java/android/os/storage/StorageManagerInternal.java @@ -89,8 +89,13 @@ public abstract class StorageManagerInternal { * @param appId The appId for the given package. * @param sharedUserId The sharedUserId for given package if it specified * {@code android:sharedUserId} in the manifest, otherwise {@code null} - * @param userId + * @param userId The userId in which the storage needs to be mounted. */ public abstract void mountExternalStorageForApp(String packageName, int appId, String sharedUserId, int userId); + + /** + * @return Labels of storage volumes that are visible to the given userId. + */ + public abstract String[] getVisibleVolumesForUser(int userId); } diff --git a/core/java/android/os/storage/VolumeInfo.java b/core/java/android/os/storage/VolumeInfo.java index afd383691300..e55afb69bab9 100644 --- a/core/java/android/os/storage/VolumeInfo.java +++ b/core/java/android/os/storage/VolumeInfo.java @@ -157,7 +157,7 @@ public class VolumeInfo implements Parcelable { public final DiskInfo disk; public final String partGuid; public int mountFlags = 0; - public int mountUserId = -1; + public int mountUserId = UserHandle.USER_NULL; @UnsupportedAppUsage public int state = STATE_UNMOUNTED; public String fsType; diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java index f126c49227fb..112329e48703 100644 --- a/core/java/android/provider/ContactsContract.java +++ b/core/java/android/provider/ContactsContract.java @@ -2783,48 +2783,7 @@ public final class ContactsContract { * The content:// style URI for this table, which requests a directory of * raw contact rows matching the selection criteria. */ - public static final Uri CONTENT_URI = - Uri.withAppendedPath(AUTHORITY_URI, "raw_contacts"); - - /** - * The URI to register for all raw contacts change notification. - * - * @hide - */ - @SystemApi - @TestApi - public static final Uri RAW_CONTACTS_NOTIFICATION_URI = - Uri.parse("content://com.android.contacts.raw_contacts"); - - /** - * The URI to register for raw contacts insert notification. - * - * @hide - */ - @SystemApi - @TestApi - public static final Uri RAW_CONTACTS_NOTIFICATION_INSERT_URI = - Uri.withAppendedPath(RAW_CONTACTS_NOTIFICATION_URI, "insert"); - - /** - * The URI to register for raw contacts update notification. - * - * @hide - */ - @SystemApi - @TestApi - public static final Uri RAW_CONTACTS_NOTIFICATION_UPDATE_URI = - Uri.withAppendedPath(RAW_CONTACTS_NOTIFICATION_URI, "update"); - - /** - * The URI to register for raw contacts delete notification. - * - * @hide - */ - @SystemApi - @TestApi - public static final Uri RAW_CONTACTS_NOTIFICATION_DELETE_URI = - Uri.withAppendedPath(RAW_CONTACTS_NOTIFICATION_URI, "delete"); + public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "raw_contacts"); /** * The MIME type of the results from {@link #CONTENT_URI} when a specific diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java index ee64ca2b8673..8c40e0e6cb8c 100644 --- a/core/java/android/provider/DocumentsContract.java +++ b/core/java/android/provider/DocumentsContract.java @@ -942,13 +942,26 @@ public final class DocumentsContract { return false; } - /** {@hide} */ + /** + * Test if the given URI represents roots backed by {@link DocumentsProvider}. + * + * @see #buildRootsUri(String) + * + * {@hide} + */ + public static boolean isRootsUri(Context context, @Nullable Uri uri) { + return isRootUri(context, uri, 1 /* pathSize */); + } + + /** + * Test if the given URI represents specific root backed by {@link DocumentsProvider}. + * + * @see #buildRootUri(String, String) + * + * {@hide} + */ public static boolean isRootUri(Context context, @Nullable Uri uri) { - if (isContentUri(uri) && isDocumentsProvider(context, uri.getAuthority())) { - final List<String> paths = uri.getPathSegments(); - return (paths.size() == 2 && PATH_ROOT.equals(paths.get(0))); - } - return false; + return isRootUri(context, uri, 2 /* pathSize */); } /** {@hide} */ @@ -967,6 +980,14 @@ public final class DocumentsContract { return (paths.size() >= 2 && PATH_TREE.equals(paths.get(0))); } + private static boolean isRootUri(Context context, @Nullable Uri uri, int pathSize) { + if (isContentUri(uri) && isDocumentsProvider(context, uri.getAuthority())) { + final List<String> paths = uri.getPathSegments(); + return (paths.size() == pathSize && PATH_ROOT.equals(paths.get(0))); + } + return false; + } + private static boolean isDocumentsProvider(Context context, String authority) { final Intent intent = new Intent(PROVIDER_INTERFACE); final List<ResolveInfo> infos = context.getPackageManager() diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java index 82459b13a4eb..828fd7386d80 100644 --- a/core/java/android/provider/MediaStore.java +++ b/core/java/android/provider/MediaStore.java @@ -1318,18 +1318,6 @@ public final class MediaStore { } public static final class Media implements AudioColumns { - - private static final String[] EXTERNAL_PATHS; - - static { - String secondary_storage = System.getenv("SECONDARY_STORAGE"); - if (secondary_storage != null) { - EXTERNAL_PATHS = secondary_storage.split(":"); - } else { - EXTERNAL_PATHS = new String[0]; - } - } - /** * Get the content:// style URI for the audio media table on the * given volume. @@ -1343,14 +1331,9 @@ public final class MediaStore { } public static Uri getContentUriForPath(String path) { - for (String ep : EXTERNAL_PATHS) { - if (path.startsWith(ep)) { - return EXTERNAL_CONTENT_URI; - } - } - - return (path.startsWith(Environment.getExternalStorageDirectory().getPath()) ? - EXTERNAL_CONTENT_URI : INTERNAL_CONTENT_URI); + return (path.startsWith( + Environment.getStorageDirectory().getAbsolutePath() + "/") + ? EXTERNAL_CONTENT_URI : INTERNAL_CONTENT_URI); } /** diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index ad8626ce736b..3f264b717b6c 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -497,6 +497,16 @@ public final class Settings { "android.settings.BLUETOOTH_SETTINGS"; /** + * Activity action: Show Settings app search UI when this action is available for device. + * <p> + * Input: Nothing. + * <p> + * Output: Nothing. + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_APP_SEARCH_SETTINGS = "android.settings.APP_SEARCH_SETTINGS"; + + /** * Activity Action: Show settings to allow configuration of Assist Gesture. * <p> * In some cases, a matching Activity may not exist, so ensure you @@ -7267,12 +7277,12 @@ public final class Settings { private static final Validator DOZE_DOUBLE_TAP_GESTURE_VALIDATOR = BOOLEAN_VALIDATOR; /** - * Whether the device should pulse on reach gesture. + * Gesture that wakes up the lock screen. * @hide */ - public static final String DOZE_REACH_GESTURE = "doze_reach_gesture"; + public static final String DOZE_WAKE_LOCK_SCREEN_GESTURE = "doze_wake_lock_screen_gesture"; - private static final Validator DOZE_REACH_GESTURE_VALIDATOR = BOOLEAN_VALIDATOR; + private static final Validator DOZE_WAKE_LOCK_SCREEN_GESTURE_VALIDATOR = BOOLEAN_VALIDATOR; /** * Gesture that wakes up the display, showing the ambient version of the status bar. @@ -8193,7 +8203,7 @@ public final class Settings { DOZE_ALWAYS_ON, DOZE_PICK_UP_GESTURE, DOZE_DOUBLE_TAP_GESTURE, - DOZE_REACH_GESTURE, + DOZE_WAKE_LOCK_SCREEN_GESTURE, DOZE_WAKE_SCREEN_GESTURE, NFC_PAYMENT_DEFAULT_COMPONENT, AUTOMATIC_STORAGE_MANAGER_DAYS_TO_RETAIN, @@ -8341,7 +8351,7 @@ public final class Settings { VALIDATORS.put(DOZE_ALWAYS_ON, DOZE_ALWAYS_ON_VALIDATOR); VALIDATORS.put(DOZE_PICK_UP_GESTURE, DOZE_PICK_UP_GESTURE_VALIDATOR); VALIDATORS.put(DOZE_DOUBLE_TAP_GESTURE, DOZE_DOUBLE_TAP_GESTURE_VALIDATOR); - VALIDATORS.put(DOZE_REACH_GESTURE, DOZE_REACH_GESTURE_VALIDATOR); + VALIDATORS.put(DOZE_WAKE_LOCK_SCREEN_GESTURE, DOZE_WAKE_LOCK_SCREEN_GESTURE_VALIDATOR); VALIDATORS.put(DOZE_WAKE_SCREEN_GESTURE, DOZE_WAKE_SCREEN_GESTURE_VALIDATOR); VALIDATORS.put(NFC_PAYMENT_DEFAULT_COMPONENT, NFC_PAYMENT_DEFAULT_COMPONENT_VALIDATOR); VALIDATORS.put(AUTOMATIC_STORAGE_MANAGER_DAYS_TO_RETAIN, @@ -9253,6 +9263,19 @@ public final class Settings { public static final String MOBILE_DATA_ALWAYS_ON = "mobile_data_always_on"; /** + * Whether the wifi data connection should remain active even when higher + * priority networks like Ethernet are active, to keep both networks. + * In the case where higher priority networks are connected, wifi will be + * unused unless an application explicitly requests to use it. + * + * See ConnectivityService for more info. + * + * (0 = disabled, 1 = enabled) + * @hide + */ + public static final String WIFI_ALWAYS_REQUESTED = "wifi_always_requested"; + + /** * Size of the event buffer for IP connectivity metrics. * @hide */ @@ -9604,8 +9627,7 @@ public final class Settings { * Use the old dnsmasq DHCP server for tethering instead of the framework implementation. * * Integer values are interpreted as boolean, and the absence of an explicit setting - * is interpreted as |true|. - * TODO: make the default |false| + * is interpreted as |false|. * @hide */ public static final String TETHER_ENABLE_LEGACY_DHCP_SERVER = @@ -10822,6 +10844,12 @@ public final class Settings { = "activity_starts_logging_enabled"; /** + * @hide + * @see com.android.server.appbinding.AppBindingConstants + */ + public static final String APP_BINDING_CONSTANTS = "app_binding_constants"; + + /** * App ops specific settings. * This is encoded as a key=value list, separated by commas. Ex: * diff --git a/core/java/android/transition/ChangeImageTransform.java b/core/java/android/transition/ChangeImageTransform.java index d7a912057858..9fa9961d21bc 100644 --- a/core/java/android/transition/ChangeImageTransform.java +++ b/core/java/android/transition/ChangeImageTransform.java @@ -97,22 +97,13 @@ public class ChangeImageTransform extends Transition { values.put(PROPNAME_BOUNDS, bounds); Matrix matrix; ImageView.ScaleType scaleType = imageView.getScaleType(); - if (scaleType == ImageView.ScaleType.FIT_XY) { - matrix = imageView.getImageMatrix(); - if (!matrix.isIdentity()) { - matrix = new Matrix(matrix); - } else { - int drawableWidth = drawable.getIntrinsicWidth(); - int drawableHeight = drawable.getIntrinsicHeight(); - if (drawableWidth > 0 && drawableHeight > 0) { - float scaleX = ((float) bounds.width()) / drawableWidth; - float scaleY = ((float) bounds.height()) / drawableHeight; - matrix = new Matrix(); - matrix.setScale(scaleX, scaleY); - } else { - matrix = null; - } - } + int drawableWidth = drawable.getIntrinsicWidth(); + int drawableHeight = drawable.getIntrinsicHeight(); + if (scaleType == ImageView.ScaleType.FIT_XY && drawableWidth > 0 && drawableHeight > 0) { + float scaleX = ((float) bounds.width()) / drawableWidth; + float scaleY = ((float) bounds.height()) / drawableHeight; + matrix = new Matrix(); + matrix.setScale(scaleX, scaleY); } else { matrix = new Matrix(imageView.getImageMatrix()); } @@ -152,17 +143,13 @@ public class ChangeImageTransform extends Transition { } Rect startBounds = (Rect) startValues.values.get(PROPNAME_BOUNDS); Rect endBounds = (Rect) endValues.values.get(PROPNAME_BOUNDS); - if (startBounds == null || endBounds == null) { - return null; - } - Matrix startMatrix = (Matrix) startValues.values.get(PROPNAME_MATRIX); Matrix endMatrix = (Matrix) endValues.values.get(PROPNAME_MATRIX); + if (startBounds == null || endBounds == null || startMatrix == null || endMatrix == null) { + return null; + } - boolean matricesEqual = (startMatrix == null && endMatrix == null) || - (startMatrix != null && startMatrix.equals(endMatrix)); - - if (startBounds.equals(endBounds) && matricesEqual) { + if (startBounds.equals(endBounds) && startMatrix.equals(endMatrix)) { return null; } @@ -172,15 +159,9 @@ public class ChangeImageTransform extends Transition { int drawableHeight = drawable.getIntrinsicHeight(); ObjectAnimator animator; - if (drawableWidth == 0 || drawableHeight == 0) { + if (drawableWidth <= 0 || drawableHeight <= 0) { animator = createNullAnimator(imageView); } else { - if (startMatrix == null) { - startMatrix = Matrix.IDENTITY_MATRIX; - } - if (endMatrix == null) { - endMatrix = Matrix.IDENTITY_MATRIX; - } ANIMATED_TRANSFORM_PROPERTY.set(imageView, startMatrix); animator = createMatrixAnimator(imageView, startMatrix, endMatrix); } @@ -189,7 +170,7 @@ public class ChangeImageTransform extends Transition { private ObjectAnimator createNullAnimator(ImageView imageView) { return ObjectAnimator.ofObject(imageView, ANIMATED_TRANSFORM_PROPERTY, - NULL_MATRIX_EVALUATOR, null, null); + NULL_MATRIX_EVALUATOR, Matrix.IDENTITY_MATRIX, Matrix.IDENTITY_MATRIX); } private ObjectAnimator createMatrixAnimator(final ImageView imageView, Matrix startMatrix, diff --git a/core/java/android/transition/TransitionManager.java b/core/java/android/transition/TransitionManager.java index c4ef77a3e7ae..4608e205ec7c 100644 --- a/core/java/android/transition/TransitionManager.java +++ b/core/java/android/transition/TransitionManager.java @@ -188,8 +188,12 @@ public class TransitionManager { final ViewGroup sceneRoot = scene.getSceneRoot(); if (!sPendingTransitions.contains(sceneRoot)) { + Scene oldScene = Scene.getCurrentScene(sceneRoot); if (transition == null) { - exitPreviousScene(sceneRoot); + // Notify old scene that it is being exited + if (oldScene != null) { + oldScene.exit(); + } scene.enter(); } else { @@ -198,7 +202,6 @@ public class TransitionManager { Transition transitionClone = transition.clone(); transitionClone.setSceneRoot(sceneRoot); - Scene oldScene = Scene.getCurrentScene(sceneRoot); if (oldScene != null && oldScene.isCreatedFromLayoutResource()) { transitionClone.setCanRemoveViews(true); } @@ -212,14 +215,6 @@ public class TransitionManager { } } - private static void exitPreviousScene(final ViewGroup sceneRoot) { - // Notify previous scene that it is being exited - final Scene previousScene = Scene.getCurrentScene(sceneRoot); - if (previousScene != null) { - previousScene.exit(); - } - } - @UnsupportedAppUsage private static ArrayMap<ViewGroup, ArrayList<Transition>> getRunningTransitions() { WeakReference<ArrayMap<ViewGroup, ArrayList<Transition>>> runningTransitions = @@ -349,7 +344,11 @@ public class TransitionManager { transition.captureValues(sceneRoot, true); } - exitPreviousScene(sceneRoot); + // Notify previous scene that it is being exited + Scene previousScene = Scene.getCurrentScene(sceneRoot); + if (previousScene != null) { + previousScene.exit(); + } } /** diff --git a/core/java/android/util/ArrayMap.java b/core/java/android/util/ArrayMap.java index 5108a796a036..294a1799a2d3 100644 --- a/core/java/android/util/ArrayMap.java +++ b/core/java/android/util/ArrayMap.java @@ -71,19 +71,19 @@ public final class ArrayMap<K, V> implements Map<K, V> { /** * Maximum number of entries to have in array caches. */ - @UnsupportedAppUsage + @UnsupportedAppUsage(maxTargetSdk = 28) // Allocations are an implementation detail. private static final int CACHE_SIZE = 10; /** * Special hash array value that indicates the container is immutable. */ - @UnsupportedAppUsage + @UnsupportedAppUsage(maxTargetSdk = 28) // Allocations are an implementation detail. static final int[] EMPTY_IMMUTABLE_INTS = new int[0]; /** * @hide Special immutable empty ArrayMap. */ - @UnsupportedAppUsage + @UnsupportedAppUsage(maxTargetSdk = 28) // Use your own singleton empty map. public static final ArrayMap EMPTY = new ArrayMap<>(-1); /** @@ -92,21 +92,21 @@ public final class ArrayMap<K, V> implements Map<K, V> { * The first entry in the array is a pointer to the next array in the * list; the second entry is a pointer to the int[] hash code array for it. */ - @UnsupportedAppUsage + @UnsupportedAppUsage(maxTargetSdk = 28) // Allocations are an implementation detail. static Object[] mBaseCache; - @UnsupportedAppUsage + @UnsupportedAppUsage(maxTargetSdk = 28) // Allocations are an implementation detail. static int mBaseCacheSize; - @UnsupportedAppUsage + @UnsupportedAppUsage(maxTargetSdk = 28) // Allocations are an implementation detail. static Object[] mTwiceBaseCache; - @UnsupportedAppUsage + @UnsupportedAppUsage(maxTargetSdk = 28) // Allocations are an implementation detail. static int mTwiceBaseCacheSize; final boolean mIdentityHashCode; - @UnsupportedAppUsage + @UnsupportedAppUsage(maxTargetSdk = 28) // Hashes are an implementation detail. Use public key/value API. int[] mHashes; - @UnsupportedAppUsage + @UnsupportedAppUsage(maxTargetSdk = 28) // Storage is an implementation detail. Use public key/value API. Object[] mArray; - @UnsupportedAppUsage + @UnsupportedAppUsage(maxTargetSdk = 28) // Use size() int mSize; MapCollections<K, V> mCollections; @@ -122,7 +122,7 @@ public final class ArrayMap<K, V> implements Map<K, V> { } } - @UnsupportedAppUsage + @UnsupportedAppUsage(maxTargetSdk = 28) // Hashes are an implementation detail. Use indexOfKey(Object). int indexOf(Object key, int hash) { final int N = mSize; @@ -161,7 +161,7 @@ public final class ArrayMap<K, V> implements Map<K, V> { return ~end; } - @UnsupportedAppUsage + @UnsupportedAppUsage(maxTargetSdk = 28) // Use indexOf(null) int indexOfNull() { final int N = mSize; @@ -200,7 +200,7 @@ public final class ArrayMap<K, V> implements Map<K, V> { return ~end; } - @UnsupportedAppUsage + @UnsupportedAppUsage(maxTargetSdk = 28) // Allocations are an implementation detail. private void allocArrays(final int size) { if (mHashes == EMPTY_IMMUTABLE_INTS) { throw new UnsupportedOperationException("ArrayMap is immutable"); @@ -239,7 +239,7 @@ public final class ArrayMap<K, V> implements Map<K, V> { mArray = new Object[size<<1]; } - @UnsupportedAppUsage + @UnsupportedAppUsage(maxTargetSdk = 28) // Allocations are an implementation detail. private static void freeArrays(final int[] hashes, final Object[] array, final int size) { if (hashes.length == (BASE_SIZE*2)) { synchronized (ArrayMap.class) { @@ -393,8 +393,15 @@ public final class ArrayMap<K, V> implements Map<K, V> { : indexOf(key, mIdentityHashCode ? System.identityHashCode(key) : key.hashCode()); } - @UnsupportedAppUsage - int indexOfValue(Object value) { + /** + * Returns an index for which {@link #valueAt} would return the + * specified value, or a negative number if no keys map to the + * specified value. + * Beware that this is a linear search, unlike lookups by key, + * and that multiple keys can map to the same value and this will + * find only one of them. + */ + public int indexOfValue(Object value) { final int N = mSize*2; final Object[] array = mArray; if (value == null) { @@ -551,7 +558,7 @@ public final class ArrayMap<K, V> implements Map<K, V> { * The array must already be large enough to contain the item. * @hide */ - @UnsupportedAppUsage + @UnsupportedAppUsage(maxTargetSdk = 28) // Storage is an implementation detail. Use put(K, V). public void append(K key, V value) { int index = mSize; final int hash = key == null ? 0 diff --git a/core/java/android/util/ArraySet.java b/core/java/android/util/ArraySet.java index 526a950b4820..d74a0fe8d2c1 100644 --- a/core/java/android/util/ArraySet.java +++ b/core/java/android/util/ArraySet.java @@ -71,15 +71,15 @@ public final class ArraySet<E> implements Collection<E>, Set<E> { static int sTwiceBaseCacheSize; final boolean mIdentityHashCode; - @UnsupportedAppUsage + @UnsupportedAppUsage(maxTargetSdk = 28) // Hashes are an implementation detail. Use public API. int[] mHashes; - @UnsupportedAppUsage + @UnsupportedAppUsage(maxTargetSdk = 28) // Storage is an implementation detail. Use public API. Object[] mArray; - @UnsupportedAppUsage + @UnsupportedAppUsage(maxTargetSdk = 28) // Use size() int mSize; MapCollections<E, E> mCollections; - @UnsupportedAppUsage + @UnsupportedAppUsage(maxTargetSdk = 28) // Hashes are an implementation detail. Use indexOfKey(Object). private int indexOf(Object key, int hash) { final int N = mSize; @@ -118,7 +118,7 @@ public final class ArraySet<E> implements Collection<E>, Set<E> { return ~end; } - @UnsupportedAppUsage + @UnsupportedAppUsage(maxTargetSdk = 28) // Use indexOf(null) private int indexOfNull() { final int N = mSize; @@ -157,7 +157,7 @@ public final class ArraySet<E> implements Collection<E>, Set<E> { return ~end; } - @UnsupportedAppUsage + @UnsupportedAppUsage(maxTargetSdk = 28) // Allocations are an implementation detail. private void allocArrays(final int size) { if (size == (BASE_SIZE * 2)) { synchronized (ArraySet.class) { @@ -215,7 +215,7 @@ public final class ArraySet<E> implements Collection<E>, Set<E> { mArray = new Object[size]; } - @UnsupportedAppUsage + @UnsupportedAppUsage(maxTargetSdk = 28) // Allocations are an implementation detail. private static void freeArrays(final int[] hashes, final Object[] array, final int size) { if (hashes.length == (BASE_SIZE * 2)) { synchronized (ArraySet.class) { @@ -289,9 +289,10 @@ public final class ArraySet<E> implements Collection<E>, Set<E> { } } - /** {@hide} */ - @UnsupportedAppUsage - public ArraySet(Collection<E> set) { + /** + * Create a new ArraySet with items from the given collection. + */ + public ArraySet(Collection<? extends E> set) { this(); if (set != null) { addAll(set); diff --git a/core/java/android/util/JsonReader.java b/core/java/android/util/JsonReader.java index 7d1c6c4918db..50f63f8db304 100644 --- a/core/java/android/util/JsonReader.java +++ b/core/java/android/util/JsonReader.java @@ -16,13 +16,15 @@ package android.util; +import libcore.internal.StringPool; + import java.io.Closeable; import java.io.EOFException; import java.io.IOException; import java.io.Reader; import java.util.ArrayList; import java.util.List; -import libcore.internal.StringPool; + /** * Reads a JSON (<a href="http://www.ietf.org/rfc/rfc4627.txt">RFC 4627</a>) @@ -295,7 +297,7 @@ public final class JsonReader implements Closeable { /** * Consumes the next token from the JSON stream and asserts that it is the - * end of the current array. + * end of the current object. */ public void endObject() throws IOException { expect(JsonToken.END_OBJECT); diff --git a/core/java/android/util/LongSparseLongArray.java b/core/java/android/util/LongSparseLongArray.java index d5af92234b31..af163ac8f246 100644 --- a/core/java/android/util/LongSparseLongArray.java +++ b/core/java/android/util/LongSparseLongArray.java @@ -46,11 +46,11 @@ import libcore.util.EmptyArray; * @hide */ public class LongSparseLongArray implements Cloneable { - @UnsupportedAppUsage + @UnsupportedAppUsage(maxTargetSdk = 28) // The type isn't even public. private long[] mKeys; - @UnsupportedAppUsage + @UnsupportedAppUsage(maxTargetSdk = 28) // The type isn't even public. private long[] mValues; - @UnsupportedAppUsage + @UnsupportedAppUsage(maxTargetSdk = 28) // The type isn't even public. private int mSize; /** diff --git a/core/java/android/util/SparseArray.java b/core/java/android/util/SparseArray.java index aa5ca3530621..89ea2d35fc2f 100644 --- a/core/java/android/util/SparseArray.java +++ b/core/java/android/util/SparseArray.java @@ -56,11 +56,11 @@ public class SparseArray<E> implements Cloneable { private static final Object DELETED = new Object(); private boolean mGarbage = false; - @UnsupportedAppUsage + @UnsupportedAppUsage(maxTargetSdk = 28) // Use keyAt(int) private int[] mKeys; - @UnsupportedAppUsage + @UnsupportedAppUsage(maxTargetSdk = 28) // Use valueAt(int), setValueAt(int, E) private Object[] mValues; - @UnsupportedAppUsage + @UnsupportedAppUsage(maxTargetSdk = 28) // Use size() private int mSize; /** diff --git a/core/java/android/util/SparseBooleanArray.java b/core/java/android/util/SparseBooleanArray.java index 9c6b9698d1ae..d4c40954bdd1 100644 --- a/core/java/android/util/SparseBooleanArray.java +++ b/core/java/android/util/SparseBooleanArray.java @@ -185,7 +185,9 @@ public class SparseBooleanArray implements Cloneable { return mValues[index]; } - /** @hide */ + /** + * Directly set the value at a particular index. + */ public void setValueAt(int index, boolean value) { mValues[index] = value; } @@ -304,10 +306,10 @@ public class SparseBooleanArray implements Cloneable { return buffer.toString(); } - @UnsupportedAppUsage + @UnsupportedAppUsage(maxTargetSdk = 28) // Use keyAt(int) private int[] mKeys; - @UnsupportedAppUsage + @UnsupportedAppUsage(maxTargetSdk = 28) // Use valueAt(int), setValueAt(int, boolean) private boolean[] mValues; - @UnsupportedAppUsage + @UnsupportedAppUsage(maxTargetSdk = 28) // Use size() private int mSize; } diff --git a/core/java/android/util/SparseIntArray.java b/core/java/android/util/SparseIntArray.java index 19547534aef5..9e6bad1d9ae0 100644 --- a/core/java/android/util/SparseIntArray.java +++ b/core/java/android/util/SparseIntArray.java @@ -46,11 +46,11 @@ import libcore.util.EmptyArray; * order in the case of <code>valueAt(int)</code>.</p> */ public class SparseIntArray implements Cloneable { - @UnsupportedAppUsage + @UnsupportedAppUsage(maxTargetSdk = 28) // Use keyAt(int) private int[] mKeys; - @UnsupportedAppUsage + @UnsupportedAppUsage(maxTargetSdk = 28) // Use valueAt(int), setValueAt(int, int) private int[] mValues; - @UnsupportedAppUsage + @UnsupportedAppUsage(maxTargetSdk = 28) // Use size() private int mSize; /** @@ -191,7 +191,6 @@ public class SparseIntArray implements Cloneable { /** * Directly set the value at a particular index. - * @hide */ public void setValueAt(int index, int value) { mValues[index] = value; diff --git a/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java b/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java index 1203541756e8..1bbef8e9cfff 100644 --- a/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java +++ b/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java @@ -410,7 +410,7 @@ public class ApkSignatureSchemeV2Verifier { NoSuchAlgorithmException { try (RandomAccessFile apk = new RandomAccessFile(apkPath, "r")) { SignatureInfo signatureInfo = findSignature(apk); - return ApkVerityBuilder.generateApkVerity(apkPath, bufferFactory, signatureInfo); + return VerityBuilder.generateApkVerity(apkPath, bufferFactory, signatureInfo); } } @@ -423,7 +423,7 @@ public class ApkSignatureSchemeV2Verifier { if (vSigner.verityRootHash == null) { return null; } - return ApkVerityBuilder.generateApkVerityRootHash( + return VerityBuilder.generateApkVerityRootHash( apk, ByteBuffer.wrap(vSigner.verityRootHash), signatureInfo); } } diff --git a/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java b/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java index 939522dcd57f..1471870bd7d2 100644 --- a/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java +++ b/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java @@ -534,7 +534,7 @@ public class ApkSignatureSchemeV3Verifier { NoSuchAlgorithmException { try (RandomAccessFile apk = new RandomAccessFile(apkPath, "r")) { SignatureInfo signatureInfo = findSignature(apk); - return ApkVerityBuilder.generateApkVerity(apkPath, bufferFactory, signatureInfo); + return VerityBuilder.generateApkVerity(apkPath, bufferFactory, signatureInfo); } } @@ -547,7 +547,7 @@ public class ApkSignatureSchemeV3Verifier { if (vSigner.verityRootHash == null) { return null; } - return ApkVerityBuilder.generateApkVerityRootHash( + return VerityBuilder.generateApkVerityRootHash( apk, ByteBuffer.wrap(vSigner.verityRootHash), signatureInfo); } } diff --git a/core/java/android/util/apk/ApkSignatureVerifier.java b/core/java/android/util/apk/ApkSignatureVerifier.java index a299b11c2b25..ac4ea75b38e3 100644 --- a/core/java/android/util/apk/ApkSignatureVerifier.java +++ b/core/java/android/util/apk/ApkSignatureVerifier.java @@ -81,19 +81,17 @@ public class ApkSignatureVerifier { Certificate[][] signerCerts = new Certificate[][] { vSigner.certs }; Signature[] signerSigs = convertToSignatures(signerCerts); Signature[] pastSignerSigs = null; - int[] pastSignerSigsFlags = null; if (vSigner.por != null) { // populate proof-of-rotation information pastSignerSigs = new Signature[vSigner.por.certs.size()]; - pastSignerSigsFlags = new int[vSigner.por.flagsList.size()]; for (int i = 0; i < pastSignerSigs.length; i++) { pastSignerSigs[i] = new Signature(vSigner.por.certs.get(i).getEncoded()); - pastSignerSigsFlags[i] = vSigner.por.flagsList.get(i); + pastSignerSigs[i].setFlags(vSigner.por.flagsList.get(i)); } } return new PackageParser.SigningDetails( signerSigs, SignatureSchemeVersion.SIGNING_BLOCK_V3, - pastSignerSigs, pastSignerSigsFlags); + pastSignerSigs); } catch (SignatureNotFoundException e) { // not signed with v3, try older if allowed if (minSignatureSchemeVersion >= SignatureSchemeVersion.SIGNING_BLOCK_V3) { @@ -323,19 +321,17 @@ public class ApkSignatureVerifier { Certificate[][] signerCerts = new Certificate[][] { vSigner.certs }; Signature[] signerSigs = convertToSignatures(signerCerts); Signature[] pastSignerSigs = null; - int[] pastSignerSigsFlags = null; if (vSigner.por != null) { // populate proof-of-rotation information pastSignerSigs = new Signature[vSigner.por.certs.size()]; - pastSignerSigsFlags = new int[vSigner.por.flagsList.size()]; for (int i = 0; i < pastSignerSigs.length; i++) { pastSignerSigs[i] = new Signature(vSigner.por.certs.get(i).getEncoded()); - pastSignerSigsFlags[i] = vSigner.por.flagsList.get(i); + pastSignerSigs[i].setFlags(vSigner.por.flagsList.get(i)); } } return new PackageParser.SigningDetails( signerSigs, SignatureSchemeVersion.SIGNING_BLOCK_V3, - pastSignerSigs, pastSignerSigsFlags); + pastSignerSigs); } catch (SignatureNotFoundException e) { // not signed with v3, try older if allowed if (minSignatureSchemeVersion >= SignatureSchemeVersion.SIGNING_BLOCK_V3) { diff --git a/core/java/android/util/apk/ApkSigningBlockUtils.java b/core/java/android/util/apk/ApkSigningBlockUtils.java index 081033ae84e9..87af5364c945 100644 --- a/core/java/android/util/apk/ApkSigningBlockUtils.java +++ b/core/java/android/util/apk/ApkSigningBlockUtils.java @@ -332,7 +332,7 @@ final class ApkSigningBlockUtils { try { byte[] expectedRootHash = parseVerityDigestAndVerifySourceLength(expectedDigest, apk.length(), signatureInfo); - ApkVerityBuilder.ApkVerityResult verity = ApkVerityBuilder.generateApkVerityTree(apk, + VerityBuilder.VerityResult verity = VerityBuilder.generateApkVerityTree(apk, signatureInfo, new ByteBufferFactory() { @Override public ByteBuffer create(int capacity) { diff --git a/core/java/android/util/apk/ApkVerityBuilder.java b/core/java/android/util/apk/VerityBuilder.java index edd09f8f73c4..443bbd8597af 100644 --- a/core/java/android/util/apk/ApkVerityBuilder.java +++ b/core/java/android/util/apk/VerityBuilder.java @@ -29,19 +29,18 @@ import java.security.NoSuchAlgorithmException; import java.util.ArrayList; /** - * ApkVerityBuilder builds the APK verity tree and the verity header. The generated tree format can - * be stored on disk for apk-verity setup and used by kernel. Note that since the current - * implementation is different from the upstream, we call this implementation apk-verity instead of - * fs-verity. + * VerityBuilder builds the verity Merkle tree and other metadata. The generated tree format can + * be stored on disk for fs-verity setup and used by kernel. The builder support standard + * fs-verity, and Android specific apk-verity that requires additional kernel patches. * - * <p>Unlike a regular Merkle tree, APK verity tree does not cover the content fully. Due to - * the existing APK format, it has to skip APK Signing Block and also has some special treatment for - * the "Central Directory offset" field of ZIP End of Central Directory. + * <p>Unlike a regular Merkle tree of fs-verity, the apk-verity tree does not cover the file content + * fully, and has to skip APK Signing Block with some special treatment for the "Central Directory + * offset" field of ZIP End of Central Directory. * * @hide */ -public abstract class ApkVerityBuilder { - private ApkVerityBuilder() {} +public abstract class VerityBuilder { + private VerityBuilder() {} private static final int CHUNK_SIZE_BYTES = 4096; // Typical Linux block size private static final int DIGEST_SIZE_BYTES = 32; // SHA-256 size @@ -52,7 +51,7 @@ public abstract class ApkVerityBuilder { private static final byte[] DEFAULT_SALT = new byte[8]; /** Result generated by the builder. */ - public static class ApkVerityResult { + public static class VerityResult { /** Raw fs-verity metadata and Merkle tree ready to be deployed on disk. */ public final ByteBuffer verityData; @@ -62,7 +61,7 @@ public abstract class ApkVerityBuilder { /** Root hash of the Merkle tree. */ public final byte[] rootHash; - private ApkVerityResult(ByteBuffer verityData, int merkleTreeSize, byte[] rootHash) { + private VerityResult(ByteBuffer verityData, int merkleTreeSize, byte[] rootHash) { this.verityData = verityData; this.merkleTreeSize = merkleTreeSize; this.rootHash = rootHash; @@ -74,14 +73,14 @@ public abstract class ApkVerityBuilder { * ByteBuffer} created by the {@link ByteBufferFactory}. The output is suitable to be used as * the on-disk format for fs-verity to use. * - * @return ApkVerityResult containing a buffer with the generated Merkle tree stored at the + * @return VerityResult containing a buffer with the generated Merkle tree stored at the * front, the tree size, and the calculated root hash. */ @NonNull - public static ApkVerityResult generateFsVerityTree(@NonNull RandomAccessFile apk, + public static VerityResult generateFsVerityTree(@NonNull RandomAccessFile apk, @NonNull ByteBufferFactory bufferFactory) throws IOException, SecurityException, NoSuchAlgorithmException, DigestException { - return generateVerityTree(apk, bufferFactory, null /* signatureInfo */, + return generateVerityTreeInternal(apk, bufferFactory, null /* signatureInfo */, false /* skipSigningBlock */); } @@ -91,18 +90,19 @@ public abstract class ApkVerityBuilder { * Block specificed in {@code signatureInfo}. The output is suitable to be used as the on-disk * format for fs-verity to use (with elide and patch extensions). * - * @return ApkVerityResult containing a buffer with the generated Merkle tree stored at the + * @return VerityResult containing a buffer with the generated Merkle tree stored at the * front, the tree size, and the calculated root hash. */ @NonNull - public static ApkVerityResult generateApkVerityTree(@NonNull RandomAccessFile apk, + public static VerityResult generateApkVerityTree(@NonNull RandomAccessFile apk, @Nullable SignatureInfo signatureInfo, @NonNull ByteBufferFactory bufferFactory) throws IOException, SecurityException, NoSuchAlgorithmException, DigestException { - return generateVerityTree(apk, bufferFactory, signatureInfo, true /* skipSigningBlock */); + return generateVerityTreeInternal(apk, bufferFactory, signatureInfo, + true /* skipSigningBlock */); } @NonNull - private static ApkVerityResult generateVerityTree(@NonNull RandomAccessFile apk, + private static VerityResult generateVerityTreeInternal(@NonNull RandomAccessFile apk, @NonNull ByteBufferFactory bufferFactory, @Nullable SignatureInfo signatureInfo, boolean skipSigningBlock) throws IOException, SecurityException, NoSuchAlgorithmException, DigestException { @@ -124,7 +124,7 @@ public abstract class ApkVerityBuilder { byte[] salt = skipSigningBlock ? DEFAULT_SALT : null; byte[] apkRootHash = generateVerityTreeInternal(apk, signatureInfo, salt, levelOffset, tree, skipSigningBlock); - return new ApkVerityResult(output, merkleTreeSize, apkRootHash); + return new VerityResult(output, merkleTreeSize, apkRootHash); } static void generateApkVerityFooter(@NonNull RandomAccessFile apk, @@ -173,7 +173,7 @@ public abstract class ApkVerityBuilder { throws IOException, SignatureNotFoundException, SecurityException, DigestException, NoSuchAlgorithmException { try (RandomAccessFile apk = new RandomAccessFile(apkPath, "r")) { - ApkVerityResult result = generateVerityTree(apk, bufferFactory, signatureInfo, + VerityResult result = generateVerityTreeInternal(apk, bufferFactory, signatureInfo, true /* skipSigningBlock */); ByteBuffer footer = slice(result.verityData, result.merkleTreeSize, result.verityData.limit()); diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java index 4d96fc3175cd..719a401ce0cf 100644 --- a/core/java/android/view/Display.java +++ b/core/java/android/view/Display.java @@ -221,6 +221,18 @@ public final class Display { public static final int FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD = 1 << 5; /** + * Display flag: Indicates that the display should show system decorations. + * <p> + * This flag identifies secondary displays that should show system decorations, such as status + * bar, navigation bar, home activity or IME. + * </p> + * + * @see #supportsSystemDecorations + * @hide + */ + public static final int FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS = 1 << 6; + + /** * Display flag: Indicates that the contents of the display should not be scaled * to fit the physical screen dimensions. Used for development only to emulate * devices with smaller physicals screens while preserving density. @@ -874,6 +886,16 @@ public final class Display { } /** + * Returns whether this display should support showing system decorations. + * + * @see #FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS + * @hide + */ + public boolean supportsSystemDecorations() { + return (mDisplayInfo.flags & FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS) != 0; + } + + /** * Returns the display's HDR capabilities. * * @see #isHdr() diff --git a/core/java/android/view/DisplayCutout.java b/core/java/android/view/DisplayCutout.java index 5f80d31651a8..f428c798d7f5 100644 --- a/core/java/android/view/DisplayCutout.java +++ b/core/java/android/view/DisplayCutout.java @@ -18,12 +18,19 @@ package android.view; import static android.util.DisplayMetrics.DENSITY_DEFAULT; import static android.util.DisplayMetrics.DENSITY_DEVICE_STABLE; -import static android.view.DisplayCutoutProto.BOUNDS; +import static android.view.DisplayCutoutProto.BOUND_BOTTOM; +import static android.view.DisplayCutoutProto.BOUND_LEFT; +import static android.view.DisplayCutoutProto.BOUND_RIGHT; +import static android.view.DisplayCutoutProto.BOUND_TOP; import static android.view.DisplayCutoutProto.INSETS; import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE; +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.content.res.Resources; +import android.graphics.Insets; import android.graphics.Matrix; import android.graphics.Path; import android.graphics.Rect; @@ -42,7 +49,10 @@ import com.android.internal.R; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; /** @@ -68,14 +78,14 @@ public final class DisplayCutout { "com.android.internal.display_cutout_emulation"; private static final Rect ZERO_RECT = new Rect(); - private static final Region EMPTY_REGION = new Region(); /** * An instance where {@link #isEmpty()} returns {@code true}. * * @hide */ - public static final DisplayCutout NO_CUTOUT = new DisplayCutout(ZERO_RECT, EMPTY_REGION, + public static final DisplayCutout NO_CUTOUT = new DisplayCutout( + ZERO_RECT, ZERO_RECT, ZERO_RECT, ZERO_RECT, ZERO_RECT, false /* copyArguments */); @@ -94,7 +104,152 @@ public final class DisplayCutout { private static Pair<Path, DisplayCutout> sCachedCutout = NULL_PAIR; private final Rect mSafeInsets; - private final Region mBounds; + + + /** + * The bound is at the left of the screen. + * @hide + */ + public static final int BOUNDS_POSITION_LEFT = 0; + + /** + * The bound is at the top of the screen. + * @hide + */ + public static final int BOUNDS_POSITION_TOP = 1; + + /** + * The bound is at the right of the screen. + * @hide + */ + public static final int BOUNDS_POSITION_RIGHT = 2; + + /** + * The bound is at the bottom of the screen. + * @hide + */ + public static final int BOUNDS_POSITION_BOTTOM = 3; + + /** + * The number of possible positions at which bounds can be located. + * @hide + */ + public static final int BOUNDS_POSITION_LENGTH = 4; + + /** @hide */ + @IntDef(prefix = { "BOUNDS_POSITION_" }, value = { + BOUNDS_POSITION_LEFT, + BOUNDS_POSITION_TOP, + BOUNDS_POSITION_RIGHT, + BOUNDS_POSITION_BOTTOM + }) + @Retention(RetentionPolicy.SOURCE) + public @interface BoundsPosition {} + + private static class Bounds { + private final Rect[] mRects; + + private Bounds(Rect left, Rect top, Rect right, Rect bottom, boolean copyArguments) { + mRects = new Rect[BOUNDS_POSITION_LENGTH]; + mRects[BOUNDS_POSITION_LEFT] = getCopyOrRef(left, copyArguments); + mRects[BOUNDS_POSITION_TOP] = getCopyOrRef(top, copyArguments); + mRects[BOUNDS_POSITION_RIGHT] = getCopyOrRef(right, copyArguments); + mRects[BOUNDS_POSITION_BOTTOM] = getCopyOrRef(bottom, copyArguments); + + } + + private Bounds(Rect[] rects, boolean copyArguments) { + if (rects.length != BOUNDS_POSITION_LENGTH) { + throw new IllegalArgumentException( + "rects must have exactly 4 elements: rects=" + Arrays.toString(rects)); + } + if (copyArguments) { + mRects = new Rect[BOUNDS_POSITION_LENGTH]; + for (int i = 0; i < BOUNDS_POSITION_LENGTH; ++i) { + mRects[i] = new Rect(rects[i]); + } + } else { + for (Rect rect : rects) { + if (rect == null) { + throw new IllegalArgumentException( + "rects must have non-null elements: rects=" + + Arrays.toString(rects)); + } + } + mRects = rects; + } + } + + private boolean isEmpty() { + for (Rect rect : mRects) { + if (!rect.isEmpty()) { + return false; + } + } + return true; + } + + private Rect getRect(@BoundsPosition int pos) { + return new Rect(mRects[pos]); + } + + private Rect[] getRects() { + Rect[] rects = new Rect[BOUNDS_POSITION_LENGTH]; + for (int i = 0; i < BOUNDS_POSITION_LENGTH; ++i) { + rects[i] = new Rect(mRects[i]); + } + return rects; + } + + @Override + public int hashCode() { + int result = 0; + for (Rect rect : mRects) { + result = result * 48271 + rect.hashCode(); + } + return result; + } + @Override + public boolean equals(Object o) { + if (o == this) { + return true; + } + if (o instanceof Bounds) { + Bounds b = (Bounds) o; + return Arrays.deepEquals(mRects, b.mRects); + } + return false; + } + + @Override + public String toString() { + return "Bounds=" + Arrays.toString(mRects); + } + + } + + private final Bounds mBounds; + + /** + * Creates a DisplayCutout instance. + * + * @param safeInsets the insets from each edge which avoid the display cutout as returned by + * {@link #getSafeInsetTop()} etc. + * @param boundLeft the left bounding rect of the display cutout in pixels. If null is passed, + * it's treated as an empty rectangle (0,0)-(0,0). + * @param boundTop the top bounding rect of the display cutout in pixels. If null is passed, + * it's treated as an empty rectangle (0,0)-(0,0). + * @param boundRight the right bounding rect of the display cutout in pixels. If null is + * passed, it's treated as an empty rectangle (0,0)-(0,0). + * @param boundBottom the bottom bounding rect of the display cutout in pixels. If null is + * passed, it's treated as an empty rectangle (0,0)-(0,0). + */ + // TODO(b/73953958): @VisibleForTesting(visibility = PRIVATE) + public DisplayCutout( + Insets safeInsets, @Nullable Rect boundLeft, @Nullable Rect boundTop, + @Nullable Rect boundRight, @Nullable Rect boundBottom) { + this(safeInsets.toRect(), boundLeft, boundTop, boundRight, boundBottom, true); + } /** * Creates a DisplayCutout instance. @@ -103,35 +258,76 @@ public final class DisplayCutout { * {@link #getSafeInsetTop()} etc. * @param boundingRects the bounding rects of the display cutouts as returned by * {@link #getBoundingRects()} ()}. + * @deprecated Use {@link DisplayCutout#DisplayCutout(Insets, Rect, Rect, Rect, Rect)} instead. */ // TODO(b/73953958): @VisibleForTesting(visibility = PRIVATE) + @Deprecated public DisplayCutout(Rect safeInsets, List<Rect> boundingRects) { - this(safeInsets != null ? new Rect(safeInsets) : ZERO_RECT, - boundingRectsToRegion(boundingRects), + this(safeInsets, extractBoundsFromList(safeInsets, boundingRects), true /* copyArguments */); } /** * Creates a DisplayCutout instance. * + * @param safeInsets the insets from each edge which avoid the display cutout as returned by + * {@link #getSafeInsetTop()} etc. * @param copyArguments if true, create a copy of the arguments. If false, the passed arguments * are not copied and MUST remain unchanged forever. */ - private DisplayCutout(Rect safeInsets, Region bounds, boolean copyArguments) { - mSafeInsets = safeInsets == null ? ZERO_RECT : - (copyArguments ? new Rect(safeInsets) : safeInsets); - mBounds = bounds == null ? Region.obtain() : - (copyArguments ? Region.obtain(bounds) : bounds); + private DisplayCutout(Rect safeInsets, Rect boundLeft, Rect boundTop, Rect boundRight, + Rect boundBottom, boolean copyArguments) { + mSafeInsets = getCopyOrRef(safeInsets, copyArguments); + mBounds = new Bounds(boundLeft, boundTop, boundRight, boundBottom, copyArguments); + } + + private DisplayCutout(Rect safeInsets, Rect[] bounds, boolean copyArguments) { + mSafeInsets = getCopyOrRef(safeInsets, copyArguments); + mBounds = new Bounds(bounds, copyArguments); + } + + private DisplayCutout(Rect safeInsets, Bounds bounds) { + mSafeInsets = safeInsets; + mBounds = bounds; + + } + + private static Rect getCopyOrRef(Rect r, boolean copyArguments) { + if (r == null) { + return ZERO_RECT; + } else if (copyArguments) { + return new Rect(r); + } else { + return r; + } } /** - * Returns true if the safe insets are empty (and therefore the current view does not - * overlap with the cutout or cutout area). + * Find the position of the bounding rect, and create an array of Rect whose index represents + * the position (= BoundsPosition). * * @hide */ - public boolean isEmpty() { - return mSafeInsets.equals(ZERO_RECT); + public static Rect[] extractBoundsFromList(Rect safeInsets, List<Rect> boundingRects) { + Rect[] sortedBounds = new Rect[BOUNDS_POSITION_LENGTH]; + for (int i = 0; i < sortedBounds.length; ++i) { + sortedBounds[i] = ZERO_RECT; + } + for (Rect bound : boundingRects) { + // There will be at most one non-functional area per short edge of the device, and none + // on the long edges, so either safeInsets.right or safeInsets.bottom must be 0. + // TODO(b/117199965): Refine the logic to handle edge cases. + if (bound.left == 0) { + sortedBounds[BOUNDS_POSITION_LEFT] = bound; + } else if (bound.top == 0) { + sortedBounds[BOUNDS_POSITION_TOP] = bound; + } else if (safeInsets.right > 0) { + sortedBounds[BOUNDS_POSITION_RIGHT] = bound; + } else if (safeInsets.bottom > 0) { + sortedBounds[BOUNDS_POSITION_BOTTOM] = bound; + } + } + return sortedBounds; } /** @@ -143,6 +339,16 @@ public final class DisplayCutout { return mBounds.isEmpty(); } + /** + * Returns true if the safe insets are empty (and therefore the current view does not + * overlap with the cutout or cutout area). + * + * @hide + */ + public boolean isEmpty() { + return mSafeInsets.equals(ZERO_RECT); + } + /** Returns the inset from the top which avoids the display cutout in pixels. */ public int getSafeInsetTop() { return mSafeInsets.top; @@ -174,69 +380,89 @@ public final class DisplayCutout { } /** - * Returns the bounding region of the cutout. - * - * <p> - * <strong>Note:</strong> There may be more than one cutout, in which case the returned - * {@code Region} will be non-contiguous and its bounding rect will be meaningless without - * intersecting it first. + * Returns a list of {@code Rect}s, each of which is the bounding rectangle for a non-functional + * area on the display. * - * Example: - * <pre> - * // Getting the bounding rectangle of the top display cutout - * Region bounds = displayCutout.getBounds(); - * bounds.op(0, 0, Integer.MAX_VALUE, displayCutout.getSafeInsetTop(), Region.Op.INTERSECT); - * Rect topDisplayCutout = bounds.getBoundingRect(); - * </pre> + * There will be at most one non-functional area per short edge of the device, and none on + * the long edges. * - * @return the bounding region of the cutout. Coordinates are relative - * to the top-left corner of the content view and in pixel units. - * @hide + * @return a list of bounding {@code Rect}s, one for each display cutout area. No empty Rect is + * returned. */ - public Region getBounds() { - return Region.obtain(mBounds); + public List<Rect> getBoundingRects() { + List<Rect> result = new ArrayList<>(); + for (Rect bound : getBoundingRectsAll()) { + if (!bound.isEmpty()) { + result.add(new Rect(bound)); + } + } + return result; } /** - * Returns a list of {@code Rect}s, each of which is the bounding rectangle for a non-functional - * area on the display. + * Returns an array of {@code Rect}s, each of which is the bounding rectangle for a non- + * functional area on the display. Ordinal value of BoundPosition is used as an index of + * the array. * * There will be at most one non-functional area per short edge of the device, and none on * the long edges. * - * @return a list of bounding {@code Rect}s, one for each display cutout area. + * @return an array of bounding {@code Rect}s, one for each display cutout area. This might + * contain ZERO_RECT, which means there is no cutout area at the position. + * + * @hide */ - public List<Rect> getBoundingRects() { - List<Rect> result = new ArrayList<>(); - Region bounds = Region.obtain(); - // top - bounds.set(mBounds); - bounds.op(0, 0, Integer.MAX_VALUE, getSafeInsetTop(), Region.Op.INTERSECT); - if (!bounds.isEmpty()) { - result.add(bounds.getBounds()); - } - // left - bounds.set(mBounds); - bounds.op(0, 0, getSafeInsetLeft(), Integer.MAX_VALUE, Region.Op.INTERSECT); - if (!bounds.isEmpty()) { - result.add(bounds.getBounds()); - } - // right & bottom - bounds.set(mBounds); - bounds.op(getSafeInsetLeft() + 1, getSafeInsetTop() + 1, - Integer.MAX_VALUE, Integer.MAX_VALUE, Region.Op.INTERSECT); - if (!bounds.isEmpty()) { - result.add(bounds.getBounds()); - } - bounds.recycle(); - return result; + public Rect[] getBoundingRectsAll() { + return mBounds.getRects(); + } + + /** + * Returns a bounding rectangle for a non-functional area on the display which is located on + * the left of the screen. + * + * @return bounding rectangle in pixels. In case of no bounding rectangle, an empty rectangle + * is returned. + */ + public @NonNull Rect getBoundingRectLeft() { + return mBounds.getRect(BOUNDS_POSITION_LEFT); + } + + /** + * Returns a bounding rectangle for a non-functional area on the display which is located on + * the top of the screen. + * + * @return bounding rectangle in pixels. In case of no bounding rectangle, an empty rectangle + * is returned. + */ + public @NonNull Rect getBoundingRectTop() { + return mBounds.getRect(BOUNDS_POSITION_TOP); + } + + /** + * Returns a bounding rectangle for a non-functional area on the display which is located on + * the right of the screen. + * + * @return bounding rectangle in pixels. In case of no bounding rectangle, an empty rectangle + * is returned. + */ + public @NonNull Rect getBoundingRectRight() { + return mBounds.getRect(BOUNDS_POSITION_RIGHT); + } + + /** + * Returns a bounding rectangle for a non-functional area on the display which is located on + * the bottom of the screen. + * + * @return bounding rectangle in pixels. In case of no bounding rectangle, an empty rectangle + * is returned. + */ + public @NonNull Rect getBoundingRectBottom() { + return mBounds.getRect(BOUNDS_POSITION_BOTTOM); } @Override public int hashCode() { - int result = mSafeInsets.hashCode(); - result = result * 31 + mBounds.getBounds().hashCode(); - return result; + return mSafeInsets.hashCode() * 48271 + mBounds.hashCode(); } @Override @@ -246,8 +472,7 @@ public final class DisplayCutout { } if (o instanceof DisplayCutout) { DisplayCutout c = (DisplayCutout) o; - return mSafeInsets.equals(c.mSafeInsets) - && mBounds.equals(c.mBounds); + return mSafeInsets.equals(c.mSafeInsets) && mBounds.equals(c.mBounds); } return false; } @@ -255,7 +480,7 @@ public final class DisplayCutout { @Override public String toString() { return "DisplayCutout{insets=" + mSafeInsets - + " boundingRect=" + mBounds.getBounds() + + " boundingRect={" + mBounds + "}" + "}"; } @@ -265,7 +490,10 @@ public final class DisplayCutout { public void writeToProto(ProtoOutputStream proto, long fieldId) { final long token = proto.start(fieldId); mSafeInsets.writeToProto(proto, INSETS); - mBounds.getBounds().writeToProto(proto, BOUNDS); + mBounds.getRect(BOUNDS_POSITION_LEFT).writeToProto(proto, BOUND_LEFT); + mBounds.getRect(BOUNDS_POSITION_TOP).writeToProto(proto, BOUND_TOP); + mBounds.getRect(BOUNDS_POSITION_RIGHT).writeToProto(proto, BOUND_RIGHT); + mBounds.getRect(BOUNDS_POSITION_BOTTOM).writeToProto(proto, BOUND_BOTTOM); proto.end(token); } @@ -276,13 +504,12 @@ public final class DisplayCutout { * @hide */ public DisplayCutout inset(int insetLeft, int insetTop, int insetRight, int insetBottom) { - if (mBounds.isEmpty() + if (isBoundsEmpty() || insetLeft == 0 && insetTop == 0 && insetRight == 0 && insetBottom == 0) { return this; } Rect safeInsets = new Rect(mSafeInsets); - Region bounds = Region.obtain(mBounds); // Note: it's not really well defined what happens when the inset is negative, because we // don't know if the safe inset needs to expand in general. @@ -299,7 +526,13 @@ public final class DisplayCutout { safeInsets.right = atLeastZero(safeInsets.right - insetRight); } - bounds.translate(-insetLeft, -insetTop); + Rect[] bounds = mBounds.getRects(); + for (int i = 0; i < bounds.length; ++i) { + if (!bounds[i].equals(ZERO_RECT)) { + bounds[i].offset(-insetLeft, -insetTop); + } + } + return new DisplayCutout(safeInsets, bounds, false /* copyArguments */); } @@ -312,7 +545,7 @@ public final class DisplayCutout { * @hide */ public DisplayCutout replaceSafeInsets(Rect safeInsets) { - return new DisplayCutout(new Rect(safeInsets), mBounds, false /* copyArguments */); + return new DisplayCutout(new Rect(safeInsets), mBounds); } private static int atLeastZero(int value) { @@ -326,10 +559,13 @@ public final class DisplayCutout { * @hide */ @VisibleForTesting - public static DisplayCutout fromBoundingRect(int left, int top, int right, int bottom) { - Region r = Region.obtain(); - r.set(left, top, right, bottom); - return fromBounds(r); + public static DisplayCutout fromBoundingRect( + int left, int top, int right, int bottom, @BoundsPosition int pos) { + Rect[] bounds = new Rect[BOUNDS_POSITION_LENGTH]; + for (int i = 0; i < BOUNDS_POSITION_LENGTH; ++i) { + bounds[i] = (pos == i) ? new Rect(left, top, right, bottom) : new Rect(); + } + return new DisplayCutout(ZERO_RECT, bounds, false /* copyArguments */); } /** @@ -337,8 +573,8 @@ public final class DisplayCutout { * * @hide */ - public static DisplayCutout fromBounds(Region region) { - return new DisplayCutout(ZERO_RECT, region, false /* copyArguments */); + public static DisplayCutout fromBounds(Rect[] bounds) { + return new DisplayCutout(ZERO_RECT, bounds, false /* copyArguments */); } /** @@ -423,10 +659,11 @@ public final class DisplayCutout { m.postTranslate(offsetX, 0); p.transform(m); - final Rect tmpRect = new Rect(); - toRectAndAddToRegion(p, r, tmpRect); - final int topInset = tmpRect.bottom; + Rect boundTop = new Rect(); + toRectAndAddToRegion(p, r, boundTop); + final int topInset = boundTop.bottom; + Rect boundBottom = null; final int bottomInset; if (bottomSpec != null) { final Path bottomPath; @@ -440,15 +677,17 @@ public final class DisplayCutout { m.postTranslate(0, displayHeight); bottomPath.transform(m); p.addPath(bottomPath); - toRectAndAddToRegion(bottomPath, r, tmpRect); - bottomInset = displayHeight - tmpRect.top; + boundBottom = new Rect(); + toRectAndAddToRegion(bottomPath, r, boundBottom); + bottomInset = displayHeight - boundBottom.top; } else { bottomInset = 0; } - // Reuse tmpRect as the inset rect we store into the DisplayCutout instance. - tmpRect.set(0, topInset, 0, bottomInset); - final DisplayCutout cutout = new DisplayCutout(tmpRect, r, false /* copyArguments */); + Rect safeInset = new Rect(0, topInset, 0, bottomInset); + final DisplayCutout cutout = new DisplayCutout( + safeInset, null /* boundLeft */, boundTop, null /* boundRight */, boundBottom, + false /* copyArguments */); final Pair<Path, DisplayCutout> result = new Pair<>(p, cutout); synchronized (CACHE_LOCK) { @@ -468,16 +707,6 @@ public final class DisplayCutout { inoutRegion.op(inoutRect, Op.UNION); } - private static Region boundingRectsToRegion(List<Rect> rects) { - Region result = Region.obtain(); - if (rects != null) { - for (Rect r : rects) { - result.op(r, Region.Op.UNION); - } - } - return result; - } - /** * Helper class for passing {@link DisplayCutout} through binder. * @@ -520,7 +749,7 @@ public final class DisplayCutout { } else { out.writeInt(1); out.writeTypedObject(cutout.mSafeInsets, flags); - out.writeTypedObject(cutout.mBounds, flags); + out.writeTypedArray(cutout.mBounds.getRects(), flags); } } @@ -561,7 +790,8 @@ public final class DisplayCutout { } Rect safeInsets = in.readTypedObject(Rect.CREATOR); - Region bounds = in.readTypedObject(Region.CREATOR); + Rect[] bounds = new Rect[BOUNDS_POSITION_LENGTH]; + in.readTypedArray(bounds, Rect.CREATOR); return new DisplayCutout(safeInsets, bounds, false /* copyArguments */); } diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl index 3bab87aea644..3e559d9e106f 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -87,7 +87,6 @@ interface IWindowManager void setEventDispatching(boolean enabled); void addWindowToken(IBinder token, int type, int displayId); void removeWindowToken(IBinder token, int displayId); - void setFocusedApp(IBinder token, boolean moveFocusNow); void prepareAppTransition(int transit, boolean alwaysKeepCurrent); int getPendingAppTransition(); void overridePendingAppTransition(String packageName, int enterAnim, int exitAnim, diff --git a/core/java/android/view/KeyEvent.java b/core/java/android/view/KeyEvent.java index c38fcf36a65c..0a3403bba351 100644 --- a/core/java/android/view/KeyEvent.java +++ b/core/java/android/view/KeyEvent.java @@ -896,8 +896,8 @@ public class KeyEvent extends InputEvent implements Parcelable { * @deprecated No longer used by the input system. * {@link #getAction} value: multiple duplicate key events have * occurred in a row, or a complex string is being delivered. If the - * key code is not {#link {@link #KEYCODE_UNKNOWN} then the - * {#link {@link #getRepeatCount()} method returns the number of times + * key code is not {@link #KEYCODE_UNKNOWN} then the + * {@link #getRepeatCount()} method returns the number of times * the given key code should be executed. * Otherwise, if the key code is {@link #KEYCODE_UNKNOWN}, then * this is a sequence of characters as returned by {@link #getCharacters}. diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 78e6dd8fb139..cddd83c45c75 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -14639,9 +14639,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * Recomputes the matrix if necessary. * * @return True if the transform matrix is the identity matrix, false otherwise. + * @hide */ @UnsupportedAppUsage - final boolean hasIdentityMatrix() { + public final boolean hasIdentityMatrix() { return mRenderNode.hasIdentityMatrix(); } @@ -15636,7 +15637,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, /** * Sets the visual z position of this view, in pixels. This is equivalent to setting the * {@link #setTranslationZ(float) translationZ} property to be the difference between - * the x value passed in and the current {@link #getElevation() elevation} property. + * the z value passed in and the current {@link #getElevation() elevation} property. * * @param z The visual z position of this view, in pixels. */ diff --git a/core/java/android/view/animation/Animation.java b/core/java/android/view/animation/Animation.java index 87b7b057e668..0e1f7675c8c3 100644 --- a/core/java/android/view/animation/Animation.java +++ b/core/java/android/view/animation/Animation.java @@ -203,11 +203,6 @@ public abstract class Animation implements Cloneable { */ private float mScaleFactor = 1f; - /** - * Don't animate the wallpaper. - */ - private boolean mDetachWallpaper = false; - private boolean mShowWallpaper; private boolean mMore = true; @@ -667,9 +662,10 @@ public abstract class Animation implements Cloneable { * * @param detachWallpaper true if the wallpaper should be detached from the animation * @attr ref android.R.styleable#Animation_detachWallpaper + * + * @deprecated All window animations are running with detached wallpaper. */ public void setDetachWallpaper(boolean detachWallpaper) { - mDetachWallpaper = detachWallpaper; } /** @@ -793,9 +789,11 @@ public abstract class Animation implements Cloneable { /** * Return value of {@link #setDetachWallpaper(boolean)}. * @attr ref android.R.styleable#Animation_detachWallpaper + * + * @deprecated All window animations are running with detached wallpaper. */ public boolean getDetachWallpaper() { - return mDetachWallpaper; + return false; } /** diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java index 7abe19e795f4..d4c7069cdbf4 100644 --- a/core/java/android/view/autofill/AutofillManager.java +++ b/core/java/android/view/autofill/AutofillManager.java @@ -803,10 +803,6 @@ public final class AutofillManager { return true; } } - if (sVerbose) { - Log.v(TAG, "not ignoring notifyViewEntered(flags=" + flags + ", view=" + id - + ", state " + getStateAsStringLocked() + ", enteredIds=" + mEnteredIds); - } return false; } @@ -845,6 +841,9 @@ public final class AutofillManager { ensureServiceClientAddedIfNeededLocked(); if (!mEnabled) { + if (sVerbose) { + Log.v(TAG, "ignoring notifyViewEntered(" + id + "): disabled"); + } if (mCallback != null) { callback = mCallback; } @@ -995,6 +994,9 @@ public final class AutofillManager { ensureServiceClientAddedIfNeededLocked(); if (!mEnabled) { + if (sVerbose) { + Log.v(TAG, "ignoring notifyViewEntered(" + id + "): disabled"); + } if (mCallback != null) { callback = mCallback; } diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java index 9d74c98a04ba..8027dd7cdb10 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -101,6 +101,7 @@ import android.view.View.OnClickListener; import android.view.ViewConfiguration; import android.view.ViewGroup; import android.view.ViewGroup.LayoutParams; +import android.view.ViewParent; import android.view.ViewTreeObserver; import android.view.WindowManager; import android.view.accessibility.AccessibilityNodeInfo; @@ -4800,6 +4801,24 @@ public class Editor { return glyphHeight > magnifierContentHeight; } + private boolean viewIsMatrixTransformed() { + if (mMagnifierAnimator.mMagnifierIsShowing) { + // Do not check again when the magnifier is currently showing. + return false; + } + if (!mTextView.hasIdentityMatrix()) { + return true; + } + ViewParent viewParent = mTextView.getParent(); + while (viewParent != null) { + if (viewParent instanceof View && !((View) viewParent).hasIdentityMatrix()) { + return true; + } + viewParent = viewParent.getParent(); + } + return false; + } + /** * Computes the position where the magnifier should be shown, relative to * {@code mTextView}, and writes them to {@code showPosInView}. Also decides @@ -4928,6 +4947,7 @@ public class Editor { final PointF showPosInView = new PointF(); final boolean shouldShow = !tooLargeTextForMagnifier() + && !viewIsMatrixTransformed() && obtainMagnifierShowCoordinates(event, showPosInView); if (shouldShow) { // Make the cursor visible and stop blinking. diff --git a/core/java/android/widget/ImageView.java b/core/java/android/widget/ImageView.java index 0fef9a54a2ff..12cc54d7241e 100644 --- a/core/java/android/widget/ImageView.java +++ b/core/java/android/widget/ImageView.java @@ -1338,7 +1338,9 @@ public class ImageView extends View { return; } if (matrix == null) { - mDrawable.setBounds(0, 0, getWidth(), getHeight()); + final int vwidth = getWidth() - mPaddingLeft - mPaddingRight; + final int vheight = getHeight() - mPaddingTop - mPaddingBottom; + mDrawable.setBounds(0, 0, vwidth, vheight); } else { mDrawable.setBounds(0, 0, mDrawableWidth, mDrawableHeight); if (mDrawMatrix == null) { diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index f95b3ce98264..9d06680c4c07 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -882,9 +882,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private boolean mTextSetFromXmlOrResourceId = false; // Resource id used to set the text. private @StringRes int mTextId = ResourceId.ID_NULL; - // Last value used on AFM.notifyValueChanged(), used to optimize autofill workflow by avoiding - // calls when the value did not change - private CharSequence mLastValueSentToAutofillManager; // // End of autofill-related attributes @@ -5884,7 +5881,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (needEditableForNotification) { sendAfterTextChanged((Editable) text); } else { - notifyAutoFillManagerAfterTextChangedIfNeeded(); + notifyAutoFillManagerAfterTextChanged(); } // SelectionModifierCursorController depends on textCanBeSelected, which depends on text @@ -9933,33 +9930,23 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } // Always notify AutoFillManager - it will return right away if autofill is disabled. - notifyAutoFillManagerAfterTextChangedIfNeeded(); + notifyAutoFillManagerAfterTextChanged(); hideErrorIfUnchanged(); } - private void notifyAutoFillManagerAfterTextChangedIfNeeded() { + private void notifyAutoFillManagerAfterTextChanged() { // It is important to not check whether the view is important for autofill // since the user can trigger autofill manually on not important views. if (!isAutofillable()) { return; } final AutofillManager afm = mContext.getSystemService(AutofillManager.class); - if (afm == null) { - return; - } - - if (mLastValueSentToAutofillManager == null - || !mLastValueSentToAutofillManager.equals(mText)) { + if (afm != null) { if (android.view.autofill.Helper.sVerbose) { - Log.v(LOG_TAG, "notifying AFM after text changed"); + Log.v(LOG_TAG, "notifyAutoFillManagerAfterTextChanged"); } afm.notifyValueChanged(TextView.this); - mLastValueSentToAutofillManager = mText; - } else { - if (android.view.autofill.Helper.sVerbose) { - Log.v(LOG_TAG, "not notifying AFM on unchanged text"); - } } } diff --git a/core/java/com/android/internal/hardware/AmbientDisplayConfiguration.java b/core/java/com/android/internal/hardware/AmbientDisplayConfiguration.java index cb282b69845c..0080ace230a2 100644 --- a/core/java/com/android/internal/hardware/AmbientDisplayConfiguration.java +++ b/core/java/com/android/internal/hardware/AmbientDisplayConfiguration.java @@ -66,13 +66,13 @@ public class AmbientDisplayConfiguration { return !TextUtils.isEmpty(doubleTapSensorType()); } - public boolean reachGestureEnabled(int user) { - return boolSettingDefaultOn(Settings.Secure.DOZE_REACH_GESTURE, user) - && reachGestureAvailable(); + public boolean wakeLockScreenGestureEnabled(int user) { + return boolSettingDefaultOn(Settings.Secure.DOZE_WAKE_LOCK_SCREEN_GESTURE, user) + && wakeLockScreenGestureAvailable(); } - public boolean reachGestureAvailable() { - return !TextUtils.isEmpty(reachSensorType()); + public boolean wakeLockScreenGestureAvailable() { + return !TextUtils.isEmpty(wakeLockScreenSensorType()); } public boolean wakeScreenGestureEnabled(int user) { @@ -92,8 +92,8 @@ public class AmbientDisplayConfiguration { return mContext.getResources().getString(R.string.config_dozeLongPressSensorType); } - public String reachSensorType() { - return mContext.getResources().getString(R.string.config_dozeReachSensorType); + public String wakeLockScreenSensorType() { + return mContext.getResources().getString(R.string.config_dozeWakeLockScreenSensorType); } public String wakeScreenSensorType() { diff --git a/core/java/com/android/internal/os/BinderCallsStats.java b/core/java/com/android/internal/os/BinderCallsStats.java index c0c358d9b44b..856712f48297 100644 --- a/core/java/com/android/internal/os/BinderCallsStats.java +++ b/core/java/com/android/internal/os/BinderCallsStats.java @@ -436,6 +436,12 @@ public class BinderCallsStats implements BinderInternal.Observer { } public void setSamplingInterval(int samplingInterval) { + if (samplingInterval <= 0) { + Slog.w(TAG, "Ignored invalid sampling interval (value must be positive): " + + samplingInterval); + return; + } + synchronized (mLock) { if (samplingInterval != mPeriodicSamplingInterval) { mPeriodicSamplingInterval = samplingInterval; diff --git a/core/java/com/android/internal/os/PowerProfile.java b/core/java/com/android/internal/os/PowerProfile.java index 997b722681ba..8338d78af3a1 100644 --- a/core/java/com/android/internal/os/PowerProfile.java +++ b/core/java/com/android/internal/os/PowerProfile.java @@ -21,6 +21,7 @@ import android.annotation.UnsupportedAppUsage; import android.content.Context; import android.content.res.Resources; import android.content.res.XmlResourceParser; +import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.XmlUtils; @@ -501,4 +502,181 @@ public class PowerProfile { public double getBatteryCapacity() { return getAveragePower(POWER_BATTERY_CAPACITY); } + + /** + * Dump power constants into PowerProfileProto + */ + public void writeToProto(ProtoOutputStream proto) { + // cpu.suspend + writePowerConstantToProto(proto, POWER_CPU_SUSPEND, PowerProfileProto.CPU_SUSPEND); + + // cpu.idle + writePowerConstantToProto(proto, POWER_CPU_IDLE, PowerProfileProto.CPU_IDLE); + + // cpu.active + writePowerConstantToProto(proto, POWER_CPU_ACTIVE, PowerProfileProto.CPU_ACTIVE); + + // cpu.clusters.cores + // cpu.cluster_power.cluster + // cpu.core_speeds.cluster + // cpu.core_power.cluster + for (int cluster = 0; cluster < mCpuClusters.length; cluster++) { + final long token = proto.start(PowerProfileProto.CPU_CLUSTER); + proto.write(PowerProfileProto.CpuCluster.ID, cluster); + proto.write(PowerProfileProto.CpuCluster.CLUSTER_POWER, + sPowerItemMap.get(mCpuClusters[cluster].clusterPowerKey)); + proto.write(PowerProfileProto.CpuCluster.CORES, mCpuClusters[cluster].numCpus); + for (Double speed : sPowerArrayMap.get(mCpuClusters[cluster].freqKey)) { + proto.write(PowerProfileProto.CpuCluster.SPEED, speed); + } + for (Double corePower : sPowerArrayMap.get(mCpuClusters[cluster].corePowerKey)) { + proto.write(PowerProfileProto.CpuCluster.CORE_POWER, corePower); + } + proto.end(token); + } + + // wifi.scan + writePowerConstantToProto(proto, POWER_WIFI_SCAN, PowerProfileProto.WIFI_SCAN); + + // wifi.on + writePowerConstantToProto(proto, POWER_WIFI_ON, PowerProfileProto.WIFI_ON); + + // wifi.active + writePowerConstantToProto(proto, POWER_WIFI_ACTIVE, PowerProfileProto.WIFI_ACTIVE); + + // wifi.controller.idle + writePowerConstantToProto(proto, POWER_WIFI_CONTROLLER_IDLE, + PowerProfileProto.WIFI_CONTROLLER_IDLE); + + // wifi.controller.rx + writePowerConstantToProto(proto, POWER_WIFI_CONTROLLER_RX, + PowerProfileProto.WIFI_CONTROLLER_RX); + + // wifi.controller.tx + writePowerConstantToProto(proto, POWER_WIFI_CONTROLLER_TX, + PowerProfileProto.WIFI_CONTROLLER_TX); + + // wifi.controller.tx_levels + writePowerConstantArrayToProto(proto, POWER_WIFI_CONTROLLER_TX_LEVELS, + PowerProfileProto.WIFI_CONTROLLER_TX_LEVELS); + + // wifi.controller.voltage + writePowerConstantToProto(proto, POWER_WIFI_CONTROLLER_OPERATING_VOLTAGE, + PowerProfileProto.WIFI_CONTROLLER_OPERATING_VOLTAGE); + + // bluetooth.controller.idle + writePowerConstantToProto(proto, POWER_BLUETOOTH_CONTROLLER_IDLE, + PowerProfileProto.BLUETOOTH_CONTROLLER_IDLE); + + // bluetooth.controller.rx + writePowerConstantToProto(proto, POWER_BLUETOOTH_CONTROLLER_RX, + PowerProfileProto.BLUETOOTH_CONTROLLER_RX); + + // bluetooth.controller.tx + writePowerConstantToProto(proto, POWER_BLUETOOTH_CONTROLLER_TX, + PowerProfileProto.BLUETOOTH_CONTROLLER_TX); + + // bluetooth.controller.voltage + writePowerConstantToProto(proto, POWER_BLUETOOTH_CONTROLLER_OPERATING_VOLTAGE, + PowerProfileProto.BLUETOOTH_CONTROLLER_OPERATING_VOLTAGE); + + // modem.controller.sleep + writePowerConstantToProto(proto, POWER_MODEM_CONTROLLER_SLEEP, + PowerProfileProto.MODEM_CONTROLLER_SLEEP); + + // modem.controller.idle + writePowerConstantToProto(proto, POWER_MODEM_CONTROLLER_IDLE, + PowerProfileProto.MODEM_CONTROLLER_IDLE); + + // modem.controller.rx + writePowerConstantToProto(proto, POWER_MODEM_CONTROLLER_RX, + PowerProfileProto.MODEM_CONTROLLER_RX); + + // modem.controller.tx + writePowerConstantArrayToProto(proto, POWER_MODEM_CONTROLLER_TX, + PowerProfileProto.MODEM_CONTROLLER_TX); + + // modem.controller.voltage + writePowerConstantToProto(proto, POWER_MODEM_CONTROLLER_OPERATING_VOLTAGE, + PowerProfileProto.MODEM_CONTROLLER_OPERATING_VOLTAGE); + + // gps.on + writePowerConstantToProto(proto, POWER_GPS_ON, PowerProfileProto.GPS_ON); + + // gps.signalqualitybased + writePowerConstantArrayToProto(proto, POWER_GPS_SIGNAL_QUALITY_BASED, + PowerProfileProto.GPS_SIGNAL_QUALITY_BASED); + + // gps.voltage + writePowerConstantToProto(proto, POWER_GPS_OPERATING_VOLTAGE, + PowerProfileProto.GPS_OPERATING_VOLTAGE); + + // bluetooth.on + writePowerConstantToProto(proto, POWER_BLUETOOTH_ON, PowerProfileProto.BLUETOOTH_ON); + + // bluetooth.active + writePowerConstantToProto(proto, POWER_BLUETOOTH_ACTIVE, + PowerProfileProto.BLUETOOTH_ACTIVE); + + // bluetooth.at + writePowerConstantToProto(proto, POWER_BLUETOOTH_AT_CMD, + PowerProfileProto.BLUETOOTH_AT_CMD); + + // ambient.on + writePowerConstantToProto(proto, POWER_AMBIENT_DISPLAY, PowerProfileProto.AMBIENT_DISPLAY); + + // screen.on + writePowerConstantToProto(proto, POWER_SCREEN_ON, PowerProfileProto.SCREEN_ON); + + // radio.on + writePowerConstantToProto(proto, POWER_RADIO_ON, PowerProfileProto.RADIO_ON); + + // radio.scanning + writePowerConstantToProto(proto, POWER_RADIO_SCANNING, PowerProfileProto.RADIO_SCANNING); + + // radio.active + writePowerConstantToProto(proto, POWER_RADIO_ACTIVE, PowerProfileProto.RADIO_ACTIVE); + + // screen.full + writePowerConstantToProto(proto, POWER_SCREEN_FULL, PowerProfileProto.SCREEN_FULL); + + // audio + writePowerConstantToProto(proto, POWER_AUDIO, PowerProfileProto.AUDIO); + + // video + writePowerConstantToProto(proto, POWER_VIDEO, PowerProfileProto.VIDEO); + + // camera.flashlight + writePowerConstantToProto(proto, POWER_FLASHLIGHT, PowerProfileProto.FLASHLIGHT); + + // memory.bandwidths + writePowerConstantToProto(proto, POWER_MEMORY, PowerProfileProto.MEMORY); + + // camera.avg + writePowerConstantToProto(proto, POWER_CAMERA, PowerProfileProto.CAMERA); + + // wifi.batchedscan + writePowerConstantToProto(proto, POWER_WIFI_BATCHED_SCAN, + PowerProfileProto.WIFI_BATCHED_SCAN); + + // battery.capacity + writePowerConstantToProto(proto, POWER_BATTERY_CAPACITY, + PowerProfileProto.BATTERY_CAPACITY); + } + + // Writes items in sPowerItemMap to proto if exists. + private void writePowerConstantToProto(ProtoOutputStream proto, String key, long fieldId) { + if (sPowerItemMap.containsKey(key)) { + proto.write(fieldId, sPowerItemMap.get(key)); + } + } + + // Writes items in sPowerArrayMap to proto if exists. + private void writePowerConstantArrayToProto(ProtoOutputStream proto, String key, long fieldId) { + if (sPowerArrayMap.containsKey(key)) { + for (Double d : sPowerArrayMap.get(key)) { + proto.write(fieldId, d); + } + } + } } diff --git a/core/java/com/android/internal/os/StoragedUidIoStatsReader.java b/core/java/com/android/internal/os/StoragedUidIoStatsReader.java new file mode 100644 index 000000000000..9b0346923cd3 --- /dev/null +++ b/core/java/com/android/internal/os/StoragedUidIoStatsReader.java @@ -0,0 +1,113 @@ +/* + * 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.internal.os; + +import android.os.StrictMode; +import android.text.TextUtils; +import android.util.Slog; + +import com.android.internal.annotations.VisibleForTesting; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; + + +/** + * Reads /proc/uid_io/stats which has the line format: + * + * uid: foreground_read_chars foreground_write_chars foreground_read_bytes foreground_write_bytes + * background_read_chars background_write_chars background_read_bytes background_write_bytes + * foreground_fsync background_fsync + * + * This provides the number of bytes/chars read/written in foreground/background for each uid. + * The file contains a monotonically increasing count of bytes/chars for a single boot. + */ +public class StoragedUidIoStatsReader { + + private static final String TAG = StoragedUidIoStatsReader.class.getSimpleName(); + private static String sUidIoFile = "/proc/uid_io/stats"; + + public StoragedUidIoStatsReader() { + } + + @VisibleForTesting + public StoragedUidIoStatsReader(String file) { + sUidIoFile = file; + } + + /** + * Notifies when new data is available. + */ + public interface Callback { + + /** + * Provides data to the client. + * + * Note: Bytes are I/O events from a storage device. Chars are data requested by syscalls, + * and can be satisfied by caching. + */ + void onUidStorageStats(int uid, long fgCharsRead, long fgCharsWrite, long fgBytesRead, + long fgBytesWrite, long bgCharsRead, long bgCharsWrite, long bgBytesRead, + long bgBytesWrite, long fgFsync, long bgFsync); + } + + /** + * Reads the proc file, calling into the callback with raw absolute value of I/O stats + * for each UID. + * + * @param callback The callback to invoke for each line of the proc file. + */ + public void readAbsolute(Callback callback) { + final int oldMask = StrictMode.allowThreadDiskReadsMask(); + File file = new File(sUidIoFile); + try (BufferedReader reader = Files.newBufferedReader(file.toPath())) { + String line; + while ((line = reader.readLine()) != null) { + String[] fields = TextUtils.split(line, " "); + if (fields.length != 11) { + Slog.e(TAG, "Malformed entry in " + sUidIoFile + ": " + line); + continue; + } + try { + final String uidStr = fields[0]; + final int uid = Integer.parseInt(fields[0], 10); + final long fgCharsRead = Long.parseLong(fields[1], 10); + final long fgCharsWrite = Long.parseLong(fields[2], 10); + final long fgBytesRead = Long.parseLong(fields[3], 10); + final long fgBytesWrite = Long.parseLong(fields[4], 10); + final long bgCharsRead = Long.parseLong(fields[5], 10); + final long bgCharsWrite = Long.parseLong(fields[6], 10); + final long bgBytesRead = Long.parseLong(fields[7], 10); + final long bgBytesWrite = Long.parseLong(fields[8], 10); + final long fgFsync = Long.parseLong(fields[9], 10); + final long bgFsync = Long.parseLong(fields[10], 10); + callback.onUidStorageStats(uid, fgCharsRead, fgCharsWrite, fgBytesRead, + fgBytesWrite, bgCharsRead, bgCharsWrite, bgBytesRead, bgBytesWrite, + fgFsync, bgFsync); + } catch (NumberFormatException e) { + Slog.e(TAG, "Could not parse entry in " + sUidIoFile + ": " + e.getMessage()); + } + } + } catch (IOException e) { + Slog.e(TAG, "Failed to read " + sUidIoFile + ": " + e.getMessage()); + } finally { + StrictMode.setThreadPolicyMask(oldMask); + } + } +} diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java index 927322e97e28..98b7b5d28779 100644 --- a/core/java/com/android/internal/os/Zygote.java +++ b/core/java/com/android/internal/os/Zygote.java @@ -133,15 +133,16 @@ public final class Zygote { * if this is the parent, or -1 on error. */ public static int forkAndSpecialize(int uid, int gid, int[] gids, int runtimeFlags, - int[][] rlimits, int mountExternal, String seInfo, String niceName, int[] fdsToClose, - int[] fdsToIgnore, boolean startChildZygote, String instructionSet, String appDataDir, - String packageName) { + int[][] rlimits, int mountExternal, String seInfo, String niceName, int[] fdsToClose, + int[] fdsToIgnore, boolean startChildZygote, String instructionSet, String appDataDir, + String packageName, String[] packagesForUid, String[] visibleVolIds) { VM_HOOKS.preFork(); // Resets nice priority for zygote process. resetNicePriority(); int pid = nativeForkAndSpecialize( uid, gid, gids, runtimeFlags, rlimits, mountExternal, seInfo, niceName, fdsToClose, - fdsToIgnore, startChildZygote, instructionSet, appDataDir, packageName); + fdsToIgnore, startChildZygote, instructionSet, appDataDir, packageName, + packagesForUid, visibleVolIds); // Enable tracing as soon as possible for the child process. if (pid == 0) { Trace.setTracingEnabled(true, runtimeFlags); @@ -154,9 +155,9 @@ public final class Zygote { } native private static int nativeForkAndSpecialize(int uid, int gid, int[] gids,int runtimeFlags, - int[][] rlimits, int mountExternal, String seInfo, String niceName, int[] fdsToClose, - int[] fdsToIgnore, boolean startChildZygote, String instructionSet, String appDataDir, - String packageName); + int[][] rlimits, int mountExternal, String seInfo, String niceName, int[] fdsToClose, + int[] fdsToIgnore, boolean startChildZygote, String instructionSet, String appDataDir, + String packageName, String[] packagesForUid, String[] visibleVolIds); /** * Called to do any initialization before starting an application. diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java index 06c41d858f7c..4a94ec4a4071 100644 --- a/core/java/com/android/internal/os/ZygoteConnection.java +++ b/core/java/com/android/internal/os/ZygoteConnection.java @@ -241,7 +241,8 @@ class ZygoteConnection { pid = Zygote.forkAndSpecialize(parsedArgs.uid, parsedArgs.gid, parsedArgs.gids, parsedArgs.runtimeFlags, rlimits, parsedArgs.mountExternal, parsedArgs.seInfo, parsedArgs.niceName, fdsToClose, fdsToIgnore, parsedArgs.startChildZygote, - parsedArgs.instructionSet, parsedArgs.appDataDir, parsedArgs.packageName); + parsedArgs.instructionSet, parsedArgs.appDataDir, parsedArgs.packageName, + parsedArgs.packagesForUid, parsedArgs.visibleVolIds); try { if (pid == 0) { @@ -432,6 +433,12 @@ class ZygoteConnection { /** from --package-name */ String packageName; + /** from --packages-for-uid */ + String[] packagesForUid; + + /** from --visible-vols */ + String[] visibleVolIds; + /** * Any args after and including the first non-option arg * (or after a '--') @@ -687,6 +694,10 @@ class ZygoteConnection { throw new IllegalArgumentException("Duplicate arg specified"); } packageName = arg.substring(arg.indexOf('=') + 1); + } else if (arg.startsWith("--packages-for-uid=")) { + packagesForUid = arg.substring(arg.indexOf('=') + 1).split(","); + } else if (arg.startsWith("--visible-vols=")) { + visibleVolIds = arg.substring(arg.indexOf('=') + 1).split(","); } else { break; } diff --git a/core/jni/Android.bp b/core/jni/Android.bp index 762b43079c31..8e24d10a206f 100644 --- a/core/jni/Android.bp +++ b/core/jni/Android.bp @@ -209,6 +209,8 @@ cc_library_shared { "com_android_internal_view_animation_NativeInterpolatorFactoryHelper.cpp", "hwbinder/EphemeralStorage.cpp", "fd_utils.cpp", + "android_hardware_input_InputWindowHandle.cpp", + "android_hardware_input_InputApplicationHandle.cpp", ], include_dirs: [ diff --git a/core/jni/android/graphics/Bitmap.cpp b/core/jni/android/graphics/Bitmap.cpp index 897f6fa53774..644a9746cf2a 100755 --- a/core/jni/android/graphics/Bitmap.cpp +++ b/core/jni/android/graphics/Bitmap.cpp @@ -9,7 +9,6 @@ #include "SkColor.h" #include "SkColorPriv.h" #include "SkColorSpace.h" -#include "SkColorSpaceXform.h" #include "SkHalf.h" #include "SkMatrix44.h" #include "SkPM4f.h" @@ -559,13 +558,10 @@ static jboolean Bitmap_compress(JNIEnv* env, jobject clazz, jlong bitmapHandle, if (!p3.tryAllocPixels(info)) { return JNI_FALSE; } - auto xform = SkColorSpaceXform::New(skbitmap.colorSpace(), info.colorSpace()); - if (!xform) { - return JNI_FALSE; - } - if (!xform->apply(SkColorSpaceXform::kRGBA_8888_ColorFormat, p3.getPixels(), - SkColorSpaceXform::kRGBA_F16_ColorFormat, skbitmap.getPixels(), - info.width() * info.height(), kUnpremul_SkAlphaType)) { + + SkPixmap pm; + SkAssertResult(p3.peekPixels(&pm)); // should always work if tryAllocPixels() did. + if (!skbitmap.readPixels(pm)) { return JNI_FALSE; } skbitmap = p3; diff --git a/core/jni/android_graphics_Canvas.cpp b/core/jni/android_graphics_Canvas.cpp index 343aef291720..dca2da369540 100644 --- a/core/jni/android_graphics_Canvas.cpp +++ b/core/jni/android_graphics_Canvas.cpp @@ -273,6 +273,32 @@ static void drawRect(JNIEnv* env, jobject, jlong canvasHandle, jfloat left, jflo get_canvas(canvasHandle)->drawRect(left, top, right, bottom, *paint); } +static void drawDoubleRoundRectXY(JNIEnv* env, jobject, jlong canvasHandle, jfloat outerLeft, + jfloat outerTop, jfloat outerRight, jfloat outerBottom, jfloat outerRx, + jfloat outerRy, jfloat innerLeft, jfloat innerTop, jfloat innerRight, + jfloat innerBottom, jfloat innerRx, jfloat innerRy, jlong paintHandle) { + const Paint* paint = reinterpret_cast<Paint*>(paintHandle); + get_canvas(canvasHandle)->drawDoubleRoundRectXY( + outerLeft, outerTop, outerRight, outerBottom, outerRx, outerRy, + innerLeft, innerTop, innerRight, innerBottom, innerRx, innerRy, *paint); +} + +static void drawDoubleRoundRectRadii(JNIEnv* env, jobject, jlong canvasHandle, jfloat outerLeft, + jfloat outerTop, jfloat outerRight, jfloat outerBottom, jfloatArray jouterRadii, + jfloat innerLeft, jfloat innerTop, jfloat innerRight, + jfloat innerBottom, jfloatArray jinnerRadii, jlong paintHandle) { + const Paint* paint = reinterpret_cast<Paint*>(paintHandle); + + float outerRadii[8]; + float innerRadii[8]; + env->GetFloatArrayRegion(jouterRadii, 0, 8, outerRadii); + env->GetFloatArrayRegion(jinnerRadii, 0, 8, innerRadii); + get_canvas(canvasHandle)->drawDoubleRoundRectRadii( + outerLeft, outerTop, outerRight, outerBottom, outerRadii, + innerLeft, innerTop, innerRight, innerBottom, innerRadii, *paint); + +} + static void drawRegion(JNIEnv* env, jobject, jlong canvasHandle, jlong regionHandle, jlong paintHandle) { const SkRegion* region = reinterpret_cast<SkRegion*>(regionHandle); @@ -651,6 +677,8 @@ static const JNINativeMethod gDrawMethods[] = { {"nDrawRect","(JFFFFJ)V", (void*) CanvasJNI::drawRect}, {"nDrawRegion", "(JJJ)V", (void*) CanvasJNI::drawRegion }, {"nDrawRoundRect","(JFFFFFFJ)V", (void*) CanvasJNI::drawRoundRect}, + {"nDrawDoubleRoundRect", "(JFFFFFFFFFFFFJ)V", (void*) CanvasJNI::drawDoubleRoundRectXY}, + {"nDrawDoubleRoundRect", "(JFFFF[FFFFF[FJ)V", (void*) CanvasJNI::drawDoubleRoundRectRadii}, {"nDrawCircle","(JFFFJ)V", (void*) CanvasJNI::drawCircle}, {"nDrawOval","(JFFFFJ)V", (void*) CanvasJNI::drawOval}, {"nDrawArc","(JFFFFFFZJ)V", (void*) CanvasJNI::drawArc}, diff --git a/core/jni/android_hardware_display_DisplayViewport.cpp b/core/jni/android_hardware_display_DisplayViewport.cpp index ab8e685d5441..05f6556bfb35 100644 --- a/core/jni/android_hardware_display_DisplayViewport.cpp +++ b/core/jni/android_hardware_display_DisplayViewport.cpp @@ -40,6 +40,7 @@ static struct { jfieldID deviceWidth; jfieldID deviceHeight; jfieldID uniqueId; + jfieldID type; } gDisplayViewportClassInfo; static struct { @@ -64,6 +65,9 @@ status_t android_hardware_display_DisplayViewport_toNative(JNIEnv* env, jobject viewport->uniqueId = ScopedUtfChars(env, uniqueId).c_str(); } + viewport->type = static_cast<ViewportType>(env->GetIntField(viewportObj, + gDisplayViewportClassInfo.type)); + jobject logicalFrameObj = env->GetObjectField(viewportObj, gDisplayViewportClassInfo.logicalFrame); viewport->logicalLeft = env->GetIntField(logicalFrameObj, gRectClassInfo.left); @@ -108,6 +112,9 @@ int register_android_hardware_display_DisplayViewport(JNIEnv* env) { gDisplayViewportClassInfo.uniqueId = GetFieldIDOrDie(env, gDisplayViewportClassInfo.clazz, "uniqueId", "Ljava/lang/String;"); + gDisplayViewportClassInfo.type = GetFieldIDOrDie(env, + gDisplayViewportClassInfo.clazz, "type", "I"); + clazz = FindClassOrDie(env, "android/graphics/Rect"); gRectClassInfo.left = GetFieldIDOrDie(env, clazz, "left", "I"); gRectClassInfo.top = GetFieldIDOrDie(env, clazz, "top", "I"); diff --git a/services/core/jni/com_android_server_input_InputApplicationHandle.cpp b/core/jni/android_hardware_input_InputApplicationHandle.cpp index 514b6e1c2f70..8ace8da77b2f 100644 --- a/services/core/jni/com_android_server_input_InputApplicationHandle.cpp +++ b/core/jni/android_hardware_input_InputApplicationHandle.cpp @@ -21,7 +21,7 @@ #include <android_runtime/AndroidRuntime.h> #include <utils/threads.h> -#include "com_android_server_input_InputApplicationHandle.h" +#include "android_hardware_input_InputApplicationHandle.h" namespace android { diff --git a/services/core/jni/com_android_server_input_InputApplicationHandle.h b/core/jni/android_hardware_input_InputApplicationHandle.h index c9af711a7586..711561150e51 100644 --- a/services/core/jni/com_android_server_input_InputApplicationHandle.h +++ b/core/jni/android_hardware_input_InputApplicationHandle.h @@ -17,7 +17,9 @@ #ifndef _ANDROID_SERVER_INPUT_APPLICATION_HANDLE_H #define _ANDROID_SERVER_INPUT_APPLICATION_HANDLE_H -#include <inputflinger/InputApplication.h> +#include <string> + +#include <input/InputApplication.h> #include <nativehelper/JNIHelp.h> #include "jni.h" diff --git a/services/core/jni/com_android_server_input_InputWindowHandle.cpp b/core/jni/android_hardware_input_InputWindowHandle.cpp index c13aa38dc133..f4829ad26aef 100644 --- a/services/core/jni/com_android_server_input_InputWindowHandle.cpp +++ b/core/jni/android_hardware_input_InputWindowHandle.cpp @@ -25,8 +25,8 @@ #include <android/graphics/Region.h> #include <ui/Region.h> -#include "com_android_server_input_InputWindowHandle.h" -#include "com_android_server_input_InputApplicationHandle.h" +#include "android_hardware_input_InputWindowHandle.h" +#include "android_hardware_input_InputApplicationHandle.h" namespace android { diff --git a/services/core/jni/com_android_server_input_InputWindowHandle.h b/core/jni/android_hardware_input_InputWindowHandle.h index 44d4620c8b0c..2be267e958ec 100644 --- a/services/core/jni/com_android_server_input_InputWindowHandle.h +++ b/core/jni/android_hardware_input_InputWindowHandle.h @@ -17,7 +17,7 @@ #ifndef _ANDROID_SERVER_INPUT_WINDOW_HANDLE_H #define _ANDROID_SERVER_INPUT_WINDOW_HANDLE_H -#include <inputflinger/InputWindow.h> +#include <input/InputWindow.h> #include <nativehelper/JNIHelp.h> #include "jni.h" diff --git a/core/jni/android_net_LocalSocketImpl.cpp b/core/jni/android_net_LocalSocketImpl.cpp index 6df23f72bdd3..a1f2377041e8 100644 --- a/core/jni/android_net_LocalSocketImpl.cpp +++ b/core/jni/android_net_LocalSocketImpl.cpp @@ -58,6 +58,11 @@ socket_connect_local(JNIEnv *env, jobject object, int ret; int fd; + if (name == NULL) { + jniThrowNullPointerException(env, NULL); + return; + } + fd = jniGetFDFromFileDescriptor(env, fileDescriptor); if (env->ExceptionCheck()) { diff --git a/core/jni/android_os_GraphicsEnvironment.cpp b/core/jni/android_os_GraphicsEnvironment.cpp index e3bec3cb29c9..b70485d9a0ea 100644 --- a/core/jni/android_os_GraphicsEnvironment.cpp +++ b/core/jni/android_os_GraphicsEnvironment.cpp @@ -28,9 +28,12 @@ void setDriverPath(JNIEnv* env, jobject clazz, jstring path) { android::GraphicsEnv::getInstance().setDriverPath(pathChars.c_str()); } -void setAnglePath(JNIEnv* env, jobject clazz, jstring path) { +void setAngleInfo_native(JNIEnv* env, jobject clazz, jstring path, jstring appName, jstring appPref, jboolean devOptIn) { ScopedUtfChars pathChars(env, path); - android::GraphicsEnv::getInstance().setAnglePath(pathChars.c_str()); + ScopedUtfChars appNameChars(env, appName); + ScopedUtfChars appPrefChars(env, appPref); + android::GraphicsEnv::getInstance().setAngleInfo(pathChars.c_str(), appNameChars.c_str(), + appPrefChars.c_str(), devOptIn); } void setLayerPaths_native(JNIEnv* env, jobject clazz, jobject classLoader, jstring layerPaths) { @@ -49,7 +52,7 @@ void setDebugLayers_native(JNIEnv* env, jobject clazz, jstring layers) { const JNINativeMethod g_methods[] = { { "setDriverPath", "(Ljava/lang/String;)V", reinterpret_cast<void*>(setDriverPath) }, - { "setAnglePath", "(Ljava/lang/String;)V", reinterpret_cast<void*>(setAnglePath) }, + { "setAngleInfo", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Z)V", reinterpret_cast<void*>(setAngleInfo_native) }, { "setLayerPaths", "(Ljava/lang/ClassLoader;Ljava/lang/String;)V", reinterpret_cast<void*>(setLayerPaths_native) }, { "setDebugLayers", "(Ljava/lang/String;)V", reinterpret_cast<void*>(setDebugLayers_native) }, }; diff --git a/core/jni/android_view_InputChannel.cpp b/core/jni/android_view_InputChannel.cpp index 0a90b97d55ef..2f179078aed8 100644 --- a/core/jni/android_view_InputChannel.cpp +++ b/core/jni/android_view_InputChannel.cpp @@ -202,17 +202,9 @@ static void android_view_InputChannel_nativeReadFromParcel(JNIEnv* env, jobject if (parcel) { bool isInitialized = parcel->readInt32(); if (isInitialized) { - String8 name = parcel->readString8(); - int rawFd = parcel->readFileDescriptor(); - int dupFd = dup(rawFd); - if (dupFd < 0) { - ALOGE("Error %d dup channel fd %d.", errno, rawFd); - jniThrowRuntimeException(env, - "Could not read input channel file descriptors from parcel."); - return; - } - - InputChannel* inputChannel = new InputChannel(name.string(), dupFd); + InputChannel* inputChannel = new InputChannel(); + inputChannel->read(*parcel); + NativeInputChannel* nativeInputChannel = new NativeInputChannel(inputChannel); android_view_InputChannel_setNativeInputChannel(env, obj, nativeInputChannel); @@ -230,8 +222,7 @@ static void android_view_InputChannel_nativeWriteToParcel(JNIEnv* env, jobject o sp<InputChannel> inputChannel = nativeInputChannel->getInputChannel(); parcel->writeInt32(1); - parcel->writeString8(String8(inputChannel->getName().c_str())); - parcel->writeDupFileDescriptor(inputChannel->getFd()); + inputChannel->write(*parcel); } else { parcel->writeInt32(0); } diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp index 364393e1c649..1f958628374d 100644 --- a/core/jni/com_android_internal_os_Zygote.cpp +++ b/core/jni/com_android_internal_os_Zygote.cpp @@ -382,11 +382,10 @@ static int UnmountTree(const char* path) { return 0; } -static bool createPkgSandbox(uid_t uid, const char* package_name, std::string& pkg_sandbox_dir, - std::string* error_msg) { +static bool createPkgSandbox(uid_t uid, const std::string& package_name, std::string* error_msg) { // Create /mnt/user/0/package/<package-name> userid_t user_id = multiuser_get_user_id(uid); - StringAppendF(&pkg_sandbox_dir, "/%d", user_id); + std::string pkg_sandbox_dir = StringPrintf("/mnt/user/%d", user_id); if (fs_prepare_dir(pkg_sandbox_dir.c_str(), 0751, AID_ROOT, AID_ROOT) != 0) { *error_msg = CREATE_ERROR("fs_prepare_dir failed on %s", pkg_sandbox_dir.c_str()); return false; @@ -396,7 +395,7 @@ static bool createPkgSandbox(uid_t uid, const char* package_name, std::string& p *error_msg = CREATE_ERROR("fs_prepare_dir failed on %s", pkg_sandbox_dir.c_str()); return false; } - StringAppendF(&pkg_sandbox_dir, "/%s", package_name); + StringAppendF(&pkg_sandbox_dir, "/%s", package_name.c_str()); if (fs_prepare_dir(pkg_sandbox_dir.c_str(), 0755, uid, uid) != 0) { *error_msg = CREATE_ERROR("fs_prepare_dir failed on %s", pkg_sandbox_dir.c_str()); return false; @@ -404,10 +403,51 @@ static bool createPkgSandbox(uid_t uid, const char* package_name, std::string& p return true; } +static bool mountPkgSpecificDir(const std::string& mntSourceRoot, + const std::string& mntTargetRoot, const std::string& packageName, + const char* dirName, std::string* error_msg) { + std::string mntSourceDir = StringPrintf("%s/Android/%s/%s", + mntSourceRoot.c_str(), dirName, packageName.c_str()); + std::string mntTargetDir = StringPrintf("%s/Android/%s/%s", + mntTargetRoot.c_str(), dirName, packageName.c_str()); + if (TEMP_FAILURE_RETRY(mount(mntSourceDir.c_str(), mntTargetDir.c_str(), + nullptr, MS_BIND | MS_REC, nullptr)) == -1) { + *error_msg = CREATE_ERROR("Failed to mount %s to %s: %s", + mntSourceDir.c_str(), mntTargetDir.c_str(), strerror(errno)); + return false; + } + if (TEMP_FAILURE_RETRY(mount(nullptr, mntTargetDir.c_str(), + nullptr, MS_SLAVE | MS_REC, nullptr)) == -1) { + *error_msg = CREATE_ERROR("Failed to set MS_SLAVE for %s", mntTargetDir.c_str()); + return false; + } + return true; +} + +static bool preparePkgSpecificDirs(const std::vector<std::string>& packageNames, + const std::vector<std::string>& volumeLabels, userid_t userId, std::string* error_msg) { + for (auto& label : volumeLabels) { + std::string mntSource = StringPrintf("/mnt/runtime/write/%s", label.c_str()); + std::string mntTarget = StringPrintf("/storage/%s", label.c_str()); + if (label == "emulated") { + StringAppendF(&mntSource, "/%d", userId); + StringAppendF(&mntTarget, "/%d", userId); + } + for (auto& package : packageNames) { + mountPkgSpecificDir(mntSource, mntTarget, package, "data", error_msg); + mountPkgSpecificDir(mntSource, mntTarget, package, "media", error_msg); + mountPkgSpecificDir(mntSource, mntTarget, package, "obb", error_msg); + } + } + return true; +} + // Create a private mount namespace and bind mount appropriate emulated // storage for the given user. static bool MountEmulatedStorage(uid_t uid, jint mount_mode, - bool force_mount_namespace, std::string* error_msg, const char* package_name) { + bool force_mount_namespace, std::string* error_msg, const std::string& package_name, + const std::vector<std::string>& packages_for_uid, + const std::vector<std::string>& visible_vol_ids) { // See storage config details at http://source.android.com/tech/storage/ String8 storageSource; @@ -459,12 +499,25 @@ static bool MountEmulatedStorage(uid_t uid, jint mount_mode, return false; } } else { - if (package_name == nullptr) { + if (package_name.empty()) { return true; } - std::string pkgSandboxDir("/mnt/user"); - if (!createPkgSandbox(uid, package_name, pkgSandboxDir, error_msg)) { - return false; + userid_t user_id = multiuser_get_user_id(uid); + std::string pkgSandboxDir = StringPrintf("/mnt/user/%d/package/%s", + user_id, package_name.c_str()); + struct stat sb; + bool sandboxAlreadyCreated = true; + if (TEMP_FAILURE_RETRY(lstat(pkgSandboxDir.c_str(), &sb)) == -1) { + if (errno == ENOENT) { + ALOGD("Sandbox not yet created for %s", pkgSandboxDir.c_str()); + sandboxAlreadyCreated = false; + if (!createPkgSandbox(uid, package_name, error_msg)) { + return false; + } + } else { + ALOGE("Failed to lstat %s", pkgSandboxDir.c_str()); + return false; + } } if (TEMP_FAILURE_RETRY(mount(pkgSandboxDir.c_str(), "/storage", nullptr, MS_BIND | MS_REC | MS_SLAVE, nullptr)) == -1) { @@ -472,6 +525,15 @@ static bool MountEmulatedStorage(uid_t uid, jint mount_mode, pkgSandboxDir.c_str(), strerror(errno)); return false; } + // If the sandbox was already created by vold, only then set up the bind mounts for + // pkg specific directories. Otherwise, leave as is and bind mounts will be taken + // care of by vold later. + if (sandboxAlreadyCreated) { + if (!preparePkgSpecificDirs(packages_for_uid, visible_vol_ids, + user_id, error_msg)) { + return false; + } + } } } else { if (TEMP_FAILURE_RETRY(mount(storageSource.string(), "/storage", @@ -611,7 +673,8 @@ static void SpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArray javaGi jlong permittedCapabilities, jlong effectiveCapabilities, jint mount_external, jstring java_se_info, jstring java_se_name, bool is_system_server, bool is_child_zygote, jstring instructionSet, - jstring dataDir, jstring packageName) { + jstring dataDir, jstring packageName, jobjectArray packagesForUid, + jobjectArray visibleVolIds) { std::string error_msg; auto fail_fn = [env, java_se_name, is_system_server](const std::string& msg) @@ -661,17 +724,33 @@ static void SpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArray javaGi ALOGW("Native bridge will not be used because dataDir == NULL."); } - ScopedUtfChars* package_name = nullptr; - const char* package_name_c_str = nullptr; + std::string package_name_str(""); if (packageName != nullptr) { - package_name = new ScopedUtfChars(env, packageName); - package_name_c_str = package_name->c_str(); + ScopedUtfChars package(env, packageName); + package_name_str = package.c_str(); } else if (is_system_server) { - package_name_c_str = "android"; + package_name_str = "android"; + } + std::vector<std::string> packages_for_uid; + if (packagesForUid != nullptr) { + jsize count = env->GetArrayLength(packagesForUid); + for (jsize i = 0; i < count; ++i) { + jstring package_for_uid = (jstring) env->GetObjectArrayElement(packagesForUid, i); + ScopedUtfChars package(env, package_for_uid); + packages_for_uid.push_back(package.c_str()); + } + } + std::vector<std::string> visible_vol_ids; + if (visibleVolIds != nullptr) { + jsize count = env->GetArrayLength(visibleVolIds); + for (jsize i = 0; i < count; ++i) { + jstring visible_vol_id = (jstring) env->GetObjectArrayElement(visibleVolIds, i); + ScopedUtfChars vol(env, visible_vol_id); + visible_vol_ids.push_back(vol.c_str()); + } } bool success = MountEmulatedStorage(uid, mount_external, use_native_bridge, &error_msg, - package_name_c_str); - delete package_name; + package_name_str, packages_for_uid, visible_vol_ids); if (!success) { ALOGW("Failed to mount emulated storage: %s (%s)", error_msg.c_str(), strerror(errno)); if (errno == ENOTCONN || errno == EROFS) { @@ -936,7 +1015,8 @@ static jint com_android_internal_os_Zygote_nativeForkAndSpecialize( jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jstring se_name, jintArray fdsToClose, jintArray fdsToIgnore, jboolean is_child_zygote, - jstring instructionSet, jstring appDataDir, jstring packageName) { + jstring instructionSet, jstring appDataDir, jstring packageName, + jobjectArray packagesForUid, jobjectArray visibleVolIds) { jlong capabilities = 0; // Grant CAP_WAKE_ALARM to the Bluetooth process. @@ -989,7 +1069,8 @@ static jint com_android_internal_os_Zygote_nativeForkAndSpecialize( SpecializeCommon(env, uid, gid, gids, runtime_flags, rlimits, capabilities, capabilities, mount_external, se_info, se_name, false, - is_child_zygote == JNI_TRUE, instructionSet, appDataDir, packageName); + is_child_zygote == JNI_TRUE, instructionSet, appDataDir, packageName, + packagesForUid, visibleVolIds); } return pid; } @@ -1003,7 +1084,7 @@ static jint com_android_internal_os_Zygote_nativeForkSystemServer( SpecializeCommon(env, uid, gid, gids, runtime_flags, rlimits, permittedCapabilities, effectiveCapabilities, MOUNT_EXTERNAL_DEFAULT, NULL, NULL, true, - false, NULL, NULL, nullptr); + false, NULL, NULL, nullptr, nullptr, nullptr); } else if (pid > 0) { // The zygote process checks whether the child process has died or not. ALOGI("System server process %d has been created", pid); @@ -1084,7 +1165,7 @@ static const JNINativeMethod gMethods[] = { { "nativeSecurityInit", "()V", (void *) com_android_internal_os_Zygote_nativeSecurityInit }, { "nativeForkAndSpecialize", - "(II[II[[IILjava/lang/String;Ljava/lang/String;[I[IZLjava/lang/String;Ljava/lang/String;Ljava/lang/String;)I", + "(II[II[[IILjava/lang/String;Ljava/lang/String;[I[IZLjava/lang/String;Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;[Ljava/lang/String;)I", (void *) com_android_internal_os_Zygote_nativeForkAndSpecialize }, { "nativeForkSystemServer", "(II[II[[IJJ)I", (void *) com_android_internal_os_Zygote_nativeForkSystemServer }, diff --git a/core/proto/android/internal/powerprofile.proto b/core/proto/android/internal/powerprofile.proto new file mode 100644 index 000000000000..9dd5e7ee556c --- /dev/null +++ b/core/proto/android/internal/powerprofile.proto @@ -0,0 +1,111 @@ +/* + * 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"; +package com.android.internal.os; + +option java_multiple_files = true; + +// next: 41 +message PowerProfileProto { + optional double cpu_suspend = 1; + + optional double cpu_idle = 2; + + optional double cpu_active = 3; + + message CpuCluster { + optional int32 id = 1; + optional double cluster_power = 2; + optional int32 cores = 3; + repeated int64 speed = 4; + repeated double core_power = 5; + } + + repeated CpuCluster cpu_cluster = 41; + + optional double wifi_scan = 4; + + optional double wifi_on = 5; + + optional double wifi_active = 6; + + optional double wifi_controller_idle = 7; + + optional double wifi_controller_rx = 8; + + optional double wifi_controller_tx = 9; + + repeated double wifi_controller_tx_levels = 10; + + optional double wifi_controller_operating_voltage = 11; + + optional double bluetooth_controller_idle = 12; + + optional double bluetooth_controller_rx = 13; + + optional double bluetooth_controller_tx = 14; + + optional double bluetooth_controller_operating_voltage = 15; + + optional double modem_controller_sleep = 16; + + optional double modem_controller_idle = 17; + + optional double modem_controller_rx = 18; + + repeated double modem_controller_tx = 19; + + optional double modem_controller_operating_voltage = 20; + + optional double gps_on = 21; + + repeated double gps_signal_quality_based = 22; + + optional double gps_operating_voltage = 23; + + optional double bluetooth_on = 24; + + optional double bluetooth_active = 25; + + optional double bluetooth_at_cmd = 26; + + optional double ambient_display = 27; + + optional double screen_on = 29; + + optional double radio_on = 30; + + optional double radio_scanning = 31; + + optional double radio_active = 32; + + optional double screen_full = 33; + + optional double audio = 34; + + optional double video = 35; + + optional double flashlight = 36; + + optional double memory = 37; + + optional double camera = 38; + + optional double wifi_batched_scan = 39; + + optional double battery_capacity = 40; +} diff --git a/core/proto/android/providers/settings/global.proto b/core/proto/android/providers/settings/global.proto index f9f725a130ed..14ed9e6eeadb 100644 --- a/core/proto/android/providers/settings/global.proto +++ b/core/proto/android/providers/settings/global.proto @@ -397,6 +397,9 @@ message GlobalSettingsProto { // Ordered GPU debug layer list // i.e. <layer1>:<layer2>:...:<layerN> optional SettingProto debug_layers = 2 [ (android.privacy).dest = DEST_AUTOMATIC ]; + + // App will load ANGLE instead of native GLES drivers. + optional SettingProto angle_enabled_app = 3; } optional Gpu gpu = 59; diff --git a/core/proto/android/server/usagestatsservice.proto b/core/proto/android/server/usagestatsservice.proto new file mode 100644 index 000000000000..941c81fbb8df --- /dev/null +++ b/core/proto/android/server/usagestatsservice.proto @@ -0,0 +1,100 @@ +/* + * 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"; +package com.android.server.usage; +import "frameworks/base/core/proto/android/content/configuration.proto"; +import "frameworks/base/libs/incident/proto/android/privacy.proto"; + +option java_multiple_files = true; + +message IntervalStatsProto { + message StringPool { + optional int32 size = 1; + repeated string strings = 2; + } + + message CountAndTime { + optional int32 count = 1; + optional int64 time_ms = 2; + } + + // Stores the relevant information from a UsageStats + message UsageStats { + message ChooserAction { + message CategoryCount { + optional string name = 1; + optional int32 count = 3; + } + optional string name = 1; + repeated CategoryCount counts = 3; + } + optional string package = 1; + // package_index contains the index + 1 of the package name in the string pool + optional int32 package_index = 2; + optional int64 last_time_active_ms = 3; + optional int64 total_time_active_ms = 4; + optional int32 last_event = 5; + optional int32 app_launch_count = 6; + repeated ChooserAction chooser_actions = 7; + } + + // Stores the relevant information an IntervalStats will have about a Configuration + message Configuration { + optional .android.content.ConfigurationProto config = 1; + optional int64 last_time_active_ms = 2; + optional int64 total_time_active_ms = 3; + optional int32 count = 4; + optional bool active = 5; + } + + // Stores the relevant information from a UsageEvents.Event + message Event { + optional string package = 1; + // package_index contains the index + 1 of the package name in the string pool + optional int32 package_index = 2; + optional string class = 3; + // class_index contains the index + 1 of the class name in the string pool + optional int32 class_index = 4; + optional int64 time_ms = 5; + optional int32 flags = 6; + optional int32 type = 7; + optional .android.content.ConfigurationProto config = 8; + optional string shortcut_id = 9; + optional int32 standby_bucket = 11; + optional string notification_channel = 12; + // notification_channel_index contains the index + 1 of the channel name in the string pool + optional int32 notification_channel_index = 13; + } + + // The following fields contain supplemental data used to build IntervalStats, such as a string + // pool. + optional int64 end_time_ms = 1; + // stringpool contains all the package and class names used by UsageStats and Event + // They will hold a number that is equal to the index + 1 of their string in the pool + optional StringPool stringpool = 2; + + // The following fields contain aggregated usage stats data + optional CountAndTime interactive = 10; + optional CountAndTime non_interactive = 11; + optional CountAndTime keyguard_shown = 12; + optional CountAndTime keyguard_hidden = 13; + + // The following fields contain listed usage stats data + repeated UsageStats packages = 20; + repeated Configuration configurations = 21; + repeated Event event_log = 22; +} diff --git a/core/proto/android/server/windowmanagerservice.proto b/core/proto/android/server/windowmanagerservice.proto index d33ea0ce503d..c1c86f088873 100644 --- a/core/proto/android/server/windowmanagerservice.proto +++ b/core/proto/android/server/windowmanagerservice.proto @@ -158,6 +158,7 @@ message DisplayContentProto { optional ScreenRotationAnimationProto screen_rotation_animation = 12; optional DisplayFramesProto display_frames = 13; optional int32 surface_size = 14; + optional string focused_app = 15; } /* represents DisplayFrames */ diff --git a/core/proto/android/view/displaycutout.proto b/core/proto/android/view/displaycutout.proto index f4744daa4285..0a3310148145 100644 --- a/core/proto/android/view/displaycutout.proto +++ b/core/proto/android/view/displaycutout.proto @@ -26,5 +26,9 @@ message DisplayCutoutProto { option (.android.msg_privacy).dest = DEST_AUTOMATIC; optional .android.graphics.RectProto insets = 1; - optional .android.graphics.RectProto bounds = 2; + reserved 2; + optional .android.graphics.RectProto bound_left = 3; + optional .android.graphics.RectProto bound_top = 4; + optional .android.graphics.RectProto bound_right = 5; + optional .android.graphics.RectProto bound_bottom = 6; } diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 85a52d5f714f..f3f012de53be 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -941,7 +941,6 @@ Requesting this by itself is not sufficient to give you location access. <p>Protection level: dangerous - @hide --> <permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" android:permissionGroup="android.permission-group.LOCATION" @@ -1101,7 +1100,7 @@ <!-- Allows a calling application which manages it own calls through the self-managed {@link android.telecom.ConnectionService} APIs. See - {@link android.telecom.PhoneAccount#CAPABILITY_SELF_MANAGED for more information on the + {@link android.telecom.PhoneAccount#CAPABILITY_SELF_MANAGED} for more information on the self-managed ConnectionService APIs. <p>Protection level: normal --> @@ -1186,9 +1185,9 @@ android:priority="700" /> <!-- Required to be able to access the camera device. - <p>This will automatically enforce the <a - href="{@docRoot}guide/topics/manifest/uses-feature-element.html"> - <uses-feature>}</a> manifest element for <em>all</em> camera features. + <p>This will automatically enforce the + <a href="{@docRoot}guide/topics/manifest/uses-feature-element.html"> + uses-feature</a> manifest element for <em>all</em> camera features. If you do not require all camera features or can properly operate if a camera is not available, then you must modify your manifest as appropriate in order to install on devices that don't support all camera features.</p> @@ -1933,6 +1932,13 @@ <permission android:name="android.permission.BIND_SCREENING_SERVICE" android:protectionLevel="signature|privileged" /> + <!-- Must be required by a {@link android.telecom.CallRedirectionService}, + to ensure that only the system can bind to it. + <p>Protection level: signature|privileged + --> + <permission android:name="android.permission.BIND_CALL_REDIRECTION_SERVICE" + android:protectionLevel="signature|privileged" /> + <!-- Must be required by a {@link android.telecom.ConnectionService}, to ensure that only the system can bind to it. @deprecated {@link android.telecom.ConnectionService}s should require @@ -3407,6 +3413,12 @@ <permission android:name="android.permission.DEVICE_POWER" android:protectionLevel="signature" /> + <!-- Allows toggling battery saver on the system. + Superseded by DEVICE_POWER permission. @hide @SystemApi + --> + <permission android:name="android.permission.POWER_SAVER" + android:protectionLevel="signature|privileged" /> + <!-- Allows access to the PowerManager.userActivity function. <p>Not for use by third-party applications. @hide @SystemApi --> <permission android:name="android.permission.USER_ACTIVITY" @@ -4148,6 +4160,11 @@ <permission android:name="android.permission.BIND_SMS_APP_SERVICE" android:protectionLevel="signature" /> + <!-- @hide Permission that allows background clipboard access. + <p>Not for use by third-party applications. --> + <permission android:name="android.permission.READ_CLIPBOARD_IN_BACKGROUND" + android:protectionLevel="signature" /> + <application android:process="system" android:persistent="true" android:hasCode="false" diff --git a/core/res/res/values-hi/strings.xml b/core/res/res/values-hi/strings.xml index 6ae25417e386..bebd4896e89b 100644 --- a/core/res/res/values-hi/strings.xml +++ b/core/res/res/values-hi/strings.xml @@ -531,7 +531,7 @@ <skip /> <string name="fingerprint_authenticated" msgid="5309333983002526448">"फ़िंगरप्रिंट की पुष्टि हो गई"</string> <string name="face_authenticated_no_confirmation_required" msgid="4018680978348659031">"चेहरे की पहचान की गई"</string> - <string name="face_authenticated_confirmation_required" msgid="8778347003507633610">"चेहरा की पहचान की गई, कृपया पुष्टि बटन दबाएं"</string> + <string name="face_authenticated_confirmation_required" msgid="8778347003507633610">"चेहरे की पहचान की गई, कृपया पुष्टि बटन दबाएं"</string> <string name="fingerprint_error_hw_not_available" msgid="7955921658939936596">"फ़िंगरप्रिंट हार्डवेयर उपलब्ध नहीं है."</string> <string name="fingerprint_error_no_space" msgid="1055819001126053318">"फ़िंगरप्रिंट को संग्रहित नहीं किया जा सका. कृपया कोई मौजूदा फ़िंगरप्रिंट निकालें."</string> <string name="fingerprint_error_timeout" msgid="3927186043737732875">"फ़िंगरप्रिंट का समय समाप्त हो गया. पुनः प्रयास करें."</string> diff --git a/core/res/res/values-night/values.xml b/core/res/res/values-night/values.xml index 45cf0f0fedb3..a2ad3b90e464 100644 --- a/core/res/res/values-night/values.xml +++ b/core/res/res/values-night/values.xml @@ -32,6 +32,8 @@ <item name="panelColorBackground">@color/material_grey_800</item> </style> + <style name="Theme.DeviceDefault.QuickSettings.Dialog" parent="Theme.DeviceDefault.Dialog" /> + <style name="TextAppearance.Material.Notification"> <item name="textColor">@color/notification_secondary_text_color_dark</item> <item name="textSize">@dimen/notification_text_size</item> diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml index 41616df4209d..912db2001851 100644 --- a/core/res/res/values-tr/strings.xml +++ b/core/res/res/values-tr/strings.xml @@ -529,7 +529,7 @@ <string name="biometric_not_recognized" msgid="5770511773560736082">"Tanınmadı"</string> <string name="fingerprint_authenticated" msgid="5309333983002526448">"Parmak izi kimlik doğrulaması yapıldı"</string> <string name="face_authenticated_no_confirmation_required" msgid="4018680978348659031">"Yüz kimliği doğrulandı"</string> - <string name="face_authenticated_confirmation_required" msgid="8778347003507633610">"Yüz kimliği doğrulandı, lütfen doğrula\'ya basın"</string> + <string name="face_authenticated_confirmation_required" msgid="8778347003507633610">"Yüz kimliği doğrulandı, lütfen onayla\'ya basın"</string> <string name="fingerprint_error_hw_not_available" msgid="7955921658939936596">"Parmak izi donanımı kullanılamıyor."</string> <string name="fingerprint_error_no_space" msgid="1055819001126053318">"Parmak izi depolanamıyor. Lütfen mevcut parmak izlerinden birini kaldırın."</string> <string name="fingerprint_error_timeout" msgid="3927186043737732875">"Parmak izi için zaman aşımı oluştu. Tekrar deneyin."</string> diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 65b88076abf0..cdb65edd81e7 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -1425,7 +1425,7 @@ at {@link android.view.inputmethod.InputConnection#performEditorAction(int) InputConnection.performEditorAction(int)}. <p>Corresponds to - {@link android.view.inputmethod.EditorInfo#IME_FLAG_NO_FULLSCREEN}. --> + {@link android.view.inputmethod.EditorInfo#IME_FLAG_NAVIGATE_PREVIOUS}. --> <flag name="flagNavigatePrevious" value="0x4000000" /> <!-- Used to specify that there is something interesting that a forward navigation can focus on. This is like using @@ -4184,9 +4184,9 @@ row is full. The rowCount attribute may be used similarly in the vertical case. The default is horizontal. --> <attr name="orientation" /> - <!-- The maxmimum number of rows to create when automatically positioning children. --> + <!-- The maximum number of rows to create when automatically positioning children. --> <attr name="rowCount" format="integer" /> - <!-- The maxmimum number of columns to create when automatically positioning children. --> + <!-- The maximum number of columns to create when automatically positioning children. --> <attr name="columnCount" format="integer" /> <!-- When set to true, tells GridLayout to use default margins when none are specified in a view's layout parameters. diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml index 3c0e51ed4cdd..8ff29ba4614c 100644 --- a/core/res/res/values/attrs_manifest.xml +++ b/core/res/res/values/attrs_manifest.xml @@ -1102,6 +1102,10 @@ <p>The default value of this attribute is <code>false</code>. --> <attr name="isFeatureSplit" format="boolean" /> + <!-- Flag to specify if this APK requires at least one split [either feature or + resource] to be present in order to function. Default value is false. --> + <attr name="isSplitRequired" format="boolean" /> + <!-- Extra options for an activity's UI. Applies to either the {@code <activity>} or {@code <application>} tag. If specified on the {@code <application>} tag these will be considered defaults for all activities in the @@ -1422,6 +1426,7 @@ <attr name="targetSandboxVersion" /> <attr name="compileSdkVersion" /> <attr name="compileSdkVersionCodename" /> + <attr name="isSplitRequired" /> </declare-styleable> <!-- The <code>application</code> tag describes application-level components diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 9aebf6c4597f..8b73b6768479 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -1527,7 +1527,7 @@ <bool name="config_checkWallpaperAtBoot">true</bool> <!-- Class name of WallpaperManagerService. --> - <string name="config_wallpaperManagerServiceName">com.android.server.wallpaper.WallpaperManagerService</string> + <string name="config_wallpaperManagerServiceName" translatable="false">com.android.server.wallpaper.WallpaperManagerService</string> <!-- Enables the TimeZoneRuleManager service. This is the master switch for the updateable time zone update mechanism. --> @@ -2113,8 +2113,8 @@ <!-- Type of the long press sensor. Empty if long press is not supported. --> <string name="config_dozeLongPressSensorType" translatable="false"></string> - <!-- Type of the reach sensor. Empty if reach is not supported. --> - <string name="config_dozeReachSensorType" translatable="false"></string> + <!-- Type of sensor that wakes up the lock screen. Empty if not supported. --> + <string name="config_dozeWakeLockScreenSensorType" translatable="false"></string> <!-- Type of the wake up sensor. Empty if not supported. --> <string name="config_dozeWakeScreenSensorType" translatable="false"></string> @@ -3550,4 +3550,7 @@ <!-- Pre-scale volume at volume step 3 for Absolute Volume --> <fraction name="config_prescaleAbsoluteVolume_index3">85%</fraction> + + <!-- Whether or not the "SMS app service" feature is enabled --> + <bool name="config_useSmsAppService">true</bool> </resources> diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index cdaff18cf38a..fadefff6a030 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -2913,6 +2913,7 @@ <public name="usesNonSdkApi" /> <public name="minimumUiTimeout" /> <public name="isLightTheme" /> + <public name="isSplitRequired" /> </public-group> <public-group type="drawable" first-id="0x010800b4"> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 9f2256a6461a..a7b6dde12628 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -3279,7 +3279,7 @@ <java-symbol type="array" name="config_hideWhenDisabled_packageNames" /> <java-symbol type="string" name="config_dozeLongPressSensorType" /> - <java-symbol type="string" name="config_dozeReachSensorType" /> + <java-symbol type="string" name="config_dozeWakeLockScreenSensorType" /> <java-symbol type="array" name="config_allowedGlobalInstantAppSettings" /> <java-symbol type="array" name="config_allowedSystemInstantAppSettings" /> @@ -3475,4 +3475,6 @@ <java-symbol type="fraction" name="config_prescaleAbsoluteVolume_index1" /> <java-symbol type="fraction" name="config_prescaleAbsoluteVolume_index2" /> <java-symbol type="fraction" name="config_prescaleAbsoluteVolume_index3" /> + + <java-symbol type="bool" name="config_useSmsAppService" /> </resources> diff --git a/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java b/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java index 3ce258969822..6fdb71fcbab0 100644 --- a/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java +++ b/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java @@ -24,6 +24,7 @@ import static org.junit.Assert.assertTrue; import android.content.Context; import android.content.res.AssetManager; import android.graphics.fonts.Font; +import android.graphics.fonts.FontCustomizationParser; import android.graphics.fonts.FontFamily; import android.graphics.fonts.SystemFonts; import android.support.test.InstrumentationRegistry; @@ -36,12 +37,15 @@ import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.xmlpull.v1.XmlPullParserException; +import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.StandardCopyOption; import java.util.ArrayList; @@ -62,6 +66,8 @@ public class TypefaceSystemFallbackTest { }; private static final String TEST_FONTS_XML; private static final String TEST_FONT_DIR; + private static final String TEST_OEM_XML; + private static final String TEST_OEM_DIR; private static final float GLYPH_1EM_WIDTH; private static final float GLYPH_2EM_WIDTH; @@ -73,8 +79,13 @@ public class TypefaceSystemFallbackTest { if (!cacheDir.isDirectory()) { cacheDir.mkdirs(); } - TEST_FONT_DIR = cacheDir.getAbsolutePath() + "/"; + TEST_FONT_DIR = cacheDir.getAbsolutePath() + "/fonts/"; TEST_FONTS_XML = new File(cacheDir, "fonts.xml").getAbsolutePath(); + TEST_OEM_DIR = cacheDir.getAbsolutePath() + "/oem_fonts/"; + TEST_OEM_XML = new File(cacheDir, "fonts_customization.xml").getAbsolutePath(); + + new File(TEST_FONT_DIR).mkdirs(); + new File(TEST_OEM_DIR).mkdirs(); final AssetManager am = InstrumentationRegistry.getInstrumentation().getContext().getAssets(); @@ -99,6 +110,12 @@ public class TypefaceSystemFallbackTest { } catch (IOException e) { throw new RuntimeException(e); } + final File outOemInCache = new File(TEST_OEM_DIR, fontFile); + try (InputStream is = am.open(sourceInAsset)) { + Files.copy(is, outOemInCache.toPath(), StandardCopyOption.REPLACE_EXISTING); + } catch (IOException e) { + throw new RuntimeException(e); + } } } @@ -107,11 +124,14 @@ public class TypefaceSystemFallbackTest { for (final String fontFile : TEST_FONT_FILES) { final File outInCache = new File(TEST_FONT_DIR, fontFile); outInCache.delete(); + final File outOemInCache = new File(TEST_OEM_DIR, fontFile); + outInCache.delete(); } } private static void buildSystemFallback(String xml, - ArrayMap<String, Typeface> fontMap, ArrayMap<String, FontFamily[]> fallbackMap) { + FontCustomizationParser.Result oemCustomization, ArrayMap<String, Typeface> fontMap, + ArrayMap<String, FontFamily[]> fallbackMap) { final ArrayList<Font> availableFonts = new ArrayList<>(); try (FileOutputStream fos = new FileOutputStream(TEST_FONTS_XML)) { fos.write(xml.getBytes(Charset.forName("UTF-8"))); @@ -119,18 +139,28 @@ public class TypefaceSystemFallbackTest { throw new RuntimeException(e); } final FontConfig.Alias[] aliases = SystemFonts.buildSystemFallback(TEST_FONTS_XML, - TEST_FONT_DIR, fallbackMap, availableFonts); + TEST_FONT_DIR, oemCustomization, fallbackMap, availableFonts); Typeface.initSystemDefaultTypefaces(fontMap, fallbackMap, aliases); } + private static FontCustomizationParser.Result readFontCustomization(String oemXml) { + try (InputStream is = new ByteArrayInputStream(oemXml.getBytes(StandardCharsets.UTF_8))) { + return FontCustomizationParser.parse(is, TEST_OEM_DIR); + } catch (IOException | XmlPullParserException e) { + throw new RuntimeException(e); + } + } + @Test public void testBuildSystemFallback() { final ArrayMap<String, Typeface> fontMap = new ArrayMap<>(); final ArrayMap<String, FontFamily[]> fallbackMap = new ArrayMap<>(); final ArrayList<Font> availableFonts = new ArrayList<>(); + final FontCustomizationParser.Result oemCustomization = + new FontCustomizationParser.Result(); final FontConfig.Alias[] aliases = SystemFonts.buildSystemFallback(SYSTEM_FONTS_XML, - SYSTEM_FONT_DIR, fallbackMap, availableFonts); + SYSTEM_FONT_DIR, oemCustomization, fallbackMap, availableFonts); assertNotNull(aliases); assertFalse(fallbackMap.isEmpty()); @@ -156,8 +186,10 @@ public class TypefaceSystemFallbackTest { + "</familyset>"; final ArrayMap<String, Typeface> fontMap = new ArrayMap<>(); final ArrayMap<String, FontFamily[]> fallbackMap = new ArrayMap<>(); + final FontCustomizationParser.Result oemCustomization = + new FontCustomizationParser.Result(); - buildSystemFallback(xml, fontMap, fallbackMap); + buildSystemFallback(xml, oemCustomization, fontMap, fallbackMap); assertEquals(1, fontMap.size()); assertTrue(fontMap.containsKey("sans-serif")); @@ -184,8 +216,10 @@ public class TypefaceSystemFallbackTest { + "</familyset>"; final ArrayMap<String, Typeface> fontMap = new ArrayMap<>(); final ArrayMap<String, FontFamily[]> fallbackMap = new ArrayMap<>(); + final FontCustomizationParser.Result oemCustomization = + new FontCustomizationParser.Result(); - buildSystemFallback(xml, fontMap, fallbackMap); + buildSystemFallback(xml, oemCustomization, fontMap, fallbackMap); final Paint paint = new Paint(); @@ -230,8 +264,10 @@ public class TypefaceSystemFallbackTest { + "</familyset>"; final ArrayMap<String, Typeface> fontMap = new ArrayMap<>(); final ArrayMap<String, FontFamily[]> fallbackMap = new ArrayMap<>(); + final FontCustomizationParser.Result oemCustomization = + new FontCustomizationParser.Result(); - buildSystemFallback(xml, fontMap, fallbackMap); + buildSystemFallback(xml, oemCustomization, fontMap, fallbackMap); final Paint paint = new Paint(); @@ -275,8 +311,10 @@ public class TypefaceSystemFallbackTest { + "</familyset>"; final ArrayMap<String, Typeface> fontMap = new ArrayMap<>(); final ArrayMap<String, FontFamily[]> fallbackMap = new ArrayMap<>(); + final FontCustomizationParser.Result oemCustomization = + new FontCustomizationParser.Result(); - buildSystemFallback(xml, fontMap, fallbackMap); + buildSystemFallback(xml, oemCustomization, fontMap, fallbackMap); final Paint paint = new Paint(); @@ -325,8 +363,10 @@ public class TypefaceSystemFallbackTest { + "</familyset>"; final ArrayMap<String, Typeface> fontMap = new ArrayMap<>(); final ArrayMap<String, FontFamily[]> fallbackMap = new ArrayMap<>(); + final FontCustomizationParser.Result oemCustomization = + new FontCustomizationParser.Result(); - buildSystemFallback(xml, fontMap, fallbackMap); + buildSystemFallback(xml, oemCustomization, fontMap, fallbackMap); final Paint paint = new Paint(); @@ -371,8 +411,10 @@ public class TypefaceSystemFallbackTest { + "</familyset>"; final ArrayMap<String, Typeface> fontMap = new ArrayMap<>(); final ArrayMap<String, FontFamily[]> fallbackMap = new ArrayMap<>(); + final FontCustomizationParser.Result oemCustomization = + new FontCustomizationParser.Result(); - buildSystemFallback(xml, fontMap, fallbackMap); + buildSystemFallback(xml, oemCustomization, fontMap, fallbackMap); final Paint paint = new Paint(); @@ -410,8 +452,10 @@ public class TypefaceSystemFallbackTest { + "</familyset>"; final ArrayMap<String, Typeface> fontMap = new ArrayMap<>(); final ArrayMap<String, FontFamily[]> fallbackMap = new ArrayMap<>(); + final FontCustomizationParser.Result oemCustomization = + new FontCustomizationParser.Result(); - buildSystemFallback(xml, fontMap, fallbackMap); + buildSystemFallback(xml, oemCustomization, fontMap, fallbackMap); final Paint paint = new Paint(); @@ -449,8 +493,10 @@ public class TypefaceSystemFallbackTest { + "</familyset>"; final ArrayMap<String, Typeface> fontMap = new ArrayMap<>(); final ArrayMap<String, FontFamily[]> fallbackMap = new ArrayMap<>(); + final FontCustomizationParser.Result oemCustomization = + new FontCustomizationParser.Result(); - buildSystemFallback(xml, fontMap, fallbackMap); + buildSystemFallback(xml, oemCustomization, fontMap, fallbackMap); final Paint paint = new Paint(); @@ -497,8 +543,10 @@ public class TypefaceSystemFallbackTest { + "</familyset>"; final ArrayMap<String, Typeface> fontMap = new ArrayMap<>(); final ArrayMap<String, FontFamily[]> fallbackMap = new ArrayMap<>(); + final FontCustomizationParser.Result oemCustomization = + new FontCustomizationParser.Result(); - buildSystemFallback(xml, fontMap, fallbackMap); + buildSystemFallback(xml, oemCustomization, fontMap, fallbackMap); final Paint paint = new Paint(); paint.setTypeface(fontMap.get("sans-serif")); @@ -539,8 +587,10 @@ public class TypefaceSystemFallbackTest { + "</familyset>"; final ArrayMap<String, Typeface> fontMap = new ArrayMap<>(); final ArrayMap<String, FontFamily[]> fallbackMap = new ArrayMap<>(); + final FontCustomizationParser.Result oemCustomization = + new FontCustomizationParser.Result(); - buildSystemFallback(xml, fontMap, fallbackMap); + buildSystemFallback(xml, oemCustomization, fontMap, fallbackMap); final Paint paint = new Paint(); @@ -578,8 +628,10 @@ public class TypefaceSystemFallbackTest { + "</familyset>"; final ArrayMap<String, Typeface> fontMap = new ArrayMap<>(); final ArrayMap<String, FontFamily[]> fallbackMap = new ArrayMap<>(); + final FontCustomizationParser.Result oemCustomization = + new FontCustomizationParser.Result(); - buildSystemFallback(xml, fontMap, fallbackMap); + buildSystemFallback(xml, oemCustomization, fontMap, fallbackMap); final Paint paint = new Paint(); @@ -598,4 +650,191 @@ public class TypefaceSystemFallbackTest { assertEquals(GLYPH_1EM_WIDTH, paint.measureText("c"), 0.0f); } + @Test + public void testBuildSystemFallback__Customization_new_named_family() { + final String xml = "<?xml version='1.0' encoding='UTF-8'?>" + + "<familyset>" + + " <family name='sans-serif'>" + + " <font weight='400' style='normal'>a3em.ttf</font>" + + " </family>" + + "</familyset>"; + final String oemXml = "<?xml version='1.0' encoding='UTF-8'?>" + + "<fonts-modification version='1'>" + + " <family customizationType='new-named-family' name='google-sans'>" + + " <font weight='400' style='normal'>b3em.ttf</font>" + + " </family>" + + "</fonts-modification>"; + final ArrayMap<String, Typeface> fontMap = new ArrayMap<>(); + final ArrayMap<String, FontFamily[]> fallbackMap = new ArrayMap<>(); + final FontCustomizationParser.Result oemCustomization = + readFontCustomization(oemXml); + + buildSystemFallback(xml, oemCustomization, fontMap, fallbackMap); + + final Paint paint = new Paint(); + + Typeface testTypeface = fontMap.get("sans-serif"); + assertNotNull(testTypeface); + paint.setTypeface(testTypeface); + assertEquals(GLYPH_3EM_WIDTH, paint.measureText("a"), 0.0f); + assertEquals(GLYPH_1EM_WIDTH, paint.measureText("b"), 0.0f); + assertEquals(GLYPH_1EM_WIDTH, paint.measureText("c"), 0.0f); + + testTypeface = fontMap.get("google-sans"); + assertNotNull(testTypeface); + paint.setTypeface(testTypeface); + assertEquals(GLYPH_1EM_WIDTH, paint.measureText("a"), 0.0f); + assertEquals(GLYPH_3EM_WIDTH, paint.measureText("b"), 0.0f); + assertEquals(GLYPH_1EM_WIDTH, paint.measureText("c"), 0.0f); + } + + @Test + public void testBuildSystemFallback__Customization_new_named_family_override() { + final String xml = "<?xml version='1.0' encoding='UTF-8'?>" + + "<familyset>" + + " <family name='sans-serif'>" + + " <font weight='400' style='normal'>a3em.ttf</font>" + + " </family>" + + "</familyset>"; + final String oemXml = "<?xml version='1.0' encoding='UTF-8'?>" + + "<fonts-modification version='1'>" + + " <family customizationType='new-named-family' name='sans-serif'>" + + " <font weight='400' style='normal'>b3em.ttf</font>" + + " </family>" + + "</fonts-modification>"; + final ArrayMap<String, Typeface> fontMap = new ArrayMap<>(); + final ArrayMap<String, FontFamily[]> fallbackMap = new ArrayMap<>(); + final FontCustomizationParser.Result oemCustomization = + readFontCustomization(oemXml); + + buildSystemFallback(xml, oemCustomization, fontMap, fallbackMap); + + final Paint paint = new Paint(); + + Typeface testTypeface = fontMap.get("sans-serif"); + assertNotNull(testTypeface); + paint.setTypeface(testTypeface); + assertEquals(GLYPH_1EM_WIDTH, paint.measureText("a"), 0.0f); + assertEquals(GLYPH_3EM_WIDTH, paint.measureText("b"), 0.0f); + assertEquals(GLYPH_1EM_WIDTH, paint.measureText("c"), 0.0f); + } + + @Test + public void testBuildSystemFallback__Customization_additional_alias() { + final String xml = "<?xml version='1.0' encoding='UTF-8'?>" + + "<familyset>" + + " <family name='sans-serif'>" + + " <font weight='400' style='normal'>a3em.ttf</font>" + + " </family>" + + "</familyset>"; + final String oemXml = "<?xml version='1.0' encoding='UTF-8'?>" + + "<fonts-modification version='1'>" + + " <family customizationType='new-named-family' name='google-sans'>" + + " <font weight='400' style='normal'>b3em.ttf</font>" + + " <font weight='700' style='normal'>c3em.ttf</font>" + + " </family>" + + " <alias name='another-google-sans' to='google-sans' />" + + " <alias name='google-sans-bold' to='google-sans' weight='700' />" + + "</fonts-modification>"; + final ArrayMap<String, Typeface> fontMap = new ArrayMap<>(); + final ArrayMap<String, FontFamily[]> fallbackMap = new ArrayMap<>(); + final FontCustomizationParser.Result oemCustomization = + readFontCustomization(oemXml); + + buildSystemFallback(xml, oemCustomization, fontMap, fallbackMap); + + final Paint paint = new Paint(); + + Typeface testTypeface = fontMap.get("sans-serif"); + assertNotNull(testTypeface); + paint.setTypeface(testTypeface); + assertEquals(GLYPH_3EM_WIDTH, paint.measureText("a"), 0.0f); + assertEquals(GLYPH_1EM_WIDTH, paint.measureText("b"), 0.0f); + assertEquals(GLYPH_1EM_WIDTH, paint.measureText("c"), 0.0f); + + testTypeface = fontMap.get("google-sans"); + assertNotNull(testTypeface); + paint.setTypeface(testTypeface); + assertEquals(GLYPH_1EM_WIDTH, paint.measureText("a"), 0.0f); + assertEquals(GLYPH_3EM_WIDTH, paint.measureText("b"), 0.0f); + assertEquals(GLYPH_1EM_WIDTH, paint.measureText("c"), 0.0f); + + testTypeface = fontMap.get("another-google-sans"); + assertNotNull(testTypeface); + paint.setTypeface(testTypeface); + assertEquals(GLYPH_1EM_WIDTH, paint.measureText("a"), 0.0f); + assertEquals(GLYPH_3EM_WIDTH, paint.measureText("b"), 0.0f); + assertEquals(GLYPH_1EM_WIDTH, paint.measureText("c"), 0.0f); + + testTypeface = fontMap.get("google-sans-bold"); + assertNotNull(testTypeface); + paint.setTypeface(testTypeface); + assertEquals(GLYPH_1EM_WIDTH, paint.measureText("a"), 0.0f); + assertEquals(GLYPH_1EM_WIDTH, paint.measureText("b"), 0.0f); + assertEquals(GLYPH_3EM_WIDTH, paint.measureText("c"), 0.0f); + } + + @Test + public void testBuildSystemFallback__Customization_additional_alias_conflict_with_new_name() { + final String xml = "<?xml version='1.0' encoding='UTF-8'?>" + + "<familyset>" + + " <family name='named-family'>" + + " <font weight='400' style='normal'>a3em.ttf</font>" + + " </family>" + + " <alias name='named-alias' to='named-family' />" + + "</familyset>"; + final String oemXml = "<?xml version='1.0' encoding='UTF-8'?>" + + "<fonts-modification version='1'>" + + " <family customizationType='new-named-family' name='named-alias'>" + + " <font weight='400' style='normal'>b3em.ttf</font>" + + " </family>" + + "</fonts-modification>"; + final ArrayMap<String, Typeface> fontMap = new ArrayMap<>(); + final ArrayMap<String, FontFamily[]> fallbackMap = new ArrayMap<>(); + final FontCustomizationParser.Result oemCustomization = + readFontCustomization(oemXml); + + buildSystemFallback(xml, oemCustomization, fontMap, fallbackMap); + + final Paint paint = new Paint(); + + Typeface testTypeface = fontMap.get("named-family"); + assertNotNull(testTypeface); + paint.setTypeface(testTypeface); + assertEquals(GLYPH_3EM_WIDTH, paint.measureText("a"), 0.0f); + assertEquals(GLYPH_1EM_WIDTH, paint.measureText("b"), 0.0f); + assertEquals(GLYPH_1EM_WIDTH, paint.measureText("c"), 0.0f); + + testTypeface = fontMap.get("named-alias"); + assertNotNull(testTypeface); + paint.setTypeface(testTypeface); + assertEquals(GLYPH_1EM_WIDTH, paint.measureText("a"), 0.0f); + assertEquals(GLYPH_3EM_WIDTH, paint.measureText("b"), 0.0f); + assertEquals(GLYPH_1EM_WIDTH, paint.measureText("c"), 0.0f); + } + + @Test(expected = IllegalArgumentException.class) + public void testBuildSystemFallback__Customization_new_named_family_no_name_exception() { + final String oemXml = "<?xml version='1.0' encoding='UTF-8'?>" + + "<fonts-modification version='1'>" + + " <family customizationType='new-named-family'>" + + " <font weight='400' style='normal'>b3em.ttf</font>" + + " </family>" + + "</fonts-modification>"; + readFontCustomization(oemXml); + } + + @Test(expected = IllegalArgumentException.class) + public void testBuildSystemFallback__Customization_new_named_family_dup_name_exception() { + final String oemXml = "<?xml version='1.0' encoding='UTF-8'?>" + + "<fonts-modification version='1'>" + + " <family customizationType='new-named-family' name='google-sans'>" + + " <font weight='400' style='normal'>b3em.ttf</font>" + + " </family>" + + " <family customizationType='new-named-family' name='google-sans'>" + + " <font weight='400' style='normal'>b3em.ttf</font>" + + " </family>" + + "</fonts-modification>"; + readFontCustomization(oemXml); + } } diff --git a/core/tests/coretests/src/android/net/LocalSocketTest.java b/core/tests/coretests/src/android/net/LocalSocketTest.java index 1349844c80cf..1286b137cc1f 100644 --- a/core/tests/coretests/src/android/net/LocalSocketTest.java +++ b/core/tests/coretests/src/android/net/LocalSocketTest.java @@ -22,6 +22,7 @@ import android.net.LocalSocket; import android.net.LocalSocketAddress; import android.test.MoreAsserts; import android.test.suitebuilder.annotation.SmallTest; + import junit.framework.TestCase; import java.io.FileDescriptor; @@ -39,6 +40,20 @@ public class LocalSocketTest extends TestCase { ls = new LocalSocket(); + try { + ls.connect(new LocalSocketAddress(null)); + fail("Expected NullPointerException"); + } catch (NullPointerException e) { + // pass + } + + try { + ls.bind(new LocalSocketAddress(null)); + fail("Expected NullPointerException"); + } catch (NullPointerException e) { + // pass + } + ls.connect(new LocalSocketAddress("android.net.LocalSocketTest")); ls1 = ss.accept(); diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java index 8f0e76bdc0c9..4ecdc42e3372 100644 --- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java +++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java @@ -114,6 +114,7 @@ public class SettingsBackupTest { Settings.Global.ANOMALY_CONFIG_VERSION, Settings.Global.APN_DB_UPDATE_CONTENT_URL, Settings.Global.APN_DB_UPDATE_METADATA_URL, + Settings.Global.APP_BINDING_CONSTANTS, Settings.Global.APP_IDLE_CONSTANTS, Settings.Global.APP_OPS_CONSTANTS, Settings.Global.APP_STANDBY_ENABLED, @@ -462,6 +463,7 @@ public class SettingsBackupTest { Settings.Global.WFC_IMS_MODE, Settings.Global.WFC_IMS_ROAMING_ENABLED, Settings.Global.WFC_IMS_ROAMING_MODE, + Settings.Global.WIFI_ALWAYS_REQUESTED, Settings.Global.WIFI_BADGING_THRESHOLDS, Settings.Global.WIFI_BOUNCE_DELAY_OVERRIDE_MS, Settings.Global.WIFI_CONNECTED_MAC_RANDOMIZATION_ENABLED, diff --git a/core/tests/coretests/src/android/text/FontFallbackSetup.java b/core/tests/coretests/src/android/text/FontFallbackSetup.java index 898e78c651e6..5592aac5a7c0 100644 --- a/core/tests/coretests/src/android/text/FontFallbackSetup.java +++ b/core/tests/coretests/src/android/text/FontFallbackSetup.java @@ -21,6 +21,7 @@ import android.content.Context; import android.content.res.AssetManager; import android.graphics.Typeface; import android.graphics.fonts.Font; +import android.graphics.fonts.FontCustomizationParser; import android.graphics.fonts.FontFamily; import android.graphics.fonts.SystemFonts; import android.support.test.InstrumentationRegistry; @@ -77,8 +78,10 @@ public class FontFallbackSetup implements AutoCloseable { final ArrayMap<String, FontFamily[]> fallbackMap = new ArrayMap<>(); final ArrayList<Font> availableFonts = new ArrayList<>(); + final FontCustomizationParser.Result oemCustomization = + new FontCustomizationParser.Result(); final FontConfig.Alias[] aliases = SystemFonts.buildSystemFallback(testFontsXml, - mTestFontsDir, fallbackMap, availableFonts); + mTestFontsDir, oemCustomization, fallbackMap, availableFonts); Typeface.initSystemDefaultTypefaces(mFontMap, fallbackMap, aliases); } diff --git a/core/tests/coretests/src/android/view/DisplayCutoutTest.java b/core/tests/coretests/src/android/view/DisplayCutoutTest.java index fe45fe7d3aaf..c8d994c4f6c1 100644 --- a/core/tests/coretests/src/android/view/DisplayCutoutTest.java +++ b/core/tests/coretests/src/android/view/DisplayCutoutTest.java @@ -17,6 +17,7 @@ package android.view; import static android.view.DisplayCutout.NO_CUTOUT; +import static android.view.DisplayCutout.extractBoundsFromList; import static android.view.DisplayCutout.fromSpec; import static org.hamcrest.Matchers.equalTo; @@ -28,6 +29,7 @@ import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; +import android.graphics.Insets; import android.graphics.Rect; import android.os.Parcel; import android.platform.test.annotations.Presubmit; @@ -39,38 +41,83 @@ import org.junit.Test; import org.junit.runner.RunWith; import java.util.Arrays; +import java.util.Collections; + @RunWith(AndroidJUnit4.class) @SmallTest @Presubmit public class DisplayCutoutTest { + private static final Rect ZERO_RECT = new Rect(); + /** This is not a consistent cutout. Useful for verifying insets in one go though. */ final DisplayCutout mCutoutNumbers = new DisplayCutout( - new Rect(1, 2, 3, 4), - Arrays.asList(new Rect(5, 6, 7, 8))); + Insets.of(5, 6, 7, 8) /* safeInsets */, + null /* boundLeft */, + new Rect(9, 0, 10, 1) /* boundTop */, + null /* boundRight */, + null /* boundBottom */); final DisplayCutout mCutoutTop = createCutoutTop(); @Test - public void hasCutout() throws Exception { - assertTrue(NO_CUTOUT.isEmpty()); - assertFalse(mCutoutTop.isEmpty()); + public void testExtractBoundsFromList_left() { + Rect safeInsets = new Rect(10, 0, 0, 0); + Rect bound = new Rect(0, 80, 10, 120); + assertThat(extractBoundsFromList(safeInsets, Collections.singletonList(bound)), + equalTo(new Rect[]{bound, ZERO_RECT, ZERO_RECT, ZERO_RECT})); + } + + @Test + public void testExtractBoundsFromList_top() { + Rect safeInsets = new Rect(0, 10, 0, 0); + Rect bound = new Rect(80, 0, 120, 10); + assertThat(extractBoundsFromList(safeInsets, Collections.singletonList(bound)), + equalTo(new Rect[]{ZERO_RECT, bound, ZERO_RECT, ZERO_RECT})); } @Test - public void getSafeInsets() throws Exception { - assertEquals(1, mCutoutNumbers.getSafeInsetLeft()); - assertEquals(2, mCutoutNumbers.getSafeInsetTop()); - assertEquals(3, mCutoutNumbers.getSafeInsetRight()); - assertEquals(4, mCutoutNumbers.getSafeInsetBottom()); + public void testExtractBoundsFromList_right() { + Rect safeInsets = new Rect(0, 0, 10, 0); + Rect bound = new Rect(190, 80, 200, 120); + assertThat(extractBoundsFromList(safeInsets, Collections.singletonList(bound)), + equalTo(new Rect[]{ZERO_RECT, ZERO_RECT, bound, ZERO_RECT})); + } + + @Test + public void testExtractBoundsFromList_bottom() { + Rect safeInsets = new Rect(0, 0, 0, 10); + Rect bound = new Rect(80, 190, 120, 200); + assertThat(extractBoundsFromList(safeInsets, Collections.singletonList(bound)), + equalTo(new Rect[]{ZERO_RECT, ZERO_RECT, ZERO_RECT, bound})); + } + + @Test + public void testExtractBoundsFromList_top_and_bottom() { + Rect safeInsets = new Rect(0, 1, 0, 10); + Rect boundTop = new Rect(80, 0, 120, 10); + Rect boundBottom = new Rect(80, 190, 120, 200); + assertThat(extractBoundsFromList(safeInsets, + Arrays.asList(new Rect[]{boundTop, boundBottom})), + equalTo(new Rect[]{ZERO_RECT, boundTop, ZERO_RECT, boundBottom})); + } + - assertEquals(new Rect(1, 2, 3, 4), mCutoutNumbers.getSafeInsets()); + @Test + public void hasCutout() throws Exception { + assertTrue(NO_CUTOUT.isEmpty()); + assertFalse(mCutoutTop.isEmpty()); } @Test - public void getBoundingRect() throws Exception { - assertEquals(new Rect(50, 0, 75, 100), mCutoutTop.getBounds().getBounds()); + public void testGetSafeInsets() throws Exception { + assertEquals(5, mCutoutNumbers.getSafeInsetLeft()); + assertEquals(6, mCutoutNumbers.getSafeInsetTop()); + assertEquals(7, mCutoutNumbers.getSafeInsetRight()); + assertEquals(8, mCutoutNumbers.getSafeInsetBottom()); + + assertEquals(new Rect(5, 6, 7, 8), mCutoutNumbers.getSafeInsets()); } @Test @@ -102,30 +149,30 @@ public class DisplayCutoutTest { public void inset_insets_withLeftCutout() throws Exception { DisplayCutout cutout = createCutoutWithInsets(100, 0, 0, 0).inset(1, 2, 3, 4); - assertEquals(cutout.getSafeInsetLeft(), 99); - assertEquals(cutout.getSafeInsetTop(), 0); - assertEquals(cutout.getSafeInsetRight(), 0); - assertEquals(cutout.getSafeInsetBottom(), 0); + assertEquals(99, cutout.getSafeInsetLeft()); + assertEquals(0, cutout.getSafeInsetTop()); + assertEquals(0, cutout.getSafeInsetRight()); + assertEquals(0, cutout.getSafeInsetBottom()); } @Test public void inset_insets_withTopCutout() throws Exception { DisplayCutout cutout = mCutoutTop.inset(1, 2, 3, 4); - assertEquals(cutout.getSafeInsetLeft(), 0); - assertEquals(cutout.getSafeInsetTop(), 98); - assertEquals(cutout.getSafeInsetRight(), 0); - assertEquals(cutout.getSafeInsetBottom(), 0); + assertEquals(0, cutout.getSafeInsetLeft()); + assertEquals(98, cutout.getSafeInsetTop()); + assertEquals(0, cutout.getSafeInsetRight()); + assertEquals(0, cutout.getSafeInsetBottom()); } @Test public void inset_insets_withRightCutout() throws Exception { DisplayCutout cutout = createCutoutWithInsets(0, 0, 100, 0).inset(1, 2, 3, 4); - assertEquals(cutout.getSafeInsetLeft(), 0); - assertEquals(cutout.getSafeInsetTop(), 0); - assertEquals(cutout.getSafeInsetRight(), 97); - assertEquals(cutout.getSafeInsetBottom(), 0); + assertEquals(0, cutout.getSafeInsetLeft()); + assertEquals(0, cutout.getSafeInsetTop()); + assertEquals(97, cutout.getSafeInsetRight()); + assertEquals(0, cutout.getSafeInsetBottom()); } @Test @@ -153,16 +200,20 @@ public class DisplayCutoutTest { @Test public void inset_bounds() throws Exception { DisplayCutout cutout = mCutoutTop.inset(1, 2, 3, 4); - - assertEquals(new Rect(49, -2, 74, 98), cutout.getBounds().getBounds()); + assertThat(cutout.getBoundingRectsAll(), equalTo( + new Rect[]{ ZERO_RECT, new Rect(49, -2, 74, 98), ZERO_RECT, ZERO_RECT })); } + + // TODO: Deprecate fromBoundingRect. + /* @Test public void fromBoundingPolygon() throws Exception { assertEquals( new Rect(50, 0, 75, 100), DisplayCutout.fromBoundingRect(50, 0, 75, 100).getBounds().getBounds()); } + */ @Test public void parcel_unparcel_regular() { @@ -231,6 +282,10 @@ public class DisplayCutoutTest { DisplayCutout cutout = fromSpec("M -50,0 v 20 h 100 v -20 z" + "@bottom M -50,0 v -10,0 h 100 v 20 z", 200, 400, 2f); assertThat(cutout.getSafeInsets(), equalTo(new Rect(0, 20, 0, 10))); + assertThat(cutout.getBoundingRectsAll(), equalTo(new Rect[]{ + ZERO_RECT, new Rect(50, 0, 150, 20), + ZERO_RECT, new Rect(50, 390, 150, 410) + })); } @Test @@ -281,8 +336,10 @@ public class DisplayCutoutTest { } private static DisplayCutout createCutoutWithInsets(int left, int top, int right, int bottom) { + Insets safeInset = Insets.of(left, top, right, bottom); + Rect boundTop = new Rect(50, 0, 75, 100); return new DisplayCutout( - new Rect(left, top, right, bottom), - Arrays.asList(new Rect(50, 0, 75, 100))); + safeInset, null /* boundLeft */, boundTop, null /* boundRight */, + null /* boundBottom */); } } diff --git a/core/tests/coretests/src/com/android/internal/os/KernelCpuProcReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelCpuProcReaderTest.java index efdd7e978853..8360126f3751 100644 --- a/core/tests/coretests/src/com/android/internal/os/KernelCpuProcReaderTest.java +++ b/core/tests/coretests/src/com/android/internal/os/KernelCpuProcReaderTest.java @@ -43,7 +43,7 @@ import java.util.Random; /** * Test class for {@link KernelCpuProcReader}. * - * $ atest FrameworksCoreTests:com.android.internal.os.KernelCpuProcReader + * $ atest FrameworksCoreTests:com.android.internal.os.KernelCpuProcReaderTest */ @SmallTest @RunWith(AndroidJUnit4.class) diff --git a/core/tests/coretests/src/com/android/internal/os/StoragedUidIoStatsReaderTest.java b/core/tests/coretests/src/com/android/internal/os/StoragedUidIoStatsReaderTest.java new file mode 100644 index 000000000000..c051a1cdf052 --- /dev/null +++ b/core/tests/coretests/src/com/android/internal/os/StoragedUidIoStatsReaderTest.java @@ -0,0 +1,171 @@ +/* + * 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.internal.os; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.verifyZeroInteractions; + +import android.content.Context; +import android.os.FileUtils; +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.io.BufferedWriter; +import java.io.File; +import java.nio.file.Files; + + +/** + * Test class for {@link StoragedUidIoStatsReader}. + * + * To run it: + * atest FrameworksCoreTests:com.android.internal.os.StoragedUidIoStatsReaderTest + */ +@SmallTest +@RunWith(AndroidJUnit4.class) +public class StoragedUidIoStatsReaderTest { + + private File mRoot; + private File mTestDir; + private File mTestFile; + // private Random mRand = new Random(); + + private StoragedUidIoStatsReader mStoragedUidIoStatsReader; + @Mock + private StoragedUidIoStatsReader.Callback mCallback; + + private Context getContext() { + return InstrumentationRegistry.getContext(); + } + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mTestDir = getContext().getDir("test", Context.MODE_PRIVATE); + mRoot = getContext().getFilesDir(); + mTestFile = new File(mTestDir, "test.file"); + mStoragedUidIoStatsReader = new StoragedUidIoStatsReader(mTestFile.getAbsolutePath()); + } + + @After + public void tearDown() throws Exception { + FileUtils.deleteContents(mTestDir); + FileUtils.deleteContents(mRoot); + } + + + /** + * Tests that reading will never call the callback. + */ + @Test + public void testReadNonexistentFile() throws Exception { + mStoragedUidIoStatsReader.readAbsolute(mCallback); + verifyZeroInteractions(mCallback); + + } + + /** + * Tests that reading a file with 3 uids works as expected. + */ + @Test + public void testReadExpected() throws Exception { + BufferedWriter bufferedWriter = Files.newBufferedWriter(mTestFile.toPath()); + int[] uids = {0, 100, 200}; + long[] fg_chars_read = {1L, 101L, 201L}; + long[] fg_chars_write = {2L, 102L, 202L}; + long[] fg_bytes_read = {3L, 103L, 203L}; + long[] fg_bytes_write = {4L, 104L, 204L}; + long[] bg_chars_read = {5L, 105L, 205L}; + long[] bg_chars_write = {6L, 106L, 206L}; + long[] bg_bytes_read = {7L, 107L, 207L}; + long[] bg_bytes_write = {8L, 108L, 208L}; + long[] fg_fsync = {9L, 109L, 209L}; + long[] bg_fsync = {10L, 110L, 210L}; + + for (int i = 0; i < uids.length; i++) { + bufferedWriter.write(String + .format("%d %d %d %d %d %d %d %d %d %d %d\n", uids[i], fg_chars_read[i], + fg_chars_write[i], fg_bytes_read[i], fg_bytes_write[i], + bg_chars_read[i], bg_chars_write[i], bg_bytes_read[i], + bg_bytes_write[i], fg_fsync[i], bg_fsync[i])); + } + bufferedWriter.close(); + + mStoragedUidIoStatsReader.readAbsolute(mCallback); + for (int i = 0; i < uids.length; i++) { + verify(mCallback).onUidStorageStats(uids[i], fg_chars_read[i], fg_chars_write[i], + fg_bytes_read[i], fg_bytes_write[i], bg_chars_read[i], bg_chars_write[i], + bg_bytes_read[i], bg_bytes_write[i], fg_fsync[i], bg_fsync[i]); + } + verifyNoMoreInteractions(mCallback); + + } + + /** + * Tests that a line with less than 11 items is passed over. + */ + @Test + public void testLineDoesNotElevenEntries() throws Exception { + BufferedWriter bufferedWriter = Files.newBufferedWriter(mTestFile.toPath()); + + // Only has 10 numbers. + bufferedWriter.write(String + .format("%d %d %d %d %d %d %d %d %d %d\n", 0, 1, 2, 3, 4, 5, 6, 7, 8, 9)); + + bufferedWriter.write(String + .format("%d %d %d %d %d %d %d %d %d %d %d\n", 10, 11, 12, 13, 14, 15, 16, 17, 18, + 19, 20)); + bufferedWriter.close(); + + // Make sure we get the second line, but the first is skipped. + mStoragedUidIoStatsReader.readAbsolute(mCallback); + verify(mCallback).onUidStorageStats(10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20); + verifyNoMoreInteractions(mCallback); + } + + + /** + * Tests that a line that is malformed is passed over. + */ + @Test + public void testLineIsMalformed() throws Exception { + BufferedWriter bufferedWriter = Files.newBufferedWriter(mTestFile.toPath()); + + // Line is not formatted properly. It has a string. + bufferedWriter.write(String + .format("%d %d %d %d %d %s %d %d %d %d %d\n", 0, 1, 2, 3, 4, "NotANumber", 5, 6, 7, + 8, 9)); + + bufferedWriter.write(String + .format("%d %d %d %d %d %d %d %d %d %d %d\n", 10, 11, 12, 13, 14, 15, 16, 17, 18, + 19, 20)); + bufferedWriter.close(); + + // Make sure we get the second line, but the first is skipped. + mStoragedUidIoStatsReader.readAbsolute(mCallback); + verify(mCallback).onUidStorageStats(10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20); + verifyNoMoreInteractions(mCallback); + } +} diff --git a/core/tests/coretests/src/com/android/internal/widget/ActionBarOverlayLayoutTest.java b/core/tests/coretests/src/com/android/internal/widget/ActionBarOverlayLayoutTest.java index cac4e88c8edc..218566e8cdf0 100644 --- a/core/tests/coretests/src/com/android/internal/widget/ActionBarOverlayLayoutTest.java +++ b/core/tests/coretests/src/com/android/internal/widget/ActionBarOverlayLayoutTest.java @@ -27,6 +27,7 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertThat; import android.content.Context; +import android.graphics.Insets; import android.graphics.Rect; import android.support.test.InstrumentationRegistry; import android.support.test.filters.SmallTest; @@ -44,18 +45,20 @@ import org.junit.Test; import org.junit.runner.RunWith; import java.lang.reflect.Field; -import java.util.Arrays; @RunWith(AndroidJUnit4.class) @SmallTest public class ActionBarOverlayLayoutTest { - private static final Rect TOP_INSET_5 = new Rect(0, 5, 0, 0); - private static final Rect TOP_INSET_25 = new Rect(0, 25, 0, 0); - private static final Rect ZERO_INSET = new Rect(0, 0, 0, 0); + private static final Insets TOP_INSET_5 = Insets.of(0, 5, 0, 0); + private static final Insets TOP_INSET_25 = Insets.of(0, 25, 0, 0); private static final DisplayCutout CONSUMED_CUTOUT = null; - private static final DisplayCutout CUTOUT_5 = new DisplayCutout(TOP_INSET_5, Arrays.asList( - new Rect(100, 0, 200, 5))); + private static final DisplayCutout CUTOUT_5 = new DisplayCutout( + TOP_INSET_5, + null /* boundLeft */, + new Rect(100, 0, 200, 5), + null /* boundRight */, + null /* boundBottom*/); private static final int EXACTLY_1000 = makeMeasureSpec(1000, EXACTLY); private Context mContext; @@ -112,7 +115,7 @@ public class ActionBarOverlayLayoutTest { mLayout.measure(EXACTLY_1000, EXACTLY_1000); - assertThat(mContentInsetsListener.captured, is(insetsWith(ZERO_INSET, CONSUMED_CUTOUT))); + assertThat(mContentInsetsListener.captured, is(insetsWith(Insets.NONE, CONSUMED_CUTOUT))); } @Test @@ -136,7 +139,7 @@ public class ActionBarOverlayLayoutTest { mLayout.measure(EXACTLY_1000, EXACTLY_1000); - assertThat(mContentInsetsListener.captured, is(insetsWith(ZERO_INSET, NO_CUTOUT))); + assertThat(mContentInsetsListener.captured, is(insetsWith(Insets.NONE, NO_CUTOUT))); } @Test @@ -160,11 +163,11 @@ public class ActionBarOverlayLayoutTest { mLayout.measure(EXACTLY_1000, EXACTLY_1000); - assertThat(mContentInsetsListener.captured, is(insetsWith(ZERO_INSET, NO_CUTOUT))); + assertThat(mContentInsetsListener.captured, is(insetsWith(Insets.NONE, NO_CUTOUT))); } - private WindowInsets insetsWith(Rect content, DisplayCutout cutout) { - return new WindowInsets(content, null, null, false, false, cutout); + private WindowInsets insetsWith(Insets content, DisplayCutout cutout) { + return new WindowInsets(content.toRect(), null, null, false, false, cutout); } private ViewGroup createViewGroupWithId(int id) { diff --git a/data/fonts/Android.mk b/data/fonts/Android.mk index 76eb4e676923..454dceb9c82c 100644 --- a/data/fonts/Android.mk +++ b/data/fonts/Android.mk @@ -89,23 +89,7 @@ include $(CLEAR_VARS) LOCAL_MODULE := fonts.xml LOCAL_MODULE_CLASS := ETC - -AOSP_FONTS_FILE := frameworks/base/data/fonts/fonts.xml - -ifdef ADDITIONAL_FONTS_FILE -ADDITIONAL_FONTS_SCRIPT := frameworks/base/tools/fonts/add_additional_fonts.py -ADD_ADDITIONAL_FONTS := $(local-generated-sources-dir)/fonts.xml - -$(ADD_ADDITIONAL_FONTS): PRIVATE_SCRIPT := $(ADDITIONAL_FONTS_SCRIPT) -$(ADD_ADDITIONAL_FONTS): PRIVATE_ADDITIONAL_FONTS_FILE := $(ADDITIONAL_FONTS_FILE) -$(ADD_ADDITIONAL_FONTS): $(ADDITIONAL_FONTS_SCRIPT) $(AOSP_FONTS_FILE) $(ADDITIONAL_FONTS_FILE) - rm -f $@ - python $(PRIVATE_SCRIPT) $@ $(PRIVATE_ADDITIONAL_FONTS_FILE) -else -ADD_ADDITIONAL_FONTS := $(AOSP_FONTS_FILE) -endif - -LOCAL_PREBUILT_MODULE_FILE := $(ADD_ADDITIONAL_FONTS) +LOCAL_PREBUILT_MODULE_FILE := frameworks/base/data/fonts/fonts.xml include $(BUILD_PREBUILT) diff --git a/graphics/java/android/graphics/BaseCanvas.java b/graphics/java/android/graphics/BaseCanvas.java index fa37bedb06c4..0885a05c74f3 100644 --- a/graphics/java/android/graphics/BaseCanvas.java +++ b/graphics/java/android/graphics/BaseCanvas.java @@ -376,6 +376,53 @@ public abstract class BaseCanvas { drawRoundRect(rect.left, rect.top, rect.right, rect.bottom, rx, ry, paint); } + /** + * Make lint happy. + * See {@link Canvas#drawDoubleRoundRect(RectF, float, float, RectF, float, float, Paint)} + */ + public void drawDoubleRoundRect(@NonNull RectF outer, float outerRx, float outerRy, + @NonNull RectF inner, float innerRx, float innerRy, @NonNull Paint paint) { + throwIfHasHwBitmapInSwMode(paint); + float outerLeft = outer.left; + float outerTop = outer.top; + float outerRight = outer.right; + float outerBottom = outer.bottom; + + float innerLeft = inner.left; + float innerTop = inner.top; + float innerRight = inner.right; + float innerBottom = inner.bottom; + nDrawDoubleRoundRect(mNativeCanvasWrapper, outerLeft, outerTop, outerRight, outerBottom, + outerRx, outerRy, innerLeft, innerTop, innerRight, innerBottom, innerRx, innerRy, + paint.getNativeInstance()); + } + + /** + * Make lint happy. + * See {@link Canvas#drawDoubleRoundRect(RectF, float[], RectF, float[], Paint)} + */ + public void drawDoubleRoundRect(@NonNull RectF outer, float[] outerRadii, + @NonNull RectF inner, float[] innerRadii, @NonNull Paint paint) { + throwIfHasHwBitmapInSwMode(paint); + if (innerRadii == null || outerRadii == null + || innerRadii.length != 8 || outerRadii.length != 8) { + throw new IllegalArgumentException("Both inner and outer radii arrays must contain " + + "exactly 8 values"); + } + float outerLeft = outer.left; + float outerTop = outer.top; + float outerRight = outer.right; + float outerBottom = outer.bottom; + + float innerLeft = inner.left; + float innerTop = inner.top; + float innerRight = inner.right; + float innerBottom = inner.bottom; + nDrawDoubleRoundRect(mNativeCanvasWrapper, outerLeft, outerTop, outerRight, + outerBottom, outerRadii, innerLeft, innerTop, innerRight, innerBottom, innerRadii, + paint.getNativeInstance()); + } + public void drawText(@NonNull char[] text, int index, int count, float x, float y, @NonNull Paint paint) { if ((index | count | (index + count) | @@ -631,6 +678,16 @@ public abstract class BaseCanvas { private static native void nDrawRoundRect(long nativeCanvas, float left, float top, float right, float bottom, float rx, float ry, long nativePaint); + private static native void nDrawDoubleRoundRect(long nativeCanvas, float outerLeft, + float outerTop, float outerRight, float outerBottom, float outerRx, float outerRy, + float innerLeft, float innerTop, float innerRight, float innerBottom, float innerRx, + float innerRy, long nativePaint); + + private static native void nDrawDoubleRoundRect(long nativeCanvas, float outerLeft, + float outerTop, float outerRight, float outerBottom, float[] outerRadii, + float innerLeft, float innerTop, float innerRight, float innerBottom, + float[] innerRadii, long nativePaint); + private static native void nDrawPath(long nativeCanvas, long nativePath, long nativePaint); private static native void nDrawRegion(long nativeCanvas, long nativeRegion, long nativePaint); diff --git a/graphics/java/android/graphics/BaseRecordingCanvas.java b/graphics/java/android/graphics/BaseRecordingCanvas.java index 6e936910ef40..fb30ca2d4090 100644 --- a/graphics/java/android/graphics/BaseRecordingCanvas.java +++ b/graphics/java/android/graphics/BaseRecordingCanvas.java @@ -377,6 +377,24 @@ public class BaseRecordingCanvas extends Canvas { } @Override + public final void drawDoubleRoundRect(@NonNull RectF outer, float outerRx, float outerRy, + @NonNull RectF inner, float innerRx, float innerRy, @NonNull Paint paint) { + nDrawDoubleRoundRect(mNativeCanvasWrapper, + outer.left, outer.top, outer.right, outer.bottom, outerRx, outerRy, + inner.left, inner.top, inner.right, inner.bottom, innerRx, innerRy, + paint.getNativeInstance()); + } + + @Override + public final void drawDoubleRoundRect(@NonNull RectF outer, float[] outerRadii, + @NonNull RectF inner, float[] innerRadii, @NonNull Paint paint) { + nDrawDoubleRoundRect(mNativeCanvasWrapper, + outer.left, outer.top, outer.right, outer.bottom, outerRadii, + inner.left, inner.top, inner.right, inner.bottom, innerRadii, + paint.getNativeInstance()); + } + + @Override public final void drawText(@NonNull char[] text, int index, int count, float x, float y, @NonNull Paint paint) { if ((index | count | (index + count) @@ -593,6 +611,18 @@ public class BaseRecordingCanvas extends Canvas { float bottom, float rx, float ry, long nativePaint); @FastNative + private static native void nDrawDoubleRoundRect(long nativeCanvas, + float outerLeft, float outerTop, float outerRight, float outerBottom, + float outerRx, float outerRy, float innerLeft, float innerTop, float innerRight, + float innerBottom, float innerRx, float innerRy, long nativePaint); + + @FastNative + private static native void nDrawDoubleRoundRect(long nativeCanvas, float outerLeft, + float outerTop, float outerRight, float outerBottom, float[] outerRadii, + float innerLeft, float innerTop, float innerRight, float innerBottom, + float[] innerRadii, long nativePaint); + + @FastNative private static native void nDrawPath(long nativeCanvas, long nativePath, long nativePaint); @FastNative diff --git a/graphics/java/android/graphics/Canvas.java b/graphics/java/android/graphics/Canvas.java index 36c1c2133c2c..e35a3be6dbbf 100644 --- a/graphics/java/android/graphics/Canvas.java +++ b/graphics/java/android/graphics/Canvas.java @@ -1877,6 +1877,51 @@ public class Canvas extends BaseCanvas { } /** + * Draws a double rounded rectangle using the specified paint. The resultant round rect + * will be filled in the area defined between the outer and inner rectangular bounds if + * the {@link Paint} configured with {@link Paint.Style#FILL}. + * Otherwise if {@link Paint.Style#STROKE} is used, then 2 rounded rect strokes will + * be drawn at the outer and inner rounded rectangles + * + * @param outer The outer rectangular bounds of the roundRect to be drawn + * @param outerRx The x-radius of the oval used to round the corners on the outer rectangle + * @param outerRy The y-radius of the oval used to round the corners on the outer rectangle + * @param inner The inner rectangular bounds of the roundRect to be drawn + * @param innerRx The x-radius of the oval used to round the corners on the inner rectangle + * @param innerRy The y-radius of the oval used to round the corners on the outer rectangle + * @param paint The paint used to draw the double roundRect + */ + @Override + public void drawDoubleRoundRect(@NonNull RectF outer, float outerRx, float outerRy, + @NonNull RectF inner, float innerRx, float innerRy, @NonNull Paint paint) { + super.drawDoubleRoundRect(outer, outerRx, outerRy, inner, innerRx, innerRy, paint); + } + + /** + * Draws a double rounded rectangle using the specified paint. The resultant round rect + * will be filled in the area defined between the outer and inner rectangular bounds if + * the {@link Paint} configured with {@link Paint.Style#FILL}. + * Otherwise if {@link Paint.Style#STROKE} is used, then 2 rounded rect strokes will + * be drawn at the outer and inner rounded rectangles + * + * @param outer The outer rectangular bounds of the roundRect to be drawn + * @param outerRadii Array of 8 float representing the x, y corner radii for top left, + * top right, bottom right, bottom left corners respectively on the outer + * rounded rectangle + * + * @param inner The inner rectangular bounds of the roundRect to be drawn + * @param innerRadii Array of 8 float representing the x, y corner radii for top left, + * top right, bottom right, bottom left corners respectively on the + * outer rounded rectangle + * @param paint The paint used to draw the double roundRect + */ + @Override + public void drawDoubleRoundRect(@NonNull RectF outer, float[] outerRadii, + @NonNull RectF inner, float[] innerRadii, @NonNull Paint paint) { + super.drawDoubleRoundRect(outer, outerRadii, inner, innerRadii, paint); + } + + /** * Draw the text, with origin at (x,y), using the specified paint. The origin is interpreted * based on the Align setting in the paint. * diff --git a/graphics/java/android/graphics/FontListParser.java b/graphics/java/android/graphics/FontListParser.java index 82435d5511f5..21cc3757a40e 100644 --- a/graphics/java/android/graphics/FontListParser.java +++ b/graphics/java/android/graphics/FontListParser.java @@ -40,17 +40,25 @@ public class FontListParser { /* Parse fallback list (no names) */ @UnsupportedAppUsage public static FontConfig parse(InputStream in) throws XmlPullParserException, IOException { + return parse(in, "/system/fonts"); + } + + /** + * Parse the fonts.xml + */ + public static FontConfig parse(InputStream in, String fontDir) + throws XmlPullParserException, IOException { try { XmlPullParser parser = Xml.newPullParser(); parser.setInput(in, null); parser.nextTag(); - return readFamilies(parser); + return readFamilies(parser, fontDir); } finally { in.close(); } } - private static FontConfig readFamilies(XmlPullParser parser) + private static FontConfig readFamilies(XmlPullParser parser, String fontDir) throws XmlPullParserException, IOException { List<FontConfig.Family> families = new ArrayList<>(); List<FontConfig.Alias> aliases = new ArrayList<>(); @@ -60,7 +68,7 @@ public class FontListParser { if (parser.getEventType() != XmlPullParser.START_TAG) continue; String tag = parser.getName(); if (tag.equals("family")) { - families.add(readFamily(parser)); + families.add(readFamily(parser, fontDir)); } else if (tag.equals("alias")) { aliases.add(readAlias(parser)); } else { @@ -71,7 +79,10 @@ public class FontListParser { aliases.toArray(new FontConfig.Alias[aliases.size()])); } - private static FontConfig.Family readFamily(XmlPullParser parser) + /** + * Reads a family element + */ + public static FontConfig.Family readFamily(XmlPullParser parser, String fontDir) throws XmlPullParserException, IOException { final String name = parser.getAttributeValue(null, "name"); final String lang = parser.getAttributeValue("", "lang"); @@ -81,7 +92,7 @@ public class FontListParser { if (parser.getEventType() != XmlPullParser.START_TAG) continue; final String tag = parser.getName(); if (tag.equals("font")) { - fonts.add(readFont(parser)); + fonts.add(readFont(parser, fontDir)); } else { skip(parser); } @@ -102,7 +113,7 @@ public class FontListParser { private static final Pattern FILENAME_WHITESPACE_PATTERN = Pattern.compile("^[ \\n\\r\\t]+|[ \\n\\r\\t]+$"); - private static FontConfig.Font readFont(XmlPullParser parser) + private static FontConfig.Font readFont(XmlPullParser parser, String fontDir) throws XmlPullParserException, IOException { String indexStr = parser.getAttributeValue(null, "index"); int index = indexStr == null ? 0 : Integer.parseInt(indexStr); @@ -125,7 +136,7 @@ public class FontListParser { } } String sanitizedName = FILENAME_WHITESPACE_PATTERN.matcher(filename).replaceAll(""); - return new FontConfig.Font(sanitizedName, index, axes.toArray( + return new FontConfig.Font(fontDir + sanitizedName, index, axes.toArray( new FontVariationAxis[axes.size()]), weight, isItalic, fallbackFor); } @@ -137,7 +148,10 @@ public class FontListParser { return new FontVariationAxis(tagStr, Float.parseFloat(styleValueStr)); } - private static FontConfig.Alias readAlias(XmlPullParser parser) + /** + * Reads alias elements + */ + public static FontConfig.Alias readAlias(XmlPullParser parser) throws XmlPullParserException, IOException { String name = parser.getAttributeValue(null, "name"); String toName = parser.getAttributeValue(null, "to"); @@ -152,7 +166,10 @@ public class FontListParser { return new FontConfig.Alias(name, toName, weight); } - private static void skip(XmlPullParser parser) throws XmlPullParserException, IOException { + /** + * Skip until next element + */ + public static void skip(XmlPullParser parser) throws XmlPullParserException, IOException { int depth = 1; while (depth > 0) { switch (parser.next()) { diff --git a/graphics/java/android/graphics/ImageDecoder.java b/graphics/java/android/graphics/ImageDecoder.java index 6ce66bdc925b..009e042f74a7 100644 --- a/graphics/java/android/graphics/ImageDecoder.java +++ b/graphics/java/android/graphics/ImageDecoder.java @@ -59,6 +59,7 @@ import java.io.IOException; import java.io.InputStream; import java.lang.annotation.Retention; import java.nio.ByteBuffer; +import java.util.concurrent.Callable; import java.util.concurrent.atomic.AtomicBoolean; /** @@ -283,26 +284,7 @@ public final class ImageDecoder implements AutoCloseable { return createFromStream(is, true, this); } - - final FileDescriptor fd = assetFd.getFileDescriptor(); - final long offset = assetFd.getStartOffset(); - - ImageDecoder decoder = null; - try { - try { - Os.lseek(fd, offset, SEEK_SET); - decoder = nCreate(fd, this); - } catch (ErrnoException e) { - decoder = createFromStream(new FileInputStream(fd), true, this); - } - } finally { - if (decoder == null) { - IoUtils.closeQuietly(assetFd); - } else { - decoder.mAssetFd = assetFd; - } - } - return decoder; + return createFromAssetFileDescriptor(assetFd, this); } } @@ -354,6 +336,30 @@ public final class ImageDecoder implements AutoCloseable { return decoder; } + @NonNull + private static ImageDecoder createFromAssetFileDescriptor(@NonNull AssetFileDescriptor assetFd, + Source source) throws IOException { + final FileDescriptor fd = assetFd.getFileDescriptor(); + final long offset = assetFd.getStartOffset(); + + ImageDecoder decoder = null; + try { + try { + Os.lseek(fd, offset, SEEK_SET); + decoder = nCreate(fd, source); + } catch (ErrnoException e) { + decoder = createFromStream(new FileInputStream(fd), true, source); + } + } finally { + if (decoder == null) { + IoUtils.closeQuietly(assetFd); + } else { + decoder.mAssetFd = assetFd; + } + } + return decoder; + } + /** * For backwards compatibility, this does *not* close the InputStream. * @@ -528,6 +534,29 @@ public final class ImageDecoder implements AutoCloseable { } } + private static class CallableSource extends Source { + CallableSource(@NonNull Callable<AssetFileDescriptor> callable) { + mCallable = callable; + } + + private final Callable<AssetFileDescriptor> mCallable; + + @Override + public ImageDecoder createImageDecoder() throws IOException { + AssetFileDescriptor assetFd = null; + try { + assetFd = mCallable.call(); + } catch (Exception e) { + if (e instanceof IOException) { + throw (IOException) e; + } else { + throw new IOException(e); + } + } + return createFromAssetFileDescriptor(assetFd, this); + } + } + /** * Information about an encoded image. */ @@ -971,6 +1000,27 @@ public final class ImageDecoder implements AutoCloseable { } /** + * Create a new {@link Source Source} from a {@link Callable} that returns a + * new {@link AssetFileDescriptor} for each request. This provides control + * over how the {@link AssetFileDescriptor} is created, such as passing + * options into {@link ContentResolver#openTypedAssetFileDescriptor}, or + * enabling use of a {@link android.os.CancellationSignal}. + * <p> + * It's important for the given {@link Callable} to return a new, unique + * {@link AssetFileDescriptor} for each invocation, to support reuse of the + * returned {@link Source Source}. + * + * @return a new Source object, which can be passed to + * {@link #decodeDrawable decodeDrawable} or {@link #decodeBitmap + * decodeBitmap}. + */ + @AnyThread + @NonNull + public static Source createSource(@NonNull Callable<AssetFileDescriptor> callable) { + return new CallableSource(callable); + } + + /** * Return the width and height of a given sample size. * * <p>This takes an input that functions like diff --git a/graphics/java/android/graphics/Insets.java b/graphics/java/android/graphics/Insets.java index c3449dd4fb47..de110c849338 100644 --- a/graphics/java/android/graphics/Insets.java +++ b/graphics/java/android/graphics/Insets.java @@ -73,6 +73,15 @@ public final class Insets { } /** + * Returns a Rect intance with the appropriate values. + * + * @hide + */ + public @NonNull Rect toRect() { + return new Rect(left, top, right, bottom); + } + + /** * Two Insets instances are equal iff they belong to the same class and their fields are * pairwise equal. * diff --git a/graphics/java/android/graphics/Rect.java b/graphics/java/android/graphics/Rect.java index 4fec33f19d92..c4dc0adb3be0 100644 --- a/graphics/java/android/graphics/Rect.java +++ b/graphics/java/android/graphics/Rect.java @@ -23,8 +23,11 @@ import android.annotation.UnsupportedAppUsage; import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; +import android.util.proto.ProtoInputStream; import android.util.proto.ProtoOutputStream; +import android.util.proto.WireTypeMismatchException; +import java.io.IOException; import java.io.PrintWriter; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -232,6 +235,40 @@ public final class Rect implements Parcelable { } /** + * Read from a protocol buffer input stream. + * Protocol buffer message definition at {@link android.graphics.RectProto} + * + * @param proto Stream to read the Rect object from. + * @param fieldId Field Id of the Rect as defined in the parent message + * @hide + */ + public void readFromProto(@NonNull ProtoInputStream proto, long fieldId) throws IOException, + WireTypeMismatchException { + final long token = proto.start(fieldId); + try { + while (proto.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (proto.getFieldNumber()) { + case (int) RectProto.LEFT: + left = proto.readInt(RectProto.LEFT); + break; + case (int) RectProto.TOP: + top = proto.readInt(RectProto.TOP); + break; + case (int) RectProto.RIGHT: + right = proto.readInt(RectProto.RIGHT); + break; + case (int) RectProto.BOTTOM: + bottom = proto.readInt(RectProto.BOTTOM); + break; + } + } + } finally { + // Let caller handle any exceptions + proto.end(token); + } + } + + /** * Returns true if the rectangle is empty (left >= right or top >= bottom) */ public final boolean isEmpty() { diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java index e6ac06011e23..7ad207f339d1 100644 --- a/graphics/java/android/graphics/Typeface.java +++ b/graphics/java/android/graphics/Typeface.java @@ -1103,6 +1103,9 @@ public class Typeface { } for (FontConfig.Alias alias : aliases) { + if (systemFontMap.containsKey(alias.getName())) { + continue; // If alias and named family are conflict, use named family. + } final Typeface base = systemFontMap.get(alias.getToName()); final int weight = alias.getWeight(); final Typeface newFace = weight == 400 ? base : diff --git a/graphics/java/android/graphics/fonts/FontCustomizationParser.java b/graphics/java/android/graphics/fonts/FontCustomizationParser.java new file mode 100644 index 000000000000..0291d7484dc5 --- /dev/null +++ b/graphics/java/android/graphics/fonts/FontCustomizationParser.java @@ -0,0 +1,105 @@ +/* + * 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.graphics.fonts; + +import android.annotation.NonNull; +import android.graphics.FontListParser; +import android.text.FontConfig; +import android.util.Xml; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.HashSet; + +/** + * Parser for font customization + * + * @hide + */ +public class FontCustomizationParser { + /** + * Represents a customization XML + */ + public static class Result { + ArrayList<FontConfig.Family> mAdditionalNamedFamilies = new ArrayList<>(); + ArrayList<FontConfig.Alias> mAdditionalAliases = new ArrayList<>(); + } + + /** + * Parses the customization XML + * + * Caller must close the input stream + */ + public static Result parse(@NonNull InputStream in, @NonNull String fontDir) + throws XmlPullParserException, IOException { + XmlPullParser parser = Xml.newPullParser(); + parser.setInput(in, null); + parser.nextTag(); + return readFamilies(parser, fontDir); + } + + private static void validate(Result result) { + HashSet<String> familyNames = new HashSet<>(); + for (int i = 0; i < result.mAdditionalNamedFamilies.size(); ++i) { + final FontConfig.Family family = result.mAdditionalNamedFamilies.get(i); + final String name = family.getName(); + if (name == null) { + throw new IllegalArgumentException("new-named-family requires name attribute"); + } + if (!familyNames.add(name)) { + throw new IllegalArgumentException( + "new-named-family requires unique name attribute"); + } + } + } + + private static Result readFamilies(XmlPullParser parser, String fontDir) + throws XmlPullParserException, IOException { + Result out = new Result(); + parser.require(XmlPullParser.START_TAG, null, "fonts-modification"); + while (parser.next() != XmlPullParser.END_TAG) { + if (parser.getEventType() != XmlPullParser.START_TAG) continue; + String tag = parser.getName(); + if (tag.equals("family")) { + readFamily(parser, fontDir, out); + } else if (tag.equals("alias")) { + out.mAdditionalAliases.add(FontListParser.readAlias(parser)); + } else { + FontListParser.skip(parser); + } + } + validate(out); + return out; + } + + private static void readFamily(XmlPullParser parser, String fontDir, Result out) + throws XmlPullParserException, IOException { + final String customizationType = parser.getAttributeValue(null, "customizationType"); + if (customizationType == null) { + throw new IllegalArgumentException("customizationType must be specified"); + } + if (customizationType.equals("new-named-family")) { + out.mAdditionalNamedFamilies.add(FontListParser.readFamily(parser, fontDir)); + } else { + throw new IllegalArgumentException("Unknown customizationType=" + customizationType); + } + } +} diff --git a/graphics/java/android/graphics/fonts/SystemFonts.java b/graphics/java/android/graphics/fonts/SystemFonts.java index 5e80749e9233..2d21bbbd4e43 100644 --- a/graphics/java/android/graphics/fonts/SystemFonts.java +++ b/graphics/java/android/graphics/fonts/SystemFonts.java @@ -113,7 +113,6 @@ public final class SystemFonts { private static void pushFamilyToFallback(@NonNull FontConfig.Family xmlFamily, @NonNull ArrayMap<String, ArrayList<FontFamily>> fallbackMap, @NonNull Map<String, ByteBuffer> cache, - @NonNull String fontDir, @NonNull ArrayList<Font> availableFonts) { final String languageTags = xmlFamily.getLanguages(); @@ -138,8 +137,7 @@ public final class SystemFonts { } final FontFamily defaultFamily = defaultFonts.isEmpty() ? null : createFontFamily( - xmlFamily.getName(), defaultFonts, languageTags, variant, cache, fontDir, - availableFonts); + xmlFamily.getName(), defaultFonts, languageTags, variant, cache, availableFonts); // Insert family into fallback map. for (int i = 0; i < fallbackMap.size(); i++) { @@ -151,7 +149,7 @@ public final class SystemFonts { } } else { final FontFamily family = createFontFamily( - xmlFamily.getName(), fallback, languageTags, variant, cache, fontDir, + xmlFamily.getName(), fallback, languageTags, variant, cache, availableFonts); if (family != null) { fallbackMap.valueAt(i).add(family); @@ -169,7 +167,6 @@ public final class SystemFonts { @NonNull String languageTags, @FontConfig.Family.Variant int variant, @NonNull Map<String, ByteBuffer> cache, - @NonNull String fontDir, @NonNull ArrayList<Font> availableFonts) { if (fonts.size() == 0) { return null; @@ -178,7 +175,7 @@ public final class SystemFonts { FontFamily.Builder b = null; for (int i = 0; i < fonts.size(); i++) { final FontConfig.Font fontConfig = fonts.get(i); - final String fullPath = fontDir + fontConfig.getFontName(); + final String fullPath = fontConfig.getFontName(); ByteBuffer buffer = cache.get(fullPath); if (buffer == null) { if (cache.containsKey(fullPath)) { @@ -213,6 +210,22 @@ public final class SystemFonts { return b == null ? null : b.build(languageTags, variant); } + private static void appendNamedFamily(@NonNull FontConfig.Family xmlFamily, + @NonNull HashMap<String, ByteBuffer> bufferCache, + @NonNull ArrayMap<String, ArrayList<FontFamily>> fallbackListMap, + @NonNull ArrayList<Font> availableFonts) { + final String familyName = xmlFamily.getName(); + final FontFamily family = createFontFamily( + familyName, Arrays.asList(xmlFamily.getFonts()), + xmlFamily.getLanguages(), xmlFamily.getVariant(), bufferCache, availableFonts); + if (family == null) { + return; + } + final ArrayList<FontFamily> fallback = new ArrayList<>(); + fallback.add(family); + fallbackListMap.put(familyName, fallback); + } + /** * Build the system fallback from xml file. * @@ -226,11 +239,12 @@ public final class SystemFonts { @VisibleForTesting public static FontConfig.Alias[] buildSystemFallback(@NonNull String xmlPath, @NonNull String fontDir, + @NonNull FontCustomizationParser.Result oemCustomization, @NonNull ArrayMap<String, FontFamily[]> fallbackMap, @NonNull ArrayList<Font> availableFonts) { try { final FileInputStream fontsIn = new FileInputStream(xmlPath); - final FontConfig fontConfig = FontListParser.parse(fontsIn); + final FontConfig fontConfig = FontListParser.parse(fontsIn, fontDir); final HashMap<String, ByteBuffer> bufferCache = new HashMap<String, ByteBuffer>(); final FontConfig.Family[] xmlFamilies = fontConfig.getFamilies(); @@ -242,16 +256,12 @@ public final class SystemFonts { if (familyName == null) { continue; } - final FontFamily family = createFontFamily( - xmlFamily.getName(), Arrays.asList(xmlFamily.getFonts()), - xmlFamily.getLanguages(), xmlFamily.getVariant(), bufferCache, fontDir, - availableFonts); - if (family == null) { - continue; - } - final ArrayList<FontFamily> fallback = new ArrayList<>(); - fallback.add(family); - fallbackListMap.put(familyName, fallback); + appendNamedFamily(xmlFamily, bufferCache, fallbackListMap, availableFonts); + } + + for (int i = 0; i < oemCustomization.mAdditionalNamedFamilies.size(); ++i) { + appendNamedFamily(oemCustomization.mAdditionalNamedFamilies.get(i), + bufferCache, fallbackListMap, availableFonts); } // Then, add fallback fonts to the each fallback map. @@ -260,8 +270,7 @@ public final class SystemFonts { // The first family (usually the sans-serif family) is always placed immediately // after the primary family in the fallback. if (i == 0 || xmlFamily.getName() == null) { - pushFamilyToFallback(xmlFamily, fallbackListMap, bufferCache, fontDir, - availableFonts); + pushFamilyToFallback(xmlFamily, fallbackListMap, bufferCache, availableFonts); } } @@ -274,20 +283,36 @@ public final class SystemFonts { fallbackMap.put(fallbackName, families); } - return fontConfig.getAliases(); + final ArrayList<FontConfig.Alias> list = new ArrayList<>(); + list.addAll(Arrays.asList(fontConfig.getAliases())); + list.addAll(oemCustomization.mAdditionalAliases); + return list.toArray(new FontConfig.Alias[list.size()]); } catch (IOException | XmlPullParserException e) { Log.e(TAG, "Failed initialize system fallbacks.", e); return ArrayUtils.emptyArray(FontConfig.Alias.class); } } + private static FontCustomizationParser.Result readFontCustomization( + @NonNull String customizeXml, @NonNull String customFontsDir) { + try (FileInputStream f = new FileInputStream(customizeXml)) { + return FontCustomizationParser.parse(f, customFontsDir); + } catch (IOException e) { + return new FontCustomizationParser.Result(); + } catch (XmlPullParserException e) { + Log.e(TAG, "Failed to parse font customization XML", e); + return new FontCustomizationParser.Result(); + } + } + static { final ArrayMap<String, FontFamily[]> systemFallbackMap = new ArrayMap<>(); final ArrayList<Font> availableFonts = new ArrayList<>(); + final FontCustomizationParser.Result oemCustomization = + readFontCustomization("/product/etc/fonts_customization.xml", "/product/fonts/"); sAliases = buildSystemFallback("/system/etc/fonts.xml", "/system/fonts/", - systemFallbackMap, availableFonts); + oemCustomization, systemFallbackMap, availableFonts); sSystemFallbackMap = Collections.unmodifiableMap(systemFallbackMap); sAvailableFonts = Collections.unmodifiableList(availableFonts); } - } diff --git a/libs/androidfw/include/androidfw/Util.h b/libs/androidfw/include/androidfw/Util.h index e4cd6a8ab6b8..6c9eee0b8835 100644 --- a/libs/androidfw/include/androidfw/Util.h +++ b/libs/androidfw/include/androidfw/Util.h @@ -47,11 +47,11 @@ class unique_cptr { constexpr unique_cptr() : ptr_(nullptr) {} constexpr unique_cptr(std::nullptr_t) : ptr_(nullptr) {} explicit unique_cptr(pointer ptr) : ptr_(ptr) {} - unique_cptr(unique_cptr&& o) : ptr_(o.ptr_) { o.ptr_ = nullptr; } + unique_cptr(unique_cptr&& o) noexcept : ptr_(o.ptr_) { o.ptr_ = nullptr; } ~unique_cptr() { std::free(reinterpret_cast<void*>(ptr_)); } - inline unique_cptr& operator=(unique_cptr&& o) { + inline unique_cptr& operator=(unique_cptr&& o) noexcept { if (&o == this) { return *this; } diff --git a/libs/hwui/Layer.cpp b/libs/hwui/Layer.cpp index cc95051fc952..32aaa54e696c 100644 --- a/libs/hwui/Layer.cpp +++ b/libs/hwui/Layer.cpp @@ -77,5 +77,13 @@ void Layer::postDecStrong() { mRenderState.postDecStrong(this); } +SkBlendMode Layer::getMode() const { + if (mBlend || mode != SkBlendMode::kSrcOver) { + return mode; + } else { + return SkBlendMode::kSrc; + } +} + }; // namespace uirenderer }; // namespace android diff --git a/libs/hwui/Layer.h b/libs/hwui/Layer.h index 6f07a43ceb58..e4f96e914c36 100644 --- a/libs/hwui/Layer.h +++ b/libs/hwui/Layer.h @@ -67,7 +67,7 @@ public: inline int getAlpha() const { return alpha; } - inline SkBlendMode getMode() const { return mode; } + SkBlendMode getMode() const; inline SkColorFilter* getColorFilter() const { return mColorFilter.get(); } diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp index c30af842ebbb..f928de9b92a6 100644 --- a/libs/hwui/RecordingCanvas.cpp +++ b/libs/hwui/RecordingCanvas.cpp @@ -23,6 +23,7 @@ #include "SkDrawShadowInfo.h" #include "SkImage.h" #include "SkImageFilter.h" +#include "SkLatticeIter.h" #include "SkMath.h" #include "SkPicture.h" #include "SkRSXform.h" @@ -280,7 +281,8 @@ struct DrawPicture final : Op { struct DrawImage final : Op { static const auto kType = Type::DrawImage; - DrawImage(sk_sp<const SkImage>&& image, SkScalar x, SkScalar y, const SkPaint* paint, BitmapPalette palette) + DrawImage(sk_sp<const SkImage>&& image, SkScalar x, SkScalar y, const SkPaint* paint, + BitmapPalette palette) : image(std::move(image)), x(x), y(y), palette(palette) { if (paint) { this->paint = *paint; @@ -312,7 +314,8 @@ struct DrawImageNine final : Op { struct DrawImageRect final : Op { static const auto kType = Type::DrawImageRect; DrawImageRect(sk_sp<const SkImage>&& image, const SkRect* src, const SkRect& dst, - const SkPaint* paint, SkCanvas::SrcRectConstraint constraint, BitmapPalette palette) + const SkPaint* paint, SkCanvas::SrcRectConstraint constraint, + BitmapPalette palette) : image(std::move(image)), dst(dst), constraint(constraint), palette(palette) { this->src = src ? *src : SkRect::MakeIWH(this->image->width(), this->image->height()); if (paint) { @@ -331,8 +334,14 @@ struct DrawImageRect final : Op { struct DrawImageLattice final : Op { static const auto kType = Type::DrawImageLattice; DrawImageLattice(sk_sp<const SkImage>&& image, int xs, int ys, int fs, const SkIRect& src, - const SkRect& dst, const SkPaint* paint) - : image(std::move(image)), xs(xs), ys(ys), fs(fs), src(src), dst(dst) { + const SkRect& dst, const SkPaint* paint, BitmapPalette palette) + : image(std::move(image)) + , xs(xs) + , ys(ys) + , fs(fs) + , src(src) + , dst(dst) + , palette(palette) { if (paint) { this->paint = *paint; } @@ -342,6 +351,7 @@ struct DrawImageLattice final : Op { SkIRect src; SkRect dst; SkPaint paint; + BitmapPalette palette; void draw(SkCanvas* c, const SkMatrix&) const { auto xdivs = pod<int>(this, 0), ydivs = pod<int>(this, xs * sizeof(int)); auto colors = (0 == fs) ? nullptr : pod<SkColor>(this, (xs + ys) * sizeof(int)); @@ -511,16 +521,13 @@ struct DrawVectorDrawable final : Op { tree->getPaintFor(&paint, tree->stagingProperties()); } - void draw(SkCanvas* canvas, const SkMatrix&) const { - mRoot->draw(canvas, mBounds, paint); - } + void draw(SkCanvas* canvas, const SkMatrix&) const { mRoot->draw(canvas, mBounds, paint); } sp<VectorDrawableRoot> mRoot; SkRect mBounds; SkPaint paint; BitmapPalette palette; }; - } template <typename T, typename... Args> @@ -647,14 +654,15 @@ void DisplayListData::drawImageRect(sk_sp<const SkImage> image, const SkRect* sr this->push<DrawImageRect>(0, std::move(image), src, dst, paint, constraint, palette); } void DisplayListData::drawImageLattice(sk_sp<const SkImage> image, const SkCanvas::Lattice& lattice, - const SkRect& dst, const SkPaint* paint) { + const SkRect& dst, const SkPaint* paint, + BitmapPalette palette) { int xs = lattice.fXCount, ys = lattice.fYCount; int fs = lattice.fRectTypes ? (xs + 1) * (ys + 1) : 0; size_t bytes = (xs + ys) * sizeof(int) + fs * sizeof(SkCanvas::Lattice::RectType) + fs * sizeof(SkColor); SkASSERT(lattice.fBounds); void* pod = this->push<DrawImageLattice>(bytes, std::move(image), xs, ys, fs, *lattice.fBounds, - dst, paint); + dst, paint, palette); copy_v(pod, lattice.fXDivs, xs, lattice.fYDivs, ys, lattice.fColors, fs, lattice.fRectTypes, fs); } @@ -779,22 +787,26 @@ constexpr bool has_palette = std::experimental::is_detected_v<has_palette_helper template <class T> constexpr color_transform_fn colorTransformForOp() { - if constexpr(has_paint<T> && has_palette<T>) { - // It's a bitmap - return [](const void* opRaw, ColorTransform transform) { - // TODO: We should be const. Or not. Or just use a different map - // Unclear, but this is the quick fix - const T* op = reinterpret_cast<const T*>(opRaw); - transformPaint(transform, const_cast<SkPaint*>(&(op->paint)), op->palette); - }; - } else if constexpr(has_paint<T>) { - return [](const void* opRaw, ColorTransform transform) { - // TODO: We should be const. Or not. Or just use a different map - // Unclear, but this is the quick fix - const T* op = reinterpret_cast<const T*>(opRaw); - transformPaint(transform, const_cast<SkPaint*>(&(op->paint))); - }; - } else { + if + constexpr(has_paint<T> && has_palette<T>) { + // It's a bitmap + return [](const void* opRaw, ColorTransform transform) { + // TODO: We should be const. Or not. Or just use a different map + // Unclear, but this is the quick fix + const T* op = reinterpret_cast<const T*>(opRaw); + transformPaint(transform, const_cast<SkPaint*>(&(op->paint)), op->palette); + }; + } + else if + constexpr(has_paint<T>) { + return [](const void* opRaw, ColorTransform transform) { + // TODO: We should be const. Or not. Or just use a different map + // Unclear, but this is the quick fix + const T* op = reinterpret_cast<const T*>(opRaw); + transformPaint(transform, const_cast<SkPaint*>(&(op->paint))); + }; + } + else { return nullptr; } } @@ -931,11 +943,12 @@ void RecordingCanvas::onDrawBitmapNine(const SkBitmap& bm, const SkIRect& center } void RecordingCanvas::onDrawBitmapRect(const SkBitmap& bm, const SkRect* src, const SkRect& dst, const SkPaint* paint, SrcRectConstraint constraint) { - fDL->drawImageRect(SkImage::MakeFromBitmap(bm), src, dst, paint, constraint, BitmapPalette::Unknown); + fDL->drawImageRect(SkImage::MakeFromBitmap(bm), src, dst, paint, constraint, + BitmapPalette::Unknown); } void RecordingCanvas::onDrawBitmapLattice(const SkBitmap& bm, const SkCanvas::Lattice& lattice, const SkRect& dst, const SkPaint* paint) { - fDL->drawImageLattice(SkImage::MakeFromBitmap(bm), lattice, dst, paint); + fDL->drawImageLattice(SkImage::MakeFromBitmap(bm), lattice, dst, paint, BitmapPalette::Unknown); } void RecordingCanvas::drawImage(const sk_sp<SkImage>& image, SkScalar x, SkScalar y, @@ -943,11 +956,34 @@ void RecordingCanvas::drawImage(const sk_sp<SkImage>& image, SkScalar x, SkScala fDL->drawImage(image, x, y, paint, palette); } -void RecordingCanvas::drawImageRect(const sk_sp<SkImage>& image, const SkRect& src, const SkRect& dst, - const SkPaint* paint, SrcRectConstraint constraint, BitmapPalette palette) { +void RecordingCanvas::drawImageRect(const sk_sp<SkImage>& image, const SkRect& src, + const SkRect& dst, const SkPaint* paint, + SrcRectConstraint constraint, BitmapPalette palette) { fDL->drawImageRect(image, &src, dst, paint, constraint, palette); } +void RecordingCanvas::drawImageLattice(const sk_sp<SkImage>& image, const Lattice& lattice, + const SkRect& dst, const SkPaint* paint, + BitmapPalette palette) { + if (!image || dst.isEmpty()) { + return; + } + + SkIRect bounds; + Lattice latticePlusBounds = lattice; + if (!latticePlusBounds.fBounds) { + bounds = SkIRect::MakeWH(image->width(), image->height()); + latticePlusBounds.fBounds = &bounds; + } + + if (SkLatticeIter::Valid(image->width(), image->height(), latticePlusBounds)) { + fDL->drawImageLattice(image, latticePlusBounds, dst, paint, palette); + } else { + fDL->drawImageRect(image, nullptr, dst, paint, SrcRectConstraint::kFast_SrcRectConstraint, + palette); + } +} + void RecordingCanvas::onDrawImage(const SkImage* img, SkScalar x, SkScalar y, const SkPaint* paint) { fDL->drawImage(sk_ref_sp(img), x, y, paint, BitmapPalette::Unknown); @@ -962,7 +998,7 @@ void RecordingCanvas::onDrawImageRect(const SkImage* img, const SkRect* src, con } void RecordingCanvas::onDrawImageLattice(const SkImage* img, const SkCanvas::Lattice& lattice, const SkRect& dst, const SkPaint* paint) { - fDL->drawImageLattice(sk_ref_sp(img), lattice, dst, paint); + fDL->drawImageLattice(sk_ref_sp(img), lattice, dst, paint, BitmapPalette::Unknown); } void RecordingCanvas::onDrawPatch(const SkPoint cubics[12], const SkColor colors[4], @@ -975,8 +1011,8 @@ void RecordingCanvas::onDrawPoints(SkCanvas::PointMode mode, size_t count, const fDL->drawPoints(mode, count, pts, paint); } void RecordingCanvas::onDrawVerticesObject(const SkVertices* vertices, - const SkVertices::Bone bones[], int boneCount, - SkBlendMode mode, const SkPaint& paint) { + const SkVertices::Bone bones[], int boneCount, + SkBlendMode mode, const SkPaint& paint) { fDL->drawVertices(vertices, bones, boneCount, mode, paint); } void RecordingCanvas::onDrawAtlas(const SkImage* atlas, const SkRSXform xforms[], diff --git a/libs/hwui/RecordingCanvas.h b/libs/hwui/RecordingCanvas.h index 80c80ca46515..099e0be433ea 100644 --- a/libs/hwui/RecordingCanvas.h +++ b/libs/hwui/RecordingCanvas.h @@ -110,7 +110,7 @@ private: void drawImageRect(sk_sp<const SkImage>, const SkRect*, const SkRect&, const SkPaint*, SkCanvas::SrcRectConstraint, BitmapPalette palette); void drawImageLattice(sk_sp<const SkImage>, const SkCanvas::Lattice&, const SkRect&, - const SkPaint*); + const SkPaint*, BitmapPalette); void drawPatch(const SkPoint[12], const SkColor[4], const SkPoint[4], SkBlendMode, const SkPaint&); @@ -185,11 +185,13 @@ public: void onDrawBitmapRect(const SkBitmap&, const SkRect*, const SkRect&, const SkPaint*, SrcRectConstraint) override; - void drawImage(const sk_sp<SkImage>& image, SkScalar left, SkScalar top, - const SkPaint* paint, BitmapPalette pallete); + void drawImage(const sk_sp<SkImage>& image, SkScalar left, SkScalar top, const SkPaint* paint, + BitmapPalette pallete); void drawImageRect(const sk_sp<SkImage>& image, const SkRect& src, const SkRect& dst, const SkPaint* paint, SrcRectConstraint constraint, BitmapPalette palette); + void drawImageLattice(const sk_sp<SkImage>& image, const Lattice& lattice, const SkRect& dst, + const SkPaint* paint, BitmapPalette palette); void onDrawImage(const SkImage*, SkScalar, SkScalar, const SkPaint*) override; void onDrawImageLattice(const SkImage*, const Lattice&, const SkRect&, const SkPaint*) override; diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp index d9a7cc3a575f..d2a8f02cc6a7 100644 --- a/libs/hwui/RenderNode.cpp +++ b/libs/hwui/RenderNode.cpp @@ -282,25 +282,45 @@ void RenderNode::syncDisplayList(TreeObserver& observer, TreeInfo* info) { mStagingDisplayList = nullptr; if (mDisplayList) { mDisplayList->syncContents(); + handleForceDark(info); + } +} - if (CC_UNLIKELY(info && !info->disableForceDark)) { - auto usage = usageHint(); - if (mDisplayList->hasText()) { - usage = UsageHint::Foreground; - } - if (usage == UsageHint::Unknown) { - if (mDisplayList->mChildNodes.size() > 1) { - usage = UsageHint::Background; - } else if (mDisplayList->mChildNodes.size() == 1 && - mDisplayList->mChildNodes.front().getRenderNode()->usageHint() != - UsageHint::Background) { - usage = UsageHint::Background; - } +void RenderNode::handleForceDark(android::uirenderer::TreeInfo *info) { + if (CC_LIKELY(!info || info->disableForceDark)) { + return; + } + auto usage = usageHint(); + const auto& children = mDisplayList->mChildNodes; + if (mDisplayList->hasText()) { + usage = UsageHint::Foreground; + } + if (usage == UsageHint::Unknown) { + if (children.size() > 1) { + usage = UsageHint::Background; + } else if (children.size() == 1 && + children.front().getRenderNode()->usageHint() != + UsageHint::Background) { + usage = UsageHint::Background; + } + } + if (children.size() > 1) { + // Crude overlap check + SkRect drawn = SkRect::MakeEmpty(); + for (auto iter = children.rbegin(); iter != children.rend(); ++iter) { + const auto& child = iter->getRenderNode(); + // We use stagingProperties here because we haven't yet sync'd the children + SkRect bounds = SkRect::MakeXYWH(child->stagingProperties().getX(), child->stagingProperties().getY(), + child->stagingProperties().getWidth(), child->stagingProperties().getHeight()); + if (bounds.contains(drawn)) { + // This contains everything drawn after it, so make it a background + child->setUsageHint(UsageHint::Background); } - mDisplayList->mDisplayList.applyColorTransform( - usage == UsageHint::Background ? ColorTransform::Dark : ColorTransform::Light); + drawn.join(bounds); } } + mDisplayList->mDisplayList.applyColorTransform( + usage == UsageHint::Background ? ColorTransform::Dark : ColorTransform::Light); } void RenderNode::pushStagingDisplayListChanges(TreeObserver& observer, TreeInfo& info) { diff --git a/libs/hwui/RenderNode.h b/libs/hwui/RenderNode.h index 211dd2db5cf8..be0b46b1c45f 100644 --- a/libs/hwui/RenderNode.h +++ b/libs/hwui/RenderNode.h @@ -220,6 +220,7 @@ private: void syncProperties(); void syncDisplayList(TreeObserver& observer, TreeInfo* info); + void handleForceDark(TreeInfo* info); void prepareTreeImpl(TreeObserver& observer, TreeInfo& info, bool functorsNeedLayer); void pushStagingPropertiesChanges(TreeInfo& info); diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp index 17f1a3b4db3c..2e5aef5347d5 100644 --- a/libs/hwui/SkiaCanvas.cpp +++ b/libs/hwui/SkiaCanvas.cpp @@ -504,6 +504,11 @@ void SkiaCanvas::drawRoundRect(float left, float top, float right, float bottom, mCanvas->drawRoundRect(rect, rx, ry, *filterPaint(paint)); } +void SkiaCanvas::drawDoubleRoundRect(const SkRRect& outer, const SkRRect& inner, + const SkPaint& paint) { + mCanvas->drawDRRect(outer, inner, *filterPaint(paint)); +} + void SkiaCanvas::drawCircle(float x, float y, float radius, const SkPaint& paint) { if (CC_UNLIKELY(radius <= 0 || paint.nothingToDraw())) return; mCanvas->drawCircle(x, y, radius, *filterPaint(paint)); diff --git a/libs/hwui/SkiaCanvas.h b/libs/hwui/SkiaCanvas.h index 24b7ec6d5c7b..3a877cf84010 100644 --- a/libs/hwui/SkiaCanvas.h +++ b/libs/hwui/SkiaCanvas.h @@ -107,6 +107,10 @@ public: virtual void drawRegion(const SkRegion& region, const SkPaint& paint) override; virtual void drawRoundRect(float left, float top, float right, float bottom, float rx, float ry, const SkPaint& paint) override; + + virtual void drawDoubleRoundRect(const SkRRect& outer, const SkRRect& inner, + const SkPaint& paint) override; + virtual void drawCircle(float x, float y, float radius, const SkPaint& paint) override; virtual void drawOval(float left, float top, float right, float bottom, const SkPaint& paint) override; diff --git a/libs/hwui/VectorDrawable.cpp b/libs/hwui/VectorDrawable.cpp index dbbe9f3acf53..6cf04bf5f811 100644 --- a/libs/hwui/VectorDrawable.cpp +++ b/libs/hwui/VectorDrawable.cpp @@ -470,10 +470,10 @@ void Tree::drawStaging(Canvas* outCanvas) { void Tree::getPaintFor(SkPaint* outPaint, const TreeProperties &prop) const { // HWUI always draws VD with bilinear filtering. outPaint->setFilterQuality(kLow_SkFilterQuality); - if (prop.getRootAlpha() < 1.0f || prop.getColorFilter() != nullptr) { + if (prop.getColorFilter() != nullptr) { outPaint->setColorFilter(sk_ref_sp(prop.getColorFilter())); - outPaint->setAlpha(prop.getRootAlpha() * 255); } + outPaint->setAlpha(prop.getRootAlpha() * 255); } Bitmap& Tree::getBitmapUpdateIfDirty() { diff --git a/libs/hwui/hwui/Canvas.cpp b/libs/hwui/hwui/Canvas.cpp index af7f013e27b1..e2ea2bc37375 100644 --- a/libs/hwui/hwui/Canvas.cpp +++ b/libs/hwui/hwui/Canvas.cpp @@ -178,6 +178,41 @@ void Canvas::drawText(const uint16_t* text, int textSize, int start, int count, MinikinUtils::forFontRun(layout, &paint, f); } +void Canvas::drawDoubleRoundRectXY(float outerLeft, float outerTop, float outerRight, + float outerBottom, float outerRx, float outerRy, float innerLeft, + float innerTop, float innerRight, float innerBottom, float innerRx, + float innerRy, const SkPaint& paint) { + if (CC_UNLIKELY(paint.nothingToDraw())) return; + SkRect outer = SkRect::MakeLTRB(outerLeft, outerTop, outerRight, outerBottom); + SkRect inner = SkRect::MakeLTRB(innerLeft, innerTop, innerRight, innerBottom); + + SkRRect outerRRect; + outerRRect.setRectXY(outer, outerRx, outerRy); + + SkRRect innerRRect; + innerRRect.setRectXY(inner, innerRx, innerRy); + drawDoubleRoundRect(outerRRect, innerRRect, paint); +} + +void Canvas::drawDoubleRoundRectRadii(float outerLeft, float outerTop, float outerRight, + float outerBottom, const float* outerRadii, float innerLeft, + float innerTop, float innerRight, float innerBottom, + const float* innerRadii, const SkPaint& paint) { + static_assert(sizeof(SkVector) == sizeof(float) * 2); + if (CC_UNLIKELY(paint.nothingToDraw())) return; + SkRect outer = SkRect::MakeLTRB(outerLeft, outerTop, outerRight, outerBottom); + SkRect inner = SkRect::MakeLTRB(innerLeft, innerTop, innerRight, innerBottom); + + SkRRect outerRRect; + const SkVector* outerSkVector = reinterpret_cast<const SkVector*>(outerRadii); + outerRRect.setRectRadii(outer, outerSkVector); + + SkRRect innerRRect; + const SkVector* innerSkVector = reinterpret_cast<const SkVector*>(innerRadii); + innerRRect.setRectRadii(inner, innerSkVector); + drawDoubleRoundRect(outerRRect, innerRRect, paint); +} + class DrawTextOnPathFunctor { public: DrawTextOnPathFunctor(const minikin::Layout& layout, Canvas* canvas, float hOffset, diff --git a/libs/hwui/hwui/Canvas.h b/libs/hwui/hwui/Canvas.h index b9af7de26721..e99742bc2eba 100644 --- a/libs/hwui/hwui/Canvas.h +++ b/libs/hwui/hwui/Canvas.h @@ -236,6 +236,8 @@ public: virtual void drawRegion(const SkRegion& region, const SkPaint& paint) = 0; virtual void drawRoundRect(float left, float top, float right, float bottom, float rx, float ry, const SkPaint& paint) = 0; + virtual void drawDoubleRoundRect(const SkRRect& outer, const SkRRect& inner, + const SkPaint& paint) = 0; virtual void drawCircle(float x, float y, float radius, const SkPaint& paint) = 0; virtual void drawOval(float left, float top, float right, float bottom, const SkPaint& paint) = 0; @@ -284,6 +286,16 @@ public: const SkPath& path, float hOffset, float vOffset, const Paint& paint, const Typeface* typeface); + void drawDoubleRoundRectXY(float outerLeft, float outerTop, float outerRight, + float outerBottom, float outerRx, float outerRy, float innerLeft, + float innerTop, float innerRight, float innerBottom, float innerRx, + float innerRy, const SkPaint& paint); + + void drawDoubleRoundRectRadii(float outerLeft, float outerTop, float outerRight, + float outerBottom, const float* outerRadii, float innerLeft, + float innerTop, float innerRight, float innerBottom, + const float* innerRadii, const SkPaint& paint); + static int GetApiLevel() { return sApiLevel; } protected: diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp index fac07d74dad4..3fa73a4dadda 100644 --- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp +++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp @@ -245,8 +245,9 @@ void SkiaRecordingCanvas::drawNinePatch(Bitmap& bitmap, const Res_png_9patch& ch } sk_sp<SkColorFilter> colorFilter; sk_sp<SkImage> image = bitmap.makeImage(&colorFilter); - mRecorder.drawImageLattice(image.get(), lattice, dst, - filterBitmap(std::move(filteredPaint), std::move(colorFilter))); + mRecorder.drawImageLattice(image, lattice, dst, + filterBitmap(std::move(filteredPaint), std::move(colorFilter)), + bitmap.palette()); if (!bitmap.isImmutable() && image.get() && !image->unique() && !dst.isEmpty()) { mDisplayList->mMutableImages.push_back(image.get()); } diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp index a2d811993f2f..2ca110f0d0b1 100644 --- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp +++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp @@ -126,6 +126,12 @@ bool SkiaVulkanPipeline::setSurface(Surface* surface, SwapBehavior swapBehavior, mVkSurface = mVkManager.createSurface(surface, colorMode); } + if (colorMode == ColorMode::SRGB) { + mSurfaceColorType = SkColorType::kN32_SkColorType; + } else if (colorMode == ColorMode::WideColorGamut) { + mSurfaceColorType = SkColorType::kRGBA_F16_SkColorType; + } + return mVkSurface != nullptr; } diff --git a/libs/hwui/renderthread/VulkanManager.cpp b/libs/hwui/renderthread/VulkanManager.cpp index 83e9db359356..b0d45052ca49 100644 --- a/libs/hwui/renderthread/VulkanManager.cpp +++ b/libs/hwui/renderthread/VulkanManager.cpp @@ -78,7 +78,7 @@ bool VulkanManager::setupDevice(GrVkExtensions& grExtensions, VkPhysicalDeviceFe 0, // applicationVersion "android framework", // pEngineName 0, // engineVerison - VK_MAKE_VERSION(1, 0, 0), // apiVersion + VK_MAKE_VERSION(1, 1, 0), // apiVersion }; std::vector<const char*> instanceExtensions; @@ -133,6 +133,7 @@ bool VulkanManager::setupDevice(GrVkExtensions& grExtensions, VkPhysicalDeviceFe GET_INST_PROC(DestroyInstance); GET_INST_PROC(EnumeratePhysicalDevices); + GET_INST_PROC(GetPhysicalDeviceProperties); GET_INST_PROC(GetPhysicalDeviceQueueFamilyProperties); GET_INST_PROC(GetPhysicalDeviceFeatures2); GET_INST_PROC(CreateDevice); @@ -164,6 +165,13 @@ bool VulkanManager::setupDevice(GrVkExtensions& grExtensions, VkPhysicalDeviceFe return false; } + VkPhysicalDeviceProperties physDeviceProperties; + mGetPhysicalDeviceProperties(mPhysicalDevice, &physDeviceProperties); + if (physDeviceProperties.apiVersion < VK_MAKE_VERSION(1, 1, 0)) { + this->destroy(); + return false; + } + // query to get the initial queue props size uint32_t queueCount; mGetPhysicalDeviceQueueFamilyProperties(mPhysicalDevice, &queueCount, nullptr); diff --git a/libs/hwui/renderthread/VulkanManager.h b/libs/hwui/renderthread/VulkanManager.h index 7c59b6d340d2..6702649402e6 100644 --- a/libs/hwui/renderthread/VulkanManager.h +++ b/libs/hwui/renderthread/VulkanManager.h @@ -176,6 +176,7 @@ private: VkPtr<PFN_vkDestroyInstance> mDestroyInstance; VkPtr<PFN_vkEnumeratePhysicalDevices> mEnumeratePhysicalDevices; + VkPtr<PFN_vkGetPhysicalDeviceProperties> mGetPhysicalDeviceProperties; VkPtr<PFN_vkGetPhysicalDeviceQueueFamilyProperties> mGetPhysicalDeviceQueueFamilyProperties; VkPtr<PFN_vkGetPhysicalDeviceFeatures2> mGetPhysicalDeviceFeatures2; VkPtr<PFN_vkCreateDevice> mCreateDevice; diff --git a/libs/services/include/android/os/StatsLogEventWrapper.h b/libs/services/include/android/os/StatsLogEventWrapper.h index 52cb75e8e639..f60c338bf9c4 100644 --- a/libs/services/include/android/os/StatsLogEventWrapper.h +++ b/libs/services/include/android/os/StatsLogEventWrapper.h @@ -58,6 +58,11 @@ struct StatsLogValue { type = FLOAT; } + StatsLogValue(double v) { + double_value = v; + type = DOUBLE; + } + StatsLogValue(const std::string& v) { str_value = v; type = STRING; diff --git a/libs/services/src/os/StatsLogEventWrapper.cpp b/libs/services/src/os/StatsLogEventWrapper.cpp index 04c4629b5432..a1a6d9fe0e22 100644 --- a/libs/services/src/os/StatsLogEventWrapper.cpp +++ b/libs/services/src/os/StatsLogEventWrapper.cpp @@ -85,6 +85,9 @@ status_t StatsLogEventWrapper::readFromParcel(const Parcel* in) { case StatsLogValue::FLOAT: mElements.push_back(StatsLogValue(in->readFloat())); break; + case StatsLogValue::DOUBLE: + mElements.push_back(StatsLogValue(in->readDouble())); + break; case StatsLogValue::STORAGE: mElements.push_back(StatsLogValue()); mElements.back().setType(StatsLogValue::STORAGE); diff --git a/location/java/android/location/Criteria.java b/location/java/android/location/Criteria.java index a6099be60875..74eb4450d42c 100644 --- a/location/java/android/location/Criteria.java +++ b/location/java/android/location/Criteria.java @@ -21,9 +21,9 @@ import android.os.Parcelable; /** * A class indicating the application criteria for selecting a - * location provider. Providers maybe ordered according to accuracy, - * power usage, ability to report altitude, speed, - * and bearing, and monetary cost. + * location provider. Providers may be ordered according to accuracy, + * power usage, ability to report altitude, speed, bearing, and monetary + * cost. */ public class Criteria implements Parcelable { /** diff --git a/location/lib/Android.bp b/location/lib/Android.bp index 447195d6d532..b09335c6707f 100644 --- a/location/lib/Android.bp +++ b/location/lib/Android.bp @@ -18,4 +18,5 @@ java_sdk_library { name: "com.android.location.provider", srcs: ["java/**/*.java"], api_packages: ["com.android.location.provider"], + metalava_enabled: false, } diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index 85eac4bd626f..c074ccebc62c 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -4607,7 +4607,7 @@ public class AudioManager { /** * The message sent to apps when the contents of the device list changes if they provide - * a {#link Handler} object to addOnAudioDeviceConnectionListener(). + * a {@link Handler} object to addOnAudioDeviceConnectionListener(). */ private final static int MSG_DEVICES_CALLBACK_REGISTERED = 0; private final static int MSG_DEVICES_DEVICES_ADDED = 1; diff --git a/media/java/android/media/AudioRecord.java b/media/java/android/media/AudioRecord.java index 452ba0f2e8ec..2a575b626a44 100644 --- a/media/java/android/media/AudioRecord.java +++ b/media/java/android/media/AudioRecord.java @@ -473,7 +473,7 @@ public class AudioRecord implements AudioRouting * .setSampleRate(32000) * .setChannelMask(AudioFormat.CHANNEL_IN_MONO) * .build()) - * .setBufferSize(2*minBuffSize) + * .setBufferSizeInBytes(2*minBuffSize) * .build(); * </pre> * <p> diff --git a/media/java/android/media/Image.java b/media/java/android/media/Image.java index dff5e9ae2e22..26b9b8cf85a7 100644 --- a/media/java/android/media/Image.java +++ b/media/java/android/media/Image.java @@ -75,7 +75,7 @@ public abstract class Image implements AutoCloseable { /** * Get the format for this image. This format determines the number of * ByteBuffers needed to represent the image, and the general layout of the - * pixel data in each in ByteBuffer. + * pixel data in each ByteBuffer. * * <p> * The format is one of the values from diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java index ed4da22f69e7..340f27950638 100644 --- a/media/java/android/media/MediaPlayer.java +++ b/media/java/android/media/MediaPlayer.java @@ -336,7 +336,7 @@ import java.util.Vector; * * <table border="0" cellspacing="0" cellpadding="0"> * <tr><td>Method Name </p></td> - * <td>Valid Sates </p></td> + * <td>Valid States </p></td> * <td>Invalid States </p></td> * <td>Comments </p></td></tr> * <tr><td>attachAuxEffect </p></td> diff --git a/media/java/android/media/MediaPlayer2.java b/media/java/android/media/MediaPlayer2.java index 7492aa605dc6..0f83fd5803de 100644 --- a/media/java/android/media/MediaPlayer2.java +++ b/media/java/android/media/MediaPlayer2.java @@ -30,6 +30,8 @@ import android.os.PersistableBundle; import android.view.Surface; import android.view.SurfaceHolder; +import dalvik.system.CloseGuard; + import java.io.FileDescriptor; import java.io.IOException; import java.io.InputStream; @@ -457,6 +459,8 @@ import java.util.concurrent.Executor; public abstract class MediaPlayer2 implements SubtitleController.Listener , AutoCloseable , AudioRouting { + private final CloseGuard mGuard = CloseGuard.get(); + /** * Create a MediaPlayer2 object. * @@ -512,7 +516,9 @@ public abstract class MediaPlayer2 implements SubtitleController.Listener * @hide */ // add hidden empty constructor so it doesn't show in SDK - public MediaPlayer2() { } + public MediaPlayer2() { + mGuard.open("close"); + } /** * Returns a {@link MediaPlayerBase} implementation which runs based on @@ -545,7 +551,22 @@ public abstract class MediaPlayer2 implements SubtitleController.Listener */ // This is a synchronous call. @Override - public abstract void close(); + public void close() { + synchronized (mGuard) { + mGuard.close(); + } + } + + // Have to declare protected for finalize() since it is protected + // in the base class Object. + @Override + protected void finalize() throws Throwable { + if (mGuard != null) { + mGuard.warnIfOpen(); + } + + close(); + } /** * Starts or resumes playback. If playback had previously been paused, diff --git a/media/java/android/media/MediaPlayer2Impl.java b/media/java/android/media/MediaPlayer2Impl.java index 84d246f50ee3..ec8a1f2cdede 100644 --- a/media/java/android/media/MediaPlayer2Impl.java +++ b/media/java/android/media/MediaPlayer2Impl.java @@ -36,8 +36,6 @@ import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; import android.os.Message; -import android.os.Parcel; -import android.os.Parcelable; import android.os.PersistableBundle; import android.os.PowerManager; import android.os.Process; @@ -57,10 +55,7 @@ import com.android.framework.protobuf.InvalidProtocolBufferException; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.Preconditions; -import dalvik.system.CloseGuard; - import libcore.io.IoBridge; -import libcore.io.Streams; import java.io.ByteArrayOutputStream; import java.io.File; @@ -82,7 +77,6 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Scanner; -import java.util.Set; import java.util.UUID; import java.util.Vector; import java.util.concurrent.Executor; @@ -108,7 +102,6 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { private boolean mScreenOnWhilePlaying; private boolean mStayAwake; private int mStreamType = AudioManager.USE_DEFAULT_STREAM_TYPE; - private final CloseGuard mGuard = CloseGuard.get(); private final Object mSrcLock = new Object(); //--- guarded by |mSrcLock| start @@ -148,6 +141,9 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { @GuardedBy("mTaskLock") private Task mCurrentTask; + @GuardedBy("this") + private boolean mReleased; + /** * Default constructor. * <p>When done with the MediaPlayer2Impl, you should call {@link #close()}, @@ -162,7 +158,6 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { mTimeProvider = new TimeProvider(this); mOpenSubtitleSources = new Vector<InputStream>(); - mGuard.open("close"); /* Native setup requires a weak reference to our object. * It's easier to create it here than in C++. @@ -200,9 +195,8 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { */ @Override public void close() { - synchronized (mGuard) { - release(); - } + super.close(); + release(); } /** @@ -335,19 +329,14 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { final String msg = "Cannot set AudioAttributes to null"; throw new IllegalArgumentException(msg); } - Parcel pattributes = Parcel.obtain(); - attributes.writeToParcel(pattributes, AudioAttributes.FLATTEN_TAGS); - setParameter(KEY_PARAMETER_AUDIO_ATTRIBUTES, pattributes); - pattributes.recycle(); + setParameter(KEY_PARAMETER_AUDIO_ATTRIBUTES, attributes); } }); } @Override public @NonNull AudioAttributes getAudioAttributes() { - Parcel pattributes = getParameter(KEY_PARAMETER_AUDIO_ATTRIBUTES); - AudioAttributes attributes = AudioAttributes.CREATOR.createFromParcel(pattributes); - pattributes.recycle(); + AudioAttributes attributes = (AudioAttributes) getParameter(KEY_PARAMETER_AUDIO_ATTRIBUTES); return attributes; } @@ -1588,9 +1577,9 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { * @param value value of the parameter to be set. * @return true if the parameter is set successfully, false otherwise */ - private native boolean setParameter(int key, Parcel value); + private native boolean setParameter(int key, Object value); - private native Parcel getParameter(int key); + private native Object getParameter(int key); /** @@ -2455,15 +2444,14 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { // in the base class Object. @Override protected void finalize() throws Throwable { - if (mGuard != null) { - mGuard.warnIfOpen(); - } - - close(); + super.finalize(); native_finalize(); } - private void release() { + private synchronized void release() { + if (mReleased) { + return; + } stayAwake(false); updateSurfaceScreenOn(); synchronized (mEventCbLock) { @@ -2486,6 +2474,7 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { resetDrmState(); _release(); + mReleased = true; } private native void _release(); @@ -3681,7 +3670,7 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { supportedSchemes = new UUID[supportedDRMsCount]; for (int i = 0; i < supportedDRMsCount; i++) { byte[] uuid = new byte[16]; - in.next().getBytesValue().copyTo(uuid, uuid.length); + in.next().getBytesValue().copyTo(uuid, 0); supportedSchemes[i] = bytesToUUID(uuid); @@ -3689,7 +3678,7 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { supportedSchemes[i]); } - Log.v(TAG, "DrmInfoImpl() Parcel psshsize: " + pssh.length + + Log.v(TAG, "DrmInfoImpl() psshsize: " + pssh.length + " supportedDRMsCount: " + supportedDRMsCount); } @@ -3954,7 +3943,7 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { connection.setReadTimeout(TIMEOUT_MS); connection.connect(); - response = Streams.readFully(connection.getInputStream()); + response = readInputStreamFully(connection.getInputStream()); Log.v(TAG, "HandleProvisioninig: Thread run: response " + response.length + " " + response); @@ -4034,6 +4023,29 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { finished = true; } // run() + /** + * Returns a byte[] containing the remainder of 'in', closing it when done. + */ + private byte[] readInputStreamFully(InputStream in) throws IOException { + try { + return readInputStreamFullyNoClose(in); + } finally { + in.close(); + } + } + + /** + * Returns a byte[] containing the remainder of 'in'. + */ + private byte[] readInputStreamFullyNoClose(InputStream in) throws IOException { + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + byte[] buffer = new byte[1024]; + int count; + while ((count = in.read(buffer)) != -1) { + bytes.write(buffer, 0, count); + } + return bytes.toByteArray(); + } } // ProvisioningThread private int HandleProvisioninig(UUID uuid) { @@ -4669,7 +4681,7 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { private void sendCompleteNotification(int status) { // In {@link #notifyWhenCommandLabelReached} case, a separate callback - // {#link #onCommandLabelReached} is already called in {@code process()}. + // {@link #onCommandLabelReached} is already called in {@code process()}. if (mMediaCallType == CALL_COMPLETED_NOTIFY_WHEN_COMMAND_LABEL_REACHED) { return; } diff --git a/media/java/android/media/MediaScanner.java b/media/java/android/media/MediaScanner.java index 0ff2d8f6b6ec..c537945624fe 100644 --- a/media/java/android/media/MediaScanner.java +++ b/media/java/android/media/MediaScanner.java @@ -160,8 +160,9 @@ public class MediaScanner implements AutoCloseable { public static final String SCANNED_BUILD_PREFS_NAME = "MediaScanBuild"; public static final String LAST_INTERNAL_SCAN_FINGERPRINT = "lastScanFingerprint"; - private static final String SYSTEM_SOUNDS_DIR = "/system/media/audio"; - private static final String PRODUCT_SOUNDS_DIR = "/product/media/audio"; + private static final String SYSTEM_SOUNDS_DIR = Environment.getRootDirectory() + "/media/audio"; + private static final String OEM_SOUNDS_DIR = Environment.getOemDirectory() + "/media/audio"; + private static final String PRODUCT_SOUNDS_DIR = Environment.getProductDirectory() + "/media/audio"; private static String sLastInternalScanFingerprint; private static final String[] ID3_GENRES = { @@ -1193,6 +1194,9 @@ public class MediaScanner implements AutoCloseable { if (path.startsWith(SYSTEM_SOUNDS_DIR + ALARMS_DIR) || path.startsWith(SYSTEM_SOUNDS_DIR + RINGTONES_DIR) || path.startsWith(SYSTEM_SOUNDS_DIR + NOTIFICATIONS_DIR) + || path.startsWith(OEM_SOUNDS_DIR + ALARMS_DIR) + || path.startsWith(OEM_SOUNDS_DIR + RINGTONES_DIR) + || path.startsWith(OEM_SOUNDS_DIR + NOTIFICATIONS_DIR) || path.startsWith(PRODUCT_SOUNDS_DIR + ALARMS_DIR) || path.startsWith(PRODUCT_SOUNDS_DIR + RINGTONES_DIR) || path.startsWith(PRODUCT_SOUNDS_DIR + NOTIFICATIONS_DIR)) { diff --git a/media/jni/OWNERS b/media/jni/OWNERS new file mode 100644 index 000000000000..bb91d4b26ecc --- /dev/null +++ b/media/jni/OWNERS @@ -0,0 +1,2 @@ +# extra for MTP related files +per-file android_mtp_*.cpp=marcone@google.com,jsharkey@android.com,jameswei@google.com,rmojumder@google.com diff --git a/media/jni/android_media_MediaPlayer2.cpp b/media/jni/android_media_MediaPlayer2.cpp index 1a844cc678c6..693a3d0b23df 100644 --- a/media/jni/android_media_MediaPlayer2.cpp +++ b/media/jni/android_media_MediaPlayer2.cpp @@ -1490,8 +1490,8 @@ static const JNINativeMethod gMethods[] = { {"_release", "()V", (void *)android_media_MediaPlayer2_release}, {"_reset", "()V", (void *)android_media_MediaPlayer2_reset}, {"_getAudioStreamType", "()I", (void *)android_media_MediaPlayer2_getAudioStreamType}, - {"setParameter", "(ILandroid/os/Parcel;)Z", (void *)android_media_MediaPlayer2_setParameter}, - {"getParameter", "(I)Landroid/os/Parcel;", (void *)android_media_MediaPlayer2_getParameter}, + {"setParameter", "(ILjava/lang/Object;)Z", (void *)android_media_MediaPlayer2_setParameter}, + {"getParameter", "(I)Ljava/lang/Object;", (void *)android_media_MediaPlayer2_getParameter}, {"setLooping", "(Z)V", (void *)android_media_MediaPlayer2_setLooping}, {"isLooping", "()Z", (void *)android_media_MediaPlayer2_isLooping}, {"_setVolume", "(FF)V", (void *)android_media_MediaPlayer2_setVolume}, diff --git a/media/lib/remotedisplay/Android.bp b/media/lib/remotedisplay/Android.bp index 1e9320d1414d..5f4b930f350e 100644 --- a/media/lib/remotedisplay/Android.bp +++ b/media/lib/remotedisplay/Android.bp @@ -14,22 +14,8 @@ // limitations under the License. // -droiddoc { - name: "com.android.media.remotedisplay.stubs-gen-docs", - srcs: [ - "java/**/*.java", - ], - args: " -hide 111 -hide 113 -hide 125 -hide 126 -hide 127 -hide 128 " + - " -stubpackages com.android.media.remotedisplay " + - " -nodocs ", - custom_template: "droiddoc-templates-sdk", - installable: false, -} - -java_library_static { - name: "com.android.media.remotedisplay.stubs", - srcs: [ - ":com.android.media.remotedisplay.stubs-gen-docs", - ], - sdk_version: "current", +java_sdk_library { + name: "com.android.media.remotedisplay", + srcs: ["java/**/*.java"], + api_packages: ["com.android.media.remotedisplay"], } diff --git a/media/lib/remotedisplay/Android.mk b/media/lib/remotedisplay/Android.mk deleted file mode 100644 index e88c0f1a8dc8..000000000000 --- a/media/lib/remotedisplay/Android.mk +++ /dev/null @@ -1,44 +0,0 @@ -# -# Copyright (C) 2013 The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -LOCAL_PATH := $(call my-dir) - -# the remotedisplay library -# ============================================================ -include $(CLEAR_VARS) - -LOCAL_MODULE:= com.android.media.remotedisplay -LOCAL_MODULE_TAGS := optional - -LOCAL_SRC_FILES := $(call all-java-files-under, java) - -include $(BUILD_JAVA_LIBRARY) - - -# ==== com.android.media.remotedisplay.xml lib def ======================== -include $(CLEAR_VARS) - -LOCAL_MODULE := com.android.media.remotedisplay.xml -LOCAL_MODULE_TAGS := optional - -LOCAL_MODULE_CLASS := ETC - -# This will install the file in /system/etc/permissions -# -LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)/permissions - -LOCAL_SRC_FILES := $(LOCAL_MODULE) - -include $(BUILD_PREBUILT) diff --git a/media/lib/remotedisplay/api/current.txt b/media/lib/remotedisplay/api/current.txt new file mode 100644 index 000000000000..e69de29bb2d1 --- /dev/null +++ b/media/lib/remotedisplay/api/current.txt diff --git a/media/lib/remotedisplay/api/removed.txt b/media/lib/remotedisplay/api/removed.txt new file mode 100644 index 000000000000..e69de29bb2d1 --- /dev/null +++ b/media/lib/remotedisplay/api/removed.txt diff --git a/media/lib/remotedisplay/api/system-current.txt b/media/lib/remotedisplay/api/system-current.txt new file mode 100644 index 000000000000..69bbd35fce59 --- /dev/null +++ b/media/lib/remotedisplay/api/system-current.txt @@ -0,0 +1,52 @@ +package com.android.media.remotedisplay { + + public class RemoteDisplay { + ctor public RemoteDisplay(java.lang.String, java.lang.String); + method public java.lang.String getDescription(); + method public java.lang.String getId(); + method public java.lang.String getName(); + method public int getPresentationDisplayId(); + method public int getStatus(); + method public int getVolume(); + method public int getVolumeHandling(); + method public int getVolumeMax(); + method public void setDescription(java.lang.String); + method public void setName(java.lang.String); + method public void setPresentationDisplayId(int); + method public void setStatus(int); + method public void setVolume(int); + method public void setVolumeHandling(int); + method public void setVolumeMax(int); + field public static final int PLAYBACK_VOLUME_FIXED = 0; // 0x0 + field public static final int PLAYBACK_VOLUME_VARIABLE = 1; // 0x1 + field public static final int STATUS_AVAILABLE = 2; // 0x2 + field public static final int STATUS_CONNECTED = 4; // 0x4 + field public static final int STATUS_CONNECTING = 3; // 0x3 + field public static final int STATUS_IN_USE = 1; // 0x1 + field public static final int STATUS_NOT_AVAILABLE = 0; // 0x0 + } + + public abstract class RemoteDisplayProvider { + ctor public RemoteDisplayProvider(android.content.Context); + method public void addDisplay(com.android.media.remotedisplay.RemoteDisplay); + method public com.android.media.remotedisplay.RemoteDisplay findRemoteDisplay(java.lang.String); + method public android.os.IBinder getBinder(); + method public final android.content.Context getContext(); + method public int getDiscoveryMode(); + method public java.util.Collection<com.android.media.remotedisplay.RemoteDisplay> getDisplays(); + method public android.app.PendingIntent getSettingsPendingIntent(); + method public void onAdjustVolume(com.android.media.remotedisplay.RemoteDisplay, int); + method public void onConnect(com.android.media.remotedisplay.RemoteDisplay); + method public void onDisconnect(com.android.media.remotedisplay.RemoteDisplay); + method public void onDiscoveryModeChanged(int); + method public void onSetVolume(com.android.media.remotedisplay.RemoteDisplay, int); + method public void removeDisplay(com.android.media.remotedisplay.RemoteDisplay); + method public void updateDisplay(com.android.media.remotedisplay.RemoteDisplay); + field public static final int DISCOVERY_MODE_ACTIVE = 2; // 0x2 + field public static final int DISCOVERY_MODE_NONE = 0; // 0x0 + field public static final int DISCOVERY_MODE_PASSIVE = 1; // 0x1 + field public static final java.lang.String SERVICE_INTERFACE = "com.android.media.remotedisplay.RemoteDisplayProvider"; + } + +} + diff --git a/media/lib/remotedisplay/api/system-removed.txt b/media/lib/remotedisplay/api/system-removed.txt new file mode 100644 index 000000000000..e69de29bb2d1 --- /dev/null +++ b/media/lib/remotedisplay/api/system-removed.txt diff --git a/media/lib/remotedisplay/api/test-current.txt b/media/lib/remotedisplay/api/test-current.txt new file mode 100644 index 000000000000..e69de29bb2d1 --- /dev/null +++ b/media/lib/remotedisplay/api/test-current.txt diff --git a/media/lib/remotedisplay/api/test-removed.txt b/media/lib/remotedisplay/api/test-removed.txt new file mode 100644 index 000000000000..e69de29bb2d1 --- /dev/null +++ b/media/lib/remotedisplay/api/test-removed.txt diff --git a/media/lib/remotedisplay/com.android.media.remotedisplay.xml b/media/lib/remotedisplay/com.android.media.remotedisplay.xml deleted file mode 100644 index 77a91d23e1d8..000000000000 --- a/media/lib/remotedisplay/com.android.media.remotedisplay.xml +++ /dev/null @@ -1,20 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2013 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> - -<permissions> - <library name="com.android.media.remotedisplay" - file="/system/framework/com.android.media.remotedisplay.jar" /> -</permissions> diff --git a/media/lib/remotedisplay/java/com/android/media/remotedisplay/RemoteDisplay.java b/media/lib/remotedisplay/java/com/android/media/remotedisplay/RemoteDisplay.java index dc9dd79eb2b1..8de414b45a4d 100644 --- a/media/lib/remotedisplay/java/com/android/media/remotedisplay/RemoteDisplay.java +++ b/media/lib/remotedisplay/java/com/android/media/remotedisplay/RemoteDisplay.java @@ -16,6 +16,7 @@ package com.android.media.remotedisplay; +import android.annotation.SystemApi; import android.media.RemoteDisplayState.RemoteDisplayInfo; import android.text.TextUtils; @@ -23,7 +24,10 @@ import java.util.Objects; /** * Represents a remote display that has been discovered. + * + * @hide */ +@SystemApi public class RemoteDisplay { private final RemoteDisplayInfo mMutableInfo; private RemoteDisplayInfo mImmutableInfo; diff --git a/media/lib/remotedisplay/java/com/android/media/remotedisplay/RemoteDisplayProvider.java b/media/lib/remotedisplay/java/com/android/media/remotedisplay/RemoteDisplayProvider.java index 4d3edb896eb5..7017e444d717 100644 --- a/media/lib/remotedisplay/java/com/android/media/remotedisplay/RemoteDisplayProvider.java +++ b/media/lib/remotedisplay/java/com/android/media/remotedisplay/RemoteDisplayProvider.java @@ -16,6 +16,7 @@ package com.android.media.remotedisplay; +import android.annotation.SystemApi; import android.app.PendingIntent; import android.app.Service; import android.content.Context; @@ -88,7 +89,10 @@ import java.util.Collection; * IMPORTANT: This class is effectively a public API for unbundled applications, and * must remain API stable. See README.txt in the root of this package for more information. * </p> + * + * @hide */ +@SystemApi public abstract class RemoteDisplayProvider { private static final int MSG_SET_CALLBACK = 1; private static final int MSG_SET_DISCOVERY_MODE = 2; diff --git a/media/lib/signer/Android.bp b/media/lib/signer/Android.bp index 3b2578754087..8c43683c2eec 100644 --- a/media/lib/signer/Android.bp +++ b/media/lib/signer/Android.bp @@ -18,4 +18,5 @@ java_sdk_library { name: "com.android.mediadrm.signer", srcs: ["java/**/*.java"], api_packages: ["com.android.mediadrm.signer"], + metalava_enabled: false, } diff --git a/media/mca/filterpacks/java/android/filterpacks/videosrc/SurfaceTextureSource.java b/media/mca/filterpacks/java/android/filterpacks/videosrc/SurfaceTextureSource.java index 6595baa594d2..7919723b80a8 100644 --- a/media/mca/filterpacks/java/android/filterpacks/videosrc/SurfaceTextureSource.java +++ b/media/mca/filterpacks/java/android/filterpacks/videosrc/SurfaceTextureSource.java @@ -38,7 +38,7 @@ import android.util.Log; * <p>To use, connect up the sourceListener callback, and then when executing * the graph, use the SurfaceTexture object passed to the callback to feed * frames into the filter graph. For example, pass the SurfaceTexture into - * {#link + * {@link * android.hardware.Camera.setPreviewTexture(android.graphics.SurfaceTexture)}. * This filter is intended for applications that need for flexibility than the * CameraSource and MediaSource provide. Note that the application needs to diff --git a/media/tests/MtpTests/OWNERS b/media/tests/MtpTests/OWNERS new file mode 100644 index 000000000000..1928ba811e7e --- /dev/null +++ b/media/tests/MtpTests/OWNERS @@ -0,0 +1,7 @@ +set noparent + +marcone@google.com +jsharkey@android.com +jameswei@google.com +rmojumder@google.com + diff --git a/native/android/system_fonts.cpp b/native/android/system_fonts.cpp index b95adad78f89..761a475a2773 100644 --- a/native/android/system_fonts.cpp +++ b/native/android/system_fonts.cpp @@ -43,6 +43,9 @@ using XmlDocUniquePtr = std::unique_ptr<xmlDoc, XmlDocDeleter>; struct ASystemFontIterator { XmlDocUniquePtr mXmlDoc; xmlNode* mFontNode; + + // The OEM customization XML. + XmlDocUniquePtr mCustomizationXmlDoc; }; struct ASystemFont { @@ -93,29 +96,30 @@ xmlNode* nextSibling(xmlNode* node, const xmlChar* tag) { return nullptr; } -void copyFont(ASystemFontIterator* ite, ASystemFont* out) { +void copyFont(const XmlDocUniquePtr& xmlDoc, xmlNode* fontNode, ASystemFont* out, + const std::string& pathPrefix) { const xmlChar* LOCALE_ATTR_NAME = BAD_CAST("lang"); XmlCharUniquePtr filePathStr( - xmlNodeListGetString(ite->mXmlDoc.get(), ite->mFontNode->xmlChildrenNode, 1)); - out->mFilePath = "/system/fonts/" + xmlTrim( + xmlNodeListGetString(xmlDoc.get(), fontNode->xmlChildrenNode, 1)); + out->mFilePath = pathPrefix + xmlTrim( std::string(filePathStr.get(), filePathStr.get() + xmlStrlen(filePathStr.get()))); const xmlChar* WEIGHT_ATTR_NAME = BAD_CAST("weight"); - XmlCharUniquePtr weightStr(xmlGetProp(ite->mFontNode, WEIGHT_ATTR_NAME)); + XmlCharUniquePtr weightStr(xmlGetProp(fontNode, WEIGHT_ATTR_NAME)); out->mWeight = weightStr ? strtol(reinterpret_cast<const char*>(weightStr.get()), nullptr, 10) : 400; const xmlChar* STYLE_ATTR_NAME = BAD_CAST("style"); const xmlChar* ITALIC_ATTR_VALUE = BAD_CAST("italic"); - XmlCharUniquePtr styleStr(xmlGetProp(ite->mFontNode, STYLE_ATTR_NAME)); + XmlCharUniquePtr styleStr(xmlGetProp(fontNode, STYLE_ATTR_NAME)); out->mItalic = styleStr ? xmlStrEqual(styleStr.get(), ITALIC_ATTR_VALUE) : false; const xmlChar* INDEX_ATTR_NAME = BAD_CAST("index"); - XmlCharUniquePtr indexStr(xmlGetProp(ite->mFontNode, INDEX_ATTR_NAME)); + XmlCharUniquePtr indexStr(xmlGetProp(fontNode, INDEX_ATTR_NAME)); out->mCollectionIndex = indexStr ? strtol(reinterpret_cast<const char*>(indexStr.get()), nullptr, 10) : 0; - XmlCharUniquePtr localeStr(xmlGetProp(ite->mXmlDoc->parent, LOCALE_ATTR_NAME)); + XmlCharUniquePtr localeStr(xmlGetProp(xmlDoc->parent, LOCALE_ATTR_NAME)); out->mLocale.reset( localeStr ? new std::string(reinterpret_cast<const char*>(localeStr.get())) : nullptr); @@ -123,7 +127,7 @@ void copyFont(ASystemFontIterator* ite, ASystemFont* out) { const xmlChar* STYLEVALUE_ATTR_NAME = BAD_CAST("stylevalue"); const xmlChar* AXIS_TAG = BAD_CAST("axis"); out->mAxes.clear(); - for (xmlNode* axis = firstElement(ite->mFontNode, AXIS_TAG); axis; + for (xmlNode* axis = firstElement(fontNode, AXIS_TAG); axis; axis = nextSibling(axis, AXIS_TAG)) { XmlCharUniquePtr tagStr(xmlGetProp(axis, TAG_ATTR_NAME)); if (!tagStr || xmlStrlen(tagStr.get()) != 4) { @@ -154,8 +158,8 @@ bool isFontFileAvailable(const std::string& filePath) { return S_ISREG(st.st_mode); } -xmlNode* findFirstFontNode(xmlDoc* doc) { - xmlNode* familySet = xmlDocGetRootElement(doc); +xmlNode* findFirstFontNode(const XmlDocUniquePtr& doc) { + xmlNode* familySet = xmlDocGetRootElement(doc.get()); if (familySet == nullptr) { return nullptr; } @@ -180,6 +184,7 @@ xmlNode* findFirstFontNode(xmlDoc* doc) { ASystemFontIterator* ASystemFontIterator_open() { std::unique_ptr<ASystemFontIterator> ite(new ASystemFontIterator()); ite->mXmlDoc.reset(xmlReadFile("/system/etc/fonts.xml", nullptr, 0)); + ite->mCustomizationXmlDoc.reset(xmlReadFile("/product/etc/fonts_customization.xml", nullptr, 0)); return ite.release(); } @@ -187,45 +192,62 @@ void ASystemFontIterator_close(ASystemFontIterator* ite) { delete ite; } -ASystemFont* ASystemFontIterator_next(ASystemFontIterator* ite) { - LOG_ALWAYS_FATAL_IF(ite == nullptr, "nullptr has passed as iterator argument"); - if (ite->mFontNode == nullptr) { - if (ite->mXmlDoc == nullptr) { +xmlNode* findNextFontNode(const XmlDocUniquePtr& xmlDoc, xmlNode* fontNode) { + if (fontNode == nullptr) { + if (!xmlDoc) { return nullptr; // Already at the end. } else { // First time to query font. - ite->mFontNode = findFirstFontNode(ite->mXmlDoc.get()); - if (ite->mFontNode == nullptr) { - ite->mXmlDoc.reset(); - return nullptr; // No font node found. - } - std::unique_ptr<ASystemFont> font = std::make_unique<ASystemFont>(); - copyFont(ite, font.get()); - return font.release(); + return findFirstFontNode(xmlDoc); } } else { - xmlNode* nextNode = nextSibling(ite->mFontNode, FONT_TAG); + xmlNode* nextNode = nextSibling(fontNode, FONT_TAG); while (nextNode == nullptr) { - xmlNode* family = nextSibling(ite->mFontNode->parent, FAMILY_TAG); + xmlNode* family = nextSibling(fontNode->parent, FAMILY_TAG); if (family == nullptr) { break; } nextNode = firstElement(family, FONT_TAG); } - ite->mFontNode = nextNode; - if (nextNode == nullptr) { + return nextNode; + } +} + +ASystemFont* ASystemFontIterator_next(ASystemFontIterator* ite) { + LOG_ALWAYS_FATAL_IF(ite == nullptr, "nullptr has passed as iterator argument"); + if (ite->mXmlDoc) { + ite->mFontNode = findNextFontNode(ite->mXmlDoc, ite->mFontNode); + if (ite->mFontNode == nullptr) { + // Reached end of the XML file. Continue OEM customization. ite->mXmlDoc.reset(); - return nullptr; + ite->mFontNode = nullptr; + } else { + std::unique_ptr<ASystemFont> font = std::make_unique<ASystemFont>(); + copyFont(ite->mXmlDoc, ite->mFontNode, font.get(), "/system/fonts/"); + if (!isFontFileAvailable(font->mFilePath)) { + return ASystemFontIterator_next(ite); + } + return font.release(); } - - std::unique_ptr<ASystemFont> font = std::make_unique<ASystemFont>(); - copyFont(ite, font.get()); - if (!isFontFileAvailable(font->mFilePath)) { - // fonts.xml intentionally contains missing font configuration. Skip it. - return ASystemFontIterator_next(ite); + } + if (ite->mCustomizationXmlDoc) { + // TODO: Filter only customizationType="new-named-family" + ite->mFontNode = findNextFontNode(ite->mCustomizationXmlDoc, ite->mFontNode); + if (ite->mFontNode == nullptr) { + // Reached end of the XML file. Finishing + ite->mCustomizationXmlDoc.reset(); + ite->mFontNode = nullptr; + return nullptr; + } else { + std::unique_ptr<ASystemFont> font = std::make_unique<ASystemFont>(); + copyFont(ite->mCustomizationXmlDoc, ite->mFontNode, font.get(), "/product/fonts/"); + if (!isFontFileAvailable(font->mFilePath)) { + return ASystemFontIterator_next(ite); + } + return font.release(); } - return font.release(); } + return nullptr; } void ASystemFont_close(ASystemFont* font) { diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java index 580308a4cffd..8c29a2520390 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java @@ -430,9 +430,14 @@ public class PackageInstallerActivity extends AlertActivity { // Check for unknown sources restriction final int unknownSourcesRestrictionSource = mUserManager.getUserRestrictionSource( UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES, Process.myUserHandle()); - if ((unknownSourcesRestrictionSource & UserManager.RESTRICTION_SOURCE_SYSTEM) != 0) { + final int unknownSourcesGlobalRestrictionSource = mUserManager.getUserRestrictionSource( + UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY, Process.myUserHandle()); + final int systemRestriction = UserManager.RESTRICTION_SOURCE_SYSTEM + & (unknownSourcesRestrictionSource | unknownSourcesGlobalRestrictionSource); + if (systemRestriction != 0) { showDialogInner(DLG_UNKNOWN_SOURCES_RESTRICTED_FOR_USER); - } else if (unknownSourcesRestrictionSource != UserManager.RESTRICTION_NOT_SET) { + } else if (unknownSourcesRestrictionSource != UserManager.RESTRICTION_NOT_SET + || unknownSourcesGlobalRestrictionSource != UserManager.RESTRICTION_NOT_SET) { startActivity(new Intent(Settings.ACTION_SHOW_ADMIN_SUPPORT_DETAILS)); finish(); } else { diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp index ee4c95445bff..89438e555b0d 100644 --- a/packages/SettingsLib/Android.bp +++ b/packages/SettingsLib/Android.bp @@ -15,6 +15,7 @@ android_library { "SettingsLibHelpUtils", "SettingsLibRestrictedLockUtils", "SettingsLibAppPreference", + "SettingsLibSearchWidget", ], // ANDROIDMK TRANSLATION ERROR: unsupported assignment to LOCAL_SHARED_JAVA_LIBRARIES diff --git a/packages/SettingsLib/HelpUtils/res/values-nl/strings.xml b/packages/SettingsLib/HelpUtils/res/values-nl/strings.xml index a034d297bac8..2f576e62202e 100644 --- a/packages/SettingsLib/HelpUtils/res/values-nl/strings.xml +++ b/packages/SettingsLib/HelpUtils/res/values-nl/strings.xml @@ -17,5 +17,5 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="help_feedback_label" msgid="4550436169116444686">"Help en feedback"</string> + <string name="help_feedback_label" msgid="4550436169116444686">"Hulp en feedback"</string> </resources> diff --git a/packages/SettingsLib/SearchWidget/Android.bp b/packages/SettingsLib/SearchWidget/Android.bp new file mode 100644 index 000000000000..7541ca456138 --- /dev/null +++ b/packages/SettingsLib/SearchWidget/Android.bp @@ -0,0 +1,8 @@ +android_library { + name: "SettingsLibSearchWidget", + + srcs: ["src/**/*.java"], + resource_dirs: ["res"], + sdk_version: "system_current", + min_sdk_version: "21", +} diff --git a/packages/SettingsLib/SearchWidget/AndroidManifest.xml b/packages/SettingsLib/SearchWidget/AndroidManifest.xml new file mode 100644 index 000000000000..b86544ec68c1 --- /dev/null +++ b/packages/SettingsLib/SearchWidget/AndroidManifest.xml @@ -0,0 +1,23 @@ +<?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. + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.settingslib.search"> + + <uses-sdk android:minSdkVersion="21" /> + +</manifest> diff --git a/packages/SettingsLib/SearchWidget/res/drawable/ic_search_24dp.xml b/packages/SettingsLib/SearchWidget/res/drawable/ic_search_24dp.xml new file mode 100644 index 000000000000..7e65848de189 --- /dev/null +++ b/packages/SettingsLib/SearchWidget/res/drawable/ic_search_24dp.xml @@ -0,0 +1,27 @@ +<?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="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24" + android:tint="?android:attr/colorControlNormal"> + <path + android:fillColor="#FF000000" + android:pathData="M20.49,19l-5.73,-5.73C15.53,12.2 16,10.91 16,9.5C16,5.91 13.09,3 9.5,3S3,5.91 3,9.5C3,13.09 5.91,16 9.5,16c1.41,0 2.7,-0.47 3.77,-1.24L19,20.49L20.49,19zM5,9.5C5,7.01 7.01,5 9.5,5S14,7.01 14,9.5S11.99,14 9.5,14S5,11.99 5,9.5z"/> +</vector> diff --git a/packages/SettingsLib/SearchWidget/res/values/strings.xml b/packages/SettingsLib/SearchWidget/res/values/strings.xml new file mode 100644 index 000000000000..0b12810ffc38 --- /dev/null +++ b/packages/SettingsLib/SearchWidget/res/values/strings.xml @@ -0,0 +1,20 @@ +<!-- + 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. + --> + +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- Text used as a search hint into the search box [CHAR_LIMIT=60]--> + <string name="search_menu">Search settings</string> +</resources> diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml index 332ced66ef77..508adbd2a121 100644 --- a/packages/SettingsLib/res/values/strings.xml +++ b/packages/SettingsLib/res/values/strings.xml @@ -1116,4 +1116,6 @@ <!-- time label for event have that happened very recently [CHAR LIMIT=60] --> <string name="time_unit_just_now">Just now</string> - </resources> + <!-- The notice header of Third-party licenses. not translatable --> + <string name="notice_header" translatable="false"></string> +</resources> diff --git a/packages/SettingsLib/search/Android.mk b/packages/SettingsLib/search/Android.mk index cb1989157db8..14f96269c54e 100644 --- a/packages/SettingsLib/search/Android.mk +++ b/packages/SettingsLib/search/Android.mk @@ -5,6 +5,9 @@ LOCAL_MODULE = SettingsLib-search LOCAL_SRC_FILES := $(call all-java-files-under, src) +LOCAL_RESOURCE_DIR := \ + $(LOCAL_PATH)/main/res + include $(BUILD_STATIC_JAVA_LIBRARY) diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java index 0c29f431ef3f..9653972414ee 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java @@ -30,6 +30,7 @@ import android.bluetooth.BluetoothMapClient; import android.bluetooth.BluetoothPan; import android.bluetooth.BluetoothPbap; import android.bluetooth.BluetoothPbapClient; +import android.bluetooth.BluetoothSap; import android.bluetooth.BluetoothProfile; import android.bluetooth.BluetoothUuid; import android.content.Context; @@ -98,6 +99,7 @@ public class LocalBluetoothProfileManager { private PbapClientProfile mPbapClientProfile; private PbapServerProfile mPbapProfile; private HearingAidProfile mHearingAidProfile; + private SapProfile mSapProfile; /** * Mapping from profile name, e.g. "HEADSET" to profile object. @@ -210,6 +212,13 @@ public class LocalBluetoothProfileManager { addProfile(mPbapClientProfile, PbapClientProfile.NAME, BluetoothPbapClient.ACTION_CONNECTION_STATE_CHANGED); } + if (mSapProfile == null && supportedList.contains(BluetoothProfile.SAP)) { + if (DEBUG) { + Log.d(TAG, "Adding local SAP profile"); + } + mSapProfile = new SapProfile(mContext, mDeviceManager, this); + addProfile(mSapProfile, SapProfile.NAME, BluetoothSap.ACTION_CONNECTION_STATE_CHANGED); + } mEventManager.registerProfileIntentReceiver(); } @@ -550,6 +559,11 @@ public class LocalBluetoothProfileManager { removedProfiles.remove(mHearingAidProfile); } + if (mSapProfile != null && BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.SAP)) { + profiles.add(mSapProfile); + removedProfiles.remove(mSapProfile); + } + if (DEBUG) { Log.d(TAG,"New Profiles" + profiles.toString()); } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/SapProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/SapProfile.java index 9a6f104fadd5..b4acc4810faf 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/SapProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/SapProfile.java @@ -36,12 +36,10 @@ import java.util.List; */ final class SapProfile implements LocalBluetoothProfile { private static final String TAG = "SapProfile"; - private static boolean V = true; private BluetoothSap mService; private boolean mIsProfileReady; - private final LocalBluetoothAdapter mLocalAdapter; private final CachedBluetoothDeviceManager mDeviceManager; private final LocalBluetoothProfileManager mProfileManager; @@ -59,7 +57,7 @@ final class SapProfile implements LocalBluetoothProfile { implements BluetoothProfile.ServiceListener { public void onServiceConnected(int profile, BluetoothProfile proxy) { - if (V) Log.d(TAG,"Bluetooth service connected"); + Log.d(TAG, "Bluetooth service connected, profile:" + profile); mService = (BluetoothSap) proxy; // We just bound to the service, so refresh the UI for any connected SAP devices. List<BluetoothDevice> deviceList = mService.getConnectedDevices(); @@ -81,7 +79,7 @@ final class SapProfile implements LocalBluetoothProfile { } public void onServiceDisconnected(int profile) { - if (V) Log.d(TAG,"Bluetooth service disconnected"); + Log.d(TAG, "Bluetooth service disconnected, profile:" + profile); mProfileManager.callServiceDisconnectedListeners(); mIsProfileReady=false; } @@ -96,13 +94,11 @@ final class SapProfile implements LocalBluetoothProfile { return BluetoothProfile.SAP; } - SapProfile(Context context, LocalBluetoothAdapter adapter, - CachedBluetoothDeviceManager deviceManager, + SapProfile(Context context, CachedBluetoothDeviceManager deviceManager, LocalBluetoothProfileManager profileManager) { - mLocalAdapter = adapter; mDeviceManager = deviceManager; mProfileManager = profileManager; - mLocalAdapter.getProfileProxy(context, new SapServiceListener(), + BluetoothAdapter.getDefaultAdapter().getProfileProxy(context, new SapServiceListener(), BluetoothProfile.SAP); } @@ -115,50 +111,47 @@ final class SapProfile implements LocalBluetoothProfile { } public boolean connect(BluetoothDevice device) { - if (mService == null) return false; - List<BluetoothDevice> sinks = mService.getConnectedDevices(); - if (sinks != null) { - for (BluetoothDevice sink : sinks) { - mService.disconnect(sink); - } + if (mService == null) { + return false; } return mService.connect(device); } public boolean disconnect(BluetoothDevice device) { - if (mService == null) return false; - List<BluetoothDevice> deviceList = mService.getConnectedDevices(); - if (!deviceList.isEmpty() && deviceList.get(0).equals(device)) { - if (mService.getPriority(device) > BluetoothProfile.PRIORITY_ON) { - mService.setPriority(device, BluetoothProfile.PRIORITY_ON); - } - return mService.disconnect(device); - } else { + if (mService == null) { return false; } + if (mService.getPriority(device) > BluetoothProfile.PRIORITY_ON) { + mService.setPriority(device, BluetoothProfile.PRIORITY_ON); + } + return mService.disconnect(device); } public int getConnectionStatus(BluetoothDevice device) { - if (mService == null) return BluetoothProfile.STATE_DISCONNECTED; - List<BluetoothDevice> deviceList = mService.getConnectedDevices(); - - return !deviceList.isEmpty() && deviceList.get(0).equals(device) - ? mService.getConnectionState(device) - : BluetoothProfile.STATE_DISCONNECTED; + if (mService == null) { + return BluetoothProfile.STATE_DISCONNECTED; + } + return mService.getConnectionState(device); } public boolean isPreferred(BluetoothDevice device) { - if (mService == null) return false; + if (mService == null) { + return false; + } return mService.getPriority(device) > BluetoothProfile.PRIORITY_OFF; } public int getPreferred(BluetoothDevice device) { - if (mService == null) return BluetoothProfile.PRIORITY_OFF; + if (mService == null) { + return BluetoothProfile.PRIORITY_OFF; + } return mService.getPriority(device); } public void setPreferred(BluetoothDevice device, boolean preferred) { - if (mService == null) return; + if (mService == null) { + return; + } if (preferred) { if (mService.getPriority(device) < BluetoothProfile.PRIORITY_ON) { mService.setPriority(device, BluetoothProfile.PRIORITY_ON); @@ -169,7 +162,9 @@ final class SapProfile implements LocalBluetoothProfile { } public List<BluetoothDevice> getConnectedDevices() { - if (mService == null) return new ArrayList<BluetoothDevice>(0); + if (mService == null) { + return new ArrayList<BluetoothDevice>(0); + } return mService.getDevicesMatchingConnectionStates( new int[] {BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_CONNECTING, @@ -207,11 +202,11 @@ final class SapProfile implements LocalBluetoothProfile { } protected void finalize() { - if (V) Log.d(TAG, "finalize()"); + Log.d(TAG, "finalize()"); if (mService != null) { try { BluetoothAdapter.getDefaultAdapter().closeProfileProxy(BluetoothProfile.SAP, - mService); + mService); mService = null; }catch (Throwable t) { Log.w(TAG, "Error cleaning up SAP proxy", t); diff --git a/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXml.java b/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXml.java index 42306f6d46d0..9db4a35c1d78 100644 --- a/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXml.java +++ b/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXml.java @@ -46,7 +46,7 @@ import java.util.zip.GZIPInputStream; * TODO: Remove duplicate codes once backward support ends. */ class LicenseHtmlGeneratorFromXml { - private static final String TAG = "LicenseHtmlGeneratorFromXml"; + private static final String TAG = "LicenseGeneratorFromXml"; private static final String TAG_ROOT = "licenses"; private static final String TAG_FILE_NAME = "file-name"; @@ -107,12 +107,13 @@ class LicenseHtmlGeneratorFromXml { mXmlFiles = xmlFiles; } - public static boolean generateHtml(List<File> xmlFiles, File outputFile) { + public static boolean generateHtml(List<File> xmlFiles, File outputFile, + String noticeHeader) { LicenseHtmlGeneratorFromXml genertor = new LicenseHtmlGeneratorFromXml(xmlFiles); - return genertor.generateHtml(outputFile); + return genertor.generateHtml(outputFile, noticeHeader); } - private boolean generateHtml(File outputFile) { + private boolean generateHtml(File outputFile, String noticeHeader) { for (File xmlFile : mXmlFiles) { parse(xmlFile); } @@ -125,7 +126,8 @@ class LicenseHtmlGeneratorFromXml { try { writer = new PrintWriter(outputFile); - generateHtml(mFileNameToContentIdMap, mContentIdToFileContentMap, writer); + generateHtml(mFileNameToContentIdMap, mContentIdToFileContentMap, writer, + noticeHeader); writer.flush(); writer.close(); @@ -239,13 +241,18 @@ class LicenseHtmlGeneratorFromXml { @VisibleForTesting static void generateHtml(Map<String, String> fileNameToContentIdMap, - Map<String, String> contentIdToFileContentMap, PrintWriter writer) { + Map<String, String> contentIdToFileContentMap, PrintWriter writer, + String noticeHeader) { List<String> fileNameList = new ArrayList(); fileNameList.addAll(fileNameToContentIdMap.keySet()); Collections.sort(fileNameList); writer.println(HTML_HEAD_STRING); + if (!TextUtils.isEmpty(noticeHeader)) { + writer.println(noticeHeader); + } + int count = 0; Map<String, Integer> contentIdToOrderMap = new HashMap(); List<ContentIdAndFileNames> contentIdAndFileNamesList = new ArrayList(); diff --git a/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlLoader.java b/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlLoader.java index 393006940740..78e807cf1a1c 100644 --- a/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlLoader.java +++ b/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlLoader.java @@ -60,7 +60,7 @@ public class LicenseHtmlLoader extends AsyncLoader<File> { File cachedHtmlFile = getCachedHtmlFile(mContext); if (!isCachedHtmlFileOutdated(xmlFiles, cachedHtmlFile) - || generateHtmlFile(xmlFiles, cachedHtmlFile)) { + || generateHtmlFile(mContext, xmlFiles, cachedHtmlFile)) { return cachedHtmlFile; } diff --git a/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlLoaderCompat.java b/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlLoaderCompat.java index 360c19c2b795..ca6248505dc0 100644 --- a/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlLoaderCompat.java +++ b/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlLoaderCompat.java @@ -19,6 +19,7 @@ package com.android.settingslib.license; import android.content.Context; import android.util.Log; +import com.android.settingslib.R; import com.android.settingslib.utils.AsyncLoaderCompat; import java.io.File; @@ -65,7 +66,7 @@ public class LicenseHtmlLoaderCompat extends AsyncLoaderCompat<File> { File cachedHtmlFile = getCachedHtmlFile(mContext); if (!isCachedHtmlFileOutdated(xmlFiles, cachedHtmlFile) - || generateHtmlFile(xmlFiles, cachedHtmlFile)) { + || generateHtmlFile(mContext, xmlFiles, cachedHtmlFile)) { return cachedHtmlFile; } @@ -101,7 +102,8 @@ public class LicenseHtmlLoaderCompat extends AsyncLoaderCompat<File> { return outdated; } - static boolean generateHtmlFile(List<File> xmlFiles, File htmlFile) { - return LicenseHtmlGeneratorFromXml.generateHtml(xmlFiles, htmlFile); + static boolean generateHtmlFile(Context context, List<File> xmlFiles, File htmlFile) { + return LicenseHtmlGeneratorFromXml.generateHtml(xmlFiles, htmlFile, + context.getString(R.string.notice_header)); } } diff --git a/packages/SettingsLib/src/com/android/settingslib/net/ChartDataLoaderCompat.java b/packages/SettingsLib/src/com/android/settingslib/net/ChartDataLoaderCompat.java index 74bd97f40ff7..e9c523881373 100644 --- a/packages/SettingsLib/src/com/android/settingslib/net/ChartDataLoaderCompat.java +++ b/packages/SettingsLib/src/com/android/settingslib/net/ChartDataLoaderCompat.java @@ -37,7 +37,8 @@ import com.android.settingslib.AppItem; /** * Loader for historical chart data for both network and UID details. * - * Deprecated in favor of {@link NetworkCycleDataLoader} + * Deprecated in favor of {@link NetworkCycleChartDataLoader} and + * {@link NetworkCycleDataForUidLoader} * * @deprecated */ diff --git a/packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleChartData.java b/packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleChartData.java new file mode 100644 index 000000000000..9b3ff8b2e165 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleChartData.java @@ -0,0 +1,56 @@ +/* + * 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.settingslib.net; + +import java.util.List; +import java.util.concurrent.TimeUnit; + +/** + * Usage data in a billing cycle with bucketized data for plotting the usage chart. + */ +public class NetworkCycleChartData extends NetworkCycleData { + public static final long BUCKET_DURATION_MS = TimeUnit.DAYS.toMillis(1); + + private List<NetworkCycleData> mUsageBuckets; + + private NetworkCycleChartData() { + } + + public List<NetworkCycleData> getUsageBuckets() { + return mUsageBuckets; + } + + public static class Builder extends NetworkCycleData.Builder { + private NetworkCycleChartData mObject = new NetworkCycleChartData(); + + public Builder setUsageBuckets(List<NetworkCycleData> buckets) { + getObject().mUsageBuckets = buckets; + return this; + } + + @Override + protected NetworkCycleChartData getObject() { + return mObject; + } + + @Override + public NetworkCycleChartData build() { + return getObject(); + } + } + +} diff --git a/packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleChartDataLoader.java b/packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleChartDataLoader.java new file mode 100644 index 000000000000..7ae3398d42ea --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleChartDataLoader.java @@ -0,0 +1,106 @@ +/* + * 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.settingslib.net; + +import android.app.usage.NetworkStats; +import android.content.Context; +import android.os.RemoteException; +import android.util.Log; + +import java.util.ArrayList; +import java.util.List; + +/** + * Loader for network data usage history. It returns a list of usage data per billing cycle with + * bucketized usages. + */ +public class NetworkCycleChartDataLoader + extends NetworkCycleDataLoader<List<NetworkCycleChartData>> { + + private static final String TAG = "NetworkCycleChartLoader"; + + private final List<NetworkCycleChartData> mData; + + private NetworkCycleChartDataLoader(Builder builder) { + super(builder); + mData = new ArrayList<NetworkCycleChartData>(); + } + + @Override + void recordUsage(long start, long end) { + try { + final NetworkStats stats = mNetworkStatsManager.querySummary( + mNetworkType, mSubId, start, end); + final long total = getTotalUsage(stats); + if (total > 0L) { + final NetworkCycleChartData.Builder builder = new NetworkCycleChartData.Builder(); + builder.setUsageBuckets(getUsageBuckets(start, end)) + .setStartTime(start) + .setEndTime(end) + .setTotalUsage(total); + mData.add(builder.build()); + } + } catch (RemoteException e) { + Log.e(TAG, "Exception querying network detail.", e); + } + } + + @Override + List<NetworkCycleChartData> getCycleUsage() { + return mData; + } + + public static Builder<?> builder(Context context) { + return new Builder<NetworkCycleChartDataLoader>(context) { + @Override + public NetworkCycleChartDataLoader build() { + return new NetworkCycleChartDataLoader(this); + } + }; + } + + private List<NetworkCycleData> getUsageBuckets(long start, long end) { + final List<NetworkCycleData> data = new ArrayList<>(); + long bucketStart = start; + long bucketEnd = start + NetworkCycleChartData.BUCKET_DURATION_MS; + while (bucketEnd <= end) { + long usage = 0L; + try { + final NetworkStats stats = mNetworkStatsManager.querySummary( + mNetworkType, mSubId, bucketStart, bucketEnd); + usage = getTotalUsage(stats); + } catch (RemoteException e) { + Log.e(TAG, "Exception querying network detail.", e); + } + data.add(new NetworkCycleData.Builder() + .setStartTime(bucketStart).setEndTime(bucketEnd).setTotalUsage(usage).build()); + bucketStart = bucketEnd; + bucketEnd += NetworkCycleChartData.BUCKET_DURATION_MS; + } + return data; + } + + public static abstract class Builder<T extends NetworkCycleChartDataLoader> + extends NetworkCycleDataLoader.Builder<T> { + + public Builder(Context context) { + super(context); + } + + } + +} diff --git a/packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleData.java b/packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleData.java index 2d8c0de42ba4..26c65a2c4a48 100644 --- a/packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleData.java +++ b/packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleData.java @@ -16,54 +16,55 @@ package com.android.settingslib.net; -import java.util.List; -import java.util.concurrent.TimeUnit; - /** - * Data structure representing usage data in a billing cycle. + * Base data structure representing usage data in a billing cycle. */ public class NetworkCycleData { - public static final long BUCKET_DURATION_MS = TimeUnit.DAYS.toMillis(1); - public long startTime; - public long endTime; - public long totalUsage; - public List<NetworkCycleData> usageBuckets; - private NetworkCycleData(Builder builder) { - startTime = builder.mStart; - endTime = builder.mEnd; - totalUsage = builder.mTotalUsage; - usageBuckets = builder.mUsageBuckets; + private long mStartTime; + private long mEndTime; + private long mTotalUsage; + + protected NetworkCycleData() { + } + + public long getStartTime() { + return mStartTime; + } + + public long getEndTime() { + return mEndTime; + } + + public long getTotalUsage() { + return mTotalUsage; } public static class Builder { - private long mStart; - private long mEnd; - private long mTotalUsage; - private List<NetworkCycleData> mUsageBuckets; + + private NetworkCycleData mObject = new NetworkCycleData(); public Builder setStartTime(long start) { - mStart = start; + getObject().mStartTime = start; return this; } public Builder setEndTime(long end) { - mEnd = end; + getObject().mEndTime = end; return this; } public Builder setTotalUsage(long total) { - mTotalUsage = total; + getObject().mTotalUsage = total; return this; } - public Builder setUsageBuckets(List<NetworkCycleData> buckets) { - mUsageBuckets = buckets; - return this; + protected NetworkCycleData getObject() { + return mObject; } public NetworkCycleData build() { - return new NetworkCycleData(this); + return getObject(); } } } diff --git a/packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleDataForUid.java b/packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleDataForUid.java new file mode 100644 index 000000000000..9d13717bbbcc --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleDataForUid.java @@ -0,0 +1,65 @@ +/* + * 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.settingslib.net; + +import java.util.concurrent.TimeUnit; + +/** + * Usage data in a billing cycle for a specific Uid. + */ +public class NetworkCycleDataForUid extends NetworkCycleData { + + private long mBackgroudUsage; + private long mForegroudUsage; + + private NetworkCycleDataForUid() { + } + + public long getBackgroudUsage() { + return mBackgroudUsage; + } + + public long getForegroudUsage() { + return mForegroudUsage; + } + + public static class Builder extends NetworkCycleData.Builder { + + private NetworkCycleDataForUid mObject = new NetworkCycleDataForUid(); + + public Builder setBackgroundUsage(long backgroundUsage) { + getObject().mBackgroudUsage = backgroundUsage; + return this; + } + + public Builder setForegroundUsage(long foregroundUsage) { + getObject().mForegroudUsage = foregroundUsage; + return this; + } + + @Override + public NetworkCycleDataForUid getObject() { + return mObject; + } + + @Override + public NetworkCycleDataForUid build() { + return getObject(); + } + } + +} diff --git a/packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleDataForUidLoader.java b/packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleDataForUidLoader.java new file mode 100644 index 000000000000..cc970b93f601 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleDataForUidLoader.java @@ -0,0 +1,112 @@ +/* + * 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.settingslib.net; + +import static android.app.usage.NetworkStats.Bucket.STATE_FOREGROUND; +import static android.net.NetworkStats.TAG_NONE; + +import android.app.usage.NetworkStats; +import android.content.Context; +import android.util.Log; + +import java.util.ArrayList; +import java.util.List; + +/** + * Loader for network data usage history. It returns a list of usage data per billing cycle for a + * specific Uid. + */ +public class NetworkCycleDataForUidLoader extends + NetworkCycleDataLoader<List<NetworkCycleDataForUid>> { + private static final String TAG = "NetworkDataForUidLoader"; + + private final List<NetworkCycleDataForUid> mData; + private final int mUid; + private final boolean mRetrieveDetail; + + private NetworkCycleDataForUidLoader(Builder builder) { + super(builder); + mUid = builder.mUid; + mRetrieveDetail = builder.mRetrieveDetail; + mData = new ArrayList<NetworkCycleDataForUid>(); + } + + @Override + void recordUsage(long start, long end) { + try { + final NetworkStats stats = mNetworkStatsManager.queryDetailsForUid( + mNetworkType, mSubId, start, end, mUid); + final long total = getTotalUsage(stats); + if (total > 0L) { + final NetworkCycleDataForUid.Builder builder = new NetworkCycleDataForUid.Builder(); + builder.setStartTime(start) + .setEndTime(end) + .setTotalUsage(total); + if (mRetrieveDetail) { + final long foreground = getForegroundUsage(start, end); + builder.setBackgroundUsage(total - foreground) + .setForegroundUsage(foreground); + } + mData.add(builder.build()); + } + } catch (Exception e) { + Log.e(TAG, "Exception querying network detail.", e); + } + } + + @Override + List<NetworkCycleDataForUid> getCycleUsage() { + return mData; + } + + public static Builder<?> builder(Context context) { + return new Builder<NetworkCycleDataForUidLoader>(context) { + @Override + public NetworkCycleDataForUidLoader build() { + return new NetworkCycleDataForUidLoader(this); + } + }; + } + + private long getForegroundUsage(long start, long end) { + final NetworkStats stats = mNetworkStatsManager.queryDetailsForUidTagState( + mNetworkType, mSubId, start, end, mUid, TAG_NONE, STATE_FOREGROUND); + return getTotalUsage(stats); + } + + public static abstract class Builder<T extends NetworkCycleDataForUidLoader> + extends NetworkCycleDataLoader.Builder<T> { + + private int mUid; + private boolean mRetrieveDetail = true; + + public Builder(Context context) { + super(context); + } + + public Builder<T> setUid(int uid) { + mUid = uid; + return this; + } + + public Builder<T> setRetrieveDetail(boolean retrieveDetail) { + mRetrieveDetail = retrieveDetail; + return this; + } + } + +} diff --git a/packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleDataLoader.java b/packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleDataLoader.java index 80e13563d74b..b1c2c3a2d2e5 100644 --- a/packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleDataLoader.java +++ b/packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleDataLoader.java @@ -22,6 +22,7 @@ import static android.net.NetworkStatsHistory.FIELD_TX_BYTES; import android.app.usage.NetworkStats; import android.app.usage.NetworkStatsManager; import android.content.Context; +import android.net.ConnectivityManager; import android.net.INetworkStatsService; import android.net.INetworkStatsSession; import android.net.NetworkPolicy; @@ -32,34 +33,31 @@ import android.net.TrafficStats; import android.os.RemoteException; import android.os.ServiceManager; import android.text.format.DateUtils; -import android.util.Log; import android.util.Pair; +import com.android.settingslib.NetworkPolicyEditor; + import java.time.ZonedDateTime; -import java.util.ArrayList; import java.util.Iterator; -import java.util.List; -import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; import androidx.loader.content.AsyncTaskLoader; /** * Loader for network data usage history. It returns a list of usage data per billing cycle. */ -public class NetworkCycleDataLoader extends AsyncTaskLoader<List<NetworkCycleData>> { - private static final String TAG = "CycleDataSummaryLoader"; - private final NetworkStatsManager mNetworkStatsManager; - private final String mSubId; - private final int mNetworkType; +public abstract class NetworkCycleDataLoader<D> extends AsyncTaskLoader<D> { + private static final String TAG = "NetworkCycleDataLoader"; + protected final NetworkStatsManager mNetworkStatsManager; + protected final String mSubId; + protected final int mNetworkType; private final NetworkPolicy mPolicy; private final NetworkTemplate mNetworkTemplate; @VisibleForTesting final INetworkStatsService mNetworkStatsService; - private NetworkCycleDataLoader(Builder builder) { + protected NetworkCycleDataLoader(Builder<?> builder) { super(builder.mContext); - mPolicy = builder.mPolicy; mSubId = builder.mSubId; mNetworkType = builder.mNetworkType; mNetworkTemplate = builder.mNetworkTemplate; @@ -67,6 +65,10 @@ public class NetworkCycleDataLoader extends AsyncTaskLoader<List<NetworkCycleDat builder.mContext.getSystemService(Context.NETWORK_STATS_SERVICE); mNetworkStatsService = INetworkStatsService.Stub.asInterface( ServiceManager.getService(Context.NETWORK_STATS_SERVICE)); + final NetworkPolicyEditor policyEditor = + new NetworkPolicyEditor(NetworkPolicyManager.from(builder.mContext)); + policyEditor.read(); + mPolicy = policyEditor.getPolicy(mNetworkTemplate); } @Override @@ -75,21 +77,25 @@ public class NetworkCycleDataLoader extends AsyncTaskLoader<List<NetworkCycleDat forceLoad(); } - @Override - public List<NetworkCycleData> loadInBackground() { + public D loadInBackground() { if (mPolicy == null) { - return loadFourWeeksData(); + loadFourWeeksData(); + } else { + loadPolicyData(); } - final List<NetworkCycleData> data = new ArrayList<>(); - final Iterator<Pair<ZonedDateTime, ZonedDateTime>> iterator = NetworkPolicyManager - .cycleIterator(mPolicy); + return getCycleUsage(); + } + + @VisibleForTesting + void loadPolicyData() { + final Iterator<Pair<ZonedDateTime, ZonedDateTime>> iterator = + NetworkPolicyManager.cycleIterator(mPolicy); while (iterator.hasNext()) { final Pair<ZonedDateTime, ZonedDateTime> cycle = iterator.next(); final long cycleStart = cycle.first.toInstant().toEpochMilli(); final long cycleEnd = cycle.second.toInstant().toEpochMilli(); - getUsage(cycleStart, cycleEnd, data); + recordUsage(cycleStart, cycleEnd); } - return data; } @Override @@ -105,8 +111,7 @@ public class NetworkCycleDataLoader extends AsyncTaskLoader<List<NetworkCycleDat } @VisibleForTesting - List<NetworkCycleData> loadFourWeeksData() { - final List<NetworkCycleData> data = new ArrayList<>(); + void loadFourWeeksData() { try { final INetworkStatsSession networkSession = mNetworkStatsService.openSession(); final NetworkStatsHistory networkHistory = networkSession.getHistoryForNetwork( @@ -116,8 +121,9 @@ public class NetworkCycleDataLoader extends AsyncTaskLoader<List<NetworkCycleDat long cycleEnd = historyEnd; while (cycleEnd > historyStart) { - final long cycleStart = cycleEnd - (DateUtils.WEEK_IN_MILLIS * 4); - getUsage(cycleStart, cycleEnd, data); + final long cycleStart = Math.max( + historyStart, cycleEnd - (DateUtils.WEEK_IN_MILLIS * 4)); + recordUsage(cycleStart, cycleEnd); cycleEnd = cycleStart; } @@ -125,29 +131,23 @@ public class NetworkCycleDataLoader extends AsyncTaskLoader<List<NetworkCycleDat } catch (RemoteException e) { throw new RuntimeException(e); } - return data; } @VisibleForTesting - void getUsage(long start, long end, @NonNull List<NetworkCycleData> data) { - try { - final NetworkStats stats = mNetworkStatsManager.querySummary( - mNetworkType, mSubId, start, end); - final long total = getTotalUsage(stats); - if (total > 0L) { - data.add(new NetworkCycleData.Builder() - .setStartTime(start) - .setEndTime(end) - .setTotalUsage(total) - .setUsageBuckets(getUsageBuckets(start, end)) - .build()); + abstract void recordUsage(long start, long end); + + abstract D getCycleUsage(); + + public static Builder<?> builder(Context context) { + return new Builder<NetworkCycleDataLoader>(context) { + @Override + public NetworkCycleDataLoader build() { + return null; } - } catch (RemoteException e) { - Log.e(TAG, "Exception querying network detail.", e); - } + }; } - private long getTotalUsage(NetworkStats stats) { + protected long getTotalUsage(NetworkStats stats) { long bytes = 0L; if (stats != null) { final NetworkStats.Bucket bucket = new NetworkStats.Bucket(); @@ -159,60 +159,47 @@ public class NetworkCycleDataLoader extends AsyncTaskLoader<List<NetworkCycleDat return bytes; } - private List<NetworkCycleData> getUsageBuckets(long start, long end) { - final List<NetworkCycleData> data = new ArrayList<>(); - long bucketStart = start; - long bucketEnd = start + NetworkCycleData.BUCKET_DURATION_MS; - while (bucketEnd <= end) { - long usage = 0L; - try { - final NetworkStats stats = mNetworkStatsManager.querySummary( - mNetworkType, mSubId, bucketStart, bucketEnd); - usage = getTotalUsage(stats); - } catch (RemoteException e) { - Log.e(TAG, "Exception querying network detail.", e); - } - data.add(new NetworkCycleData.Builder() - .setStartTime(bucketStart).setEndTime(bucketEnd).setTotalUsage(usage).build()); - bucketStart = bucketEnd; - bucketEnd += NetworkCycleData.BUCKET_DURATION_MS; - } - return data; - } - - public static class Builder { + public static abstract class Builder<T extends NetworkCycleDataLoader> { private final Context mContext; - private NetworkPolicy mPolicy; private String mSubId; private int mNetworkType; private NetworkTemplate mNetworkTemplate; - public Builder(Context context) { + public Builder (Context context) { mContext = context; } - public Builder setNetworkPolicy(NetworkPolicy policy) { - mPolicy = policy; - return this; - } - - public Builder setSubscriberId(String subId) { + public Builder<T> setSubscriberId(String subId) { mSubId = subId; return this; } - public Builder setNetworkType(int networkType) { - mNetworkType = networkType; - return this; - } - - public Builder setNetworkTemplate(NetworkTemplate template) { + public Builder<T> setNetworkTemplate(NetworkTemplate template) { mNetworkTemplate = template; + setNetworkType(); return this; } - public NetworkCycleDataLoader build() { - return new NetworkCycleDataLoader(this); + public abstract T build(); + + private void setNetworkType() { + if (mNetworkTemplate != null) { + final int matchRule = mNetworkTemplate.getMatchRule(); + switch (matchRule) { + case NetworkTemplate.MATCH_MOBILE: + case NetworkTemplate.MATCH_MOBILE_WILDCARD: + mNetworkType = ConnectivityManager.TYPE_MOBILE; + break; + case NetworkTemplate.MATCH_WIFI: + mNetworkType = ConnectivityManager.TYPE_WIFI; + break; + case NetworkTemplate.MATCH_ETHERNET: + mNetworkType = ConnectivityManager.TYPE_ETHERNET; + break; + default: + mNetworkType = ConnectivityManager.TYPE_MOBILE; + } + } } } diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java index fe0b35b4191c..089f773ec1e9 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java @@ -163,6 +163,12 @@ public class WifiStatusTracker extends ConnectivityManager.NetworkCallback { ? null : AccessPoint.getSpeedLabel(mContext, scoredNetwork, rssi); } + /** Refresh the status label on Locale changed. */ + public void refreshLocale() { + updateStatusLabel(); + mCallback.run(); + } + private String getValidSsid(WifiInfo info) { String ssid = info.getSSID(); if (ssid != null && !WifiSsid.NONE.equals(ssid)) { diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/SapProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/SapProfileTest.java new file mode 100644 index 000000000000..9bb53ee6a343 --- /dev/null +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/SapProfileTest.java @@ -0,0 +1,90 @@ +/* + * 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.settingslib.bluetooth; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.when; +import static org.mockito.Mockito.verify; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothSap; +import android.bluetooth.BluetoothProfile; + +import com.android.settingslib.SettingsLibRobolectricTestRunner; +import com.android.settingslib.testutils.shadow.ShadowBluetoothAdapter; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.annotation.Config; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.shadow.api.Shadow; + +@RunWith(SettingsLibRobolectricTestRunner.class) +@Config(shadows = {ShadowBluetoothAdapter.class}) +public class SapProfileTest { + + @Mock + private CachedBluetoothDeviceManager mDeviceManager; + @Mock + private LocalBluetoothProfileManager mProfileManager; + @Mock + private BluetoothSap mService; + @Mock + private CachedBluetoothDevice mCachedBluetoothDevice; + @Mock + private BluetoothDevice mBluetoothDevice; + private BluetoothProfile.ServiceListener mServiceListener; + private SapProfile mProfile; + private ShadowBluetoothAdapter mShadowBluetoothAdapter; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mShadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter()); + mProfile = new SapProfile(RuntimeEnvironment.application, mDeviceManager, mProfileManager); + mServiceListener = mShadowBluetoothAdapter.getServiceListener(); + mServiceListener.onServiceConnected(BluetoothProfile.SAP, mService); + } + + @Test + public void connect_shouldConnectBluetoothSap() { + mProfile.connect(mBluetoothDevice); + verify(mService).connect(mBluetoothDevice); + } + + @Test + public void disconnect_shouldDisconnectBluetoothSap() { + mProfile.disconnect(mBluetoothDevice); + verify(mService).disconnect(mBluetoothDevice); + } + + @Test + public void getConnectionStatus_shouldReturnConnectionState() { + when(mService.getConnectionState(mBluetoothDevice)). + thenReturn(BluetoothProfile.STATE_CONNECTED); + assertThat(mProfile.getConnectionStatus(mBluetoothDevice)). + isEqualTo(BluetoothProfile.STATE_CONNECTED); + } +}
\ No newline at end of file diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXmlTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXmlTest.java index 96b2a1433f53..b00476b24921 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXmlTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXmlTest.java @@ -50,7 +50,7 @@ public class LicenseHtmlGeneratorFromXmlTest { + "<file-content contentId=\"0\"><![CDATA[license content #0]]></file-content>\n" + "</licenses2>"; - private static final String EXPECTED_HTML_STRING = + private static final String HTML_HEAD_STRING = "<html><head>\n" + "<style type=\"text/css\">\n" + "body { padding: 0; font-family: sans-serif; }\n" @@ -63,8 +63,12 @@ public class LicenseHtmlGeneratorFromXmlTest { + "</head>" + "<body topmargin=\"0\" leftmargin=\"0\" rightmargin=\"0\" bottommargin=\"0\">\n" + "<div class=\"toc\">\n" - + "<ul>\n" - + "<li><a href=\"#id0\">/file0</a></li>\n" + + "<ul>\n"; + + private static final String HTML_CUSTOM_HEADING = "Custom heading"; + + private static final String HTML_BODY_STRING = + "<li><a href=\"#id0\">/file0</a></li>\n" + "<li><a href=\"#id0\">/file1</a></li>\n" + "</ul>\n" + "</div><!-- table of contents -->\n" @@ -81,6 +85,11 @@ public class LicenseHtmlGeneratorFromXmlTest { + "</td></tr><!-- same-license -->\n" + "</table></body></html>\n"; + private static final String EXPECTED_HTML_STRING = HTML_HEAD_STRING + HTML_BODY_STRING; + + private static final String EXPECTED_HTML_STRING_WITH_CUSTOM_HEADING = + HTML_HEAD_STRING + HTML_CUSTOM_HEADING + "\n" + HTML_BODY_STRING; + @Test public void testParseValidXmlStream() throws XmlPullParserException, IOException { Map<String, String> fileNameToContentIdMap = new HashMap<String, String>(); @@ -117,7 +126,23 @@ public class LicenseHtmlGeneratorFromXmlTest { StringWriter output = new StringWriter(); LicenseHtmlGeneratorFromXml.generateHtml( - fileNameToContentIdMap, contentIdToFileContentMap, new PrintWriter(output)); + fileNameToContentIdMap, contentIdToFileContentMap, new PrintWriter(output), ""); assertThat(output.toString()).isEqualTo(EXPECTED_HTML_STRING); } + + @Test + public void testGenerateHtmlWithCustomHeading() { + Map<String, String> fileNameToContentIdMap = new HashMap<String, String>(); + Map<String, String> contentIdToFileContentMap = new HashMap<String, String>(); + + fileNameToContentIdMap.put("/file0", "0"); + fileNameToContentIdMap.put("/file1", "0"); + contentIdToFileContentMap.put("0", "license content #0"); + + StringWriter output = new StringWriter(); + LicenseHtmlGeneratorFromXml.generateHtml( + fileNameToContentIdMap, contentIdToFileContentMap, new PrintWriter(output), + HTML_CUSTOM_HEADING); + assertThat(output.toString()).isEqualTo(EXPECTED_HTML_STRING_WITH_CUSTOM_HEADING); + } } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/license/LicenseHtmlLoaderCompatTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/license/LicenseHtmlLoaderCompatTest.java index 12a4e699fb76..c32cc99daf96 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/license/LicenseHtmlLoaderCompatTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/license/LicenseHtmlLoaderCompatTest.java @@ -142,7 +142,7 @@ public class LicenseHtmlLoaderCompatTest { } @Implementation - static boolean generateHtmlFile(List<File> xmlFiles, File htmlFile) { + static boolean generateHtmlFile(Context context, List<File> xmlFiles, File htmlFile) { return sGenerateHtmlFileSucceeded; } } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/NetworkCycleChartDataLoaderTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/NetworkCycleChartDataLoaderTest.java new file mode 100644 index 000000000000..cad88b1f97a4 --- /dev/null +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/NetworkCycleChartDataLoaderTest.java @@ -0,0 +1,73 @@ +/* + * 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.settingslib.net; + +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.usage.NetworkStatsManager; +import android.content.Context; +import android.net.ConnectivityManager; +import android.net.NetworkPolicy; +import android.net.NetworkPolicyManager; +import android.os.RemoteException; +import android.text.format.DateUtils; + +import com.android.settingslib.SettingsLibRobolectricTestRunner; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@RunWith(SettingsLibRobolectricTestRunner.class) +public class NetworkCycleChartDataLoaderTest { + + @Mock + private NetworkStatsManager mNetworkStatsManager; + @Mock + private NetworkPolicyManager mNetworkPolicyManager; + @Mock + private Context mContext; + + private NetworkCycleChartDataLoader mLoader; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + when(mContext.getSystemService(Context.NETWORK_STATS_SERVICE)) + .thenReturn(mNetworkStatsManager); + when(mContext.getSystemService(Context.NETWORK_POLICY_SERVICE)) + .thenReturn(mNetworkPolicyManager); + when(mNetworkPolicyManager.getNetworkPolicies()).thenReturn(new NetworkPolicy[0]); + } + + @Test + public void recordUsage_shouldQueryNetworkSummary() throws RemoteException { + final long end = System.currentTimeMillis(); + final long start = end - (DateUtils.WEEK_IN_MILLIS * 4); + final int networkType = ConnectivityManager.TYPE_MOBILE; + final String subId = "TestSubscriber"; + mLoader = NetworkCycleChartDataLoader.builder(mContext) + .setSubscriberId(subId).build(); + + mLoader.recordUsage(start, end); + + verify(mNetworkStatsManager).querySummary(networkType, subId, start, end); + } +} diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/NetworkCycleDataForUidLoaderTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/NetworkCycleDataForUidLoaderTest.java new file mode 100644 index 000000000000..2314f272c8ea --- /dev/null +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/NetworkCycleDataForUidLoaderTest.java @@ -0,0 +1,99 @@ +/* + * 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.settingslib.net; + +import static android.app.usage.NetworkStats.Bucket.STATE_FOREGROUND; +import static android.net.NetworkStats.TAG_NONE; + +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.usage.NetworkStatsManager; +import android.content.Context; +import android.net.ConnectivityManager; +import android.net.NetworkPolicy; +import android.net.NetworkPolicyManager; +import android.text.format.DateUtils; + +import com.android.settingslib.SettingsLibRobolectricTestRunner; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@RunWith(SettingsLibRobolectricTestRunner.class) +public class NetworkCycleDataForUidLoaderTest { + + @Mock + private NetworkStatsManager mNetworkStatsManager; + @Mock + private NetworkPolicyManager mNetworkPolicyManager; + @Mock + private Context mContext; + + private NetworkCycleDataForUidLoader mLoader; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + when(mContext.getSystemService(Context.NETWORK_STATS_SERVICE)) + .thenReturn(mNetworkStatsManager); + when(mContext.getSystemService(Context.NETWORK_POLICY_SERVICE)) + .thenReturn(mNetworkPolicyManager); + when(mNetworkPolicyManager.getNetworkPolicies()).thenReturn(new NetworkPolicy[0]); + } + + @Test + public void recordUsage_shouldQueryNetworkDetailsForUidAndForegroundState() { + final long end = System.currentTimeMillis(); + final long start = end - (DateUtils.WEEK_IN_MILLIS * 4); + final int networkType = ConnectivityManager.TYPE_MOBILE; + final String subId = "TestSubscriber"; + final int uid = 1; + mLoader = spy(NetworkCycleDataForUidLoader.builder(mContext) + .setUid(uid).setSubscriberId(subId).build()); + doReturn(1024L).when(mLoader).getTotalUsage(any()); + + mLoader.recordUsage(start, end); + + verify(mNetworkStatsManager).queryDetailsForUid(networkType, subId, start, end, uid); + verify(mNetworkStatsManager).queryDetailsForUidTagState( + networkType, subId, start, end, uid, TAG_NONE, STATE_FOREGROUND); + } + + @Test + public void recordUsage_retrieveDetailIsFalse_shouldNotQueryNetworkForegroundState() { + final long end = System.currentTimeMillis(); + final long start = end - (DateUtils.WEEK_IN_MILLIS * 4); + final int networkType = ConnectivityManager.TYPE_MOBILE; + final String subId = "TestSubscriber"; + final int uid = 1; + mLoader = spy(NetworkCycleDataForUidLoader.builder(mContext) + .setRetrieveDetail(false).setUid(uid).setSubscriberId(subId).build()); + doReturn(1024L).when(mLoader).getTotalUsage(any()); + + mLoader.recordUsage(start, end); + verify(mNetworkStatsManager, never()).queryDetailsForUidTagState( + networkType, subId, start, end, uid, TAG_NONE, STATE_FOREGROUND); + } +} diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/NetworkCycleDataLoaderTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/NetworkCycleDataLoaderTest.java index 4c4207b23cab..9d60a97f8584 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/NetworkCycleDataLoaderTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/NetworkCycleDataLoaderTest.java @@ -16,13 +16,10 @@ package com.android.settingslib.net; -import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyInt; -import static org.mockito.Mockito.anyLong; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.eq; -import static org.mockito.Mockito.mock; import static org.mockito.Matchers.nullable; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -33,6 +30,7 @@ import android.net.ConnectivityManager; import android.net.INetworkStatsService; import android.net.INetworkStatsSession; import android.net.NetworkPolicy; +import android.net.NetworkPolicyManager; import android.net.NetworkStatsHistory; import android.net.NetworkTemplate; import android.os.RemoteException; @@ -50,6 +48,7 @@ import org.robolectric.util.ReflectionHelpers; import java.time.ZonedDateTime; import java.util.Iterator; +import java.util.List; @RunWith(SettingsLibRobolectricTestRunner.class) public class NetworkCycleDataLoaderTest { @@ -57,6 +56,8 @@ public class NetworkCycleDataLoaderTest { @Mock private NetworkStatsManager mNetworkStatsManager; @Mock + private NetworkPolicyManager mNetworkPolicyManager; + @Mock private Context mContext; @Mock private NetworkPolicy mPolicy; @@ -65,20 +66,23 @@ public class NetworkCycleDataLoaderTest { @Mock private INetworkStatsService mNetworkStatsService; - private NetworkCycleDataLoader mLoader; + private NetworkCycleDataTestLoader mLoader; @Before public void setUp() { MockitoAnnotations.initMocks(this); when(mContext.getSystemService(Context.NETWORK_STATS_SERVICE)) .thenReturn(mNetworkStatsManager); + when(mContext.getSystemService(Context.NETWORK_POLICY_SERVICE)) + .thenReturn(mNetworkPolicyManager); when(mPolicy.cycleIterator()).thenReturn(mIterator); + when(mNetworkPolicyManager.getNetworkPolicies()).thenReturn(new NetworkPolicy[0]); } @Test public void loadInBackground_noNetworkPolicy_shouldLoad4WeeksData() { - mLoader = spy(new NetworkCycleDataLoader.Builder(mContext).build()); - doReturn(null).when(mLoader).loadFourWeeksData(); + mLoader = spy(new NetworkCycleDataTestLoader(mContext)); + doNothing().when(mLoader).loadFourWeeksData(); mLoader.loadInBackground(); @@ -86,31 +90,45 @@ public class NetworkCycleDataLoaderTest { } @Test - public void loadInBackground_shouldQueryNetworkSummary() throws RemoteException { + public void loadInBackground_hasNetworkPolicy_shouldLoadPolicyData() { + mLoader = spy(new NetworkCycleDataTestLoader(mContext)); + ReflectionHelpers.setField(mLoader, "mPolicy", mPolicy); + + mLoader.loadInBackground(); + + verify(mLoader).loadPolicyData(); + } + + @Test + public void loadPolicyData_shouldRecordUsageFromPolicyCycle() { final int networkType = ConnectivityManager.TYPE_MOBILE; final String subId = "TestSubscriber"; final ZonedDateTime now = ZonedDateTime.now(); final Range<ZonedDateTime> cycle = new Range<>(now, now); + final long nowInMs = now.toInstant().toEpochMilli(); // mock 1 cycle data. // hasNext() will be called internally in next(), hence setting it to return true twice. when(mIterator.hasNext()).thenReturn(true).thenReturn(true).thenReturn(false); when(mIterator.next()).thenReturn(cycle); - mLoader = new NetworkCycleDataLoader.Builder(mContext) - .setNetworkPolicy(mPolicy).setNetworkType(networkType).setSubscriberId(subId).build(); + mLoader = spy(new NetworkCycleDataTestLoader(mContext)); + ReflectionHelpers.setField(mLoader, "mPolicy", mPolicy); + ReflectionHelpers.setField(mLoader, "mNetworkType", networkType); + ReflectionHelpers.setField(mLoader, "mSubId", subId); - mLoader.loadInBackground(); + mLoader.loadPolicyData(); - verify(mNetworkStatsManager).querySummary(eq(networkType), eq(subId), anyLong(), anyLong()); + verify(mLoader).recordUsage(nowInMs, nowInMs); } @Test - public void loadFourWeeksData_shouldGetUsageForLast4Weeks() throws RemoteException { - mLoader = spy(new NetworkCycleDataLoader.Builder(mContext).build()); + public void loadFourWeeksData_shouldRecordUsageForLast4Weeks() throws RemoteException { + mLoader = spy(new NetworkCycleDataTestLoader(mContext)); ReflectionHelpers.setField(mLoader, "mNetworkStatsService", mNetworkStatsService); final INetworkStatsSession networkSession = mock(INetworkStatsSession.class); when(mNetworkStatsService.openSession()).thenReturn(networkSession); final NetworkStatsHistory networkHistory = mock(NetworkStatsHistory.class); - when(networkSession.getHistoryForNetwork(nullable(NetworkTemplate.class), anyInt())).thenReturn(networkHistory); + when(networkSession.getHistoryForNetwork(nullable(NetworkTemplate.class), anyInt())) + .thenReturn(networkHistory); final long now = System.currentTimeMillis(); final long fourWeeksAgo = now - (DateUtils.WEEK_IN_MILLIS * 4); when(networkHistory.getStart()).thenReturn(fourWeeksAgo); @@ -118,6 +136,23 @@ public class NetworkCycleDataLoaderTest { mLoader.loadFourWeeksData(); - verify(mLoader).getUsage(eq(fourWeeksAgo), eq(now), any()); + verify(mLoader).recordUsage(fourWeeksAgo, now); + } + + public class NetworkCycleDataTestLoader extends NetworkCycleDataLoader<List<NetworkCycleData>> { + + private NetworkCycleDataTestLoader(Context context) { + super(NetworkCycleDataLoader.builder(mContext)); + mContext = context; + } + + @Override + void recordUsage(long start, long end) { + } + + @Override + List<NetworkCycleData> getCycleUsage() { + return null; + } } } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/FragmentTestUtils.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/FragmentTestUtils.java deleted file mode 100644 index d8e73b7db322..000000000000 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/FragmentTestUtils.java +++ /dev/null @@ -1,74 +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 com.android.settingslib.testutils; - -import android.os.Bundle; -import android.widget.LinearLayout; - -import androidx.fragment.app.Fragment; -import androidx.fragment.app.FragmentActivity; -import androidx.fragment.app.FragmentManager; - -import org.robolectric.Robolectric; - -/** - * Utilities for creating Fragments for testing. - * <p> - * TODO(b/111195449) - Duplicated from org.robolectric.shadows.support.v4.SupportFragmentTestUtil - */ -@Deprecated -public class FragmentTestUtils { - - public static void startFragment(Fragment fragment) { - buildFragmentManager(FragmentUtilActivity.class) - .beginTransaction().add(fragment, null).commit(); - } - - public static void startFragment(Fragment fragment, - Class<? extends FragmentActivity> activityClass) { - buildFragmentManager(activityClass) - .beginTransaction().add(fragment, null).commit(); - } - - public static void startVisibleFragment(Fragment fragment) { - buildFragmentManager(FragmentUtilActivity.class) - .beginTransaction().add(1, fragment, null).commit(); - } - - public static void startVisibleFragment(Fragment fragment, - Class<? extends FragmentActivity> activityClass, int containerViewId) { - buildFragmentManager(activityClass) - .beginTransaction().add(containerViewId, fragment, null).commit(); - } - - private static FragmentManager buildFragmentManager( - Class<? extends FragmentActivity> activityClass) { - FragmentActivity activity = Robolectric.setupActivity(activityClass); - return activity.getSupportFragmentManager(); - } - - private static class FragmentUtilActivity extends FragmentActivity { - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - LinearLayout view = new LinearLayout(this); - view.setId(1); - - setContentView(view); - } - } -} diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java index bd21b8353136..c09b76394340 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java @@ -659,6 +659,9 @@ class SettingsProtoDumpUtil { dumpSetting(s, p, Settings.Global.GPU_DEBUG_LAYERS, GlobalSettingsProto.Gpu.DEBUG_LAYERS); + dumpSetting(s, p, + Settings.Global.ANGLE_ENABLED_APP, + GlobalSettingsProto.Gpu.ANGLE_ENABLED_APP); p.end(gpuToken); final long hdmiToken = p.start(GlobalSettingsProto.HDMI); diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index 3d193db392a4..18ec9c34e98c 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java @@ -870,7 +870,11 @@ public class SettingsProvider extends ContentProvider { } } if (newRestrictions.getBoolean(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES) - != prevRestrictions.getBoolean(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES)) { + != prevRestrictions.getBoolean(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES) || + newRestrictions.getBoolean( + UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY) + != prevRestrictions.getBoolean( + UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY)) { final long identity = Binder.clearCallingIdentity(); try { synchronized (mLock) { diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index da870bd134bf..5c654b44d1dc 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -151,6 +151,7 @@ <uses-permission android:name="android.permission.CONTROL_KEYGUARD" /> <uses-permission android:name="android.permission.SUSPEND_APPS" /> + <uses-permission android:name="android.permission.READ_CLIPBOARD_IN_BACKGROUND" /> <application android:label="@string/app_label" android:defaultToDeviceProtectedStorage="true" diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/phone/NavGesture.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/phone/NavGesture.java index aa2fb32f13a8..814324e63d19 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/phone/NavGesture.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/phone/NavGesture.java @@ -20,6 +20,7 @@ import android.view.View; import com.android.systemui.plugins.Plugin; import com.android.systemui.plugins.annotations.ProvidesInterface; +import java.io.PrintWriter; @ProvidesInterface(action = NavGesture.ACTION, version = NavGesture.VERSION) public interface NavGesture extends Plugin { @@ -46,6 +47,8 @@ public interface NavGesture extends Plugin { public void onNavigationButtonLongPress(View v); public default void destroy() { } + + public default void dump(PrintWriter pw) { } } } diff --git a/packages/SystemUI/res/layout/qs_paged_tile_layout.xml b/packages/SystemUI/res/layout/qs_paged_tile_layout.xml index 11a01871b782..708326971454 100644 --- a/packages/SystemUI/res/layout/qs_paged_tile_layout.xml +++ b/packages/SystemUI/res/layout/qs_paged_tile_layout.xml @@ -20,10 +20,13 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_weight="1" - android:clipChildren="false" - android:clipToPadding="false" + android:clipChildren="true" + android:clipToPadding="true" + android:paddingStart="@dimen/notification_side_paddings" + android:paddingEnd="@dimen/notification_side_paddings" android:paddingBottom="@dimen/qs_paged_tile_layout_padding_bottom"> + <FrameLayout android:id="@+id/page_decor" android:layout_width="wrap_content" diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 2b51aaab3805..e1c71fae7559 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -1081,7 +1081,7 @@ <string name="clear_all_notifications_text">Clear all</string> <!-- The text for the manage notifications link. [CHAR LIMIT=40] --> - <string name="manage_notifications_text">Manage notifications</string> + <string name="manage_notifications_text">Manage</string> <!-- The text to show in the notifications shade when dnd is suppressing notifications. [CHAR LIMIT=100] --> <string name="dnd_suppressing_shade_text">Notifications paused by Do Not Disturb</string> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index 8442dd13a85b..6446367e19a5 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -279,7 +279,7 @@ <item name="android:fontFamily">sans-serif</item> </style> - <style name="BaseBrightnessDialogContainer"> + <style name="BaseBrightnessDialogContainer" parent="@style/Theme.SystemUI"> <item name="android:layout_width">match_parent</item> <item name="android:layout_height">wrap_content</item> </style> diff --git a/packages/SystemUI/shared/Android.bp b/packages/SystemUI/shared/Android.bp index 710b5f724add..defc49b5fac1 100644 --- a/packages/SystemUI/shared/Android.bp +++ b/packages/SystemUI/shared/Android.bp @@ -20,6 +20,10 @@ android_library { "src/**/I*.aidl", ], + static_libs: [ + "SystemUIPluginLib" + ], + // Enforce that the library is build agains java 7 so that there are // no compatibility issues with launcher java_version: "1.7", diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInitializer.java b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInitializer.java new file mode 100644 index 000000000000..985789415363 --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInitializer.java @@ -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. + */ + +package com.android.systemui.shared.plugins; + +import android.content.Context; +import android.os.Looper; + +/** + * Provides necessary components for initializing {@link PluginManagerImpl}. + */ +public interface PluginInitializer { + + Looper getBgLooper(); + + /** + * This Runnable is run on the bg looper during initialization of {@link PluginManagerImpl}. + * It can be null. + */ + Runnable getBgInitCallback(); + + String[] getWhitelistedPlugins(Context context); +} diff --git a/packages/SystemUI/src/com/android/systemui/plugins/PluginInstanceManager.java b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstanceManager.java index 7bc7e5f0095e..e80c079f60de 100644 --- a/packages/SystemUI/src/com/android/systemui/plugins/PluginInstanceManager.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstanceManager.java @@ -12,7 +12,7 @@ * permissions and limitations under the License. */ -package com.android.systemui.plugins; +package com.android.systemui.shared.plugins; import android.app.Notification; import android.app.Notification.Action; @@ -39,12 +39,14 @@ import android.view.LayoutInflater; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; -import com.android.systemui.plugins.VersionInfo.InvalidVersionException; +import com.android.systemui.plugins.Plugin; +import com.android.systemui.plugins.PluginFragment; +import com.android.systemui.plugins.PluginListener; +import com.android.systemui.shared.plugins.VersionInfo.InvalidVersionException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import com.android.systemui.R; public class PluginInstanceManager<T extends Plugin> { @@ -71,8 +73,7 @@ public class PluginInstanceManager<T extends Plugin> { PluginInstanceManager(Context context, String action, PluginListener<T> listener, boolean allowMultiple, Looper looper, VersionInfo version, PluginManagerImpl manager) { this(context, context.getPackageManager(), action, listener, allowMultiple, looper, version, - manager, Build.IS_DEBUGGABLE, - context.getResources().getStringArray(R.array.config_pluginWhitelist)); + manager, Build.IS_DEBUGGABLE, manager.getWhitelistedPlugins()); } @VisibleForTesting @@ -114,7 +115,7 @@ public class PluginInstanceManager<T extends Plugin> { public void destroy() { if (DEBUG) Log.d(TAG, "stopListening"); - ArrayList<PluginInfo> plugins = new ArrayList<>(mPluginHandler.mPlugins); + ArrayList<PluginInfo> plugins = new ArrayList<PluginInfo>(mPluginHandler.mPlugins); for (PluginInfo plugin : plugins) { mMainHandler.obtainMessage(MainHandler.PLUGIN_DISCONNECTED, plugin.mPlugin).sendToTarget(); @@ -132,7 +133,7 @@ public class PluginInstanceManager<T extends Plugin> { public boolean checkAndDisable(String className) { boolean disableAny = false; - ArrayList<PluginInfo> plugins = new ArrayList<>(mPluginHandler.mPlugins); + ArrayList<PluginInfo> plugins = new ArrayList<PluginInfo>(mPluginHandler.mPlugins); for (PluginInfo info : plugins) { if (className.startsWith(info.mPackage)) { disable(info); @@ -143,7 +144,7 @@ public class PluginInstanceManager<T extends Plugin> { } public boolean disableAll() { - ArrayList<PluginInfo> plugins = new ArrayList<>(mPluginHandler.mPlugins); + ArrayList<PluginInfo> plugins = new ArrayList<PluginInfo>(mPluginHandler.mPlugins); for (int i = 0; i < plugins.size(); i++) { disable(plugins.get(i)); } @@ -165,7 +166,7 @@ public class PluginInstanceManager<T extends Plugin> { } public <T> boolean dependsOn(Plugin p, Class<T> cls) { - ArrayList<PluginInfo> plugins = new ArrayList<>(mPluginHandler.mPlugins); + ArrayList<PluginInfo> plugins = new ArrayList<PluginInfo>(mPluginHandler.mPlugins); for (PluginInfo info : plugins) { if (info.mPlugin.getClass().getName().equals(p.getClass().getName())) { return info.mVersion != null && info.mVersion.hasClass(cls); diff --git a/packages/SystemUI/src/com/android/systemui/plugins/PluginManager.java b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginManager.java index 298eaf18dce5..208f4fedfe27 100644 --- a/packages/SystemUI/src/com/android/systemui/plugins/PluginManager.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginManager.java @@ -12,10 +12,12 @@ * permissions and limitations under the License. */ -package com.android.systemui.plugins; +package com.android.systemui.shared.plugins; import android.text.TextUtils; +import com.android.systemui.plugins.Plugin; +import com.android.systemui.plugins.PluginListener; import com.android.systemui.plugins.annotations.ProvidesInterface; public interface PluginManager { @@ -40,14 +42,17 @@ public interface PluginManager { <T> boolean dependsOn(Plugin p, Class<T> cls); - static <P> String getAction(Class<P> cls) { - ProvidesInterface info = cls.getDeclaredAnnotation(ProvidesInterface.class); - if (info == null) { - throw new RuntimeException(cls + " doesn't provide an interface"); + class Helper { + public static <P> String getAction(Class<P> cls) { + ProvidesInterface info = cls.getDeclaredAnnotation(ProvidesInterface.class); + if (info == null) { + throw new RuntimeException(cls + " doesn't provide an interface"); + } + if (TextUtils.isEmpty(info.action())) { + throw new RuntimeException(cls + " doesn't provide an action"); + } + return info.action(); } - if (TextUtils.isEmpty(info.action())) { - throw new RuntimeException(cls + " doesn't provide an action"); - } - return info.action(); } + } diff --git a/packages/SystemUI/src/com/android/systemui/plugins/PluginManagerImpl.java b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginManagerImpl.java index 1cbf1fe0f2c4..7f1d161123f2 100644 --- a/packages/SystemUI/src/com/android/systemui/plugins/PluginManagerImpl.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginManagerImpl.java @@ -12,7 +12,7 @@ * permissions and limitations under the License. */ -package com.android.systemui.plugins; +package com.android.systemui.shared.plugins; import android.app.Notification; import android.app.Notification.Action; @@ -41,10 +41,11 @@ import android.widget.Toast; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; -import com.android.systemui.Dependency; -import com.android.systemui.R; -import com.android.systemui.plugins.PluginInstanceManager.PluginContextWrapper; -import com.android.systemui.plugins.PluginInstanceManager.PluginInfo; + +import com.android.systemui.plugins.Plugin; +import com.android.systemui.plugins.PluginListener; +import com.android.systemui.shared.plugins.PluginInstanceManager.PluginContextWrapper; +import com.android.systemui.shared.plugins.PluginInstanceManager.PluginInfo; import com.android.systemui.plugins.annotations.ProvidesInterface; import dalvik.system.PathClassLoader; @@ -79,31 +80,33 @@ public class PluginManagerImpl extends BroadcastReceiver implements PluginManage private Looper mLooper; private boolean mWtfsSet; - public PluginManagerImpl(Context context) { + public PluginManagerImpl(Context context, PluginInitializer initializer) { this(context, new PluginInstanceManagerFactory(), Build.IS_DEBUGGABLE, - context.getResources().getStringArray(R.array.config_pluginWhitelist), - Thread.getUncaughtExceptionPreHandler()); + Thread.getUncaughtExceptionPreHandler(), initializer); } @VisibleForTesting PluginManagerImpl(Context context, PluginInstanceManagerFactory factory, boolean debuggable, - String[] whitelistedPlugins, UncaughtExceptionHandler defaultHandler) { + UncaughtExceptionHandler defaultHandler, PluginInitializer initializer) { mContext = context; mFactory = factory; - mLooper = Dependency.get(Dependency.BG_LOOPER); + mLooper = initializer.getBgLooper(); isDebuggable = debuggable; - mWhitelistedPlugins.addAll(Arrays.asList(whitelistedPlugins)); + mWhitelistedPlugins.addAll(Arrays.asList(initializer.getWhitelistedPlugins(mContext))); mPluginPrefs = new PluginPrefs(mContext); PluginExceptionHandler uncaughtExceptionHandler = new PluginExceptionHandler( defaultHandler); Thread.setUncaughtExceptionPreHandler(uncaughtExceptionHandler); - new Handler(mLooper).post(() -> { - // Plugin dependencies that don't have another good home can go here, but - // dependencies that have better places to init can happen elsewhere. - Dependency.get(PluginDependencyProvider.class) - .allowPluginDependency(ActivityStarter.class); - }); + + Runnable bgRunnable = initializer.getBgInitCallback(); + if (bgRunnable != null) { + new Handler(mLooper).post(bgRunnable); + } + } + + public String[] getWhitelistedPlugins() { + return mWhitelistedPlugins.toArray(new String[0]); } public <T extends Plugin> T getOneShotPlugin(Class<T> cls) { @@ -121,7 +124,9 @@ public class PluginManagerImpl extends BroadcastReceiver implements PluginManage if (Looper.myLooper() != Looper.getMainLooper()) { throw new RuntimeException("Must be called from UI thread"); } - PluginInstanceManager<T> p = mFactory.createPluginInstanceManager(mContext, action, null, + // Passing null causes compiler to complain about incompatible (generic) types. + PluginListener<Plugin> dummy = null; + PluginInstanceManager<T> p = mFactory.createPluginInstanceManager(mContext, action, dummy, false, mLooper, cls, this); mPluginPrefs.addAction(action); PluginInfo<T> info = p.getPlugin(); @@ -140,7 +145,7 @@ public class PluginManagerImpl extends BroadcastReceiver implements PluginManage public <T extends Plugin> void addPluginListener(PluginListener<T> listener, Class<?> cls, boolean allowMultiple) { - addPluginListener(PluginManager.getAction(cls), listener, cls, allowMultiple); + addPluginListener(PluginManager.Helper.getAction(cls), listener, cls, allowMultiple); } public <T extends Plugin> void addPluginListener(String action, PluginListener<T> listener, @@ -293,8 +298,12 @@ public class PluginManagerImpl extends BroadcastReceiver implements PluginManage public void handleWtfs() { if (!mWtfsSet) { mWtfsSet = true; - Log.setWtfHandler((tag, what, system) -> { - throw new CrashWhilePluginActiveException(what); + Log.setWtfHandler(new Log.TerribleFailureHandler() { + @Override + public void onTerribleFailure(String tag, Log.TerribleFailure what, + boolean system) { + throw new CrashWhilePluginActiveException(what); + } }); } } diff --git a/packages/SystemUI/src/com/android/systemui/plugins/PluginPrefs.java b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginPrefs.java index 3671b3c1689f..c0c5d7051cea 100644 --- a/packages/SystemUI/src/com/android/systemui/plugins/PluginPrefs.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginPrefs.java @@ -12,7 +12,7 @@ * permissions and limitations under the License. */ -package com.android.systemui.plugins; +package com.android.systemui.shared.plugins; import android.content.Context; import android.content.SharedPreferences; diff --git a/packages/SystemUI/src/com/android/systemui/plugins/VersionInfo.java b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/VersionInfo.java index facfd982408a..bb845cd87923 100644 --- a/packages/SystemUI/src/com/android/systemui/plugins/VersionInfo.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/VersionInfo.java @@ -12,7 +12,9 @@ * permissions and limitations under the License. */ -package com.android.systemui.plugins; +package com.android.systemui.shared.plugins; + +import android.util.ArrayMap; import com.android.systemui.plugins.annotations.Dependencies; import com.android.systemui.plugins.annotations.DependsOn; @@ -20,7 +22,7 @@ import com.android.systemui.plugins.annotations.ProvidesInterface; import com.android.systemui.plugins.annotations.Requirements; import com.android.systemui.plugins.annotations.Requires; -import android.util.ArrayMap; +import java.util.function.BiConsumer; public class VersionInfo { @@ -73,25 +75,32 @@ public class VersionInfo { } public void checkVersion(VersionInfo plugin) throws InvalidVersionException { - ArrayMap<Class<?>, Version> versions = new ArrayMap<>(mVersions); - plugin.mVersions.forEach((aClass, version) -> { - Version v = versions.remove(aClass); - if (v == null) { - v = createVersion(aClass); - } - if (v == null) { - throw new InvalidVersionException(aClass.getSimpleName() - + " does not provide an interface", false); - } - if (v.mVersion != version.mVersion) { - throw new InvalidVersionException(aClass, v.mVersion < version.mVersion, v.mVersion, - version.mVersion); + final ArrayMap<Class<?>, Version> versions = new ArrayMap<>(mVersions); + plugin.mVersions.forEach(new BiConsumer<Class<?>, Version>() { + @Override + public void accept(Class<?> aClass, Version version) { + Version v = versions.remove(aClass); + if (v == null) { + v = VersionInfo.this.createVersion(aClass); + } + if (v == null) { + throw new InvalidVersionException(aClass.getSimpleName() + + " does not provide an interface", false); + } + if (v.mVersion != version.mVersion) { + throw new InvalidVersionException(aClass, v.mVersion < version.mVersion, + v.mVersion, + version.mVersion); + } } }); - versions.forEach((aClass, version) -> { - if (version.mRequired) { - throw new InvalidVersionException("Missing required dependency " - + aClass.getSimpleName(), false); + versions.forEach(new BiConsumer<Class<?>, Version>() { + @Override + public void accept(Class<?> aClass, Version version) { + if (version.mRequired) { + throw new InvalidVersionException("Missing required dependency " + + aClass.getSimpleName(), false); + } } }); } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/NavigationBarCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/NavigationBarCompat.java index d38cc0f608ce..69aea2c59029 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/NavigationBarCompat.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/NavigationBarCompat.java @@ -24,8 +24,6 @@ import android.util.DisplayMetrics; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; -import sun.misc.Resource; - public class NavigationBarCompat { /** * Touch slopes and thresholds for quick step operations. Drag slop is the point where the diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java index 5bbbc52f4cbc..28eff46db1d0 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java @@ -14,7 +14,7 @@ import androidx.annotation.VisibleForTesting; import com.android.systemui.Dependency; import com.android.systemui.plugins.ClockPlugin; import com.android.systemui.plugins.PluginListener; -import com.android.systemui.plugins.PluginManager; +import com.android.systemui.shared.plugins.PluginManager; /** * Switch to show plugin clock when plugin is connected, otherwise it will show default clock. diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index f1b53fec0714..c7685f832cbb 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -41,7 +41,6 @@ import android.app.admin.DevicePolicyManager; import android.app.trust.TrustManager; import android.content.BroadcastReceiver; import android.content.ComponentName; -import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; @@ -49,13 +48,14 @@ import android.content.pm.IPackageManager; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.database.ContentObserver; +import android.hardware.biometrics.BiometricManager; import android.hardware.biometrics.BiometricSourceType; +import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback; import android.hardware.face.FaceManager; import android.hardware.fingerprint.FingerprintManager; import android.hardware.fingerprint.FingerprintManager.AuthenticationCallback; import android.hardware.fingerprint.FingerprintManager.AuthenticationResult; import android.media.AudioManager; -import android.net.Uri; import android.os.BatteryManager; import android.os.CancellationSignal; import android.os.Handler; @@ -250,51 +250,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener { private static final int HW_UNAVAILABLE_TIMEOUT = 3000; // ms private static final int HW_UNAVAILABLE_RETRY_MAX = 3; - private class SettingObserver extends ContentObserver { - private final Uri FACE_UNLOCK_KEYGUARD_ENABLED = - Settings.Secure.getUriFor(Settings.Secure.FACE_UNLOCK_KEYGUARD_ENABLED); - - private final ContentResolver mContentResolver; - - /** - * Creates a content observer. - * - * @param handler The handler to run {@link #onChange} on, or null if none. - */ - public SettingObserver(Handler handler) { - super(handler); - mContentResolver = mContext.getContentResolver(); - updateContentObserver(); - } - - public void updateContentObserver() { - mContentResolver.unregisterContentObserver(this); - mContentResolver.registerContentObserver(FACE_UNLOCK_KEYGUARD_ENABLED, - false /* notifyForDescendents */, - this, - UserHandle.USER_CURRENT); - - // Update the value immediately - onChange(true /* selfChange */, FACE_UNLOCK_KEYGUARD_ENABLED); - } - - @Override - public void onChange(boolean selfChange, Uri uri) { - if (FACE_UNLOCK_KEYGUARD_ENABLED.equals(uri)) { - mFaceSettingEnabledForUser = - Settings.Secure.getIntForUser( - mContentResolver, - Settings.Secure.FACE_UNLOCK_KEYGUARD_ENABLED, - 1 /* default */, - UserHandle.USER_CURRENT) != 0; - updateBiometricListeningState(); - } - } - } - - private final SettingObserver mSettingObserver; - private boolean mFaceSettingEnabledForUser; - private final Handler mHandler = new Handler(Looper.getMainLooper()) { @Override public void handleMessage(Message msg) { @@ -400,6 +355,18 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener { } }; + private boolean mFaceSettingEnabledForUser; + private BiometricManager mBiometricManager; + private IBiometricEnabledOnKeyguardCallback mBiometricEnabledCallback = + new IBiometricEnabledOnKeyguardCallback.Stub() { + @Override + public void onChanged(BiometricSourceType type, boolean enabled) throws RemoteException { + if (type == BiometricSourceType.FACE) { + mFaceSettingEnabledForUser = enabled; + } + } + }; + private OnSubscriptionsChangedListener mSubscriptionListener = new OnSubscriptionsChangedListener() { @Override @@ -1165,7 +1132,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener { private CancellationSignal mFingerprintCancelSignal; private CancellationSignal mFaceCancelSignal; private FingerprintManager mFpm; - private FaceManager mFaceAuthenticationManager; + private FaceManager mFaceManager; /** * When we receive a @@ -1434,7 +1401,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener { mSubscriptionManager = SubscriptionManager.from(context); mDeviceProvisioned = isDeviceProvisionedInSettingsDb(); mStrongAuthTracker = new StrongAuthTracker(context); - mSettingObserver = new SettingObserver(mHandler); // Since device can't be un-provisioned, we only need to register a content observer // to update mDeviceProvisioned when we are... @@ -1504,17 +1470,21 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener { if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) { mFpm = (FingerprintManager) context.getSystemService(Context.FINGERPRINT_SERVICE); } - if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FACE)) { - mFaceAuthenticationManager = - (FaceManager) context.getSystemService(Context.FACE_SERVICE); + mFaceManager = (FaceManager) context.getSystemService(Context.FACE_SERVICE); } + + if (mFpm != null || mFaceManager != null) { + mBiometricManager = context.getSystemService(BiometricManager.class); + mBiometricManager.registerEnabledOnKeyguardCallback(mBiometricEnabledCallback); + } + updateBiometricListeningState(); if (mFpm != null) { mFpm.addLockoutResetCallback(mFingerprintLockoutResetCallback); } - if (mFaceAuthenticationManager != null) { - mFaceAuthenticationManager.addLockoutResetCallback(mFaceLockoutResetCallback); + if (mFaceManager != null) { + mFaceManager.addLockoutResetCallback(mFaceLockoutResetCallback); } ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener); @@ -1629,7 +1599,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener { mFaceCancelSignal.cancel(); } mFaceCancelSignal = new CancellationSignal(); - mFaceAuthenticationManager.authenticate(null, mFaceCancelSignal, 0, + mFaceManager.authenticate(null, mFaceCancelSignal, 0, mFaceAuthenticationCallback, null); setFaceRunningState(BIOMETRIC_STATE_RUNNING); } @@ -1641,9 +1611,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener { } public boolean isUnlockWithFacePossible(int userId) { - return mFaceAuthenticationManager != null && mFaceAuthenticationManager.isHardwareDetected() + return mFaceManager != null && mFaceManager.isHardwareDetected() && !isFaceDisabled(userId) - && mFaceAuthenticationManager.hasEnrolledTemplates(userId); + && mFaceManager.hasEnrolledTemplates(userId); } private void stopListeningForFingerprint() { @@ -1765,7 +1735,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener { * Handle {@link #MSG_USER_SWITCH_COMPLETE} */ private void handleUserSwitchComplete(int userId) { - mSettingObserver.updateContentObserver(); for (int i = 0; i < mCallbacks.size(); i++) { KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); if (cb != null) { @@ -2437,7 +2406,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener { pw.println(" strongAuthFlags=" + Integer.toHexString(strongAuthFlags)); pw.println(" trustManaged=" + getUserTrustIsManaged(userId)); } - if (mFaceAuthenticationManager != null && mFaceAuthenticationManager.isHardwareDetected()) { + if (mFaceManager != null && mFaceManager.isHardwareDetected()) { final int userId = ActivityManager.getCurrentUser(); final int strongAuthFlags = mStrongAuthTracker.getStrongAuthForUser(userId); pw.println(" Face authentication state (user=" + userId + ")"); @@ -2449,6 +2418,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener { pw.println(" possible=" + isUnlockWithFacePossible(userId)); pw.println(" strongAuthFlags=" + Integer.toHexString(strongAuthFlags)); pw.println(" trustManaged=" + getUserTrustIsManaged(userId)); + pw.println(" enabledByUser=" + mFaceSettingEnabledForUser); } } } diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java index b2cf305d0533..fe1fe1aba908 100644 --- a/packages/SystemUI/src/com/android/systemui/Dependency.java +++ b/packages/SystemUI/src/com/android/systemui/Dependency.java @@ -39,9 +39,10 @@ import com.android.systemui.fragments.FragmentService; import com.android.systemui.keyguard.ScreenLifecycle; import com.android.systemui.keyguard.WakefulnessLifecycle; import com.android.systemui.plugins.ActivityStarter; +import com.android.systemui.plugins.PluginInitializerImpl; import com.android.systemui.plugins.PluginDependencyProvider; -import com.android.systemui.plugins.PluginManager; -import com.android.systemui.plugins.PluginManagerImpl; +import com.android.systemui.shared.plugins.PluginManager; +import com.android.systemui.shared.plugins.PluginManagerImpl; import com.android.systemui.plugins.VolumeDialogController; import com.android.systemui.power.EnhancedEstimates; import com.android.systemui.power.EnhancedEstimatesImpl; @@ -236,7 +237,7 @@ public class Dependency extends SystemUI { new DeviceProvisionedControllerImpl(mContext)); mProviders.put(PluginManager.class, () -> - new PluginManagerImpl(mContext)); + new PluginManagerImpl(mContext, new PluginInitializerImpl())); mProviders.put(AssistManager.class, () -> new AssistManager(getDependency(DeviceProvisionedController.class), mContext)); diff --git a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java index 408e599bc289..77f4bf529f21 100644 --- a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java +++ b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java @@ -256,6 +256,12 @@ public class ImageWallpaper extends WallpaperService { Log.d(TAG, "onSurfaceRedrawNeeded"); } super.onSurfaceRedrawNeeded(holder); + // At the end of this method we should have drawn into the surface. + // This means that the bitmap should be loaded synchronously if + // it was already unloaded. + if (mBackground == null) { + updateBitmap(mWallpaperManager.getBitmap(true /* hardware */)); + } mSurfaceRedrawNeeded = true; drawFrame(); } diff --git a/packages/SystemUI/src/com/android/systemui/PluginInflateContainer.java b/packages/SystemUI/src/com/android/systemui/PluginInflateContainer.java index ddd48330e679..f6ad62616a96 100644 --- a/packages/SystemUI/src/com/android/systemui/PluginInflateContainer.java +++ b/packages/SystemUI/src/com/android/systemui/PluginInflateContainer.java @@ -21,7 +21,7 @@ import android.util.Log; import android.view.View; import com.android.systemui.plugins.PluginListener; -import com.android.systemui.plugins.PluginManager; +import com.android.systemui.shared.plugins.PluginManager; import com.android.systemui.plugins.ViewProvider; /** diff --git a/packages/SystemUI/src/com/android/systemui/RegionInterceptingFrameLayout.java b/packages/SystemUI/src/com/android/systemui/RegionInterceptingFrameLayout.java index 646f69eede94..6dc2d67a1c78 100644 --- a/packages/SystemUI/src/com/android/systemui/RegionInterceptingFrameLayout.java +++ b/packages/SystemUI/src/com/android/systemui/RegionInterceptingFrameLayout.java @@ -76,7 +76,7 @@ public class RegionInterceptingFrameLayout extends FrameLayout { continue; } - internalInsetsInfo.touchableRegion.op(riv.getInterceptRegion(), Op.UNION); + internalInsetsInfo.touchableRegion.op(unionRegion, Op.UNION); } }; diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java index 48181bc629a8..4d24d82bd7ee 100644 --- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java +++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java @@ -76,6 +76,9 @@ import com.android.systemui.tuner.TunerService; import com.android.systemui.tuner.TunerService.Tunable; import com.android.systemui.util.leak.RotationUtils; +import java.util.ArrayList; +import java.util.List; + import androidx.annotation.VisibleForTesting; /** @@ -108,6 +111,23 @@ public class ScreenDecorations extends SystemUI implements Tunable { private boolean mPendingRotationChange; private Handler mHandler; + /** + * Converts a set of {@link Rect}s into a {@link Region} + * + * @hide + */ + public static Region rectsToRegion(List<Rect> rects) { + Region result = Region.obtain(); + if (rects != null) { + for (Rect r : rects) { + if (r != null && !r.isEmpty()) { + result.op(r, Region.Op.UNION); + } + } + } + return result; + } + @Override public void start() { mHandler = startHandlerThread(); @@ -539,7 +559,7 @@ public class ScreenDecorations extends SystemUI implements Tunable { private final DisplayInfo mInfo = new DisplayInfo(); private final Paint mPaint = new Paint(); - private final Region mBounds = new Region(); + private final List<Rect> mBounds = new ArrayList(); private final Rect mBoundingRect = new Rect(); private final Path mBoundingPath = new Path(); private final int[] mLocation = new int[2]; @@ -629,12 +649,12 @@ public class ScreenDecorations extends SystemUI implements Tunable { mStart = isStart(); requestLayout(); getDisplay().getDisplayInfo(mInfo); - mBounds.setEmpty(); + mBounds.clear(); mBoundingRect.setEmpty(); mBoundingPath.reset(); int newVisible; if (shouldDrawCutout(getContext()) && hasCutout()) { - mBounds.set(mInfo.displayCutout.getBounds()); + mBounds.addAll(mInfo.displayCutout.getBoundingRects()); localBounds(mBoundingRect); updateBoundingPath(); invalidate(); @@ -713,32 +733,17 @@ public class ScreenDecorations extends SystemUI implements Tunable { public static void boundsFromDirection(DisplayCutout displayCutout, int gravity, Rect out) { - Region bounds = boundsFromDirection(displayCutout, gravity); - out.set(bounds.getBounds()); - bounds.recycle(); - } - - public static Region boundsFromDirection(DisplayCutout displayCutout, int gravity) { - Region bounds = displayCutout.getBounds(); switch (gravity) { case Gravity.TOP: - bounds.op(0, 0, Integer.MAX_VALUE, displayCutout.getSafeInsetTop(), - Region.Op.INTERSECT); - break; + out.set(displayCutout.getBoundingRectTop()); case Gravity.LEFT: - bounds.op(0, 0, displayCutout.getSafeInsetLeft(), Integer.MAX_VALUE, - Region.Op.INTERSECT); - break; + out.set(displayCutout.getBoundingRectLeft()); case Gravity.BOTTOM: - bounds.op(0, displayCutout.getSafeInsetTop() + 1, Integer.MAX_VALUE, - Integer.MAX_VALUE, Region.Op.INTERSECT); - break; + out.set(displayCutout.getBoundingRectBottom()); case Gravity.RIGHT: - bounds.op(displayCutout.getSafeInsetLeft() + 1, 0, Integer.MAX_VALUE, - Integer.MAX_VALUE, Region.Op.INTERSECT); - break; + out.set(displayCutout.getBoundingRectRight()); } - return bounds; + out.setEmpty(); } private void localBounds(Rect out) { @@ -771,7 +776,8 @@ public class ScreenDecorations extends SystemUI implements Tunable { } View rootView = getRootView(); - Region cutoutBounds = mInfo.displayCutout.getBounds(); + Region cutoutBounds = rectsToRegion( + mInfo.displayCutout.getBoundingRects()); // Transform to window's coordinate space rootView.getLocationOnScreen(mLocation); diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java index b96a6049421b..78053b28c4c3 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java @@ -34,7 +34,7 @@ import android.util.TimingsTraceLog; import com.android.systemui.plugins.OverlayPlugin; import com.android.systemui.plugins.PluginListener; -import com.android.systemui.plugins.PluginManager; +import com.android.systemui.shared.plugins.PluginManager; import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.phone.StatusBarWindowController; import com.android.systemui.util.NotificationChannels; diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIService.java b/packages/SystemUI/src/com/android/systemui/SystemUIService.java index bb82a54c12f1..8e29841e887d 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIService.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUIService.java @@ -28,8 +28,8 @@ import java.io.FileDescriptor; import java.io.PrintWriter; import com.android.internal.os.BinderInternal; -import com.android.systemui.plugins.PluginManager; -import com.android.systemui.plugins.PluginManagerImpl; +import com.android.systemui.shared.plugins.PluginManager; +import com.android.systemui.shared.plugins.PluginManagerImpl; public class SystemUIService extends Service { diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java index e6026c14cd28..21b21d9f5527 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java @@ -44,7 +44,7 @@ public class DozeLog { public static final int PULSE_REASON_SENSOR_PICKUP = 3; public static final int PULSE_REASON_SENSOR_DOUBLE_TAP = 4; public static final int PULSE_REASON_SENSOR_LONG_PRESS = 5; - public static final int PULSE_REASON_SENSOR_REACH = 6; + public static final int PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN = 6; public static final int REASON_SENSOR_WAKE_UP = 7; private static boolean sRegisterKeyguardCallback = true; @@ -177,9 +177,9 @@ public class DozeLog { log("state " + state); } - public static void traceReachWakeUp() { + public static void traceWakeLockScreenWakeUp() { if (!ENABLED) return; - log("reachWakeUp"); + log("wakeLockScreenWakeUp"); } public static void traceProximityResult(Context context, boolean near, long millis, @@ -199,7 +199,7 @@ public class DozeLog { case PULSE_REASON_SENSOR_PICKUP: return "pickup"; case PULSE_REASON_SENSOR_DOUBLE_TAP: return "doubletap"; case PULSE_REASON_SENSOR_LONG_PRESS: return "longpress"; - case PULSE_REASON_SENSOR_REACH: return "reach"; + case PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN: return "wakeLockScreen"; case REASON_SENSOR_WAKE_UP: return "wakeup"; default: throw new IllegalArgumentException("bad reason: " + pulseReason); } diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java index f9dfb5d10403..701394763fbd 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java @@ -113,10 +113,10 @@ public class DozeSensors { true /* reports touch coordinates */, true /* touchscreen */), new TriggerSensor( - findSensorWithType(config.reachSensorType()), - Settings.Secure.DOZE_REACH_GESTURE, + findSensorWithType(config.wakeLockScreenSensorType()), + Settings.Secure.DOZE_WAKE_LOCK_SCREEN_GESTURE, true /* configured */, - DozeLog.PULSE_REASON_SENSOR_REACH, + DozeLog.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN, false /* reports touch coordinates */, false /* touchscreen */), new WakeScreenSensor(), diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java index 73393047cc45..c61e10aa77ab 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java @@ -26,7 +26,7 @@ import com.android.systemui.Dependency; import com.android.systemui.plugins.DozeServicePlugin; import com.android.systemui.plugins.DozeServicePlugin.RequestDoze; import com.android.systemui.plugins.PluginListener; -import com.android.systemui.plugins.PluginManager; +import com.android.systemui.shared.plugins.PluginManager; import java.io.FileDescriptor; import java.io.PrintWriter; diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java index 31548b93ea60..cb91d7815be5 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java @@ -128,7 +128,7 @@ public class DozeTriggers implements DozeMachine.Part { boolean isDoubleTap = pulseReason == DozeLog.PULSE_REASON_SENSOR_DOUBLE_TAP; boolean isPickup = pulseReason == DozeLog.PULSE_REASON_SENSOR_PICKUP; boolean isLongPress = pulseReason == DozeLog.PULSE_REASON_SENSOR_LONG_PRESS; - boolean isReach = pulseReason == DozeLog.PULSE_REASON_SENSOR_REACH; + boolean isWakeLockScreen = pulseReason == DozeLog.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN; if (isLongPress) { requestPulse(pulseReason, sensorPerformedProxCheck); @@ -141,7 +141,7 @@ public class DozeTriggers implements DozeMachine.Part { if (isDoubleTap) { mDozeHost.onDoubleTap(screenX, screenY); mMachine.wakeUp(); - } else if (isPickup || isReach) { + } else if (isPickup || isWakeLockScreen) { mMachine.wakeUp(); } else { mDozeHost.extendPulse(); @@ -156,8 +156,8 @@ public class DozeTriggers implements DozeMachine.Part { final boolean withinVibrationThreshold = timeSinceNotification < mDozeParameters.getPickupVibrationThreshold(); DozeLog.tracePickupWakeUp(mContext, withinVibrationThreshold); - } else if (isReach) { - DozeLog.traceReachWakeUp(); + } else if (isWakeLockScreen) { + DozeLog.traceWakeLockScreenWakeUp(); } } diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java index bde7f1bd86ad..512cd82731c2 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java @@ -90,6 +90,7 @@ import com.android.systemui.Interpolators; import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.plugins.GlobalActions.GlobalActionsManager; import com.android.systemui.statusbar.phone.ScrimController; +import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.util.EmergencyDialerConstants; import com.android.systemui.volume.SystemUIInterpolators.LogAccelerateInterpolator; @@ -102,7 +103,8 @@ import java.util.List; * is provisioned. */ class GlobalActionsDialog implements DialogInterface.OnDismissListener, - DialogInterface.OnClickListener, DialogInterface.OnShowListener { + DialogInterface.OnClickListener, DialogInterface.OnShowListener, + ConfigurationController.ConfigurationListener { static public final String SYSTEM_DIALOG_REASON_KEY = "reason"; static public final String SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS = "globalactions"; @@ -197,6 +199,8 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener, mEmergencyAffordanceManager = new EmergencyAffordanceManager(context); mScreenshotHelper = new ScreenshotHelper(context); + + Dependency.get(ConfigurationController.class).addCallback(this); } /** @@ -417,6 +421,15 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener, || state == SOME_AUTH_REQUIRED_AFTER_USER_REQUEST); } + @Override + public void onUiModeChanged() { + mContext.getTheme().applyStyle(mContext.getThemeResId(), true); + } + + public void destroy() { + Dependency.get(ConfigurationController.class).removeCallback(this); + } + private final class PowerAction extends SinglePressAction implements LongPressAction { private PowerAction() { super(R.drawable.ic_lock_power_off, @@ -1530,7 +1543,6 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener, * ScrimController.GRADIENT_SCRIM_ALPHA * 255); mGradientDrawable.setAlpha(alpha); }) - .withEndAction(() -> getWindow().getDecorView().requestAccessibilityFocus()) .start(); } diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java index 1489c21c5a5c..039499858603 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java @@ -61,6 +61,10 @@ public class GlobalActionsImpl implements GlobalActions, CommandQueue.Callbacks @Override public void destroy() { SysUiServiceProvider.getComponent(mContext, CommandQueue.class).removeCallbacks(this); + if (mGlobalActions != null) { + mGlobalActions.destroy(); + mGlobalActions = null; + } } @Override diff --git a/packages/SystemUI/src/com/android/systemui/plugins/PluginDependencyProvider.java b/packages/SystemUI/src/com/android/systemui/plugins/PluginDependencyProvider.java index c58d889270e2..03daa95567ee 100644 --- a/packages/SystemUI/src/com/android/systemui/plugins/PluginDependencyProvider.java +++ b/packages/SystemUI/src/com/android/systemui/plugins/PluginDependencyProvider.java @@ -18,6 +18,7 @@ import android.util.ArrayMap; import com.android.systemui.Dependency; import com.android.systemui.plugins.PluginDependency.DependencyProvider; +import com.android.systemui.shared.plugins.PluginManager; public class PluginDependencyProvider extends DependencyProvider { diff --git a/packages/SystemUI/src/com/android/systemui/plugins/PluginInitializerImpl.java b/packages/SystemUI/src/com/android/systemui/plugins/PluginInitializerImpl.java new file mode 100644 index 000000000000..108c2d05d76c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/plugins/PluginInitializerImpl.java @@ -0,0 +1,47 @@ +/* + * 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.plugins; + +import android.content.Context; +import android.os.Looper; + +import com.android.systemui.Dependency; +import com.android.systemui.shared.plugins.PluginInitializer; +import com.android.systemui.R; + +public class PluginInitializerImpl implements PluginInitializer { + @Override + public Looper getBgLooper() { + return Dependency.get(Dependency.BG_LOOPER); + } + + @Override + public Runnable getBgInitCallback() { + return new Runnable() { + @Override + public void run() { + // Plugin dependencies that don't have another good home can go here, but + // dependencies that have better places to init can happen elsewhere. + Dependency.get(PluginDependencyProvider.class) + .allowPluginDependency(ActivityStarter.class); + } + }; + } + + @Override + public String[] getWhitelistedPlugins(Context context) { + return context.getResources().getStringArray(R.array.config_pluginWhitelist); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java index 0b9067e5dc1f..568a03954b36 100644 --- a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java +++ b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java @@ -301,9 +301,10 @@ public class PowerUI extends SystemUI { // mark if we've already shown a warning this cycle. This will prevent the notification // trigger from spamming users by only showing low/critical warnings once per cycle if (hybridEnabled) { - if (mTimeRemaining < mEnhancedEstimates.getSevereWarningThreshold() - || mBatteryLevel < mLowBatteryReminderLevels[1]) { + if (mTimeRemaining <= mEnhancedEstimates.getSevereWarningThreshold() + || mBatteryLevel <= mLowBatteryReminderLevels[1]) { mSevereWarningShownThisChargeCycle = true; + mLowWarningShownThisChargeCycle = true; } else { mLowWarningShownThisChargeCycle = true; } diff --git a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java index fcd479ce6627..c398a4a68976 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java +++ b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java @@ -6,9 +6,9 @@ import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.animation.PropertyValuesHolder; import android.content.Context; - -import androidx.viewpager.widget.PagerAdapter; -import androidx.viewpager.widget.ViewPager; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.os.Bundle; import android.util.AttributeSet; import android.util.Log; import android.view.LayoutInflater; @@ -18,6 +18,9 @@ import android.view.animation.Interpolator; import android.view.animation.OvershootInterpolator; import android.widget.Scroller; +import androidx.viewpager.widget.PagerAdapter; +import androidx.viewpager.widget.ViewPager; + import com.android.systemui.R; import com.android.systemui.qs.QSPanel.QSTileLayout; import com.android.systemui.qs.QSPanel.TileRecord; @@ -28,6 +31,7 @@ import java.util.Set; public class PagedTileLayout extends ViewPager implements QSTileLayout { private static final boolean DEBUG = false; + private static final String CURRENT_PAGE = "current_page"; private static final String TAG = "PagedTileLayout"; private static final int REVEAL_SCROLL_DURATION_MILLIS = 750; @@ -53,6 +57,9 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { private AnimatorSet mBounceAnimatorSet; private float mLastExpansion; private boolean mDistributeTiles = false; + private int mPageToRestore = -1; + private int mLayoutOrientation; + private int mLayoutDirection; public PagedTileLayout(Context context, AttributeSet attrs) { super(context, attrs); @@ -60,13 +67,37 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { setAdapter(mAdapter); setOnPageChangeListener(mOnPageChangeListener); setCurrentItem(0, false); + mLayoutOrientation = getResources().getConfiguration().orientation; + mLayoutDirection = getLayoutDirection(); + } + + public void saveInstanceState(Bundle outState) { + outState.putInt(CURRENT_PAGE, getCurrentItem()); + } + + public void restoreInstanceState(Bundle savedInstanceState) { + // There's only 1 page at this point. We want to restore the correct page once the + // pages have been inflated + mPageToRestore = savedInstanceState.getInt(CURRENT_PAGE, -1); + } + + @Override + protected void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + if (mLayoutOrientation != newConfig.orientation) { + mLayoutOrientation = newConfig.orientation; + setCurrentItem(0, false); + } } @Override public void onRtlPropertiesChanged(int layoutDirection) { super.onRtlPropertiesChanged(layoutDirection); - setAdapter(mAdapter); - setCurrentItem(0, false); + if (mLayoutDirection != layoutDirection) { + mLayoutDirection = layoutDirection; + setAdapter(mAdapter); + setCurrentItem(0, false); + } } @Override @@ -115,6 +146,7 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { super.onFinishInflate(); mPages.add((TilePage) LayoutInflater.from(getContext()) .inflate(R.layout.qs_paged_page, this, false)); + mAdapter.notifyDataSetChanged(); } public void setPageIndicator(PageIndicator indicator) { @@ -217,14 +249,19 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { mPageIndicator.setNumPages(mPages.size()); setAdapter(mAdapter); mAdapter.notifyDataSetChanged(); - setCurrentItem(0, false); + if (mPageToRestore != -1) { + setCurrentItem(mPageToRestore, false); + mPageToRestore = -1; + } } @Override public boolean updateResources() { // Update bottom padding, useful for removing extra space once the panel page indicator is // hidden. - setPadding(0, 0, 0, + Resources res = getContext().getResources(); + final int sidePadding = res.getDimensionPixelSize(R.dimen.notification_side_paddings); + setPadding(sidePadding, 0, sidePadding, getContext().getResources().getDimensionPixelSize( R.dimen.qs_paged_tile_layout_padding_bottom)); boolean changed = false; diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java index 79e508611750..42dfceed1e0d 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java @@ -103,6 +103,9 @@ public class QSFragment extends Fragment implements QS, CommandQueue.Callbacks { setListening(savedInstanceState.getBoolean(EXTRA_LISTENING)); setEditLocation(view); mQSCustomizer.restoreInstanceState(savedInstanceState); + if (mQsExpanded) { + mQSPanel.getTileLayout().restoreInstanceState(savedInstanceState); + } } SysUiServiceProvider.getComponent(getContext(), CommandQueue.class).addCallbacks(this); } @@ -127,6 +130,9 @@ public class QSFragment extends Fragment implements QS, CommandQueue.Callbacks { outState.putBoolean(EXTRA_EXPANDED, mQsExpanded); outState.putBoolean(EXTRA_LISTENING, mListening); mQSCustomizer.saveInstanceState(outState); + if (mQsExpanded) { + mQSPanel.getTileLayout().saveInstanceState(outState); + } } @VisibleForTesting diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java index 8b2e1d5eef64..cf63e478312c 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java @@ -25,6 +25,7 @@ import android.content.Context; import android.content.res.Configuration; import android.content.res.Resources; import android.metrics.LogMaker; +import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.service.quicksettings.Tile; @@ -199,7 +200,11 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne public void openDetails(String subPanel) { QSTile tile = getTile(subPanel); - showDetailAdapter(true, tile.getDetailAdapter(), new int[]{getWidth() / 2, 0}); + // If there's no tile with that name (as defined in QSFactoryImpl or other QSFactory), + // QSFactory will not be able to create a tile and getTile will return null + if (tile != null) { + showDetailAdapter(true, tile.getDetailAdapter(), new int[]{getWidth() / 2, 0}); + } } private QSTile getTile(String subPanel) { @@ -662,6 +667,11 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne } public interface QSTileLayout { + + default void saveInstanceState(Bundle outState) {} + + default void restoreInstanceState(Bundle savedInstanceState) {} + void addTile(TileRecord tile); void removeTile(TileRecord tile); diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java index 86e69e34fb9e..cefeeb526968 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java @@ -31,7 +31,7 @@ import android.util.Log; import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.plugins.PluginListener; -import com.android.systemui.plugins.PluginManager; +import com.android.systemui.shared.plugins.PluginManager; import com.android.systemui.plugins.qs.QSFactory; import com.android.systemui.plugins.qs.QSTileView; import com.android.systemui.plugins.qs.QSTile; diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java index 556786a16caf..6f847c86f8da 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java @@ -253,7 +253,8 @@ public class QuickQSPanel extends QSPanel { final int availableWidth = getMeasuredWidth() - getPaddingStart() - getPaddingEnd(); final int leftoverWithespace = availableWidth - maxTiles * mCellWidth; - final int smallestHorizontalMarginNeeded = leftoverWithespace / (maxTiles - 1); + final int smallestHorizontalMarginNeeded; + smallestHorizontalMarginNeeded = leftoverWithespace / Math.max(1, maxTiles - 1); if (smallestHorizontalMarginNeeded > 0){ mCellMarginHorizontal = smallestHorizontalMarginNeeded; diff --git a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java index 01ff72e6d152..e884302af075 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java +++ b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java @@ -103,8 +103,7 @@ public class TileLayout extends ViewGroup implements QSTileLayout { // it will show all its tiles. In this case, the tiles have to be entered before the // container is measured. Any change in the tiles, should trigger a remeasure. final int numTiles = mRecords.size(); - final int width = MeasureSpec.getSize(widthMeasureSpec) - - getPaddingStart() - getPaddingEnd(); + final int width = MeasureSpec.getSize(widthMeasureSpec); final int heightMode = MeasureSpec.getMode(heightMeasureSpec); if (heightMode == MeasureSpec.UNSPECIFIED) { mRows = (numTiles + mColumns - 1) / mColumns; diff --git a/packages/SystemUI/src/com/android/systemui/settings/BrightnessDialog.java b/packages/SystemUI/src/com/android/systemui/settings/BrightnessDialog.java index 6918a6309ae1..2ae53b5d81fe 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/BrightnessDialog.java +++ b/packages/SystemUI/src/com/android/systemui/settings/BrightnessDialog.java @@ -46,11 +46,7 @@ public class BrightnessDialog extends Activity { window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND); window.requestFeature(Window.FEATURE_NO_TITLE); - // Use a dialog theme as the activity theme, but inflate the content as - // the QS content. - ContextThemeWrapper themedContext = new ContextThemeWrapper(this, - com.android.internal.R.style.Theme_DeviceDefault_QuickSettings); - View v = LayoutInflater.from(themedContext).inflate( + View v = LayoutInflater.from(this).inflate( R.layout.quick_settings_brightness_dialog, null); setContentView(v); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java index 9b1d334c614d..bce613a33859 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java @@ -72,7 +72,7 @@ import com.android.systemui.Interpolators; import com.android.systemui.R; import com.android.systemui.classifier.FalsingManager; import com.android.systemui.plugins.PluginListener; -import com.android.systemui.plugins.PluginManager; +import com.android.systemui.shared.plugins.PluginManager; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem; import com.android.systemui.statusbar.notification.NotificationData; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java index 8969acaac532..057784153dd6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java @@ -426,6 +426,10 @@ public class AmbientState { return mDarkAmount == 1; } + public boolean isDarkAtAll() { + return mDarkAmount != 0; + } + public void setDarkTopPadding(int darkTopPadding) { mDarkTopPadding = darkTopPadding; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index 9978ec364cdb..9ddab7cc0b2f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -25,7 +25,6 @@ import android.animation.ObjectAnimator; import android.animation.PropertyValuesHolder; import android.animation.TimeAnimator; import android.animation.ValueAnimator; -import android.animation.ValueAnimator.AnimatorUpdateListener; import android.annotation.Nullable; import android.app.WallpaperManager; import android.content.Context; @@ -40,7 +39,6 @@ import android.graphics.PorterDuff; import android.graphics.PorterDuffXfermode; import android.graphics.Rect; import android.os.Bundle; -import android.os.Handler; import android.os.ServiceManager; import android.provider.Settings; import android.service.notification.StatusBarNotification; @@ -82,7 +80,6 @@ import com.android.systemui.ExpandHelper; import com.android.systemui.Interpolators; import com.android.systemui.R; import com.android.systemui.SwipeHelper; -import com.android.systemui.SwipeHelper.Callback; import com.android.systemui.classifier.FalsingManager; import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; @@ -3734,12 +3731,14 @@ public class NotificationStackScrollLayout extends ViewGroup return y < getHeight() - getEmptyBottomMargin(); } + @VisibleForTesting @ShadeViewRefactor(RefactorComponent.INPUT) - private void setIsBeingDragged(boolean isDragged) { + void setIsBeingDragged(boolean isDragged) { mIsBeingDragged = isDragged; if (isDragged) { requestDisallowInterceptTouchEvent(true); cancelLongPress(); + resetExposedMenuView(true /* animate */, true /* force */); } } @@ -3869,6 +3868,7 @@ public class NotificationStackScrollLayout extends ViewGroup public void onPanelTrackingStarted() { mPanelTracking = true; mAmbientState.setPanelTracking(true); + resetExposedMenuView(true /* animate */, true /* force */); } @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) @@ -4271,8 +4271,10 @@ public class NotificationStackScrollLayout extends ViewGroup mLinearDarkAmount = linearDarkAmount; mInterpolatedDarkAmount = interpolatedDarkAmount; boolean wasFullyDark = mAmbientState.isFullyDark(); + boolean wasDarkAtAll = mAmbientState.isDarkAtAll(); mAmbientState.setDarkAmount(interpolatedDarkAmount); boolean nowFullyDark = mAmbientState.isFullyDark(); + boolean nowDarkAtAll = mAmbientState.isDarkAtAll(); if (nowFullyDark != wasFullyDark) { updateContentHeight(); DozeParameters dozeParameters = DozeParameters.getInstance(mContext); @@ -4283,6 +4285,9 @@ public class NotificationStackScrollLayout extends ViewGroup mIconAreaController.setFullyDark(nowFullyDark); } } + if (!wasDarkAtAll && nowDarkAtAll) { + resetExposedMenuView(true /* animate */, true /* animate */); + } updateAlgorithmHeightAndPadding(); updateBackgroundDimming(); updatePanelTranslation(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java index cfc327116303..976327a49f58 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java @@ -22,6 +22,8 @@ import android.content.Context; import android.content.res.Configuration; import android.content.res.Resources; import androidx.collection.ArraySet; + +import android.graphics.Rect; import android.graphics.Region; import android.graphics.Region.Op; import android.util.Log; @@ -324,11 +326,10 @@ public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable, // Expand touchable region such that we also catch touches that just start below the notch // area. - Region bounds = ScreenDecorations.DisplayCutoutView.boundsFromDirection( - cutout, Gravity.TOP); - bounds.translate(0, mDisplayCutoutTouchableRegionSize); + Rect bounds = new Rect(); + ScreenDecorations.DisplayCutoutView.boundsFromDirection(cutout, Gravity.TOP, bounds); + bounds.offset(0, mDisplayCutoutTouchableRegionSize); region.op(bounds, Op.UNION); - bounds.recycle(); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java deleted file mode 100644 index 62d2099204e8..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.statusbar.phone; - -import android.content.Context; -import android.graphics.Canvas; -import android.view.MotionEvent; -import android.view.View; -import com.android.systemui.SysUiServiceProvider; -import com.android.systemui.plugins.statusbar.phone.NavGesture.GestureHelper; - -/** - * TODO: Remove and replace with QuickStepController - */ -public class NavigationBarGestureHelper implements GestureHelper { - - private static final String TAG = "NavigationBarGestureHelper"; - - private NavigationBarView mNavigationBarView; - - private final QuickStepController mQuickStepController; - private final StatusBar mStatusBar; - - public NavigationBarGestureHelper(Context context) { - mStatusBar = SysUiServiceProvider.getComponent(context, StatusBar.class); - mQuickStepController = new QuickStepController(context); - } - - public void setComponents(NavigationBarView navigationBarView) { - mNavigationBarView = navigationBarView; - mQuickStepController.setComponents(mNavigationBarView); - } - - public void setBarState(boolean isVertical, boolean isRTL) { - mQuickStepController.setBarState(isVertical, isRTL); - } - - public boolean onInterceptTouchEvent(MotionEvent event) { - if (!canHandleGestures()) { - return false; - } - return mQuickStepController.onInterceptTouchEvent(event); - } - - public boolean onTouchEvent(MotionEvent event) { - if (!canHandleGestures()) { - return false; - } - return mQuickStepController.onTouchEvent(event); - } - - public void onDraw(Canvas canvas) { - mQuickStepController.onDraw(canvas); - } - - public void onLayout(boolean changed, int left, int top, int right, int bottom) { - mQuickStepController.onLayout(changed, left, top, right, bottom); - } - - public void onDarkIntensityChange(float intensity) { - mQuickStepController.onDarkIntensityChange(intensity); - } - - public void onNavigationButtonLongPress(View v) { - mQuickStepController.onNavigationButtonLongPress(v); - } - - private boolean canHandleGestures() { - return !mStatusBar.isKeyguardShowing(); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java index e6f2c33c33d2..52134d9d713c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java @@ -37,7 +37,7 @@ import com.android.systemui.Dependency; import com.android.systemui.OverviewProxyService; import com.android.systemui.R; import com.android.systemui.plugins.PluginListener; -import com.android.systemui.plugins.PluginManager; +import com.android.systemui.shared.plugins.PluginManager; import com.android.systemui.plugins.statusbar.phone.NavBarButtonProvider; import com.android.systemui.statusbar.phone.ReverseLinearLayout.ReverseRelativeLayout; import com.android.systemui.statusbar.policy.KeyButtonView; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java index e5c910069f82..16b2987558d3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java @@ -40,7 +40,6 @@ import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.os.SystemProperties; -import androidx.annotation.ColorInt; import android.util.AttributeSet; import android.util.Log; import android.util.SparseArray; @@ -64,12 +63,11 @@ import com.android.systemui.R; import com.android.systemui.RecentsComponent; import com.android.systemui.SysUiServiceProvider; import com.android.systemui.plugins.PluginListener; -import com.android.systemui.plugins.PluginManager; +import com.android.systemui.shared.plugins.PluginManager; import com.android.systemui.plugins.statusbar.phone.NavGesture; import com.android.systemui.plugins.statusbar.phone.NavGesture.GestureHelper; import com.android.systemui.recents.Recents; import com.android.systemui.recents.RecentsOnboarding; -import com.android.systemui.shared.recents.IOverviewProxy; import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.NavigationBarCompat; import com.android.systemui.shared.system.WindowManagerWrapper; @@ -314,8 +312,8 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav public void setComponents(NotificationPanelView panel) { mPanelView = panel; - if (mGestureHelper instanceof NavigationBarGestureHelper) { - ((NavigationBarGestureHelper) mGestureHelper).setComponents(this); + if (mGestureHelper instanceof QuickStepController) { + ((QuickStepController) mGestureHelper).setComponents(this); } } @@ -1071,7 +1069,7 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav @Override public void onPluginDisconnected(NavGesture plugin) { - NavigationBarGestureHelper defaultHelper = new NavigationBarGestureHelper(getContext()); + QuickStepController defaultHelper = new QuickStepController(getContext()); defaultHelper.setComponents(this); if (mGestureHelper != null) { mGestureHelper.destroy(); @@ -1117,6 +1115,7 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav pw.println(" }"); mContextualButtonGroup.dump(pw); + mGestureHelper.dump(pw); mRecentsOnboarding.dump(pw); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationListenerWithPlugins.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationListenerWithPlugins.java index 9ff907b17564..9e561d13f347 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationListenerWithPlugins.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationListenerWithPlugins.java @@ -24,7 +24,7 @@ import com.android.systemui.Dependency; import com.android.systemui.plugins.NotificationListenerController; import com.android.systemui.plugins.NotificationListenerController.NotificationProvider; import com.android.systemui.plugins.PluginListener; -import com.android.systemui.plugins.PluginManager; +import com.android.systemui.shared.plugins.PluginManager; import java.util.ArrayList; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java index 3a4c218d337e..6ea4c92e4143 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java @@ -733,7 +733,11 @@ public class NotificationPanelView extends PanelView implements mQsExpandImmediate = true; mNotificationStackScroller.setShouldShowShelfOnly(true); } - expand(true /* animate */); + if (isFullyCollapsed()){ + expand(true /* animate */); + } else { + flingSettings(0 /* velocity */, FLING_EXPAND); + } } public void expandWithoutQs() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStepController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStepController.java index 30e6afa8465f..bce52a294e85 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStepController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStepController.java @@ -58,10 +58,12 @@ import com.android.systemui.Dependency; import com.android.systemui.Interpolators; import com.android.systemui.OverviewProxyService; import com.android.systemui.R; +import com.android.systemui.SysUiServiceProvider; import com.android.systemui.plugins.statusbar.phone.NavGesture.GestureHelper; import com.android.systemui.shared.recents.IOverviewProxy; import com.android.systemui.shared.recents.utilities.Utilities; import com.android.systemui.shared.system.NavigationBarCompat; +import java.io.PrintWriter; /** * Class to detect gestures on the navigation bar and implement quick scrub. @@ -117,6 +119,7 @@ public class QuickStepController implements GestureHelper { private final int mTrackEndPadding; private final int mHomeBackGestureDragLimit; private final Context mContext; + private final StatusBar mStatusBar; private final Matrix mTransformGlobalMatrix = new Matrix(); private final Matrix mTransformLocalMatrix = new Matrix(); private final Paint mTrackPaint = new Paint(); @@ -195,6 +198,7 @@ public class QuickStepController implements GestureHelper { public QuickStepController(Context context) { final Resources res = context.getResources(); mContext = context; + mStatusBar = SysUiServiceProvider.getComponent(context, StatusBar.class); mOverviewEventSender = Dependency.get(OverviewProxyService.class); mTrackThickness = res.getDimensionPixelSize(R.dimen.nav_quick_scrub_track_thickness); mTrackEndPadding = res.getDimensionPixelSize(R.dimen.nav_quick_scrub_track_edge_padding); @@ -218,6 +222,10 @@ public class QuickStepController implements GestureHelper { */ @Override public boolean onInterceptTouchEvent(MotionEvent event) { + if (mStatusBar.isKeyguardShowing()) { + // Disallow any handling when the keyguard is showing + return false; + } return handleTouchEvent(event); } @@ -227,6 +235,11 @@ public class QuickStepController implements GestureHelper { */ @Override public boolean onTouchEvent(MotionEvent event) { + if (mStatusBar.isKeyguardShowing()) { + // Disallow any handling when the keyguard is showing + return false; + } + // The same down event was just sent on intercept and therefore can be ignored here final boolean ignoreProxyDownEvent = event.getAction() == MotionEvent.ACTION_DOWN && mOverviewEventSender.getProxy() != null; @@ -483,6 +496,21 @@ public class QuickStepController implements GestureHelper { mHandler.removeCallbacksAndMessages(null); } + @Override + public void dump(PrintWriter pw) { + pw.println("QuickStepController {"); + pw.print(" "); pw.println("mQuickScrubActive=" + mQuickScrubActive); + pw.print(" "); pw.println("mQuickStepStarted=" + mQuickStepStarted); + pw.print(" "); pw.println("mAllowGestureDetection=" + mAllowGestureDetection); + pw.print(" "); pw.println("mBackGestureActive=" + mBackGestureActive); + pw.print(" "); pw.println("mCanPerformBack=" + mCanPerformBack); + pw.print(" "); pw.println("mNotificationsVisibleOnDown=" + mNotificationsVisibleOnDown); + pw.print(" "); pw.println("mIsVertical=" + mIsVertical); + pw.print(" "); pw.println("mIsRTL=" + mIsRTL); + pw.print(" "); pw.println("mIsInScreenPinning=" + mIsInScreenPinning); + pw.println("}"); + } + private void startQuickStep(MotionEvent event) { if (mIsInScreenPinning) { mNavigationBarView.showPinningEscapeToast(); 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 226b6453dbab..c3b87af612cc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -2182,7 +2182,7 @@ public class StatusBar extends SystemUI implements DemoMode, } @Override - public void animateExpandSettingsPanel(String subPanel) { + public void animateExpandSettingsPanel(@Nullable String subPanel) { if (SPEW) Log.d(TAG, "animateExpand: mExpandedVisible=" + mExpandedVisible); if (!panelsEnabled()) { return; @@ -2191,7 +2191,6 @@ public class StatusBar extends SystemUI implements DemoMode, // Settings are not available in setup if (!mUserSetup) return; - if (subPanel != null) { mQSPanel.openDetails(subPanel); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ExtensionControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ExtensionControllerImpl.java index 6d75cfcb38f3..a6146a625193 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ExtensionControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ExtensionControllerImpl.java @@ -22,7 +22,7 @@ import android.util.ArrayMap; import com.android.systemui.Dependency; import com.android.systemui.plugins.Plugin; import com.android.systemui.plugins.PluginListener; -import com.android.systemui.plugins.PluginManager; +import com.android.systemui.shared.plugins.PluginManager; import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener; import com.android.systemui.tuner.TunerService; import com.android.systemui.tuner.TunerService.Tunable; @@ -71,7 +71,7 @@ public class ExtensionControllerImpl implements ExtensionController { @Override public <P extends T> ExtensionController.ExtensionBuilder<T> withPlugin(Class<P> cls) { - return withPlugin(cls, PluginManager.getAction(cls)); + return withPlugin(cls, PluginManager.Helper.getAction(cls)); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java index cf394043b091..24a28cb45952 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java @@ -668,6 +668,7 @@ public class NetworkControllerImpl extends BroadcastReceiver Locale current = mContext.getResources().getConfiguration().locale; if (!current.equals(mLocale)) { mLocale = current; + mWifiSignalController.refreshLocale(); notifyAllListeners(); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java index 0233ad1c86ed..693df884690a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java @@ -73,6 +73,10 @@ public class WifiSignalController extends return new WifiState(); } + void refreshLocale() { + mWifiTracker.refreshLocale(); + } + @Override public void notifyListeners(SignalCallback callback) { // only show wifi in the cluster if connected or if wifi-only diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java index ef51bf06e411..8d2552f9b166 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java @@ -55,6 +55,7 @@ public class ZenModeControllerImpl extends CurrentUserTracker private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); private final ArrayList<Callback> mCallbacks = new ArrayList<>(); + private final Object mCallbacksLock = new Object(); private final Context mContext; private final GlobalSetting mModeSetting; private final GlobalSetting mConfigSetting; @@ -114,12 +115,16 @@ public class ZenModeControllerImpl extends CurrentUserTracker @Override public void addCallback(Callback callback) { - mCallbacks.add(callback); + synchronized (mCallbacksLock) { + mCallbacks.add(callback); + } } @Override public void removeCallback(Callback callback) { - mCallbacks.remove(callback); + synchronized (mCallbacksLock) { + mCallbacks.remove(callback); + } } @Override @@ -183,28 +188,40 @@ public class ZenModeControllerImpl extends CurrentUserTracker } private void fireNextAlarmChanged() { - Utils.safeForeach(mCallbacks, c -> c.onNextAlarmChanged()); + synchronized (mCallbacksLock) { + Utils.safeForeach(mCallbacks, c -> c.onNextAlarmChanged()); + } } private void fireEffectsSuppressorChanged() { - Utils.safeForeach(mCallbacks, c -> c.onEffectsSupressorChanged()); + synchronized (mCallbacksLock) { + Utils.safeForeach(mCallbacks, c -> c.onEffectsSupressorChanged()); + } } private void fireZenChanged(int zen) { - Utils.safeForeach(mCallbacks, c -> c.onZenChanged(zen)); + synchronized (mCallbacksLock) { + Utils.safeForeach(mCallbacks, c -> c.onZenChanged(zen)); + } } private void fireZenAvailableChanged(boolean available) { - Utils.safeForeach(mCallbacks, c -> c.onZenAvailableChanged(available)); + synchronized (mCallbacksLock) { + Utils.safeForeach(mCallbacks, c -> c.onZenAvailableChanged(available)); + } } private void fireManualRuleChanged(ZenRule rule) { - Utils.safeForeach(mCallbacks, c -> c.onManualRuleChanged(rule)); + synchronized (mCallbacksLock) { + Utils.safeForeach(mCallbacks, c -> c.onManualRuleChanged(rule)); + } } @VisibleForTesting protected void fireConfigChanged(ZenModeConfig config) { - Utils.safeForeach(mCallbacks, c -> c.onConfigChanged(config)); + synchronized (mCallbacksLock) { + Utils.safeForeach(mCallbacks, c -> c.onConfigChanged(config)); + } } @VisibleForTesting diff --git a/packages/SystemUI/src/com/android/systemui/tuner/PluginFragment.java b/packages/SystemUI/src/com/android/systemui/tuner/PluginFragment.java index c2948060d6bf..71414a266724 100644 --- a/packages/SystemUI/src/com/android/systemui/tuner/PluginFragment.java +++ b/packages/SystemUI/src/com/android/systemui/tuner/PluginFragment.java @@ -34,11 +34,10 @@ import android.util.ArraySet; import android.view.View; import com.android.systemui.R; -import com.android.systemui.plugins.PluginInstanceManager; -import com.android.systemui.plugins.PluginManager; -import com.android.systemui.plugins.PluginPrefs; +import com.android.systemui.shared.plugins.PluginInstanceManager; +import com.android.systemui.shared.plugins.PluginManager; +import com.android.systemui.shared.plugins.PluginPrefs; -import java.util.ArrayList; import java.util.List; import java.util.Set; diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerFragment.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerFragment.java index 088630fa3f56..5aa303530ae5 100644 --- a/packages/SystemUI/src/com/android/systemui/tuner/TunerFragment.java +++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerFragment.java @@ -32,7 +32,7 @@ import com.android.internal.hardware.AmbientDisplayConfiguration; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.systemui.R; -import com.android.systemui.plugins.PluginPrefs; +import com.android.systemui.shared.plugins.PluginPrefs; public class TunerFragment extends PreferenceFragment { diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java index 13c43f7009a1..4810b0b91c10 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java @@ -18,6 +18,7 @@ package com.android.systemui.volume; import static android.accessibilityservice.AccessibilityServiceInfo.FEEDBACK_ALL_MASK; import static android.accessibilityservice.AccessibilityServiceInfo.FEEDBACK_GENERIC; +import static android.app.ActivityManager.LOCK_TASK_MODE_NONE; import static android.media.AudioManager.RINGER_MODE_NORMAL; import static android.media.AudioManager.RINGER_MODE_SILENT; import static android.media.AudioManager.RINGER_MODE_VIBRATE; @@ -34,6 +35,7 @@ import static com.android.systemui.volume.Events.DISMISS_REASON_SETTINGS_CLICKED import android.accessibilityservice.AccessibilityServiceInfo; import android.animation.ObjectAnimator; import android.annotation.SuppressLint; +import android.app.ActivityManager; import android.app.Dialog; import android.app.KeyguardManager; import android.content.ContentResolver; @@ -129,6 +131,7 @@ public class VolumeDialogImpl implements VolumeDialog { private ConfigurableTexts mConfigurableTexts; private final SparseBooleanArray mDynamic = new SparseBooleanArray(); private final KeyguardManager mKeyguard; + private final ActivityManager mActivityManager; private final AccessibilityManagerWrapper mAccessibilityMgr; private final Object mSafetyWarningLock = new Object(); private final Accessibility mAccessibility = new Accessibility(); @@ -154,6 +157,7 @@ public class VolumeDialogImpl implements VolumeDialog { mContext = new ContextThemeWrapper(context, com.android.systemui.R.style.qs_theme); mController = Dependency.get(VolumeDialogController.class); mKeyguard = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE); + mActivityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE); mAccessibilityMgr = Dependency.get(AccessibilityManagerWrapper.class); mDeviceProvisionedController = Dependency.get(DeviceProvisionedController.class); mShowActiveStreamOnly = showActiveStreamOnly(); @@ -431,7 +435,9 @@ public class VolumeDialogImpl implements VolumeDialog { public void initSettingsH() { if (mSettingsView != null) { mSettingsView.setVisibility( - mDeviceProvisionedController.isCurrentUserSetup() ? VISIBLE : GONE); + mDeviceProvisionedController.isCurrentUserSetup() && + mActivityManager.getLockTaskModeState() == LOCK_TASK_MODE_NONE ? + VISIBLE : GONE); } if (mSettingsIcon != null) { mSettingsIcon.setOnClickListener(v -> { diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java index e6e485740a7b..62ca3f34bd48 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java @@ -40,7 +40,7 @@ import android.widget.TextClock; import com.android.systemui.SysuiTestCase; import com.android.systemui.plugins.ClockPlugin; import com.android.systemui.plugins.PluginListener; -import com.android.systemui.plugins.PluginManager; +import com.android.systemui.shared.plugins.PluginManager; import org.junit.Before; import org.junit.Test; diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java index cc969177ab2e..b84f85bdbc4c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java @@ -16,6 +16,7 @@ package com.android.systemui; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY; +import static com.android.systemui.ScreenDecorations.rectsToRegion; import static com.android.systemui.tuner.TunablePadding.FLAG_END; import static com.android.systemui.tuner.TunablePadding.FLAG_START; @@ -35,6 +36,7 @@ import static org.mockito.Mockito.when; import android.app.Fragment; import android.content.res.Configuration; +import android.graphics.Rect; import android.os.Handler; import android.support.test.filters.SmallTest; import android.testing.AndroidTestingRunner; @@ -58,6 +60,8 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import java.util.Collections; + @RunWithLooper @RunWith(AndroidTestingRunner.class) @SmallTest @@ -240,4 +244,11 @@ public class ScreenDecorationsTest extends SysuiTestCase { mScreenDecorations.onConfigurationChanged(null); assertEquals(mScreenDecorations.mRoundedDefault, 5); } + + @Test + public void testBoundingRectsToRegion() throws Exception { + Rect rect = new Rect(1, 2, 3, 4); + assertThat(rectsToRegion(Collections.singletonList(rect)).getBounds(), is(rect)); + } + } diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java b/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java index a9d49f91e44e..b44630a0cec0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java @@ -65,10 +65,12 @@ public class PowerUITest extends SysuiTestCase { private static final long ONE_HOUR_MILLIS = Duration.ofHours(1).toMillis(); public static final int BELOW_WARNING_BUCKET = -1; public static final long BELOW_HYBRID_THRESHOLD = TimeUnit.HOURS.toMillis(2); + public static final long BELOW_SEVERE_HYBRID_THRESHOLD = TimeUnit.MINUTES.toMillis(30); public static final long ABOVE_HYBRID_THRESHOLD = TimeUnit.HOURS.toMillis(4); private static final long ABOVE_CHARGE_CYCLE_THRESHOLD = Duration.ofHours(8).toMillis(); private static final int OLD_BATTERY_LEVEL_NINE = 9; private static final int OLD_BATTERY_LEVEL_10 = 10; + private static final long VERY_BELOW_SEVERE_HYBRID_THRESHOLD = TimeUnit.MINUTES.toMillis(15); private HardwarePropertiesManager mHardProps; private WarningsUI mMockWarnings; private PowerUI mPowerUI; @@ -467,6 +469,35 @@ public class PowerUITest extends SysuiTestCase { } @Test + public void testSevereWarning_countsAsLowAndSevere_WarningOnlyShownOnce() { + mPowerUI.start(); + when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(true); + when(mEnhancedEstimates.getLowWarningThreshold()).thenReturn(PowerUI.THREE_HOURS_IN_MILLIS); + when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS); + when(mEnhancedEstimates.getEstimate()) + .thenReturn(new Estimate(BELOW_SEVERE_HYBRID_THRESHOLD, true)); + mPowerUI.mBatteryStatus = BatteryManager.BATTERY_HEALTH_GOOD; + + // reduce battery level to handle time based trigger -> level trigger interactions + mPowerUI.mBatteryLevel = 5; + boolean shouldShow = + mPowerUI.shouldShowLowBatteryWarning(UNPLUGGED, UNPLUGGED, ABOVE_WARNING_BUCKET, + ABOVE_WARNING_BUCKET, BELOW_SEVERE_HYBRID_THRESHOLD, + POWER_SAVER_OFF, BatteryManager.BATTERY_HEALTH_GOOD); + assertTrue(shouldShow); + + // actually run the end to end since it handles changing the internal state. + mPowerUI.maybeShowBatteryWarning(OLD_BATTERY_LEVEL_10, UNPLUGGED, UNPLUGGED, + ABOVE_WARNING_BUCKET, ABOVE_WARNING_BUCKET); + + shouldShow = + mPowerUI.shouldShowLowBatteryWarning(UNPLUGGED, UNPLUGGED, ABOVE_WARNING_BUCKET, + ABOVE_WARNING_BUCKET, VERY_BELOW_SEVERE_HYBRID_THRESHOLD, + POWER_SAVER_OFF, BatteryManager.BATTERY_HEALTH_GOOD); + assertFalse(shouldShow); + } + + @Test public void testMaybeShowBatteryWarning_onlyQueriesEstimateOnBatteryLevelChangeOrNull() { mPowerUI.start(); Estimate estimate = new Estimate(BELOW_HYBRID_THRESHOLD, true); diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java index 85cdfcc2ce09..12a122a5c734 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java @@ -14,8 +14,12 @@ package com.android.systemui.qs; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -23,15 +27,21 @@ import android.support.test.filters.SmallTest; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; +import android.view.ViewGroup; +import android.widget.FrameLayout; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.systemui.SysuiTestCase; +import com.android.systemui.plugins.qs.QSTile; +import com.android.systemui.plugins.qs.QSTileView; import com.android.systemui.qs.customize.QSCustomizer; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; import java.util.Collections; @@ -41,19 +51,37 @@ import java.util.Collections; public class QSPanelTest extends SysuiTestCase { private MetricsLogger mMetricsLogger; + private TestableLooper mTestableLooper; private QSPanel mQsPanel; + @Mock private QSTileHost mHost; + @Mock private QSCustomizer mCustomizer; + @Mock + private QSTile dndTile; + private ViewGroup mParentView; + @Mock + private QSDetail.Callback mCallback; @Before public void setup() throws Exception { - TestableLooper.get(this).runWithLooper(() -> { + MockitoAnnotations.initMocks(this); + + mTestableLooper = TestableLooper.get(this); + mTestableLooper.runWithLooper(() -> { mMetricsLogger = mDependency.injectMockDependency(MetricsLogger.class); mQsPanel = new QSPanel(mContext, null); - mHost = mock(QSTileHost.class); + // Provides a parent with non-zero size for QSPanel + mParentView = new FrameLayout(mContext); + mParentView.addView(mQsPanel); + + when(dndTile.getTileSpec()).thenReturn("dnd"); when(mHost.getTiles()).thenReturn(Collections.emptyList()); - mCustomizer = mock(QSCustomizer.class); + when(mHost.createTileView(any(), anyBoolean())).thenReturn(mock(QSTileView.class)); + mQsPanel.setHost(mHost, mCustomizer); + mQsPanel.addTile(dndTile, true); + mQsPanel.setCallback(mCallback); }); } @@ -64,4 +92,31 @@ public class QSPanelTest extends SysuiTestCase { mQsPanel.setExpanded(false); verify(mMetricsLogger).visibility(eq(MetricsEvent.QS_PANEL), eq(false)); } + + @Test + public void testOpenDetailsWithExistingTile_NoException() { + mTestableLooper.processAllMessages(); + mQsPanel.openDetails("dnd"); + mTestableLooper.processAllMessages(); + + verify(mCallback).onShowingDetail(any(), anyInt(), anyInt()); + } + +/* @Test + public void testOpenDetailsWithNullParameter_NoException() { + mTestableLooper.processAllMessages(); + mQsPanel.openDetails(null); + mTestableLooper.processAllMessages(); + + verify(mCallback, never()).onShowingDetail(any(), anyInt(), anyInt()); + }*/ + + @Test + public void testOpenDetailsWithNonExistingTile_NoException() { + mTestableLooper.processAllMessages(); + mQsPanel.openDetails("invalid-name"); + mTestableLooper.processAllMessages(); + + verify(mCallback, never()).onShowingDetail(any(), anyInt(), anyInt()); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginInstanceManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceManagerTest.java index 19974f8fc710..6d1ff8c06acf 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginInstanceManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceManagerTest.java @@ -12,7 +12,7 @@ * permissions and limitations under the License. */ -package com.android.systemui.plugins; +package com.android.systemui.shared.plugins; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertNotNull; @@ -27,8 +27,10 @@ import static org.mockito.Mockito.when; import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; import com.android.systemui.SysuiTestCase; -import com.android.systemui.plugins.PluginInstanceManager.PluginInfo; -import com.android.systemui.plugins.VersionInfo.InvalidVersionException; +import com.android.systemui.plugins.Plugin; +import com.android.systemui.plugins.PluginListener; +import com.android.systemui.shared.plugins.PluginInstanceManager.PluginInfo; +import com.android.systemui.shared.plugins.VersionInfo.InvalidVersionException; import com.android.systemui.plugins.annotations.Requires; import org.junit.After; diff --git a/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginManagerTest.java index 438f9e49699b..3c7020569db4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginManagerTest.java @@ -11,7 +11,7 @@ * KIND, either express or implied. See the License for the specific language governing * permissions and limitations under the License. */ -package com.android.systemui.plugins; +package com.android.systemui.shared.plugins; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; @@ -27,6 +27,7 @@ import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.net.Uri; +import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.SmallTest; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; @@ -35,9 +36,12 @@ import android.testing.TestableLooper.RunWithLooper; import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; import com.android.systemui.Dependency; import com.android.systemui.SysuiTestCase; -import com.android.systemui.plugins.PluginInstanceManager.PluginInfo; -import com.android.systemui.plugins.PluginManagerImpl.PluginInstanceManagerFactory; +import com.android.systemui.plugins.Plugin; +import com.android.systemui.plugins.PluginInitializerImpl; +import com.android.systemui.plugins.PluginListener; import com.android.systemui.plugins.annotations.ProvidesInterface; +import com.android.systemui.shared.plugins.PluginInstanceManager.PluginInfo; +import com.android.systemui.shared.plugins.PluginManagerImpl.PluginInstanceManagerFactory; import org.junit.Before; import org.junit.Test; @@ -74,8 +78,14 @@ public class PluginManagerTest extends SysuiTestCase { when(mMockFactory.createPluginInstanceManager(Mockito.any(), Mockito.any(), Mockito.any(), Mockito.anyBoolean(), Mockito.any(), Mockito.any(), Mockito.any())) .thenReturn(mMockPluginInstance); - mPluginManager = new PluginManagerImpl(getContext(), mMockFactory, true, new String[0], - mMockExceptionHandler); + + mPluginManager = new PluginManagerImpl(getContext(), mMockFactory, true, + mMockExceptionHandler, new PluginInitializerImpl() { + @Override + public String[] getWhitelistedPlugins(Context context) { + return new String[0]; + } + }); resetExceptionHandler(); mMockListener = mock(PluginListener.class); } @@ -109,7 +119,12 @@ public class PluginManagerTest extends SysuiTestCase { @RunWithLooper(setAsMainLooper = true) public void testNonDebuggable_noWhitelist() { mPluginManager = new PluginManagerImpl(getContext(), mMockFactory, false, - new String[0], mMockExceptionHandler); + mMockExceptionHandler, new PluginInitializerImpl() { + @Override + public String[] getWhitelistedPlugins(Context context) { + return new String[0]; + } + }); resetExceptionHandler(); mPluginManager.addPluginListener("myAction", mMockListener, TestPlugin.class); @@ -121,7 +136,12 @@ public class PluginManagerTest extends SysuiTestCase { @RunWithLooper(setAsMainLooper = true) public void testNonDebuggable_whitelistedPkg() { mPluginManager = new PluginManagerImpl(getContext(), mMockFactory, false, - new String[] {WHITELISTED_PACKAGE}, mMockExceptionHandler); + mMockExceptionHandler, new PluginInitializerImpl() { + @Override + public String[] getWhitelistedPlugins(Context context) { + return new String[] {WHITELISTED_PACKAGE}; + } + }); resetExceptionHandler(); mPluginManager.addPluginListener("myAction", mMockListener, TestPlugin.class); diff --git a/packages/SystemUI/tests/src/com/android/systemui/plugins/VersionInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/VersionInfoTest.java index 0b4d9b525c1b..9bad78d2d45c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/plugins/VersionInfoTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/VersionInfoTest.java @@ -12,7 +12,7 @@ * permissions and limitations under the License. */ -package com.android.systemui.plugins; +package com.android.systemui.shared.plugins; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -20,7 +20,8 @@ import static org.junit.Assert.assertTrue; import android.support.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; -import com.android.systemui.plugins.VersionInfo.InvalidVersionException; +import com.android.systemui.plugins.OverlayPlugin; +import com.android.systemui.shared.plugins.VersionInfo.InvalidVersionException; import com.android.systemui.plugins.annotations.Requires; import com.android.systemui.plugins.qs.QS; import com.android.systemui.plugins.qs.DetailAdapter; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java index b545e61a446a..f8b24363da8f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java @@ -15,6 +15,8 @@ package com.android.systemui.statusbar.notification.stack; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertNotNull; +import static junit.framework.Assert.assertNull; + import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; @@ -36,6 +38,7 @@ import android.service.dreams.IDreamManager; import android.support.test.annotation.UiThreadTest; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; +import android.view.View; import com.android.systemui.ExpandHelper; import com.android.systemui.R; @@ -314,6 +317,36 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { verify(mStackScroller).setEmptyShadeView(any()); } + @Test + @UiThreadTest + public void testSetIsBeingDraggedResetsExposedMenu() { + NotificationSwipeHelper swipeActionHelper = + (NotificationSwipeHelper) mStackScroller.getSwipeActionHelper(); + swipeActionHelper.setExposedMenuView(new View(mContext)); + mStackScroller.setIsBeingDragged(true); + assertNull(swipeActionHelper.getExposedMenuView()); + } + + @Test + @UiThreadTest + public void testPanelTrackingStartResetsExposedMenu() { + NotificationSwipeHelper swipeActionHelper = + (NotificationSwipeHelper) mStackScroller.getSwipeActionHelper(); + swipeActionHelper.setExposedMenuView(new View(mContext)); + mStackScroller.onPanelTrackingStarted(); + assertNull(swipeActionHelper.getExposedMenuView()); + } + + @Test + @UiThreadTest + public void testDarkModeResetsExposedMenu() { + NotificationSwipeHelper swipeActionHelper = + (NotificationSwipeHelper) mStackScroller.getSwipeActionHelper(); + swipeActionHelper.setExposedMenuView(new View(mContext)); + mStackScroller.setDarkAmount(0.1f, 0.1f); + assertNull(swipeActionHelper.getExposedMenuView()); + } + private void setBarStateForTest(int state) { ArgumentCaptor<StatusBarStateController.StateListener> captor = ArgumentCaptor.forClass(StatusBarStateController.StateListener.class); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ExtensionControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ExtensionControllerImplTest.java index b22a6468f5fa..1cceefa7910c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ExtensionControllerImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ExtensionControllerImplTest.java @@ -33,7 +33,7 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.plugins.OverlayPlugin; import com.android.systemui.plugins.Plugin; import com.android.systemui.plugins.PluginListener; -import com.android.systemui.plugins.PluginManager; +import com.android.systemui.shared.plugins.PluginManager; import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener; import com.android.systemui.statusbar.policy.ExtensionController.Extension; import com.android.systemui.statusbar.policy.ExtensionController.TunerFactory; diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakePluginManager.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakePluginManager.java index 0a83a896dfaf..5f54bceb6b9b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakePluginManager.java +++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakePluginManager.java @@ -14,12 +14,11 @@ package com.android.systemui.utils.leaks; -import android.content.Context; import android.testing.LeakCheck; import com.android.systemui.plugins.Plugin; import com.android.systemui.plugins.PluginListener; -import com.android.systemui.plugins.PluginManager; +import com.android.systemui.shared.plugins.PluginManager; public class FakePluginManager implements PluginManager { diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/LeakCheckedTest.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/LeakCheckedTest.java index ecda9620f7fe..f47912623e1f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/LeakCheckedTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/LeakCheckedTest.java @@ -20,7 +20,7 @@ import android.testing.LeakCheck; import android.util.ArrayMap; import com.android.systemui.SysuiTestCase; -import com.android.systemui.plugins.PluginManager; +import com.android.systemui.shared.plugins.PluginManager; import com.android.systemui.statusbar.phone.ManagedProfileController; import com.android.systemui.statusbar.phone.StatusBarIconController; import com.android.systemui.statusbar.policy.BatteryController; diff --git a/proto/src/metrics_constants/metrics_constants.proto b/proto/src/metrics_constants/metrics_constants.proto index 90c10fdcbfde..5e87707c39e6 100644 --- a/proto/src/metrics_constants/metrics_constants.proto +++ b/proto/src/metrics_constants/metrics_constants.proto @@ -5926,7 +5926,7 @@ message MetricsEvent { // Tag used to determine what type of charging was started/ended // 1 = Plugged AC // 2 = Plugged USB - // 3 = Wireless + // 4 = Wireless FIELD_PLUG_TYPE = 1421; // ACTION: USB-C Connector connected. @@ -6447,7 +6447,7 @@ message MetricsEvent { // OPEN: Settings > System > Input & Gesture > Reach up gesture // OS: Q - SETTINGS_GESTURE_REACH = 1557; + SETTINGS_GESTURE_WAKE_LOCK_SCREEN = 1557; // OPEN: Emergency dialer opened // CLOSE: Emergency dialer closed diff --git a/proto/src/wifi.proto b/proto/src/wifi.proto index 7f8989d8548e..033e99605d3f 100644 --- a/proto/src/wifi.proto +++ b/proto/src/wifi.proto @@ -479,6 +479,9 @@ message WifiLog { // Hardware revision (EVT, DVT, PVT etc.) optional string hardware_revision = 124; + + // Total wifi link layer usage data over the logging duration in ms. + optional WifiLinkLayerUsageStats wifi_link_layer_usage_stats = 125; } // Information that gets logged for every WiFi connection. @@ -1654,4 +1657,21 @@ message PasspointProfileTypeCount { // Num of installed Passpoint profile with same eap method optional int32 count = 2; +} + +message WifiLinkLayerUsageStats { + // Total logging duration in ms. + optional int64 logging_duration_ms = 1; + + // Total time the wifi radio is on in ms over the logging duration. + optional int64 radio_on_time_ms = 2; + + // Total time the wifi radio is doing tx in ms over the logging duration. + optional int64 radio_tx_time_ms = 3; + + // Total time the wifi radio is doing rx in ms over the logging duration. + optional int64 radio_rx_time_ms = 4; + + // Total time the wifi radio is scanning in ms over the logging duration. + optional int64 radio_scan_time_ms = 5; }
\ No newline at end of file diff --git a/services/art-profile b/services/art-profile index 3c60eee4a2a9..328f8f7a37a7 100644 --- a/services/art-profile +++ b/services/art-profile @@ -2254,8 +2254,8 @@ HPLcom/android/server/wm/DisplayContent;->lambda$new$7(Lcom/android/server/wm/Di HPLcom/android/server/wm/DisplayContent;->lambda$new$8(Lcom/android/server/wm/DisplayContent;Lcom/android/server/wm/WindowState;)V HPLcom/android/server/wm/DisplayContent;->prepareSurfaces()V HPLcom/android/server/wm/DisplayContent;->resetAnimationBackgroundAnimator()V -HPLcom/android/server/wm/DisplayContent;->setTouchExcludeRegion(Lcom/android/server/wm/Task;)V HPLcom/android/server/wm/DisplayContent;->skipTraverseChild(Lcom/android/server/wm/WindowContainer;)Z +HPLcom/android/server/wm/DisplayContent;->updateTouchExcludeRegion()V HPLcom/android/server/wm/DockedStackDividerController;->isResizing()Z HPLcom/android/server/wm/DragDropController;->dragDropActiveLocked()Z HPLcom/android/server/wm/InputMonitor$UpdateInputForAllWindowsConsumer;->accept(Lcom/android/server/wm/WindowState;)V diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java index ad2f82c9b19f..af33cbc0ac1c 100644 --- a/services/core/java/com/android/server/AlarmManagerService.java +++ b/services/core/java/com/android/server/AlarmManagerService.java @@ -142,6 +142,8 @@ class AlarmManagerService extends SystemService { static final boolean RECORD_DEVICE_IDLE_ALARMS = false; static final String TIMEZONE_PROPERTY = "persist.sys.timezone"; + static final int TICK_HISTORY_DEPTH = 10; + // Indices into the APP_STANDBY_MIN_DELAYS and KEYS_APP_STANDBY_DELAY arrays static final int ACTIVE_INDEX = 0; static final int WORKING_INDEX = 1; @@ -176,21 +178,25 @@ class AlarmManagerService extends SystemService { private long mNextNonWakeUpSetAt; private long mLastWakeup; private long mLastTrigger; + private long mLastTickSet; - private long mLastTickIssued; // elapsed private long mLastTickReceived; private long mLastTickAdded; private long mLastTickRemoved; + // ring buffer of recent TIME_TICK issuance, in the elapsed timebase + private final long[] mTickHistory = new long[TICK_HISTORY_DEPTH]; + private int mNextTickHistory; + private final Injector mInjector; int mBroadcastRefCount = 0; PowerManager.WakeLock mWakeLock; - boolean mLastWakeLockUnimportantForLogging; ArrayList<Alarm> mPendingNonWakeupAlarms = new ArrayList<>(); ArrayList<InFlight> mInFlight = new ArrayList<>(); AlarmHandler mHandler; ClockReceiver mClockReceiver; final DeliveryTracker mDeliveryTracker = new DeliveryTracker(); - PendingIntent mTimeTickSender; + Intent mTimeTickIntent; + IAlarmListener mTimeTickTrigger; PendingIntent mDateChangeSender; Random mRandom; boolean mInteractive = true; @@ -509,7 +515,7 @@ class AlarmManagerService extends SystemService { end = clampPositive(seed.maxWhenElapsed); flags = seed.flags; alarms.add(seed); - if (seed.operation == mTimeTickSender) { + if (seed.listener == mTimeTickTrigger) { mLastTickAdded = mInjector.getCurrentTimeMillis(); } } @@ -534,7 +540,7 @@ class AlarmManagerService extends SystemService { index = 0 - index - 1; } alarms.add(index, alarm); - if (alarm.operation == mTimeTickSender) { + if (alarm.listener == mTimeTickTrigger) { mLastTickAdded = mInjector.getCurrentTimeMillis(); } if (DEBUG_BATCH) { @@ -572,7 +578,7 @@ class AlarmManagerService extends SystemService { if (alarm.alarmClock != null) { mNextAlarmClockMayChange = true; } - if (alarm.operation == mTimeTickSender) { + if (alarm.listener == mTimeTickTrigger) { mLastTickRemoved = mInjector.getCurrentTimeMillis(); } } else { @@ -690,8 +696,7 @@ class AlarmManagerService extends SystemService { Alarm a = alarms.get(i); final int alarmPrio; - if (a.operation != null - && Intent.ACTION_TIME_TICK.equals(a.operation.getIntent().getAction())) { + if (a.listener == mTimeTickTrigger) { alarmPrio = PRIO_TICK; } else if (a.wakeup) { alarmPrio = PRIO_WAKEUP; @@ -823,7 +828,7 @@ class AlarmManagerService extends SystemService { } final int batchSize = alarms.size(); for (int j = 0; j < batchSize; j++) { - if (alarms.get(j).operation == mTimeTickSender) { + if (alarms.get(j).listener == mTimeTickTrigger) { return true; } } @@ -1111,10 +1116,7 @@ class AlarmManagerService extends SystemService { updateNextAlarmClockLocked(); // And send a TIME_TICK right now, since it is important to get the UI updated. - try { - mTimeTickSender.send(); - } catch (PendingIntent.CanceledException e) { - } + mHandler.post(() -> getContext().sendBroadcastAsUser(mTimeTickIntent, UserHandle.ALL)); } static final class InFlight { @@ -1312,12 +1314,36 @@ class AlarmManagerService extends SystemService { } mWakeLock = mInjector.getAlarmWakeLock(); - mTimeTickSender = PendingIntent.getBroadcastAsUser(getContext(), 0, - new Intent(Intent.ACTION_TIME_TICK).addFlags( - Intent.FLAG_RECEIVER_REGISTERED_ONLY - | Intent.FLAG_RECEIVER_FOREGROUND - | Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS), 0, - UserHandle.ALL); + mTimeTickIntent = new Intent(Intent.ACTION_TIME_TICK).addFlags( + Intent.FLAG_RECEIVER_REGISTERED_ONLY + | Intent.FLAG_RECEIVER_FOREGROUND + | Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS); + + mTimeTickTrigger = new IAlarmListener.Stub() { + @Override + public void doAlarm(final IAlarmCompleteListener callback) throws RemoteException { + if (DEBUG_BATCH) { + Slog.v(TAG, "Received TIME_TICK alarm; rescheduling"); + } + + // Via handler because dispatch invokes this within its lock. OnAlarmListener + // takes care of this automatically, but we're using the direct internal + // interface here rather than that client-side wrapper infrastructure. + mHandler.post(() -> { + getContext().sendBroadcastAsUser(mTimeTickIntent, UserHandle.ALL); + + try { + callback.alarmComplete(this); + } catch (RemoteException e) { /* local method call */ } + }); + + synchronized (mLock) { + mLastTickReceived = mInjector.getCurrentTimeMillis(); + } + mClockReceiver.scheduleTimeTickEvent(); + } + }; + Intent intent = new Intent(Intent.ACTION_DATE_CHANGED); intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING | Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS); @@ -1438,12 +1464,9 @@ class AlarmManagerService extends SystemService { } } - void removeImpl(PendingIntent operation) { - if (operation == null) { - return; - } + void removeImpl(PendingIntent operation, IAlarmListener listener) { synchronized (mLock) { - removeLocked(operation, null); + removeLocked(operation, listener); } } @@ -1887,9 +1910,9 @@ class AlarmManagerService extends SystemService { pw.println(" App Standby Parole: " + mAppStandbyParole); pw.println(); - final long nowRTC = mInjector.getCurrentTimeMillis(); final long nowELAPSED = mInjector.getElapsedRealtime(); final long nowUPTIME = SystemClock.uptimeMillis(); + final long nowRTC = mInjector.getCurrentTimeMillis(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); pw.print(" nowRTC="); pw.print(nowRTC); @@ -1899,13 +1922,27 @@ class AlarmManagerService extends SystemService { pw.print(" mLastTimeChangeClockTime="); pw.print(mLastTimeChangeClockTime); pw.print("="); pw.println(sdf.format(new Date(mLastTimeChangeClockTime))); pw.print(" mLastTimeChangeRealtime="); pw.println(mLastTimeChangeRealtime); - pw.print(" mLastTickIssued="); - pw.println(sdf.format(new Date(nowRTC - (nowELAPSED - mLastTickIssued)))); pw.print(" mLastTickReceived="); pw.println(sdf.format(new Date(mLastTickReceived))); pw.print(" mLastTickSet="); pw.println(sdf.format(new Date(mLastTickSet))); pw.print(" mLastTickAdded="); pw.println(sdf.format(new Date(mLastTickAdded))); pw.print(" mLastTickRemoved="); pw.println(sdf.format(new Date(mLastTickRemoved))); + if (RECORD_ALARMS_IN_HISTORY) { + pw.println(); + pw.println(" Recent TIME_TICK history:"); + int i = mNextTickHistory; + do { + i--; + if (i < 0) i = TICK_HISTORY_DEPTH - 1; + final long time = mTickHistory[i]; + pw.print(" "); + pw.println((time > 0) + ? sdf.format(new Date(nowRTC - (nowELAPSED - time))) + : "-"); + } while (i != mNextTickHistory); + pw.println(); + } + SystemServiceManager ssm = LocalServices.getService(SystemServiceManager.class); if (ssm != null) { pw.println(); @@ -3640,8 +3677,8 @@ class AlarmManagerService extends SystemService { } // StatsLog requires currentTimeMillis(), which == nowRTC to within usecs. StatsLog.write(StatsLog.WALL_CLOCK_TIME_SHIFTED, nowRTC); - removeImpl(mTimeTickSender); - removeImpl(mDateChangeSender); + removeImpl(null, mTimeTickTrigger); + removeImpl(mDateChangeSender, null); rebatchAllAlarms(); mClockReceiver.scheduleTimeTickEvent(); mClockReceiver.scheduleDateChangedEvent(); @@ -3764,14 +3801,8 @@ class AlarmManagerService extends SystemService { void setWakelockWorkSource(PendingIntent pi, WorkSource ws, int type, String tag, int knownUid, boolean first) { try { - final boolean unimportant = pi == mTimeTickSender; - mWakeLock.setUnimportantForLogging(unimportant); - if (first || mLastWakeLockUnimportantForLogging) { - mWakeLock.setHistoryTag(tag); - } else { - mWakeLock.setHistoryTag(null); - } - mLastWakeLockUnimportantForLogging = unimportant; + mWakeLock.setHistoryTag(first ? tag : null); + if (ws != null) { mWakeLock.setWorkSource(ws); return; @@ -3828,7 +3859,7 @@ class AlarmManagerService extends SystemService { if (alarm.repeatInterval > 0) { // This IntentSender is no longer valid, but this // is a repeating alarm, so toss the hoser. - removeImpl(alarm.operation); + removeImpl(alarm.operation, null); } } } @@ -3886,22 +3917,13 @@ class AlarmManagerService extends SystemService { class ClockReceiver extends BroadcastReceiver { public ClockReceiver() { IntentFilter filter = new IntentFilter(); - filter.addAction(Intent.ACTION_TIME_TICK); filter.addAction(Intent.ACTION_DATE_CHANGED); getContext().registerReceiver(this, filter); } @Override public void onReceive(Context context, Intent intent) { - if (intent.getAction().equals(Intent.ACTION_TIME_TICK)) { - if (DEBUG_BATCH) { - Slog.v(TAG, "Received TIME_TICK alarm; rescheduling"); - } - synchronized (mLock) { - mLastTickReceived = mInjector.getCurrentTimeMillis(); - } - scheduleTimeTickEvent(); - } else if (intent.getAction().equals(Intent.ACTION_DATE_CHANGED)) { + if (intent.getAction().equals(Intent.ACTION_DATE_CHANGED)) { // Since the kernel does not keep track of DST, we need to // reset the TZ information at the beginning of each day // based off of the current Zone gmt offset + userspace tracked @@ -3923,7 +3945,7 @@ class AlarmManagerService extends SystemService { final WorkSource workSource = null; // Let system take blame for time tick events. setImpl(ELAPSED_REALTIME, mInjector.getElapsedRealtime() + tickEventDelay, 0, - 0, mTimeTickSender, null, null, AlarmManager.FLAG_STANDALONE, workSource, + 0, null, mTimeTickTrigger, null, AlarmManager.FLAG_STANDALONE, workSource, null, Process.myUid(), "android"); // Finally, remember when we set the tick alarm @@ -4333,10 +4355,6 @@ class AlarmManagerService extends SystemService { // PendingIntent alarm mSendCount++; - if (alarm.priorityClass.priority == PRIO_TICK) { - mLastTickIssued = nowELAPSED; - } - try { alarm.operation.send(getContext(), 0, mBackgroundIntent.putExtra( @@ -4344,13 +4362,10 @@ class AlarmManagerService extends SystemService { mDeliveryTracker, mHandler, null, allowWhileIdle ? mIdleOptions : null); } catch (PendingIntent.CanceledException e) { - if (alarm.operation == mTimeTickSender) { - Slog.wtf(TAG, "mTimeTickSender canceled"); - } if (alarm.repeatInterval > 0) { // This IntentSender is no longer valid, but this // is a repeating alarm, so toss it - removeImpl(alarm.operation); + removeImpl(alarm.operation, null); } // No actual delivery was possible, so the delivery tracker's // 'finished' callback won't be invoked. We also don't need @@ -4362,6 +4377,16 @@ class AlarmManagerService extends SystemService { } else { // Direct listener callback alarm mListenerCount++; + + if (RECORD_ALARMS_IN_HISTORY) { + if (alarm.listener == mTimeTickTrigger) { + mTickHistory[mNextTickHistory++] = nowELAPSED; + if (mNextTickHistory >= TICK_HISTORY_DEPTH) { + mNextTickHistory = 0; + } + } + } + try { if (DEBUG_LISTENER_CALLBACK) { Slog.v(TAG, "Alarm to uid=" + alarm.uid diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index e41a09ef672e..953c99ff2474 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -57,6 +57,7 @@ import android.net.ConnectivityManager; import android.net.ConnectivityManager.PacketKeepalive; import android.net.IConnectivityManager; import android.net.IIpConnectivityMetrics; +import android.net.INetd; import android.net.INetdEventCallback; import android.net.INetworkManagementEventObserver; import android.net.INetworkPolicyListener; @@ -88,6 +89,7 @@ import android.net.metrics.IpConnectivityLog; import android.net.metrics.NetworkEvent; import android.net.netlink.InetDiagMessage; import android.net.util.MultinetworkPolicyTracker; +import android.net.util.NetdService; import android.os.Binder; import android.os.Build; import android.os.Bundle; @@ -259,7 +261,8 @@ public class ConnectivityService extends IConnectivityManager.Stub // 0 is full bad, 100 is full good private int mDefaultInetConditionPublished = 0; - private INetworkManagementService mNetd; + private INetworkManagementService mNMS; + private INetd mNetd; private INetworkStatsService mStatsService; private INetworkPolicyManager mPolicyManager; private NetworkPolicyManagerInternal mPolicyManagerInternal; @@ -390,9 +393,9 @@ public class ConnectivityService extends IConnectivityManager.Stub private static final int EVENT_PROMPT_UNVALIDATED = 29; /** - * used internally to (re)configure mobile data always-on settings. + * used internally to (re)configure always-on networks. */ - private static final int EVENT_CONFIGURE_MOBILE_DATA_ALWAYS_ON = 30; + private static final int EVENT_CONFIGURE_ALWAYS_ON_NETWORKS = 30; /** * used to add a network listener with a pending intent @@ -748,6 +751,12 @@ public class ConnectivityService extends IConnectivityManager.Stub mDefaultMobileDataRequest = createDefaultInternetRequestForTransport( NetworkCapabilities.TRANSPORT_CELLULAR, NetworkRequest.Type.BACKGROUND_REQUEST); + // The default WiFi request is a background request so that apps using WiFi are + // migrated to a better network (typically ethernet) when one comes up, instead + // of staying on WiFi forever. + mDefaultWifiRequest = createDefaultInternetRequestForTransport( + NetworkCapabilities.TRANSPORT_WIFI, NetworkRequest.Type.BACKGROUND_REQUEST); + mHandlerThread = new HandlerThread("ConnectivityServiceThread"); mHandlerThread.start(); mHandler = new InternalHandler(mHandlerThread.getLooper()); @@ -759,7 +768,7 @@ public class ConnectivityService extends IConnectivityManager.Stub mLingerDelayMs = mSystemProperties.getInt(LINGER_DELAY_PROPERTY, DEFAULT_LINGER_DELAY_MS); mContext = checkNotNull(context, "missing Context"); - mNetd = checkNotNull(netManager, "missing INetworkManagementService"); + mNMS = checkNotNull(netManager, "missing INetworkManagementService"); mStatsService = checkNotNull(statsService, "missing INetworkStatsService"); mPolicyManager = checkNotNull(policyManager, "missing INetworkPolicyManager"); mPolicyManagerInternal = checkNotNull( @@ -767,6 +776,7 @@ public class ConnectivityService extends IConnectivityManager.Stub "missing NetworkPolicyManagerInternal"); mProxyTracker = new ProxyTracker(context, mHandler, EVENT_PROXY_HAS_CHANGED); + mNetd = NetdService.getInstance(); mKeyStore = KeyStore.getInstance(); mTelephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE); @@ -849,7 +859,7 @@ public class ConnectivityService extends IConnectivityManager.Stub mTethering = makeTethering(); - mPermissionMonitor = new PermissionMonitor(mContext, mNetd); + mPermissionMonitor = new PermissionMonitor(mContext, mNMS); //set up the listener for user state for creating user VPNs IntentFilter intentFilter = new IntentFilter(); @@ -864,8 +874,8 @@ public class ConnectivityService extends IConnectivityManager.Stub new IntentFilter(Intent.ACTION_USER_PRESENT), null, null); try { - mNetd.registerObserver(mTethering); - mNetd.registerObserver(mDataActivityObserver); + mNMS.registerObserver(mTethering); + mNMS.registerObserver(mDataActivityObserver); } catch (RemoteException e) { loge("Error registering observer :" + e); } @@ -896,7 +906,7 @@ public class ConnectivityService extends IConnectivityManager.Stub mMultipathPolicyTracker = new MultipathPolicyTracker(mContext, mHandler); - mDnsManager = new DnsManager(mContext, mNetd, mSystemProperties); + mDnsManager = new DnsManager(mContext, mNMS, mSystemProperties); registerPrivateDnsSettingsCallbacks(); } @@ -912,7 +922,7 @@ public class ConnectivityService extends IConnectivityManager.Stub return mDefaultRequest; } }; - return new Tethering(mContext, mNetd, mStatsService, mPolicyManager, + return new Tethering(mContext, mNMS, mStatsService, mPolicyManager, IoThread.get().getLooper(), new MockableSystemProperties(), deps); } @@ -944,8 +954,8 @@ public class ConnectivityService extends IConnectivityManager.Stub // 2. Give FakeSettingsProvider an alternative notification mechanism and have the test use it // by subclassing SettingsObserver. @VisibleForTesting - void updateMobileDataAlwaysOn() { - mHandler.sendEmptyMessage(EVENT_CONFIGURE_MOBILE_DATA_ALWAYS_ON); + void updateAlwaysOnNetworks() { + mHandler.sendEmptyMessage(EVENT_CONFIGURE_ALWAYS_ON_NETWORKS); } // See FakeSettingsProvider comment above. @@ -954,22 +964,30 @@ public class ConnectivityService extends IConnectivityManager.Stub mHandler.sendEmptyMessage(EVENT_PRIVATE_DNS_SETTINGS_CHANGED); } - private void handleMobileDataAlwaysOn() { + private void handleAlwaysOnNetworkRequest( + NetworkRequest networkRequest, String settingName, boolean defaultValue) { final boolean enable = toBool(Settings.Global.getInt( - mContext.getContentResolver(), Settings.Global.MOBILE_DATA_ALWAYS_ON, 1)); - final boolean isEnabled = (mNetworkRequests.get(mDefaultMobileDataRequest) != null); + mContext.getContentResolver(), settingName, encodeBool(defaultValue))); + final boolean isEnabled = (mNetworkRequests.get(networkRequest) != null); if (enable == isEnabled) { return; // Nothing to do. } if (enable) { handleRegisterNetworkRequest(new NetworkRequestInfo( - null, mDefaultMobileDataRequest, new Binder())); + null, networkRequest, new Binder())); } else { - handleReleaseNetworkRequest(mDefaultMobileDataRequest, Process.SYSTEM_UID); + handleReleaseNetworkRequest(networkRequest, Process.SYSTEM_UID); } } + private void handleConfigureAlwaysOnNetworks() { + handleAlwaysOnNetworkRequest( + mDefaultMobileDataRequest,Settings.Global.MOBILE_DATA_ALWAYS_ON, true); + handleAlwaysOnNetworkRequest(mDefaultWifiRequest, Settings.Global.WIFI_ALWAYS_REQUESTED, + false); + } + private void registerSettingsCallbacks() { // Watch for global HTTP proxy changes. mSettingsObserver.observe( @@ -979,7 +997,12 @@ public class ConnectivityService extends IConnectivityManager.Stub // Watch for whether or not to keep mobile data always on. mSettingsObserver.observe( Settings.Global.getUriFor(Settings.Global.MOBILE_DATA_ALWAYS_ON), - EVENT_CONFIGURE_MOBILE_DATA_ALWAYS_ON); + EVENT_CONFIGURE_ALWAYS_ON_NETWORKS); + + // Watch for whether or not to keep wifi always on. + mSettingsObserver.observe( + Settings.Global.getUriFor(Settings.Global.WIFI_ALWAYS_REQUESTED), + EVENT_CONFIGURE_ALWAYS_ON_NETWORKS); } private void registerPrivateDnsSettingsCallbacks() { @@ -1476,6 +1499,20 @@ public class ConnectivityService extends IConnectivityManager.Stub }; /** + * Ensures that the system cannot call a particular method. + */ + private boolean disallowedBecauseSystemCaller() { + // TODO: start throwing a SecurityException when GnssLocationProvider stops calling + // requestRouteToHost. + if (isSystem(Binder.getCallingUid())) { + log("This method exists only for app backwards compatibility" + + " and must not be called by system services."); + return true; + } + return false; + } + + /** * Ensure that a network route exists to deliver traffic to the specified * host via the specified network interface. * @param networkType the type of the network over which traffic to the @@ -1486,6 +1523,9 @@ public class ConnectivityService extends IConnectivityManager.Stub */ @Override public boolean requestRouteToHostAddress(int networkType, byte[] hostAddress) { + if (disallowedBecauseSystemCaller()) { + return false; + } enforceChangePermission(); if (mProtectedNetworks.contains(networkType)) { enforceConnectivityInternalPermission(); @@ -1563,7 +1603,7 @@ public class ConnectivityService extends IConnectivityManager.Stub if (DBG) log("Adding legacy route " + bestRoute + " for UID/PID " + uid + "/" + Binder.getCallingPid()); try { - mNetd.addLegacyRouteForNetId(netId, bestRoute, uid); + mNMS.addLegacyRouteForNetId(netId, bestRoute, uid); } catch (Exception e) { // never crash - catch them all if (DBG) loge("Exception trying to add a route: " + e); @@ -1814,8 +1854,8 @@ public class ConnectivityService extends IConnectivityManager.Stub // for user to unlock device too. updateLockdownVpn(); - // Configure whether mobile data is always on. - mHandler.sendMessage(mHandler.obtainMessage(EVENT_CONFIGURE_MOBILE_DATA_ALWAYS_ON)); + // Create network requests for always-on networks. + mHandler.sendMessage(mHandler.obtainMessage(EVENT_CONFIGURE_ALWAYS_ON_NETWORKS)); mHandler.sendMessage(mHandler.obtainMessage(EVENT_SYSTEM_READY)); @@ -1853,7 +1893,7 @@ public class ConnectivityService extends IConnectivityManager.Stub if (timeout > 0 && iface != null && type != ConnectivityManager.TYPE_NONE) { try { - mNetd.addIdleTimer(iface, timeout, type); + mNMS.addIdleTimer(iface, timeout, type); } catch (Exception e) { // You shall not crash! loge("Exception in setupDataActivityTracking " + e); @@ -1872,7 +1912,7 @@ public class ConnectivityService extends IConnectivityManager.Stub caps.hasTransport(NetworkCapabilities.TRANSPORT_WIFI))) { try { // the call fails silently if no idle timer setup for this interface - mNetd.removeIdleTimer(iface); + mNMS.removeIdleTimer(iface); } catch (Exception e) { loge("Exception in removeDataActivityTracking " + e); } @@ -1880,6 +1920,18 @@ public class ConnectivityService extends IConnectivityManager.Stub } /** + * Update data activity tracking when network state is updated. + */ + private void updateDataActivityTracking(NetworkAgentInfo newNetwork, + NetworkAgentInfo oldNetwork) { + if (newNetwork != null) { + setupDataActivityTracking(newNetwork); + } + if (oldNetwork != null) { + removeDataActivityTracking(oldNetwork); + } + } + /** * Reads the network specific MTU size from resources. * and set it on it's iface. */ @@ -1907,7 +1959,7 @@ public class ConnectivityService extends IConnectivityManager.Stub try { if (VDBG) log("Setting MTU size: " + iface + ", " + mtu); - mNetd.setMtu(iface, mtu); + mNMS.setMtu(iface, mtu); } catch (Exception e) { Slog.e(TAG, "exception in setMtu()" + e); } @@ -2561,7 +2613,7 @@ public class ConnectivityService extends IConnectivityManager.Stub } nai.clearLingerState(); if (nai.isSatisfyingRequest(mDefaultRequest.requestId)) { - removeDataActivityTracking(nai); + updateDataActivityTracking(null /* newNetwork */, nai); notifyLockdownVpn(nai); ensureNetworkTransitionWakelock(nai.name()); } @@ -2581,7 +2633,7 @@ public class ConnectivityService extends IConnectivityManager.Stub // NetworkFactories, so network traffic isn't interrupted for an unnecessarily // long time. try { - mNetd.removeNetwork(nai.network.netId); + mNMS.removeNetwork(nai.network.netId); } catch (Exception e) { loge("Exception removing network: " + e); } @@ -2779,20 +2831,6 @@ public class ConnectivityService extends IConnectivityManager.Stub } } - // TODO: remove this code once we know that the Slog.wtf is never hit. - // - // Find all networks that are satisfying this request and remove the request - // from their request lists. - // TODO - it's my understanding that for a request there is only a single - // network satisfying it, so this loop is wasteful - for (NetworkAgentInfo otherNai : mNetworkAgentInfos.values()) { - if (otherNai.isSatisfyingRequest(nri.request.requestId) && otherNai != nai) { - Slog.wtf(TAG, "Request " + nri.request + " satisfied by " + - otherNai.name() + ", but mNetworkAgentInfos says " + - (nai != null ? nai.name() : "null")); - } - } - // Maintain the illusion. When this request arrived, we might have pretended // that a network connected to serve it, even though the network was already // connected. Now that this request has gone away, we might have to pretend @@ -3106,8 +3144,8 @@ public class ConnectivityService extends IConnectivityManager.Stub handlePromptUnvalidated((Network) msg.obj); break; } - case EVENT_CONFIGURE_MOBILE_DATA_ALWAYS_ON: { - handleMobileDataAlwaysOn(); + case EVENT_CONFIGURE_ALWAYS_ON_NETWORKS: { + handleConfigureAlwaysOnNetworks(); break; } // Sent by KeepaliveTracker to process an app request on the state machine thread. @@ -3760,7 +3798,7 @@ public class ConnectivityService extends IConnectivityManager.Stub Slog.w(TAG, "VPN for user " + user + " not ready yet. Skipping lockdown"); return false; } - setLockdownTracker(new LockdownVpnTracker(mContext, mNetd, this, vpn, profile)); + setLockdownTracker(new LockdownVpnTracker(mContext, mNMS, this, vpn, profile)); } else { setLockdownTracker(null); } @@ -4015,7 +4053,7 @@ public class ConnectivityService extends IConnectivityManager.Stub loge("Starting user already has a VPN"); return; } - userVpn = new Vpn(mHandler.getLooper(), mContext, mNetd, userId); + userVpn = new Vpn(mHandler.getLooper(), mContext, mNMS, userId); mVpns.put(userId, userVpn); if (mUserManager.getUserInfo(userId).isPrimary() && LockdownVpnTracker.isEnabled()) { updateLockdownVpn(); @@ -4535,6 +4573,10 @@ public class ConnectivityService extends IConnectivityManager.Stub // priority networks like Wi-Fi are active. private final NetworkRequest mDefaultMobileDataRequest; + // Request used to optionally keep wifi data active even when higher + // priority networks like ethernet are active. + private final NetworkRequest mDefaultWifiRequest; + private NetworkAgentInfo getNetworkForRequest(int requestId) { synchronized (mNetworkForRequestId) { return mNetworkForRequestId.get(requestId); @@ -4632,7 +4674,7 @@ public class ConnectivityService extends IConnectivityManager.Stub mDnsManager.updatePrivateDnsStatus(netId, newLp); // Start or stop clat accordingly to network state. - networkAgent.updateClat(mNetd); + networkAgent.updateClat(mNMS); if (isDefaultNetwork(networkAgent)) { handleApplyDefaultProxy(newLp.getHttpProxy()); } else { @@ -4671,9 +4713,9 @@ public class ConnectivityService extends IConnectivityManager.Stub final String prefix = "iface:" + iface; try { if (add) { - mNetd.getNetdService().wakeupAddInterface(iface, prefix, mark, mask); + mNetd.wakeupAddInterface(iface, prefix, mark, mask); } else { - mNetd.getNetdService().wakeupDelInterface(iface, prefix, mark, mask); + mNetd.wakeupDelInterface(iface, prefix, mark, mask); } } catch (Exception e) { loge("Exception modifying wakeup packet monitoring: " + e); @@ -4689,7 +4731,7 @@ public class ConnectivityService extends IConnectivityManager.Stub for (String iface : interfaceDiff.added) { try { if (DBG) log("Adding iface " + iface + " to network " + netId); - mNetd.addInterfaceToNetwork(iface, netId); + mNMS.addInterfaceToNetwork(iface, netId); wakeupModifyInterface(iface, caps, true); } catch (Exception e) { loge("Exception adding interface: " + e); @@ -4699,7 +4741,7 @@ public class ConnectivityService extends IConnectivityManager.Stub try { if (DBG) log("Removing iface " + iface + " from network " + netId); wakeupModifyInterface(iface, caps, false); - mNetd.removeInterfaceFromNetwork(iface, netId); + mNMS.removeInterfaceFromNetwork(iface, netId); } catch (Exception e) { loge("Exception removing interface: " + e); } @@ -4723,7 +4765,7 @@ public class ConnectivityService extends IConnectivityManager.Stub if (route.hasGateway()) continue; if (VDBG) log("Adding Route [" + route + "] to network " + netId); try { - mNetd.addRoute(netId, route); + mNMS.addRoute(netId, route); } catch (Exception e) { if ((route.getDestination().getAddress() instanceof Inet4Address) || VDBG) { loge("Exception in addRoute for non-gateway: " + e); @@ -4734,7 +4776,7 @@ public class ConnectivityService extends IConnectivityManager.Stub if (route.hasGateway() == false) continue; if (VDBG) log("Adding Route [" + route + "] to network " + netId); try { - mNetd.addRoute(netId, route); + mNMS.addRoute(netId, route); } catch (Exception e) { if ((route.getGateway() instanceof Inet4Address) || VDBG) { loge("Exception in addRoute for gateway: " + e); @@ -4745,7 +4787,7 @@ public class ConnectivityService extends IConnectivityManager.Stub for (RouteInfo route : routeDiff.removed) { if (VDBG) log("Removing Route [" + route + "] from network " + netId); try { - mNetd.removeRoute(netId, route); + mNMS.removeRoute(netId, route); } catch (Exception e) { loge("Exception in removeRoute: " + e); } @@ -4857,7 +4899,7 @@ public class ConnectivityService extends IConnectivityManager.Stub final String newPermission = getNetworkPermission(newNc); if (!Objects.equals(oldPermission, newPermission) && nai.created && !nai.isVPN()) { try { - mNetd.setNetworkPermission(nai.network.netId, newPermission); + mNMS.setNetworkPermission(nai.network.netId, newPermission); } catch (RemoteException e) { loge("Exception in setNetworkPermission: " + e); } @@ -4917,12 +4959,12 @@ public class ConnectivityService extends IConnectivityManager.Stub if (!newRanges.isEmpty()) { final UidRange[] addedRangesArray = new UidRange[newRanges.size()]; newRanges.toArray(addedRangesArray); - mNetd.addVpnUidRanges(nai.network.netId, addedRangesArray); + mNMS.addVpnUidRanges(nai.network.netId, addedRangesArray); } if (!prevRanges.isEmpty()) { final UidRange[] removedRangesArray = new UidRange[prevRanges.size()]; prevRanges.toArray(removedRangesArray); - mNetd.removeVpnUidRanges(nai.network.netId, removedRangesArray); + mNMS.removeVpnUidRanges(nai.network.netId, removedRangesArray); } } catch (Exception e) { // Never crash! @@ -5091,9 +5133,9 @@ public class ConnectivityService extends IConnectivityManager.Stub private void makeDefault(NetworkAgentInfo newNetwork) { if (DBG) log("Switching to new default network: " + newNetwork); - setupDataActivityTracking(newNetwork); + try { - mNetd.setDefaultNetId(newNetwork.network.netId); + mNMS.setDefaultNetId(newNetwork.network.netId); } catch (Exception e) { loge("Exception setting default network :" + e); } @@ -5266,6 +5308,7 @@ public class ConnectivityService extends IConnectivityManager.Stub } } if (isNewDefault) { + updateDataActivityTracking(newNetwork, oldDefaultNetwork); // Notify system services that this network is up. makeDefault(newNetwork); // Log 0 -> X and Y -> X default network transitions, where X is the new default. @@ -5488,12 +5531,12 @@ public class ConnectivityService extends IConnectivityManager.Stub try { // This should never fail. Specifying an already in use NetID will cause failure. if (networkAgent.isVPN()) { - mNetd.createVirtualNetwork(networkAgent.network.netId, + mNMS.createVirtualNetwork(networkAgent.network.netId, !networkAgent.linkProperties.getDnsServers().isEmpty(), (networkAgent.networkMisc == null || !networkAgent.networkMisc.allowBypass)); } else { - mNetd.createPhysicalNetwork(networkAgent.network.netId, + mNMS.createPhysicalNetwork(networkAgent.network.netId, getNetworkPermission(networkAgent.networkCapabilities)); } } catch (Exception e) { diff --git a/services/core/java/com/android/server/DeviceIdleController.java b/services/core/java/com/android/server/DeviceIdleController.java index a34c2b93e3bf..6cba76424e6d 100644 --- a/services/core/java/com/android/server/DeviceIdleController.java +++ b/services/core/java/com/android/server/DeviceIdleController.java @@ -77,6 +77,7 @@ import android.util.TimeUtils; import android.util.Xml; import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.IBatteryStats; import com.android.internal.os.AtomicFile; import com.android.internal.os.BackgroundThread; @@ -104,6 +105,8 @@ import java.util.Arrays; /** * Keeps track of device idleness and drives low power mode based on that. + * + * Test: atest com.android.server.DeviceIdleControllerTest */ public class DeviceIdleController extends SystemService implements AnyMotionDetector.DeviceIdleCallback { @@ -148,21 +151,29 @@ public class DeviceIdleController extends SystemService private boolean mScreenLocked; /** Device is currently active. */ - private static final int STATE_ACTIVE = 0; + @VisibleForTesting + static final int STATE_ACTIVE = 0; /** Device is inactive (screen off, no motion) and we are waiting to for idle. */ - private static final int STATE_INACTIVE = 1; + @VisibleForTesting + static final int STATE_INACTIVE = 1; /** Device is past the initial inactive period, and waiting for the next idle period. */ - private static final int STATE_IDLE_PENDING = 2; + @VisibleForTesting + static final int STATE_IDLE_PENDING = 2; /** Device is currently sensing motion. */ - private static final int STATE_SENSING = 3; + @VisibleForTesting + static final int STATE_SENSING = 3; /** Device is currently finding location (and may still be sensing). */ - private static final int STATE_LOCATING = 4; + @VisibleForTesting + static final int STATE_LOCATING = 4; /** Device is in the idle state, trying to stay asleep as much as possible. */ - private static final int STATE_IDLE = 5; + @VisibleForTesting + static final int STATE_IDLE = 5; /** Device is in the idle state, but temporarily out of idle to do regular maintenance. */ - private static final int STATE_IDLE_MAINTENANCE = 6; + @VisibleForTesting + static final int STATE_IDLE_MAINTENANCE = 6; - private static String stateToString(int state) { + @VisibleForTesting + static String stateToString(int state) { switch (state) { case STATE_ACTIVE: return "ACTIVE"; case STATE_INACTIVE: return "INACTIVE"; @@ -176,21 +187,30 @@ public class DeviceIdleController extends SystemService } /** Device is currently active. */ - private static final int LIGHT_STATE_ACTIVE = 0; + @VisibleForTesting + static final int LIGHT_STATE_ACTIVE = 0; /** Device is inactive (screen off) and we are waiting to for the first light idle. */ - private static final int LIGHT_STATE_INACTIVE = 1; + @VisibleForTesting + static final int LIGHT_STATE_INACTIVE = 1; /** Device is about to go idle for the first time, wait for current work to complete. */ - private static final int LIGHT_STATE_PRE_IDLE = 3; + @VisibleForTesting + static final int LIGHT_STATE_PRE_IDLE = 3; /** Device is in the light idle state, trying to stay asleep as much as possible. */ - private static final int LIGHT_STATE_IDLE = 4; + @VisibleForTesting + static final int LIGHT_STATE_IDLE = 4; /** Device is in the light idle state, we want to go in to idle maintenance but are * waiting for network connectivity before doing so. */ - private static final int LIGHT_STATE_WAITING_FOR_NETWORK = 5; + @VisibleForTesting + static final int LIGHT_STATE_WAITING_FOR_NETWORK = 5; /** Device is in the light idle state, but temporarily out of idle to do regular maintenance. */ - private static final int LIGHT_STATE_IDLE_MAINTENANCE = 6; + @VisibleForTesting + static final int LIGHT_STATE_IDLE_MAINTENANCE = 6; /** Device light idle state is overriden, now applying deep doze state. */ - private static final int LIGHT_STATE_OVERRIDE = 7; - private static String lightStateToString(int state) { + @VisibleForTesting + static final int LIGHT_STATE_OVERRIDE = 7; + + @VisibleForTesting + static String lightStateToString(int state) { switch (state) { case LIGHT_STATE_ACTIVE: return "ACTIVE"; case LIGHT_STATE_INACTIVE: return "INACTIVE"; @@ -382,6 +402,8 @@ public class DeviceIdleController extends SystemService public void onAlarm() { if (mState == STATE_SENSING) { synchronized (DeviceIdleController.this) { + // Restart the device idle progression in case the device moved but the screen + // didn't turn on. becomeInactiveIfAppropriateLocked(); } } @@ -422,11 +444,16 @@ public class DeviceIdleController extends SystemService } }; - private final class MotionListener extends TriggerEventListener + @VisibleForTesting + final class MotionListener extends TriggerEventListener implements SensorEventListener { boolean active = false; + public boolean isActive() { + return active; + } + @Override public void onTrigger(TriggerEvent event) { synchronized (DeviceIdleController.this) { @@ -472,7 +499,7 @@ public class DeviceIdleController extends SystemService active = false; } } - private final MotionListener mMotionListener = new MotionListener(); + @VisibleForTesting final MotionListener mMotionListener = new MotionListener(); private final LocationListener mGenericLocationListener = new LocationListener() { @Override @@ -594,7 +621,7 @@ public class DeviceIdleController extends SystemService public float LIGHT_IDLE_FACTOR; /** - * This is the maximum time we will run in idle maintenence mode. + * This is the maximum time we will run in idle maintenance mode. * @see Settings.Global#DEVICE_IDLE_CONSTANTS * @see #KEY_LIGHT_MAX_IDLE_TIMEOUT */ @@ -1360,6 +1387,45 @@ public class DeviceIdleController extends SystemService } } + static class Injector { + private final Context mContext; + + Injector(Context ctx) { + mContext = ctx; + } + + AlarmManager getAlarmManager() { + return mContext.getSystemService(AlarmManager.class); + } + + AnyMotionDetector getAnyMotionDetector(Handler handler, SensorManager sm, + AnyMotionDetector.DeviceIdleCallback callback, float angleThreshold) { + return new AnyMotionDetector(getPowerManager(), handler, sm, callback, angleThreshold); + } + + AppStateTracker getAppStateTracker(Context ctx, Looper looper) { + return new AppStateTracker(ctx, looper); + } + + ConnectivityService getConnectivityService() { + return (ConnectivityService) ServiceManager.getService(Context.CONNECTIVITY_SERVICE); + } + + LocationManager getLocationManager() { + return mContext.getSystemService(LocationManager.class); + } + + MyHandler getHandler(DeviceIdleController ctlr) { + return ctlr.new MyHandler(BackgroundThread.getHandler().getLooper()); + } + + PowerManager getPowerManager() { + return mContext.getSystemService(PowerManager.class); + } + } + + private final Injector mInjector; + private ActivityTaskManagerInternal.ScreenObserver mScreenObserver = new ActivityTaskManagerInternal.ScreenObserver() { @Override @@ -1373,14 +1439,19 @@ public class DeviceIdleController extends SystemService } }; - public DeviceIdleController(Context context) { + @VisibleForTesting DeviceIdleController(Context context, Injector injector) { super(context); + mInjector = injector; mConfigFile = new AtomicFile(new File(getSystemDir(), "deviceidle.xml")); - mHandler = new MyHandler(BackgroundThread.getHandler().getLooper()); - mAppStateTracker = new AppStateTracker(context, FgThread.get().getLooper()); + mHandler = mInjector.getHandler(this); + mAppStateTracker = mInjector.getAppStateTracker(context, FgThread.get().getLooper()); LocalServices.addService(AppStateTracker.class, mAppStateTracker); } + public DeviceIdleController(Context context) { + this(context, new Injector(context)); + } + boolean isAppOnWhitelistInternal(int appid) { synchronized (this) { return Arrays.binarySearch(mPowerSaveWhitelistAllAppIdArray, appid) >= 0; @@ -1459,20 +1530,19 @@ public class DeviceIdleController extends SystemService public void onBootPhase(int phase) { if (phase == PHASE_SYSTEM_SERVICES_READY) { synchronized (this) { - mAlarmManager = (AlarmManager) getContext().getSystemService(Context.ALARM_SERVICE); + mAlarmManager = mInjector.getAlarmManager(); mBatteryStats = BatteryStatsService.getService(); mLocalActivityManager = getLocalService(ActivityManagerInternal.class); mLocalActivityTaskManager = getLocalService(ActivityTaskManagerInternal.class); mLocalPowerManager = getLocalService(PowerManagerInternal.class); - mPowerManager = getContext().getSystemService(PowerManager.class); + mPowerManager = mInjector.getPowerManager(); mActiveIdleWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "deviceidle_maint"); mActiveIdleWakeLock.setReferenceCounted(false); mGoingIdleWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "deviceidle_going_idle"); mGoingIdleWakeLock.setReferenceCounted(true); - mConnectivityService = (ConnectivityService)ServiceManager.getService( - Context.CONNECTIVITY_SERVICE); + mConnectivityService = mInjector.getConnectivityService(); mNetworkPolicyManager = INetworkPolicyManager.Stub.asInterface( ServiceManager.getService(Context.NETWORK_POLICY_SERVICE)); mNetworkPolicyManagerInternal = getLocalService(NetworkPolicyManagerInternal.class); @@ -1495,8 +1565,7 @@ public class DeviceIdleController extends SystemService if (getContext().getResources().getBoolean( com.android.internal.R.bool.config_autoPowerModePrefetchLocation)) { - mLocationManager = (LocationManager) getContext().getSystemService( - Context.LOCATION_SERVICE); + mLocationManager = mInjector.getLocationManager(); mLocationRequest = new LocationRequest() .setQuality(LocationRequest.ACCURACY_FINE) .setInterval(0) @@ -1506,9 +1575,8 @@ public class DeviceIdleController extends SystemService float angleThreshold = getContext().getResources().getInteger( com.android.internal.R.integer.config_autoPowerModeThresholdAngle) / 100f; - mAnyMotionDetector = new AnyMotionDetector( - (PowerManager) getContext().getSystemService(Context.POWER_SERVICE), - mHandler, mSensorManager, this, angleThreshold); + mAnyMotionDetector = mInjector.getAnyMotionDetector(mHandler, mSensorManager, this, + angleThreshold); mAppStateTracker.onSystemServicesReady(); @@ -2005,6 +2073,11 @@ public class DeviceIdleController extends SystemService } } + @VisibleForTesting + boolean isScreenOn() { + return mScreenOn; + } + void updateInteractivityLocked() { // The interactivity state from the power manager tells us whether the display is // in a state that we need to keep things running so they will update at a normal @@ -2024,6 +2097,11 @@ public class DeviceIdleController extends SystemService } } + @VisibleForTesting + boolean isCharging() { + return mCharging; + } + void updateChargingLocked(boolean charging) { if (DEBUG) Slog.i(TAG, "updateChargingLocked: charging=" + charging); if (!charging && mCharging) { @@ -2071,6 +2149,18 @@ public class DeviceIdleController extends SystemService } } + /** Must only be used in tests. */ + @VisibleForTesting + void setDeepEnabledForTest(boolean enabled) { + mDeepEnabled = enabled; + } + + /** Must only be used in tests. */ + @VisibleForTesting + void setLightEnabledForTest(boolean enabled) { + mLightEnabled = enabled; + } + void becomeInactiveIfAppropriateLocked() { if (DEBUG) Slog.d(TAG, "becomeInactiveIfAppropriateLocked()"); if ((!mScreenOn && !mCharging) || mForceIdle) { @@ -2093,7 +2183,7 @@ public class DeviceIdleController extends SystemService } } - void resetIdleManagementLocked() { + private void resetIdleManagementLocked() { mNextIdlePendingDelay = 0; mNextIdleDelay = 0; mNextLightIdleDelay = 0; @@ -2104,7 +2194,7 @@ public class DeviceIdleController extends SystemService mAnyMotionDetector.stop(); } - void resetLightIdleManagementLocked() { + private void resetLightIdleManagementLocked() { cancelLightAlarmLocked(); } @@ -2117,6 +2207,11 @@ public class DeviceIdleController extends SystemService } } + @VisibleForTesting + int getLightState() { + return mLightState; + } + void stepLightIdleStateLocked(String reason) { if (mLightState == LIGHT_STATE_OVERRIDE) { // If we are already in deep device idle mode, then @@ -2200,6 +2295,18 @@ public class DeviceIdleController extends SystemService } } + /** Must only be used in tests. */ + @VisibleForTesting + void setLocationManagerForTest(LocationManager lm) { + mLocationManager = lm; + } + + @VisibleForTesting + int getState() { + return mState; + } + + @VisibleForTesting void stepIdleStateLocked(String reason) { if (DEBUG) Slog.d(TAG, "stepIdleStateLocked: mState=" + mState); EventLogTags.writeDeviceIdleStep(); diff --git a/services/core/java/com/android/server/GestureLauncherService.java b/services/core/java/com/android/server/GestureLauncherService.java index b7b5bd930f8e..8077e3499bb2 100644 --- a/services/core/java/com/android/server/GestureLauncherService.java +++ b/services/core/java/com/android/server/GestureLauncherService.java @@ -356,6 +356,12 @@ public class GestureLauncherService extends SystemService { public boolean interceptPowerKeyDown(KeyEvent event, boolean interactive, MutableBoolean outLaunched) { + if (event.isLongPress()) { + // Long presses are sent as a second key down. If the long press threshold is set lower + // than the double tap of sequence interval thresholds, this could cause false double + // taps or consecutive taps, so we want to ignore the long press event. + return false; + } boolean launched = false; boolean intercept = false; long powerTapInterval; diff --git a/services/core/java/com/android/server/LooperStatsService.java b/services/core/java/com/android/server/LooperStatsService.java index 4f0e17055769..96ce6a4ee6a4 100644 --- a/services/core/java/com/android/server/LooperStatsService.java +++ b/services/core/java/com/android/server/LooperStatsService.java @@ -129,7 +129,12 @@ public class LooperStatsService extends Binder { } private void setSamplingInterval(int samplingInterval) { - mStats.setSamplingInterval(samplingInterval); + if (samplingInterval > 0) { + mStats.setSamplingInterval(samplingInterval); + } else { + Slog.w(TAG, "Ignored invalid sampling interval (value must be positive): " + + samplingInterval); + } } /** diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java index 1d163eed00a4..de930f794e50 100644 --- a/services/core/java/com/android/server/NetworkManagementService.java +++ b/services/core/java/com/android/server/NetworkManagementService.java @@ -161,6 +161,8 @@ public class NetworkManagementService extends INetworkManagementService.Stub private static final int MAX_UID_RANGES_PER_COMMAND = 10; + private static final String[] EMPTY_STRING_ARRAY = new String[0]; + /** * Name representing {@link #setGlobalAlert(long)} limit when delivered to * {@link INetworkManagementEventObserver#limitReached(String, String)}. @@ -1234,18 +1236,12 @@ public class NetworkManagementService extends INetworkManagementService.Stub @Override public void startTethering(String[] dhcpRange) { mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); - // cmd is "tether start first_start first_stop second_start second_stop ..." // an odd number of addrs will fail - final Command cmd = new Command("tether", "start"); - for (String d : dhcpRange) { - cmd.appendArg(d); - } - try { - mConnector.execute(cmd); - } catch (NativeDaemonConnectorException e) { - throw e.rethrowAsParcelableException(); + mNetdService.tetherStart(dhcpRange); + } catch (RemoteException | ServiceSpecificException e) { + throw new IllegalStateException(e); } } @@ -1253,9 +1249,9 @@ public class NetworkManagementService extends INetworkManagementService.Stub public void stopTethering() { mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); try { - mConnector.execute("tether", "stop"); - } catch (NativeDaemonConnectorException e) { - throw e.rethrowAsParcelableException(); + mNetdService.tetherStop(); + } catch (RemoteException | ServiceSpecificException e) { + throw new IllegalStateException(e); } } @@ -1263,25 +1259,21 @@ public class NetworkManagementService extends INetworkManagementService.Stub public boolean isTetheringStarted() { mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); - final NativeDaemonEvent event; try { - event = mConnector.execute("tether", "status"); - } catch (NativeDaemonConnectorException e) { - throw e.rethrowAsParcelableException(); + final boolean isEnabled = mNetdService.tetherIsEnabled(); + return isEnabled; + } catch (RemoteException | ServiceSpecificException e) { + throw new IllegalStateException(e); } - - // 210 Tethering services started - event.checkCode(TetherStatusResult); - return event.getMessage().endsWith("started"); } @Override public void tetherInterface(String iface) { mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); try { - mConnector.execute("tether", "interface", "add", iface); - } catch (NativeDaemonConnectorException e) { - throw e.rethrowAsParcelableException(); + mNetdService.tetherInterfaceAdd(iface); + } catch (RemoteException | ServiceSpecificException e) { + throw new IllegalStateException(e); } List<RouteInfo> routes = new ArrayList<>(); // The RouteInfo constructor truncates the LinkAddress to a network prefix, thus making it @@ -1294,9 +1286,9 @@ public class NetworkManagementService extends INetworkManagementService.Stub public void untetherInterface(String iface) { mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); try { - mConnector.execute("tether", "interface", "remove", iface); - } catch (NativeDaemonConnectorException e) { - throw e.rethrowAsParcelableException(); + mNetdService.tetherInterfaceRemove(iface); + } catch (RemoteException | ServiceSpecificException e) { + throw new IllegalStateException(e); } finally { removeInterfaceFromLocalNetwork(iface); } @@ -1306,11 +1298,10 @@ public class NetworkManagementService extends INetworkManagementService.Stub public String[] listTetheredInterfaces() { mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); try { - return NativeDaemonEvent.filterMessageList( - mConnector.executeForList("tether", "interface", "list"), - TetherInterfaceListResult); - } catch (NativeDaemonConnectorException e) { - throw e.rethrowAsParcelableException(); + final List<String> result = mNetdService.tetherInterfaceList(); + return result.toArray(EMPTY_STRING_ARRAY); + } catch (RemoteException | ServiceSpecificException e) { + throw new IllegalStateException(e); } } @@ -1319,16 +1310,11 @@ public class NetworkManagementService extends INetworkManagementService.Stub mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); int netId = (network != null) ? network.netId : ConnectivityManager.NETID_UNSET; - final Command cmd = new Command("tether", "dns", "set", netId); - - for (String s : dns) { - cmd.appendArg(NetworkUtils.numericToInetAddress(s).getHostAddress()); - } try { - mConnector.execute(cmd); - } catch (NativeDaemonConnectorException e) { - throw e.rethrowAsParcelableException(); + mNetdService.tetherDnsSet(netId, dns); + } catch (RemoteException | ServiceSpecificException e) { + throw new IllegalStateException(e); } } @@ -1336,10 +1322,10 @@ public class NetworkManagementService extends INetworkManagementService.Stub public String[] getDnsForwarders() { mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); try { - return NativeDaemonEvent.filterMessageList( - mConnector.executeForList("tether", "dns", "list"), TetherDnsFwdTgtListResult); - } catch (NativeDaemonConnectorException e) { - throw e.rethrowAsParcelableException(); + final List<String> result = mNetdService.tetherDnsList(); + return result.toArray(EMPTY_STRING_ARRAY); + } catch (RemoteException | ServiceSpecificException e) { + throw new IllegalStateException(e); } } diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java index d505a77c9192..21f54dd33d3e 100644 --- a/services/core/java/com/android/server/StorageManagerService.java +++ b/services/core/java/com/android/server/StorageManagerService.java @@ -329,6 +329,12 @@ class StorageManagerService extends IStorageManager.Stub @GuardedBy("mPackagesLock") private final SparseArray<String> mSandboxIds = new SparseArray<>(); + /** + * List of volumes visible to any user. + * TODO: may be have a map of userId -> volumes? + */ + private final CopyOnWriteArrayList<VolumeInfo> mVisibleVols = new CopyOnWriteArrayList<>(); + private volatile int mCurrentUserId = UserHandle.USER_SYSTEM; /** Holding lock for AppFuse business */ @@ -623,16 +629,12 @@ class StorageManagerService extends IStorageManager.Stub Slog.i(TAG, "Ignoring mount " + vol.getId() + " due to policy"); break; } - try { - mVold.mount(vol.id, vol.mountFlags, vol.mountUserId); - } catch (Exception e) { - Slog.wtf(TAG, e); - } + mount(vol); break; } case H_VOLUME_UNMOUNT: { final VolumeInfo vol = (VolumeInfo) msg.obj; - unmount(vol.getId()); + unmount(vol); break; } case H_VOLUME_BROADCAST: { @@ -869,6 +871,8 @@ class StorageManagerService extends IStorageManager.Stub addInternalVolumeLocked(); } + mVisibleVols.clear(); + try { mVold.reset(); @@ -1466,7 +1470,7 @@ class StorageManagerService extends IStorageManager.Stub = mContext.getPackageManager().getInstalledApplicationsAsUser( PackageManager.MATCH_UNINSTALLED_PACKAGES, userId); synchronized (mPackagesLock) { - final ArraySet<String> userPackages = getPackagesForUserPL(userId); + final ArraySet<String> userPackages = getAvailablePackagesForUserPL(userId); for (int i = appInfos.size() - 1; i >= 0; --i) { if (appInfos.get(i).isInstantApp()) { continue; @@ -1523,7 +1527,7 @@ class StorageManagerService extends IStorageManager.Stub } @GuardedBy("mPackagesLock") - private ArraySet<String> getPackagesForUserPL(int userId) { + private ArraySet<String> getAvailablePackagesForUserPL(int userId) { ArraySet<String> userPackages = mPackages.get(userId); if (userPackages == null) { userPackages = new ArraySet<>(); @@ -1535,8 +1539,24 @@ class StorageManagerService extends IStorageManager.Stub private String[] getPackagesArrayForUser(int userId) { if (!ENABLE_ISOLATED_STORAGE) return EmptyArray.STRING; + final ArraySet<String> userPackages; synchronized (mPackagesLock) { - return getPackagesForUserPL(userId).toArray(new String[0]); + userPackages = getAvailablePackagesForUserPL(userId); + if (!userPackages.isEmpty()) { + return userPackages.toArray(new String[0]); + } + } + final List<ApplicationInfo> appInfos = + mContext.getPackageManager().getInstalledApplicationsAsUser( + PackageManager.MATCH_UNINSTALLED_PACKAGES, userId); + synchronized (mPackagesLock) { + for (int i = appInfos.size() - 1; i >= 0; --i) { + if (appInfos.get(i).isInstantApp()) { + continue; + } + userPackages.add(appInfos.get(i).packageName); + } + return userPackages.toArray(new String[0]); } } @@ -1747,8 +1767,15 @@ class StorageManagerService extends IStorageManager.Stub if (isMountDisallowed(vol)) { throw new SecurityException("Mounting " + volId + " restricted by policy"); } + mount(vol); + } + + private void mount(VolumeInfo vol) { try { mVold.mount(vol.id, vol.mountFlags, vol.mountUserId); + if ((vol.mountFlags & VolumeInfo.MOUNT_FLAG_VISIBLE) != 0) { + mVisibleVols.add(vol); + } } catch (Exception e) { Slog.wtf(TAG, e); } @@ -1759,8 +1786,15 @@ class StorageManagerService extends IStorageManager.Stub enforcePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS); final VolumeInfo vol = findVolumeByIdOrThrow(volId); + unmount(vol); + } + + private void unmount(VolumeInfo vol) { try { mVold.unmount(vol.id); + if ((vol.mountFlags & VolumeInfo.MOUNT_FLAG_VISIBLE) != 0) { + mVisibleVols.remove(vol); + } } catch (Exception e) { Slog.wtf(TAG, e); } @@ -3596,6 +3630,14 @@ class StorageManagerService extends IStorageManager.Stub pw.decreaseIndent(); pw.println(); + pw.println("mVisibleVols:"); + pw.increaseIndent(); + for (int i = 0; i < mVisibleVols.size(); i++) { + mVisibleVols.get(i).dump(pw); + } + pw.decreaseIndent(); + + pw.println(); pw.println("Primary storage UUID: " + mPrimaryStorageUuid); final Pair<String, Long> pair = StorageManager.getPrimaryStoragePathAndSize(); if (pair == null) { @@ -3716,7 +3758,7 @@ class StorageManagerService extends IStorageManager.Stub int userId) { final String sandboxId; synchronized (mPackagesLock) { - final ArraySet<String> userPackages = getPackagesForUserPL(userId); + final ArraySet<String> userPackages = getAvailablePackagesForUserPL(userId); // If userPackages is empty, it means the user is not started yet, so no need to // do anything now. if (userPackages.isEmpty() || userPackages.contains(packageName)) { @@ -3734,5 +3776,29 @@ class StorageManagerService extends IStorageManager.Stub Slog.wtf(TAG, e); } } + + @Override + public String[] getVisibleVolumesForUser(int userId) { + final ArrayList<String> visibleVolsForUser = new ArrayList<>(); + for (int i = mVisibleVols.size() - 1; i >= 0; --i) { + final VolumeInfo vol = mVisibleVols.get(i); + if (vol.isVisibleForUser(userId)) { + visibleVolsForUser.add(getVolumeLabel(vol)); + } + } + return visibleVolsForUser.toArray(new String[visibleVolsForUser.size()]); + } + + private String getVolumeLabel(VolumeInfo vol) { + // STOPSHIP: Label needs to part of VolumeInfo and need to be passed on from vold + switch (vol.getType()) { + case VolumeInfo.TYPE_EMULATED: + return "emulated"; + case VolumeInfo.TYPE_PUBLIC: + return vol.fsUuid == null ? vol.id : vol.fsUuid; + default: + return null; + } + } } } diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java index 98b88cb557af..fb8894b48411 100644 --- a/services/core/java/com/android/server/TelephonyRegistry.java +++ b/services/core/java/com/android/server/TelephonyRegistry.java @@ -213,6 +213,8 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { private PhoneCapability mPhoneCapability = null; + private int mPreferredDataSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; + private final LocalLog mLocalLog = new LocalLog(100); private PreciseDataConnectionState mPreciseDataConnectionState = @@ -752,6 +754,13 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { remove(r.binder); } } + if ((events & PhoneStateListener.LISTEN_PREFERRED_DATA_SUBID_CHANGE) != 0) { + try { + r.callback.onPreferredDataSubIdChanged(mPreferredDataSubId); + } catch (RemoteException ex) { + remove(r.binder); + } + } } } } else { @@ -1573,6 +1582,31 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { } } + public void notifyPreferredDataSubIdChanged(int preferredSubId) { + if (!checkNotifyPermission("notifyPreferredDataSubIdChanged()")) { + return; + } + + if (VDBG) { + log("notifyPreferredDataSubIdChanged: preferredSubId=" + preferredSubId); + } + + synchronized (mRecords) { + mPreferredDataSubId = preferredSubId; + + for (Record r : mRecords) { + if (r.matchPhoneStateListenerEvent( + PhoneStateListener.LISTEN_PREFERRED_DATA_SUBID_CHANGE)) { + try { + r.callback.onPreferredDataSubIdChanged(preferredSubId); + } catch (RemoteException ex) { + mRemoveList.add(r.binder); + } + } + } + handleRemoveListLocked(); + } + } @Override public void dump(FileDescriptor fd, PrintWriter writer, String[] args) { @@ -1610,6 +1644,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { pw.println("mBackgroundCallState=" + mBackgroundCallState); pw.println("mVoLteServiceState=" + mVoLteServiceState); pw.println("mPhoneCapability=" + mPhoneCapability); + pw.println("mPreferredDataSubId=" + mPreferredDataSubId); pw.decreaseIndent(); @@ -1647,6 +1682,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { intent.putExtras(data); // Pass the subscription along with the intent. intent.putExtra(PhoneConstants.SUBSCRIPTION_KEY, subId); + intent.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, subId); intent.putExtra(PhoneConstants.SLOT_KEY, phoneId); mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL); } @@ -1701,6 +1737,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { if (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) { intent.setAction(PhoneConstants.ACTION_SUBSCRIPTION_PHONE_STATE_CHANGED); intent.putExtra(PhoneConstants.SUBSCRIPTION_KEY, subId); + intent.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, subId); } // If the phoneId is invalid, the broadcast is for overall call state. if (phoneId != SubscriptionManager.INVALID_PHONE_INDEX) { diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java index 6d69fcd3e453..0b836f0d186f 100644 --- a/services/core/java/com/android/server/Watchdog.java +++ b/services/core/java/com/android/server/Watchdog.java @@ -94,7 +94,7 @@ public class Watchdog extends Thread { "media.metrics", // system/bin/mediametrics "media.codec", // vendor/bin/hw/android.hardware.media.omx@1.0-service "com.android.bluetooth", // Bluetooth service - "statsd", // Stats daemon + "/system/bin/statsd", // Stats daemon }; public static final List<String> HAL_INTERFACES_OF_INTEREST = Arrays.asList( diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index 461d39d0a29a..8e64b5063302 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -1466,9 +1466,9 @@ public final class ActiveServices { + ") when binding service " + service); } - ActivityRecord activity = null; + ActivityServiceConnectionsHolder<ConnectionRecord> activity = null; if (token != null) { - activity = ActivityRecord.isInStackLocked(token); + activity = mAm.mAtmInternal.getServiceConnectionsHolder(token); if (activity == null) { Slog.w(TAG, "Binding with unknown activity: " + token); return 0; @@ -1644,10 +1644,7 @@ public final class ActiveServices { clist.add(c); b.connections.add(c); if (activity != null) { - if (activity.connections == null) { - activity.connections = new HashSet<ConnectionRecord>(); - } - activity.connections.add(c); + activity.addConnection(c); } b.client.connections.add(c); c.startAssociationIfNeeded(); @@ -2861,8 +2858,8 @@ public final class ActiveServices { smap.ensureNotStartingBackgroundLocked(r); } - void removeConnectionLocked( - ConnectionRecord c, ProcessRecord skipApp, ActivityRecord skipAct) { + void removeConnectionLocked(ConnectionRecord c, ProcessRecord skipApp, + ActivityServiceConnectionsHolder skipAct) { IBinder binder = c.conn.asBinder(); AppBindRecord b = c.binding; ServiceRecord s = b.service; @@ -2876,9 +2873,7 @@ public final class ActiveServices { b.connections.remove(c); c.stopAssociation(); if (c.activity != null && c.activity != skipAct) { - if (c.activity.connections != null) { - c.activity.connections.remove(c); - } + c.activity.removeConnection(c); } if (b.client != skipApp) { b.client.connections.remove(c); diff --git a/services/core/java/com/android/server/am/ActivityDisplay.java b/services/core/java/com/android/server/am/ActivityDisplay.java index fab967c01086..fcb717ed51f0 100644 --- a/services/core/java/com/android/server/am/ActivityDisplay.java +++ b/services/core/java/com/android/server/am/ActivityDisplay.java @@ -51,6 +51,7 @@ import android.annotation.Nullable; import android.app.ActivityOptions; import android.app.WindowConfiguration; import android.graphics.Point; +import android.os.UserHandle; import android.util.IntArray; import android.util.Slog; import android.util.proto.ProtoOutputStream; @@ -112,6 +113,13 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> */ private boolean mRemoved; + /** + * A focusable stack that is purposely to be positioned at the top. Although the stack may not + * have the topmost index, it is used as a preferred candidate to prevent being unable to resume + * target stack properly when there are other focusable always-on-top stacks. + */ + private ActivityStack mPreferredTopFocusableStack; + // Cached reference to some special stacks we tend to get a lot so we don't need to loop // through the list to find them. private ActivityStack mHomeStack = null; @@ -164,6 +172,9 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> if (DEBUG_STACK) Slog.v(TAG_STACK, "removeChild: detaching " + stack + " from displayId=" + mDisplayId); mStacks.remove(stack); + if (mPreferredTopFocusableStack == stack) { + mPreferredTopFocusableStack = null; + } removeStackReferenceIfNeeded(stack); releaseSelfIfNeeded(); mSupervisor.mService.updateSleepIfNeededLocked(); @@ -185,9 +196,21 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> private void positionChildAt(ActivityStack stack, int position, boolean includingParents) { // 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 boolean wasContained = mStacks.remove(stack); final int insertPosition = getTopInsertPosition(stack, position); mStacks.add(insertPosition, stack); + + // The insert position may be adjusted to non-top when there is always-on-top stack. Since + // the original position is preferred to be top, the stack should have higher priority when + // we are looking for top focusable stack. The condition {@code wasContained} restricts the + // preferred stack is set only when moving an existing stack to top instead of adding a new + // stack that may be too early (e.g. in the middle of launching or reparenting). + if (wasContained && position >= mStacks.size() - 1 && stack.isFocusableAndVisible()) { + mPreferredTopFocusableStack = stack; + } else if (mPreferredTopFocusableStack == stack) { + mPreferredTopFocusableStack = null; + } + // Since positionChildAt() is called during the creation process of pinned stacks, // ActivityStack#getWindowContainerController() can be null. In this special case, // since DisplayContest#positionStackAt() is called in TaskStack#onConfigurationChanged(), @@ -356,10 +379,18 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> this, stackId, mSupervisor, windowingMode, activityType, onTop); } + /** + * Get the preferred focusable stack in priority. If the preferred stack does not exist, find a + * focusable and visible stack from the top of stacks in this display. + */ ActivityStack getFocusedStack() { + if (mPreferredTopFocusableStack != null) { + return mPreferredTopFocusableStack; + } + for (int i = mStacks.size() - 1; i >= 0; --i) { final ActivityStack stack = mStacks.get(i); - if (stack.isFocusable() && stack.shouldBeVisible(null /* starting */)) { + if (stack.isFocusableAndVisible()) { return stack; } } @@ -381,7 +412,7 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> if (ignoreCurrent && stack == currentFocus) { continue; } - if (!stack.isFocusable() || !stack.shouldBeVisible(null)) { + if (!stack.isFocusableAndVisible()) { continue; } @@ -911,6 +942,13 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> return mDisplayAccessUIDs; } + /** + * @see Display#FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS + */ + boolean supportsSystemDecorations() { + return mDisplay.supportsSystemDecorations(); + } + private boolean shouldDestroyContentOnRemove() { return mDisplay.getRemoveMode() == REMOVE_MODE_DESTROY_CONTENT; } @@ -920,6 +958,10 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> && (mSupervisor.mService.mRunningVoice == null); } + void setFocusedApp(ActivityRecord r, boolean moveFocusNow) { + mWindowContainerController.setFocusedApp(r.appToken, moveFocusNow); + } + /** * @return the stack currently above the {@param stack}. Can be null if the {@param stack} is * already top-most. @@ -981,6 +1023,57 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> positionChildAt(stack, Math.max(0, insertIndex)); } + void moveHomeStackToFront(String reason) { + if (mHomeStack != null) { + mHomeStack.moveToFront(reason); + } + } + + /** Returns true if the focus activity was adjusted to the home stack top activity. */ + boolean moveHomeActivityToTop(String reason) { + final ActivityRecord top = getHomeActivity(); + if (top == null) { + return false; + } + mSupervisor.moveFocusableActivityToTop(top, reason); + return true; + } + + @Nullable + ActivityStack getHomeStack() { + return mHomeStack; + } + + @Nullable + ActivityRecord getHomeActivity() { + return getHomeActivityForUser(mSupervisor.mCurrentUser); + } + + @Nullable + ActivityRecord getHomeActivityForUser(int userId) { + if (mHomeStack == null) { + return null; + } + + final ArrayList<TaskRecord> tasks = mHomeStack.getAllTasks(); + for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) { + final TaskRecord task = tasks.get(taskNdx); + if (!task.isActivityTypeHome()) { + continue; + } + + final ArrayList<ActivityRecord> activities = task.mActivities; + for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) { + final ActivityRecord r = activities.get(activityNdx); + if (r.isActivityTypeHome() + && ((userId == UserHandle.USER_ALL) || (r.userId == userId))) { + return r; + } + } + } + return null; + } + boolean isSleeping() { return mSleeping; } @@ -1042,6 +1135,9 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> if (mSplitScreenPrimaryStack != null) { pw.println(myPrefix + "mSplitScreenPrimaryStack=" + mSplitScreenPrimaryStack); } + if (mPreferredTopFocusableStack != null) { + pw.println(myPrefix + "mPreferredTopFocusableStack=" + mPreferredTopFocusableStack); + } } public void dumpStacks(PrintWriter pw) { diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 2fd699e5a829..9c96968e566f 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -90,6 +90,7 @@ import static android.provider.Settings.Global.DEBUG_APP; import static android.provider.Settings.Global.NETWORK_ACCESS_TIMEOUT_MS; import static android.provider.Settings.Global.WAIT_FOR_DEBUGGER; import static android.text.format.DateUtils.DAY_IN_MILLIS; +import static android.view.Display.DEFAULT_DISPLAY; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ALL; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ANR; @@ -368,7 +369,6 @@ import java.io.InputStreamReader; import java.io.PrintWriter; import java.io.StringWriter; import java.io.UnsupportedEncodingException; -import java.lang.ref.WeakReference; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; @@ -2398,6 +2398,9 @@ public class ActivityManagerService extends IActivityManager.Stub mUserController = new UserController(this); + mPendingIntentController = new PendingIntentController( + mHandlerThread.getLooper(), mUserController); + GL_ES_VERSION = SystemProperties.getInt("ro.opengles.version", ConfigurationInfo.GL_ES_VERSION_UNDEFINED); @@ -2413,9 +2416,6 @@ public class ActivityManagerService extends IActivityManager.Stub mAtmInternal = LocalServices.getService(ActivityTaskManagerInternal.class); mStackSupervisor = mActivityTaskManager.mStackSupervisor; - mPendingIntentController = new PendingIntentController( - mHandlerThread.getLooper(), mUserController); - mProcessCpuThread = new Thread("CpuTracker") { @Override public void run() { @@ -3538,6 +3538,9 @@ public class ActivityManagerService extends IActivityManager.Stub String seInfo, String requiredAbi, String instructionSet, String invokeWith, long startTime) { try { + final String[] packageNames = mContext.getPackageManager().getPackagesForUid(uid); + final String[] visibleVolIds = LocalServices.getService(StorageManagerInternal.class) + .getVisibleVolumesForUser(UserHandle.getUserId(uid)); Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "Start proc: " + app.processName); checkTime(startTime, "startProcess: asking zygote to start proc"); @@ -3547,12 +3550,14 @@ public class ActivityManagerService extends IActivityManager.Stub app.processName, uid, uid, gids, runtimeFlags, mountExternal, app.info.targetSdkVersion, seInfo, requiredAbi, instructionSet, app.info.dataDir, null, app.info.packageName, + packageNames, visibleVolIds, new String[] {PROC_START_SEQ_IDENT + app.startSeq}); } else { startResult = Process.start(entryPoint, app.processName, uid, uid, gids, runtimeFlags, mountExternal, app.info.targetSdkVersion, seInfo, requiredAbi, instructionSet, app.info.dataDir, invokeWith, app.info.packageName, + packageNames, visibleVolIds, new String[] {PROC_START_SEQ_IDENT + app.startSeq}); } checkTime(startTime, "startProcess: returned from zygote!"); @@ -3721,6 +3726,14 @@ public class ActivityManagerService extends IActivityManager.Stub } boolean startHomeActivityLocked(int userId, String reason) { + return startHomeActivityLocked(userId, reason, DEFAULT_DISPLAY); + } + + /** + * This starts home activity on displays that can have system decorations and only if the + * home activity can have multiple instances. + */ + boolean startHomeActivityLocked(int userId, String reason, int displayId) { if (mFactoryTest == FactoryTest.FACTORY_TEST_LOW_LEVEL && mTopAction == null) { // We are running in factory test mode, but unable to find @@ -3744,7 +3757,8 @@ public class ActivityManagerService extends IActivityManager.Stub // For ANR debugging to verify if the user activity is the one that actually // launched. final String myReason = reason + ":" + userId + ":" + resolvedUserId; - mActivityTaskManager.getActivityStartController().startHomeActivity(intent, aInfo, myReason); + mActivityTaskManager.getActivityStartController().startHomeActivity(intent, aInfo, + myReason, displayId); } } else { Slog.wtf(TAG, "No home screen found for " + intent, new Throwable()); @@ -4198,7 +4212,6 @@ public class ActivityManagerService extends IActivityManager.Stub private final void handleAppDiedLocked(ProcessRecord app, boolean restarting, boolean allowRestart) { int pid = app.pid; - final boolean clearLaunchStartTime = !restarting && app.removed && app.foregroundActivities; boolean kept = cleanUpApplicationRecordLocked(app, restarting, allowRestart, -1, false /*replacingPid*/); if (!kept && !restarting) { @@ -4239,18 +4252,6 @@ public class ActivityManagerService extends IActivityManager.Stub mWindowManager.continueSurfaceLayout(); } - // TODO (b/67683350) - // When an app process is removed, activities from the process may be relaunched. In the - // case of forceStopPackageLocked the activities are finished before any window is drawn, - // and the launch time is not cleared. This will be incorrectly used to calculate launch - // time for the next launched activity launched in the same windowing mode. - if (clearLaunchStartTime) { - final LaunchTimeTracker.Entry entry = mStackSupervisor - .getLaunchTimeTracker().getEntry(mStackSupervisor.getWindowingMode()); - if (entry != null) { - entry.mLaunchStartTime = 0; - } - } } private final int getLRURecordIndexForAppLocked(IApplicationThread thread) { @@ -7637,7 +7638,23 @@ public class ActivityManagerService extends IActivityManager.Stub } } - boolean providerRunning = cpr != null && cpr.proc != null && !cpr.proc.killed; + boolean providerRunning = false; + + if (cpr != null && cpr.proc != null) { + providerRunning = !cpr.proc.killed; + + // Note if killedByAm is also set, this means the provider process has just been + // killed by AM (in ProcessRecord.kill()), but appDiedLocked() hasn't been called + // yet. So we need to call appDiedLocked() here and let it clean up. + // (See the commit message on I2c4ba1e87c2d47f2013befff10c49b3dc337a9a7 to see + // how to test this case.) + if (cpr.proc.killed && cpr.proc.killedByAm) { + checkTime(startTime, "getContentProviderImpl: before appDied (killedByAm)"); + appDiedLocked(cpr.proc); + checkTime(startTime, "getContentProviderImpl: after appDied (killedByAm)"); + } + } + if (providerRunning) { cpi = cpr.info; String msg; @@ -8810,7 +8827,7 @@ public class ActivityManagerService extends IActivityManager.Stub ? new ActivityOptions(options) : ActivityOptions.makeBasic(); activityOptions.setLaunchTaskId( - mStackSupervisor.getHomeActivity().getTask().taskId); + mStackSupervisor.getDefaultDisplayHomeActivity().getTask().taskId); mContext.startActivityAsUser(intent, activityOptions.toBundle(), UserHandle.CURRENT); } finally { @@ -10589,9 +10606,13 @@ public class ActivityManagerService extends IActivityManager.Stub currApp.importanceReasonImportance = ActivityManager.RunningAppProcessInfo.procStateToImportance( app.adjSourceProcState); - } else if (app.adjSource instanceof ActivityRecord) { - ActivityRecord r = (ActivityRecord)app.adjSource; - if (r.app != null) currApp.importanceReasonPid = r.app.getPid(); + } else if (app.adjSource instanceof ActivityServiceConnectionsHolder) { + ActivityServiceConnectionsHolder r = + (ActivityServiceConnectionsHolder) app.adjSource; + final int pid = r.getActivityPid(); + if (pid != -1) { + currApp.importanceReasonPid = pid; + } } if (app.adjTarget instanceof ComponentName) { currApp.importanceReasonComponent = (ComponentName)app.adjTarget; @@ -17766,10 +17787,10 @@ public class ActivityManagerService extends IActivityManager.Stub if ((cr.flags&Context.BIND_TREAT_LIKE_ACTIVITY) != 0) { app.treatLikeActivity = true; } - final ActivityRecord a = cr.activity; + final ActivityServiceConnectionsHolder a = cr.activity; if ((cr.flags&Context.BIND_ADJUST_WITH_ACTIVITY) != 0) { - if (a != null && adj > ProcessList.FOREGROUND_APP_ADJ && (a.visible - || a.isState(ActivityState.RESUMED, ActivityState.PAUSING))) { + if (a != null && adj > ProcessList.FOREGROUND_APP_ADJ + && a.isActivityVisible()) { adj = ProcessList.FOREGROUND_APP_ADJ; if ((cr.flags&Context.BIND_NOT_FOREGROUND) == 0) { if ((cr.flags&Context.BIND_IMPORTANT) != 0) { @@ -20884,6 +20905,16 @@ public class ActivityManagerService extends IActivityManager.Stub return res; } } + + @Override + public void disconnectActivityFromServices(Object connectionHolder) { + synchronized(ActivityManagerService.this) { + final ActivityServiceConnectionsHolder c = + (ActivityServiceConnectionsHolder) connectionHolder; + c.forEachConnection(cr -> mServices.removeConnectionLocked( + (ConnectionRecord) cr, null, c)); + } + } } /** diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java index 4bcaf7145e60..40c555f8c2e6 100644 --- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java +++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java @@ -568,9 +568,6 @@ final class ActivityManagerShellCommand extends ShellCommand { if (result.who != null) { pw.println("Activity: " + result.who.flattenToShortString()); } - if (result.thisTime >= 0) { - pw.println("ThisTime: " + result.thisTime); - } if (result.totalTime >= 0) { pw.println("TotalTime: " + result.totalTime); } diff --git a/services/core/java/com/android/server/am/ActivityMetricsLogger.java b/services/core/java/com/android/server/am/ActivityMetricsLogger.java index 78b42f2068ee..18cdb054e648 100644 --- a/services/core/java/com/android/server/am/ActivityMetricsLogger.java +++ b/services/core/java/com/android/server/am/ActivityMetricsLogger.java @@ -75,6 +75,7 @@ import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_T import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_METRICS; import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; +import static com.android.server.am.EventLogTags.AM_ACTIVITY_LAUNCH_TIME; import static com.android.server.am.MemoryStatUtil.MemoryStat; import static com.android.server.am.MemoryStatUtil.readMemoryStatFromFilesystem; import static com.android.server.wm.ActivityTaskManagerInternal.APP_TRANSITION_TIMEOUT; @@ -89,10 +90,14 @@ import android.os.Handler; import android.os.Looper; import android.os.Message; import android.os.SystemClock; +import android.os.Trace; +import android.util.EventLog; +import android.util.Log; import android.util.Slog; import android.util.SparseArray; import android.util.SparseIntArray; import android.util.StatsLog; +import android.util.TimeUtils; import com.android.internal.logging.MetricsLogger; import com.android.internal.os.BackgroundThread; @@ -100,7 +105,12 @@ import com.android.internal.os.SomeArgs; import com.android.server.LocalServices; /** - * Handles logging into Tron. + * Listens to activity launches, transitions, visibility changes and window drawn callbacks to + * determine app launch times and draw delays. Source of truth for activity metrics and provides + * data for Tron, logcat, event logs and {@link android.app.WaitResult}. + * + * Tests: + * atest SystemMetricsFunctionalTests */ class ActivityMetricsLogger { @@ -115,6 +125,8 @@ class ActivityMetricsLogger { private static final int WINDOW_STATE_INVALID = -1; private static final long INVALID_START_TIME = -1; + private static final int INVALID_DELAY = -1; + private static final int INVALID_TRANSITION_TYPE = -1; private static final int MSG_CHECK_VISIBILITY = 0; @@ -143,6 +155,8 @@ class ActivityMetricsLogger { private final H mHandler; private ArtManagerInternal mArtManagerInternal; + private boolean mDrawingTraceActive; + private final StringBuilder mStringBuilder = new StringBuilder(); private final class H extends Handler { @@ -165,36 +179,56 @@ class ActivityMetricsLogger { private ActivityRecord launchedActivity; private int startResult; private boolean currentTransitionProcessRunning; + /** Elapsed time from when we launch an activity to when its windows are drawn. */ private int windowsDrawnDelayMs; - private int startingWindowDelayMs = -1; - private int bindApplicationDelayMs = -1; + private int startingWindowDelayMs = INVALID_DELAY; + private int bindApplicationDelayMs = INVALID_DELAY; private int reason = APP_TRANSITION_TIMEOUT; private boolean loggedWindowsDrawn; private boolean loggedStartingWindowDrawn; + private boolean launchTraceActive; } - private final class WindowingModeTransitionInfoSnapshot { + final class WindowingModeTransitionInfoSnapshot { final private ApplicationInfo applicationInfo; final private WindowProcessController processRecord; - final private String packageName; - final private String launchedActivityName; + final String packageName; + final String launchedActivityName; final private String launchedActivityLaunchedFromPackage; final private String launchedActivityLaunchToken; final private String launchedActivityAppRecordRequiredAbi; + final String launchedActivityShortComponentName; final private String processName; final private int reason; final private int startingWindowDelayMs; final private int bindApplicationDelayMs; - final private int windowsDrawnDelayMs; - final private int type; + final int windowsDrawnDelayMs; + final int type; + final int userId; + /** + * Elapsed time from when we launch an activity to when the app reported it was + * fully drawn. If this is not reported then the value is set to INVALID_DELAY. + */ + final int windowsFullyDrawnDelayMs; + final int activityRecordIdHashCode; private WindowingModeTransitionInfoSnapshot(WindowingModeTransitionInfo info) { - applicationInfo = info.launchedActivity.appInfo; - packageName = info.launchedActivity.packageName; - launchedActivityName = info.launchedActivity.info.name; - launchedActivityLaunchedFromPackage = info.launchedActivity.launchedFromPackage; - launchedActivityLaunchToken = info.launchedActivity.info.launchToken; - launchedActivityAppRecordRequiredAbi = info.launchedActivity.app == null + this(info, info.launchedActivity); + } + + private WindowingModeTransitionInfoSnapshot(WindowingModeTransitionInfo info, + ActivityRecord launchedActivity) { + this(info, launchedActivity, INVALID_DELAY); + } + + private WindowingModeTransitionInfoSnapshot(WindowingModeTransitionInfo info, + ActivityRecord launchedActivity, int windowsFullyDrawnDelayMs) { + applicationInfo = launchedActivity.appInfo; + packageName = launchedActivity.packageName; + launchedActivityName = launchedActivity.info.name; + launchedActivityLaunchedFromPackage = launchedActivity.launchedFromPackage; + launchedActivityLaunchToken = launchedActivity.info.launchToken; + launchedActivityAppRecordRequiredAbi = launchedActivity.app == null ? null : info.launchedActivity.app.getRequiredAbi(); reason = info.reason; @@ -204,6 +238,10 @@ class ActivityMetricsLogger { type = getTransitionType(info); processRecord = findProcessForActivity(info.launchedActivity); processName = info.launchedActivity.processName; + userId = launchedActivity.userId; + launchedActivityShortComponentName = launchedActivity.shortComponentName; + activityRecordIdHashCode = System.identityHashCode(launchedActivity); + this.windowsFullyDrawnDelayMs = windowsFullyDrawnDelayMs; } } @@ -335,7 +373,7 @@ class ActivityMetricsLogger { || windowingMode == WINDOWING_MODE_UNDEFINED) && !otherWindowModesLaunching) { // Failed to launch or it was not a process switch, so we don't care about the timing. - reset(true /* abort */); + reset(true /* abort */, info); return; } else if (otherWindowModesLaunching) { // Don't log this windowing mode but continue with the other windowing modes. @@ -351,6 +389,7 @@ class ActivityMetricsLogger { mWindowingModeTransitionInfo.put(windowingMode, newInfo); mLastWindowingModeTransitionInfo.put(windowingMode, newInfo); mCurrentTransitionDeviceUptime = (int) (SystemClock.uptimeMillis() / 1000); + startTraces(newInfo); } /** @@ -364,18 +403,21 @@ class ActivityMetricsLogger { /** * Notifies the tracker that all windows of the app have been drawn. */ - void notifyWindowsDrawn(int windowingMode, long timestamp) { + WindowingModeTransitionInfoSnapshot notifyWindowsDrawn(int windowingMode, long timestamp) { if (DEBUG_METRICS) Slog.i(TAG, "notifyWindowsDrawn windowingMode=" + windowingMode); final WindowingModeTransitionInfo info = mWindowingModeTransitionInfo.get(windowingMode); if (info == null || info.loggedWindowsDrawn) { - return; + return null; } info.windowsDrawnDelayMs = calculateDelay(timestamp); info.loggedWindowsDrawn = true; + final WindowingModeTransitionInfoSnapshot infoSnapshot = + new WindowingModeTransitionInfoSnapshot(info); if (allWindowsDrawn() && mLoggedTransitionStarting) { - reset(false /* abort */); + reset(false /* abort */, info); } + return infoSnapshot; } /** @@ -394,7 +436,7 @@ class ActivityMetricsLogger { * Notifies the tracker that the app transition is starting. * * @param windowingModeToReason A map from windowing mode to a reason integer, which must be on - * of ActivityManagerInternal.APP_TRANSITION_* reasons. + * of ActivityTaskManagerInternal.APP_TRANSITION_* reasons. */ void notifyTransitionStarting(SparseIntArray windowingModeToReason, long timestamp) { if (!isAnyTransitionActive() || mLoggedTransitionStarting) { @@ -413,7 +455,7 @@ class ActivityMetricsLogger { info.reason = windowingModeToReason.valueAt(index); } if (allWindowsDrawn()) { - reset(false /* abort */); + reset(false /* abort */, null /* WindowingModeTransitionInfo */); } } @@ -452,8 +494,9 @@ class ActivityMetricsLogger { logAppTransitionCancel(info); mWindowingModeTransitionInfo.remove(r.getWindowingMode()); if (mWindowingModeTransitionInfo.size() == 0) { - reset(true /* abort */); + reset(true /* abort */, info); } + stopFullyDrawnTraceIfNeeded(); } } } @@ -488,19 +531,19 @@ class ActivityMetricsLogger { && mWindowingModeTransitionInfo.size() > 0; } - private void reset(boolean abort) { + private void reset(boolean abort, WindowingModeTransitionInfo info) { if (DEBUG_METRICS) Slog.i(TAG, "reset abort=" + abort); if (!abort && isAnyTransitionActive()) { logAppTransitionMultiEvents(); } + stopLaunchTrace(info); mCurrentTransitionStartTime = INVALID_START_TIME; - mCurrentTransitionDelayMs = -1; + mCurrentTransitionDelayMs = INVALID_DELAY; mLoggedTransitionStarting = false; mWindowingModeTransitionInfo.clear(); } private int calculateCurrentDelay() { - // Shouldn't take more than 25 days to launch an app, so int is fine here. return (int) (SystemClock.uptimeMillis() - mCurrentTransitionStartTime); } @@ -512,7 +555,7 @@ class ActivityMetricsLogger { private void logAppTransitionCancel(WindowingModeTransitionInfo info) { final int type = getTransitionType(info); - if (type == -1) { + if (type == INVALID_TRANSITION_TYPE) { return; } final LogMaker builder = new LogMaker(APP_TRANSITION_CANCELLED); @@ -533,7 +576,7 @@ class ActivityMetricsLogger { for (int index = mWindowingModeTransitionInfo.size() - 1; index >= 0; index--) { final WindowingModeTransitionInfo info = mWindowingModeTransitionInfo.valueAt(index); final int type = getTransitionType(info); - if (type == -1) { + if (type == INVALID_TRANSITION_TYPE) { return; } @@ -545,6 +588,7 @@ class ActivityMetricsLogger { final int currentTransitionDelayMs = mCurrentTransitionDelayMs; BackgroundThread.getHandler().post(() -> logAppTransition( currentTransitionDeviceUptime, currentTransitionDelayMs, infoSnapshot)); + BackgroundThread.getHandler().post(() -> logAppDisplayed(infoSnapshot)); info.launchedActivity.info.launchToken = null; } @@ -571,11 +615,11 @@ class ActivityMetricsLogger { currentTransitionDeviceUptime); builder.addTaggedData(APP_TRANSITION_DELAY_MS, currentTransitionDelayMs); builder.setSubtype(info.reason); - if (info.startingWindowDelayMs != -1) { + if (info.startingWindowDelayMs != INVALID_DELAY) { builder.addTaggedData(APP_TRANSITION_STARTING_WINDOW_DELAY_MS, info.startingWindowDelayMs); } - if (info.bindApplicationDelayMs != -1) { + if (info.bindApplicationDelayMs != INVALID_DELAY) { builder.addTaggedData(APP_TRANSITION_BIND_APPLICATION_DELAY_MS, info.bindApplicationDelayMs); } @@ -612,6 +656,24 @@ class ActivityMetricsLogger { logAppStartMemoryStateCapture(info); } + private void logAppDisplayed(WindowingModeTransitionInfoSnapshot info) { + if (info.type != TYPE_TRANSITION_WARM_LAUNCH && info.type != TYPE_TRANSITION_COLD_LAUNCH) { + return; + } + + EventLog.writeEvent(AM_ACTIVITY_LAUNCH_TIME, + info.userId, info.activityRecordIdHashCode, info.launchedActivityShortComponentName, + info.windowsDrawnDelayMs); + + StringBuilder sb = mStringBuilder; + sb.setLength(0); + sb.append("Displayed "); + sb.append(info.launchedActivityShortComponentName); + sb.append(": "); + TimeUtils.formatDuration(info.windowsDrawnDelayMs, sb); + Log.i(TAG, sb.toString()); + } + private int convertAppStartTransitionType(int tronType) { if (tronType == TYPE_TRANSITION_COLD_LAUNCH) { return StatsLog.APP_START_OCCURRED__TYPE__COLD; @@ -625,11 +687,12 @@ class ActivityMetricsLogger { return StatsLog.APP_START_OCCURRED__TYPE__UNKNOWN; } - void logAppTransitionReportedDrawn(ActivityRecord r, boolean restoredFromBundle) { + WindowingModeTransitionInfoSnapshot logAppTransitionReportedDrawn(ActivityRecord r, + boolean restoredFromBundle) { final WindowingModeTransitionInfo info = mLastWindowingModeTransitionInfo.get( r.getWindowingMode()); if (info == null) { - return; + return null; } final LogMaker builder = new LogMaker(APP_TRANSITION_REPORTED_DRAWN); builder.setPackageName(r.packageName); @@ -652,6 +715,25 @@ class ActivityMetricsLogger { info.launchedActivity.info.name, info.currentTransitionProcessRunning, startupTimeMs); + stopFullyDrawnTraceIfNeeded(); + final WindowingModeTransitionInfoSnapshot infoSnapshot = + new WindowingModeTransitionInfoSnapshot(info, r, (int) startupTimeMs); + BackgroundThread.getHandler().post(() -> logAppFullyDrawn(infoSnapshot)); + return infoSnapshot; + } + + private void logAppFullyDrawn(WindowingModeTransitionInfoSnapshot info) { + if (info.type != TYPE_TRANSITION_WARM_LAUNCH && info.type != TYPE_TRANSITION_COLD_LAUNCH) { + return; + } + + StringBuilder sb = mStringBuilder; + sb.setLength(0); + sb.append("Fully drawn "); + sb.append(info.launchedActivityShortComponentName); + sb.append(": "); + TimeUtils.formatDuration(info.windowsFullyDrawnDelayMs, sb); + Log.i(TAG, sb.toString()); } void logActivityStart(Intent intent, ProcessRecord callerApp, ActivityRecord r, @@ -753,7 +835,7 @@ class ActivityMetricsLogger { } else if (info.startResult == START_SUCCESS) { return TYPE_TRANSITION_COLD_LAUNCH; } - return -1; + return INVALID_TRANSITION_TYPE; } private void logAppStartMemoryStateCapture(WindowingModeTransitionInfoSnapshot info) { @@ -798,4 +880,46 @@ class ActivityMetricsLogger { } return mArtManagerInternal; } + + /** + * Starts traces for app launch and draw times. We stop the fully drawn trace if its already + * active since the app may not have reported fully drawn in the previous launch. + * + * See {@link android.app.Activity#reportFullyDrawn()} + * + * @param info + * */ + private void startTraces(WindowingModeTransitionInfo info) { + if (info == null) { + return; + } + stopFullyDrawnTraceIfNeeded(); + int transitionType = getTransitionType(info); + if (!info.launchTraceActive && transitionType == TYPE_TRANSITION_WARM_LAUNCH + || transitionType == TYPE_TRANSITION_COLD_LAUNCH) { + Trace.asyncTraceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "launching: " + + info.launchedActivity.packageName, 0); + Trace.asyncTraceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "drawing", 0); + mDrawingTraceActive = true; + info.launchTraceActive = true; + } + } + + private void stopLaunchTrace(WindowingModeTransitionInfo info) { + if (info == null) { + return; + } + if (info.launchTraceActive) { + Trace.asyncTraceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER, "launching: " + + info.launchedActivity.packageName, 0); + info.launchTraceActive = false; + } + } + + void stopFullyDrawnTraceIfNeeded() { + if (mDrawingTraceActive) { + Trace.asyncTraceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER, "drawing", 0); + mDrawingTraceActive = false; + } + } } diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java index 77cfb124ec80..fe10baf33354 100644 --- a/services/core/java/com/android/server/am/ActivityRecord.java +++ b/services/core/java/com/android/server/am/ActivityRecord.java @@ -31,6 +31,7 @@ import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_UP; import static android.app.ActivityTaskManager.INVALID_STACK_ID; import static android.app.AppOpsManager.MODE_ALLOWED; import static android.app.AppOpsManager.OP_PICTURE_IN_PICTURE; +import static android.app.WaitResult.INVALID_DELAY; import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT; import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; @@ -80,7 +81,6 @@ import static android.content.res.Configuration.UI_MODE_TYPE_VR_HEADSET; import static android.os.Build.VERSION_CODES.HONEYCOMB; import static android.os.Build.VERSION_CODES.O; import static android.os.Process.SYSTEM_UID; -import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER; import static android.view.WindowManagerPolicyConstants.NAV_BAR_LEFT; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_CONFIGURATION; @@ -112,8 +112,6 @@ import static com.android.server.am.ActivityStack.LAUNCH_TICK; import static com.android.server.am.ActivityStack.LAUNCH_TICK_MSG; import static com.android.server.am.ActivityStack.PAUSE_TIMEOUT_MSG; import static com.android.server.am.ActivityStack.STOP_TIMEOUT_MSG; -import static com.android.server.am.EventLogTags.AM_ACTIVITY_FULLY_DRAWN_TIME; -import static com.android.server.am.EventLogTags.AM_ACTIVITY_LAUNCH_TIME; import static com.android.server.am.EventLogTags.AM_RELAUNCH_ACTIVITY; import static com.android.server.am.EventLogTags.AM_RELAUNCH_RESUME_ACTIVITY; import static com.android.server.am.TaskPersister.DEBUG; @@ -164,7 +162,6 @@ import android.os.PersistableBundle; import android.os.Process; import android.os.RemoteException; import android.os.SystemClock; -import android.os.Trace; import android.os.UserHandle; import android.os.storage.StorageManager; import android.service.voice.IVoiceInteractionSession; @@ -186,6 +183,7 @@ import com.android.internal.content.ReferrerIntent; import com.android.internal.util.XmlUtils; import com.android.server.AttributeCache; import com.android.server.AttributeCache.Entry; +import com.android.server.am.ActivityMetricsLogger.WindowingModeTransitionInfoSnapshot; import com.android.server.am.ActivityStack.ActivityState; import com.android.server.uri.UriPermissionOwner; import com.android.server.wm.AppWindowContainerController; @@ -266,9 +264,6 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo private int windowFlags; // custom window flags for preview window. private TaskRecord task; // the task this is in. private long createTime = System.currentTimeMillis(); - long displayStartTime; // when we started launching this activity - long fullyDrawnStartTime; // when we started launching this activity - private long startTime; // last time this activity was started long lastVisibleTime; // last time this activity became visible long cpuTimeAtResume; // the cpu time of host process at the time of resuming activity long pauseTime; // last time we started pausing the activity @@ -288,7 +283,7 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo ActivityOptions pendingOptions; // most recently given options ActivityOptions returningOptions; // options that are coming back via convertToTranslucent AppTimeTracker appTimeTracker; // set if we are tracking the time in this app/task/activity - HashSet<ConnectionRecord> connections; // All ConnectionRecord we hold + ActivityServiceConnectionsHolder mServiceConnectionsHolder; // Service connections. UriPermissionOwner uriPermissions; // current special URI access perms. WindowProcessController app; // if non-null, hosting application private ActivityState mState; // current state we are in @@ -536,15 +531,6 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo pw.print("requestedVrComponent="); pw.println(requestedVrComponent); } - if (displayStartTime != 0 || startTime != 0) { - pw.print(prefix); pw.print("displayStartTime="); - if (displayStartTime == 0) pw.print("0"); - else TimeUtils.formatDuration(displayStartTime, now, pw); - pw.print(" startTime="); - if (startTime == 0) pw.print("0"); - else TimeUtils.formatDuration(startTime, now, pw); - pw.println(); - } final boolean waitingVisible = mStackSupervisor.mActivitiesWaitingForVisibleActivity.contains(this); if (lastVisibleTime != 0 || waitingVisible || nowVisible) { @@ -563,8 +549,8 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo pw.print(" configChangeFlags="); pw.println(Integer.toHexString(configChangeFlags)); } - if (connections != null) { - pw.print(prefix); pw.print("connections="); pw.println(connections); + if (mServiceConnectionsHolder != null) { + pw.print(prefix); pw.print("connections="); pw.println(mServiceConnectionsHolder); } if (info != null) { pw.println(prefix + "resizeMode=" + ActivityInfo.resizeModeToString(info.resizeMode)); @@ -2006,79 +1992,13 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo } public void reportFullyDrawnLocked(boolean restoredFromBundle) { - final long curTime = SystemClock.uptimeMillis(); - if (displayStartTime != 0) { - reportLaunchTimeLocked(curTime); - } - final LaunchTimeTracker.Entry entry = mStackSupervisor.getLaunchTimeTracker().getEntry( - getWindowingMode()); - if (fullyDrawnStartTime != 0 && entry != null) { - final long thisTime = curTime - fullyDrawnStartTime; - final long totalTime = entry.mFullyDrawnStartTime != 0 - ? (curTime - entry.mFullyDrawnStartTime) : thisTime; - if (SHOW_ACTIVITY_START_TIME) { - Trace.asyncTraceEnd(TRACE_TAG_ACTIVITY_MANAGER, "drawing", 0); - EventLog.writeEvent(AM_ACTIVITY_FULLY_DRAWN_TIME, - userId, System.identityHashCode(this), shortComponentName, - thisTime, totalTime); - StringBuilder sb = service.mStringBuilder; - sb.setLength(0); - sb.append("Fully drawn "); - sb.append(shortComponentName); - sb.append(": "); - TimeUtils.formatDuration(thisTime, sb); - if (thisTime != totalTime) { - sb.append(" (total "); - TimeUtils.formatDuration(totalTime, sb); - sb.append(")"); - } - Log.i(TAG, sb.toString()); - } - if (totalTime > 0) { - //service.mUsageStatsService.noteFullyDrawnTime(realActivity, (int) totalTime); - } - entry.mFullyDrawnStartTime = 0; - } - mStackSupervisor.getActivityMetricsLogger().logAppTransitionReportedDrawn(this, - restoredFromBundle); - fullyDrawnStartTime = 0; - } - - private void reportLaunchTimeLocked(final long curTime) { - final LaunchTimeTracker.Entry entry = mStackSupervisor.getLaunchTimeTracker().getEntry( - getWindowingMode()); - if (entry == null) { - return; - } - final long thisTime = curTime - displayStartTime; - final long totalTime = entry.mLaunchStartTime != 0 - ? (curTime - entry.mLaunchStartTime) : thisTime; - if (SHOW_ACTIVITY_START_TIME) { - Trace.asyncTraceEnd(TRACE_TAG_ACTIVITY_MANAGER, "launching: " + packageName, 0); - EventLog.writeEvent(AM_ACTIVITY_LAUNCH_TIME, - userId, System.identityHashCode(this), shortComponentName, - thisTime, totalTime); - StringBuilder sb = service.mStringBuilder; - sb.setLength(0); - sb.append("Displayed "); - sb.append(shortComponentName); - sb.append(": "); - TimeUtils.formatDuration(thisTime, sb); - if (thisTime != totalTime) { - sb.append(" (total "); - TimeUtils.formatDuration(totalTime, sb); - sb.append(")"); - } - Log.i(TAG, sb.toString()); - } - mStackSupervisor.reportActivityLaunchedLocked(false, this, thisTime, totalTime); - if (totalTime > 0) { - //service.mUsageStatsService.noteLaunchTime(realActivity, (int)totalTime); + final WindowingModeTransitionInfoSnapshot info = mStackSupervisor + .getActivityMetricsLogger().logAppTransitionReportedDrawn(this, restoredFromBundle); + if (info != null) { + mStackSupervisor.reportActivityLaunchedLocked(false /* timeout */, this, + info.windowsFullyDrawnDelayMs); } - displayStartTime = 0; - entry.mLaunchStartTime = 0; } - @Override public void onStartingWindowDrawn(long timestamp) { synchronized (service.mGlobalLock) { @@ -2090,13 +2010,12 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo @Override public void onWindowsDrawn(long timestamp) { synchronized (service.mGlobalLock) { - mStackSupervisor.getActivityMetricsLogger().notifyWindowsDrawn(getWindowingMode(), - timestamp); - if (displayStartTime != 0) { - reportLaunchTimeLocked(timestamp); - } + final WindowingModeTransitionInfoSnapshot info = mStackSupervisor + .getActivityMetricsLogger().notifyWindowsDrawn(getWindowingMode(), timestamp); + final int windowsDrawnDelayMs = info != null ? info.windowsDrawnDelayMs : INVALID_DELAY; + mStackSupervisor.reportActivityLaunchedLocked(false /* timeout */, this, + windowsDrawnDelayMs); mStackSupervisor.sendWaitingVisibleReportLocked(this); - startTime = 0; finishLaunchTickingLocked(); if (task != null) { task.hasBeenVisible = true; diff --git a/services/core/java/com/android/server/am/ActivityServiceConnectionsHolder.java b/services/core/java/com/android/server/am/ActivityServiceConnectionsHolder.java new file mode 100644 index 000000000000..b1ced29da4c2 --- /dev/null +++ b/services/core/java/com/android/server/am/ActivityServiceConnectionsHolder.java @@ -0,0 +1,113 @@ +/* + * 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.am; + +import static com.android.server.am.ActivityStack.ActivityState.PAUSING; +import static com.android.server.am.ActivityStack.ActivityState.RESUMED; + +import java.io.PrintWriter; +import java.util.HashSet; +import java.util.Iterator; +import java.util.function.Consumer; + +/** + * Class for tracking the connections to services on the AM side that activities on the + * WM side (in the future) bind with for things like oom score adjustment. Would normally be one + * instance of this per activity for tracking all services connected to that activity. AM will + * sometimes query this to bump the OOM score for the processes with services connected to visible + * activities. + */ +public class ActivityServiceConnectionsHolder<T> { + + private final ActivityTaskManagerService mService; + + /** The activity the owns this service connection object. */ + private final ActivityRecord mActivity; + + /** + * The service connection object bounded with the owning activity. They represent + * ConnectionRecord on the AM side, however we don't need to know their object representation + * on the WM side since we don't perform operations on the object. Mainly here for communication + * and booking with the AM side. + */ + private HashSet<T> mConnections; + + ActivityServiceConnectionsHolder(ActivityTaskManagerService service, ActivityRecord activity) { + mService = service; + mActivity = activity; + } + + /** Adds a connection record that the activity has bound to a specific service. */ + public void addConnection(T c) { + synchronized (mService.mGlobalLock) { + if (mConnections == null) { + mConnections = new HashSet<>(); + } + mConnections.add(c); + } + } + + /** Removed a connection record between the activity and a specific service. */ + public void removeConnection(T c) { + synchronized (mService.mGlobalLock) { + if (mConnections == null) { + return; + } + mConnections.remove(c); + } + } + + public boolean isActivityVisible() { + synchronized (mService.mGlobalLock) { + return mActivity.visible || mActivity.isState(RESUMED, PAUSING); + } + } + + public int getActivityPid() { + synchronized (mService.mGlobalLock) { + return mActivity.hasProcess() ? mActivity.app.getPid() : -1; + } + } + + public void forEachConnection(Consumer<T> consumer) { + synchronized (mService.mGlobalLock) { + if (mConnections == null || mConnections.isEmpty()) { + return; + } + final Iterator<T> it = mConnections.iterator(); + while (it.hasNext()) { + T c = it.next(); + consumer.accept(c); + } + } + } + + /** Removes the connection between the activity and all services that were connected to it. */ + void disconnectActivityFromServices() { + if (mConnections == null || mConnections.isEmpty()) { + return; + } + mService.mH.post(() -> mService.mAmInternal.disconnectActivityFromServices(this)); + } + + public void dump(PrintWriter pw, String prefix) { + synchronized (mService.mGlobalLock) { + pw.println(prefix + "activity=" + mActivity); + } + } + +} diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java index 9f59bd8db62a..ea807adc7d4f 100644 --- a/services/core/java/com/android/server/am/ActivityStack.java +++ b/services/core/java/com/android/server/am/ActivityStack.java @@ -151,7 +151,6 @@ import android.view.Display; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.IVoiceInteractor; -import com.android.internal.os.BatteryStatsImpl; import com.android.internal.util.function.pooled.PooledLambda; import com.android.server.Watchdog; import com.android.server.am.ActivityManagerService.ItemMatcher; @@ -1103,8 +1102,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai if (!isActivityTypeHome() && returnsToHomeStack()) { // Make sure the home stack is behind this stack since that is where we should return to // when this stack is no longer visible. - // TODO(b/111541062): Move home stack on the current display - mStackSupervisor.moveHomeStackToFront(reason + " returnToHome"); + display.moveHomeStackToFront(reason + " returnToHome"); } display.positionChildAtTop(this, true /* includingParents */); @@ -1148,6 +1146,10 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai return mStackSupervisor.isFocusable(this, r != null && r.isFocusable()); } + boolean isFocusableAndVisible() { + return isFocusable() && shouldBeVisible(null /* starting */); + } + final boolean isAttached() { return getParent() != null; } @@ -1319,16 +1321,13 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai + " callers=" + Debug.getCallers(5)); r.setState(RESUMED, "minimalResumeActivityLocked"); r.completeResumeLocked(); - mStackSupervisor.getLaunchTimeTracker().setLaunchTime(r); if (DEBUG_SAVED_STATE) Slog.i(TAG_SAVED_STATE, "Launch completed; removing icicle of " + r.icicle); } private void clearLaunchTime(ActivityRecord r) { // Make sure that there is no activity waiting for this to launch. - if (mStackSupervisor.mWaitingActivityLaunched.isEmpty()) { - r.displayStartTime = r.fullyDrawnStartTime = 0; - } else { + if (!mStackSupervisor.mWaitingActivityLaunched.isEmpty()) { mStackSupervisor.removeTimeoutsForActivityLocked(r); mStackSupervisor.scheduleIdleTimeoutLocked(r); } @@ -1514,7 +1513,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai prev.getTask().touchActiveTime(); clearLaunchTime(prev); - mStackSupervisor.getLaunchTimeTracker().stopFullyDrawnTraceIfNeeded(getWindowingMode()); + mStackSupervisor.getActivityMetricsLogger().stopFullyDrawnTraceIfNeeded(); mService.updateCpuStats(); @@ -2858,9 +2857,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai if (DEBUG_STATES) Slog.d(TAG_STATES, "resumeTopActivityInNextFocusableStack: " + reason + ", go home"); if (DEBUG_STACK) mStackSupervisor.validateTopActivitiesLocked(); - // Only resume home if on home display - return isOnHomeDisplay() && - mStackSupervisor.resumeHomeStackTask(prev, reason); + return mStackSupervisor.resumeHomeActivity(prev, reason, mDisplayId); } /** Returns the position the input task should be placed in this stack. */ @@ -3454,8 +3451,8 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai final String myReason = reason + " adjustFocus"; if (next == r) { - mStackSupervisor.moveFocusableActivityStackToFrontLocked( - mStackSupervisor.topRunningActivityLocked(), myReason); + mStackSupervisor.moveFocusableActivityToTop(mStackSupervisor.topRunningActivityLocked(), + myReason); return; } @@ -3486,7 +3483,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai } // Whatever...go home. - mStackSupervisor.moveHomeStackTaskToTop(myReason); + getDisplay().moveHomeActivityToTop(myReason); } /** @@ -3515,7 +3512,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai if (stack.isActivityTypeHome() && (top == null || !top.visible)) { // If we will be focusing on the home stack next and its current top activity isn't // visible, then use the move the home stack task to top to make the activity visible. - mStackSupervisor.moveHomeStackTaskToTop(reason); + stack.getDisplay().moveHomeActivityToTop(reason); return stack; } @@ -4237,15 +4234,11 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai * Perform clean-up of service connections in an activity record. */ private void cleanUpActivityServicesLocked(ActivityRecord r) { - // Throw away any services that have been bound by this activity. - if (r.connections != null) { - Iterator<ConnectionRecord> it = r.connections.iterator(); - while (it.hasNext()) { - ConnectionRecord c = it.next(); - mService.mAm.mServices.removeConnectionLocked(c, null, r); - } - r.connections = null; + if (r.mServiceConnectionsHolder == null) { + return; } + // Throw away any services that have been bound by this activity. + r.mServiceConnectionsHolder.disconnectActivityFromServices(); } final void scheduleDestroyActivities(WindowProcessController owner, String reason) { @@ -4623,22 +4616,6 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai mStackSupervisor.invalidateTaskLayers(); } - void moveHomeStackTaskToTop() { - if (!isActivityTypeHome()) { - throw new IllegalStateException("Calling moveHomeStackTaskToTop() on non-home stack: " - + this); - } - final int top = mTaskHistory.size() - 1; - if (top >= 0) { - final TaskRecord task = mTaskHistory.get(top); - if (DEBUG_TASKS || DEBUG_STACK) Slog.d(TAG_STACK, - "moveHomeStackTaskToTop: moving " + task); - mTaskHistory.remove(top); - mTaskHistory.add(top, task); - updateTaskMovement(task, true); - } - } - final void moveTaskToFrontLocked(TaskRecord tr, boolean noAnimation, ActivityOptions options, AppTimeTracker timeTracker, String reason) { if (DEBUG_SWITCH) Slog.v(TAG_SWITCH, "moveTaskToFront: " + tr); @@ -4686,7 +4663,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai // Set focus to the top running activity of this stack. final ActivityRecord r = topRunningActivityLocked(); - mStackSupervisor.moveFocusableActivityStackToFrontLocked(r, reason); + mStackSupervisor.moveFocusableActivityToTop(r, reason); if (DEBUG_TRANSITION) Slog.v(TAG_TRANSITION, "Prepare to front transition: task=" + tr); if (noAnimation) { @@ -5227,11 +5204,11 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai if (DEBUG_STACK) Slog.i(TAG_STACK, "removeTask: removing stack=" + this); // We only need to adjust focused stack if this stack is in focus and we are not in the // process of moving the task to the top of the stack that will be focused. - if (isOnHomeDisplay() && mode != REMOVE_TASK_MODE_MOVING_TO_TOP + if (mode != REMOVE_TASK_MODE_MOVING_TO_TOP && mStackSupervisor.isTopDisplayFocusedStack(this)) { String myReason = reason + " leftTaskHistoryEmpty"; if (!inMultiWindowMode() || adjustFocusToNextFocusableStack(myReason) == null) { - mStackSupervisor.moveHomeStackToFront(myReason); + getDisplay().moveHomeStackToFront(myReason); } } if (isAttached()) { @@ -5438,7 +5415,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai // Do not sleep activities in this stack if we're marked as focused and the keyguard // is in the process of going away. - if (mStackSupervisor.getTopDisplayFocusedStack() == this + if (isFocusedStackOnDisplay() && mStackSupervisor.getKeyguardController().isKeyguardGoingAway()) { return false; } diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java index 877c8567b9d0..a968ae4e0201 100644 --- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java @@ -25,6 +25,7 @@ import static android.app.ActivityManager.START_TASK_TO_FRONT; import static android.app.ActivityTaskManager.INVALID_STACK_ID; import static android.app.ITaskStackListener.FORCED_RESIZEABLE_REASON_SECONDARY_DISPLAY; import static android.app.ITaskStackListener.FORCED_RESIZEABLE_REASON_SPLIT_SCREEN; +import static android.app.WaitResult.INVALID_DELAY; import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT; import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; @@ -39,6 +40,8 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECOND import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.app.WindowConfiguration.activityTypeToString; import static android.app.WindowConfiguration.windowingModeToString; +import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_INSTANCE; +import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_TASK; import static android.content.pm.PackageManager.PERMISSION_DENIED; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.graphics.Rect.copyOrNull; @@ -49,7 +52,6 @@ import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.INVALID_DISPLAY; import static android.view.Display.TYPE_VIRTUAL; import static android.view.WindowManager.TRANSIT_DOCK_TASK_FROM_RECENTS; - import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ALL; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_FOCUS; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_IDLE; @@ -96,7 +98,6 @@ import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_WHITELISTED; import static com.android.server.am.TaskRecord.REPARENT_KEEP_STACK_AT_FRONT; import static com.android.server.am.TaskRecord.REPARENT_LEAVE_STACK_IN_PLACE; import static com.android.server.am.TaskRecord.REPARENT_MOVE_STACK_TO_FRONT; - import static java.lang.Integer.MAX_VALUE; import android.Manifest; @@ -131,6 +132,7 @@ import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.pm.UserInfo; import android.content.res.Configuration; +import android.content.res.Resources; import android.graphics.Rect; import android.hardware.display.DisplayManager; import android.hardware.display.DisplayManager.DisplayListener; @@ -155,6 +157,7 @@ import android.provider.MediaStore; import android.service.voice.IVoiceInteractionSession; import android.util.ArrayMap; import android.util.ArraySet; +import android.util.DisplayMetrics; import android.util.EventLog; import android.util.IntArray; import android.util.MergedConfiguration; @@ -334,10 +337,6 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D /** The current user */ int mCurrentUser; - /** The stack containing the launcher app. Assumed to always be attached to - * Display.DEFAULT_DISPLAY. */ - ActivityStack mHomeStack; - /** If this is the same as mFocusedStack then the activity on the top of the focused stack has * been resumed. If stacks are changing position this will hold the old stack until the new * stack becomes resumed after which it will be set to mFocusedStack. */ @@ -444,13 +443,12 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D // The default minimal size that will be used if the activity doesn't specify its minimal size. // It will be calculated when the default display gets added. - int mDefaultMinSizeOfResizeableTask = -1; + int mDefaultMinSizeOfResizeableTaskDp = -1; // Whether tasks have moved and we need to rank the tasks before next OOM scoring private boolean mTaskLayersChanged = true; private ActivityMetricsLogger mActivityMetricsLogger; - private LaunchTimeTracker mLaunchTimeTracker = new LaunchTimeTracker(); private final ArrayList<ActivityRecord> mTmpActivityList = new ArrayList<>(); @@ -646,10 +644,6 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D return mActivityMetricsLogger; } - LaunchTimeTracker getLaunchTimeTracker() { - return mLaunchTimeTracker; - } - public KeyguardController getKeyguardController() { return mKeyguardController; } @@ -693,11 +687,12 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D mDefaultDisplay = activityDisplay; } addChild(activityDisplay, ActivityDisplay.POSITION_TOP); - calculateDefaultMinimalSizeOfResizeableTasks(activityDisplay); } + calculateDefaultMinimalSizeOfResizeableTasks(); final ActivityDisplay defaultDisplay = getDefaultDisplay(); - mHomeStack = mLastFocusedStack = defaultDisplay.getOrCreateStack( + + mLastFocusedStack = defaultDisplay.getOrCreateStack( WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, ON_TOP); positionChildAt(defaultDisplay, ActivityDisplay.POSITION_TOP); } @@ -738,10 +733,6 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D } ActivityRecord getTopResumedActivity() { - if (mWindowManager == null) { - return null; - } - final ActivityStack focusedStack = getTopDisplayFocusedStack(); if (focusedStack == null) { return null; @@ -786,7 +777,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D if (focusCandidate == null) { Slog.w(TAG, "setFocusStackUnchecked: No focusable stack found, focus home as default"); - focusCandidate = mHomeStack; + focusCandidate = getDefaultDisplay().getHomeStack(); } } @@ -807,10 +798,6 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D } } - void moveHomeStackToFront(String reason) { - mHomeStack.moveToFront(reason); - } - void moveRecentsStackToFront(String reason) { final ActivityStack recentsStack = getDefaultDisplay().getStack( WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_RECENTS); @@ -819,34 +806,47 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D } } - /** Returns true if the focus activity was adjusted to the home stack top activity. */ - boolean moveHomeStackTaskToTop(String reason) { - mHomeStack.moveHomeStackTaskToTop(); - - final ActivityRecord top = getHomeActivity(); - if (top == null) { - return false; - } - moveFocusableActivityStackToFrontLocked(top, reason); - return true; - } - - boolean resumeHomeStackTask(ActivityRecord prev, String reason) { + boolean resumeHomeActivity(ActivityRecord prev, String reason, int displayId) { if (!mService.isBooting() && !mService.isBooted()) { // Not ready yet! return false; } - mHomeStack.moveHomeStackTaskToTop(); - ActivityRecord r = getHomeActivity(); - final String myReason = reason + " resumeHomeStackTask"; + if (displayId == INVALID_DISPLAY) { + displayId = DEFAULT_DISPLAY; + } + + final ActivityRecord r = getActivityDisplay(displayId).getHomeActivity(); + final String myReason = reason + " resumeHomeActivity"; // Only resume home activity if isn't finishing. if (r != null && !r.finishing) { - moveFocusableActivityStackToFrontLocked(r, myReason); - return resumeFocusedStacksTopActivitiesLocked(mHomeStack, prev, null); + moveFocusableActivityToTop(r, myReason); + return resumeFocusedStacksTopActivitiesLocked(r.getStack(), prev, null); } - return mService.mAm.startHomeActivityLocked(mCurrentUser, myReason); + return mService.mAm.startHomeActivityLocked(mCurrentUser, myReason, displayId); + } + + boolean canStartHomeOnDisplay(ActivityInfo homeActivity, int displayId) { + if (displayId == DEFAULT_DISPLAY) { + // No restrictions to default display. + return true; + } + + final ActivityDisplay display = getActivityDisplay(displayId); + if (display == null || display.isRemoved() || !display.supportsSystemDecorations()) { + // Can't launch home on display that doesn't support system decorations. + return false; + } + + final boolean supportMultipleInstance = homeActivity.launchMode != LAUNCH_SINGLE_TASK + && homeActivity.launchMode != LAUNCH_SINGLE_INSTANCE; + if (!supportMultipleInstance) { + // Can't launch home on other displays if it requested to be single instance. + return false; + } + + return true; } TaskRecord anyTaskForIdLocked(int id) { @@ -1179,8 +1179,8 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D } } - void waitActivityVisible(ComponentName name, WaitResult result) { - final WaitInfo waitInfo = new WaitInfo(name, result); + void waitActivityVisible(ComponentName name, WaitResult result, long startTimeMs) { + final WaitInfo waitInfo = new WaitInfo(name, result, startTimeMs); mWaitingForActivityVisible.add(waitInfo); } @@ -1211,8 +1211,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D changed = true; result.timeout = false; result.who = w.getComponent(); - result.totalTime = SystemClock.uptimeMillis() - result.thisTime; - result.thisTime = result.totalTime; + result.totalTime = SystemClock.uptimeMillis() - w.getStartTime(); mWaitingForActivityVisible.remove(w); } } @@ -1251,8 +1250,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D } } - void reportActivityLaunchedLocked(boolean timeout, ActivityRecord r, - long thisTime, long totalTime) { + void reportActivityLaunchedLocked(boolean timeout, ActivityRecord r, long totalTime) { boolean changed = false; for (int i = mWaitingActivityLaunched.size() - 1; i >= 0; i--) { WaitResult w = mWaitingActivityLaunched.remove(i); @@ -1262,7 +1260,6 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D if (r != null) { w.who = new ComponentName(r.info.packageName, r.info.name); } - w.thisTime = thisTime; w.totalTime = totalTime; // Do not modify w.result. } @@ -1728,8 +1725,6 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D ProcessRecord app = mService.mAm.getProcessRecordLocked(r.processName, r.info.applicationInfo.uid, true); - getLaunchTimeTracker().setLaunchTime(r); - if (app != null && app.thread != null) { try { if ((r.info.flags&ActivityInfo.FLAG_MULTIPROCESS) == 0 @@ -2082,7 +2077,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D mHandler.removeMessages(IDLE_TIMEOUT_MSG, r); r.finishLaunchTickingLocked(); if (fromTimeout) { - reportActivityLaunchedLocked(fromTimeout, r, -1, -1); + reportActivityLaunchedLocked(fromTimeout, r, INVALID_DELAY); } // This is a hack to semi-deal with a race condition @@ -2215,7 +2210,8 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D */ void updateUserStackLocked(int userId, ActivityStack stack) { if (userId != mCurrentUser) { - mUserStackInFront.put(userId, stack != null ? stack.getStackId() : mHomeStack.mStackId); + mUserStackInFront.put(userId, stack != null ? stack.getStackId() + : getDefaultDisplay().getHomeStack().mStackId); } } @@ -2284,7 +2280,8 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D return false; } - if (targetStack != null && targetStack.isTopStackOnDisplay()) { + if (targetStack != null && (targetStack.isTopStackOnDisplay() + || getTopDisplayFocusedStack() == targetStack)) { return targetStack.resumeTopActivityUncheckedLocked(target, targetOptions); } @@ -2357,7 +2354,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D */ void findTaskToMoveToFront(TaskRecord task, int flags, ActivityOptions options, String reason, boolean forceNonResizeable) { - final ActivityStack currentStack = task.getStack(); + ActivityStack currentStack = task.getStack(); if (currentStack == null) { Slog.e(TAG, "findTaskToMoveToFront: can't move task=" + task + " to front. Stack is null"); @@ -2368,13 +2365,15 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D mUserLeaving = true; } + // TODO(b/111363427): The moving-to-top task may not be on the top display, so it could be + // different from where the prev activity stays on. final ActivityRecord prev = topRunningActivityLocked(); if ((flags & ActivityManager.MOVE_TASK_WITH_HOME) != 0 || (prev != null && prev.isActivityTypeRecents())) { // Caller wants the home activity moved with it or the previous task is recents in which // case we always return home from the task we are moving to the front. - moveHomeStackToFront("findTaskToMoveToFront"); + currentStack.getDisplay().moveHomeStackToFront("findTaskToMoveToFront"); } if (task.isResizeable() && canUseActivityOptionsLaunchBounds(options)) { @@ -2386,7 +2385,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D if (stack != currentStack) { task.reparent(stack, ON_TOP, REPARENT_KEEP_STACK_AT_FRONT, !ANIMATE, DEFER_RESUME, "findTaskToMoveToFront"); - stack = currentStack; + currentStack = stack; // moveTaskToStackUncheckedLocked() should already placed the task on top, // still need moveTaskToFrontLocked() below for any transition settings. } @@ -2653,6 +2652,12 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D if (preferredFocusableStack != null) { return preferredFocusableStack; } + if (preferredDisplay.supportsSystemDecorations()) { + // Stop looking for focusable stack on other displays because the preferred display + // supports system decorations. Home activity would be launched on the same display if + // no focusable stack found. + return null; + } // Now look through all displays for (int i = mActivityDisplays.size() - 1; i >= 0; --i) { @@ -2696,25 +2701,12 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D return null; } - ActivityRecord getHomeActivity() { - return getHomeActivityForUser(mCurrentUser); + ActivityRecord getDefaultDisplayHomeActivity() { + return getDefaultDisplayHomeActivityForUser(mCurrentUser); } - ActivityRecord getHomeActivityForUser(int userId) { - final ArrayList<TaskRecord> tasks = mHomeStack.getAllTasks(); - for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) { - final TaskRecord task = tasks.get(taskNdx); - if (task.isActivityTypeHome()) { - final ArrayList<ActivityRecord> activities = task.mActivities; - for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) { - final ActivityRecord r = activities.get(activityNdx); - if (r.isActivityTypeHome() - && ((userId == UserHandle.USER_ALL) || (r.userId == userId))) { - return r; - } - } - } - } + ActivityRecord getDefaultDisplayHomeActivityForUser(int userId) { + getActivityDisplay(DEFAULT_DISPLAY).getHomeActivityForUser(userId); return null; } @@ -3428,7 +3420,8 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D } /** Move activity with its stack to front and make the stack focused. */ - boolean moveFocusableActivityStackToFrontLocked(ActivityRecord r, String reason) { + // TODO(b/111363427): Move this method to ActivityRecord. + boolean moveFocusableActivityToTop(ActivityRecord r, String reason) { if (r == null || !r.isFocusable()) { if (DEBUG_FOCUS) Slog.d(TAG_FOCUS, "moveActivityStackToFront: unfocusable r=" + r); @@ -3597,7 +3590,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D stack.goToSleepIfPossible(false /* shuttingDown */); } else { stack.awakeFromSleepingLocked(); - if (isTopDisplayFocusedStack(stack) && !getKeyguardController() + if (stack.isFocusedStackOnDisplay() && !getKeyguardController() .isKeyguardOrAodShowing(display.mDisplayId)) { // If the keyguard is unlocked - resume immediately. // It is possible that the display will not be awake at the time we @@ -3837,7 +3830,8 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D removeStacksInWindowingModes(WINDOWING_MODE_PINNED); mUserStackInFront.put(mCurrentUser, focusStackId); - final int restoreStackId = mUserStackInFront.get(userId, mHomeStack.mStackId); + final int restoreStackId = + mUserStackInFront.get(userId, getDefaultDisplay().getHomeStack().mStackId); mCurrentUser = userId; mStartingUsers.add(uss); @@ -3855,14 +3849,14 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D ActivityStack stack = getStack(restoreStackId); if (stack == null) { - stack = mHomeStack; + stack = getDefaultDisplay().getHomeStack(); } final boolean homeInFront = stack.isActivityTypeHome(); if (stack.isOnHomeDisplay()) { stack.moveToFront("switchUserOnHomeDisplay"); } else { // Stack was moved to another display while user was swapped out. - resumeHomeStackTask(null, "switchUserOnOtherDisplay"); + resumeHomeActivity(null, "switchUserOnOtherDisplay", DEFAULT_DISPLAY); } return homeInFront; } @@ -3985,8 +3979,12 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D } public void dump(PrintWriter pw, String prefix) { - pw.print(prefix); pw.print("mFocusedStack=" + getTopDisplayFocusedStack()); - pw.print(" mLastFocusedStack="); pw.println(mLastFocusedStack); + pw.println(); + pw.println("ActivityStackSupervisor state:"); + pw.print(prefix); + pw.println("topDisplayFocusedStack=" + getTopDisplayFocusedStack()); + pw.print(prefix); + pw.println("mLastFocusedStack=" + mLastFocusedStack); pw.print(prefix); pw.println("mCurTaskIdForUser=" + mCurTaskIdForUser); pw.print(prefix); pw.println("mUserStackInFront=" + mUserStackInFront); @@ -4282,6 +4280,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D private void handleDisplayAdded(int displayId) { synchronized (mService.mGlobalLock) { getActivityDisplayOrCreateLocked(displayId); + mService.mAm.startHomeActivityLocked(mCurrentUser, "displayAdded", displayId); } } @@ -4328,7 +4327,6 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D // The display hasn't been added to ActivityManager yet, create a new record now. activityDisplay = new ActivityDisplay(this, display); addChild(activityDisplay, ActivityDisplay.POSITION_BOTTOM); - calculateDefaultMinimalSizeOfResizeableTasks(activityDisplay); mWindowManager.onDisplayAdded(displayId); return activityDisplay; } @@ -4346,10 +4344,13 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D mActivityDisplays.remove(activityDisplay); } - private void calculateDefaultMinimalSizeOfResizeableTasks(ActivityDisplay display) { - mDefaultMinSizeOfResizeableTask = - mService.mContext.getResources().getDimensionPixelSize( - com.android.internal.R.dimen.default_minimal_size_resizable_task); + private void calculateDefaultMinimalSizeOfResizeableTasks() { + final Resources res = mService.mContext.getResources(); + final float minimalSize = res.getDimension( + com.android.internal.R.dimen.default_minimal_size_resizable_task); + final DisplayMetrics dm = res.getDisplayMetrics(); + + mDefaultMinSizeOfResizeableTaskDp = (int) (minimalSize / dm.density); } private void handleDisplayRemoved(int displayId) { @@ -4845,7 +4846,8 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D // We always want to return to the home activity instead of the recents activity // from whatever is started from the recents activity, so move the home stack // forward. - moveHomeStackToFront("startActivityFromRecents"); + // TODO (b/115289124): Multi-display supports for recents. + getDefaultDisplay().moveHomeStackToFront("startActivityFromRecents"); } // If the user must confirm credentials (e.g. when first launching a work app and the @@ -4888,12 +4890,13 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D final ActivityStack topSecondaryStack = display.getTopStackInWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY); if (topSecondaryStack.isActivityTypeHome()) { - // If the home activity if the top split-screen secondary stack, then the + // If the home activity is the top split-screen secondary stack, then the // primary split-screen stack is in the minimized mode which means it can't // receive input keys, so we should move the focused app to the home app so that // window manager can correctly calculate the focus window that can receive // input keys. - moveHomeStackToFront("startActivityFromRecents: homeVisibleInSplitScreen"); + display.moveHomeStackToFront( + "startActivityFromRecents: homeVisibleInSplitScreen"); // Immediately update the minimized docked stack mode, the upcoming animation // for the docked activity (WMS.overridePendingAppTransitionMultiThumbFuture) @@ -4940,10 +4943,13 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D static class WaitInfo { private final ComponentName mTargetComponent; private final WaitResult mResult; + /** Time stamp when we started to wait for {@link WaitResult}. */ + private final long mStartTimeMs; - public WaitInfo(ComponentName targetComponent, WaitResult result) { + WaitInfo(ComponentName targetComponent, WaitResult result, long startTimeMs) { this.mTargetComponent = targetComponent; this.mResult = result; + this.mStartTimeMs = startTimeMs; } public boolean matches(ComponentName targetComponent) { @@ -4954,6 +4960,10 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D return mResult; } + public long getStartTime() { + return mStartTimeMs; + } + public ComponentName getComponent() { return mTargetComponent; } diff --git a/services/core/java/com/android/server/am/ActivityStartController.java b/services/core/java/com/android/server/am/ActivityStartController.java index 6e3a79c3aa95..5e73bc3a2259 100644 --- a/services/core/java/com/android/server/am/ActivityStartController.java +++ b/services/core/java/com/android/server/am/ActivityStartController.java @@ -17,12 +17,14 @@ package com.android.server.am; import static android.app.ActivityManager.START_SUCCESS; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; -import static android.app.ActivityManagerInternal.ALLOW_FULL_ONLY; +import android.app.ActivityOptions; import android.app.IApplicationThread; import android.content.ComponentName; import android.content.ContentResolver; @@ -75,7 +77,7 @@ public class ActivityStartController { /** Temporary array to capture start activity results */ private ActivityRecord[] tmpOutRecord = new ActivityRecord[1]; - /**The result of the last home activity we attempted to start. */ + /** The result of the last home activity we attempted to start. */ private int mLastHomeActivityStartResult; /** A list of activities that are waiting to launch. */ @@ -161,13 +163,20 @@ public class ActivityStartController { mLastStarter.postStartActivityProcessing(r, result, targetStack); } - void startHomeActivity(Intent intent, ActivityInfo aInfo, String reason) { - mSupervisor.moveHomeStackTaskToTop(reason); + void startHomeActivity(Intent intent, ActivityInfo aInfo, String reason, int displayId) { + if (!mSupervisor.canStartHomeOnDisplay(aInfo, displayId)) { + return; + } + final ActivityOptions options = ActivityOptions.makeBasic(); + options.setLaunchWindowingMode(WINDOWING_MODE_FULLSCREEN); + options.setLaunchActivityType(ACTIVITY_TYPE_HOME); + options.setLaunchDisplayId(displayId); mLastHomeActivityStartResult = obtainStarter(intent, "startHomeActivity: " + reason) .setOutActivity(tmpOutRecord) .setCallingUid(0) .setActivityInfo(aInfo) + .setActivityOptions(options.toBundle()) .execute(); mLastHomeActivityStartRecord = tmpOutRecord[0]; if (mSupervisor.inResumeTopActivity) { diff --git a/services/core/java/com/android/server/am/ActivityStartInterceptor.java b/services/core/java/com/android/server/am/ActivityStartInterceptor.java index 1fb8f871efcd..4789ff334398 100644 --- a/services/core/java/com/android/server/am/ActivityStartInterceptor.java +++ b/services/core/java/com/android/server/am/ActivityStartInterceptor.java @@ -277,7 +277,7 @@ class ActivityStartInterceptor { mActivityOptions = ActivityOptions.makeBasic(); } - ActivityRecord homeActivityRecord = mSupervisor.getHomeActivity(); + ActivityRecord homeActivityRecord = mSupervisor.getDefaultDisplayHomeActivity(); if (homeActivityRecord != null && homeActivityRecord.getTask() != null) { // Showing credential confirmation activity in home task to avoid stopping multi-windowed // mode after showing the full-screen credential confirmation activity. diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java index 890aafefdf0f..de3b9cf3ccde 100644 --- a/services/core/java/com/android/server/am/ActivityStarter.java +++ b/services/core/java/com/android/server/am/ActivityStarter.java @@ -975,7 +975,8 @@ class ActivityStarter { clearedTask); break; case WINDOWING_MODE_SPLIT_SCREEN_PRIMARY: - final ActivityStack homeStack = mSupervisor.mHomeStack; + final ActivityStack homeStack = + startedActivityStack.getDisplay().getHomeStack(); if (homeStack != null && homeStack.shouldBeVisible(null /* starting */)) { mService.mWindowManager.showRecentApps(); } @@ -1154,6 +1155,9 @@ class ActivityStarter { mService.updateConfigurationLocked(globalConfig, null, false); } + // Notify ActivityMetricsLogger that the activity has launched. ActivityMetricsLogger + // will then wait for the windows to be drawn and populate WaitResult. + mSupervisor.getActivityMetricsLogger().notifyActivityLaunched(res, outRecord[0]); if (outResult != null) { outResult.result = res; @@ -1178,7 +1182,6 @@ class ActivityStarter { outResult.timeout = false; outResult.who = r.realActivity; outResult.totalTime = 0; - outResult.thisTime = 0; break; } case START_TASK_TO_FRONT: { @@ -1188,10 +1191,9 @@ class ActivityStarter { outResult.timeout = false; outResult.who = r.realActivity; outResult.totalTime = 0; - outResult.thisTime = 0; } else { - outResult.thisTime = SystemClock.uptimeMillis(); - mSupervisor.waitActivityVisible(r.realActivity, outResult); + final long startTimeMs = SystemClock.uptimeMillis(); + mSupervisor.waitActivityVisible(r.realActivity, outResult, startTimeMs); // Note: the timeout variable is not currently not ever set. do { try { @@ -1205,7 +1207,6 @@ class ActivityStarter { } } - mSupervisor.getActivityMetricsLogger().notifyActivityLaunched(res, outRecord[0]); return res; } } @@ -1280,6 +1281,15 @@ class ActivityStarter { setInitialState(r, options, inTask, doResume, startFlags, sourceRecord, voiceSession, voiceInteractor); + // Do not start home activity if it cannot be launched on preferred display. We are not + // doing this in ActivityStackSupervisor#canPlaceEntityOnDisplay because it might + // fallback to launch on other displays. + if (r.isActivityTypeHome() + && !mSupervisor.canStartHomeOnDisplay(r.info, mPreferredDisplayId)) { + Slog.w(TAG, "Cannot launch home on display " + mPreferredDisplayId); + return START_CANCELED; + } + computeLaunchingTaskFlags(); computeSourceStack(); @@ -1430,7 +1440,11 @@ class ActivityStarter { && top.userId == mStartActivity.userId && top.attachedToProcess() && ((mLaunchFlags & FLAG_ACTIVITY_SINGLE_TOP) != 0 - || isLaunchModeOneOf(LAUNCH_SINGLE_TOP, LAUNCH_SINGLE_TASK)); + || isLaunchModeOneOf(LAUNCH_SINGLE_TOP, LAUNCH_SINGLE_TASK)) + // This allows home activity to automatically launch on secondary display when + // display added, if home was the top activity on default display, instead of + // sending new intent to the home activity on default display. + && (!top.isActivityTypeHome() || top.getDisplayId() == mPreferredDisplayId); if (dontStart) { // For paranoia, make sure we have correctly resumed the top activity. topStack.mLastPausedActivity = null; @@ -1858,6 +1872,13 @@ class ActivityStarter { intentActivity = mSupervisor.findTaskLocked(mStartActivity, mPreferredDisplayId); } } + + if (mStartActivity.isActivityTypeHome() && intentActivity != null + && intentActivity.getDisplayId() != mPreferredDisplayId) { + // Do not reuse home activity on other displays. + intentActivity = null; + } + return intentActivity; } diff --git a/services/core/java/com/android/server/am/ActivityTaskManagerService.java b/services/core/java/com/android/server/am/ActivityTaskManagerService.java index 36261b505a94..c1eab821d084 100644 --- a/services/core/java/com/android/server/am/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/am/ActivityTaskManagerService.java @@ -1692,8 +1692,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { return; } final ActivityRecord r = stack.topRunningActivityLocked(); - if (mStackSupervisor.moveFocusableActivityStackToFrontLocked( - r, "setFocusedStack")) { + if (mStackSupervisor.moveFocusableActivityToTop(r, "setFocusedStack")) { mStackSupervisor.resumeFocusedStacksTopActivitiesLocked(); } } @@ -1714,7 +1713,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { return; } final ActivityRecord r = task.topRunningActivityLocked(); - if (mStackSupervisor.moveFocusableActivityStackToFrontLocked(r, "setFocusedTask")) { + if (mStackSupervisor.moveFocusableActivityToTop(r, "setFocusedTask")) { mStackSupervisor.resumeFocusedStacksTopActivitiesLocked(); } } @@ -4846,8 +4845,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { updateResumedAppTrace(r); mLastResumedActivity = r; - // TODO(b/111361570): Support multiple focused apps in WM - mWindowManager.setFocusedApp(r.appToken, true); + r.getDisplay().setFocusedApp(r, true); applyUpdateLockStateLocked(r); applyUpdateVrModeLocked(r); @@ -5263,7 +5261,8 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { @Override public ComponentName getHomeActivityForUser(int userId) { synchronized (mGlobalLock) { - ActivityRecord homeActivity = mStackSupervisor.getHomeActivityForUser(userId); + ActivityRecord homeActivity = + mStackSupervisor.getDefaultDisplayHomeActivityForUser(userId); return homeActivity == null ? null : homeActivity.realActivity; } } @@ -5437,8 +5436,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { throw new IllegalArgumentException( "setFocusedActivity: No activity record matching token=" + token); } - if (mStackSupervisor.moveFocusableActivityStackToFrontLocked( - r, "setFocusedActivity")) { + if (mStackSupervisor.moveFocusableActivityToTop(r, "setFocusedActivity")) { mStackSupervisor.resumeFocusedStacksTopActivitiesLocked(); } } @@ -5781,5 +5779,21 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { resultWho, requestCode, intents, resolvedTypes, flags, bOptions); } } + + @Override + public ActivityServiceConnectionsHolder getServiceConnectionsHolder(IBinder token) { + synchronized (mGlobalLock) { + final ActivityRecord r = ActivityRecord.isInStackLocked(token); + if (r == null) { + return null; + } + if (r.mServiceConnectionsHolder == null) { + r.mServiceConnectionsHolder = new ActivityServiceConnectionsHolder( + ActivityTaskManagerService.this, r); + } + + return r.mServiceConnectionsHolder; + } + } } } diff --git a/services/core/java/com/android/server/am/ConnectionRecord.java b/services/core/java/com/android/server/am/ConnectionRecord.java index fa8e6c431770..1242ed626a18 100644 --- a/services/core/java/com/android/server/am/ConnectionRecord.java +++ b/services/core/java/com/android/server/am/ConnectionRecord.java @@ -35,7 +35,7 @@ import java.io.PrintWriter; */ final class ConnectionRecord { final AppBindRecord binding; // The application/service binding. - final ActivityRecord activity; // If non-null, the owning activity. + final ActivityServiceConnectionsHolder<ConnectionRecord> activity; // If non-null, the owning activity. final IServiceConnection conn; // The client connection. final int flags; // Binding options. final int clientLabel; // String resource labeling this client. @@ -85,13 +85,14 @@ final class ConnectionRecord { void dump(PrintWriter pw, String prefix) { pw.println(prefix + "binding=" + binding); if (activity != null) { - pw.println(prefix + "activity=" + activity); + activity.dump(pw, prefix); } pw.println(prefix + "conn=" + conn.asBinder() + " flags=0x" + Integer.toHexString(flags)); } - ConnectionRecord(AppBindRecord _binding, ActivityRecord _activity, + ConnectionRecord(AppBindRecord _binding, + ActivityServiceConnectionsHolder<ConnectionRecord> _activity, IServiceConnection _conn, int _flags, int _clientLabel, PendingIntent _clientIntent, int _clientUid, String _clientProcessName) { diff --git a/services/core/java/com/android/server/am/EventLogTags.logtags b/services/core/java/com/android/server/am/EventLogTags.logtags index ed891dfb0e70..0ef2a0a90e13 100644 --- a/services/core/java/com/android/server/am/EventLogTags.logtags +++ b/services/core/java/com/android/server/am/EventLogTags.logtags @@ -87,9 +87,6 @@ option java_package com.android.server.am # User switched 30041 am_switch_user (id|1|5) -# Activity fully drawn time -30042 am_activity_fully_drawn_time (User|1|5),(Token|1|5),(Component Name|3),(time|2|3) - # Activity set to resumed 30043 am_set_resumed_activity (User|1|5),(Component Name|3),(Reason|3) diff --git a/services/core/java/com/android/server/am/LaunchTimeTracker.java b/services/core/java/com/android/server/am/LaunchTimeTracker.java deleted file mode 100644 index ee869691f7ca..000000000000 --- a/services/core/java/com/android/server/am/LaunchTimeTracker.java +++ /dev/null @@ -1,86 +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 com.android.server.am; - -import android.app.WaitResult; -import android.os.SystemClock; -import android.os.Trace; -import android.util.SparseArray; - -/** - * Tracks launch time of apps to be reported by {@link WaitResult}. Note that this is slightly - * different from {@link ActivityMetricsLogger}, but should eventually merged with it. - */ -class LaunchTimeTracker { - - private final SparseArray<Entry> mWindowingModeLaunchTime = new SparseArray<>(); - - void setLaunchTime(ActivityRecord r) { - Entry entry = mWindowingModeLaunchTime.get(r.getWindowingMode()); - if (entry == null){ - entry = new Entry(); - mWindowingModeLaunchTime.append(r.getWindowingMode(), entry); - } - entry.setLaunchTime(r); - } - - void stopFullyDrawnTraceIfNeeded(int windowingMode) { - final Entry entry = mWindowingModeLaunchTime.get(windowingMode); - if (entry == null) { - return; - } - entry.stopFullyDrawnTraceIfNeeded(); - } - - Entry getEntry(int windowingMode) { - return mWindowingModeLaunchTime.get(windowingMode); - } - - static class Entry { - - long mLaunchStartTime; - long mFullyDrawnStartTime; - - void setLaunchTime(ActivityRecord r) { - if (r.displayStartTime == 0) { - r.fullyDrawnStartTime = r.displayStartTime = SystemClock.uptimeMillis(); - if (mLaunchStartTime == 0) { - startLaunchTraces(r.packageName); - mLaunchStartTime = mFullyDrawnStartTime = r.displayStartTime; - } - } else if (mLaunchStartTime == 0) { - startLaunchTraces(r.packageName); - mLaunchStartTime = mFullyDrawnStartTime = SystemClock.uptimeMillis(); - } - } - - private void startLaunchTraces(String packageName) { - if (mFullyDrawnStartTime != 0) { - Trace.asyncTraceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER, "drawing", 0); - } - Trace.asyncTraceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "launching: " + packageName, 0); - Trace.asyncTraceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "drawing", 0); - } - - private void stopFullyDrawnTraceIfNeeded() { - if (mFullyDrawnStartTime != 0 && mLaunchStartTime == 0) { - Trace.asyncTraceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER, "drawing", 0); - mFullyDrawnStartTime = 0; - } - } - } -} diff --git a/services/core/java/com/android/server/am/MemoryStatUtil.java b/services/core/java/com/android/server/am/MemoryStatUtil.java index 228c71d91b8f..a8e1ccca8b9d 100644 --- a/services/core/java/com/android/server/am/MemoryStatUtil.java +++ b/services/core/java/com/android/server/am/MemoryStatUtil.java @@ -38,6 +38,7 @@ import java.util.regex.Pattern; */ final class MemoryStatUtil { static final int BYTES_IN_KILOBYTE = 1024; + static final int PAGE_SIZE = 4096; private static final String TAG = TAG_WITH_CLASS_NAME ? "MemoryStatUtil" : TAG_AM; @@ -68,7 +69,7 @@ final class MemoryStatUtil { private static final int PGFAULT_INDEX = 9; private static final int PGMAJFAULT_INDEX = 11; - private static final int RSS_IN_BYTES_INDEX = 23; + private static final int RSS_IN_PAGES_INDEX = 23; private MemoryStatUtil() {} @@ -146,15 +147,15 @@ final class MemoryStatUtil { final MemoryStat memoryStat = new MemoryStat(); Matcher m; m = PGFAULT.matcher(memoryStatContents); - memoryStat.pgfault = m.find() ? Long.valueOf(m.group(1)) : 0; + memoryStat.pgfault = m.find() ? Long.parseLong(m.group(1)) : 0; m = PGMAJFAULT.matcher(memoryStatContents); - memoryStat.pgmajfault = m.find() ? Long.valueOf(m.group(1)) : 0; + memoryStat.pgmajfault = m.find() ? Long.parseLong(m.group(1)) : 0; m = RSS_IN_BYTES.matcher(memoryStatContents); - memoryStat.rssInBytes = m.find() ? Long.valueOf(m.group(1)) : 0; + memoryStat.rssInBytes = m.find() ? Long.parseLong(m.group(1)) : 0; m = CACHE_IN_BYTES.matcher(memoryStatContents); - memoryStat.cacheInBytes = m.find() ? Long.valueOf(m.group(1)) : 0; + memoryStat.cacheInBytes = m.find() ? Long.parseLong(m.group(1)) : 0; m = SWAP_IN_BYTES.matcher(memoryStatContents); - memoryStat.swapInBytes = m.find() ? Long.valueOf(m.group(1)) : 0; + memoryStat.swapInBytes = m.find() ? Long.parseLong(m.group(1)) : 0; return memoryStat; } @@ -163,7 +164,12 @@ final class MemoryStatUtil { if (memoryMaxUsageContents == null || memoryMaxUsageContents.isEmpty()) { return 0; } - return Long.valueOf(memoryMaxUsageContents); + try { + return Long.parseLong(memoryMaxUsageContents); + } catch (NumberFormatException e) { + Slog.e(TAG, "Failed to parse value", e); + return 0; + } } /** @@ -181,11 +187,16 @@ final class MemoryStatUtil { return null; } - final MemoryStat memoryStat = new MemoryStat(); - memoryStat.pgfault = Long.valueOf(splits[PGFAULT_INDEX]); - memoryStat.pgmajfault = Long.valueOf(splits[PGMAJFAULT_INDEX]); - memoryStat.rssInBytes = Long.valueOf(splits[RSS_IN_BYTES_INDEX]); - return memoryStat; + try { + final MemoryStat memoryStat = new MemoryStat(); + memoryStat.pgfault = Long.parseLong(splits[PGFAULT_INDEX]); + memoryStat.pgmajfault = Long.parseLong(splits[PGMAJFAULT_INDEX]); + memoryStat.rssInBytes = Long.parseLong(splits[RSS_IN_PAGES_INDEX]) * PAGE_SIZE; + return memoryStat; + } catch (NumberFormatException e) { + Slog.e(TAG, "Failed to parse value", e); + return null; + } } /** @@ -199,7 +210,7 @@ final class MemoryStatUtil { } Matcher m = RSS_HIGH_WATERMARK_IN_BYTES.matcher(procStatusContents); // Convert value read from /proc/pid/status from kilobytes to bytes. - return m.find() ? Long.valueOf(m.group(1)) * BYTES_IN_KILOBYTE : 0; + return m.find() ? Long.parseLong(m.group(1)) * BYTES_IN_KILOBYTE : 0; } /** diff --git a/services/core/java/com/android/server/am/PersistentConnection.java b/services/core/java/com/android/server/am/PersistentConnection.java index c5edb26892f8..3490b1d18953 100644 --- a/services/core/java/com/android/server/am/PersistentConnection.java +++ b/services/core/java/com/android/server/am/PersistentConnection.java @@ -24,7 +24,7 @@ import android.os.Handler; import android.os.IBinder; import android.os.SystemClock; import android.os.UserHandle; -import android.util.Slog; +import android.util.Log; import android.util.TimeUtils; import com.android.internal.annotations.GuardedBy; @@ -59,6 +59,8 @@ import java.io.PrintWriter; * know what to do when the service component has gone missing, for example. If the user of this * class wants to restore the connection, then it should call {@link #unbind()} and {@link #bind} * explicitly. + * + * atest ${ANDROID_BUILD_TOP}/frameworks/base/services/tests/mockingservicestests/src/com/android/server/am/PersistentConnectionTest.java */ public abstract class PersistentConnection<T> { private final Object mLock = new Object(); @@ -76,6 +78,7 @@ public abstract class PersistentConnection<T> { private final long mRebindBackoffMs; private final double mRebindBackoffIncrease; private final long mRebindMaxBackoffMs; + private final long mResetBackoffDelay; private long mReconnectTime; @@ -100,6 +103,18 @@ public abstract class PersistentConnection<T> { @GuardedBy("mLock") private T mService; + @GuardedBy("mLock") + private int mNumConnected; + + @GuardedBy("mLock") + private int mNumDisconnected; + + @GuardedBy("mLock") + private int mNumBindingDied; + + @GuardedBy("mLock") + private long mLastConnectedTime; + private final ServiceConnection mServiceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { @@ -108,25 +123,35 @@ public abstract class PersistentConnection<T> { // Callback came in after PersistentConnection.unbind() was called. // We just ignore this. // (We've already called unbindService() already in unbind) - Slog.w(mTag, "Connected: " + mComponentName.flattenToShortString() + Log.w(mTag, "Connected: " + mComponentName.flattenToShortString() + " u" + mUserId + " but not bound, ignore."); return; } - Slog.i(mTag, "Connected: " + mComponentName.flattenToShortString() + Log.i(mTag, "Connected: " + mComponentName.flattenToShortString() + " u" + mUserId); + mNumConnected++; + mIsConnected = true; + mLastConnectedTime = injectUptimeMillis(); mService = asInterface(service); + + scheduleStableCheckLocked(); } } @Override public void onServiceDisconnected(ComponentName name) { synchronized (mLock) { - Slog.i(mTag, "Disconnected: " + mComponentName.flattenToShortString() + Log.i(mTag, "Disconnected: " + mComponentName.flattenToShortString() + " u" + mUserId); + mNumDisconnected++; + cleanUpConnectionLocked(); + + // Note we won't increase the rebind timeout here, because we don't explicitly + // rebind in this case. } } @@ -136,13 +161,16 @@ public abstract class PersistentConnection<T> { synchronized (mLock) { if (!mBound) { // Callback came in late? - Slog.w(mTag, "Binding died: " + mComponentName.flattenToShortString() + Log.w(mTag, "Binding died: " + mComponentName.flattenToShortString() + " u" + mUserId + " but not bound, ignore."); return; } - Slog.w(mTag, "Binding died: " + mComponentName.flattenToShortString() + Log.w(mTag, "Binding died: " + mComponentName.flattenToShortString() + " u" + mUserId); + + mNumBindingDied++; + scheduleRebindLocked(); } } @@ -152,7 +180,8 @@ public abstract class PersistentConnection<T> { public PersistentConnection(@NonNull String tag, @NonNull Context context, @NonNull Handler handler, int userId, @NonNull ComponentName componentName, - long rebindBackoffSeconds, double rebindBackoffIncrease, long rebindMaxBackoffSeconds) { + long rebindBackoffSeconds, double rebindBackoffIncrease, long rebindMaxBackoffSeconds, + long resetBackoffDelay) { mTag = tag; mContext = context; mHandler = handler; @@ -162,6 +191,7 @@ public abstract class PersistentConnection<T> { mRebindBackoffMs = rebindBackoffSeconds * 1000; mRebindBackoffIncrease = rebindBackoffIncrease; mRebindMaxBackoffMs = rebindMaxBackoffSeconds * 1000; + mResetBackoffDelay = resetBackoffDelay * 1000; mNextBackoffMs = mRebindBackoffMs; } @@ -170,6 +200,12 @@ public abstract class PersistentConnection<T> { return mComponentName; } + public final int getUserId() { + return mUserId; + } + + protected abstract int getBindFlags(); + /** * @return whether {@link #bind()} has been called and {@link #unbind()} hasn't. * @@ -220,6 +256,42 @@ public abstract class PersistentConnection<T> { } } + /** Return the next back-off time */ + public long getNextBackoffMs() { + synchronized (mLock) { + return mNextBackoffMs; + } + } + + /** Return the number of times the connected callback called. */ + public int getNumConnected() { + synchronized (mLock) { + return mNumConnected; + } + } + + /** Return the number of times the disconnected callback called. */ + public int getNumDisconnected() { + synchronized (mLock) { + return mNumDisconnected; + } + } + + /** Return the number of times the binding died callback called. */ + public int getNumBindingDied() { + synchronized (mLock) { + return mNumBindingDied; + } + } + + @GuardedBy("mLock") + private void resetBackoffLocked() { + if (mNextBackoffMs != mRebindBackoffMs) { + mNextBackoffMs = mRebindBackoffMs; + Log.i(mTag, "Backoff reset to " + mNextBackoffMs); + } + } + @GuardedBy("mLock") public final void bindInnerLocked(boolean resetBackoff) { unscheduleRebindLocked(); @@ -229,23 +301,24 @@ public abstract class PersistentConnection<T> { } mBound = true; + unscheduleStableCheckLocked(); + if (resetBackoff) { - // Note this is the only place we reset the backoff time. - mNextBackoffMs = mRebindBackoffMs; + resetBackoffLocked(); } final Intent service = new Intent().setComponent(mComponentName); if (DEBUG) { - Slog.d(mTag, "Attempting to connect to " + mComponentName); + Log.d(mTag, "Attempting to connect to " + mComponentName); } final boolean success = mContext.bindServiceAsUser(service, mServiceConnection, - Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE, + Context.BIND_AUTO_CREATE | getBindFlags(), mHandler, UserHandle.of(mUserId)); if (!success) { - Slog.e(mTag, "Binding: " + service.getComponent() + " u" + mUserId + Log.e(mTag, "Binding: " + service.getComponent() + " u" + mUserId + " failed."); } } @@ -265,6 +338,8 @@ public abstract class PersistentConnection<T> { private void cleanUpConnectionLocked() { mIsConnected = false; mService = null; + + unscheduleStableCheckLocked(); } /** @@ -275,6 +350,7 @@ public abstract class PersistentConnection<T> { mShouldBeBound = false; unbindLocked(); + unscheduleStableCheckLocked(); } } @@ -285,7 +361,7 @@ public abstract class PersistentConnection<T> { if (!mBound) { return; } - Slog.i(mTag, "Stopping: " + mComponentName.flattenToShortString() + " u" + mUserId); + Log.i(mTag, "Stopping: " + mComponentName.flattenToShortString() + " u" + mUserId); mBound = false; mContext.unbindService(mServiceConnection); @@ -303,7 +379,7 @@ public abstract class PersistentConnection<T> { unbindLocked(); if (!mRebindScheduled) { - Slog.i(mTag, "Scheduling to reconnect in " + mNextBackoffMs + " ms (uptime)"); + Log.i(mTag, "Scheduling to reconnect in " + mNextBackoffMs + " ms (uptime)"); mReconnectTime = injectUptimeMillis() + mNextBackoffMs; @@ -316,6 +392,33 @@ public abstract class PersistentConnection<T> { } } + private final Runnable mStableCheck = this::stableConnectionCheck; + + private void stableConnectionCheck() { + synchronized (mLock) { + final long now = injectUptimeMillis(); + final long timeRemaining = (mLastConnectedTime + mResetBackoffDelay) - now; + if (DEBUG) { + Log.d(mTag, "stableConnectionCheck: bound=" + mBound + " connected=" + mIsConnected + + " remaining=" + timeRemaining); + } + if (mBound && mIsConnected && timeRemaining <= 0) { + resetBackoffLocked(); + } + } + } + + @GuardedBy("mLock") + private void unscheduleStableCheckLocked() { + injectRemoveCallbacks(mStableCheck); + } + + @GuardedBy("mLock") + private void scheduleStableCheckLocked() { + unscheduleStableCheckLocked(); + injectPostAtTime(mStableCheck, injectUptimeMillis() + mResetBackoffDelay); + } + /** Must be implemented by a subclass to convert an {@link IBinder} to a stub. */ protected abstract T asInterface(IBinder binder); @@ -323,10 +426,12 @@ public abstract class PersistentConnection<T> { synchronized (mLock) { pw.print(prefix); pw.print(mComponentName.flattenToShortString()); - pw.print(mBound ? " [bound]" : " [not bound]"); - pw.print(mIsConnected ? " [connected]" : " [not connected]"); + pw.print(" u"); + pw.print(mUserId); + pw.print(mBound ? " [bound]" : " [not bound]"); + pw.print(mIsConnected ? " [connected]" : " [not connected]"); if (mRebindScheduled) { - pw.print(" reconnect in "); + pw.print(" reconnect in "); TimeUtils.formatDuration((mReconnectTime - injectUptimeMillis()), pw); } pw.println(); @@ -334,6 +439,20 @@ public abstract class PersistentConnection<T> { pw.print(prefix); pw.print(" Next backoff(sec): "); pw.print(mNextBackoffMs / 1000); + pw.println(); + + pw.print(prefix); + pw.print(" Connected: "); + pw.print(mNumConnected); + pw.print(" Disconnected: "); + pw.print(mNumDisconnected); + pw.print(" Died: "); + pw.print(mNumBindingDied); + if (mIsConnected) { + pw.print(" Duration: "); + TimeUtils.formatDuration((injectUptimeMillis() - mLastConnectedTime), pw); + } + pw.println(); } } @@ -373,6 +492,11 @@ public abstract class PersistentConnection<T> { } @VisibleForTesting + Runnable getStableCheckRunnableForTest() { + return mStableCheck; + } + + @VisibleForTesting boolean shouldBeBoundForTest() { return mShouldBeBound; } diff --git a/services/core/java/com/android/server/am/PersisterQueue.java b/services/core/java/com/android/server/am/PersisterQueue.java new file mode 100644 index 000000000000..60ea0faaffee --- /dev/null +++ b/services/core/java/com/android/server/am/PersisterQueue.java @@ -0,0 +1,277 @@ +/* + * 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.am; + +import android.os.Process; +import android.os.SystemClock; +import android.util.Slog; + +import com.android.internal.annotations.VisibleForTesting; + +import java.util.ArrayList; +import java.util.function.Predicate; + +/** + * The common threading logic for persisters to use so that they can run in the same threads. + * Methods in this class are synchronized on its instance, so caller could also synchronize on + * its instance to perform modifications in items. + */ +class PersisterQueue { + private static final String TAG = "PersisterQueue"; + private static final boolean DEBUG = false; + + /** When not flushing don't write out files faster than this */ + private static final long INTER_WRITE_DELAY_MS = 500; + + /** + * When not flushing delay this long before writing the first file out. This gives the next task + * being launched a chance to load its resources without this occupying IO bandwidth. + */ + private static final long PRE_TASK_DELAY_MS = 3000; + + /** The maximum number of entries to keep in the queue before draining it automatically. */ + private static final int MAX_WRITE_QUEUE_LENGTH = 6; + + /** Special value for mWriteTime to mean don't wait, just write */ + private static final long FLUSH_QUEUE = -1; + + /** An {@link WriteQueueItem} that doesn't do anything. Used to trigger {@link + * Listener#onPreProcessItem}. */ + static final WriteQueueItem EMPTY_ITEM = () -> { }; + + private final long mInterWriteDelayMs; + private final long mPreTaskDelayMs; + private final LazyTaskWriterThread mLazyTaskWriterThread; + private final ArrayList<WriteQueueItem> mWriteQueue = new ArrayList<>(); + + private final ArrayList<Listener> mListeners = new ArrayList<>(); + + /** + * Value determines write delay mode as follows: < 0 We are Flushing. No delays between writes + * until the image queue is drained and all tasks needing persisting are written to disk. There + * is no delay between writes. == 0 We are Idle. Next writes will be delayed by + * #PRE_TASK_DELAY_MS. > 0 We are Actively writing. Next write will be at this time. Subsequent + * writes will be delayed by #INTER_WRITE_DELAY_MS. + */ + private long mNextWriteTime = 0; + + PersisterQueue() { + this(INTER_WRITE_DELAY_MS, PRE_TASK_DELAY_MS); + } + + /** Used for tests to reduce waiting time. */ + @VisibleForTesting + PersisterQueue(long interWriteDelayMs, long preTaskDelayMs) { + if (interWriteDelayMs < 0 || preTaskDelayMs < 0) { + throw new IllegalArgumentException("Both inter-write delay and pre-task delay need to" + + "be non-negative. inter-write delay: " + interWriteDelayMs + + "ms pre-task delay: " + preTaskDelayMs); + } + mInterWriteDelayMs = interWriteDelayMs; + mPreTaskDelayMs = preTaskDelayMs; + mLazyTaskWriterThread = new LazyTaskWriterThread("LazyTaskWriterThread"); + } + + synchronized void startPersisting() { + if (!mLazyTaskWriterThread.isAlive()) { + mLazyTaskWriterThread.start(); + } + } + + /** Stops persisting thread. Should only be used in tests. */ + @VisibleForTesting + void stopPersisting() throws InterruptedException { + if (!mLazyTaskWriterThread.isAlive()) { + return; + } + + synchronized (this) { + mLazyTaskWriterThread.interrupt(); + } + mLazyTaskWriterThread.join(); + } + + synchronized void addItem(WriteQueueItem item, boolean flush) { + mWriteQueue.add(item); + + if (flush || mWriteQueue.size() > MAX_WRITE_QUEUE_LENGTH) { + mNextWriteTime = FLUSH_QUEUE; + } else if (mNextWriteTime == 0) { + mNextWriteTime = SystemClock.uptimeMillis() + mPreTaskDelayMs; + } + notify(); + } + + synchronized <T extends WriteQueueItem> T findLastItem(Predicate<T> predicate, Class<T> clazz) { + for (int i = mWriteQueue.size() - 1; i >= 0; --i) { + WriteQueueItem writeQueueItem = mWriteQueue.get(i); + if (clazz.isInstance(writeQueueItem)) { + T item = clazz.cast(writeQueueItem); + if (predicate.test(item)) { + return item; + } + } + } + + return null; + } + + synchronized <T extends WriteQueueItem> void removeItems(Predicate<T> predicate, + Class<T> clazz) { + for (int i = mWriteQueue.size() - 1; i >= 0; --i) { + WriteQueueItem writeQueueItem = mWriteQueue.get(i); + if (clazz.isInstance(writeQueueItem)) { + T item = clazz.cast(writeQueueItem); + if (predicate.test(item)) { + if (DEBUG) Slog.d(TAG, "Removing " + item + " from write queue."); + mWriteQueue.remove(i); + } + } + } + } + + synchronized void flush() { + mNextWriteTime = FLUSH_QUEUE; + notifyAll(); + do { + try { + wait(); + } catch (InterruptedException e) { + } + } while (mNextWriteTime == FLUSH_QUEUE); + } + + void yieldIfQueueTooDeep() { + boolean stall = false; + synchronized (this) { + if (mNextWriteTime == FLUSH_QUEUE) { + stall = true; + } + } + if (stall) { + Thread.yield(); + } + } + + void addListener(Listener listener) { + mListeners.add(listener); + } + + private void processNextItem() throws InterruptedException { + // This part is extracted into a method so that the GC can clearly see the end of the + // scope of the variable 'item'. If this part was in the loop in LazyTaskWriterThread, the + // last item it processed would always "leak". + // See https://b.corp.google.com/issues/64438652#comment7 + + // If mNextWriteTime, then don't delay between each call to saveToXml(). + final WriteQueueItem item; + synchronized (this) { + if (mNextWriteTime != FLUSH_QUEUE) { + // The next write we don't have to wait so long. + mNextWriteTime = SystemClock.uptimeMillis() + mInterWriteDelayMs; + if (DEBUG) { + Slog.d(TAG, "Next write time may be in " + mInterWriteDelayMs + + " msec. (" + mNextWriteTime + ")"); + } + } + + while (mWriteQueue.isEmpty()) { + if (mNextWriteTime != 0) { + mNextWriteTime = 0; // idle. + notify(); // May need to wake up flush(). + } + // Make sure we exit this thread correctly when interrupted before going to + // indefinite wait. + if (Thread.currentThread().isInterrupted()) { + throw new InterruptedException(); + } + if (DEBUG) Slog.d(TAG, "LazyTaskWriter: waiting indefinitely."); + wait(); + // Invariant: mNextWriteTime is either FLUSH_QUEUE or PRE_WRITE_DELAY_MS + // from now. + } + item = mWriteQueue.remove(0); + + long now = SystemClock.uptimeMillis(); + if (DEBUG) { + Slog.d(TAG, "LazyTaskWriter: now=" + now + " mNextWriteTime=" + mNextWriteTime + + " mWriteQueue.size=" + mWriteQueue.size()); + } + while (now < mNextWriteTime) { + if (DEBUG) { + Slog.d(TAG, "LazyTaskWriter: waiting " + (mNextWriteTime - now)); + } + wait(mNextWriteTime - now); + now = SystemClock.uptimeMillis(); + } + + // Got something to do. + } + + item.process(); + } + + interface WriteQueueItem { + void process(); + } + + interface Listener { + /** + * Called before {@link PersisterQueue} tries to process next item. + * + * Note if the queue is empty, this callback will be called before the indefinite wait. This + * will be called once when {@link PersisterQueue} starts the internal thread before the + * indefinite wait. + * + * This callback is called w/o locking the instance of {@link PersisterQueue}. + * + * @param queueEmpty {@code true} if the queue is empty, which indicates {@link + * PersisterQueue} is likely to enter indefinite wait; or {@code false} if there is still + * item to process. + */ + void onPreProcessItem(boolean queueEmpty); + } + + private class LazyTaskWriterThread extends Thread { + + private LazyTaskWriterThread(String name) { + super(name); + } + + @Override + public void run() { + Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); + try { + while (true) { + final boolean probablyDone; + synchronized (PersisterQueue.this) { + probablyDone = mWriteQueue.isEmpty(); + } + + for (int i = mListeners.size() - 1; i >= 0; --i) { + mListeners.get(i).onPreProcessItem(probablyDone); + } + + processNextItem(); + } + } catch (InterruptedException e) { + Slog.e(TAG, "Persister thread is exiting. Should never happen in prod, but" + + "it's OK in tests."); + } + } + } +} diff --git a/services/core/java/com/android/server/am/RecentTasks.java b/services/core/java/com/android/server/am/RecentTasks.java index e11e00368a78..4d0b1da98fde 100644 --- a/services/core/java/com/android/server/am/RecentTasks.java +++ b/services/core/java/com/android/server/am/RecentTasks.java @@ -30,8 +30,9 @@ import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS; import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK; import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; - import static android.os.Process.SYSTEM_UID; +import static android.view.Display.DEFAULT_DISPLAY; + import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_RECENTS; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_RECENTS_TRIM_TASKS; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_TASKS; @@ -55,7 +56,6 @@ import android.content.pm.ParceledListSlice; import android.content.pm.UserInfo; import android.content.res.Resources; import android.graphics.Bitmap; -import android.graphics.Rect; import android.os.Bundle; import android.os.Environment; import android.os.IBinder; @@ -431,7 +431,7 @@ class RecentTasks { void onSystemReadyLocked() { loadRecentsComponent(mService.mContext.getResources()); mTasks.clear(); - mTaskPersister.startPersisting(); + mTaskPersister.onSystemReady(); } Bitmap getTaskDescriptionIcon(String path) { @@ -1244,7 +1244,6 @@ class RecentTasks { */ protected boolean isTrimmable(TaskRecord task) { final ActivityStack stack = task.getStack(); - final ActivityStack homeStack = mSupervisor.mHomeStack; // No stack for task, just trim it if (stack == null) { @@ -1252,13 +1251,14 @@ class RecentTasks { } // Ignore tasks from different displays - if (stack.getDisplay() != homeStack.getDisplay()) { + // TODO (b/115289124): No Recents on non-default displays. + if (stack.mDisplayId != DEFAULT_DISPLAY) { return false; } // Trim tasks that are in stacks that are behind the home stack final ActivityDisplay display = stack.getDisplay(); - return display.getIndexOf(stack) < display.getIndexOf(homeStack); + return display.getIndexOf(stack) < display.getIndexOf(display.getHomeStack()); } /** diff --git a/services/core/java/com/android/server/am/TaskPersister.java b/services/core/java/com/android/server/am/TaskPersister.java index 481bb2bbe118..fc5dfb4db1fc 100644 --- a/services/core/java/com/android/server/am/TaskPersister.java +++ b/services/core/java/com/android/server/am/TaskPersister.java @@ -16,13 +16,14 @@ package com.android.server.am; +import static com.android.server.am.ActivityStackSupervisor.MATCH_TASK_IN_STACKS_OR_RECENT_TASKS; + import android.annotation.NonNull; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.os.Debug; import android.os.Environment; import android.os.FileUtils; -import android.os.Process; import android.os.SystemClock; import android.util.ArraySet; import android.util.AtomicFile; @@ -34,6 +35,7 @@ import android.util.Xml; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.FastXmlSerializer; import com.android.internal.util.XmlUtils; + import libcore.io.IoUtils; import org.xmlpull.v1.XmlPullParser; @@ -54,32 +56,18 @@ import java.util.Collections; import java.util.Comparator; import java.util.List; -import static com.android.server.am.ActivityStackSupervisor.MATCH_TASK_IN_STACKS_OR_RECENT_TASKS; - -public class TaskPersister { +/** + * Persister that saves recent tasks into disk. + */ +public class TaskPersister implements PersisterQueue.Listener { static final String TAG = "TaskPersister"; static final boolean DEBUG = false; - - /** When not flushing don't write out files faster than this */ - private static final long INTER_WRITE_DELAY_MS = 500; - - /** - * When not flushing delay this long before writing the first file out. This gives the next task - * being launched a chance to load its resources without this occupying IO bandwidth. - */ - private static final long PRE_TASK_DELAY_MS = 3000; - - /** The maximum number of entries to keep in the queue before draining it automatically. */ - private static final int MAX_WRITE_QUEUE_LENGTH = 6; - - /** Special value for mWriteTime to mean don't wait, just write */ - private static final long FLUSH_QUEUE = -1; + static final String IMAGE_EXTENSION = ".png"; private static final String TASKS_DIRNAME = "recent_tasks"; private static final String TASK_FILENAME_SUFFIX = "_task.xml"; private static final String IMAGES_DIRNAME = "recent_images"; private static final String PERSISTED_TASK_IDS_FILENAME = "persisted_taskIds.txt"; - static final String IMAGE_EXTENSION = ".png"; private static final String TAG_TASK = "task"; @@ -90,39 +78,9 @@ public class TaskPersister { private final File mTaskIdsDir; // To lock file operations in TaskPersister private final Object mIoLock = new Object(); + private final PersisterQueue mPersisterQueue; - /** - * Value determines write delay mode as follows: < 0 We are Flushing. No delays between writes - * until the image queue is drained and all tasks needing persisting are written to disk. There - * is no delay between writes. == 0 We are Idle. Next writes will be delayed by - * #PRE_TASK_DELAY_MS. > 0 We are Actively writing. Next write will be at this time. Subsequent - * writes will be delayed by #INTER_WRITE_DELAY_MS. - */ - private long mNextWriteTime = 0; - - private final LazyTaskWriterThread mLazyTaskWriterThread; - - private static class WriteQueueItem {} - - private static class TaskWriteQueueItem extends WriteQueueItem { - final TaskRecord mTask; - - TaskWriteQueueItem(TaskRecord task) { - mTask = task; - } - } - - private static class ImageWriteQueueItem extends WriteQueueItem { - final String mFilePath; - Bitmap mImage; - - ImageWriteQueueItem(String filePath, Bitmap image) { - mFilePath = filePath; - mImage = image; - } - } - - ArrayList<WriteQueueItem> mWriteQueue = new ArrayList<WriteQueueItem>(); + private final ArraySet<Integer> mTmpTaskIds = new ArraySet<>(); TaskPersister(File systemDir, ActivityStackSupervisor stackSupervisor, ActivityTaskManagerService service, RecentTasks recentTasks) { @@ -145,7 +103,8 @@ public class TaskPersister { mStackSupervisor = stackSupervisor; mService = service; mRecentTasks = recentTasks; - mLazyTaskWriterThread = new LazyTaskWriterThread("LazyTaskWriterThread"); + mPersisterQueue = new PersisterQueue(); + mPersisterQueue.addListener(this); } @VisibleForTesting @@ -154,42 +113,21 @@ public class TaskPersister { mStackSupervisor = null; mService = null; mRecentTasks = null; - mLazyTaskWriterThread = new LazyTaskWriterThread("LazyTaskWriterThreadTest"); + mPersisterQueue = new PersisterQueue(); + mPersisterQueue.addListener(this); } - void startPersisting() { - if (!mLazyTaskWriterThread.isAlive()) { - mLazyTaskWriterThread.start(); - } + void onSystemReady() { + mPersisterQueue.startPersisting(); } private void removeThumbnails(TaskRecord task) { - final String taskString = Integer.toString(task.taskId); - for (int queueNdx = mWriteQueue.size() - 1; queueNdx >= 0; --queueNdx) { - final WriteQueueItem item = mWriteQueue.get(queueNdx); - if (item instanceof ImageWriteQueueItem) { - final File thumbnailFile = new File(((ImageWriteQueueItem) item).mFilePath); - if (thumbnailFile.getName().startsWith(taskString)) { - if (DEBUG) { - Slog.d(TAG, "Removing " + ((ImageWriteQueueItem) item).mFilePath + - " from write queue"); - } - mWriteQueue.remove(queueNdx); - } - } - } - } - - private void yieldIfQueueTooDeep() { - boolean stall = false; - synchronized (this) { - if (mNextWriteTime == FLUSH_QUEUE) { - stall = true; - } - } - if (stall) { - Thread.yield(); - } + mPersisterQueue.removeItems( + item -> { + File file = new File(item.mFilePath); + return file.getName().startsWith(Integer.toString(task.taskId)); + }, + ImageWriteQueueItem.class); } @NonNull @@ -251,84 +189,51 @@ public class TaskPersister { } void wakeup(TaskRecord task, boolean flush) { - synchronized (this) { + synchronized (mPersisterQueue) { if (task != null) { - int queueNdx; - for (queueNdx = mWriteQueue.size() - 1; queueNdx >= 0; --queueNdx) { - final WriteQueueItem item = mWriteQueue.get(queueNdx); - if (item instanceof TaskWriteQueueItem && - ((TaskWriteQueueItem) item).mTask == task) { - if (!task.inRecents) { - // This task is being removed. - removeThumbnails(task); - } - break; - } + final TaskWriteQueueItem item = mPersisterQueue.findLastItem( + queueItem -> task == queueItem.mTask, TaskWriteQueueItem.class); + if (item != null && !task.inRecents) { + removeThumbnails(task); } - if (queueNdx < 0 && task.isPersistable) { - mWriteQueue.add(new TaskWriteQueueItem(task)); + + if (item == null && task.isPersistable) { + mPersisterQueue.addItem(new TaskWriteQueueItem(task, mService), flush); } } else { // Dummy. Ensures removeObsoleteFiles is called when LazyTaskThreadWriter is // notified. - mWriteQueue.add(new WriteQueueItem()); + mPersisterQueue.addItem(PersisterQueue.EMPTY_ITEM, flush); } - if (flush || mWriteQueue.size() > MAX_WRITE_QUEUE_LENGTH) { - mNextWriteTime = FLUSH_QUEUE; - } else if (mNextWriteTime == 0) { - mNextWriteTime = SystemClock.uptimeMillis() + PRE_TASK_DELAY_MS; + if (DEBUG) { + Slog.d(TAG, "wakeup: task=" + task + " flush=" + flush + " Callers=" + + Debug.getCallers(4)); } - if (DEBUG) Slog.d(TAG, "wakeup: task=" + task + " flush=" + flush + " mNextWriteTime=" - + mNextWriteTime + " mWriteQueue.size=" + mWriteQueue.size() - + " Callers=" + Debug.getCallers(4)); - notifyAll(); } - yieldIfQueueTooDeep(); + mPersisterQueue.yieldIfQueueTooDeep(); } void flush() { - synchronized (this) { - mNextWriteTime = FLUSH_QUEUE; - notifyAll(); - do { - try { - wait(); - } catch (InterruptedException e) { - } - } while (mNextWriteTime == FLUSH_QUEUE); - } + mPersisterQueue.flush(); } void saveImage(Bitmap image, String filePath) { - synchronized (this) { - int queueNdx; - for (queueNdx = mWriteQueue.size() - 1; queueNdx >= 0; --queueNdx) { - final WriteQueueItem item = mWriteQueue.get(queueNdx); - if (item instanceof ImageWriteQueueItem) { - ImageWriteQueueItem imageWriteQueueItem = (ImageWriteQueueItem) item; - if (imageWriteQueueItem.mFilePath.equals(filePath)) { - // replace the Bitmap with the new one. - imageWriteQueueItem.mImage = image; - break; - } - } - } - if (queueNdx < 0) { - mWriteQueue.add(new ImageWriteQueueItem(filePath, image)); - } - if (mWriteQueue.size() > MAX_WRITE_QUEUE_LENGTH) { - mNextWriteTime = FLUSH_QUEUE; - } else if (mNextWriteTime == 0) { - mNextWriteTime = SystemClock.uptimeMillis() + PRE_TASK_DELAY_MS; + synchronized (mPersisterQueue) { + final ImageWriteQueueItem item = mPersisterQueue.findLastItem( + queueItem -> queueItem.mFilePath.equals(filePath), ImageWriteQueueItem.class); + if (item != null) { + // replace the Bitmap with the new one. + item.mImage = image; + } else { + mPersisterQueue.addItem(new ImageWriteQueueItem(filePath, image), + /* flush */ false); } if (DEBUG) Slog.d(TAG, "saveImage: filePath=" + filePath + " now=" + - SystemClock.uptimeMillis() + " mNextWriteTime=" + - mNextWriteTime + " Callers=" + Debug.getCallers(4)); - notifyAll(); + SystemClock.uptimeMillis() + " Callers=" + Debug.getCallers(4)); } - yieldIfQueueTooDeep(); + mPersisterQueue.yieldIfQueueTooDeep(); } Bitmap getTaskDescriptionIcon(String filePath) { @@ -340,41 +245,10 @@ public class TaskPersister { return restoreImage(filePath); } - Bitmap getImageFromWriteQueue(String filePath) { - synchronized (this) { - for (int queueNdx = mWriteQueue.size() - 1; queueNdx >= 0; --queueNdx) { - final WriteQueueItem item = mWriteQueue.get(queueNdx); - if (item instanceof ImageWriteQueueItem) { - ImageWriteQueueItem imageWriteQueueItem = (ImageWriteQueueItem) item; - if (imageWriteQueueItem.mFilePath.equals(filePath)) { - return imageWriteQueueItem.mImage; - } - } - } - return null; - } - } - - private StringWriter saveToXml(TaskRecord task) throws IOException, XmlPullParserException { - if (DEBUG) Slog.d(TAG, "saveToXml: task=" + task); - final XmlSerializer xmlSerializer = new FastXmlSerializer(); - StringWriter stringWriter = new StringWriter(); - xmlSerializer.setOutput(stringWriter); - - if (DEBUG) xmlSerializer.setFeature( - "http://xmlpull.org/v1/doc/features.html#indent-output", true); - - // save task - xmlSerializer.startDocument(null, true); - - xmlSerializer.startTag(null, TAG_TASK); - task.saveToXml(xmlSerializer); - xmlSerializer.endTag(null, TAG_TASK); - - xmlSerializer.endDocument(); - xmlSerializer.flush(); - - return stringWriter; + private Bitmap getImageFromWriteQueue(String filePath) { + final ImageWriteQueueItem item = mPersisterQueue.findLastItem( + queueItem -> queueItem.mFilePath.equals(filePath), ImageWriteQueueItem.class); + return item != null ? item.mImage : null; } private String fileToString(File file) { @@ -534,6 +408,26 @@ public class TaskPersister { return tasks; } + @Override + public void onPreProcessItem(boolean queueEmpty) { + // We can't lock mService while locking the queue, but we don't want to + // call removeObsoleteFiles before every item, only the last time + // before going to sleep. The risk is that we call removeObsoleteFiles() + // successively. + if (queueEmpty) { + if (DEBUG) Slog.d(TAG, "Looking for obsolete files."); + mTmpTaskIds.clear(); + synchronized (mService.mGlobalLock) { + if (DEBUG) Slog.d(TAG, "mRecents=" + mRecentTasks); + mRecentTasks.getPersistableTaskIds(mTmpTaskIds); + mService.mWindowManager.removeObsoleteTaskFiles(mTmpTaskIds, + mRecentTasks.usersWithRecentsLoadedLocked()); + } + removeObsoleteFiles(mTmpTaskIds); + } + writeTaskIdsFiles(); + } + private static void removeObsoleteFiles(ArraySet<Integer> persistentTaskIds, File[] files) { if (DEBUG) Slog.d(TAG, "removeObsoleteFiles: persistentTaskIds=" + persistentTaskIds + " files=" + files); @@ -631,143 +525,117 @@ public class TaskPersister { return parentDir.exists() || parentDir.mkdirs(); } - private class LazyTaskWriterThread extends Thread { + private static class TaskWriteQueueItem implements PersisterQueue.WriteQueueItem { + private final ActivityTaskManagerService mService; + private final TaskRecord mTask; - LazyTaskWriterThread(String name) { - super(name); + TaskWriteQueueItem(TaskRecord task, ActivityTaskManagerService service) { + mTask = task; + mService = service; } - @Override - public void run() { - Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); - ArraySet<Integer> persistentTaskIds = new ArraySet<>(); - while (true) { - // We can't lock mService while holding TaskPersister.this, but we don't want to - // call removeObsoleteFiles every time through the loop, only the last time before - // going to sleep. The risk is that we call removeObsoleteFiles() successively. - final boolean probablyDone; - synchronized (TaskPersister.this) { - probablyDone = mWriteQueue.isEmpty(); - } - if (probablyDone) { - if (DEBUG) Slog.d(TAG, "Looking for obsolete files."); - persistentTaskIds.clear(); - synchronized (mService.mGlobalLock) { - if (DEBUG) Slog.d(TAG, "mRecents=" + mRecentTasks); - mRecentTasks.getPersistableTaskIds(persistentTaskIds); - mService.mWindowManager.removeObsoleteTaskFiles(persistentTaskIds, - mRecentTasks.usersWithRecentsLoadedLocked()); - } - removeObsoleteFiles(persistentTaskIds); - } - writeTaskIdsFiles(); + private StringWriter saveToXml(TaskRecord task) throws IOException, XmlPullParserException { + if (DEBUG) Slog.d(TAG, "saveToXml: task=" + task); + final XmlSerializer xmlSerializer = new FastXmlSerializer(); + StringWriter stringWriter = new StringWriter(); + xmlSerializer.setOutput(stringWriter); - processNextItem(); + if (DEBUG) { + xmlSerializer.setFeature( + "http://xmlpull.org/v1/doc/features.html#indent-output", true); } - } - private void processNextItem() { - // This part is extracted into a method so that the GC can clearly see the end of the - // scope of the variable 'item'. If this part was in the loop above, the last item - // it processed would always "leak". - // See https://b.corp.google.com/issues/64438652#comment7 - - // If mNextWriteTime, then don't delay between each call to saveToXml(). - final WriteQueueItem item; - synchronized (TaskPersister.this) { - if (mNextWriteTime != FLUSH_QUEUE) { - // The next write we don't have to wait so long. - mNextWriteTime = SystemClock.uptimeMillis() + INTER_WRITE_DELAY_MS; - if (DEBUG) Slog.d(TAG, "Next write time may be in " + - INTER_WRITE_DELAY_MS + " msec. (" + mNextWriteTime + ")"); - } + // save task + xmlSerializer.startDocument(null, true); - while (mWriteQueue.isEmpty()) { - if (mNextWriteTime != 0) { - mNextWriteTime = 0; // idle. - TaskPersister.this.notifyAll(); // wake up flush() if needed. - } - try { - if (DEBUG) Slog.d(TAG, "LazyTaskWriter: waiting indefinitely."); - TaskPersister.this.wait(); - } catch (InterruptedException e) { - } - // Invariant: mNextWriteTime is either FLUSH_QUEUE or PRE_WRITE_DELAY_MS - // from now. - } - item = mWriteQueue.remove(0); + xmlSerializer.startTag(null, TAG_TASK); + task.saveToXml(xmlSerializer); + xmlSerializer.endTag(null, TAG_TASK); + + xmlSerializer.endDocument(); + xmlSerializer.flush(); + + return stringWriter; + } - long now = SystemClock.uptimeMillis(); - if (DEBUG) Slog.d(TAG, "LazyTaskWriter: now=" + now + " mNextWriteTime=" + - mNextWriteTime + " mWriteQueue.size=" + mWriteQueue.size()); - while (now < mNextWriteTime) { + @Override + public void process() { + // Write out one task. + StringWriter stringWriter = null; + TaskRecord task = mTask; + if (DEBUG) Slog.d(TAG, "Writing task=" + task); + synchronized (mService.mGlobalLock) { + if (task.inRecents) { + // Still there. try { - if (DEBUG) Slog.d(TAG, "LazyTaskWriter: waiting " + - (mNextWriteTime - now)); - TaskPersister.this.wait(mNextWriteTime - now); - } catch (InterruptedException e) { + if (DEBUG) Slog.d(TAG, "Saving task=" + task); + stringWriter = saveToXml(task); + } catch (IOException e) { + } catch (XmlPullParserException e) { } - now = SystemClock.uptimeMillis(); } - - // Got something to do. } - - if (item instanceof ImageWriteQueueItem) { - ImageWriteQueueItem imageWriteQueueItem = (ImageWriteQueueItem) item; - final String filePath = imageWriteQueueItem.mFilePath; - if (!createParentDirectory(filePath)) { - Slog.e(TAG, "Error while creating images directory for file: " + filePath); - return; - } - final Bitmap bitmap = imageWriteQueueItem.mImage; - if (DEBUG) Slog.d(TAG, "writing bitmap: filename=" + filePath); - FileOutputStream imageFile = null; + if (stringWriter != null) { + // Write out xml file while not holding mService lock. + FileOutputStream file = null; + AtomicFile atomicFile = null; try { - imageFile = new FileOutputStream(new File(filePath)); - bitmap.compress(Bitmap.CompressFormat.PNG, 100, imageFile); - } catch (Exception e) { - Slog.e(TAG, "saveImage: unable to save " + filePath, e); - } finally { - IoUtils.closeQuietly(imageFile); - } - } else if (item instanceof TaskWriteQueueItem) { - // Write out one task. - StringWriter stringWriter = null; - TaskRecord task = ((TaskWriteQueueItem) item).mTask; - if (DEBUG) Slog.d(TAG, "Writing task=" + task); - synchronized (mService.mGlobalLock) { - if (task.inRecents) { - // Still there. - try { - if (DEBUG) Slog.d(TAG, "Saving task=" + task); - stringWriter = saveToXml(task); - } catch (IOException e) { - } catch (XmlPullParserException e) { - } - } - } - if (stringWriter != null) { - // Write out xml file while not holding mService lock. - FileOutputStream file = null; - AtomicFile atomicFile = null; - try { - atomicFile = new AtomicFile(new File( - getUserTasksDir(task.userId), - String.valueOf(task.taskId) + TASK_FILENAME_SUFFIX)); - file = atomicFile.startWrite(); - file.write(stringWriter.toString().getBytes()); - file.write('\n'); - atomicFile.finishWrite(file); - } catch (IOException e) { - if (file != null) { - atomicFile.failWrite(file); - } - Slog.e(TAG, - "Unable to open " + atomicFile + " for persisting. " + e); + atomicFile = new AtomicFile(new File( + getUserTasksDir(task.userId), + String.valueOf(task.taskId) + TASK_FILENAME_SUFFIX)); + file = atomicFile.startWrite(); + file.write(stringWriter.toString().getBytes()); + file.write('\n'); + atomicFile.finishWrite(file); + } catch (IOException e) { + if (file != null) { + atomicFile.failWrite(file); } + Slog.e(TAG, + "Unable to open " + atomicFile + " for persisting. " + e); } } } + + @Override + public String toString() { + return "TaskWriteQueueItem{task=" + mTask + "}"; + } + } + + private static class ImageWriteQueueItem implements PersisterQueue.WriteQueueItem { + final String mFilePath; + Bitmap mImage; + + ImageWriteQueueItem(String filePath, Bitmap image) { + mFilePath = filePath; + mImage = image; + } + + @Override + public void process() { + final String filePath = mFilePath; + if (!createParentDirectory(filePath)) { + Slog.e(TAG, "Error while creating images directory for file: " + filePath); + return; + } + final Bitmap bitmap = mImage; + if (DEBUG) Slog.d(TAG, "writing bitmap: filename=" + filePath); + FileOutputStream imageFile = null; + try { + imageFile = new FileOutputStream(new File(filePath)); + bitmap.compress(Bitmap.CompressFormat.PNG, 100, imageFile); + } catch (Exception e) { + Slog.e(TAG, "saveImage: unable to save " + filePath, e); + } finally { + IoUtils.closeQuietly(imageFile); + } + } + + @Override + public String toString() { + return "ImageWriteQueueItem{path=" + mFilePath + + ", image=(" + mImage.getWidth() + "x" + mImage.getHeight() + ")}"; + } } } diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java index ef8cb1c07969..05b0d598f878 100644 --- a/services/core/java/com/android/server/am/TaskRecord.java +++ b/services/core/java/com/android/server/am/TaskRecord.java @@ -45,7 +45,6 @@ import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE_VIA_SDK_VER import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER; import static android.provider.Settings.Secure.USER_SETUP_COMPLETE; import static android.view.Display.DEFAULT_DISPLAY; - import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ADD_REMOVE; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_LOCKTASK; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_RECENTS; @@ -75,7 +74,6 @@ import static com.android.server.am.TaskRecordProto.ORIG_ACTIVITY; import static com.android.server.am.TaskRecordProto.REAL_ACTIVITY; import static com.android.server.am.TaskRecordProto.RESIZE_MODE; import static com.android.server.am.TaskRecordProto.STACK_ID; - import static java.lang.Integer.MAX_VALUE; import android.annotation.IntDef; @@ -1686,11 +1684,19 @@ public class TaskRecord extends ConfigurationContainer implements TaskWindowCont // so that the user can not render the task too small to manipulate. We don't need // to do this for the pinned stack as the bounds are controlled by the system. if (!inPinnedWindowingMode()) { + final int defaultMinSizeDp = + mService.mStackSupervisor.mDefaultMinSizeOfResizeableTaskDp; + final ActivityDisplay display = + mService.mStackSupervisor.getActivityDisplay(mStack.mDisplayId); + final float density = + (float) display.getConfiguration().densityDpi / DisplayMetrics.DENSITY_DEFAULT; + final int defaultMinSize = (int) (defaultMinSizeDp * density); + if (minWidth == INVALID_MIN_SIZE) { - minWidth = mService.mStackSupervisor.mDefaultMinSizeOfResizeableTask; + minWidth = defaultMinSize; } if (minHeight == INVALID_MIN_SIZE) { - minHeight = mService.mStackSupervisor.mDefaultMinSizeOfResizeableTask; + minHeight = defaultMinSize; } } final boolean adjustWidth = minWidth > bounds.width(); diff --git a/services/core/java/com/android/server/appbinding/AppBindingConstants.java b/services/core/java/com/android/server/appbinding/AppBindingConstants.java new file mode 100644 index 000000000000..71847694b1d8 --- /dev/null +++ b/services/core/java/com/android/server/appbinding/AppBindingConstants.java @@ -0,0 +1,170 @@ +/* + * 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.appbinding; + +import android.content.Context; +import android.util.KeyValueListParser; +import android.util.Slog; + +import java.io.PrintWriter; +import java.util.concurrent.TimeUnit; + +/** + * Constants that are configurable via the global settings for {@link AppBindingService}. + */ +public class AppBindingConstants { + private static final String TAG = AppBindingService.TAG; + + private static final String SERVICE_RECONNECT_BACKOFF_SEC_KEY = + "service_reconnect_backoff_sec"; + + private static final String SERVICE_RECONNECT_BACKOFF_INCREASE_KEY = + "service_reconnect_backoff_increase"; + + private static final String SERVICE_RECONNECT_MAX_BACKOFF_SEC_KEY = + "service_reconnect_max_backoff_sec"; + + private static final String SERVICE_STABLE_CONNECTION_THRESHOLD_SEC_KEY = + "service_stable_connection_threshold_sec"; + + private static final String SMS_SERVICE_ENABLED_KEY = + "sms_service_enabled"; + + private static final String SMS_APP_BIND_FLAGS_KEY = + "sms_app_bind_flags"; + + public final String sourceSettings; + + /** + * The back-off before re-connecting, when a service binding died, due to the app + * crashing repeatedly. + */ + public final long SERVICE_RECONNECT_BACKOFF_SEC; + + /** + * The exponential back-off increase factor when a binding dies multiple times. + */ + public final double SERVICE_RECONNECT_BACKOFF_INCREASE; + + /** + * The max back-off + */ + public final long SERVICE_RECONNECT_MAX_BACKOFF_SEC; + + /** + * If a connection lasts more than this duration, we reset the re-connect back-off time. + */ + public final long SERVICE_STABLE_CONNECTION_THRESHOLD_SEC; + + /** + * Whether to actually bind to the default SMS app service. (Feature flag) + */ + public final boolean SMS_SERVICE_ENABLED; + + /** + * Extra binding flags for SMS service. + */ + public final int SMS_APP_BIND_FLAGS; + + private AppBindingConstants(String settings) { + sourceSettings = settings; + + final KeyValueListParser parser = new KeyValueListParser(','); + try { + parser.setString(settings); + } catch (IllegalArgumentException e) { + // Failed to parse the settings string, log this and move on + // with defaults. + Slog.e(TAG, "Bad setting: " + settings); + } + + long serviceReconnectBackoffSec = parser.getLong( + SERVICE_RECONNECT_BACKOFF_SEC_KEY, 10); + + double serviceReconnectBackoffIncrease = parser.getFloat( + SERVICE_RECONNECT_BACKOFF_INCREASE_KEY, 2f); + + long serviceReconnectMaxBackoffSec = parser.getLong( + SERVICE_RECONNECT_MAX_BACKOFF_SEC_KEY, TimeUnit.HOURS.toSeconds(1)); + + boolean smsServiceEnabled = parser.getBoolean(SMS_SERVICE_ENABLED_KEY, true); + + int smsAppBindFlags = parser.getInt( + SMS_APP_BIND_FLAGS_KEY, + Context.BIND_NOT_VISIBLE | Context.BIND_FOREGROUND_SERVICE); + + long serviceStableConnectionThresholdSec = parser.getLong( + SERVICE_STABLE_CONNECTION_THRESHOLD_SEC_KEY, TimeUnit.MINUTES.toSeconds(2)); + + // Set minimum: 5 seconds. + serviceReconnectBackoffSec = Math.max(5, serviceReconnectBackoffSec); + + // Set minimum: 1.0. + serviceReconnectBackoffIncrease = + Math.max(1, serviceReconnectBackoffIncrease); + + // Make sure max >= default back off. + serviceReconnectMaxBackoffSec = Math.max(serviceReconnectBackoffSec, + serviceReconnectMaxBackoffSec); + + SERVICE_RECONNECT_BACKOFF_SEC = serviceReconnectBackoffSec; + SERVICE_RECONNECT_BACKOFF_INCREASE = serviceReconnectBackoffIncrease; + SERVICE_RECONNECT_MAX_BACKOFF_SEC = serviceReconnectMaxBackoffSec; + SERVICE_STABLE_CONNECTION_THRESHOLD_SEC = serviceStableConnectionThresholdSec; + SMS_SERVICE_ENABLED = smsServiceEnabled; + SMS_APP_BIND_FLAGS = smsAppBindFlags; + } + + /** + * Create a new instance from a settings string. + */ + public static AppBindingConstants initializeFromString(String settings) { + return new AppBindingConstants(settings); + } + + /** + * dumpsys support. + */ + public void dump(String prefix, PrintWriter pw) { + pw.print(prefix); + pw.print("Constants: "); + pw.println(sourceSettings); + + pw.print(prefix); + pw.print(" SERVICE_RECONNECT_BACKOFF_SEC: "); + pw.println(SERVICE_RECONNECT_BACKOFF_SEC); + + pw.print(prefix); + pw.print(" SERVICE_RECONNECT_BACKOFF_INCREASE: "); + pw.println(SERVICE_RECONNECT_BACKOFF_INCREASE); + + pw.print(prefix); + pw.print(" SERVICE_RECONNECT_MAX_BACKOFF_SEC: "); + pw.println(SERVICE_RECONNECT_MAX_BACKOFF_SEC); + + pw.print(prefix); + pw.print(" SERVICE_STABLE_CONNECTION_THRESHOLD_SEC: "); + pw.println(SERVICE_STABLE_CONNECTION_THRESHOLD_SEC); + + pw.print(prefix); + pw.print(" SMS_SERVICE_ENABLED: "); + pw.println(SMS_SERVICE_ENABLED); + + pw.print(prefix); + pw.print(" SMS_APP_BIND_FLAGS: 0x"); + pw.println(Integer.toHexString(SMS_APP_BIND_FLAGS)); + } +} diff --git a/services/core/java/com/android/server/appbinding/AppBindingService.java b/services/core/java/com/android/server/appbinding/AppBindingService.java index 91b3b21632c6..3131255a61cd 100644 --- a/services/core/java/com/android/server/appbinding/AppBindingService.java +++ b/services/core/java/com/android/server/appbinding/AppBindingService.java @@ -16,26 +16,59 @@ package com.android.server.appbinding; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.app.AppGlobals; +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.ContentResolver; import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; import android.content.pm.IPackageManager; +import android.content.pm.ServiceInfo; +import android.database.ContentObserver; +import android.net.Uri; import android.os.Binder; import android.os.Handler; +import android.os.IBinder; +import android.os.IInterface; +import android.os.UserHandle; +import android.provider.Settings; +import android.provider.Settings.Global; +import android.text.TextUtils; +import android.util.Slog; +import android.util.SparseBooleanArray; +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.BackgroundThread; import com.android.internal.util.DumpUtils; import com.android.server.SystemService; +import com.android.server.am.PersistentConnection; +import com.android.server.appbinding.finders.AppServiceFinder; +import com.android.server.appbinding.finders.SmsAppServiceFinder; import java.io.FileDescriptor; import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.function.Consumer; /** * System server that keeps a binding to an app to keep it always running. + * + * <p>As of android Q, we only use it for the default SMS app. + * + * Relevant tests: + * atest CtsAppBindingHostTestCases + * + * TODO Maybe handle force-stop differently. Right now we just get "binder died" and re-bind + * after a timeout. b/116813347 */ public class AppBindingService extends Binder { public static final String TAG = "AppBindingService"; - private static final boolean DEBUG = false; + public static final boolean DEBUG = false; // DO NOT SUBMIT WITH TRUE private final Object mLock = new Object(); @@ -44,38 +77,492 @@ public class AppBindingService extends Binder { private final Handler mHandler; private final IPackageManager mIPackageManager; + @GuardedBy("mLock") + private AppBindingConstants mConstants; + + @GuardedBy("mLock") + private final SparseBooleanArray mRunningUsers = new SparseBooleanArray(2); + + @GuardedBy("mLock") + private final ArrayList<AppServiceFinder> mApps = new ArrayList<>(); + + @GuardedBy("mLock") + private final ArrayList<AppServiceConnection> mConnections = new ArrayList<>(); + static class Injector { public IPackageManager getIPackageManager() { return AppGlobals.getPackageManager(); } + + public String getGlobalSettingString(ContentResolver resolver, String key) { + return Settings.Global.getString(resolver, key); + } } /** - * System service interacts with this service via this class. + * {@link SystemService} for this service. */ - public static final class Lifecycle extends SystemService { + public static class Lifecycle extends SystemService { final AppBindingService mService; public Lifecycle(Context context) { + this(context, new Injector()); + } + + Lifecycle(Context context, Injector injector) { super(context); - mService = new AppBindingService(new Injector(), context); + mService = new AppBindingService(injector, context); } @Override public void onStart() { publishBinderService(Context.APP_BINDING_SERVICE, mService); } + + @Override + public void onBootPhase(int phase) { + mService.onBootPhase(phase); + } + + @Override + public void onStartUser(int userHandle) { + mService.onStartUser(userHandle); + } + + @Override + public void onUnlockUser(int userId) { + mService.onUnlockUser(userId); + } + + @Override + public void onStopUser(int userHandle) { + mService.onStopUser(userHandle); + } } private AppBindingService(Injector injector, Context context) { mInjector = injector; mContext = context; + mIPackageManager = injector.getIPackageManager(); + mHandler = BackgroundThread.getHandler(); + mApps.add(new SmsAppServiceFinder(context, this::onAppChanged, mHandler)); + + // Initialize with the default value to make it non-null. + mConstants = AppBindingConstants.initializeFromString(""); + } + + private void forAllAppsLocked(Consumer<AppServiceFinder> consumer) { + for (int i = 0; i < mApps.size(); i++) { + consumer.accept(mApps.get(i)); + } + } + + private void onBootPhase(int phase) { + if (DEBUG) { + Slog.d(TAG, "onBootPhase: " + phase); + } + switch (phase) { + case SystemService.PHASE_ACTIVITY_MANAGER_READY: + onPhaseActivityManagerReady(); + break; + case SystemService.PHASE_THIRD_PARTY_APPS_CAN_START: + onPhaseThirdPartyAppsCanStart(); + break; + } + } + + /** + * Handle boot phase PHASE_ACTIVITY_MANAGER_READY. + */ + private void onPhaseActivityManagerReady() { + final IntentFilter packageFilter = new IntentFilter(); + packageFilter.addAction(Intent.ACTION_PACKAGE_ADDED); + packageFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); + packageFilter.addAction(Intent.ACTION_PACKAGE_CHANGED); + packageFilter.addDataScheme("package"); + + packageFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY); + mContext.registerReceiverAsUser(mPackageUserMonitor, UserHandle.ALL, + packageFilter, null, mHandler); + + final IntentFilter userFilter = new IntentFilter(); + userFilter.addAction(Intent.ACTION_USER_REMOVED); + mContext.registerReceiverAsUser(mPackageUserMonitor, UserHandle.ALL, + userFilter, null, mHandler); + + mContext.getContentResolver().registerContentObserver(Settings.Global.getUriFor( + Settings.Global.APP_BINDING_CONSTANTS), false, mSettingsObserver); + + refreshConstants(); + } + + private final ContentObserver mSettingsObserver = new ContentObserver(null) { + @Override + public void onChange(boolean selfChange) { + refreshConstants(); + } + }; + + private void refreshConstants() { + final String newSetting = mInjector.getGlobalSettingString( + mContext.getContentResolver(), Global.APP_BINDING_CONSTANTS); + + synchronized (mLock) { + if (TextUtils.equals(mConstants.sourceSettings, newSetting)) { + return; + } + Slog.i(TAG, "Updating constants with: " + newSetting); + mConstants = AppBindingConstants.initializeFromString(newSetting); + + rebindAllLocked("settings update"); + } + } + + @VisibleForTesting + final BroadcastReceiver mPackageUserMonitor = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (DEBUG) { + Slog.d(TAG, "Broadcast received: " + intent); + } + final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL); + if (userId == UserHandle.USER_NULL) { + Slog.w(TAG, "Intent broadcast does not contain user handle: " + intent); + return; + } + + final String action = intent.getAction(); + + if (Intent.ACTION_USER_REMOVED.equals(action)) { + onUserRemoved(userId); + return; + } + + final Uri intentUri = intent.getData(); + final String packageName = (intentUri != null) ? intentUri.getSchemeSpecificPart() + : null; + if (packageName == null) { + Slog.w(TAG, "Intent broadcast does not contain package name: " + intent); + return; + } + + final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false); + + switch (action) { + case Intent.ACTION_PACKAGE_ADDED: + if (replacing) { + handlePackageAddedReplacing(packageName, userId); + } + break; + case Intent.ACTION_PACKAGE_REMOVED: + if (!replacing) { + handlePackageRemoved(packageName, userId); + } + break; + case Intent.ACTION_PACKAGE_CHANGED: + handlePackageChanged(packageName, userId); + break; + } + } + }; + + /** + * Handle boot phase PHASE_THIRD_PARTY_APPS_CAN_START. + */ + private void onPhaseThirdPartyAppsCanStart() { + synchronized (mLock) { + forAllAppsLocked(AppServiceFinder::startMonitoring); + } + } + + /** User lifecycle callback. */ + private void onStartUser(int userId) { + if (DEBUG) { + Slog.d(TAG, "onStartUser: u" + userId); + } + synchronized (mLock) { + mRunningUsers.append(userId, true); + bindServicesLocked(userId, null, "user start"); + } + } + + /** User lifecycle callback. */ + private void onUnlockUser(int userId) { + if (DEBUG) { + Slog.d(TAG, "onUnlockUser: u" + userId); + } + synchronized (mLock) { + bindServicesLocked(userId, null, "user unlock"); + } + } + + /** User lifecycle callback. */ + private void onStopUser(int userId) { + if (DEBUG) { + Slog.d(TAG, "onStopUser: u" + userId); + } + synchronized (mLock) { + unbindServicesLocked(userId, null, "user stop"); + + mRunningUsers.delete(userId); + } + } + + private void onUserRemoved(int userId) { + if (DEBUG) { + Slog.d(TAG, "onUserRemoved: u" + userId); + } + synchronized (mLock) { + forAllAppsLocked((app) -> app.onUserRemoved(userId)); + + mRunningUsers.delete(userId); + } + } + + /** + * Called when a target package changes; e.g. when the user changes the default SMS app. + */ + private void onAppChanged(AppServiceFinder finder, int userId) { + if (DEBUG) { + Slog.d(TAG, "onAppChanged: u" + userId + " " + finder.getAppDescription()); + } + synchronized (mLock) { + final String reason = finder.getAppDescription() + " changed"; + unbindServicesLocked(userId, finder, reason); + bindServicesLocked(userId, finder, reason); + } + } + + @Nullable + private AppServiceFinder findFinderLocked(int userId, @NonNull String packageName) { + for (int i = 0; i < mApps.size(); i++) { + final AppServiceFinder app = mApps.get(i); + if (packageName.equals(app.getTargetPackage(userId))) { + return app; + } + } + return null; + } + + @Nullable + private AppServiceConnection findConnectionLock( + int userId, @NonNull AppServiceFinder target) { + for (int i = 0; i < mConnections.size(); i++) { + final AppServiceConnection conn = mConnections.get(i); + if ((conn.getUserId() == userId) && (conn.getFinder() == target)) { + return conn; + } + } + return null; + } + + private void handlePackageAddedReplacing(String packageName, int userId) { + if (DEBUG) { + Slog.d(TAG, "handlePackageAddedReplacing: u" + userId + " " + packageName); + } + synchronized (mLock) { + final AppServiceFinder finder = findFinderLocked(userId, packageName); + if (finder != null) { + unbindServicesLocked(userId, finder, "package update"); + bindServicesLocked(userId, finder, "package update"); + } + } + } + + private void handlePackageRemoved(String packageName, int userId) { + if (DEBUG) { + Slog.d(TAG, "handlePackageRemoved: u" + userId + " " + packageName); + } + synchronized (mLock) { + final AppServiceFinder finder = findFinderLocked(userId, packageName); + if (finder != null) { + unbindServicesLocked(userId, finder, "package uninstall"); + } + } + } + + private void handlePackageChanged(String packageName, int userId) { + if (DEBUG) { + Slog.d(TAG, "handlePackageChanged: u" + userId + " " + packageName); + } + synchronized (mLock) { + final AppServiceFinder finder = findFinderLocked(userId, packageName); + if (finder != null) { + unbindServicesLocked(userId, finder, "package changed"); + bindServicesLocked(userId, finder, "package changed"); + } + } + } + + private void rebindAllLocked(String reason) { + for (int i = 0; i < mRunningUsers.size(); i++) { + if (!mRunningUsers.valueAt(i)) { + continue; + } + final int userId = mRunningUsers.keyAt(i); + + unbindServicesLocked(userId, null, reason); + bindServicesLocked(userId, null, reason); + } + } + + private void bindServicesLocked(int userId, @Nullable AppServiceFinder target, + @NonNull String reasonForLog) { + for (int i = 0; i < mApps.size(); i++) { + final AppServiceFinder app = mApps.get(i); + if (target != null && target != app) { + continue; + } + + // Disconnect from existing binding. + final AppServiceConnection existingConn = findConnectionLock(userId, app); + if (existingConn != null) { + unbindServicesLocked(userId, target, reasonForLog); + } + + final ServiceInfo service = app.findService(userId, mIPackageManager, mConstants); + if (service == null) { + continue; + } + if (DEBUG) { + Slog.d(TAG, "bindServicesLocked: u" + userId + " " + app.getAppDescription() + + " binding " + service.getComponentName() + " for " + reasonForLog); + } + final AppServiceConnection conn = + new AppServiceConnection(mContext, userId, mConstants, mHandler, + app, service.getComponentName()); + mConnections.add(conn); + conn.bind(); + } + } + + private void unbindServicesLocked(int userId, @Nullable AppServiceFinder target, + @NonNull String reasonForLog) { + for (int i = mConnections.size() - 1; i >= 0; i--) { + final AppServiceConnection conn = mConnections.get(i); + if ((conn.getUserId() != userId) + || (target != null && conn.getFinder() != target)) { + continue; + } + if (DEBUG) { + Slog.d(TAG, "unbindServicesLocked: u" + userId + + " " + conn.getFinder().getAppDescription() + + " unbinding " + conn.getComponentName() + " for " + reasonForLog); + } + mConnections.remove(i); + conn.unbind(); + } + } + + private static class AppServiceConnection extends PersistentConnection<IInterface> { + private final AppBindingConstants mConstants; + private final AppServiceFinder mFinder; + + AppServiceConnection(Context context, int userId, AppBindingConstants constants, + Handler handler, AppServiceFinder finder, + @NonNull ComponentName componentName) { + super(TAG, context, handler, userId, componentName, + constants.SERVICE_RECONNECT_BACKOFF_SEC, + constants.SERVICE_RECONNECT_BACKOFF_INCREASE, + constants.SERVICE_RECONNECT_MAX_BACKOFF_SEC, + constants.SERVICE_STABLE_CONNECTION_THRESHOLD_SEC); + mFinder = finder; + mConstants = constants; + } + + @Override + protected int getBindFlags() { + return mFinder.getBindFlags(mConstants); + } + + @Override + protected IInterface asInterface(IBinder obj) { + return mFinder.asInterface(obj); + } + + public AppServiceFinder getFinder() { + return mFinder; + } } @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return; + + if (args.length > 0 && "-s".equals(args[0])) { + dumpSimple(pw); + return; + } + + synchronized (mLock) { + mConstants.dump(" ", pw); + + pw.println(); + pw.print(" Running users:"); + for (int i = 0; i < mRunningUsers.size(); i++) { + if (mRunningUsers.valueAt(i)) { + pw.print(" "); + pw.print(mRunningUsers.keyAt(i)); + } + } + + pw.println(); + pw.println(" Connections:"); + for (int i = 0; i < mConnections.size(); i++) { + final AppServiceConnection conn = mConnections.get(i); + pw.print(" App type: "); + pw.print(conn.getFinder().getAppDescription()); + pw.println(); + + conn.dump(" ", pw); + } + if (mConnections.size() == 0) { + pw.println(" None:"); + } + + pw.println(); + pw.println(" Finders:"); + forAllAppsLocked((app) -> app.dump(" ", pw)); + } + } + + /** + * Print simple output for CTS. + */ + private void dumpSimple(PrintWriter pw) { + synchronized (mLock) { + for (int i = 0; i < mConnections.size(); i++) { + final AppServiceConnection conn = mConnections.get(i); + + pw.print("conn,"); + pw.print(conn.getFinder().getAppDescription()); + pw.print(","); + pw.print(conn.getUserId()); + pw.print(","); + pw.print(conn.getComponentName().getPackageName()); + pw.print(","); + pw.print(conn.getComponentName().getClassName()); + pw.print(","); + pw.print(conn.isBound() ? "bound" : "not-bound"); + pw.print(","); + pw.print(conn.isConnected() ? "connected" : "not-connected"); + pw.print(",#con="); + pw.print(conn.getNumConnected()); + pw.print(",#dis="); + pw.print(conn.getNumDisconnected()); + pw.print(",#died="); + pw.print(conn.getNumBindingDied()); + pw.print(",backoff="); + pw.print(conn.getNextBackoffMs()); + pw.println(); + } + forAllAppsLocked((app) -> app.dumpSimple(pw)); + } + } + + AppBindingConstants getConstantsForTest() { + return mConstants; } } diff --git a/services/core/java/com/android/server/appbinding/AppBindingUtils.java b/services/core/java/com/android/server/appbinding/AppBindingUtils.java new file mode 100644 index 000000000000..fcbaecf7e059 --- /dev/null +++ b/services/core/java/com/android/server/appbinding/AppBindingUtils.java @@ -0,0 +1,89 @@ +/* + * 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.appbinding; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Intent; +import android.content.pm.IPackageManager; +import android.content.pm.ParceledListSlice; +import android.content.pm.ResolveInfo; +import android.content.pm.ServiceInfo; +import android.os.RemoteException; +import android.util.Log; + +import java.util.List; + +/** + * Utility class to find a persistent bound service within an app. + */ +public class AppBindingUtils { + private static final String TAG = "AppBindingUtils"; + private AppBindingUtils() { + } + + /** + * Find a service with the action {@code serviceAction} in the package {@code packageName}. + * Returns null in any of the following cases. + * - No service with the action is found. + * - More than 1 service with the action is found. + * - Found service is not protected with the permission {@code servicePermission}. + */ + @Nullable + public static ServiceInfo findService(@NonNull String packageName, int userId, + String serviceAction, String servicePermission, + Class<?> serviceClassForLogging, + IPackageManager ipm, + StringBuilder errorMessage) { + final String simpleClassName = serviceClassForLogging.getSimpleName(); + final Intent intent = new Intent(serviceAction); + intent.setPackage(packageName); + + errorMessage.setLength(0); // Clear it. + try { + final ParceledListSlice<ResolveInfo> pls = ipm + .queryIntentServices(intent, null, /* flags=*/ 0, userId); + if (pls == null || pls.getList().size() == 0) { + errorMessage.append("Service with " + serviceAction + " not found."); + return null; + } + final List<ResolveInfo> list = pls.getList(); + // Note if multiple services are found, that's an error, even if only one of them + // is exported. + if (list.size() > 1) { + errorMessage.append("More than one " + simpleClassName + "'s found in package " + + packageName + ". They'll all be ignored."); + Log.e(TAG, errorMessage.toString()); + return null; + } + final ServiceInfo si = list.get(0).serviceInfo; + + if (!servicePermission.equals(si.permission)) { + errorMessage.append(simpleClassName + " " + + si.getComponentName().flattenToShortString() + + " must be protected with " + servicePermission + + "."); + Log.e(TAG, errorMessage.toString()); + return null; + } + return si; + } catch (RemoteException e) { + // Shouldn't happen + } + return null; + } +} diff --git a/services/core/java/com/android/server/appbinding/finders/AppServiceFinder.java b/services/core/java/com/android/server/appbinding/finders/AppServiceFinder.java new file mode 100644 index 000000000000..a075c50733d5 --- /dev/null +++ b/services/core/java/com/android/server/appbinding/finders/AppServiceFinder.java @@ -0,0 +1,246 @@ +/* + * 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.appbinding.finders; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.content.pm.IPackageManager; +import android.content.pm.ServiceInfo; +import android.os.Handler; +import android.os.IBinder; +import android.os.IInterface; +import android.util.Log; +import android.util.Slog; +import android.util.SparseArray; + +import com.android.internal.annotations.GuardedBy; +import com.android.server.appbinding.AppBindingConstants; +import com.android.server.appbinding.AppBindingService; +import com.android.server.appbinding.AppBindingUtils; + +import java.io.PrintWriter; +import java.util.function.BiConsumer; + +/** + * Baseclss that finds "persistent" service from a type of an app. + * + * @param <TServiceType> Type of the target service class. + * @param <TServiceInterfaceType> Type of the IInterface class used by TServiceType. + */ +public abstract class AppServiceFinder<TServiceType, TServiceInterfaceType extends IInterface> { + protected static final String TAG = AppBindingService.TAG; + protected static final boolean DEBUG = AppBindingService.DEBUG; + + protected final Context mContext; + protected final BiConsumer<AppServiceFinder, Integer> mListener; + protected final Handler mHandler; + + private final Object mLock = new Object(); + + @GuardedBy("mLock") + private final SparseArray<String> mTargetPackages = new SparseArray(4); + + @GuardedBy("mLock") + private final SparseArray<ServiceInfo> mTargetServices = new SparseArray(4); + + @GuardedBy("mLock") + private final SparseArray<String> mLastMessages = new SparseArray(4); + + public AppServiceFinder(Context context, + BiConsumer<AppServiceFinder, Integer> listener, + Handler callbackHandler) { + mContext = context; + mListener = listener; + mHandler = callbackHandler; + } + + /** Whether this service should really be enabled. */ + protected boolean isEnabled(AppBindingConstants constants) { + return true; + } + + /** Human readable description of the type of apps; e.g. [Default SMS app] */ + @NonNull + public abstract String getAppDescription(); + + /** Start monitoring apps. (e.g. Start watching the default SMS app changes.) */ + public void startMonitoring() { + } + + /** Called when a user is removed. */ + public void onUserRemoved(int userId) { + synchronized (mLock) { + mTargetPackages.delete(userId); + mTargetServices.delete(userId); + mLastMessages.delete(userId); + } + } + + /** + * Find the target service from the target app on a given user. + */ + @Nullable + public final ServiceInfo findService(int userId, IPackageManager ipm, + AppBindingConstants constants) { + synchronized (mLock) { + mTargetPackages.put(userId, null); + mTargetServices.put(userId, null); + mLastMessages.put(userId, null); + + if (!isEnabled(constants)) { + final String message = "feature disabled"; + mLastMessages.put(userId, message); + Slog.i(TAG, getAppDescription() + " " + message); + return null; + } + + final String targetPackage = getTargetPackage(userId); + if (DEBUG) { + Slog.d(TAG, getAppDescription() + " package=" + targetPackage); + } + if (targetPackage == null) { + final String message = "Target package not found"; + mLastMessages.put(userId, message); + Slog.w(TAG, getAppDescription() + " u" + userId + " " + message); + return null; + } + mTargetPackages.put(userId, targetPackage); + + final StringBuilder errorMessage = new StringBuilder(); + final ServiceInfo service = AppBindingUtils.findService( + targetPackage, + userId, + getServiceAction(), + getServicePermission(), + getServiceClass(), + ipm, + errorMessage); + + if (service == null) { + final String message = errorMessage.toString(); + mLastMessages.put(userId, message); + if (DEBUG) { + // This log is optional because findService() already did Log.e(). + Slog.w(TAG, getAppDescription() + " package " + targetPackage + " u" + userId + + " " + message); + } + return null; + } + final String error = validateService(service); + if (error != null) { + mLastMessages.put(userId, error); + Log.e(TAG, error); + return null; + } + + final String message = "Valid service found"; + mLastMessages.put(userId, message); + mTargetServices.put(userId, service); + return service; + } + } + + protected abstract Class<TServiceType> getServiceClass(); + + /** + * Convert a binder reference to a service interface type. + */ + public abstract TServiceInterfaceType asInterface(IBinder obj); + + /** + * @return the target package on a given user. + */ + @Nullable + public abstract String getTargetPackage(int userId); + + /** + * @return the intent action that identifies the target service in the target app. + */ + @NonNull + protected abstract String getServiceAction(); + + /** + * @return the permission that the target service must be protected with. + */ + @NonNull + protected abstract String getServicePermission(); + + /** + * Subclass can implement it to decide whether to accept a service (by returning null) or not + * (by returning an error message.) + */ + protected String validateService(ServiceInfo service) { + return null; + } + + /** Return the bind flags for this service. */ + public abstract int getBindFlags(AppBindingConstants constants); + + /** Dumpsys support. */ + public void dump(String prefix, PrintWriter pw) { + pw.print(prefix); + pw.print("App type: "); + pw.print(getAppDescription()); + pw.println(); + + synchronized (mLock) { + for (int i = 0; i < mTargetPackages.size(); i++) { + final int userId = mTargetPackages.keyAt(i); + pw.print(prefix); + pw.print(" User: "); + pw.print(userId); + pw.println(); + + pw.print(prefix); + pw.print(" Package: "); + pw.print(mTargetPackages.get(userId)); + pw.println(); + + pw.print(prefix); + pw.print(" Service: "); + pw.print(mTargetServices.get(userId)); + pw.println(); + + pw.print(prefix); + pw.print(" Message: "); + pw.print(mLastMessages.get(userId)); + pw.println(); + } + } + } + + /** Dumpys support */ + public void dumpSimple(PrintWriter pw) { + synchronized (mLock) { + for (int i = 0; i < mTargetPackages.size(); i++) { + final int userId = mTargetPackages.keyAt(i); + pw.print("finder,"); + pw.print(getAppDescription()); + pw.print(","); + pw.print(userId); + pw.print(","); + pw.print(mTargetPackages.get(userId)); + pw.print(","); + pw.print(mTargetServices.get(userId)); + pw.print(","); + pw.print(mLastMessages.get(userId)); + pw.println(); + } + } + } +} diff --git a/services/core/java/com/android/server/appbinding/finders/SmsAppServiceFinder.java b/services/core/java/com/android/server/appbinding/finders/SmsAppServiceFinder.java new file mode 100644 index 000000000000..fcc28f8e2886 --- /dev/null +++ b/services/core/java/com/android/server/appbinding/finders/SmsAppServiceFinder.java @@ -0,0 +1,128 @@ +/* + * 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.appbinding.finders; + +import static android.provider.Telephony.Sms.Intents.ACTION_DEFAULT_SMS_PACKAGE_CHANGED_INTERNAL; + +import android.Manifest.permission; +import android.app.ISmsAppService; +import android.app.SmsAppService; +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.ServiceInfo; +import android.os.Handler; +import android.os.IBinder; +import android.os.UserHandle; +import android.telephony.TelephonyManager; +import android.text.TextUtils; +import android.util.Slog; + +import com.android.internal.R; +import com.android.internal.telephony.SmsApplication; +import com.android.server.appbinding.AppBindingConstants; + +import java.util.function.BiConsumer; + +/** + * Find the SmsAppService service within the default SMS app. + */ +public class SmsAppServiceFinder extends AppServiceFinder<SmsAppService, ISmsAppService> { + public SmsAppServiceFinder(Context context, + BiConsumer<AppServiceFinder, Integer> listener, + Handler callbackHandler) { + super(context, listener, callbackHandler); + } + + @Override + protected boolean isEnabled(AppBindingConstants constants) { + return constants.SMS_SERVICE_ENABLED + && mContext.getResources().getBoolean(R.bool.config_useSmsAppService); + } + + @Override + public String getAppDescription() { + return "[Default SMS app]"; + } + + @Override + protected Class<SmsAppService> getServiceClass() { + return SmsAppService.class; + } + + @Override + public ISmsAppService asInterface(IBinder obj) { + return ISmsAppService.Stub.asInterface(obj); + } + + @Override + protected String getServiceAction() { + return TelephonyManager.ACTION_SMS_APP_SERVICE; + } + + @Override + protected String getServicePermission() { + return permission.BIND_SMS_APP_SERVICE; + } + + @Override + public String getTargetPackage(int userId) { + final ComponentName cn = SmsApplication.getDefaultSmsApplicationAsUser( + mContext, /* updateIfNeeded= */ true, userId); + String ret = cn == null ? null : cn.getPackageName(); + + if (DEBUG) { + Slog.d(TAG, "getTargetPackage()=" + ret); + } + + return ret; + } + + @Override + public void startMonitoring() { + final IntentFilter filter = new IntentFilter(ACTION_DEFAULT_SMS_PACKAGE_CHANGED_INTERNAL); + mContext.registerReceiverAsUser(mSmsAppChangedWatcher, UserHandle.ALL, filter, + /* permission= */ null, mHandler); + } + + @Override + protected String validateService(ServiceInfo service) { + final String packageName = service.packageName; + final String process = service.processName; + + if (process == null || TextUtils.equals(packageName, process)) { + return "Service must not run on the main process"; + } + return null; // Null means accept this service. + } + + @Override + public int getBindFlags(AppBindingConstants constants) { + return constants.SMS_APP_BIND_FLAGS; + } + + private final BroadcastReceiver mSmsAppChangedWatcher = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (ACTION_DEFAULT_SMS_PACKAGE_CHANGED_INTERNAL.equals(intent.getAction())) { + mListener.accept(SmsAppServiceFinder.this, getSendingUserId()); + } + } + }; +} diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 66c7c437284b..f0ff570e385b 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -4685,7 +4685,9 @@ public class AudioService extends IAudioService.Stub @Override public void setHearingAidDeviceConnectionState(BluetoothDevice device, int state) { - Log.i(TAG, "setBluetoothHearingAidDeviceConnectionState"); + mDeviceLogger.log((new AudioEventLogger.StringEvent( + "setHearingAidDeviceConnectionState state=" + state + + " addr=" + device.getAddress())).printLog(TAG)); setBluetoothHearingAidDeviceConnectionState( device, state, false /* suppressNoisyIntent */, AudioSystem.DEVICE_NONE); @@ -4723,12 +4725,12 @@ public class AudioService extends IAudioService.Stub public int setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(BluetoothDevice device, int state, int profile, boolean suppressNoisyIntent, int a2dpVolume) { - mDeviceLogger.log(new AudioEventLogger.StringEvent( + mDeviceLogger.log((new AudioEventLogger.StringEvent( "setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent state=" + state // only querying address as this is the only readily available field on the device + " addr=" + device.getAddress() + " prof=" + profile + " supprNoisy=" + suppressNoisyIntent - + " vol=" + a2dpVolume)); + + " vol=" + a2dpVolume)).printLog(TAG)); if (mAudioHandler.hasMessages(MSG_SET_A2DP_SINK_CONNECTION_STATE, device)) { mDeviceLogger.log(new AudioEventLogger.StringEvent("A2DP connection state ignored")); return 0; @@ -5888,6 +5890,8 @@ public class AudioService extends IAudioService.Stub } private void onSendBecomingNoisyIntent() { + mDeviceLogger.log((new AudioEventLogger.StringEvent( + "broadcast ACTION_AUDIO_BECOMING_NOISY")).printLog(TAG)); sendBroadcastToAll(new Intent(AudioManager.ACTION_AUDIO_BECOMING_NOISY)); } @@ -7253,7 +7257,7 @@ public class AudioService extends IAudioService.Stub // - wired: logged before onSetWiredDeviceConnectionState() is executed // - A2DP: logged at reception of method call final private AudioEventLogger mDeviceLogger = new AudioEventLogger( - LOG_NB_EVENTS_DEVICE_CONNECTION, "wired/A2DP device connection"); + LOG_NB_EVENTS_DEVICE_CONNECTION, "wired/A2DP/hearing aid device connection"); final private AudioEventLogger mForceUseLogger = new AudioEventLogger( LOG_NB_EVENTS_FORCE_USE, diff --git a/services/core/java/com/android/server/biometrics/AuthenticationClient.java b/services/core/java/com/android/server/biometrics/AuthenticationClient.java index aa4d34ef78b6..8a72a6d215ba 100644 --- a/services/core/java/com/android/server/biometrics/AuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/AuthenticationClient.java @@ -195,7 +195,7 @@ public abstract class AuthenticationClient extends ClientMonitor { // ERROR_CANCELED message. return true; } - if (mBundle != null) { + if (mBundle != null && error != BiometricConstants.BIOMETRIC_ERROR_USER_CANCELED) { try { mStatusBarService.onBiometricError(getErrorString(error, vendorCode)); } catch (RemoteException e) { diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java index 87cf9c434fc0..abc0107f8eaa 100644 --- a/services/core/java/com/android/server/biometrics/BiometricService.java +++ b/services/core/java/com/android/server/biometrics/BiometricService.java @@ -17,13 +17,20 @@ package com.android.server.biometrics; import static android.Manifest.permission.USE_BIOMETRIC; +import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL; import static android.Manifest.permission.USE_FINGERPRINT; +import android.app.ActivityManager; import android.app.AppOpsManager; +import android.app.UserSwitchObserver; +import android.content.ContentResolver; import android.content.Context; import android.content.pm.PackageManager; +import android.database.ContentObserver; import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricConstants; +import android.hardware.biometrics.BiometricSourceType; +import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback; import android.hardware.biometrics.IBiometricPromptReceiver; import android.hardware.biometrics.IBiometricService; import android.hardware.biometrics.IBiometricServiceReceiver; @@ -31,8 +38,10 @@ import android.hardware.face.FaceManager; import android.hardware.face.IFaceService; import android.hardware.fingerprint.FingerprintManager; import android.hardware.fingerprint.IFingerprintService; +import android.net.Uri; import android.os.Binder; import android.os.Bundle; +import android.os.DeadObjectException; import android.os.Handler; import android.os.IBinder; import android.os.Looper; @@ -40,19 +49,21 @@ import android.os.RemoteException; import android.os.ServiceManager; import android.os.UserHandle; import android.provider.Settings; +import android.util.Pair; import android.util.Slog; import com.android.internal.R; import com.android.server.SystemService; import java.util.ArrayList; +import java.util.List; /** * System service that arbitrates the modality for BiometricPrompt to use. */ public class BiometricService extends SystemService { - private static final String TAG = "BiometricPromptService"; + private static final String TAG = "BiometricService"; /** * No biometric methods or nothing has been enrolled. @@ -87,6 +98,8 @@ public class BiometricService extends SystemService { private final boolean mHasFeatureFingerprint; private final boolean mHasFeatureIris; private final boolean mHasFeatureFace; + private final SettingObserver mSettingObserver; + private final List<EnabledOnKeyguardCallback> mEnabledOnKeyguardCallbacks; private IFingerprintService mFingerprintService; private IFaceService mFaceService; @@ -120,6 +133,107 @@ public class BiometricService extends SystemService { } } + private final class SettingObserver extends ContentObserver { + private final Uri FACE_UNLOCK_KEYGUARD_ENABLED = + Settings.Secure.getUriFor(Settings.Secure.FACE_UNLOCK_KEYGUARD_ENABLED); + private final Uri FACE_UNLOCK_APP_ENABLED = + Settings.Secure.getUriFor(Settings.Secure.FACE_UNLOCK_APP_ENABLED); + + private final ContentResolver mContentResolver; + private boolean mFaceEnabledOnKeyguard; + private boolean mFaceEnabledForApps; + + /** + * Creates a content observer. + * + * @param handler The handler to run {@link #onChange} on, or null if none. + */ + SettingObserver(Handler handler) { + super(handler); + mContentResolver = getContext().getContentResolver(); + updateContentObserver(); + } + + void updateContentObserver() { + mContentResolver.unregisterContentObserver(this); + mContentResolver.registerContentObserver(FACE_UNLOCK_KEYGUARD_ENABLED, + false /* notifyForDescendents */, + this /* observer */, + UserHandle.USER_CURRENT); + mContentResolver.registerContentObserver(FACE_UNLOCK_APP_ENABLED, + false /* notifyForDescendents */, + this /* observer */, + UserHandle.USER_CURRENT); + + // Update the value immediately + onChange(true /* selfChange */, FACE_UNLOCK_KEYGUARD_ENABLED); + onChange(true /* selfChange */, FACE_UNLOCK_APP_ENABLED); + } + + @Override + public void onChange(boolean selfChange, Uri uri) { + if (FACE_UNLOCK_KEYGUARD_ENABLED.equals(uri)) { + mFaceEnabledOnKeyguard = + Settings.Secure.getIntForUser( + mContentResolver, + Settings.Secure.FACE_UNLOCK_KEYGUARD_ENABLED, + 1 /* default */, + UserHandle.USER_CURRENT) != 0; + + List<EnabledOnKeyguardCallback> callbacks = mEnabledOnKeyguardCallbacks; + for (int i = 0; i < callbacks.size(); i++) { + callbacks.get(i).notify(BiometricSourceType.FACE, mFaceEnabledOnKeyguard); + } + } else if (FACE_UNLOCK_APP_ENABLED.equals(uri)) { + mFaceEnabledForApps = + Settings.Secure.getIntForUser( + mContentResolver, + Settings.Secure.FACE_UNLOCK_APP_ENABLED, + 1 /* default */, + UserHandle.USER_CURRENT) != 0; + } + } + + boolean getFaceEnabledOnKeyguard() { + return mFaceEnabledOnKeyguard; + } + + boolean getFaceEnabledForApps() { + return mFaceEnabledForApps; + } + } + + private final class EnabledOnKeyguardCallback implements IBinder.DeathRecipient { + + private final IBiometricEnabledOnKeyguardCallback mCallback; + + EnabledOnKeyguardCallback(IBiometricEnabledOnKeyguardCallback callback) { + mCallback = callback; + try { + mCallback.asBinder().linkToDeath(EnabledOnKeyguardCallback.this, 0); + } catch (RemoteException e) { + Slog.w(TAG, "Unable to linkToDeath", e); + } + } + + void notify(BiometricSourceType sourceType, boolean enabled) { + try { + mCallback.onChanged(sourceType, enabled); + } catch (DeadObjectException e) { + Slog.w(TAG, "Death while invoking notify", e); + mEnabledOnKeyguardCallbacks.remove(this); + } catch (RemoteException e) { + Slog.w(TAG, "Failed to invoke onChanged", e); + } + } + + @Override + public void binderDied() { + Slog.e(TAG, "Enabled callback binder died"); + mEnabledOnKeyguardCallbacks.remove(this); + } + } + /** * This is just a pass-through service that wraps Fingerprint, Iris, Face services. This service * should not carry any state. The reality is we need to keep a tiny amount of state so that @@ -131,7 +245,7 @@ public class BiometricService extends SystemService { public void authenticate(IBinder token, long sessionId, int userId, IBiometricServiceReceiver receiver, int flags, String opPackageName, Bundle bundle, IBiometricPromptReceiver dialogReceiver) throws RemoteException { - // Check the USE_BIOMETRIC permission here. In the BiometricService, check do the + // Check the USE_BIOMETRIC permission here. In the BiometricServiceBase, check do the // AppOps and foreground check. checkPermission(); @@ -146,8 +260,38 @@ public class BiometricService extends SystemService { final int callingUserId = UserHandle.getCallingUserId(); mHandler.post(() -> { - mCurrentModality = checkAndGetBiometricModality(receiver); + final Pair<Integer, Integer> result = checkAndGetBiometricModality(); + final int modality = result.first; + final int error = result.second; + + // Check for errors, notify callback, and return + if (error != BiometricConstants.BIOMETRIC_ERROR_NONE) { + try { + final String hardwareUnavailable = + getContext().getString(R.string.biometric_error_hw_unavailable); + switch (error) { + case BiometricConstants.BIOMETRIC_ERROR_HW_NOT_PRESENT: + receiver.onError(0 /* deviceId */, error, hardwareUnavailable); + break; + case BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE: + receiver.onError(0 /* deviceId */, error, hardwareUnavailable); + break; + case BiometricConstants.BIOMETRIC_ERROR_NO_BIOMETRICS: + receiver.onError(0 /* deviceId */, error, + getErrorString(modality, error, 0 /* vendorCode */)); + break; + default: + Slog.e(TAG, "Unhandled error"); + break; + } + } catch (RemoteException e) { + Slog.e(TAG, "Unable to send error", e); + } + return; + } + // Actually start authentication + mCurrentModality = modality; try { // No polymorphism :( if (mCurrentModality == BIOMETRIC_FINGERPRINT) { @@ -157,18 +301,9 @@ public class BiometricService extends SystemService { } else if (mCurrentModality == BIOMETRIC_IRIS) { Slog.w(TAG, "Unsupported modality"); } else if (mCurrentModality == BIOMETRIC_FACE) { - // If the user disabled face for apps, return ERROR_HW_UNAVAILABLE - if (isFaceEnabledForApps()) { - receiver.onError(0 /* deviceId */, - BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE, - FaceManager.getErrorString(getContext(), - BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE, - 0 /* vendorCode */)); - } else { - mFaceService.authenticateFromService(true /* requireConfirmation */, - token, sessionId, userId, receiver, flags, opPackageName, - bundle, dialogReceiver, callingUid, callingPid, callingUserId); - } + mFaceService.authenticateFromService(true /* requireConfirmation */, + token, sessionId, userId, receiver, flags, opPackageName, + bundle, dialogReceiver, callingUid, callingPid, callingUserId); } else { Slog.w(TAG, "Unsupported modality"); } @@ -178,15 +313,6 @@ public class BiometricService extends SystemService { }); } - private boolean isFaceEnabledForApps() { - // TODO: maybe cache this and eliminate duplicated code with KeyguardUpdateMonitor - return Settings.Secure.getIntForUser( - getContext().getContentResolver(), - Settings.Secure.FACE_UNLOCK_APP_ENABLED, - 1 /* default */, - UserHandle.USER_CURRENT) == 0; - } - @Override // Binder call public void cancelAuthentication(IBinder token, String opPackageName) throws RemoteException { @@ -221,33 +347,48 @@ public class BiometricService extends SystemService { } @Override // Binder call - public boolean hasEnrolledBiometrics(String opPackageName) { + public int canAuthenticate(String opPackageName) { checkPermission(); - - if (mAppOps.noteOp(AppOpsManager.OP_USE_BIOMETRIC, Binder.getCallingUid(), - opPackageName) != AppOpsManager.MODE_ALLOWED) { - Slog.w(TAG, "Rejecting " + opPackageName + "; permission denied"); - throw new SecurityException("Permission denied"); - } + checkAppOp(opPackageName, Binder.getCallingUid()); final long ident = Binder.clearCallingIdentity(); - boolean hasEnrolled = false; + int error; try { - // Note: On devices with multi-modal authentication, the selection logic will need - // to be updated. - for (int i = 0; i < mAuthenticators.size(); i++) { - if (mAuthenticators.get(i).getAuthenticator().hasEnrolledTemplates()) { - hasEnrolled = true; - break; - } - } + final Pair<Integer, Integer> result = checkAndGetBiometricModality(); + error = result.second; } finally { Binder.restoreCallingIdentity(ident); } - return hasEnrolled; + return error; + } + + @Override + public void registerEnabledOnKeyguardCallback(IBiometricEnabledOnKeyguardCallback callback) + throws RemoteException { + checkInternalPermission(); + mEnabledOnKeyguardCallbacks.add(new EnabledOnKeyguardCallback(callback)); + try { + callback.onChanged(BiometricSourceType.FACE, + mSettingObserver.getFaceEnabledOnKeyguard()); + } catch (RemoteException e) { + Slog.w(TAG, "Remote exception", e); + } + } + } + + private void checkAppOp(String opPackageName, int callingUid) { + if (mAppOps.noteOp(AppOpsManager.OP_USE_BIOMETRIC, callingUid, + opPackageName) != AppOpsManager.MODE_ALLOWED) { + Slog.w(TAG, "Rejecting " + opPackageName + "; permission denied"); + throw new SecurityException("Permission denied"); } } + private void checkInternalPermission() { + getContext().enforceCallingPermission(USE_BIOMETRIC_INTERNAL, + "Must have MANAGE_BIOMETRIC permission"); + } + private void checkPermission() { if (getContext().checkCallingPermission(USE_FINGERPRINT) != PackageManager.PERMISSION_GRANTED) { @@ -270,11 +411,26 @@ public class BiometricService extends SystemService { mAppOps = context.getSystemService(AppOpsManager.class); mHandler = new Handler(Looper.getMainLooper()); + mEnabledOnKeyguardCallbacks = new ArrayList<>(); + mSettingObserver = new SettingObserver(mHandler); final PackageManager pm = context.getPackageManager(); mHasFeatureFingerprint = pm.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT); mHasFeatureIris = pm.hasSystemFeature(PackageManager.FEATURE_IRIS); mHasFeatureFace = pm.hasSystemFeature(PackageManager.FEATURE_FACE); + + try { + ActivityManager.getService().registerUserSwitchObserver( + new UserSwitchObserver() { + @Override + public void onUserSwitchComplete(int newUserId) { + mSettingObserver.updateContentObserver(); + } + }, BiometricService.class.getName() + ); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to register user switch observer", e); + } } @Override @@ -305,65 +461,91 @@ public class BiometricService extends SystemService { * Checks if there are any available biometrics, and returns the modality. This method also * returns errors through the callback (no biometric feature, hardware not detected, no * templates enrolled, etc). This service must not start authentication if errors are sent. + * + * @Returns A pair [Modality, Error] with Modality being one of {@link #BIOMETRIC_NONE}, + * {@link #BIOMETRIC_FINGERPRINT}, {@link #BIOMETRIC_IRIS}, {@link #BIOMETRIC_FACE} + * and the error containing one of the {@link BiometricConstants} errors. */ - private int checkAndGetBiometricModality(IBiometricServiceReceiver receiver) { + private Pair<Integer, Integer> checkAndGetBiometricModality() { int modality = BIOMETRIC_NONE; - final String hardwareUnavailable = - getContext().getString(R.string.biometric_error_hw_unavailable); // No biometric features, send error if (mAuthenticators.isEmpty()) { - try { - receiver.onError(0 /* deviceId */, - BiometricConstants.BIOMETRIC_ERROR_HW_NOT_PRESENT, - hardwareUnavailable); - } catch (RemoteException e) { - Slog.e(TAG, "Unable to send error", e); - } - return BIOMETRIC_NONE; + return new Pair<>(BIOMETRIC_NONE, BiometricConstants.BIOMETRIC_ERROR_HW_NOT_PRESENT); } - // Find first authenticator that's both detected and enrolled + // Assuming that authenticators are listed in priority-order, the rest of this function + // will go through and find the first authenticator that's available, enrolled, and enabled. + // The tricky part is returning the correct error. Error strings that are modality-specific + // should also respect the priority-order. + + // Find first authenticator that's detected, enrolled, and enabled. boolean isHardwareDetected = false; boolean hasTemplatesEnrolled = false; + boolean enabledForApps = false; + + int firstHwAvailable = BIOMETRIC_NONE; for (int i = 0; i < mAuthenticators.size(); i++) { - int featureId = mAuthenticators.get(i).getType(); + modality = mAuthenticators.get(i).getType(); BiometricAuthenticator authenticator = mAuthenticators.get(i).getAuthenticator(); if (authenticator.isHardwareDetected()) { isHardwareDetected = true; + if (firstHwAvailable == BIOMETRIC_NONE) { + // Store the first one since we want to return the error in correct priority + // order. + firstHwAvailable = modality; + } if (authenticator.hasEnrolledTemplates()) { hasTemplatesEnrolled = true; - modality = featureId; - break; + if (isEnabledForApp(modality)) { + enabledForApps = true; + break; + } } } } // Check error conditions if (!isHardwareDetected) { - try { - receiver.onError(0 /* deviceId */, - BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE, - hardwareUnavailable); - } catch (RemoteException e) { - Slog.e(TAG, "Unable to send error", e); - } - return BIOMETRIC_NONE; + return new Pair<>(BIOMETRIC_NONE, BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE); + } else if (!hasTemplatesEnrolled) { + // Return the modality here so the correct error string can be sent. This error is + // preferred over !enabledForApps + return new Pair<>(firstHwAvailable, BiometricConstants.BIOMETRIC_ERROR_NO_BIOMETRICS); + } else if (!enabledForApps) { + return new Pair<>(BIOMETRIC_NONE, BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE); } - if (!hasTemplatesEnrolled) { - try { - receiver.onError(0 /* deviceId */, - BiometricConstants.BIOMETRIC_ERROR_NO_BIOMETRICS, - FaceManager.getErrorString(getContext(), - BiometricConstants.BIOMETRIC_ERROR_NO_BIOMETRICS, - 0 /* vendorCode */)); - } catch (RemoteException e) { - Slog.e(TAG, "Unable to send error", e); - } - return BIOMETRIC_NONE; + + return new Pair<>(modality, BiometricConstants.BIOMETRIC_ERROR_NONE); + } + + private boolean isEnabledForApp(int modality) { + switch(modality) { + case BIOMETRIC_FINGERPRINT: + return true; + case BIOMETRIC_IRIS: + return true; + case BIOMETRIC_FACE: + return mSettingObserver.getFaceEnabledForApps(); + default: + Slog.w(TAG, "Unsupported modality: " + modality); + return false; } + } - return modality; + private String getErrorString(int type, int error, int vendorCode) { + switch (type) { + case BIOMETRIC_FINGERPRINT: + return FingerprintManager.getErrorString(getContext(), error, vendorCode); + case BIOMETRIC_IRIS: + Slog.w(TAG, "Modality not supported"); + return null; // not supported + case BIOMETRIC_FACE: + return FaceManager.getErrorString(getContext(), error, vendorCode); + default: + Slog.w(TAG, "Unable to get error string for modality: " + type); + return null; + } } private BiometricAuthenticator getAuthenticator(int type) { diff --git a/services/core/java/com/android/server/clipboard/ClipboardService.java b/services/core/java/com/android/server/clipboard/ClipboardService.java index 873a8e314bb1..a769590447ab 100644 --- a/services/core/java/com/android/server/clipboard/ClipboardService.java +++ b/services/core/java/com/android/server/clipboard/ClipboardService.java @@ -629,6 +629,11 @@ public class ClipboardService extends SystemService { if (mAppOps.noteOp(op, callingUid, callingPackage) != AppOpsManager.MODE_ALLOWED) { return false; } + // Shell can access the clipboard for testing purposes. + if (mPm.checkPermission(android.Manifest.permission.READ_CLIPBOARD_IN_BACKGROUND, + callingPackage) == PackageManager.PERMISSION_GRANTED) { + return true; + } // The default IME is always allowed to access the clipboard. String defaultIme = Settings.Secure.getStringForUser(getContext().getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD, UserHandle.getUserId(callingUid)); diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java index 2a80f0e7c291..48082b64ddfc 100644 --- a/services/core/java/com/android/server/connectivity/Vpn.java +++ b/services/core/java/com/android/server/connectivity/Vpn.java @@ -163,8 +163,8 @@ public class Vpn { // TODO: create separate trackers for each unique VPN to support // automated reconnection - private Context mContext; - private NetworkInfo mNetworkInfo; + private final Context mContext; + private final NetworkInfo mNetworkInfo; private String mPackage; private int mOwnerUID; private String mInterface; diff --git a/services/core/java/com/android/server/connectivity/tethering/TetheringConfiguration.java b/services/core/java/com/android/server/connectivity/tethering/TetheringConfiguration.java index 2b1d9196fe18..1e6bb04858a1 100644 --- a/services/core/java/com/android/server/connectivity/tethering/TetheringConfiguration.java +++ b/services/core/java/com/android/server/connectivity/tethering/TetheringConfiguration.java @@ -320,9 +320,8 @@ public class TetheringConfiguration { } private static boolean getEnableLegacyDhcpServer(Context ctx) { - // TODO: make the default false (0) and update javadoc in Settings.java final ContentResolver cr = ctx.getContentResolver(); - final int intVal = Settings.Global.getInt(cr, TETHER_ENABLE_LEGACY_DHCP_SERVER, 1); + final int intVal = Settings.Global.getInt(cr, TETHER_ENABLE_LEGACY_DHCP_SERVER, 0); return intVal != 0; } diff --git a/services/core/java/com/android/server/display/DisplayDevice.java b/services/core/java/com/android/server/display/DisplayDevice.java index 240592528565..7bfe9ce7017c 100644 --- a/services/core/java/com/android/server/display/DisplayDevice.java +++ b/services/core/java/com/android/server/display/DisplayDevice.java @@ -19,7 +19,6 @@ package com.android.server.display; import android.graphics.Rect; import android.hardware.display.DisplayViewport; import android.os.IBinder; -import android.view.Display; import android.view.Surface; import android.view.SurfaceControl; @@ -224,6 +223,8 @@ abstract class DisplayDevice { DisplayDeviceInfo info = getDisplayDeviceInfoLocked(); viewport.deviceWidth = isRotated ? info.height : info.width; viewport.deviceHeight = isRotated ? info.width : info.height; + + viewport.uniqueId = info.uniqueId; } /** diff --git a/services/core/java/com/android/server/display/DisplayDeviceInfo.java b/services/core/java/com/android/server/display/DisplayDeviceInfo.java index 512e85192d36..c51dc52b0515 100644 --- a/services/core/java/com/android/server/display/DisplayDeviceInfo.java +++ b/services/core/java/com/android/server/display/DisplayDeviceInfo.java @@ -110,6 +110,13 @@ final class DisplayDeviceInfo { public static final int FLAG_MASK_DISPLAY_CUTOUT = 1 << 11; /** + * Flag: This flag identifies secondary displays that should show system decorations, such as + * status bar, navigation bar, home activity or IME. + * @hide + */ + public static final int FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS = 1 << 12; + + /** * Touch attachment: Display does not receive touch. */ public static final int TOUCH_NONE = 0; diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index 0eff7f57895b..e70460aef3e0 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -17,15 +17,14 @@ package com.android.server.display; import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR; +import static android.hardware.display.DisplayManager + .VIRTUAL_DISPLAY_FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD; import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY; import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC; import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_SECURE; -import static android.hardware.display.DisplayManager - .VIRTUAL_DISPLAY_FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD; - -import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.util.DumpUtils; -import com.android.internal.util.IndentingPrintWriter; +import static android.hardware.display.DisplayViewport.VIEWPORT_EXTERNAL; +import static android.hardware.display.DisplayViewport.VIEWPORT_INTERNAL; +import static android.hardware.display.DisplayViewport.VIEWPORT_VIRTUAL; import android.Manifest; import android.annotation.NonNull; @@ -45,8 +44,8 @@ import android.hardware.display.BrightnessConfiguration; import android.hardware.display.Curve; import android.hardware.display.DisplayManagerGlobal; import android.hardware.display.DisplayManagerInternal; -import android.hardware.display.DisplayViewport; import android.hardware.display.DisplayManagerInternal.DisplayTransactionListener; +import android.hardware.display.DisplayViewport; import android.hardware.display.IDisplayManager; import android.hardware.display.IDisplayManagerCallback; import android.hardware.display.IVirtualDisplayCallback; @@ -83,14 +82,17 @@ import android.view.DisplayInfo; import android.view.Surface; import android.view.SurfaceControl; -import com.android.internal.util.Preconditions; +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.DumpUtils; +import com.android.internal.util.IndentingPrintWriter; import com.android.server.AnimationThread; import com.android.server.DisplayThread; import com.android.server.LocalServices; import com.android.server.SystemService; import com.android.server.UiThread; -import com.android.server.wm.WindowManagerInternal; import com.android.server.wm.SurfaceAnimationThread; +import com.android.server.wm.WindowManagerInternal; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -256,9 +258,8 @@ public final class DisplayManagerService extends SystemService { // Viewports of the default display and the display that should receive touch // input from an external source. Used by the input system. - private final DisplayViewport mDefaultViewport = new DisplayViewport(); - private final DisplayViewport mExternalTouchViewport = new DisplayViewport(); - private final ArrayList<DisplayViewport> mVirtualTouchViewports = new ArrayList<>(); + @GuardedBy("mSyncRoot") + private final ArrayList<DisplayViewport> mViewports = new ArrayList<>(); // Persistent data store for all internal settings maintained by the display manager service. private final PersistentDataStore mPersistentDataStore = new PersistentDataStore(); @@ -272,9 +273,7 @@ public final class DisplayManagerService extends SystemService { // Temporary viewports, used when sending new viewport information to the // input system. May be used outside of the lock but only on the handler thread. - private final DisplayViewport mTempDefaultViewport = new DisplayViewport(); - private final DisplayViewport mTempExternalTouchViewport = new DisplayViewport(); - private final ArrayList<DisplayViewport> mTempVirtualTouchViewports = new ArrayList<>(); + private final ArrayList<DisplayViewport> mTempViewports = new ArrayList<>(); // The default color mode for default displays. Overrides the usual // Display.Display.COLOR_MODE_DEFAULT for displays with the @@ -1255,9 +1254,7 @@ public final class DisplayManagerService extends SystemService { } private void clearViewportsLocked() { - mDefaultViewport.valid = false; - mExternalTouchViewport.valid = false; - mVirtualTouchViewports.clear(); + mViewports.clear(); } private void configureDisplayLocked(SurfaceControl.Transaction t, DisplayDevice device) { @@ -1287,40 +1284,89 @@ public final class DisplayManagerService extends SystemService { } display.configureDisplayLocked(t, device, info.state == Display.STATE_OFF); - // Update the viewports if needed. - if (!mDefaultViewport.valid - && (info.flags & DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY) != 0) { - setViewportLocked(mDefaultViewport, display, device); + // Update the corresponding viewport. + DisplayViewport internalViewport = getInternalViewportLocked(); + if ((info.flags & DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY) != 0) { + populateViewportLocked(internalViewport, display, device); } - if (!mExternalTouchViewport.valid - && info.touch == DisplayDeviceInfo.TOUCH_EXTERNAL) { - setViewportLocked(mExternalTouchViewport, display, device); + DisplayViewport externalViewport = getExternalViewportLocked(); + if (info.touch == DisplayDeviceInfo.TOUCH_EXTERNAL) { + populateViewportLocked(externalViewport, display, device); + } else if (!externalViewport.valid) { + // TODO (b/116850516) move this logic into InputReader + externalViewport.copyFrom(internalViewport); + externalViewport.type = DisplayViewport.VIEWPORT_EXTERNAL; } if (info.touch == DisplayDeviceInfo.TOUCH_VIRTUAL && !TextUtils.isEmpty(info.uniqueId)) { - final DisplayViewport viewport = getVirtualTouchViewportLocked(info.uniqueId); - setViewportLocked(viewport, display, device); + final DisplayViewport viewport = getVirtualViewportLocked(info.uniqueId); + populateViewportLocked(viewport, display, device); } } - /** Gets the virtual device viewport or creates it if not yet created. */ - private DisplayViewport getVirtualTouchViewportLocked(@NonNull String uniqueId) { + /** Get the virtual device viewport that has the specified uniqueId. + * If such viewport does not exist, create it. */ + private DisplayViewport getVirtualViewportLocked(@NonNull String uniqueId) { DisplayViewport viewport; - final int count = mVirtualTouchViewports.size(); + final int count = mViewports.size(); for (int i = 0; i < count; i++) { - viewport = mVirtualTouchViewports.get(i); + viewport = mViewports.get(i); if (uniqueId.equals(viewport.uniqueId)) { + if (viewport.type != VIEWPORT_VIRTUAL) { + Slog.wtf(TAG, "Found a viewport with uniqueId '" + uniqueId + + "' but it has type " + DisplayViewport.typeToString(viewport.type) + + " (expected VIRTUAL)"); + continue; + } return viewport; } } viewport = new DisplayViewport(); viewport.uniqueId = uniqueId; - mVirtualTouchViewports.add(viewport); + viewport.type = VIEWPORT_VIRTUAL; + mViewports.add(viewport); return viewport; } - private static void setViewportLocked(DisplayViewport viewport, + private DisplayViewport getInternalViewportLocked() { + return getViewportByTypeLocked(VIEWPORT_INTERNAL); + } + + private DisplayViewport getExternalViewportLocked() { + return getViewportByTypeLocked(VIEWPORT_EXTERNAL); + } + + /** + * Get internal or external viewport. Create it if does not currently exist. + * @param viewportType - either INTERNAL or EXTERNAL + * @return the viewport with the requested type + */ + private DisplayViewport getViewportByTypeLocked(int viewportType) { + // Only allow a single INTERNAL or EXTERNAL viewport, which makes this function possible. + // TODO (b/116824030) allow multiple EXTERNAL viewports and remove this function. + // Creates the viewport if none exists. + if (viewportType != VIEWPORT_INTERNAL && viewportType != VIEWPORT_EXTERNAL) { + Slog.wtf(TAG, "Cannot call getViewportByTypeLocked for type " + + DisplayViewport.typeToString(viewportType)); + return null; + } + DisplayViewport viewport; + final int count = mViewports.size(); + for (int i = 0; i < count; i++) { + viewport = mViewports.get(i); + if (viewport.type == viewportType) { + return viewport; + } + } + + viewport = new DisplayViewport(); + viewport.type = viewportType; + mViewports.add(viewport); + return viewport; + } + + private static void populateViewportLocked(DisplayViewport viewport, LogicalDisplay display, DisplayDevice device) { viewport.valid = true; viewport.displayId = display.getDisplayIdLocked(); @@ -1400,9 +1446,7 @@ public final class DisplayManagerService extends SystemService { pw.println(" mPendingTraversal=" + mPendingTraversal); pw.println(" mGlobalDisplayState=" + Display.stateToString(mGlobalDisplayState)); pw.println(" mNextNonDefaultDisplayId=" + mNextNonDefaultDisplayId); - pw.println(" mDefaultViewport=" + mDefaultViewport); - pw.println(" mExternalTouchViewport=" + mExternalTouchViewport); - pw.println(" mVirtualTouchViewports=" + mVirtualTouchViewports); + pw.println(" mViewports=" + mViewports); pw.println(" mDefaultDisplayDefaultColorMode=" + mDefaultDisplayDefaultColorMode); pw.println(" mSingleDisplayDemoMode=" + mSingleDisplayDemoMode); pw.println(" mWifiDisplayScanRequestCount=" + mWifiDisplayScanRequestCount); @@ -1522,18 +1566,19 @@ public final class DisplayManagerService extends SystemService { break; case MSG_UPDATE_VIEWPORT: { + final boolean changed; synchronized (mSyncRoot) { - mTempDefaultViewport.copyFrom(mDefaultViewport); - mTempExternalTouchViewport.copyFrom(mExternalTouchViewport); - if (!mTempVirtualTouchViewports.equals(mVirtualTouchViewports)) { - mTempVirtualTouchViewports.clear(); - for (DisplayViewport d : mVirtualTouchViewports) { - mTempVirtualTouchViewports.add(d.makeCopy()); - } + changed = !mTempViewports.equals(mViewports); + if (changed) { + mTempViewports.clear(); + for (DisplayViewport d : mViewports) { + mTempViewports.add(d.makeCopy()); + } } } - mInputManagerInternal.setDisplayViewports(mTempDefaultViewport, - mTempExternalTouchViewport, mTempVirtualTouchViewports); + if (changed) { + mInputManagerInternal.setDisplayViewports(mTempViewports); + } break; } diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java index 5b7c5205ce3a..6f726e605d5e 100644 --- a/services/core/java/com/android/server/display/LogicalDisplay.java +++ b/services/core/java/com/android/server/display/LogicalDisplay.java @@ -18,7 +18,6 @@ package com.android.server.display; import android.graphics.Rect; import android.hardware.display.DisplayManagerInternal; -import android.os.SystemProperties; import android.view.Display; import android.view.DisplayInfo; import android.view.Surface; @@ -256,6 +255,9 @@ final class LogicalDisplay { if ((deviceInfo.flags & DisplayDeviceInfo.FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD) != 0) { mBaseDisplayInfo.flags |= Display.FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD; } + if ((deviceInfo.flags & DisplayDeviceInfo.FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS) != 0) { + mBaseDisplayInfo.flags |= Display.FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS; + } Rect maskingInsets = getMaskingInsets(deviceInfo); int maskedWidth = deviceInfo.width - maskingInsets.left - maskingInsets.right; int maskedHeight = deviceInfo.height - maskingInsets.top - maskingInsets.bottom; diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java index 6111c23f252c..5aa585fcad75 100644 --- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java +++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java @@ -17,15 +17,14 @@ package com.android.server.display; import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR; -import static android.hardware.display.DisplayManager - .VIRTUAL_DISPLAY_FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD; +import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD; +import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL; import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION; import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC; +import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT; import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_SECURE; +import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS; import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH; -import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT; -import static android.hardware.display.DisplayManager - .VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL; import android.content.Context; import android.hardware.display.IVirtualDisplayCallback; @@ -33,10 +32,10 @@ import android.media.projection.IMediaProjection; import android.media.projection.IMediaProjectionCallback; import android.os.Handler; import android.os.IBinder; -import android.os.SystemProperties; import android.os.IBinder.DeathRecipient; import android.os.Message; import android.os.RemoteException; +import android.os.SystemProperties; import android.util.ArrayMap; import android.util.Slog; import android.view.Display; @@ -60,7 +59,8 @@ public class VirtualDisplayAdapter extends DisplayAdapter { static final boolean DEBUG = false; // Unique id prefix for virtual displays - private static final String UNIQUE_ID_PREFIX = "virtual:"; + @VisibleForTesting + static final String UNIQUE_ID_PREFIX = "virtual:"; private final ArrayMap<IBinder, VirtualDisplayDevice> mVirtualDisplayDevices = new ArrayMap<IBinder, VirtualDisplayDevice>(); @@ -366,7 +366,10 @@ public class VirtualDisplayAdapter extends DisplayAdapter { mInfo.flags |= DisplayDeviceInfo.FLAG_ROTATES_WITH_CONTENT; } if ((mFlags & VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL) != 0) { - mInfo.flags |= DisplayDeviceInfo.FLAG_DESTROY_CONTENT_ON_REMOVAL; + mInfo.flags |= DisplayDeviceInfo.FLAG_DESTROY_CONTENT_ON_REMOVAL; + } + if ((mFlags & VIRTUAL_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS) != 0) { + mInfo.flags |= DisplayDeviceInfo.FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS; } mInfo.type = Display.TYPE_VIRTUAL; diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java index 0f2843917420..4913e8bda6ad 100644 --- a/services/core/java/com/android/server/input/InputManagerService.java +++ b/services/core/java/com/android/server/input/InputManagerService.java @@ -16,9 +16,6 @@ package com.android.server.input; -import static android.hardware.display.DisplayViewport.VIEWPORT_INTERNAL; -import static android.hardware.display.DisplayViewport.VIEWPORT_EXTERNAL; - import android.annotation.NonNull; import android.app.IInputForwarder; import android.app.Notification; @@ -188,13 +185,8 @@ public class InputManagerService extends IInputManager.Stub private static native long nativeInit(InputManagerService service, Context context, MessageQueue messageQueue); private static native void nativeStart(long ptr); - private static native void nativeSetVirtualDisplayViewports(long ptr, + private static native void nativeSetDisplayViewports(long ptr, DisplayViewport[] viewports); - private static native void nativeSetDisplayViewport(long ptr, int viewportType, - int displayId, int rotation, - int logicalLeft, int logicalTop, int logicalRight, int logicalBottom, - int physicalLeft, int physicalTop, int physicalRight, int physicalBottom, - int deviceWidth, int deviceHeight, String uniqueId); private static native int nativeGetScanCodeState(long ptr, int deviceId, int sourceMask, int scanCode); @@ -217,7 +209,8 @@ public class InputManagerService extends IInputManager.Stub private static native void nativeSetInputDispatchMode(long ptr, boolean enabled, boolean frozen); private static native void nativeSetSystemUiVisibility(long ptr, int visibility); private static native void nativeSetFocusedApplication(long ptr, - InputApplicationHandle application); + int displayId, InputApplicationHandle application); + private static native void nativeSetFocusedDisplay(long ptr, int displayId); private static native boolean nativeTransferTouchFocus(long ptr, InputChannel fromChannel, InputChannel toChannel); private static native void nativeSetPointerSpeed(long ptr, int speed); @@ -409,31 +402,8 @@ public class InputManagerService extends IInputManager.Stub nativeReloadDeviceAliases(mPtr); } - private void setDisplayViewportsInternal(DisplayViewport defaultViewport, - DisplayViewport externalTouchViewport, - List<DisplayViewport> virtualTouchViewports) { - if (defaultViewport.valid) { - setDisplayViewport(VIEWPORT_INTERNAL, defaultViewport); - } - - if (externalTouchViewport.valid) { - setDisplayViewport(VIEWPORT_EXTERNAL, externalTouchViewport); - } else if (defaultViewport.valid) { - setDisplayViewport(VIEWPORT_EXTERNAL, defaultViewport); - } - - nativeSetVirtualDisplayViewports(mPtr, - virtualTouchViewports.toArray(new DisplayViewport[0])); - } - - private void setDisplayViewport(int viewportType, DisplayViewport viewport) { - nativeSetDisplayViewport(mPtr, viewportType, - viewport.displayId, viewport.orientation, - viewport.logicalFrame.left, viewport.logicalFrame.top, - viewport.logicalFrame.right, viewport.logicalFrame.bottom, - viewport.physicalFrame.left, viewport.physicalFrame.top, - viewport.physicalFrame.right, viewport.physicalFrame.bottom, - viewport.deviceWidth, viewport.deviceHeight, viewport.uniqueId); + private void setDisplayViewportsInternal(List<DisplayViewport> viewports) { + nativeSetDisplayViewports(mPtr, viewports.toArray(new DisplayViewport[0])); } /** @@ -1462,21 +1432,27 @@ public class InputManagerService extends IInputManager.Stub } } - public void setInputWindows(InputWindowHandle[] windowHandles, - InputWindowHandle focusedWindowHandle, int displayId) { + public void setInputWindows(InputWindowHandle[] windowHandles, int displayId) { + nativeSetInputWindows(mPtr, windowHandles, displayId); + } + + public void setFocusedApplication(int displayId, InputApplicationHandle application) { + nativeSetFocusedApplication(mPtr, displayId, application); + } + + public void setFocusedWindow(InputWindowHandle focusedWindowHandle) { final IWindow newFocusedWindow = focusedWindowHandle != null ? focusedWindowHandle.clientWindow : null; if (mFocusedWindow != newFocusedWindow) { - mFocusedWindow = newFocusedWindow; if (mFocusedWindowHasCapture) { setPointerCapture(false); } + mFocusedWindow = newFocusedWindow; } - nativeSetInputWindows(mPtr, windowHandles, displayId); } - public void setFocusedApplication(InputApplicationHandle application) { - nativeSetFocusedApplication(mPtr, application); + public void setFocusedDisplay(int displayId) { + nativeSetFocusedDisplay(mPtr, displayId); } @Override @@ -1491,16 +1467,18 @@ public class InputManagerService extends IInputManager.Stub return; } setPointerCapture(enabled); - try { - mFocusedWindow.dispatchPointerCaptureChanged(enabled); - } catch (RemoteException ex) { - /* ignore */ - } } private void setPointerCapture(boolean enabled) { - mFocusedWindowHasCapture = enabled; - nativeSetPointerCapture(mPtr, enabled); + if (mFocusedWindowHasCapture != enabled) { + mFocusedWindowHasCapture = enabled; + try { + mFocusedWindow.dispatchPointerCaptureChanged(enabled); + } catch (RemoteException ex) { + /* ignore */ + } + nativeSetPointerCapture(mPtr, enabled); + } } public void setInputDispatchMode(boolean enabled, boolean frozen) { @@ -2203,11 +2181,8 @@ public class InputManagerService extends IInputManager.Stub private final class LocalService extends InputManagerInternal { @Override - public void setDisplayViewports(DisplayViewport defaultViewport, - DisplayViewport externalTouchViewport, - List<DisplayViewport> virtualTouchViewports) { - setDisplayViewportsInternal(defaultViewport, externalTouchViewport, - virtualTouchViewports); + public void setDisplayViewports(List<DisplayViewport> viewports) { + setDisplayViewportsInternal(viewports); } @Override diff --git a/services/core/java/com/android/server/net/NetworkStatsService.java b/services/core/java/com/android/server/net/NetworkStatsService.java index 60e9eaab5721..3f031699bd7a 100644 --- a/services/core/java/com/android/server/net/NetworkStatsService.java +++ b/services/core/java/com/android/server/net/NetworkStatsService.java @@ -159,8 +159,10 @@ public class NetworkStatsService extends INetworkStatsService.Stub { static final boolean LOGD = Log.isLoggable(TAG, Log.DEBUG); static final boolean LOGV = Log.isLoggable(TAG, Log.VERBOSE); + // Perform polling and persist all (FLAG_PERSIST_ALL). private static final int MSG_PERFORM_POLL = 1; - private static final int MSG_REGISTER_GLOBAL_ALERT = 2; + // Perform polling, persist network, and register the global alert again. + private static final int MSG_PERFORM_POLL_REGISTER_ALERT = 2; /** Flags to control detail level of poll event. */ private static final int FLAG_PERSIST_NETWORK = 0x1; @@ -168,6 +170,14 @@ public class NetworkStatsService extends INetworkStatsService.Stub { private static final int FLAG_PERSIST_ALL = FLAG_PERSIST_NETWORK | FLAG_PERSIST_UID; private static final int FLAG_PERSIST_FORCE = 0x100; + /** + * When global alert quota is high, wait for this delay before processing each polling, + * and do not schedule further polls once there is already one queued. + * This avoids firing the global alert too often on devices with high transfer speeds and + * high quota. + */ + private static final int PERFORM_POLL_DELAY_MS = 1000; + private static final String TAG_NETSTATS_ERROR = "netstats_error"; private final Context mContext; @@ -920,7 +930,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { } // Create baseline stats - mHandler.sendMessage(mHandler.obtainMessage(MSG_PERFORM_POLL, FLAG_PERSIST_ALL)); + mHandler.sendMessage(mHandler.obtainMessage(MSG_PERFORM_POLL)); return normalizedRequest; } @@ -1055,13 +1065,12 @@ public class NetworkStatsService extends INetworkStatsService.Stub { mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); if (LIMIT_GLOBAL_ALERT.equals(limitName)) { - // kick off background poll to collect network stats; UID stats - // are handled during normal polling interval. - final int flags = FLAG_PERSIST_NETWORK; - mHandler.obtainMessage(MSG_PERFORM_POLL, flags, 0).sendToTarget(); - - // re-arm global alert for next update - mHandler.obtainMessage(MSG_REGISTER_GLOBAL_ALERT).sendToTarget(); + // kick off background poll to collect network stats unless there is already + // such a call pending; UID stats are handled during normal polling interval. + if (!mHandler.hasMessages(MSG_PERFORM_POLL_REGISTER_ALERT)) { + mHandler.sendEmptyMessageDelayed(MSG_PERFORM_POLL_REGISTER_ALERT, + PERFORM_POLL_DELAY_MS); + } } } }; @@ -1673,11 +1682,11 @@ public class NetworkStatsService extends INetworkStatsService.Stub { public boolean handleMessage(Message msg) { switch (msg.what) { case MSG_PERFORM_POLL: { - final int flags = msg.arg1; - mService.performPoll(flags); + mService.performPoll(FLAG_PERSIST_ALL); return true; } - case MSG_REGISTER_GLOBAL_ALERT: { + case MSG_PERFORM_POLL_REGISTER_ALERT: { + mService.performPoll(FLAG_PERSIST_NETWORK); mService.registerGlobalAlert(); return true; } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index cade07cfb821..f9d49d7ce0d1 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -16,6 +16,7 @@ package com.android.server.notification; +import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND; import static android.app.Notification.FLAG_FOREGROUND_SERVICE; import static android.app.NotificationManager.ACTION_APP_BLOCK_STATE_CHANGED; import static android.app.NotificationManager.ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED; @@ -2001,7 +2002,8 @@ public class NotificationManagerService extends SystemService { return mInternalService; } - private final IBinder mService = new INotificationManager.Stub() { + @VisibleForTesting + final IBinder mService = new INotificationManager.Stub() { // Toasts // ============================================================================ @@ -2015,21 +2017,30 @@ public class NotificationManagerService extends SystemService { } if (pkg == null || callback == null) { - Slog.e(TAG, "Not doing toast. pkg=" + pkg + " callback=" + callback); + Slog.e(TAG, "Not enqueuing toast. pkg=" + pkg + " callback=" + callback); return ; } - final boolean isSystemToast = isCallerSystemOrPhone() || ("android".equals(pkg)); - final boolean isPackageSuspended = - isPackageSuspendedForUser(pkg, Binder.getCallingUid()); - - if (ENABLE_BLOCKED_TOASTS && !isSystemToast && - (!areNotificationsEnabledForPackage(pkg, Binder.getCallingUid()) - || isPackageSuspended)) { - Slog.e(TAG, "Suppressing toast from package " + pkg - + (isPackageSuspended - ? " due to package suspended by administrator." - : " by user request.")); - return; + + final int callingUid = Binder.getCallingUid(); + final boolean isSystemToast = isCallerSystemOrPhone() + || PackageManagerService.PLATFORM_PACKAGE_NAME.equals(pkg); + final boolean isPackageSuspended = isPackageSuspendedForUser(pkg, callingUid); + final boolean notificationsDisabledForPackage = !areNotificationsEnabledForPackage(pkg, + callingUid); + + long callingIdentity = Binder.clearCallingIdentity(); + try { + final boolean appIsForeground = mActivityManager.getUidImportance(callingUid) + == IMPORTANCE_FOREGROUND; + if (ENABLE_BLOCKED_TOASTS && !isSystemToast && ((notificationsDisabledForPackage + && !appIsForeground) || isPackageSuspended)) { + Slog.e(TAG, "Suppressing toast from package " + pkg + + (isPackageSuspended ? " due to package suspended." + : " by user request.")); + return; + } + } finally { + Binder.restoreCallingIdentity(callingIdentity); } synchronized (mToastQueue) { @@ -4289,7 +4300,8 @@ public class NotificationManagerService extends SystemService { } // posted from app A on behalf of app A if (isCallerSameApp(targetPkg, callingUid, userId) - && TextUtils.equals(callingPkg, targetPkg)) { + && (TextUtils.equals(callingPkg, targetPkg) + || isCallerSameApp(callingPkg, callingUid, userId))) { return callingUid; } @@ -4306,7 +4318,8 @@ public class NotificationManagerService extends SystemService { return targetUid; } - throw new SecurityException("Caller " + callingUid + " cannot post for pkg " + targetPkg); + throw new SecurityException("Caller " + callingPkg + ":" + callingUid + + " cannot post for pkg " + targetPkg + " in user " + userId); } /** @@ -4326,7 +4339,7 @@ public class NotificationManagerService extends SystemService { if (!isSystemNotification && !isNotificationFromListener) { synchronized (mNotificationLock) { if (mNotificationsByKey.get(r.sbn.getKey()) == null - && isCallerInstantApp(pkg, callingUid, r.getUserId())) { + && isCallerInstantApp(pkg, Binder.getCallingUid(), userId)) { // Ephemeral apps have some special constraints for notifications. // They are not allowed to create new notifications however they are allowed to // update notifications created by the system (e.g. a foreground service @@ -4732,7 +4745,8 @@ public class NotificationManagerService extends SystemService { if (notification.getSmallIcon() != null) { StatusBarNotification oldSbn = (old != null) ? old.sbn : null; mListeners.notifyPostedLocked(r, old); - if (oldSbn == null || !Objects.equals(oldSbn.getGroup(), n.getGroup())) { + if ((oldSbn == null || !Objects.equals(oldSbn.getGroup(), n.getGroup())) + && !isCritical(r)) { mHandler.post(new Runnable() { @Override public void run() { @@ -4902,6 +4916,19 @@ public class NotificationManagerService extends SystemService { } /** + * Check if the notification is classified as critical. + * + * @param record the record to test for criticality + * @return {@code true} if notification is considered critical + * + * @see CriticalNotificationExtractor for criteria + */ + private boolean isCritical(NotificationRecord record) { + // 0 is the most critical + return record.getCriticality() < CriticalNotificationExtractor.NORMAL; + } + + /** * Keeps the last 5 packages that have notified, by user. */ @GuardedBy("mNotificationLock") diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index 8f2833f68192..006ea75ea160 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -21,6 +21,7 @@ import static android.content.pm.PackageManager.INSTALL_FAILED_CONTAINER_ERROR; import static android.content.pm.PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE; import static android.content.pm.PackageManager.INSTALL_FAILED_INTERNAL_ERROR; import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_APK; +import static android.content.pm.PackageManager.INSTALL_FAILED_MISSING_SPLIT; import static android.content.pm.PackageParser.APK_FILE_EXTENSION; import static android.system.OsConstants.O_CREAT; import static android.system.OsConstants.O_RDONLY; @@ -1060,6 +1061,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { @GuardedBy("mLock") private void validateInstallLocked(@Nullable PackageInfo pkgInfo) throws PackageManagerException { + ApkLite baseApk = null; mPackageName = null; mVersionCode = -1; mSigningDetails = PackageParser.SigningDetails.UNKNOWN; @@ -1136,6 +1138,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { // Base is coming from session if (apk.splitName == null) { mResolvedBaseFile = targetFile; + baseApk = apk; } mResolvedStagedFiles.add(targetFile); @@ -1221,6 +1224,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { if (baseDexMetadataFile != null) { mResolvedInheritedFiles.add(baseDexMetadataFile); } + baseApk = existingBase; } // Inherit splits if not overridden @@ -1300,6 +1304,10 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } } } + if (baseApk.isSplitRequired && stagedSplits.size() <= 1) { + throw new PackageManagerException(INSTALL_FAILED_MISSING_SPLIT, + "Missing split for " + mPackageName); + } } @GuardedBy("mLock") diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 10980b79f1f4..329b1da82608 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -8474,7 +8474,7 @@ public class PackageManagerService extends IPackageManager.Stub private boolean canSkipFullApkVerification(String apkPath) { final byte[] rootHashObserved; try { - rootHashObserved = VerityUtils.generateFsverityRootHash(apkPath); + rootHashObserved = VerityUtils.generateApkVerityRootHash(apkPath); if (rootHashObserved == null) { return false; // APK does not contain Merkle tree root hash. } @@ -16010,7 +16010,8 @@ public class PackageManagerService extends IPackageManager.Stub if (Build.IS_DEBUGGABLE) Slog.i(TAG, "Enabling apk verity to " + apkPath); FileDescriptor fd = result.getUnownedFileDescriptor(); try { - final byte[] signedRootHash = VerityUtils.generateFsverityRootHash(apkPath); + final byte[] signedRootHash = + VerityUtils.generateApkVerityRootHash(apkPath); mInstaller.installApkVerity(apkPath, fd, result.getContentSize()); mInstaller.assertFsverityRootHashMatches(apkPath, signedRootHash); } catch (InstallerException | IOException | DigestException | @@ -23098,7 +23099,9 @@ public class PackageManagerService extends IPackageManager.Stub return false; } } - if (sUserManager.hasUserRestriction(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES, userId)) { + if (sUserManager.hasUserRestriction(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES, userId) + || sUserManager.hasUserRestriction( + UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY, userId)) { return false; } if (mExternalSourcesPolicy != null) { diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java index a9f1b5c05a7f..93729d1949b0 100644 --- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java +++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java @@ -124,6 +124,7 @@ class PackageManagerShellCommand extends ShellCommand { int mTargetUser; boolean mBrief; boolean mComponents; + int mQueryFlags; PackageManagerShellCommand(PackageManagerService service) { mInterface = service; @@ -739,6 +740,9 @@ class PackageManagerShellCommand extends ShellCommand { } else if ("--components".equals(opt)) { mComponents = true; return true; + } else if ("--query-flags".equals(opt)) { + mQueryFlags = Integer.decode(cmd.getNextArgRequired()); + return true; } return false; } @@ -784,7 +788,8 @@ class PackageManagerShellCommand extends ShellCommand { throw new RuntimeException(e.getMessage(), e); } try { - ResolveInfo ri = mInterface.resolveIntent(intent, intent.getType(), 0, mTargetUser); + ResolveInfo ri = mInterface.resolveIntent(intent, intent.getType(), mQueryFlags, + mTargetUser); PrintWriter pw = getOutPrintWriter(); if (ri == null) { pw.println("No activity found"); @@ -806,8 +811,8 @@ class PackageManagerShellCommand extends ShellCommand { throw new RuntimeException(e.getMessage(), e); } try { - List<ResolveInfo> result = mInterface.queryIntentActivities(intent, intent.getType(), 0, - mTargetUser).getList(); + List<ResolveInfo> result = mInterface.queryIntentActivities(intent, intent.getType(), + mQueryFlags, mTargetUser).getList(); PrintWriter pw = getOutPrintWriter(); if (result == null || result.size() <= 0) { pw.println("No activities found"); @@ -840,8 +845,8 @@ class PackageManagerShellCommand extends ShellCommand { throw new RuntimeException(e.getMessage(), e); } try { - List<ResolveInfo> result = mInterface.queryIntentServices(intent, intent.getType(), 0, - mTargetUser).getList(); + List<ResolveInfo> result = mInterface.queryIntentServices(intent, intent.getType(), + mQueryFlags, mTargetUser).getList(); PrintWriter pw = getOutPrintWriter(); if (result == null || result.size() <= 0) { pw.println("No services found"); @@ -874,8 +879,8 @@ class PackageManagerShellCommand extends ShellCommand { throw new RuntimeException(e.getMessage(), e); } try { - List<ResolveInfo> result = mInterface.queryIntentReceivers(intent, intent.getType(), 0, - mTargetUser).getList(); + List<ResolveInfo> result = mInterface.queryIntentReceivers(intent, intent.getType(), + mQueryFlags, mTargetUser).getList(); PrintWriter pw = getOutPrintWriter(); if (result == null || result.size() <= 0) { pw.println("No receivers found"); @@ -2731,16 +2736,20 @@ class PackageManagerShellCommand extends ShellCommand { pw.println(" -d: only list dangerous permissions"); pw.println(" -u: list only the permissions users will see"); pw.println(""); - pw.println(" resolve-activity [--brief] [--components] [--user USER_ID] INTENT"); + pw.println(" resolve-activity [--brief] [--components] [--query-flags FLAGS]"); + pw.println(" [--user USER_ID] INTENT"); pw.println(" Prints the activity that resolves to the given INTENT."); pw.println(""); - pw.println(" query-activities [--brief] [--components] [--user USER_ID] INTENT"); + pw.println(" query-activities [--brief] [--components] [--query-flags FLAGS]"); + pw.println(" [--user USER_ID] INTENT"); pw.println(" Prints all activities that can handle the given INTENT."); pw.println(""); - pw.println(" query-services [--brief] [--components] [--user USER_ID] INTENT"); + pw.println(" query-services [--brief] [--components] [--query-flags FLAGS]"); + pw.println(" [--user USER_ID] INTENT"); pw.println(" Prints all services that can handle the given INTENT."); pw.println(""); - pw.println(" query-receivers [--brief] [--components] [--user USER_ID] INTENT"); + pw.println(" query-receivers [--brief] [--components] [--query-flags FLAGS]"); + pw.println(" [--user USER_ID] INTENT"); pw.println(" Prints all broadcast receivers that can handle the given INTENT."); pw.println(""); pw.println(" install [-lrtsfdg] [-i PACKAGE] [--user USER_ID|all|current]"); diff --git a/services/core/java/com/android/server/pm/PackageSignatures.java b/services/core/java/com/android/server/pm/PackageSignatures.java index 471729ee649d..6bce78862087 100644 --- a/services/core/java/com/android/server/pm/PackageSignatures.java +++ b/services/core/java/com/android/server/pm/PackageSignatures.java @@ -16,18 +16,18 @@ package com.android.server.pm; -import com.android.internal.util.XmlUtils; - -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; -import org.xmlpull.v1.XmlSerializer; - import android.annotation.NonNull; import android.content.pm.PackageParser; import android.content.pm.PackageParser.SigningDetails.SignatureSchemeVersion; import android.content.pm.Signature; import android.util.Log; +import com.android.internal.util.XmlUtils; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; + import java.io.IOException; import java.security.cert.CertificateException; import java.util.ArrayList; @@ -61,23 +61,22 @@ class PackageSignatures { serializer.attribute(null, "count", Integer.toString(mSigningDetails.signatures.length)); serializer.attribute(null, "schemeVersion", Integer.toString(mSigningDetails.signatureSchemeVersion)); - writeCertsListXml(serializer, writtenSignatures, mSigningDetails.signatures, null); + writeCertsListXml(serializer, writtenSignatures, mSigningDetails.signatures, false); // if we have past signer certificate information, write it out if (mSigningDetails.pastSigningCertificates != null) { serializer.startTag(null, "pastSigs"); serializer.attribute(null, "count", Integer.toString(mSigningDetails.pastSigningCertificates.length)); - writeCertsListXml( - serializer, writtenSignatures, mSigningDetails.pastSigningCertificates, - mSigningDetails.pastSigningCertificatesFlags); + writeCertsListXml(serializer, writtenSignatures, + mSigningDetails.pastSigningCertificates, true); serializer.endTag(null, "pastSigs"); } serializer.endTag(null, tagName); } private void writeCertsListXml(XmlSerializer serializer, ArrayList<Signature> writtenSignatures, - Signature[] signatures, int[] flags) throws IOException { + Signature[] signatures, boolean isPastSigs) throws IOException { for (int i=0; i<signatures.length; i++) { serializer.startTag(null, "cert"); final Signature sig = signatures[i]; @@ -96,8 +95,10 @@ class PackageSignatures { serializer.attribute(null, "index", Integer.toString(numWritten)); serializer.attribute(null, "key", sig.toCharsString()); } - if (flags != null) { - serializer.attribute(null, "flags", Integer.toString(flags[i])); + // The flags attribute is only written for previous signatures to represent the + // capabilities the developer wants to grant to the previous signing certificates. + if (isPastSigs) { + serializer.attribute(null, "flags", Integer.toString(sig.getFlags())); } serializer.endTag(null, "cert"); } @@ -114,6 +115,7 @@ class PackageSignatures { "Error in package manager settings: <sigs> has" + " no count at " + parser.getPositionDescription()); XmlUtils.skipCurrentTag(parser); + return; } final int count = Integer.parseInt(countStr); @@ -128,16 +130,11 @@ class PackageSignatures { signatureSchemeVersion = Integer.parseInt(schemeVersionStr); } builder.setSignatureSchemeVersion(signatureSchemeVersion); - Signature[] signatures = new Signature[count]; - int pos = readCertsListXml(parser, readSignatures, signatures, null, builder); + ArrayList<Signature> signatureList = new ArrayList<>(); + int pos = readCertsListXml(parser, readSignatures, signatureList, count, false, builder); + Signature[] signatures = signatureList.toArray(new Signature[signatureList.size()]); builder.setSignatures(signatures); if (pos < count) { - // Should never happen -- there is an error in the written - // settings -- but if it does we don't want to generate - // a bad array. - Signature[] newSigs = new Signature[pos]; - System.arraycopy(signatures, 0, newSigs, 0, pos); - builder = builder.setSignatures(newSigs); PackageManagerService.reportSettingsProblem(Log.WARN, "Error in package manager settings: <sigs> count does not match number of " + " <cert> entries" + parser.getPositionDescription()); @@ -154,9 +151,9 @@ class PackageSignatures { } private int readCertsListXml(XmlPullParser parser, ArrayList<Signature> readSignatures, - Signature[] signatures, int[] flags, PackageParser.SigningDetails.Builder builder) + ArrayList<Signature> signatures, int count, boolean isPastSigs, + PackageParser.SigningDetails.Builder builder) throws IOException, XmlPullParserException { - int count = signatures.length; int pos = 0; int outerDepth = parser.getDepth(); @@ -174,6 +171,7 @@ class PackageSignatures { if (pos < count) { String index = parser.getAttributeValue(null, "index"); if (index != null) { + boolean signatureParsed = false; try { int idx = Integer.parseInt(index); String key = parser.getAttributeValue(null, "key"); @@ -181,7 +179,8 @@ class PackageSignatures { if (idx >= 0 && idx < readSignatures.size()) { Signature sig = readSignatures.get(idx); if (sig != null) { - signatures[pos] = readSignatures.get(idx); + signatures.add(sig); + signatureParsed = true; } else { PackageManagerService.reportSettingsProblem(Log.WARN, "Error in package manager settings: <cert> " @@ -195,12 +194,15 @@ class PackageSignatures { + parser.getPositionDescription()); } } else { - while (readSignatures.size() <= idx) { + // Create the signature first to prevent adding null entries to the + // output List if the key value is invalid. + Signature sig = new Signature(key); + while (readSignatures.size() < idx) { readSignatures.add(null); } - Signature sig = new Signature(key); - readSignatures.set(idx, sig); - signatures[pos] = sig; + readSignatures.add(sig); + signatures.add(sig); + signatureParsed = true; } } catch (NumberFormatException e) { PackageManagerService.reportSettingsProblem(Log.WARN, @@ -215,11 +217,22 @@ class PackageSignatures { + e.getMessage()); } - if (flags != null) { + if (isPastSigs) { String flagsStr = parser.getAttributeValue(null, "flags"); if (flagsStr != null) { try { - flags[pos] = Integer.parseInt(flagsStr); + int flagsValue = Integer.parseInt(flagsStr); + // only modify the flags if the signature of the previous signer + // was successfully parsed above + if (signatureParsed) { + signatures.get(signatures.size() - 1).setFlags(flagsValue); + } else { + PackageManagerService.reportSettingsProblem(Log.WARN, + "Error in package manager settings: signature not " + + "available at index " + + pos + " to set flags at " + + parser.getPositionDescription()); + } } catch (NumberFormatException e) { PackageManagerService.reportSettingsProblem(Log.WARN, "Error in package manager settings: <cert> " @@ -246,7 +259,7 @@ class PackageSignatures { pos++; XmlUtils.skipCurrentTag(parser); } else if (tagName.equals("pastSigs")) { - if (flags == null) { + if (!isPastSigs) { // we haven't encountered pastSigs yet, go ahead String countStr = parser.getAttributeValue(null, "count"); if (countStr == null) { @@ -254,32 +267,23 @@ class PackageSignatures { "Error in package manager settings: <pastSigs> has" + " no count at " + parser.getPositionDescription()); XmlUtils.skipCurrentTag(parser); + continue; } try { final int pastSigsCount = Integer.parseInt(countStr); - Signature[] pastSignatures = new Signature[pastSigsCount]; - int[] pastSignaturesFlags = new int[pastSigsCount]; - int pastSigsPos = readCertsListXml(parser, readSignatures, pastSignatures, - pastSignaturesFlags, builder); - builder = builder - .setPastSigningCertificates(pastSignatures) - .setPastSigningCertificatesFlags(pastSignaturesFlags); + ArrayList<Signature> pastSignatureList = new ArrayList<>(); + int pastSigsPos = readCertsListXml(parser, readSignatures, + pastSignatureList, + pastSigsCount, true, builder); + Signature[] pastSignatures = pastSignatureList.toArray( + new Signature[pastSignatureList.size()]); + builder = builder.setPastSigningCertificates(pastSignatures); if (pastSigsPos < pastSigsCount) { - // Should never happen -- there is an error in the written - // settings -- but if it does we don't want to generate - // a bad array. - Signature[] newSigs = new Signature[pastSigsPos]; - System.arraycopy(pastSignatures, 0, newSigs, 0, pastSigsPos); - int[] newFlags = new int[pastSigsPos]; - System.arraycopy(pastSignaturesFlags, 0, newFlags, 0, pastSigsPos); - builder = builder - .setPastSigningCertificates(newSigs) - .setPastSigningCertificatesFlags(newFlags); PackageManagerService.reportSettingsProblem(Log.WARN, "Error in package manager settings: <pastSigs> count does not " - + "match number of <cert> entries " - + parser.getPositionDescription()); + + "match number of <cert> entries " + + parser.getPositionDescription()); } } catch (NumberFormatException e) { PackageManagerService.reportSettingsProblem(Log.WARN, @@ -326,7 +330,8 @@ class PackageSignatures { buf.append(Integer.toHexString( mSigningDetails.pastSigningCertificates[i].hashCode())); buf.append(" flags: "); - buf.append(Integer.toHexString(mSigningDetails.pastSigningCertificatesFlags[i])); + buf.append( + Integer.toHexString(mSigningDetails.pastSigningCertificates[i].getFlags())); } } buf.append("]}"); diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java index 3f28ee659a58..13155027a387 100644 --- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java +++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java @@ -16,10 +16,6 @@ package com.android.server.pm; -import com.google.android.collect.Sets; - -import com.android.internal.util.Preconditions; - import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; @@ -42,6 +38,10 @@ import android.util.Log; import android.util.Slog; import android.util.SparseArray; +import com.android.internal.util.Preconditions; + +import com.google.android.collect.Sets; + import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlSerializer; @@ -77,6 +77,7 @@ public class UserRestrictionsUtils { UserManager.DISALLOW_UNINSTALL_APPS, UserManager.DISALLOW_SHARE_LOCATION, UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES, + UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY, UserManager.DISALLOW_CONFIG_BLUETOOTH, UserManager.DISALLOW_BLUETOOTH, UserManager.DISALLOW_BLUETOOTH_SHARING, @@ -211,7 +212,8 @@ public class UserRestrictionsUtils { */ private static final Set<String> PROFILE_GLOBAL_RESTRICTIONS = Sets.newArraySet( UserManager.ENSURE_VERIFY_APPS, - UserManager.DISALLOW_AIRPLANE_MODE + UserManager.DISALLOW_AIRPLANE_MODE, + UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY ); /** @@ -517,13 +519,18 @@ public class UserRestrictionsUtils { userId); } break; + case UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY: + setInstallMarketAppsRestriction(cr, userId, getNewUserRestrictionSetting( + context, userId, UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES, + newValue)); + break; case UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES: // Since Android O, the secure setting is not available to be changed by the // user. Hence, when the restriction is cleared, we need to reset the state of // the setting to its default value which is now 1. - android.provider.Settings.Secure.putIntForUser(cr, - android.provider.Settings.Secure.INSTALL_NON_MARKET_APPS, - newValue ? 0 : 1, userId); + setInstallMarketAppsRestriction(cr, userId, getNewUserRestrictionSetting( + context, userId, UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY, + newValue)); break; case UserManager.DISALLOW_RUN_IN_BACKGROUND: if (newValue) { @@ -813,4 +820,16 @@ public class UserRestrictionsUtils { } return false; } + + private static void setInstallMarketAppsRestriction(ContentResolver cr, int userId, + int settingValue) { + android.provider.Settings.Secure.putIntForUser( + cr, android.provider.Settings.Secure.INSTALL_NON_MARKET_APPS, settingValue, userId); + } + + private static int getNewUserRestrictionSetting(Context context, int userId, + String userRestriction, boolean newValue) { + return (newValue || UserManager.get(context).hasUserRestriction(userRestriction, + UserHandle.of(userId))) ? 0 : 1; + } } diff --git a/services/core/java/com/android/server/pm/dex/TEST_MAPPING b/services/core/java/com/android/server/pm/dex/TEST_MAPPING new file mode 100644 index 000000000000..ad5255904d20 --- /dev/null +++ b/services/core/java/com/android/server/pm/dex/TEST_MAPPING @@ -0,0 +1,22 @@ +{ + "presubmit": [ + { + "name": "DexLoggerTests" + }, + { + "name": "DexManagerTests" + }, + { + "name": "DexoptOptionsTests" + }, + { + "name": "DexoptUtilsTest" + }, + { + "name": "PackageDexUsageTests" + }, + { + "name": "DexLoggerIntegrationTests" + } + ] +} diff --git a/services/core/java/com/android/server/pm/permission/PermissionsState.java b/services/core/java/com/android/server/pm/permission/PermissionsState.java index 5e66bfc3cd3e..82d6b226df9f 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionsState.java +++ b/services/core/java/com/android/server/pm/permission/PermissionsState.java @@ -135,7 +135,8 @@ public final class PermissionsState { final int userCount = other.mPermissionReviewRequired.size(); for (int i = 0; i < userCount; i++) { final boolean reviewRequired = other.mPermissionReviewRequired.valueAt(i); - mPermissionReviewRequired.put(i, reviewRequired); + mPermissionReviewRequired.put(other.mPermissionReviewRequired.keyAt(i), + reviewRequired); } } } diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 2557f46ba34b..21bf488f828c 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -1655,11 +1655,9 @@ public class PhoneWindowManager implements WindowManagerPolicy { } final boolean keyguardShowing = isKeyguardShowingAndNotOccluded(); mGlobalActions.showDialog(keyguardShowing, isDeviceProvisioned()); - if (keyguardShowing) { - // since it took two seconds of long press to bring this up, - // poke the wake lock so they have some time to see the dialog. - mPowerManager.userActivity(SystemClock.uptimeMillis(), false); - } + // since it took two seconds of long press to bring this up, + // poke the wake lock so they have some time to see the dialog. + mPowerManager.userActivity(SystemClock.uptimeMillis(), false); } boolean isDeviceProvisioned() { @@ -4777,6 +4775,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { pf.bottom = df.bottom = of.bottom = displayFrames.mUnrestricted.bottom; // ...with content insets above the nav bar cf.bottom = vf.bottom = displayFrames.mStable.bottom; + // TODO (b/111364446): Support showing IME on non-default displays if (mStatusBar != null && mFocusedWindow == mStatusBar && canReceiveInput(mStatusBar)) { // The status bar forces the navigation bar while it's visible. Make sure the IME // avoids the navigation bar in that case. diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java index 9f6b3dde5a49..b3f2a27cf99a 100644 --- a/services/core/java/com/android/server/power/PowerManagerService.java +++ b/services/core/java/com/android/server/power/PowerManagerService.java @@ -4404,8 +4404,11 @@ public final class PowerManagerService extends SystemService @Override // Binder call public boolean setPowerSaveMode(boolean enabled) { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.DEVICE_POWER, null); + if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.POWER_SAVER) + != PackageManager.PERMISSION_GRANTED) { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.DEVICE_POWER, null); + } final long ident = Binder.clearCallingIdentity(); try { return setLowPowerModeInternal(enabled); diff --git a/services/core/java/com/android/server/security/VerityUtils.java b/services/core/java/com/android/server/security/VerityUtils.java index 37966108fe64..8070f3add5c6 100644 --- a/services/core/java/com/android/server/security/VerityUtils.java +++ b/services/core/java/com/android/server/security/VerityUtils.java @@ -26,9 +26,9 @@ import android.system.Os; import android.util.Pair; import android.util.Slog; import android.util.apk.ApkSignatureVerifier; -import android.util.apk.ApkVerityBuilder; import android.util.apk.ByteBufferFactory; import android.util.apk.SignatureNotFoundException; +import android.util.apk.VerityBuilder; import libcore.util.HexEncoding; @@ -115,9 +115,9 @@ abstract public class VerityUtils { } /** - * {@see ApkSignatureVerifier#generateFsverityRootHash(String)}. + * {@see ApkSignatureVerifier#generateApkVerityRootHash(String)}. */ - public static byte[] generateFsverityRootHash(@NonNull String apkPath) + public static byte[] generateApkVerityRootHash(@NonNull String apkPath) throws NoSuchAlgorithmException, DigestException, IOException { return ApkSignatureVerifier.generateApkVerityRootHash(apkPath); } @@ -146,7 +146,7 @@ abstract public class VerityUtils { throws IOException, SignatureNotFoundException, SecurityException, DigestException, NoSuchAlgorithmException { try (RandomAccessFile file = new RandomAccessFile(filePath, "r")) { - ApkVerityBuilder.ApkVerityResult result = ApkVerityBuilder.generateFsVerityTree( + VerityBuilder.VerityResult result = VerityBuilder.generateFsVerityTree( file, trackedBufferFactory); ByteBuffer buffer = result.verityData; diff --git a/services/core/java/com/android/server/stats/StatsCompanionService.java b/services/core/java/com/android/server/stats/StatsCompanionService.java index c6e6449313c0..97992cf1232b 100644 --- a/services/core/java/com/android/server/stats/StatsCompanionService.java +++ b/services/core/java/com/android/server/stats/StatsCompanionService.java @@ -15,6 +15,8 @@ */ package com.android.server.stats; +import static com.android.internal.util.Preconditions.checkNotNull; + import android.annotation.Nullable; import android.app.ActivityManagerInternal; import android.app.AlarmManager; @@ -47,6 +49,7 @@ import android.os.IStatsManager; import android.os.IStoraged; import android.os.IThermalEventListener; import android.os.IThermalService; +import android.os.ParcelFileDescriptor; import android.os.Parcelable; import android.os.Process; import android.os.RemoteException; @@ -63,10 +66,14 @@ import android.os.storage.StorageManager; import android.telephony.ModemActivityInfo; import android.telephony.TelephonyManager; import android.util.ArrayMap; +import android.util.Log; import android.util.Slog; import android.util.StatsLog; +import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.GuardedBy; +import com.android.internal.app.procstats.IProcessStats; +import com.android.internal.app.procstats.ProcessStats; import com.android.internal.net.NetworkStatsFactory; import com.android.internal.os.BinderCallsStats.ExportedCallStat; import com.android.internal.os.KernelCpuSpeedReader; @@ -78,10 +85,12 @@ import com.android.internal.os.KernelWakelockReader; import com.android.internal.os.KernelWakelockStats; import com.android.internal.os.LooperStats; import com.android.internal.os.PowerProfile; +import com.android.internal.os.StoragedUidIoStatsReader; import com.android.internal.util.DumpUtils; import com.android.server.BinderCallsStatsService; import com.android.server.LocalServices; import com.android.server.SystemService; +import com.android.server.SystemServiceManager; import com.android.server.storage.DiskStatsFileLogger; import com.android.server.storage.DiskStatsLoggingService; @@ -95,6 +104,7 @@ import java.io.File; import java.io.FileDescriptor; import java.io.FileOutputStream; import java.io.IOException; +import java.io.InputStream; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; @@ -123,7 +133,7 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { public static final String CONFIG_DIR = "/data/misc/stats-service"; static final String TAG = "StatsCompanionService"; - static final boolean DEBUG = true; + static final boolean DEBUG = false; public static final int CODE_DATA_BROADCAST = 1; public static final int CODE_SUBSCRIBER_BROADCAST = 1; @@ -172,14 +182,18 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { new KernelUidCpuActiveTimeReader(); private KernelUidCpuClusterTimeReader mKernelUidCpuClusterTimeReader = new KernelUidCpuClusterTimeReader(); + private StoragedUidIoStatsReader mStoragedUidIoStatsReader = + new StoragedUidIoStatsReader(); private static IThermalService sThermalService; + private File mBaseDir = + new File(SystemServiceManager.ensureSystemDir(), "stats_companion"); public StatsCompanionService(Context context) { super(); mContext = context; mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); - + mBaseDir.mkdirs(); mAppUpdateReceiver = new AppUpdateReceiver(); mUserUpdateReceiver = new BroadcastReceiver() { @Override @@ -1245,6 +1259,123 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { Binder.restoreCallingIdentity(token); } + long mLastProcStatsHighWaterMark = readProcStatsHighWaterMark(); + + private long readProcStatsHighWaterMark() { + try { + File[] files = mBaseDir.listFiles(); + if (files == null || files.length == 0) { + return 0; + } + if (files.length > 1) { + Log.e(TAG, "Only 1 file expected for high water mark. Found " + files.length); + } + return Long.valueOf(files[0].getName()); + } catch (SecurityException e) { + Log.e(TAG, "Failed to get procstats high watermark file.", e); + } catch (NumberFormatException e) { + Log.e(TAG, "Failed to parse file name.", e); + } + return 0; + } + + private IProcessStats mProcessStats = + IProcessStats.Stub.asInterface(ServiceManager.getService(ProcessStats.SERVICE_NAME)); + + private void pullProcessStats( + int tagId, long elapsedNanos, long wallClockNanos, + List<StatsLogEventWrapper> pulledData) { + try { + List<ParcelFileDescriptor> statsFiles = new ArrayList<>(); + long highWaterMark = mProcessStats.getCommittedStats( + mLastProcStatsHighWaterMark, ProcessStats.REPORT_ALL, true, statsFiles); + if (statsFiles.size() != 1) { + return; + } + InputStream stream = new ParcelFileDescriptor.AutoCloseInputStream(statsFiles.get(0)); + int[] len = new int[1]; + byte[] stats = readFully(stream, len); + StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos); + e.writeStorage(Arrays.copyOf(stats, len[0])); + pulledData.add(e); + new File(mBaseDir.getAbsolutePath() + "/" + mLastProcStatsHighWaterMark).delete(); + mLastProcStatsHighWaterMark = highWaterMark; + new File( + mBaseDir.getAbsolutePath() + "/" + mLastProcStatsHighWaterMark).createNewFile(); + } catch (IOException e) { + Log.e(TAG, "Getting procstats failed: ", e); + } catch (RemoteException e) { + Log.e(TAG, "Getting procstats failed: ", e); + } catch (SecurityException e) { + Log.e(TAG, "Getting procstats failed: ", e); + } + } + + static byte[] readFully(InputStream stream, int[] outLen) throws IOException { + int pos = 0; + final int initialAvail = stream.available(); + byte[] data = new byte[initialAvail > 0 ? (initialAvail + 1) : 16384]; + while (true) { + int amt = stream.read(data, pos, data.length - pos); + if (DEBUG) { + Slog.i(TAG, "Read " + amt + " bytes at " + pos + " of avail " + data.length); + } + if (amt < 0) { + if (DEBUG) { + Slog.i(TAG, "**** FINISHED READING: pos=" + pos + " len=" + data.length); + } + outLen[0] = pos; + return data; + } + pos += amt; + if (pos >= data.length) { + byte[] newData = new byte[pos + 16384]; + if (DEBUG) { + Slog.i(TAG, "Copying " + pos + " bytes to new array len " + newData.length); + } + System.arraycopy(data, 0, newData, 0, pos); + data = newData; + } + } + } + + private void pullPowerProfile( + int tagId, long elapsedNanos, long wallClockNanos, + List<StatsLogEventWrapper> pulledData) { + PowerProfile powerProfile = new PowerProfile(mContext); + checkNotNull(powerProfile); + + StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, + wallClockNanos); + ProtoOutputStream proto = new ProtoOutputStream(); + powerProfile.writeToProto(proto); + proto.flush(); + e.writeStorage(proto.getBytes()); + pulledData.add(e); + } + + private void pullDiskIo(int tagId, long elapsedNanos, final long wallClockNanos, + List<StatsLogEventWrapper> pulledData) { + mStoragedUidIoStatsReader.readAbsolute((uid, fgCharsRead, fgCharsWrite, fgBytesRead, + fgBytesWrite, bgCharsRead, bgCharsWrite, bgBytesRead, bgBytesWrite, + fgFsync, bgFsync) -> { + StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, + wallClockNanos); + e.writeInt(uid); + e.writeLong(fgCharsRead); + e.writeLong(fgCharsWrite); + e.writeLong(fgBytesRead); + e.writeLong(fgBytesWrite); + e.writeLong(bgCharsRead); + e.writeLong(bgCharsWrite); + e.writeLong(bgBytesRead); + e.writeLong(bgBytesWrite); + e.writeLong(fgFsync); + e.writeLong(bgFsync); + pulledData.add(e); + }); + } + /** * Pulls various data. */ @@ -1358,6 +1489,18 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { pullNumFingerprints(tagId, elapsedNanos, wallClockNanos, ret); break; } + case StatsLog.PROC_STATS: { + pullProcessStats(tagId, elapsedNanos, wallClockNanos, ret); + break; + } + case StatsLog.DISK_IO: { + pullDiskIo(tagId, elapsedNanos, wallClockNanos, ret); + break; + } + case StatsLog.POWER_PROFILE: { + pullPowerProfile(tagId, elapsedNanos, wallClockNanos, ret); + break; + } default: Slog.w(TAG, "No such tagId data as " + tagId); return null; @@ -1368,13 +1511,13 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { @Override // Binder call public void statsdReady() { enforceCallingPermission(); - if (DEBUG) Slog.d(TAG, "learned that statsdReady"); + if (DEBUG) { + Slog.d(TAG, "learned that statsdReady"); + } sayHiToStatsd(); // tell statsd that we're ready too and link to it - mContext.sendBroadcastAsUser( - new Intent(StatsManager.ACTION_STATSD_STARTED) + mContext.sendBroadcastAsUser(new Intent(StatsManager.ACTION_STATSD_STARTED) .addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND), - UserHandle.SYSTEM, - android.Manifest.permission.DUMP); + UserHandle.SYSTEM, android.Manifest.permission.DUMP); } @Override @@ -1419,7 +1562,8 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { public void onStart() { mStatsCompanionService = new StatsCompanionService(getContext()); try { - publishBinderService(Context.STATS_COMPANION_SERVICE, mStatsCompanionService); + publishBinderService(Context.STATS_COMPANION_SERVICE, + mStatsCompanionService); if (DEBUG) Slog.d(TAG, "Published " + Context.STATS_COMPANION_SERVICE); } catch (Exception e) { Slog.e(TAG, "Failed to publishBinderService", e); @@ -1444,18 +1588,22 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { } /** - * Tells statsd that statscompanion is ready. If the binder call returns, link to statsd. + * Tells statsd that statscompanion is ready. If the binder call returns, link to + * statsd. */ private void sayHiToStatsd() { synchronized (sStatsdLock) { if (sStatsd != null) { Slog.e(TAG, "Trying to fetch statsd, but it was already fetched", - new IllegalStateException("sStatsd is not null when being fetched")); + new IllegalStateException( + "sStatsd is not null when being fetched")); return; } sStatsd = fetchStatsdService(); if (sStatsd == null) { - Slog.i(TAG, "Could not yet find statsd to tell it that StatsCompanion is alive."); + Slog.i(TAG, + "Could not yet find statsd to tell it that StatsCompanion is " + + "alive."); return; } if (DEBUG) Slog.d(TAG, "Saying hi to statsd"); @@ -1473,10 +1621,12 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { filter.addAction(Intent.ACTION_PACKAGE_ADDED); filter.addAction(Intent.ACTION_PACKAGE_REMOVED); filter.addDataScheme("package"); - mContext.registerReceiverAsUser(mAppUpdateReceiver, UserHandle.ALL, filter, null, + mContext.registerReceiverAsUser(mAppUpdateReceiver, UserHandle.ALL, filter, + null, null); - // Setup receiver for user initialize (which happens once for a new user) and + // Setup receiver for user initialize (which happens once for a new user) + // and // if a user is removed. filter = new IntentFilter(Intent.ACTION_USER_INITIALIZE); filter.addAction(Intent.ACTION_USER_REMOVED); @@ -1490,7 +1640,8 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { mShutdownEventReceiver, UserHandle.ALL, filter, null, null); final long token = Binder.clearCallingIdentity(); try { - // Pull the latest state of UID->app name, version mapping when statsd starts. + // Pull the latest state of UID->app name, version mapping when + // statsd starts. informAllUidsLocked(mContext); } finally { restoreCallingIdentity(token); @@ -1552,7 +1703,8 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { if (!DumpUtils.checkDumpPermission(mContext, TAG, writer)) return; synchronized (sStatsdLock) { - writer.println("Number of configuration files deleted: " + mDeletedFiles.size()); + writer.println( + "Number of configuration files deleted: " + mDeletedFiles.size()); if (mDeletedFiles.size() > 0) { writer.println(" timestamp, deleted file name"); } @@ -1560,7 +1712,8 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { SystemClock.currentThreadTimeMillis() - SystemClock.elapsedRealtime(); for (Long elapsedMillis : mDeletedFiles.keySet()) { long deletionMillis = lastBootMillis + elapsedMillis; - writer.println(" " + deletionMillis + ", " + mDeletedFiles.get(elapsedMillis)); + writer.println( + " " + deletionMillis + ", " + mDeletedFiles.get(elapsedMillis)); } } } diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java index e4491113cfb8..74922f650ad8 100644 --- a/services/core/java/com/android/server/wm/AccessibilityController.java +++ b/services/core/java/com/android/server/wm/AccessibilityController.java @@ -1043,7 +1043,8 @@ final class AccessibilityController { // Do not send the windows if there is no current focus as // the window manager is still looking for where to put it. // We will do the work when we get a focus change callback. - if (mService.mCurrentFocus == null) { + // TODO(b/112273690): Support multiple displays + if (mService.getDefaultDisplayContentLocked().mCurrentFocus == null) { return; } diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java index bcf9212464db..db0bd4f72fd4 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java @@ -33,6 +33,7 @@ import android.service.voice.IVoiceInteractionSession; import android.util.SparseIntArray; import com.android.internal.app.IVoiceInteractor; +import com.android.server.am.ActivityServiceConnectionsHolder; import com.android.server.am.PendingIntentRecord; import com.android.server.am.SafeActivityOptions; import com.android.server.am.TaskRecord; @@ -332,4 +333,7 @@ public abstract class ActivityTaskManagerInternal { int callingUid, int userId, IBinder token, String resultWho, int requestCode, Intent[] intents, String[] resolvedTypes, int flags, Bundle bOptions); + + /** @return the service connection holder for a given activity token. */ + public abstract ActivityServiceConnectionsHolder getServiceConnectionsHolder(IBinder token); } diff --git a/services/core/java/com/android/server/wm/AnimationAdapter.java b/services/core/java/com/android/server/wm/AnimationAdapter.java index 00e30507d232..be8a0bd7ad32 100644 --- a/services/core/java/com/android/server/wm/AnimationAdapter.java +++ b/services/core/java/com/android/server/wm/AnimationAdapter.java @@ -35,12 +35,6 @@ interface AnimationAdapter { long STATUS_BAR_TRANSITION_DURATION = 120L; /** - * @return Whether we should detach the wallpaper during the animation. - * @see Animation#setDetachWallpaper - */ - boolean getDetachWallpaper(); - - /** * @return Whether we should show the wallpaper during the animation. * @see Animation#getShowWallpaper() */ diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java index d73606f3003f..a9d09781d223 100644 --- a/services/core/java/com/android/server/wm/AppTransition.java +++ b/services/core/java/com/android/server/wm/AppTransition.java @@ -77,6 +77,7 @@ import static com.android.server.wm.AppTransitionProto.APP_TRANSITION_STATE; import static com.android.server.wm.AppTransitionProto.LAST_USED_APP_TRANSITION; import android.annotation.DrawableRes; +import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; import android.content.ComponentName; @@ -93,6 +94,7 @@ import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.os.Binder; import android.os.Debug; +import android.os.Handler; import android.os.IBinder; import android.os.IRemoteCallback; import android.os.RemoteException; @@ -120,8 +122,8 @@ import android.view.animation.TranslateAnimation; import com.android.internal.R; import com.android.internal.util.DumpUtils.Dump; +import com.android.internal.util.function.pooled.PooledLambda; import com.android.server.AttributeCache; -import com.android.server.wm.WindowManagerService.H; import com.android.server.wm.animation.ClipRectLRAnimation; import com.android.server.wm.animation.ClipRectTBAnimation; import com.android.server.wm.animation.CurvedTranslateAnimation; @@ -252,9 +254,13 @@ public class AppTransition implements Dump { private RemoteAnimationController mRemoteAnimationController; + final Handler mHandler; + final Runnable mHandleAppTransitionTimeoutRunnable = () -> handleAppTransitionTimeout(); + AppTransition(Context context, WindowManagerService service) { mContext = context; mService = service; + mHandler = new Handler(service.mH.getLooper()); mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(context, com.android.internal.R.interpolator.linear_out_slow_in); mFastOutLinearInInterpolator = AnimationUtils.loadInterpolator(context, @@ -1349,7 +1355,8 @@ public class AppTransition implements Dump { @Override public void onAnimationEnd(Animation animation) { - mService.mH.obtainMessage(H.DO_ANIMATION_CALLBACK, callback).sendToTarget(); + mHandler.sendMessage(PooledLambda.obtainMessage( + AppTransition::doAnimationCallback, callback)); } @Override @@ -1756,7 +1763,7 @@ public class AppTransition implements Dump { void postAnimationCallback() { if (mNextAppTransitionCallback != null) { - mService.mH.sendMessage(mService.mH.obtainMessage(H.DO_ANIMATION_CALLBACK, + mHandler.sendMessage(PooledLambda.obtainMessage(AppTransition::doAnimationCallback, mNextAppTransitionCallback)); mNextAppTransitionCallback = null; } @@ -1869,7 +1876,7 @@ public class AppTransition implements Dump { clear(); mNextAppTransitionType = NEXT_TRANSIT_TYPE_REMOTE; mRemoteAnimationController = new RemoteAnimationController(mService, - remoteAnimationAdapter, mService.mH); + remoteAnimationAdapter, mHandler); } } @@ -2162,8 +2169,8 @@ public class AppTransition implements Dump { } boolean prepared = prepare(); if (isTransitionSet()) { - mService.mH.removeMessages(H.APP_TRANSITION_TIMEOUT); - mService.mH.sendEmptyMessageDelayed(H.APP_TRANSITION_TIMEOUT, APP_TRANSITION_TIMEOUT_MS); + removeAppTransitionTimeoutCallbacks(); + mHandler.postDelayed(mHandleAppTransitionTimeoutRunnable, APP_TRANSITION_TIMEOUT_MS); } return prepared; } @@ -2208,4 +2215,32 @@ public class AppTransition implements Dump { return mGridLayoutRecentsEnabled || orientation == Configuration.ORIENTATION_PORTRAIT; } + + private void handleAppTransitionTimeout() { + synchronized (mService.mWindowMap) { + if (isTransitionSet() || !mService.mOpeningApps.isEmpty() + || !mService.mClosingApps.isEmpty()) { + if (DEBUG_APP_TRANSITIONS) { + Slog.v(TAG_WM, "*** APP TRANSITION TIMEOUT." + + " isTransitionSet()=" + + mService.mAppTransition.isTransitionSet() + + " mOpeningApps.size()=" + mService.mOpeningApps.size() + + " mClosingApps.size()=" + mService.mClosingApps.size()); + } + setTimeout(); + mService.mWindowPlacerLocked.performSurfacePlacement(); + } + } + } + + private static void doAnimationCallback(@NonNull IRemoteCallback callback) { + try { + ((IRemoteCallback) callback).sendResult(null); + } catch (RemoteException e) { + } + } + + void removeAppTransitionTimeoutCallbacks() { + mHandler.removeCallbacks(mHandleAppTransitionTimeoutRunnable); + } } diff --git a/services/core/java/com/android/server/wm/AppWindowContainerController.java b/services/core/java/com/android/server/wm/AppWindowContainerController.java index 36280dd98180..330c54ca4c1d 100644 --- a/services/core/java/com/android/server/wm/AppWindowContainerController.java +++ b/services/core/java/com/android/server/wm/AppWindowContainerController.java @@ -407,8 +407,7 @@ public class AppWindowContainerController if (mService.mAppTransition.getAppTransition() == WindowManager.TRANSIT_TASK_OPEN_BEHIND) { // We're launchingBehind, add the launching activity to mOpeningApps. - final WindowState win = - mService.getDefaultDisplayContentLocked().findFocusedWindow(); + final WindowState win = mContainer.getDisplayContent().findFocusedWindow(); if (win != null) { final AppWindowToken focusedToken = win.mAppToken; if (focusedToken != null) { diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java index 6da9f104a212..e57fea31f1a8 100644 --- a/services/core/java/com/android/server/wm/AppWindowToken.java +++ b/services/core/java/com/android/server/wm/AppWindowToken.java @@ -679,11 +679,12 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree removed = true; stopFreezingScreen(true, true); - if (mService.mFocusedApp == this) { - if (DEBUG_FOCUS_LIGHT) Slog.v(TAG_WM, "Removing focused app token:" + this); - mService.mFocusedApp = null; + final DisplayContent dc = getDisplayContent(); + if (dc.mFocusedApp == this) { + if (DEBUG_FOCUS_LIGHT) Slog.v(TAG_WM, "Removing focused app token:" + this + + " displayId=" + dc.getDisplayId()); + dc.setFocusedApp(null); mService.updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL, true /*updateInputWindows*/); - getDisplayContent().getInputMonitor().setFocusedAppLw(null); } if (!delayed) { @@ -1064,6 +1065,18 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree } } + @Override + void onDisplayChanged(DisplayContent dc) { + DisplayContent prevDc = mDisplayContent; + super.onDisplayChanged(dc); + if (prevDc != null && prevDc.mFocusedApp == this) { + prevDc.setFocusedApp(null); + if (dc.getTopStack().getTopChild().getTopChild() == this) { + dc.setFocusedApp(this); + } + } + } + /** * Freezes the task bounds. The size of this task reported the app will be fixed to the bounds * freezed by {@link Task#prepareFreezingBounds} until {@link #unfreezeBounds} gets called, even @@ -1632,17 +1645,6 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree return null; } - int getLowestAnimLayer() { - for (int i = 0; i < mChildren.size(); i++) { - final WindowState w = mChildren.get(i); - if (w.mRemoved) { - continue; - } - return w.mWinAnimator.mAnimLayer; - } - return Integer.MAX_VALUE; - } - WindowState getHighestAnimLayerWindow(WindowState currentTarget) { WindowState candidate = null; for (int i = mChildren.indexOf(currentTarget); i >= 0; i--) { @@ -1650,8 +1652,7 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree if (w.mRemoved) { continue; } - if (candidate == null || w.mWinAnimator.mAnimLayer > - candidate.mWinAnimator.mAnimLayer) { + if (candidate == null) { candidate = w; } } diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 236982fd76ac..6f728fcf1f00 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -70,6 +70,7 @@ import static com.android.server.wm.DisplayContentProto.DISPLAY_FRAMES; import static com.android.server.wm.DisplayContentProto.DISPLAY_INFO; import static com.android.server.wm.DisplayContentProto.DOCKED_STACK_DIVIDER_CONTROLLER; import static com.android.server.wm.DisplayContentProto.DPI; +import static com.android.server.wm.DisplayContentProto.FOCUSED_APP; import static com.android.server.wm.DisplayContentProto.ID; import static com.android.server.wm.DisplayContentProto.IME_WINDOWS; import static com.android.server.wm.DisplayContentProto.PINNED_STACK_CONTROLLER; @@ -98,12 +99,17 @@ import static com.android.server.wm.WindowManagerDebugConfig.SHOW_TRANSACTIONS; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import static com.android.server.wm.WindowManagerService.CUSTOM_SCREEN_ROTATION; +import static com.android.server.wm.WindowManagerService.H.REPORT_FOCUS_CHANGE; +import static com.android.server.wm.WindowManagerService.H.REPORT_LOSING_FOCUS; import static com.android.server.wm.WindowManagerService.H.SEND_NEW_CONFIGURATION; import static com.android.server.wm.WindowManagerService.H.UPDATE_DOCKED_STACK_DIVIDER; import static com.android.server.wm.WindowManagerService.H.WINDOW_HIDE_TIMEOUT; import static com.android.server.wm.WindowManagerService.LAYOUT_REPEAT_THRESHOLD; import static com.android.server.wm.WindowManagerService.MAX_ANIMATION_DURATION; import static com.android.server.wm.WindowManagerService.SEAMLESS_ROTATION_TIMEOUT_DURATION; +import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_PLACING_SURFACES; +import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_REMOVING_FOCUS; +import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_WILL_ASSIGN_LAYERS; import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_WILL_PLACE_SURFACES; import static com.android.server.wm.WindowManagerService.WINDOWS_FREEZING_SCREENS_ACTIVE; import static com.android.server.wm.WindowManagerService.WINDOWS_FREEZING_SCREENS_TIMEOUT; @@ -114,7 +120,6 @@ import static com.android.server.wm.WindowState.RESIZE_HANDLE_WIDTH_IN_DP; import static com.android.server.wm.WindowStateAnimator.DRAW_PENDING; import static com.android.server.wm.WindowStateAnimator.READY_TO_SHOW; import static com.android.server.wm.WindowSurfacePlacer.SET_WALLPAPER_MAY_CHANGE; -import static com.android.server.wm.utils.CoordinateTransforms.transformPhysicalToLogicalCoordinates; import android.annotation.CallSuper; import android.annotation.NonNull; @@ -152,6 +157,7 @@ import android.view.SurfaceSession; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ToBooleanFunction; import com.android.server.policy.WindowManagerPolicy; +import com.android.server.wm.utils.DisplayRotationUtil; import com.android.server.wm.utils.RotationCache; import com.android.server.wm.utils.WmDisplayCutout; @@ -334,6 +340,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo private final Matrix mTmpMatrix = new Matrix(); private final Region mTmpRegion = new Region(); + /** Used for handing back size of display */ private final Rect mTmpBounds = new Rect(); @@ -371,6 +378,36 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo private final SurfaceSession mSession = new SurfaceSession(); /** + * Window that is currently interacting with the user. This window is responsible for receiving + * key events and pointer events from the user. + */ + WindowState mCurrentFocus = null; + + /** + * The last focused window that we've notified the client that the focus is changed. + */ + WindowState mLastFocus = null; + + /** + * Windows that have lost input focus and are waiting for the new focus window to be displayed + * before they are told about this. + */ + ArrayList<WindowState> mLosingFocus = new ArrayList<>(); + + /** + * The foreground app of this display. Windows below this app cannot be the focused window. If + * the user taps on the area outside of the task of the focused app, we will notify AM about the + * new task the user wants to interact with. + */ + AppWindowToken mFocusedApp = null; + + /** Windows added since {@link #mCurrentFocus} was set to null. Used for ANR blaming. */ + final ArrayList<WindowState> mWinAddedSinceNullFocus = new ArrayList<>(); + + /** Windows removed since {@link #mCurrentFocus} was set to null. Used for ANR blaming. */ + final ArrayList<WindowState> mWinRemovedSinceNullFocus = new ArrayList<>(); + + /** * We organize all top-level Surfaces in to the following layers. * mOverlayLayer contains a few Surfaces which are always on top of others * and omitted from Screen-Magnification, for example the strict mode flash or @@ -412,6 +449,8 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo /** Caches the value whether told display manager that we have content. */ private boolean mLastHasContent; + private DisplayRotationUtil mRotationUtil = new DisplayRotationUtil(); + /** * The input method window for this display. */ @@ -439,36 +478,12 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo return; } - final int flags = w.mAttrs.flags; - - // If this window is animating, make a note that we have an animating window and take - // care of a request to run a detached wallpaper animation. - if (winAnimator.isAnimationSet()) { - final AnimationAdapter anim = w.getAnimation(); - if (anim != null) { - if ((flags & FLAG_SHOW_WALLPAPER) != 0 && anim.getDetachWallpaper()) { - mTmpWindow = w; - } - final int color = anim.getBackgroundColor(); - if (color != 0) { - final TaskStack stack = w.getStack(); - if (stack != null) { - stack.setAnimationBackground(winAnimator, color); - } - } - } - } - - // If this window's app token is running a detached wallpaper animation, make a note so - // we can ensure the wallpaper is displayed behind it. - final AppWindowToken atoken = winAnimator.mWin.mAppToken; - final AnimationAdapter animation = atoken != null ? atoken.getAnimation() : null; - if (animation != null) { - if ((flags & FLAG_SHOW_WALLPAPER) != 0 && animation.getDetachWallpaper()) { - mTmpWindow = w; - } - - final int color = animation.getBackgroundColor(); + // If this window is animating, ensure the animation background is set. + final AnimationAdapter anim = w.mAppToken != null + ? w.mAppToken.getAnimation() + : w.getAnimation(); + if (anim != null) { + final int color = anim.getBackgroundColor(); if (color != 0) { final TaskStack stack = w.getStack(); if (stack != null) { @@ -490,7 +505,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo }; private final ToBooleanFunction<WindowState> mFindFocusedWindow = w -> { - final AppWindowToken focusedApp = mService.mFocusedApp; + final AppWindowToken focusedApp = mFocusedApp; if (DEBUG_FOCUS) Slog.v(TAG_WM, "Looking for focus: " + w + ", flags=" + w.mAttrs.flags + ", canReceive=" + w.canReceiveKeys()); @@ -649,8 +664,6 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo final boolean obscuredChanged = w.mObscured != mTmpApplySurfaceChangesTransactionState.obscured; final RootWindowContainer root = mService.mRoot; - // Only used if default window - final boolean someoneLosingFocus = !mService.mLosingFocus.isEmpty(); // Update effect. w.mObscured = mTmpApplySurfaceChangesTransactionState.obscured; @@ -741,9 +754,8 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo } } - if (isDefaultDisplay && someoneLosingFocus && w == mService.mCurrentFocus - && w.isDisplayedLw()) { - mTmpApplySurfaceChangesTransactionState.focusDisplayed = true; + if (!mLosingFocus.isEmpty() && w.isFocused() && w.isDisplayedLw()) { + mService.mH.obtainMessage(REPORT_LOSING_FOCUS, this).sendToTarget(); } w.updateResizingWindowIfNeeded(); @@ -1367,21 +1379,12 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo cutout, mInitialDisplayWidth, mInitialDisplayHeight); } final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270); - final List<Rect> bounds = WmDisplayCutout.computeSafeInsets( + final Rect[] newBounds = mRotationUtil.getRotatedBounds( + WmDisplayCutout.computeSafeInsets( cutout, mInitialDisplayWidth, mInitialDisplayHeight) - .getDisplayCutout().getBoundingRects(); - transformPhysicalToLogicalCoordinates(rotation, mInitialDisplayWidth, mInitialDisplayHeight, - mTmpMatrix); - final Region region = Region.obtain(); - for (int i = 0; i < bounds.size(); i++) { - final Rect rect = bounds.get(i); - final RectF rectF = new RectF(bounds.get(i)); - mTmpMatrix.mapRect(rectF); - rectF.round(rect); - region.op(rect, Op.UNION); - } - - return WmDisplayCutout.computeSafeInsets(DisplayCutout.fromBounds(region), + .getDisplayCutout().getBoundingRectsAll(), + rotation, mInitialDisplayWidth, mInitialDisplayHeight); + return WmDisplayCutout.computeSafeInsets(DisplayCutout.fromBounds(newBounds), rotated ? mInitialDisplayHeight : mInitialDisplayWidth, rotated ? mInitialDisplayWidth : mInitialDisplayHeight); } @@ -2094,22 +2097,22 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo return null; } - void setTouchExcludeRegion(Task focusedTask) { - // The provided task is the task on this display with focus, so if WindowManagerService's - // focused app is not on this display, focusedTask will be null. + void updateTouchExcludeRegion() { + final Task focusedTask = (mFocusedApp != null ? mFocusedApp.getTask() : null); if (focusedTask == null) { mTouchExcludeRegion.setEmpty(); } else { mTouchExcludeRegion.set(mBaseDisplayRect); final int delta = dipToPixel(RESIZE_HANDLE_WIDTH_IN_DP, mDisplayMetrics); mTmpRect2.setEmpty(); - for (int stackNdx = mTaskStackContainers.getChildCount() - 1; stackNdx >= 0; --stackNdx) { + for (int stackNdx = mTaskStackContainers.getChildCount() - 1; stackNdx >= 0; + --stackNdx) { final TaskStack stack = mTaskStackContainers.getChildAt(stackNdx); stack.setTouchExcludeRegion(focusedTask, delta, mTouchExcludeRegion, mDisplayFrames.mContent, mTmpRect2); } // If we removed the focused task above, add it back and only leave its - // outside touch area in the exclusion. TapDectector is not interested in + // outside touch area in the exclusion. TapDetector is not interested in // any touch inside the focused task itself. if (!mTmpRect2.isEmpty()) { mTouchExcludeRegion.op(mTmpRect2, Region.Op.UNION); @@ -2120,12 +2123,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo // events to be intercepted and used to change focus. This would likely cause a // disappearance of the input method. mInputMethodWindow.getTouchableRegion(mTmpRegion); - if (mInputMethodWindow.getDisplayId() == mDisplayId) { - mTouchExcludeRegion.op(mTmpRegion, Op.UNION); - } else { - // IME is on a different display, so we need to update its tap detector. - setTouchExcludeRegion(null /* focusedTask */); - } + mTouchExcludeRegion.op(mTmpRegion, Op.UNION); } for (int i = mTapExcludedWindows.size() - 1; i >= 0; i--) { final WindowState win = mTapExcludedWindows.get(i); @@ -2307,21 +2305,6 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo mPinnedStackControllerLocked.setAdjustedForIme(imeVisible, imeHeight); } - /** - * If a window that has an animation specifying a colored background and the current wallpaper - * is visible, then the color goes *below* the wallpaper so we don't cause the wallpaper to - * suddenly disappear. - */ - int getLayerForAnimationBackground(WindowStateAnimator winAnimator) { - final WindowState visibleWallpaper = mBelowAppWindowsContainers.getWindow( - w -> w.mIsWallpaper && w.isVisibleNow()); - - if (visibleWallpaper != null) { - return visibleWallpaper.mWinAnimator.mAnimLayer; - } - return winAnimator.mAnimLayer; - } - void prepareFreezingTaskBounds() { for (int stackNdx = mTaskStackContainers.getChildCount() - 1; stackNdx >= 0; --stackNdx) { final TaskStack stack = mTaskStackContainers.getChildAt(stackNdx); @@ -2411,6 +2394,9 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo } mDisplayFrames.writeToProto(proto, DISPLAY_FRAMES); proto.write(SURFACE_SIZE, mSurfaceSize); + if (mFocusedApp != null) { + mFocusedApp.writeNameToProto(proto, FOCUSED_APP); + } proto.end(token); } @@ -2451,6 +2437,27 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo pw.print(prefix); pw.print("mDeferredRotationPauseCount="); pw.println(mDeferredRotationPauseCount); + pw.print(" mCurrentFocus="); pw.println(mCurrentFocus); + if (mLastFocus != mCurrentFocus) { + pw.print(" mLastFocus="); pw.println(mLastFocus); + } + if (mLosingFocus.size() > 0) { + pw.println(); + pw.println(" Windows losing focus:"); + for (int i = mLosingFocus.size() - 1; i >= 0; i--) { + final WindowState w = mLosingFocus.get(i); + pw.print(" Losing #"); pw.print(i); pw.print(' '); + pw.print(w); + if (dumpAll) { + pw.println(":"); + w.dump(pw, " ", true); + } else { + pw.println(); + } + } + } + pw.print(" mFocusedApp="); pw.println(mFocusedApp); + pw.println(); pw.println(prefix + "Application tokens in top down Z order:"); for (int stackNdx = mTaskStackContainers.getChildCount() - 1; stackNdx >= 0; --stackNdx) { @@ -2582,6 +2589,132 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo return mTmpWindow; } + + /** + * Update the focused window and make some adjustments if the focus has changed. + * + * @param mode Indicates the situation we are in. Possible modes are: + * {@link WindowManagerService#UPDATE_FOCUS_NORMAL}, + * {@link WindowManagerService#UPDATE_FOCUS_PLACING_SURFACES}, + * {@link WindowManagerService#UPDATE_FOCUS_WILL_PLACE_SURFACES}, + * {@link WindowManagerService#UPDATE_FOCUS_REMOVING_FOCUS} + * @param updateInputWindows Whether to sync the window information to the input module. + * @return {@code true} if the focused window has changed. + */ + boolean updateFocusedWindowLocked(int mode, boolean updateInputWindows, boolean focusFound) { + final WindowState newFocus = findFocusedWindow(); + if (mCurrentFocus == newFocus) { + return false; + } + mService.mH.obtainMessage(REPORT_FOCUS_CHANGE, this).sendToTarget(); + boolean imWindowChanged = false; + // TODO (b/111080190): Multi-Session IME + if (!focusFound) { + final WindowState imWindow = mInputMethodWindow; + if (imWindow != null) { + final WindowState prevTarget = mService.mInputMethodTarget; + + final WindowState newTarget = computeImeTarget(true /* updateImeTarget*/); + imWindowChanged = prevTarget != newTarget; + + if (mode != UPDATE_FOCUS_WILL_ASSIGN_LAYERS + && mode != UPDATE_FOCUS_WILL_PLACE_SURFACES) { + assignWindowLayers(false /* setLayoutNeeded */); + } + } + } + + if (imWindowChanged) { + mService.mWindowsChanged = true; + setLayoutNeeded(); + } + + if (DEBUG_FOCUS_LIGHT || mService.localLOGV) Slog.v(TAG_WM, "Changing focus from " + + mCurrentFocus + " to " + newFocus + " displayId=" + getDisplayId() + + " Callers=" + Debug.getCallers(4)); + final WindowState oldFocus = mCurrentFocus; + mCurrentFocus = newFocus; + mLosingFocus.remove(newFocus); + + if (newFocus != null) { + mWinAddedSinceNullFocus.clear(); + mWinRemovedSinceNullFocus.clear(); + + if (newFocus.canReceiveKeys()) { + // Displaying a window implicitly causes dispatching to be unpaused. + // This is to protect against bugs if someone pauses dispatching but + // forgets to resume. + newFocus.mToken.paused = false; + } + } + + // System UI is only shown on the default display. + int focusChanged = isDefaultDisplay + ? mService.mPolicy.focusChangedLw(oldFocus, newFocus) : 0; + + if (imWindowChanged && oldFocus != mInputMethodWindow) { + // Focus of the input method window changed. Perform layout if needed. + if (mode == UPDATE_FOCUS_PLACING_SURFACES) { + performLayout(true /*initial*/, updateInputWindows); + focusChanged &= ~FINISH_LAYOUT_REDO_LAYOUT; + } else if (mode == UPDATE_FOCUS_WILL_PLACE_SURFACES) { + // Client will do the layout, but we need to assign layers + // for handleNewWindowLocked() below. + assignWindowLayers(false /* setLayoutNeeded */); + } + } + + if ((focusChanged & FINISH_LAYOUT_REDO_LAYOUT) != 0) { + // The change in focus caused us to need to do a layout. Okay. + setLayoutNeeded(); + if (mode == UPDATE_FOCUS_PLACING_SURFACES) { + performLayout(true /*initial*/, updateInputWindows); + } else if (mode == UPDATE_FOCUS_REMOVING_FOCUS) { + mService.mRoot.performSurfacePlacement(false); + } + } + + if (mode != UPDATE_FOCUS_WILL_ASSIGN_LAYERS) { + // If we defer assigning layers, then the caller is responsible for doing this part. + getInputMonitor().setInputFocusLw(newFocus, updateInputWindows); + } + + adjustForImeIfNeeded(); + + // We may need to schedule some toast windows to be removed. The toasts for an app that + // does not have input focus are removed within a timeout to prevent apps to redress + // other apps' UI. + scheduleToastWindowsTimeoutIfNeededLocked(oldFocus, newFocus); + + if (mode == UPDATE_FOCUS_PLACING_SURFACES) { + pendingLayoutChanges |= FINISH_LAYOUT_REDO_ANIM; + } + return true; + } + + /** + * Set the new focused app to this display. + * + * @param newFocus the new focused AppWindowToken. + * @return true if the focused app is changed. + */ + boolean setFocusedApp(AppWindowToken newFocus) { + if (newFocus != null) { + final DisplayContent appDisplay = newFocus.getDisplayContent(); + if (appDisplay != this) { + throw new IllegalStateException(newFocus + " is not on " + getName() + + " but " + ((appDisplay != null) ? appDisplay.getName() : "none")); + } + } + if (mFocusedApp == newFocus) { + return false; + } + mFocusedApp = newFocus; + getInputMonitor().setFocusedAppLw(newFocus); + updateTouchExcludeRegion(); + return true; + } + /** Updates the layer assignment of windows on this display. */ void assignWindowLayers(boolean setLayoutNeeded) { Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "assignWindowLayers"); @@ -2746,22 +2879,13 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo if (highestTarget != null) { final AppTransition appTransition = mService.mAppTransition; if (DEBUG_INPUT_METHOD) Slog.v(TAG_WM, appTransition + " " + highestTarget - + " animating=" + highestTarget.mWinAnimator.isAnimationSet() - + " layer=" + highestTarget.mWinAnimator.mAnimLayer - + " new layer=" + target.mWinAnimator.mAnimLayer); + + " animating=" + highestTarget.isAnimating()); if (appTransition.isTransitionSet()) { // If we are currently setting up for an animation, hold everything until we // can find out what will happen. setInputMethodTarget(highestTarget, true); return highestTarget; - } else if (highestTarget.mWinAnimator.isAnimationSet() && - highestTarget.mWinAnimator.mAnimLayer > target.mWinAnimator.mAnimLayer) { - // If the window we are currently targeting is involved with an animation, - // and it is on top of the next target we will be over, then hold off on - // moving until that is done. - setInputMethodTarget(highestTarget, true); - return highestTarget; } } } @@ -2934,26 +3058,16 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo return false; } - void updateWindowsForAnimator(WindowAnimator animator) { - mTmpWindowAnimator = animator; + void updateWindowsForAnimator() { forAllWindows(mUpdateWindowsForAnimator, true /* traverseTopToBottom */); } - void updateWallpaperForAnimator(WindowAnimator animator) { + /** + * Updates the {@link TaskStack#setAnimationBackground} for all windows. + */ + void updateBackgroundForAnimator() { resetAnimationBackgroundAnimator(); - - // Used to indicate a detached wallpaper. - mTmpWindow = null; - mTmpWindowAnimator = animator; - forAllWindows(mUpdateWallpaperForAnimator, true /* traverseTopToBottom */); - - if (animator.mWindowDetachedWallpaper != mTmpWindow) { - if (DEBUG_WALLPAPER) Slog.v(TAG, "Detached wallpaper changed from " - + animator.mWindowDetachedWallpaper + " to " + mTmpWindow); - animator.mWindowDetachedWallpaper = mTmpWindow; - animator.mBulkUpdateParams |= SET_WALLPAPER_MAY_CHANGE; - } } boolean isInputMethodClientFocus(int uid, int pid) { @@ -2964,8 +3078,8 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo if (DEBUG_INPUT_METHOD) { Slog.i(TAG_WM, "Desired input method target: " + imFocus); - Slog.i(TAG_WM, "Current focus: " + mService.mCurrentFocus); - Slog.i(TAG_WM, "Last focus: " + mService.mLastFocus); + Slog.i(TAG_WM, "Current focus: " + mCurrentFocus + " displayId=" + mDisplayId); + Slog.i(TAG_WM, "Last focus: " + mLastFocus + " displayId=" + mDisplayId); } if (DEBUG_INPUT_METHOD) { @@ -3033,7 +3147,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo } // TODO: Super crazy long method that should be broken down... - boolean applySurfaceChangesTransaction(boolean recoveringMemory) { + void applySurfaceChangesTransaction(boolean recoveringMemory) { final int dw = mDisplayInfo.logicalWidth; final int dh = mDisplayInfo.logicalHeight; @@ -3117,8 +3231,6 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo // can now be shown. atoken.updateAllDrawn(); } - - return mTmpApplySurfaceChangesTransactionState.focusDisplayed; } private void updateBounds() { @@ -3372,7 +3484,6 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo boolean displayHasContent; boolean obscured; boolean syswin; - boolean focusDisplayed; float preferredRefreshRate; int preferredModeId; @@ -3380,7 +3491,6 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo displayHasContent = false; obscured = false; syswin = false; - focusDisplayed = false; preferredRefreshRate = 0; preferredModeId = 0; } diff --git a/services/core/java/com/android/server/wm/DisplayWindowController.java b/services/core/java/com/android/server/wm/DisplayWindowController.java index 76b6dbea36b9..ab8775983291 100644 --- a/services/core/java/com/android/server/wm/DisplayWindowController.java +++ b/services/core/java/com/android/server/wm/DisplayWindowController.java @@ -17,11 +17,14 @@ package com.android.server.wm; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DISPLAY; +import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_FOCUS_LIGHT; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STACK; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; +import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_NORMAL; import android.content.res.Configuration; import android.os.Binder; +import android.os.IBinder; import android.util.Slog; import android.view.Display; @@ -124,6 +127,42 @@ public class DisplayWindowController } } + /** + * Sets a focused app on this display. + * + * @param token Specifies which app should be focused. + * @param moveFocusNow Specifies if we should update the focused window immediately. + */ + public void setFocusedApp(IBinder token, boolean moveFocusNow) { + synchronized (mWindowMap) { + if (mContainer == null) { + if (DEBUG_FOCUS_LIGHT) Slog.i(TAG_WM, "setFocusedApp: could not find displayId=" + + mDisplayId); + return; + } + final AppWindowToken newFocus; + if (token == null) { + if (DEBUG_FOCUS_LIGHT) Slog.v(TAG_WM, "Clearing focused app, displayId=" + + mDisplayId); + newFocus = null; + } else { + newFocus = mRoot.getAppWindowToken(token); + if (newFocus == null) { + Slog.w(TAG_WM, "Attempted to set focus to non-existing app token: " + token + + ", displayId=" + mDisplayId); + } + if (DEBUG_FOCUS_LIGHT) Slog.v(TAG_WM, "Set focused app to: " + newFocus + + " moveFocusNow=" + moveFocusNow + " displayId=" + mDisplayId); + } + + final boolean changed = mContainer.setFocusedApp(newFocus); + if (moveFocusNow && changed) { + mService.updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL, + true /*updateInputWindows*/); + } + } + } + @Override public String toString() { return "{DisplayWindowController displayId=" + mDisplayId + "}"; diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java index ef3a770390ef..15f693872158 100644 --- a/services/core/java/com/android/server/wm/InputMonitor.java +++ b/services/core/java/com/android/server/wm/InputMonitor.java @@ -64,7 +64,6 @@ final class InputMonitor { // Array of window handles to provide to the input dispatcher. private InputWindowHandle[] mInputWindowHandles; private int mInputWindowHandleCount; - private InputWindowHandle mFocusedInputWindowHandle; private boolean mDisableWallpaperTouchEvents; private final Rect mTmpRect = new Rect(); @@ -229,16 +228,12 @@ final class InputMonitor { + child + ", " + inputWindowHandle); } addInputWindowHandle(inputWindowHandle); - if (hasFocus) { - mFocusedInputWindowHandle = inputWindowHandle; - } } private void clearInputWindowHandlesLw() { while (mInputWindowHandleCount != 0) { mInputWindowHandles[--mInputWindowHandleCount] = null; } - mFocusedInputWindowHandle = null; } void setUpdateInputWindowsNeededLw() { @@ -325,13 +320,13 @@ final class InputMonitor { public void setFocusedAppLw(AppWindowToken newApp) { // Focused app has changed. if (newApp == null) { - mService.mInputManager.setFocusedApplication(null); + mService.mInputManager.setFocusedApplication(mDisplayId, null); } else { final InputApplicationHandle handle = newApp.mInputApplicationHandle; handle.name = newApp.toString(); handle.dispatchingTimeoutNanos = newApp.mInputDispatchingTimeoutNanos; - mService.mInputManager.setFocusedApplication(handle); + mService.mInputManager.setFocusedApplication(mDisplayId, handle); } } @@ -370,8 +365,7 @@ final class InputMonitor { void onRemoved() { // If DisplayContent removed, we need find a way to remove window handles of this display // from InputDispatcher, so pass an empty InputWindowHandles to remove them. - mService.mInputManager.setInputWindows(mInputWindowHandles, mFocusedInputWindowHandle, - mDisplayId); + mService.mInputManager.setInputWindows(mInputWindowHandles, mDisplayId); } private final class UpdateInputForAllWindowsConsumer implements Consumer<WindowState> { @@ -414,8 +408,7 @@ final class InputMonitor { } // Send windows to native code. - mService.mInputManager.setInputWindows(mInputWindowHandles, mFocusedInputWindowHandle, - mDisplayId); + mService.mInputManager.setInputWindows(mInputWindowHandles, mDisplayId); clearInputWindowHandlesLw(); @@ -435,7 +428,6 @@ final class InputMonitor { final int flags = w.mAttrs.flags; final int privateFlags = w.mAttrs.privateFlags; final int type = w.mAttrs.type; - // TODO(b/111361570): multi-display focus, one focus window per display. final boolean hasFocus = w.isFocused(); final boolean isVisible = w.isVisibleLw(); diff --git a/services/core/java/com/android/server/wm/LocalAnimationAdapter.java b/services/core/java/com/android/server/wm/LocalAnimationAdapter.java index d89d6f056218..77a024cc2e99 100644 --- a/services/core/java/com/android/server/wm/LocalAnimationAdapter.java +++ b/services/core/java/com/android/server/wm/LocalAnimationAdapter.java @@ -44,11 +44,6 @@ class LocalAnimationAdapter implements AnimationAdapter { } @Override - public boolean getDetachWallpaper() { - return mSpec.getDetachWallpaper(); - } - - @Override public boolean getShowWallpaper() { return mSpec.getShowWallpaper(); } @@ -98,13 +93,6 @@ class LocalAnimationAdapter implements AnimationAdapter { interface AnimationSpec { /** - * @see AnimationAdapter#getDetachWallpaper - */ - default boolean getDetachWallpaper() { - return false; - } - - /** * @see AnimationAdapter#getShowWallpaper */ default boolean getShowWallpaper() { diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java index e718c7b2a36e..6fef16304d42 100644 --- a/services/core/java/com/android/server/wm/RecentsAnimationController.java +++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java @@ -609,11 +609,6 @@ public class RecentsAnimationController implements DeathRecipient { } @Override - public boolean getDetachWallpaper() { - return false; - } - - @Override public boolean getShowWallpaper() { return false; } diff --git a/services/core/java/com/android/server/wm/RemoteAnimationController.java b/services/core/java/com/android/server/wm/RemoteAnimationController.java index 00422e3497be..8ec0a014e4a9 100644 --- a/services/core/java/com/android/server/wm/RemoteAnimationController.java +++ b/services/core/java/com/android/server/wm/RemoteAnimationController.java @@ -325,11 +325,6 @@ class RemoteAnimationController implements DeathRecipient { } @Override - public boolean getDetachWallpaper() { - return false; - } - - @Override public boolean getShowWallpaper() { return false; } diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index a9571be9599d..3fef87dce460 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -17,19 +17,20 @@ package com.android.server.wm; import static android.view.Display.DEFAULT_DISPLAY; +import static android.view.Display.INVALID_DISPLAY; import static android.view.WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_SUSTAINED_PERFORMANCE_MODE; import static android.view.WindowManager.LayoutParams.TYPE_DREAM; import static android.view.WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG; -import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM; import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT; import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; import static com.android.server.wm.RootWindowContainerProto.DISPLAYS; import static com.android.server.wm.RootWindowContainerProto.WINDOWS; import static com.android.server.wm.RootWindowContainerProto.WINDOW_CONTAINER; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DISPLAY; +import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_FOCUS_LIGHT; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_KEEP_SCREEN_ON; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT_REPEATS; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ORIENTATION; @@ -41,9 +42,9 @@ import static com.android.server.wm.WindowManagerDebugConfig.SHOW_TRANSACTIONS; import static com.android.server.wm.WindowManagerDebugConfig.TAG_KEEP_SCREEN_ON; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; -import static com.android.server.wm.WindowManagerService.H.REPORT_LOSING_FOCUS; import static com.android.server.wm.WindowManagerService.H.SEND_NEW_CONFIGURATION; import static com.android.server.wm.WindowManagerService.H.WINDOW_FREEZE_TIMEOUT; +import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_NORMAL; import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_PLACING_SURFACES; import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_WILL_PLACE_SURFACES; import static com.android.server.wm.WindowManagerService.WINDOWS_FREEZING_SCREENS_NONE; @@ -124,6 +125,10 @@ class RootWindowContainer extends WindowContainer<DisplayContent> { private String mCloseSystemDialogsReason; + // The ID of the display which is responsible for receiving display-unspecified key and pointer + // events. + private int mTopFocusedDisplayId = INVALID_DISPLAY; + // Only a seperate transaction until we seperate the apply surface changes // transaction from the global transaction. private final SurfaceControl.Transaction mDisplayTransaction = new SurfaceControl.Transaction(); @@ -153,23 +158,40 @@ class RootWindowContainer extends WindowContainer<DisplayContent> { mWallpaperController = new WallpaperController(mService); } - WindowState computeFocusedWindow() { - // While the keyguard is showing, we must focus anything besides the main display. - // Otherwise we risk input not going to the keyguard when the user expects it to. - final boolean forceDefaultDisplay = mService.isKeyguardShowingAndNotOccluded(); - + boolean updateFocusedWindowLocked(int mode, boolean updateInputWindows) { + boolean changed = false; + int topFocusedDisplayId = INVALID_DISPLAY; for (int i = mChildren.size() - 1; i >= 0; i--) { final DisplayContent dc = mChildren.get(i); - final WindowState win = dc.findFocusedWindow(); - if (win != null) { - if (forceDefaultDisplay && !dc.isDefaultDisplay) { - EventLog.writeEvent(0x534e4554, "71786287", win.mOwnerUid, ""); - continue; - } - return win; + changed |= dc.updateFocusedWindowLocked(mode, updateInputWindows, + topFocusedDisplayId != INVALID_DISPLAY /* focusFound */); + if (topFocusedDisplayId == INVALID_DISPLAY && dc.mCurrentFocus != null) { + topFocusedDisplayId = dc.getDisplayId(); } } - return null; + if (topFocusedDisplayId == INVALID_DISPLAY) { + topFocusedDisplayId = DEFAULT_DISPLAY; + } + if (mTopFocusedDisplayId != topFocusedDisplayId) { + mTopFocusedDisplayId = topFocusedDisplayId; + mService.mInputManager.setFocusedDisplay(topFocusedDisplayId); + if (DEBUG_FOCUS_LIGHT) Slog.v(TAG_WM, "New topFocusedDisplayId=" + + topFocusedDisplayId); + } + final WindowState topFocusedWindow = getTopFocusedDisplayContent().mCurrentFocus; + mService.mInputManager.setFocusedWindow( + topFocusedWindow != null ? topFocusedWindow.mInputWindowHandle : null); + return changed; + } + + DisplayContent getTopFocusedDisplayContent() { + return getDisplayContent(mTopFocusedDisplayId == INVALID_DISPLAY + ? DEFAULT_DISPLAY : mTopFocusedDisplayId); + } + + @Override + void onChildPositionChanged() { + mService.updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL, false /* updateInputWindows */); } DisplayContent getDisplayContent(int displayId) { @@ -636,7 +658,6 @@ class RootWindowContainer extends WindowContainer<DisplayContent> { if (mService.updateFocusedWindowLocked(UPDATE_FOCUS_PLACING_SURFACES, false /*updateInputWindows*/)) { updateInputWindowsNeeded = true; - defaultDisplay.pendingLayoutChanges |= FINISH_LAYOUT_REDO_ANIM; } } @@ -646,7 +667,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> { defaultDisplay.pendingLayoutChanges); } - final ArraySet<DisplayContent> touchExcludeRegionUpdateDisplays = handleResizingWindows(); + handleResizingWindows(); if (DEBUG_ORIENTATION && mService.mDisplayFrozen) Slog.v(TAG, "With display frozen, orientationChangeComplete=" + mOrientationChangeComplete); @@ -765,17 +786,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> { dc.getInputMonitor().updateInputWindowsLw(false /*force*/); }); } - mService.setFocusTaskRegionLocked(null); - if (touchExcludeRegionUpdateDisplays != null) { - final DisplayContent focusedDc = mService.mFocusedApp != null - ? mService.mFocusedApp.getDisplayContent() : null; - for (DisplayContent dc : touchExcludeRegionUpdateDisplays) { - // The focused DisplayContent was recalcuated in setFocusTaskRegionLocked - if (focusedDc != dc) { - dc.setTouchExcludeRegion(null /* focusedTask */); - } - } - } + forAllDisplays(DisplayContent::updateTouchExcludeRegion); // Check to see if we are now in a state where the screen should // be enabled, because the window obscured flags have changed. @@ -808,16 +819,10 @@ class RootWindowContainer extends WindowContainer<DisplayContent> { mService.getDefaultDisplayRotation()); } - boolean focusDisplayed = false; - final int count = mChildren.size(); for (int j = 0; j < count; ++j) { final DisplayContent dc = mChildren.get(j); - focusDisplayed |= dc.applySurfaceChangesTransaction(recoveringMemory); - } - - if (focusDisplayed) { - mService.mH.sendEmptyMessage(REPORT_LOSING_FOCUS); + dc.applySurfaceChangesTransaction(recoveringMemory); } // Give the display manager a chance to adjust properties like display rotation if it needs @@ -828,12 +833,8 @@ class RootWindowContainer extends WindowContainer<DisplayContent> { /** * Handles resizing windows during surface placement. - * - * @return A set of any DisplayContent whose touch exclude region needs to be recalculated due - * to a tap-exclude window resizing, or null if no such DisplayContents were found. */ - private ArraySet<DisplayContent> handleResizingWindows() { - ArraySet<DisplayContent> touchExcludeRegionUpdateSet = null; + private void handleResizingWindows() { for (int i = mService.mResizingWindows.size() - 1; i >= 0; i--) { WindowState win = mService.mResizingWindows.get(i); if (win.mAppFreezing) { @@ -842,15 +843,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> { } win.reportResized(); mService.mResizingWindows.remove(i); - if (WindowManagerService.excludeWindowTypeFromTapOutTask(win.mAttrs.type)) { - final DisplayContent dc = win.getDisplayContent(); - if (touchExcludeRegionUpdateSet == null) { - touchExcludeRegionUpdateSet = new ArraySet<>(); - } - touchExcludeRegionUpdateSet.add(dc); - } } - return touchExcludeRegionUpdateSet; } /** @@ -1004,6 +997,10 @@ class RootWindowContainer extends WindowContainer<DisplayContent> { } } + void dumpTopFocusedDisplayId(PrintWriter pw) { + pw.print(" mTopFocusedDisplayId="); pw.println(mTopFocusedDisplayId); + } + void dumpLayoutNeededDisplayIds(PrintWriter pw) { if (!isLayoutNeeded()) { return; diff --git a/services/core/java/com/android/server/wm/TaskPositioningController.java b/services/core/java/com/android/server/wm/TaskPositioningController.java index 33416f66f85b..b7e37b2fd47a 100644 --- a/services/core/java/com/android/server/wm/TaskPositioningController.java +++ b/services/core/java/com/android/server/wm/TaskPositioningController.java @@ -131,9 +131,9 @@ class TaskPositioningController { // of the app, it may not have focus since there might be other windows // on top (eg. a dialog window). WindowState transferFocusFromWin = win; - if (mService.mCurrentFocus != null && mService.mCurrentFocus != win - && mService.mCurrentFocus.mAppToken == win.mAppToken) { - transferFocusFromWin = mService.mCurrentFocus; + if (displayContent.mCurrentFocus != null && displayContent.mCurrentFocus != win + && displayContent.mCurrentFocus.mAppToken == win.mAppToken) { + transferFocusFromWin = displayContent.mCurrentFocus; } if (!mInputManager.transferTouchFocus( transferFocusFromWin.mInputChannel, mTaskPositioner.mServerChannel)) { diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java index 2b8493749c79..00cacebe2960 100644 --- a/services/core/java/com/android/server/wm/TaskStack.java +++ b/services/core/java/com/android/server/wm/TaskStack.java @@ -1070,11 +1070,8 @@ public class TaskStack extends WindowContainer<Task> implements } void setAnimationBackground(WindowStateAnimator winAnimator, int color) { - int animLayer = winAnimator.mAnimLayer; - if (mAnimationBackgroundAnimator == null - || animLayer < mAnimationBackgroundAnimator.mAnimLayer) { + if (mAnimationBackgroundAnimator == null) { mAnimationBackgroundAnimator = winAnimator; - animLayer = mDisplayContent.getLayerForAnimationBackground(winAnimator); showAnimationSurface(((color >> 24) & 0xff) / 255f); } } diff --git a/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java b/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java index f1e1592da83e..52f8510d6a99 100644 --- a/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java +++ b/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java @@ -19,12 +19,12 @@ package com.android.server.wm; import android.graphics.Rect; import android.graphics.Region; import android.hardware.input.InputManager; +import android.os.Handler; import android.view.MotionEvent; import android.view.WindowManagerPolicyConstants.PointerEventListener; import com.android.server.wm.WindowManagerService.H; -import static android.view.Display.DEFAULT_DISPLAY; import static android.view.PointerIcon.TYPE_NOT_SPECIFIED; import static android.view.PointerIcon.TYPE_HORIZONTAL_DOUBLE_ARROW; import static android.view.PointerIcon.TYPE_VERTICAL_DOUBLE_ARROW; @@ -36,6 +36,8 @@ public class TaskTapPointerEventListener implements PointerEventListener { final private Region mTouchExcludeRegion = new Region(); private final WindowManagerService mService; private final DisplayContent mDisplayContent; + private final Handler mHandler; + private final Runnable mMoveDisplayToTop; private final Rect mTmpRect = new Rect(); private int mPointerIconType = TYPE_NOT_SPECIFIED; @@ -43,6 +45,13 @@ public class TaskTapPointerEventListener implements PointerEventListener { DisplayContent displayContent) { mService = service; mDisplayContent = displayContent; + mHandler = new Handler(mService.mH.getLooper()); + mMoveDisplayToTop = () -> { + synchronized (mService.mWindowMap) { + mDisplayContent.getParent().positionChildAt(WindowContainer.POSITION_TOP, + mDisplayContent, true /* includingParents */); + } + }; } @Override @@ -61,6 +70,7 @@ public class TaskTapPointerEventListener implements PointerEventListener { mService.mTaskPositioningController.handleTapOutsideTask( mDisplayContent, x, y); } + mHandler.post(mMoveDisplayToTop); } } break; diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java index 3d349ce34d6b..a448f97306f0 100644 --- a/services/core/java/com/android/server/wm/WallpaperController.java +++ b/services/core/java/com/android/server/wm/WallpaperController.java @@ -120,13 +120,11 @@ class WallpaperController { } mFindResults.resetTopWallpaper = true; - if (w != winAnimator.mWindowDetachedWallpaper && w.mAppToken != null) { + if (w.mAppToken != null && w.mAppToken.isHidden() && !w.mAppToken.isSelfAnimating()) { + // If this window's app token is hidden and not animating, it is of no interest to us. - if (w.mAppToken.isHidden() && !w.mAppToken.isSelfAnimating()) { - if (DEBUG_WALLPAPER) Slog.v(TAG, - "Skipping hidden and not animating token: " + w); - return false; - } + if (DEBUG_WALLPAPER) Slog.v(TAG, "Skipping hidden and not animating token: " + w); + return false; } if (DEBUG_WALLPAPER) Slog.v(TAG, "Win " + w + ": isOnScreen=" + w.isOnScreen() + " mDrawState=" + w.mWinAnimator.mDrawState); @@ -177,7 +175,7 @@ class WallpaperController { && (mWallpaperTarget == w || w.isDrawFinishedLw())) { if (DEBUG_WALLPAPER) Slog.v(TAG, "Found wallpaper target: " + w); mFindResults.setWallpaperTarget(w); - if (w == mWallpaperTarget && w.mWinAnimator.isAnimationSet()) { + if (w == mWallpaperTarget && w.isAnimating()) { // The current wallpaper target is animating, so we'll look behind it for // another possible target and figure out what is going on later. if (DEBUG_WALLPAPER) Slog.v(TAG, @@ -185,10 +183,6 @@ class WallpaperController { } // Found a target! End search. return true; - } else if (w == winAnimator.mWindowDetachedWallpaper) { - if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG, - "Found animating detached wallpaper target win: " + w); - mFindResults.setUseTopWallpaperAsTarget(true); } return false; }; @@ -243,7 +237,7 @@ class WallpaperController { } boolean isWallpaperTargetAnimating() { - return mWallpaperTarget != null && mWallpaperTarget.mWinAnimator.isAnimationSet() + return mWallpaperTarget != null && mWallpaperTarget.isAnimating() && (mWallpaperTarget.mAppToken == null || !mWallpaperTarget.mAppToken.isWaitingForTransitionStart()); } diff --git a/services/core/java/com/android/server/wm/WallpaperWindowToken.java b/services/core/java/com/android/server/wm/WallpaperWindowToken.java index ddda027595da..e15b783b5606 100644 --- a/services/core/java/com/android/server/wm/WallpaperWindowToken.java +++ b/services/core/java/com/android/server/wm/WallpaperWindowToken.java @@ -138,7 +138,7 @@ class WallpaperWindowToken extends WindowToken { wallpaper.dispatchWallpaperVisibility(visible); if (DEBUG_LAYERS || DEBUG_WALLPAPER_LIGHT) Slog.v(TAG, "adjustWallpaper win " - + wallpaper + " anim layer: " + wallpaper.mWinAnimator.mAnimLayer); + + wallpaper); } } diff --git a/services/core/java/com/android/server/wm/WindowAnimationSpec.java b/services/core/java/com/android/server/wm/WindowAnimationSpec.java index 825255e556ff..98c77ac719f9 100644 --- a/services/core/java/com/android/server/wm/WindowAnimationSpec.java +++ b/services/core/java/com/android/server/wm/WindowAnimationSpec.java @@ -72,11 +72,6 @@ public class WindowAnimationSpec implements AnimationSpec { } @Override - public boolean getDetachWallpaper() { - return mAnimation.getDetachWallpaper(); - } - - @Override public boolean getShowWallpaper() { return mAnimation.getShowWallpaper(); } diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java index a1d6ffd0249e..ad0b8ece7a80 100644 --- a/services/core/java/com/android/server/wm/WindowAnimator.java +++ b/services/core/java/com/android/server/wm/WindowAnimator.java @@ -58,17 +58,6 @@ public class WindowAnimator { /** Time of current animation step. Reset on each iteration */ long mCurrentTime; - boolean mAppWindowAnimating; - /** Skip repeated AppWindowTokens initialization. Note that AppWindowsToken's version of this - * is a long initialized to Long.MIN_VALUE so that it doesn't match this value on startup. */ - int mAnimTransactionSequence; - - /** Window currently running an animation that has requested it be detached - * from the wallpaper. This means we need to ensure the wallpaper is - * visible behind it in case it animates in a way that would allow it to be - * seen. If multiple windows satisfy this, use the lowest window. */ - WindowState mWindowDetachedWallpaper = null; - int mBulkUpdateParams = 0; Object mLastWindowFreezeSource; @@ -191,9 +180,8 @@ public class WindowAnimator { // Update animations of all applications, including those // associated with exiting/removed apps - ++mAnimTransactionSequence; - dc.updateWindowsForAnimator(this); - dc.updateWallpaperForAnimator(this); + dc.updateWindowsForAnimator(); + dc.updateBackgroundForAnimator(); dc.prepareSurfaces(); } @@ -314,8 +302,6 @@ public class WindowAnimator { pw.println(); if (dumpAll) { - pw.print(prefix); pw.print("mAnimTransactionSequence="); - pw.print(mAnimTransactionSequence); pw.print(prefix); pw.print("mCurrentTime="); pw.println(TimeUtils.formatUptime(mCurrentTime)); } @@ -324,10 +310,6 @@ public class WindowAnimator { pw.print(Integer.toHexString(mBulkUpdateParams)); pw.println(bulkUpdateParamsToString(mBulkUpdateParams)); } - if (mWindowDetachedWallpaper != null) { - pw.print(prefix); pw.print("mWindowDetachedWallpaper="); - pw.println(mWindowDetachedWallpaper); - } } int getPendingLayoutChanges(final int displayId) { diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index 4883f972f1e5..46999a2a847e 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -273,6 +273,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< parent.mTreeWeight += child.mTreeWeight; parent = parent.getParent(); } + onChildPositionChanged(); } /** @@ -298,6 +299,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< parent.mTreeWeight -= child.mTreeWeight; parent = parent.getParent(); } + onChildPositionChanged(); } /** @@ -455,9 +457,15 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< mChildren.remove(child); mChildren.add(position, child); } + onChildPositionChanged(); } /** + * Notify that a child's position has changed. Possible changes are adding or removing a child. + */ + void onChildPositionChanged() { } + + /** * Update override configuration and recalculate full config. * @see #mOverrideConfiguration * @see #mFullConfiguration diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index b627df4a3313..10ba63e0a9f7 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -499,12 +499,6 @@ public class WindowManagerService extends IWindowManager.Stub final ArrayList<WindowState> mDestroyPreservedSurface = new ArrayList<>(); /** - * Windows that have lost input focus and are waiting for the new - * focus window to be displayed before they are told about this. - */ - ArrayList<WindowState> mLosingFocus = new ArrayList<>(); - - /** * This is set when we have run out of memory, and will either be an empty * list or contain windows that need to be force removed. */ @@ -639,14 +633,6 @@ public class WindowManagerService extends IWindowManager.Stub */ final Handler mAnimationHandler = new Handler(AnimationThread.getHandler().getLooper()); - WindowState mCurrentFocus = null; - WindowState mLastFocus = null; - - /** Windows added since {@link #mCurrentFocus} was set to null. Used for ANR blaming. */ - private final ArrayList<WindowState> mWinAddedSinceNullFocus = new ArrayList<>(); - /** Windows removed since {@link #mCurrentFocus} was set to null. Used for ANR blaming. */ - private final ArrayList<WindowState> mWinRemovedSinceNullFocus = new ArrayList<>(); - /** This just indicates the window the input method is on top of, not * necessarily the window its input is going to. */ WindowState mInputMethodTarget = null; @@ -721,9 +707,6 @@ public class WindowManagerService extends IWindowManager.Stub } } - // TODO: Move to RootWindowContainer - AppWindowToken mFocusedApp = null; - PowerManager mPowerManager; PowerManagerInternal mPowerManagerInternal; @@ -1371,8 +1354,8 @@ public class WindowManagerService extends IWindowManager.Stub // the screen after the activity goes away. if (addToastWindowRequiresToken || (attrs.flags & LayoutParams.FLAG_NOT_FOCUSABLE) == 0 - || mCurrentFocus == null - || mCurrentFocus.mOwnerUid != callingUid) { + || displayContent.mCurrentFocus == null + || displayContent.mCurrentFocus.mOwnerUid != callingUid) { mH.sendMessageDelayed( mH.obtainMessage(H.WINDOW_HIDE_TIMEOUT, win), win.mAttrs.hideTimeoutMilliseconds); @@ -1382,8 +1365,8 @@ public class WindowManagerService extends IWindowManager.Stub // From now on, no exceptions or errors allowed! res = WindowManagerGlobal.ADD_OKAY; - if (mCurrentFocus == null) { - mWinAddedSinceNullFocus.add(win); + if (displayContent.mCurrentFocus == null) { + displayContent.mWinAddedSinceNullFocus.add(win); } if (excludeWindowTypeFromTapOutTask(type)) { @@ -1504,7 +1487,7 @@ public class WindowManagerService extends IWindowManager.Stub win.getParent().assignChildLayers(); if (focusChanged) { - displayContent.getInputMonitor().setInputFocusLw(mCurrentFocus, + displayContent.getInputMonitor().setInputFocusLw(displayContent.mCurrentFocus, false /*updateInputWindows*/); } displayContent.getInputMonitor().updateInputWindowsLw(false /*force*/); @@ -1679,8 +1662,9 @@ public class WindowManagerService extends IWindowManager.Stub win.resetAppOpsState(); - if (mCurrentFocus == null) { - mWinRemovedSinceNullFocus.add(win); + final DisplayContent dc = win.getDisplayContent(); + if (dc.mCurrentFocus == null) { + dc.mWinRemovedSinceNullFocus.add(win); } mPendingRemove.remove(win); mResizingWindows.remove(win); @@ -1716,7 +1700,6 @@ public class WindowManagerService extends IWindowManager.Stub atoken.postWindowRemoveStartingWindowCleanup(win); } - final DisplayContent dc = win.getDisplayContent(); if (win.mAttrs.type == TYPE_WALLPAPER) { dc.mWallpaperController.clearLastWallpaperTimeoutTime(); dc.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER; @@ -1978,9 +1961,9 @@ public class WindowManagerService extends IWindowManager.Stub boolean imMayMove = (flagChanges & (FLAG_ALT_FOCUSABLE_IM | FLAG_NOT_FOCUSABLE)) != 0 || becameVisible; final boolean isDefaultDisplay = win.isDefaultDisplay(); - boolean focusMayChange = isDefaultDisplay && (win.mViewVisibility != viewVisibility + boolean focusMayChange = win.mViewVisibility != viewVisibility || ((flagChanges & FLAG_NOT_FOCUSABLE) != 0) - || (!win.mRelayoutCalled)); + || (!win.mRelayoutCalled); boolean wallpaperMayMove = win.mViewVisibility != viewVisibility && (win.mAttrs.flags & FLAG_SHOW_WALLPAPER) != 0; @@ -2025,8 +2008,7 @@ public class WindowManagerService extends IWindowManager.Stub } result |= RELAYOUT_RES_SURFACE_CHANGED; if (!win.mWillReplaceWindow) { - focusMayChange = tryStartExitingAnimation(win, winAnimator, isDefaultDisplay, - focusMayChange); + focusMayChange = tryStartExitingAnimation(win, winAnimator, focusMayChange); } } @@ -2051,7 +2033,7 @@ public class WindowManagerService extends IWindowManager.Stub return 0; } if ((result & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) { - focusMayChange = isDefaultDisplay; + focusMayChange = true; } final DisplayContent displayContent = win.getDisplayContent(); if (win.mAttrs.type == TYPE_INPUT_METHOD @@ -2199,7 +2181,7 @@ public class WindowManagerService extends IWindowManager.Stub } private boolean tryStartExitingAnimation(WindowState win, WindowStateAnimator winAnimator, - boolean isDefaultDisplay, boolean focusMayChange) { + boolean focusMayChange) { // Try starting an animation; if there isn't one, we // can destroy the surface right away. int transit = WindowManagerPolicy.TRANSIT_EXIT; @@ -2207,9 +2189,9 @@ public class WindowManagerService extends IWindowManager.Stub transit = WindowManagerPolicy.TRANSIT_PREVIEW_DONE; } if (win.isWinVisibleLw() && winAnimator.applyAnimationLocked(transit, false)) { - focusMayChange = isDefaultDisplay; + focusMayChange = true; win.mAnimatingExit = true; - } else if (win.mWinAnimator.isAnimationSet()) { + } else if (win.isAnimating()) { // Currently in a hide animation... turn this into // an exit. win.mAnimatingExit = true; @@ -2504,57 +2486,6 @@ public class WindowManagerService extends IWindowManager.Stub } } - void setFocusTaskRegionLocked(AppWindowToken previousFocus) { - final Task focusedTask = mFocusedApp != null ? mFocusedApp.getTask() : null; - final Task previousTask = previousFocus != null ? previousFocus.getTask() : null; - final DisplayContent focusedDisplayContent = - focusedTask != null ? focusedTask.getDisplayContent() : null; - final DisplayContent previousDisplayContent = - previousTask != null ? previousTask.getDisplayContent() : null; - if (previousDisplayContent != null && previousDisplayContent != focusedDisplayContent) { - previousDisplayContent.setTouchExcludeRegion(null); - } - if (focusedDisplayContent != null) { - focusedDisplayContent.setTouchExcludeRegion(focusedTask); - } - } - - @Override - public void setFocusedApp(IBinder token, boolean moveFocusNow) { - if (!checkCallingPermission(MANAGE_APP_TOKENS, "setFocusedApp()")) { - throw new SecurityException("Requires MANAGE_APP_TOKENS permission"); - } - - synchronized(mWindowMap) { - final AppWindowToken newFocus; - if (token == null) { - if (DEBUG_FOCUS_LIGHT) Slog.v(TAG_WM, "Clearing focused app, was " + mFocusedApp); - newFocus = null; - } else { - newFocus = mRoot.getAppWindowToken(token); - if (newFocus == null) { - Slog.w(TAG_WM, "Attempted to set focus to non-existing app token: " + token); - } - if (DEBUG_FOCUS_LIGHT) Slog.v(TAG_WM, "Set focused app to: " + newFocus - + " old focus=" + mFocusedApp + " moveFocusNow=" + moveFocusNow); - } - - final boolean changed = mFocusedApp != newFocus; - if (changed) { - AppWindowToken prev = mFocusedApp; - mFocusedApp = newFocus; - mFocusedApp.getDisplayContent().getInputMonitor().setFocusedAppLw(newFocus); - setFocusTaskRegionLocked(prev); - } - - if (moveFocusNow && changed) { - final long origId = Binder.clearCallingIdentity(); - updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL, true /*updateInputWindows*/); - Binder.restoreCallingIdentity(origId); - } - } - } - @Override public void prepareAppTransition(@TransitionType int transit, boolean alwaysKeepCurrent) { prepareAppTransition(transit, alwaysKeepCurrent, 0 /* flags */, false /* forceOverride */); @@ -4395,7 +4326,8 @@ public class WindowManagerService extends IWindowManager.Stub } private WindowState getFocusedWindowLocked() { - return mCurrentFocus; + // Return the focused window in the focused display. + return mRoot.getTopFocusedDisplayContent().mCurrentFocus; } TaskStack getImeFocusStackLocked() { @@ -4403,8 +4335,11 @@ public class WindowManagerService extends IWindowManager.Stub // Also don't use mInputMethodTarget's stack, because some window with FLAG_NOT_FOCUSABLE // and FLAG_ALT_FOCUSABLE_IM flags both set might be set to IME target so they're moved // to make room for IME, but the window is not the focused window that's taking input. - return (mFocusedApp != null && mFocusedApp.getTask() != null) ? - mFocusedApp.getTask().mStack : null; + // TODO (b/111080190): Consider the case of multiple IMEs on multi-display. + final DisplayContent topFocusedDisplay = mRoot.getTopFocusedDisplayContent(); + final AppWindowToken focusedApp = topFocusedDisplay.mFocusedApp; + return (focusedApp != null && focusedApp.getTask() != null) + ? focusedApp.getTask().mStack : null; } public boolean detectSafeMode() { @@ -4542,7 +4477,6 @@ public class WindowManagerService extends IWindowManager.Stub public static final int REPORT_LOSING_FOCUS = 3; public static final int WINDOW_FREEZE_TIMEOUT = 11; - public static final int APP_TRANSITION_TIMEOUT = 13; public static final int PERSIST_ANIMATION_SCALE = 14; public static final int FORCE_GC = 15; public static final int ENABLE_SCREEN = 16; @@ -4554,7 +4488,6 @@ public class WindowManagerService extends IWindowManager.Stub public static final int BOOT_TIMEOUT = 23; public static final int WAITING_FOR_DRAWN_TIMEOUT = 24; public static final int SHOW_STRICT_MODE_VIOLATION = 25; - public static final int DO_ANIMATION_CALLBACK = 26; public static final int CLIENT_FREEZE_TIMEOUT = 30; public static final int NOTIFY_ACTIVITY_DRAWN = 32; @@ -4601,6 +4534,7 @@ public class WindowManagerService extends IWindowManager.Stub } switch (msg.what) { case REPORT_FOCUS_CHANGE: { + final DisplayContent displayContent = (DisplayContent) msg.obj; WindowState lastFocus; WindowState newFocus; @@ -4608,24 +4542,22 @@ public class WindowManagerService extends IWindowManager.Stub synchronized(mWindowMap) { // TODO(multidisplay): Accessibility supported only of default desiplay. - if (mAccessibilityController != null && getDefaultDisplayContentLocked() - .getDisplayId() == DEFAULT_DISPLAY) { + if (mAccessibilityController != null && displayContent.isDefaultDisplay) { accessibilityController = mAccessibilityController; } - lastFocus = mLastFocus; - newFocus = mCurrentFocus; + lastFocus = displayContent.mLastFocus; + newFocus = displayContent.mCurrentFocus; if (lastFocus == newFocus) { // Focus is not changing, so nothing to do. return; } - mLastFocus = newFocus; + displayContent.mLastFocus = newFocus; if (DEBUG_FOCUS_LIGHT) Slog.i(TAG_WM, "Focus moving from " + lastFocus + - " to " + newFocus); - if (newFocus != null && lastFocus != null - && !newFocus.isDisplayedLw()) { - //Slog.i(TAG_WM, "Delaying loss of focus..."); - mLosingFocus.add(lastFocus); + " to " + newFocus + " displayId=" + displayContent.getDisplayId()); + if (newFocus != null && lastFocus != null && !newFocus.isDisplayedLw()) { + if (DEBUG_FOCUS_LIGHT) Slog.i(TAG_WM, "Delaying loss of focus..."); + displayContent.mLosingFocus.add(lastFocus); lastFocus = null; } } @@ -4636,8 +4568,6 @@ public class WindowManagerService extends IWindowManager.Stub accessibilityController.onWindowFocusChangedNotLocked(); } - //System.out.println("Changing focus from " + lastFocus - // + " to " + newFocus); if (newFocus != null) { if (DEBUG_FOCUS_LIGHT) Slog.i(TAG_WM, "Gaining focus: " + newFocus); newFocus.reportFocusChangedSerialized(true, mInTouchMode); @@ -4651,15 +4581,16 @@ public class WindowManagerService extends IWindowManager.Stub } break; case REPORT_LOSING_FOCUS: { + final DisplayContent displayContent = (DisplayContent) msg.obj; ArrayList<WindowState> losers; synchronized(mWindowMap) { - losers = mLosingFocus; - mLosingFocus = new ArrayList<WindowState>(); + losers = displayContent.mLosingFocus; + displayContent.mLosingFocus = new ArrayList<>(); } final int N = losers.size(); - for (int i=0; i<N; i++) { + for (int i = 0; i < N; i++) { if (DEBUG_FOCUS_LIGHT) Slog.i(TAG_WM, "Losing delayed focus: " + losers.get(i)); losers.get(i).reportFocusChangedSerialized(false, mInTouchMode); @@ -4674,21 +4605,6 @@ public class WindowManagerService extends IWindowManager.Stub break; } - case APP_TRANSITION_TIMEOUT: { - synchronized (mWindowMap) { - if (mAppTransition.isTransitionSet() || !mOpeningApps.isEmpty() - || !mClosingApps.isEmpty()) { - if (DEBUG_APP_TRANSITIONS) Slog.v(TAG_WM, "*** APP TRANSITION TIMEOUT." - + " isTransitionSet()=" + mAppTransition.isTransitionSet() - + " mOpeningApps.size()=" + mOpeningApps.size() - + " mClosingApps.size()=" + mClosingApps.size()); - mAppTransition.setTimeout(); - mWindowPlacerLocked.performSurfacePlacement(); - } - } - break; - } - case PERSIST_ANIMATION_SCALE: { Settings.Global.putFloat(mContext.getContentResolver(), Settings.Global.WINDOW_ANIMATION_SCALE, mWindowAnimationScaleSetting); @@ -4841,14 +4757,6 @@ public class WindowManagerService extends IWindowManager.Stub break; } - case DO_ANIMATION_CALLBACK: { - try { - ((IRemoteCallback)msg.obj).sendResult(null); - } catch (RemoteException e) { - } - break; - } - case NOTIFY_ACTIVITY_DRAWN: try { mActivityTaskManager.notifyActivityDrawn((IBinder) msg.obj); @@ -5553,93 +5461,11 @@ public class WindowManagerService extends IWindowManager.Stub } } - // TODO: Move to DisplayContent boolean updateFocusedWindowLocked(int mode, boolean updateInputWindows) { - WindowState newFocus = mRoot.computeFocusedWindow(); - if (mCurrentFocus != newFocus) { - Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "wmUpdateFocus"); - // This check makes sure that we don't already have the focus - // change message pending. - mH.removeMessages(H.REPORT_FOCUS_CHANGE); - mH.sendEmptyMessage(H.REPORT_FOCUS_CHANGE); - final DisplayContent displayContent = (newFocus != null) ? newFocus.getDisplayContent() - : getDefaultDisplayContentLocked(); - boolean imWindowChanged = false; - if (displayContent.mInputMethodWindow != null) { - final WindowState prevTarget = mInputMethodTarget; - - final WindowState newTarget = - displayContent.computeImeTarget(true /* updateImeTarget*/); - imWindowChanged = prevTarget != newTarget; - - if (mode != UPDATE_FOCUS_WILL_ASSIGN_LAYERS - && mode != UPDATE_FOCUS_WILL_PLACE_SURFACES) { - final int prevImeAnimLayer = - displayContent.mInputMethodWindow.mWinAnimator.mAnimLayer; - displayContent.assignWindowLayers(false /* setLayoutNeeded */); - imWindowChanged |= prevImeAnimLayer - != displayContent.mInputMethodWindow.mWinAnimator.mAnimLayer; - } - } - - if (imWindowChanged) { - mWindowsChanged = true; - displayContent.setLayoutNeeded(); - newFocus = mRoot.computeFocusedWindow(); - } - - if (DEBUG_FOCUS_LIGHT || localLOGV) Slog.v(TAG_WM, "Changing focus from " + - mCurrentFocus + " to " + newFocus + " Callers=" + Debug.getCallers(4)); - final WindowState oldFocus = mCurrentFocus; - mCurrentFocus = newFocus; - mLosingFocus.remove(newFocus); - - if (mCurrentFocus != null) { - mWinAddedSinceNullFocus.clear(); - mWinRemovedSinceNullFocus.clear(); - } - - int focusChanged = mPolicy.focusChangedLw(oldFocus, newFocus); - - if (imWindowChanged && oldFocus != displayContent.mInputMethodWindow) { - // Focus of the input method window changed. Perform layout if needed. - if (mode == UPDATE_FOCUS_PLACING_SURFACES) { - displayContent.performLayout(true /*initial*/, updateInputWindows); - focusChanged &= ~FINISH_LAYOUT_REDO_LAYOUT; - } else if (mode == UPDATE_FOCUS_WILL_PLACE_SURFACES) { - // Client will do the layout, but we need to assign layers - // for handleNewWindowLocked() below. - displayContent.assignWindowLayers(false /* setLayoutNeeded */); - } - } - - if ((focusChanged & FINISH_LAYOUT_REDO_LAYOUT) != 0) { - // The change in focus caused us to need to do a layout. Okay. - displayContent.setLayoutNeeded(); - if (mode == UPDATE_FOCUS_PLACING_SURFACES) { - displayContent.performLayout(true /*initial*/, updateInputWindows); - } else if (mode == UPDATE_FOCUS_REMOVING_FOCUS) { - mRoot.performSurfacePlacement(false); - } - } - - if (mode != UPDATE_FOCUS_WILL_ASSIGN_LAYERS) { - // If we defer assigning layers, then the caller is responsible for - // doing this part. - displayContent.getInputMonitor().setInputFocusLw(mCurrentFocus, updateInputWindows); - } - - displayContent.adjustForImeIfNeeded(); - - // We may need to schedule some toast windows to be removed. The toasts for an app that - // does not have input focus are removed within a timeout to prevent apps to redress - // other apps' UI. - displayContent.scheduleToastWindowsTimeoutIfNeededLocked(oldFocus, newFocus); - - Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); - return true; - } - return false; + Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "wmUpdateFocus"); + boolean changed = mRoot.updateFocusedWindowLocked(mode, updateInputWindows); + Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); + return changed; } void startFreezingDisplayLocked(int exitAnim, int enterAnim) { @@ -6201,11 +6027,12 @@ public class WindowManagerService extends IWindowManager.Stub void writeToProtoLocked(ProtoOutputStream proto, boolean trim) { mPolicy.writeToProto(proto, POLICY); mRoot.writeToProto(proto, ROOT_WINDOW_CONTAINER, trim); - if (mCurrentFocus != null) { - mCurrentFocus.writeIdentifierToProto(proto, FOCUSED_WINDOW); + final DisplayContent topFocusedDisplayContent = mRoot.getTopFocusedDisplayContent(); + if (topFocusedDisplayContent.mCurrentFocus != null) { + topFocusedDisplayContent.mCurrentFocus.writeIdentifierToProto(proto, FOCUSED_WINDOW); } - if (mFocusedApp != null) { - mFocusedApp.writeNameToProto(proto, FOCUSED_APP); + if (topFocusedDisplayContent.mFocusedApp != null) { + topFocusedDisplayContent.mFocusedApp.writeNameToProto(proto, FOCUSED_APP); } final WindowState imeWindow = mRoot.getCurrentInputMethodWindow(); if (imeWindow != null) { @@ -6303,23 +6130,6 @@ public class WindowManagerService extends IWindowManager.Stub } } } - if (mLosingFocus.size() > 0) { - pw.println(); - pw.println(" Windows losing focus:"); - for (int i=mLosingFocus.size()-1; i>=0; i--) { - WindowState w = mLosingFocus.get(i); - if (windows == null || windows.contains(w)) { - pw.print(" Losing #"); pw.print(i); pw.print(' '); - pw.print(w); - if (dumpAll) { - pw.println(":"); - w.dump(pw, " ", true); - } else { - pw.println(); - } - } - } - } if (mResizingWindows.size() > 0) { pw.println(); pw.println(" Windows waiting to resize:"); @@ -6348,11 +6158,7 @@ public class WindowManagerService extends IWindowManager.Stub pw.println(); pw.print(" mGlobalConfiguration="); pw.println(mRoot.getConfiguration()); pw.print(" mHasPermanentDpad="); pw.println(mHasPermanentDpad); - pw.print(" mCurrentFocus="); pw.println(mCurrentFocus); - if (mLastFocus != mCurrentFocus) { - pw.print(" mLastFocus="); pw.println(mLastFocus); - } - pw.print(" mFocusedApp="); pw.println(mFocusedApp); + mRoot.dumpTopFocusedDisplayId(pw); if (mInputMethodTarget != null) { pw.print(" mInputMethodTarget="); pw.println(mInputMethodTarget); } @@ -6483,11 +6289,17 @@ public class WindowManagerService extends IWindowManager.Stub if (reason != null) { pw.println(" Reason: " + reason); } - if (!mWinAddedSinceNullFocus.isEmpty()) { - pw.println(" Windows added since null focus: " + mWinAddedSinceNullFocus); - } - if (!mWinRemovedSinceNullFocus.isEmpty()) { - pw.println(" Windows removed since null focus: " + mWinRemovedSinceNullFocus); + for (int i = mRoot.getChildCount() - 1; i >= 0; i--) { + final DisplayContent dc = mRoot.getChildAt(i); + final int displayId = dc.getDisplayId(); + if (!dc.mWinAddedSinceNullFocus.isEmpty()) { + pw.println(" Windows added in display #" + displayId + " since null focus: " + + dc.mWinAddedSinceNullFocus); + } + if (!dc.mWinRemovedSinceNullFocus.isEmpty()) { + pw.println(" Windows removed in display #" + displayId + " since null focus: " + + dc.mWinRemovedSinceNullFocus); + } } pw.println(); dumpWindowsNoHeaderLocked(pw, true, null); @@ -6822,9 +6634,9 @@ public class WindowManagerService extends IWindowManager.Stub @Override public void setDockedStackDividerTouchRegion(Rect touchRegion) { synchronized (mWindowMap) { - getDefaultDisplayContentLocked().getDockedDividerController() - .setTouchRegion(touchRegion); - setFocusTaskRegionLocked(null); + final DisplayContent dc = getDefaultDisplayContentLocked(); + dc.getDockedDividerController().setTouchRegion(touchRegion); + dc.updateTouchExcludeRegion(); } } @@ -7377,7 +7189,14 @@ public class WindowManagerService extends IWindowManager.Stub @Override public boolean isUidFocused(int uid) { synchronized (mWindowMap) { - return mCurrentFocus != null ? uid == mCurrentFocus.getOwningUid() : false; + for (int i = mRoot.getChildCount() - 1; i >= 0; i--) { + final DisplayContent displayContent = mRoot.getChildAt(i); + if (displayContent.mCurrentFocus != null + && uid == displayContent.mCurrentFocus.getOwningUid()) { + return true; + } + } + return false; } } @@ -7400,8 +7219,9 @@ public class WindowManagerService extends IWindowManager.Stub // press home. Sometimes the IME won't go down.) // Would be nice to fix this more correctly, but it's // way at the end of a release, and this should be good enough. - if (mCurrentFocus != null && mCurrentFocus.mSession.mUid == uid - && mCurrentFocus.mSession.mPid == pid) { + final WindowState currentFocus = mRoot.getTopFocusedDisplayContent().mCurrentFocus; + if (currentFocus != null && currentFocus.mSession.mUid == uid + && currentFocus.mSession.mPid == pid) { return true; } } diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index f7c6d77b4163..8276952d8a6c 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -1364,7 +1364,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP @Override boolean hasContentToDisplay() { if (!mAppFreezing && isDrawnLw() && (mViewVisibility == View.VISIBLE - || (mWinAnimator.isAnimationSet() && !mService.mAppTransition.isTransitionSet()))) { + || (isAnimating() && !mService.mAppTransition.isTransitionSet()))) { return true; } @@ -1443,9 +1443,9 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP final AppWindowToken atoken = mAppToken; if (atoken != null) { return ((!isParentWindowHidden() && !atoken.hiddenRequested) - || mWinAnimator.isAnimationSet()); + || isAnimating()); } - return !isParentWindowHidden() || mWinAnimator.isAnimationSet(); + return !isParentWindowHidden() || isAnimating(); } /** @@ -1476,9 +1476,10 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP if (mToken.waitingToShow && mService.mAppTransition.isTransitionSet()) { return false; } + final boolean parentAndClientVisible = !isParentWindowHidden() + && mViewVisibility == View.VISIBLE && !mToken.isHidden(); return mHasSurface && mPolicyVisibility && !mDestroying - && ((!isParentWindowHidden() && mViewVisibility == View.VISIBLE && !mToken.isHidden()) - || mWinAnimator.isAnimationSet()); + && (parentAndClientVisible || isAnimating()); } // TODO: Another visibility method that was added late in the release to minimize risk. @@ -1508,7 +1509,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP final AppWindowToken atoken = mAppToken; return isDrawnLw() && mPolicyVisibility && ((!isParentWindowHidden() && (atoken == null || !atoken.hiddenRequested)) - || mWinAnimator.isAnimationSet()); + || isAnimating()); } /** @@ -1562,7 +1563,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP // to determine if it's occluding apps. return ((!mIsWallpaper && mAttrs.format == PixelFormat.OPAQUE) || (mIsWallpaper && mWallpaperVisible)) - && isDrawnLw() && !mWinAnimator.isAnimationSet(); + && isDrawnLw() && !isAnimating(); } @Override @@ -1832,7 +1833,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP if (startingWindow && DEBUG_STARTING_WINDOW) Slog.d(TAG_WM, "Starting window removed " + this); - if (localLOGV || DEBUG_FOCUS || DEBUG_FOCUS_LIGHT && this == mService.mCurrentFocus) + if (localLOGV || DEBUG_FOCUS || DEBUG_FOCUS_LIGHT && isFocused()) Slog.v(TAG_WM, "Remove " + this + " client=" + Integer.toHexString(System.identityHashCode(mClient.asBinder())) + ", surfaceController=" + mWinAnimator.mSurfaceController + " Callers=" @@ -1849,7 +1850,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP + " mRemoveOnExit=" + mRemoveOnExit + " mHasSurface=" + mHasSurface + " surfaceShowing=" + mWinAnimator.getShown() - + " isAnimationSet=" + mWinAnimator.isAnimationSet() + + " animating=" + isAnimating() + " app-animation=" + (mAppToken != null ? mAppToken.isSelfAnimating() : "false") + " mWillReplaceWindow=" + mWillReplaceWindow @@ -1916,7 +1917,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP mService.mAccessibilityController.onWindowTransitionLocked(this, transit); } } - final boolean isAnimating = mWinAnimator.isAnimationSet() + final boolean isAnimating = isAnimating() && (mAppToken == null || !mAppToken.isWaitingForTransitionStart()); final boolean lastWindowIsStartingWindow = startingWindow && mAppToken != null && mAppToken.isLastWindow(this); @@ -1944,7 +1945,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP if (wasVisible && mService.updateOrientationFromAppTokensLocked(displayId)) { mService.mH.obtainMessage(SEND_NEW_CONFIGURATION, displayId).sendToTarget(); } - mService.updateFocusedWindowLocked(mService.mCurrentFocus == this + mService.updateFocusedWindowLocked(isFocused() ? UPDATE_FOCUS_REMOVING_FOCUS : UPDATE_FOCUS_NORMAL, true /*updateInputWindows*/); @@ -2179,7 +2180,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP mPolicyVisibility = mPolicyVisibilityAfterAnim; if (!mPolicyVisibility) { mWinAnimator.hide("checkPolicyVisibilityChange"); - if (mService.mCurrentFocus == this) { + if (isFocused()) { if (DEBUG_FOCUS_LIGHT) Slog.i(TAG, "setAnimationLocked: setting mFocusMayChange true"); mService.mFocusMayChange = true; @@ -2434,10 +2435,10 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP if (DEBUG_VISIBILITY) Slog.v(TAG, "Policy visibility true: " + this); if (doAnimation) { if (DEBUG_VISIBILITY) Slog.v(TAG, "doAnimation: mPolicyVisibility=" - + mPolicyVisibility + " isAnimationSet=" + mWinAnimator.isAnimationSet()); + + mPolicyVisibility + " animating=" + isAnimating()); if (!mToken.okToAnimate()) { doAnimation = false; - } else if (mPolicyVisibility && !mWinAnimator.isAnimationSet()) { + } else if (mPolicyVisibility && !isAnimating()) { // Check for the case where we are currently visible and // not animating; we do not want to do animation at such a // point to become visible when we already are. @@ -2476,11 +2477,12 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } if (doAnimation) { mWinAnimator.applyAnimationLocked(TRANSIT_EXIT, false); - if (!mWinAnimator.isAnimationSet()) { + if (!isAnimating()) { doAnimation = false; } } mPolicyVisibilityAfterAnim = false; + final boolean isFocused = isFocused(); if (!doAnimation) { if (DEBUG_VISIBILITY) Slog.v(TAG, "Policy visibility false: " + this); mPolicyVisibility = false; @@ -2488,7 +2490,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP // for it to be displayed before enabling the display, that // we allow the display to be enabled now. mService.enableScreenIfNeededLocked(); - if (mService.mCurrentFocus == this) { + if (isFocused) { if (DEBUG_FOCUS_LIGHT) Slog.i(TAG, "WindowState.hideLw: setting mFocusMayChange true"); mService.mFocusMayChange = true; @@ -2497,7 +2499,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP if (requestAnim) { mService.scheduleAnimationLocked(); } - if (mService.mCurrentFocus == this) { + if (isFocused) { mService.updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL, false /* updateImWindows */); } return true; @@ -2993,10 +2995,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } } - public boolean isFocused() { - synchronized(mService.mWindowMap) { - return mService.mCurrentFocus == this; - } + boolean isFocused() { + return getDisplayContent().mCurrentFocus == this; } @Override @@ -3216,10 +3216,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP + " mWallpaperVisible=" + mWallpaperVisible); } if (dumpAll) { - pw.println(prefix + "mBaseLayer=" + mBaseLayer - + " mSubLayer=" + mSubLayer - + " mAnimLayer=" + mLayer + "=" + mWinAnimator.mAnimLayer - + " mLastLayer=" + mWinAnimator.mLastLayer); + pw.print(prefix); pw.print("mBaseLayer="); pw.print(mBaseLayer); + pw.print(" mSubLayer="); pw.print(mSubLayer); } if (dumpAll) { pw.println(prefix + "mToken=" + mToken); @@ -3697,7 +3695,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP + " tok.hiddenRequested=" + (mAppToken != null && mAppToken.hiddenRequested) + " tok.hidden=" + (mAppToken != null && mAppToken.isHidden()) - + " animationSet=" + mWinAnimator.isAnimationSet() + + " animating=" + isAnimating() + " tok animating=" + (mAppToken != null && mAppToken.isSelfAnimating()) + " Callers=" + Debug.getCallers(4)); @@ -3749,18 +3747,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP return windowInfo; } - int getHighestAnimLayer() { - int highest = mWinAnimator.mAnimLayer; - for (int i = mChildren.size() - 1; i >= 0; i--) { - final WindowState c = mChildren.get(i); - final int childLayer = c.getHighestAnimLayer(); - if (childLayer > highest) { - highest = childLayer; - } - } - return highest; - } - @Override boolean forAllWindows(ToBooleanFunction<WindowState> callback, boolean traverseTopToBottom) { if (mChildren.isEmpty()) { @@ -4110,25 +4096,25 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } if (DEBUG_VISIBILITY) { Slog.v(TAG, "Win " + this + ": isDrawn=" + isDrawnLw() - + ", isAnimationSet=" + mWinAnimator.isAnimationSet()); + + ", animating=" + isAnimating()); if (!isDrawnLw()) { Slog.v(TAG, "Not displayed: s=" + mWinAnimator.mSurfaceController + " pv=" + mPolicyVisibility + " mDrawState=" + mWinAnimator.mDrawState + " ph=" + isParentWindowHidden() + " th=" + (mAppToken != null ? mAppToken.hiddenRequested : false) - + " a=" + mWinAnimator.isAnimationSet()); + + " a=" + isAnimating()); } } results.numInteresting++; if (isDrawnLw()) { results.numDrawn++; - if (!mWinAnimator.isAnimationSet()) { + if (!isAnimating()) { results.numVisible++; } results.nowGone = false; - } else if (mWinAnimator.isAnimationSet()) { + } else if (isAnimating()) { results.nowGone = false; } } @@ -4448,7 +4434,12 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP @Override public boolean isFocused() { final WindowState outer = mOuter.get(); - return outer != null && outer.isFocused(); + if (outer != null) { + synchronized (outer.mService.mWindowMap) { + return outer.isFocused(); + } + } + return false; } } @@ -4676,10 +4667,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP mTapExcludeRegionHolder.updateRegion(regionId, left, top, width, height); // Trigger touch exclude region update on current display. - final boolean isAppFocusedOnDisplay = mService.mFocusedApp != null - && mService.mFocusedApp.getDisplayContent() == currentDisplay; - currentDisplay.setTouchExcludeRegion(isAppFocusedOnDisplay ? mService.mFocusedApp.getTask() - : null); + currentDisplay.updateTouchExcludeRegion(); } /** Union the region with current tap exclude region that this window provides. */ diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java index 979149a854cb..2beb7887698e 100644 --- a/services/core/java/com/android/server/wm/WindowStateAnimator.java +++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java @@ -107,8 +107,6 @@ class WindowStateAnimator { private final WallpaperController mWallpaperControllerLocked; boolean mAnimationIsEntrance; - int mAnimLayer; - int mLastLayer; /** * Set when we have changed the size of the surface, to know that @@ -135,7 +133,6 @@ class WindowStateAnimator { float mLastAlpha = 0; Rect mTmpClipRect = new Rect(); - Rect mTmpFinalClipRect = new Rect(); Rect mLastClipRect = new Rect(); Rect mLastFinalClipRect = new Rect(); Rect mTmpStackBounds = new Rect(); @@ -162,8 +159,6 @@ class WindowStateAnimator { * window is first added or shown, cleared when the callback has been made. */ boolean mEnteringAnimation; - private boolean mAnimationStartDelayed; - private final SurfaceControl.Transaction mTmpTransaction = new SurfaceControl.Transaction(); /** The pixel format of the underlying SurfaceControl */ @@ -253,13 +248,6 @@ class WindowStateAnimator { mWallpaperControllerLocked = mService.mRoot.mWallpaperController; } - /** - * Is the window or its container currently set to animate or currently animating? - */ - boolean isAnimationSet() { - return mWin.isAnimating(); - } - void cancelExitAnimationForNextAnimationLocked() { if (DEBUG_ANIM) Slog.d(TAG, "cancelExitAnimationForNextAnimationLocked: " + mWin); @@ -275,10 +263,6 @@ class WindowStateAnimator { + ", reportedVisible=" + (mWin.mAppToken != null ? mWin.mAppToken.reportedVisible : false)); - if (mAnimator.mWindowDetachedWallpaper == mWin) { - mAnimator.mWindowDetachedWallpaper = null; - } - mWin.checkPolicyVisibilityChange(); final DisplayContent displayContent = mWin.getDisplayContent(); if (mAttrType == LayoutParams.TYPE_STATUS_BAR && mWin.mPolicyVisibility) { @@ -288,7 +272,6 @@ class WindowStateAnimator { displayContent.setLayoutNeeded(); } } - mWin.onExitAnimationDone(); final int displayId = mWin.getDisplayId(); int pendingLayoutChanges = FINISH_LAYOUT_REDO_ANIM; @@ -539,14 +522,13 @@ class WindowStateAnimator { } if (WindowManagerService.localLOGV) Slog.v(TAG, "Got surface: " + mSurfaceController - + ", set left=" + w.getFrameLw().left + " top=" + w.getFrameLw().top - + ", animLayer=" + mAnimLayer); + + ", set left=" + w.getFrameLw().left + " top=" + w.getFrameLw().top); if (SHOW_LIGHT_TRANSACTIONS) { Slog.i(TAG, ">>> OPEN TRANSACTION createSurfaceLocked"); WindowManagerService.logSurface(w, "CREATE pos=(" + w.getFrameLw().left + "," + w.getFrameLw().top + ") (" - + width + "x" + height + "), layer=" + mAnimLayer + " HIDE", false); + + width + "x" + height + ")" + " HIDE", false); } mLastHidden = true; @@ -1133,8 +1115,7 @@ class WindowStateAnimator { if (DEBUG_ORIENTATION) Slog.v(TAG, "Orientation change skips hidden " + w); } - } else if (mLastLayer != mAnimLayer - || mLastAlpha != mShownAlpha + } else if (mLastAlpha != mShownAlpha || mLastDsDx != mDsDx || mLastDtDx != mDtDx || mLastDsDy != mDsDy @@ -1144,7 +1125,6 @@ class WindowStateAnimator { || mLastHidden) { displayed = true; mLastAlpha = mShownAlpha; - mLastLayer = mAnimLayer; mLastDsDx = mDsDx; mLastDtDx = mDtDx; mLastDsDy = mDsDy; @@ -1153,7 +1133,7 @@ class WindowStateAnimator { w.mLastVScale = w.mVScale; if (SHOW_TRANSACTIONS) WindowManagerService.logSurface(w, "controller=" + mSurfaceController + - "alpha=" + mShownAlpha + " layer=" + mAnimLayer + "alpha=" + mShownAlpha + " matrix=[" + mDsDx + "*" + w.mHScale + "," + mDtDx + "*" + w.mVScale + "][" + mDtDy + "*" + w.mHScale @@ -1197,7 +1177,7 @@ class WindowStateAnimator { w.mToken.hasVisible = true; } } else { - if (DEBUG_ANIM && isAnimationSet()) { + if (DEBUG_ANIM && mWin.isAnimating()) { Slog.v(TAG, "prepareSurface: No changes in animation for " + this); } displayed = true; @@ -1407,7 +1387,7 @@ class WindowStateAnimator { } Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER); - return isAnimationSet(); + return mWin.isAnimating(); } void writeToProto(ProtoOutputStream proto, long fieldId) { @@ -1460,9 +1440,6 @@ class WindowStateAnimator { pw.print(" mDtDy="); pw.print(mDtDy); pw.print(" mDsDy="); pw.println(mDsDy); } - if (mAnimationStartDelayed) { - pw.print(prefix); pw.print("mAnimationStartDelayed="); pw.print(mAnimationStartDelayed); - } } @Override @@ -1520,10 +1497,6 @@ class WindowStateAnimator { mChildrenDetached = true; } - int getLayer() { - return mLastLayer; - } - void setOffsetPositionForStackResize(boolean offsetPositionForStackResize) { mOffsetPositionForStackResize = offsetPositionForStackResize; } diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java index c8d1a8b14e82..e13a70a399e2 100644 --- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java +++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java @@ -69,7 +69,6 @@ import android.view.WindowManager.TransitionType; import android.view.animation.Animation; import com.android.internal.annotations.VisibleForTesting; -import com.android.server.wm.WindowManagerService.H; import java.io.PrintWriter; import java.util.function.Predicate; @@ -98,12 +97,6 @@ class WindowSurfacePlacer { private boolean mTraversalScheduled; private int mDeferDepth = 0; - private static final class LayerAndToken { - public int layer; - public AppWindowToken token; - } - private final LayerAndToken mTmpLayerAndToken = new LayerAndToken(); - private final SparseIntArray mTempTransitionReasons = new SparseIntArray(); private final Runnable mPerformSurfacePlacement; @@ -258,7 +251,7 @@ class WindowSurfacePlacer { mService.mSkipAppTransitionAnimation = false; mService.mNoAnimationNotifyOnTransitionFinished.clear(); - mService.mH.removeMessages(H.APP_TRANSITION_TIMEOUT); + mService.mAppTransition.removeAppTransitionTimeoutCallbacks(); final DisplayContent displayContent = mService.getDefaultDisplayContentLocked(); @@ -298,10 +291,16 @@ class WindowSurfacePlacer { // done behind a dream window. final ArraySet<Integer> activityTypes = collectActivityTypes(mService.mOpeningApps, mService.mClosingApps); - final AppWindowToken animLpToken = mService.mPolicy.allowAppAnimationsLw() + final boolean allowAnimations = mService.mPolicy.allowAppAnimationsLw(); + final AppWindowToken animLpToken = allowAnimations ? findAnimLayoutParamsToken(transit, activityTypes) : null; - + final AppWindowToken topOpeningApp = allowAnimations + ? getTopApp(mService.mOpeningApps, false /* ignoreHidden */) + : null; + final AppWindowToken topClosingApp = allowAnimations + ? getTopApp(mService.mClosingApps, false /* ignoreHidden */) + : null; final LayoutParams animLp = getAnimLp(animLpToken); overrideWithRemoteAnimationIfSet(animLpToken, transit, activityTypes); @@ -313,17 +312,14 @@ class WindowSurfacePlacer { try { processApplicationsAnimatingInPlace(transit); - mTmpLayerAndToken.token = null; - handleClosingApps(transit, animLp, voiceInteraction, mTmpLayerAndToken); - final AppWindowToken topClosingApp = mTmpLayerAndToken.token; - final AppWindowToken topOpeningApp = handleOpeningApps(transit, animLp, - voiceInteraction); + handleClosingApps(transit, animLp, voiceInteraction); + handleOpeningApps(transit, animLp, voiceInteraction); mService.mAppTransition.setLastAppTransition(transit, topOpeningApp, topClosingApp); final int flags = mService.mAppTransition.getTransitFlags(); - layoutRedo = mService.mAppTransition.goodToGo(transit, topOpeningApp, - topClosingApp, mService.mOpeningApps, mService.mClosingApps); + layoutRedo = mService.mAppTransition.goodToGo(transit, topOpeningApp, topClosingApp, + mService.mOpeningApps, mService.mClosingApps); handleNonAppWindowsInTransition(transit, flags); mService.mAppTransition.postAnimationCallback(); mService.mAppTransition.clear(); @@ -450,10 +446,7 @@ class WindowSurfacePlacer { return false; } - private AppWindowToken handleOpeningApps(int transit, LayoutParams animLp, - boolean voiceInteraction) { - AppWindowToken topOpeningApp = null; - int topOpeningLayer = Integer.MIN_VALUE; + private void handleOpeningApps(int transit, LayoutParams animLp, boolean voiceInteraction) { final int appsCount = mService.mOpeningApps.size(); for (int i = 0; i < appsCount; i++) { AppWindowToken wtoken = mService.mOpeningApps.valueAt(i); @@ -478,24 +471,15 @@ class WindowSurfacePlacer { "<<< CLOSE TRANSACTION handleAppTransitionReadyLocked()"); } - if (animLp != null) { - final int layer = wtoken.getHighestAnimLayer(); - if (topOpeningApp == null || layer > topOpeningLayer) { - topOpeningApp = wtoken; - topOpeningLayer = layer; - } - } if (mService.mAppTransition.isNextAppTransitionThumbnailUp()) { wtoken.attachThumbnailAnimation(); } else if (mService.mAppTransition.isNextAppTransitionOpenCrossProfileApps()) { wtoken.attachCrossProfileAppsThumbnailAnimation(); } } - return topOpeningApp; } - private void handleClosingApps(int transit, LayoutParams animLp, boolean voiceInteraction, - LayerAndToken layerAndToken) { + private void handleClosingApps(int transit, LayoutParams animLp, boolean voiceInteraction) { final int appsCount; appsCount = mService.mClosingApps.size(); for (int i = 0; i < appsCount; i++) { @@ -518,13 +502,6 @@ class WindowSurfacePlacer { wtoken.getController().removeStartingWindow(); } - if (animLp != null) { - int layer = wtoken.getHighestAnimLayer(); - if (layerAndToken.token == null || layer > layerAndToken.layer) { - layerAndToken.token = wtoken; - layerAndToken.layer = layer; - } - } if (mService.mAppTransition.isNextAppTransitionThumbnailDown()) { wtoken.attachThumbnailAnimation(); } @@ -785,6 +762,7 @@ class WindowSurfacePlacer { private void processApplicationsAnimatingInPlace(int transit) { if (transit == TRANSIT_TASK_IN_PLACE) { + // TODO (b/111362605): non-default-display transition. // Find the focused window final WindowState win = mService.getDefaultDisplayContentLocked().findFocusedWindow(); if (win != null) { diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java index 8972c3891ec1..0cf79b63e9dc 100644 --- a/services/core/java/com/android/server/wm/WindowToken.java +++ b/services/core/java/com/android/server/wm/WindowToken.java @@ -163,7 +163,7 @@ class WindowToken extends WindowContainer<WindowState> { for (int i = 0; i < count; i++) { final WindowState win = mChildren.get(i); - if (win.mWinAnimator.isAnimationSet()) { + if (win.isAnimating()) { delayed = true; } changed |= win.onSetAppExiting(); @@ -235,18 +235,6 @@ class WindowToken extends WindowContainer<WindowState> { return false; } - int getHighestAnimLayer() { - int highest = -1; - for (int j = 0; j < mChildren.size(); j++) { - final WindowState w = mChildren.get(j); - final int wLayer = w.getHighestAnimLayer(); - if (wLayer > highest) { - highest = wLayer; - } - } - return highest; - } - AppWindowToken asAppWindowToken() { // TODO: Not sure if this is the best way to handle this vs. using instanceof and casting. // I am not an app window token! @@ -267,6 +255,7 @@ class WindowToken extends WindowContainer<WindowState> { super.removeImmediately(); } + @Override void onDisplayChanged(DisplayContent dc) { dc.reParentWindowToken(this); mDisplayContent = dc; diff --git a/services/core/java/com/android/server/wm/utils/DisplayRotationUtil.java b/services/core/java/com/android/server/wm/utils/DisplayRotationUtil.java new file mode 100644 index 000000000000..9f307bb0f98f --- /dev/null +++ b/services/core/java/com/android/server/wm/utils/DisplayRotationUtil.java @@ -0,0 +1,96 @@ +/* + * 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.wm.utils; + +import static android.view.DisplayCutout.BOUNDS_POSITION_LENGTH; +import static android.view.Surface.ROTATION_0; +import static android.view.Surface.ROTATION_180; +import static android.view.Surface.ROTATION_270; +import static android.view.Surface.ROTATION_90; + +import static com.android.server.wm.utils.CoordinateTransforms.transformPhysicalToLogicalCoordinates; + +import android.graphics.Matrix; +import android.graphics.Rect; +import android.graphics.RectF; + +import com.android.internal.annotations.VisibleForTesting; + +/** + * Utility to compute bounds after rotating the screen. + */ +public class DisplayRotationUtil { + private final Matrix mTmpMatrix = new Matrix(); + + private static int getRotationToBoundsOffset(int rotation) { + switch (rotation) { + case ROTATION_0: + return 0; + case ROTATION_90: + return -1; + case ROTATION_180: + return 2; + case ROTATION_270: + return 1; + default: + // should not happen + return 0; + } + } + + @VisibleForTesting + static int getBoundIndexFromRotation(int i, int rotation) { + return Math.floorMod(i + getRotationToBoundsOffset(rotation), + BOUNDS_POSITION_LENGTH); + } + + /** + * Compute bounds after rotating teh screen. + * + * @param bounds Bounds before the rotation. The array must contain exactly 4 non-null elements. + * @param rotation rotation constant defined in android.view.Surface. + * @param initialDisplayWidth width of the display before the rotation. + * @param initialDisplayHeight height of the display before the rotation. + * @return Bounds after the rotation. + * + * @hide + */ + public Rect[] getRotatedBounds( + Rect[] bounds, int rotation, int initialDisplayWidth, int initialDisplayHeight) { + if (bounds.length != BOUNDS_POSITION_LENGTH) { + throw new IllegalArgumentException( + "bounds must have exactly 4 elements: bounds=" + bounds); + } + if (rotation == ROTATION_0) { + return bounds; + } + transformPhysicalToLogicalCoordinates(rotation, initialDisplayWidth, initialDisplayHeight, + mTmpMatrix); + Rect[] newBounds = new Rect[BOUNDS_POSITION_LENGTH]; + for (int i = 0; i < bounds.length; i++) { + + final Rect rect = bounds[i]; + if (!rect.isEmpty()) { + final RectF rectF = new RectF(rect); + mTmpMatrix.mapRect(rectF); + rectF.round(rect); + } + newBounds[getBoundIndexFromRotation(i, rotation)] = rect; + } + return newBounds; + } +} diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp index 061f8e2ba0fb..045f4ebf980f 100644 --- a/services/core/jni/Android.bp +++ b/services/core/jni/Android.bp @@ -8,6 +8,7 @@ cc_library_static { "-Wall", "-Werror", "-Wno-unused-parameter", + "-Wthread-safety", "-DEGL_EGLEXT_PROTOTYPES", "-DGL_GLEXT_PROTOTYPES", @@ -29,9 +30,7 @@ cc_library_static { "com_android_server_devicepolicy_CryptoTestHelper.cpp", "com_android_server_HardwarePropertiesManagerService.cpp", "com_android_server_hdmi_HdmiCecController.cpp", - "com_android_server_input_InputApplicationHandle.cpp", "com_android_server_input_InputManagerService.cpp", - "com_android_server_input_InputWindowHandle.cpp", "com_android_server_lights_LightsService.cpp", "com_android_server_location_GnssLocationProvider.cpp", "com_android_server_locksettings_SyntheticPasswordManager.cpp", @@ -90,7 +89,6 @@ cc_defaults { "libsensorservicehidl", "libgui", "libusbhost", - "libsuspend", "libtinyalsa", "libEGL", "libGLESv2", @@ -122,6 +120,7 @@ cc_defaults { "android.hardware.vr@1.0", "android.frameworks.schedulerservice@1.0", "android.frameworks.sensorservice@1.0", + "android.system.suspend@1.0", ], static_libs: [ diff --git a/services/core/jni/com_android_server_SystemServer.cpp b/services/core/jni/com_android_server_SystemServer.cpp index 3901cebcc787..dc0d53b41595 100644 --- a/services/core/jni/com_android_server_SystemServer.cpp +++ b/services/core/jni/com_android_server_SystemServer.cpp @@ -17,6 +17,7 @@ #include <jni.h> #include <nativehelper/JNIHelp.h> +#include <binder/IServiceManager.h> #include <hidl/HidlTransportSupport.h> #include <schedulerservice/SchedulingPolicyService.h> @@ -34,7 +35,8 @@ static void android_server_SystemServer_startSensorService(JNIEnv* /* env */, jo char propBuf[PROPERTY_VALUE_MAX]; property_get("system_init.startsensorservice", propBuf, "1"); if (strcmp(propBuf, "1") == 0) { - SensorService::instantiate(); + SensorService::publish(false /* allowIsolated */, + IServiceManager::DUMP_FLAG_PRIORITY_CRITICAL); } } diff --git a/services/core/jni/com_android_server_am_BatteryStatsService.cpp b/services/core/jni/com_android_server_am_BatteryStatsService.cpp index 02ad6c71b586..0ff60e44b0ce 100644 --- a/services/core/jni/com_android_server_am_BatteryStatsService.cpp +++ b/services/core/jni/com_android_server_am_BatteryStatsService.cpp @@ -30,6 +30,8 @@ #include <android/hardware/power/1.0/IPower.h> #include <android/hardware/power/1.1/IPower.h> +#include <android/system/suspend/1.0/ISystemSuspend.h> +#include <android/system/suspend/1.0/ISystemSuspendCallback.h> #include <android_runtime/AndroidRuntime.h> #include <jni.h> @@ -39,7 +41,6 @@ #include <log/log.h> #include <utils/misc.h> #include <utils/Log.h> -#include <suspend/autosuspend.h> using android::hardware::Return; using android::hardware::Void; @@ -49,6 +50,8 @@ using android::hardware::power::V1_0::Status; using android::hardware::power::V1_1::PowerStateSubsystem; using android::hardware::power::V1_1::PowerStateSubsystemSleepState; using android::hardware::hidl_vec; +using android::system::suspend::V1_0::ISystemSuspend; +using android::system::suspend::V1_0::ISystemSuspendCallback; using IPowerV1_1 = android::hardware::power::V1_1::IPower; using IPowerV1_0 = android::hardware::power::V1_0::IPower; @@ -63,6 +66,7 @@ static sem_t wakeup_sem; extern sp<IPowerV1_0> getPowerHalV1_0(); extern sp<IPowerV1_1> getPowerHalV1_1(); extern bool processPowerHalReturn(const Return<void> &ret, const char* functionName); +extern sp<ISystemSuspend> getSuspendHal(); // Java methods used in getLowPowerStats static jmethodID jgetAndUpdatePlatformState = NULL; @@ -70,16 +74,19 @@ static jmethodID jgetSubsystem = NULL; static jmethodID jputVoter = NULL; static jmethodID jputState = NULL; -static void wakeup_callback(bool success) -{ - ALOGV("In wakeup_callback: %s", success ? "resumed from suspend" : "suspend aborted"); - int ret = sem_post(&wakeup_sem); - if (ret < 0) { - char buf[80]; - strerror_r(errno, buf, sizeof(buf)); - ALOGE("Error posting wakeup sem: %s\n", buf); +class WakeupCallback : public ISystemSuspendCallback { +public: + Return<void> notifyWakeup(bool success) override { + ALOGV("In wakeup_callback: %s", success ? "resumed from suspend" : "suspend aborted"); + int ret = sem_post(&wakeup_sem); + if (ret < 0) { + char buf[80]; + strerror_r(errno, buf, sizeof(buf)); + ALOGE("Error posting wakeup sem: %s\n", buf); + } + return Void(); } -} +}; static jint nativeWaitWakeup(JNIEnv *env, jobject clazz, jobject outBuf) { @@ -101,11 +108,14 @@ static jint nativeWaitWakeup(JNIEnv *env, jobject clazz, jobject outBuf) return -1; } ALOGV("Registering callback..."); - autosuspend_set_wakeup_callback(&wakeup_callback); + sp<ISystemSuspend> suspendHal = getSuspendHal(); + suspendHal->registerCallback(new WakeupCallback()); } // Wait for wakeup. ALOGV("Waiting for wakeup..."); + // TODO(b/116747600): device can suspend and wakeup after sem_wait() finishes and before wakeup + // reason is recorded, i.e. BatteryStats might occasionally miss wakeup events. int ret = sem_wait(&wakeup_sem); if (ret < 0) { char buf[80]; diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp index 42ade38fbe51..c66d03c3a5a3 100644 --- a/services/core/jni/com_android_server_input_InputManagerService.cpp +++ b/services/core/jni/com_android_server_input_InputManagerService.cpp @@ -60,10 +60,12 @@ #include <nativehelper/ScopedUtfChars.h> #include "com_android_server_power_PowerManagerService.h" -#include "com_android_server_input_InputApplicationHandle.h" -#include "com_android_server_input_InputWindowHandle.h" +#include "android_hardware_input_InputApplicationHandle.h" +#include "android_hardware_input_InputWindowHandle.h" #include "android_hardware_display_DisplayViewport.h" +#include <vector> + #define INDENT " " using android::base::StringPrintf; @@ -144,8 +146,8 @@ static inline const char* toString(bool value) { static jobject getInputApplicationHandleObjLocalRef(JNIEnv* env, const sp<InputApplicationHandle>& inputApplicationHandle) { - if (inputApplicationHandle == NULL) { - return NULL; + if (inputApplicationHandle == nullptr) { + return nullptr; } return static_cast<NativeInputApplicationHandle*>(inputApplicationHandle.get())-> getInputApplicationHandleObjLocalRef(env); @@ -153,8 +155,8 @@ static jobject getInputApplicationHandleObjLocalRef(JNIEnv* env, static jobject getInputWindowHandleObjLocalRef(JNIEnv* env, const sp<InputWindowHandle>& inputWindowHandle) { - if (inputWindowHandle == NULL) { - return NULL; + if (inputWindowHandle == nullptr) { + return nullptr; } return static_cast<NativeInputWindowHandle*>(inputWindowHandle.get())-> getInputWindowHandleObjLocalRef(env); @@ -182,6 +184,15 @@ static void loadSystemIconAsSprite(JNIEnv* env, jobject contextObj, int32_t styl loadSystemIconAsSpriteWithPointerIcon(env, contextObj, style, &pointerIcon, outSpriteIcon); } +static void updatePointerControllerFromViewport( + sp<PointerController> controller, const DisplayViewport* const viewport) { + if (controller != nullptr && viewport != nullptr) { + const int32_t width = viewport->logicalRight - viewport->logicalLeft; + const int32_t height = viewport->logicalBottom - viewport->logicalTop; + controller->setDisplayViewport(width, height, viewport->orientation); + } +} + enum { WM_ACTION_PASS_TO_USER = 1, }; @@ -203,15 +214,15 @@ public: void dump(std::string& dump); - void setVirtualDisplayViewports(JNIEnv* env, jobjectArray viewportObjArray); - void setDisplayViewport(int32_t viewportType, const DisplayViewport& viewport); + void setDisplayViewports(JNIEnv* env, jobjectArray viewportObjArray); status_t registerInputChannel(JNIEnv* env, const sp<InputChannel>& inputChannel, const sp<InputWindowHandle>& inputWindowHandle, bool monitor); status_t unregisterInputChannel(JNIEnv* env, const sp<InputChannel>& inputChannel); void setInputWindows(JNIEnv* env, jobjectArray windowHandleObjArray, int32_t displayId); - void setFocusedApplication(JNIEnv* env, jobject applicationHandleObj); + void setFocusedApplication(JNIEnv* env, int32_t displayId, jobject applicationHandleObj); + void setFocusedDisplay(JNIEnv* env, int32_t displayId); void setInputDispatchMode(bool enabled, bool frozen); void setSystemUiVisibility(int32_t visibility); void setPointerSpeed(int32_t speed); @@ -277,9 +288,7 @@ private: Mutex mLock; struct Locked { // Display size information. - DisplayViewport internalViewport; - DisplayViewport externalViewport; - Vector<DisplayViewport> virtualViewports; + std::vector<DisplayViewport> viewports; // System UI visibility. int32_t systemUiVisibility; @@ -304,7 +313,7 @@ private: // Input devices to be disabled SortedVector<int32_t> disabledInputDevices; - } mLocked; + } mLocked GUARDED_BY(mLock); std::atomic<bool> mInteractive; @@ -384,8 +393,17 @@ bool NativeInputManager::checkAndClearExceptionFromCallback(JNIEnv* env, const c return false; } -void NativeInputManager::setVirtualDisplayViewports(JNIEnv* env, jobjectArray viewportObjArray) { - Vector<DisplayViewport> viewports; +static const DisplayViewport* findInternalViewport(const std::vector<DisplayViewport>& viewports) { + for (const DisplayViewport& v : viewports) { + if (v.type == ViewportType::VIEWPORT_INTERNAL) { + return &v; + } + } + return nullptr; +} + +void NativeInputManager::setDisplayViewports(JNIEnv* env, jobjectArray viewportObjArray) { + std::vector<DisplayViewport> viewports; if (viewportObjArray) { jsize length = env->GetArrayLength(viewportObjArray); @@ -397,57 +415,32 @@ void NativeInputManager::setVirtualDisplayViewports(JNIEnv* env, jobjectArray vi DisplayViewport viewport; android_hardware_display_DisplayViewport_toNative(env, viewportObj, &viewport); - ALOGI("Viewport [%d] to add: %s", (int) length, viewport.uniqueId.c_str()); - viewports.push(viewport); + ALOGI("Viewport [%d] to add: %s", (int) i, viewport.uniqueId.c_str()); + viewports.push_back(viewport); env->DeleteLocalRef(viewportObj); } } + const DisplayViewport* newInternalViewport = findInternalViewport(viewports); { AutoMutex _l(mLock); - mLocked.virtualViewports = viewports; + const DisplayViewport* oldInternalViewport = findInternalViewport(mLocked.viewports); + // Internal viewport has changed if there wasn't one earlier, and there is one now, or, + // if they are different. + const bool internalViewportChanged = (newInternalViewport != nullptr) && + (oldInternalViewport == nullptr || (*oldInternalViewport != *newInternalViewport)); + if (internalViewportChanged) { + sp<PointerController> controller = mLocked.pointerController.promote(); + updatePointerControllerFromViewport(controller, newInternalViewport); + } + mLocked.viewports = viewports; } mInputManager->getReader()->requestRefreshConfiguration( InputReaderConfiguration::CHANGE_DISPLAY_INFO); } -void NativeInputManager::setDisplayViewport(int32_t type, const DisplayViewport& viewport) { - bool changed = false; - { - AutoMutex _l(mLock); - - ViewportType viewportType = static_cast<ViewportType>(type); - DisplayViewport* v = NULL; - if (viewportType == ViewportType::VIEWPORT_EXTERNAL) { - v = &mLocked.externalViewport; - } else if (viewportType == ViewportType::VIEWPORT_INTERNAL) { - v = &mLocked.internalViewport; - } - - if (v != NULL && *v != viewport) { - changed = true; - *v = viewport; - - if (viewportType == ViewportType::VIEWPORT_INTERNAL) { - sp<PointerController> controller = mLocked.pointerController.promote(); - if (controller != NULL) { - controller->setDisplayViewport( - viewport.logicalRight - viewport.logicalLeft, - viewport.logicalBottom - viewport.logicalTop, - viewport.orientation); - } - } - } - } - - if (changed) { - mInputManager->getReader()->requestRefreshConfiguration( - InputReaderConfiguration::CHANGE_DISPLAY_INFO); - } -} - status_t NativeInputManager::registerInputChannel(JNIEnv* /* env */, const sp<InputChannel>& inputChannel, const sp<InputWindowHandle>& inputWindowHandle, bool monitor) { @@ -479,7 +472,7 @@ void NativeInputManager::getReaderConfiguration(InputReaderConfiguration* outCon jsize length = env->GetArrayLength(excludedDeviceNames); for (jsize i = 0; i < length; i++) { jstring item = jstring(env->GetObjectArrayElement(excludedDeviceNames, i)); - const char* deviceNameChars = env->GetStringUTFChars(item, NULL); + const char* deviceNameChars = env->GetStringUTFChars(item, nullptr); outConfig->excludedDeviceNames.push_back(deviceNameChars); env->ReleaseStringUTFChars(item, deviceNameChars); env->DeleteLocalRef(item); @@ -526,11 +519,7 @@ void NativeInputManager::getReaderConfiguration(InputReaderConfiguration* outCon outConfig->pointerCapture = mLocked.pointerCapture; - outConfig->setPhysicalDisplayViewport(ViewportType::VIEWPORT_INTERNAL, - mLocked.internalViewport); - outConfig->setPhysicalDisplayViewport(ViewportType::VIEWPORT_EXTERNAL, - mLocked.externalViewport); - outConfig->setVirtualDisplayViewports(mLocked.virtualViewports); + outConfig->setDisplayViewports(mLocked.viewports); outConfig->disabledDevices = mLocked.disabledInputDevices; } // release lock @@ -541,25 +530,22 @@ sp<PointerControllerInterface> NativeInputManager::obtainPointerController(int32 AutoMutex _l(mLock); sp<PointerController> controller = mLocked.pointerController.promote(); - if (controller == NULL) { + if (controller == nullptr) { ensureSpriteControllerLocked(); controller = new PointerController(this, mLooper, mLocked.spriteController); mLocked.pointerController = controller; - DisplayViewport& v = mLocked.internalViewport; - controller->setDisplayViewport( - v.logicalRight - v.logicalLeft, - v.logicalBottom - v.logicalTop, - v.orientation); + const DisplayViewport* internalViewport = findInternalViewport(mLocked.viewports); + updatePointerControllerFromViewport(controller, internalViewport); updateInactivityTimeoutLocked(controller); } return controller; } -void NativeInputManager::ensureSpriteControllerLocked() { - if (mLocked.spriteController == NULL) { +void NativeInputManager::ensureSpriteControllerLocked() REQUIRES(mLock) { + if (mLocked.spriteController == nullptr) { JNIEnv* env = jniEnv(); jint layer = env->CallIntMethod(mServiceObj, gServiceClassInfo.getPointerLayer); if (checkAndClearExceptionFromCallback(env, "getPointerLayer")) { @@ -575,7 +561,7 @@ void NativeInputManager::notifyInputDevicesChanged(const Vector<InputDeviceInfo> size_t count = inputDevices.size(); jobjectArray inputDevicesObjArray = env->NewObjectArray( - count, gInputDeviceClassInfo.clazz, NULL); + count, gInputDeviceClassInfo.clazz, nullptr); if (inputDevicesObjArray) { bool error = false; for (size_t i = 0; i < count; i++) { @@ -750,7 +736,7 @@ void NativeInputManager::setInputWindows(JNIEnv* env, jobjectArray windowHandleO sp<InputWindowHandle> windowHandle = android_server_InputWindowHandle_getHandle(env, windowHandleObj); - if (windowHandle != NULL) { + if (windowHandle != nullptr) { windowHandles.push(windowHandle); } env->DeleteLocalRef(windowHandleObj); @@ -786,10 +772,15 @@ void NativeInputManager::setInputWindows(JNIEnv* env, jobjectArray windowHandleO } } -void NativeInputManager::setFocusedApplication(JNIEnv* env, jobject applicationHandleObj) { +void NativeInputManager::setFocusedApplication(JNIEnv* env, int32_t displayId, + jobject applicationHandleObj) { sp<InputApplicationHandle> applicationHandle = android_server_InputApplicationHandle_getHandle(env, applicationHandleObj); - mInputManager->getDispatcher()->setFocusedApplication(applicationHandle); + mInputManager->getDispatcher()->setFocusedApplication(displayId, applicationHandle); +} + +void NativeInputManager::setFocusedDisplay(JNIEnv* env, int32_t displayId) { + mInputManager->getDispatcher()->setFocusedDisplay(displayId); } void NativeInputManager::setInputDispatchMode(bool enabled, bool frozen) { @@ -803,13 +794,14 @@ void NativeInputManager::setSystemUiVisibility(int32_t visibility) { mLocked.systemUiVisibility = visibility; sp<PointerController> controller = mLocked.pointerController.promote(); - if (controller != NULL) { + if (controller != nullptr) { updateInactivityTimeoutLocked(controller); } } } -void NativeInputManager::updateInactivityTimeoutLocked(const sp<PointerController>& controller) { +void NativeInputManager::updateInactivityTimeoutLocked(const sp<PointerController>& controller) + REQUIRES(mLock) { bool lightsOut = mLocked.systemUiVisibility & ASYSTEM_UI_VISIBILITY_STATUS_BAR_HIDDEN; controller->setInactivityTimeout(lightsOut ? PointerController::INACTIVITY_TIMEOUT_SHORT @@ -894,7 +886,7 @@ void NativeInputManager::reloadCalibration() { void NativeInputManager::setPointerIconType(int32_t iconId) { AutoMutex _l(mLock); sp<PointerController> controller = mLocked.pointerController.promote(); - if (controller != NULL) { + if (controller != nullptr) { controller->updatePointerIcon(iconId); } } @@ -902,7 +894,7 @@ void NativeInputManager::setPointerIconType(int32_t iconId) { void NativeInputManager::reloadPointerIcons() { AutoMutex _l(mLock); sp<PointerController> controller = mLocked.pointerController.promote(); - if (controller != NULL) { + if (controller != nullptr) { controller->reloadPointerResources(); } } @@ -910,7 +902,7 @@ void NativeInputManager::reloadPointerIcons() { void NativeInputManager::setCustomPointerIcon(const SpriteIcon& icon) { AutoMutex _l(mLock); sp<PointerController> controller = mLocked.pointerController.promote(); - if (controller != NULL) { + if (controller != nullptr) { controller->setCustomPointerIcon(icon); } } @@ -1122,7 +1114,7 @@ bool NativeInputManager::dispatchUnhandledKey(const sp<InputWindowHandle>& input gServiceClassInfo.dispatchUnhandledKey, inputWindowHandleObj, keyEventObj, policyFlags); if (checkAndClearExceptionFromCallback(env, "dispatchUnhandledKey")) { - fallbackKeyEventObj = NULL; + fallbackKeyEventObj = nullptr; } android_view_KeyEvent_recycle(env, keyEventObj); env->DeleteLocalRef(keyEventObj); @@ -1235,7 +1227,7 @@ int32_t NativeInputManager::getCustomPointerIconId() { static jlong nativeInit(JNIEnv* env, jclass /* clazz */, jobject serviceObj, jobject contextObj, jobject messageQueueObj) { sp<MessageQueue> messageQueue = android_os_MessageQueue_getMessageQueue(env, messageQueueObj); - if (messageQueue == NULL) { + if (messageQueue == nullptr) { jniThrowRuntimeException(env, "MessageQueue is not initialized."); return 0; } @@ -1255,37 +1247,10 @@ static void nativeStart(JNIEnv* env, jclass /* clazz */, jlong ptr) { } } -static void nativeSetVirtualDisplayViewports(JNIEnv* env, jclass /* clazz */, jlong ptr, +static void nativeSetDisplayViewports(JNIEnv* env, jclass /* clazz */, jlong ptr, jobjectArray viewportObjArray) { NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr); - im->setVirtualDisplayViewports(env, viewportObjArray); -} - -static void nativeSetDisplayViewport(JNIEnv* env, jclass /* clazz */, jlong ptr, - jint viewportType, jint displayId, jint orientation, - jint logicalLeft, jint logicalTop, jint logicalRight, jint logicalBottom, - jint physicalLeft, jint physicalTop, jint physicalRight, jint physicalBottom, - jint deviceWidth, jint deviceHeight, jstring uniqueId) { - NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr); - - DisplayViewport v; - v.displayId = displayId; - v.orientation = orientation; - v.logicalLeft = logicalLeft; - v.logicalTop = logicalTop; - v.logicalRight = logicalRight; - v.logicalBottom = logicalBottom; - v.physicalLeft = physicalLeft; - v.physicalTop = physicalTop; - v.physicalRight = physicalRight; - v.physicalBottom = physicalBottom; - v.deviceWidth = deviceWidth; - v.deviceHeight = deviceHeight; - if (uniqueId != nullptr) { - v.uniqueId = ScopedUtfChars(env, uniqueId).c_str(); - } - - im->setDisplayViewport(viewportType, v); + im->setDisplayViewports(env, viewportObjArray); } static jint nativeGetScanCodeState(JNIEnv* /* env */, jclass /* clazz */, @@ -1316,8 +1281,8 @@ static jboolean nativeHasKeys(JNIEnv* env, jclass /* clazz */, jlong ptr, jint deviceId, jint sourceMask, jintArray keyCodes, jbooleanArray outFlags) { NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr); - int32_t* codes = env->GetIntArrayElements(keyCodes, NULL); - uint8_t* flags = env->GetBooleanArrayElements(outFlags, NULL); + int32_t* codes = env->GetIntArrayElements(keyCodes, nullptr); + uint8_t* flags = env->GetBooleanArrayElements(outFlags, nullptr); jsize numCodes = env->GetArrayLength(keyCodes); jboolean result; if (numCodes == env->GetArrayLength(keyCodes)) { @@ -1356,7 +1321,7 @@ static void nativeRegisterInputChannel(JNIEnv* env, jclass /* clazz */, sp<InputChannel> inputChannel = android_view_InputChannel_getInputChannel(env, inputChannelObj); - if (inputChannel == NULL) { + if (inputChannel == nullptr) { throwInputChannelNotInitialized(env); return; } @@ -1385,12 +1350,12 @@ static void nativeUnregisterInputChannel(JNIEnv* env, jclass /* clazz */, sp<InputChannel> inputChannel = android_view_InputChannel_getInputChannel(env, inputChannelObj); - if (inputChannel == NULL) { + if (inputChannel == nullptr) { throwInputChannelNotInitialized(env); return; } - android_view_InputChannel_setDisposeCallback(env, inputChannelObj, NULL, NULL); + android_view_InputChannel_setDisposeCallback(env, inputChannelObj, nullptr, nullptr); status_t status = im->unregisterInputChannel(env, inputChannel); if (status && status != BAD_VALUE) { // ignore already unregistered channel @@ -1454,10 +1419,17 @@ static void nativeSetInputWindows(JNIEnv* env, jclass /* clazz */, } static void nativeSetFocusedApplication(JNIEnv* env, jclass /* clazz */, - jlong ptr, jobject applicationHandleObj) { + jlong ptr, jint displayId, jobject applicationHandleObj) { + NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr); + + im->setFocusedApplication(env, displayId, applicationHandleObj); +} + +static void nativeSetFocusedDisplay(JNIEnv* env, jclass /* clazz */, + jlong ptr, jint displayId) { NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr); - im->setFocusedApplication(env, applicationHandleObj); + im->setFocusedDisplay(env, displayId); } static void nativeSetPointerCapture(JNIEnv* env, jclass /* clazz */, jlong ptr, @@ -1490,7 +1462,7 @@ static jboolean nativeTransferTouchFocus(JNIEnv* env, sp<InputChannel> toChannel = android_view_InputChannel_getInputChannel(env, toChannelObj); - if (fromChannel == NULL || toChannel == NULL) { + if (fromChannel == nullptr || toChannel == nullptr) { return JNI_FALSE; } @@ -1543,7 +1515,7 @@ static void nativeVibrate(JNIEnv* env, } jlong* patternMillis = static_cast<jlong*>(env->GetPrimitiveArrayCritical( - patternObj, NULL)); + patternObj, nullptr)); nsecs_t pattern[patternSize]; for (size_t i = 0; i < patternSize; i++) { pattern[i] = max(jlong(0), min(patternMillis[i], @@ -1656,10 +1628,8 @@ static const JNINativeMethod gInputManagerMethods[] = { (void*) nativeInit }, { "nativeStart", "(J)V", (void*) nativeStart }, - { "nativeSetVirtualDisplayViewports", "(J[Landroid/hardware/display/DisplayViewport;)V", - (void*) nativeSetVirtualDisplayViewports }, - { "nativeSetDisplayViewport", "(JIIIIIIIIIIIIILjava/lang/String;)V", - (void*) nativeSetDisplayViewport }, + { "nativeSetDisplayViewports", "(J[Landroid/hardware/display/DisplayViewport;)V", + (void*) nativeSetDisplayViewports }, { "nativeGetScanCodeState", "(JIII)I", (void*) nativeGetScanCodeState }, { "nativeGetKeyCodeState", "(JIII)I", @@ -1681,8 +1651,10 @@ static const JNINativeMethod gInputManagerMethods[] = { (void*) nativeToggleCapsLock }, { "nativeSetInputWindows", "(J[Lcom/android/server/input/InputWindowHandle;I)V", (void*) nativeSetInputWindows }, - { "nativeSetFocusedApplication", "(JLcom/android/server/input/InputApplicationHandle;)V", + { "nativeSetFocusedApplication", "(JILcom/android/server/input/InputApplicationHandle;)V", (void*) nativeSetFocusedApplication }, + { "nativeSetFocusedDisplay", "(JI)V", + (void*) nativeSetFocusedDisplay }, { "nativeSetPointerCapture", "(JZ)V", (void*) nativeSetPointerCapture }, { "nativeSetInputDispatchMode", "(JZZ)V", diff --git a/services/core/jni/com_android_server_power_PowerManagerService.cpp b/services/core/jni/com_android_server_power_PowerManagerService.cpp index b2d35d4153a0..0c9b5f4999a0 100644 --- a/services/core/jni/com_android_server_power_PowerManagerService.cpp +++ b/services/core/jni/com_android_server_power_PowerManagerService.cpp @@ -19,6 +19,7 @@ //#define LOG_NDEBUG 0 #include <android/hardware/power/1.1/IPower.h> +#include <android/system/suspend/1.0/ISystemSuspend.h> #include <nativehelper/JNIHelp.h> #include "jni.h" @@ -35,7 +36,7 @@ #include <utils/Log.h> #include <hardware/power.h> #include <hardware_legacy/power.h> -#include <suspend/autosuspend.h> +#include <hidl/ServiceManagement.h> #include "com_android_server_power_PowerManagerService.h" @@ -44,6 +45,9 @@ using android::hardware::Void; using android::hardware::power::V1_0::PowerHint; using android::hardware::power::V1_0::Feature; using android::String8; +using android::system::suspend::V1_0::ISystemSuspend; +using android::system::suspend::V1_0::IWakeLock; +using android::system::suspend::V1_0::WakeLockType; using IPowerV1_1 = android::hardware::power::V1_1::IPower; using IPowerV1_0 = android::hardware::power::V1_0::IPower; @@ -171,6 +175,46 @@ void android_server_PowerManagerService_userActivity(nsecs_t eventTime, int32_t } } +static sp<ISystemSuspend> gSuspendHal = nullptr; +static sp<IWakeLock> gSuspendBlocker = nullptr; +static std::mutex gSuspendMutex; + +// Assume SystemSuspend HAL is always alive. +// TODO: Force device to restart if SystemSuspend HAL dies. +sp<ISystemSuspend> getSuspendHal() { + static std::once_flag suspendHalFlag; + std::call_once(suspendHalFlag, [](){ + ::android::hardware::details::waitForHwService(ISystemSuspend::descriptor, "default"); + gSuspendHal = ISystemSuspend::getService(); + assert(gSuspendHal != nullptr); + }); + return gSuspendHal; +} + +void enableAutoSuspend() { + static bool enabled = false; + + std::lock_guard<std::mutex> lock(gSuspendMutex); + if (!enabled) { + sp<ISystemSuspend> suspendHal = getSuspendHal(); + suspendHal->enableAutosuspend(); + enabled = true; + } + if (gSuspendBlocker) { + gSuspendBlocker->release(); + gSuspendBlocker.clear(); + } +} + +void disableAutoSuspend() { + std::lock_guard<std::mutex> lock(gSuspendMutex); + if (!gSuspendBlocker) { + sp<ISystemSuspend> suspendHal = getSuspendHal(); + gSuspendBlocker = suspendHal->acquireWakeLock(WakeLockType::PARTIAL, + "PowerManager.SuspendLockout"); + } +} + // ---------------------------------------------------------------------------- static void nativeInit(JNIEnv* env, jobject obj) { @@ -207,13 +251,13 @@ static void nativeSetInteractive(JNIEnv* /* env */, jclass /* clazz */, jboolean static void nativeSetAutoSuspend(JNIEnv* /* env */, jclass /* clazz */, jboolean enable) { if (enable) { android::base::Timer t; - autosuspend_enable(); + enableAutoSuspend(); if (t.duration() > 100ms) { ALOGD("Excessive delay in autosuspend_enable() while turning screen off"); } } else { android::base::Timer t; - autosuspend_disable(); + disableAutoSuspend(); if (t.duration() > 100ms) { ALOGD("Excessive delay in autosuspend_disable() while turning screen on"); } diff --git a/services/devicepolicy/Android.bp b/services/devicepolicy/Android.bp index 05052047e0fb..47790ce68dc1 100644 --- a/services/devicepolicy/Android.bp +++ b/services/devicepolicy/Android.bp @@ -3,7 +3,6 @@ java_library_static { srcs: ["java/**/*.java"], libs: [ - "conscrypt", "services.core", ], } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java index 84de6b4b5281..66cf48c8ad24 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java @@ -15,20 +15,10 @@ */ package com.android.server.devicepolicy; -import android.annotation.UserIdInt; import android.app.admin.IDevicePolicyManager; -import android.content.ComponentName; -import android.os.PersistableBundle; -import android.security.keymaster.KeymasterCertificateChain; -import android.security.keystore.ParcelableKeyGenParameterSpec; -import android.telephony.data.ApnSetting; import com.android.server.SystemService; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - /** * Defines the required interface for IDevicePolicyManager implemenation. * @@ -64,94 +54,10 @@ abstract class BaseIDevicePolicyManager extends IDevicePolicyManager.Stub { */ abstract void handleStopUser(int userId); - public void setSystemSetting(ComponentName who, String setting, String value){} - - public void transferOwnership(ComponentName admin, ComponentName target, PersistableBundle bundle) {} - - public PersistableBundle getTransferOwnershipBundle() { - return null; - } - - public boolean generateKeyPair(ComponentName who, String callerPackage, String algorithm, - ParcelableKeyGenParameterSpec keySpec, int idAttestationFlags, - KeymasterCertificateChain attestationChain) { - return false; - } - - public boolean isUsingUnifiedPassword(ComponentName who) { - return true; - } - - public boolean setKeyPairCertificate(ComponentName who, String callerPackage, String alias, - byte[] cert, byte[] chain, boolean isUserSelectable) { - return false; - } - - @Override - public void setStartUserSessionMessage( - ComponentName admin, CharSequence startUserSessionMessage) {} - - @Override - public void setEndUserSessionMessage(ComponentName admin, CharSequence endUserSessionMessage) {} - - @Override - public String getStartUserSessionMessage(ComponentName admin) { - return null; - } - - @Override - public String getEndUserSessionMessage(ComponentName admin) { - return null; - } - - @Override - public List<String> setMeteredDataDisabledPackages(ComponentName admin, List<String> packageNames) { - return packageNames; - } - - @Override - public List<String> getMeteredDataDisabledPackages(ComponentName admin) { - return new ArrayList<>(); - } - - @Override - public int addOverrideApn(ComponentName admin, ApnSetting apnSetting) { - return -1; - } - - @Override - public boolean updateOverrideApn(ComponentName admin, int apnId, ApnSetting apnSetting) { - return false; - } - - @Override - public boolean removeOverrideApn(ComponentName admin, int apnId) { - return false; - } - - @Override - public List<ApnSetting> getOverrideApns(ComponentName admin) { - return Collections.emptyList(); - } - - @Override - public void setOverrideApnsEnabled(ComponentName admin, boolean enabled) {} - - @Override - public boolean isOverrideApnEnabled(ComponentName admin) { - return false; - } - public void clearSystemUpdatePolicyFreezePeriodRecord() { } @Override - public boolean isMeteredDataDisabledPackageForUser(ComponentName admin, - String packageName, int userId) { - return false; - } - - @Override public long forceNetworkLogs() { return 0; } @@ -160,8 +66,4 @@ 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/DeviceAdminServiceController.java b/services/devicepolicy/java/com/android/server/devicepolicy/DeviceAdminServiceController.java index 0c0ce8dd5174..85ca52e17ecb 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DeviceAdminServiceController.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DeviceAdminServiceController.java @@ -18,27 +18,23 @@ package com.android.server.devicepolicy; import android.Manifest.permission; import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.admin.DeviceAdminService; import android.app.admin.DevicePolicyManager; import android.app.admin.IDeviceAdminService; import android.content.ComponentName; import android.content.Context; -import android.content.Intent; -import android.content.pm.ParceledListSlice; -import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.os.Handler; import android.os.IBinder; -import android.os.RemoteException; -import android.util.Log; import android.util.Slog; import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.os.BackgroundThread; import com.android.server.am.PersistentConnection; +import com.android.server.appbinding.AppBindingUtils; import java.io.PrintWriter; -import java.util.List; /** * Manages connections to persistent services in owner packages. @@ -70,7 +66,13 @@ public class DeviceAdminServiceController { super(TAG, mContext, mHandler, userId, componentName, mConstants.DAS_DIED_SERVICE_RECONNECT_BACKOFF_SEC, mConstants.DAS_DIED_SERVICE_RECONNECT_BACKOFF_INCREASE, - mConstants.DAS_DIED_SERVICE_RECONNECT_MAX_BACKOFF_SEC); + mConstants.DAS_DIED_SERVICE_RECONNECT_MAX_BACKOFF_SEC, + mConstants.DAS_DIED_SERVICE_STABLE_CONNECTION_THRESHOLD_SEC); + } + + @Override + protected int getBindFlags() { + return Context.BIND_FOREGROUND_SERVICE; } @Override @@ -100,40 +102,14 @@ public class DeviceAdminServiceController { */ @Nullable private ServiceInfo findService(@NonNull String packageName, int userId) { - final Intent intent = new Intent(DevicePolicyManager.ACTION_DEVICE_ADMIN_SERVICE); - intent.setPackage(packageName); - - try { - final ParceledListSlice<ResolveInfo> pls = mInjector.getIPackageManager() - .queryIntentServices(intent, null, /* flags=*/ 0, userId); - if (pls == null) { - return null; - } - final List<ResolveInfo> list = pls.getList(); - if (list.size() == 0) { - return null; - } - // Note if multiple services are found, that's an error, even if only one of them - // is exported. - if (list.size() > 1) { - Log.e(TAG, "More than one DeviceAdminService's found in package " - + packageName - + ". They'll all be ignored."); - return null; - } - final ServiceInfo si = list.get(0).serviceInfo; - - if (!permission.BIND_DEVICE_ADMIN.equals(si.permission)) { - Log.e(TAG, "DeviceAdminService " - + si.getComponentName().flattenToShortString() - + " must be protected with " + permission.BIND_DEVICE_ADMIN - + "."); - return null; - } - return si; - } catch (RemoteException e) { - } - return null; + return AppBindingUtils.findService( + packageName, + userId, + DevicePolicyManager.ACTION_DEVICE_ADMIN_SERVICE, + permission.BIND_DEVICE_ADMIN, + DeviceAdminService.class, + mInjector.getIPackageManager(), + new StringBuilder() /* ignore error message */); } /** diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyConstants.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyConstants.java index 616c669de939..71fea02c282f 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyConstants.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyConstants.java @@ -30,14 +30,17 @@ import java.util.concurrent.TimeUnit; public class DevicePolicyConstants { private static final String TAG = DevicePolicyManagerService.LOG_TAG; - private static final String DAS_DIED_SERVICE_RECONNECT_BACKOFF_SEC_KEY - = "das_died_service_reconnect_backoff_sec"; + private static final String DAS_DIED_SERVICE_RECONNECT_BACKOFF_SEC_KEY = + "das_died_service_reconnect_backoff_sec"; - private static final String DAS_DIED_SERVICE_RECONNECT_BACKOFF_INCREASE_KEY - = "das_died_service_reconnect_backoff_increase"; + private static final String DAS_DIED_SERVICE_RECONNECT_BACKOFF_INCREASE_KEY = + "das_died_service_reconnect_backoff_increase"; - private static final String DAS_DIED_SERVICE_RECONNECT_MAX_BACKOFF_SEC_KEY - = "das_died_service_reconnect_max_backoff_sec"; + private static final String DAS_DIED_SERVICE_RECONNECT_MAX_BACKOFF_SEC_KEY = + "das_died_service_reconnect_max_backoff_sec"; + + private static final String DAS_DIED_SERVICE_STABLE_CONNECTION_THRESHOLD_SEC_KEY = + "das_died_service_stable_connection_threshold_sec"; /** * The back-off before re-connecting, when a service binding died, due to the owner @@ -55,6 +58,11 @@ public class DevicePolicyConstants { */ public final long DAS_DIED_SERVICE_RECONNECT_MAX_BACKOFF_SEC; + /** + * If a connection lasts more than this duration, we reset the re-connect back-off time. + */ + public final long DAS_DIED_SERVICE_STABLE_CONNECTION_THRESHOLD_SEC; + private DevicePolicyConstants(String settings) { final KeyValueListParser parser = new KeyValueListParser(','); @@ -75,6 +83,10 @@ public class DevicePolicyConstants { long dasDiedServiceReconnectMaxBackoffSec = parser.getLong( DAS_DIED_SERVICE_RECONNECT_MAX_BACKOFF_SEC_KEY, TimeUnit.DAYS.toSeconds(1)); + long dasDiedServiceStableConnectionThresholdSec = parser.getLong( + DAS_DIED_SERVICE_STABLE_CONNECTION_THRESHOLD_SEC_KEY, + TimeUnit.MINUTES.toSeconds(2)); + // Set minimum: 5 seconds. dasDiedServiceReconnectBackoffSec = Math.max(5, dasDiedServiceReconnectBackoffSec); @@ -89,7 +101,8 @@ public class DevicePolicyConstants { DAS_DIED_SERVICE_RECONNECT_BACKOFF_SEC = dasDiedServiceReconnectBackoffSec; DAS_DIED_SERVICE_RECONNECT_BACKOFF_INCREASE = dasDiedServiceReconnectBackoffIncrease; DAS_DIED_SERVICE_RECONNECT_MAX_BACKOFF_SEC = dasDiedServiceReconnectMaxBackoffSec; - + DAS_DIED_SERVICE_STABLE_CONNECTION_THRESHOLD_SEC = + dasDiedServiceStableConnectionThresholdSec; } public static DevicePolicyConstants loadFromString(String settings) { @@ -102,14 +115,18 @@ public class DevicePolicyConstants { pw.print(prefix); pw.print(" DAS_DIED_SERVICE_RECONNECT_BACKOFF_SEC: "); - pw.println( DAS_DIED_SERVICE_RECONNECT_BACKOFF_SEC); + pw.println(DAS_DIED_SERVICE_RECONNECT_BACKOFF_SEC); pw.print(prefix); pw.print(" DAS_DIED_SERVICE_RECONNECT_BACKOFF_INCREASE: "); - pw.println( DAS_DIED_SERVICE_RECONNECT_BACKOFF_INCREASE); + pw.println(DAS_DIED_SERVICE_RECONNECT_BACKOFF_INCREASE); pw.print(prefix); pw.print(" DAS_DIED_SERVICE_RECONNECT_MAX_BACKOFF_SEC: "); - pw.println( DAS_DIED_SERVICE_RECONNECT_MAX_BACKOFF_SEC); + pw.println(DAS_DIED_SERVICE_RECONNECT_MAX_BACKOFF_SEC); + + pw.print(prefix); + pw.print(" DAS_DIED_SERVICE_STABLE_CONNECTION_THRESHOLD_SEC: "); + pw.println(DAS_DIED_SERVICE_STABLE_CONNECTION_THRESHOLD_SEC); } } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index e76afa3c144a..eeb4ad32408c 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -10106,13 +10106,15 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { if (setting.equals(Settings.Secure.INSTALL_NON_MARKET_APPS)) { if (getTargetSdk(who.getPackageName(), callingUserId) >= Build.VERSION_CODES.O) { throw new UnsupportedOperationException(Settings.Secure.INSTALL_NON_MARKET_APPS - + " is deprecated. Please use the user restriction " - + UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES + " instead."); + + " is deprecated. Please use one of the user restrictions " + + UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES + " or " + + UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY + " instead."); } if (!mUserManager.isManagedProfile(callingUserId)) { Slog.e(LOG_TAG, "Ignoring setSecureSetting request for " + setting + ". User restriction " - + UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES + + UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES + " or " + + UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY + " should be used instead."); } else { try { diff --git a/services/net/java/android/net/dhcp/DhcpPacket.java b/services/net/java/android/net/dhcp/DhcpPacket.java index 77a3e2102452..6ba7d94117b1 100644 --- a/services/net/java/android/net/dhcp/DhcpPacket.java +++ b/services/net/java/android/net/dhcp/DhcpPacket.java @@ -1,5 +1,8 @@ package android.net.dhcp; +import static android.net.util.NetworkConstants.IPV4_MAX_MTU; +import static android.net.util.NetworkConstants.IPV4_MIN_MTU; + import android.annotation.Nullable; import android.net.DhcpResults; import android.net.LinkAddress; @@ -381,6 +384,26 @@ public abstract class DhcpPacket { } /** + * Returns whether a parameter is included in the parameter request list option of this packet. + * + * <p>If there is no parameter request list option in the packet, false is returned. + * + * @param paramId ID of the parameter, such as {@link #DHCP_MTU} or {@link #DHCP_HOST_NAME}. + */ + public boolean hasRequestedParam(byte paramId) { + if (mRequestedParams == null) { + return false; + } + + for (byte reqParam : mRequestedParams) { + if (reqParam == paramId) { + return true; + } + } + return false; + } + + /** * Creates a new L3 packet (including IP header) containing the * DHCP udp packet. This method relies upon the delegated method * finishPacket() to insert the per-packet contents. @@ -696,7 +719,11 @@ public abstract class DhcpPacket { addTlv(buf, DHCP_ROUTER, mGateways); addTlv(buf, DHCP_DNS_SERVER, mDnsServers); addTlv(buf, DHCP_DOMAIN_NAME, mDomainName); + addTlv(buf, DHCP_HOST_NAME, mHostName); addTlv(buf, DHCP_VENDOR_INFO, mVendorInfo); + if (mMtu != null && Short.toUnsignedInt(mMtu) >= IPV4_MIN_MTU) { + addTlv(buf, DHCP_MTU, mMtu); + } } /** @@ -1259,7 +1286,8 @@ public abstract class DhcpPacket { boolean broadcast, Inet4Address serverIpAddr, Inet4Address relayIp, Inet4Address yourIp, byte[] mac, Integer timeout, Inet4Address netMask, Inet4Address bcAddr, List<Inet4Address> gateways, List<Inet4Address> dnsServers, - Inet4Address dhcpServerIdentifier, String domainName, boolean metered) { + Inet4Address dhcpServerIdentifier, String domainName, String hostname, boolean metered, + short mtu) { DhcpPacket pkt = new DhcpOfferPacket( transactionId, (short) 0, broadcast, serverIpAddr, relayIp, INADDR_ANY /* clientIp */, yourIp, mac); @@ -1267,9 +1295,11 @@ public abstract class DhcpPacket { pkt.mDnsServers = dnsServers; pkt.mLeaseTime = timeout; pkt.mDomainName = domainName; + pkt.mHostName = hostname; pkt.mServerIdentifier = dhcpServerIdentifier; pkt.mSubnetMask = netMask; pkt.mBroadcastAddress = bcAddr; + pkt.mMtu = mtu; if (metered) { pkt.mVendorInfo = VENDOR_INFO_ANDROID_METERED; } @@ -1283,7 +1313,8 @@ public abstract class DhcpPacket { boolean broadcast, Inet4Address serverIpAddr, Inet4Address relayIp, Inet4Address yourIp, Inet4Address requestClientIp, byte[] mac, Integer timeout, Inet4Address netMask, Inet4Address bcAddr, List<Inet4Address> gateways, List<Inet4Address> dnsServers, - Inet4Address dhcpServerIdentifier, String domainName, boolean metered) { + Inet4Address dhcpServerIdentifier, String domainName, String hostname, boolean metered, + short mtu) { DhcpPacket pkt = new DhcpAckPacket( transactionId, (short) 0, broadcast, serverIpAddr, relayIp, requestClientIp, yourIp, mac); @@ -1291,9 +1322,11 @@ public abstract class DhcpPacket { pkt.mDnsServers = dnsServers; pkt.mLeaseTime = timeout; pkt.mDomainName = domainName; + pkt.mHostName = hostname; pkt.mSubnetMask = netMask; pkt.mServerIdentifier = dhcpServerIdentifier; pkt.mBroadcastAddress = bcAddr; + pkt.mMtu = mtu; if (metered) { pkt.mVendorInfo = VENDOR_INFO_ANDROID_METERED; } diff --git a/services/net/java/android/net/dhcp/DhcpServer.java b/services/net/java/android/net/dhcp/DhcpServer.java index 2b3d577b0eae..cee6fa96bbc5 100644 --- a/services/net/java/android/net/dhcp/DhcpServer.java +++ b/services/net/java/android/net/dhcp/DhcpServer.java @@ -20,6 +20,7 @@ import static android.net.NetworkUtils.getBroadcastAddress; import static android.net.NetworkUtils.getPrefixMaskAsInet4Address; import static android.net.TrafficStats.TAG_SYSTEM_DHCP_SERVER; import static android.net.dhcp.DhcpPacket.DHCP_CLIENT; +import static android.net.dhcp.DhcpPacket.DHCP_HOST_NAME; import static android.net.dhcp.DhcpPacket.DHCP_SERVER; import static android.net.dhcp.DhcpPacket.ENCAP_BOOTP; import static android.net.dhcp.DhcpPacket.INFINITE_LEASE; @@ -46,6 +47,7 @@ import android.os.Message; import android.os.SystemClock; import android.system.ErrnoException; import android.system.Os; +import android.text.TextUtils; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.HexDump; @@ -350,6 +352,19 @@ public class DhcpServer { return isEmpty(request.mClientIp) && (request.mBroadcast || isEmpty(lease.getNetAddr())); } + /** + * Get the hostname from a lease if non-empty and requested in the incoming request. + * @param request The incoming request. + * @return The hostname, or null if not requested or empty. + */ + @Nullable + private static String getHostnameIfRequested(@NonNull DhcpPacket request, + @NonNull DhcpLease lease) { + return request.hasRequestedParam(DHCP_HOST_NAME) && !TextUtils.isEmpty(lease.getHostname()) + ? lease.getHostname() + : null; + } + private boolean transmitOffer(@NonNull DhcpPacket request, @NonNull DhcpLease lease, @NonNull MacAddress clientMac) { final boolean broadcastFlag = getBroadcastFlag(request, lease); @@ -358,12 +373,14 @@ public class DhcpServer { getPrefixMaskAsInet4Address(mServingParams.serverAddr.getPrefixLength()); final Inet4Address broadcastAddr = getBroadcastAddress( mServingParams.getServerInet4Addr(), mServingParams.serverAddr.getPrefixLength()); + final String hostname = getHostnameIfRequested(request, lease); final ByteBuffer offerPacket = DhcpPacket.buildOfferPacket( ENCAP_BOOTP, request.mTransId, broadcastFlag, mServingParams.getServerInet4Addr(), request.mRelayIp, lease.getNetAddr(), request.mClientMac, timeout, prefixMask, broadcastAddr, new ArrayList<>(mServingParams.defaultRouters), new ArrayList<>(mServingParams.dnsServers), - mServingParams.getServerInet4Addr(), null /* domainName */, mServingParams.metered); + mServingParams.getServerInet4Addr(), null /* domainName */, hostname, + mServingParams.metered, (short) mServingParams.linkMtu); return transmitOfferOrAckPacket(offerPacket, request, lease, clientMac, broadcastFlag); } @@ -374,13 +391,15 @@ public class DhcpServer { // transmitOffer above final boolean broadcastFlag = getBroadcastFlag(request, lease); final int timeout = getLeaseTimeout(lease); + final String hostname = getHostnameIfRequested(request, lease); final ByteBuffer ackPacket = DhcpPacket.buildAckPacket(ENCAP_BOOTP, request.mTransId, broadcastFlag, mServingParams.getServerInet4Addr(), request.mRelayIp, lease.getNetAddr(), request.mClientIp, request.mClientMac, timeout, mServingParams.getPrefixMaskAsAddress(), mServingParams.getBroadcastAddress(), new ArrayList<>(mServingParams.defaultRouters), new ArrayList<>(mServingParams.dnsServers), - mServingParams.getServerInet4Addr(), null /* domainName */, mServingParams.metered); + mServingParams.getServerInet4Addr(), null /* domainName */, hostname, + mServingParams.metered, (short) mServingParams.linkMtu); return transmitOfferOrAckPacket(ackPacket, request, lease, clientMac, broadcastFlag); } diff --git a/services/robotests/src/com/android/server/backup/fullbackup/AppMetadataBackupWriterTest.java b/services/robotests/src/com/android/server/backup/fullbackup/AppMetadataBackupWriterTest.java index 112e1e385fed..b771039b2936 100644 --- a/services/robotests/src/com/android/server/backup/fullbackup/AppMetadataBackupWriterTest.java +++ b/services/robotests/src/com/android/server/backup/fullbackup/AppMetadataBackupWriterTest.java @@ -183,7 +183,6 @@ public class AppMetadataBackupWriterTest { new Signature[] {new Signature("1234"), new Signature("5678")}, SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3, null, - null, null)); File manifestFile = createFile(BACKUP_MANIFEST_FILENAME); diff --git a/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java new file mode 100644 index 000000000000..66650700f599 --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java @@ -0,0 +1,543 @@ +/* + * 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; + +import static androidx.test.InstrumentationRegistry.getContext; + +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; +import static com.android.server.DeviceIdleController.LIGHT_STATE_ACTIVE; +import static com.android.server.DeviceIdleController.LIGHT_STATE_IDLE; +import static com.android.server.DeviceIdleController.LIGHT_STATE_IDLE_MAINTENANCE; +import static com.android.server.DeviceIdleController.LIGHT_STATE_INACTIVE; +import static com.android.server.DeviceIdleController.LIGHT_STATE_OVERRIDE; +import static com.android.server.DeviceIdleController.LIGHT_STATE_PRE_IDLE; +import static com.android.server.DeviceIdleController.LIGHT_STATE_WAITING_FOR_NETWORK; +import static com.android.server.DeviceIdleController.STATE_ACTIVE; +import static com.android.server.DeviceIdleController.STATE_IDLE; +import static com.android.server.DeviceIdleController.STATE_IDLE_MAINTENANCE; +import static com.android.server.DeviceIdleController.STATE_IDLE_PENDING; +import static com.android.server.DeviceIdleController.STATE_INACTIVE; +import static com.android.server.DeviceIdleController.STATE_LOCATING; +import static com.android.server.DeviceIdleController.STATE_SENSING; +import static com.android.server.DeviceIdleController.lightStateToString; +import static com.android.server.DeviceIdleController.stateToString; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; + +import android.app.ActivityManagerInternal; +import android.app.AlarmManager; +import android.app.IActivityManager; +import android.content.Context; +import android.hardware.SensorManager; +import android.location.LocationManager; +import android.location.LocationProvider; +import android.os.Handler; +import android.os.Looper; +import android.os.PowerManager; +import android.os.PowerManagerInternal; + +import androidx.test.runner.AndroidJUnit4; + +import com.android.server.net.NetworkPolicyManagerInternal; +import com.android.server.wm.ActivityTaskManagerInternal; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoSession; +import org.mockito.quality.Strictness; + +/** + * Tests for {@link com.android.server.DeviceIdleController}. + */ +@RunWith(AndroidJUnit4.class) +public class DeviceIdleControllerTest { + private DeviceIdleController mDeviceIdleController; + private AnyMotionDetectorForTest mAnyMotionDetector; + private AppStateTrackerForTest mAppStateTracker; + + private MockitoSession mMockingSession; + @Mock + private PowerManager mPowerManager; + @Mock + private PowerManager.WakeLock mWakeLock; + @Mock + private AlarmManager mAlarmManager; + @Mock + private LocationManager mLocationManager; + @Mock + private IActivityManager mIActivityManager; + + class InjectorForTest extends DeviceIdleController.Injector { + + InjectorForTest(Context ctx) { + super(ctx); + } + + @Override + AlarmManager getAlarmManager() { + return mAlarmManager; + } + + @Override + AnyMotionDetector getAnyMotionDetector(Handler handler, SensorManager sm, + AnyMotionDetector.DeviceIdleCallback callback, float angleThreshold) { + return mAnyMotionDetector; + } + + @Override + AppStateTracker getAppStateTracker(Context ctx, Looper loop) { + return mAppStateTracker; + } + + @Override + ConnectivityService getConnectivityService() { + return null; + } + + @Override + LocationManager getLocationManager() { + return mLocationManager; + } + + @Override + DeviceIdleController.MyHandler getHandler(DeviceIdleController ctlr) { + return mock(DeviceIdleController.MyHandler.class); + } + + @Override + PowerManager getPowerManager() { + return mPowerManager; + } + } + + private class AnyMotionDetectorForTest extends AnyMotionDetector { + boolean isMonitoring = false; + + AnyMotionDetectorForTest() { + super(mPowerManager, mock(Handler.class), mock(SensorManager.class), + mock(DeviceIdleCallback.class), 0.5f); + } + + @Override + public void checkForAnyMotion() { + isMonitoring = true; + } + + @Override + public void stop() { + isMonitoring = false; + } + } + + private class AppStateTrackerForTest extends AppStateTracker { + AppStateTrackerForTest(Context ctx, Looper looper) { + super(ctx, looper); + } + + @Override + public void onSystemServicesReady() { + // Do nothing. + } + + @Override + IActivityManager injectIActivityManager() { + return mIActivityManager; + } + } + + @Before + public void setUp() { + mMockingSession = mockitoSession() + .initMocks(this) + .strictness(Strictness.LENIENT) + .mockStatic(LocalServices.class) + .startMocking(); + doReturn(mock(ActivityManagerInternal.class)) + .when(() -> LocalServices.getService(ActivityManagerInternal.class)); + doReturn(mock(ActivityTaskManagerInternal.class)) + .when(() -> LocalServices.getService(ActivityTaskManagerInternal.class)); + doReturn(mock(PowerManagerInternal.class)) + .when(() -> LocalServices.getService(PowerManagerInternal.class)); + doReturn(mock(NetworkPolicyManagerInternal.class)) + .when(() -> LocalServices.getService(NetworkPolicyManagerInternal.class)); + when(mPowerManager.newWakeLock(anyInt(), anyString())).thenReturn(mWakeLock); + doNothing().when(mWakeLock).acquire(); + mAppStateTracker = new AppStateTrackerForTest(getContext(), Looper.getMainLooper()); + mAnyMotionDetector = new AnyMotionDetectorForTest(); + mDeviceIdleController = new DeviceIdleController(getContext(), + new InjectorForTest(getContext())); + spyOn(mDeviceIdleController); + doNothing().when(mDeviceIdleController).publishBinderService(any(), any()); + mDeviceIdleController.onStart(); + mDeviceIdleController.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY); + mDeviceIdleController.setDeepEnabledForTest(true); + mDeviceIdleController.setLightEnabledForTest(true); + } + + @After + public void tearDown() { + if (mMockingSession != null) { + mMockingSession.finishMocking(); + } + // DeviceIdleController adds this to LocalServices in the constructor, so we have to remove + // it after each test, otherwise, subsequent tests will fail. + LocalServices.removeServiceForTest(AppStateTracker.class); + } + + @Test + public void testUpdateInteractivityLocked() { + doReturn(false).when(mPowerManager).isInteractive(); + mDeviceIdleController.updateInteractivityLocked(); + assertFalse(mDeviceIdleController.isScreenOn()); + + // Make sure setting false when screen is already off doesn't change anything. + doReturn(false).when(mPowerManager).isInteractive(); + mDeviceIdleController.updateInteractivityLocked(); + assertFalse(mDeviceIdleController.isScreenOn()); + + // Test changing from screen off to screen on. + doReturn(true).when(mPowerManager).isInteractive(); + mDeviceIdleController.updateInteractivityLocked(); + assertTrue(mDeviceIdleController.isScreenOn()); + + // Make sure setting true when screen is already on doesn't change anything. + doReturn(true).when(mPowerManager).isInteractive(); + mDeviceIdleController.updateInteractivityLocked(); + assertTrue(mDeviceIdleController.isScreenOn()); + + // Test changing from screen on to screen off. + doReturn(false).when(mPowerManager).isInteractive(); + mDeviceIdleController.updateInteractivityLocked(); + assertFalse(mDeviceIdleController.isScreenOn()); + } + + @Test + public void testUpdateChargingLocked() { + mDeviceIdleController.updateChargingLocked(false); + assertFalse(mDeviceIdleController.isCharging()); + + // Make sure setting false when charging is already off doesn't change anything. + mDeviceIdleController.updateChargingLocked(false); + assertFalse(mDeviceIdleController.isCharging()); + + // Test changing from charging off to charging on. + mDeviceIdleController.updateChargingLocked(true); + assertTrue(mDeviceIdleController.isCharging()); + + // Make sure setting true when charging is already on doesn't change anything. + mDeviceIdleController.updateChargingLocked(true); + assertTrue(mDeviceIdleController.isCharging()); + + // Test changing from charging on to charging off. + mDeviceIdleController.updateChargingLocked(false); + assertFalse(mDeviceIdleController.isCharging()); + } + + @Test + public void testStateActiveToStateInactive_ConditionsNotMet() { + mDeviceIdleController.becomeActiveLocked("testing", 0); + verifyStateConditions(STATE_ACTIVE); + + // State should stay ACTIVE with screen on and charging. + setChargingOn(true); + setScreenOn(true); + + mDeviceIdleController.becomeInactiveIfAppropriateLocked(); + verifyStateConditions(STATE_ACTIVE); + + // State should stay ACTIVE with charging on. + setChargingOn(true); + setScreenOn(false); + + mDeviceIdleController.becomeInactiveIfAppropriateLocked(); + verifyStateConditions(STATE_ACTIVE); + + // State should stay ACTIVE with screen on. + // Note the different operation order here makes sure the state doesn't change before test. + setScreenOn(true); + setChargingOn(false); + + mDeviceIdleController.becomeInactiveIfAppropriateLocked(); + verifyStateConditions(STATE_ACTIVE); + } + + @Test + public void testLightStateActiveToLightStateInactive_ConditionsNotMet() { + mDeviceIdleController.becomeActiveLocked("testing", 0); + verifyLightStateConditions(LIGHT_STATE_ACTIVE); + + // State should stay ACTIVE with screen on and charging. + setChargingOn(true); + setScreenOn(true); + + mDeviceIdleController.becomeInactiveIfAppropriateLocked(); + verifyLightStateConditions(LIGHT_STATE_ACTIVE); + + // State should stay ACTIVE with charging on. + setChargingOn(true); + setScreenOn(false); + + mDeviceIdleController.becomeInactiveIfAppropriateLocked(); + verifyLightStateConditions(LIGHT_STATE_ACTIVE); + + // State should stay ACTIVE with screen on. + // Note the different operation order here makes sure the state doesn't change before test. + setScreenOn(true); + setChargingOn(false); + + mDeviceIdleController.becomeInactiveIfAppropriateLocked(); + verifyLightStateConditions(LIGHT_STATE_ACTIVE); + } + + @Test + public void testStateActiveToStateInactive_ConditionsMet() { + mDeviceIdleController.becomeActiveLocked("testing", 0); + verifyStateConditions(STATE_ACTIVE); + + setChargingOn(false); + setScreenOn(false); + + mDeviceIdleController.becomeInactiveIfAppropriateLocked(); + verifyStateConditions(STATE_INACTIVE); + } + + @Test + public void testLightStateActiveToLightStateInactive_ConditionsMet() { + mDeviceIdleController.becomeActiveLocked("testing", 0); + verifyLightStateConditions(LIGHT_STATE_ACTIVE); + + setChargingOn(false); + setScreenOn(false); + + mDeviceIdleController.becomeInactiveIfAppropriateLocked(); + verifyLightStateConditions(LIGHT_STATE_INACTIVE); + } + + @Test + public void testStepIdleStateLocked_InvalidStates() { + mDeviceIdleController.becomeActiveLocked("testing", 0); + mDeviceIdleController.stepIdleStateLocked("testing"); + // mDeviceIdleController.stepIdleStateLocked doesn't handle the ACTIVE case, so the state + // should stay as ACTIVE. + verifyStateConditions(STATE_ACTIVE); + } + + @Test + public void testStepIdleStateLocked_ValidStates_NoLocationManager() { + mDeviceIdleController.setLocationManagerForTest(null); + // Make sure the controller doesn't think there's a wake-from-idle alarm coming soon. + doReturn(Long.MAX_VALUE).when(mAlarmManager).getNextWakeFromIdleTime(); + // Set state to INACTIVE. + mDeviceIdleController.becomeActiveLocked("testing", 0); + setChargingOn(false); + setScreenOn(false); + verifyStateConditions(STATE_INACTIVE); + + mDeviceIdleController.stepIdleStateLocked("testing"); + verifyStateConditions(STATE_IDLE_PENDING); + + mDeviceIdleController.stepIdleStateLocked("testing"); + verifyStateConditions(STATE_SENSING); + + mDeviceIdleController.stepIdleStateLocked("testing"); + // No location manager, so SENSING should go straight to IDLE. + verifyStateConditions(STATE_IDLE); + + // Should just alternate between IDLE and IDLE_MAINTENANCE now. + + mDeviceIdleController.stepIdleStateLocked("testing"); + verifyStateConditions(STATE_IDLE_MAINTENANCE); + + mDeviceIdleController.stepIdleStateLocked("testing"); + verifyStateConditions(STATE_IDLE); + + mDeviceIdleController.stepIdleStateLocked("testing"); + verifyStateConditions(STATE_IDLE_MAINTENANCE); + } + + @Test + public void testStepIdleStateLocked_ValidStates_WithLocationManager_NoProviders() { + // Make sure the controller doesn't think there's a wake-from-idle alarm coming soon. + doReturn(Long.MAX_VALUE).when(mAlarmManager).getNextWakeFromIdleTime(); + // Set state to INACTIVE. + mDeviceIdleController.becomeActiveLocked("testing", 0); + setChargingOn(false); + setScreenOn(false); + verifyStateConditions(STATE_INACTIVE); + + mDeviceIdleController.stepIdleStateLocked("testing"); + verifyStateConditions(STATE_IDLE_PENDING); + + mDeviceIdleController.stepIdleStateLocked("testing"); + verifyStateConditions(STATE_SENSING); + + mDeviceIdleController.stepIdleStateLocked("testing"); + // Location manager exists but there isn't a network or GPS provider, + // so SENSING should go straight to IDLE. + verifyStateConditions(STATE_IDLE); + + // Should just alternate between IDLE and IDLE_MAINTENANCE now. + + mDeviceIdleController.stepIdleStateLocked("testing"); + verifyStateConditions(STATE_IDLE_MAINTENANCE); + + mDeviceIdleController.stepIdleStateLocked("testing"); + verifyStateConditions(STATE_IDLE); + + mDeviceIdleController.stepIdleStateLocked("testing"); + verifyStateConditions(STATE_IDLE_MAINTENANCE); + } + + @Test + public void testStepIdleStateLocked_ValidStates_WithLocationManager_WithProviders() { + doReturn(mock(LocationProvider.class)).when(mLocationManager).getProvider(anyString()); + // Make sure the controller doesn't think there's a wake-from-idle alarm coming soon. + // TODO: add tests for when there's a wake-from-idle alarm coming soon. + doReturn(Long.MAX_VALUE).when(mAlarmManager).getNextWakeFromIdleTime(); + // Set state to INACTIVE. + mDeviceIdleController.becomeActiveLocked("testing", 0); + setChargingOn(false); + setScreenOn(false); + verifyStateConditions(STATE_INACTIVE); + + mDeviceIdleController.stepIdleStateLocked("testing"); + verifyStateConditions(STATE_IDLE_PENDING); + + mDeviceIdleController.stepIdleStateLocked("testing"); + verifyStateConditions(STATE_SENSING); + + mDeviceIdleController.stepIdleStateLocked("testing"); + // Location manager exists with a provider, so SENSING should go to LOCATING. + verifyStateConditions(STATE_LOCATING); + + mDeviceIdleController.stepIdleStateLocked("testing"); + verifyStateConditions(STATE_IDLE); + + // Should just alternate between IDLE and IDLE_MAINTENANCE now. + + mDeviceIdleController.stepIdleStateLocked("testing"); + verifyStateConditions(STATE_IDLE_MAINTENANCE); + + mDeviceIdleController.stepIdleStateLocked("testing"); + verifyStateConditions(STATE_IDLE); + + mDeviceIdleController.stepIdleStateLocked("testing"); + verifyStateConditions(STATE_IDLE_MAINTENANCE); + } + + private void setChargingOn(boolean on) { + mDeviceIdleController.updateChargingLocked(on); + } + + private void setScreenOn(boolean on) { + doReturn(on).when(mPowerManager).isInteractive(); + mDeviceIdleController.updateInteractivityLocked(); + } + + private void verifyStateConditions(int expectedState) { + int curState = mDeviceIdleController.getState(); + assertEquals( + "Expected " + stateToString(expectedState) + " but was " + stateToString(curState), + expectedState, curState); + + switch (expectedState) { + case STATE_ACTIVE: + assertFalse(mDeviceIdleController.mMotionListener.isActive()); + assertFalse(mAnyMotionDetector.isMonitoring); + break; + case STATE_INACTIVE: + assertFalse(mDeviceIdleController.mMotionListener.isActive()); + assertFalse(mAnyMotionDetector.isMonitoring); + assertFalse(mDeviceIdleController.isCharging()); + assertFalse(mDeviceIdleController.isScreenOn()); + break; + case STATE_IDLE_PENDING: + assertTrue(mDeviceIdleController.mMotionListener.isActive()); + assertFalse(mAnyMotionDetector.isMonitoring); + assertFalse(mDeviceIdleController.isCharging()); + assertFalse(mDeviceIdleController.isScreenOn()); + break; + case STATE_SENSING: + assertTrue(mDeviceIdleController.mMotionListener.isActive()); + assertTrue(mAnyMotionDetector.isMonitoring); + assertFalse(mDeviceIdleController.isCharging()); + assertFalse(mDeviceIdleController.isScreenOn()); + break; + case STATE_LOCATING: + assertTrue(mDeviceIdleController.mMotionListener.isActive()); + assertTrue(mAnyMotionDetector.isMonitoring); + assertFalse(mDeviceIdleController.isCharging()); + assertFalse(mDeviceIdleController.isScreenOn()); + break; + case STATE_IDLE: + assertTrue(mDeviceIdleController.mMotionListener.isActive()); + assertFalse(mAnyMotionDetector.isMonitoring); + assertFalse(mDeviceIdleController.isCharging()); + assertFalse(mDeviceIdleController.isScreenOn()); + // Light state should be OVERRIDE at this point. + verifyLightStateConditions(LIGHT_STATE_OVERRIDE); + break; + case STATE_IDLE_MAINTENANCE: + assertTrue(mDeviceIdleController.mMotionListener.isActive()); + assertFalse(mAnyMotionDetector.isMonitoring); + assertFalse(mDeviceIdleController.isCharging()); + assertFalse(mDeviceIdleController.isScreenOn()); + break; + default: + fail("Conditions for " + stateToString(expectedState) + " unknown."); + } + } + + private void verifyLightStateConditions(int expectedLightState) { + int curLightState = mDeviceIdleController.getLightState(); + assertEquals( + "Expected " + lightStateToString(expectedLightState) + + " but was " + lightStateToString(curLightState), + expectedLightState, curLightState); + + switch (expectedLightState) { + case LIGHT_STATE_ACTIVE: + assertTrue( + mDeviceIdleController.isCharging() || mDeviceIdleController.isScreenOn()); + break; + case LIGHT_STATE_INACTIVE: + case LIGHT_STATE_PRE_IDLE: + case LIGHT_STATE_IDLE: + case LIGHT_STATE_WAITING_FOR_NETWORK: + case LIGHT_STATE_IDLE_MAINTENANCE: + case LIGHT_STATE_OVERRIDE: + assertFalse(mDeviceIdleController.isCharging()); + assertFalse(mDeviceIdleController.isScreenOn()); + break; + default: + fail("Conditions for " + lightStateToString(expectedLightState) + " unknown."); + } + } +} diff --git a/services/tests/servicestests/src/com/android/server/am/PersistentConnectionTest.java b/services/tests/mockingservicestests/src/com/android/server/am/PersistentConnectionTest.java index 54f93a88a387..26e77eb166ca 100644 --- a/services/tests/servicestests/src/com/android/server/am/PersistentConnectionTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/PersistentConnectionTest.java @@ -43,6 +43,8 @@ import java.util.Collections; @SmallTest public class PersistentConnectionTest extends AndroidTestCase { + private static final String TAG = "PersistentConnectionTest"; + private static class MyConnection extends PersistentConnection<IDeviceAdminService> { public long uptimeMillis = 12345; @@ -50,9 +52,16 @@ public class PersistentConnectionTest extends AndroidTestCase { public MyConnection(String tag, Context context, Handler handler, int userId, ComponentName componentName, long rebindBackoffSeconds, - double rebindBackoffIncrease, long rebindMaxBackoffSeconds) { + double rebindBackoffIncrease, long rebindMaxBackoffSeconds, + long resetBackoffDelay) { super(tag, context, handler, userId, componentName, - rebindBackoffSeconds, rebindBackoffIncrease, rebindMaxBackoffSeconds); + rebindBackoffSeconds, rebindBackoffIncrease, rebindMaxBackoffSeconds, + resetBackoffDelay); + } + + @Override + protected int getBindFlags() { + return Context.BIND_FOREGROUND_SERVICE; } @Override @@ -108,10 +117,11 @@ public class PersistentConnectionTest extends AndroidTestCase { final ComponentName cn = ComponentName.unflattenFromString("a.b.c/def"); final Handler handler = new Handler(Looper.getMainLooper()); - final MyConnection conn = new MyConnection("tag", context, handler, userId, cn, + final MyConnection conn = new MyConnection(TAG, context, handler, userId, cn, /* rebindBackoffSeconds= */ 5, /* rebindBackoffIncrease= */ 1.5, - /* rebindMaxBackoffSeconds= */ 11); + /* rebindMaxBackoffSeconds= */ 11, + /* resetBackoffDelay= */ 999); assertFalse(conn.isBound()); assertFalse(conn.isConnected()); @@ -310,10 +320,11 @@ public class PersistentConnectionTest extends AndroidTestCase { final ComponentName cn = ComponentName.unflattenFromString("a.b.c/def"); final Handler handler = new Handler(Looper.getMainLooper()); - final MyConnection conn = new MyConnection("tag", context, handler, userId, cn, + final MyConnection conn = new MyConnection(TAG, context, handler, userId, cn, /* rebindBackoffSeconds= */ 5, /* rebindBackoffIncrease= */ 1.5, - /* rebindMaxBackoffSeconds= */ 11); + /* rebindMaxBackoffSeconds= */ 11, + /* resetBackoffDelay= */ 999); when(context.bindServiceAsUser(any(Intent.class), any(ServiceConnection.class), anyInt(), any(Handler.class), any(UserHandle.class))) @@ -351,4 +362,78 @@ public class PersistentConnectionTest extends AndroidTestCase { assertFalse(conn.isBound()); assertFalse(conn.shouldBeBoundForTest()); } + + public void testResetBackoff() { + final Context context = mock(Context.class); + final int userId = 11; + final ComponentName cn = ComponentName.unflattenFromString("a.b.c/def"); + final Handler handler = new Handler(Looper.getMainLooper()); + + final MyConnection conn = new MyConnection(TAG, context, handler, userId, cn, + /* rebindBackoffSeconds= */ 5, + /* rebindBackoffIncrease= */ 1.5, + /* rebindMaxBackoffSeconds= */ 11, + /* resetBackoffDelay= */ 20); + + when(context.bindServiceAsUser(any(Intent.class), any(ServiceConnection.class), anyInt(), + any(Handler.class), any(UserHandle.class))) + .thenReturn(true); + + // Bind. + conn.bind(); + + assertTrue(conn.isBound()); + assertTrue(conn.shouldBeBoundForTest()); + assertFalse(conn.isRebindScheduled()); + + conn.elapse(1000); + + // Then the binding is "died"... + conn.getServiceConnectionForTest().onBindingDied(cn); + + assertFalse(conn.isBound()); + assertTrue(conn.shouldBeBoundForTest()); + assertFalse(conn.isConnected()); + assertNull(conn.getServiceBinder()); + assertTrue(conn.isRebindScheduled()); + + assertEquals(7500, conn.getNextBackoffMsForTest()); + + assertEquals( + Arrays.asList(Pair.create(conn.getBindForBackoffRunnableForTest(), + conn.uptimeMillis + 5000)), + conn.scheduledRunnables); + + // 5000 ms later... + conn.elapse(5000); + + assertTrue(conn.isBound()); + assertTrue(conn.shouldBeBoundForTest()); + assertFalse(conn.isConnected()); + assertNull(conn.getServiceBinder()); + assertFalse(conn.isRebindScheduled()); + + assertEquals(7500, conn.getNextBackoffMsForTest()); + + // Connected. + conn.getServiceConnectionForTest().onServiceConnected(cn, + new IDeviceAdminService.Stub() {}); + + assertTrue(conn.isBound()); + assertTrue(conn.shouldBeBoundForTest()); + assertTrue(conn.isConnected()); + assertNotNull(conn.getServiceBinder()); + assertFalse(conn.isRebindScheduled()); + + assertEquals(7500, conn.getNextBackoffMsForTest()); + + assertEquals( + Arrays.asList(Pair.create(conn.getStableCheckRunnableForTest(), + conn.uptimeMillis + 20000)), + conn.scheduledRunnables); + + conn.elapse(20000); + + assertEquals(5000, conn.getNextBackoffMsForTest()); + } } diff --git a/services/tests/servicestests/Android.mk b/services/tests/servicestests/Android.mk index 80307eebf53b..2957267d2e5b 100644 --- a/services/tests/servicestests/Android.mk +++ b/services/tests/servicestests/Android.mk @@ -58,6 +58,7 @@ LOCAL_JNI_SHARED_LIBRARIES := \ libbacktrace \ libbase \ libbinder \ + libbinderthreadstate \ libc++ \ libcutils \ liblog \ diff --git a/services/tests/servicestests/assets/PackageSignaturesTest/certs/ec-p256-lineage-2-signers b/services/tests/servicestests/assets/PackageSignaturesTest/certs/ec-p256-lineage-2-signers Binary files differnew file mode 100644 index 000000000000..509ea3b2e61d --- /dev/null +++ b/services/tests/servicestests/assets/PackageSignaturesTest/certs/ec-p256-lineage-2-signers diff --git a/services/tests/servicestests/assets/PackageSignaturesTest/certs/ec-p256-lineage-3-signers b/services/tests/servicestests/assets/PackageSignaturesTest/certs/ec-p256-lineage-3-signers Binary files differnew file mode 100644 index 000000000000..bee71c0189fa --- /dev/null +++ b/services/tests/servicestests/assets/PackageSignaturesTest/certs/ec-p256-lineage-3-signers diff --git a/services/tests/servicestests/assets/PackageSignaturesTest/certs/ec-p256.pk8 b/services/tests/servicestests/assets/PackageSignaturesTest/certs/ec-p256.pk8 Binary files differnew file mode 100644 index 000000000000..f781c3083e54 --- /dev/null +++ b/services/tests/servicestests/assets/PackageSignaturesTest/certs/ec-p256.pk8 diff --git a/services/tests/servicestests/assets/PackageSignaturesTest/certs/ec-p256.x509.der b/services/tests/servicestests/assets/PackageSignaturesTest/certs/ec-p256.x509.der Binary files differnew file mode 100644 index 000000000000..e611e3d9383f --- /dev/null +++ b/services/tests/servicestests/assets/PackageSignaturesTest/certs/ec-p256.x509.der diff --git a/services/tests/servicestests/assets/PackageSignaturesTest/certs/ec-p256_2.pk8 b/services/tests/servicestests/assets/PackageSignaturesTest/certs/ec-p256_2.pk8 Binary files differnew file mode 100644 index 000000000000..5e73f27847d2 --- /dev/null +++ b/services/tests/servicestests/assets/PackageSignaturesTest/certs/ec-p256_2.pk8 diff --git a/services/tests/servicestests/assets/PackageSignaturesTest/certs/ec-p256_2.x509.der b/services/tests/servicestests/assets/PackageSignaturesTest/certs/ec-p256_2.x509.der Binary files differnew file mode 100644 index 000000000000..7723beab5715 --- /dev/null +++ b/services/tests/servicestests/assets/PackageSignaturesTest/certs/ec-p256_2.x509.der diff --git a/services/tests/servicestests/assets/PackageSignaturesTest/certs/ec-p256_3.pk8 b/services/tests/servicestests/assets/PackageSignaturesTest/certs/ec-p256_3.pk8 Binary files differnew file mode 100644 index 000000000000..d7309dd3f024 --- /dev/null +++ b/services/tests/servicestests/assets/PackageSignaturesTest/certs/ec-p256_3.pk8 diff --git a/services/tests/servicestests/assets/PackageSignaturesTest/certs/ec-p256_3.x509.der b/services/tests/servicestests/assets/PackageSignaturesTest/certs/ec-p256_3.x509.der Binary files differnew file mode 100644 index 000000000000..cc82af9c0310 --- /dev/null +++ b/services/tests/servicestests/assets/PackageSignaturesTest/certs/ec-p256_3.x509.der diff --git a/services/tests/servicestests/assets/PackageSignaturesTest/xml/README b/services/tests/servicestests/assets/PackageSignaturesTest/xml/README new file mode 100644 index 000000000000..43d5bb8864df --- /dev/null +++ b/services/tests/servicestests/assets/PackageSignaturesTest/xml/README @@ -0,0 +1,58 @@ +The XML files in this directory are taken from the packages tag of a test APK signed with the +certificates and keys under the certs/ directory. To recreate the XML files run the following: + +1. Build the test APK: +mmm -j cts/hostsidetests/appsecurity/test-apps/tinyapp/ + +2. Sign the APK with the first signer: +apksigner sign --in ${OUT}/data/app/CtsPkgInstallTinyApp/CtsPkgInstallTinyApp.apk --out test.apk \ + --cert certs/ec-p256.x509.der --key certs/ec-p256.pk8 + +3. Install the APK on a device: +adb install test.apk + +4. Pull the packages.xml file containing the new entry for the APK from the device: +adb pull /data/system/packages.xml + +5. Search the packages.xml file for the package name 'android.appsecurity.cts.tinyapp'. Following is + the full entry when the APK is signed as above: + + <package name="android.appsecurity.cts.tinyapp" codePath="/data/app/android.appsecurity.cts.tiny + app-4ix3umoWct_iD26jQ03Z_g==" nativeLibraryPath="/data/app/android.appsecurity.cts.tinyapp-4ix3u + moWct_iD26jQ03Z_g==/lib" publicFlags="805879364" privateFlags="0" ft="1663710dd00" it="1663710de + 41" ut="1663710de41" version="10" userId="10051"> + <sigs count="1" schemeVersion="3"> + <cert index="16" key="3082016c30820111a003020102020900ca0fb64dfb66e772300a06082a8648ce3d + 04030230123110300e06035504030c0765632d70323536301e170d3136303333313134353830365a170d3433 + 303831373134353830365a30123110300e06035504030c0765632d703235363059301306072a8648ce3d0201 + 06082a8648ce3d03010703420004a65f113d22cb4913908307ac31ee2ba0e9138b785fac6536d14ea2ce90d2 + b4bfe194b50cdc8e169f54a73a991ef0fa76329825be078cc782740703da44b4d7eba350304e301d0603551d + 0e04160414d4133568b95b30158b322071ea8c43ff5b05ccc8301f0603551d23041830168014d4133568b95b + 30158b322071ea8c43ff5b05ccc8300c0603551d13040530030101ff300a06082a8648ce3d04030203490030 + 46022100f504a0866caef029f417142c5cb71354c79ffcd1d640618dfca4f19e16db78d6022100f8eea48297 + 99c06cad08c6d3d2d2ec05e0574154e747ea0fdbb8042cb655aadd" /> + </sigs> + <proper-signing-keyset identifier="480" /> + </package> + +The PackageSignatures#readXml and writeXml methods read and write everything within the sigs tag. +The tags and attributes within the sigs tag can be modified and used to verify various good and +error paths for the PackageSignaturesTest. + +Step 2 can be modified to sign with multiple signers by running one of the following commands: + +- To sign with two signers in the lineage (after the signing key has been rotated once): +apksigner sign --in ${OUT}/data/app/CtsPkgInstallTinyApp/CtsPkgInstallTinyApp.apk --out test.apk \ + --cert certs/ec-p256.x509.der --key certs/ec-p256.pk8 --next-signer --cert \ + certs/ec-p256_2.x509.der --key certs/ec-p256_2.pk8 --lineage certs/ec-p256-lineage-2-signers + +- To sign with three signers in the lineage (after the second key rotation): +apksigner sign --in ${OUT}/data/app/CtsPkgInstallTinyApp/CtsPkgInstallTinyApp.apk --out test.apk \ + --cert certs/ec-p256.x509.der --key certs/ec-p256.pk8 --next-signer --cert \ + certs/ec-p256_3.x509.der --key certs/ec-p256_3.pk8 --lineage certs/ec-p256-lineage-3-signers + +- To sign with two distinct signers (NOTE: The V3 signature scheme only supports a single signer, + so this method can only be used with signature schemes V1 and V2): +apksigner sign --in ${OUT}/data/app/CtsPkgInstallTinyApp/CtsPkgInstallTinyApp.apk --out test.apk \ + --cert certs/ec-p256.x509.der --key certs/ec-p256.pk8 --next-signer --cert \ + certs/ec-p256_3.x509.der --key certs/ec-p256_3.pk8 --v3-signing-enabled false diff --git a/services/tests/servicestests/assets/PackageSignaturesTest/xml/one-signer-extra-cert-tag.xml b/services/tests/servicestests/assets/PackageSignaturesTest/xml/one-signer-extra-cert-tag.xml new file mode 100644 index 000000000000..4d55bad7cc90 --- /dev/null +++ b/services/tests/servicestests/assets/PackageSignaturesTest/xml/one-signer-extra-cert-tag.xml @@ -0,0 +1,5 @@ + <sigs count="1" schemeVersion="3"> + <cert index="0" key="3082016c30820111a003020102020900ca0fb64dfb66e772300a06082a8648ce3d04030230123110300e06035504030c0765632d70323536301e170d3136303333313134353830365a170d3433303831373134353830365a30123110300e06035504030c0765632d703235363059301306072a8648ce3d020106082a8648ce3d03010703420004a65f113d22cb4913908307ac31ee2ba0e9138b785fac6536d14ea2ce90d2b4bfe194b50cdc8e169f54a73a991ef0fa76329825be078cc782740703da44b4d7eba350304e301d0603551d0e04160414d4133568b95b30158b322071ea8c43ff5b05ccc8301f0603551d23041830168014d4133568b95b30158b322071ea8c43ff5b05ccc8300c0603551d13040530030101ff300a06082a8648ce3d0403020349003046022100f504a0866caef029f417142c5cb71354c79ffcd1d640618dfca4f19e16db78d6022100f8eea4829799c06cad08c6d3d2d2ec05e0574154e747ea0fdbb8042cb655aadd" /> + <cert index="0" key="3082016c30820111a003020102020900ca0fb64dfb66e772300a06082a8648ce3d04030230123110300e06035504030c0765632d70323536301e170d3136303333313134353830365a170d3433303831373134353830365a30123110300e06035504030c0765632d703235363059301306072a8648ce3d020106082a8648ce3d03010703420004a65f113d22cb4913908307ac31ee2ba0e9138b785fac6536d14ea2ce90d2b4bfe194b50cdc8e169f54a73a991ef0fa76329825be078cc782740703da44b4d7eba350304e301d0603551d0e04160414d4133568b95b30158b322071ea8c43ff5b05ccc8301f0603551d23041830168014d4133568b95b30158b322071ea8c43ff5b05ccc8300c0603551d13040530030101ff300a06082a8648ce3d0403020349003046022100f504a0866caef029f417142c5cb71354c79ffcd1d640618dfca4f19e16db78d6022100f8eea4829799c06cad08c6d3d2d2ec05e0574154e747ea0fdbb8042cb655aadd" /> + </sigs> + diff --git a/services/tests/servicestests/assets/PackageSignaturesTest/xml/one-signer-invalid-cert-index.xml b/services/tests/servicestests/assets/PackageSignaturesTest/xml/one-signer-invalid-cert-index.xml new file mode 100644 index 000000000000..f7882b1389b0 --- /dev/null +++ b/services/tests/servicestests/assets/PackageSignaturesTest/xml/one-signer-invalid-cert-index.xml @@ -0,0 +1,4 @@ + <sigs count="1" schemeVersion="3"> + <cert index="x" key="3082016c30820111a003020102020900ca0fb64dfb66e772300a06082a8648ce3d04030230123110300e06035504030c0765632d70323536301e170d3136303333313134353830365a170d3433303831373134353830365a30123110300e06035504030c0765632d703235363059301306072a8648ce3d020106082a8648ce3d03010703420004a65f113d22cb4913908307ac31ee2ba0e9138b785fac6536d14ea2ce90d2b4bfe194b50cdc8e169f54a73a991ef0fa76329825be078cc782740703da44b4d7eba350304e301d0603551d0e04160414d4133568b95b30158b322071ea8c43ff5b05ccc8301f0603551d23041830168014d4133568b95b30158b322071ea8c43ff5b05ccc8300c0603551d13040530030101ff300a06082a8648ce3d0403020349003046022100f504a0866caef029f417142c5cb71354c79ffcd1d640618dfca4f19e16db78d6022100f8eea4829799c06cad08c6d3d2d2ec05e0574154e747ea0fdbb8042cb655aadd" /> + </sigs> + diff --git a/services/tests/servicestests/assets/PackageSignaturesTest/xml/one-signer-invalid-cert-key.xml b/services/tests/servicestests/assets/PackageSignaturesTest/xml/one-signer-invalid-cert-key.xml new file mode 100644 index 000000000000..af2c293c0f4a --- /dev/null +++ b/services/tests/servicestests/assets/PackageSignaturesTest/xml/one-signer-invalid-cert-key.xml @@ -0,0 +1,4 @@ + <sigs count="1" schemeVersion="3"> + <cert index="0" key="082016c30820111a003020102020900ca0fb64dfb66e772300a06082a8648ce3d04030230123110300e06035504030c0765632d70323536301e170d3136303333313134353830365a170d3433303831373134353830365a30123110300e06035504030c0765632d703235363059301306072a8648ce3d020106082a8648ce3d03010703420004a65f113d22cb4913908307ac31ee2ba0e9138b785fac6536d14ea2ce90d2b4bfe194b50cdc8e169f54a73a991ef0fa76329825be078cc782740703da44b4d7eba350304e301d0603551d0e04160414d4133568b95b30158b322071ea8c43ff5b05ccc8301f0603551d23041830168014d4133568b95b30158b322071ea8c43ff5b05ccc8300c0603551d13040530030101ff300a06082a8648ce3d0403020349003046022100f504a0866caef029f417142c5cb71354c79ffcd1d640618dfca4f19e16db78d6022100f8eea4829799c06cad08c6d3d2d2ec05e0574154e747ea0fdbb8042cb655aadd" /> + </sigs> + diff --git a/services/tests/servicestests/assets/PackageSignaturesTest/xml/one-signer-invalid-public-key-cert-key.xml b/services/tests/servicestests/assets/PackageSignaturesTest/xml/one-signer-invalid-public-key-cert-key.xml new file mode 100644 index 000000000000..893402d1004e --- /dev/null +++ b/services/tests/servicestests/assets/PackageSignaturesTest/xml/one-signer-invalid-public-key-cert-key.xml @@ -0,0 +1,4 @@ + <sigs count="1" schemeVersion="1"> + <cert index="0" key="4082016c30820111a003020102020900ca0fb64dfb66e772300a06082a8648ce3d04030230123110300e06035504030c0765632d70323536301e170d3136303333313134353830365a170d3433303831373134353830365a30123110300e06035504030c0765632d703235363059301306072a8648ce3d020106082a8648ce3d03010703420004a65f113d22cb4913908307ac31ee2ba0e9138b785fac6536d14ea2ce90d2b4bfe194b50cdc8e169f54a73a991ef0fa76329825be078cc782740703da44b4d7eba350304e301d0603551d0e04160414d4133568b95b30158b322071ea8c43ff5b05ccc8301f0603551d23041830168014d4133568b95b30158b322071ea8c43ff5b05ccc8300c0603551d13040530030101ff300a06082a8648ce3d0403020349003046022100f504a0866caef029f417142c5cb71354c79ffcd1d640618dfca4f19e16db78d6022100f8eea4829799c06cad08c6d3d2d2ec05e0574154e747ea0fdbb8042cb655aadd" /> + </sigs> + diff --git a/services/tests/servicestests/assets/PackageSignaturesTest/xml/one-signer-invalid-tag.xml b/services/tests/servicestests/assets/PackageSignaturesTest/xml/one-signer-invalid-tag.xml new file mode 100644 index 000000000000..1f81dacc2727 --- /dev/null +++ b/services/tests/servicestests/assets/PackageSignaturesTest/xml/one-signer-invalid-tag.xml @@ -0,0 +1,5 @@ + <sigs count="1" schemeVersion="3"> + <cert index="0" key="3082016c30820111a003020102020900ca0fb64dfb66e772300a06082a8648ce3d04030230123110300e06035504030c0765632d70323536301e170d3136303333313134353830365a170d3433303831373134353830365a30123110300e06035504030c0765632d703235363059301306072a8648ce3d020106082a8648ce3d03010703420004a65f113d22cb4913908307ac31ee2ba0e9138b785fac6536d14ea2ce90d2b4bfe194b50cdc8e169f54a73a991ef0fa76329825be078cc782740703da44b4d7eba350304e301d0603551d0e04160414d4133568b95b30158b322071ea8c43ff5b05ccc8301f0603551d23041830168014d4133568b95b30158b322071ea8c43ff5b05ccc8300c0603551d13040530030101ff300a06082a8648ce3d0403020349003046022100f504a0866caef029f417142c5cb71354c79ffcd1d640618dfca4f19e16db78d6022100f8eea4829799c06cad08c6d3d2d2ec05e0574154e747ea0fdbb8042cb655aadd" /> + <invalid /> + </sigs> + diff --git a/services/tests/servicestests/assets/PackageSignaturesTest/xml/one-signer-missing-cert-index.xml b/services/tests/servicestests/assets/PackageSignaturesTest/xml/one-signer-missing-cert-index.xml new file mode 100644 index 000000000000..c38e4d94a6d0 --- /dev/null +++ b/services/tests/servicestests/assets/PackageSignaturesTest/xml/one-signer-missing-cert-index.xml @@ -0,0 +1,4 @@ + <sigs count="1" schemeVersion="3"> + <cert key="3082016c30820111a003020102020900ca0fb64dfb66e772300a06082a8648ce3d04030230123110300e06035504030c0765632d70323536301e170d3136303333313134353830365a170d3433303831373134353830365a30123110300e06035504030c0765632d703235363059301306072a8648ce3d020106082a8648ce3d03010703420004a65f113d22cb4913908307ac31ee2ba0e9138b785fac6536d14ea2ce90d2b4bfe194b50cdc8e169f54a73a991ef0fa76329825be078cc782740703da44b4d7eba350304e301d0603551d0e04160414d4133568b95b30158b322071ea8c43ff5b05ccc8301f0603551d23041830168014d4133568b95b30158b322071ea8c43ff5b05ccc8300c0603551d13040530030101ff300a06082a8648ce3d0403020349003046022100f504a0866caef029f417142c5cb71354c79ffcd1d640618dfca4f19e16db78d6022100f8eea4829799c06cad08c6d3d2d2ec05e0574154e747ea0fdbb8042cb655aadd" /> + </sigs> + diff --git a/services/tests/servicestests/assets/PackageSignaturesTest/xml/one-signer-missing-cert-key.xml b/services/tests/servicestests/assets/PackageSignaturesTest/xml/one-signer-missing-cert-key.xml new file mode 100644 index 000000000000..8e8cbcf7174e --- /dev/null +++ b/services/tests/servicestests/assets/PackageSignaturesTest/xml/one-signer-missing-cert-key.xml @@ -0,0 +1,4 @@ + <sigs count="1" schemeVersion="3"> + <cert index="0" /> + </sigs> + diff --git a/services/tests/servicestests/assets/PackageSignaturesTest/xml/one-signer-missing-cert-tag.xml b/services/tests/servicestests/assets/PackageSignaturesTest/xml/one-signer-missing-cert-tag.xml new file mode 100644 index 000000000000..57e96a8db0a0 --- /dev/null +++ b/services/tests/servicestests/assets/PackageSignaturesTest/xml/one-signer-missing-cert-tag.xml @@ -0,0 +1,3 @@ + <sigs count="1" schemeVersion="3"> + </sigs> + diff --git a/services/tests/servicestests/assets/PackageSignaturesTest/xml/one-signer-missing-scheme-version.xml b/services/tests/servicestests/assets/PackageSignaturesTest/xml/one-signer-missing-scheme-version.xml new file mode 100644 index 000000000000..d9f7a5f29d22 --- /dev/null +++ b/services/tests/servicestests/assets/PackageSignaturesTest/xml/one-signer-missing-scheme-version.xml @@ -0,0 +1,4 @@ + <sigs count="1"> + <cert index="0" key="3082016c30820111a003020102020900ca0fb64dfb66e772300a06082a8648ce3d04030230123110300e06035504030c0765632d70323536301e170d3136303333313134353830365a170d3433303831373134353830365a30123110300e06035504030c0765632d703235363059301306072a8648ce3d020106082a8648ce3d03010703420004a65f113d22cb4913908307ac31ee2ba0e9138b785fac6536d14ea2ce90d2b4bfe194b50cdc8e169f54a73a991ef0fa76329825be078cc782740703da44b4d7eba350304e301d0603551d0e04160414d4133568b95b30158b322071ea8c43ff5b05ccc8301f0603551d23041830168014d4133568b95b30158b322071ea8c43ff5b05ccc8300c0603551d13040530030101ff300a06082a8648ce3d0403020349003046022100f504a0866caef029f417142c5cb71354c79ffcd1d640618dfca4f19e16db78d6022100f8eea4829799c06cad08c6d3d2d2ec05e0574154e747ea0fdbb8042cb655aadd" /> + </sigs> + diff --git a/services/tests/servicestests/assets/PackageSignaturesTest/xml/one-signer-missing-sigs-count.xml b/services/tests/servicestests/assets/PackageSignaturesTest/xml/one-signer-missing-sigs-count.xml new file mode 100644 index 000000000000..4eefdd9dbbbb --- /dev/null +++ b/services/tests/servicestests/assets/PackageSignaturesTest/xml/one-signer-missing-sigs-count.xml @@ -0,0 +1,4 @@ + <sigs schemeVersion="3"> + <cert index="0" key="3082016c30820111a003020102020900ca0fb64dfb66e772300a06082a8648ce3d04030230123110300e06035504030c0765632d70323536301e170d3136303333313134353830365a170d3433303831373134353830365a30123110300e06035504030c0765632d703235363059301306072a8648ce3d020106082a8648ce3d03010703420004a65f113d22cb4913908307ac31ee2ba0e9138b785fac6536d14ea2ce90d2b4bfe194b50cdc8e169f54a73a991ef0fa76329825be078cc782740703da44b4d7eba350304e301d0603551d0e04160414d4133568b95b30158b322071ea8c43ff5b05ccc8301f0603551d23041830168014d4133568b95b30158b322071ea8c43ff5b05ccc8300c0603551d13040530030101ff300a06082a8648ce3d0403020349003046022100f504a0866caef029f417142c5cb71354c79ffcd1d640618dfca4f19e16db78d6022100f8eea4829799c06cad08c6d3d2d2ec05e0574154e747ea0fdbb8042cb655aadd" /> + </sigs> + diff --git a/services/tests/servicestests/assets/PackageSignaturesTest/xml/one-signer-previous-cert.xml b/services/tests/servicestests/assets/PackageSignaturesTest/xml/one-signer-previous-cert.xml new file mode 100644 index 000000000000..2aeeb7124be9 --- /dev/null +++ b/services/tests/servicestests/assets/PackageSignaturesTest/xml/one-signer-previous-cert.xml @@ -0,0 +1,4 @@ + <sigs count="1" schemeVersion="2"> + <cert index="0" /> + </sigs> + diff --git a/services/tests/servicestests/assets/PackageSignaturesTest/xml/one-signer.xml b/services/tests/servicestests/assets/PackageSignaturesTest/xml/one-signer.xml new file mode 100644 index 000000000000..14471f84cb1b --- /dev/null +++ b/services/tests/servicestests/assets/PackageSignaturesTest/xml/one-signer.xml @@ -0,0 +1,4 @@ + <sigs count="1" schemeVersion="1"> + <cert index="0" key="3082016c30820111a003020102020900ca0fb64dfb66e772300a06082a8648ce3d04030230123110300e06035504030c0765632d70323536301e170d3136303333313134353830365a170d3433303831373134353830365a30123110300e06035504030c0765632d703235363059301306072a8648ce3d020106082a8648ce3d03010703420004a65f113d22cb4913908307ac31ee2ba0e9138b785fac6536d14ea2ce90d2b4bfe194b50cdc8e169f54a73a991ef0fa76329825be078cc782740703da44b4d7eba350304e301d0603551d0e04160414d4133568b95b30158b322071ea8c43ff5b05ccc8301f0603551d23041830168014d4133568b95b30158b322071ea8c43ff5b05ccc8300c0603551d13040530030101ff300a06082a8648ce3d0403020349003046022100f504a0866caef029f417142c5cb71354c79ffcd1d640618dfca4f19e16db78d6022100f8eea4829799c06cad08c6d3d2d2ec05e0574154e747ea0fdbb8042cb655aadd" /> + </sigs> + diff --git a/services/tests/servicestests/assets/PackageSignaturesTest/xml/three-signers-in-lineage-invalid-pastSigs-count.xml b/services/tests/servicestests/assets/PackageSignaturesTest/xml/three-signers-in-lineage-invalid-pastSigs-count.xml new file mode 100644 index 000000000000..2b2e383cd098 --- /dev/null +++ b/services/tests/servicestests/assets/PackageSignaturesTest/xml/three-signers-in-lineage-invalid-pastSigs-count.xml @@ -0,0 +1,9 @@ + <sigs count="1" schemeVersion="3"> + <cert index="0" key="3082016e30820115a0030201020209008394f5cad16a89a7300a06082a8648ce3d04030230143112301006035504030c0965632d703235365f32301e170d3138303731343030303532365a170d3238303731313030303532365a30143112301006035504030c0965632d703235365f333059301306072a8648ce3d020106082a8648ce3d03010703420004f31e62430e9db6fc5928d975fc4e47419bacfcb2e07c89299e6cd7e344dd21adfd308d58cb49a1a2a3fecacceea4862069f30be1643bcc255040d8089dfb3743a350304e301d0603551d0e041604146f8d0828b13efaf577fc86b0e99fa3e54bcbcff0301f0603551d230418301680147991d92b0208fc448bf506d4efc9fff428cb5e5f300c0603551d13040530030101ff300a06082a8648ce3d04030203470030440220256bdaa2784c273e4cc291a595a46779dee9de9044dc9f7ab820309567df9fe902201a4ad8c69891b5a8c47434fe9540ed1f4979b5fad3483f3fa04d5677355a579e" /> + <pastSigs count="x"> + <cert index="1" key="3082016c30820111a003020102020900ca0fb64dfb66e772300a06082a8648ce3d04030230123110300e06035504030c0765632d70323536301e170d3136303333313134353830365a170d3433303831373134353830365a30123110300e06035504030c0765632d703235363059301306072a8648ce3d020106082a8648ce3d03010703420004a65f113d22cb4913908307ac31ee2ba0e9138b785fac6536d14ea2ce90d2b4bfe194b50cdc8e169f54a73a991ef0fa76329825be078cc782740703da44b4d7eba350304e301d0603551d0e04160414d4133568b95b30158b322071ea8c43ff5b05ccc8301f0603551d23041830168014d4133568b95b30158b322071ea8c43ff5b05ccc8300c0603551d13040530030101ff300a06082a8648ce3d0403020349003046022100f504a0866caef029f417142c5cb71354c79ffcd1d640618dfca4f19e16db78d6022100f8eea4829799c06cad08c6d3d2d2ec05e0574154e747ea0fdbb8042cb655aadd" flags="3" /> + <cert index="2" key="3082016d30820113a0030201020209008855bd1dd2b2b225300a06082a8648ce3d04030230123110300e06035504030c0765632d70323536301e170d3138303731333137343135315a170d3238303731303137343135315a30143112301006035504030c0965632d703235365f323059301306072a8648ce3d020106082a8648ce3d030107034200041d4cca0472ad97ee3cecef0da93d62b450c6788333b36e7553cde9f74ab5df00bbba6ba950e68461d70bbc271b62151dad2de2bf6203cd2076801c7a9d4422e1a350304e301d0603551d0e041604147991d92b0208fc448bf506d4efc9fff428cb5e5f301f0603551d23041830168014d4133568b95b30158b322071ea8c43ff5b05ccc8300c0603551d13040530030101ff300a06082a8648ce3d040302034800304502202769abb1b49fc2f53479c4ae92a6631dabfd522c9acb0bba2b43ebeb99c63011022100d260fb1d1f176cf9b7fa60098bfd24319f4905a3e5fda100a6fe1a2ab19ff09e" flags="7" /> + <cert index="0" flags="23" /> + </pastSigs> + </sigs> + diff --git a/services/tests/servicestests/assets/PackageSignaturesTest/xml/three-signers-in-lineage-missing-pastSigs-cert-tag.xml b/services/tests/servicestests/assets/PackageSignaturesTest/xml/three-signers-in-lineage-missing-pastSigs-cert-tag.xml new file mode 100644 index 000000000000..f992104ba00d --- /dev/null +++ b/services/tests/servicestests/assets/PackageSignaturesTest/xml/three-signers-in-lineage-missing-pastSigs-cert-tag.xml @@ -0,0 +1,8 @@ + <sigs count="1" schemeVersion="3"> + <cert index="0" key="3082016e30820115a0030201020209008394f5cad16a89a7300a06082a8648ce3d04030230143112301006035504030c0965632d703235365f32301e170d3138303731343030303532365a170d3238303731313030303532365a30143112301006035504030c0965632d703235365f333059301306072a8648ce3d020106082a8648ce3d03010703420004f31e62430e9db6fc5928d975fc4e47419bacfcb2e07c89299e6cd7e344dd21adfd308d58cb49a1a2a3fecacceea4862069f30be1643bcc255040d8089dfb3743a350304e301d0603551d0e041604146f8d0828b13efaf577fc86b0e99fa3e54bcbcff0301f0603551d230418301680147991d92b0208fc448bf506d4efc9fff428cb5e5f300c0603551d13040530030101ff300a06082a8648ce3d04030203470030440220256bdaa2784c273e4cc291a595a46779dee9de9044dc9f7ab820309567df9fe902201a4ad8c69891b5a8c47434fe9540ed1f4979b5fad3483f3fa04d5677355a579e" /> + <pastSigs count="3"> + <cert index="1" key="3082016c30820111a003020102020900ca0fb64dfb66e772300a06082a8648ce3d04030230123110300e06035504030c0765632d70323536301e170d3136303333313134353830365a170d3433303831373134353830365a30123110300e06035504030c0765632d703235363059301306072a8648ce3d020106082a8648ce3d03010703420004a65f113d22cb4913908307ac31ee2ba0e9138b785fac6536d14ea2ce90d2b4bfe194b50cdc8e169f54a73a991ef0fa76329825be078cc782740703da44b4d7eba350304e301d0603551d0e04160414d4133568b95b30158b322071ea8c43ff5b05ccc8301f0603551d23041830168014d4133568b95b30158b322071ea8c43ff5b05ccc8300c0603551d13040530030101ff300a06082a8648ce3d0403020349003046022100f504a0866caef029f417142c5cb71354c79ffcd1d640618dfca4f19e16db78d6022100f8eea4829799c06cad08c6d3d2d2ec05e0574154e747ea0fdbb8042cb655aadd" flags="3" /> + <cert index="0" flags="23" /> + </pastSigs> + </sigs> + diff --git a/services/tests/servicestests/assets/PackageSignaturesTest/xml/three-signers-in-lineage-missing-pastSigs-count.xml b/services/tests/servicestests/assets/PackageSignaturesTest/xml/three-signers-in-lineage-missing-pastSigs-count.xml new file mode 100644 index 000000000000..6ef0fe5c38da --- /dev/null +++ b/services/tests/servicestests/assets/PackageSignaturesTest/xml/three-signers-in-lineage-missing-pastSigs-count.xml @@ -0,0 +1,9 @@ + <sigs count="1" schemeVersion="3"> + <cert index="0" key="3082016e30820115a0030201020209008394f5cad16a89a7300a06082a8648ce3d04030230143112301006035504030c0965632d703235365f32301e170d3138303731343030303532365a170d3238303731313030303532365a30143112301006035504030c0965632d703235365f333059301306072a8648ce3d020106082a8648ce3d03010703420004f31e62430e9db6fc5928d975fc4e47419bacfcb2e07c89299e6cd7e344dd21adfd308d58cb49a1a2a3fecacceea4862069f30be1643bcc255040d8089dfb3743a350304e301d0603551d0e041604146f8d0828b13efaf577fc86b0e99fa3e54bcbcff0301f0603551d230418301680147991d92b0208fc448bf506d4efc9fff428cb5e5f300c0603551d13040530030101ff300a06082a8648ce3d04030203470030440220256bdaa2784c273e4cc291a595a46779dee9de9044dc9f7ab820309567df9fe902201a4ad8c69891b5a8c47434fe9540ed1f4979b5fad3483f3fa04d5677355a579e" /> + <pastSigs> + <cert index="1" key="3082016c30820111a003020102020900ca0fb64dfb66e772300a06082a8648ce3d04030230123110300e06035504030c0765632d70323536301e170d3136303333313134353830365a170d3433303831373134353830365a30123110300e06035504030c0765632d703235363059301306072a8648ce3d020106082a8648ce3d03010703420004a65f113d22cb4913908307ac31ee2ba0e9138b785fac6536d14ea2ce90d2b4bfe194b50cdc8e169f54a73a991ef0fa76329825be078cc782740703da44b4d7eba350304e301d0603551d0e04160414d4133568b95b30158b322071ea8c43ff5b05ccc8301f0603551d23041830168014d4133568b95b30158b322071ea8c43ff5b05ccc8300c0603551d13040530030101ff300a06082a8648ce3d0403020349003046022100f504a0866caef029f417142c5cb71354c79ffcd1d640618dfca4f19e16db78d6022100f8eea4829799c06cad08c6d3d2d2ec05e0574154e747ea0fdbb8042cb655aadd" flags="3" /> + <cert index="2" key="3082016d30820113a0030201020209008855bd1dd2b2b225300a06082a8648ce3d04030230123110300e06035504030c0765632d70323536301e170d3138303731333137343135315a170d3238303731303137343135315a30143112301006035504030c0965632d703235365f323059301306072a8648ce3d020106082a8648ce3d030107034200041d4cca0472ad97ee3cecef0da93d62b450c6788333b36e7553cde9f74ab5df00bbba6ba950e68461d70bbc271b62151dad2de2bf6203cd2076801c7a9d4422e1a350304e301d0603551d0e041604147991d92b0208fc448bf506d4efc9fff428cb5e5f301f0603551d23041830168014d4133568b95b30158b322071ea8c43ff5b05ccc8300c0603551d13040530030101ff300a06082a8648ce3d040302034800304502202769abb1b49fc2f53479c4ae92a6631dabfd522c9acb0bba2b43ebeb99c63011022100d260fb1d1f176cf9b7fa60098bfd24319f4905a3e5fda100a6fe1a2ab19ff09e" flags="7" /> + <cert index="0" flags="23" /> + </pastSigs> + </sigs> + diff --git a/services/tests/servicestests/assets/PackageSignaturesTest/xml/three-signers-in-lineage-missing-scheme-version.xml b/services/tests/servicestests/assets/PackageSignaturesTest/xml/three-signers-in-lineage-missing-scheme-version.xml new file mode 100644 index 000000000000..d98573dbc08c --- /dev/null +++ b/services/tests/servicestests/assets/PackageSignaturesTest/xml/three-signers-in-lineage-missing-scheme-version.xml @@ -0,0 +1,9 @@ + <sigs count="1"> + <cert index="0" key="3082016e30820115a0030201020209008394f5cad16a89a7300a06082a8648ce3d04030230143112301006035504030c0965632d703235365f32301e170d3138303731343030303532365a170d3238303731313030303532365a30143112301006035504030c0965632d703235365f333059301306072a8648ce3d020106082a8648ce3d03010703420004f31e62430e9db6fc5928d975fc4e47419bacfcb2e07c89299e6cd7e344dd21adfd308d58cb49a1a2a3fecacceea4862069f30be1643bcc255040d8089dfb3743a350304e301d0603551d0e041604146f8d0828b13efaf577fc86b0e99fa3e54bcbcff0301f0603551d230418301680147991d92b0208fc448bf506d4efc9fff428cb5e5f300c0603551d13040530030101ff300a06082a8648ce3d04030203470030440220256bdaa2784c273e4cc291a595a46779dee9de9044dc9f7ab820309567df9fe902201a4ad8c69891b5a8c47434fe9540ed1f4979b5fad3483f3fa04d5677355a579e" /> + <pastSigs count="3"> + <cert index="1" key="3082016c30820111a003020102020900ca0fb64dfb66e772300a06082a8648ce3d04030230123110300e06035504030c0765632d70323536301e170d3136303333313134353830365a170d3433303831373134353830365a30123110300e06035504030c0765632d703235363059301306072a8648ce3d020106082a8648ce3d03010703420004a65f113d22cb4913908307ac31ee2ba0e9138b785fac6536d14ea2ce90d2b4bfe194b50cdc8e169f54a73a991ef0fa76329825be078cc782740703da44b4d7eba350304e301d0603551d0e04160414d4133568b95b30158b322071ea8c43ff5b05ccc8301f0603551d23041830168014d4133568b95b30158b322071ea8c43ff5b05ccc8300c0603551d13040530030101ff300a06082a8648ce3d0403020349003046022100f504a0866caef029f417142c5cb71354c79ffcd1d640618dfca4f19e16db78d6022100f8eea4829799c06cad08c6d3d2d2ec05e0574154e747ea0fdbb8042cb655aadd" flags="3" /> + <cert index="2" key="3082016d30820113a0030201020209008855bd1dd2b2b225300a06082a8648ce3d04030230123110300e06035504030c0765632d70323536301e170d3138303731333137343135315a170d3238303731303137343135315a30143112301006035504030c0965632d703235365f323059301306072a8648ce3d020106082a8648ce3d030107034200041d4cca0472ad97ee3cecef0da93d62b450c6788333b36e7553cde9f74ab5df00bbba6ba950e68461d70bbc271b62151dad2de2bf6203cd2076801c7a9d4422e1a350304e301d0603551d0e041604147991d92b0208fc448bf506d4efc9fff428cb5e5f301f0603551d23041830168014d4133568b95b30158b322071ea8c43ff5b05ccc8300c0603551d13040530030101ff300a06082a8648ce3d040302034800304502202769abb1b49fc2f53479c4ae92a6631dabfd522c9acb0bba2b43ebeb99c63011022100d260fb1d1f176cf9b7fa60098bfd24319f4905a3e5fda100a6fe1a2ab19ff09e" flags="7" /> + <cert index="0" flags="23" /> + </pastSigs> + </sigs> + diff --git a/services/tests/servicestests/assets/PackageSignaturesTest/xml/three-signers-in-lineage.xml b/services/tests/servicestests/assets/PackageSignaturesTest/xml/three-signers-in-lineage.xml new file mode 100644 index 000000000000..2ccf5060f9e2 --- /dev/null +++ b/services/tests/servicestests/assets/PackageSignaturesTest/xml/three-signers-in-lineage.xml @@ -0,0 +1,9 @@ + <sigs count="1" schemeVersion="3"> + <cert index="0" key="3082016e30820115a0030201020209008394f5cad16a89a7300a06082a8648ce3d04030230143112301006035504030c0965632d703235365f32301e170d3138303731343030303532365a170d3238303731313030303532365a30143112301006035504030c0965632d703235365f333059301306072a8648ce3d020106082a8648ce3d03010703420004f31e62430e9db6fc5928d975fc4e47419bacfcb2e07c89299e6cd7e344dd21adfd308d58cb49a1a2a3fecacceea4862069f30be1643bcc255040d8089dfb3743a350304e301d0603551d0e041604146f8d0828b13efaf577fc86b0e99fa3e54bcbcff0301f0603551d230418301680147991d92b0208fc448bf506d4efc9fff428cb5e5f300c0603551d13040530030101ff300a06082a8648ce3d04030203470030440220256bdaa2784c273e4cc291a595a46779dee9de9044dc9f7ab820309567df9fe902201a4ad8c69891b5a8c47434fe9540ed1f4979b5fad3483f3fa04d5677355a579e" /> + <pastSigs count="3"> + <cert index="1" key="3082016c30820111a003020102020900ca0fb64dfb66e772300a06082a8648ce3d04030230123110300e06035504030c0765632d70323536301e170d3136303333313134353830365a170d3433303831373134353830365a30123110300e06035504030c0765632d703235363059301306072a8648ce3d020106082a8648ce3d03010703420004a65f113d22cb4913908307ac31ee2ba0e9138b785fac6536d14ea2ce90d2b4bfe194b50cdc8e169f54a73a991ef0fa76329825be078cc782740703da44b4d7eba350304e301d0603551d0e04160414d4133568b95b30158b322071ea8c43ff5b05ccc8301f0603551d23041830168014d4133568b95b30158b322071ea8c43ff5b05ccc8300c0603551d13040530030101ff300a06082a8648ce3d0403020349003046022100f504a0866caef029f417142c5cb71354c79ffcd1d640618dfca4f19e16db78d6022100f8eea4829799c06cad08c6d3d2d2ec05e0574154e747ea0fdbb8042cb655aadd" flags="3" /> + <cert index="2" key="3082016d30820113a0030201020209008855bd1dd2b2b225300a06082a8648ce3d04030230123110300e06035504030c0765632d70323536301e170d3138303731333137343135315a170d3238303731303137343135315a30143112301006035504030c0965632d703235365f323059301306072a8648ce3d020106082a8648ce3d030107034200041d4cca0472ad97ee3cecef0da93d62b450c6788333b36e7553cde9f74ab5df00bbba6ba950e68461d70bbc271b62151dad2de2bf6203cd2076801c7a9d4422e1a350304e301d0603551d0e041604147991d92b0208fc448bf506d4efc9fff428cb5e5f301f0603551d23041830168014d4133568b95b30158b322071ea8c43ff5b05ccc8300c0603551d13040530030101ff300a06082a8648ce3d040302034800304502202769abb1b49fc2f53479c4ae92a6631dabfd522c9acb0bba2b43ebeb99c63011022100d260fb1d1f176cf9b7fa60098bfd24319f4905a3e5fda100a6fe1a2ab19ff09e" flags="7" /> + <cert index="0" flags="23" /> + </pastSigs> + </sigs> + diff --git a/services/tests/servicestests/assets/PackageSignaturesTest/xml/two-signers-in-lineage-invalid-certs-flags.xml b/services/tests/servicestests/assets/PackageSignaturesTest/xml/two-signers-in-lineage-invalid-certs-flags.xml new file mode 100644 index 000000000000..6d567e994d91 --- /dev/null +++ b/services/tests/servicestests/assets/PackageSignaturesTest/xml/two-signers-in-lineage-invalid-certs-flags.xml @@ -0,0 +1,8 @@ + <sigs count="1" schemeVersion="3"> + <cert index="0" key="3082016d30820113a0030201020209008855bd1dd2b2b225300a06082a8648ce3d04030230123110300e06035504030c0765632d70323536301e170d3138303731333137343135315a170d3238303731303137343135315a30143112301006035504030c0965632d703235365f323059301306072a8648ce3d020106082a8648ce3d030107034200041d4cca0472ad97ee3cecef0da93d62b450c6788333b36e7553cde9f74ab5df00bbba6ba950e68461d70bbc271b62151dad2de2bf6203cd2076801c7a9d4422e1a350304e301d0603551d0e041604147991d92b0208fc448bf506d4efc9fff428cb5e5f301f0603551d23041830168014d4133568b95b30158b322071ea8c43ff5b05ccc8300c0603551d13040530030101ff300a06082a8648ce3d040302034800304502202769abb1b49fc2f53479c4ae92a6631dabfd522c9acb0bba2b43ebeb99c63011022100d260fb1d1f176cf9b7fa60098bfd24319f4905a3e5fda100a6fe1a2ab19ff09e" /> + <pastSigs count="2"> + <cert index="1" key="3082016c30820111a003020102020900ca0fb64dfb66e772300a06082a8648ce3d04030230123110300e06035504030c0765632d70323536301e170d3136303333313134353830365a170d3433303831373134353830365a30123110300e06035504030c0765632d703235363059301306072a8648ce3d020106082a8648ce3d03010703420004a65f113d22cb4913908307ac31ee2ba0e9138b785fac6536d14ea2ce90d2b4bfe194b50cdc8e169f54a73a991ef0fa76329825be078cc782740703da44b4d7eba350304e301d0603551d0e04160414d4133568b95b30158b322071ea8c43ff5b05ccc8301f0603551d23041830168014d4133568b95b30158b322071ea8c43ff5b05ccc8300c0603551d13040530030101ff300a06082a8648ce3d0403020349003046022100f504a0866caef029f417142c5cb71354c79ffcd1d640618dfca4f19e16db78d6022100f8eea4829799c06cad08c6d3d2d2ec05e0574154e747ea0fdbb8042cb655aadd" flags="x" /> + <cert index="0" flags="23" /> + </pastSigs> + </sigs> + diff --git a/services/tests/servicestests/assets/PackageSignaturesTest/xml/two-signers-in-lineage-invalid-pastSigs-cert-index.xml b/services/tests/servicestests/assets/PackageSignaturesTest/xml/two-signers-in-lineage-invalid-pastSigs-cert-index.xml new file mode 100644 index 000000000000..a2146b7b63ec --- /dev/null +++ b/services/tests/servicestests/assets/PackageSignaturesTest/xml/two-signers-in-lineage-invalid-pastSigs-cert-index.xml @@ -0,0 +1,8 @@ + <sigs count="1" schemeVersion="3"> + <cert index="0" key="3082016d30820113a0030201020209008855bd1dd2b2b225300a06082a8648ce3d04030230123110300e06035504030c0765632d70323536301e170d3138303731333137343135315a170d3238303731303137343135315a30143112301006035504030c0965632d703235365f323059301306072a8648ce3d020106082a8648ce3d030107034200041d4cca0472ad97ee3cecef0da93d62b450c6788333b36e7553cde9f74ab5df00bbba6ba950e68461d70bbc271b62151dad2de2bf6203cd2076801c7a9d4422e1a350304e301d0603551d0e041604147991d92b0208fc448bf506d4efc9fff428cb5e5f301f0603551d23041830168014d4133568b95b30158b322071ea8c43ff5b05ccc8300c0603551d13040530030101ff300a06082a8648ce3d040302034800304502202769abb1b49fc2f53479c4ae92a6631dabfd522c9acb0bba2b43ebeb99c63011022100d260fb1d1f176cf9b7fa60098bfd24319f4905a3e5fda100a6fe1a2ab19ff09e" /> + <pastSigs count="2"> + <cert index="x" key="3082016c30820111a003020102020900ca0fb64dfb66e772300a06082a8648ce3d04030230123110300e06035504030c0765632d70323536301e170d3136303333313134353830365a170d3433303831373134353830365a30123110300e06035504030c0765632d703235363059301306072a8648ce3d020106082a8648ce3d03010703420004a65f113d22cb4913908307ac31ee2ba0e9138b785fac6536d14ea2ce90d2b4bfe194b50cdc8e169f54a73a991ef0fa76329825be078cc782740703da44b4d7eba350304e301d0603551d0e04160414d4133568b95b30158b322071ea8c43ff5b05ccc8301f0603551d23041830168014d4133568b95b30158b322071ea8c43ff5b05ccc8300c0603551d13040530030101ff300a06082a8648ce3d0403020349003046022100f504a0866caef029f417142c5cb71354c79ffcd1d640618dfca4f19e16db78d6022100f8eea4829799c06cad08c6d3d2d2ec05e0574154e747ea0fdbb8042cb655aadd" flags="7" /> + <cert index="0" flags="0" /> + </pastSigs> + </sigs> + diff --git a/services/tests/servicestests/assets/PackageSignaturesTest/xml/two-signers-in-lineage-missing-certs-flags.xml b/services/tests/servicestests/assets/PackageSignaturesTest/xml/two-signers-in-lineage-missing-certs-flags.xml new file mode 100644 index 000000000000..90a4a8472e40 --- /dev/null +++ b/services/tests/servicestests/assets/PackageSignaturesTest/xml/two-signers-in-lineage-missing-certs-flags.xml @@ -0,0 +1,8 @@ + <sigs count="1" schemeVersion="3"> + <cert index="0" key="3082016d30820113a0030201020209008855bd1dd2b2b225300a06082a8648ce3d04030230123110300e06035504030c0765632d70323536301e170d3138303731333137343135315a170d3238303731303137343135315a30143112301006035504030c0965632d703235365f323059301306072a8648ce3d020106082a8648ce3d030107034200041d4cca0472ad97ee3cecef0da93d62b450c6788333b36e7553cde9f74ab5df00bbba6ba950e68461d70bbc271b62151dad2de2bf6203cd2076801c7a9d4422e1a350304e301d0603551d0e041604147991d92b0208fc448bf506d4efc9fff428cb5e5f301f0603551d23041830168014d4133568b95b30158b322071ea8c43ff5b05ccc8300c0603551d13040530030101ff300a06082a8648ce3d040302034800304502202769abb1b49fc2f53479c4ae92a6631dabfd522c9acb0bba2b43ebeb99c63011022100d260fb1d1f176cf9b7fa60098bfd24319f4905a3e5fda100a6fe1a2ab19ff09e" /> + <pastSigs count="2"> + <cert index="1" key="3082016c30820111a003020102020900ca0fb64dfb66e772300a06082a8648ce3d04030230123110300e06035504030c0765632d70323536301e170d3136303333313134353830365a170d3433303831373134353830365a30123110300e06035504030c0765632d703235363059301306072a8648ce3d020106082a8648ce3d03010703420004a65f113d22cb4913908307ac31ee2ba0e9138b785fac6536d14ea2ce90d2b4bfe194b50cdc8e169f54a73a991ef0fa76329825be078cc782740703da44b4d7eba350304e301d0603551d0e04160414d4133568b95b30158b322071ea8c43ff5b05ccc8301f0603551d23041830168014d4133568b95b30158b322071ea8c43ff5b05ccc8300c0603551d13040530030101ff300a06082a8648ce3d0403020349003046022100f504a0866caef029f417142c5cb71354c79ffcd1d640618dfca4f19e16db78d6022100f8eea4829799c06cad08c6d3d2d2ec05e0574154e747ea0fdbb8042cb655aadd" /> + <cert index="0" flags="23" /> + </pastSigs> + </sigs> + diff --git a/services/tests/servicestests/assets/PackageSignaturesTest/xml/two-signers-in-lineage-missing-pastSigs-cert-index.xml b/services/tests/servicestests/assets/PackageSignaturesTest/xml/two-signers-in-lineage-missing-pastSigs-cert-index.xml new file mode 100644 index 000000000000..6525e48061ea --- /dev/null +++ b/services/tests/servicestests/assets/PackageSignaturesTest/xml/two-signers-in-lineage-missing-pastSigs-cert-index.xml @@ -0,0 +1,8 @@ + <sigs count="1" schemeVersion="3"> + <cert index="0" key="3082016d30820113a0030201020209008855bd1dd2b2b225300a06082a8648ce3d04030230123110300e06035504030c0765632d70323536301e170d3138303731333137343135315a170d3238303731303137343135315a30143112301006035504030c0965632d703235365f323059301306072a8648ce3d020106082a8648ce3d030107034200041d4cca0472ad97ee3cecef0da93d62b450c6788333b36e7553cde9f74ab5df00bbba6ba950e68461d70bbc271b62151dad2de2bf6203cd2076801c7a9d4422e1a350304e301d0603551d0e041604147991d92b0208fc448bf506d4efc9fff428cb5e5f301f0603551d23041830168014d4133568b95b30158b322071ea8c43ff5b05ccc8300c0603551d13040530030101ff300a06082a8648ce3d040302034800304502202769abb1b49fc2f53479c4ae92a6631dabfd522c9acb0bba2b43ebeb99c63011022100d260fb1d1f176cf9b7fa60098bfd24319f4905a3e5fda100a6fe1a2ab19ff09e" /> + <pastSigs count="2"> + <cert key="3082016c30820111a003020102020900ca0fb64dfb66e772300a06082a8648ce3d04030230123110300e06035504030c0765632d70323536301e170d3136303333313134353830365a170d3433303831373134353830365a30123110300e06035504030c0765632d703235363059301306072a8648ce3d020106082a8648ce3d03010703420004a65f113d22cb4913908307ac31ee2ba0e9138b785fac6536d14ea2ce90d2b4bfe194b50cdc8e169f54a73a991ef0fa76329825be078cc782740703da44b4d7eba350304e301d0603551d0e04160414d4133568b95b30158b322071ea8c43ff5b05ccc8301f0603551d23041830168014d4133568b95b30158b322071ea8c43ff5b05ccc8300c0603551d13040530030101ff300a06082a8648ce3d0403020349003046022100f504a0866caef029f417142c5cb71354c79ffcd1d640618dfca4f19e16db78d6022100f8eea4829799c06cad08c6d3d2d2ec05e0574154e747ea0fdbb8042cb655aadd" flags="7" /> + <cert flags="0" /> + </pastSigs> + </sigs> + diff --git a/services/tests/servicestests/assets/PackageSignaturesTest/xml/two-signers-in-lineage-multiple-pastSigs-tags.xml b/services/tests/servicestests/assets/PackageSignaturesTest/xml/two-signers-in-lineage-multiple-pastSigs-tags.xml new file mode 100644 index 000000000000..e06892c6f7da --- /dev/null +++ b/services/tests/servicestests/assets/PackageSignaturesTest/xml/two-signers-in-lineage-multiple-pastSigs-tags.xml @@ -0,0 +1,12 @@ + <sigs count="1" schemeVersion="3"> + <cert index="0" key="3082016d30820113a0030201020209008855bd1dd2b2b225300a06082a8648ce3d04030230123110300e06035504030c0765632d70323536301e170d3138303731333137343135315a170d3238303731303137343135315a30143112301006035504030c0965632d703235365f323059301306072a8648ce3d020106082a8648ce3d030107034200041d4cca0472ad97ee3cecef0da93d62b450c6788333b36e7553cde9f74ab5df00bbba6ba950e68461d70bbc271b62151dad2de2bf6203cd2076801c7a9d4422e1a350304e301d0603551d0e041604147991d92b0208fc448bf506d4efc9fff428cb5e5f301f0603551d23041830168014d4133568b95b30158b322071ea8c43ff5b05ccc8300c0603551d13040530030101ff300a06082a8648ce3d040302034800304502202769abb1b49fc2f53479c4ae92a6631dabfd522c9acb0bba2b43ebeb99c63011022100d260fb1d1f176cf9b7fa60098bfd24319f4905a3e5fda100a6fe1a2ab19ff09e" /> + <pastSigs count="2"> + <cert index="1" key="3082016c30820111a003020102020900ca0fb64dfb66e772300a06082a8648ce3d04030230123110300e06035504030c0765632d70323536301e170d3136303333313134353830365a170d3433303831373134353830365a30123110300e06035504030c0765632d703235363059301306072a8648ce3d020106082a8648ce3d03010703420004a65f113d22cb4913908307ac31ee2ba0e9138b785fac6536d14ea2ce90d2b4bfe194b50cdc8e169f54a73a991ef0fa76329825be078cc782740703da44b4d7eba350304e301d0603551d0e04160414d4133568b95b30158b322071ea8c43ff5b05ccc8301f0603551d23041830168014d4133568b95b30158b322071ea8c43ff5b05ccc8300c0603551d13040530030101ff300a06082a8648ce3d0403020349003046022100f504a0866caef029f417142c5cb71354c79ffcd1d640618dfca4f19e16db78d6022100f8eea4829799c06cad08c6d3d2d2ec05e0574154e747ea0fdbb8042cb655aadd" flags="23" /> + <cert index="0" flags="23" /> + <pastSigs count="2"> + <cert index="1" key="3082016c30820111a003020102020900ca0fb64dfb66e772300a06082a8648ce3d04030230123110300e06035504030c0765632d70323536301e170d3136303333313134353830365a170d3433303831373134353830365a30123110300e06035504030c0765632d703235363059301306072a8648ce3d020106082a8648ce3d03010703420004a65f113d22cb4913908307ac31ee2ba0e9138b785fac6536d14ea2ce90d2b4bfe194b50cdc8e169f54a73a991ef0fa76329825be078cc782740703da44b4d7eba350304e301d0603551d0e04160414d4133568b95b30158b322071ea8c43ff5b05ccc8301f0603551d23041830168014d4133568b95b30158b322071ea8c43ff5b05ccc8300c0603551d13040530030101ff300a06082a8648ce3d0403020349003046022100f504a0866caef029f417142c5cb71354c79ffcd1d640618dfca4f19e16db78d6022100f8eea4829799c06cad08c6d3d2d2ec05e0574154e747ea0fdbb8042cb655aadd" flags="23" /> + <cert index="0" flags="23" /> + </pastSigs> + </pastSigs> + </sigs> + diff --git a/services/tests/servicestests/assets/PackageSignaturesTest/xml/two-signers-in-lineage-no-caps.xml b/services/tests/servicestests/assets/PackageSignaturesTest/xml/two-signers-in-lineage-no-caps.xml new file mode 100644 index 000000000000..8081d2e6d78d --- /dev/null +++ b/services/tests/servicestests/assets/PackageSignaturesTest/xml/two-signers-in-lineage-no-caps.xml @@ -0,0 +1,8 @@ + <sigs count="1" schemeVersion="3"> + <cert index="0" key="3082016d30820113a0030201020209008855bd1dd2b2b225300a06082a8648ce3d04030230123110300e06035504030c0765632d70323536301e170d3138303731333137343135315a170d3238303731303137343135315a30143112301006035504030c0965632d703235365f323059301306072a8648ce3d020106082a8648ce3d030107034200041d4cca0472ad97ee3cecef0da93d62b450c6788333b36e7553cde9f74ab5df00bbba6ba950e68461d70bbc271b62151dad2de2bf6203cd2076801c7a9d4422e1a350304e301d0603551d0e041604147991d92b0208fc448bf506d4efc9fff428cb5e5f301f0603551d23041830168014d4133568b95b30158b322071ea8c43ff5b05ccc8300c0603551d13040530030101ff300a06082a8648ce3d040302034800304502202769abb1b49fc2f53479c4ae92a6631dabfd522c9acb0bba2b43ebeb99c63011022100d260fb1d1f176cf9b7fa60098bfd24319f4905a3e5fda100a6fe1a2ab19ff09e" /> + <pastSigs count="2"> + <cert index="1" key="3082016c30820111a003020102020900ca0fb64dfb66e772300a06082a8648ce3d04030230123110300e06035504030c0765632d70323536301e170d3136303333313134353830365a170d3433303831373134353830365a30123110300e06035504030c0765632d703235363059301306072a8648ce3d020106082a8648ce3d03010703420004a65f113d22cb4913908307ac31ee2ba0e9138b785fac6536d14ea2ce90d2b4bfe194b50cdc8e169f54a73a991ef0fa76329825be078cc782740703da44b4d7eba350304e301d0603551d0e04160414d4133568b95b30158b322071ea8c43ff5b05ccc8301f0603551d23041830168014d4133568b95b30158b322071ea8c43ff5b05ccc8300c0603551d13040530030101ff300a06082a8648ce3d0403020349003046022100f504a0866caef029f417142c5cb71354c79ffcd1d640618dfca4f19e16db78d6022100f8eea4829799c06cad08c6d3d2d2ec05e0574154e747ea0fdbb8042cb655aadd" flags="0" /> + <cert index="0" flags="7" /> + </pastSigs> + </sigs> + diff --git a/services/tests/servicestests/assets/PackageSignaturesTest/xml/two-signers-in-lineage-undefined-pastSigs-index.xml b/services/tests/servicestests/assets/PackageSignaturesTest/xml/two-signers-in-lineage-undefined-pastSigs-index.xml new file mode 100644 index 000000000000..127000a160da --- /dev/null +++ b/services/tests/servicestests/assets/PackageSignaturesTest/xml/two-signers-in-lineage-undefined-pastSigs-index.xml @@ -0,0 +1,8 @@ + <sigs count="2" schemeVersion="3"> + <cert index="1" key="3082016c30820111a003020102020900ca0fb64dfb66e772300a06082a8648ce3d04030230123110300e06035504030c0765632d70323536301e170d3136303333313134353830365a170d3433303831373134353830365a30123110300e06035504030c0765632d703235363059301306072a8648ce3d020106082a8648ce3d03010703420004a65f113d22cb4913908307ac31ee2ba0e9138b785fac6536d14ea2ce90d2b4bfe194b50cdc8e169f54a73a991ef0fa76329825be078cc782740703da44b4d7eba350304e301d0603551d0e04160414d4133568b95b30158b322071ea8c43ff5b05ccc8301f0603551d23041830168014d4133568b95b30158b322071ea8c43ff5b05ccc8300c0603551d13040530030101ff300a06082a8648ce3d0403020349003046022100f504a0866caef029f417142c5cb71354c79ffcd1d640618dfca4f19e16db78d6022100f8eea4829799c06cad08c6d3d2d2ec05e0574154e747ea0fdbb8042cb655aadd" /> + <pastSigs count="2"> + <cert index="1" flags="23" /> + <cert index="0" flags="23" /> + </pastSigs> + </sigs> + diff --git a/services/tests/servicestests/assets/PackageSignaturesTest/xml/two-signers-in-lineage.xml b/services/tests/servicestests/assets/PackageSignaturesTest/xml/two-signers-in-lineage.xml new file mode 100644 index 000000000000..6097ea6d7bd0 --- /dev/null +++ b/services/tests/servicestests/assets/PackageSignaturesTest/xml/two-signers-in-lineage.xml @@ -0,0 +1,8 @@ + <sigs count="1" schemeVersion="3"> + <cert index="0" key="3082016d30820113a0030201020209008855bd1dd2b2b225300a06082a8648ce3d04030230123110300e06035504030c0765632d70323536301e170d3138303731333137343135315a170d3238303731303137343135315a30143112301006035504030c0965632d703235365f323059301306072a8648ce3d020106082a8648ce3d030107034200041d4cca0472ad97ee3cecef0da93d62b450c6788333b36e7553cde9f74ab5df00bbba6ba950e68461d70bbc271b62151dad2de2bf6203cd2076801c7a9d4422e1a350304e301d0603551d0e041604147991d92b0208fc448bf506d4efc9fff428cb5e5f301f0603551d23041830168014d4133568b95b30158b322071ea8c43ff5b05ccc8300c0603551d13040530030101ff300a06082a8648ce3d040302034800304502202769abb1b49fc2f53479c4ae92a6631dabfd522c9acb0bba2b43ebeb99c63011022100d260fb1d1f176cf9b7fa60098bfd24319f4905a3e5fda100a6fe1a2ab19ff09e" /> + <pastSigs count="2"> + <cert index="1" key="3082016c30820111a003020102020900ca0fb64dfb66e772300a06082a8648ce3d04030230123110300e06035504030c0765632d70323536301e170d3136303333313134353830365a170d3433303831373134353830365a30123110300e06035504030c0765632d703235363059301306072a8648ce3d020106082a8648ce3d03010703420004a65f113d22cb4913908307ac31ee2ba0e9138b785fac6536d14ea2ce90d2b4bfe194b50cdc8e169f54a73a991ef0fa76329825be078cc782740703da44b4d7eba350304e301d0603551d0e04160414d4133568b95b30158b322071ea8c43ff5b05ccc8301f0603551d23041830168014d4133568b95b30158b322071ea8c43ff5b05ccc8300c0603551d13040530030101ff300a06082a8648ce3d0403020349003046022100f504a0866caef029f417142c5cb71354c79ffcd1d640618dfca4f19e16db78d6022100f8eea4829799c06cad08c6d3d2d2ec05e0574154e747ea0fdbb8042cb655aadd" flags="7" /> + <cert index="0" flags="3" /> + </pastSigs> + </sigs> + diff --git a/services/tests/servicestests/assets/PackageSignaturesTest/xml/two-signers-v1v2-missing-cert-tag.xml b/services/tests/servicestests/assets/PackageSignaturesTest/xml/two-signers-v1v2-missing-cert-tag.xml new file mode 100644 index 000000000000..6ed3be8615cb --- /dev/null +++ b/services/tests/servicestests/assets/PackageSignaturesTest/xml/two-signers-v1v2-missing-cert-tag.xml @@ -0,0 +1,4 @@ + <sigs count="2" schemeVersion="1"> + <cert index="0" key="3082016c30820111a003020102020900ca0fb64dfb66e772300a06082a8648ce3d04030230123110300e06035504030c0765632d70323536301e170d3136303333313134353830365a170d3433303831373134353830365a30123110300e06035504030c0765632d703235363059301306072a8648ce3d020106082a8648ce3d03010703420004a65f113d22cb4913908307ac31ee2ba0e9138b785fac6536d14ea2ce90d2b4bfe194b50cdc8e169f54a73a991ef0fa76329825be078cc782740703da44b4d7eba350304e301d0603551d0e04160414d4133568b95b30158b322071ea8c43ff5b05ccc8301f0603551d23041830168014d4133568b95b30158b322071ea8c43ff5b05ccc8300c0603551d13040530030101ff300a06082a8648ce3d0403020349003046022100f504a0866caef029f417142c5cb71354c79ffcd1d640618dfca4f19e16db78d6022100f8eea4829799c06cad08c6d3d2d2ec05e0574154e747ea0fdbb8042cb655aadd" /> + </sigs> + diff --git a/services/tests/servicestests/assets/PackageSignaturesTest/xml/two-signers-v1v2.xml b/services/tests/servicestests/assets/PackageSignaturesTest/xml/two-signers-v1v2.xml new file mode 100644 index 000000000000..ee4c4ebde3a9 --- /dev/null +++ b/services/tests/servicestests/assets/PackageSignaturesTest/xml/two-signers-v1v2.xml @@ -0,0 +1,5 @@ + <sigs count="2" schemeVersion="2"> + <cert index="0" key="3082016c30820111a003020102020900ca0fb64dfb66e772300a06082a8648ce3d04030230123110300e06035504030c0765632d70323536301e170d3136303333313134353830365a170d3433303831373134353830365a30123110300e06035504030c0765632d703235363059301306072a8648ce3d020106082a8648ce3d03010703420004a65f113d22cb4913908307ac31ee2ba0e9138b785fac6536d14ea2ce90d2b4bfe194b50cdc8e169f54a73a991ef0fa76329825be078cc782740703da44b4d7eba350304e301d0603551d0e04160414d4133568b95b30158b322071ea8c43ff5b05ccc8301f0603551d23041830168014d4133568b95b30158b322071ea8c43ff5b05ccc8300c0603551d13040530030101ff300a06082a8648ce3d0403020349003046022100f504a0866caef029f417142c5cb71354c79ffcd1d640618dfca4f19e16db78d6022100f8eea4829799c06cad08c6d3d2d2ec05e0574154e747ea0fdbb8042cb655aadd" /> + <cert index="1" key="3082016d30820113a0030201020209008855bd1dd2b2b225300a06082a8648ce3d04030230123110300e06035504030c0765632d70323536301e170d3138303731333137343135315a170d3238303731303137343135315a30143112301006035504030c0965632d703235365f323059301306072a8648ce3d020106082a8648ce3d030107034200041d4cca0472ad97ee3cecef0da93d62b450c6788333b36e7553cde9f74ab5df00bbba6ba950e68461d70bbc271b62151dad2de2bf6203cd2076801c7a9d4422e1a350304e301d0603551d0e041604147991d92b0208fc448bf506d4efc9fff428cb5e5f301f0603551d23041830168014d4133568b95b30158b322071ea8c43ff5b05ccc8300c0603551d13040530030101ff300a06082a8648ce3d040302034800304502202769abb1b49fc2f53479c4ae92a6631dabfd522c9acb0bba2b43ebeb99c63011022100d260fb1d1f176cf9b7fa60098bfd24319f4905a3e5fda100a6fe1a2ab19ff09e" /> + </sigs> + diff --git a/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java b/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java index 2ec6830b7755..4fbc587c8a87 100644 --- a/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java @@ -73,6 +73,9 @@ public class GestureLauncherServiceTest { private static final int IGNORED_ACTION = 13; private static final int IGNORED_CODE = 1999; private static final int IGNORED_REPEAT = 42; + private static final int IGNORED_META_STATE = 0; + private static final int IGNORED_DEVICE_ID = 0; + private static final int IGNORED_SCANCODE = 0; private @Mock Context mContext; private @Mock Resources mResources; @@ -369,6 +372,50 @@ public class GestureLauncherServiceTest { } @Test + public void testInterceptPowerKeyDown_longpress() { + withCameraDoubleTapPowerEnableConfigValue(true); + withCameraDoubleTapPowerDisableSettingValue(0); + mGestureLauncherService.updateCameraDoubleTapPowerEnabled(); + withUserSetupCompleteValue(true); + + long eventTime = INITIAL_EVENT_TIME_MILLIS; + KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE, + IGNORED_REPEAT); + boolean interactive = true; + MutableBoolean outLaunched = new MutableBoolean(true); + boolean intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive, + outLaunched); + assertFalse(intercepted); + assertFalse(outLaunched.value); + + final long interval = GestureLauncherService.CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS - 1; + eventTime += interval; + keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE, + IGNORED_REPEAT, IGNORED_META_STATE, IGNORED_DEVICE_ID, IGNORED_SCANCODE, + KeyEvent.FLAG_LONG_PRESS); + outLaunched.value = false; + intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive, + outLaunched); + assertFalse(intercepted); + assertFalse(outLaunched.value); + + verify(mMetricsLogger, never()) + .action(eq(MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE), anyInt()); + + final ArgumentCaptor<Integer> intervalCaptor = ArgumentCaptor.forClass(Integer.class); + verify(mMetricsLogger, times(1)).histogram( + eq("power_double_tap_interval"), intervalCaptor.capture()); + List<Integer> intervals = intervalCaptor.getAllValues(); + assertEquals((int) INITIAL_EVENT_TIME_MILLIS, intervals.get(0).intValue()); + + final ArgumentCaptor<Integer> tapCountCaptor = ArgumentCaptor.forClass(Integer.class); + verify(mMetricsLogger, times(1)).histogram( + eq("power_consecutive_short_tap_count"), tapCountCaptor.capture()); + List<Integer> tapCounts = tapCountCaptor.getAllValues(); + assertEquals(1, tapCounts.get(0).intValue()); + } + + @Test public void testInterceptPowerKeyDown_intervalInBoundsCameraPowerGestureOnInteractiveSetupIncomplete() { withCameraDoubleTapPowerEnableConfigValue(true); diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityDisplayTests.java b/services/tests/servicestests/src/com/android/server/am/ActivityDisplayTests.java new file mode 100644 index 000000000000..44981b3a90cf --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/am/ActivityDisplayTests.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.am; + +import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; +import static android.content.pm.ActivityInfo.FLAG_ALWAYS_FOCUSABLE; + +import static com.android.server.am.ActivityStackSupervisor.ON_TOP; + +import static org.junit.Assert.assertTrue; + +import android.platform.test.annotations.Presubmit; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Tests for the {@link ActivityDisplay} class. + * + * Build/Install/Run: + * atest WmTests:ActivityDisplayTests + */ +@SmallTest +@Presubmit +@RunWith(AndroidJUnit4.class) +public class ActivityDisplayTests extends ActivityTestsBase { + + @Before + @Override + public void setUp() throws Exception { + super.setUp(); + setupActivityTaskManagerService(); + } + + /** + * This test simulates the picture-in-picture menu activity launches an activity to fullscreen + * stack. The fullscreen stack should be the top focused for resuming correctly. + */ + @Test + public void testFullscreenStackCanBeFocusedWhenFocusablePinnedStackExists() { + // Create a pinned stack and move to front. + final ActivityStack pinnedStack = mSupervisor.getDefaultDisplay().createStack( + WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, ON_TOP); + final TaskRecord pinnedTask = new TaskBuilder(mService.mStackSupervisor) + .setStack(pinnedStack).build(); + new ActivityBuilder(mService).setActivityFlags(FLAG_ALWAYS_FOCUSABLE) + .setTask(pinnedTask).build(); + pinnedStack.moveToFront("movePinnedStackToFront"); + + // The focused stack should be the pinned stack. + assertTrue(pinnedStack.isFocusedStackOnDisplay()); + + // Create a fullscreen stack and move to front. + final ActivityStack fullscreenStack = createFullscreenStackWithSimpleActivityAt( + mSupervisor.getDefaultDisplay()); + fullscreenStack.moveToFront("moveFullscreenStackToFront"); + + // The focused stack should be the fullscreen stack. + assertTrue(fullscreenStack.isFocusedStackOnDisplay()); + } + + /** + * Test {@link ActivityDisplay#mPreferredTopFocusableStack} will be cleared when the stack is + * removed or moved to back, and the focused stack will be according to z-order. + */ + @Test + public void testStackShouldNotBeFocusedAfterMovingToBackOrRemoving() { + // Create a display which only contains 2 stacks. + final ActivityDisplay display = addNewActivityDisplayAt(ActivityDisplay.POSITION_TOP); + final ActivityStack stack1 = createFullscreenStackWithSimpleActivityAt(display); + final ActivityStack stack2 = createFullscreenStackWithSimpleActivityAt(display); + + // Put stack1 and stack2 on top. + stack1.moveToFront("moveStack1ToFront"); + stack2.moveToFront("moveStack2ToFront"); + assertTrue(stack2.isFocusedStackOnDisplay()); + + // Stack1 should be focused after moving stack2 to back. + stack2.moveToBack("moveStack2ToBack", null /* task */); + assertTrue(stack1.isFocusedStackOnDisplay()); + + // Stack2 should be focused after removing stack1. + display.removeChild(stack1); + assertTrue(stack2.isFocusedStackOnDisplay()); + } + + private ActivityStack createFullscreenStackWithSimpleActivityAt(ActivityDisplay display) { + final ActivityStack fullscreenStack = display.createStack( + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, ON_TOP); + final TaskRecord fullscreenTask = new TaskBuilder(mService.mStackSupervisor) + .setStack(fullscreenStack).build(); + new ActivityBuilder(mService).setTask(fullscreenTask).build(); + return fullscreenStack; + } +} diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityStackSupervisorTests.java b/services/tests/servicestests/src/com/android/server/am/ActivityStackSupervisorTests.java index 0345a81b5bec..81a0934a3460 100644 --- a/services/tests/servicestests/src/com/android/server/am/ActivityStackSupervisorTests.java +++ b/services/tests/servicestests/src/com/android/server/am/ActivityStackSupervisorTests.java @@ -33,10 +33,12 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; -import static org.mockito.Matchers.anyInt; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -232,6 +234,7 @@ public class ActivityStackSupervisorTests extends ActivityTestsBase { doReturn(displaySleeping).when(display).isSleeping(); doReturn(keyguardShowing).when(keyguard).isKeyguardOrAodShowing(anyInt()); + doReturn(isFocusedStack).when(stack).isFocusedStackOnDisplay(); doReturn(isFocusedStack ? stack : null).when(display).getFocusedStack(); mSupervisor.applySleepTokensLocked(true); verify(stack, times(expectWakeFromSleep ? 1 : 0)).awakeFromSleepingLocked(); @@ -402,4 +405,32 @@ public class ActivityStackSupervisorTests extends ActivityTestsBase { assertEquals(primaryStack.getBounds(), STACK_SIZE); assertEquals(task.getBounds(), TASK_SIZE); } + + /** + * Verify if a stack is not at the topmost position, it should be able to resume its activity if + * the stack is the top focused. + */ + @Test + public void testResumeActivityWhenNonTopmostStackIsTopFocused() throws Exception { + // Create a stack at bottom. + final ActivityDisplay display = mSupervisor.getDefaultDisplay(); + final ActivityStack targetStack = spy(display.createStack(WINDOWING_MODE_FULLSCREEN, + ACTIVITY_TYPE_STANDARD, false /* onTop */)); + final TaskRecord task = new TaskBuilder(mSupervisor).setStack(targetStack).build(); + final ActivityRecord activity = new ActivityBuilder(mService).setTask(task).build(); + display.positionChildAtBottom(targetStack); + + // Assume the stack is not at the topmost position (e.g. behind always-on-top stacks) but it + // is the current top focused stack. + assertFalse(targetStack.isTopStackOnDisplay()); + doReturn(targetStack).when(mSupervisor).getTopDisplayFocusedStack(); + + // Use the stack as target to resume. + mSupervisor.resumeFocusedStacksTopActivitiesLocked( + targetStack, activity, null /* targetOptions */); + + // Verify the target stack should resume its activity. + verify(targetStack, times(1)).resumeTopActivityUncheckedLocked( + eq(activity), eq(null /* targetOptions */)); + } } 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 ab814ee15df0..5fcd2aa35e05 100644 --- a/services/tests/servicestests/src/com/android/server/am/ActivityStackTests.java +++ b/services/tests/servicestests/src/com/android/server/am/ActivityStackTests.java @@ -38,6 +38,7 @@ import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; import android.content.pm.ActivityInfo; import android.os.UserHandle; @@ -71,8 +72,8 @@ public class ActivityStackTests extends ActivityTestsBase { setupActivityTaskManagerService(); mDefaultDisplay = mSupervisor.getDefaultDisplay(); - mStack = mDefaultDisplay.createStack(WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD, - true /* onTop */); + mStack = spy(mDefaultDisplay.createStack(WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD, + true /* onTop */)); mTask = new TaskBuilder(mSupervisor).setStack(mStack).build(); } @@ -720,7 +721,7 @@ public class ActivityStackTests extends ActivityTestsBase { doReturn(display).when(mSupervisor).getActivityDisplay(anyInt()); doReturn(keyguardGoingAway).when(keyguardController).isKeyguardGoingAway(); doReturn(displaySleeping).when(display).isSleeping(); - doReturn(focusedStack ? mStack : null).when(mSupervisor).getTopDisplayFocusedStack(); + doReturn(focusedStack).when(mStack).isFocusedStackOnDisplay(); assertEquals(expected, mStack.shouldSleepActivities()); } diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java b/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java index 22add018f570..2008861907a6 100644 --- a/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java +++ b/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java @@ -168,6 +168,9 @@ public class ActivityTestsBase { // Makes sure the supervisor is using with the spy object. atm.mStackSupervisor.setService(atm); doReturn(mock(IPackageManager.class)).when(am).getPackageManager(); + PackageManagerInternal mockPackageManager = mock(PackageManagerInternal.class); + doReturn(mockPackageManager).when(am).getPackageManagerInternalLocked(); + doReturn(null).when(mockPackageManager).getDefaultHomeActivity(anyInt()); doNothing().when(am).grantEphemeralAccessLocked(anyInt(), any(), anyInt(), anyInt()); am.mWindowManager = prepareMockWindowManager(); atm.setWindowManager(am.mWindowManager); @@ -175,10 +178,9 @@ public class ActivityTestsBase { // Put a home stack on the default display, so that we'll always have something focusable. final TestActivityStackSupervisor supervisor = (TestActivityStackSupervisor) atm.mStackSupervisor; - supervisor.mHomeStack = supervisor.mDisplay.createStack(WINDOWING_MODE_FULLSCREEN, - ACTIVITY_TYPE_HOME, ON_TOP); + supervisor.mDisplay.createStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, ON_TOP); final TaskRecord task = new TaskBuilder(atm.mStackSupervisor) - .setStack(supervisor.mHomeStack).build(); + .setStack(supervisor.getDefaultDisplay().getHomeStack()).build(); new ActivityBuilder(atm).setTask(task).build(); } @@ -447,9 +449,6 @@ public class ActivityTestsBase { final ActivityStackSupervisor supervisor = spy(createTestSupervisor()); final KeyguardController keyguardController = mock(KeyguardController.class); - // No home stack is set. - doNothing().when(supervisor).moveHomeStackToFront(any()); - doReturn(true).when(supervisor).moveHomeStackTaskToTop(any()); // Invoked during {@link ActivityStack} creation. doNothing().when(supervisor).updateUIDsPresentOnDisplay(); // Always keep things awake. diff --git a/services/tests/servicestests/src/com/android/server/am/MemoryStatUtilTest.java b/services/tests/servicestests/src/com/android/server/am/MemoryStatUtilTest.java index e8a824a12300..9a283febe906 100644 --- a/services/tests/servicestests/src/com/android/server/am/MemoryStatUtilTest.java +++ b/services/tests/servicestests/src/com/android/server/am/MemoryStatUtilTest.java @@ -18,6 +18,7 @@ package com.android.server.am; import static com.android.server.am.MemoryStatUtil.BYTES_IN_KILOBYTE; import static com.android.server.am.MemoryStatUtil.MemoryStat; +import static com.android.server.am.MemoryStatUtil.PAGE_SIZE; import static com.android.server.am.MemoryStatUtil.parseMemoryMaxUsageFromMemCg; import static com.android.server.am.MemoryStatUtil.parseMemoryStatFromMemcg; import static com.android.server.am.MemoryStatUtil.parseMemoryStatFromProcfs; @@ -32,6 +33,8 @@ import androidx.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; +import java.util.Collections; + @RunWith(AndroidJUnit4.class) @SmallTest public class MemoryStatUtilTest { @@ -95,7 +98,7 @@ public class MemoryStatUtilTest { "0", "2206", "1257177088", - "3", // this is rss in bytes + "3", // this is RSS (number of pages) "4294967295", "2936971264", "2936991289", @@ -173,7 +176,7 @@ public class MemoryStatUtilTest { + "nonvoluntary_ctxt_switches:\t104\n"; @Test - public void testParseMemoryStatFromMemcg_parsesCorrectValues() throws Exception { + public void testParseMemoryStatFromMemcg_parsesCorrectValues() { MemoryStat stat = parseMemoryStatFromMemcg(MEMORY_STAT_CONTENTS); assertEquals(1, stat.pgfault); assertEquals(2, stat.pgmajfault); @@ -183,7 +186,7 @@ public class MemoryStatUtilTest { } @Test - public void testParseMemoryStatFromMemcg_emptyMemoryStatContents() throws Exception { + public void testParseMemoryStatFromMemcg_emptyMemoryStatContents() { MemoryStat stat = parseMemoryStatFromMemcg(""); assertNull(stat); @@ -204,17 +207,22 @@ public class MemoryStatUtilTest { } @Test - public void testParseMemoryStatFromProcfs_parsesCorrectValues() throws Exception { + public void testParseMemoryMaxUsageFromMemCg_incorrectValue() { + assertEquals(0, parseMemoryMaxUsageFromMemCg("memory")); + } + + @Test + public void testParseMemoryStatFromProcfs_parsesCorrectValues() { MemoryStat stat = parseMemoryStatFromProcfs(PROC_STAT_CONTENTS); assertEquals(1, stat.pgfault); assertEquals(2, stat.pgmajfault); - assertEquals(3, stat.rssInBytes); + assertEquals(3 * PAGE_SIZE, stat.rssInBytes); assertEquals(0, stat.cacheInBytes); assertEquals(0, stat.swapInBytes); } @Test - public void testParseMemoryStatFromProcfs_emptyContents() throws Exception { + public void testParseMemoryStatFromProcfs_emptyContents() { MemoryStat stat = parseMemoryStatFromProcfs(""); assertNull(stat); @@ -223,6 +231,12 @@ public class MemoryStatUtilTest { } @Test + public void testParseMemoryStatFromProcfs_invalidValue() { + String contents = String.join(" ", Collections.nCopies(24, "memory")); + assertNull(parseMemoryStatFromProcfs(contents)); + } + + @Test public void testParseVmHWMFromProcfs_parsesCorrectValue() { assertEquals(137668, parseVmHWMFromProcfs(PROC_STATUS_CONTENTS) / BYTES_IN_KILOBYTE); } diff --git a/services/tests/servicestests/src/com/android/server/am/PersisterQueueTests.java b/services/tests/servicestests/src/com/android/server/am/PersisterQueueTests.java new file mode 100644 index 000000000000..d7794b04adca --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/am/PersisterQueueTests.java @@ -0,0 +1,300 @@ +/* + * 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.am; + +import static junit.framework.Assert.assertNull; +import static junit.framework.Assert.assertSame; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import android.os.SystemClock; +import android.platform.test.annotations.Presubmit; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Predicate; + +import androidx.test.filters.FlakyTest; +import androidx.test.filters.MediumTest; +import androidx.test.runner.AndroidJUnit4; + +/** + * atest PersisterQueueTests + */ +@RunWith(AndroidJUnit4.class) +@MediumTest +@Presubmit +@FlakyTest(detail = "Confirm stable in post-submit before removing") +public class PersisterQueueTests implements PersisterQueue.Listener { + private static final long INTER_WRITE_DELAY_MS = 50; + private static final long PRE_TASK_DELAY_MS = 300; + // We allow at most 1s more than the expected timeout. + private static final long TIMEOUT_ALLOWANCE = 100; + + private static final Predicate<MatchingTestItem> TEST_ITEM_PREDICATE = item -> item.mMatching; + + private AtomicInteger mItemCount; + private CountDownLatch mSetUpLatch; + private volatile CountDownLatch mLatch; + private List<Boolean> mProbablyDoneResults; + + private PersisterQueue mTarget; + + @Before + public void setUp() throws Exception { + mItemCount = new AtomicInteger(0); + mProbablyDoneResults = new ArrayList<>(); + mSetUpLatch = new CountDownLatch(1); + + mTarget = new PersisterQueue(INTER_WRITE_DELAY_MS, PRE_TASK_DELAY_MS); + mTarget.addListener(this); + mTarget.startPersisting(); + + assertTrue("Target didn't call callback on start up.", + mSetUpLatch.await(TIMEOUT_ALLOWANCE, TimeUnit.MILLISECONDS)); + } + + @After + public void tearDown() throws Exception { + mTarget.stopPersisting(); + } + + @Test + public void testCallCallbackOnStartUp() throws Exception { + // The onPreProcessItem() must be called on start up. + assertEquals(1, mProbablyDoneResults.size()); + // The last one must be called with probably done being true. + assertTrue("The last probablyDone must be true.", mProbablyDoneResults.get(0)); + } + + @Test + public void testProcessOneItem() throws Exception { + mLatch = new CountDownLatch(1); + + final long dispatchTime = SystemClock.uptimeMillis(); + mTarget.addItem(new TestItem(), false); + assertTrue("Target didn't call callback enough times.", + mLatch.await(PRE_TASK_DELAY_MS + TIMEOUT_ALLOWANCE, TimeUnit.MILLISECONDS)); + assertEquals("Target didn't process item.", 1, mItemCount.get()); + final long processDuration = SystemClock.uptimeMillis() - dispatchTime; + assertTrue("Target didn't wait enough time before processing item. duration: " + + processDuration + "ms pretask delay: " + PRE_TASK_DELAY_MS + "ms", + processDuration >= PRE_TASK_DELAY_MS); + + // Once before processing this item, once after that. + assertEquals(2, mProbablyDoneResults.size()); + // The last one must be called with probably done being true. + assertTrue("The last probablyDone must be true.", mProbablyDoneResults.get(1)); + } + + @Test + public void testProcessOneItem_Flush() throws Exception { + mLatch = new CountDownLatch(1); + + final long dispatchTime = SystemClock.uptimeMillis(); + mTarget.addItem(new TestItem(), true); + assertTrue("Target didn't call callback enough times.", + mLatch.await(TIMEOUT_ALLOWANCE, TimeUnit.MILLISECONDS)); + assertEquals("Target didn't process item.", 1, mItemCount.get()); + final long processDuration = SystemClock.uptimeMillis() - dispatchTime; + assertTrue("Target didn't process item immediately when flushing. duration: " + + processDuration + "ms pretask delay: " + + PRE_TASK_DELAY_MS + "ms", + processDuration < PRE_TASK_DELAY_MS); + + // Once before processing this item, once after that. + assertEquals(2, mProbablyDoneResults.size()); + // The last one must be called with probably done being true. + assertTrue("The last probablyDone must be true.", mProbablyDoneResults.get(1)); + } + + @Test + public void testProcessTwoItems() throws Exception { + mLatch = new CountDownLatch(2); + + final long dispatchTime = SystemClock.uptimeMillis(); + mTarget.addItem(new TestItem(), false); + mTarget.addItem(new TestItem(), false); + assertTrue("Target didn't call callback enough times.", + mLatch.await(PRE_TASK_DELAY_MS + INTER_WRITE_DELAY_MS + TIMEOUT_ALLOWANCE, + TimeUnit.MILLISECONDS)); + assertEquals("Target didn't process all items.", 2, mItemCount.get()); + final long processDuration = SystemClock.uptimeMillis() - dispatchTime; + assertTrue("Target didn't wait enough time before processing item. duration: " + + processDuration + "ms pretask delay: " + PRE_TASK_DELAY_MS + + "ms inter write delay: " + INTER_WRITE_DELAY_MS + "ms", + processDuration >= PRE_TASK_DELAY_MS + INTER_WRITE_DELAY_MS); + + // Once before processing this item, once after that. + assertEquals(3, mProbablyDoneResults.size()); + // The first one must be called with probably done being false. + assertFalse("The first probablyDone must be false.", mProbablyDoneResults.get(1)); + // The last one must be called with probably done being true. + assertTrue("The last probablyDone must be true.", mProbablyDoneResults.get(2)); + } + + @Test + public void testProcessTwoItems_OneAfterAnother() throws Exception { + // First item + mLatch = new CountDownLatch(1); + long dispatchTime = SystemClock.uptimeMillis(); + mTarget.addItem(new TestItem(), false); + assertTrue("Target didn't call callback enough times.", + mLatch.await(PRE_TASK_DELAY_MS + TIMEOUT_ALLOWANCE, TimeUnit.MILLISECONDS)); + long processDuration = SystemClock.uptimeMillis() - dispatchTime; + assertTrue("Target didn't wait enough time before processing item." + + processDuration + "ms pretask delay: " + + PRE_TASK_DELAY_MS + "ms", + processDuration >= PRE_TASK_DELAY_MS); + assertEquals("Target didn't process item.", 1, mItemCount.get()); + + // Second item + mLatch = new CountDownLatch(1); + dispatchTime = SystemClock.uptimeMillis(); + // Synchronize on the instance to make sure we schedule the item after it starts to wait for + // task indefinitely. + synchronized (mTarget) { + mTarget.addItem(new TestItem(), false); + } + assertTrue("Target didn't call callback enough times.", + mLatch.await(PRE_TASK_DELAY_MS + TIMEOUT_ALLOWANCE, TimeUnit.MILLISECONDS)); + assertEquals("Target didn't process all items.", 2, mItemCount.get()); + processDuration = SystemClock.uptimeMillis() - dispatchTime; + assertTrue("Target didn't wait enough time before processing item." + + processDuration + "ms pre task delay: " + + PRE_TASK_DELAY_MS + "ms", + processDuration >= PRE_TASK_DELAY_MS); + + // Once before processing this item, once after that. + assertEquals(3, mProbablyDoneResults.size()); + // The last one must be called with probably done being true. + assertTrue("The last probablyDone must be true.", mProbablyDoneResults.get(2)); + } + + @Test + public void testFindLastItemNotReturnDifferentType() throws Exception { + synchronized (mTarget) { + mTarget.addItem(new TestItem(), false); + assertNull(mTarget.findLastItem(TEST_ITEM_PREDICATE, MatchingTestItem.class)); + } + } + + @Test + public void testFindLastItemNotReturnMismatchItem() throws Exception { + synchronized (mTarget) { + mTarget.addItem(new MatchingTestItem(false), false); + assertNull(mTarget.findLastItem(TEST_ITEM_PREDICATE, MatchingTestItem.class)); + } + } + + @Test + public void testFindLastItemReturnMatchedItem() throws Exception { + synchronized (mTarget) { + final MatchingTestItem item = new MatchingTestItem(true); + mTarget.addItem(item, false); + assertSame(item, mTarget.findLastItem(TEST_ITEM_PREDICATE, MatchingTestItem.class)); + } + } + + @Test + public void testRemoveItemsNotRemoveDifferentType() throws Exception { + mLatch = new CountDownLatch(1); + synchronized (mTarget) { + mTarget.addItem(new TestItem(), false); + mTarget.removeItems(TEST_ITEM_PREDICATE, MatchingTestItem.class); + } + assertTrue("Target didn't call callback enough times.", + mLatch.await(PRE_TASK_DELAY_MS + TIMEOUT_ALLOWANCE, TimeUnit.MILLISECONDS)); + assertEquals("Target didn't process item.", 1, mItemCount.get()); + } + + @Test + public void testRemoveItemsNotRemoveMismatchedItem() throws Exception { + mLatch = new CountDownLatch(1); + synchronized (mTarget) { + mTarget.addItem(new MatchingTestItem(false), false); + mTarget.removeItems(TEST_ITEM_PREDICATE, MatchingTestItem.class); + } + assertTrue("Target didn't call callback enough times.", + mLatch.await(PRE_TASK_DELAY_MS + TIMEOUT_ALLOWANCE, TimeUnit.MILLISECONDS)); + assertEquals("Target didn't process item.", 1, mItemCount.get()); + } + + @Test + public void testRemoveItemsRemoveMatchedItem() throws Exception { + mLatch = new CountDownLatch(1); + synchronized (mTarget) { + mTarget.addItem(new TestItem(), false); + mTarget.addItem(new MatchingTestItem(true), false); + mTarget.removeItems(TEST_ITEM_PREDICATE, MatchingTestItem.class); + } + assertTrue("Target didn't call callback enough times.", + mLatch.await(PRE_TASK_DELAY_MS + TIMEOUT_ALLOWANCE, TimeUnit.MILLISECONDS)); + assertEquals("Target didn't process item.", 1, mItemCount.get()); + } + + @Test + public void testFlushWaitSynchronously() { + final long dispatchTime = SystemClock.uptimeMillis(); + mTarget.addItem(new TestItem(), false); + mTarget.addItem(new TestItem(), false); + mTarget.flush(); + assertEquals("Flush should wait until all items are processed before return.", + 2, mItemCount.get()); + final long processTime = SystemClock.uptimeMillis() - dispatchTime; + assertTrue("Flush should trigger immediate flush without delays. processTime: " + + processTime, processTime < TIMEOUT_ALLOWANCE); + } + + @Override + public void onPreProcessItem(boolean queueEmpty) { + mProbablyDoneResults.add(queueEmpty); + + final CountDownLatch latch = mLatch; + if (latch != null) { + latch.countDown(); + } + + mSetUpLatch.countDown(); + } + + private class TestItem implements PersisterQueue.WriteQueueItem { + @Override + public void process() { + mItemCount.getAndIncrement(); + } + } + + private class MatchingTestItem extends TestItem { + private boolean mMatching; + + private MatchingTestItem(boolean matching) { + mMatching = matching; + } + } +} diff --git a/services/tests/servicestests/src/com/android/server/am/RecentTasksTest.java b/services/tests/servicestests/src/com/android/server/am/RecentTasksTest.java index 70cfad1d0f2f..1276f656f914 100644 --- a/services/tests/servicestests/src/com/android/server/am/RecentTasksTest.java +++ b/services/tests/servicestests/src/com/android/server/am/RecentTasksTest.java @@ -125,7 +125,6 @@ public class RecentTasksTest extends ActivityTestsBase { WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */); mStack = mService.mStackSupervisor.getDefaultDisplay().createStack( WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); - ((MyTestActivityStackSupervisor) mService.mStackSupervisor).setHomeStack(mHomeStack); mCallbacksRecorder = new CallbacksRecorder(); mRecentTasks.registerCallback(mCallbacksRecorder); QUIET_USER_INFO.flags = UserInfo.FLAG_MANAGED_PROFILE | UserInfo.FLAG_QUIET_MODE; @@ -558,9 +557,8 @@ public class RecentTasksTest extends ActivityTestsBase { final MyTestActivityStackSupervisor supervisor = (MyTestActivityStackSupervisor) mService.mStackSupervisor; - final ActivityStack homeStack = new MyTestActivityStack(mDisplay, supervisor); + final ActivityStack homeStack = mDisplay.getHomeStack(); final ActivityStack aboveHomeStack = new MyTestActivityStack(mDisplay, supervisor); - supervisor.setHomeStack(homeStack); // Add a number of tasks (beyond the max) but ensure that nothing is trimmed because all // the tasks belong in stacks above the home stack @@ -579,9 +577,8 @@ public class RecentTasksTest extends ActivityTestsBase { final MyTestActivityStackSupervisor supervisor = (MyTestActivityStackSupervisor) mService.mStackSupervisor; final ActivityStack behindHomeStack = new MyTestActivityStack(mDisplay, supervisor); - final ActivityStack homeStack = new MyTestActivityStack(mDisplay, supervisor); + final ActivityStack homeStack = mDisplay.getHomeStack(); final ActivityStack aboveHomeStack = new MyTestActivityStack(mDisplay, supervisor); - supervisor.setHomeStack(homeStack); // Add a number of tasks (beyond the max) but ensure that only the task in the stack behind // the home stack is trimmed once a new task is added @@ -601,9 +598,8 @@ public class RecentTasksTest extends ActivityTestsBase { final MyTestActivityStackSupervisor supervisor = (MyTestActivityStackSupervisor) mService.mStackSupervisor; - final ActivityStack homeStack = new MyTestActivityStack(mDisplay, supervisor); + final ActivityStack homeStack = mDisplay.getHomeStack(); final ActivityStack otherDisplayStack = new MyTestActivityStack(mOtherDisplay, supervisor); - supervisor.setHomeStack(homeStack); // Add a number of tasks (beyond the max) on each display, ensure that the tasks are not // removed @@ -870,7 +866,7 @@ public class RecentTasksTest extends ActivityTestsBase { @Override public void initialize() { super.initialize(); - mDisplay = TestActivityDisplay.create(this, DEFAULT_DISPLAY); + mDisplay = getActivityDisplay(DEFAULT_DISPLAY); mOtherDisplay = TestActivityDisplay.create(this, DEFAULT_DISPLAY + 1); addChild(mOtherDisplay, ActivityDisplay.POSITION_TOP); addChild(mDisplay, ActivityDisplay.POSITION_TOP); @@ -881,10 +877,6 @@ public class RecentTasksTest extends ActivityTestsBase { mRunningTasks = new TestRunningTasks(); return mRunningTasks; } - - void setHomeStack(ActivityStack stack) { - mHomeStack = stack; - } } private class MyTestActivityStack extends TestActivityStack { 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 9fcdf2d517e9..d52051eec5bd 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 @@ -436,7 +436,6 @@ public class AppBackupUtilsTest { new Signature[] {SIGNATURE_1}, PackageParser.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3, null, - null, null)); packageInfo.applicationInfo = new ApplicationInfo(); @@ -456,7 +455,6 @@ public class AppBackupUtilsTest { new Signature[] {SIGNATURE_1}, PackageParser.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3, null, - null, null)); packageInfo.applicationInfo = new ApplicationInfo(); @@ -537,7 +535,6 @@ public class AppBackupUtilsTest { new Signature[] {SIGNATURE_1, SIGNATURE_2, SIGNATURE_3}, PackageParser.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3, null, - null, null)); packageInfo.applicationInfo = new ApplicationInfo(); @@ -560,7 +557,6 @@ public class AppBackupUtilsTest { new Signature[] {SIGNATURE_1, SIGNATURE_2, SIGNATURE_3}, PackageParser.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3, null, - null, null)); packageInfo.applicationInfo = new ApplicationInfo(); @@ -583,7 +579,6 @@ public class AppBackupUtilsTest { new Signature[] {signature1Copy, signature2Copy}, PackageParser.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3, null, - null, null)); packageInfo.applicationInfo = new ApplicationInfo(); @@ -606,7 +601,6 @@ public class AppBackupUtilsTest { new Signature[] {SIGNATURE_1, SIGNATURE_2, SIGNATURE_3}, PackageParser.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3, null, - null, null)); packageInfo.applicationInfo = new ApplicationInfo(); @@ -629,7 +623,6 @@ public class AppBackupUtilsTest { new Signature[] {SIGNATURE_1}, PackageParser.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3, null, - null, null)); packageInfo.applicationInfo = new ApplicationInfo(); @@ -654,8 +647,7 @@ public class AppBackupUtilsTest { new Signature[] {SIGNATURE_2}, PackageParser.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3, null, - new Signature[] {SIGNATURE_1, SIGNATURE_2}, - new int[] {0, 0})); + new Signature[] {SIGNATURE_1, SIGNATURE_2})); packageInfo.applicationInfo = new ApplicationInfo(); // we know signature1Copy is in history, and we want to assume it has @@ -682,8 +674,7 @@ public class AppBackupUtilsTest { new Signature[] {SIGNATURE_2}, PackageParser.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3, null, - new Signature[] {SIGNATURE_1, SIGNATURE_2}, - new int[] {0, 0})); + 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 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 12f2991b3fea..47749856a7ec 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 @@ -377,7 +377,6 @@ public class TarBackupReaderTest { new Signature[] {FAKE_SIGNATURE_2}, PackageParser.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3, null, - null, null)); PackageManagerStub.sPackageInfo = packageInfo; @@ -414,7 +413,6 @@ public class TarBackupReaderTest { new Signature[] {FAKE_SIGNATURE_1}, PackageParser.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3, null, - null, null)); PackageManagerStub.sPackageInfo = packageInfo; @@ -452,7 +450,6 @@ public class TarBackupReaderTest { new Signature[] {FAKE_SIGNATURE_1}, PackageParser.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3, null, - null, null)); PackageManagerStub.sPackageInfo = packageInfo; @@ -493,7 +490,6 @@ public class TarBackupReaderTest { new Signature[] {FAKE_SIGNATURE_1}, PackageParser.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3, null, - null, null)); packageInfo.versionCode = 2; PackageManagerStub.sPackageInfo = packageInfo; @@ -537,7 +533,6 @@ public class TarBackupReaderTest { new Signature[] {FAKE_SIGNATURE_1}, PackageParser.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3, null, - null, null)); packageInfo.versionCode = 1; PackageManagerStub.sPackageInfo = packageInfo; @@ -577,7 +572,6 @@ public class TarBackupReaderTest { new Signature[] {FAKE_SIGNATURE_1}, PackageParser.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3, null, - null, null)); packageInfo.versionCode = 1; PackageManagerStub.sPackageInfo = packageInfo; diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java index ceee60c017e5..b4212808d585 100644 --- a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java @@ -16,6 +16,8 @@ package com.android.server.display; +import static com.android.server.display.VirtualDisplayAdapter.UNIQUE_ID_PREFIX; + import android.content.Context; import android.hardware.display.BrightnessConfiguration; import android.hardware.display.Curve; @@ -25,35 +27,48 @@ import android.hardware.display.IVirtualDisplayCallback; import android.hardware.input.InputManagerInternal; import android.os.Handler; import android.os.IBinder; -import android.os.UserHandle; -import android.test.AndroidTestCase; -import android.test.suitebuilder.annotation.SmallTest; +import android.view.Display; +import android.view.DisplayInfo; import android.view.SurfaceControl; +import androidx.test.runner.AndroidJUnit4; +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.SmallTest; + import com.android.server.LocalServices; import com.android.server.SystemService; import com.android.server.display.DisplayDeviceInfo; import com.android.server.display.DisplayManagerService.SyncRoot; -import com.android.server.display.VirtualDisplayAdapter.SurfaceControlDisplayFactory; import com.android.server.lights.LightsManager; import com.android.server.wm.WindowManagerInternal; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.Arrays; import java.util.List; -import static org.mockito.Matchers.any; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.mockito.Mockito.mock; @SmallTest -public class DisplayManagerServiceTest extends AndroidTestCase { +@RunWith(AndroidJUnit4.class) +public class DisplayManagerServiceTest { private static final int MSG_REGISTER_DEFAULT_DISPLAY_ADAPTERS = 1; private static final long SHORT_DEFAULT_DISPLAY_TIMEOUT_MILLIS = 10; + private Context mContext; + private final DisplayManagerService.Injector mShortMockedInjector = new DisplayManagerService.Injector() { @Override @@ -86,8 +101,8 @@ public class DisplayManagerServiceTest extends AndroidTestCase { @Mock VirtualDisplayAdapter mMockVirtualDisplayAdapter; @Mock IBinder mMockDisplayToken; - @Override - protected void setUp() throws Exception { + @Before + public void setUp() throws Exception { MockitoAnnotations.initMocks(this); LocalServices.removeServiceForTest(InputManagerInternal.class); @@ -96,15 +111,12 @@ public class DisplayManagerServiceTest extends AndroidTestCase { LocalServices.addService(WindowManagerInternal.class, mMockWindowManagerInternal); LocalServices.removeServiceForTest(LightsManager.class); LocalServices.addService(LightsManager.class, mMockLightsManager); - super.setUp(); - } - @Override - protected void tearDown() throws Exception { - super.tearDown(); + mContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); } - public void testCreateVirtualDisplay_sentToInputManager() throws Exception { + @Test + public void testCreateVirtualDisplay_sentToInputManager() { DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector); registerDefaultDisplays(displayManager); @@ -115,7 +127,7 @@ public class DisplayManagerServiceTest extends AndroidTestCase { DisplayManagerService.BinderService bs = displayManager.new BinderService(); String uniqueId = "uniqueId --- Test"; - String uniqueIdPrefix = "virtual:" + mContext.getPackageName() + ":"; + String uniqueIdPrefix = UNIQUE_ID_PREFIX + mContext.getPackageName() + ":"; int width = 600; int height = 800; int dpi = 320; @@ -132,19 +144,113 @@ public class DisplayManagerServiceTest extends AndroidTestCase { // flush the handler displayManager.getDisplayHandler().runWithScissors(() -> {}, 0 /* now */); - ArgumentCaptor<List<DisplayViewport>> virtualViewportCaptor = - ArgumentCaptor.forClass(List.class); - verify(mMockInputManagerInternal).setDisplayViewports( - any(), any(), virtualViewportCaptor.capture()); - - assertEquals(1, virtualViewportCaptor.getValue().size()); - DisplayViewport dv = virtualViewportCaptor.getValue().get(0); - assertEquals(height, dv.deviceHeight); - assertEquals(width, dv.deviceWidth); - assertEquals(uniqueIdPrefix + uniqueId, dv.uniqueId); - assertEquals(displayId, dv.displayId); + ArgumentCaptor<List<DisplayViewport>> viewportCaptor = ArgumentCaptor.forClass(List.class); + verify(mMockInputManagerInternal).setDisplayViewports(viewportCaptor.capture()); + List<DisplayViewport> viewports = viewportCaptor.getValue(); + + // Expect to receive 3 viewports: internal, external, and virtual + assertEquals(3, viewports.size()); + + DisplayViewport virtualViewport = null; + DisplayViewport internalViewport = null; + DisplayViewport externalViewport = null; + for (int i = 0; i < viewports.size(); i++) { + DisplayViewport v = viewports.get(i); + switch (v.type) { + case DisplayViewport.VIEWPORT_INTERNAL: { + internalViewport = v; + break; + } + case DisplayViewport.VIEWPORT_EXTERNAL: { + externalViewport = v; + break; + } + case DisplayViewport.VIEWPORT_VIRTUAL: { + virtualViewport = v; + break; + } + } + } + // INTERNAL and EXTERNAL viewports get created upon access + assertNotNull(internalViewport); + assertNotNull(externalViewport); + assertNotNull(virtualViewport); + + // INTERNAL and EXTERNAL + assertTrue(internalViewport.valid); + assertTrue(externalViewport.valid); + + // VIRTUAL + assertEquals(height, virtualViewport.deviceHeight); + assertEquals(width, virtualViewport.deviceWidth); + assertEquals(uniqueIdPrefix + uniqueId, virtualViewport.uniqueId); + assertEquals(displayId, virtualViewport.displayId); + } + + @Test + public void testPhysicalViewports() { + DisplayManagerService displayManager = + new DisplayManagerService(mContext, mBasicInjector); + registerDefaultDisplays(displayManager); + displayManager.systemReady(false /* safeMode */, true /* onlyCore */); + displayManager.windowManagerAndInputReady(); + + // This is effectively the DisplayManager service published to ServiceManager. + DisplayManagerService.BinderService bs = displayManager.new BinderService(); + + when(mMockAppToken.asBinder()).thenReturn(mMockAppToken); + + final int displayIds[] = bs.getDisplayIds(); + assertEquals(1, displayIds.length); + final int displayId = displayIds[0]; + DisplayInfo info = bs.getDisplayInfo(displayId); + assertEquals(info.type, Display.TYPE_BUILT_IN); + + displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class)); + + // flush the handler + displayManager.getDisplayHandler().runWithScissors(() -> {}, 0 /* now */); + + ArgumentCaptor<List<DisplayViewport>> viewportCaptor = ArgumentCaptor.forClass(List.class); + verify(mMockInputManagerInternal).setDisplayViewports(viewportCaptor.capture()); + List<DisplayViewport> viewports = viewportCaptor.getValue(); + + // Expect to receive 2 viewports: 1 internal, 1 external + assertEquals(2, viewports.size()); + + DisplayViewport internalViewport = null; + DisplayViewport externalViewport = null; + for (int i = 0; i < viewports.size(); i++) { + DisplayViewport v = viewports.get(i); + switch (v.type) { + case DisplayViewport.VIEWPORT_INTERNAL: { + internalViewport = v; + break; + } + case DisplayViewport.VIEWPORT_EXTERNAL: { + externalViewport = v; + break; + } + default: { + fail("Unexpected viewport type: " + DisplayViewport.typeToString(v.type)); + break; + } + } + } + // INTERNAL and EXTERNAL viewports get created upon access + assertNotNull(internalViewport); + assertNotNull(externalViewport); + assertTrue(internalViewport.valid); + assertEquals(displayId, internalViewport.displayId); + + // To simplify comparison, override the type for external Viewport + // TODO (b/116850516) remove this + externalViewport.type = internalViewport.type; + assertEquals(internalViewport, externalViewport); + externalViewport.type = DisplayViewport.VIEWPORT_EXTERNAL; // undo the changes above } + @Test public void testCreateVirtualDisplayRotatesWithContent() throws Exception { DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector); @@ -178,6 +284,7 @@ public class DisplayManagerServiceTest extends AndroidTestCase { /** * Tests that the virtual display is created along-side the default display. */ + @Test public void testStartVirtualDisplayWithDefaultDisplay_Succeeds() throws Exception { DisplayManagerService displayManager = new DisplayManagerService(mContext, mShortMockedInjector); @@ -188,6 +295,7 @@ public class DisplayManagerServiceTest extends AndroidTestCase { /** * Tests that we get a Runtime exception when we cannot initialize the default display. */ + @Test public void testStartVirtualDisplayWithDefDisplay_NoDefaultDisplay() throws Exception { DisplayManagerService displayManager = new DisplayManagerService(mContext, mShortMockedInjector); @@ -206,6 +314,7 @@ public class DisplayManagerServiceTest extends AndroidTestCase { /** * Tests that we get a Runtime exception when we cannot initialize the virtual display. */ + @Test public void testStartVirtualDisplayWithDefDisplay_NoVirtualDisplayAdapter() throws Exception { DisplayManagerService displayManager = new DisplayManagerService(mContext, new DisplayManagerService.Injector() { @@ -232,6 +341,7 @@ public class DisplayManagerServiceTest extends AndroidTestCase { /** * Tests that an exception is raised for too dark a brightness configuration. */ + @Test public void testTooDarkBrightnessConfigurationThrowException() { DisplayManagerService displayManager = new DisplayManagerService(mContext, mShortMockedInjector); @@ -266,6 +376,7 @@ public class DisplayManagerServiceTest extends AndroidTestCase { /** * Tests that no exception is raised for not too dark a brightness configuration. */ + @Test public void testBrightEnoughBrightnessConfigurationDoesNotThrowException() { DisplayManagerService displayManager = new DisplayManagerService(mContext, mShortMockedInjector); @@ -279,6 +390,7 @@ public class DisplayManagerServiceTest extends AndroidTestCase { /** * Tests that null brightness configurations are alright. */ + @Test public void testNullBrightnessConfiguration() { DisplayManagerService displayManager = new DisplayManagerService(mContext, mShortMockedInjector); diff --git a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java index 2de5d87c063c..a3348c26f772 100644 --- a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java @@ -1052,7 +1052,6 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase { genSignatures(signatures), PackageParser.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3, null, - null, null)); return pi; } diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java index 318ed3a5217c..9af7b1c911e1 100644 --- a/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java @@ -504,7 +504,6 @@ public class PackageParserTest { new Signature[] { new Signature(new byte[16]) }, 2, new ArraySet<>(), - null, null); pkg.mExtras = new Bundle(); pkg.mRestrictedAccountType = "foo19"; diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageSignaturesTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageSignaturesTest.java new file mode 100644 index 000000000000..d3a77d3e80f1 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/pm/PackageSignaturesTest.java @@ -0,0 +1,474 @@ +/* + * 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.pm; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import android.content.Context; +import android.content.pm.PackageParser; +import android.content.pm.Signature; +import android.util.Xml; + +import androidx.test.InstrumentationRegistry; +import androidx.test.runner.AndroidJUnit4; + +import com.android.internal.util.HexDump; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.xmlpull.v1.XmlPullParser; + +import java.io.File; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +@RunWith(AndroidJUnit4.class) +public class PackageSignaturesTest { + private static final String TEST_RESOURCES_FOLDER = "PackageSignaturesTest"; + + private Context mContext; + + private PackageSetting mPackageSetting; + + // These signatures are the DER encoding of the ec-p256[_X] X509 certificates in the certs/ + // directory. The apksigner tool was used to sign a test APK with these certificates and the + // corresponding ec-p256{_X].pk8 private key file. For the lineage tests the + // ec-p256-lineage-X-signers file was provided as the parameter to the --lineage option when + // signing the APK. The APK was then installed on a test device, the packages.xml file was + // pulled from the device, and the APK's <sig> tag was used as the basis for these tests. + // For more details see the README under the xml/ directory. + private static final String FIRST_EXPECTED_SIGNATURE = + "3082016c30820111a003020102020900ca0fb64dfb66e772300a06082a8648ce3d04030230123110300e06" + + "035504030c0765632d70323536301e170d3136303333313134353830365a170d34333038313731343538" + + "30365a30123110300e06035504030c0765632d703235363059301306072a8648ce3d020106082a8648ce" + + "3d03010703420004a65f113d22cb4913908307ac31ee2ba0e9138b785fac6536d14ea2ce90d2b4bfe194" + + "b50cdc8e169f54a73a991ef0fa76329825be078cc782740703da44b4d7eba350304e301d0603551d0e04" + + "160414d4133568b95b30158b322071ea8c43ff5b05ccc8301f0603551d23041830168014d4133568b95b" + + "30158b322071ea8c43ff5b05ccc8300c0603551d13040530030101ff300a06082a8648ce3d0403020349" + + "003046022100f504a0866caef029f417142c5cb71354c79ffcd1d640618dfca4f19e16db78d6022100f8" + + "eea4829799c06cad08c6d3d2d2ec05e0574154e747ea0fdbb8042cb655aadd"; + private static final String SECOND_EXPECTED_SIGNATURE = + "3082016d30820113a0030201020209008855bd1dd2b2b225300a06082a8648ce3d04030230123110300e06" + + "035504030c0765632d70323536301e170d3138303731333137343135315a170d32383037313031373431" + + "35315a30143112301006035504030c0965632d703235365f323059301306072a8648ce3d020106082a86" + + "48ce3d030107034200041d4cca0472ad97ee3cecef0da93d62b450c6788333b36e7553cde9f74ab5df00" + + "bbba6ba950e68461d70bbc271b62151dad2de2bf6203cd2076801c7a9d4422e1a350304e301d0603551d" + + "0e041604147991d92b0208fc448bf506d4efc9fff428cb5e5f301f0603551d23041830168014d4133568" + + "b95b30158b322071ea8c43ff5b05ccc8300c0603551d13040530030101ff300a06082a8648ce3d040302" + + "034800304502202769abb1b49fc2f53479c4ae92a6631dabfd522c9acb0bba2b43ebeb99c63011022100" + + "d260fb1d1f176cf9b7fa60098bfd24319f4905a3e5fda100a6fe1a2ab19ff09e"; + private static final String THIRD_EXPECTED_SIGNATURE = + "3082016e30820115a0030201020209008394f5cad16a89a7300a06082a8648ce3d04030230143112301006" + + "035504030c0965632d703235365f32301e170d3138303731343030303532365a170d3238303731313030" + + "303532365a30143112301006035504030c0965632d703235365f333059301306072a8648ce3d02010608" + + "2a8648ce3d03010703420004f31e62430e9db6fc5928d975fc4e47419bacfcb2e07c89299e6cd7e344dd" + + "21adfd308d58cb49a1a2a3fecacceea4862069f30be1643bcc255040d8089dfb3743a350304e301d0603" + + "551d0e041604146f8d0828b13efaf577fc86b0e99fa3e54bcbcff0301f0603551d230418301680147991" + + "d92b0208fc448bf506d4efc9fff428cb5e5f300c0603551d13040530030101ff300a06082a8648ce3d04" + + "030203470030440220256bdaa2784c273e4cc291a595a46779dee9de9044dc9f7ab820309567df9fe902" + + "201a4ad8c69891b5a8c47434fe9540ed1f4979b5fad3483f3fa04d5677355a579e"; + + // When running tests using the pastSigs tag / lineage the past signers and their capabilities + // should be returned in the SigningDetails. The flags attribute of the cert tag under the + // pastSigs tag contains these capabilities; for tests that verify the lineage the capabilities + // of the signers should be set to the values in this Map. + private static final Map<String, Integer> SIGNATURE_TO_CAPABILITY_MAP; + + static { + SIGNATURE_TO_CAPABILITY_MAP = new HashMap<>(); + SIGNATURE_TO_CAPABILITY_MAP.put(FIRST_EXPECTED_SIGNATURE, 3); + SIGNATURE_TO_CAPABILITY_MAP.put(SECOND_EXPECTED_SIGNATURE, 7); + SIGNATURE_TO_CAPABILITY_MAP.put(THIRD_EXPECTED_SIGNATURE, 23); + } + + private static final int[] CAPABILITIES = + {PackageParser.SigningDetails.CertCapabilities.INSTALLED_DATA, + PackageParser.SigningDetails.CertCapabilities.SHARED_USER_ID, + PackageParser.SigningDetails.CertCapabilities.PERMISSION, + PackageParser.SigningDetails.CertCapabilities.ROLLBACK}; + + @Before + public void setUp() throws Exception { + mContext = InstrumentationRegistry.getContext(); + mPackageSetting = createPackageSetting(); + } + + @Test + public void testReadXmlWithOneSignerCompletesSuccessfully() throws Exception { + // Verifies the good path of reading a single sigs tag with one signer returns the + // expected signature and scheme version. + verifyReadXmlReturnsExpectedSignatures("xml/one-signer.xml", 1, FIRST_EXPECTED_SIGNATURE); + } + + @Test + public void testReadXmlWithTwoV1V2Signers() throws Exception { + // Verifies the good path of reading a single sigs tag with multiple signers returns the + // expected signatures and scheme version. + verifyReadXmlReturnsExpectedSignatures("xml/two-signers-v1v2.xml", 2, + FIRST_EXPECTED_SIGNATURE, SECOND_EXPECTED_SIGNATURE); + } + + @Test + public void testReadXmlFromTwoSigsTagsWithSameSigner() throws Exception { + // Verifies the good path of reading two separate packages tags from the same signer. The + // first call to readXml should return the list with the expected signature, then the second + // call should reference this signature and complete successfully with no new entries in the + // List. + XmlPullParser parser = getXMLFromResources("xml/one-signer.xml"); + ArrayList<Signature> signatures = new ArrayList<>(); + mPackageSetting.signatures.readXml(parser, signatures); + Set<String> expectedSignatures = createSetOfSignatures(FIRST_EXPECTED_SIGNATURE); + verifySignaturesContainExpectedValues(signatures, expectedSignatures); + parser = getXMLFromResources("xml/one-signer-previous-cert.xml"); + mPackageSetting.signatures.readXml(parser, signatures); + expectedSignatures = createSetOfSignatures(FIRST_EXPECTED_SIGNATURE); + verifySignaturesContainExpectedValues(signatures, expectedSignatures); + } + + @Test + public void testReadXmlWithSigningLineage() throws Exception { + // Verifies the good path of reading a single sigs tag including pastSigs with the + // signing lineage returns the expected signatures and lineage for two and three signers + // in the lineage. + verifyReadXmlReturnsExpectedSignaturesAndLineage("xml/two-signers-in-lineage.xml", 3, + FIRST_EXPECTED_SIGNATURE, SECOND_EXPECTED_SIGNATURE); + verifyReadXmlReturnsExpectedSignaturesAndLineage("xml/three-signers-in-lineage.xml", 3, + FIRST_EXPECTED_SIGNATURE, SECOND_EXPECTED_SIGNATURE, THIRD_EXPECTED_SIGNATURE); + } + + @Test + public void testReadXmlWithInvalidPublicKeyInCertKey() throws Exception { + // If the cert tag key attribute does not contain a valid public key then a + // CertificateException should be thrown when attempting to build the SigningDetails; in + // this case the signing details should be set to UNKNOWN. + XmlPullParser parser = getXMLFromResources( + "xml/one-signer-invalid-public-key-cert-key.xml"); + ArrayList<Signature> signatures = new ArrayList<>(); + mPackageSetting.signatures.readXml(parser, signatures); + assertEquals( + "The signing details was not UNKNOWN after parsing an invalid public key cert key" + + " attribute", + PackageParser.SigningDetails.UNKNOWN, mPackageSetting.signatures.mSigningDetails); + } + + @Test + public void testReadXmlWithMissingSigsCount() throws Exception { + // Verifies if the sigs count attribute is missing then the signature cannot be read but the + // method does not throw an exception. + verifyReadXmlReturnsExpectedSignatures("xml/one-signer-missing-sigs-count.xml", + PackageParser.SigningDetails.SignatureSchemeVersion.UNKNOWN); + } + + @Test + public void testReadXmlWithMissingSchemeVersion() throws Exception { + // Verifies if the schemeVersion is an invalid value the signature can still be obtained. + verifyReadXmlReturnsExpectedSignatures("xml/one-signer-missing-scheme-version.xml", + PackageParser.SigningDetails.SignatureSchemeVersion.UNKNOWN, + FIRST_EXPECTED_SIGNATURE); + } + + @Test + public void testReadXmlWithSigningLineageWithMissingSchemeVersion() throws Exception { + // Verifies if the scheme version cannot be read the signers in the lineage can still be + // obtained. + verifyReadXmlReturnsExpectedSignaturesAndLineage( + "xml/three-signers-in-lineage-missing-scheme-version.xml", + PackageParser.SigningDetails.SignatureSchemeVersion.UNKNOWN, + FIRST_EXPECTED_SIGNATURE, SECOND_EXPECTED_SIGNATURE, THIRD_EXPECTED_SIGNATURE); + } + + @Test + public void testReadXmlWithInvalidCertIndex() throws Exception { + // If the cert index attribute is invalid the signature will not be read but the call + // should exit gracefully. + verifyReadXmlReturnsExpectedSignatures("xml/one-signer-invalid-cert-index.xml", 3); + } + + @Test + public void testReadXmlWithMissingCertIndex() throws Exception { + // If the cert index attribute is missing the signature will not be read but the call should + // exit gracefully. + verifyReadXmlReturnsExpectedSignatures("xml/one-signer-missing-cert-index.xml", 3); + } + + @Test + public void testReadXmlWithInvalidCertKey() throws Exception { + // If the cert key value is invalid the signature cannot be read but the call should exit + // gracefully. + verifyReadXmlReturnsExpectedSignatures("xml/one-signer-invalid-cert-key.xml", 3); + } + + @Test + public void testReadXmlWithMissingCertKey() throws Exception { + // If the cert key is missing the signature cannot be read but the call should exit + // gracefully. + verifyReadXmlReturnsExpectedSignatures("xml/one-signer-missing-cert-key.xml", 3); + } + + @Test + public void testReadXmlWithMissingCertTag() throws Exception { + // If the cert tag is missing there is no signature to read but the call should exit + // gracefully. + verifyReadXmlReturnsExpectedSignatures("xml/one-signer-missing-cert-tag.xml", 3); + } + + @Test + public void testReadXmlWithTooFewCertTags() throws Exception { + // If the number of cert tags is less than that specified in the count attribute then the + // signatures that could be read are copied to a smaller array to be used when building + // the SigningDetails object. This test verifies if there are too few cert tags the + // available signatures can still be obtained. + verifyReadXmlReturnsExpectedSignatures("xml/two-signers-v1v2-missing-cert-tag.xml", 1, + FIRST_EXPECTED_SIGNATURE); + } + + @Test + public void testReadXmlWithExtraCertTag() throws Exception { + // Verifies if there are more cert tags than specified by the count attribute the extra cert + // tag is ignored and the expected signature from the first cert tag is returned. + verifyReadXmlReturnsExpectedSignatures("xml/one-signer-extra-cert-tag.xml", 3, + FIRST_EXPECTED_SIGNATURE); + } + + @Test + public void testReadXmlWithInvalidTag() throws Exception { + // Verifies an invalid tag under sigs is ignored and the expected signature is returned. + verifyReadXmlReturnsExpectedSignatures("xml/one-signer-invalid-tag.xml", 3, + FIRST_EXPECTED_SIGNATURE); + } + + @Test + public void testReadXmlWithInvalidPastSigsCount() throws Exception { + // Verifies if the pastSigs tag contains an invalid count attribute the current signature + // is still returned; in this case the third expected signature is the most recent signer. + verifyReadXmlReturnsExpectedSignatures( + "xml/three-signers-in-lineage-invalid-pastSigs-count.xml", 3, + THIRD_EXPECTED_SIGNATURE); + } + + @Test + public void testReadXmlWithMissingPastSigsCount() throws Exception { + // Verifies if the pastSigs tag is missing the count attribute the current signature is + // still returned; in this case the third expected signature is the most recent signer. + verifyReadXmlReturnsExpectedSignaturesAndLineage( + "xml/three-signers-in-lineage-missing-pastSigs-count.xml", 3, + THIRD_EXPECTED_SIGNATURE); + } + + @Test + public void testReadXmlWithInvalidCertFlags() throws Exception { + // Verifies if the cert tag contains an invalid flags attribute the expected signatures + // are still returned, although since the flags could not be read these signatures will not + // include the capabilities of the previous signers in the lineage. + verifyReadXmlReturnsExpectedSignatures("xml/two-signers-in-lineage-invalid-certs-flags.xml", + 3, FIRST_EXPECTED_SIGNATURE, SECOND_EXPECTED_SIGNATURE); + } + + @Test + public void testReadXmlWithMissingCertFlags() throws Exception { + // Verifies if the cert tag does not contain a flags attribute the expected signatures are + // still returned, although since there are no flags to read these signatures will not + // include the capabilities of the previous signers in the lineage. + verifyReadXmlReturnsExpectedSignatures("xml/two-signers-in-lineage-missing-certs-flags.xml", + 3, FIRST_EXPECTED_SIGNATURE, SECOND_EXPECTED_SIGNATURE); + } + + @Test + public void testReadXmlWithMultiplePastSigsTags() throws Exception { + // Verifies if multiple pastSigs tags are found under the sigs tag the additional pastSigs + // tag is ignored and the expected signatures are returned along with the previous signer in + // the lineage. + verifyReadXmlReturnsExpectedSignaturesAndLineage( + "xml/two-signers-in-lineage-multiple-pastSigs-tags.xml", 3, + FIRST_EXPECTED_SIGNATURE, SECOND_EXPECTED_SIGNATURE); + } + + @Test + public void testReadXmlWithInvalidPastSigsCertIndex() throws Exception { + // If the pastSigs cert tag contains an invalid index attribute that signature cannot be + // read but the current signature should still be returned. + verifyReadXmlReturnsExpectedSignaturesAndLineage( + "xml/two-signers-in-lineage-invalid-pastSigs-cert-index.xml", 3, + SECOND_EXPECTED_SIGNATURE); + } + + @Test + public void testReadXmlWithMissingPastSigsCertIndex() throws Exception { + // If the pastSigs cert tag does not contain an index attribute that signature cannot be + // read but the current signature should still be returned. + verifyReadXmlReturnsExpectedSignaturesAndLineage( + "xml/two-signers-in-lineage-missing-pastSigs-cert-index.xml", 3, + SECOND_EXPECTED_SIGNATURE); + } + + @Test + public void testReadXmlWithUndefinedPastSigsIndex() throws Exception { + // If a cert tag does not contain a key attribute it is assumed that the index attribute + // refers to a previously seen signature. If a signature does not yet exist at this index + // then the current signature cannot be read but any other signatures should still be + // returned. + verifyReadXmlReturnsExpectedSignatures( + "xml/two-signers-in-lineage-undefined-pastSigs-index.xml", 3, + FIRST_EXPECTED_SIGNATURE, null); + } + + @Test + public void testReadXmlWithTooFewPastSigsCertTags() throws Exception { + // If the number of cert tags is less than that specified in the count attribute of the + // pastSigs tag then the signatures that could be read are copied to a smaller array to be + // used when building the SigningDetails object. This test verifies if there are too few + // cert tags the available signatures and lineage can still be obtained. + verifyReadXmlReturnsExpectedSignaturesAndLineage( + "xml/three-signers-in-lineage-missing-pastSigs-cert-tag.xml", 3, + FIRST_EXPECTED_SIGNATURE, THIRD_EXPECTED_SIGNATURE); + } + + @Test + public void testReadXmlWithPastSignerWithNoCapabilities() throws Exception { + // When rotating the signing key a developer is able to specify the capabilities granted to + // the apps signed with the previous key. This test verifies a previous signing certificate + // with the flags set to 0 does not have any capabilities. + XmlPullParser parser = getXMLFromResources("xml/two-signers-in-lineage-no-caps.xml"); + ArrayList<Signature> signatures = new ArrayList<>(); + mPackageSetting.signatures.readXml(parser, signatures); + // obtain the Signature in the list matching the previous signing certificate + Signature previousSignature = null; + for (Signature signature : signatures) { + String signatureValue = HexDump.toHexString(signature.toByteArray(), false); + if (signatureValue.equals(FIRST_EXPECTED_SIGNATURE)) { + previousSignature = signature; + break; + } + } + assertNotNull("Unable to find the expected previous signer", previousSignature); + for (int capability : CAPABILITIES) { + assertFalse("The previous signer should not have the " + capability + " capability", + mPackageSetting.signatures.mSigningDetails.hasCertificate(previousSignature, + capability)); + } + } + + /** + * Verifies reading the sigs tag of the provided XML file returns the specified signature scheme + * version and the provided signatures. + */ + private void verifyReadXmlReturnsExpectedSignatures(String xmlFile, int expectedSchemeVersion, + String... expectedSignatureValues) throws Exception { + XmlPullParser parser = getXMLFromResources(xmlFile); + ArrayList<Signature> signatures = new ArrayList<>(); + mPackageSetting.signatures.readXml(parser, signatures); + Set<String> expectedSignatures = createSetOfSignatures(expectedSignatureValues); + verifySignaturesContainExpectedValues(signatures, expectedSignatures); + assertEquals("The returned signature scheme is not the expected value", + expectedSchemeVersion, + mPackageSetting.signatures.mSigningDetails.signatureSchemeVersion); + } + + /** + * Verifies reading the sigs tag of the provided XML file returns the specified signature scheme + * version, the provided signatures, and that the previous signers have the expected + * capabilities. + */ + private void verifyReadXmlReturnsExpectedSignaturesAndLineage(String xmlFile, + int schemeVersion, String... expectedSignatureValues) throws Exception { + XmlPullParser parser = getXMLFromResources(xmlFile); + ArrayList<Signature> signatures = new ArrayList<>(); + mPackageSetting.signatures.readXml(parser, signatures); + Set<String> expectedSignatures = createSetOfSignatures(expectedSignatureValues); + verifySignaturesContainExpectedValues(signatures, expectedSignatures); + assertEquals("The returned signature scheme is not the expected value", schemeVersion, + mPackageSetting.signatures.mSigningDetails.signatureSchemeVersion); + for (Signature signature : signatures) { + String signatureValue = HexDump.toHexString(signature.toByteArray(), false); + int expectedCapabilities = SIGNATURE_TO_CAPABILITY_MAP.get(signatureValue); + assertTrue("The signature " + signatureValue + + " was not found with the expected capabilities of " + + expectedCapabilities + + " in the signing details", + mPackageSetting.signatures.mSigningDetails.hasCertificate(signature, + expectedCapabilities)); + } + } + + /** + * Verifies the provided {@code List} contains Signatures that match the provided hex encoded + * signature values. + * + * The provided {@code Set} will be modified by this method as elements will be removed to + * ensure duplicate expected Signatures are not in the {@code List}. + */ + private static void verifySignaturesContainExpectedValues(ArrayList<Signature> signatures, + Set<String> expectedSignatures) { + assertEquals("The number of signatures does not equal the expected number of signatures", + expectedSignatures.size(), signatures.size()); + for (Signature signature : signatures) { + String signatureString = null; + if (signature != null) { + signatureString = HexDump.toHexString(signature.toByteArray(), false); + } + // If the signature is in the expected set then remove it so that duplicate matching + // signatures are reported. + if (expectedSignatures.contains(signatureString)) { + expectedSignatures.remove(signatureString); + } else { + fail("The following unexpected signature was returned: " + signatureString); + } + } + } + + private static Set<String> createSetOfSignatures(String... signatures) { + Set<String> result = new HashSet<String>(); + for (String signature : signatures) { + result.add(signature); + } + return result; + } + + private XmlPullParser getXMLFromResources(String xmlFile) throws Exception { + InputStream xmlStream = mContext.getResources().getAssets().open( + TEST_RESOURCES_FOLDER + "/" + xmlFile); + XmlPullParser result = Xml.newPullParser(); + result.setInput(xmlStream, StandardCharsets.UTF_8.name()); + int type; + // advance the parser to the first tag + while ((type = result.next()) != XmlPullParser.START_TAG + && type != XmlPullParser.END_DOCUMENT) { + ; + } + return result; + } + + private static PackageSetting createPackageSetting() { + // Generic PackageSetting object with values from a test app installed on a device to be + // used to test the methods under the PackageSignatures signatures data member. + File appPath = new File("/data/app/app"); + PackageSetting result = new PackageSetting("test.app", null, appPath, appPath, + "/data/app/app", null, null, null, + 1, 940097092, 0, null, + null, 0 /*userId*/, null, null); + return result; + } +} 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 13612a1f93f7..182760b30005 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 @@ -99,7 +99,6 @@ public class BackupUtilsTest { new Signature[] {SIGNATURE_1}, PackageParser.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3, null, - null, null)); packageInfo.applicationInfo = new ApplicationInfo(); @@ -119,7 +118,6 @@ public class BackupUtilsTest { new Signature[] {SIGNATURE_1}, PackageParser.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3, null, - null, null)); packageInfo.applicationInfo = new ApplicationInfo(); @@ -203,7 +201,6 @@ public class BackupUtilsTest { new Signature[] {SIGNATURE_1, SIGNATURE_2, SIGNATURE_3}, PackageParser.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3, null, - null, null)); packageInfo.applicationInfo = new ApplicationInfo(); @@ -226,7 +223,6 @@ public class BackupUtilsTest { new Signature[] {SIGNATURE_1, SIGNATURE_2, SIGNATURE_3}, PackageParser.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3, null, - null, null)); packageInfo.applicationInfo = new ApplicationInfo(); @@ -248,7 +244,6 @@ public class BackupUtilsTest { new Signature[] {SIGNATURE_1, SIGNATURE_2}, PackageParser.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3, null, - null, null)); packageInfo.applicationInfo = new ApplicationInfo(); @@ -271,7 +266,6 @@ public class BackupUtilsTest { new Signature[] {SIGNATURE_1, SIGNATURE_2, SIGNATURE_3}, PackageParser.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3, null, - null, null)); packageInfo.applicationInfo = new ApplicationInfo(); @@ -295,7 +289,6 @@ public class BackupUtilsTest { new Signature[] {SIGNATURE_1}, PackageParser.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3, null, - null, null)); packageInfo.applicationInfo = new ApplicationInfo(); @@ -320,7 +313,6 @@ public class BackupUtilsTest { new Signature[] {SIGNATURE_1, SIGNATURE_2}, PackageParser.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3, null, - null, null)); packageInfo.applicationInfo = new ApplicationInfo(); @@ -348,7 +340,6 @@ public class BackupUtilsTest { new Signature[] {SIGNATURE_1, SIGNATURE_2}, PackageParser.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3, null, - null, null)); packageInfo.applicationInfo = new ApplicationInfo(); diff --git a/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerTestBase.java b/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerTestBase.java index acd065e90a0c..e16f118cd63b 100644 --- a/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerTestBase.java +++ b/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerTestBase.java @@ -16,6 +16,10 @@ package com.android.server.policy; +import static android.view.DisplayCutout.BOUNDS_POSITION_BOTTOM; +import static android.view.DisplayCutout.BOUNDS_POSITION_LEFT; +import static android.view.DisplayCutout.BOUNDS_POSITION_RIGHT; +import static android.view.DisplayCutout.BOUNDS_POSITION_TOP; import static android.view.Surface.ROTATION_0; import static android.view.Surface.ROTATION_180; import static android.view.Surface.ROTATION_270; @@ -179,8 +183,25 @@ public class PhoneWindowManagerTestBase { transformPhysicalToLogicalCoordinates(rotation, DISPLAY_WIDTH, DISPLAY_HEIGHT, m); m.mapRect(rectF); + int pos = -1; + switch (rotation) { + case ROTATION_0: + pos = BOUNDS_POSITION_TOP; + break; + case ROTATION_90: + pos = BOUNDS_POSITION_LEFT; + break; + case ROTATION_180: + pos = BOUNDS_POSITION_BOTTOM; + break; + case ROTATION_270: + pos = BOUNDS_POSITION_RIGHT; + break; + } + + return DisplayCutout.fromBoundingRect((int) rectF.left, (int) rectF.top, - (int) rectF.right, (int) rectF.bottom); + (int) rectF.right, (int) rectF.bottom, pos); } static class TestContextWrapper extends ContextWrapper { diff --git a/services/tests/servicestests/src/com/android/server/power/BatterySaverPolicyTest.java b/services/tests/servicestests/src/com/android/server/power/BatterySaverPolicyTest.java index 54ac6fc7db93..bd4a356fcb5c 100644 --- a/services/tests/servicestests/src/com/android/server/power/BatterySaverPolicyTest.java +++ b/services/tests/servicestests/src/com/android/server/power/BatterySaverPolicyTest.java @@ -84,9 +84,6 @@ public class BatterySaverPolicyTest extends AndroidTestCase { } @Mock - Handler mHandler; - - @Mock MetricsLogger mMetricsLogger = mock(MetricsLogger.class); private BatterySaverPolicyForTest mBatterySaverPolicy; diff --git a/services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java b/services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java new file mode 100644 index 000000000000..5a787ec1cff3 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java @@ -0,0 +1,352 @@ +/* + * 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.usage; + +import static junit.framework.TestCase.assertNull; +import static junit.framework.TestCase.fail; + +import static org.testng.Assert.assertEquals; + +import android.app.usage.EventList; +import android.app.usage.UsageEvents; +import android.app.usage.UsageStats; +import android.app.usage.UsageStatsManager; +import android.content.Context; +import android.content.res.Configuration; +import android.test.suitebuilder.annotation.SmallTest; + +import androidx.test.InstrumentationRegistry; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.File; +import java.io.IOException; +import java.util.List; +import java.util.Locale; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class UsageStatsDatabaseTest { + protected Context mContext; + private UsageStatsDatabase mUsageStatsDatabase; + private File mTestDir; + + private IntervalStats mIntervalStats = new IntervalStats(); + private long mEndTime = 0; + + private static final UsageStatsDatabase.StatCombiner<IntervalStats> mIntervalStatsVerifier = + new UsageStatsDatabase.StatCombiner<IntervalStats>() { + @Override + public void combine(IntervalStats stats, boolean mutable, + List<IntervalStats> accResult) { + accResult.add(stats); + } + }; + + @Before + public void setUp() { + mContext = InstrumentationRegistry.getTargetContext(); + mTestDir = new File(mContext.getFilesDir(), "UsageStatsDatabaseTest"); + mUsageStatsDatabase = new UsageStatsDatabase(mTestDir); + mUsageStatsDatabase.init(1); + populateIntervalStats(); + clearUsageStatsFiles(); + } + + /** + * A debugging utility for viewing the files currently in the test directory + */ + private void clearUsageStatsFiles() { + File[] intervalDirs = mTestDir.listFiles(); + for (File intervalDir : intervalDirs) { + if (intervalDir.isDirectory()) { + File[] usageFiles = intervalDir.listFiles(); + for (File f : usageFiles) { + f.delete(); + } + } + } + } + + /** + * A debugging utility for viewing the files currently in the test directory + */ + private String dumpUsageStatsFiles() { + StringBuilder sb = new StringBuilder(); + File[] intervalDirs = mTestDir.listFiles(); + for (File intervalDir : intervalDirs) { + if (intervalDir.isDirectory()) { + File[] usageFiles = intervalDir.listFiles(); + for (File f : usageFiles) { + sb.append(f.toString()); + } + } + } + return sb.toString(); + } + + private void populateIntervalStats() { + final int numberOfEvents = 3000; + long time = 1; + mIntervalStats = new IntervalStats(); + + mIntervalStats.beginTime = 1; + mIntervalStats.interactiveTracker.count = 2; + mIntervalStats.interactiveTracker.duration = 111111; + mIntervalStats.nonInteractiveTracker.count = 3; + mIntervalStats.nonInteractiveTracker.duration = 222222; + mIntervalStats.keyguardShownTracker.count = 4; + mIntervalStats.keyguardShownTracker.duration = 333333; + mIntervalStats.keyguardHiddenTracker.count = 5; + mIntervalStats.keyguardHiddenTracker.duration = 4444444; + + if (mIntervalStats.events == null) { + mIntervalStats.events = new EventList(); + } + + for (int i = 0; i < numberOfEvents; i++) { + UsageEvents.Event event = new UsageEvents.Event(); + final int packageInt = ((i / 3) % 7); + event.mPackage = "fake.package.name" + packageInt; //clusters of 3 events from 7 "apps" + if (packageInt == 3) { + // Third app is an instant app + event.mFlags |= UsageEvents.Event.FLAG_IS_PACKAGE_INSTANT_APP; + } else if (packageInt == 2 || packageInt == 4) { + event.mClass = ".fake.class.name" + i % 11; + } + + + event.mTimeStamp = time; + event.mEventType = i % 19; //"random" event type + + switch (event.mEventType) { + case UsageEvents.Event.CONFIGURATION_CHANGE: + //empty config, + event.mConfiguration = new Configuration(); + break; + case UsageEvents.Event.SHORTCUT_INVOCATION: + //"random" shortcut + event.mShortcutId = "shortcut" + (i % 8); + break; + case UsageEvents.Event.STANDBY_BUCKET_CHANGED: + //"random" bucket and reason + event.mBucketAndReason = (((i % 5 + 1) * 10) << 16) & (i % 5 + 1) << 8; + break; + case UsageEvents.Event.NOTIFICATION_INTERRUPTION: + //"random" channel + event.mNotificationChannelId = "channel" + (i % 5); + break; + } + + mIntervalStats.events.insert(event); + mIntervalStats.update(event.mPackage, event.mTimeStamp, event.mEventType); + + time += 23; // Arbitrary progression of time + } + mEndTime = time; + + Configuration config1 = new Configuration(); + config1.fontScale = 3.3f; + config1.mcc = 4; + mIntervalStats.getOrCreateConfigurationStats(config1); + + Configuration config2 = new Configuration(); + config2.mnc = 5; + config2.setLocale(new Locale("en", "US")); + mIntervalStats.getOrCreateConfigurationStats(config2); + + Configuration config3 = new Configuration(); + config3.touchscreen = 6; + config3.keyboard = 7; + mIntervalStats.getOrCreateConfigurationStats(config3); + + Configuration config4 = new Configuration(); + config4.keyboardHidden = 8; + config4.hardKeyboardHidden = 9; + mIntervalStats.getOrCreateConfigurationStats(config4); + + Configuration config5 = new Configuration(); + config5.navigation = 10; + config5.navigationHidden = 11; + mIntervalStats.getOrCreateConfigurationStats(config5); + + Configuration config6 = new Configuration(); + config6.orientation = 12; + //Ignore screen layout, it's determined by locale + mIntervalStats.getOrCreateConfigurationStats(config6); + + Configuration config7 = new Configuration(); + config7.colorMode = 14; + config7.uiMode = 15; + mIntervalStats.getOrCreateConfigurationStats(config7); + + Configuration config8 = new Configuration(); + config8.screenWidthDp = 16; + config8.screenHeightDp = 17; + mIntervalStats.getOrCreateConfigurationStats(config8); + + Configuration config9 = new Configuration(); + config9.smallestScreenWidthDp = 18; + config9.densityDpi = 19; + mIntervalStats.getOrCreateConfigurationStats(config9); + + mIntervalStats.activeConfiguration = config9; + } + + void compareUsageStats(UsageStats us1, UsageStats us2) { + assertEquals(us1.mPackageName, us2.mPackageName); + // mBeginTimeStamp is based on the enclosing IntervalStats, don't bother checking + // mEndTimeStamp is based on the enclosing IntervalStats, don't bother checking + assertEquals(us1.mLastTimeUsed, us2.mLastTimeUsed); + assertEquals(us1.mTotalTimeInForeground, us2.mTotalTimeInForeground); + // mLaunchCount not persisted, so skipped + assertEquals(us1.mAppLaunchCount, us2.mAppLaunchCount); + assertEquals(us1.mLastEvent, us2.mLastEvent); + assertEquals(us1.mChooserCounts, us2.mChooserCounts); + } + + void compareUsageEvent(UsageEvents.Event e1, UsageEvents.Event e2, int debugId) { + assertEquals(e1.mPackage, e2.mPackage, "Usage event " + debugId); + assertEquals(e1.mClass, e2.mClass, "Usage event " + debugId); + assertEquals(e1.mTimeStamp, e2.mTimeStamp, "Usage event " + debugId); + assertEquals(e1.mEventType, e2.mEventType, "Usage event " + debugId); + switch (e1.mEventType) { + case UsageEvents.Event.CONFIGURATION_CHANGE: + assertEquals(e1.mConfiguration, e2.mConfiguration, + "Usage event " + debugId + e2.mConfiguration.toString()); + break; + case UsageEvents.Event.SHORTCUT_INVOCATION: + assertEquals(e1.mShortcutId, e2.mShortcutId, "Usage event " + debugId); + break; + case UsageEvents.Event.STANDBY_BUCKET_CHANGED: + assertEquals(e1.mBucketAndReason, e2.mBucketAndReason, "Usage event " + debugId); + break; + case UsageEvents.Event.NOTIFICATION_INTERRUPTION: + assertEquals(e1.mNotificationChannelId, e2.mNotificationChannelId, + "Usage event " + debugId); + break; + } + assertEquals(e1.mFlags, e2.mFlags); + } + + void compareIntervalStats(IntervalStats stats1, IntervalStats stats2) { + assertEquals(stats1.beginTime, stats2.beginTime); + assertEquals(stats1.endTime, stats2.endTime); + assertEquals(stats1.interactiveTracker.count, stats2.interactiveTracker.count); + assertEquals(stats1.interactiveTracker.duration, stats2.interactiveTracker.duration); + assertEquals(stats1.nonInteractiveTracker.count, stats2.nonInteractiveTracker.count); + assertEquals(stats1.nonInteractiveTracker.duration, stats2.nonInteractiveTracker.duration); + assertEquals(stats1.keyguardShownTracker.count, stats2.keyguardShownTracker.count); + assertEquals(stats1.keyguardShownTracker.duration, stats2.keyguardShownTracker.duration); + assertEquals(stats1.keyguardHiddenTracker.count, stats2.keyguardHiddenTracker.count); + assertEquals(stats1.keyguardHiddenTracker.duration, stats2.keyguardHiddenTracker.duration); + + String[] usageKey1 = stats1.packageStats.keySet().toArray(new String[0]); + String[] usageKey2 = stats2.packageStats.keySet().toArray(new String[0]); + for (int i = 0; i < usageKey1.length; i++) { + UsageStats usageStats1 = stats1.packageStats.get(usageKey1[i]); + UsageStats usageStats2 = stats2.packageStats.get(usageKey2[i]); + compareUsageStats(usageStats1, usageStats2); + } + + assertEquals(stats1.configurations.size(), stats2.configurations.size()); + Configuration[] configSet1 = stats1.configurations.keySet().toArray(new Configuration[0]); + for (int i = 0; i < configSet1.length; i++) { + if (!stats2.configurations.containsKey(configSet1[i])) { + Configuration[] configSet2 = stats2.configurations.keySet().toArray( + new Configuration[0]); + String debugInfo = ""; + for (Configuration c : configSet1) { + debugInfo += c.toString() + "\n"; + } + debugInfo += "\n"; + for (Configuration c : configSet2) { + debugInfo += c.toString() + "\n"; + } + fail("Config " + configSet1[i].toString() + + " not found in deserialized IntervalStat\n" + debugInfo); + } + } + assertEquals(stats1.activeConfiguration, stats2.activeConfiguration); + + assertEquals(stats1.events.size(), stats2.events.size()); + for (int i = 0; i < stats1.events.size(); i++) { + compareUsageEvent(stats1.events.get(i), stats2.events.get(i), i); + } + } + + /** + * Runs the Write Read test. + * Will write the generated IntervalStat to disk, read it from disk and compare the two + */ + void runWriteReadTest(int interval) throws IOException { + mUsageStatsDatabase.putUsageStats(interval, mIntervalStats); + List<IntervalStats> stats = mUsageStatsDatabase.queryUsageStats(interval, 0, mEndTime, + mIntervalStatsVerifier); + + assertEquals(1, stats.size()); + compareIntervalStats(mIntervalStats, stats.get(0)); + } + + /** + * Demonstrate that IntervalStats can be serialized and deserialized from disk without loss of + * relevant data. + */ + @Test + public void testWriteRead() throws IOException { + runWriteReadTest(UsageStatsManager.INTERVAL_DAILY); + runWriteReadTest(UsageStatsManager.INTERVAL_WEEKLY); + runWriteReadTest(UsageStatsManager.INTERVAL_MONTHLY); + runWriteReadTest(UsageStatsManager.INTERVAL_YEARLY); + } + + /** + * Runs the Version Change tests. + * Will write the generated IntervalStat to disk in one version format, "upgrade" to another + * version and read the automatically upgraded files on disk in the new file format. + */ + void runVersionChangeTest(int oldVersion, int newVersion, int interval) throws IOException { + // Write IntervalStats to disk in old version format + UsageStatsDatabase prevDB = new UsageStatsDatabase(mTestDir, oldVersion); + prevDB.init(1); + prevDB.putUsageStats(interval, mIntervalStats); + + // Simulate an upgrade to a new version and read from the disk + UsageStatsDatabase newDB = new UsageStatsDatabase(mTestDir, newVersion); + newDB.init(mEndTime); + List<IntervalStats> stats = newDB.queryUsageStats(interval, 0, mEndTime, + mIntervalStatsVerifier); + + assertEquals(1, stats.size()); + // The written and read IntervalStats should match + compareIntervalStats(mIntervalStats, stats.get(0)); + } + + /** + * Test the version upgrade from 3 to 4 + */ + @Test + public void testVersionUpgradeFrom3to4() throws IOException { + runVersionChangeTest(3, 4, UsageStatsManager.INTERVAL_DAILY); + runVersionChangeTest(3, 4, UsageStatsManager.INTERVAL_WEEKLY); + runVersionChangeTest(3, 4, UsageStatsManager.INTERVAL_MONTHLY); + runVersionChangeTest(3, 4, UsageStatsManager.INTERVAL_YEARLY); + } +} diff --git a/services/tests/servicestests/src/com/android/server/wm/BoundsAnimationControllerTests.java b/services/tests/servicestests/src/com/android/server/wm/BoundsAnimationControllerTests.java index 12be0b3ae6f8..e6e08bb93c8e 100644 --- a/services/tests/servicestests/src/com/android/server/wm/BoundsAnimationControllerTests.java +++ b/services/tests/servicestests/src/com/android/server/wm/BoundsAnimationControllerTests.java @@ -90,7 +90,7 @@ public class BoundsAnimationControllerTests extends WindowTestsBase { private AppTransitionListener mListener; MockAppTransition(Context context) { - super(context, null); + super(context, sWm); } @Override diff --git a/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java index b3303049be39..3dcdd23acf1a 100644 --- a/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java +++ b/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java @@ -24,6 +24,8 @@ import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; import static android.view.Display.DEFAULT_DISPLAY; +import static android.view.DisplayCutout.BOUNDS_POSITION_LEFT; +import static android.view.DisplayCutout.BOUNDS_POSITION_TOP; import static android.view.DisplayCutout.fromBoundingRect; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG; @@ -32,6 +34,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR; import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION; import static com.android.server.wm.WindowContainer.POSITION_TOP; +import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_NORMAL; import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertEquals; @@ -303,7 +306,8 @@ public class DisplayContentTests extends WindowTestsBase { createTapEvent(dm0.widthPixels / 2, dm0.heightPixels / 2, false)); // Check focus is on primary display. - assertEquals(sWm.mCurrentFocus, dc0.findFocusedWindow()); + assertEquals(sWm.mRoot.getTopFocusedDisplayContent().mCurrentFocus, + dc0.findFocusedWindow()); // Tap on secondary display DisplayMetrics dm1 = dc1.getDisplayMetrics(); @@ -313,7 +317,8 @@ public class DisplayContentTests extends WindowTestsBase { createTapEvent(dm1.widthPixels / 2, dm1.heightPixels / 2, false)); // Check focus is on secondary. - assertEquals(sWm.mCurrentFocus, dc1.findFocusedWindow()); + assertEquals(sWm.mRoot.getTopFocusedDisplayContent().mCurrentFocus, + dc1.findFocusedWindow()); } @Test @@ -321,34 +326,29 @@ public class DisplayContentTests extends WindowTestsBase { // Create a focusable window and check that focus is calculated correctly final WindowState window1 = createWindow(null, TYPE_BASE_APPLICATION, mDisplayContent, "window1"); - assertEquals(window1, sWm.mRoot.computeFocusedWindow()); + updateFocusedWindow(); + assertTrue(window1.isFocused()); + assertEquals(window1, sWm.mRoot.getTopFocusedDisplayContent().mCurrentFocus); // Check that a new display doesn't affect focus final DisplayContent dc = createNewDisplay(); - assertEquals(window1, sWm.mRoot.computeFocusedWindow()); + updateFocusedWindow(); + assertTrue(window1.isFocused()); + assertEquals(window1, sWm.mRoot.getTopFocusedDisplayContent().mCurrentFocus); // Add a window to the second display, and it should be focused final WindowState window2 = createWindow(null, TYPE_BASE_APPLICATION, dc, "window2"); - assertEquals(window2, sWm.mRoot.computeFocusedWindow()); + updateFocusedWindow(); + assertTrue(window1.isFocused()); + assertTrue(window2.isFocused()); + assertEquals(window2, sWm.mRoot.getTopFocusedDisplayContent().mCurrentFocus); // Move the first window to the to including parents, and make sure focus is updated window1.getParent().positionChildAt(POSITION_TOP, window1, true); - assertEquals(window1, sWm.mRoot.computeFocusedWindow()); - } - - @Test - public void testKeyguard_preventsSecondaryDisplayFocus() throws Exception { - final WindowState keyguard = createWindow(null, TYPE_STATUS_BAR, - sWm.getDefaultDisplayContentLocked(), "keyguard"); - assertEquals(keyguard, sWm.mRoot.computeFocusedWindow()); - - // Add a window to a second display, and it should be focused - final DisplayContent dc = createNewDisplay(); - final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, dc, "win"); - assertEquals(win, sWm.mRoot.computeFocusedWindow()); - - mWmRule.getWindowManagerPolicy().keyguardShowingAndNotOccluded = true; - assertEquals(keyguard, sWm.mRoot.computeFocusedWindow()); + updateFocusedWindow(); + assertTrue(window1.isFocused()); + assertTrue(window2.isFocused()); + assertEquals(window1, sWm.mRoot.getTopFocusedDisplayContent().mCurrentFocus); } /** @@ -454,7 +454,7 @@ public class DisplayContentTests extends WindowTestsBase { dc.mInitialDisplayHeight = 400; Rect r = new Rect(80, 0, 120, 10); final DisplayCutout cutout = new WmDisplayCutout( - fromBoundingRect(r.left, r.top, r.right, r.bottom), null) + fromBoundingRect(r.left, r.top, r.right, r.bottom, BOUNDS_POSITION_TOP), null) .computeSafeInsets(200, 400).getDisplayCutout(); dc.mInitialDisplayCutout = cutout; @@ -484,7 +484,7 @@ public class DisplayContentTests extends WindowTestsBase { final Rect r1 = new Rect(left, top, right, bottom); final DisplayCutout cutout = new WmDisplayCutout( - fromBoundingRect(r1.left, r1.top, r1.right, r1.bottom), null) + fromBoundingRect(r1.left, r1.top, r1.right, r1.bottom, BOUNDS_POSITION_TOP), null) .computeSafeInsets(displayWidth, displayHeight).getDisplayCutout(); dc.mInitialDisplayCutout = cutout; @@ -501,7 +501,7 @@ public class DisplayContentTests extends WindowTestsBase { // | | ------------- final Rect r = new Rect(top, left, bottom, right); assertEquals(new WmDisplayCutout( - fromBoundingRect(r.left, r.top, r.right, r.bottom), null) + fromBoundingRect(r.left, r.top, r.right, r.bottom, BOUNDS_POSITION_LEFT), null) .computeSafeInsets(displayHeight, displayWidth) .getDisplayCutout(), dc.getDisplayInfo().displayCutout); } @@ -590,6 +590,12 @@ public class DisplayContentTests extends WindowTestsBase { assertEquals(displayContent.mBaseDisplayDensity, expectedBaseDensity); } + private void updateFocusedWindow() { + synchronized (sWm.mWindowMap) { + sWm.updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL, false); + } + } + /** * Create DisplayContent that does not update display base/initial values from device to keep * the values set by test. diff --git a/services/tests/servicestests/src/com/android/server/wm/RecentsAnimationControllerTest.java b/services/tests/servicestests/src/com/android/server/wm/RecentsAnimationControllerTest.java index aaa00452204b..088e22972cc9 100644 --- a/services/tests/servicestests/src/com/android/server/wm/RecentsAnimationControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/wm/RecentsAnimationControllerTest.java @@ -40,6 +40,7 @@ import android.util.SparseBooleanArray; import android.view.IRecentsAnimationRunner; import android.view.SurfaceControl; +import androidx.test.filters.FlakyTest; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; @@ -114,6 +115,7 @@ public class RecentsAnimationControllerTest extends WindowTestsBase { } @Test + @FlakyTest(bugId = 117117823) public void testIncludedApps_expectTargetAndVisible() throws Exception { sWm.setRecentsAnimationController(mController); final AppWindowToken homeAppWindow = createAppWindowToken(mDisplayContent, diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowFrameTests.java b/services/tests/servicestests/src/com/android/server/wm/WindowFrameTests.java index 088672973524..7cd131420049 100644 --- a/services/tests/servicestests/src/com/android/server/wm/WindowFrameTests.java +++ b/services/tests/servicestests/src/com/android/server/wm/WindowFrameTests.java @@ -16,12 +16,12 @@ package com.android.server.wm; +import static android.view.DisplayCutout.BOUNDS_POSITION_TOP; import static android.view.DisplayCutout.fromBoundingRect; import static android.view.WindowManager.LayoutParams.FILL_PARENT; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; import android.app.ActivityManager.TaskDescription; import android.content.res.Configuration; @@ -474,7 +474,8 @@ public class WindowFrameTests extends WindowTestsBase { final Rect pf = new Rect(0, 0, 1000, 2000); // Create a display cutout of size 50x50, aligned top-center final WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets( - fromBoundingRect(500, 0, 550, 50), pf.width(), pf.height()); + fromBoundingRect(500, 0, 550, 50, BOUNDS_POSITION_TOP), + pf.width(), pf.height()); final WindowFrames windowFrames = w.getWindowFrames(); windowFrames.setFrames(pf, pf, pf, pf, pf, pf, pf, pf); @@ -499,7 +500,8 @@ public class WindowFrameTests extends WindowTestsBase { final Rect pf = new Rect(0, -500, 1000, 1500); // Create a display cutout of size 50x50, aligned top-center final WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets( - fromBoundingRect(500, 0, 550, 50), pf.width(), pf.height()); + fromBoundingRect(500, 0, 550, 50, BOUNDS_POSITION_TOP), + pf.width(), pf.height()); final WindowFrames windowFrames = w.getWindowFrames(); windowFrames.setFrames(pf, pf, pf, pf, pf, pf, pf, pf); diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowStateTests.java b/services/tests/servicestests/src/com/android/server/wm/WindowStateTests.java index b7cc9ce7a619..3637baf13c72 100644 --- a/services/tests/servicestests/src/com/android/server/wm/WindowStateTests.java +++ b/services/tests/servicestests/src/com/android/server/wm/WindowStateTests.java @@ -49,6 +49,7 @@ import static org.mockito.Mockito.reset; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; +import android.graphics.Insets; import android.graphics.Matrix; import android.graphics.Rect; import android.platform.test.annotations.Presubmit; @@ -404,8 +405,12 @@ public class WindowStateTests extends WindowTestsBase { WindowFrames wf = app.getWindowFrames(); wf.mParentFrame.set(7, 10, 185, 380); wf.mDisplayFrame.set(wf.mParentFrame); - final DisplayCutout cutout = new DisplayCutout(new Rect(0, 15, 0, 22), - Arrays.asList(new Rect(95, 0, 105, 15), new Rect(95, 378, 105, 400))); + final DisplayCutout cutout = new DisplayCutout( + Insets.of(0, 15, 0, 22) /* safeInset */, + null /* boundLeft */, + new Rect(95, 0, 105, 15), + null /* boundRight */, + new Rect(95, 378, 105, 400)); wf.setDisplayCutout(new WmDisplayCutout(cutout, new Size(200, 400))); app.computeFrameLw(); diff --git a/services/tests/servicestests/src/com/android/server/wm/utils/DisplayRotationUtilTest.java b/services/tests/servicestests/src/com/android/server/wm/utils/DisplayRotationUtilTest.java new file mode 100644 index 000000000000..ba8869b1692d --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/wm/utils/DisplayRotationUtilTest.java @@ -0,0 +1,166 @@ +/* + * 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.wm.utils; + +import static android.view.DisplayCutout.BOUNDS_POSITION_BOTTOM; +import static android.view.DisplayCutout.BOUNDS_POSITION_LEFT; +import static android.view.DisplayCutout.BOUNDS_POSITION_RIGHT; +import static android.view.DisplayCutout.BOUNDS_POSITION_TOP; +import static android.view.Surface.ROTATION_0; +import static android.view.Surface.ROTATION_180; +import static android.view.Surface.ROTATION_270; +import static android.view.Surface.ROTATION_90; +import static com.android.server.wm.utils.DisplayRotationUtil.getBoundIndexFromRotation; + +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertThat; + +import android.graphics.Rect; +import android.platform.test.annotations.Presubmit; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + + + +/** + * Tests for {@link DisplayRotationUtil} + * + * Run with: atest DisplayRotationUtilTest + */ +@RunWith(AndroidJUnit4.class) +@SmallTest +@Presubmit +public class DisplayRotationUtilTest { + private static Rect ZERO_RECT = new Rect(); + + @Test + public void testGetBoundIndexFromRotation_rot0() { + assertThat(getBoundIndexFromRotation(BOUNDS_POSITION_LEFT, ROTATION_0), + equalTo(BOUNDS_POSITION_LEFT)); + assertThat(getBoundIndexFromRotation(BOUNDS_POSITION_TOP, ROTATION_0), + equalTo(BOUNDS_POSITION_TOP)); + assertThat(getBoundIndexFromRotation(BOUNDS_POSITION_RIGHT, ROTATION_0), + equalTo(BOUNDS_POSITION_RIGHT)); + assertThat(getBoundIndexFromRotation(BOUNDS_POSITION_BOTTOM, ROTATION_0), + equalTo(BOUNDS_POSITION_BOTTOM)); + } + + @Test + public void testGetBoundIndexFromRotation_rot90() { + assertThat(getBoundIndexFromRotation(BOUNDS_POSITION_LEFT, ROTATION_90), + equalTo(BOUNDS_POSITION_BOTTOM)); + assertThat(getBoundIndexFromRotation(BOUNDS_POSITION_TOP, ROTATION_90), + equalTo(BOUNDS_POSITION_LEFT)); + assertThat(getBoundIndexFromRotation(BOUNDS_POSITION_RIGHT, ROTATION_90), + equalTo(BOUNDS_POSITION_TOP)); + assertThat(getBoundIndexFromRotation(BOUNDS_POSITION_BOTTOM, ROTATION_90), + equalTo(BOUNDS_POSITION_RIGHT)); + } + + @Test + public void testGetBoundIndexFromRotation_rot180() { + assertThat(getBoundIndexFromRotation(BOUNDS_POSITION_LEFT, ROTATION_180), + equalTo(BOUNDS_POSITION_RIGHT)); + assertThat(getBoundIndexFromRotation(BOUNDS_POSITION_TOP, ROTATION_180), + equalTo(BOUNDS_POSITION_BOTTOM)); + assertThat(getBoundIndexFromRotation(BOUNDS_POSITION_RIGHT, ROTATION_180), + equalTo(BOUNDS_POSITION_LEFT)); + assertThat(getBoundIndexFromRotation(BOUNDS_POSITION_BOTTOM, ROTATION_180), + equalTo(BOUNDS_POSITION_TOP)); + } + + @Test + public void testGetBoundIndexFromRotation_rot270() { + assertThat(getBoundIndexFromRotation(BOUNDS_POSITION_LEFT, ROTATION_270), + equalTo(BOUNDS_POSITION_TOP)); + assertThat(getBoundIndexFromRotation(BOUNDS_POSITION_TOP, ROTATION_270), + equalTo(BOUNDS_POSITION_RIGHT)); + assertThat(getBoundIndexFromRotation(BOUNDS_POSITION_RIGHT, ROTATION_270), + equalTo(BOUNDS_POSITION_BOTTOM)); + assertThat(getBoundIndexFromRotation(BOUNDS_POSITION_BOTTOM, ROTATION_270), + equalTo(BOUNDS_POSITION_LEFT)); + + } + + @Test + public void testGetRotatedBounds_top_rot0() { + DisplayRotationUtil util = new DisplayRotationUtil(); + Rect[] bounds = new Rect[] { ZERO_RECT, new Rect(50,0,150,10), ZERO_RECT, ZERO_RECT }; + assertThat(util.getRotatedBounds(bounds, ROTATION_0, 200, 300), + equalTo(bounds)); + } + + @Test + public void testGetRotatedBounds_top_rot90() { + DisplayRotationUtil util = new DisplayRotationUtil(); + Rect[] bounds = new Rect[] { ZERO_RECT, new Rect(50,0,150,10), ZERO_RECT, ZERO_RECT }; + assertThat(util.getRotatedBounds(bounds, ROTATION_90, 200, 300), + equalTo(new Rect[] { new Rect(0, 50, 10, 150), ZERO_RECT, ZERO_RECT, ZERO_RECT })); + } + + @Test + public void testGetRotatedBounds_top_rot180() { + DisplayRotationUtil util = new DisplayRotationUtil(); + Rect[] bounds = new Rect[] { ZERO_RECT, new Rect(50,0,150,10), ZERO_RECT, ZERO_RECT }; + assertThat(util.getRotatedBounds(bounds, ROTATION_180, 200, 300), + equalTo(new Rect[] { ZERO_RECT, ZERO_RECT, ZERO_RECT, new Rect(50, 290, 150, 300) })); + } + + @Test + public void testGetRotatedBounds_top_rot270() { + DisplayRotationUtil util = new DisplayRotationUtil(); + Rect[] bounds = new Rect[] { ZERO_RECT, new Rect(50,0,150,10), ZERO_RECT, ZERO_RECT }; + assertThat(util.getRotatedBounds(bounds, ROTATION_270, 200, 300), + equalTo(new Rect[] { ZERO_RECT, ZERO_RECT, new Rect(290, 50, 300, 150), ZERO_RECT })); + } + + @Test + public void testGetRotatedBounds_left_rot0() { + DisplayRotationUtil util = new DisplayRotationUtil(); + Rect[] bounds = new Rect[] { new Rect(0, 50, 10, 150), ZERO_RECT, ZERO_RECT, ZERO_RECT }; + assertThat(util.getRotatedBounds(bounds, ROTATION_0, 300, 200), + equalTo(bounds)); + } + + @Test + public void testGetRotatedBounds_left_rot90() { + DisplayRotationUtil util = new DisplayRotationUtil(); + Rect[] bounds = new Rect[] { new Rect(0, 50, 10, 150), ZERO_RECT, ZERO_RECT, ZERO_RECT }; + assertThat(util.getRotatedBounds(bounds, ROTATION_90, 300, 200), + equalTo(new Rect[]{ ZERO_RECT, ZERO_RECT, ZERO_RECT, new Rect(50, 290, 150, 300) })); + } + + @Test + public void testGetRotatedBounds_left_rot180() { + DisplayRotationUtil util = new DisplayRotationUtil(); + Rect[] bounds = new Rect[] { new Rect(0, 50, 10, 150), ZERO_RECT, ZERO_RECT, ZERO_RECT }; + assertThat(util.getRotatedBounds(bounds, ROTATION_180, 300, 200), + equalTo(new Rect[]{ ZERO_RECT, ZERO_RECT, new Rect(290, 50, 300, 150), ZERO_RECT })); + } + + @Test + public void testGetRotatedBounds_left_rot270() { + DisplayRotationUtil util = new DisplayRotationUtil(); + Rect[] bounds = new Rect[] { new Rect(0, 50, 10, 150), ZERO_RECT, ZERO_RECT, ZERO_RECT }; + assertThat(util.getRotatedBounds(bounds, ROTATION_270, 300, 200), + equalTo(new Rect[]{ ZERO_RECT, new Rect(50, 0, 150, 10), ZERO_RECT, ZERO_RECT })); + } +} diff --git a/services/tests/servicestests/src/com/android/server/wm/utils/WmDisplayCutoutTest.java b/services/tests/servicestests/src/com/android/server/wm/utils/WmDisplayCutoutTest.java index 9ce3dca7e096..c5e35e7304c4 100644 --- a/services/tests/servicestests/src/com/android/server/wm/utils/WmDisplayCutoutTest.java +++ b/services/tests/servicestests/src/com/android/server/wm/utils/WmDisplayCutoutTest.java @@ -18,11 +18,19 @@ package com.android.server.wm.utils; import static android.view.DisplayCutout.NO_CUTOUT; +import static android.view.DisplayCutout.BOUNDS_POSITION_BOTTOM; +import static android.view.DisplayCutout.BOUNDS_POSITION_LEFT; +import static android.view.DisplayCutout.BOUNDS_POSITION_RIGHT; +import static android.view.DisplayCutout.BOUNDS_POSITION_TOP; import static android.view.DisplayCutout.fromBoundingRect; +import static org.hamcrest.Matchers.equalTo; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertThat; + +import android.graphics.Insets; import android.graphics.Rect; import android.platform.test.annotations.Presubmit; import android.util.Size; @@ -45,15 +53,17 @@ import java.util.Arrays; @SmallTest @Presubmit public class WmDisplayCutoutTest { + private static final Rect ZERO_RECT = new Rect(); private final DisplayCutout mCutoutTop = new DisplayCutout( - new Rect(0, 100, 0, 0), - Arrays.asList(new Rect(50, 0, 75, 100))); + Insets.of(0, 100, 0, 0), + null /* boundLeft */, new Rect(50, 0, 75, 100) /* boundTop */, + null /* boundRight */, null /* boundBottom */); @Test public void calculateRelativeTo_top() { WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets( - fromBoundingRect(0, 0, 100, 20), 200, 400) + fromBoundingRect(0, 0, 100, 20, BOUNDS_POSITION_TOP), 200, 400) .calculateRelativeTo(new Rect(5, 5, 95, 195)); assertEquals(new Rect(0, 15, 0, 0), cutout.getDisplayCutout().getSafeInsets()); @@ -62,7 +72,7 @@ public class WmDisplayCutoutTest { @Test public void calculateRelativeTo_left() { WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets( - fromBoundingRect(0, 0, 20, 100), 400, 200) + fromBoundingRect(0, 0, 20, 100, BOUNDS_POSITION_LEFT), 400, 200) .calculateRelativeTo(new Rect(5, 5, 195, 95)); assertEquals(new Rect(15, 0, 0, 0), cutout.getDisplayCutout().getSafeInsets()); @@ -71,7 +81,7 @@ public class WmDisplayCutoutTest { @Test public void calculateRelativeTo_bottom() { WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets( - fromBoundingRect(0, 180, 100, 200), 100, 200) + fromBoundingRect(0, 180, 100, 200, BOUNDS_POSITION_BOTTOM), 100, 200) .calculateRelativeTo(new Rect(5, 5, 95, 195)); assertEquals(new Rect(0, 0, 0, 15), cutout.getDisplayCutout().getSafeInsets()); @@ -80,7 +90,7 @@ public class WmDisplayCutoutTest { @Test public void calculateRelativeTo_right() { WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets( - fromBoundingRect(180, 0, 200, 100), 200, 100) + fromBoundingRect(180, 0, 200, 100, BOUNDS_POSITION_RIGHT), 200, 100) .calculateRelativeTo(new Rect(5, 5, 195, 95)); assertEquals(new Rect(0, 0, 15, 0), cutout.getDisplayCutout().getSafeInsets()); @@ -89,16 +99,17 @@ public class WmDisplayCutoutTest { @Test public void calculateRelativeTo_bounds() { WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets( - fromBoundingRect(0, 0, 100, 20), 200, 400) + fromBoundingRect(0, 0, 100, 20, BOUNDS_POSITION_TOP), 200, 400) .calculateRelativeTo(new Rect(5, 10, 95, 180)); - assertEquals(new Rect(-5, -10, 95, 10), cutout.getDisplayCutout().getBounds().getBounds()); + assertThat(cutout.getDisplayCutout().getBoundingRectTop(), + equalTo(new Rect(-5, -10, 95, 10))); } @Test public void computeSafeInsets_top() { WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets( - fromBoundingRect(0, 0, 100, 20), 200, 400); + fromBoundingRect(0, 0, 100, 20, BOUNDS_POSITION_TOP), 200, 400); assertEquals(new Rect(0, 20, 0, 0), cutout.getDisplayCutout().getSafeInsets()); } @@ -106,7 +117,7 @@ public class WmDisplayCutoutTest { @Test public void computeSafeInsets_left() { WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets( - fromBoundingRect(0, 0, 20, 100), 400, 200); + fromBoundingRect(0, 0, 20, 100, BOUNDS_POSITION_LEFT), 400, 200); assertEquals(new Rect(20, 0, 0, 0), cutout.getDisplayCutout().getSafeInsets()); } @@ -114,7 +125,7 @@ public class WmDisplayCutoutTest { @Test public void computeSafeInsets_bottom() { WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets( - fromBoundingRect(0, 180, 100, 200), 100, 200); + fromBoundingRect(0, 180, 100, 200, BOUNDS_POSITION_BOTTOM), 100, 200); assertEquals(new Rect(0, 0, 0, 20), cutout.getDisplayCutout().getSafeInsets()); } @@ -122,7 +133,7 @@ public class WmDisplayCutoutTest { @Test public void computeSafeInsets_right() { WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets( - fromBoundingRect(180, 0, 200, 100), 200, 100); + fromBoundingRect(180, 0, 200, 100, BOUNDS_POSITION_RIGHT), 200, 100); assertEquals(new Rect(0, 0, 20, 0), cutout.getDisplayCutout().getSafeInsets()); } @@ -132,8 +143,7 @@ public class WmDisplayCutoutTest { DisplayCutout cutout = WmDisplayCutout.computeSafeInsets(mCutoutTop, 1000, 2000).getDisplayCutout(); - assertEquals(mCutoutTop.getBounds().getBounds(), - cutout.getBounds().getBounds()); + assertEquals(mCutoutTop.getBoundingRects(), cutout.getBoundingRects()); } @Test diff --git a/services/tests/uiservicestests/Android.mk b/services/tests/uiservicestests/Android.mk index 840517954a0a..f3f43558115b 100644 --- a/services/tests/uiservicestests/Android.mk +++ b/services/tests/uiservicestests/Android.mk @@ -45,6 +45,7 @@ LOCAL_JNI_SHARED_LIBRARIES := \ libbacktrace \ libbase \ libbinder \ + libbinderthreadstate \ libc++ \ libcutils \ liblog \ diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index 45d2fa2d16a4..4e007c2d9929 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -16,6 +16,8 @@ package com.android.server.notification; +import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND; +import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE; import static android.app.Notification.FLAG_FOREGROUND_SERVICE; import static android.app.NotificationManager.EXTRA_BLOCKED_STATE; import static android.app.NotificationManager.IMPORTANCE_HIGH; @@ -74,6 +76,7 @@ import android.app.Notification.MessagingStyle.Message; import android.app.NotificationChannel; import android.app.NotificationChannelGroup; import android.app.NotificationManager; +import android.app.ITransientNotification; import android.app.IUriGrantsManager; import android.app.admin.DevicePolicyManagerInternal; import android.app.usage.UsageStatsManagerInternal; @@ -118,12 +121,14 @@ import android.util.Log; import com.android.internal.R; import com.android.internal.statusbar.NotificationVisibility; import com.android.server.LocalServices; +import com.android.server.SystemService; import com.android.server.UiServiceTestCase; import com.android.server.lights.Light; import com.android.server.lights.LightsManager; import com.android.server.notification.NotificationManagerService.NotificationAssistants; import com.android.server.notification.NotificationManagerService.NotificationListeners; import com.android.server.uri.UriGrantsManagerInternal; +import com.android.server.wm.WindowManagerInternal; import org.junit.After; import org.junit.Before; @@ -160,6 +165,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { private IPackageManager mPackageManager; @Mock private PackageManager mPackageManagerClient; + @Mock + private WindowManagerInternal mWindowManagerInternal; private TestableContext mContext = spy(getContext()); private final String PKG = mContext.getPackageName(); private TestableLooper mTestableLooper; @@ -238,6 +245,16 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } } + private class TestableToastCallback extends ITransientNotification.Stub { + @Override + public void show(IBinder windowToken) { + } + + @Override + public void hide() { + } + } + @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); @@ -249,6 +266,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { LocalServices.removeServiceForTest(UriGrantsManagerInternal.class); LocalServices.addService(UriGrantsManagerInternal.class, mUgmInternal); + LocalServices.removeServiceForTest(WindowManagerInternal.class); + LocalServices.addService(WindowManagerInternal.class, mWindowManagerInternal); mService = new TestableNotificationManagerService(mContext); @@ -302,6 +321,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mGroupHelper, mAm, mAppUsageStats, mock(DevicePolicyManagerInternal.class), mUgm, mUgmInternal, mAppOpsManager); + mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY); } catch (SecurityException e) { if (!e.getMessage().contains("Permission Denial: not allowed to send broadcast")) { throw e; @@ -2192,6 +2212,26 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test + public void testDontAutogroupIfCritical() throws Exception { + NotificationRecord r = generateNotificationRecord(mTestNotificationChannel, 0, null, false); + r.setCriticality(CriticalNotificationExtractor.CRITICAL_LOW); + mService.addEnqueuedNotification(r); + NotificationManagerService.PostNotificationRunnable runnable = + mService.new PostNotificationRunnable(r.getKey()); + runnable.run(); + + r = generateNotificationRecord(mTestNotificationChannel, 1, null, false); + r.setCriticality(CriticalNotificationExtractor.CRITICAL); + runnable = mService.new PostNotificationRunnable(r.getKey()); + mService.addEnqueuedNotification(r); + + runnable.run(); + waitForIdle(); + + verify(mGroupHelper, never()).onNotificationPosted(any(), anyBoolean()); + } + + @Test public void testNoFakeColorizedPermission() throws Exception { when(mPackageManagerClient.checkPermission(any(), any())).thenReturn(PERMISSION_DENIED); Notification.Builder nb = new Notification.Builder(mContext, @@ -3428,17 +3468,14 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test - public void testResolveNotificationUid_sameAppWrongPkg() throws Exception { + public void testResolveNotificationUid_sameAppDiffPackage() throws Exception { ApplicationInfo info = new ApplicationInfo(); info.uid = Binder.getCallingUid(); - when(mPackageManager.getApplicationInfo(anyString(), anyInt(), anyInt())).thenReturn(info); + when(mPackageManager.getApplicationInfo(anyString(), anyInt(), eq(0))).thenReturn(info); - try { - mService.resolveNotificationUid("caller", "other", info.uid, 0); - fail("Incorrect pkg didn't throw security exception"); - } catch (SecurityException e) { - // yay - } + int actualUid = mService.resolveNotificationUid("caller", "callerAlso", info.uid, 0); + + assertEquals(info.uid, actualUid); } @Test @@ -3531,4 +3568,93 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { assertEquals(0, captor.getValue().getNotification().flags); } + + @Test + public void testAllowForegroundToasts() throws Exception { + final String testPackage = "testPackageName"; + assertEquals(0, mService.mToastQueue.size()); + mService.isSystemUid = false; + + // package is not suspended + when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid))) + .thenReturn(false); + + // notifications from this package are blocked by the user + mService.setPreferencesHelper(mPreferencesHelper); + when(mPreferencesHelper.getImportance(testPackage, mUid)).thenReturn(IMPORTANCE_NONE); + + // this app is in the foreground + when(mActivityManager.getUidImportance(mUid)).thenReturn(IMPORTANCE_FOREGROUND); + + // enqueue toast -> toast should still enqueue + ((INotificationManager)mService.mService).enqueueToast(testPackage, + new TestableToastCallback(), 2000, 0); + assertEquals(1, mService.mToastQueue.size()); + } + + @Test + public void testDisallowToastsFromSuspendedPackages() throws Exception { + final String testPackage = "testPackageName"; + assertEquals(0, mService.mToastQueue.size()); + mService.isSystemUid = false; + + // package is suspended + when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid))) + .thenReturn(true); + + // notifications from this package are NOT blocked by the user + mService.setPreferencesHelper(mPreferencesHelper); + when(mPreferencesHelper.getImportance(testPackage, mUid)).thenReturn(IMPORTANCE_LOW); + + // enqueue toast -> no toasts enqueued + ((INotificationManager)mService.mService).enqueueToast(testPackage, + new TestableToastCallback(), 2000, 0); + assertEquals(0, mService.mToastQueue.size()); + } + + @Test + public void testDisallowToastsFromBlockedApps() throws Exception { + final String testPackage = "testPackageName"; + assertEquals(0, mService.mToastQueue.size()); + mService.isSystemUid = false; + + // package is not suspended + when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid))) + .thenReturn(false); + + // notifications from this package are blocked by the user + mService.setPreferencesHelper(mPreferencesHelper); + when(mPreferencesHelper.getImportance(testPackage, mUid)).thenReturn(IMPORTANCE_NONE); + + // this app is NOT in the foreground + when(mActivityManager.getUidImportance(mUid)).thenReturn(IMPORTANCE_GONE); + + // enqueue toast -> no toasts enqueued + ((INotificationManager)mService.mService).enqueueToast(testPackage, + new TestableToastCallback(), 2000, 0); + assertEquals(0, mService.mToastQueue.size()); + } + + @Test + public void testAlwaysAllowSystemToasts() throws Exception { + final String testPackage = "testPackageName"; + assertEquals(0, mService.mToastQueue.size()); + mService.isSystemUid = true; + + // package is suspended + when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid))) + .thenReturn(true); + + // notifications from this package ARE blocked by the user + mService.setPreferencesHelper(mPreferencesHelper); + when(mPreferencesHelper.getImportance(testPackage, mUid)).thenReturn(IMPORTANCE_NONE); + + // this app is NOT in the foreground + when(mActivityManager.getUidImportance(mUid)).thenReturn(IMPORTANCE_GONE); + + // enqueue toast -> system toast can still be enqueued + ((INotificationManager)mService.mService).enqueueToast(testPackage, + new TestableToastCallback(), 2000, 0); + assertEquals(1, mService.mToastQueue.size()); + } } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java index 702161e48b75..13f3e5e7a3ad 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java @@ -1030,6 +1030,14 @@ public class ZenModeHelperTest extends UiServiceTestCase { assertEquals(1, mZenModeHelperSpy.mConditions.mSubscriptions.size()); } + @Test + public void testEmptyDefaultRulesMap() { + ZenModeConfig config = new ZenModeConfig(); + config.automaticRules = new ArrayMap<>(); + mZenModeHelperSpy.mConfig = config; + mZenModeHelperSpy.updateDefaultZenRules(); // shouldn't throw null pointer + } + private void setupZenConfig() { mZenModeHelperSpy.mZenMode = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS; mZenModeHelperSpy.mConfig.allowAlarms = false; diff --git a/services/usage/java/com/android/server/usage/IntervalStats.java b/services/usage/java/com/android/server/usage/IntervalStats.java index 4b7e21f2d536..db9972f96b21 100644 --- a/services/usage/java/com/android/server/usage/IntervalStats.java +++ b/services/usage/java/com/android/server/usage/IntervalStats.java @@ -24,7 +24,9 @@ import android.app.usage.UsageStats; import android.content.res.Configuration; import android.util.ArrayMap; import android.util.ArraySet; +import android.util.proto.ProtoInputStream; +import java.io.IOException; import java.util.List; import com.android.internal.annotations.VisibleForTesting; @@ -46,7 +48,7 @@ public class IntervalStats { // keep hundreds of strings that have the same contents. We will read the string // and only keep it if it's not in the cache. The GC will take care of the // strings that had identical copies in the cache. - private final ArraySet<String> mStringCache = new ArraySet<>(); + public final ArraySet<String> mStringCache = new ArraySet<>(); public static final class EventTracker { public long curStartTime; @@ -129,6 +131,90 @@ public class IntervalStats { return event; } + /** + * Builds a UsageEvents.Event from a proto, but does not add it internally. + * Built here to take advantage of the cached String Refs + */ + UsageEvents.Event buildEvent(ProtoInputStream parser, List<String> stringPool) + throws IOException { + final UsageEvents.Event event = new UsageEvents.Event(); + while (true) { + switch (parser.nextField()) { + case (int) IntervalStatsProto.Event.PACKAGE: + event.mPackage = getCachedStringRef( + parser.readString(IntervalStatsProto.Event.PACKAGE)); + break; + case (int) IntervalStatsProto.Event.PACKAGE_INDEX: + event.mPackage = getCachedStringRef(stringPool.get( + parser.readInt(IntervalStatsProto.Event.PACKAGE_INDEX) - 1)); + break; + case (int) IntervalStatsProto.Event.CLASS: + event.mClass = getCachedStringRef( + parser.readString(IntervalStatsProto.Event.CLASS)); + break; + case (int) IntervalStatsProto.Event.CLASS_INDEX: + event.mClass = getCachedStringRef(stringPool.get( + parser.readInt(IntervalStatsProto.Event.CLASS_INDEX) - 1)); + break; + case (int) IntervalStatsProto.Event.TIME_MS: + event.mTimeStamp = beginTime + parser.readLong( + IntervalStatsProto.Event.TIME_MS); + break; + case (int) IntervalStatsProto.Event.FLAGS: + event.mFlags = parser.readInt(IntervalStatsProto.Event.FLAGS); + break; + case (int) IntervalStatsProto.Event.TYPE: + event.mEventType = parser.readInt(IntervalStatsProto.Event.TYPE); + break; + case (int) IntervalStatsProto.Event.CONFIG: + event.mConfiguration = new Configuration(); + event.mConfiguration.readFromProto(parser, IntervalStatsProto.Event.CONFIG); + break; + case (int) IntervalStatsProto.Event.SHORTCUT_ID: + event.mShortcutId = parser.readString( + IntervalStatsProto.Event.SHORTCUT_ID).intern(); + break; + case (int) IntervalStatsProto.Event.STANDBY_BUCKET: + event.mBucketAndReason = parser.readInt( + IntervalStatsProto.Event.STANDBY_BUCKET); + break; + case (int) IntervalStatsProto.Event.NOTIFICATION_CHANNEL: + event.mNotificationChannelId = parser.readString( + IntervalStatsProto.Event.NOTIFICATION_CHANNEL); + break; + case (int) IntervalStatsProto.Event.NOTIFICATION_CHANNEL_INDEX: + event.mNotificationChannelId = getCachedStringRef(stringPool.get( + parser.readInt(IntervalStatsProto.Event.NOTIFICATION_CHANNEL_INDEX) + - 1)); + break; + case ProtoInputStream.NO_MORE_FIELDS: + // Handle default values for certain events types + switch (event.mEventType) { + case UsageEvents.Event.CONFIGURATION_CHANGE: + if (event.mConfiguration == null) { + event.mConfiguration = new Configuration(); + } + break; + case UsageEvents.Event.SHORTCUT_INVOCATION: + if (event.mShortcutId == null) { + event.mShortcutId = ""; + } + break; + case UsageEvents.Event.NOTIFICATION_INTERRUPTION: + if (event.mNotificationChannelId == null) { + event.mNotificationChannelId = ""; + } + break; + } + if (event.mTimeStamp == 0) { + //mTimestamp not set, assume default value 0 plus beginTime + event.mTimeStamp = beginTime; + } + return event; + } + } + } + private boolean isStatefulEvent(int eventType) { switch (eventType) { case UsageEvents.Event.MOVE_TO_FOREGROUND: @@ -143,8 +229,6 @@ public class IntervalStats { /** * Returns whether the event type is one caused by user visible * interaction. Excludes those that are internally generated. - * @param eventType - * @return */ private boolean isUserVisibleEvent(int eventType) { return eventType != UsageEvents.Event.SYSTEM_INTERACTION @@ -184,6 +268,25 @@ public class IntervalStats { endTime = timeStamp; } + /** + * @hide + */ + @VisibleForTesting + public void addEvent(UsageEvents.Event event) { + if (events == null) { + events = new EventList(); + } + // Cache common use strings + event.mPackage = getCachedStringRef(event.mPackage); + if (event.mClass != null) { + event.mClass = getCachedStringRef(event.mClass); + } + if (event.mEventType == UsageEvents.Event.NOTIFICATION_INTERRUPTION) { + event.mNotificationChannelId = getCachedStringRef(event.mNotificationChannelId); + } + events.insert(event); + } + void updateChooserCounts(String packageName, String category, String action) { UsageStats usageStats = getOrCreateUsageStats(packageName); if (usageStats.mChooserCounts == null) { diff --git a/services/usage/java/com/android/server/usage/UsageStatsDatabase.java b/services/usage/java/com/android/server/usage/UsageStatsDatabase.java index 5ab5dc223d9e..8946d251c61d 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsDatabase.java +++ b/services/usage/java/com/android/server/usage/UsageStatsDatabase.java @@ -17,6 +17,7 @@ package com.android.server.usage; import android.app.usage.TimeSparseArray; +import android.app.usage.UsageEvents; import android.app.usage.UsageStats; import android.app.usage.UsageStatsManager; import android.os.Build; @@ -25,6 +26,10 @@ import android.util.AtomicFile; import android.util.Slog; import android.util.TimeUtils; +import com.android.internal.annotations.VisibleForTesting; + +import libcore.io.IoUtils; + import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.ByteArrayInputStream; @@ -32,18 +37,49 @@ import java.io.ByteArrayOutputStream; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; import java.io.FileReader; import java.io.FileWriter; import java.io.FilenameFilter; +import java.io.InputStream; import java.io.IOException; +import java.io.OutputStream; +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; import java.util.ArrayList; import java.util.List; /** - * Provides an interface to query for UsageStat data from an XML database. + * Provides an interface to query for UsageStat data from a Protocol Buffer database. + * + * Prior to version 4, UsageStatsDatabase used XML to store Usage Stats data to disk. + * When the UsageStatsDatabase version is upgraded, the files on disk are migrated to the new + * version on init. The steps of migration are as follows: + * 1) Check if version upgrade breadcrumb exists on disk, if so skip to step 4. + * 2) Copy current files to versioned backup files. + * 3) Write a temporary breadcrumb file with some info about the backed up files. + * 4) Deserialize a versioned backup file using the info written to the breadcrumb for the + * correct deserialization methodology. + * 5) Reserialize the data read from the file with the new version format and replace the old files + * 6) Repeat Step 3 and 4 for each versioned backup file matching the breadcrumb file. + * 7) Update the version file with the new version and build fingerprint. + * 8) Delete the versioned backup files (unless flagged to be kept). + * 9) Delete the breadcrumb file. + * + * Performing the upgrade steps in this order, protects against unexpected shutdowns mid upgrade + * + * A versioned backup file is simply a copy of a Usage Stats file with some extra info embedded in + * the file name. The structure of the versioned backup filename is as followed: + * (original file name).(backup timestamp).(original file version).vak + * + * During the version upgrade process, the new upgraded file will have it's name set to the original + * file name. The backup timestamp helps distinguish between versioned backups if multiple upgrades + * and downgrades have taken place. The original file version denotes how to parse the file. */ public class UsageStatsDatabase { - private static final int CURRENT_VERSION = 3; + private static final int DEFAULT_CURRENT_VERSION = 3; // Current version of the backup schema static final int BACKUP_VERSION = 1; @@ -52,10 +88,16 @@ public class UsageStatsDatabase { // same as UsageStatsBackupHelper.KEY_USAGE_STATS static final String KEY_USAGE_STATS = "usage_stats"; + // Persist versioned backup files. + // Should be false, except when testing new versions + // STOPSHIP: b/111422946 this should be false on launch + static final boolean KEEP_VAK_FILES = true; private static final String TAG = "UsageStatsDatabase"; - private static final boolean DEBUG = UsageStatsService.DEBUG; + // STOPSHIP: b/111422946 this should be boolean DEBUG = UsageStatsService.DEBUG; on launch + private static final boolean DEBUG = true; private static final String BAK_SUFFIX = ".bak"; + private static final String VERSIONED_BAK_SUFFIX = ".vak"; private static final String CHECKED_IN_SUFFIX = UsageStatsXml.CHECKED_IN_SUFFIX; private static final String RETENTION_LEN_KEY = "ro.usagestats.chooser.retention"; private static final int SELECTION_LOG_RETENTION_LEN = @@ -66,21 +108,40 @@ public class UsageStatsDatabase { private final TimeSparseArray<AtomicFile>[] mSortedStatFiles; private final UnixCalendar mCal; private final File mVersionFile; + // If this file exists on disk, UsageStatsDatabase is in the middle of migrating files to a new + // version. If this file exists on boot, the upgrade was interrupted and needs to be picked up + // where it left off. + private final File mUpdateBreadcrumb; + // Current version of the database files schema + private final int mCurrentVersion; private boolean mFirstUpdate; private boolean mNewUpdate; - public UsageStatsDatabase(File dir) { - mIntervalDirs = new File[] { + /** + * UsageStatsDatabase constructor that allows setting the version number. + * This should only be used for testing. + * + * @hide + */ + @VisibleForTesting + public UsageStatsDatabase(File dir, int version) { + mIntervalDirs = new File[]{ new File(dir, "daily"), new File(dir, "weekly"), new File(dir, "monthly"), new File(dir, "yearly"), }; + mCurrentVersion = version; mVersionFile = new File(dir, "version"); + mUpdateBreadcrumb = new File(dir, "breadcrumb"); mSortedStatFiles = new TimeSparseArray[mIntervalDirs.length]; mCal = new UnixCalendar(0); } + public UsageStatsDatabase(File dir) { + this(dir, DEFAULT_CURRENT_VERSION); + } + /** * Initialize any directories required and index what stats are available. */ @@ -154,7 +215,7 @@ public class UsageStatsDatabase { try { IntervalStats stats = new IntervalStats(); for (int i = start; i < fileCount - 1; i++) { - UsageStatsXml.read(files.valueAt(i), stats); + readLocked(files.valueAt(i), stats); if (!checkinAction.checkin(stats)) { return false; } @@ -190,7 +251,7 @@ public class UsageStatsDatabase { final FilenameFilter backupFileFilter = new FilenameFilter() { @Override public boolean accept(File dir, String name) { - return !name.endsWith(BAK_SUFFIX); + return !name.endsWith(BAK_SUFFIX) && !name.endsWith(VERSIONED_BAK_SUFFIX); } }; @@ -210,7 +271,7 @@ public class UsageStatsDatabase { for (File f : files) { final AtomicFile af = new AtomicFile(f); try { - mSortedStatFiles[i].put(UsageStatsXml.parseBeginTime(af), af); + mSortedStatFiles[i].put(parseBeginTime(af), af); } catch (IOException e) { Slog.e(TAG, "failed to index file: " + f, e); } @@ -252,14 +313,32 @@ public class UsageStatsDatabase { version = 0; } - if (version != CURRENT_VERSION) { - Slog.i(TAG, "Upgrading from version " + version + " to " + CURRENT_VERSION); - doUpgradeLocked(version); + if (version != mCurrentVersion) { + Slog.i(TAG, "Upgrading from version " + version + " to " + mCurrentVersion); + if (!mUpdateBreadcrumb.exists()) { + doUpgradeLocked(version); + } else { + Slog.i(TAG, "Version upgrade breadcrumb found on disk! Continuing version upgrade"); + } + + if (mUpdateBreadcrumb.exists()) { + int previousVersion; + long token; + try (BufferedReader reader = new BufferedReader( + new FileReader(mUpdateBreadcrumb))) { + token = Long.parseLong(reader.readLine()); + previousVersion = Integer.parseInt(reader.readLine()); + } catch (NumberFormatException | IOException e) { + Slog.e(TAG, "Failed read version upgrade breadcrumb"); + throw new RuntimeException(e); + } + continueUpgradeLocked(previousVersion, token); + } } - if (version != CURRENT_VERSION || mNewUpdate) { + if (version != mCurrentVersion || mNewUpdate) { try (BufferedWriter writer = new BufferedWriter(new FileWriter(mVersionFile))) { - writer.write(Integer.toString(CURRENT_VERSION)); + writer.write(Integer.toString(mCurrentVersion)); writer.write("\n"); writer.write(currentFingerprint); writer.write("\n"); @@ -269,6 +348,14 @@ public class UsageStatsDatabase { throw new RuntimeException(e); } } + + if (mUpdateBreadcrumb.exists()) { + // Files should be up to date with current version. Clear the version update breadcrumb + if (!KEEP_VAK_FILES) { + removeVersionedBackupFiles(); + } + mUpdateBreadcrumb.delete(); + } } private String getBuildFingerprint() { @@ -290,6 +377,119 @@ public class UsageStatsDatabase { } } } + } else { + // Turn all current usage stats files into versioned backup files + final long token = System.currentTimeMillis(); + final FilenameFilter backupFileFilter = new FilenameFilter() { + @Override + public boolean accept(File dir, String name) { + return !name.endsWith(BAK_SUFFIX) && !name.endsWith(VERSIONED_BAK_SUFFIX); + } + }; + + for (int i = 0; i < mIntervalDirs.length; i++) { + File[] files = mIntervalDirs[i].listFiles(backupFileFilter); + if (files != null) { + for (int j = 0; j < files.length; j++) { + final File backupFile = new File( + files[j].toString() + "." + Long.toString(token) + "." + + Integer.toString(thisVersion) + VERSIONED_BAK_SUFFIX); + if (DEBUG) { + Slog.d(TAG, "Creating versioned (" + Integer.toString(thisVersion) + + ") backup of " + files[j].toString() + + " stat files for interval " + + i + " to " + backupFile.toString()); + } + + try { + // Backup file should not already exist, but make sure it doesn't + Files.deleteIfExists(backupFile.toPath()); + Files.move(files[j].toPath(), backupFile.toPath(), + StandardCopyOption.ATOMIC_MOVE); + } catch (IOException e) { + Slog.e(TAG, "Failed to back up file : " + files[j].toString()); + throw new RuntimeException(e); + } + } + } + } + + // Leave a breadcrumb behind noting that all the usage stats have been copied to a + // versioned backup. + BufferedWriter writer = null; + try { + writer = new BufferedWriter(new FileWriter(mUpdateBreadcrumb)); + writer.write(Long.toString(token)); + writer.write("\n"); + writer.write(Integer.toString(thisVersion)); + writer.write("\n"); + writer.flush(); + } catch (IOException e) { + Slog.e(TAG, "Failed to write new version upgrade breadcrumb"); + throw new RuntimeException(e); + } finally { + IoUtils.closeQuietly(writer); + } + } + } + + private void continueUpgradeLocked(int version, long token) { + // Read all the backed ups for the specified version and rewrite them with the current + // version's file format. + final FilenameFilter versionedBackupFileFilter = new FilenameFilter() { + @Override + public boolean accept(File dir, String name) { + return name.endsWith("." + Long.toString(token) + "." + Integer.toString(version) + + VERSIONED_BAK_SUFFIX); + } + }; + + for (int i = 0; i < mIntervalDirs.length; i++) { + File[] files = mIntervalDirs[i].listFiles(versionedBackupFileFilter); + if (files != null) { + for (int j = 0; j < files.length; j++) { + if (DEBUG) { + Slog.d(TAG, + "Upgrading " + files[j].toString() + " to version (" + + Integer.toString( + mCurrentVersion) + ") for interval " + i); + } + try { + IntervalStats stats = new IntervalStats(); + readLocked(new AtomicFile(files[j]), stats, version); + writeLocked(new AtomicFile(new File(mIntervalDirs[i], + Long.toString(stats.beginTime))), stats, mCurrentVersion); + } catch (IOException e) { + Slog.e(TAG, + "Failed to upgrade versioned backup file : " + files[j].toString()); + throw new RuntimeException(e); + } + } + } + } + } + + private void removeVersionedBackupFiles() { + final FilenameFilter versionedBackupFileFilter = new FilenameFilter() { + @Override + public boolean accept(File dir, String name) { + return name.endsWith(VERSIONED_BAK_SUFFIX); + } + }; + + for (int i = 0; i < mIntervalDirs.length; i++) { + File[] files = mIntervalDirs[i].listFiles(versionedBackupFileFilter); + if (files != null) { + for (int j = 0; j < files.length; j++) { + if (DEBUG) { + Slog.d(TAG, + "Removing " + files[j].toString() + " for interval " + i); + } + if (!files[j].delete()) { + Slog.e(TAG, "Failed to delete file : " + files[j].toString()); + } + } + } } } @@ -357,7 +557,7 @@ public class UsageStatsDatabase { try { final AtomicFile f = mSortedStatFiles[intervalType].valueAt(fileCount - 1); IntervalStats stats = new IntervalStats(); - UsageStatsXml.read(f, stats); + readLocked(f, stats); return stats; } catch (IOException e) { Slog.e(TAG, "Failed to read usage stats file", e); @@ -379,8 +579,8 @@ public class UsageStatsDatabase { * which means you should make a copy of the data before adding it to the * <code>accumulatedResult</code> list. * - * @param stats The {@link IntervalStats} object selected. - * @param mutable Whether or not the data inside the stats object is mutable. + * @param stats The {@link IntervalStats} object selected. + * @param mutable Whether or not the data inside the stats object is mutable. * @param accumulatedResult The list to which to add extracted data. */ void combine(IntervalStats stats, boolean mutable, List<T> accumulatedResult); @@ -443,7 +643,7 @@ public class UsageStatsDatabase { } try { - UsageStatsXml.read(f, stats); + readLocked(f, stats); if (beginTime < stats.endTime) { combiner.combine(stats, false, results); } @@ -523,14 +723,9 @@ public class UsageStatsDatabase { File[] files = dir.listFiles(); if (files != null) { for (File f : files) { - String path = f.getPath(); - if (path.endsWith(BAK_SUFFIX)) { - f = new File(path.substring(0, path.length() - BAK_SUFFIX.length())); - } - long beginTime; try { - beginTime = UsageStatsXml.parseBeginTime(f); + beginTime = parseBeginTime(f); } catch (IOException e) { beginTime = 0; } @@ -542,18 +737,13 @@ public class UsageStatsDatabase { } } - private static void pruneChooserCountsOlderThan(File dir, long expiryTime) { + private void pruneChooserCountsOlderThan(File dir, long expiryTime) { File[] files = dir.listFiles(); if (files != null) { for (File f : files) { - String path = f.getPath(); - if (path.endsWith(BAK_SUFFIX)) { - f = new File(path.substring(0, path.length() - BAK_SUFFIX.length())); - } - long beginTime; try { - beginTime = UsageStatsXml.parseBeginTime(f); + beginTime = parseBeginTime(f); } catch (IOException e) { beginTime = 0; } @@ -562,7 +752,7 @@ public class UsageStatsDatabase { try { final AtomicFile af = new AtomicFile(f); final IntervalStats stats = new IntervalStats(); - UsageStatsXml.read(af, stats); + readLocked(af, stats); final int pkgCount = stats.packageStats.size(); for (int i = 0; i < pkgCount; i++) { UsageStats pkgStats = stats.packageStats.valueAt(i); @@ -570,7 +760,7 @@ public class UsageStatsDatabase { pkgStats.mChooserCounts.clear(); } } - UsageStatsXml.write(af, stats); + writeLocked(af, stats); } catch (IOException e) { Slog.e(TAG, "Failed to delete chooser counts from usage stats file", e); } @@ -579,6 +769,225 @@ public class UsageStatsDatabase { } } + + private static long parseBeginTime(AtomicFile file) throws IOException { + return parseBeginTime(file.getBaseFile()); + } + + private static long parseBeginTime(File file) throws IOException { + String name = file.getName(); + + // Parse out the digits from the the front of the file name + for (int i = 0; i < name.length(); i++) { + final char c = name.charAt(i); + if (c < '0' || c > '9') { + // found first char that is not a digit. + name = name.substring(0, i); + break; + } + } + + try { + return Long.parseLong(name); + } catch (NumberFormatException e) { + throw new IOException(e); + } + } + + private void writeLocked(AtomicFile file, IntervalStats stats) throws IOException { + writeLocked(file, stats, mCurrentVersion); + } + + private static void writeLocked(AtomicFile file, IntervalStats stats, int version) + throws IOException { + FileOutputStream fos = file.startWrite(); + try { + writeLocked(fos, stats, version); + file.finishWrite(fos); + fos = null; + } finally { + // When fos is null (successful write), this will no-op + file.failWrite(fos); + } + } + + private void writeLocked(OutputStream out, IntervalStats stats) throws IOException { + writeLocked(out, stats, mCurrentVersion); + } + + private static void writeLocked(OutputStream out, IntervalStats stats, int version) + throws IOException { + switch (version) { + case 1: + case 2: + case 3: + UsageStatsXml.write(out, stats); + break; + case 4: + UsageStatsProto.write(out, stats); + break; + default: + throw new RuntimeException( + "Unhandled UsageStatsDatabase version: " + Integer.toString(version) + + " on write."); + } + } + + private void readLocked(AtomicFile file, IntervalStats statsOut) throws IOException { + readLocked(file, statsOut, mCurrentVersion); + } + + private static void readLocked(AtomicFile file, IntervalStats statsOut, int version) + throws IOException { + try { + FileInputStream in = file.openRead(); + try { + statsOut.beginTime = parseBeginTime(file); + readLocked(in, statsOut, version); + statsOut.lastTimeSaved = file.getLastModifiedTime(); + } finally { + try { + in.close(); + } catch (IOException e) { + // Empty + } + } + } catch (FileNotFoundException e) { + Slog.e(TAG, "UsageStatsDatabase", e); + throw e; + } + // If old version, don't bother sanity checking + if (version < 4) return; + + // STOPSHIP: b/111422946, b/115429334 + // Everything below this comment is sanity check against the new database version. + // After the new version has soaked for some time the following should removed. + // The goal of this check is to make sure the the ProtoInputStream is properly reading from + // the UsageStats files. + final StringBuilder sb = new StringBuilder(); + final int failureLogLimit = 10; + int failures = 0; + + final int packagesSize = statsOut.packageStats.size(); + for (int i = 0; i < packagesSize; i++) { + final UsageStats stat = statsOut.packageStats.valueAt(i); + if (stat == null) { + // ArrayMap may contain null values, skip them + continue; + } + if (stat.mPackageName.isEmpty()) { + if (failures++ < failureLogLimit) { + sb.append("\nUnexpected empty usage stats package name loaded"); + } + } + if (stat.mBeginTimeStamp > statsOut.endTime) { + if (failures++ < failureLogLimit) { + sb.append("\nUnreasonable usage stats stat begin timestamp "); + sb.append(stat.mBeginTimeStamp); + sb.append(" loaded (beginTime : "); + sb.append(statsOut.beginTime); + sb.append(", endTime : "); + sb.append(statsOut.endTime); + sb.append(")"); + } + } + if (stat.mEndTimeStamp > statsOut.endTime) { + if (failures++ < failureLogLimit) { + sb.append("\nUnreasonable usage stats stat end timestamp "); + sb.append(stat.mEndTimeStamp); + sb.append(" loaded (beginTime : "); + sb.append(statsOut.beginTime); + sb.append(", endTime : "); + sb.append(statsOut.endTime); + sb.append(")"); + } + } + if (stat.mLastTimeUsed > statsOut.endTime) { + if (failures++ < failureLogLimit) { + sb.append("\nUnreasonable usage stats stat last used timestamp "); + sb.append(stat.mLastTimeUsed); + sb.append(" loaded (beginTime : "); + sb.append(statsOut.beginTime); + sb.append(", endTime : "); + sb.append(statsOut.endTime); + sb.append(")"); + } + } + } + + if (statsOut.events != null) { + final int eventSize = statsOut.events.size(); + for (int i = 0; i < eventSize; i++) { + final UsageEvents.Event event = statsOut.events.get(i); + if (event.mPackage.isEmpty()) { + if (failures++ < failureLogLimit) { + sb.append("\nUnexpected empty empty package name loaded"); + } + } + if (event.mTimeStamp < statsOut.beginTime || event.mTimeStamp > statsOut.endTime) { + if (failures++ < failureLogLimit) { + sb.append("\nUnexpected event timestamp "); + sb.append(event.mTimeStamp); + sb.append(" loaded (beginTime : "); + sb.append(statsOut.beginTime); + sb.append(", endTime : "); + sb.append(statsOut.endTime); + sb.append(")"); + } + } + if (event.mEventType < 0 || event.mEventType > UsageEvents.Event.MAX_EVENT_TYPE) { + if (failures++ < failureLogLimit) { + sb.append("\nUnexpected event type "); + sb.append(event.mEventType); + sb.append(" loaded"); + } + } + if ((event.mFlags & ~UsageEvents.Event.VALID_FLAG_BITS) != 0) { + if (failures++ < failureLogLimit) { + sb.append("\nUnexpected event flag bit 0b"); + sb.append(Integer.toBinaryString(event.mFlags)); + sb.append(" loaded"); + } + } + } + } + + if (failures != 0) { + if (failures > failureLogLimit) { + sb.append("\nFailure log limited ("); + sb.append(failures); + sb.append(" total failures found!)"); + } + sb.append("\nError found in:\n"); + sb.append(file.getBaseFile().getAbsolutePath()); + sb.append("\nPlease go to b/115429334 to help root cause this issue"); + Slog.wtf(TAG,sb.toString()); + } + } + + private void readLocked(InputStream in, IntervalStats statsOut) throws IOException { + readLocked(in, statsOut, mCurrentVersion); + } + + private static void readLocked(InputStream in, IntervalStats statsOut, int version) + throws IOException { + switch (version) { + case 1: + case 2: + case 3: + UsageStatsXml.read(in, statsOut); + break; + case 4: + UsageStatsProto.read(in, statsOut); + break; + default: + throw new RuntimeException( + "Unhandled UsageStatsDatabase version: " + Integer.toString(version) + + " on read."); + } + + } + /** * Update the stats in the database. They may not be written to disk immediately. */ @@ -596,7 +1005,7 @@ public class UsageStatsDatabase { mSortedStatFiles[intervalType].put(stats.beginTime, f); } - UsageStatsXml.write(f, stats); + writeLocked(f, stats); stats.lastTimeSaved = f.getLastModifiedTime(); } } @@ -730,7 +1139,7 @@ public class UsageStatsDatabase { throws IOException { IntervalStats stats = new IntervalStats(); try { - UsageStatsXml.read(statsFile, stats); + readLocked(statsFile, stats); } catch (IOException e) { Slog.e(TAG, "Failed to read usage stats file", e); out.writeInt(0); @@ -756,12 +1165,12 @@ public class UsageStatsDatabase { if (stats.events != null) stats.events.clear(); } - private static byte[] serializeIntervalStats(IntervalStats stats) { + private byte[] serializeIntervalStats(IntervalStats stats) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); DataOutputStream out = new DataOutputStream(baos); try { out.writeLong(stats.beginTime); - UsageStatsXml.write(out, stats); + writeLocked(out, stats); } catch (IOException ioe) { Slog.d(TAG, "Serializing IntervalStats Failed", ioe); baos.reset(); @@ -769,13 +1178,13 @@ public class UsageStatsDatabase { return baos.toByteArray(); } - private static IntervalStats deserializeIntervalStats(byte[] data) { + private IntervalStats deserializeIntervalStats(byte[] data) { ByteArrayInputStream bais = new ByteArrayInputStream(data); DataInputStream in = new DataInputStream(bais); IntervalStats stats = new IntervalStats(); try { stats.beginTime = in.readLong(); - UsageStatsXml.read(in, stats); + readLocked(in, stats); } catch (IOException ioe) { Slog.d(TAG, "DeSerializing IntervalStats Failed", ioe); stats = null; diff --git a/services/usage/java/com/android/server/usage/UsageStatsProto.java b/services/usage/java/com/android/server/usage/UsageStatsProto.java new file mode 100644 index 000000000000..30d303f426bf --- /dev/null +++ b/services/usage/java/com/android/server/usage/UsageStatsProto.java @@ -0,0 +1,552 @@ +/* + * 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.usage; + +import android.app.usage.ConfigurationStats; +import android.app.usage.EventList; +import android.app.usage.UsageEvents; +import android.app.usage.UsageStats; +import android.content.res.Configuration; +import android.util.ArrayMap; + +import android.util.Slog; +import android.util.proto.ProtoInputStream; +import android.util.proto.ProtoOutputStream; + +import java.io.InputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.net.ProtocolException; +import java.util.ArrayList; +import java.util.List; + +/** + * UsageStats reader/writer for Protocol Buffer format + */ +final class UsageStatsProto { + private static String TAG = "UsageStatsProto"; + + // Static-only utility class. + private UsageStatsProto() {} + + private static List<String> readStringPool(ProtoInputStream proto) throws IOException { + + final long token = proto.start(IntervalStatsProto.STRINGPOOL); + List<String> stringPool; + if (proto.isNextField(IntervalStatsProto.StringPool.SIZE)) { + stringPool = new ArrayList(proto.readInt(IntervalStatsProto.StringPool.SIZE)); + } else { + stringPool = new ArrayList(); + } + while (proto.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (proto.getFieldNumber()) { + case (int) IntervalStatsProto.StringPool.STRINGS: + stringPool.add(proto.readString(IntervalStatsProto.StringPool.STRINGS)); + break; + } + } + proto.end(token); + return stringPool; + } + + private static void loadUsageStats(ProtoInputStream proto, long fieldId, + IntervalStats statsOut, List<String> stringPool) + throws IOException { + + final long token = proto.start(fieldId); + UsageStats stats; + if (proto.isNextField(IntervalStatsProto.UsageStats.PACKAGE_INDEX)) { + // Fast path reading the package name index. Most cases this should work since it is + // written first + stats = statsOut.getOrCreateUsageStats( + stringPool.get(proto.readInt(IntervalStatsProto.UsageStats.PACKAGE_INDEX) - 1)); + } else if (proto.isNextField(IntervalStatsProto.UsageStats.PACKAGE)) { + // No package index, try package name instead + stats = statsOut.getOrCreateUsageStats( + proto.readString(IntervalStatsProto.UsageStats.PACKAGE)); + } else { + // Temporarily store collected data to a UsageStats object. This is not efficient. + stats = new UsageStats(); + } + + while (proto.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (proto.getFieldNumber()) { + case (int) IntervalStatsProto.UsageStats.PACKAGE: + // Fast track failed from some reason, add UsageStats object to statsOut now + UsageStats tempPackage = statsOut.getOrCreateUsageStats( + proto.readString(IntervalStatsProto.UsageStats.PACKAGE)); + tempPackage.mLastTimeUsed = stats.mLastTimeUsed; + tempPackage.mTotalTimeInForeground = stats.mTotalTimeInForeground; + tempPackage.mLastEvent = stats.mLastEvent; + tempPackage.mAppLaunchCount = stats.mAppLaunchCount; + stats = tempPackage; + break; + case (int) IntervalStatsProto.UsageStats.PACKAGE_INDEX: + // Fast track failed from some reason, add UsageStats object to statsOut now + UsageStats tempPackageIndex = statsOut.getOrCreateUsageStats(stringPool.get( + proto.readInt(IntervalStatsProto.UsageStats.PACKAGE_INDEX) - 1)); + tempPackageIndex.mLastTimeUsed = stats.mLastTimeUsed; + tempPackageIndex.mTotalTimeInForeground = stats.mTotalTimeInForeground; + tempPackageIndex.mLastEvent = stats.mLastEvent; + tempPackageIndex.mAppLaunchCount = stats.mAppLaunchCount; + stats = tempPackageIndex; + break; + case (int) IntervalStatsProto.UsageStats.LAST_TIME_ACTIVE_MS: + stats.mLastTimeUsed = statsOut.beginTime + proto.readLong( + IntervalStatsProto.UsageStats.LAST_TIME_ACTIVE_MS); + break; + case (int) IntervalStatsProto.UsageStats.TOTAL_TIME_ACTIVE_MS: + stats.mTotalTimeInForeground = proto.readLong( + IntervalStatsProto.UsageStats.TOTAL_TIME_ACTIVE_MS); + break; + case (int) IntervalStatsProto.UsageStats.LAST_EVENT: + stats.mLastEvent = proto.readInt(IntervalStatsProto.UsageStats.LAST_EVENT); + break; + case (int) IntervalStatsProto.UsageStats.APP_LAUNCH_COUNT: + stats.mAppLaunchCount = proto.readInt( + IntervalStatsProto.UsageStats.APP_LAUNCH_COUNT); + break; + case (int) IntervalStatsProto.UsageStats.CHOOSER_ACTIONS: + final long chooserToken = proto.start( + IntervalStatsProto.UsageStats.CHOOSER_ACTIONS); + loadChooserCounts(proto, stats); + proto.end(chooserToken); + break; + } + } + if (stats.mLastTimeUsed == 0) { + // mLastTimeUsed was not assigned, assume default value of 0 plus beginTime; + stats.mLastTimeUsed = statsOut.beginTime; + } + proto.end(token); + } + + private static void loadCountAndTime(ProtoInputStream proto, long fieldId, + IntervalStats.EventTracker tracker) throws IOException { + final long token = proto.start(fieldId); + while (true) { + switch (proto.nextField()) { + case (int) IntervalStatsProto.CountAndTime.COUNT: + tracker.count = proto.readInt(IntervalStatsProto.CountAndTime.COUNT); + break; + case (int) IntervalStatsProto.CountAndTime.TIME_MS: + tracker.duration = proto.readLong(IntervalStatsProto.CountAndTime.TIME_MS); + break; + case ProtoInputStream.NO_MORE_FIELDS: + proto.end(token); + return; + } + } + } + + private static void loadChooserCounts(ProtoInputStream proto, UsageStats usageStats) + throws IOException { + if (usageStats.mChooserCounts == null) { + usageStats.mChooserCounts = new ArrayMap<>(); + } + String action = null; + ArrayMap<String, Integer> counts; + if (proto.isNextField(IntervalStatsProto.UsageStats.ChooserAction.NAME)) { + // Fast path reading the action name. Most cases this should work since it is written + // first + action = proto.readString(IntervalStatsProto.UsageStats.ChooserAction.NAME); + counts = usageStats.mChooserCounts.get(action); + if (counts == null) { + counts = new ArrayMap<>(); + usageStats.mChooserCounts.put(action, counts); + } + } else { + // Temporarily store collected data to an ArrayMap. This is not efficient. + counts = new ArrayMap<>(); + } + + while (true) { + switch (proto.nextField()) { + case (int) IntervalStatsProto.UsageStats.ChooserAction.NAME: + // Fast path failed from some reason, add the ArrayMap object to usageStats now + action = proto.readString(IntervalStatsProto.UsageStats.ChooserAction.NAME); + usageStats.mChooserCounts.put(action, counts); + break; + case (int) IntervalStatsProto.UsageStats.ChooserAction.COUNTS: + final long token = proto.start( + IntervalStatsProto.UsageStats.ChooserAction.COUNTS); + loadCountsForAction(proto, counts); + proto.end(token); + case ProtoInputStream.NO_MORE_FIELDS: + if (action == null) { + // default string + usageStats.mChooserCounts.put("", counts); + } + return; + } + } + } + + private static void loadCountsForAction(ProtoInputStream proto, + ArrayMap<String, Integer> counts) throws IOException { + String category = null; + int count = 0; + while (true) { + switch (proto.nextField()) { + case (int) IntervalStatsProto.UsageStats.ChooserAction.CategoryCount.NAME: + category = proto.readString( + IntervalStatsProto.UsageStats.ChooserAction.CategoryCount.NAME); + break; + case (int) IntervalStatsProto.UsageStats.ChooserAction.CategoryCount.COUNT: + count = proto.readInt( + IntervalStatsProto.UsageStats.ChooserAction.CategoryCount.COUNT); + break; + case ProtoInputStream.NO_MORE_FIELDS: + if (category == null) { + counts.put("", count); + } else { + counts.put(category, count); + } + return; + } + } + } + + private static void loadConfigStats(ProtoInputStream proto, long fieldId, + IntervalStats statsOut) throws IOException { + final long token = proto.start(fieldId); + boolean configActive = false; + final Configuration config = new Configuration(); + ConfigurationStats configStats; + if (proto.isNextField(IntervalStatsProto.Configuration.CONFIG)) { + // Fast path reading the configuration. Most cases this should work since it is + // written first + config.readFromProto(proto, IntervalStatsProto.Configuration.CONFIG); + configStats = statsOut.getOrCreateConfigurationStats(config); + } else { + // Temporarily store collected data to a ConfigurationStats object. This is not + // efficient. + configStats = new ConfigurationStats(); + } + while (true) { + switch (proto.nextField()) { + case (int) IntervalStatsProto.Configuration.CONFIG: + // Fast path failed from some reason, add ConfigStats object to statsOut now + config.readFromProto(proto, IntervalStatsProto.Configuration.CONFIG); + final ConfigurationStats temp = statsOut.getOrCreateConfigurationStats(config); + temp.mLastTimeActive = configStats.mLastTimeActive; + temp.mTotalTimeActive = configStats.mTotalTimeActive; + temp.mActivationCount = configStats.mActivationCount; + configStats = temp; + break; + case (int) IntervalStatsProto.Configuration.LAST_TIME_ACTIVE_MS: + configStats.mLastTimeActive = statsOut.beginTime + proto.readLong( + IntervalStatsProto.Configuration.LAST_TIME_ACTIVE_MS); + break; + case (int) IntervalStatsProto.Configuration.TOTAL_TIME_ACTIVE_MS: + configStats.mTotalTimeActive = proto.readLong( + IntervalStatsProto.Configuration.TOTAL_TIME_ACTIVE_MS); + break; + case (int) IntervalStatsProto.Configuration.COUNT: + configStats.mActivationCount = proto.readInt( + IntervalStatsProto.Configuration.COUNT); + break; + case (int) IntervalStatsProto.Configuration.ACTIVE: + configActive = proto.readBoolean(IntervalStatsProto.Configuration.ACTIVE); + break; + case ProtoInputStream.NO_MORE_FIELDS: + if (configStats.mLastTimeActive == 0) { + //mLastTimeActive was not assigned, assume default value of 0 plus beginTime + configStats.mLastTimeActive = statsOut.beginTime; + } + if (configActive) { + statsOut.activeConfiguration = configStats.mConfiguration; + } + proto.end(token); + return; + } + } + } + + private static void loadEvent(ProtoInputStream proto, long fieldId, IntervalStats statsOut, + List<String> stringPool) throws IOException { + final long token = proto.start(fieldId); + UsageEvents.Event event = statsOut.buildEvent(proto, stringPool); + proto.end(token); + if (event.mPackage == null) { + throw new ProtocolException("no package field present"); + } + + if (statsOut.events == null) { + statsOut.events = new EventList(); + } + statsOut.events.insert(event); + } + + private static void writeStringPool(ProtoOutputStream proto, final IntervalStats stats) + throws IOException { + final long token = proto.start(IntervalStatsProto.STRINGPOOL); + final int size = stats.mStringCache.size(); + proto.write(IntervalStatsProto.StringPool.SIZE, size); + for (int i = 0; i < size; i++) { + proto.write(IntervalStatsProto.StringPool.STRINGS, stats.mStringCache.valueAt(i)); + } + proto.end(token); + } + + private static void writeUsageStats(ProtoOutputStream proto, long fieldId, + final IntervalStats stats, final UsageStats usageStats) throws IOException { + final long token = proto.start(fieldId); + // Write the package name first, so loadUsageStats can avoid creating an extra object + final int packageIndex = stats.mStringCache.indexOf(usageStats.mPackageName); + if (packageIndex >= 0) { + proto.write(IntervalStatsProto.UsageStats.PACKAGE_INDEX, packageIndex + 1); + } else { + // Package not in Stringpool for some reason, write full string instead + Slog.w(TAG, "UsageStats package name (" + usageStats.mPackageName + + ") not found in IntervalStats string cache"); + proto.write(IntervalStatsProto.UsageStats.PACKAGE, usageStats.mPackageName); + } + proto.write(IntervalStatsProto.UsageStats.LAST_TIME_ACTIVE_MS, + usageStats.mLastTimeUsed - stats.beginTime); + proto.write(IntervalStatsProto.UsageStats.TOTAL_TIME_ACTIVE_MS, + usageStats.mTotalTimeInForeground); + proto.write(IntervalStatsProto.UsageStats.LAST_EVENT, usageStats.mLastEvent); + proto.write(IntervalStatsProto.UsageStats.APP_LAUNCH_COUNT, usageStats.mAppLaunchCount); + writeChooserCounts(proto, usageStats); + proto.end(token); + } + + private static void writeCountAndTime(ProtoOutputStream proto, long fieldId, int count, + long time) throws IOException { + final long token = proto.start(fieldId); + proto.write(IntervalStatsProto.CountAndTime.COUNT, count); + proto.write(IntervalStatsProto.CountAndTime.TIME_MS, time); + proto.end(token); + } + + + private static void writeChooserCounts(ProtoOutputStream proto, final UsageStats usageStats) + throws IOException { + if (usageStats == null || usageStats.mChooserCounts == null + || usageStats.mChooserCounts.keySet().isEmpty()) { + return; + } + final int chooserCountSize = usageStats.mChooserCounts.size(); + for (int i = 0; i < chooserCountSize; i++) { + final String action = usageStats.mChooserCounts.keyAt(i); + final ArrayMap<String, Integer> counts = usageStats.mChooserCounts.valueAt(i); + if (action == null || counts == null || counts.isEmpty()) { + continue; + } + final long token = proto.start(IntervalStatsProto.UsageStats.CHOOSER_ACTIONS); + proto.write(IntervalStatsProto.UsageStats.ChooserAction.NAME, action); + writeCountsForAction(proto, counts); + proto.end(token); + } + } + + private static void writeCountsForAction(ProtoOutputStream proto, + ArrayMap<String, Integer> counts) throws IOException { + final int countsSize = counts.size(); + for (int i = 0; i < countsSize; i++) { + String key = counts.keyAt(i); + int count = counts.valueAt(i); + if (count > 0) { + final long token = proto.start(IntervalStatsProto.UsageStats.ChooserAction.COUNTS); + proto.write(IntervalStatsProto.UsageStats.ChooserAction.CategoryCount.NAME, key); + proto.write(IntervalStatsProto.UsageStats.ChooserAction.CategoryCount.COUNT, count); + proto.end(token); + } + } + } + + private static void writeConfigStats(ProtoOutputStream proto, long fieldId, + final IntervalStats stats, final ConfigurationStats configStats, boolean isActive) + throws IOException { + final long token = proto.start(fieldId); + configStats.mConfiguration.writeToProto(proto, IntervalStatsProto.Configuration.CONFIG); + proto.write(IntervalStatsProto.Configuration.LAST_TIME_ACTIVE_MS, + configStats.mLastTimeActive - stats.beginTime); + proto.write(IntervalStatsProto.Configuration.TOTAL_TIME_ACTIVE_MS, + configStats.mTotalTimeActive); + proto.write(IntervalStatsProto.Configuration.COUNT, configStats.mActivationCount); + proto.write(IntervalStatsProto.Configuration.ACTIVE, isActive); + proto.end(token); + + } + + private static void writeEvent(ProtoOutputStream proto, long fieldId, final IntervalStats stats, + final UsageEvents.Event event) throws IOException { + final long token = proto.start(fieldId); + final int packageIndex = stats.mStringCache.indexOf(event.mPackage); + if (packageIndex >= 0) { + proto.write(IntervalStatsProto.Event.PACKAGE_INDEX, packageIndex + 1); + } else { + // Package not in Stringpool for some reason, write full string instead + Slog.w(TAG, "Usage event package name (" + event.mPackage + + ") not found in IntervalStats string cache"); + proto.write(IntervalStatsProto.Event.PACKAGE, event.mPackage); + } + if (event.mClass != null) { + final int classIndex = stats.mStringCache.indexOf(event.mClass); + if (classIndex >= 0) { + proto.write(IntervalStatsProto.Event.CLASS_INDEX, classIndex + 1); + } else { + // Class not in Stringpool for some reason, write full string instead + Slog.w(TAG, "Usage event class name (" + event.mClass + + ") not found in IntervalStats string cache"); + proto.write(IntervalStatsProto.Event.CLASS, event.mClass); + } + } + proto.write(IntervalStatsProto.Event.TIME_MS, event.mTimeStamp - stats.beginTime); + proto.write(IntervalStatsProto.Event.FLAGS, event.mFlags); + proto.write(IntervalStatsProto.Event.TYPE, event.mEventType); + switch (event.mEventType) { + case UsageEvents.Event.CONFIGURATION_CHANGE: + if (event.mConfiguration != null) { + event.mConfiguration.writeToProto(proto, IntervalStatsProto.Event.CONFIG); + } + break; + case UsageEvents.Event.SHORTCUT_INVOCATION: + if (event.mShortcutId != null) { + proto.write(IntervalStatsProto.Event.SHORTCUT_ID, event.mShortcutId); + } + break; + case UsageEvents.Event.STANDBY_BUCKET_CHANGED: + if (event.mBucketAndReason != 0) { + proto.write(IntervalStatsProto.Event.STANDBY_BUCKET, event.mBucketAndReason); + } + break; + case UsageEvents.Event.NOTIFICATION_INTERRUPTION: + if (event.mNotificationChannelId != null) { + final int channelIndex = stats.mStringCache.indexOf( + event.mNotificationChannelId); + if (channelIndex >= 0) { + proto.write(IntervalStatsProto.Event.NOTIFICATION_CHANNEL_INDEX, + channelIndex + 1); + } else { + // Channel not in Stringpool for some reason, write full string instead + Slog.w(TAG, "Usage event notification channel name (" + + event.mNotificationChannelId + + ") not found in IntervalStats string cache"); + proto.write(IntervalStatsProto.Event.NOTIFICATION_CHANNEL, + event.mNotificationChannelId); + } + } + break; + } + proto.end(token); + } + + /** + * Reads from the {@link ProtoInputStream}. + * + * @param proto The proto from which to read events. + * @param statsOut The stats object to populate with the data from the XML file. + */ + public static void read(InputStream in, IntervalStats statsOut) throws IOException { + final ProtoInputStream proto = new ProtoInputStream(in); + List<String> stringPool = null; + + statsOut.packageStats.clear(); + statsOut.configurations.clear(); + statsOut.activeConfiguration = null; + + if (statsOut.events != null) { + statsOut.events.clear(); + } + + while (true) { + switch (proto.nextField()) { + case (int) IntervalStatsProto.END_TIME_MS: + statsOut.endTime = statsOut.beginTime + proto.readLong( + IntervalStatsProto.END_TIME_MS); + break; + case (int) IntervalStatsProto.INTERACTIVE: + loadCountAndTime(proto, IntervalStatsProto.INTERACTIVE, + statsOut.interactiveTracker); + break; + case (int) IntervalStatsProto.NON_INTERACTIVE: + loadCountAndTime(proto, IntervalStatsProto.NON_INTERACTIVE, + statsOut.nonInteractiveTracker); + break; + case (int) IntervalStatsProto.KEYGUARD_SHOWN: + loadCountAndTime(proto, IntervalStatsProto.KEYGUARD_SHOWN, + statsOut.keyguardShownTracker); + break; + case (int) IntervalStatsProto.KEYGUARD_HIDDEN: + loadCountAndTime(proto, IntervalStatsProto.KEYGUARD_HIDDEN, + statsOut.keyguardHiddenTracker); + break; + case (int) IntervalStatsProto.STRINGPOOL: + stringPool = readStringPool(proto); + statsOut.mStringCache.addAll(stringPool); + break; + case (int) IntervalStatsProto.PACKAGES: + loadUsageStats(proto, IntervalStatsProto.PACKAGES, statsOut, stringPool); + break; + case (int) IntervalStatsProto.CONFIGURATIONS: + loadConfigStats(proto, IntervalStatsProto.CONFIGURATIONS, statsOut); + break; + case (int) IntervalStatsProto.EVENT_LOG: + loadEvent(proto, IntervalStatsProto.EVENT_LOG, statsOut, stringPool); + break; + case ProtoInputStream.NO_MORE_FIELDS: + if (statsOut.endTime == 0) { + // endTime not assigned, assume default value of 0 plus beginTime + statsOut.endTime = statsOut.beginTime; + } + return; + } + } + } + + /** + * Writes the stats object to an ProtoBuf file. + * + * @param proto The serializer to which to write the packageStats data. + * @param stats The stats object to write to the XML file. + */ + public static void write(OutputStream out, IntervalStats stats) throws IOException { + final ProtoOutputStream proto = new ProtoOutputStream(out); + proto.write(IntervalStatsProto.END_TIME_MS, stats.endTime - stats.beginTime); + // String pool should be written before the rest of the usage stats + writeStringPool(proto, stats); + + writeCountAndTime(proto, IntervalStatsProto.INTERACTIVE, stats.interactiveTracker.count, + stats.interactiveTracker.duration); + writeCountAndTime(proto, IntervalStatsProto.NON_INTERACTIVE, + stats.nonInteractiveTracker.count, stats.nonInteractiveTracker.duration); + writeCountAndTime(proto, IntervalStatsProto.KEYGUARD_SHOWN, + stats.keyguardShownTracker.count, stats.keyguardShownTracker.duration); + writeCountAndTime(proto, IntervalStatsProto.KEYGUARD_HIDDEN, + stats.keyguardHiddenTracker.count, stats.keyguardHiddenTracker.duration); + + final int statsCount = stats.packageStats.size(); + for (int i = 0; i < statsCount; i++) { + writeUsageStats(proto, IntervalStatsProto.PACKAGES, stats, + stats.packageStats.valueAt(i)); + } + final int configCount = stats.configurations.size(); + for (int i = 0; i < configCount; i++) { + boolean active = stats.activeConfiguration.equals(stats.configurations.keyAt(i)); + writeConfigStats(proto, IntervalStatsProto.CONFIGURATIONS, stats, + stats.configurations.valueAt(i), active); + } + final int eventCount = stats.events != null ? stats.events.size() : 0; + for (int i = 0; i < eventCount; i++) { + writeEvent(proto, IntervalStatsProto.EVENT_LOG, stats, stats.events.get(i)); + } + + proto.flush(); + } +} diff --git a/services/usage/java/com/android/server/usage/UsageStatsXml.java b/services/usage/java/com/android/server/usage/UsageStatsXml.java index e7db74149b4a..f8d1113e8460 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsXml.java +++ b/services/usage/java/com/android/server/usage/UsageStatsXml.java @@ -19,6 +19,9 @@ package com.android.server.usage; import android.util.AtomicFile; import android.util.Slog; import android.util.Xml; +import android.util.proto.ProtoInputStream; +import android.util.proto.ProtoOutputStream; + import com.android.internal.util.FastXmlSerializer; import com.android.internal.util.XmlUtils; import org.xmlpull.v1.XmlPullParser; @@ -33,61 +36,7 @@ public class UsageStatsXml { private static final String VERSION_ATTR = "version"; static final String CHECKED_IN_SUFFIX = "-c"; - public static long parseBeginTime(AtomicFile file) throws IOException { - return parseBeginTime(file.getBaseFile()); - } - - public static long parseBeginTime(File file) throws IOException { - String name = file.getName(); - - // Eat as many occurrences of -c as possible. This is due to a bug where -c - // would be appended more than once to a checked-in file, causing a crash - // on boot when indexing files since Long.parseLong() will puke on anything but - // a number. - while (name.endsWith(CHECKED_IN_SUFFIX)) { - name = name.substring(0, name.length() - CHECKED_IN_SUFFIX.length()); - } - - try { - return Long.parseLong(name); - } catch (NumberFormatException e) { - throw new IOException(e); - } - } - - public static void read(AtomicFile file, IntervalStats statsOut) throws IOException { - try { - FileInputStream in = file.openRead(); - try { - statsOut.beginTime = parseBeginTime(file); - read(in, statsOut); - statsOut.lastTimeSaved = file.getLastModifiedTime(); - } finally { - try { - in.close(); - } catch (IOException e) { - // Empty - } - } - } catch (FileNotFoundException e) { - Slog.e(TAG, "UsageStats Xml", e); - throw e; - } - } - - public static void write(AtomicFile file, IntervalStats stats) throws IOException { - FileOutputStream fos = file.startWrite(); - try { - write(fos, stats); - file.finishWrite(fos); - fos = null; - } finally { - // When fos is null (successful write), this will no-op - file.failWrite(fos); - } - } - - static void read(InputStream in, IntervalStats statsOut) throws IOException { + public static void read(InputStream in, IntervalStats statsOut) throws IOException { XmlPullParser parser = Xml.newPullParser(); try { parser.setInput(in, "utf-8"); @@ -113,7 +62,7 @@ public class UsageStatsXml { } } - static void write(OutputStream out, IntervalStats stats) throws IOException { + public static void write(OutputStream out, IntervalStats stats) throws IOException { FastXmlSerializer xml = new FastXmlSerializer(); xml.setOutput(out, "utf-8"); xml.startDocument("utf-8", true); diff --git a/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java b/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java index 6a1e97a51453..a68f9d385ca5 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java +++ b/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java @@ -196,11 +196,7 @@ final class UsageStatsXmlV1 { event.mNotificationChannelId = (channelId != null) ? channelId.intern() : null; break; } - - if (statsOut.events == null) { - statsOut.events = new EventList(); - } - statsOut.events.insert(event); + statsOut.addEvent(event); } private static void writeUsageStats(XmlSerializer xml, final IntervalStats stats, diff --git a/services/usage/java/com/android/server/usage/UserUsageStatsService.java b/services/usage/java/com/android/server/usage/UserUsageStatsService.java index 9b194e9ec638..1a8aba085d24 100644 --- a/services/usage/java/com/android/server/usage/UserUsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UserUsageStatsService.java @@ -176,12 +176,8 @@ class UserUsageStatsService { currentDailyStats.activeConfiguration, newFullConfig); } - // Add the event to the daily list. - if (currentDailyStats.events == null) { - currentDailyStats.events = new EventList(); - } if (event.mEventType != UsageEvents.Event.SYSTEM_INTERACTION) { - currentDailyStats.events.insert(event); + currentDailyStats.addEvent(event); } boolean incrementAppLaunch = false; diff --git a/startop/iorap/Android.bp b/startop/iorap/Android.bp new file mode 100644 index 000000000000..b3b09001e7ee --- /dev/null +++ b/startop/iorap/Android.bp @@ -0,0 +1,28 @@ +// 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. + +java_library_static { + name: "libiorap-java", + + aidl: { + include_dirs: [ + "system/iorap/binder", + ], + }, + + srcs: [ + ":iorap-aidl", + "**/*.java", + ], +} diff --git a/startop/iorap/src/com/google/android/startop/iorap/ActivityHintEvent.java b/startop/iorap/src/com/google/android/startop/iorap/ActivityHintEvent.java new file mode 100644 index 000000000000..1d38f4c1e23d --- /dev/null +++ b/startop/iorap/src/com/google/android/startop/iorap/ActivityHintEvent.java @@ -0,0 +1,139 @@ +/* + * 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.google.android.startop.iorap; + +import android.os.Parcelable; +import android.os.Parcel; + +import android.annotation.IntDef; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Objects; + +/** + * Provide a hint to iorapd that an activity has transitioned state.<br /><br /> + * + * Knowledge of when an activity starts/stops can be used by iorapd to increase system + * performance (e.g. by launching perfetto tracing to record an io profile, or by + * playing back an ioprofile via readahead) over the long run.<br /><br /> + * + * /@see com.google.android.startop.iorap.IIorap#onActivityHintEvent<br /><br /> + * + * Once an activity hint is in {@link #TYPE_STARTED} it must transition to another type. + * All other states could be terminal, see below: <br /><br /> + * + * <pre> + * + * ┌──────────────────────────────────────┐ + * │ ▼ + * ┌─────────┐ ╔════════════════╗ ╔═══════════╗ + * ──▶ │ STARTED │ ──▶ ║ COMPLETED ║ ──▶ ║ CANCELLED ║ + * └─────────┘ ╚════════════════╝ ╚═══════════╝ + * │ + * │ + * ▼ + * ╔════════════════╗ + * ║ POST_COMPLETED ║ + * ╚════════════════╝ + * + * </pre> <!-- system/iorap/docs/binder/ActivityHint.dot --> + * + * @hide + */ +public class ActivityHintEvent implements Parcelable { + + public static final int TYPE_STARTED = 0; + public static final int TYPE_CANCELLED = 1; + public static final int TYPE_COMPLETED = 2; + public static final int TYPE_POST_COMPLETED = 3; + private static final int TYPE_MAX = TYPE_POST_COMPLETED; + + /** @hide */ + @IntDef(flag = true, prefix = { "TYPE_" }, value = { + TYPE_STARTED, + TYPE_CANCELLED, + TYPE_COMPLETED, + TYPE_POST_COMPLETED, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface Type {} + + @Type public final int type; + public final ActivityInfo activityInfo; + + public ActivityHintEvent(@Type int type, ActivityInfo activityInfo) { + this.type = type; + this.activityInfo = activityInfo; + checkConstructorArguments(); + } + + private void checkConstructorArguments() { + CheckHelpers.checkTypeInRange(type, TYPE_MAX); + Objects.requireNonNull(activityInfo, "activityInfo"); + } + + @Override + public String toString() { + return String.format("{type: %d, activityInfo: %s}", type, activityInfo); + } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } else if (other instanceof ActivityHintEvent) { + return equals((ActivityHintEvent) other); + } + return false; + } + + private boolean equals(ActivityHintEvent other) { + return type == other.type && + Objects.equals(activityInfo, other.activityInfo); + } + + //<editor-fold desc="Binder boilerplate"> + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeInt(type); + activityInfo.writeToParcel(out, flags); + } + + private ActivityHintEvent(Parcel in) { + this.type = in.readInt(); + this.activityInfo = ActivityInfo.CREATOR.createFromParcel(in); + checkConstructorArguments(); + } + + @Override + public int describeContents() { + return 0; + } + + public static final Parcelable.Creator<ActivityHintEvent> CREATOR + = new Parcelable.Creator<ActivityHintEvent>() { + public ActivityHintEvent createFromParcel(Parcel in) { + return new ActivityHintEvent(in); + } + + public ActivityHintEvent[] newArray(int size) { + return new ActivityHintEvent[size]; + } + }; + //</editor-fold> +} diff --git a/startop/iorap/src/com/google/android/startop/iorap/ActivityInfo.java b/startop/iorap/src/com/google/android/startop/iorap/ActivityInfo.java new file mode 100644 index 000000000000..f47a42cffdd8 --- /dev/null +++ b/startop/iorap/src/com/google/android/startop/iorap/ActivityInfo.java @@ -0,0 +1,104 @@ +/* + * 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.google.android.startop.iorap; + +import java.util.Objects; + +import android.os.Parcelable; +import android.os.Parcel; + +/** + * Provide minimal information for launched activities to iorap.<br /><br /> + * + * This uniquely identifies a system-wide activity by providing the {@link #packageName} and + * {@link #activityName}. + * + * @see ActivityHintEvent + * @see AppIntentEvent + * + * @hide + */ +public class ActivityInfo implements Parcelable { + + /** The name of the package, for example {@code com.android.calculator}. */ + public final String packageName; + /** The name of the activity, for example {@code .activities.activity.MainActivity} */ + public final String activityName; + + public ActivityInfo(String packageName, String activityName) { + this.packageName = packageName; + this.activityName = activityName; + + checkConstructorArguments(); + } + + private void checkConstructorArguments() { + Objects.requireNonNull(packageName, "packageName"); + Objects.requireNonNull(activityName, "activityName"); + } + + @Override + public String toString() { + return String.format("{packageName: %s, activityName: %s}", packageName, activityName); + } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } else if (other instanceof ActivityInfo) { + return equals((ActivityInfo) other); + } + return false; + } + + private boolean equals(ActivityInfo other) { + return Objects.equals(packageName, other.packageName) && + Objects.equals(activityName, other.activityName); + } + + //<editor-fold desc="Binder boilerplate"> + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeString(packageName); + out.writeString(activityName); + } + + private ActivityInfo(Parcel in) { + packageName = in.readString(); + activityName = in.readString(); + + checkConstructorArguments(); + } + + @Override + public int describeContents() { + return 0; + } + + public static final Parcelable.Creator<ActivityInfo> CREATOR + = new Parcelable.Creator<ActivityInfo>() { + public ActivityInfo createFromParcel(Parcel in) { + return new ActivityInfo(in); + } + + public ActivityInfo[] newArray(int size) { + return new ActivityInfo[size]; + } + }; + //</editor-fold> +} diff --git a/startop/iorap/src/com/google/android/startop/iorap/AppIntentEvent.java b/startop/iorap/src/com/google/android/startop/iorap/AppIntentEvent.java new file mode 100644 index 000000000000..1cd37b5546b9 --- /dev/null +++ b/startop/iorap/src/com/google/android/startop/iorap/AppIntentEvent.java @@ -0,0 +1,138 @@ +/* + * 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.google.android.startop.iorap; + +import android.os.Parcelable; +import android.os.Parcel; + +import android.annotation.IntDef; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Objects; + +/** + * Notifications for iorapd specifying when a system-wide intent defaults change.<br /><br /> + * + * Intent defaults provide a mechanism for an app to register itself as an automatic handler. + * For example the camera app might be registered as the default handler for + * {@link android.provider.MediaStore#INTENT_ACTION_STILL_IMAGE_CAMERA} intent. Subsequently, + * if an arbitrary other app requests for a still image camera photo to be taken, the system + * will launch the respective default camera app to be launched to handle that request.<br /><br /> + * + * In some cases iorapd might need to know default intents, e.g. for boot-time pinning of + * applications that resolve from the default intent. If the application would now be resolved + * differently, iorapd would unpin the old application and pin the new application.<br /><br /> + * + * @hide + */ +public class AppIntentEvent implements Parcelable { + + /** @see android.content.Intent#CATEGORY_DEFAULT */ + public static final int TYPE_DEFAULT_INTENT_CHANGED = 0; + private static final int TYPE_MAX = 0; + + /** @hide */ + @IntDef(flag = true, prefix = { "TYPE_" }, value = { + TYPE_DEFAULT_INTENT_CHANGED, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface Type {} + + @Type public final int type; + + public final ActivityInfo oldActivityInfo; + public final ActivityInfo newActivityInfo; + + // TODO: Probably need the corresponding action here as well. + + public static AppIntentEvent createDefaultIntentChanged(ActivityInfo oldActivityInfo, + ActivityInfo newActivityInfo) { + return new AppIntentEvent(TYPE_DEFAULT_INTENT_CHANGED, oldActivityInfo, + newActivityInfo); + } + + private AppIntentEvent(@Type int type, ActivityInfo oldActivityInfo, + ActivityInfo newActivityInfo) { + this.type = type; + this.oldActivityInfo = oldActivityInfo; + this.newActivityInfo = newActivityInfo; + + checkConstructorArguments(); + } + + private void checkConstructorArguments() { + CheckHelpers.checkTypeInRange(type, TYPE_MAX); + Objects.requireNonNull(oldActivityInfo, "oldActivityInfo"); + Objects.requireNonNull(oldActivityInfo, "newActivityInfo"); + } + + @Override + public String toString() { + return String.format("{oldActivityInfo: %s, newActivityInfo: %s}", oldActivityInfo, + newActivityInfo); + } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } else if (other instanceof AppIntentEvent) { + return equals((AppIntentEvent) other); + } + return false; + } + + private boolean equals(AppIntentEvent other) { + return type == other.type && + Objects.equals(oldActivityInfo, other.oldActivityInfo) && + Objects.equals(newActivityInfo, other.newActivityInfo); + } + + //<editor-fold desc="Binder boilerplate"> + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeInt(type); + oldActivityInfo.writeToParcel(out, flags); + newActivityInfo.writeToParcel(out, flags); + } + + private AppIntentEvent(Parcel in) { + this.type = in.readInt(); + this.oldActivityInfo = ActivityInfo.CREATOR.createFromParcel(in); + this.newActivityInfo = ActivityInfo.CREATOR.createFromParcel(in); + + checkConstructorArguments(); + } + + @Override + public int describeContents() { + return 0; + } + + public static final Parcelable.Creator<AppIntentEvent> CREATOR + = new Parcelable.Creator<AppIntentEvent>() { + public AppIntentEvent createFromParcel(Parcel in) { + return new AppIntentEvent(in); + } + + public AppIntentEvent[] newArray(int size) { + return new AppIntentEvent[size]; + } + }; + //</editor-fold> +} diff --git a/startop/iorap/src/com/google/android/startop/iorap/CheckHelpers.java b/startop/iorap/src/com/google/android/startop/iorap/CheckHelpers.java new file mode 100644 index 000000000000..34aedd7685d8 --- /dev/null +++ b/startop/iorap/src/com/google/android/startop/iorap/CheckHelpers.java @@ -0,0 +1,46 @@ +/* + * 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.google.android.startop.iorap; + +/** + * Convenience short-hand to throw {@link IllegalAccessException} when the arguments + * are out-of-range. + */ +public class CheckHelpers { + /** @throws IllegalAccessException if {@param type} is not in {@code [0..maxValue]} */ + public static void checkTypeInRange(int type, int maxValue) { + if (type < 0) { + throw new IllegalArgumentException( + String.format("type must be non-negative (value=%d)", type)); + } + if (type > maxValue) { + throw new IllegalArgumentException( + String.format("type out of range (value=%d, max=%d)", type, maxValue)); + } + } + + /** @throws IllegalAccessException if {@param state} is not in {@code [0..maxValue]} */ + public static void checkStateInRange(int state, int maxValue) { + if (state < 0) { + throw new IllegalArgumentException( + String.format("state must be non-negative (value=%d)", state)); + } + if (state > maxValue) { + throw new IllegalArgumentException( + String.format("state out of range (value=%d, max=%d)", state, maxValue)); + } + } +} diff --git a/startop/iorap/src/com/google/android/startop/iorap/PackageEvent.java b/startop/iorap/src/com/google/android/startop/iorap/PackageEvent.java new file mode 100644 index 000000000000..aa4eea716363 --- /dev/null +++ b/startop/iorap/src/com/google/android/startop/iorap/PackageEvent.java @@ -0,0 +1,131 @@ +/* + * 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.google.android.startop.iorap; + +import android.annotation.NonNull; +import android.os.Parcelable; +import android.os.Parcel; +import android.net.Uri; + +import android.annotation.IntDef; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Objects; + +/** + * Forward package manager events to iorapd. <br /><br /> + * + * Knowing when packages are modified by the system are a useful tidbit to help with performance: + * for example when a package is replaced, it could be a hint used to invalidate any collected + * io profiles used for prefetching or pinning. + * + * @hide + */ +public class PackageEvent implements Parcelable { + + /** @see android.content.Intent#ACTION_PACKAGE_REPLACED */ + public static final int TYPE_REPLACED = 0; + private static final int TYPE_MAX = 0; + + /** @hide */ + @IntDef(flag = true, prefix = { "TYPE_" }, value = { + TYPE_REPLACED, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface Type {} + + @Type public final int type; + + /** The path that a package is installed in, for example {@code /data/app/.../base.apk}. */ + public final Uri packageUri; + /** The name of the package, for example {@code com.android.calculator}. */ + public final String packageName; + + @NonNull + public static PackageEvent createReplaced(Uri packageUri, String packageName) { + return new PackageEvent(TYPE_REPLACED, packageUri, packageName); + } + + private PackageEvent(@Type int type, Uri packageUri, String packageName) { + this.type = type; + this.packageUri = packageUri; + this.packageName = packageName; + + checkConstructorArguments(); + } + + private void checkConstructorArguments() { + CheckHelpers.checkTypeInRange(type, TYPE_MAX); + Objects.requireNonNull(packageUri, "packageUri"); + Objects.requireNonNull(packageName, "packageName"); + } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } else if (other instanceof PackageEvent) { + return equals((PackageEvent) other); + } + return false; + } + + private boolean equals(PackageEvent other) { + return type == other.type && + Objects.equals(packageUri, other.packageUri) && + Objects.equals(packageName, other.packageName); + } + + @Override + public String toString() { + return String.format("{packageUri: %s, packageName: %s}", packageUri, packageName); + } + + //<editor-fold desc="Binder boilerplate"> + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeInt(type); + packageUri.writeToParcel(out, flags); + out.writeString(packageName); + } + + private PackageEvent(Parcel in) { + this.type = in.readInt(); + this.packageUri = Uri.CREATOR.createFromParcel(in); + this.packageName = in.readString(); + + checkConstructorArguments(); + } + + @Override + public int describeContents() { + return 0; + } + + public static final Parcelable.Creator<PackageEvent> CREATOR + = new Parcelable.Creator<PackageEvent>() { + public PackageEvent createFromParcel(Parcel in) { + return new PackageEvent(in); + } + + public PackageEvent[] newArray(int size) { + return new PackageEvent[size]; + } + }; + //</editor-fold> +} diff --git a/startop/iorap/src/com/google/android/startop/iorap/RequestId.java b/startop/iorap/src/com/google/android/startop/iorap/RequestId.java new file mode 100644 index 000000000000..2c79319a1459 --- /dev/null +++ b/startop/iorap/src/com/google/android/startop/iorap/RequestId.java @@ -0,0 +1,120 @@ +/* + * 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.google.android.startop.iorap; + +import android.os.Parcelable; +import android.os.Parcel; + +import android.annotation.NonNull; + +/** + * Uniquely identify an {@link com.google.android.startop.iorap.IIorap} method invocation, + * used for asynchronous callbacks by the server. <br /><br /> + * + * As all system server binder calls must be {@code oneway}, this means all invocations + * into {@link com.google.android.startop.iorap.IIorap} are non-blocking. The request ID + * exists to associate all calls with their respective callbacks in + * {@link com.google.android.startop.iorap.ITaskListener}. + * + * @see com.google.android.startop.iorap.IIorap + * + * @hide + */ +public class RequestId implements Parcelable { + + public final long requestId; + + private static Object mLock = new Object(); + private static long mNextRequestId = 0; + + /** + * Create a monotonically increasing request ID.<br /><br /> + * + * It is invalid to re-use the same request ID for multiple method calls on + * {@link com.google.android.startop.iorap.IIorap}; a new request ID must be created + * each time. + */ + @NonNull public static RequestId nextValueForSequence() { + long currentRequestId; + synchronized (mLock) { + currentRequestId = mNextRequestId; + ++mNextRequestId; + } + return new RequestId(currentRequestId); + } + + private RequestId(long requestId) { + this.requestId = requestId; + + checkConstructorArguments(); + } + + private void checkConstructorArguments() { + if (requestId < 0) { + throw new IllegalArgumentException("request id must be non-negative"); + } + } + + @Override + public String toString() { + return String.format("{requestId: %ld}", requestId); + } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } else if (other instanceof RequestId) { + return equals((RequestId) other); + } + return false; + } + + private boolean equals(RequestId other) { + return requestId == other.requestId; + } + + + //<editor-fold desc="Binder boilerplate"> + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeLong(requestId); + } + + private RequestId(Parcel in) { + requestId = in.readLong(); + + checkConstructorArguments(); + } + + @Override + public int describeContents() { + return 0; + } + + public static final Parcelable.Creator<RequestId> CREATOR + = new Parcelable.Creator<RequestId>() { + public RequestId createFromParcel(Parcel in) { + return new RequestId(in); + } + + public RequestId[] newArray(int size) { + return new RequestId[size]; + } + }; + //</editor-fold> +} diff --git a/startop/iorap/src/com/google/android/startop/iorap/SystemServiceEvent.java b/startop/iorap/src/com/google/android/startop/iorap/SystemServiceEvent.java new file mode 100644 index 000000000000..75d47f9e3d17 --- /dev/null +++ b/startop/iorap/src/com/google/android/startop/iorap/SystemServiceEvent.java @@ -0,0 +1,109 @@ +/* + * 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.google.android.startop.iorap; + +import android.os.Parcelable; +import android.os.Parcel; + +import android.annotation.IntDef; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Forward system service events to iorapd. + * + * @see com.android.server.SystemService + * + * @hide + */ +public class SystemServiceEvent implements Parcelable { + + /** @see com.android.server.SystemService#onBootPhase */ + public static final int TYPE_BOOT_PHASE = 0; + /** @see com.android.server.SystemService#onStart */ + public static final int TYPE_START = 1; + private static final int TYPE_MAX = TYPE_START; + + /** @hide */ + @IntDef(flag = true, prefix = { "TYPE_" }, value = { + TYPE_BOOT_PHASE, + TYPE_START, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface Type {} + + @Type public final int type; + + // TODO: do we want to pass the exact build phase enum? + + public SystemServiceEvent(@Type int type) { + this.type = type; + checkConstructorArguments(); + } + + private void checkConstructorArguments() { + CheckHelpers.checkTypeInRange(type, TYPE_MAX); + } + + @Override + public String toString() { + return String.format("{type: %d}", type); + } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } else if (other instanceof SystemServiceEvent) { + return equals((SystemServiceEvent) other); + } + return false; + } + + private boolean equals(SystemServiceEvent other) { + return type == other.type; + } + + //<editor-fold desc="Binder boilerplate"> + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeInt(type); + } + + private SystemServiceEvent(Parcel in) { + this.type = in.readInt(); + checkConstructorArguments(); + } + + @Override + public int describeContents() { + return 0; + } + + public static final Parcelable.Creator<SystemServiceEvent> CREATOR + = new Parcelable.Creator<SystemServiceEvent>() { + public SystemServiceEvent createFromParcel(Parcel in) { + return new SystemServiceEvent(in); + } + + public SystemServiceEvent[] newArray(int size) { + return new SystemServiceEvent[size]; + } + }; + //</editor-fold> +} diff --git a/startop/iorap/src/com/google/android/startop/iorap/SystemServiceUserEvent.java b/startop/iorap/src/com/google/android/startop/iorap/SystemServiceUserEvent.java new file mode 100644 index 000000000000..b77c03c1584a --- /dev/null +++ b/startop/iorap/src/com/google/android/startop/iorap/SystemServiceUserEvent.java @@ -0,0 +1,127 @@ +/* + * 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.google.android.startop.iorap; + +import android.os.Parcelable; +import android.os.Parcel; + +import android.annotation.IntDef; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Forward user events to iorapd.<br /><br /> + * + * Knowledge of the logged-in user is reserved to be used to set-up appropriate policies + * by iorapd (e.g. to handle user default pinned applications changing). + * + * @see com.android.server.SystemService + * + * @hide + */ +public class SystemServiceUserEvent implements Parcelable { + + /** @see com.android.server.SystemService#onStartUser */ + public static final int TYPE_START_USER = 0; + /** @see com.android.server.SystemService#onUnlockUser */ + public static final int TYPE_UNLOCK_USER = 1; + /** @see com.android.server.SystemService#onSwitchUser*/ + public static final int TYPE_SWITCH_USER = 2; + /** @see com.android.server.SystemService#onStopUser */ + public static final int TYPE_STOP_USER = 3; + /** @see com.android.server.SystemService#onCleanupUser */ + public static final int TYPE_CLEANUP_USER = 4; + private static final int TYPE_MAX = TYPE_CLEANUP_USER; + + /** @hide */ + @IntDef(flag = true, prefix = { "TYPE_" }, value = { + TYPE_START_USER, + TYPE_UNLOCK_USER, + TYPE_SWITCH_USER, + TYPE_STOP_USER, + TYPE_CLEANUP_USER, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface Type {} + + @Type public final int type; + public final int userHandle; + + public SystemServiceUserEvent(@Type int type, int userHandle) { + this.type = type; + this.userHandle = userHandle; + checkConstructorArguments(); + } + + private void checkConstructorArguments() { + CheckHelpers.checkTypeInRange(type, TYPE_MAX); + if (userHandle < 0) { + throw new IllegalArgumentException("userHandle must be non-negative"); + } + } + + @Override + public String toString() { + return String.format("{type: %d, userHandle: %d}", type, userHandle); + } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } else if (other instanceof SystemServiceUserEvent) { + return equals((SystemServiceUserEvent) other); + } + return false; + } + + private boolean equals(SystemServiceUserEvent other) { + return type == other.type && + userHandle == other.userHandle; + } + + //<editor-fold desc="Binder boilerplate"> + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeInt(type); + out.writeInt(userHandle); + } + + private SystemServiceUserEvent(Parcel in) { + this.type = in.readInt(); + this.userHandle = in.readInt(); + checkConstructorArguments(); + } + + @Override + public int describeContents() { + return 0; + } + + public static final Parcelable.Creator<SystemServiceUserEvent> CREATOR + = new Parcelable.Creator<SystemServiceUserEvent>() { + public SystemServiceUserEvent createFromParcel(Parcel in) { + return new SystemServiceUserEvent(in); + } + + public SystemServiceUserEvent[] newArray(int size) { + return new SystemServiceUserEvent[size]; + } + }; + //</editor-fold> +} diff --git a/startop/iorap/src/com/google/android/startop/iorap/TaskResult.java b/startop/iorap/src/com/google/android/startop/iorap/TaskResult.java new file mode 100644 index 000000000000..b5fd6d8d1c45 --- /dev/null +++ b/startop/iorap/src/com/google/android/startop/iorap/TaskResult.java @@ -0,0 +1,130 @@ +/* + * 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.google.android.startop.iorap; + +import android.os.Parcelable; +import android.os.Parcel; + +import android.annotation.IntDef; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Result data accompanying a request for {@link com.google.android.startop.iorap.ITaskListener} + * callbacks.<br /><br /> + * + * Following {@link com.google.android.startop.iorap.IIorap} method invocation, + * iorapd will issue in-order callbacks for that corresponding {@link RequestId}.<br /><br /> + * + * State transitions are as follows: <br /><br /> + * + * <pre> + * ┌─────────────────────────────┐ + * │ ▼ + * ┌───────┐ ┌─────────┐ ╔═══════════╗ + * ──▶ │ BEGAN │ ──▶ │ ONGOING │ ──▶ ║ COMPLETED ║ + * └───────┘ └─────────┘ ╚═══════════╝ + * │ │ + * │ │ + * ▼ │ + * ╔═══════╗ │ + * ──▶ ║ ERROR ║ ◀─────┘ + * ╚═══════╝ + * + * </pre> <!-- system/iorap/docs/binder/TaskResult.dot --> + * + * @hide + */ +public class TaskResult implements Parcelable { + + public static final int STATE_BEGAN = 0; + public static final int STATE_ONGOING = 1; + public static final int STATE_COMPLETED = 2; + public static final int STATE_ERROR = 3; + private static final int STATE_MAX = STATE_ERROR; + + /** @hide */ + @IntDef(flag = true, prefix = { "STATE_" }, value = { + STATE_BEGAN, + STATE_ONGOING, + STATE_COMPLETED, + STATE_ERROR, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface State {} + + @State public final int state; + + @Override + public String toString() { + return String.format("{state: %d}", state); + } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } else if (other instanceof TaskResult) { + return equals((TaskResult) other); + } + return false; + } + + private boolean equals(TaskResult other) { + return state == other.state; + } + + public TaskResult(@State int state) { + this.state = state; + + checkConstructorArguments(); + } + + private void checkConstructorArguments() { + CheckHelpers.checkStateInRange(state, STATE_MAX); + } + + //<editor-fold desc="Binder boilerplate"> + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeInt(state); + } + + private TaskResult(Parcel in) { + state = in.readInt(); + + checkConstructorArguments(); + } + + @Override + public int describeContents() { + return 0; + } + + public static final Parcelable.Creator<TaskResult> CREATOR + = new Parcelable.Creator<TaskResult>() { + public TaskResult createFromParcel(Parcel in) { + return new TaskResult(in); + } + + public TaskResult[] newArray(int size) { + return new TaskResult[size]; + } + }; + //</editor-fold> +} diff --git a/startop/iorap/tests/Android.bp b/startop/iorap/tests/Android.bp new file mode 100644 index 000000000000..76057846e896 --- /dev/null +++ b/startop/iorap/tests/Android.bp @@ -0,0 +1,40 @@ +// 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. + +// TODO: once b/80095087 is fixed, rewrite this back to android_test +java_library { + name: "libiorap-java-test-lib", + srcs: ["src/**/*.kt"], + + static_libs: [ + // non-test dependencies + "libiorap-java", + // test android dependencies + "platform-test-annotations", + "android-support-test", + // test framework dependencies + "mockito-target-inline-minus-junit4", + // "mockito-target-minus-junit4", + // Mockito also requires JNI (see Android.mk) + // and android:debuggable=true (see AndroidManifest.xml) + "truth-prebuilt", + ], + + // sdk_version: "current", + // certificate: "platform", + + libs: ["android.test.base", "android.test.runner"], + + // test_suites: ["device-tests"], +} diff --git a/startop/iorap/tests/Android.mk b/startop/iorap/tests/Android.mk new file mode 100644 index 000000000000..1b2aa46a6418 --- /dev/null +++ b/startop/iorap/tests/Android.mk @@ -0,0 +1,46 @@ +# 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. + +# android_test does not support JNI libraries +# TODO: once b/80095087 is fixed, rewrite this back to android_test +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_MODULE_TAGS := tests + +LOCAL_JACK_FLAGS := --multi-dex native +LOCAL_DX_FLAGS := --multi-dex + +LOCAL_PACKAGE_NAME := libiorap-java-tests +LOCAL_COMPATIBILITY_SUITE := device-tests + +LOCAL_STATIC_JAVA_LIBRARIES := \ + libiorap-java-test-lib + +LOCAL_MULTILIB := both + +LOCAL_JNI_SHARED_LIBRARIES := \ + libdexmakerjvmtiagent \ + libstaticjvmtiagent \ + libmultiplejvmtiagentsinterferenceagent + +LOCAL_JAVA_LIBRARIES := \ + android.test.base \ + android.test.runner + +# Use private APIs +LOCAL_CERTIFICATE := platform +LOCAL_PRIVATE_PLATFORM_APIS := true + +include $(BUILD_PACKAGE) diff --git a/startop/iorap/tests/AndroidManifest.xml b/startop/iorap/tests/AndroidManifest.xml new file mode 100644 index 000000000000..99f4add6579f --- /dev/null +++ b/startop/iorap/tests/AndroidManifest.xml @@ -0,0 +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. +--> +<!--suppress AndroidUnknownAttribute --> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.google.android.startop.iorap.tests" + android:sharedUserId="com.google.android.startop.iorap.tests" + android:versionCode="1" + android:versionName="1.0" > + + <!--suppress AndroidDomInspection --> + <instrumentation + android:name="android.support.test.runner.AndroidJUnitRunner" + android:targetPackage="com.google.android.startop.iorap.tests" /> + + <!-- + 'debuggable=true' is required to properly load mockito jvmti dependencies, + otherwise it gives the following error at runtime: + + Openjdkjvmti plugin was loaded on a non-debuggable Runtime. + Plugin was loaded too late to change runtime state to DEBUGGABLE. --> + <application android:debuggable="true"> + <uses-library android:name="android.test.runner" /> + </application> +</manifest> diff --git a/startop/iorap/tests/src/com/google/android/startop/iorap/IIorapIntegrationTest.kt b/startop/iorap/tests/src/com/google/android/startop/iorap/IIorapIntegrationTest.kt new file mode 100644 index 000000000000..4ba44a93f2a8 --- /dev/null +++ b/startop/iorap/tests/src/com/google/android/startop/iorap/IIorapIntegrationTest.kt @@ -0,0 +1,117 @@ +/* + * 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.google.android.startop.iorap + +import android.net.Uri +import android.os.ServiceManager +import android.support.test.filters.MediumTest +import org.junit.Test +import org.junit.Ignore +import org.mockito.Mockito.* + +// @Ignore("Test is disabled until iorapd is added to init and there's selinux policies for it") +@MediumTest +class IIorapIntegrationTest { + /** + * @throws ServiceManager.ServiceNotFoundException if iorapd service could not be found + */ + private val iorapService : IIorap by lazy { + // TODO: connect to 'iorapd.stub' which doesn't actually do any work other than reply. + IIorap.Stub.asInterface(ServiceManager.getServiceOrThrow("iorapd")) + + // Use 'adb shell setenforce 0' otherwise this whole test fails, + // because the servicemanager is not allowed to hand out the binder token for iorapd. + + // TODO: implement the selinux policies for iorapd. + } + + // A dummy binder stub implementation is required to use with mockito#spy. + // Mockito overrides the methods at runtime and tracks how methods were invoked. + open class DummyTaskListener : ITaskListener.Stub() { + // Note: make parameters nullable to avoid the kotlin IllegalStateExceptions + // from using the mockito matchers (eq, argThat, etc). + override fun onProgress(requestId: RequestId?, result: TaskResult?) { + } + + override fun onComplete(requestId: RequestId?, result: TaskResult?) { + } + } + + private fun testAnyMethod(func : (RequestId) -> Unit) { + val taskListener = spy(DummyTaskListener())!! + + try { + iorapService.setTaskListener(taskListener) + // Note: Binder guarantees total order for oneway messages sent to the same binder + // interface, so we don't need any additional blocking here before sending later calls. + + // Every new method call should have a unique request id. + val requestId = RequestId.nextValueForSequence()!! + + // Apply the specific function under test. + func(requestId) + + // Typical mockito behavior is to allow any-order callbacks, but we want to test order. + val inOrder = inOrder(taskListener) + + // The "stub" behavior of iorapd is that every request immediately gets a response of + // BEGAN,ONGOING,COMPLETED + inOrder.verify(taskListener, timeout(100)). + onProgress(eq(requestId), argThat { it!!.state == TaskResult.STATE_BEGAN }) + inOrder.verify(taskListener, timeout(100)). + onProgress(eq(requestId), argThat { it!!.state == TaskResult.STATE_ONGOING }) + inOrder.verify(taskListener, timeout(100)). + onComplete(eq(requestId), argThat { it!!.state == TaskResult.STATE_COMPLETED }) + inOrder.verifyNoMoreInteractions() + + } finally { + iorapService.setTaskListener(null) + } + } + + @Test + fun testOnPackageEvent() { + testAnyMethod { requestId : RequestId -> + iorapService.onPackageEvent(requestId, + PackageEvent.createReplaced( + Uri.parse("https://www.google.com"), "com.fake.package")) + } + } + + @Test + fun testOnAppIntentEvent() { + testAnyMethod { requestId : RequestId -> + iorapService.onAppIntentEvent(requestId, AppIntentEvent.createDefaultIntentChanged( + ActivityInfo("dont care", "dont care"), + ActivityInfo("dont care 2", "dont care 2"))) + } + } + + @Test + fun testOnSystemServiceEvent() { + testAnyMethod { requestId : RequestId -> + iorapService.onSystemServiceEvent(requestId, + SystemServiceEvent(SystemServiceEvent.TYPE_START)) + } + } + + @Test + fun testOnSystemServiceUserEvent() { + testAnyMethod { requestId : RequestId -> + iorapService.onSystemServiceUserEvent(requestId, + SystemServiceUserEvent(SystemServiceUserEvent.TYPE_START_USER,0)) + } + } +} diff --git a/startop/iorap/tests/src/com/google/android/startop/iorap/ParcelablesTest.kt b/startop/iorap/tests/src/com/google/android/startop/iorap/ParcelablesTest.kt new file mode 100644 index 000000000000..4abbb3e9f162 --- /dev/null +++ b/startop/iorap/tests/src/com/google/android/startop/iorap/ParcelablesTest.kt @@ -0,0 +1,140 @@ +/* + * 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.google.android.startop.iorap + +import android.net.Uri +import android.os.Parcel +import android.os.Parcelable +import android.support.test.filters.SmallTest +import org.junit.Test +import org.junit.runner.RunWith +import com.google.common.truth.Truth.assertThat +import org.junit.runners.Parameterized + +/** + * Basic unit tests to ensure that all of the [Parcelable]s in [com.google.android.startop.iorap] + * have a valid-conforming interface implementation. + */ +@SmallTest +@RunWith(Parameterized::class) +class ParcelablesTest<T : Parcelable>(private val inputData : InputData<T>) { + companion object { + private val initialRequestId = RequestId.nextValueForSequence()!! + + @JvmStatic + @Parameterized.Parameters + fun data() = listOf( + InputData( + newActivityInfo(), + newActivityInfo(), + ActivityInfo("some package", "some other activity")), + InputData( + ActivityHintEvent(ActivityHintEvent.TYPE_COMPLETED, newActivityInfo()), + ActivityHintEvent(ActivityHintEvent.TYPE_COMPLETED, newActivityInfo()), + ActivityHintEvent(ActivityHintEvent.TYPE_POST_COMPLETED, + newActivityInfo())), + InputData( + AppIntentEvent.createDefaultIntentChanged(newActivityInfo(), + newActivityInfoOther()), + AppIntentEvent.createDefaultIntentChanged(newActivityInfo(), + newActivityInfoOther()), + AppIntentEvent.createDefaultIntentChanged(newActivityInfoOther(), + newActivityInfo())), + InputData( + PackageEvent.createReplaced(newUri(), "some package"), + PackageEvent.createReplaced(newUri(), "some package"), + PackageEvent.createReplaced(newUri(), "some other package") + ), + InputData(initialRequestId, cloneRequestId(initialRequestId), + RequestId.nextValueForSequence()), + InputData( + SystemServiceEvent(SystemServiceEvent.TYPE_BOOT_PHASE), + SystemServiceEvent(SystemServiceEvent.TYPE_BOOT_PHASE), + SystemServiceEvent(SystemServiceEvent.TYPE_START)), + InputData( + SystemServiceUserEvent(SystemServiceUserEvent.TYPE_START_USER, 12345), + SystemServiceUserEvent(SystemServiceUserEvent.TYPE_START_USER, 12345), + SystemServiceUserEvent(SystemServiceUserEvent.TYPE_CLEANUP_USER, 12345)), + InputData( + TaskResult(TaskResult.STATE_COMPLETED), + TaskResult(TaskResult.STATE_COMPLETED), + TaskResult(TaskResult.STATE_ONGOING)) + ) + + private fun newActivityInfo() : ActivityInfo { + return ActivityInfo("some package", "some activity") + } + + private fun newActivityInfoOther() : ActivityInfo { + return ActivityInfo("some package 2", "some activity 2") + } + + private fun newUri() : Uri { + return Uri.parse("https://www.google.com") + } + + private fun cloneRequestId(requestId: RequestId) : RequestId { + val constructor = requestId::class.java.declaredConstructors[0] + constructor.isAccessible = true + return constructor.newInstance(requestId.requestId) as RequestId + } + } + + /** + * Test for [Object.equals] implementation. + */ + @Test + fun testEquality() { + assertThat(inputData.valid).isEqualTo(inputData.valid) + assertThat(inputData.valid).isEqualTo(inputData.validCopy) + assertThat(inputData.valid).isNotEqualTo(inputData.validOther) + } + + /** + * Test for [Parcelable] implementation. + */ + @Test + fun testParcelRoundTrip() { + // calling writeToParcel and then T::CREATOR.createFromParcel would return the same data. + val assertParcels = { it : T, data : InputData<T> -> + val parcel = Parcel.obtain() + it.writeToParcel(parcel, 0) + parcel.setDataPosition(0) // future reads will see all previous writes. + assertThat(it).isEqualTo(data.createFromParcel(parcel)) + parcel.recycle() + } + + assertParcels(inputData.valid, inputData) + assertParcels(inputData.validCopy, inputData) + assertParcels(inputData.validOther, inputData) + } + + data class InputData<T : Parcelable>(val valid : T, val validCopy : T, val validOther : T) { + val kls = valid.javaClass + init { + assertThat(valid).isNotSameAs(validCopy) + // Don't use isInstanceOf because of phantom warnings in intellij about Class! + assertThat(validCopy.javaClass).isEqualTo(valid.javaClass) + assertThat(validOther.javaClass).isEqualTo(valid.javaClass) + } + + fun createFromParcel(parcel : Parcel) : T { + val field = kls.getDeclaredField("CREATOR") + val creator = field.get(null) as Parcelable.Creator<T> + + return creator.createFromParcel(parcel) + } + } +} diff --git a/startop/tools/view_compiler/Android.bp b/startop/tools/view_compiler/Android.bp new file mode 100644 index 000000000000..c3e91849e636 --- /dev/null +++ b/startop/tools/view_compiler/Android.bp @@ -0,0 +1,49 @@ +// +// 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. +// + +cc_library_host_static { + name: "libviewcompiler", + srcs: [ + "java_lang_builder.cc", + "util.cc", + ], + static_libs: [ + "libbase" + ] +} + +cc_binary_host { + name: "viewcompiler", + srcs: [ + "main.cc", + ], + static_libs: [ + "libbase", + "libtinyxml2", + "libgflags", + "libviewcompiler", + ], +} + +cc_test_host { + name: "view-compiler-tests", + srcs: [ + "util_test.cc", + ], + static_libs: [ + "libviewcompiler", + ] +} diff --git a/startop/tools/view_compiler/README.md b/startop/tools/view_compiler/README.md new file mode 100644 index 000000000000..56595016cbb9 --- /dev/null +++ b/startop/tools/view_compiler/README.md @@ -0,0 +1,25 @@ +# View Compiler + +This directory contains an experimental compiler for layout files. + +It will take a layout XML file and produce a CompiledLayout.java file with a +specialized layout inflation function. + +To use it, let's assume you had a layout in `my_layout.xml` and your app was in +the Java language package `com.example.myapp`. Run the following command: + + viewcompiler my_layout.xml --package com.example.myapp --out CompiledView.java + +This will produce a `CompiledView.java`, which can then be compiled into your +Android app. Then to use it, in places where you would have inflated +`R.layouts.my_layout`, instead call `CompiledView.inflate`. + +Precompiling views like this generally improves the time needed to inflate them. + +This tool is still in its early stages and has a number of limitations. +* Currently only one layout can be compiled at a time. +* `merge` and `include` nodes are not supported. +* View compilation is a manual process that requires code changes in the + application. +* This only works for apps that do not use a custom layout inflater. +* Other limitations yet to be discovered. diff --git a/startop/tools/view_compiler/TEST_MAPPING b/startop/tools/view_compiler/TEST_MAPPING new file mode 100644 index 000000000000..cc4b17a7a65a --- /dev/null +++ b/startop/tools/view_compiler/TEST_MAPPING @@ -0,0 +1,7 @@ +{ + "presubmit": [ + { + "name": "view-compiler-tests" + } + ] +} diff --git a/startop/tools/view_compiler/java_lang_builder.cc b/startop/tools/view_compiler/java_lang_builder.cc new file mode 100644 index 000000000000..0b8754fc7096 --- /dev/null +++ b/startop/tools/view_compiler/java_lang_builder.cc @@ -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. + */ + +#include "java_lang_builder.h" + +#include "android-base/stringprintf.h" + +using android::base::StringPrintf; +using std::string; + +void JavaLangViewBuilder::Start() const { + out_ << StringPrintf("package %s;\n", package_.c_str()) + << "import android.content.Context;\n" + "import android.content.res.Resources;\n" + "import android.content.res.XmlResourceParser;\n" + "import android.util.AttributeSet;\n" + "import android.util.Xml;\n" + "import android.view.*;\n" + "import android.widget.*;\n" + "\n" + "public final class CompiledView {\n" + "\n" + "static <T extends View> T createView(Context context, AttributeSet attrs, View parent, " + "String name, LayoutInflater.Factory factory, LayoutInflater.Factory2 factory2) {" + "\n" + " if (factory2 != null) {\n" + " return (T)factory2.onCreateView(parent, name, context, attrs);\n" + " } else if (factory != null) {\n" + " return (T)factory.onCreateView(name, context, attrs);\n" + " }\n" + // TODO: find a way to call the private factory + " return null;\n" + "}\n" + "\n" + " public static View inflate(Context context) {\n" + " try {\n" + " LayoutInflater inflater = LayoutInflater.from(context);\n" + " LayoutInflater.Factory factory = inflater.getFactory();\n" + " LayoutInflater.Factory2 factory2 = inflater.getFactory2();\n" + " Resources res = context.getResources();\n" + << StringPrintf(" XmlResourceParser xml = res.getLayout(%s.R.layout.%s);\n", + package_.c_str(), + layout_name_.c_str()) + << " AttributeSet attrs = Xml.asAttributeSet(xml);\n" + // The Java-language XmlPullParser needs a call to next to find the start document tag. + " xml.next(); // start document\n"; +} + +void JavaLangViewBuilder::Finish() const { + out_ << " } catch (Exception e) {\n" + " return null;\n" + " }\n" // end try + " }\n" // end inflate + "}\n"; // end CompiledView +} + +void JavaLangViewBuilder::StartView(const string& class_name) { + const string view_var = MakeVar("view"); + const string layout_var = MakeVar("layout"); + std::string parent = "null"; + if (!view_stack_.empty()) { + const StackEntry& parent_entry = view_stack_.back(); + parent = parent_entry.view_var; + } + out_ << " xml.next(); // <" << class_name << ">\n" + << StringPrintf(" %s %s = createView(context, attrs, %s, \"%s\", factory, factory2);\n", + class_name.c_str(), + view_var.c_str(), + parent.c_str(), + class_name.c_str()) + << StringPrintf(" if (%s == null) %s = new %s(context, attrs);\n", + view_var.c_str(), + view_var.c_str(), + class_name.c_str()); + if (!view_stack_.empty()) { + out_ << StringPrintf(" ViewGroup.LayoutParams %s = %s.generateLayoutParams(attrs);\n", + layout_var.c_str(), + parent.c_str()); + } + view_stack_.push_back({class_name, view_var, layout_var}); +} + +void JavaLangViewBuilder::FinishView() { + const StackEntry var = view_stack_.back(); + view_stack_.pop_back(); + if (!view_stack_.empty()) { + const string& parent = view_stack_.back().view_var; + out_ << StringPrintf(" xml.next(); // </%s>\n", var.class_name.c_str()) + << StringPrintf(" %s.addView(%s, %s);\n", + parent.c_str(), + var.view_var.c_str(), + var.layout_params_var.c_str()); + } else { + out_ << StringPrintf(" return %s;\n", var.view_var.c_str()); + } +} + +const std::string JavaLangViewBuilder::MakeVar(std::string prefix) { + std::stringstream v; + v << prefix << view_id_++; + return v.str(); +} diff --git a/startop/tools/view_compiler/java_lang_builder.h b/startop/tools/view_compiler/java_lang_builder.h new file mode 100644 index 000000000000..c8d20b23cd13 --- /dev/null +++ b/startop/tools/view_compiler/java_lang_builder.h @@ -0,0 +1,65 @@ +/* + * 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. + */ +#ifndef JAVA_LANG_BUILDER_H_ +#define JAVA_LANG_BUILDER_H_ + +#include <iostream> +#include <sstream> +#include <vector> + +// Build Java language code to instantiate views. +// +// This has a very small interface to make it easier to generate additional +// backends, such as a direct-to-DEX version. +class JavaLangViewBuilder { + public: + JavaLangViewBuilder(std::string package, std::string layout_name, std::ostream& out = std::cout) + : package_(package), layout_name_(layout_name), out_(out) {} + + // Begin generating a class. Adds the package boilerplate, etc. + void Start() const; + // Finish generating a class, closing off any open curly braces, etc. + void Finish() const; + + // Begin creating a view (i.e. process the opening tag) + void StartView(const std::string& class_name); + // Finish a view, after all of its child nodes have been processed. + void FinishView(); + + private: + const std::string MakeVar(std::string prefix); + + std::string const package_; + std::string const layout_name_; + + std::ostream& out_; + + size_t view_id_ = 0; + + struct StackEntry { + // The class name for this view object + const std::string class_name; + + // The variable name that is holding the view object + const std::string view_var; + + // The variable name that holds the object's layout parameters + const std::string layout_params_var; + }; + std::vector<StackEntry> view_stack_; +}; + +#endif // JAVA_LANG_BUILDER_H_ diff --git a/startop/tools/view_compiler/main.cc b/startop/tools/view_compiler/main.cc new file mode 100644 index 000000000000..0ad7e24feb3b --- /dev/null +++ b/startop/tools/view_compiler/main.cc @@ -0,0 +1,105 @@ +/* + * 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 "gflags/gflags.h" + +#include "java_lang_builder.h" +#include "util.h" + +#include "tinyxml2.h" + +#include <fstream> +#include <iostream> +#include <sstream> +#include <string> +#include <vector> + +using namespace tinyxml2; +using std::string; + +constexpr char kStdoutFilename[]{"stdout"}; + +DEFINE_string(package, "", "The package name for the generated class (required)"); +DEFINE_string(out, kStdoutFilename, "Where to write the generated class"); + +namespace { +class ViewCompilerXmlVisitor : public XMLVisitor { + public: + ViewCompilerXmlVisitor(JavaLangViewBuilder* builder) : builder_(builder) {} + + bool VisitEnter(const XMLDocument& /*doc*/) override { + builder_->Start(); + return true; + } + + bool VisitExit(const XMLDocument& /*doc*/) override { + builder_->Finish(); + return true; + } + + bool VisitEnter(const XMLElement& element, const XMLAttribute* /*firstAttribute*/) override { + builder_->StartView(element.Name()); + return true; + } + + bool VisitExit(const XMLElement& /*element*/) override { + builder_->FinishView(); + return true; + } + + private: + JavaLangViewBuilder* builder_; +}; +} // end namespace + +int main(int argc, char** argv) { + constexpr size_t kProgramName = 0; + constexpr size_t kFileNameParam = 1; + constexpr size_t kNumRequiredArgs = 2; + + gflags::SetUsageMessage( + "Compile XML layout files into equivalent Java language code\n" + "\n" + " example usage: viewcompiler layout.xml --package com.example.androidapp"); + gflags::ParseCommandLineFlags(&argc, &argv, /*remove_flags*/ true); + + gflags::CommandLineFlagInfo cmd = gflags::GetCommandLineFlagInfoOrDie("package"); + if (argc != kNumRequiredArgs || cmd.is_default) { + gflags::ShowUsageWithFlags(argv[kProgramName]); + return 1; + } + + const char* const filename = argv[kFileNameParam]; + const string layout_name = FindLayoutNameFromFilename(filename); + + // We want to generate Java language code to inflate exactly this layout. This means + // generating code to walk the resource XML too. + + XMLDocument xml; + xml.LoadFile(filename); + + std::ofstream outfile; + if (FLAGS_out != kStdoutFilename) { + outfile.open(FLAGS_out); + } + JavaLangViewBuilder builder{ + FLAGS_package, layout_name, FLAGS_out == kStdoutFilename ? std::cout : outfile}; + + ViewCompilerXmlVisitor visitor{&builder}; + xml.Accept(&visitor); + + return 0; +}
\ No newline at end of file diff --git a/startop/tools/view_compiler/util.cc b/startop/tools/view_compiler/util.cc new file mode 100644 index 000000000000..69df41dff3d7 --- /dev/null +++ b/startop/tools/view_compiler/util.cc @@ -0,0 +1,32 @@ +/* + * 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 "util.h" + +using std::string; + +// TODO: see if we can borrow this from somewhere else, like aapt2. +string FindLayoutNameFromFilename(const string& filename) { + size_t start = filename.rfind("/"); + if (start == string::npos) { + start = 0; + } else { + start++; // advance past '/' character + } + size_t end = filename.find(".", start); + + return filename.substr(start, end - start); +} diff --git a/startop/tools/view_compiler/util.h b/startop/tools/view_compiler/util.h new file mode 100644 index 000000000000..03e093920bfa --- /dev/null +++ b/startop/tools/view_compiler/util.h @@ -0,0 +1,23 @@ +/* + * 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. + */ +#ifndef UTIL_H_ +#define UTIL_H_ + +#include <string> + +std::string FindLayoutNameFromFilename(const std::string& filename); + +#endif // UTIL_H_ diff --git a/startop/tools/view_compiler/util_test.cc b/startop/tools/view_compiler/util_test.cc new file mode 100644 index 000000000000..d1540d3a6e43 --- /dev/null +++ b/startop/tools/view_compiler/util_test.cc @@ -0,0 +1,28 @@ +/* + * 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 "util.h" + +#include "gtest/gtest.h" + +using std::string; + +TEST(UtilTest, FindLayoutNameFromFilename) { + EXPECT_EQ("bar", ::FindLayoutNameFromFilename("foo/bar.xml")); + EXPECT_EQ("bar", ::FindLayoutNameFromFilename("bar.xml")); + EXPECT_EQ("bar", ::FindLayoutNameFromFilename("./foo/bar.xml")); + EXPECT_EQ("bar", ::FindLayoutNameFromFilename("/foo/bar.xml")); +} diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java index 8c37a21afa50..d33a537f2194 100644 --- a/telecomm/java/android/telecom/TelecomManager.java +++ b/telecomm/java/android/telecom/TelecomManager.java @@ -1905,6 +1905,22 @@ public class TelecomManager { return false; } + /** + * Handles {@link Intent#ACTION_CALL} intents trampolined from UserCallActivity. + * @param intent The {@link Intent#ACTION_CALL} intent to handle. + * @hide + */ + public void handleCallIntent(Intent intent) { + try { + if (isServiceConnected()) { + getTelecomService().handleCallIntent(intent); + } + } catch (RemoteException e) { + Log.e(TAG, "RemoteException handleCallIntent: " + e); + } + + } + private ITelecomService getTelecomService() { if (mTelecomServiceOverride != null) { return mTelecomServiceOverride; diff --git a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl index 38247bc80e5c..df7d6832833a 100644 --- a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl +++ b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl @@ -284,4 +284,9 @@ interface ITelecomService { * @see TelecomServiceImpl#isInEmergencyCall */ boolean isInEmergencyCall(); + + /** + * @see TelecomServiceImpl#handleCallIntent + */ + void handleCallIntent(in Intent intent); } diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 995418ee706c..9b5b700f7572 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -271,6 +271,14 @@ public class CarrierConfigManager { KEY_HIDE_CARRIER_NETWORK_SETTINGS_BOOL = "hide_carrier_network_settings_bool"; /** + * Do only allow auto selection in Advanced Network Settings when in home network. + * Manual selection is allowed when in roaming network. + * @hide + */ + public static final String + KEY_ONLY_AUTO_SELECT_IN_HOME_NETWORK_BOOL = "only_auto_select_in_home_network"; + + /** * Control whether users receive a simplified network settings UI and improved network * selection. */ @@ -1625,11 +1633,21 @@ public class CarrierConfigManager { * When {@code false}, use default title for Enhanced 4G LTE Mode settings. * When {@code true}, use the variant. * @hide + * @deprecated use {@link #KEY_ENHANCED_4G_LTE_TITLE_VARIANT_INT}. */ + @Deprecated public static final String KEY_ENHANCED_4G_LTE_TITLE_VARIANT_BOOL = "enhanced_4g_lte_title_variant_bool"; /** + * The index indicates the carrier specified title string of Enahnce 4G LTE Mode settings. + * Default value is 0, which indicates the default title string. + * @hide + */ + public static final String KEY_ENHANCED_4G_LTE_TITLE_VARIANT_INT = + "enhanced_4g_lte_title_variant_int"; + + /** * Indicates whether the carrier wants to notify the user when handover of an LTE video call to * WIFI fails. * <p> @@ -2181,6 +2199,7 @@ public class CarrierConfigManager { sDefaults.putBoolean(KEY_ENABLE_DIALER_KEY_VIBRATION_BOOL, true); sDefaults.putBoolean(KEY_HAS_IN_CALL_NOISE_SUPPRESSION_BOOL, false); sDefaults.putBoolean(KEY_HIDE_CARRIER_NETWORK_SETTINGS_BOOL, false); + sDefaults.putBoolean(KEY_ONLY_AUTO_SELECT_IN_HOME_NETWORK_BOOL, false); sDefaults.putBoolean(KEY_SIMPLIFIED_NETWORK_SETTINGS_BOOL, false); sDefaults.putBoolean(KEY_HIDE_SIM_LOCK_SETTINGS_BOOL, false); @@ -2410,6 +2429,7 @@ public class CarrierConfigManager { sDefaults.putStringArray(KEY_IMS_REASONINFO_MAPPING_STRING_ARRAY, null); sDefaults.putBoolean(KEY_ENHANCED_4G_LTE_TITLE_VARIANT_BOOL, false); + sDefaults.putInt(KEY_ENHANCED_4G_LTE_TITLE_VARIANT_INT, 0); sDefaults.putBoolean(KEY_NOTIFY_VT_HANDOVER_TO_WIFI_FAILURE_BOOL, false); sDefaults.putStringArray(KEY_FILTERED_CNAP_NAMES_STRING_ARRAY, null); sDefaults.putBoolean(KEY_EDITABLE_WFC_ROAMING_MODE_BOOL, false); diff --git a/telephony/java/android/telephony/NeighboringCellInfo.java b/telephony/java/android/telephony/NeighboringCellInfo.java index 8e99518d78b8..79298fd54c50 100644 --- a/telephony/java/android/telephony/NeighboringCellInfo.java +++ b/telephony/java/android/telephony/NeighboringCellInfo.java @@ -32,7 +32,12 @@ import android.os.Parcelable; /** * Represents the neighboring cell information, including * Received Signal Strength and Cell ID location. + * + * @deprecated This class should not be used by any app targeting + * {@link Build.VERSION_CODES.Q Android Q} or higher. Instead callers should use + * {@Link android.telephony.CellInfo CellInfo}. */ +@Deprecated public class NeighboringCellInfo implements Parcelable { /** diff --git a/telephony/java/android/telephony/PhoneStateListener.java b/telephony/java/android/telephony/PhoneStateListener.java index 498be968265f..3ea018af97cf 100644 --- a/telephony/java/android/telephony/PhoneStateListener.java +++ b/telephony/java/android/telephony/PhoneStateListener.java @@ -281,6 +281,16 @@ public class PhoneStateListener { */ public static final int LISTEN_PHONE_CAPABILITY_CHANGE = 0x00200000; + /** + * Listen for changes to preferred data subId. + * See {@link SubscriptionManager#setPreferredData(int)} + * for more details. + * + * @see #onPreferredDataSubIdChanged + * @hide + */ + public static final int LISTEN_PREFERRED_DATA_SUBID_CHANGE = 0x00400000; + /* * Subscription used to listen to the phone state changes * @hide @@ -407,6 +417,9 @@ public class PhoneStateListener { PhoneStateListener.this.onPhoneCapabilityChanged( (PhoneCapability) msg.obj); break; + case LISTEN_PREFERRED_DATA_SUBID_CHANGE: + PhoneStateListener.this.onPreferredDataSubIdChanged((int) msg.obj); + break; } } }; @@ -647,6 +660,18 @@ public class PhoneStateListener { } /** + * Callback invoked when preferred data subId changes. Requires + * the READ_PRIVILEGED_PHONE_STATE permission. + * @param subId the new preferred data subId. If it's INVALID_SUBSCRIPTION_ID, + * it means it's unset and defaultDataSub is used to determine which + * modem is preferred. + * @hide + */ + public void onPreferredDataSubIdChanged(int subId) { + // default implementation empty + } + + /** * Callback invoked when telephony has received notice from a carrier * app that a network action that could result in connectivity loss * has been requested by an app using @@ -777,6 +802,11 @@ public class PhoneStateListener { public void onPhoneCapabilityChanged(PhoneCapability capability) { send(LISTEN_PHONE_CAPABILITY_CHANGE, 0, 0, capability); } + + public void onPreferredDataSubIdChanged(int subId) { + send(LISTEN_PREFERRED_DATA_SUBID_CHANGE, 0, 0, subId); + } + } /** diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java index f2b73dccee2d..7469186a5d51 100644 --- a/telephony/java/android/telephony/ServiceState.java +++ b/telephony/java/android/telephony/ServiceState.java @@ -24,6 +24,7 @@ import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; import android.telephony.AccessNetworkConstants.AccessNetworkType; +import android.telephony.NetworkRegistrationState.Domain; import android.text.TextUtils; import java.lang.annotation.Retention; @@ -1595,7 +1596,7 @@ public class ServiceState implements Parcelable { /** * Get all of the available network registration states. * - * @return List of registration states + * @return List of {@link NetworkRegistrationState} * @hide */ @SystemApi @@ -1606,14 +1607,30 @@ public class ServiceState implements Parcelable { } /** - * Get the network registration states with given transport type. + * Get the network registration states for the transport type. * - * @param transportType The transport type. See {@link AccessNetworkConstants.TransportType} - * @return List of registration states. + * @param transportType The {@link AccessNetworkConstants.TransportType transport type} + * @return List of {@link NetworkRegistrationState} * @hide + * + * @deprecated Use {@link #getNetworkRegistrationStatesFromTransportType(int)} */ + @Deprecated @SystemApi public List<NetworkRegistrationState> getNetworkRegistrationStates(int transportType) { + return getNetworkRegistrationStatesForTransportType(transportType); + } + + /** + * Get the network registration states for the transport type. + * + * @param transportType The {@link AccessNetworkConstants.TransportType transport type} + * @return List of {@link NetworkRegistrationState} + * @hide + */ + @SystemApi + public List<NetworkRegistrationState> getNetworkRegistrationStatesForTransportType( + int transportType) { List<NetworkRegistrationState> list = new ArrayList<>(); synchronized (mNetworkRegistrationStates) { @@ -1628,16 +1645,57 @@ public class ServiceState implements Parcelable { } /** - * Get the network registration states with given transport type and domain. + * Get the network registration states for the network domain. * - * @param domain The network domain. Must be {@link NetworkRegistrationState#DOMAIN_CS} or - * {@link NetworkRegistrationState#DOMAIN_PS}. - * @param transportType The transport type. See {@link AccessNetworkConstants.TransportType} - * @return The matching NetworkRegistrationState. + * @param domain The network {@link NetworkRegistrationState.Domain domain} + * @return List of {@link NetworkRegistrationState} * @hide */ @SystemApi - public NetworkRegistrationState getNetworkRegistrationStates(int domain, int transportType) { + public List<NetworkRegistrationState> getNetworkRegistrationStatesForDomain( + @Domain int domain) { + List<NetworkRegistrationState> list = new ArrayList<>(); + + synchronized (mNetworkRegistrationStates) { + for (NetworkRegistrationState networkRegistrationState : mNetworkRegistrationStates) { + if (networkRegistrationState.getDomain() == domain) { + list.add(networkRegistrationState); + } + } + } + + return list; + } + + /** + * Get the network registration state for the transport type and network domain. + * + * @param domain The network {@link NetworkRegistrationState.Domain domain} + * @param transportType The {@link AccessNetworkConstants.TransportType transport type} + * @return The matching {@link NetworkRegistrationState} + * @hide + * + * @deprecated Use {@link #getNetworkRegistrationState(int, int)} + */ + @Deprecated + @SystemApi + public NetworkRegistrationState getNetworkRegistrationStates(@Domain int domain, + int transportType) { + return getNetworkRegistrationState(domain, transportType); + } + + /** + * Get the network registration state for the transport type and network domain. + * + * @param domain The network {@link NetworkRegistrationState.Domain domain} + * @param transportType The {@link AccessNetworkConstants.TransportType transport type} + * @return The matching {@link NetworkRegistrationState} + * @hide + * + */ + @SystemApi + public NetworkRegistrationState getNetworkRegistrationState(@Domain int domain, + int transportType) { synchronized (mNetworkRegistrationStates) { for (NetworkRegistrationState networkRegistrationState : mNetworkRegistrationStates) { if (networkRegistrationState.getTransportType() == transportType @@ -1669,5 +1727,4 @@ public class ServiceState implements Parcelable { mNetworkRegistrationStates.add(regState); } } - } diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java index 38ee79f06690..cc143d63d624 100644 --- a/telephony/java/android/telephony/SubscriptionManager.java +++ b/telephony/java/android/telephony/SubscriptionManager.java @@ -2153,9 +2153,15 @@ public class SubscriptionManager { /** * Set preferred default data. - * Set on which slot default data will be on. + * Set on which slot most cellular data will be on. + * It's also usually what we set up internet connection on. * - * @param slotId which slot is preferred to for cellular data. + * PreferredData overwrites user setting of default data subscription. And it's used + * by AlternativeNetworkAccessService or carrier apps to switch primary and CBRS + * subscription dynamically in multi-SIM devices. + * + * @param slotId which slot is preferred to for cellular data. If it's INVALID, it means + * it's unset and defaultDataSubId is used to determine which modem is preferred. * @hide * */ diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 824533d59bf5..80b6ead339d0 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -1590,6 +1590,7 @@ public class TelephonyManager { * * @return List of NeighboringCellInfo or null if info unavailable. * + * @removed * @deprecated Use {@link #getAllCellInfo} which returns a superset of the information * from NeighboringCellInfo, including LTE cell information. */ @@ -8380,6 +8381,29 @@ public class TelephonyManager { } /** + * Checks if manual network selection is allowed. + * + * <p>If this object has been created with {@link #createForSubscriptionId}, applies to the + * given subId. Otherwise, applies to {@link SubscriptionManager#getDefaultSubscriptionId()}. + * + * @return {@code true} if manual network selection is allowed, otherwise return {@code false}. + * + * @hide + */ + @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) + public boolean isManualNetworkSelectionAllowed() { + try { + ITelephony telephony = getITelephony(); + if (telephony != null) { + return telephony.isManualNetworkSelectionAllowed(getSubId()); + } + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelephony#isManualNetworkSelectionAllowed", e); + } + return true; + } + + /** * Get the most recently available signal strength information. * * Get the most recent SignalStrength information reported by the modem. Due diff --git a/telephony/java/android/telephony/data/ApnSetting.java b/telephony/java/android/telephony/data/ApnSetting.java index c2c93da306d9..8379f8cefda0 100644 --- a/telephony/java/android/telephony/data/ApnSetting.java +++ b/telephony/java/android/telephony/data/ApnSetting.java @@ -1206,7 +1206,8 @@ public class ApnSetting implements Parcelable { /** @hide */ public static int getMvnoTypeIntFromString(String mvnoType) { - Integer mvnoTypeInt = MVNO_TYPE_STRING_MAP.get(mvnoType); + String mvnoTypeString = TextUtils.isEmpty(mvnoType) ? mvnoType : mvnoType.toLowerCase(); + Integer mvnoTypeInt = MVNO_TYPE_STRING_MAP.get(mvnoTypeString); return mvnoTypeInt == null ? UNSPECIFIED_INT : mvnoTypeInt; } diff --git a/telephony/java/android/telephony/ims/ImsCallProfile.java b/telephony/java/android/telephony/ims/ImsCallProfile.java index 5d6a8c158eed..89ef33914c12 100644 --- a/telephony/java/android/telephony/ims/ImsCallProfile.java +++ b/telephony/java/android/telephony/ims/ImsCallProfile.java @@ -117,12 +117,14 @@ public final class ImsCallProfile implements Parcelable { * @hide */ public static final String EXTRA_CONFERENCE = "conference"; + /** * Boolean extra property set on an {@link ImsCallProfile} to indicate that this call is an * emergency call. The {@link ImsService} sets this on a call to indicate that the network has * identified the call as an emergency call. */ - public static final String EXTRA_E_CALL = "e_call"; + public static final String EXTRA_EMERGENCY_CALL = "e_call"; + /** * @hide */ diff --git a/telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java b/telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java index 31381804d143..cecf2e26f139 100644 --- a/telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java +++ b/telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java @@ -21,12 +21,11 @@ import android.annotation.SystemApi; import android.net.Uri; import android.os.RemoteCallbackList; import android.os.RemoteException; +import android.telephony.ims.ImsReasonInfo; import android.telephony.ims.aidl.IImsRegistration; import android.telephony.ims.aidl.IImsRegistrationCallback; import android.util.Log; -import android.telephony.ims.ImsReasonInfo; - import com.android.internal.annotations.VisibleForTesting; import java.lang.annotation.Retention; @@ -81,13 +80,14 @@ public class ImsRegistrationImplBase { * Callback class for receiving Registration callback events. * @hide */ - public static class Callback { + public static class Callback extends IImsRegistrationCallback.Stub { /** * Notifies the framework when the IMS Provider is connected to the IMS network. * * @param imsRadioTech the radio access technology. Valid values are defined in * {@link ImsRegistrationTech}. */ + @Override public void onRegistered(@ImsRegistrationTech int imsRadioTech) { } @@ -97,6 +97,7 @@ public class ImsRegistrationImplBase { * @param imsRadioTech the radio access technology. Valid values are defined in * {@link ImsRegistrationTech}. */ + @Override public void onRegistering(@ImsRegistrationTech int imsRadioTech) { } @@ -105,6 +106,7 @@ public class ImsRegistrationImplBase { * * @param info the {@link ImsReasonInfo} associated with why registration was disconnected. */ + @Override public void onDeregistered(ImsReasonInfo info) { } @@ -115,6 +117,7 @@ public class ImsRegistrationImplBase { * @param imsRadioTech The {@link ImsRegistrationTech} type that has failed * @param info A {@link ImsReasonInfo} that identifies the reason for failure. */ + @Override public void onTechnologyChangeFailed(@ImsRegistrationTech int imsRadioTech, ImsReasonInfo info) { } @@ -125,6 +128,7 @@ public class ImsRegistrationImplBase { * @param uris new array of subscriber {@link Uri}s that are associated with this IMS * subscription. */ + @Override public void onSubscriberAssociatedUriChanged(Uri[] uris) { } diff --git a/telephony/java/com/android/internal/telephony/IPhoneStateListener.aidl b/telephony/java/com/android/internal/telephony/IPhoneStateListener.aidl index 1ebb6976b45e..38a1bc73c94d 100644 --- a/telephony/java/com/android/internal/telephony/IPhoneStateListener.aidl +++ b/telephony/java/com/android/internal/telephony/IPhoneStateListener.aidl @@ -52,5 +52,6 @@ oneway interface IPhoneStateListener { void onCarrierNetworkChange(in boolean active); void onUserMobileDataStateChanged(in boolean enabled); void onPhoneCapabilityChanged(in PhoneCapability capability); + void onPreferredDataSubIdChanged(in int subId); } diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl index ca2bcff2f4cd..32eb12b49cf0 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -808,6 +808,13 @@ interface ITelephony { */ boolean isDataEnabled(int subId); + /** + * Checks if manual network selection is allowed. + * + * @return {@code true} if manual network selection is allowed, otherwise return {@code false}. + */ + boolean isManualNetworkSelectionAllowed(int subId); + /** * Get P-CSCF address from PCO after data connection is established or modified. * @param apnType the apnType, "ims" for IMS APN, "emergency" for EMERGENCY APN diff --git a/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl b/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl index 43d56b39e0c4..c03065c34ca8 100644 --- a/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl @@ -79,4 +79,5 @@ interface ITelephonyRegistry { void notifyCarrierNetworkChange(in boolean active); void notifyUserMobileDataStateChangedForPhoneId(in int phoneId, in int subId, in boolean state); void notifyPhoneCapabilityChanged(in PhoneCapability capability); + void notifyPreferredDataSubIdChanged(int preferredSubId); } diff --git a/telephony/java/com/android/internal/telephony/PhoneConstants.java b/telephony/java/com/android/internal/telephony/PhoneConstants.java index f9de776f9a7b..21f3b92c6c4f 100644 --- a/telephony/java/com/android/internal/telephony/PhoneConstants.java +++ b/telephony/java/com/android/internal/telephony/PhoneConstants.java @@ -176,6 +176,10 @@ public class PhoneConstants { // FIXME: This is used to pass a subId via intents, we need to look at its usage, which is // FIXME: extensive, and see if this should be an array of all active subId's or ...? + /** + * @Deprecated use {@link android.telephony.SubscriptionManager#EXTRA_SUBSCRIPTION_INDEX} + * instead. + */ public static final String SUBSCRIPTION_KEY = "subscription"; public static final String SUB_SETTING = "subSettings"; diff --git a/telephony/java/com/android/internal/telephony/SmsApplication.java b/telephony/java/com/android/internal/telephony/SmsApplication.java index 39722c66e170..5ed283e8e4cb 100644 --- a/telephony/java/com/android/internal/telephony/SmsApplication.java +++ b/telephony/java/com/android/internal/telephony/SmsApplication.java @@ -584,7 +584,8 @@ public final class SmsApplication { // We only make the change if the new package is valid PackageManager packageManager = context.getPackageManager(); - Collection<SmsApplicationData> applications = getApplicationCollection(context); + Collection<SmsApplicationData> applications = getApplicationCollectionInternal( + context, userId); SmsApplicationData oldAppData = oldPackageName != null ? getApplicationForPackage(applications, oldPackageName) : null; SmsApplicationData applicationData = getApplicationForPackage(applications, packageName); diff --git a/test-base/Android.bp b/test-base/Android.bp index 0b8a02a815d9..4d765d3e5f3f 100644 --- a/test-base/Android.bp +++ b/test-base/Android.bp @@ -37,7 +37,8 @@ java_sdk_library { "junit.framework", ], - droiddoc_options: ["stubsourceonly"], + droiddoc_options: ["-stubsourceonly"], + metalava_enabled: false, compile_dex: true, } diff --git a/test-mock/Android.bp b/test-mock/Android.bp index 5eba01779f46..37158e5fe0b9 100644 --- a/test-mock/Android.bp +++ b/test-mock/Android.bp @@ -26,5 +26,6 @@ java_sdk_library { ], srcs_lib_whitelist_pkgs: ["android"], + metalava_enabled: false, compile_dex: true, } diff --git a/test-runner/Android.bp b/test-runner/Android.bp index ea615b920df6..0a0d50cc330c 100644 --- a/test-runner/Android.bp +++ b/test-runner/Android.bp @@ -40,7 +40,8 @@ java_sdk_library { "junit.textui", ], - droiddoc_options: ["stubsourceonly"], + droiddoc_options: ["-stubsourceonly"], + metalava_enabled: false, compile_dex: true } diff --git a/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java b/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java index 1f60b71c3c0f..976848c60dc7 100644 --- a/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java +++ b/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java @@ -106,9 +106,8 @@ public class AppLaunch extends InstrumentationTestCase { private static final String DROP_CACHE_SCRIPT = "/data/local/tmp/dropCache.sh"; private static final String APP_LAUNCH_CMD = "am start -W -n"; private static final String SUCCESS_MESSAGE = "Status: ok"; - private static final String WARNING_MESSAGE = "Warning:"; + private static final String TOTAL_TIME_MESSAGE = "TotalTime:"; private static final String COMPILE_SUCCESS = "Success"; - private static final String THIS_TIME = "ThisTime:"; private static final String LAUNCH_ITERATION = "LAUNCH_ITERATION - %d"; private static final String TRACE_ITERATION = "TRACE_ITERATION-%d"; private static final String LAUNCH_ITERATION_PREFIX = "LAUNCH_ITERATION"; @@ -814,15 +813,13 @@ public class AppLaunch extends InstrumentationTestCase { String launchTime = "-1"; String cpuCycles = "-1"; String majorFaults = "-1"; - boolean coldLaunchSuccess = false; - boolean hotLaunchSuccess = false; + boolean launchSuccess = false; try { InputStream inputStream = new FileInputStream(parcelDesc.getFileDescriptor()); /* SAMPLE OUTPUT : Cold launch Starting: Intent { cmp=com.google.android.calculator/com.android.calculator2.Calculator } Status: ok Activity: com.google.android.calculator/com.android.calculator2.Calculator - ThisTime: 357 TotalTime: 357 WaitTime: 377 Complete*/ @@ -831,7 +828,6 @@ public class AppLaunch extends InstrumentationTestCase { Warning: Activity not started, its current task has been brought to the front Status: ok Activity: com.google.android.calculator/com.android.calculator2.CalculatorGoogle - ThisTime: 60 TotalTime: 60 WaitTime: 67 Complete*/ @@ -842,54 +838,37 @@ public class AppLaunch extends InstrumentationTestCase { Total test time,1.462129,seconds,*/ BufferedReader bufferedReader = new BufferedReader(new InputStreamReader( inputStream)); - String line = null; - int lineCount = 1; + String line; mBufferedWriter.newLine(); mBufferedWriter.write(headerInfo); mBufferedWriter.newLine(); while ((line = bufferedReader.readLine()) != null) { - if (lineCount == 2 && line.startsWith(SUCCESS_MESSAGE)) { - coldLaunchSuccess = true; + mBufferedWriter.write(line); + mBufferedWriter.newLine(); + if (line.startsWith(SUCCESS_MESSAGE)) { + launchSuccess = true; } - if (lineCount == 2 && line.startsWith(WARNING_MESSAGE)) { - hotLaunchSuccess = true; + if (!launchSuccess) { + continue; } // Parse TotalTime which is the launch time - if (coldLaunchSuccess && lineCount == 5) { - String launchSplit[] = line.split(":"); - launchTime = launchSplit[1].trim(); - } - if (hotLaunchSuccess && lineCount == 6) { + if (line.startsWith(TOTAL_TIME_MESSAGE)) { String launchSplit[] = line.split(":"); launchTime = launchSplit[1].trim(); } if (mSimplePerfAppOnly) { - // Parse simpleperf output. - if ((lineCount == 9 && coldLaunchSuccess) - || (lineCount == 10 && hotLaunchSuccess)) { - if (!line.contains("cpu-cycles")) { - Log.e(TAG, "Error in simpleperf output"); - } else { - cpuCycles = line.split(",")[0].trim(); - } - } else if ((lineCount == 10 && coldLaunchSuccess) - || (lineCount == 11 && hotLaunchSuccess)) { - if (!line.contains("major-faults")) { - Log.e(TAG, "Error in simpleperf output"); - } else { - majorFaults = line.split(",")[0].trim(); - } + if (line.contains(",cpu-cycles,")) { + cpuCycles = line.split(",")[0].trim(); + } else if (line.contains(",major-faults,")) { + majorFaults = line.split(",")[0].trim(); } } - mBufferedWriter.write(line); - mBufferedWriter.newLine(); - lineCount++; } mBufferedWriter.flush(); inputStream.close(); } catch (IOException e) { - Log.w(TAG, "Error writing the launch file", e); + Log.w(TAG, "Error parsing launch time and writing to file", e); } return new AppLaunchResult(launchTime, cpuCycles, majorFaults); } diff --git a/tests/NetworkSecurityConfigTest/Android.mk b/tests/NetworkSecurityConfigTest/Android.mk index fe65eccedd24..c225e170c377 100644 --- a/tests/NetworkSecurityConfigTest/Android.mk +++ b/tests/NetworkSecurityConfigTest/Android.mk @@ -7,7 +7,6 @@ LOCAL_CERTIFICATE := platform LOCAL_JAVA_LIBRARIES := \ android.test.runner \ - bouncycastle \ conscrypt \ android.test.base \ diff --git a/tests/RemoteDisplayProvider/Android.mk b/tests/RemoteDisplayProvider/Android.mk index e827ec20ae3e..43bf0243b55b 100644 --- a/tests/RemoteDisplayProvider/Android.mk +++ b/tests/RemoteDisplayProvider/Android.mk @@ -18,9 +18,9 @@ LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_PACKAGE_NAME := RemoteDisplayProviderTest LOCAL_MODULE_TAGS := tests -LOCAL_SDK_VERSION := current +LOCAL_SDK_VERSION := system_current LOCAL_SRC_FILES := $(call all-java-files-under, src) LOCAL_RESOURCE_DIR = $(LOCAL_PATH)/res -LOCAL_JAVA_LIBRARIES := com.android.media.remotedisplay.stubs +LOCAL_JAVA_LIBRARIES := com.android.media.remotedisplay LOCAL_CERTIFICATE := platform include $(BUILD_PACKAGE) diff --git a/tests/net/Android.mk b/tests/net/Android.mk index e529b933bb80..750e2fb6f6b4 100644 --- a/tests/net/Android.mk +++ b/tests/net/Android.mk @@ -38,6 +38,7 @@ LOCAL_JNI_SHARED_LIBRARIES := \ libbacktrace \ libbase \ libbinder \ + libbinderthreadstate \ libc++ \ libcrypto \ libcutils \ diff --git a/tests/net/java/android/net/dhcp/DhcpPacketTest.java b/tests/net/java/android/net/dhcp/DhcpPacketTest.java index 312b3d1878d6..a592809618e6 100644 --- a/tests/net/java/android/net/dhcp/DhcpPacketTest.java +++ b/tests/net/java/android/net/dhcp/DhcpPacketTest.java @@ -25,6 +25,7 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import android.annotation.Nullable; import android.net.DhcpResults; import android.net.LinkAddress; import android.net.NetworkUtils; @@ -37,6 +38,7 @@ import com.android.internal.util.HexDump; import java.io.ByteArrayOutputStream; import java.net.Inet4Address; import java.nio.ByteBuffer; +import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -56,6 +58,8 @@ public class DhcpPacketTest { private static final Inet4Address NETMASK = getPrefixMaskAsInet4Address(PREFIX_LENGTH); private static final Inet4Address BROADCAST_ADDR = getBroadcastAddress( SERVER_ADDR, PREFIX_LENGTH); + private static final String HOSTNAME = "testhostname"; + private static final short MTU = 1500; // Use our own empty address instead of Inet4Address.ANY or INADDR_ANY to ensure that the code // doesn't use == instead of equals when comparing addresses. private static final Inet4Address ANY = (Inet4Address) v4Address("0.0.0.0"); @@ -960,7 +964,8 @@ public class DhcpPacketTest { assertTrue(msg, Arrays.equals(expected, actual)); } - public void checkBuildOfferPacket(int leaseTimeSecs) throws Exception { + public void checkBuildOfferPacket(int leaseTimeSecs, @Nullable String hostname) + throws Exception { final int renewalTime = (int) (Integer.toUnsignedLong(leaseTimeSecs) / 2); final int rebindingTime = (int) (Integer.toUnsignedLong(leaseTimeSecs) * 875 / 1000); final int transactionId = 0xdeadbeef; @@ -971,7 +976,8 @@ public class DhcpPacketTest { CLIENT_MAC, leaseTimeSecs, NETMASK /* netMask */, BROADCAST_ADDR /* bcAddr */, Collections.singletonList(SERVER_ADDR) /* gateways */, Collections.singletonList(SERVER_ADDR) /* dnsServers */, - SERVER_ADDR /* dhcpServerIdentifier */, null /* domainName */, false /* metered */); + SERVER_ADDR /* dhcpServerIdentifier */, null /* domainName */, hostname, + false /* metered */, MTU); ByteArrayOutputStream bos = new ByteArrayOutputStream(); // BOOTP headers @@ -1027,12 +1033,22 @@ public class DhcpPacketTest { // Nameserver bos.write(new byte[] { (byte) 0x06, (byte) 0x04 }); bos.write(SERVER_ADDR.getAddress()); + // Hostname + if (hostname != null) { + bos.write(new byte[]{(byte) 0x0c, (byte) hostname.length()}); + bos.write(hostname.getBytes(Charset.forName("US-ASCII"))); + } + // MTU + bos.write(new byte[] { (byte) 0x1a, (byte) 0x02 }); + bos.write(shortToByteArray(MTU)); // End options. bos.write(0xff); - final byte[] expected = bos.toByteArray(); - assertTrue((expected.length & 1) == 0); + if ((bos.size() & 1) != 0) { + bos.write(0x00); + } + final byte[] expected = bos.toByteArray(); final byte[] actual = new byte[packet.limit()]; packet.get(actual); final String msg = "Expected:\n " + HexDump.dumpHexString(expected) + @@ -1042,13 +1058,18 @@ public class DhcpPacketTest { @Test public void testOfferPacket() throws Exception { - checkBuildOfferPacket(3600); - checkBuildOfferPacket(Integer.MAX_VALUE); - checkBuildOfferPacket(0x80000000); - checkBuildOfferPacket(INFINITE_LEASE); + checkBuildOfferPacket(3600, HOSTNAME); + checkBuildOfferPacket(Integer.MAX_VALUE, HOSTNAME); + checkBuildOfferPacket(0x80000000, HOSTNAME); + checkBuildOfferPacket(INFINITE_LEASE, HOSTNAME); + checkBuildOfferPacket(3600, null); } private static byte[] intToByteArray(int val) { return ByteBuffer.allocate(4).putInt(val).array(); } + + private static byte[] shortToByteArray(short val) { + return ByteBuffer.allocate(2).putShort(val).array(); + } } diff --git a/tests/net/java/android/net/dhcp/DhcpServerTest.java b/tests/net/java/android/net/dhcp/DhcpServerTest.java index 45a50d9a8b5f..df34c7310b63 100644 --- a/tests/net/java/android/net/dhcp/DhcpServerTest.java +++ b/tests/net/java/android/net/dhcp/DhcpServerTest.java @@ -17,6 +17,7 @@ package android.net.dhcp; import static android.net.dhcp.DhcpPacket.DHCP_CLIENT; +import static android.net.dhcp.DhcpPacket.DHCP_HOST_NAME; import static android.net.dhcp.DhcpPacket.ENCAP_BOOTP; import static android.net.dhcp.DhcpPacket.INADDR_ANY; import static android.net.dhcp.DhcpPacket.INADDR_BROADCAST; @@ -87,6 +88,7 @@ public class DhcpServerTest { Arrays.asList(parseAddr("192.168.0.200"), parseAddr("192.168.0.201"))); private static final long TEST_LEASE_TIME_SECS = 3600L; private static final int TEST_MTU = 1500; + private static final String TEST_HOSTNAME = "testhostname"; private static final int TEST_TRANSACTION_ID = 123; private static final byte[] TEST_CLIENT_MAC_BYTES = new byte [] { 1, 2, 3, 4, 5, 6 }; @@ -96,7 +98,10 @@ public class DhcpServerTest { private static final long TEST_CLOCK_TIME = 1234L; private static final int TEST_LEASE_EXPTIME_SECS = 3600; private static final DhcpLease TEST_LEASE = new DhcpLease(null, TEST_CLIENT_MAC, - TEST_CLIENT_ADDR, TEST_LEASE_EXPTIME_SECS*1000L + TEST_CLOCK_TIME, null /* hostname */); + TEST_CLIENT_ADDR, TEST_LEASE_EXPTIME_SECS * 1000L + TEST_CLOCK_TIME, + null /* hostname */); + private static final DhcpLease TEST_LEASE_WITH_HOSTNAME = new DhcpLease(null, TEST_CLIENT_MAC, + TEST_CLIENT_ADDR, TEST_LEASE_EXPTIME_SECS * 1000L + TEST_CLOCK_TIME, TEST_HOSTNAME); @NonNull @Mock private Dependencies mDeps; @@ -217,15 +222,17 @@ public class DhcpServerTest { public void testRequest_Selecting_Ack() throws Exception { when(mRepository.requestLease(isNull() /* clientId */, eq(TEST_CLIENT_MAC), eq(INADDR_ANY) /* clientAddr */, eq(INADDR_ANY) /* relayAddr */, - eq(TEST_CLIENT_ADDR) /* reqAddr */, eq(true) /* sidSet */, isNull() /* hostname */)) - .thenReturn(TEST_LEASE); + eq(TEST_CLIENT_ADDR) /* reqAddr */, eq(true) /* sidSet */, eq(TEST_HOSTNAME))) + .thenReturn(TEST_LEASE_WITH_HOSTNAME); final DhcpRequestPacket request = makeRequestSelectingPacket(); + request.mHostName = TEST_HOSTNAME; + request.mRequestedParams = new byte[] { DHCP_HOST_NAME }; mServer.processPacket(request, DHCP_CLIENT); assertResponseSentTo(TEST_CLIENT_ADDR); final DhcpAckPacket packet = assertAck(getPacket()); - assertMatchesTestLease(packet); + assertMatchesTestLease(packet, TEST_HOSTNAME); } @Test @@ -270,14 +277,18 @@ public class DhcpServerTest { * - other request states (init-reboot/renewing/rebinding) */ - private void assertMatchesTestLease(@NonNull DhcpPacket packet) { + private void assertMatchesTestLease(@NonNull DhcpPacket packet, @Nullable String hostname) { assertMatchesClient(packet); assertFalse(packet.hasExplicitClientId()); assertEquals(TEST_SERVER_ADDR, packet.mServerIdentifier); assertEquals(TEST_CLIENT_ADDR, packet.mYourIp); assertNotNull(packet.mLeaseTime); assertEquals(TEST_LEASE_EXPTIME_SECS, (int) packet.mLeaseTime); - assertNull(packet.mHostName); + assertEquals(hostname, packet.mHostName); + } + + private void assertMatchesTestLease(@NonNull DhcpPacket packet) { + assertMatchesTestLease(packet, null); } private void assertMatchesClient(@NonNull DhcpPacket packet) { diff --git a/tests/net/java/android/net/netlink/InetDiagSocketTest.java b/tests/net/java/android/net/netlink/InetDiagSocketTest.java index 39ecb7e5a45e..122edbaf078c 100644 --- a/tests/net/java/android/net/netlink/InetDiagSocketTest.java +++ b/tests/net/java/android/net/netlink/InetDiagSocketTest.java @@ -69,17 +69,12 @@ public class InetDiagSocketTest { private ConnectivityManager mCm; private Context mContext; private final static int SOCKET_TIMEOUT_MS = 100; - private boolean mInetDiagUdpEnabled; @Before public void setUp() throws Exception { Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation(); mContext = instrumentation.getTargetContext(); mCm = (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE); - int expectedUid = Process.myUid(); - UdpConnection udp = new UdpConnection("127.0.0.1", "127.0.0.2"); - int uid = mCm.getConnectionOwnerUid(udp.protocol, udp.local, udp.remote); - mInetDiagUdpEnabled = (uid == expectedUid); } private class Connection { @@ -188,11 +183,6 @@ public class InetDiagSocketTest { tcp.close(); /** - * TODO: STOPSHIP: Always test for UDP, do not allow opt-out. - */ - if (!mInetDiagUdpEnabled) return; - - /** * For UDP connections, either a complete match {protocol, local, remote} or a * partial match {protocol, local} should return a valid UID. */ diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java index fceaabddfec2..1c77fcc568f6 100644 --- a/tests/net/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java @@ -1070,13 +1070,13 @@ public class ConnectivityServiceTest { // Ensure that the default setting for Captive Portals is used for most tests setCaptivePortalMode(Settings.Global.CAPTIVE_PORTAL_MODE_PROMPT); - setMobileDataAlwaysOn(false); + setAlwaysOnNetworks(false); setPrivateDnsSettings(PRIVATE_DNS_MODE_OFF, "ignored.example.com"); } @After public void tearDown() throws Exception { - setMobileDataAlwaysOn(false); + setAlwaysOnNetworks(false); if (mCellNetworkAgent != null) { mCellNetworkAgent.disconnect(); mCellNetworkAgent = null; @@ -2027,7 +2027,7 @@ public class ConnectivityServiceTest { @Test public void testNetworkGoesIntoBackgroundAfterLinger() { - setMobileDataAlwaysOn(true); + setAlwaysOnNetworks(true); NetworkRequest request = new NetworkRequest.Builder() .clearCapabilities() .build(); @@ -2772,10 +2772,10 @@ public class ConnectivityServiceTest { Settings.Global.putInt(cr, Settings.Global.CAPTIVE_PORTAL_MODE, mode); } - private void setMobileDataAlwaysOn(boolean enable) { + private void setAlwaysOnNetworks(boolean enable) { ContentResolver cr = mServiceContext.getContentResolver(); Settings.Global.putInt(cr, Settings.Global.MOBILE_DATA_ALWAYS_ON, enable ? 1 : 0); - mService.updateMobileDataAlwaysOn(); + mService.updateAlwaysOnNetworks(); waitForIdle(); } @@ -2797,7 +2797,7 @@ public class ConnectivityServiceTest { public void testBackgroundNetworks() throws Exception { // Create a background request. We can't do this ourselves because ConnectivityService // doesn't have an API for it. So just turn on mobile data always on. - setMobileDataAlwaysOn(true); + setAlwaysOnNetworks(true); final NetworkRequest request = new NetworkRequest.Builder().build(); final NetworkRequest fgRequest = new NetworkRequest.Builder() .addCapability(NET_CAPABILITY_FOREGROUND).build(); @@ -2995,7 +2995,7 @@ public class ConnectivityServiceTest { // Turn on mobile data always on. The factory starts looking again. testFactory.expectAddRequests(1); - setMobileDataAlwaysOn(true); + setAlwaysOnNetworks(true); testFactory.waitForNetworkRequests(2); assertTrue(testFactory.getMyStartRequested()); @@ -3015,7 +3015,7 @@ public class ConnectivityServiceTest { // Turn off mobile data always on and expect the request to disappear... testFactory.expectRemoveRequests(1); - setMobileDataAlwaysOn(false); + setAlwaysOnNetworks(false); testFactory.waitForNetworkRequests(1); // ... and cell data to be torn down. @@ -4536,4 +4536,78 @@ public class ConnectivityServiceTest { mCellNetworkAgent.disconnect(); mCm.unregisterNetworkCallback(networkCallback); } + + @Test + public void testDataActivityTracking() throws RemoteException { + final TestNetworkCallback networkCallback = new TestNetworkCallback(); + final NetworkRequest networkRequest = new NetworkRequest.Builder() + .addCapability(NET_CAPABILITY_INTERNET) + .build(); + mCm.registerNetworkCallback(networkRequest, networkCallback); + + mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); + final LinkProperties cellLp = new LinkProperties(); + cellLp.setInterfaceName(MOBILE_IFNAME); + mCellNetworkAgent.sendLinkProperties(cellLp); + reset(mNetworkManagementService); + mCellNetworkAgent.connect(true); + networkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); + verify(mNetworkManagementService, times(1)).addIdleTimer(eq(MOBILE_IFNAME), anyInt(), + eq(ConnectivityManager.TYPE_MOBILE)); + + mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); + final LinkProperties wifiLp = new LinkProperties(); + wifiLp.setInterfaceName(WIFI_IFNAME); + mWiFiNetworkAgent.sendLinkProperties(wifiLp); + + // Network switch + reset(mNetworkManagementService); + mWiFiNetworkAgent.connect(true); + networkCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); + networkCallback.expectCallback(CallbackState.LOSING, mCellNetworkAgent); + networkCallback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent); + verify(mNetworkManagementService, times(1)).addIdleTimer(eq(WIFI_IFNAME), anyInt(), + eq(ConnectivityManager.TYPE_WIFI)); + verify(mNetworkManagementService, times(1)).removeIdleTimer(eq(MOBILE_IFNAME)); + + // Disconnect wifi and switch back to cell + reset(mNetworkManagementService); + mWiFiNetworkAgent.disconnect(); + networkCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); + assertNoCallbacks(networkCallback); + verify(mNetworkManagementService, times(1)).removeIdleTimer(eq(WIFI_IFNAME)); + verify(mNetworkManagementService, times(1)).addIdleTimer(eq(MOBILE_IFNAME), anyInt(), + eq(ConnectivityManager.TYPE_MOBILE)); + + // reconnect wifi + mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); + wifiLp.setInterfaceName(WIFI_IFNAME); + mWiFiNetworkAgent.sendLinkProperties(wifiLp); + mWiFiNetworkAgent.connect(true); + networkCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); + networkCallback.expectCallback(CallbackState.LOSING, mCellNetworkAgent); + networkCallback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent); + + // Disconnect cell + reset(mNetworkManagementService); + mCellNetworkAgent.disconnect(); + networkCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent); + // LOST callback is triggered earlier than removing idle timer. Broadcast should also be + // sent as network being switched. Ensure rule removal for cell will not be triggered + // unexpectedly before network being removed. + waitForIdle(); + verify(mNetworkManagementService, times(0)).removeIdleTimer(eq(MOBILE_IFNAME)); + verify(mNetworkManagementService, times(1)).removeNetwork( + eq(mCellNetworkAgent.getNetwork().netId)); + + // Disconnect wifi + ConditionVariable cv = waitForConnectivityBroadcasts(1); + reset(mNetworkManagementService); + mWiFiNetworkAgent.disconnect(); + waitFor(cv); + verify(mNetworkManagementService, times(1)).removeIdleTimer(eq(WIFI_IFNAME)); + + // Clean up + mCm.unregisterNetworkCallback(networkCallback); + } } diff --git a/tests/net/java/com/android/server/connectivity/TetheringTest.java b/tests/net/java/com/android/server/connectivity/TetheringTest.java index 40d5544dccd8..a6ed9f252008 100644 --- a/tests/net/java/com/android/server/connectivity/TetheringTest.java +++ b/tests/net/java/com/android/server/connectivity/TetheringTest.java @@ -33,6 +33,7 @@ import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_INTERFACE_NAME; import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_MODE; import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_STATE; import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLED; +import static android.provider.Settings.Global.TETHER_ENABLE_LEGACY_DHCP_SERVER; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; @@ -75,6 +76,8 @@ import android.net.NetworkRequest; import android.net.NetworkState; import android.net.NetworkUtils; import android.net.RouteInfo; +import android.net.dhcp.DhcpServer; +import android.net.dhcp.DhcpServingParams; import android.net.ip.IpServer; import android.net.ip.RouterAdvertisementDaemon; import android.net.util.InterfaceParams; @@ -85,6 +88,7 @@ import android.net.wifi.WifiManager; import android.os.Bundle; import android.os.Handler; import android.os.INetworkManagementService; +import android.os.Looper; import android.os.PersistableBundle; import android.os.RemoteException; import android.os.test.TestLooper; @@ -146,6 +150,7 @@ public class TetheringTest { @Mock private UpstreamNetworkMonitor mUpstreamNetworkMonitor; @Mock private IPv6TetheringCoordinator mIPv6TetheringCoordinator; @Mock private RouterAdvertisementDaemon mRouterAdvertisementDaemon; + @Mock private DhcpServer mDhcpServer; @Mock private INetd mNetd; private final MockTetheringDependencies mTetheringDependencies = @@ -240,6 +245,12 @@ public class TetheringTest { public INetd getNetdService() { return mNetd; } + + @Override + public DhcpServer makeDhcpServer(Looper looper, InterfaceParams iface, + DhcpServingParams params, SharedLog log) { + return mDhcpServer; + } }; } @@ -333,6 +344,7 @@ public class TetheringTest { mServiceContext = new MockContext(mContext); mContentResolver = new MockContentResolver(mServiceContext); mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider()); + Settings.Global.putInt(mContentResolver, TETHER_ENABLE_LEGACY_DHCP_SERVER, 0); mIntents = new Vector<>(); mBroadcastReceiver = new BroadcastReceiver() { @Override @@ -343,12 +355,16 @@ public class TetheringTest { mServiceContext.registerReceiver(mBroadcastReceiver, new IntentFilter(ACTION_TETHER_STATE_CHANGED)); mTetheringDependencies.reset(); - mTethering = new Tethering(mServiceContext, mNMService, mStatsService, mPolicyManager, - mLooper.getLooper(), mSystemProperties, - mTetheringDependencies); + mTethering = makeTethering(); verify(mNMService).registerTetheringStatsProvider(any(), anyString()); } + private Tethering makeTethering() { + return new Tethering(mServiceContext, mNMService, mStatsService, mPolicyManager, + mLooper.getLooper(), mSystemProperties, + mTetheringDependencies); + } + @After public void tearDown() { mServiceContext.unregisterReceiver(mBroadcastReceiver); @@ -597,6 +613,18 @@ public class TetheringTest { sendIPv6TetherUpdates(upstreamState); verify(mRouterAdvertisementDaemon, never()).buildNewRa(any(), notNull()); + verify(mDhcpServer, times(1)).start(); + } + + @Test + public void workingMobileUsbTethering_IPv4LegacyDhcp() { + Settings.Global.putInt(mContentResolver, TETHER_ENABLE_LEGACY_DHCP_SERVER, 1); + mTethering = makeTethering(); + final NetworkState upstreamState = buildMobileIPv4UpstreamState(); + runUsbTethering(upstreamState); + sendIPv6TetherUpdates(upstreamState); + + verify(mDhcpServer, never()).start(); } @Test @@ -620,6 +648,7 @@ public class TetheringTest { verify(mNMService, times(1)).enableNat(TEST_USB_IFNAME, TEST_MOBILE_IFNAME); verify(mNMService, times(1)).startInterfaceForwarding(TEST_USB_IFNAME, TEST_MOBILE_IFNAME); verify(mRouterAdvertisementDaemon, times(1)).start(); + verify(mDhcpServer, times(1)).start(); sendIPv6TetherUpdates(upstreamState); verify(mRouterAdvertisementDaemon, times(1)).buildNewRa(any(), notNull()); @@ -633,6 +662,7 @@ public class TetheringTest { verify(mNMService, times(1)).enableNat(TEST_USB_IFNAME, TEST_XLAT_MOBILE_IFNAME); verify(mNMService, times(1)).enableNat(TEST_USB_IFNAME, TEST_MOBILE_IFNAME); + verify(mDhcpServer, times(1)).start(); verify(mNMService, times(1)).startInterfaceForwarding(TEST_USB_IFNAME, TEST_MOBILE_IFNAME); verify(mNMService, times(1)).startInterfaceForwarding(TEST_USB_IFNAME, TEST_XLAT_MOBILE_IFNAME); @@ -649,6 +679,7 @@ public class TetheringTest { runUsbTethering(upstreamState); verify(mNMService, times(1)).enableNat(TEST_USB_IFNAME, TEST_MOBILE_IFNAME); + verify(mDhcpServer, times(1)).start(); verify(mNMService, times(1)).startInterfaceForwarding(TEST_USB_IFNAME, TEST_MOBILE_IFNAME); // Then 464xlat comes up @@ -671,6 +702,8 @@ public class TetheringTest { // Forwarding was not re-added for v6 (still times(1)) verify(mNMService, times(1)).enableNat(TEST_USB_IFNAME, TEST_MOBILE_IFNAME); verify(mNMService, times(1)).startInterfaceForwarding(TEST_USB_IFNAME, TEST_MOBILE_IFNAME); + // DHCP not restarted on downstream (still times(1)) + verify(mDhcpServer, times(1)).start(); } @Test diff --git a/tests/net/java/com/android/server/connectivity/tethering/TetheringConfigurationTest.java b/tests/net/java/com/android/server/connectivity/tethering/TetheringConfigurationTest.java index bb312309b34f..521778484d91 100644 --- a/tests/net/java/com/android/server/connectivity/tethering/TetheringConfigurationTest.java +++ b/tests/net/java/com/android/server/connectivity/tethering/TetheringConfigurationTest.java @@ -225,13 +225,4 @@ public class TetheringConfigurationTest { final TetheringConfiguration cfg = new TetheringConfiguration(mMockContext, mLog); assertFalse(cfg.enableLegacyDhcpServer); } - - @Test - public void testNewDhcpServerDefault() { - Settings.Global.putString(mContentResolver, TETHER_ENABLE_LEGACY_DHCP_SERVER, null); - - final TetheringConfiguration cfg = new TetheringConfiguration(mMockContext, mLog); - // TODO: change to false when new server is promoted to default - assertTrue(cfg.enableLegacyDhcpServer); - } } diff --git a/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java b/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java index 9c4da1f72e38..14312cf84693 100644 --- a/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java +++ b/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java @@ -83,16 +83,6 @@ public class WindowManagerPermissionTests extends TestCase { } try { - mWm.setFocusedApp(null, false); - fail("IWindowManager.setFocusedApp did not throw SecurityException as" - + " expected"); - } catch (SecurityException e) { - // expected - } catch (RemoteException e) { - fail("Unexpected remote exception"); - } - - try { mWm.prepareAppTransition(0, false); fail("IWindowManager.prepareAppTransition did not throw SecurityException as" + " expected"); diff --git a/tests/testables/src/android/testing/TestableContext.java b/tests/testables/src/android/testing/TestableContext.java index cf84c7926549..fff9635992d4 100644 --- a/tests/testables/src/android/testing/TestableContext.java +++ b/tests/testables/src/android/testing/TestableContext.java @@ -53,7 +53,7 @@ import org.junit.runners.model.Statement; * Like the following:</p> * <pre class="prettyprint"> * @Rule - * private final TestableContext mContext = new TestableContext(InstrumentationRegister.getContext()); + * public final TestableContext mContext = new TestableContext(InstrumentationRegister.getContext()); * </pre> */ public class TestableContext extends ContextWrapper implements TestRule { diff --git a/tools/aapt2/ConfigDescription.h b/tools/aapt2/ConfigDescription.h index f71955247d78..b46a50398217 100644 --- a/tools/aapt2/ConfigDescription.h +++ b/tools/aapt2/ConfigDescription.h @@ -53,11 +53,11 @@ struct ConfigDescription : public android::ResTable_config { ConfigDescription(); ConfigDescription(const android::ResTable_config& o); // NOLINT(implicit) ConfigDescription(const ConfigDescription& o); - ConfigDescription(ConfigDescription&& o); + ConfigDescription(ConfigDescription&& o) noexcept; ConfigDescription& operator=(const android::ResTable_config& o); ConfigDescription& operator=(const ConfigDescription& o); - ConfigDescription& operator=(ConfigDescription&& o); + ConfigDescription& operator=(ConfigDescription&& o) noexcept; ConfigDescription CopyWithoutSdkVersion() const; @@ -124,7 +124,7 @@ inline ConfigDescription::ConfigDescription(const ConfigDescription& o) { *static_cast<android::ResTable_config*>(this) = o; } -inline ConfigDescription::ConfigDescription(ConfigDescription&& o) { +inline ConfigDescription::ConfigDescription(ConfigDescription&& o) noexcept { *this = o; } @@ -141,7 +141,7 @@ inline ConfigDescription& ConfigDescription::operator=( return *this; } -inline ConfigDescription& ConfigDescription::operator=(ConfigDescription&& o) { +inline ConfigDescription& ConfigDescription::operator=(ConfigDescription&& o) noexcept { *this = o; return *this; } diff --git a/tools/aapt2/ResourceUtils.cpp b/tools/aapt2/ResourceUtils.cpp index c48765b7b947..de00fcaea07b 100644 --- a/tools/aapt2/ResourceUtils.cpp +++ b/tools/aapt2/ResourceUtils.cpp @@ -38,6 +38,8 @@ using ::android::base::StringPrintf; namespace aapt { namespace ResourceUtils { +constexpr int32_t kNonBreakingSpace = 0xa0; + Maybe<ResourceName> ToResourceName( const android::ResTable::resource_name& name_in) { ResourceName name_out; @@ -810,7 +812,7 @@ StringBuilder& StringBuilder::AppendText(const std::string& text, bool preserve_ Utf8Iterator iter(text); while (iter.HasNext()) { char32_t codepoint = iter.Next(); - if (!preserve_spaces && !quote_ && iswspace(codepoint)) { + if (!preserve_spaces && !quote_ && codepoint != kNonBreakingSpace && iswspace(codepoint)) { if (!last_codepoint_was_space_) { // Emit a space if it's the first. xml_string_.text += ' '; diff --git a/tools/aapt2/cmd/Compile.cpp b/tools/aapt2/cmd/Compile.cpp index 62c19fbfcdd3..8060a8de4629 100644 --- a/tools/aapt2/cmd/Compile.cpp +++ b/tools/aapt2/cmd/Compile.cpp @@ -73,9 +73,9 @@ struct ResourcePathData { }; // Resource file paths are expected to look like: [--/res/]type[-config]/name -static Maybe<ResourcePathData> ExtractResourcePathData(const std::string& path, +static Maybe<ResourcePathData> ExtractResourcePathData(const std::string& path, const char dir_sep, std::string* out_error) { - std::vector<std::string> parts = util::Split(path, file::sDirSep); + std::vector<std::string> parts = util::Split(path, dir_sep); if (parts.size() < 2) { if (out_error) *out_error = "bad resource path"; return {}; @@ -656,7 +656,7 @@ int Compile(IAaptContext* context, io::IFileCollection* inputs, IArchiveWriter* // Extract resource type information from the full path std::string err_str; ResourcePathData path_data; - if (auto maybe_path_data = ExtractResourcePathData(path, &err_str)) { + if (auto maybe_path_data = ExtractResourcePathData(path, inputs->GetDirSeparator(), &err_str)) { path_data = maybe_path_data.value(); } else { context->GetDiagnostics()->Error(DiagMessage(file->GetSource()) << err_str); @@ -731,7 +731,7 @@ int CompileCommand::Action(const std::vector<std::string>& args) { // Collect the resources files to compile if (options_.res_dir && options_.res_zip) { context.GetDiagnostics()->Error(DiagMessage() - << "only one of --dir and --zip can be specified"); + << "only one of --dir and --zip can be specified"); return 1; } else if (options_.res_dir) { if (!args.empty()) { diff --git a/tools/aapt2/cmd/Compile_test.cpp b/tools/aapt2/cmd/Compile_test.cpp index dd5198ce86da..c0c05cda35e7 100644 --- a/tools/aapt2/cmd/Compile_test.cpp +++ b/tools/aapt2/cmd/Compile_test.cpp @@ -17,6 +17,9 @@ #include "Compile.h" #include "android-base/file.h" +#include "android-base/stringprintf.h" +#include "android-base/utf8.h" + #include "io/StringStream.h" #include "io/ZipArchive.h" #include "java/AnnotationProcessor.h" @@ -24,8 +27,20 @@ namespace aapt { +std::string BuildPath(std::vector<std::string> args) { + std::string out; + if (args.empty()) { + return out; + } + out = args[0]; + for (int i = 1; i < args.size(); i++) { + file::AppendPath(&out, args[i]); + } + return out; +} + int TestCompile(const std::string& path, const std::string& outDir, bool legacy, - StdErrDiagnostics& diag) { + StdErrDiagnostics& diag) { std::vector<android::StringPiece> args; args.push_back(path); args.push_back("-o"); @@ -39,95 +54,101 @@ int TestCompile(const std::string& path, const std::string& outDir, bool legacy, TEST(CompilerTest, MultiplePeriods) { StdErrDiagnostics diag; std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); - const std::string kResDir = android::base::Dirname(android::base::GetExecutablePath()) - + "/integration-tests/CompileTest/res"; + const std::string kResDir = BuildPath({android::base::Dirname(android::base::GetExecutablePath()), + "integration-tests", "CompileTest", "res"}); // Resource files without periods in the file name should not throw errors - const std::string path0 = kResDir + "/values/values.xml"; - const std::string path0_out = kResDir + "/values_values.arsc.flat"; - - remove(path0_out.c_str()); + const std::string path0 = BuildPath({kResDir, "values", "values.xml"}); + const std::string path0_out = BuildPath({kResDir, "values_values.arsc.flat"}); + ::android::base::utf8::unlink(path0_out.c_str()); ASSERT_EQ(TestCompile(path0, kResDir, /** legacy */ false, diag), 0); - ASSERT_EQ(remove(path0_out.c_str()), 0); + ASSERT_EQ(::android::base::utf8::unlink(path0_out.c_str()), 0); ASSERT_EQ(TestCompile(path0, kResDir, /** legacy */ true, diag), 0); - ASSERT_EQ(remove(path0_out.c_str()), 0); + ASSERT_EQ(::android::base::utf8::unlink(path0_out.c_str()), 0); - const std::string path1 = kResDir + "/drawable/image.png"; - const std::string path1_out = kResDir + "/drawable_image.png.flat"; - remove(path1_out.c_str()); + const std::string path1 = BuildPath({kResDir, "drawable", "image.png"}); + const std::string path1_out = BuildPath({kResDir, "drawable_image.png.flat"}); + ::android::base::utf8::unlink(path1_out.c_str()); ASSERT_EQ(TestCompile(path1, kResDir, /** legacy */ false, diag), 0); - ASSERT_EQ(remove(path1_out.c_str()), 0); + ASSERT_EQ(::android::base::utf8::unlink(path1_out.c_str()), 0); ASSERT_EQ(TestCompile(path1, kResDir, /** legacy */ true, diag), 0); - ASSERT_EQ(remove(path1_out.c_str()), 0); + ASSERT_EQ(::android::base::utf8::unlink(path1_out.c_str()), 0); - const std::string path2 = kResDir + "/drawable/image.9.png"; - const std::string path2_out = kResDir + "/drawable_image.9.png.flat"; - remove(path2_out.c_str()); + const std::string path2 = BuildPath({kResDir, "drawable", "image.9.png"}); + const std::string path2_out = BuildPath({kResDir, "drawable_image.9.png.flat"}); + ::android::base::utf8::unlink(path2_out.c_str()); ASSERT_EQ(TestCompile(path2, kResDir, /** legacy */ false, diag), 0); - ASSERT_EQ(remove(path2_out.c_str()), 0); + ASSERT_EQ(::android::base::utf8::unlink(path2_out.c_str()), 0); ASSERT_EQ(TestCompile(path2, kResDir, /** legacy */ true, diag), 0); - ASSERT_EQ(remove(path2_out.c_str()), 0); + ASSERT_EQ(::android::base::utf8::unlink(path2_out.c_str()), 0); // Resource files with periods in the file name should fail on non-legacy compilations - const std::string path3 = kResDir + "/values/values.all.xml"; - const std::string path3_out = kResDir + "/values_values.all.arsc.flat"; - remove(path3_out.c_str()); + const std::string path3 = BuildPath({kResDir, "values", "values.all.xml"}); + const std::string path3_out = BuildPath({kResDir, "values_values.all.arsc.flat"}); + ::android::base::utf8::unlink(path3_out.c_str()); ASSERT_NE(TestCompile(path3, kResDir, /** legacy */ false, diag), 0); - ASSERT_NE(remove(path3_out.c_str()), 0); + ASSERT_NE(::android::base::utf8::unlink(path3_out.c_str()), 0); ASSERT_EQ(TestCompile(path3, kResDir, /** legacy */ true, diag), 0); - ASSERT_EQ(remove(path3_out.c_str()), 0); + ASSERT_EQ(::android::base::utf8::unlink(path3_out.c_str()), 0); - const std::string path4 = kResDir + "/drawable/image.small.png"; - const std::string path4_out = (kResDir + std::string("/drawable_image.small.png.flat")).c_str(); - remove(path4_out.c_str()); + const std::string path4 = BuildPath({kResDir, "drawable", "image.small.png"}); + const std::string path4_out = BuildPath({kResDir, "drawable_image.small.png.flat"}); + ::android::base::utf8::unlink(path4_out.c_str()); ASSERT_NE(TestCompile(path4, kResDir, /** legacy */ false, diag), 0); - ASSERT_NE(remove(path4_out.c_str()), 0); + ASSERT_NE(::android::base::utf8::unlink(path4_out.c_str()), 0); ASSERT_EQ(TestCompile(path4, kResDir, /** legacy */ true, diag), 0); - ASSERT_EQ(remove(path4_out.c_str()), 0); + ASSERT_EQ(::android::base::utf8::unlink(path4_out.c_str()), 0); - const std::string path5 = kResDir + "/drawable/image.small.9.png"; - const std::string path5_out = (kResDir + std::string("/drawable_image.small.9.png.flat")).c_str(); - remove(path5_out.c_str()); + const std::string path5 = BuildPath({kResDir, "drawable", "image.small.9.png"}); + const std::string path5_out = BuildPath({kResDir, "drawable_image.small.9.png.flat"}); + ::android::base::utf8::unlink(path5_out.c_str()); ASSERT_NE(TestCompile(path5, kResDir, /** legacy */ false, diag), 0); - ASSERT_NE(remove(path5_out.c_str()), 0); + ASSERT_NE(::android::base::utf8::unlink(path5_out.c_str()), 0); ASSERT_EQ(TestCompile(path5, kResDir, /** legacy */ true, diag), 0); - ASSERT_EQ(remove(path5_out.c_str()), 0); + ASSERT_EQ(::android::base::utf8::unlink(path5_out.c_str()), 0); } TEST(CompilerTest, DirInput) { StdErrDiagnostics diag; std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); - const std::string kResDir = android::base::Dirname(android::base::GetExecutablePath()) - + "/integration-tests/CompileTest/DirInput/res"; - const std::string kOutputFlata = android::base::Dirname(android::base::GetExecutablePath()) - + "/integration-tests/CompileTest/DirInput/compiled.flata"; - remove(kOutputFlata.c_str()); + const std::string kResDir = BuildPath({android::base::Dirname(android::base::GetExecutablePath()), + "integration-tests", "CompileTest", "DirInput", "res"}); + const std::string kOutputFlata = + BuildPath({android::base::Dirname(android::base::GetExecutablePath()), "integration-tests", + "CompileTest", "DirInput", "compiled.flata"}); + ::android::base::utf8::unlink(kOutputFlata.c_str()); std::vector<android::StringPiece> args; args.push_back("--dir"); args.push_back(kResDir); args.push_back("-o"); args.push_back(kOutputFlata); + args.push_back("-v"); ASSERT_EQ(CompileCommand(&diag).Execute(args, &std::cerr), 0); - // Check for the presence of the compiled files - std::string err; - std::unique_ptr<io::ZipFileCollection> zip = io::ZipFileCollection::Create(kOutputFlata, &err); - ASSERT_NE(zip, nullptr) << err; - ASSERT_NE(zip->FindFile("drawable_image.png.flat"), nullptr); - ASSERT_NE(zip->FindFile("layout_layout.xml.flat"), nullptr); - ASSERT_NE(zip->FindFile("values_values.arsc.flat"), nullptr); - ASSERT_EQ(remove(kOutputFlata.c_str()), 0); + { + // Check for the presence of the compiled files + std::string err; + std::unique_ptr<io::ZipFileCollection> zip = io::ZipFileCollection::Create(kOutputFlata, &err); + ASSERT_NE(zip, nullptr) << err; + ASSERT_NE(zip->FindFile("drawable_image.png.flat"), nullptr); + ASSERT_NE(zip->FindFile("layout_layout.xml.flat"), nullptr); + ASSERT_NE(zip->FindFile("values_values.arsc.flat"), nullptr); + } + ASSERT_EQ(::android::base::utf8::unlink(kOutputFlata.c_str()), 0); } TEST(CompilerTest, ZipInput) { StdErrDiagnostics diag; std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); - const std::string kResZip = android::base::Dirname(android::base::GetExecutablePath()) - + "/integration-tests/CompileTest/ZipInput/res.zip"; - const std::string kOutputFlata = android::base::Dirname(android::base::GetExecutablePath()) - + "/integration-tests/CompileTest/ZipInput/compiled.flata"; - remove(kOutputFlata.c_str()); + const std::string kResZip = + BuildPath({android::base::Dirname(android::base::GetExecutablePath()), "integration-tests", + "CompileTest", "ZipInput", "res.zip"}); + const std::string kOutputFlata = + BuildPath({android::base::Dirname(android::base::GetExecutablePath()), "integration-tests", + "CompileTest", "ZipInput", "compiled.flata"}); + + ::android::base::utf8::unlink(kOutputFlata.c_str()); std::vector<android::StringPiece> args; args.push_back("--zip"); @@ -136,14 +157,16 @@ TEST(CompilerTest, ZipInput) { args.push_back(kOutputFlata); ASSERT_EQ(CompileCommand(&diag).Execute(args, &std::cerr), 0); - // Check for the presence of the compiled files - std::string err; - std::unique_ptr<io::ZipFileCollection> zip = io::ZipFileCollection::Create(kOutputFlata, &err); - ASSERT_NE(zip, nullptr) << err; - ASSERT_NE(zip->FindFile("drawable_image.png.flat"), nullptr); - ASSERT_NE(zip->FindFile("layout_layout.xml.flat"), nullptr); - ASSERT_NE(zip->FindFile("values_values.arsc.flat"), nullptr); - ASSERT_EQ(remove(kOutputFlata.c_str()), 0); + { + // Check for the presence of the compiled files + std::string err; + std::unique_ptr<io::ZipFileCollection> zip = io::ZipFileCollection::Create(kOutputFlata, &err); + ASSERT_NE(zip, nullptr) << err; + ASSERT_NE(zip->FindFile("drawable_image.png.flat"), nullptr); + ASSERT_NE(zip->FindFile("layout_layout.xml.flat"), nullptr); + ASSERT_NE(zip->FindFile("values_values.arsc.flat"), nullptr); + } + ASSERT_EQ(::android::base::utf8::unlink(kOutputFlata.c_str()), 0); } -} // namespace aapt
\ No newline at end of file +} // namespace aapt
\ No newline at end of file diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp index 13c10478ba3e..20ea3cb6ffac 100644 --- a/tools/aapt2/cmd/Link.cpp +++ b/tools/aapt2/cmd/Link.cpp @@ -1260,7 +1260,7 @@ class Linker { return false; } - proguard::WriteKeepSet(keep_set, &fout); + proguard::WriteKeepSet(keep_set, &fout, options_.generate_minimal_proguard_rules); fout.Flush(); if (fout.HadError()) { diff --git a/tools/aapt2/cmd/Link.h b/tools/aapt2/cmd/Link.h index e58a93ec416c..a42c0f250e3e 100644 --- a/tools/aapt2/cmd/Link.h +++ b/tools/aapt2/cmd/Link.h @@ -49,6 +49,7 @@ struct LinkOptions { Maybe<std::string> generate_proguard_rules_path; Maybe<std::string> generate_main_dex_proguard_rules_path; bool generate_conditional_proguard_rules = false; + bool generate_minimal_proguard_rules = false; bool generate_non_final_ids = false; std::vector<std::string> javadoc_annotations; Maybe<std::string> private_symbols; @@ -119,6 +120,9 @@ class LinkCommand : public Command { AddOptionalSwitch("--proguard-conditional-keep-rules", "Generate conditional Proguard keep rules.", &options_.generate_conditional_proguard_rules); + AddOptionalSwitch("--proguard-minimal-keep-rules", + "Generate a minimal set of Proguard keep rules.", + &options_.generate_minimal_proguard_rules); AddOptionalSwitch("--no-auto-version", "Disables automatic style and layout SDK versioning.", &options_.no_auto_version); AddOptionalSwitch("--no-version-vectors", diff --git a/tools/aapt2/cmd/Util_test.cpp b/tools/aapt2/cmd/Util_test.cpp index 158ef299f6e2..6aeff08e618e 100644 --- a/tools/aapt2/cmd/Util_test.cpp +++ b/tools/aapt2/cmd/Util_test.cpp @@ -16,12 +16,22 @@ #include "Util.h" +#include "android-base/stringprintf.h" + #include "AppInfo.h" #include "split/TableSplitter.h" #include "test/Builders.h" #include "test/Test.h" +#include "util/Files.h" namespace aapt { + +#ifdef _WIN32 +#define CREATE_PATH(path) android::base::StringPrintf(";%s", path) +#else +#define CREATE_PATH(path) android::base::StringPrintf(":%s", path) +#endif + #define EXPECT_CONFIG_EQ(constraints, config) \ EXPECT_EQ(constraints.configs.size(), 1); \ EXPECT_EQ(*constraints.configs.begin(), config); \ @@ -89,7 +99,7 @@ TEST (UtilTest, LongVersionCodeUndefined) { } -TEST (UtilTest, ParseSplitParameter) { +TEST (UtilTest, ParseSplitParameters) { IDiagnostics* diagnostics = test::ContextBuilder().Build().get()->GetDiagnostics(); std::string path; SplitConstraints constraints; @@ -98,14 +108,14 @@ TEST (UtilTest, ParseSplitParameter) { // ========== Test IMSI ========== // mcc: 'mcc[0-9]{3}' // mnc: 'mnc[0-9]{1,3}' - ASSERT_TRUE(ParseSplitParameter(":mcc310", + ASSERT_TRUE(ParseSplitParameter(CREATE_PATH("mcc310"), diagnostics, &path, &constraints)); expected_configuration = test::ConfigDescriptionBuilder() .setMcc(0x0136) .Build(); EXPECT_CONFIG_EQ(constraints, expected_configuration); - ASSERT_TRUE(ParseSplitParameter(":mcc310-mnc004", + ASSERT_TRUE(ParseSplitParameter(CREATE_PATH("mcc310-mnc004"), diagnostics, &path, &constraints)); expected_configuration = test::ConfigDescriptionBuilder() .setMcc(0x0136) @@ -113,7 +123,7 @@ TEST (UtilTest, ParseSplitParameter) { .Build(); EXPECT_CONFIG_EQ(constraints, expected_configuration); - ASSERT_TRUE(ParseSplitParameter(":mcc310-mnc000", + ASSERT_TRUE(ParseSplitParameter(CREATE_PATH("mcc310-mnc000"), diagnostics, &path, &constraints)); expected_configuration = test::ConfigDescriptionBuilder() .setMcc(0x0136) @@ -124,14 +134,14 @@ TEST (UtilTest, ParseSplitParameter) { // ========== Test LOCALE ========== // locale: '[a-z]{2,3}(-r[a-z]{2})?' // locale: 'b+[a-z]{2,3}(+[a-z[0-9]]{2})?' - ASSERT_TRUE(ParseSplitParameter(":es", + ASSERT_TRUE(ParseSplitParameter(CREATE_PATH("es"), diagnostics, &path, &constraints)); expected_configuration = test::ConfigDescriptionBuilder() .setLanguage(0x6573) .Build(); EXPECT_CONFIG_EQ(constraints, expected_configuration); - ASSERT_TRUE(ParseSplitParameter(":fr-rCA", + ASSERT_TRUE(ParseSplitParameter(CREATE_PATH("fr-rCA"), diagnostics, &path, &constraints)); expected_configuration = test::ConfigDescriptionBuilder() .setLanguage(0x6672) @@ -139,7 +149,7 @@ TEST (UtilTest, ParseSplitParameter) { .Build(); EXPECT_CONFIG_EQ(constraints, expected_configuration); - ASSERT_TRUE(ParseSplitParameter(":b+es+419", + ASSERT_TRUE(ParseSplitParameter(CREATE_PATH("b+es+419"), diagnostics, &path, &constraints)); expected_configuration = test::ConfigDescriptionBuilder() .setLanguage(0x6573) @@ -151,21 +161,21 @@ TEST (UtilTest, ParseSplitParameter) { // orientation: '(port|land|square)' // touchscreen: '(notouch|stylus|finger)' // density" '(anydpi|nodpi|ldpi|mdpi|tvdpi|hdpi|xhdpi|xxhdpi|xxxhdpi|[0-9]*dpi)' - ASSERT_TRUE(ParseSplitParameter(":square", + ASSERT_TRUE(ParseSplitParameter(CREATE_PATH("square"), diagnostics, &path, &constraints)); expected_configuration = test::ConfigDescriptionBuilder() .setOrientation(0x03) .Build(); EXPECT_CONFIG_EQ(constraints, expected_configuration); - ASSERT_TRUE(ParseSplitParameter(":stylus", + ASSERT_TRUE(ParseSplitParameter(CREATE_PATH("stylus"), diagnostics, &path, &constraints)); expected_configuration = test::ConfigDescriptionBuilder() .setTouchscreen(0x02) .Build(); EXPECT_CONFIG_EQ(constraints, expected_configuration); - ASSERT_TRUE(ParseSplitParameter(":xxxhdpi", + ASSERT_TRUE(ParseSplitParameter(CREATE_PATH("xxxhdpi"), diagnostics, &path, &constraints)); expected_configuration = test::ConfigDescriptionBuilder() .setDensity(0x0280) @@ -173,7 +183,7 @@ TEST (UtilTest, ParseSplitParameter) { .Build(); EXPECT_CONFIG_EQ(constraints, expected_configuration); - ASSERT_TRUE(ParseSplitParameter(":land-xhdpi-finger", + ASSERT_TRUE(ParseSplitParameter(CREATE_PATH("land-xhdpi-finger"), diagnostics, &path, &constraints)); expected_configuration = test::ConfigDescriptionBuilder() .setOrientation(0x02) @@ -188,28 +198,28 @@ TEST (UtilTest, ParseSplitParameter) { // navigation: '(nonav|dpad|trackball|wheel)' // inputFlags: '(keysexposed|keyshidden|keyssoft)' // inputFlags: '(navexposed|navhidden)' - ASSERT_TRUE(ParseSplitParameter(":qwerty", + ASSERT_TRUE(ParseSplitParameter(CREATE_PATH("qwerty"), diagnostics, &path, &constraints)); expected_configuration = test::ConfigDescriptionBuilder() .setKeyboard(0x02) .Build(); EXPECT_CONFIG_EQ(constraints, expected_configuration); - ASSERT_TRUE(ParseSplitParameter(":dpad", + ASSERT_TRUE(ParseSplitParameter(CREATE_PATH("dpad"), diagnostics, &path, &constraints)); expected_configuration = test::ConfigDescriptionBuilder() .setNavigation(0x02) .Build(); EXPECT_CONFIG_EQ(constraints, expected_configuration); - ASSERT_TRUE(ParseSplitParameter(":keyssoft-navhidden", + ASSERT_TRUE(ParseSplitParameter(CREATE_PATH("keyssoft-navhidden"), diagnostics, &path, &constraints)); expected_configuration = test::ConfigDescriptionBuilder() .setInputFlags(0x0B) .Build(); EXPECT_CONFIG_EQ(constraints, expected_configuration); - ASSERT_TRUE(ParseSplitParameter(":keyshidden-nokeys-navexposed-trackball", + ASSERT_TRUE(ParseSplitParameter(CREATE_PATH("keyshidden-nokeys-navexposed-trackball"), diagnostics, &path, &constraints)); expected_configuration = test::ConfigDescriptionBuilder() .setKeyboard(0x01) @@ -220,7 +230,7 @@ TEST (UtilTest, ParseSplitParameter) { // ========== Test SCREEN_SIZE ========== // screenWidth/screenHeight: '[0-9]+x[0-9]+' - ASSERT_TRUE(ParseSplitParameter(":1920x1080", + ASSERT_TRUE(ParseSplitParameter(CREATE_PATH("1920x1080"), diagnostics, &path, &constraints)); expected_configuration = test::ConfigDescriptionBuilder() .setScreenWidth(0x0780) @@ -238,14 +248,14 @@ TEST (UtilTest, ParseSplitParameter) { // uiMode [type]: '(desk|car|television|appliance|watch|vrheadset)' // uiMode [night]: '(night|notnight)' // smallestScreenWidthDp: 'sw[0-9]dp' - ASSERT_TRUE(ParseSplitParameter(":ldrtl", + ASSERT_TRUE(ParseSplitParameter(CREATE_PATH("ldrtl"), diagnostics, &path, &constraints)); expected_configuration = test::ConfigDescriptionBuilder() .setScreenLayout(0x80) .Build(); EXPECT_CONFIG_EQ(constraints, expected_configuration); - ASSERT_TRUE(ParseSplitParameter(":small", + ASSERT_TRUE(ParseSplitParameter(CREATE_PATH("small"), diagnostics, &path, &constraints)); expected_configuration = test::ConfigDescriptionBuilder() .setScreenLayout(0x01) @@ -253,7 +263,7 @@ TEST (UtilTest, ParseSplitParameter) { .Build(); EXPECT_CONFIG_EQ(constraints, expected_configuration); - ASSERT_TRUE(ParseSplitParameter(":notlong", + ASSERT_TRUE(ParseSplitParameter(CREATE_PATH("notlong"), diagnostics, &path, &constraints)); expected_configuration = test::ConfigDescriptionBuilder() .setScreenLayout(0x10) @@ -261,15 +271,15 @@ TEST (UtilTest, ParseSplitParameter) { .Build(); EXPECT_CONFIG_EQ(constraints, expected_configuration); - ASSERT_TRUE(ParseSplitParameter(":ldltr-normal-long", - diagnostics, &path, &constraints)); + ASSERT_TRUE(ParseSplitParameter(CREATE_PATH("ldltr-normal-long"), + diagnostics, &path, &constraints)); expected_configuration = test::ConfigDescriptionBuilder() .setScreenLayout(0x62) .setSdkVersion(0x0004) // screenLayout (size|long) requires donut .Build(); EXPECT_CONFIG_EQ(constraints, expected_configuration); - ASSERT_TRUE(ParseSplitParameter(":car", + ASSERT_TRUE(ParseSplitParameter(CREATE_PATH("car"), diagnostics, &path, &constraints)); expected_configuration = test::ConfigDescriptionBuilder() .setUiMode(0x03) @@ -277,7 +287,7 @@ TEST (UtilTest, ParseSplitParameter) { .Build(); EXPECT_CONFIG_EQ(constraints, expected_configuration); - ASSERT_TRUE(ParseSplitParameter(":vrheadset", + ASSERT_TRUE(ParseSplitParameter(CREATE_PATH("vrheadset"), diagnostics, &path, &constraints)); expected_configuration = test::ConfigDescriptionBuilder() .setUiMode(0x07) @@ -285,7 +295,7 @@ TEST (UtilTest, ParseSplitParameter) { .Build(); EXPECT_CONFIG_EQ(constraints, expected_configuration); - ASSERT_TRUE(ParseSplitParameter(":television-night", + ASSERT_TRUE(ParseSplitParameter(CREATE_PATH("television-night"), diagnostics, &path, &constraints)); expected_configuration = test::ConfigDescriptionBuilder() .setUiMode(0x24) @@ -293,7 +303,7 @@ TEST (UtilTest, ParseSplitParameter) { .Build(); EXPECT_CONFIG_EQ(constraints, expected_configuration); - ASSERT_TRUE(ParseSplitParameter(":sw1920dp", + ASSERT_TRUE(ParseSplitParameter(CREATE_PATH("sw1920dp"), diagnostics, &path, &constraints)); expected_configuration = test::ConfigDescriptionBuilder() .setSmallestScreenWidthDp(0x0780) @@ -304,7 +314,7 @@ TEST (UtilTest, ParseSplitParameter) { // ========== Test SCREEN_SIZE_DP ========== // screenWidthDp: 'w[0-9]dp' // screenHeightDp: 'h[0-9]dp' - ASSERT_TRUE(ParseSplitParameter(":w1920dp", + ASSERT_TRUE(ParseSplitParameter(CREATE_PATH("w1920dp"), diagnostics, &path, &constraints)); expected_configuration = test::ConfigDescriptionBuilder() .setScreenWidthDp(0x0780) @@ -312,7 +322,7 @@ TEST (UtilTest, ParseSplitParameter) { .Build(); EXPECT_CONFIG_EQ(constraints, expected_configuration); - ASSERT_TRUE(ParseSplitParameter(":h1080dp", + ASSERT_TRUE(ParseSplitParameter(CREATE_PATH("h1080dp"), diagnostics, &path, &constraints)); expected_configuration = test::ConfigDescriptionBuilder() .setScreenHeightDp(0x0438) @@ -324,7 +334,7 @@ TEST (UtilTest, ParseSplitParameter) { // screenLayout2: '(round|notround)' // colorMode: '(widecg|nowidecg)' // colorMode: '(highhdr|lowdr)' - ASSERT_TRUE(ParseSplitParameter(":round", + ASSERT_TRUE(ParseSplitParameter(CREATE_PATH("round"), diagnostics, &path, &constraints)); expected_configuration = test::ConfigDescriptionBuilder() .setScreenLayout2(0x02) @@ -332,7 +342,7 @@ TEST (UtilTest, ParseSplitParameter) { .Build(); EXPECT_CONFIG_EQ(constraints, expected_configuration); - ASSERT_TRUE(ParseSplitParameter(":widecg-highdr", + ASSERT_TRUE(ParseSplitParameter(CREATE_PATH("widecg-highdr"), diagnostics, &path, &constraints)); expected_configuration = test::ConfigDescriptionBuilder() .setColorMode(0x0A) @@ -349,10 +359,10 @@ TEST (UtilTest, AdjustSplitConstraintsForMinSdk) { std::string path; test_constraints.push_back({}); - ASSERT_TRUE(ParseSplitParameter(":v7", + ASSERT_TRUE(ParseSplitParameter(CREATE_PATH("v7"), diagnostics, &path, &test_constraints.back())); test_constraints.push_back({}); - ASSERT_TRUE(ParseSplitParameter(":xhdpi", + ASSERT_TRUE(ParseSplitParameter(CREATE_PATH("xhdpi"), diagnostics, &path, &test_constraints.back())); EXPECT_EQ(test_constraints.size(), 2); EXPECT_EQ(test_constraints[0].name, "v7"); diff --git a/tools/aapt2/dump/DumpManifest.cpp b/tools/aapt2/dump/DumpManifest.cpp index 2c356d1491d5..d8cdff7c6411 100644 --- a/tools/aapt2/dump/DumpManifest.cpp +++ b/tools/aapt2/dump/DumpManifest.cpp @@ -1326,29 +1326,29 @@ class MetaData : public ManifestExtractor::Element { public: MetaData() = default; std::string name; - const std::string* value; + std::string value; const int* value_int; - const std::string* resource; + std::string resource; const int* resource_int; void Extract(xml::Element* element) override { name = GetAttributeStringDefault(FindAttribute(element, NAME_ATTR), ""); - value = GetAttributeString(FindAttribute(element, VALUE_ATTR)); + value = GetAttributeStringDefault(FindAttribute(element, VALUE_ATTR), ""); value_int = GetAttributeInteger(FindAttribute(element, VALUE_ATTR)); - resource = GetAttributeString(FindAttribute(element, RESOURCE_ATTR)); + resource = GetAttributeStringDefault(FindAttribute(element, RESOURCE_ATTR), ""); resource_int = GetAttributeInteger(FindAttribute(element, RESOURCE_ATTR)); } void Print(text::Printer& printer) override { if (extractor()->options_.include_meta_data && !name.empty()) { printer.Print(StringPrintf("meta-data: name='%s' ", name.data())); - if (value) { - printer.Print(StringPrintf("value='%s' ", value->data())); + if (!value.empty()) { + printer.Print(StringPrintf("value='%s' ", value.data())); } else if (value_int) { printer.Print(StringPrintf("value='%d' ", *value_int)); } else { - if (resource) { - printer.Print(StringPrintf("resource='%s' ", resource->data())); + if (!resource.empty()) { + printer.Print(StringPrintf("resource='%s' ", resource.data())); } else if (resource_int) { printer.Print(StringPrintf("resource='%d' ", *resource_int)); } @@ -1837,10 +1837,10 @@ bool ManifestExtractor::Dump(text::Printer& printer, IDiagnostics* diag) { && offhost_apdu_action)) { // Attempt to load the resource file - if (!meta_data->resource) { + if (!meta_data->resource.empty()) { return; } - auto resource = apk->LoadXml(*meta_data->resource, diag); + auto resource = apk->LoadXml(meta_data->resource, diag); if (!resource) { return; } diff --git a/tools/aapt2/format/proto/ProtoSerialize_test.cpp b/tools/aapt2/format/proto/ProtoSerialize_test.cpp index 21fdbd8a4c1e..74295ab85846 100644 --- a/tools/aapt2/format/proto/ProtoSerialize_test.cpp +++ b/tools/aapt2/format/proto/ProtoSerialize_test.cpp @@ -33,6 +33,7 @@ class MockFileCollection : public io::IFileCollection { public: MOCK_METHOD1(FindFile, io::IFile*(const StringPiece& path)); MOCK_METHOD0(Iterator, std::unique_ptr<io::IFileCollectionIterator>()); + MOCK_METHOD0(GetDirSeparator, char()); }; TEST(ProtoSerializeTest, SerializeSinglePackage) { diff --git a/tools/aapt2/io/File.h b/tools/aapt2/io/File.h index f06e28c79c7b..565aad6f2284 100644 --- a/tools/aapt2/io/File.h +++ b/tools/aapt2/io/File.h @@ -25,6 +25,7 @@ #include "Source.h" #include "io/Data.h" +#include "util/Files.h" #include "util/Util.h" namespace aapt { @@ -103,6 +104,7 @@ class IFileCollection { virtual IFile* FindFile(const android::StringPiece& path) = 0; virtual std::unique_ptr<IFileCollectionIterator> Iterator() = 0; + virtual char GetDirSeparator() = 0; }; } // namespace io diff --git a/tools/aapt2/io/FileSystem.cpp b/tools/aapt2/io/FileSystem.cpp index 16a20f4cb09d..51cc9032fb3e 100644 --- a/tools/aapt2/io/FileSystem.cpp +++ b/tools/aapt2/io/FileSystem.cpp @@ -128,5 +128,9 @@ std::unique_ptr<IFileCollectionIterator> FileCollection::Iterator() { return util::make_unique<FileCollectionIterator>(this); } +char FileCollection::GetDirSeparator() { + return file::sDirSep; +} + } // namespace io } // namespace aapt diff --git a/tools/aapt2/io/FileSystem.h b/tools/aapt2/io/FileSystem.h index fb6bf6eeabbc..04c6fa15bc85 100644 --- a/tools/aapt2/io/FileSystem.h +++ b/tools/aapt2/io/FileSystem.h @@ -67,6 +67,7 @@ class FileCollection : public IFileCollection { IFile* InsertFile(const android::StringPiece& path); IFile* FindFile(const android::StringPiece& path) override; std::unique_ptr<IFileCollectionIterator> Iterator() override; + char GetDirSeparator() override; private: DISALLOW_COPY_AND_ASSIGN(FileCollection); diff --git a/tools/aapt2/io/ZipArchive.cpp b/tools/aapt2/io/ZipArchive.cpp index 8e6d7137640a..427dc92505d4 100644 --- a/tools/aapt2/io/ZipArchive.cpp +++ b/tools/aapt2/io/ZipArchive.cpp @@ -33,6 +33,11 @@ ZipFile::ZipFile(ZipArchiveHandle handle, const ZipEntry& entry, : zip_handle_(handle), zip_entry_(entry), source_(source) {} std::unique_ptr<IData> ZipFile::OpenAsData() { + // The file will fail to be mmaped if it is empty + if (zip_entry_.uncompressed_length == 0) { + return util::make_unique<EmptyData>(); + } + if (zip_entry_.method == kCompressStored) { int fd = GetFileDescriptor(zip_handle_); @@ -154,6 +159,13 @@ std::unique_ptr<IFileCollectionIterator> ZipFileCollection::Iterator() { return util::make_unique<ZipFileCollectionIterator>(this); } +char ZipFileCollection::GetDirSeparator() { + // According to the zip file specification, section 4.4.17.1: + // "All slashes MUST be forward slashes '/' as opposed to backwards slashes '\' for compatibility + // with Amiga and UNIX file systems etc." + return '/'; +} + ZipFileCollection::~ZipFileCollection() { if (handle_) { CloseArchive(handle_); diff --git a/tools/aapt2/io/ZipArchive.h b/tools/aapt2/io/ZipArchive.h index 8381259d77c5..b283e57d4011 100644 --- a/tools/aapt2/io/ZipArchive.h +++ b/tools/aapt2/io/ZipArchive.h @@ -66,6 +66,7 @@ class ZipFileCollection : public IFileCollection { io::IFile* FindFile(const android::StringPiece& path) override; std::unique_ptr<IFileCollectionIterator> Iterator() override; + char GetDirSeparator() override; ~ZipFileCollection() override; diff --git a/tools/aapt2/java/ManifestClassGenerator.cpp b/tools/aapt2/java/ManifestClassGenerator.cpp index be67c9c8c03c..10e504ec0752 100644 --- a/tools/aapt2/java/ManifestClassGenerator.cpp +++ b/tools/aapt2/java/ManifestClassGenerator.cpp @@ -26,21 +26,20 @@ #include "util/Maybe.h" #include "xml/XmlDom.h" -using ::android::StringPiece; using ::aapt::text::IsJavaIdentifier; namespace aapt { -static Maybe<StringPiece> ExtractJavaIdentifier(IDiagnostics* diag, const Source& source, +static Maybe<std::string> ExtractJavaIdentifier(IDiagnostics* diag, const Source& source, const std::string& value) { - StringPiece result = value; + std::string result = value; size_t pos = value.rfind('.'); if (pos != std::string::npos) { result = result.substr(pos + 1); } // Normalize only the java identifier, leave the original value unchanged. - if (result.contains("-")) { + if (result.find("-") != std::string::npos) { result = JavaClassGenerator::TransformToFieldName(result); } @@ -64,7 +63,7 @@ static bool WriteSymbol(const Source& source, IDiagnostics* diag, xml::Element* return false; } - Maybe<StringPiece> result = + Maybe<std::string> result = ExtractJavaIdentifier(diag, source.WithLine(el->line_number), attr->value); if (!result) { return false; diff --git a/tools/aapt2/java/ProguardRules.cpp b/tools/aapt2/java/ProguardRules.cpp index d40795accf79..52e168ed47aa 100644 --- a/tools/aapt2/java/ProguardRules.cpp +++ b/tools/aapt2/java/ProguardRules.cpp @@ -384,7 +384,7 @@ bool CollectProguardRules(IAaptContext* context_, xml::XmlResource* res, KeepSet return true; } -void WriteKeepSet(const KeepSet& keep_set, OutputStream* out) { +void WriteKeepSet(const KeepSet& keep_set, OutputStream* out, bool minimal_keep) { Printer printer(out); for (const auto& entry : keep_set.manifest_class_set_) { for (const UsageLocation& location : entry.second) { @@ -406,15 +406,19 @@ void WriteKeepSet(const KeepSet& keep_set, OutputStream* out) { printer.Print("-if class **.R$layout { int ") .Print(JavaClassGenerator::TransformToFieldName(location.name.entry)) .Println("; }"); - printer.Print("-keep class ").Print(entry.first.name).Print(" { <init>(") - .Print(entry.first.signature).Println("); }"); + + printer.Print("-keep class ").Print(entry.first.name).Print(" { <init>("); + printer.Print((minimal_keep) ? entry.first.signature : "..."); + printer.Println("); }"); } } else { for (const UsageLocation& location : entry.second) { printer.Print("# Referenced at ").Println(location.source.to_string()); } - printer.Print("-keep class ").Print(entry.first.name).Print(" { <init>(") - .Print(entry.first.signature).Println("); }"); + + printer.Print("-keep class ").Print(entry.first.name).Print(" { <init>("); + printer.Print((minimal_keep) ? entry.first.signature : "..."); + printer.Println("); }"); } printer.Println(); } diff --git a/tools/aapt2/java/ProguardRules.h b/tools/aapt2/java/ProguardRules.h index 01dad0b08aea..38b4860d1d61 100644 --- a/tools/aapt2/java/ProguardRules.h +++ b/tools/aapt2/java/ProguardRules.h @@ -70,7 +70,7 @@ class KeepSet { } private: - friend void WriteKeepSet(const KeepSet& keep_set, io::OutputStream* out); + friend void WriteKeepSet(const KeepSet& keep_set, io::OutputStream* out, bool minimal_keep); friend bool CollectLocations(const UsageLocation& location, const KeepSet& keep_set, std::set<UsageLocation>* locations); @@ -89,7 +89,7 @@ bool CollectProguardRules(IAaptContext* context, xml::XmlResource* res, KeepSet* bool CollectResourceReferences(IAaptContext* context, ResourceTable* table, KeepSet* keep_set); -void WriteKeepSet(const KeepSet& keep_set, io::OutputStream* out); +void WriteKeepSet(const KeepSet& keep_set, io::OutputStream* out, bool minimal_keep); bool CollectLocations(const UsageLocation& location, const KeepSet& keep_set, std::set<UsageLocation>* locations); diff --git a/tools/aapt2/java/ProguardRules_test.cpp b/tools/aapt2/java/ProguardRules_test.cpp index 83c72d89bb62..3d93cb1dd43b 100644 --- a/tools/aapt2/java/ProguardRules_test.cpp +++ b/tools/aapt2/java/ProguardRules_test.cpp @@ -26,10 +26,10 @@ using ::testing::Not; namespace aapt { -std::string GetKeepSetString(const proguard::KeepSet& set) { +std::string GetKeepSetString(const proguard::KeepSet& set, bool minimal_rules) { std::string out; StringOutputStream sout(&out); - proguard::WriteKeepSet(set, &sout); + proguard::WriteKeepSet(set, &sout, minimal_rules); sout.Flush(); return out; } @@ -53,8 +53,17 @@ TEST(ProguardRulesTest, ManifestRuleDefaultConstructorOnly) { proguard::KeepSet set; ASSERT_TRUE(proguard::CollectProguardRulesForManifest(manifest.get(), &set, false)); - std::string actual = GetKeepSetString(set); + std::string actual = GetKeepSetString(set, /** minimal_rules */ false); + EXPECT_THAT(actual, HasSubstr("-keep class com.foo.BarAppComponentFactory { <init>(); }")); + EXPECT_THAT(actual, HasSubstr("-keep class com.foo.BarBackupAgent { <init>(); }")); + EXPECT_THAT(actual, HasSubstr("-keep class com.foo.BarApplication { <init>(); }")); + EXPECT_THAT(actual, HasSubstr("-keep class com.foo.BarActivity { <init>(); }")); + EXPECT_THAT(actual, HasSubstr("-keep class com.foo.BarService { <init>(); }")); + EXPECT_THAT(actual, HasSubstr("-keep class com.foo.BarReceiver { <init>(); }")); + EXPECT_THAT(actual, HasSubstr("-keep class com.foo.BarProvider { <init>(); }")); + EXPECT_THAT(actual, HasSubstr("-keep class com.foo.BarInstrumentation { <init>(); }")); + actual = GetKeepSetString(set, /** minimal_rules */ true); EXPECT_THAT(actual, HasSubstr("-keep class com.foo.BarAppComponentFactory { <init>(); }")); EXPECT_THAT(actual, HasSubstr("-keep class com.foo.BarBackupAgent { <init>(); }")); EXPECT_THAT(actual, HasSubstr("-keep class com.foo.BarApplication { <init>(); }")); @@ -75,8 +84,10 @@ TEST(ProguardRulesTest, FragmentNameRuleIsEmitted) { proguard::KeepSet set; ASSERT_TRUE(proguard::CollectProguardRules(context.get(), layout.get(), &set)); - std::string actual = GetKeepSetString(set); + std::string actual = GetKeepSetString(set, /** minimal_rules */ false); + EXPECT_THAT(actual, HasSubstr("-keep class com.foo.Bar { <init>(...); }")); + actual = GetKeepSetString(set, /** minimal_rules */ true); EXPECT_THAT(actual, HasSubstr("-keep class com.foo.Bar { <init>(); }")); } @@ -89,8 +100,10 @@ TEST(ProguardRulesTest, FragmentClassRuleIsEmitted) { proguard::KeepSet set; ASSERT_TRUE(proguard::CollectProguardRules(context.get(), layout.get(), &set)); - std::string actual = GetKeepSetString(set); + std::string actual = GetKeepSetString(set, /** minimal_rules */ false); + EXPECT_THAT(actual, HasSubstr("-keep class com.foo.Bar { <init>(...); }")); + actual = GetKeepSetString(set, /** minimal_rules */ true); EXPECT_THAT(actual, HasSubstr("-keep class com.foo.Bar { <init>(); }")); } @@ -105,8 +118,11 @@ TEST(ProguardRulesTest, FragmentNameAndClassRulesAreEmitted) { proguard::KeepSet set; ASSERT_TRUE(proguard::CollectProguardRules(context.get(), layout.get(), &set)); - std::string actual = GetKeepSetString(set); + std::string actual = GetKeepSetString(set, /** minimal_rules */ false); + EXPECT_THAT(actual, HasSubstr("-keep class com.foo.Bar { <init>(...); }")); + EXPECT_THAT(actual, HasSubstr("-keep class com.foo.Baz { <init>(...); }")); + actual = GetKeepSetString(set, /** minimal_rules */ true); EXPECT_THAT(actual, HasSubstr("-keep class com.foo.Bar { <init>(); }")); EXPECT_THAT(actual, HasSubstr("-keep class com.foo.Baz { <init>(); }")); } @@ -133,7 +149,12 @@ TEST(ProguardRulesTest, NavigationFragmentNameAndClassRulesAreEmitted) { proguard::KeepSet set; ASSERT_TRUE(proguard::CollectProguardRules(context.get(), navigation.get(), &set)); - std::string actual = GetKeepSetString(set); + std::string actual = GetKeepSetString(set, /** minimal_rules */ false); + EXPECT_THAT(actual, HasSubstr("-keep class com.package.Foo { <init>(...); }")); + EXPECT_THAT(actual, HasSubstr("-keep class com.package.Bar { <init>(...); }")); + EXPECT_THAT(actual, HasSubstr("-keep class com.base.Nested { <init>(...); }")); + + actual = GetKeepSetString(set, /** minimal_rules */ true); EXPECT_THAT(actual, HasSubstr("-keep class com.package.Foo { <init>(...); }")); EXPECT_THAT(actual, HasSubstr("-keep class com.package.Bar { <init>(...); }")); EXPECT_THAT(actual, HasSubstr("-keep class com.base.Nested { <init>(...); }")); @@ -150,8 +171,10 @@ TEST(ProguardRulesTest, CustomViewRulesAreEmitted) { proguard::KeepSet set; ASSERT_TRUE(proguard::CollectProguardRules(context.get(), layout.get(), &set)); - std::string actual = GetKeepSetString(set); + std::string actual = GetKeepSetString(set, /** minimal_rules */ false); + EXPECT_THAT(actual, HasSubstr("-keep class com.foo.Bar { <init>(...); }")); + actual = GetKeepSetString(set, /** minimal_rules */ true); EXPECT_THAT(actual, HasSubstr( "-keep class com.foo.Bar { <init>(android.content.Context, android.util.AttributeSet); }")); } @@ -188,11 +211,16 @@ TEST(ProguardRulesTest, IncludedLayoutRulesAreConditional) { ASSERT_TRUE(proguard::CollectProguardRules(context.get(), bar_layout.get(), &set)); ASSERT_TRUE(proguard::CollectProguardRules(context.get(), foo_layout.get(), &set)); - std::string actual = GetKeepSetString(set); + std::string actual = GetKeepSetString(set, /** minimal_rules */ false); + EXPECT_THAT(actual, HasSubstr("-if class **.R$layout")); + EXPECT_THAT(actual, HasSubstr("-keep class com.foo.Bar { <init>(...); }")); + EXPECT_THAT(actual, HasSubstr("int foo")); + EXPECT_THAT(actual, HasSubstr("int bar")); + actual = GetKeepSetString(set, /** minimal_rules */ true); EXPECT_THAT(actual, HasSubstr("-if class **.R$layout")); EXPECT_THAT(actual, HasSubstr( - "-keep class com.foo.Bar { <init>(android.content.Context, android.util.AttributeSet); }")); + "-keep class com.foo.Bar { <init>(android.content.Context, android.util.AttributeSet); }")); EXPECT_THAT(actual, HasSubstr("int foo")); EXPECT_THAT(actual, HasSubstr("int bar")); } @@ -209,10 +237,16 @@ TEST(ProguardRulesTest, AliasedLayoutRulesAreConditional) { set.AddReference({test::ParseNameOrDie("layout/bar"), {}}, layout->file.name); ASSERT_TRUE(proguard::CollectProguardRules(context.get(), layout.get(), &set)); - std::string actual = GetKeepSetString(set); + std::string actual = GetKeepSetString(set, /** minimal_rules */ false); + EXPECT_THAT(actual, HasSubstr( + "-keep class com.foo.Bar { <init>(...); }")); + EXPECT_THAT(actual, HasSubstr("-if class **.R$layout")); + EXPECT_THAT(actual, HasSubstr("int foo")); + EXPECT_THAT(actual, HasSubstr("int bar")); + actual = GetKeepSetString(set, /** minimal_rules */ true); EXPECT_THAT(actual, HasSubstr( - "-keep class com.foo.Bar { <init>(android.content.Context, android.util.AttributeSet); }")); + "-keep class com.foo.Bar { <init>(android.content.Context, android.util.AttributeSet); }")); EXPECT_THAT(actual, HasSubstr("-if class **.R$layout")); EXPECT_THAT(actual, HasSubstr("int foo")); EXPECT_THAT(actual, HasSubstr("int bar")); @@ -230,11 +264,14 @@ TEST(ProguardRulesTest, NonLayoutReferencesAreUnconditional) { set.AddReference({test::ParseNameOrDie("style/MyStyle"), {}}, layout->file.name); ASSERT_TRUE(proguard::CollectProguardRules(context.get(), layout.get(), &set)); - std::string actual = GetKeepSetString(set); + std::string actual = GetKeepSetString(set, /** minimal_rules */ false); + EXPECT_THAT(actual, Not(HasSubstr("-if"))); + EXPECT_THAT(actual, HasSubstr("-keep class com.foo.Bar { <init>(...); }")); + actual = GetKeepSetString(set, /** minimal_rules */ true); EXPECT_THAT(actual, Not(HasSubstr("-if"))); EXPECT_THAT(actual, HasSubstr( - "-keep class com.foo.Bar { <init>(android.content.Context, android.util.AttributeSet); }")); + "-keep class com.foo.Bar { <init>(android.content.Context, android.util.AttributeSet); }")); } TEST(ProguardRulesTest, ViewOnClickRuleIsEmitted) { @@ -247,10 +284,13 @@ TEST(ProguardRulesTest, ViewOnClickRuleIsEmitted) { proguard::KeepSet set; ASSERT_TRUE(proguard::CollectProguardRules(context.get(), layout.get(), &set)); - std::string actual = GetKeepSetString(set); - + std::string actual = GetKeepSetString(set, /** minimal_rules */ false); EXPECT_THAT(actual, HasSubstr( "-keepclassmembers class * { *** bar_method(android.view.View); }")); + + actual = GetKeepSetString(set, /** minimal_rules */ true); + EXPECT_THAT(actual, HasSubstr( + "-keepclassmembers class * { *** bar_method(android.view.View); }")); } TEST(ProguardRulesTest, MenuRulesAreEmitted) { @@ -267,10 +307,16 @@ TEST(ProguardRulesTest, MenuRulesAreEmitted) { proguard::KeepSet set; ASSERT_TRUE(proguard::CollectProguardRules(context.get(), menu.get(), &set)); - std::string actual = GetKeepSetString(set); + std::string actual = GetKeepSetString(set, /** minimal_rules */ false); + EXPECT_THAT(actual, HasSubstr( + "-keepclassmembers class * { *** on_click(android.view.MenuItem); }")); + EXPECT_THAT(actual, HasSubstr("-keep class com.foo.Bar { <init>(...); }")); + EXPECT_THAT(actual, HasSubstr("-keep class com.foo.Baz { <init>(...); }")); + EXPECT_THAT(actual, Not(HasSubstr("com.foo.Bat"))); + actual = GetKeepSetString(set, /** minimal_rules */ true); EXPECT_THAT(actual, HasSubstr( - "-keepclassmembers class * { *** on_click(android.view.MenuItem); }")); + "-keepclassmembers class * { *** on_click(android.view.MenuItem); }")); EXPECT_THAT(actual, HasSubstr("-keep class com.foo.Bar { <init>(android.content.Context); }")); EXPECT_THAT(actual, HasSubstr("-keep class com.foo.Baz { <init>(android.content.Context); }")); EXPECT_THAT(actual, Not(HasSubstr("com.foo.Bat"))); @@ -287,10 +333,12 @@ TEST(ProguardRulesTest, TransitionPathMotionRulesAreEmitted) { proguard::KeepSet set; ASSERT_TRUE(proguard::CollectProguardRules(context.get(), transition.get(), &set)); - std::string actual = GetKeepSetString(set); + std::string actual = GetKeepSetString(set, /** minimal_rules */ false); + EXPECT_THAT(actual, HasSubstr("-keep class com.foo.Bar { <init>(...); }")); + actual = GetKeepSetString(set, /** minimal_rules */ true); EXPECT_THAT(actual, HasSubstr( - "-keep class com.foo.Bar { <init>(android.content.Context, android.util.AttributeSet); }")); + "-keep class com.foo.Bar { <init>(android.content.Context, android.util.AttributeSet); }")); } TEST(ProguardRulesTest, TransitionRulesAreEmitted) { @@ -304,10 +352,12 @@ TEST(ProguardRulesTest, TransitionRulesAreEmitted) { proguard::KeepSet set; ASSERT_TRUE(proguard::CollectProguardRules(context.get(), transitionSet.get(), &set)); - std::string actual = GetKeepSetString(set); + std::string actual = GetKeepSetString(set, /** minimal_rules */ false); + EXPECT_THAT(actual, HasSubstr("-keep class com.foo.Bar { <init>(...); }")); + actual = GetKeepSetString(set, /** minimal_rules */ true); EXPECT_THAT(actual, HasSubstr( - "-keep class com.foo.Bar { <init>(android.content.Context, android.util.AttributeSet); }")); + "-keep class com.foo.Bar { <init>(android.content.Context, android.util.AttributeSet); }")); } } // namespace aapt diff --git a/tools/aapt2/util/BigBuffer.h b/tools/aapt2/util/BigBuffer.h index 30452552888e..d4b3abce68a7 100644 --- a/tools/aapt2/util/BigBuffer.h +++ b/tools/aapt2/util/BigBuffer.h @@ -68,7 +68,7 @@ class BigBuffer { */ explicit BigBuffer(size_t block_size); - BigBuffer(BigBuffer&& rhs); + BigBuffer(BigBuffer&& rhs) noexcept; /** * Number of occupied bytes in all the allocated blocks. @@ -136,7 +136,7 @@ class BigBuffer { inline BigBuffer::BigBuffer(size_t block_size) : block_size_(block_size), size_(0) {} -inline BigBuffer::BigBuffer(BigBuffer&& rhs) +inline BigBuffer::BigBuffer(BigBuffer&& rhs) noexcept : block_size_(rhs.block_size_), size_(rhs.size_), blocks_(std::move(rhs.blocks_)) {} diff --git a/tools/aapt2/util/Files_test.cpp b/tools/aapt2/util/Files_test.cpp index 219c18397358..202cc261ad89 100644 --- a/tools/aapt2/util/Files_test.cpp +++ b/tools/aapt2/util/Files_test.cpp @@ -18,11 +18,21 @@ #include <sstream> +#include "android-base/stringprintf.h" + #include "test/Test.h" +using ::android::base::StringPrintf; + namespace aapt { namespace file { +#ifdef _WIN32 +constexpr const char sTestDirSep = '\\'; +#else +constexpr const char sTestDirSep = '/'; +#endif + class FilesTest : public ::testing::Test { public: void SetUp() override { @@ -42,16 +52,16 @@ TEST_F(FilesTest, AppendPath) { } TEST_F(FilesTest, AppendPathWithLeadingOrTrailingSeparators) { - std::string base = "hello/"; + std::string base = StringPrintf("hello%c", sTestDirSep); AppendPath(&base, "there"); EXPECT_EQ(expected_path_, base); base = "hello"; - AppendPath(&base, "/there"); + AppendPath(&base, StringPrintf("%cthere", sTestDirSep)); EXPECT_EQ(expected_path_, base); - base = "hello/"; - AppendPath(&base, "/there"); + base = StringPrintf("hello%c", sTestDirSep); + AppendPath(&base, StringPrintf("%cthere", sTestDirSep)); EXPECT_EQ(expected_path_, base); } diff --git a/tools/aapt2/util/ImmutableMap.h b/tools/aapt2/util/ImmutableMap.h index 59858e492c4c..1727b18e4106 100644 --- a/tools/aapt2/util/ImmutableMap.h +++ b/tools/aapt2/util/ImmutableMap.h @@ -32,8 +32,8 @@ class ImmutableMap { using const_iterator = typename std::vector<std::pair<TKey, TValue>>::const_iterator; - ImmutableMap(ImmutableMap&&) = default; - ImmutableMap& operator=(ImmutableMap&&) = default; + ImmutableMap(ImmutableMap&&) noexcept = default; + ImmutableMap& operator=(ImmutableMap&&) noexcept = default; static ImmutableMap<TKey, TValue> CreatePreSorted( std::initializer_list<std::pair<TKey, TValue>> list) { diff --git a/tools/aapt2/util/Maybe.h b/tools/aapt2/util/Maybe.h index 9a82418e0a5a..031276c8b885 100644 --- a/tools/aapt2/util/Maybe.h +++ b/tools/aapt2/util/Maybe.h @@ -46,7 +46,7 @@ class Maybe { template <typename U> Maybe(const Maybe<U>& rhs); // NOLINT(implicit) - Maybe(Maybe&& rhs); + Maybe(Maybe&& rhs) noexcept; template <typename U> Maybe(Maybe<U>&& rhs); // NOLINT(implicit) @@ -56,7 +56,7 @@ class Maybe { template <typename U> Maybe& operator=(const Maybe<U>& rhs); - Maybe& operator=(Maybe&& rhs); + Maybe& operator=(Maybe&& rhs) noexcept; template <typename U> Maybe& operator=(Maybe<U>&& rhs); @@ -134,7 +134,7 @@ Maybe<T>::Maybe(const Maybe<U>& rhs) : nothing_(rhs.nothing_) { } template <typename T> -Maybe<T>::Maybe(Maybe&& rhs) : nothing_(rhs.nothing_) { +Maybe<T>::Maybe(Maybe&& rhs) noexcept : nothing_(rhs.nothing_) { if (!rhs.nothing_) { rhs.nothing_ = true; @@ -192,7 +192,7 @@ Maybe<T>& Maybe<T>::copy(const Maybe<U>& rhs) { } template <typename T> -inline Maybe<T>& Maybe<T>::operator=(Maybe&& rhs) { +inline Maybe<T>& Maybe<T>::operator=(Maybe&& rhs) noexcept { // Delegate to the actual assignment. return move(std::forward<Maybe<T>>(rhs)); } diff --git a/tools/fonts/add_additional_fonts.py b/tools/fonts/add_additional_fonts.py deleted file mode 100644 index bf4af2b1c56e..000000000000 --- a/tools/fonts/add_additional_fonts.py +++ /dev/null @@ -1,44 +0,0 @@ -#!/usr/bin/env python -# -# 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. -# - -import sys - -def main(argv): - original_file = 'frameworks/base/data/fonts/fonts.xml' - - if len(argv) == 3: - output_file_path = argv[1] - override_file_path = argv[2] - else: - raise ValueError("Wrong number of arguments %s" % len(argv)) - - fallbackPlaceholderFound = False - with open(original_file, 'r') as input_file: - with open(output_file_path, 'w') as output_file: - for line in input_file: - # If we've found the spot to add additional fonts, add them. - if line.strip() == '<!-- fallback fonts -->': - fallbackPlaceholderFound = True - with open(override_file_path) as override_file: - for override_line in override_file: - output_file.write(override_line) - output_file.write(line) - if not fallbackPlaceholderFound: - raise ValueError('<!-- fallback fonts --> not found in source file: %s' % original_file) - -if __name__ == '__main__': - main(sys.argv) diff --git a/wifi/java/android/net/wifi/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java index 58c130017024..74722787f441 100644 --- a/wifi/java/android/net/wifi/WifiConfiguration.java +++ b/wifi/java/android/net/wifi/WifiConfiguration.java @@ -513,7 +513,7 @@ public class WifiConfiguration implements Parcelable { /** * @hide * Universal name for app creating the configuration - * see {#link {@link PackageManager#getNameForUid(int)} + * see {@link PackageManager#getNameForUid(int)} */ @SystemApi public String creatorName; @@ -521,7 +521,7 @@ public class WifiConfiguration implements Parcelable { /** * @hide * Universal name for app updating the configuration - * see {#link {@link PackageManager#getNameForUid(int)} + * see {@link PackageManager#getNameForUid(int)} */ @SystemApi public String lastUpdateName; diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java index 7a91347102fe..59ba8e7a6177 100644 --- a/wifi/java/android/net/wifi/WifiManager.java +++ b/wifi/java/android/net/wifi/WifiManager.java @@ -1698,9 +1698,7 @@ public class WifiManager { * @return the list of access points found in the most recent scan. An app must hold * {@link android.Manifest.permission#ACCESS_COARSE_LOCATION ACCESS_COARSE_LOCATION} or * {@link android.Manifest.permission#ACCESS_FINE_LOCATION ACCESS_FINE_LOCATION} permission - * in order to get valid results. If there is a remote exception (e.g., either a communication - * problem with the system service or an exception within the framework) an empty list will be - * returned. + * in order to get valid results. */ public List<ScanResult> getScanResults() { try { |