diff options
475 files changed, 13027 insertions, 5346 deletions
diff --git a/Android.bp b/Android.bp index 1b81306b3699..dc9c4d4b5a85 100644 --- a/Android.bp +++ b/Android.bp @@ -357,6 +357,7 @@ java_defaults { "core/java/android/view/autofill/IAutoFillManagerClient.aidl", "core/java/android/view/autofill/IAugmentedAutofillManagerClient.aidl", "core/java/android/view/autofill/IAutofillWindowPresenter.aidl", + "core/java/android/view/contentcapture/IContentCaptureDirectManager.aidl", "core/java/android/view/contentcapture/IContentCaptureManager.aidl", "core/java/android/view/IApplicationToken.aidl", "core/java/android/view/IAppTransitionAnimationSpecsFuture.aidl", @@ -387,6 +388,7 @@ java_defaults { "core/java/android/speech/tts/ITextToSpeechService.aidl", "core/java/com/android/internal/app/IAppOpsActiveCallback.aidl", "core/java/com/android/internal/app/IAppOpsCallback.aidl", + "core/java/com/android/internal/app/IAppOpsNotedCallback.aidl", "core/java/com/android/internal/app/IAppOpsService.aidl", "core/java/com/android/internal/app/IBatteryStats.aidl", "core/java/com/android/internal/app/ISoundTriggerService.aidl", diff --git a/CleanSpec.mk b/CleanSpec.mk index 478e4fe86d3b..d01e183df84d 100644 --- a/CleanSpec.mk +++ b/CleanSpec.mk @@ -251,6 +251,7 @@ $(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/framework/com.android.media. $(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/overlay/ExperimentNavigationBarSlim) $(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/vendor/overlay/ExperimentNavigationBarSlim) $(call add-clean-step, rm -rf $(PRODUCT_OUT)/vendor/overlay/ExperimentNavigationBarSlim) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/priv-app/SystemUI) # ****************************************************************** # NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST ABOVE THIS BANNER # ****************************************************************** diff --git a/apct-tests/perftests/multiuser/Android.mk b/apct-tests/perftests/multiuser/Android.mk index 5ff4ebc0eb80..5852044effa8 100644 --- a/apct-tests/perftests/multiuser/Android.mk +++ b/apct-tests/perftests/multiuser/Android.mk @@ -20,7 +20,7 @@ LOCAL_MODULE_TAGS := tests LOCAL_SRC_FILES := $(call all-java-files-under, src) LOCAL_STATIC_JAVA_LIBRARIES := \ - android-support-test \ + androidx.test.rules \ apct-perftests-utils LOCAL_PACKAGE_NAME := MultiUserPerfTests diff --git a/apct-tests/perftests/multiuser/AndroidManifest.xml b/apct-tests/perftests/multiuser/AndroidManifest.xml index adb316fe6c43..e96771cf1c17 100644 --- a/apct-tests/perftests/multiuser/AndroidManifest.xml +++ b/apct-tests/perftests/multiuser/AndroidManifest.xml @@ -25,7 +25,7 @@ <uses-library android:name="android.test.runner" /> </application> - <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner" + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" android:targetPackage="com.android.perftests.multiuser"/> </manifest> diff --git a/apct-tests/perftests/multiuser/src/android/multiuser/BenchmarkResultsReporter.java b/apct-tests/perftests/multiuser/src/android/multiuser/BenchmarkResultsReporter.java index d3a3ce54e378..ba33e6439fbd 100644 --- a/apct-tests/perftests/multiuser/src/android/multiuser/BenchmarkResultsReporter.java +++ b/apct-tests/perftests/multiuser/src/android/multiuser/BenchmarkResultsReporter.java @@ -18,9 +18,10 @@ package android.multiuser; import android.app.Activity; import android.app.Instrumentation; import android.os.Bundle; -import android.support.test.InstrumentationRegistry; import android.util.Log; +import androidx.test.InstrumentationRegistry; + import org.junit.rules.TestRule; import org.junit.runner.Description; import org.junit.runners.model.Statement; diff --git a/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java b/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java index 855be0859520..2fdba0af2c1b 100644 --- a/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java +++ b/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java @@ -27,9 +27,10 @@ import android.content.pm.UserInfo; import android.os.RemoteException; import android.os.UserHandle; import android.os.UserManager; -import android.support.test.InstrumentationRegistry; -import android.support.test.filters.LargeTest; -import android.support.test.runner.AndroidJUnit4; + +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.LargeTest; +import androidx.test.runner.AndroidJUnit4; import org.junit.After; import org.junit.Before; @@ -50,7 +51,7 @@ import java.util.concurrent.TimeUnit; * adb install -r \ * ${ANDROID_PRODUCT_OUT}/data/app/MultiUserPerfTests/MultiUserPerfTests.apk && * adb shell am instrument -e class android.multiuser.UserLifecycleTests \ - * -w com.android.perftests.multiuser/android.support.test.runner.AndroidJUnitRunner + * -w com.android.perftests.multiuser/androidx.test.runner.AndroidJUnitRunner * * or * diff --git a/api/current.txt b/api/current.txt index 4f39e25b50f5..2b34f4f0dab7 100644 --- a/api/current.txt +++ b/api/current.txt @@ -11565,18 +11565,19 @@ package android.content.pm { field public static final java.lang.String FEATURE_DEVICE_ADMIN = "android.software.device_admin"; field public static final java.lang.String FEATURE_EMBEDDED = "android.hardware.type.embedded"; field public static final java.lang.String FEATURE_ETHERNET = "android.hardware.ethernet"; - field public static final java.lang.String FEATURE_FACE = "android.hardware.face"; + field public static final java.lang.String FEATURE_FACE = "android.hardware.biometrics.face"; field public static final java.lang.String FEATURE_FAKETOUCH = "android.hardware.faketouch"; field public static final java.lang.String FEATURE_FAKETOUCH_MULTITOUCH_DISTINCT = "android.hardware.faketouch.multitouch.distinct"; field public static final java.lang.String FEATURE_FAKETOUCH_MULTITOUCH_JAZZHAND = "android.hardware.faketouch.multitouch.jazzhand"; - field public static final java.lang.String FEATURE_FINGERPRINT = "android.hardware.fingerprint"; + field public static final java.lang.String FEATURE_FINGERPRINT = "android.hardware.biometrics.fingerprint"; + field public static final java.lang.String FEATURE_FINGERPRINT_PRE_29 = "android.hardware.fingerprint"; field public static final java.lang.String FEATURE_FREEFORM_WINDOW_MANAGEMENT = "android.software.freeform_window_management"; field public static final java.lang.String FEATURE_GAMEPAD = "android.hardware.gamepad"; field public static final java.lang.String FEATURE_HIFI_SENSORS = "android.hardware.sensor.hifi_sensors"; field public static final java.lang.String FEATURE_HOME_SCREEN = "android.software.home_screen"; field public static final java.lang.String FEATURE_INPUT_METHODS = "android.software.input_methods"; field public static final java.lang.String FEATURE_IPSEC_TUNNELS = "android.software.ipsec_tunnels"; - field public static final java.lang.String FEATURE_IRIS = "android.hardware.iris"; + field public static final java.lang.String FEATURE_IRIS = "android.hardware.biometrics.iris"; field public static final java.lang.String FEATURE_LEANBACK = "android.software.leanback"; field public static final java.lang.String FEATURE_LEANBACK_ONLY = "android.software.leanback_only"; field public static final java.lang.String FEATURE_LIVE_TV = "android.software.live_tv"; @@ -14861,6 +14862,7 @@ package android.graphics { method public int getAmbientShadowColor(); method public int getBottom(); method public float getCameraDistance(); + method public boolean getClipToBounds(); method public boolean getClipToOutline(); method public float getElevation(); method public int getHeight(); @@ -14881,6 +14883,7 @@ package android.graphics { method public float getTranslationY(); method public float getTranslationZ(); method public long getUniqueId(); + method public boolean getUseCompositingLayer(); method public int getWidth(); method public boolean hasDisplayList(); method public boolean hasIdentityMatrix(); @@ -24985,8 +24988,9 @@ package android.media { field public static final int RATING_KEY_BY_USER = 268435457; // 0x10000001 } - public class MediaMetadataRetriever { + public class MediaMetadataRetriever implements java.lang.AutoCloseable { ctor public MediaMetadataRetriever(); + method public void close(); method public java.lang.String extractMetadata(int); method public byte[] getEmbeddedPicture(); method public android.graphics.Bitmap getFrameAtIndex(int, android.media.MediaMetadataRetriever.BitmapParams); @@ -25308,24 +25312,24 @@ package android.media { method public java.lang.Object clearNextDataSources(); method public void clearPendingCommands(); method public void close(); - method public java.lang.Object deselectTrack(int); + method public java.lang.Object deselectTrack(android.media.DataSourceDesc, int); method public android.media.AudioAttributes getAudioAttributes(); method public int getAudioSessionId(); - method public long getBufferedPosition(); + method public long getBufferedPosition(android.media.DataSourceDesc); method public android.media.DataSourceDesc getCurrentDataSource(); method public long getCurrentPosition(); - method public long getDuration(); + method public long getDuration(android.media.DataSourceDesc); method public float getMaxPlayerVolume(); method public android.os.PersistableBundle getMetrics(); method public android.media.PlaybackParams getPlaybackParams(); method public float getPlayerVolume(); method public android.media.AudioDeviceInfo getPreferredDevice(); method public android.media.AudioDeviceInfo getRoutedDevice(); - method public int getSelectedTrack(int); + method public int getSelectedTrack(android.media.DataSourceDesc, int); method public int getState(); method public android.media.SyncParams getSyncParams(); method public android.media.MediaTimestamp getTimestamp(); - method public java.util.List<android.media.MediaPlayer2.TrackInfo> getTrackInfo(); + method public java.util.List<android.media.MediaPlayer2.TrackInfo> getTrackInfo(android.media.DataSourceDesc); method public android.media.VideoSize getVideoSize(); method public boolean isLooping(); method public java.lang.Object loopCurrent(boolean); @@ -25338,7 +25342,7 @@ package android.media { method public void reset(); method public java.lang.Object seekTo(long); method public java.lang.Object seekTo(long, int); - method public java.lang.Object selectTrack(int); + method public java.lang.Object selectTrack(android.media.DataSourceDesc, int); method public java.lang.Object setAudioAttributes(android.media.AudioAttributes); method public java.lang.Object setAudioSessionId(int); method public java.lang.Object setAuxEffectSendLevel(float); @@ -26073,7 +26077,12 @@ package android.media { public class ThumbnailUtils { ctor public ThumbnailUtils(); - method public static android.graphics.Bitmap createVideoThumbnail(java.lang.String, int); + method public static deprecated android.graphics.Bitmap createAudioThumbnail(java.lang.String, int); + method public static android.graphics.Bitmap createAudioThumbnail(java.io.File, android.util.Size, android.os.CancellationSignal) throws java.io.IOException; + method public static deprecated android.graphics.Bitmap createImageThumbnail(java.lang.String, int); + method public static android.graphics.Bitmap createImageThumbnail(java.io.File, android.util.Size, android.os.CancellationSignal) throws java.io.IOException; + method public static deprecated android.graphics.Bitmap createVideoThumbnail(java.lang.String, int); + method public static android.graphics.Bitmap createVideoThumbnail(java.io.File, android.util.Size, android.os.CancellationSignal) throws java.io.IOException; method public static android.graphics.Bitmap extractThumbnail(android.graphics.Bitmap, int, int); method public static android.graphics.Bitmap extractThumbnail(android.graphics.Bitmap, int, int, int); field public static final int OPTIONS_RECYCLE_INPUT = 2; // 0x2 @@ -28316,6 +28325,11 @@ package android.net { field public int serverAddress; } + public class InetAddresses { + method public static boolean isNumericAddress(java.lang.String); + method public static java.net.InetAddress parseNumericAddress(java.lang.String); + } + public final class IpPrefix implements android.os.Parcelable { method public boolean contains(java.net.InetAddress); method public int describeContents(); @@ -38334,6 +38348,11 @@ package android.provider { field public static final java.lang.String VALUE = "value"; } + public static final class Settings.Panel { + field public static final java.lang.String ACTION_INTERNET_CONNECTIVITY = "android.settings.panel.action.INTERNET_CONNECTIVITY"; + field public static final java.lang.String ACTION_VOLUME = "android.settings.panel.action.VOLUME"; + } + public static final class Settings.Secure extends android.provider.Settings.NameValueTable { ctor public Settings.Secure(); method public static float getFloat(android.content.ContentResolver, java.lang.String, float); @@ -41222,10 +41241,12 @@ package android.service.quicksettings { method public android.graphics.drawable.Icon getIcon(); method public java.lang.CharSequence getLabel(); method public int getState(); + method public java.lang.CharSequence getSubtitle(); method public void setContentDescription(java.lang.CharSequence); method public void setIcon(android.graphics.drawable.Icon); method public void setLabel(java.lang.CharSequence); method public void setState(int); + method public void setSubtitle(java.lang.CharSequence); method public void updateTile(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.service.quicksettings.Tile> CREATOR; @@ -41482,6 +41503,7 @@ package android.service.wallpaper { method protected void dump(java.lang.String, java.io.FileDescriptor, java.io.PrintWriter, java.lang.String[]); method public int getDesiredMinimumHeight(); method public int getDesiredMinimumWidth(); + method public android.content.Context getDisplayContext(); method public android.view.SurfaceHolder getSurfaceHolder(); method public boolean isPreview(); method public boolean isVisible(); diff --git a/api/system-current.txt b/api/system-current.txt index a83c32def7be..4278003f4601 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -205,6 +205,10 @@ package android { field public static final int config_sendPackageName = 17891328; // 0x1110000 } + public static final class R.color { + field public static final int system_notification_accent_color = 17170460; // 0x106001c + } + public static final class R.dimen { field public static final int config_mediaMetadataBitmapMaxSize = 17104904; // 0x1050008 field public static final int config_restrictedIconSize = 17104903; // 0x1050007 @@ -1467,6 +1471,8 @@ package android.hardware.display { public final class BrightnessConfiguration implements android.os.Parcelable { method public int describeContents(); + method public android.hardware.display.BrightnessCorrection getCorrectionByCategory(int); + method public android.hardware.display.BrightnessCorrection getCorrectionByPackageName(java.lang.String); method public android.util.Pair<float[], float[]> getCurve(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.hardware.display.BrightnessConfiguration> CREATOR; @@ -1474,10 +1480,22 @@ package android.hardware.display { public static class BrightnessConfiguration.Builder { ctor public BrightnessConfiguration.Builder(float[], float[]); + method public android.hardware.display.BrightnessConfiguration.Builder addCorrectionByCategory(int, android.hardware.display.BrightnessCorrection); + method public android.hardware.display.BrightnessConfiguration.Builder addCorrectionByPackageName(java.lang.String, android.hardware.display.BrightnessCorrection); method public android.hardware.display.BrightnessConfiguration build(); + method public int getMaxCorrectionsByCategory(); + method public int getMaxCorrectionsByPackageName(); method public android.hardware.display.BrightnessConfiguration.Builder setDescription(java.lang.String); } + public final class BrightnessCorrection implements android.os.Parcelable { + method public float apply(float); + method public static android.hardware.display.BrightnessCorrection createScaleAndTranslateLog(float, float); + method public int describeContents(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.hardware.display.BrightnessCorrection> CREATOR; + } + public final class DisplayManager { method public java.util.List<android.hardware.display.AmbientBrightnessDayStats> getAmbientBrightnessStats(); method public android.hardware.display.BrightnessConfiguration getBrightnessConfiguration(); @@ -2516,7 +2534,37 @@ package android.hardware.usb { } public class UsbManager { + method public java.util.List<android.hardware.usb.UsbPort> getPorts(); method public void grantPermission(android.hardware.usb.UsbDevice, java.lang.String); + field public static final java.lang.String ACTION_USB_PORT_CHANGED = "android.hardware.usb.action.USB_PORT_CHANGED"; + } + + public final class UsbPort { + method public android.hardware.usb.UsbPortStatus getStatus(); + method public void setRoles(int, int); + } + + public final class UsbPortStatus implements android.os.Parcelable { + method public int describeContents(); + method public int getCurrentDataRole(); + method public int getCurrentMode(); + method public int getCurrentPowerRole(); + method public int getSupportedRoleCombinations(); + method public boolean isConnected(); + method public boolean isRoleCombinationSupported(int, int); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.hardware.usb.UsbPortStatus> CREATOR; + field public static final int DATA_ROLE_DEVICE = 2; // 0x2 + field public static final int DATA_ROLE_HOST = 1; // 0x1 + field public static final int DATA_ROLE_NONE = 0; // 0x0 + field public static final int MODE_AUDIO_ACCESSORY = 4; // 0x4 + field public static final int MODE_DEBUG_ACCESSORY = 8; // 0x8 + field public static final int MODE_DFP = 2; // 0x2 + field public static final int MODE_NONE = 0; // 0x0 + field public static final int MODE_UFP = 1; // 0x1 + field public static final int POWER_ROLE_NONE = 0; // 0x0 + field public static final int POWER_ROLE_SINK = 2; // 0x2 + field public static final int POWER_ROLE_SOURCE = 1; // 0x1 } } @@ -3713,6 +3761,7 @@ package android.net.wifi { method public boolean isWifiScannerSupported(); method public void registerNetworkRequestMatchCallback(android.net.wifi.WifiManager.NetworkRequestMatchCallback, android.os.Handler); method public void save(android.net.wifi.WifiConfiguration, android.net.wifi.WifiManager.ActionListener); + method public void setDeviceMobilityState(int); method public boolean setWifiApConfiguration(android.net.wifi.WifiConfiguration); method public boolean startScan(android.os.WorkSource); method public void unregisterNetworkRequestMatchCallback(android.net.wifi.WifiManager.NetworkRequestMatchCallback); @@ -3720,6 +3769,10 @@ package android.net.wifi { field public static final int CHANGE_REASON_CONFIG_CHANGE = 2; // 0x2 field public static final int CHANGE_REASON_REMOVED = 1; // 0x1 field public static final java.lang.String CONFIGURED_NETWORKS_CHANGED_ACTION = "android.net.wifi.CONFIGURED_NETWORKS_CHANGE"; + field public static final int DEVICE_MOBILITY_STATE_HIGH_MVMT = 1; // 0x1 + field public static final int DEVICE_MOBILITY_STATE_LOW_MVMT = 2; // 0x2 + field public static final int DEVICE_MOBILITY_STATE_STATIONARY = 3; // 0x3 + field public static final int DEVICE_MOBILITY_STATE_UNKNOWN = 0; // 0x0 field public static final java.lang.String EXTRA_CHANGE_REASON = "changeReason"; field public static final java.lang.String EXTRA_MULTIPLE_NETWORKS_CHANGED = "multipleChanges"; field public static final java.lang.String EXTRA_PREVIOUS_WIFI_AP_STATE = "previous_wifi_state"; @@ -5008,7 +5061,7 @@ package android.service.contentcapture { method public final java.util.Set<android.content.ComponentName> getContentCaptureDisabledActivities(); method public final java.util.Set<java.lang.String> getContentCaptureDisabledPackages(); method public void onActivitySnapshot(android.view.contentcapture.ContentCaptureSessionId, android.service.contentcapture.SnapshotData); - method public abstract void onContentCaptureEventsRequest(android.view.contentcapture.ContentCaptureSessionId, android.service.contentcapture.ContentCaptureEventsRequest); + method public void onContentCaptureEventsRequest(android.view.contentcapture.ContentCaptureSessionId, android.service.contentcapture.ContentCaptureEventsRequest); method public void onCreateContentCaptureSession(android.view.contentcapture.ContentCaptureContext, android.view.contentcapture.ContentCaptureSessionId); method public void onDestroyContentCaptureSession(android.view.contentcapture.ContentCaptureSessionId); method public final void setActivityContentCaptureEnabled(android.content.ComponentName, boolean); @@ -5689,6 +5742,83 @@ package android.telephony { field public static final java.lang.String KEY_CARRIER_SETUP_APP_STRING = "carrier_setup_app_string"; } + public class DisconnectCause { + field public static final int ALREADY_DIALING = 72; // 0x48 + field public static final int ANSWERED_ELSEWHERE = 52; // 0x34 + field public static final int BUSY = 4; // 0x4 + field public static final int CALLING_DISABLED = 74; // 0x4a + field public static final int CALL_BARRED = 20; // 0x14 + field public static final int CALL_PULLED = 51; // 0x33 + field public static final int CANT_CALL_WHILE_RINGING = 73; // 0x49 + field public static final int CDMA_ACCESS_BLOCKED = 35; // 0x23 + field public static final int CDMA_ACCESS_FAILURE = 32; // 0x20 + field public static final int CDMA_ALREADY_ACTIVATED = 49; // 0x31 + field public static final int CDMA_DROP = 27; // 0x1b + field public static final int CDMA_INTERCEPT = 28; // 0x1c + field public static final int CDMA_LOCKED_UNTIL_POWER_CYCLE = 26; // 0x1a + field public static final int CDMA_NOT_EMERGENCY = 34; // 0x22 + field public static final int CDMA_PREEMPTED = 33; // 0x21 + field public static final int CDMA_REORDER = 29; // 0x1d + field public static final int CDMA_RETRY_ORDER = 31; // 0x1f + field public static final int CDMA_SO_REJECT = 30; // 0x1e + field public static final int CONGESTION = 5; // 0x5 + field public static final int CS_RESTRICTED = 22; // 0x16 + field public static final int CS_RESTRICTED_EMERGENCY = 24; // 0x18 + field public static final int CS_RESTRICTED_NORMAL = 23; // 0x17 + field public static final int DATA_DISABLED = 54; // 0x36 + field public static final int DATA_LIMIT_REACHED = 55; // 0x37 + field public static final int DIALED_CALL_FORWARDING_WHILE_ROAMING = 57; // 0x39 + field public static final int DIALED_MMI = 39; // 0x27 + field public static final int DIAL_LOW_BATTERY = 62; // 0x3e + field public static final int DIAL_MODIFIED_TO_DIAL = 48; // 0x30 + field public static final int DIAL_MODIFIED_TO_DIAL_VIDEO = 66; // 0x42 + field public static final int DIAL_MODIFIED_TO_SS = 47; // 0x2f + field public static final int DIAL_MODIFIED_TO_USSD = 46; // 0x2e + field public static final int DIAL_VIDEO_MODIFIED_TO_DIAL = 69; // 0x45 + field public static final int DIAL_VIDEO_MODIFIED_TO_DIAL_VIDEO = 70; // 0x46 + field public static final int DIAL_VIDEO_MODIFIED_TO_SS = 67; // 0x43 + field public static final int DIAL_VIDEO_MODIFIED_TO_USSD = 68; // 0x44 + field public static final int EMERGENCY_PERM_FAILURE = 64; // 0x40 + field public static final int EMERGENCY_TEMP_FAILURE = 63; // 0x3f + field public static final int ERROR_UNSPECIFIED = 36; // 0x24 + field public static final int FDN_BLOCKED = 21; // 0x15 + field public static final int ICC_ERROR = 19; // 0x13 + field public static final int IMEI_NOT_ACCEPTED = 58; // 0x3a + field public static final int IMS_ACCESS_BLOCKED = 60; // 0x3c + field public static final int IMS_MERGED_SUCCESSFULLY = 45; // 0x2d + field public static final int IMS_SIP_ALTERNATE_EMERGENCY_CALL = 71; // 0x47 + field public static final int INCOMING_MISSED = 1; // 0x1 + field public static final int INCOMING_REJECTED = 16; // 0x10 + field public static final int INVALID_CREDENTIALS = 10; // 0xa + field public static final int INVALID_NUMBER = 7; // 0x7 + field public static final int LIMIT_EXCEEDED = 15; // 0xf + field public static final int LOCAL = 3; // 0x3 + field public static final int LOST_SIGNAL = 14; // 0xe + field public static final int LOW_BATTERY = 61; // 0x3d + field public static final int MAXIMUM_NUMBER_OF_CALLS_REACHED = 53; // 0x35 + field public static final int MMI = 6; // 0x6 + field public static final int NORMAL = 2; // 0x2 + field public static final int NORMAL_UNSPECIFIED = 65; // 0x41 + field public static final int NOT_DISCONNECTED = 0; // 0x0 + field public static final int NOT_VALID = -1; // 0xffffffff + field public static final int NO_PHONE_NUMBER_SUPPLIED = 38; // 0x26 + field public static final int NUMBER_UNREACHABLE = 8; // 0x8 + field public static final int OTASP_PROVISIONING_IN_PROCESS = 76; // 0x4c + field public static final int OUTGOING_CANCELED = 44; // 0x2c + field public static final int OUTGOING_FAILURE = 43; // 0x2b + field public static final int OUT_OF_NETWORK = 11; // 0xb + field public static final int OUT_OF_SERVICE = 18; // 0x12 + field public static final int POWER_OFF = 17; // 0x11 + field public static final int SERVER_ERROR = 12; // 0xc + field public static final int SERVER_UNREACHABLE = 9; // 0x9 + field public static final int TIMED_OUT = 13; // 0xd + field public static final int TOO_MANY_ONGOING_CALLS = 75; // 0x4b + field public static final int UNOBTAINABLE_NUMBER = 25; // 0x19 + field public static final int VIDEO_CALL_NOT_ALLOWED_WHILE_TTY_ENABLED = 50; // 0x32 + field public static final int VOICEMAIL_NUMBER_MISSING = 40; // 0x28 + field public static final int WIFI_LOST = 59; // 0x3b + } + public class MbmsDownloadSession implements java.lang.AutoCloseable { field public static final java.lang.String MBMS_DOWNLOAD_SERVICE_ACTION = "android.telephony.action.EmbmsDownload"; } @@ -5777,14 +5907,134 @@ package android.telephony { } public class PhoneStateListener { + method public void onCallDisconnectCauseChanged(int, int); + method public void onPreciseCallStateChanged(android.telephony.PreciseCallState); method public void onRadioPowerStateChanged(int); method public void onSrvccStateChanged(int); method public void onVoiceActivationStateChanged(int); + field public static final int LISTEN_CALL_DISCONNECT_CAUSES = 33554432; // 0x2000000 + field public static final int LISTEN_PRECISE_CALL_STATE = 2048; // 0x800 field public static final int LISTEN_RADIO_POWER_STATE_CHANGED = 8388608; // 0x800000 field public static final int LISTEN_SRVCC_STATE_CHANGED = 16384; // 0x4000 field public static final int LISTEN_VOICE_ACTIVATION_STATE = 131072; // 0x20000 } + public final class PreciseCallState implements android.os.Parcelable { + method public int describeContents(); + method public int getBackgroundCallState(); + method public int getForegroundCallState(); + method public int getRingingCallState(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.telephony.PreciseCallState> CREATOR; + field public static final int PRECISE_CALL_STATE_ACTIVE = 1; // 0x1 + field public static final int PRECISE_CALL_STATE_ALERTING = 4; // 0x4 + field public static final int PRECISE_CALL_STATE_DIALING = 3; // 0x3 + field public static final int PRECISE_CALL_STATE_DISCONNECTED = 7; // 0x7 + field public static final int PRECISE_CALL_STATE_DISCONNECTING = 8; // 0x8 + field public static final int PRECISE_CALL_STATE_HOLDING = 2; // 0x2 + field public static final int PRECISE_CALL_STATE_IDLE = 0; // 0x0 + field public static final int PRECISE_CALL_STATE_INCOMING = 5; // 0x5 + field public static final int PRECISE_CALL_STATE_NOT_VALID = -1; // 0xffffffff + field public static final int PRECISE_CALL_STATE_WAITING = 6; // 0x6 + } + + public class PreciseDisconnectCause { + field public static final int ACCESS_CLASS_BLOCKED = 260; // 0x104 + field public static final int ACCESS_INFORMATION_DISCARDED = 43; // 0x2b + field public static final int ACM_LIMIT_EXCEEDED = 68; // 0x44 + field public static final int BEARER_CAPABILITY_NOT_AUTHORIZED = 57; // 0x39 + field public static final int BEARER_NOT_AVAIL = 58; // 0x3a + field public static final int BEARER_SERVICE_NOT_IMPLEMENTED = 65; // 0x41 + field public static final int BUSY = 17; // 0x11 + field public static final int CALL_BARRED = 240; // 0xf0 + field public static final int CALL_REJECTED = 21; // 0x15 + field public static final int CDMA_ACCESS_BLOCKED = 1009; // 0x3f1 + field public static final int CDMA_ACCESS_FAILURE = 1006; // 0x3ee + field public static final int CDMA_DROP = 1001; // 0x3e9 + field public static final int CDMA_INTERCEPT = 1002; // 0x3ea + field public static final int CDMA_LOCKED_UNTIL_POWER_CYCLE = 1000; // 0x3e8 + field public static final int CDMA_NOT_EMERGENCY = 1008; // 0x3f0 + field public static final int CDMA_PREEMPTED = 1007; // 0x3ef + field public static final int CDMA_REORDER = 1003; // 0x3eb + field public static final int CDMA_RETRY_ORDER = 1005; // 0x3ed + field public static final int CDMA_SO_REJECT = 1004; // 0x3ec + field public static final int CHANNEL_NOT_AVAIL = 44; // 0x2c + field public static final int CHANNEL_UNACCEPTABLE = 6; // 0x6 + field public static final int CONDITIONAL_IE_ERROR = 100; // 0x64 + field public static final int DESTINATION_OUT_OF_ORDER = 27; // 0x1b + field public static final int ERROR_UNSPECIFIED = 65535; // 0xffff + field public static final int FACILITY_REJECTED = 29; // 0x1d + field public static final int FDN_BLOCKED = 241; // 0xf1 + field public static final int IMEI_NOT_ACCEPTED = 243; // 0xf3 + field public static final int IMSI_UNKNOWN_IN_VLR = 242; // 0xf2 + field public static final int INCOMING_CALLS_BARRED_WITHIN_CUG = 55; // 0x37 + field public static final int INCOMPATIBLE_DESTINATION = 88; // 0x58 + field public static final int INFORMATION_ELEMENT_NON_EXISTENT = 99; // 0x63 + field public static final int INTERWORKING_UNSPECIFIED = 127; // 0x7f + field public static final int INVALID_MANDATORY_INFORMATION = 96; // 0x60 + field public static final int INVALID_NUMBER_FORMAT = 28; // 0x1c + field public static final int INVALID_TRANSACTION_IDENTIFIER = 81; // 0x51 + field public static final int MESSAGE_NOT_COMPATIBLE_WITH_PROTOCOL_STATE = 101; // 0x65 + field public static final int MESSAGE_TYPE_NON_IMPLEMENTED = 97; // 0x61 + field public static final int MESSAGE_TYPE_NOT_COMPATIBLE_WITH_PROTOCOL_STATE = 98; // 0x62 + field public static final int NETWORK_DETACH = 261; // 0x105 + field public static final int NETWORK_OUT_OF_ORDER = 38; // 0x26 + field public static final int NETWORK_REJECT = 252; // 0xfc + field public static final int NETWORK_RESP_TIMEOUT = 251; // 0xfb + field public static final int NORMAL = 16; // 0x10 + field public static final int NORMAL_UNSPECIFIED = 31; // 0x1f + field public static final int NOT_VALID = -1; // 0xffffffff + field public static final int NO_ANSWER_FROM_USER = 19; // 0x13 + field public static final int NO_CIRCUIT_AVAIL = 34; // 0x22 + field public static final int NO_DISCONNECT_CAUSE_AVAILABLE = 0; // 0x0 + field public static final int NO_ROUTE_TO_DESTINATION = 3; // 0x3 + field public static final int NO_USER_RESPONDING = 18; // 0x12 + field public static final int NO_VALID_SIM = 249; // 0xf9 + field public static final int NUMBER_CHANGED = 22; // 0x16 + field public static final int OEM_CAUSE_1 = 61441; // 0xf001 + field public static final int OEM_CAUSE_10 = 61450; // 0xf00a + field public static final int OEM_CAUSE_11 = 61451; // 0xf00b + field public static final int OEM_CAUSE_12 = 61452; // 0xf00c + field public static final int OEM_CAUSE_13 = 61453; // 0xf00d + field public static final int OEM_CAUSE_14 = 61454; // 0xf00e + field public static final int OEM_CAUSE_15 = 61455; // 0xf00f + field public static final int OEM_CAUSE_2 = 61442; // 0xf002 + field public static final int OEM_CAUSE_3 = 61443; // 0xf003 + field public static final int OEM_CAUSE_4 = 61444; // 0xf004 + field public static final int OEM_CAUSE_5 = 61445; // 0xf005 + field public static final int OEM_CAUSE_6 = 61446; // 0xf006 + field public static final int OEM_CAUSE_7 = 61447; // 0xf007 + field public static final int OEM_CAUSE_8 = 61448; // 0xf008 + field public static final int OEM_CAUSE_9 = 61449; // 0xf009 + field public static final int ONLY_DIGITAL_INFORMATION_BEARER_AVAILABLE = 70; // 0x46 + field public static final int OPERATOR_DETERMINED_BARRING = 8; // 0x8 + field public static final int OUT_OF_SRV = 248; // 0xf8 + field public static final int PREEMPTION = 25; // 0x19 + field public static final int PROTOCOL_ERROR_UNSPECIFIED = 111; // 0x6f + field public static final int QOS_NOT_AVAIL = 49; // 0x31 + field public static final int RADIO_ACCESS_FAILURE = 253; // 0xfd + field public static final int RADIO_INTERNAL_ERROR = 250; // 0xfa + field public static final int RADIO_LINK_FAILURE = 254; // 0xfe + field public static final int RADIO_LINK_LOST = 255; // 0xff + field public static final int RADIO_OFF = 247; // 0xf7 + field public static final int RADIO_RELEASE_ABNORMAL = 259; // 0x103 + field public static final int RADIO_RELEASE_NORMAL = 258; // 0x102 + field public static final int RADIO_SETUP_FAILURE = 257; // 0x101 + field public static final int RADIO_UPLINK_FAILURE = 256; // 0x100 + field public static final int RECOVERY_ON_TIMER_EXPIRED = 102; // 0x66 + field public static final int REQUESTED_FACILITY_NOT_IMPLEMENTED = 69; // 0x45 + field public static final int REQUESTED_FACILITY_NOT_SUBSCRIBED = 50; // 0x32 + field public static final int RESOURCES_UNAVAILABLE_OR_UNSPECIFIED = 47; // 0x2f + field public static final int SEMANTICALLY_INCORRECT_MESSAGE = 95; // 0x5f + field public static final int SERVICE_OPTION_NOT_AVAILABLE = 63; // 0x3f + field public static final int SERVICE_OR_OPTION_NOT_IMPLEMENTED = 79; // 0x4f + field public static final int STATUS_ENQUIRY = 30; // 0x1e + field public static final int SWITCHING_CONGESTION = 42; // 0x2a + field public static final int TEMPORARY_FAILURE = 41; // 0x29 + field public static final int UNOBTAINABLE_NUMBER = 1; // 0x1 + field public static final int USER_NOT_MEMBER_OF_CUG = 87; // 0x57 + } + public class ServiceState implements android.os.Parcelable { method public android.telephony.NetworkRegistrationState getNetworkRegistrationState(int, int); method public java.util.List<android.telephony.NetworkRegistrationState> getNetworkRegistrationStates(); @@ -5822,11 +6072,13 @@ package android.telephony { public class SubscriptionInfo implements android.os.Parcelable { method public java.util.List<android.telephony.UiccAccessRule> getAccessRules(); + method public int getCardId(); } public class SubscriptionManager { method public java.util.List<android.telephony.SubscriptionInfo> getAvailableSubscriptionInfoList(); method public void requestEmbeddedSubscriptionInfoListRefresh(); + method public void requestEmbeddedSubscriptionInfoListRefresh(int); method public void setDefaultDataSubId(int); method public void setDefaultSmsSubId(int); field public static final android.net.Uri ADVANCED_CALLING_ENABLED_CONTENT_URI; diff --git a/api/test-current.txt b/api/test-current.txt index d534501e1a06..46e7683c3cb7 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -655,11 +655,6 @@ package android.media { method public android.media.BufferingParams.Builder setResumePlaybackMarkMs(int); } - public class MediaPlayer implements android.media.AudioRouting android.media.VolumeAutomation { - method public android.media.BufferingParams getBufferingParams(); - method public void setBufferingParams(android.media.BufferingParams); - } - public final class PlaybackParams implements android.os.Parcelable { method public int getAudioStretchMode(); method public android.media.PlaybackParams setAudioStretchMode(int); diff --git a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java index e3748f1653ab..3defdc5467c8 100644 --- a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java +++ b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java @@ -247,7 +247,7 @@ public class Bmgr { } try { - mBmgr.dataChanged(pkg); + mBmgr.dataChangedForUser(userId, pkg); } catch (RemoteException e) { System.err.println(e.toString()); System.err.println(BMGR_NOT_RUNNING_ERR); @@ -264,7 +264,8 @@ public class Bmgr { } if (allPkgs.size() > 0) { try { - mBmgr.fullTransportBackup(allPkgs.toArray(new String[allPkgs.size()])); + mBmgr.fullTransportBackupForUser( + userId, allPkgs.toArray(new String[allPkgs.size()])); } catch (RemoteException e) { System.err.println(e.toString()); System.err.println(BMGR_NOT_RUNNING_ERR); @@ -393,7 +394,7 @@ public class Bmgr { installedPackages.stream().map(p -> p.packageName).toArray(String[]::new); String[] filteredPackages = {}; try { - filteredPackages = mBmgr.filterAppsEligibleForBackup(packages); + filteredPackages = mBmgr.filterAppsEligibleForBackupForUser(userId, packages); } catch (RemoteException e) { System.err.println(e.toString()); System.err.println(BMGR_NOT_RUNNING_ERR); @@ -498,11 +499,11 @@ public class Bmgr { } if ("-c".equals(which)) { - doTransportByComponent(); + doTransportByComponent(userId); return; } - String old = mBmgr.selectBackupTransport(which); + String old = mBmgr.selectBackupTransportForUser(userId, which); if (old == null) { System.out.println("Unknown transport '" + which + "' specified; no changes made."); @@ -516,7 +517,7 @@ public class Bmgr { } } - private void doTransportByComponent() { + private void doTransportByComponent(@UserIdInt int userId) { String which = nextArg(); if (which == null) { showUsage(); @@ -526,7 +527,9 @@ public class Bmgr { final CountDownLatch latch = new CountDownLatch(1); try { - mBmgr.selectBackupTransportAsync(ComponentName.unflattenFromString(which), + mBmgr.selectBackupTransportAsyncForUser( + userId, + ComponentName.unflattenFromString(which), new ISelectBackupTransportCallback.Stub() { @Override public void onSuccess(String transportName) { @@ -567,7 +570,7 @@ public class Bmgr { } try { - mBmgr.clearBackupData(transport, pkg); + mBmgr.clearBackupDataForUser(userId, transport, pkg); System.out.println("Wiped backup data for " + pkg + " on " + transport); } catch (RemoteException e) { System.err.println(e.toString()); @@ -599,7 +602,8 @@ public class Bmgr { InitObserver observer = new InitObserver(); try { System.out.println("Initializing transports: " + transports); - mBmgr.initializeTransports(transports.toArray(new String[transports.size()]), observer); + mBmgr.initializeTransportsForUser( + userId, transports.toArray(new String[transports.size()]), observer); observer.waitForCompletion(30*1000L); System.out.println("Initialization result: " + observer.result); } catch (RemoteException e) { @@ -611,13 +615,13 @@ public class Bmgr { private void doList(@UserIdInt int userId) { String arg = nextArg(); // sets, transports, packages set# if ("transports".equals(arg)) { - doListTransports(); + doListTransports(userId); return; } // The rest of the 'list' options work with a restore session on the current transport try { - mRestore = mBmgr.beginRestoreSession(null, null); + mRestore = mBmgr.beginRestoreSessionForUser(userId, null, null); if (mRestore == null) { System.err.println(BMGR_NOT_RUNNING_ERR); return; @@ -634,19 +638,19 @@ public class Bmgr { } } - private void doListTransports() { + private void doListTransports(@UserIdInt int userId) { String arg = nextArg(); try { if ("-c".equals(arg)) { - for (ComponentName transport : mBmgr.listAllTransportComponents()) { + for (ComponentName transport : mBmgr.listAllTransportComponentsForUser(userId)) { System.out.println(transport.flattenToShortString()); } return; } - String current = mBmgr.getCurrentTransport(); - String[] transports = mBmgr.listAllTransports(); + String current = mBmgr.getCurrentTransportForUser(userId); + String[] transports = mBmgr.listAllTransportsForUser(userId); if (transports == null || transports.length == 0) { System.out.println("No transports available."); return; @@ -756,7 +760,7 @@ public class Bmgr { filter.add(arg); } - doRestoreAll(token, filter); + doRestoreAll(userId, token, filter); } catch (NumberFormatException e) { showUsage(); return; @@ -769,12 +773,12 @@ public class Bmgr { System.err.println("'restore <token> <package>'."); } - private void doRestoreAll(long token, HashSet<String> filter) { + private void doRestoreAll(@UserIdInt int userId, long token, HashSet<String> filter) { RestoreObserver observer = new RestoreObserver(); try { boolean didRestore = false; - mRestore = mBmgr.beginRestoreSession(null, null); + mRestore = mBmgr.beginRestoreSessionForUser(userId, null, null); if (mRestore == null) { System.err.println(BMGR_NOT_RUNNING_ERR); return; diff --git a/cmds/idmap2/idmap2/Lookup.cpp b/cmds/idmap2/idmap2/Lookup.cpp index 020c443582b2..8d0cee5b938d 100644 --- a/cmds/idmap2/idmap2/Lookup.cpp +++ b/cmds/idmap2/idmap2/Lookup.cpp @@ -36,6 +36,7 @@ #include "idmap2/CommandLineOptions.h" #include "idmap2/Idmap.h" +#include "idmap2/Result.h" #include "idmap2/Xml.h" #include "idmap2/ZipFile.h" @@ -53,39 +54,40 @@ using android::base::StringPrintf; using android::idmap2::CommandLineOptions; using android::idmap2::IdmapHeader; using android::idmap2::ResourceId; +using android::idmap2::Result; using android::idmap2::Xml; using android::idmap2::ZipFile; using android::util::Utf16ToUtf8; namespace { -std::pair<bool, ResourceId> WARN_UNUSED ParseResReference(const AssetManager2& am, - const std::string& res, - const std::string& fallback_package) { + +Result<ResourceId> WARN_UNUSED ParseResReference(const AssetManager2& am, const std::string& res, + const std::string& fallback_package) { // first, try to parse as a hex number char* endptr = nullptr; ResourceId resid; resid = strtol(res.c_str(), &endptr, 16); if (*endptr == '\0') { - return std::make_pair(true, resid); + return {resid}; } // next, try to parse as a package:type/name string resid = am.GetResourceId(res, "", fallback_package); if (is_valid_resid(resid)) { - return std::make_pair(true, resid); + return {resid}; } // end of the road: res could not be parsed - return std::make_pair(false, 0); + return {}; } -std::pair<bool, std::string> WARN_UNUSED GetValue(const AssetManager2& am, ResourceId resid) { +Result<std::string> WARN_UNUSED GetValue(const AssetManager2& am, ResourceId resid) { Res_value value; ResTable_config config; uint32_t flags; ApkAssetsCookie cookie = am.GetResource(resid, false, 0, &value, &config, &flags); if (cookie == kInvalidCookie) { - return std::make_pair(false, ""); + return {}; } std::string out; @@ -123,31 +125,31 @@ std::pair<bool, std::string> WARN_UNUSED GetValue(const AssetManager2& am, Resou out.append(StringPrintf("dataType=0x%02x data=0x%08x", value.dataType, value.data)); break; } - return std::make_pair(true, out); + return {out}; } -std::pair<bool, std::string> GetTargetPackageNameFromManifest(const std::string& apk_path) { +Result<std::string> GetTargetPackageNameFromManifest(const std::string& apk_path) { const auto zip = ZipFile::Open(apk_path); if (!zip) { - return std::make_pair(false, ""); + return {}; } const auto entry = zip->Uncompress("AndroidManifest.xml"); if (!entry) { - return std::make_pair(false, ""); + return {}; } const auto xml = Xml::Create(entry->buf, entry->size); if (!xml) { - return std::make_pair(false, ""); + return {}; } const auto tag = xml->FindTag("overlay"); if (!tag) { - return std::make_pair(false, ""); + return {}; } const auto iter = tag->find("targetPackage"); if (iter == tag->end()) { - return std::make_pair(false, ""); + return {}; } - return std::make_pair(true, iter->second); + return {iter->second}; } } // namespace @@ -195,14 +197,14 @@ bool Lookup(const std::vector<std::string>& args, std::ostream& out_error) { } apk_assets.push_back(std::move(target_apk)); - bool lookup_ok; - std::tie(lookup_ok, target_package_name) = + const Result<std::string> package_name = GetTargetPackageNameFromManifest(idmap_header->GetOverlayPath().to_string()); - if (!lookup_ok) { + if (!package_name) { out_error << "error: failed to parse android:targetPackage from overlay manifest" << std::endl; return false; } + target_package_name = *package_name; } else if (target_path != idmap_header->GetTargetPath()) { out_error << "error: different target APKs (expected target APK " << target_path << " but " << idmap_path << " has target APK " << idmap_header->GetTargetPath() << ")" @@ -227,21 +229,18 @@ bool Lookup(const std::vector<std::string>& args, std::ostream& out_error) { am.SetApkAssets(raw_pointer_apk_assets); am.SetConfiguration(config); - ResourceId resid; - bool lookup_ok; - std::tie(lookup_ok, resid) = ParseResReference(am, resid_str, target_package_name); - if (!lookup_ok) { + const Result<ResourceId> resid = ParseResReference(am, resid_str, target_package_name); + if (!resid) { out_error << "error: failed to parse resource ID" << std::endl; return false; } - std::string value; - std::tie(lookup_ok, value) = GetValue(am, resid); - if (!lookup_ok) { - out_error << StringPrintf("error: resource 0x%08x not found", resid) << std::endl; + const Result<std::string> value = GetValue(am, *resid); + if (!value) { + out_error << StringPrintf("error: resource 0x%08x not found", *resid) << std::endl; return false; } - std::cout << value << std::endl; + std::cout << *value << std::endl; return true; } diff --git a/cmds/idmap2/idmap2d/Idmap2Service.cpp b/cmds/idmap2/idmap2d/Idmap2Service.cpp index cf72cb94da2c..86b00f1d6e95 100644 --- a/cmds/idmap2/idmap2d/Idmap2Service.cpp +++ b/cmds/idmap2/idmap2d/Idmap2Service.cpp @@ -78,6 +78,18 @@ Status Idmap2Service::removeIdmap(const std::string& overlay_apk_path, } } +Status Idmap2Service::verifyIdmap(const std::string& overlay_apk_path, + int32_t user_id ATTRIBUTE_UNUSED, bool* _aidl_return) { + assert(_aidl_return); + const std::string idmap_path = Idmap::CanonicalIdmapPathFor(kIdmapCacheDir, overlay_apk_path); + std::ifstream fin(idmap_path); + const std::unique_ptr<const IdmapHeader> header = IdmapHeader::FromBinaryStream(fin); + fin.close(); + std::stringstream dev_null; + *_aidl_return = header && header->IsUpToDate(dev_null); + return ok(); +} + Status Idmap2Service::createIdmap(const std::string& target_apk_path, const std::string& overlay_apk_path, int32_t user_id, std::unique_ptr<std::string>* _aidl_return) { @@ -90,17 +102,6 @@ Status Idmap2Service::createIdmap(const std::string& target_apk_path, _aidl_return->reset(nullptr); - const std::string idmap_path = Idmap::CanonicalIdmapPathFor(kIdmapCacheDir, overlay_apk_path); - std::ifstream fin(idmap_path); - const std::unique_ptr<const IdmapHeader> header = IdmapHeader::FromBinaryStream(fin); - fin.close(); - // do not reuse error stream from IsUpToDate below, or error messages will be - // polluted with irrelevant data - std::stringstream dev_null; - if (header && header->IsUpToDate(dev_null)) { - return ok(); - } - const std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path); if (!target_apk) { return error("failed to load apk " + target_apk_path); @@ -119,6 +120,7 @@ Status Idmap2Service::createIdmap(const std::string& target_apk_path, } umask(0133); // u=rw,g=r,o=r + const std::string idmap_path = Idmap::CanonicalIdmapPathFor(kIdmapCacheDir, overlay_apk_path); std::ofstream fout(idmap_path); if (fout.fail()) { return error("failed to open idmap path " + idmap_path); diff --git a/cmds/idmap2/idmap2d/Idmap2Service.h b/cmds/idmap2/idmap2d/Idmap2Service.h index 2b32042d6aa3..4e5abc9069f4 100644 --- a/cmds/idmap2/idmap2d/Idmap2Service.h +++ b/cmds/idmap2/idmap2d/Idmap2Service.h @@ -39,6 +39,9 @@ class Idmap2Service : public BinderService<Idmap2Service>, public BnIdmap2 { binder::Status removeIdmap(const std::string& overlay_apk_path, int32_t user_id, bool* _aidl_return); + binder::Status verifyIdmap(const std::string& overlay_apk_path, int32_t user_id, + bool* _aidl_return); + binder::Status createIdmap(const std::string& target_apk_path, const std::string& overlay_apk_path, int32_t user_id, std::unique_ptr<std::string>* _aidl_return); diff --git a/cmds/idmap2/idmap2d/aidl/android/os/IIdmap2.aidl b/cmds/idmap2/idmap2d/aidl/android/os/IIdmap2.aidl index 5d196101a7a6..d475417a0935 100644 --- a/cmds/idmap2/idmap2d/aidl/android/os/IIdmap2.aidl +++ b/cmds/idmap2/idmap2d/aidl/android/os/IIdmap2.aidl @@ -22,6 +22,7 @@ package android.os; interface IIdmap2 { @utf8InCpp String getIdmapPath(@utf8InCpp String overlayApkPath, int userId); boolean removeIdmap(@utf8InCpp String overlayApkPath, int userId); + boolean verifyIdmap(@utf8InCpp String overlayApkPath, int userId); @nullable @utf8InCpp String createIdmap(@utf8InCpp String targetApkPath, @utf8InCpp String overlayApkPath, int userId); } diff --git a/cmds/idmap2/include/idmap2/ResourceUtils.h b/cmds/idmap2/include/idmap2/ResourceUtils.h index 88a835b6439c..d106f19af998 100644 --- a/cmds/idmap2/include/idmap2/ResourceUtils.h +++ b/cmds/idmap2/include/idmap2/ResourceUtils.h @@ -18,19 +18,18 @@ #define IDMAP2_INCLUDE_IDMAP2_RESOURCEUTILS_H_ #include <string> -#include <utility> #include "android-base/macros.h" #include "androidfw/AssetManager2.h" #include "idmap2/Idmap.h" +#include "idmap2/Result.h" namespace android { namespace idmap2 { namespace utils { -std::pair<bool, std::string> WARN_UNUSED ResToTypeEntryName(const AssetManager2& am, - ResourceId resid); +Result<std::string> WARN_UNUSED ResToTypeEntryName(const AssetManager2& am, ResourceId resid); } // namespace utils } // namespace idmap2 diff --git a/cmds/idmap2/include/idmap2/Result.h b/cmds/idmap2/include/idmap2/Result.h new file mode 100644 index 000000000000..6189ea371ee1 --- /dev/null +++ b/cmds/idmap2/include/idmap2/Result.h @@ -0,0 +1,31 @@ +/* + * 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 IDMAP2_INCLUDE_IDMAP2_RESULT_H_ +#define IDMAP2_INCLUDE_IDMAP2_RESULT_H_ + +#include <optional> + +namespace android::idmap2 { + +template <typename T> +using Result = std::optional<T>; + +static constexpr std::nullopt_t kResultError = std::nullopt; + +} // namespace android::idmap2 + +#endif // IDMAP2_INCLUDE_IDMAP2_RESULT_H_ diff --git a/cmds/idmap2/include/idmap2/ZipFile.h b/cmds/idmap2/include/idmap2/ZipFile.h index 328bd367adfc..9edbbe0cce90 100644 --- a/cmds/idmap2/include/idmap2/ZipFile.h +++ b/cmds/idmap2/include/idmap2/ZipFile.h @@ -19,10 +19,10 @@ #include <memory> #include <string> -#include <utility> #include "android-base/macros.h" #include "ziparchive/zip_archive.h" +#include "idmap2/Result.h" namespace android { namespace idmap2 { @@ -43,7 +43,7 @@ class ZipFile { static std::unique_ptr<const ZipFile> Open(const std::string& path); std::unique_ptr<const MemoryChunk> Uncompress(const std::string& entryPath) const; - std::pair<bool, uint32_t> Crc(const std::string& entryPath) const; + Result<uint32_t> Crc(const std::string& entryPath) const; ~ZipFile(); diff --git a/cmds/idmap2/libidmap2/Idmap.cpp b/cmds/idmap2/libidmap2/Idmap.cpp index 5a47e301b66c..1ef326793cb4 100644 --- a/cmds/idmap2/libidmap2/Idmap.cpp +++ b/cmds/idmap2/libidmap2/Idmap.cpp @@ -33,6 +33,7 @@ #include "idmap2/Idmap.h" #include "idmap2/ResourceUtils.h" +#include "idmap2/Result.h" #include "idmap2/ZipFile.h" namespace android { @@ -143,18 +144,16 @@ bool IdmapHeader::IsUpToDate(std::ostream& out_error) const { return false; } - bool status; - uint32_t target_crc; - std::tie(status, target_crc) = target_zip->Crc("resources.arsc"); - if (!status) { + Result<uint32_t> target_crc = target_zip->Crc("resources.arsc"); + if (!target_crc) { out_error << "error: failed to get target crc" << std::endl; return false; } - if (target_crc_ != target_crc) { + if (target_crc_ != *target_crc) { out_error << base::StringPrintf( "error: bad target crc: idmap version 0x%08x, file system version 0x%08x", - target_crc_, target_crc) + target_crc_, *target_crc) << std::endl; return false; } @@ -165,17 +164,16 @@ bool IdmapHeader::IsUpToDate(std::ostream& out_error) const { return false; } - uint32_t overlay_crc; - std::tie(status, overlay_crc) = overlay_zip->Crc("resources.arsc"); - if (!status) { + Result<uint32_t> overlay_crc = overlay_zip->Crc("resources.arsc"); + if (!overlay_crc) { out_error << "error: failed to get overlay crc" << std::endl; return false; } - if (overlay_crc_ != overlay_crc) { + if (overlay_crc_ != *overlay_crc) { out_error << base::StringPrintf( "error: bad overlay crc: idmap version 0x%08x, file system version 0x%08x", - overlay_crc_, overlay_crc) + overlay_crc_, *overlay_crc) << std::endl; return false; } @@ -322,17 +320,20 @@ std::unique_ptr<const Idmap> Idmap::FromApkAssets(const std::string& target_apk_ std::unique_ptr<IdmapHeader> header(new IdmapHeader()); header->magic_ = kIdmapMagic; header->version_ = kIdmapCurrentVersion; - bool crc_status; - std::tie(crc_status, header->target_crc_) = target_zip->Crc("resources.arsc"); - if (!crc_status) { + + Result<uint32_t> crc = target_zip->Crc("resources.arsc"); + if (!crc) { out_error << "error: failed to get zip crc for target" << std::endl; return nullptr; } - std::tie(crc_status, header->overlay_crc_) = overlay_zip->Crc("resources.arsc"); - if (!crc_status) { + header->target_crc_ = *crc; + + crc = overlay_zip->Crc("resources.arsc"); + if (!crc) { out_error << "error: failed to get zip crc for overlay" << std::endl; return nullptr; } + header->overlay_crc_ = *crc; if (target_apk_path.size() > sizeof(header->target_path_)) { out_error << "error: target apk path \"" << target_apk_path << "\" longer that maximum size " @@ -358,15 +359,14 @@ std::unique_ptr<const Idmap> Idmap::FromApkAssets(const std::string& target_apk_ const auto end = overlay_pkg->end(); for (auto iter = overlay_pkg->begin(); iter != end; ++iter) { const ResourceId overlay_resid = *iter; - bool lookup_ok; - std::string name; - std::tie(lookup_ok, name) = utils::ResToTypeEntryName(overlay_asset_manager, overlay_resid); - if (!lookup_ok) { + Result<std::string> name = utils::ResToTypeEntryName(overlay_asset_manager, overlay_resid); + if (!name) { continue; } // prepend "<package>:" to turn name into "<package>:<type>/<name>" - name = base::StringPrintf("%s:%s", target_pkg->GetPackageName().c_str(), name.c_str()); - const ResourceId target_resid = NameToResid(target_asset_manager, name); + const std::string full_name = + base::StringPrintf("%s:%s", target_pkg->GetPackageName().c_str(), name->c_str()); + const ResourceId target_resid = NameToResid(target_asset_manager, full_name); if (target_resid == 0) { continue; } diff --git a/cmds/idmap2/libidmap2/PrettyPrintVisitor.cpp b/cmds/idmap2/libidmap2/PrettyPrintVisitor.cpp index 492e6f049d68..fb3bc5ba9ae8 100644 --- a/cmds/idmap2/libidmap2/PrettyPrintVisitor.cpp +++ b/cmds/idmap2/libidmap2/PrettyPrintVisitor.cpp @@ -15,7 +15,6 @@ */ #include <string> -#include <utility> #include "android-base/macros.h" #include "android-base/stringprintf.h" @@ -23,6 +22,7 @@ #include "idmap2/PrettyPrintVisitor.h" #include "idmap2/ResourceUtils.h" +#include "idmap2/Result.h" namespace android { namespace idmap2 { @@ -63,11 +63,9 @@ void PrettyPrintVisitor::visit(const IdmapData::TypeEntry& te) { stream_ << base::StringPrintf("0x%08x -> 0x%08x", target_resid, overlay_resid); if (target_package_loaded) { - bool lookup_ok; - std::string name; - std::tie(lookup_ok, name) = utils::ResToTypeEntryName(target_am_, target_resid); - if (lookup_ok) { - stream_ << " " << name; + Result<std::string> name = utils::ResToTypeEntryName(target_am_, target_resid); + if (name) { + stream_ << " " << *name; } } stream_ << std::endl; diff --git a/cmds/idmap2/libidmap2/RawPrintVisitor.cpp b/cmds/idmap2/libidmap2/RawPrintVisitor.cpp index 57cfc8ef85b4..7c24445ef902 100644 --- a/cmds/idmap2/libidmap2/RawPrintVisitor.cpp +++ b/cmds/idmap2/libidmap2/RawPrintVisitor.cpp @@ -16,7 +16,6 @@ #include <cstdarg> #include <string> -#include <utility> #include "android-base/macros.h" #include "android-base/stringprintf.h" @@ -24,6 +23,7 @@ #include "idmap2/RawPrintVisitor.h" #include "idmap2/ResourceUtils.h" +#include "idmap2/Result.h" using android::ApkAssets; @@ -75,14 +75,13 @@ void RawPrintVisitor::visit(const IdmapData::TypeEntry& te) { const ResourceId target_resid = RESID(last_seen_package_id_, te.GetTargetTypeId(), te.GetEntryOffset() + i); const ResourceId overlay_resid = RESID(last_seen_package_id_, te.GetOverlayTypeId(), entry); - bool lookup_ok = false; - std::string name; + Result<std::string> name; if (target_package_loaded) { - std::tie(lookup_ok, name) = utils::ResToTypeEntryName(target_am_, target_resid); + name = utils::ResToTypeEntryName(target_am_, target_resid); } - if (lookup_ok) { + if (name) { print(static_cast<uint32_t>(entry), "0x%08x -> 0x%08x %s", target_resid, overlay_resid, - name.c_str()); + name->c_str()); } else { print(static_cast<uint32_t>(entry), "0x%08x -> 0x%08x", target_resid, overlay_resid); } diff --git a/cmds/idmap2/libidmap2/ResourceUtils.cpp b/cmds/idmap2/libidmap2/ResourceUtils.cpp index e98f843931c8..5c897832e9d5 100644 --- a/cmds/idmap2/libidmap2/ResourceUtils.cpp +++ b/cmds/idmap2/libidmap2/ResourceUtils.cpp @@ -15,12 +15,12 @@ */ #include <string> -#include <utility> #include "androidfw/StringPiece.h" #include "androidfw/Util.h" #include "idmap2/ResourceUtils.h" +#include "idmap2/Result.h" using android::StringPiece16; using android::util::Utf16ToUtf8; @@ -29,11 +29,10 @@ namespace android { namespace idmap2 { namespace utils { -std::pair<bool, std::string> WARN_UNUSED ResToTypeEntryName(const AssetManager2& am, - ResourceId resid) { +Result<std::string> WARN_UNUSED ResToTypeEntryName(const AssetManager2& am, ResourceId resid) { AssetManager2::ResourceName name; if (!am.GetResourceName(resid, &name)) { - return std::make_pair(false, ""); + return {}; } std::string out; if (name.type != nullptr) { @@ -47,7 +46,7 @@ std::pair<bool, std::string> WARN_UNUSED ResToTypeEntryName(const AssetManager2& } else { out += Utf16ToUtf8(StringPiece16(name.entry16, name.entry_len)); } - return std::make_pair(true, out); + return {out}; } } // namespace utils diff --git a/cmds/idmap2/libidmap2/ZipFile.cpp b/cmds/idmap2/libidmap2/ZipFile.cpp index 3f2079a380d6..9fb611dd8e8d 100644 --- a/cmds/idmap2/libidmap2/ZipFile.cpp +++ b/cmds/idmap2/libidmap2/ZipFile.cpp @@ -16,8 +16,8 @@ #include <memory> #include <string> -#include <utility> +#include "idmap2/Result.h" #include "idmap2/ZipFile.h" namespace android { @@ -57,10 +57,10 @@ std::unique_ptr<const MemoryChunk> ZipFile::Uncompress(const std::string& entryP return chunk; } -std::pair<bool, uint32_t> ZipFile::Crc(const std::string& entryPath) const { +Result<uint32_t> ZipFile::Crc(const std::string& entryPath) const { ::ZipEntry entry; int32_t status = ::FindEntry(handle_, ::ZipString(entryPath.c_str()), &entry); - return std::make_pair(status == 0, entry.crc32); + return status == 0 ? Result<uint32_t>(entry.crc32) : kResultError; } } // namespace idmap2 diff --git a/cmds/idmap2/tests/ResourceUtilsTests.cpp b/cmds/idmap2/tests/ResourceUtilsTests.cpp index 0547fa00de3d..7f60d7529a61 100644 --- a/cmds/idmap2/tests/ResourceUtilsTests.cpp +++ b/cmds/idmap2/tests/ResourceUtilsTests.cpp @@ -16,13 +16,13 @@ #include <memory> #include <string> -#include <utility> #include "gmock/gmock.h" #include "gtest/gtest.h" #include "androidfw/ApkAssets.h" #include "idmap2/ResourceUtils.h" +#include "idmap2/Result.h" #include "TestHelpers.h" @@ -52,17 +52,14 @@ class ResourceUtilsTests : public Idmap2Tests { }; TEST_F(ResourceUtilsTests, ResToTypeEntryName) { - bool lookup_ok; - std::string name; - std::tie(lookup_ok, name) = utils::ResToTypeEntryName(GetAssetManager(), 0x7f010000u); - ASSERT_TRUE(lookup_ok); - ASSERT_EQ(name, "integer/int1"); + Result<std::string> name = utils::ResToTypeEntryName(GetAssetManager(), 0x7f010000u); + ASSERT_TRUE(name); + ASSERT_EQ(*name, "integer/int1"); } TEST_F(ResourceUtilsTests, ResToTypeEntryNameNoSuchResourceId) { - bool lookup_ok; - std::tie(lookup_ok, std::ignore) = utils::ResToTypeEntryName(GetAssetManager(), 0x7f123456u); - ASSERT_FALSE(lookup_ok); + Result<std::string> name = utils::ResToTypeEntryName(GetAssetManager(), 0x7f123456u); + ASSERT_FALSE(name); } } // namespace idmap2 diff --git a/cmds/idmap2/tests/ZipFileTests.cpp b/cmds/idmap2/tests/ZipFileTests.cpp index a504d3126c05..6e4a501a51ac 100644 --- a/cmds/idmap2/tests/ZipFileTests.cpp +++ b/cmds/idmap2/tests/ZipFileTests.cpp @@ -16,8 +16,8 @@ #include <cstdio> // fclose #include <string> -#include <utility> +#include "idmap2/Result.h" #include "idmap2/ZipFile.h" #include "gmock/gmock.h" @@ -44,14 +44,12 @@ TEST(ZipFileTests, Crc) { auto zip = ZipFile::Open(GetTestDataPath() + "/target/target.apk"); ASSERT_THAT(zip, NotNull()); - bool status; - uint32_t crc; - std::tie(status, crc) = zip->Crc("AndroidManifest.xml"); - ASSERT_TRUE(status); - ASSERT_EQ(crc, 0x762f3d24); + Result<uint32_t> crc = zip->Crc("AndroidManifest.xml"); + ASSERT_TRUE(crc); + ASSERT_EQ(*crc, 0x762f3d24); - std::tie(status, std::ignore) = zip->Crc("does-not-exist"); - ASSERT_FALSE(status); + Result<uint32_t> crc2 = zip->Crc("does-not-exist"); + ASSERT_FALSE(crc2); } TEST(ZipFileTests, Uncompress) { diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto index 5899e0cfbca1..0c05be1170d1 100644 --- a/cmds/statsd/src/atoms.proto +++ b/cmds/statsd/src/atoms.proto @@ -171,6 +171,7 @@ message Atom { DocsUIUserActionReported docs_ui_user_action_reported = 112; WifiEnabledStateChanged wifi_enabled_state_changed = 113; WifiRunningStateChanged wifi_running_state_changed = 114; + AppCompacted app_compacted = 115; } // Pulled events will start at field 10000. @@ -3649,3 +3650,65 @@ message DocsUIUserActionReported { message DocsUIInvalidScopedAccessRequestReported { optional android.stats.docsui.InvalidScopedAccess type = 1; } + +/** + * Logs when an app's memory is compacted. + * + * Logged from: + * frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java + */ +message AppCompacted { + // The pid of the process being compacted. + optional int32 pid = 1; + + // The name of the process being compacted. + optional string process_name = 2; + + // The type of compaction. + enum Action { + UNKNOWN = 0; + SOME = 1; + FULL = 2; + } + optional Action action = 3 [default = UNKNOWN]; + + // Total RSS in kilobytes consumed by the process prior to compaction. + optional int64 before_rss_total_kilobytes = 4; + + // File RSS in kilobytes consumed by the process prior to compaction. + optional int64 before_rss_file_kilobytes = 5; + + // Anonymous RSS in kilobytes consumed by the process prior to compaction. + optional int64 before_rss_anon_kilobytes = 6; + + // Swap in kilobytes consumed by the process prior to compaction. + optional int64 before_swap_kilobytes = 7; + + // Total RSS in kilobytes consumed by the process after compaction. + optional int64 after_rss_total_kilobytes = 8; + + // File RSS in kilobytes consumed by the process after compaction. + optional int64 after_rss_file_kilobytes = 9; + + // Anonymous RSS in kilobytes consumed by the process after compaction. + optional int64 after_rss_anon_kilobytes = 10; + + // Swap in kilobytes consumed by the process after compaction. + optional int64 after_swap_kilobytes = 11; + + // The time taken to perform compaction in milliseconds. + optional int64 time_to_compact_millis = 12; + + // The last compaction action performed for this app. + optional Action last_action = 13; + + // The last time that compaction was attempted on this process in seconds + // since boot. + optional int64 last_compact_timestamp = 14; + + // The oom_adj at the time of compaction. + optional int32 oom_adj = 15; + + // The process state at the time of compaction. + optional android.app.ProcessStateEnum process_state = 16 [default = PROCESS_STATE_UNKNOWN]; +} diff --git a/cmds/statsd/src/metrics/CountMetricProducer.cpp b/cmds/statsd/src/metrics/CountMetricProducer.cpp index 13579d283de0..7cc57c12063c 100644 --- a/cmds/statsd/src/metrics/CountMetricProducer.cpp +++ b/cmds/statsd/src/metrics/CountMetricProducer.cpp @@ -154,7 +154,7 @@ void CountMetricProducer::onDumpReportLocked(const int64_t dumpTimeNs, flushIfNeededLocked(dumpTimeNs); } protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_ID, (long long)mMetricId); - protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_IS_ACTIVE, isActiveLocked()); + protoOutput->write(FIELD_TYPE_BOOL | FIELD_ID_IS_ACTIVE, isActiveLocked()); if (mPastBuckets.empty()) { diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.cpp b/cmds/statsd/src/metrics/DurationMetricProducer.cpp index 0425671b6367..69bafc354643 100644 --- a/cmds/statsd/src/metrics/DurationMetricProducer.cpp +++ b/cmds/statsd/src/metrics/DurationMetricProducer.cpp @@ -463,7 +463,7 @@ void DurationMetricProducer::onDumpReportLocked(const int64_t dumpTimeNs, flushIfNeededLocked(dumpTimeNs); } protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_ID, (long long)mMetricId); - protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_IS_ACTIVE, isActiveLocked()); + protoOutput->write(FIELD_TYPE_BOOL | FIELD_ID_IS_ACTIVE, isActiveLocked()); if (mPastBuckets.empty()) { VLOG(" Duration metric, empty return"); diff --git a/cmds/statsd/src/metrics/EventMetricProducer.cpp b/cmds/statsd/src/metrics/EventMetricProducer.cpp index ea125d02f337..c53c4ce5e0d6 100644 --- a/cmds/statsd/src/metrics/EventMetricProducer.cpp +++ b/cmds/statsd/src/metrics/EventMetricProducer.cpp @@ -110,7 +110,7 @@ void EventMetricProducer::onDumpReportLocked(const int64_t dumpTimeNs, std::set<string> *str_set, ProtoOutputStream* protoOutput) { protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_ID, (long long)mMetricId); - protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_IS_ACTIVE, isActiveLocked()); + protoOutput->write(FIELD_TYPE_BOOL | FIELD_ID_IS_ACTIVE, isActiveLocked()); if (mProto->size() <= 0) { return; } diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp index 98a33f5280e6..ec602445d0ed 100644 --- a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp +++ b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp @@ -194,7 +194,7 @@ void GaugeMetricProducer::onDumpReportLocked(const int64_t dumpTimeNs, } protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_ID, (long long)mMetricId); - protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_IS_ACTIVE, isActiveLocked()); + protoOutput->write(FIELD_TYPE_BOOL | FIELD_ID_IS_ACTIVE, isActiveLocked()); if (mPastBuckets.empty()) { return; diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.cpp b/cmds/statsd/src/metrics/ValueMetricProducer.cpp index 7475b53c0a84..cf56e2d43385 100644 --- a/cmds/statsd/src/metrics/ValueMetricProducer.cpp +++ b/cmds/statsd/src/metrics/ValueMetricProducer.cpp @@ -192,7 +192,7 @@ void ValueMetricProducer::onDumpReportLocked(const int64_t dumpTimeNs, flushIfNeededLocked(dumpTimeNs); } protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_ID, (long long)mMetricId); - protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_IS_ACTIVE, isActiveLocked()); + protoOutput->write(FIELD_TYPE_BOOL | FIELD_ID_IS_ACTIVE, isActiveLocked()); if (mPastBuckets.empty() && mSkippedBuckets.empty()) { return; diff --git a/cmds/statsd/tests/StatsLogProcessor_test.cpp b/cmds/statsd/tests/StatsLogProcessor_test.cpp index 237f8b902015..d52be441f6b6 100644 --- a/cmds/statsd/tests/StatsLogProcessor_test.cpp +++ b/cmds/statsd/tests/StatsLogProcessor_test.cpp @@ -279,7 +279,10 @@ TEST(StatsLogProcessorTest, TestOnDumpReportEraseData) { // Dump report again. There should be no data since we erased it. processor->onDumpReport(cfgKey, 5, true, true /* DO erase data. */, ADB_DUMP, &bytes); output.ParseFromArray(bytes.data(), bytes.size()); - bool noData = (output.reports_size() == 0) || (output.reports(0).metrics_size() == 0); + // We don't care whether statsd has a report, as long as it has no count metrics in it. + bool noData = output.reports_size() == 0 + || output.reports(0).metrics_size() == 0 + || output.reports(0).metrics(0).count_metrics().data_size() == 0; EXPECT_TRUE(noData); } diff --git a/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/Utils.java b/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/Utils.java index 8464b8df03d0..91d6490fe5a1 100644 --- a/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/Utils.java +++ b/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/Utils.java @@ -62,7 +62,7 @@ public class Utils { if (process.waitFor() == 0) { logger.fine("Adb command successful."); } else { - logger.severe("Abnormal adb shell cmd termination for: " + String.join(",", commands)); + logger.severe("Abnormal adb shell termination for: " + String.join(",", commands)); throw new RuntimeException("Error running adb command: " + err.toString()); } } @@ -118,6 +118,52 @@ public class Utils { logger.addHandler(handler); } + /** + * Attempt to determine whether tool will work with this statsd, i.e. whether statsd is + * minCodename or higher. + * Algorithm: true if (sdk >= minSdk) || (sdk == minSdk-1 && codeName.startsWith(minCodeName)) + * If all else fails, assume it will work (letting future commands deal with any errors). + */ + public static boolean isAcceptableStatsd(Logger logger, int minSdk, String minCodename) { + BufferedReader in = null; + try { + File outFileSdk = File.createTempFile("shelltools_sdk", "tmp"); + outFileSdk.deleteOnExit(); + runCommand(outFileSdk, logger, + "adb", "shell", "getprop", "ro.build.version.sdk"); + in = new BufferedReader(new InputStreamReader(new FileInputStream(outFileSdk))); + // If NullPointerException/NumberFormatException/etc., just catch and return true. + int sdk = Integer.parseInt(in.readLine().trim()); + if (sdk >= minSdk) { + return true; + } else if (sdk == minSdk - 1) { // Could be minSdk-1, or could be minSdk development. + in.close(); + File outFileCode = File.createTempFile("shelltools_codename", "tmp"); + outFileCode.deleteOnExit(); + runCommand(outFileCode, logger, + "adb", "shell", "getprop", "ro.build.version.codename"); + in = new BufferedReader(new InputStreamReader(new FileInputStream(outFileCode))); + return in.readLine().startsWith(minCodename); + } else { + return false; + } + } catch (Exception e) { + logger.fine("Could not determine whether statsd version is compatibile " + + "with tool: " + e.toString()); + } finally { + try { + if (in != null) { + in.close(); + } + } catch (IOException e) { + logger.fine("Could not close temporary file: " + e.toString()); + } + } + // Could not determine whether statsd is acceptable version. + // Just assume it is; if it isn't, we'll just get future errors via adb and deal with them. + return true; + } + public static class LocalToolsFormatter extends Formatter { public String format(LogRecord record) { return record.getMessage() + "\n"; diff --git a/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/localdrive/LocalDrive.java b/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/localdrive/LocalDrive.java index 67fb906c2570..2eb46605b28d 100644 --- a/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/localdrive/LocalDrive.java +++ b/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/localdrive/LocalDrive.java @@ -37,6 +37,9 @@ import java.util.logging.Logger; public class LocalDrive { private static final boolean DEBUG = false; + public static final int MIN_SDK = 29; + public static final String MIN_CODENAME = "Q"; + public static final long DEFAULT_CONFIG_ID = 56789; public static final String BINARY_FLAG = "--binary"; @@ -46,7 +49,7 @@ public class LocalDrive { public static final String HELP_STRING = "Usage:\n\n" + - "statsd_local upload CONFIG_FILE [CONFIG_ID] [--binary]\n" + + "statsd_localdrive upload CONFIG_FILE [CONFIG_ID] [--binary]\n" + " Uploads the given statsd config file (in binary or human-readable-text format).\n" + " If a config with this id already exists, removes it first.\n" + " CONFIG_FILE Location of config file on host.\n" + @@ -56,12 +59,12 @@ public class LocalDrive { // Similar to: adb shell cmd stats config update SHELL_UID CONFIG_ID "\n" + - "statsd_local update CONFIG_FILE [CONFIG_ID] [--binary]\n" + + "statsd_localdrive update CONFIG_FILE [CONFIG_ID] [--binary]\n" + " Same as upload, but does not remove the old config first (if it already exists).\n" + // Similar to: adb shell cmd stats config update SHELL_UID CONFIG_ID "\n" + - "statsd_local get-data [CONFIG_ID] [--clear] [--binary] [--no-uid-map]\n" + + "statsd_localdrive get-data [CONFIG_ID] [--clear] [--binary] [--no-uid-map]\n" + " Prints the output statslog data (in binary or human-readable-text format).\n" + " CONFIG_ID Long ID of the config. If absent, uses " + DEFAULT_CONFIG_ID + ".\n" + " --binary Output should be in binary, instead of default human-readable text.\n" + @@ -72,13 +75,13 @@ public class LocalDrive { // --include_current_bucket --proto "\n" + - "statsd_local remove [CONFIG_ID]\n" + + "statsd_localdrive remove [CONFIG_ID]\n" + " Removes the config.\n" + " CONFIG_ID Long ID of the config. If absent, uses " + DEFAULT_CONFIG_ID + ".\n" + // Equivalent to: adb shell cmd stats config remove SHELL_UID CONFIG_ID "\n" + - "statsd_local clear [CONFIG_ID]\n" + + "statsd_localdrive clear [CONFIG_ID]\n" + " Clears the data associated with the config.\n" + " CONFIG_ID Long ID of the config. If absent, uses " + DEFAULT_CONFIG_ID + ".\n" + // Similar to: adb shell cmd stats dump-report SHELL_UID CONFIG_ID @@ -92,6 +95,12 @@ public class LocalDrive { public static void main(String[] args) { Utils.setUpLogger(sLogger, DEBUG); + if (!Utils.isAcceptableStatsd(sLogger, MIN_SDK, MIN_CODENAME)) { + sLogger.severe("LocalDrive only works with statsd versions for Android " + + MIN_CODENAME + " or higher."); + return; + } + if (args.length > 0) { switch (args[0]) { case "clear": diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 1b45d172cb89..497e193c38ec 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -6152,7 +6152,7 @@ public final class ActivityThread extends ClientTransactionHandler { try { synchronized (getGetProviderLock(auth, userId)) { holder = ActivityManager.getService().getContentProvider( - getApplicationThread(), auth, userId, stable); + getApplicationThread(), c.getOpPackageName(), auth, userId, stable); } } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index 6905cb5cea73..a2784237247c 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -41,8 +41,10 @@ import android.os.UserManager; import android.provider.Settings; import android.util.ArrayMap; +import com.android.internal.annotations.GuardedBy; import com.android.internal.app.IAppOpsActiveCallback; import com.android.internal.app.IAppOpsCallback; +import com.android.internal.app.IAppOpsNotedCallback; import com.android.internal.app.IAppOpsService; import com.android.internal.util.Preconditions; @@ -86,10 +88,20 @@ public class AppOpsManager { */ final Context mContext; + @UnsupportedAppUsage final IAppOpsService mService; - final ArrayMap<OnOpChangedListener, IAppOpsCallback> mModeWatchers = new ArrayMap<>(); - final ArrayMap<OnOpActiveChangedListener, IAppOpsActiveCallback> mActiveWatchers = + + @GuardedBy("mModeWatchers") + private final ArrayMap<OnOpChangedListener, IAppOpsCallback> mModeWatchers = + new ArrayMap<>(); + + @GuardedBy("mActiveWatchers") + private final ArrayMap<OnOpActiveChangedListener, IAppOpsActiveCallback> mActiveWatchers = + new ArrayMap<>(); + + @GuardedBy("mNotedWatchers") + private final ArrayMap<OnOpNotedListener, IAppOpsNotedCallback> mNotedWatchers = new ArrayMap<>(); static IBinder sToken; @@ -2471,6 +2483,23 @@ public class AppOpsManager { } /** + * Callback for notification of an op being noted. + * + * @hide + */ + public interface OnOpNotedListener { + /** + * Called when an op was noted. + * + * @param code The op code. + * @param uid The UID performing the operation. + * @param packageName The package performing the operation. + * @param result The result of the note. + */ + void onOpNoted(String code, int uid, String packageName, int result); + } + + /** * Callback for notification of changes to operation state. * This allows you to see the raw op codes instead of strings. * @hide @@ -2819,7 +2848,7 @@ public class AppOpsManager { */ public void stopWatchingMode(OnOpChangedListener callback) { synchronized (mModeWatchers) { - IAppOpsCallback cb = mModeWatchers.get(callback); + IAppOpsCallback cb = mModeWatchers.remove(callback); if (cb != null) { try { mService.stopWatchingMode(cb); @@ -2893,7 +2922,7 @@ public class AppOpsManager { @TestApi public void stopWatchingActive(@NonNull OnOpActiveChangedListener callback) { synchronized (mActiveWatchers) { - final IAppOpsActiveCallback cb = mActiveWatchers.get(callback); + final IAppOpsActiveCallback cb = mActiveWatchers.remove(callback); if (cb != null) { try { mService.stopWatchingActive(cb); @@ -2904,6 +2933,74 @@ public class AppOpsManager { } } + /** + * Start watching for noted app ops. An app op may be immediate or long running. + * Immediate ops are noted while long running ones are started and stopped. This + * method allows registering a listener to be notified when an app op is noted. If + * an op is being noted by any package you will get a callback. To change the + * watched ops for a registered callback you need to unregister and register it again. + * + * <p> If you don't hold the {@link android.Manifest.permission#WATCH_APPOPS} permission + * you can watch changes only for your UID. + * + * @param ops The ops to watch. + * @param callback Where to report changes. + * + * @see #startWatchingActive(int[], OnOpActiveChangedListener) + * @see #stopWatchingNoted(OnOpNotedListener) + * @see #noteOp(String, int, String) + * + * @hide + */ + @RequiresPermission(value=Manifest.permission.WATCH_APPOPS, conditional=true) + public void startWatchingNoted(@NonNull String[] ops, @NonNull OnOpNotedListener callback) { + IAppOpsNotedCallback cb; + synchronized (mNotedWatchers) { + cb = mNotedWatchers.get(callback); + if (cb != null) { + return; + } + cb = new IAppOpsNotedCallback.Stub() { + @Override + public void opNoted(int op, int uid, String packageName, int mode) { + callback.onOpNoted(sOpToString[op], uid, packageName, mode); + } + }; + mNotedWatchers.put(callback, cb); + } + try { + final int[] opCodes = new int[ops.length]; + for (int i = 0; i < opCodes.length; i++) { + opCodes[i] = strOpToOp(ops[i]); + } + mService.startWatchingNoted(opCodes, cb); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Stop watching for noted app ops. An app op may be immediate or long running. + * Unregistering a non-registered callback has no effect. + * + * @see #startWatchingNoted(String[], OnOpNotedListener) + * @see #noteOp(String, int, String) + * + * @hide + */ + public void stopWatchingNoted(@NonNull OnOpNotedListener callback) { + synchronized (mNotedWatchers) { + final IAppOpsNotedCallback cb = mNotedWatchers.get(callback); + if (cb != null) { + try { + mService.stopWatchingNoted(cb); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } + } + private String buildSecurityExceptionMsg(int op, int uid, String packageName) { return packageName + " from uid " + uid + " not allowed to perform " + sOpNames[op]; } diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl index 88fb025bf406..fb519b625012 100644 --- a/core/java/android/app/IActivityManager.aidl +++ b/core/java/android/app/IActivityManager.aidl @@ -124,7 +124,7 @@ interface IActivityManager { int ignoreWindowingMode); void moveTaskToFront(int task, int flags, in Bundle options); int getTaskForActivity(in IBinder token, in boolean onlyRoot); - ContentProviderHolder getContentProvider(in IApplicationThread caller, + ContentProviderHolder getContentProvider(in IApplicationThread caller, in String callingPackage, in String name, int userId, boolean stable); void publishContentProviders(in IApplicationThread caller, in List<ContentProviderHolder> providers); diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java index 306c366a86e4..606b00bddc2f 100644 --- a/core/java/android/app/NotificationManager.java +++ b/core/java/android/app/NotificationManager.java @@ -1838,12 +1838,17 @@ public class NotificationManager { * Recover a list of active notifications: ones that have been posted by the calling app that * have not yet been dismissed by the user or {@link #cancel(String, int)}ed by the app. * - * Each notification is embedded in a {@link StatusBarNotification} object, including the + * <p><Each notification is embedded in a {@link StatusBarNotification} object, including the * original <code>tag</code> and <code>id</code> supplied to * {@link #notify(String, int, Notification) notify()} * (via {@link StatusBarNotification#getTag() getTag()} and * {@link StatusBarNotification#getId() getId()}) as well as a copy of the original * {@link Notification} object (via {@link StatusBarNotification#getNotification()}). + * </p> + * <p>From {@link Build.VERSION_CODES#Q}, will also return notifications you've posted as an + * app's notification delegate via + * {@link NotificationManager#notifyAsPackage(String, String, int, Notification)}. + * </p> * * @return An array of {@link StatusBarNotification}. */ diff --git a/core/java/android/app/backup/BackupManager.java b/core/java/android/app/backup/BackupManager.java index c6086f10bd00..a6f6d06ae71a 100644 --- a/core/java/android/app/backup/BackupManager.java +++ b/core/java/android/app/backup/BackupManager.java @@ -335,7 +335,8 @@ public class BackupManager { if (sService != null) { try { // All packages, current transport - IRestoreSession binder = sService.beginRestoreSession(null, null); + IRestoreSession binder = + sService.beginRestoreSessionForUser(mContext.getUserId(), null, null); if (binder != null) { session = new RestoreSession(mContext, binder); } @@ -465,7 +466,7 @@ public class BackupManager { checkServiceBinder(); if (sService != null) { try { - return sService.getCurrentTransportComponent(); + return sService.getCurrentTransportComponentForUser(mContext.getUserId()); } catch (RemoteException e) { Log.e(TAG, "getCurrentTransportComponent() couldn't connect"); } @@ -530,7 +531,8 @@ public class BackupManager { checkServiceBinder(); if (sService != null) { try { - sService.updateTransportAttributes( + sService.updateTransportAttributesForUser( + mContext.getUserId(), transportComponent, name, configurationIntent, @@ -590,7 +592,8 @@ public class BackupManager { try { SelectTransportListenerWrapper wrapper = listener == null ? null : new SelectTransportListenerWrapper(mContext, listener); - sService.selectBackupTransportAsync(transport, wrapper); + sService.selectBackupTransportAsyncForUser( + mContext.getUserId(), transport, wrapper); } catch (RemoteException e) { Log.e(TAG, "selectBackupTransportAsync() couldn't connect"); } @@ -637,7 +640,7 @@ public class BackupManager { checkServiceBinder(); if (sService != null) { try { - return sService.getAvailableRestoreToken(packageName); + return sService.getAvailableRestoreTokenForUser(mContext.getUserId(), packageName); } catch (RemoteException e) { Log.e(TAG, "getAvailableRestoreToken() couldn't connect"); } @@ -659,7 +662,7 @@ public class BackupManager { checkServiceBinder(); if (sService != null) { try { - return sService.isAppEligibleForBackup(packageName); + return sService.isAppEligibleForBackupForUser(mContext.getUserId(), packageName); } catch (RemoteException e) { Log.e(TAG, "isAppEligibleForBackup(pkg) couldn't connect"); } @@ -760,7 +763,7 @@ public class BackupManager { public Intent getConfigurationIntent(String transportName) { if (sService != null) { try { - return sService.getConfigurationIntent(transportName); + return sService.getConfigurationIntentForUser(mContext.getUserId(), transportName); } catch (RemoteException e) { Log.e(TAG, "getConfigurationIntent() couldn't connect"); } @@ -781,7 +784,7 @@ public class BackupManager { public String getDestinationString(String transportName) { if (sService != null) { try { - return sService.getDestinationString(transportName); + return sService.getDestinationStringForUser(mContext.getUserId(), transportName); } catch (RemoteException e) { Log.e(TAG, "getDestinationString() couldn't connect"); } @@ -802,7 +805,7 @@ public class BackupManager { public Intent getDataManagementIntent(String transportName) { if (sService != null) { try { - return sService.getDataManagementIntent(transportName); + return sService.getDataManagementIntentForUser(mContext.getUserId(), transportName); } catch (RemoteException e) { Log.e(TAG, "getDataManagementIntent() couldn't connect"); } @@ -825,7 +828,7 @@ public class BackupManager { public String getDataManagementLabel(String transportName) { if (sService != null) { try { - return sService.getDataManagementLabel(transportName); + return sService.getDataManagementLabelForUser(mContext.getUserId(), transportName); } catch (RemoteException e) { Log.e(TAG, "getDataManagementLabel() couldn't connect"); } diff --git a/core/java/android/app/backup/IBackupManager.aidl b/core/java/android/app/backup/IBackupManager.aidl index f1e6b06e8bac..19de19a820b1 100644 --- a/core/java/android/app/backup/IBackupManager.aidl +++ b/core/java/android/app/backup/IBackupManager.aidl @@ -43,6 +43,15 @@ interface IBackupManager { * Any application can invoke this method for its own package, but * only callers who hold the android.permission.BACKUP permission * may invoke it for arbitrary packages. + * If {@code userId} is different from the calling user id, then the caller must hold the + * android.permission.INTERACT_ACROSS_USERS_FULL permission. + * + * @param userId User id for which the caller has made changes to its data. + */ + void dataChangedForUser(int userId, String packageName); + + /** + * {@link android.app.backup.IBackupManager.dataChangedForUser} for the calling user id. */ void dataChanged(String packageName); @@ -53,6 +62,15 @@ interface IBackupManager { * Any application can invoke this method for its own package, but * only callers who hold the android.permission.BACKUP permission * may invoke it for arbitrary packages. + * If {@code userId} is different from the calling user id, then the caller must hold the + * android.permission.INTERACT_ACROSS_USERS_FULL permission. + * + * @param userId User id for which backup data should be erased. + */ + void clearBackupDataForUser(int userId, String transportName, String packageName); + + /** + * {@link android.app.backup.IBackupManager.clearBackupDataForUser} for the calling user id. */ void clearBackupData(String transportName, String packageName); @@ -62,24 +80,59 @@ interface IBackupManager { * operations. * * <p>Callers must hold the android.permission.BACKUP permission to use this method. + * If {@code userId} is different from the calling user id, then the caller must hold the + * android.permission.INTERACT_ACROSS_USERS_FULL permission. + * + * @param userId User id for which the given transports should be initialized. */ - void initializeTransports(in String[] transportNames, IBackupObserver observer); + void initializeTransportsForUser(int userId, in String[] transportNames, + IBackupObserver observer); /** * Notifies the Backup Manager Service that an agent has become available. This * method is only invoked by the Activity Manager. + * + * If {@code userId} is different from the calling user id, then the caller must hold the + * android.permission.INTERACT_ACROSS_USERS_FULL permission. + * + * @param userId User id for which an agent has become available. + */ + void agentConnectedForUser(int userId, String packageName, IBinder agent); + + /** + * {@link android.app.backup.IBackupManager.agentConnected} for the calling user id. */ void agentConnected(String packageName, IBinder agent); /** * Notify the Backup Manager Service that an agent has unexpectedly gone away. * This method is only invoked by the Activity Manager. + * + * If {@code userId} is different from the calling user id, then the caller must hold the + * android.permission.INTERACT_ACROSS_USERS_FULL permission. + * + * @param userId User id for which an agent has unexpectedly gone away. + */ + void agentDisconnectedForUser(int userId, String packageName); + + /** + * {@link android.app.backup.IBackupManager.agentDisconnected} for the calling user id. */ void agentDisconnected(String packageName); /** * Notify the Backup Manager Service that an application being installed will * need a data-restore pass. This method is only invoked by the Package Manager. + * + * If {@code userId} is different from the calling user id, then the caller must hold the + * android.permission.INTERACT_ACROSS_USERS_FULL permission. + * + * @param userId User id for which the application will need a data-restore pass. + */ + void restoreAtInstallForUser(int userId, String packageName, int token); + + /** + * {@link android.app.backup.IBackupManager.restoreAtInstallForUser} for the calling user id. */ void restoreAtInstall(String packageName, int token); @@ -112,10 +165,18 @@ interface IBackupManager { * is made generally available for launch. * * <p>Callers must hold the android.permission.BACKUP permission to use this method. + * If {@code userId} is different from the calling user id, then the caller must hold the + * android.permission.INTERACT_ACROSS_USERS_FULL permission. * + * @param userId User id for which automatic restore should be enabled/disabled. * @param doAutoRestore When true, enables the automatic app-data restore facility. When * false, this facility will be disabled. */ + void setAutoRestoreForUser(int userId, boolean doAutoRestore); + + /** + * {@link android.app.backup.IBackupManager.setAutoRestoreForUser} for the calling user id. + */ void setAutoRestore(boolean doAutoRestore); /** @@ -220,9 +281,13 @@ interface IBackupManager { * Perform a full-dataset backup of the given applications via the currently active * transport. * + * If {@code userId} is different from the calling user id, then the caller must hold the + * android.permission.INTERACT_ACROSS_USERS_FULL permission. + * + * @param userId User id for which the full-dataset backup should be performed. * @param packageNames The package names of the apps whose data are to be backed up. */ - void fullTransportBackup(in String[] packageNames); + void fullTransportBackupForUser(int userId, in String[] packageNames); /** * Restore device content from the data stream passed through the given socket. The @@ -250,6 +315,18 @@ interface IBackupManager { * backup dataset being used for restore. * * <p>Callers must hold the android.permission.BACKUP permission to use this method. + * If {@code userId} is different from the calling user id, then the caller must hold the + * android.permission.INTERACT_ACROSS_USERS_FULL permission. + * + * @param userId User id for which the requested backup/restore operation can proceed. + */ + void acknowledgeFullBackupOrRestoreForUser(int userId, int token, boolean allow, + in String curPassword, in String encryptionPassword, + IFullBackupRestoreObserver observer); + + /** + * {@link android.app.backup.IBackupManager.acknowledgeFullBackupOrRestoreForUser} for the + * calling user id. */ void acknowledgeFullBackupOrRestore(int token, boolean allow, in String curPassword, in String encryptionPassword, @@ -260,7 +337,10 @@ interface IBackupManager { * specified transport has not been bound at least once (for registration), this call will be * ignored. Only the host process of the transport can change its description, otherwise a * {@link SecurityException} will be thrown. + * If {@code userId} is different from the calling user id, then the caller must hold the + * android.permission.INTERACT_ACROSS_USERS_FULL permission. * + * @param userId User id for which the attributes of the transport should be updated. * @param transportComponent The identity of the transport being described. * @param name A {@link String} with the new name for the transport. This is NOT for * identification. MUST NOT be {@code null}. @@ -279,13 +359,23 @@ interface IBackupManager { * @throws SecurityException If the UID of the calling process differs from the package UID of * {@code transportComponent} or if the caller does NOT have BACKUP permission. */ - void updateTransportAttributes(in ComponentName transportComponent, in String name, + void updateTransportAttributesForUser(int userId, in ComponentName transportComponent, + in String name, in Intent configurationIntent, in String currentDestinationString, in Intent dataManagementIntent, in String dataManagementLabel); /** * Identify the currently selected transport. Callers must hold the * android.permission.BACKUP permission to use this method. + * If {@code userId} is different from the calling user id, then the caller must hold the + * android.permission.INTERACT_ACROSS_USERS_FULL permission. + * + * @param userId User id for which the currently selected transport should be identified. + */ + String getCurrentTransportForUser(int userId); + + /** + * {@link android.app.backup.IBackupManager.getCurrentTransportForUser} for the calling user id. */ String getCurrentTransport(); @@ -293,16 +383,35 @@ interface IBackupManager { * Returns the {@link ComponentName} of the host service of the selected transport or {@code * null} if no transport selected or if the transport selected is not registered. Callers must * hold the android.permission.BACKUP permission to use this method. + * If {@code userId} is different from the calling user id, then the caller must hold the + * android.permission.INTERACT_ACROSS_USERS_FULL permission. + * + * @param userId User id for which the currently selected transport should be identified. */ - ComponentName getCurrentTransportComponent(); + ComponentName getCurrentTransportComponentForUser(int userId); /** * Request a list of all available backup transports' names. Callers must * hold the android.permission.BACKUP permission to use this method. + * If {@code userId} is different from the calling user id, then the caller must hold the + * android.permission.INTERACT_ACROSS_USERS_FULL permission. + * + * @param userId User id for which all available backup transports' names should be listed. + */ + String[] listAllTransportsForUser(int userId); + + /** + * {@link android.app.backup.IBackupManager.listAllTransportsForUser} for the calling user id. */ String[] listAllTransports(); - ComponentName[] listAllTransportComponents(); + /** + * If {@code userId} is different from the calling user id, then the caller must hold the + * android.permission.INTERACT_ACROSS_USERS_FULL permission. + * + * @param userId User id for which all available backup transports should be listed. + */ + ComponentName[] listAllTransportComponentsForUser(int userId); /** * Retrieve the list of whitelisted transport components. Callers do </i>not</i> need @@ -315,13 +424,22 @@ interface IBackupManager { /** * Specify the current backup transport. Callers must hold the * android.permission.BACKUP permission to use this method. + * If {@code userId} is different from the calling user id, then the caller must hold the + * android.permission.INTERACT_ACROSS_USERS_FULL permission. * + * @param userId User id for which the transport should be selected. * @param transport The name of the transport to select. This should be one * of {@link BackupManager.TRANSPORT_GOOGLE} or {@link BackupManager.TRANSPORT_ADB}. * @return The name of the previously selected transport. If the given transport * name is not one of the currently available transports, no change is made to * the current transport setting and the method returns null. */ + String selectBackupTransportForUser(int userId, String transport); + + /** + * {@link android.app.backup.IBackupManager.selectBackupTransportForUser} for the calling user + * id. + */ String selectBackupTransport(String transport); /** @@ -330,43 +448,85 @@ interface IBackupManager { * which is in a separate process. * * <p>Callers must hold the android.permission.BACKUP permission to use this method. + * If {@code userId} is different from the calling user id, then the caller must hold the + * android.permission.INTERACT_ACROSS_USERS_FULL permission. * + * @param userId User id for which the transport should be selected. * @param transport ComponentName of the service hosting the transport. This is different from * the transport's name that is returned by {@link BackupTransport#name()}. * @param listener A listener object to get a callback on the transport being selected. */ - void selectBackupTransportAsync(in ComponentName transport, ISelectBackupTransportCallback listener); + void selectBackupTransportAsyncForUser(int userId, in ComponentName transport, + ISelectBackupTransportCallback listener); /** * Get the configuration Intent, if any, from the given transport. Callers must * hold the android.permission.BACKUP permission in order to use this method. + * If {@code userId} is different from the calling user id, then the caller must hold the + * android.permission.INTERACT_ACROSS_USERS_FULL permission. * + * @param userId User id for which the configuration Intent should be reported. * @param transport The name of the transport to query. * @return An Intent to use with Activity#startActivity() to bring up the configuration * UI supplied by the transport. If the transport has no configuration UI, it should * return {@code null} here. */ + Intent getConfigurationIntentForUser(int userId, String transport); + + /** + * {@link android.app.backup.IBackupManager.getConfigurationIntentForUser} for the calling user + * id. + */ Intent getConfigurationIntent(String transport); /** * Get the destination string supplied by the given transport. Callers must * hold the android.permission.BACKUP permission in order to use this method. + * If {@code userId} is different from the calling user id, then the caller must hold the + * android.permission.INTERACT_ACROSS_USERS_FULL permission. * + * @param userId User id for which the transport destination string should be reported. * @param transport The name of the transport to query. * @return A string describing the current backup destination. This string is used * verbatim by the Settings UI as the summary text of the "configure..." item. */ + String getDestinationStringForUser(int userId, String transport); + + /** + * {@link android.app.backup.IBackupManager.getDestinationStringForUser} for the calling user + * id. + */ String getDestinationString(String transport); /** * Get the manage-data UI intent, if any, from the given transport. Callers must * hold the android.permission.BACKUP permission in order to use this method. + * If {@code userId} is different from the calling user id, then the caller must hold the + * android.permission.INTERACT_ACROSS_USERS_FULL permission. + * + * @param userId User id for which the manage-data UI intent should be reported. + */ + Intent getDataManagementIntentForUser(int userId, String transport); + + /** + * {@link android.app.backup.IBackupManager.getDataManagementIntentForUser} for the calling user + * id. */ Intent getDataManagementIntent(String transport); /** * Get the manage-data menu label, if any, from the given transport. Callers must * hold the android.permission.BACKUP permission in order to use this method. + * If {@code userId} is different from the calling user id, then the caller must hold the + * android.permission.INTERACT_ACROSS_USERS_FULL permission. + * + * @param userId User id for which the manage-data menu label should be reported. + */ + String getDataManagementLabelForUser(int userId, String transport); + + /** + * {@link android.app.backup.IBackupManager.getDataManagementLabelForUser} for the calling user + * id. */ String getDataManagementLabel(String transport); @@ -381,7 +541,10 @@ interface IBackupManager { * package. In that case, the restore session returned is suitable for supporting * the BackupManager.requestRestore() functionality via RestoreSession.restorePackage() * without requiring the app to hold any special permission. + * If {@code userId} is different from the calling user id, then the caller must hold the + * android.permission.INTERACT_ACROSS_USERS_FULL permission. * + * @param userId User id for which a restore session should be begun. * @param packageName The name of the single package for which a restore will * be requested. May be null, in which case all packages in the restore * set can be restored. @@ -389,7 +552,7 @@ interface IBackupManager { * May be null, in which case the current active transport is used. * @return An interface to the restore session, or null on error. */ - IRestoreSession beginRestoreSession(String packageName, String transportID); + IRestoreSession beginRestoreSessionForUser(int userId, String packageName, String transportID); /** * Notify the backup manager that a BackupAgent has completed the operation @@ -427,13 +590,16 @@ interface IBackupManager { * restored from if we were to install it right now. * * <p>Callers must hold the android.permission.BACKUP permission to use this method. + * If {@code userId} is different from the calling user id, then the caller must hold the + * android.permission.INTERACT_ACROSS_USERS_FULL permission. * + * @param userId User id for which this operation should be performed. * @param packageName The name of the package whose most-suitable dataset we * wish to look up * @return The dataset token from which a restore should be attempted, or zero if * no suitable data is available. */ - long getAvailableRestoreToken(String packageName); + long getAvailableRestoreTokenForUser(int userId, String packageName); /** * Ask the framework whether this app is eligible for backup. @@ -442,21 +608,27 @@ interface IBackupManager { * {@link #filterAppsEligibleForBackup(String[])} to save resources. * * <p>Callers must hold the android.permission.BACKUP permission to use this method. + * If {@code userId} is different from the calling user id, then the caller must hold the + * android.permission.INTERACT_ACROSS_USERS_FULL permission. * + * @param userId User id for which this operation should be performed. * @param packageName The name of the package. * @return Whether this app is eligible for backup. */ - boolean isAppEligibleForBackup(String packageName); + boolean isAppEligibleForBackupForUser(int userId, String packageName); /** * Filter the packages that are eligible for backup and return the result. * * <p>Callers must hold the android.permission.BACKUP permission to use this method. + * If {@code userId} is different from the calling user id, then the caller must hold the + * android.permission.INTERACT_ACROSS_USERS_FULL permission. * + * @param userId User id for which the filter should be performed. * @param packages The list of packages to filter. * @return The packages eligible for backup. */ - String[] filterAppsEligibleForBackup(in String[] packages); + String[] filterAppsEligibleForBackupForUser(int userId, in String[] packages); /** * Request an immediate backup, providing an observer to which results of the backup operation diff --git a/core/java/android/bluetooth/BluetoothManager.java b/core/java/android/bluetooth/BluetoothManager.java index e08d405324ea..adedff3e9386 100644 --- a/core/java/android/bluetooth/BluetoothManager.java +++ b/core/java/android/bluetooth/BluetoothManager.java @@ -67,7 +67,9 @@ public final class BluetoothManager { } // Legacy api - getDefaultAdapter does not take in the context mAdapter = BluetoothAdapter.getDefaultAdapter(); - mAdapter.setContext(context); + if (mAdapter != null) { + mAdapter.setContext(context); + } } /** diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 566017b7372e..9d604bbd75aa 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -2288,21 +2288,28 @@ public abstract class PackageManager { * {@link #hasSystemFeature}: The device has biometric hardware to detect a fingerprint. */ @SdkConstant(SdkConstantType.FEATURE) - public static final String FEATURE_FINGERPRINT = "android.hardware.fingerprint"; + public static final String FEATURE_FINGERPRINT_PRE_29 = "android.hardware.fingerprint"; + + /** + * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: The device has biometric hardware to detect a fingerprint. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_FINGERPRINT = "android.hardware.biometrics.fingerprint"; /** * Feature for {@link #getSystemAvailableFeatures} and * {@link #hasSystemFeature}: The device has biometric hardware to perform face authentication. */ @SdkConstant(SdkConstantType.FEATURE) - public static final String FEATURE_FACE = "android.hardware.face"; + public static final String FEATURE_FACE = "android.hardware.biometrics.face"; /** * Feature for {@link #getSystemAvailableFeatures} and * {@link #hasSystemFeature}: The device has biometric hardware to perform iris authentication. */ @SdkConstant(SdkConstantType.FEATURE) - public static final String FEATURE_IRIS = "android.hardware.iris"; + public static final String FEATURE_IRIS = "android.hardware.biometrics.iris"; /** * Feature for {@link #getSystemAvailableFeatures} and diff --git a/core/java/android/hardware/display/BrightnessConfiguration.java b/core/java/android/hardware/display/BrightnessConfiguration.java index 7e52ca331f9f..be054297c769 100644 --- a/core/java/android/hardware/display/BrightnessConfiguration.java +++ b/core/java/android/hardware/display/BrightnessConfiguration.java @@ -19,26 +19,54 @@ package android.hardware.display; import android.annotation.Nullable; import android.annotation.SystemApi; import android.annotation.TestApi; +import android.content.pm.ApplicationInfo; import android.os.Parcel; import android.os.Parcelable; import android.util.Pair; import com.android.internal.util.Preconditions; +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.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; import java.util.Objects; /** @hide */ @SystemApi @TestApi public final class BrightnessConfiguration implements Parcelable { + private static final String TAG_BRIGHTNESS_CURVE = "brightness-curve"; + private static final String TAG_BRIGHTNESS_POINT = "brightness-point"; + private static final String TAG_BRIGHTNESS_CORRECTIONS = "brightness-corrections"; + private static final String TAG_BRIGHTNESS_CORRECTION = "brightness-correction"; + private static final String ATTR_LUX = "lux"; + private static final String ATTR_NITS = "nits"; + private static final String ATTR_DESCRIPTION = "description"; + private static final String ATTR_PACKAGE_NAME = "package-name"; + private static final String ATTR_CATEGORY = "category"; + private final float[] mLux; private final float[] mNits; + private final Map<String, BrightnessCorrection> mCorrectionsByPackageName; + private final Map<Integer, BrightnessCorrection> mCorrectionsByCategory; private final String mDescription; - private BrightnessConfiguration(float[] lux, float[] nits, String description) { + private BrightnessConfiguration(float[] lux, float[] nits, + Map<String, BrightnessCorrection> correctionsByPackageName, + Map<Integer, BrightnessCorrection> correctionsByCategory, String description) { mLux = lux; mNits = nits; + mCorrectionsByPackageName = correctionsByPackageName; + mCorrectionsByCategory = correctionsByCategory; mDescription = description; } @@ -56,6 +84,38 @@ public final class BrightnessConfiguration implements Parcelable { } /** + * Returns a brightness correction by app, or null. + * + * @param packageName + * The app's package name. + * + * @return The matching brightness correction, or null. + * + * @hide + */ + @SystemApi + @Nullable + public BrightnessCorrection getCorrectionByPackageName(String packageName) { + return mCorrectionsByPackageName.get(packageName); + } + + /** + * Returns a brightness correction by app category, or null. + * + * @param category + * The app category. + * + * @return The matching brightness correction, or null. + * + * @hide + */ + @SystemApi + @Nullable + public BrightnessCorrection getCorrectionByCategory(int category) { + return mCorrectionsByCategory.get(category); + } + + /** * Returns description string. * @hide */ @@ -67,6 +127,20 @@ public final class BrightnessConfiguration implements Parcelable { public void writeToParcel(Parcel dest, int flags) { dest.writeFloatArray(mLux); dest.writeFloatArray(mNits); + dest.writeInt(mCorrectionsByPackageName.size()); + for (Entry<String, BrightnessCorrection> entry : mCorrectionsByPackageName.entrySet()) { + final String packageName = entry.getKey(); + final BrightnessCorrection correction = entry.getValue(); + dest.writeString(packageName); + correction.writeToParcel(dest, flags); + } + dest.writeInt(mCorrectionsByCategory.size()); + for (Entry<Integer, BrightnessCorrection> entry : mCorrectionsByCategory.entrySet()) { + final int category = entry.getKey(); + final BrightnessCorrection correction = entry.getValue(); + dest.writeInt(category); + correction.writeToParcel(dest, flags); + } dest.writeString(mDescription); } @@ -85,7 +159,14 @@ public final class BrightnessConfiguration implements Parcelable { } sb.append("(").append(mLux[i]).append(", ").append(mNits[i]).append(")"); } - sb.append("], '"); + sb.append("], {"); + for (Entry<String, BrightnessCorrection> entry : mCorrectionsByPackageName.entrySet()) { + sb.append("'" + entry.getKey() + "': " + entry.getValue() + ", "); + } + for (Entry<Integer, BrightnessCorrection> entry : mCorrectionsByCategory.entrySet()) { + sb.append(entry.getKey() + ": " + entry.getValue() + ", "); + } + sb.append("}, '"); if (mDescription != null) { sb.append(mDescription); } @@ -98,6 +179,8 @@ public final class BrightnessConfiguration implements Parcelable { int result = 1; result = result * 31 + Arrays.hashCode(mLux); result = result * 31 + Arrays.hashCode(mNits); + result = result * 31 + mCorrectionsByPackageName.hashCode(); + result = result * 31 + mCorrectionsByCategory.hashCode(); if (mDescription != null) { result = result * 31 + mDescription.hashCode(); } @@ -114,6 +197,8 @@ public final class BrightnessConfiguration implements Parcelable { } final BrightnessConfiguration other = (BrightnessConfiguration) o; return Arrays.equals(mLux, other.mLux) && Arrays.equals(mNits, other.mNits) + && mCorrectionsByPackageName.equals(other.mCorrectionsByPackageName) + && mCorrectionsByCategory.equals(other.mCorrectionsByCategory) && Objects.equals(mDescription, other.mDescription); } @@ -123,7 +208,25 @@ public final class BrightnessConfiguration implements Parcelable { float[] lux = in.createFloatArray(); float[] nits = in.createFloatArray(); Builder builder = new Builder(lux, nits); - builder.setDescription(in.readString()); + + int n = in.readInt(); + for (int i = 0; i < n; i++) { + final String packageName = in.readString(); + final BrightnessCorrection correction = + BrightnessCorrection.CREATOR.createFromParcel(in); + builder.addCorrectionByPackageName(packageName, correction); + } + + n = in.readInt(); + for (int i = 0; i < n; i++) { + final int category = in.readInt(); + final BrightnessCorrection correction = + BrightnessCorrection.CREATOR.createFromParcel(in); + builder.addCorrectionByCategory(category, correction); + } + + final String description = in.readString(); + builder.setDescription(description); return builder.build(); } @@ -133,11 +236,146 @@ public final class BrightnessConfiguration implements Parcelable { }; /** + * Writes the configuration to an XML serializer. + * + * @param serializer + * The XML serializer. + * + * @hide + */ + public void saveToXml(XmlSerializer serializer) throws IOException { + serializer.startTag(null, TAG_BRIGHTNESS_CURVE); + if (mDescription != null) { + serializer.attribute(null, ATTR_DESCRIPTION, mDescription); + } + for (int i = 0; i < mLux.length; i++) { + serializer.startTag(null, TAG_BRIGHTNESS_POINT); + serializer.attribute(null, ATTR_LUX, Float.toString(mLux[i])); + serializer.attribute(null, ATTR_NITS, Float.toString(mNits[i])); + serializer.endTag(null, TAG_BRIGHTNESS_POINT); + } + serializer.endTag(null, TAG_BRIGHTNESS_CURVE); + serializer.startTag(null, TAG_BRIGHTNESS_CORRECTIONS); + for (Map.Entry<String, BrightnessCorrection> entry : + mCorrectionsByPackageName.entrySet()) { + final String packageName = entry.getKey(); + final BrightnessCorrection correction = entry.getValue(); + serializer.startTag(null, TAG_BRIGHTNESS_CORRECTION); + serializer.attribute(null, ATTR_PACKAGE_NAME, packageName); + correction.saveToXml(serializer); + serializer.endTag(null, TAG_BRIGHTNESS_CORRECTION); + } + for (Map.Entry<Integer, BrightnessCorrection> entry : mCorrectionsByCategory.entrySet()) { + final int category = entry.getKey(); + final BrightnessCorrection correction = entry.getValue(); + serializer.startTag(null, TAG_BRIGHTNESS_CORRECTION); + serializer.attribute(null, ATTR_CATEGORY, Integer.toString(category)); + correction.saveToXml(serializer); + serializer.endTag(null, TAG_BRIGHTNESS_CORRECTION); + } + serializer.endTag(null, TAG_BRIGHTNESS_CORRECTIONS); + } + + /** + * Read a configuration from an XML parser. + * + * @param parser + * The XML parser. + * + * @throws IOException + * The parser failed to read the XML file. + * @throws XmlPullParserException + * The parser failed to parse the XML file. + * + * @hide + */ + public static BrightnessConfiguration loadFromXml(XmlPullParser parser) + throws IOException, XmlPullParserException { + String description = null; + List<Float> luxList = new ArrayList<>(); + List<Float> nitsList = new ArrayList<>(); + Map<String, BrightnessCorrection> correctionsByPackageName = new HashMap<>(); + Map<Integer, BrightnessCorrection> correctionsByCategory = new HashMap<>(); + final int configDepth = parser.getDepth(); + while (XmlUtils.nextElementWithin(parser, configDepth)) { + if (TAG_BRIGHTNESS_CURVE.equals(parser.getName())) { + description = parser.getAttributeValue(null, ATTR_DESCRIPTION); + final int curveDepth = parser.getDepth(); + while (XmlUtils.nextElementWithin(parser, curveDepth)) { + if (!TAG_BRIGHTNESS_POINT.equals(parser.getName())) { + continue; + } + final float lux = loadFloatFromXml(parser, ATTR_LUX); + final float nits = loadFloatFromXml(parser, ATTR_NITS); + luxList.add(lux); + nitsList.add(nits); + } + } + if (TAG_BRIGHTNESS_CORRECTIONS.equals(parser.getName())) { + final int correctionsDepth = parser.getDepth(); + while (XmlUtils.nextElementWithin(parser, correctionsDepth)) { + if (!TAG_BRIGHTNESS_CORRECTION.equals(parser.getName())) { + continue; + } + final String packageName = parser.getAttributeValue(null, ATTR_PACKAGE_NAME); + final String categoryText = parser.getAttributeValue(null, ATTR_CATEGORY); + BrightnessCorrection correction = BrightnessCorrection.loadFromXml(parser); + if (packageName != null) { + correctionsByPackageName.put(packageName, correction); + } else if (categoryText != null) { + try { + final int category = Integer.parseInt(categoryText); + correctionsByCategory.put(category, correction); + } catch (NullPointerException | NumberFormatException e) { + continue; + } + } + } + } + } + final int n = luxList.size(); + float[] lux = new float[n]; + float[] nits = new float[n]; + for (int i = 0; i < n; i++) { + lux[i] = luxList.get(i); + nits[i] = nitsList.get(i); + } + final BrightnessConfiguration.Builder builder = new BrightnessConfiguration.Builder(lux, + nits); + builder.setDescription(description); + for (Map.Entry<String, BrightnessCorrection> entry : correctionsByPackageName.entrySet()) { + final String packageName = entry.getKey(); + final BrightnessCorrection correction = entry.getValue(); + builder.addCorrectionByPackageName(packageName, correction); + } + for (Map.Entry<Integer, BrightnessCorrection> entry : correctionsByCategory.entrySet()) { + final int category = entry.getKey(); + final BrightnessCorrection correction = entry.getValue(); + builder.addCorrectionByCategory(category, correction); + } + return builder.build(); + } + + private static float loadFloatFromXml(XmlPullParser parser, String attribute) { + final String string = parser.getAttributeValue(null, attribute); + try { + return Float.parseFloat(string); + } catch (NullPointerException | NumberFormatException e) { + return Float.NaN; + } + } + + /** * A builder class for {@link BrightnessConfiguration}s. */ public static class Builder { + private static final int MAX_CORRECTIONS_BY_PACKAGE_NAME = 20; + private static final int MAX_CORRECTIONS_BY_CATEGORY = 20; + private float[] mCurveLux; private float[] mCurveNits; + private Map<String, BrightnessCorrection> mCorrectionsByPackageName; + private Map<Integer, BrightnessCorrection> mCorrectionsByCategory; private String mDescription; /** @@ -169,6 +407,88 @@ public final class BrightnessConfiguration implements Parcelable { checkMonotonic(nits, false /*strictly increasing*/, "nits"); mCurveLux = lux; mCurveNits = nits; + mCorrectionsByPackageName = new HashMap<>(); + mCorrectionsByCategory = new HashMap<>(); + } + + /** + * Returns the maximum number of corrections by package name allowed. + * + * @return The maximum number of corrections by package name allowed. + * + * @hide + */ + @SystemApi + public int getMaxCorrectionsByPackageName() { + return MAX_CORRECTIONS_BY_PACKAGE_NAME; + } + + /** + * Returns the maximum number of corrections by category allowed. + * + * @return The maximum number of corrections by category allowed. + * + * @hide + */ + @SystemApi + public int getMaxCorrectionsByCategory() { + return MAX_CORRECTIONS_BY_CATEGORY; + } + + /** + * Add a brightness correction by app package name. + * This correction is applied whenever an app with this package name has the top activity + * of the focused stack. + * + * @param packageName + * The app's package name. + * @param correction + * The brightness correction. + * + * @return The builder. + * + * @throws IllegalArgumentExceptions + * Maximum number of corrections by package name exceeded (see + * {@link #getMaxCorrectionsByPackageName}). + * + * @hide + */ + @SystemApi + public Builder addCorrectionByPackageName(String packageName, + BrightnessCorrection correction) { + if (mCorrectionsByPackageName.size() >= getMaxCorrectionsByPackageName()) { + throw new IllegalArgumentException("Too many corrections by package name"); + } + mCorrectionsByPackageName.put(packageName, correction); + return this; + } + + /** + * Add a brightness correction by app category. + * This correction is applied whenever an app with this category has the top activity of + * the focused stack, and only if a correction by package name has not been applied. + * + * @param category + * The {@link android.content.pm.ApplicationInfo#category app category}. + * @param correction + * The brightness correction. + * + * @return The builder. + * + * @throws IllegalArgumentException + * Maximum number of corrections by category exceeded (see + * {@link #getMaxCorrectionsByCategory}). + * + * @hide + */ + @SystemApi + public Builder addCorrectionByCategory(@ApplicationInfo.Category int category, + BrightnessCorrection correction) { + if (mCorrectionsByCategory.size() >= getMaxCorrectionsByCategory()) { + throw new IllegalArgumentException("Too many corrections by category"); + } + mCorrectionsByCategory.put(category, correction); + return this; } /** @@ -191,7 +511,8 @@ public final class BrightnessConfiguration implements Parcelable { if (mCurveLux == null || mCurveNits == null) { throw new IllegalStateException("A curve must be set!"); } - return new BrightnessConfiguration(mCurveLux, mCurveNits, mDescription); + return new BrightnessConfiguration(mCurveLux, mCurveNits, mCorrectionsByPackageName, + mCorrectionsByCategory, mDescription); } private static void checkMonotonic(float[] vals, boolean strictlyIncreasing, String name) { diff --git a/core/java/android/hardware/display/BrightnessCorrection.aidl b/core/java/android/hardware/display/BrightnessCorrection.aidl new file mode 100644 index 000000000000..3abe29cc0076 --- /dev/null +++ b/core/java/android/hardware/display/BrightnessCorrection.aidl @@ -0,0 +1,19 @@ +/* + * 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.display; + +parcelable BrightnessCorrection; diff --git a/core/java/android/hardware/display/BrightnessCorrection.java b/core/java/android/hardware/display/BrightnessCorrection.java new file mode 100644 index 000000000000..c4e0e3b723cd --- /dev/null +++ b/core/java/android/hardware/display/BrightnessCorrection.java @@ -0,0 +1,245 @@ +/* + * 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.display; + +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.MathUtils; + +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; + +/** + * BrightnessCorrection encapsulates a correction to the brightness, without comitting to the + * actual correction scheme. + * It is used by the BrightnessConfiguration, which maps context (e.g. the foreground app's package + * name and category) to corrections that need to be applied to the brightness within that context. + * Corrections are currently done by the app that has the top activity of the focused stack, either + * by its package name, or (if its package name is not mapped to any correction) by its category. + * + * @hide + */ +@SystemApi +public final class BrightnessCorrection implements Parcelable { + + private static final int SCALE_AND_TRANSLATE_LOG = 1; + + private static final String TAG_SCALE_AND_TRANSLATE_LOG = "scale-and-translate-log"; + + private BrightnessCorrectionImplementation mImplementation; + + // Parcelable classes must be final, and protected methods are not allowed in APIs, so we can't + // make this class abstract and use composition instead of inheritence. + private BrightnessCorrection(BrightnessCorrectionImplementation implementation) { + mImplementation = implementation; + } + + /** + * Creates a BrightnessCorrection that given {@code brightness}, corrects it to be + * {@code exp(scale * log(brightness) + translate)}. + * + * @param scale + * How much to scale the log brightness. + * @param translate + * How much to translate the log brightness. + * + * @return A BrightnessCorrection that given {@code brightness}, corrects it to be + * {@code exp(scale * log(brightness) + translate)}. + * + * @throws IllegalArgumentException + * - scale or translate are NaN. + */ + @NonNull + public static BrightnessCorrection createScaleAndTranslateLog(float scale, float translate) { + BrightnessCorrectionImplementation implementation = + new ScaleAndTranslateLog(scale, translate); + return new BrightnessCorrection(implementation); + } + + /** + * Applies the brightness correction to a given brightness. + * + * @param brightness + * The brightness. + * + * @return The corrected brightness. + */ + public float apply(float brightness) { + return mImplementation.apply(brightness); + } + + /** + * Returns a string representation. + * + * @return A string representation. + */ + public String toString() { + return mImplementation.toString(); + } + + public static final Creator<BrightnessCorrection> CREATOR = + new Creator<BrightnessCorrection>() { + public BrightnessCorrection createFromParcel(Parcel in) { + final int type = in.readInt(); + switch (type) { + case SCALE_AND_TRANSLATE_LOG: + return ScaleAndTranslateLog.readFromParcel(in); + } + return null; + } + + public BrightnessCorrection[] newArray(int size) { + return new BrightnessCorrection[size]; + } + }; + + @Override + public void writeToParcel(Parcel dest, int flags) { + mImplementation.writeToParcel(dest); + } + + @Override + public int describeContents() { + return 0; + } + + /** + * Writes the correction to an XML serializer. + * + * @param serializer + * The XML serializer. + * + * @hide + */ + public void saveToXml(XmlSerializer serializer) throws IOException { + mImplementation.saveToXml(serializer); + } + + /** + * Read a correction from an XML parser. + * + * @param parser + * The XML parser. + * + * @throws IOException + * The parser failed to read the XML file. + * @throws XmlPullParserException + * The parser failed to parse the XML file. + * + * @hide + */ + public static BrightnessCorrection loadFromXml(XmlPullParser parser) throws IOException, + XmlPullParserException { + final int depth = parser.getDepth(); + while (XmlUtils.nextElementWithin(parser, depth)) { + if (TAG_SCALE_AND_TRANSLATE_LOG.equals(parser.getName())) { + return ScaleAndTranslateLog.loadFromXml(parser); + } + } + return null; + } + + private static float loadFloatFromXml(XmlPullParser parser, String attribute) { + final String string = parser.getAttributeValue(null, attribute); + try { + return Float.parseFloat(string); + } catch (NullPointerException | NumberFormatException e) { + return Float.NaN; + } + } + + private interface BrightnessCorrectionImplementation { + float apply(float brightness); + String toString(); + void writeToParcel(Parcel dest); + void saveToXml(XmlSerializer serializer) throws IOException; + // Package-private static methods: + // static BrightnessCorrection readFromParcel(Parcel in); + // static BrightnessCorrection loadFromXml(XmlPullParser parser) throws IOException, + // XmlPullParserException; + } + + /** + * A BrightnessCorrection that given {@code brightness}, corrects it to be + * {@code exp(scale * log(brightness) + translate)}. + */ + private static class ScaleAndTranslateLog implements BrightnessCorrectionImplementation { + private static final float MIN_SCALE = 0.5f; + private static final float MAX_SCALE = 2.0f; + private static final float MIN_TRANSLATE = -0.6f; + private static final float MAX_TRANSLATE = 0.7f; + + private static final String ATTR_SCALE = "scale"; + private static final String ATTR_TRANSLATE = "translate"; + + private final float mScale; + private final float mTranslate; + + ScaleAndTranslateLog(float scale, float translate) { + if (Float.isNaN(scale) || Float.isNaN(translate)) { + throw new IllegalArgumentException("scale and translate must be numbers"); + } + mScale = MathUtils.constrain(scale, MIN_SCALE, MAX_SCALE); + mTranslate = MathUtils.constrain(translate, MIN_TRANSLATE, MAX_TRANSLATE); + } + + @Override + public float apply(float brightness) { + return MathUtils.exp(mScale * MathUtils.log(brightness) + mTranslate); + } + + @Override + public String toString() { + return "ScaleAndTranslateLog(" + mScale + ", " + mTranslate + ")"; + } + + @Override + public void writeToParcel(Parcel dest) { + dest.writeInt(SCALE_AND_TRANSLATE_LOG); + dest.writeFloat(mScale); + dest.writeFloat(mTranslate); + } + + @Override + public void saveToXml(XmlSerializer serializer) throws IOException { + serializer.startTag(null, TAG_SCALE_AND_TRANSLATE_LOG); + serializer.attribute(null, ATTR_SCALE, Float.toString(mScale)); + serializer.attribute(null, ATTR_TRANSLATE, Float.toString(mTranslate)); + serializer.endTag(null, TAG_SCALE_AND_TRANSLATE_LOG); + } + + static BrightnessCorrection readFromParcel(Parcel in) { + float scale = in.readFloat(); + float translate = in.readFloat(); + return BrightnessCorrection.createScaleAndTranslateLog(scale, translate); + } + + static BrightnessCorrection loadFromXml(XmlPullParser parser) throws IOException, + XmlPullParserException { + final float scale = loadFloatFromXml(parser, ATTR_SCALE); + final float translate = loadFloatFromXml(parser, ATTR_TRANSLATE); + return BrightnessCorrection.createScaleAndTranslateLog(scale, translate); + } + } +} diff --git a/core/java/android/hardware/usb/IUsbManager.aidl b/core/java/android/hardware/usb/IUsbManager.aidl index f4e776cdb4b9..edc3f9466efc 100644 --- a/core/java/android/hardware/usb/IUsbManager.aidl +++ b/core/java/android/hardware/usb/IUsbManager.aidl @@ -20,7 +20,7 @@ import android.app.PendingIntent; import android.content.ComponentName; import android.hardware.usb.UsbAccessory; import android.hardware.usb.UsbDevice; -import android.hardware.usb.UsbPort; +import android.hardware.usb.ParcelableUsbPort; import android.hardware.usb.UsbPortStatus; import android.os.Bundle; import android.os.ParcelFileDescriptor; @@ -112,7 +112,7 @@ interface IUsbManager ParcelFileDescriptor getControlFd(long function); /* Gets the list of USB ports. */ - UsbPort[] getPorts(); + List<ParcelableUsbPort> getPorts(); /* Gets the status of the specified USB port. */ UsbPortStatus getPortStatus(in String portId); diff --git a/core/java/android/hardware/usb/UsbPort.aidl b/core/java/android/hardware/usb/ParcelableUsbPort.aidl index b7a79202914e..4431551fec92 100644 --- a/core/java/android/hardware/usb/UsbPort.aidl +++ b/core/java/android/hardware/usb/ParcelableUsbPort.aidl @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015, The Android Open Source Project + * 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. @@ -16,4 +16,4 @@ package android.hardware.usb; -parcelable UsbPort; +parcelable ParcelableUsbPort; diff --git a/core/java/android/hardware/usb/ParcelableUsbPort.java b/core/java/android/hardware/usb/ParcelableUsbPort.java new file mode 100644 index 000000000000..7f7ba96b40f2 --- /dev/null +++ b/core/java/android/hardware/usb/ParcelableUsbPort.java @@ -0,0 +1,87 @@ +/* + * 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.usb; + +import android.annotation.NonNull; +import android.os.Parcel; +import android.os.Parcelable; + +import com.android.internal.annotations.Immutable; + +/** + * A parcelable wrapper to send UsbPorts over binders. + * + * @hide + */ +@Immutable +public final class ParcelableUsbPort implements Parcelable { + private final @NonNull String mId; + private final int mSupportedModes; + + private ParcelableUsbPort(@NonNull String id, int supportedModes) { + mId = id; + mSupportedModes = supportedModes; + } + + /** + * Create the parcelable version of a {@link UsbPort}. + * + * @param port The port to create a parcealable version of + * + * @return The parcelable version of the port + */ + public static @NonNull ParcelableUsbPort of(@NonNull UsbPort port) { + return new ParcelableUsbPort(port.getId(), port.getSupportedModes()); + } + + /** + * Create a {@link UsbPort} from this object. + * + * @param usbManager A link to the usbManager in the current context + * + * @return The UsbPort for this object + */ + public @NonNull UsbPort getUsbPort(@NonNull UsbManager usbManager) { + return new UsbPort(usbManager, mId, mSupportedModes); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(mId); + dest.writeInt(mSupportedModes); + } + + public static final Creator<ParcelableUsbPort> CREATOR = + new Creator<ParcelableUsbPort>() { + @Override + public ParcelableUsbPort createFromParcel(Parcel in) { + String id = in.readString(); + int supportedModes = in.readInt(); + return new ParcelableUsbPort(id, supportedModes); + } + + @Override + public ParcelableUsbPort[] newArray(int size) { + return new ParcelableUsbPort[size]; + } + }; +} diff --git a/core/java/android/hardware/usb/UsbManager.java b/core/java/android/hardware/usb/UsbManager.java index 41119416e419..601447814952 100644 --- a/core/java/android/hardware/usb/UsbManager.java +++ b/core/java/android/hardware/usb/UsbManager.java @@ -18,6 +18,7 @@ package android.hardware.usb; import android.Manifest; +import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresFeature; import android.annotation.RequiresPermission; @@ -39,9 +40,10 @@ import android.os.Process; import android.os.RemoteException; import android.util.Log; -import com.android.internal.util.Preconditions; - +import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.StringJoiner; @@ -97,15 +99,11 @@ public class UsbManager { * Broadcast Action: A broadcast for USB port changes. * * This intent is sent when a USB port is added, removed, or changes state. - * <ul> - * <li> {@link #EXTRA_PORT} containing the {@link android.hardware.usb.UsbPort} - * for the port. - * <li> {@link #EXTRA_PORT_STATUS} containing the {@link android.hardware.usb.UsbPortStatus} - * for the port, or null if the port has been removed - * </ul> * * @hide */ + @SystemApi + @RequiresPermission(Manifest.permission.MANAGE_USB) public static final String ACTION_USB_PORT_CHANGED = "android.hardware.usb.action.USB_PORT_CHANGED"; @@ -796,34 +794,44 @@ public class UsbManager { * device class (which supports all types of ports despite its name). * </p> * - * @return The list of USB ports, or null if none. + * @return The list of USB ports * * @hide */ - @UnsupportedAppUsage - public UsbPort[] getPorts() { + @SystemApi + @RequiresPermission(Manifest.permission.MANAGE_USB) + public @NonNull List<UsbPort> getPorts() { if (mService == null) { - return null; + return Collections.emptyList(); } + + List<ParcelableUsbPort> parcelablePorts; try { - return mService.getPorts(); + parcelablePorts = mService.getPorts(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } + + if (parcelablePorts == null) { + return Collections.emptyList(); + } else { + int numPorts = parcelablePorts.size(); + + ArrayList<UsbPort> ports = new ArrayList<>(numPorts); + for (int i = 0; i < numPorts; i++) { + ports.add(parcelablePorts.get(i).getUsbPort(this)); + } + + return ports; + } } /** - * Gets the status of the specified USB port. - * - * @param port The port to query. - * @return The status of the specified USB port, or null if unknown. + * Should only be called by {@link UsbPort#getStatus}. * * @hide */ - @UnsupportedAppUsage - public UsbPortStatus getPortStatus(UsbPort port) { - Preconditions.checkNotNull(port, "port must not be null"); - + UsbPortStatus getPortStatus(UsbPort port) { try { return mService.getPortStatus(port.getId()); } catch (RemoteException e) { @@ -832,29 +840,11 @@ public class UsbManager { } /** - * Sets the desired role combination of the port. - * <p> - * The supported role combinations depend on what is connected to the port and may be - * determined by consulting - * {@link UsbPortStatus#isRoleCombinationSupported UsbPortStatus.isRoleCombinationSupported}. - * </p><p> - * Note: This function is asynchronous and may fail silently without applying - * the requested changes. If this function does cause a status change to occur then - * a {@link #ACTION_USB_PORT_CHANGED} broadcast will be sent. - * </p> - * - * @param powerRole The desired power role: {@link UsbPort#POWER_ROLE_SOURCE} - * or {@link UsbPort#POWER_ROLE_SINK}, or 0 if no power role. - * @param dataRole The desired data role: {@link UsbPort#DATA_ROLE_HOST} - * or {@link UsbPort#DATA_ROLE_DEVICE}, or 0 if no data role. + * Should only be called by {@link UsbPort#setRoles}. * * @hide */ - @UnsupportedAppUsage - public void setPortRoles(UsbPort port, int powerRole, int dataRole) { - Preconditions.checkNotNull(port, "port must not be null"); - UsbPort.checkRoles(powerRole, dataRole); - + void setPortRoles(UsbPort port, int powerRole, int dataRole) { Log.d(TAG, "setPortRoles Package:" + mContext.getPackageName()); try { mService.setPortRoles(port.getId(), powerRole, dataRole); diff --git a/core/java/android/hardware/usb/UsbPort.java b/core/java/android/hardware/usb/UsbPort.java index afdb202211dd..37154e4c81b2 100644 --- a/core/java/android/hardware/usb/UsbPort.java +++ b/core/java/android/hardware/usb/UsbPort.java @@ -16,104 +16,53 @@ package android.hardware.usb; +import static android.hardware.usb.UsbPortStatus.DATA_ROLE_DEVICE; +import static android.hardware.usb.UsbPortStatus.DATA_ROLE_HOST; +import static android.hardware.usb.UsbPortStatus.DATA_ROLE_NONE; +import static android.hardware.usb.UsbPortStatus.MODE_AUDIO_ACCESSORY; +import static android.hardware.usb.UsbPortStatus.MODE_DEBUG_ACCESSORY; +import static android.hardware.usb.UsbPortStatus.MODE_DFP; +import static android.hardware.usb.UsbPortStatus.MODE_DUAL; +import static android.hardware.usb.UsbPortStatus.MODE_NONE; +import static android.hardware.usb.UsbPortStatus.MODE_UFP; +import static android.hardware.usb.UsbPortStatus.POWER_ROLE_NONE; +import static android.hardware.usb.UsbPortStatus.POWER_ROLE_SINK; +import static android.hardware.usb.UsbPortStatus.POWER_ROLE_SOURCE; + +import android.Manifest; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.RequiresPermission; +import android.annotation.SystemApi; import android.hardware.usb.V1_0.Constants; -import android.os.Parcel; -import android.os.Parcelable; import com.android.internal.util.Preconditions; /** * Represents a physical USB port and describes its characteristics. - * <p> - * This object is immutable. - * </p> * * @hide */ -public final class UsbPort implements Parcelable { +@SystemApi +public final class UsbPort { private final String mId; private final int mSupportedModes; - - public static final int MODE_NONE = Constants.PortMode.NONE; - /** - * Mode bit: This USB port can act as a downstream facing port (host). - * <p> - * Implies that the port supports the {@link #POWER_ROLE_SOURCE} and {@link #DATA_ROLE_HOST} - * combination of roles (and possibly others as well). - * </p> - */ - public static final int MODE_DFP = Constants.PortMode.DFP; - - /** - * Mode bit: This USB port can act as an upstream facing port (device). - * <p> - * Implies that the port supports the {@link #POWER_ROLE_SINK} and {@link #DATA_ROLE_DEVICE} - * combination of roles (and possibly others as well). - * </p> - */ - public static final int MODE_UFP = Constants.PortMode.UFP; - - /** - * Mode bit: This USB port can act either as an downstream facing port (host) or as - * an upstream facing port (device). - * <p> - * Implies that the port supports the {@link #POWER_ROLE_SOURCE} and {@link #DATA_ROLE_HOST} - * combination of roles and the {@link #POWER_ROLE_SINK} and {@link #DATA_ROLE_DEVICE} - * combination of roles (and possibly others as well). - * </p> - */ - public static final int MODE_DUAL = Constants.PortMode.DRP; - - /** - * Mode bit: This USB port can support USB Type-C Audio accessory. - */ - public static final int MODE_AUDIO_ACCESSORY = - android.hardware.usb.V1_1.Constants.PortMode_1_1.AUDIO_ACCESSORY; - - /** - * Mode bit: This USB port can support USB Type-C debug accessory. - */ - public static final int MODE_DEBUG_ACCESSORY = - android.hardware.usb.V1_1.Constants.PortMode_1_1.DEBUG_ACCESSORY; - - /** - * Power role: This USB port does not have a power role. - */ - public static final int POWER_ROLE_NONE = Constants.PortPowerRole.NONE; - - /** - * Power role: This USB port can act as a source (provide power). - */ - public static final int POWER_ROLE_SOURCE = Constants.PortPowerRole.SOURCE; - - /** - * Power role: This USB port can act as a sink (receive power). - */ - public static final int POWER_ROLE_SINK = Constants.PortPowerRole.SINK; - - /** - * Power role: This USB port does not have a data role. - */ - public static final int DATA_ROLE_NONE = Constants.PortDataRole.NONE; - - /** - * Data role: This USB port can act as a host (access data services). - */ - public static final int DATA_ROLE_HOST = Constants.PortDataRole.HOST; - - /** - * Data role: This USB port can act as a device (offer data services). - */ - public static final int DATA_ROLE_DEVICE = Constants.PortDataRole.DEVICE; + private final UsbManager mUsbManager; private static final int NUM_DATA_ROLES = Constants.PortDataRole.NUM_DATA_ROLES; + /** * Points to the first power role in the IUsb HAL. */ private static final int POWER_ROLE_OFFSET = Constants.PortPowerRole.NONE; /** @hide */ - public UsbPort(String id, int supportedModes) { + public UsbPort(@NonNull UsbManager usbManager, @NonNull String id, int supportedModes) { + Preconditions.checkNotNull(id); + Preconditions.checkFlagsArgument(supportedModes, + MODE_DFP | MODE_UFP | MODE_AUDIO_ACCESSORY | MODE_DEBUG_ACCESSORY); + + mUsbManager = usbManager; mId = id; mSupportedModes = supportedModes; } @@ -122,6 +71,8 @@ public final class UsbPort implements Parcelable { * Gets the unique id of the port. * * @return The unique id of the port; not intended for display. + * + * @hide */ public String getId() { return mId; @@ -133,23 +84,62 @@ public final class UsbPort implements Parcelable { * The actual mode of the port may vary depending on what is plugged into it. * </p> * - * @return The supported modes: one of {@link #MODE_DFP}, {@link #MODE_UFP}, or - * {@link #MODE_DUAL}. + * @return The supported modes: one of {@link UsbPortStatus#MODE_DFP}, + * {@link UsbPortStatus#MODE_UFP}, or {@link UsbPortStatus#MODE_DUAL}. + * + * @hide */ public int getSupportedModes() { return mSupportedModes; } /** + * Gets the status of this USB port. + * + * @return The status of the this port, or {@code null} if port is unknown. + */ + @RequiresPermission(Manifest.permission.MANAGE_USB) + public @Nullable UsbPortStatus getStatus() { + return mUsbManager.getPortStatus(this); + } + + /** + * Sets the desired role combination of the port. + * <p> + * The supported role combinations depend on what is connected to the port and may be + * determined by consulting + * {@link UsbPortStatus#isRoleCombinationSupported UsbPortStatus.isRoleCombinationSupported}. + * </p><p> + * Note: This function is asynchronous and may fail silently without applying + * the requested changes. If this function does cause a status change to occur then + * a {@link UsbManager#ACTION_USB_PORT_CHANGED} broadcast will be sent. + * </p> + * + * @param powerRole The desired power role: {@link UsbPortStatus#POWER_ROLE_SOURCE} or + * {@link UsbPortStatus#POWER_ROLE_SINK}, or + * {@link UsbPortStatus#POWER_ROLE_NONE} if no power role. + * @param dataRole The desired data role: {@link UsbPortStatus#DATA_ROLE_HOST} or + * {@link UsbPortStatus#DATA_ROLE_DEVICE}, or + * {@link UsbPortStatus#DATA_ROLE_NONE} if no data role. + */ + @RequiresPermission(Manifest.permission.MANAGE_USB) + public void setRoles(@UsbPortStatus.UsbPowerRole int powerRole, + @UsbPortStatus.UsbDataRole int dataRole) { + UsbPort.checkRoles(powerRole, dataRole); + + mUsbManager.setPortRoles(this, powerRole, dataRole); + } + + /** * Combines one power and one data role together into a unique value with * exactly one bit set. This can be used to efficiently determine whether * a combination of roles is supported by testing whether that bit is present * in a bit-field. * - * @param powerRole The desired power role: {@link UsbPort#POWER_ROLE_SOURCE} - * or {@link UsbPort#POWER_ROLE_SINK}, or 0 if no power role. - * @param dataRole The desired data role: {@link UsbPort#DATA_ROLE_HOST} - * or {@link UsbPort#DATA_ROLE_DEVICE}, or 0 if no data role. + * @param powerRole The desired power role: {@link UsbPortStatus#POWER_ROLE_SOURCE} + * or {@link UsbPortStatus#POWER_ROLE_SINK}, or 0 if no power role. + * @param dataRole The desired data role: {@link UsbPortStatus#DATA_ROLE_HOST} + * or {@link UsbPortStatus#DATA_ROLE_DEVICE}, or 0 if no data role. * @hide */ public static int combineRolesAsBit(int powerRole, int dataRole) { @@ -276,30 +266,4 @@ public final class UsbPort implements Parcelable { public String toString() { return "UsbPort{id=" + mId + ", supportedModes=" + modeToString(mSupportedModes) + "}"; } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeString(mId); - dest.writeInt(mSupportedModes); - } - - public static final Parcelable.Creator<UsbPort> CREATOR = - new Parcelable.Creator<UsbPort>() { - @Override - public UsbPort createFromParcel(Parcel in) { - String id = in.readString(); - int supportedModes = in.readInt(); - return new UsbPort(id, supportedModes); - } - - @Override - public UsbPort[] newArray(int size) { - return new UsbPort[size]; - } - }; } diff --git a/core/java/android/hardware/usb/UsbPortStatus.java b/core/java/android/hardware/usb/UsbPortStatus.java index 2cd8209fccda..d30201a597bf 100644 --- a/core/java/android/hardware/usb/UsbPortStatus.java +++ b/core/java/android/hardware/usb/UsbPortStatus.java @@ -16,27 +16,134 @@ package android.hardware.usb; -import android.annotation.UnsupportedAppUsage; +import android.annotation.IntDef; +import android.annotation.SystemApi; +import android.hardware.usb.V1_0.Constants; import android.os.Parcel; import android.os.Parcelable; +import com.android.internal.annotations.Immutable; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** * Describes the status of a USB port. - * <p> - * This object is immutable. - * </p> * * @hide */ +@Immutable +@SystemApi public final class UsbPortStatus implements Parcelable { private final int mCurrentMode; - private final int mCurrentPowerRole; - private final int mCurrentDataRole; + private final @UsbPowerRole int mCurrentPowerRole; + private final @UsbDataRole int mCurrentDataRole; private final int mSupportedRoleCombinations; + /** + * Power role: This USB port does not have a power role. + */ + public static final int POWER_ROLE_NONE = Constants.PortPowerRole.NONE; + + /** + * Power role: This USB port can act as a source (provide power). + */ + public static final int POWER_ROLE_SOURCE = Constants.PortPowerRole.SOURCE; + + /** + * Power role: This USB port can act as a sink (receive power). + */ + public static final int POWER_ROLE_SINK = Constants.PortPowerRole.SINK; + + @IntDef(prefix = { "POWER_ROLE_" }, value = { + POWER_ROLE_NONE, + POWER_ROLE_SOURCE, + POWER_ROLE_SINK + }) + @Retention(RetentionPolicy.SOURCE) + @interface UsbPowerRole{} + + /** + * Power role: This USB port does not have a data role. + */ + public static final int DATA_ROLE_NONE = Constants.PortDataRole.NONE; + + /** + * Data role: This USB port can act as a host (access data services). + */ + public static final int DATA_ROLE_HOST = Constants.PortDataRole.HOST; + + /** + * Data role: This USB port can act as a device (offer data services). + */ + public static final int DATA_ROLE_DEVICE = Constants.PortDataRole.DEVICE; + + @IntDef(prefix = { "DATA_ROLE_" }, value = { + DATA_ROLE_NONE, + DATA_ROLE_HOST, + DATA_ROLE_DEVICE + }) + @Retention(RetentionPolicy.SOURCE) + @interface UsbDataRole{} + + /** + * There is currently nothing connected to this USB port. + */ + public static final int MODE_NONE = Constants.PortMode.NONE; + + /** + * This USB port can act as a downstream facing port (host). + * + * <p> Implies that the port supports the {@link #POWER_ROLE_SOURCE} and + * {@link #DATA_ROLE_HOST} combination of roles (and possibly others as well). + */ + public static final int MODE_DFP = Constants.PortMode.DFP; + + /** + * This USB port can act as an upstream facing port (device). + * + * <p> Implies that the port supports the {@link #POWER_ROLE_SINK} and + * {@link #DATA_ROLE_DEVICE} combination of roles (and possibly others as well). + */ + public static final int MODE_UFP = Constants.PortMode.UFP; + + /** + * This USB port can act either as an downstream facing port (host) or as + * an upstream facing port (device). + * + * <p> Implies that the port supports the {@link #POWER_ROLE_SOURCE} and + * {@link #DATA_ROLE_HOST} combination of roles and the {@link #POWER_ROLE_SINK} and + * {@link #DATA_ROLE_DEVICE} combination of roles (and possibly others as well). + * + * @hide + */ + public static final int MODE_DUAL = Constants.PortMode.DRP; + + /** + * This USB port can support USB Type-C Audio accessory. + */ + public static final int MODE_AUDIO_ACCESSORY = + android.hardware.usb.V1_1.Constants.PortMode_1_1.AUDIO_ACCESSORY; + + /** + * This USB port can support USB Type-C debug accessory. + */ + public static final int MODE_DEBUG_ACCESSORY = + android.hardware.usb.V1_1.Constants.PortMode_1_1.DEBUG_ACCESSORY; + + @IntDef(prefix = { "MODE_" }, flag = true, value = { + MODE_NONE, + MODE_DFP, + MODE_UFP, + MODE_AUDIO_ACCESSORY, + MODE_DEBUG_ACCESSORY, + }) + @Retention(RetentionPolicy.SOURCE) + @interface UsbPortMode{} + /** @hide */ - public UsbPortStatus(int currentMode, int currentPowerRole, int currentDataRole, - int supportedRoleCombinations) { + public UsbPortStatus(int currentMode, @UsbPowerRole int currentPowerRole, + @UsbDataRole int currentDataRole, int supportedRoleCombinations) { mCurrentMode = currentMode; mCurrentPowerRole = currentPowerRole; mCurrentDataRole = currentDataRole; @@ -46,9 +153,8 @@ public final class UsbPortStatus implements Parcelable { /** * Returns true if there is anything connected to the port. * - * @return True if there is anything connected to the port. + * @return {@code true} iff there is anything connected to the port. */ - @UnsupportedAppUsage public boolean isConnected() { return mCurrentMode != 0; } @@ -56,33 +162,31 @@ public final class UsbPortStatus implements Parcelable { /** * Gets the current mode of the port. * - * @return The current mode: {@link UsbPort#MODE_DFP}, {@link UsbPort#MODE_UFP}, - * or 0 if nothing is connected. + * @return The current mode: {@link #MODE_DFP}, {@link #MODE_UFP}, + * {@link #MODE_AUDIO_ACCESSORY}, {@link #MODE_DEBUG_ACCESSORY}, or {@link {@link #MODE_NONE} if + * nothing is connected. */ - @UnsupportedAppUsage - public int getCurrentMode() { + public @UsbPortMode int getCurrentMode() { return mCurrentMode; } /** * Gets the current power role of the port. * - * @return The current power role: {@link UsbPort#POWER_ROLE_SOURCE}, - * {@link UsbPort#POWER_ROLE_SINK}, or 0 if nothing is connected. + * @return The current power role: {@link #POWER_ROLE_SOURCE}, {@link #POWER_ROLE_SINK}, or + * {@link #POWER_ROLE_NONE} if nothing is connected. */ - @UnsupportedAppUsage - public int getCurrentPowerRole() { + public @UsbPowerRole int getCurrentPowerRole() { return mCurrentPowerRole; } /** * Gets the current data role of the port. * - * @return The current data role: {@link UsbPort#DATA_ROLE_HOST}, - * {@link UsbPort#DATA_ROLE_DEVICE}, or 0 if nothing is connected. + * @return The current data role: {@link #DATA_ROLE_HOST}, {@link #DATA_ROLE_DEVICE}, or + * {@link #DATA_ROLE_NONE} if nothing is connected. */ - @UnsupportedAppUsage - public int getCurrentDataRole() { + public @UsbDataRole int getCurrentDataRole() { return mCurrentDataRole; } @@ -90,19 +194,20 @@ public final class UsbPortStatus implements Parcelable { * Returns true if the specified power and data role combination is supported * given what is currently connected to the port. * - * @param powerRole The power role to check: {@link UsbPort#POWER_ROLE_SOURCE} - * or {@link UsbPort#POWER_ROLE_SINK}, or 0 if no power role. - * @param dataRole The data role to check: either {@link UsbPort#DATA_ROLE_HOST} - * or {@link UsbPort#DATA_ROLE_DEVICE}, or 0 if no data role. + * @param powerRole The power role to check: {@link #POWER_ROLE_SOURCE} or + * {@link #POWER_ROLE_SINK}, or {@link #POWER_ROLE_NONE} if no power role. + * @param dataRole The data role to check: either {@link #DATA_ROLE_HOST} or + * {@link #DATA_ROLE_DEVICE}, or {@link #DATA_ROLE_NONE} if no data role. */ - @UnsupportedAppUsage - public boolean isRoleCombinationSupported(int powerRole, int dataRole) { + public boolean isRoleCombinationSupported(@UsbPowerRole int powerRole, + @UsbDataRole int dataRole) { return (mSupportedRoleCombinations & UsbPort.combineRolesAsBit(powerRole, dataRole)) != 0; } - /** @hide */ - @UnsupportedAppUsage + /** + * Get the supported role combinations. + */ public int getSupportedRoleCombinations() { return mSupportedRoleCombinations; } diff --git a/core/java/android/net/INetdEventCallback.aidl b/core/java/android/net/INetdEventCallback.aidl index 4b1a08ded9d6..0877a1a47e2b 100644 --- a/core/java/android/net/INetdEventCallback.aidl +++ b/core/java/android/net/INetdEventCallback.aidl @@ -45,6 +45,20 @@ oneway interface INetdEventCallback { in String[] ipAddresses, int ipAddressesCount, long timestamp, int uid); /** + * Represents adding or removing a NAT64 prefix. + * This method must not block or perform long-running operations. + * + * @param netId the ID of the network the prefix was performed on. + * @param added true if the NAT64 prefix was added, or false if the NAT64 prefix was removed. + * There is only one prefix at a time for each netId. If a prefix is added, it replaces + * the previous-added prefix. + * @param prefixString the detected NAT64 prefix as a string literal. + * @param prefixLength the prefix length associated with this NAT64 prefix. + */ + void onNat64PrefixEvent(int netId, boolean added, @utf8InCpp String prefixString, + int prefixLength); + + /** * Represents a private DNS validation success or failure. * This method must not block or perform long-running operations. * diff --git a/core/java/android/net/InetAddresses.java b/core/java/android/net/InetAddresses.java new file mode 100644 index 000000000000..8e6c69a97edb --- /dev/null +++ b/core/java/android/net/InetAddresses.java @@ -0,0 +1,63 @@ +/* + * 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.net; + +import libcore.net.InetAddressUtils; + +import java.net.InetAddress; + +/** + * Utility methods for {@link InetAddress} implementations. + */ +public class InetAddresses { + + private InetAddresses() {} + + /** + * Checks to see if the {@code address} is a numeric address (such as {@code "192.0.2.1"} or + * {@code "2001:db8::1:2"}). + * + * <p>A numeric address is either an IPv4 address containing exactly 4 decimal numbers or an + * IPv6 numeric address. IPv4 addresses that consist of either hexadecimal or octal digits or + * do not have exactly 4 numbers are not treated as numeric. + * + * <p>This method will never do a DNS lookup. + * + * @param address the address to parse. + * @return true if the supplied address is numeric, false otherwise. + */ + public static boolean isNumericAddress(String address) { + return InetAddressUtils.isNumericAddress(address); + } + + /** + * Returns an InetAddress corresponding to the given numeric address (such + * as {@code "192.168.0.1"} or {@code "2001:4860:800d::68"}). + * + * <p>See {@link #isNumericAddress(String)} (String)} for a definition as to what constitutes a + * numeric address. + * + * <p>This method will never do a DNS lookup. + * + * @param address the address to parse, must be numeric. + * @return an {@link InetAddress} instance corresponding to the address. + * @throws IllegalArgumentException if {@code address} is not a numeric address. + */ + public static InetAddress parseNumericAddress(String address) { + return InetAddressUtils.parseNumericAddress(address); + } +} diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java index 8a5f43de6883..c41a56c7305f 100644 --- a/core/java/android/net/NetworkCapabilities.java +++ b/core/java/android/net/NetworkCapabilities.java @@ -1467,7 +1467,7 @@ public final class NetworkCapabilities implements Parcelable { appendStringRepresentationOfBitMaskToStringBuilder(sb, mNetworkCapabilities, NetworkCapabilities::capabilityNameOf, "&"); } - if (0 != mNetworkCapabilities) { + if (0 != mUnwantedNetworkCapabilities) { sb.append(" Unwanted: "); appendStringRepresentationOfBitMaskToStringBuilder(sb, mUnwantedNetworkCapabilities, NetworkCapabilities::capabilityNameOf, "&"); diff --git a/core/java/android/net/NetworkUtils.java b/core/java/android/net/NetworkUtils.java index c431e40ede2f..4eab49cd0fdf 100644 --- a/core/java/android/net/NetworkUtils.java +++ b/core/java/android/net/NetworkUtils.java @@ -21,6 +21,7 @@ import static android.system.OsConstants.AF_INET6; import android.annotation.NonNull; import android.annotation.UnsupportedAppUsage; +import android.os.Build; import android.os.Parcel; import android.system.Os; import android.util.Log; @@ -299,8 +300,10 @@ public class NetworkUtils { * @param addrString * @return the InetAddress * @hide + * @deprecated Use {@link InetAddresses#parseNumericAddress(String)}, if possible. */ - @UnsupportedAppUsage + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) + @Deprecated public static InetAddress numericToInetAddress(String addrString) throws IllegalArgumentException { return InetAddress.parseNumericAddress(addrString); diff --git a/core/java/android/os/FileUtils.java b/core/java/android/os/FileUtils.java index 428d9e156e37..e84a518c4986 100644 --- a/core/java/android/os/FileUtils.java +++ b/core/java/android/os/FileUtils.java @@ -23,6 +23,7 @@ import static android.os.ParcelFileDescriptor.MODE_READ_WRITE; import static android.os.ParcelFileDescriptor.MODE_TRUNCATE; import static android.os.ParcelFileDescriptor.MODE_WRITE_ONLY; import static android.system.OsConstants.F_OK; +import static android.system.OsConstants.O_ACCMODE; import static android.system.OsConstants.O_APPEND; import static android.system.OsConstants.O_CREAT; import static android.system.OsConstants.O_RDONLY; @@ -1259,11 +1260,11 @@ public class FileUtils { int res = 0; if (mode.startsWith("rw")) { - res |= O_RDWR | O_CREAT; + res = O_RDWR | O_CREAT; } else if (mode.startsWith("w")) { - res |= O_WRONLY | O_CREAT; + res = O_WRONLY | O_CREAT; } else if (mode.startsWith("r")) { - res |= O_RDONLY; + res = O_RDONLY; } else { throw new IllegalArgumentException("Bad mode: " + mode); } @@ -1279,12 +1280,12 @@ public class FileUtils { /** {@hide} */ public static String translateModePosixToString(int mode) { String res = ""; - if ((mode & O_RDWR) == O_RDWR) { - res += "rw"; - } else if ((mode & O_WRONLY) == O_WRONLY) { - res += "w"; - } else if ((mode & O_RDONLY) == O_RDONLY) { - res += "r"; + if ((mode & O_ACCMODE) == O_RDWR) { + res = "rw"; + } else if ((mode & O_ACCMODE) == O_WRONLY) { + res = "w"; + } else if ((mode & O_ACCMODE) == O_RDONLY) { + res = "r"; } else { throw new IllegalArgumentException("Bad mode: " + mode); } @@ -1300,12 +1301,12 @@ public class FileUtils { /** {@hide} */ public static int translateModePosixToPfd(int mode) { int res = 0; - if ((mode & O_RDWR) == O_RDWR) { - res |= MODE_READ_WRITE; - } else if ((mode & O_WRONLY) == O_WRONLY) { - res |= MODE_WRITE_ONLY; - } else if ((mode & O_RDONLY) == O_RDONLY) { - res |= MODE_READ_ONLY; + if ((mode & O_ACCMODE) == O_RDWR) { + res = MODE_READ_WRITE; + } else if ((mode & O_ACCMODE) == O_WRONLY) { + res = MODE_WRITE_ONLY; + } else if ((mode & O_ACCMODE) == O_RDONLY) { + res = MODE_READ_ONLY; } else { throw new IllegalArgumentException("Bad mode: " + mode); } @@ -1325,11 +1326,11 @@ public class FileUtils { public static int translateModePfdToPosix(int mode) { int res = 0; if ((mode & MODE_READ_WRITE) == MODE_READ_WRITE) { - res |= O_RDWR; + res = O_RDWR; } else if ((mode & MODE_WRITE_ONLY) == MODE_WRITE_ONLY) { - res |= O_WRONLY; + res = O_WRONLY; } else if ((mode & MODE_READ_ONLY) == MODE_READ_ONLY) { - res |= O_RDONLY; + res = O_RDONLY; } else { throw new IllegalArgumentException("Bad mode: " + mode); } @@ -1428,4 +1429,3 @@ public class FileUtils { } } } - diff --git a/core/java/android/os/INetworkManagementService.aidl b/core/java/android/os/INetworkManagementService.aidl index be8cf0e9137a..fdd74882eb39 100644 --- a/core/java/android/os/INetworkManagementService.aidl +++ b/core/java/android/os/INetworkManagementService.aidl @@ -390,7 +390,7 @@ interface INetworkManagementService /** * Setup a new VPN. */ - void createVirtualNetwork(int netId, boolean hasDNS, boolean secure); + void createVirtualNetwork(int netId, boolean secure); /** * Remove a network. diff --git a/core/java/android/os/NativeHandle.java b/core/java/android/os/NativeHandle.java index fbecc8ec1cd9..f7ffc37f085f 100644 --- a/core/java/android/os/NativeHandle.java +++ b/core/java/android/os/NativeHandle.java @@ -16,6 +16,8 @@ package android.os; +import static android.system.OsConstants.F_DUPFD_CLOEXEC; + import android.annotation.NonNull; import android.annotation.SystemApi; import android.system.ErrnoException; @@ -108,7 +110,10 @@ public final class NativeHandle implements Closeable { FileDescriptor[] fds = new FileDescriptor[mFds.length]; try { for (int i = 0; i < mFds.length; i++) { - fds[i] = Os.dup(mFds[i]); + FileDescriptor newFd = new FileDescriptor(); + int fdint = Os.fcntlInt(mFds[i], F_DUPFD_CLOEXEC, 0); + newFd.setInt$(fdint); + fds[i] = newFd; } } catch (ErrnoException e) { e.rethrowAsIOException(); diff --git a/core/java/android/os/ParcelFileDescriptor.java b/core/java/android/os/ParcelFileDescriptor.java index 6de1ff4bc097..63912ec327a4 100644 --- a/core/java/android/os/ParcelFileDescriptor.java +++ b/core/java/android/os/ParcelFileDescriptor.java @@ -17,7 +17,11 @@ package android.os; import static android.system.OsConstants.AF_UNIX; +import static android.system.OsConstants.F_DUPFD; +import static android.system.OsConstants.F_DUPFD_CLOEXEC; +import static android.system.OsConstants.O_CLOEXEC; import static android.system.OsConstants.SEEK_SET; +import static android.system.OsConstants.SOCK_CLOEXEC; import static android.system.OsConstants.SOCK_SEQPACKET; import static android.system.OsConstants.SOCK_STREAM; import static android.system.OsConstants.S_IROTH; @@ -37,6 +41,7 @@ import android.system.StructStat; import android.util.Log; import dalvik.system.CloseGuard; +import dalvik.system.VMRuntime; import libcore.io.IoUtils; import libcore.io.Memory; @@ -293,7 +298,7 @@ public class ParcelFileDescriptor implements Parcelable, Closeable { } private static FileDescriptor openInternal(File file, int mode) throws FileNotFoundException { - final int flags = FileUtils.translateModePfdToPosix(mode); + final int flags = FileUtils.translateModePfdToPosix(mode) | ifAtLeastQ(O_CLOEXEC); int realMode = S_IRWXU | S_IRWXG; if ((mode & MODE_WORLD_READABLE) != 0) realMode |= S_IROTH; @@ -315,7 +320,9 @@ public class ParcelFileDescriptor implements Parcelable, Closeable { */ public static ParcelFileDescriptor dup(FileDescriptor orig) throws IOException { try { - final FileDescriptor fd = Os.dup(orig); + final FileDescriptor fd = new FileDescriptor(); + int intfd = Os.fcntlInt(orig, (isAtLeastQ() ? F_DUPFD_CLOEXEC : F_DUPFD), 0); + fd.setInt$(intfd); return new ParcelFileDescriptor(fd); } catch (ErrnoException e) { throw e.rethrowAsIOException(); @@ -351,7 +358,9 @@ public class ParcelFileDescriptor implements Parcelable, Closeable { original.setInt$(fd); try { - final FileDescriptor dup = Os.dup(original); + final FileDescriptor dup = new FileDescriptor(); + int intfd = Os.fcntlInt(original, (isAtLeastQ() ? F_DUPFD_CLOEXEC : F_DUPFD), 0); + dup.setInt$(intfd); return new ParcelFileDescriptor(dup); } catch (ErrnoException e) { throw e.rethrowAsIOException(); @@ -413,7 +422,7 @@ public class ParcelFileDescriptor implements Parcelable, Closeable { */ public static ParcelFileDescriptor[] createPipe() throws IOException { try { - final FileDescriptor[] fds = Os.pipe(); + final FileDescriptor[] fds = Os.pipe2(ifAtLeastQ(O_CLOEXEC)); return new ParcelFileDescriptor[] { new ParcelFileDescriptor(fds[0]), new ParcelFileDescriptor(fds[1]) }; @@ -435,7 +444,7 @@ public class ParcelFileDescriptor implements Parcelable, Closeable { public static ParcelFileDescriptor[] createReliablePipe() throws IOException { try { final FileDescriptor[] comm = createCommSocketPair(); - final FileDescriptor[] fds = Os.pipe(); + final FileDescriptor[] fds = Os.pipe2(ifAtLeastQ(O_CLOEXEC)); return new ParcelFileDescriptor[] { new ParcelFileDescriptor(fds[0], comm[0]), new ParcelFileDescriptor(fds[1], comm[1]) }; @@ -459,7 +468,7 @@ public class ParcelFileDescriptor implements Parcelable, Closeable { try { final FileDescriptor fd0 = new FileDescriptor(); final FileDescriptor fd1 = new FileDescriptor(); - Os.socketpair(AF_UNIX, type, 0, fd0, fd1); + Os.socketpair(AF_UNIX, type | ifAtLeastQ(SOCK_CLOEXEC), 0, fd0, fd1); return new ParcelFileDescriptor[] { new ParcelFileDescriptor(fd0), new ParcelFileDescriptor(fd1) }; @@ -489,7 +498,7 @@ public class ParcelFileDescriptor implements Parcelable, Closeable { final FileDescriptor[] comm = createCommSocketPair(); final FileDescriptor fd0 = new FileDescriptor(); final FileDescriptor fd1 = new FileDescriptor(); - Os.socketpair(AF_UNIX, type, 0, fd0, fd1); + Os.socketpair(AF_UNIX, type | ifAtLeastQ(SOCK_CLOEXEC), 0, fd0, fd1); return new ParcelFileDescriptor[] { new ParcelFileDescriptor(fd0, comm[0]), new ParcelFileDescriptor(fd1, comm[1]) }; @@ -505,7 +514,7 @@ public class ParcelFileDescriptor implements Parcelable, Closeable { // across multiple IO operations. final FileDescriptor comm1 = new FileDescriptor(); final FileDescriptor comm2 = new FileDescriptor(); - Os.socketpair(AF_UNIX, SOCK_SEQPACKET, 0, comm1, comm2); + Os.socketpair(AF_UNIX, SOCK_SEQPACKET | ifAtLeastQ(SOCK_CLOEXEC), 0, comm1, comm2); IoUtils.setBlocking(comm1, false); IoUtils.setBlocking(comm2, false); return new FileDescriptor[] { comm1, comm2 }; @@ -1111,4 +1120,12 @@ public class ParcelFileDescriptor implements Parcelable, Closeable { return "{" + status + ": " + msg + "}"; } } + + private static boolean isAtLeastQ() { + return (VMRuntime.getRuntime().getTargetSdkVersion() >= Build.VERSION_CODES.Q); + } + + private static int ifAtLeastQ(int value) { + return isAtLeastQ() ? value : 0; + } } diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java index 8b1d3c6c839f..9594a713a506 100644 --- a/core/java/android/os/storage/StorageManager.java +++ b/core/java/android/os/storage/StorageManager.java @@ -132,6 +132,8 @@ public class StorageManager { public static final String PROP_VIRTUAL_DISK = "persist.sys.virtual_disk"; /** {@hide} */ public static final String PROP_ISOLATED_STORAGE = "persist.sys.isolated_storage"; + /** {@hide} */ + public static final String PROP_ISOLATED_STORAGE_SNAPSHOT = "sys.isolated_storage_snapshot"; /** {@hide} */ public static final String PROP_FORCE_AUDIO = "persist.fw.force_audio"; @@ -1540,7 +1542,9 @@ public class StorageManager { /** {@hide} */ @TestApi public static boolean hasIsolatedStorage() { - return SystemProperties.getBoolean(PROP_ISOLATED_STORAGE, false); + // Prefer to use snapshot for current boot when available + return SystemProperties.getBoolean(PROP_ISOLATED_STORAGE_SNAPSHOT, + SystemProperties.getBoolean(PROP_ISOLATED_STORAGE, false)); } /** diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java index f38f74046934..457453fd210b 100644 --- a/core/java/android/provider/MediaStore.java +++ b/core/java/android/provider/MediaStore.java @@ -1002,6 +1002,7 @@ public final class MediaStore { public static final int MICRO_KIND = 3; public static final Point MINI_SIZE = new Point(512, 384); + public static final Point FULL_SCREEN_SIZE = new Point(1024, 786); public static final Point MICRO_SIZE = new Point(96, 96); } @@ -1127,6 +1128,8 @@ public final class MediaStore { final Point size; if (kind == ThumbnailConstants.MICRO_KIND) { size = ThumbnailConstants.MICRO_SIZE; + } else if (kind == ThumbnailConstants.FULL_SCREEN_KIND) { + size = ThumbnailConstants.FULL_SCREEN_SIZE; } else if (kind == ThumbnailConstants.MINI_KIND) { size = ThumbnailConstants.MINI_SIZE; } else { diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 299db730b3c6..40f194693e8b 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -14260,6 +14260,44 @@ public final class Settings { } } + /** + * <p> + * A Settings panel is floating UI that contains a fixed subset of settings to address a + * particular user problem. For example, the + * {@link #ACTION_INTERNET_CONNECTIVITY Internet Panel} surfaces settings related to + * connecting to the internet. + * <p> + * Settings panels appear above the calling app to address the problem without + * the user needing to open Settings and thus leave their current screen. + */ + public static final class Panel { + private Panel() { + } + + /** + * Activity Action: Show a settings dialog containing settings to enable internet + * connection. + * <p> + * Input: Nothing. + * <p> + * Output: Nothing. + */ + @SdkConstant(SdkConstant.SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_INTERNET_CONNECTIVITY = + "android.settings.panel.action.INTERNET_CONNECTIVITY"; + + /** + * Activity Action: Show a settings dialog containing all volume streams. + * <p> + * Input: Nothing. + * <p> + * Output: Nothing. + */ + @SdkConstant(SdkConstant.SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_VOLUME = + "android.settings.panel.action.VOLUME"; + } + private static final String[] PM_WRITE_SETTINGS = { android.Manifest.permission.WRITE_SETTINGS }; diff --git a/core/java/android/service/contentcapture/ContentCaptureEventsRequest.java b/core/java/android/service/contentcapture/ContentCaptureEventsRequest.java index df58f52dc59c..028180dd3bf0 100644 --- a/core/java/android/service/contentcapture/ContentCaptureEventsRequest.java +++ b/core/java/android/service/contentcapture/ContentCaptureEventsRequest.java @@ -17,6 +17,7 @@ package android.service.contentcapture; import android.annotation.NonNull; import android.annotation.SystemApi; +import android.content.pm.ParceledListSlice; import android.os.Parcel; import android.os.Parcelable; import android.view.contentcapture.ContentCaptureEvent; @@ -31,10 +32,10 @@ import java.util.List; @SystemApi public final class ContentCaptureEventsRequest implements Parcelable { - private final List<ContentCaptureEvent> mEvents; + private final ParceledListSlice<ContentCaptureEvent> mEvents; /** @hide */ - public ContentCaptureEventsRequest(@NonNull List<ContentCaptureEvent> events) { + public ContentCaptureEventsRequest(@NonNull ParceledListSlice<ContentCaptureEvent> events) { mEvents = events; } @@ -43,7 +44,7 @@ public final class ContentCaptureEventsRequest implements Parcelable { */ @NonNull public List<ContentCaptureEvent> getEvents() { - return mEvents; + return mEvents.getList(); } @Override @@ -53,7 +54,7 @@ public final class ContentCaptureEventsRequest implements Parcelable { @Override public void writeToParcel(Parcel parcel, int flags) { - parcel.writeTypedList(mEvents, flags); + parcel.writeParcelable(mEvents, flags); } public static final Parcelable.Creator<ContentCaptureEventsRequest> CREATOR = @@ -61,8 +62,7 @@ public final class ContentCaptureEventsRequest implements Parcelable { @Override public ContentCaptureEventsRequest createFromParcel(Parcel parcel) { - return new ContentCaptureEventsRequest(parcel - .createTypedArrayList(ContentCaptureEvent.CREATOR)); + return new ContentCaptureEventsRequest(parcel.readParcelable(null)); } @Override diff --git a/core/java/android/service/contentcapture/ContentCaptureService.java b/core/java/android/service/contentcapture/ContentCaptureService.java index 58848fce43d6..953ccf1e4575 100644 --- a/core/java/android/service/contentcapture/ContentCaptureService.java +++ b/core/java/android/service/contentcapture/ContentCaptureService.java @@ -24,15 +24,27 @@ import android.annotation.SystemApi; import android.app.Service; import android.content.ComponentName; import android.content.Intent; +import android.content.pm.ParceledListSlice; +import android.os.Binder; +import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.RemoteException; +import android.util.ArrayMap; import android.util.Log; +import android.util.Slog; import android.view.contentcapture.ContentCaptureContext; import android.view.contentcapture.ContentCaptureEvent; +import android.view.contentcapture.ContentCaptureManager; +import android.view.contentcapture.ContentCaptureSession; import android.view.contentcapture.ContentCaptureSessionId; +import android.view.contentcapture.IContentCaptureDirectManager; +import com.android.internal.os.IResultReceiver; + +import java.io.FileDescriptor; +import java.io.PrintWriter; import java.util.List; import java.util.Set; @@ -63,39 +75,54 @@ public abstract class ContentCaptureService extends Service { private Handler mHandler; - private final IContentCaptureService mInterface = new IContentCaptureService.Stub() { + /** + * Binder that receives calls from the system server. + */ + private final IContentCaptureService mServerInterface = new IContentCaptureService.Stub() { @Override - public void onSessionLifecycle(ContentCaptureContext context, String sessionId) - throws RemoteException { - if (context != null) { - mHandler.sendMessage( - obtainMessage(ContentCaptureService::handleOnCreateSession, - ContentCaptureService.this, context, sessionId)); - } else { - mHandler.sendMessage( - obtainMessage(ContentCaptureService::handleOnDestroySession, - ContentCaptureService.this, sessionId)); - } + public void onSessionStarted(ContentCaptureContext context, String sessionId, int uid, + IResultReceiver clientReceiver) { + mHandler.sendMessage(obtainMessage(ContentCaptureService::handleOnCreateSession, + ContentCaptureService.this, context, sessionId, uid, clientReceiver)); } @Override - public void onContentCaptureEventsRequest(String sessionId, - ContentCaptureEventsRequest request) { + public void onActivitySnapshot(String sessionId, SnapshotData snapshotData) { mHandler.sendMessage( - obtainMessage(ContentCaptureService::handleOnContentCaptureEventsRequest, - ContentCaptureService.this, sessionId, request)); + obtainMessage(ContentCaptureService::handleOnActivitySnapshot, + ContentCaptureService.this, sessionId, snapshotData)); + } + @Override + public void onSessionFinished(String sessionId) { + mHandler.sendMessage(obtainMessage(ContentCaptureService::handleFinishSession, + ContentCaptureService.this, sessionId)); } + }; + + /** + * Binder that receives calls from the app. + */ + private final IContentCaptureDirectManager mClientInterface = + new IContentCaptureDirectManager.Stub() { @Override - public void onActivitySnapshot(String sessionId, SnapshotData snapshotData) { - mHandler.sendMessage( - obtainMessage(ContentCaptureService::handleOnActivitySnapshot, - ContentCaptureService.this, sessionId, snapshotData)); + public void sendEvents(String sessionId, + @SuppressWarnings("rawtypes") ParceledListSlice events) { + mHandler.sendMessage(obtainMessage(ContentCaptureService::handleSendEvents, + ContentCaptureService.this, sessionId, Binder.getCallingUid(), events)); } }; + /** + * List of sessions per UID. + * + * <p>This map is populated when an session is started, which is called by the system server + * and can be trusted. Then subsequent calls made by the app are verified against this map. + */ + private final ArrayMap<String, Integer> mSessionsByUid = new ArrayMap<>(); + @CallSuper @Override public void onCreate() { @@ -107,7 +134,7 @@ public abstract class ContentCaptureService extends Service { @Override public final IBinder onBind(Intent intent) { if (SERVICE_INTERFACE.equals(intent.getAction())) { - return mInterface.asBinder(); + return mServerInterface.asBinder(); } Log.w(TAG, "Tried to bind to wrong intent (should be " + SERVICE_INTERFACE + ": " + intent); return null; @@ -196,8 +223,10 @@ public abstract class ContentCaptureService extends Service { * @param sessionId the session's Id * @param request the events */ - public abstract void onContentCaptureEventsRequest(@NonNull ContentCaptureSessionId sessionId, - @NonNull ContentCaptureEventsRequest request); + public void onContentCaptureEventsRequest(@NonNull ContentCaptureSessionId sessionId, + @NonNull ContentCaptureEventsRequest request) { + if (VERBOSE) Log.v(TAG, "onContentCaptureEventsRequest(id=" + sessionId + ")"); + } /** * Notifies the service of {@link SnapshotData snapshot data} associated with a session. @@ -212,10 +241,22 @@ public abstract class ContentCaptureService extends Service { * Destroys the content capture session. * * @param sessionId the id of the session to destroy - */ + * */ public void onDestroyContentCaptureSession(@NonNull ContentCaptureSessionId sessionId) { - if (VERBOSE) { - Log.v(TAG, "onDestroyContentCaptureSession(id=" + sessionId + ")"); + if (VERBOSE) Log.v(TAG, "onDestroyContentCaptureSession(id=" + sessionId + ")"); + } + + @Override + @CallSuper + protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + final int size = mSessionsByUid.size(); + pw.print("Number sessions: "); pw.println(size); + if (size > 0) { + final String prefix = " "; + for (int i = 0; i < size; i++) { + pw.print(prefix); pw.print(mSessionsByUid.keyAt(i)); + pw.print(": uid="); pw.println(mSessionsByUid.valueAt(i)); + } } } @@ -223,13 +264,19 @@ public abstract class ContentCaptureService extends Service { // so we don't need to create a temporary InteractionSessionId for each event. private void handleOnCreateSession(@NonNull ContentCaptureContext context, - @NonNull String sessionId) { + @NonNull String sessionId, int uid, IResultReceiver clientReceiver) { + mSessionsByUid.put(sessionId, uid); onCreateContentCaptureSession(context, new ContentCaptureSessionId(sessionId)); + setClientState(clientReceiver, ContentCaptureSession.STATE_ACTIVE, + mClientInterface.asBinder()); } - private void handleOnContentCaptureEventsRequest(@NonNull String sessionId, - @NonNull ContentCaptureEventsRequest request) { - onContentCaptureEventsRequest(new ContentCaptureSessionId(sessionId), request); + private void handleSendEvents(@NonNull String sessionId, int uid, + @NonNull ParceledListSlice<ContentCaptureEvent> events) { + if (handleIsRightCallerFor(sessionId, uid)) { + onContentCaptureEventsRequest(new ContentCaptureSessionId(sessionId), + new ContentCaptureEventsRequest(events)); + } } private void handleOnActivitySnapshot(@NonNull String sessionId, @@ -237,7 +284,52 @@ public abstract class ContentCaptureService extends Service { onActivitySnapshot(new ContentCaptureSessionId(sessionId), snapshotData); } - private void handleOnDestroySession(@NonNull String sessionId) { + private void handleFinishSession(@NonNull String sessionId) { + mSessionsByUid.remove(sessionId); onDestroyContentCaptureSession(new ContentCaptureSessionId(sessionId)); } + + /** + * Checks if the given {@code uid} owns the session. + */ + private boolean handleIsRightCallerFor(@NonNull String sessionId, int uid) { + final Integer rightUid = mSessionsByUid.get(sessionId); + if (rightUid == null) { + if (VERBOSE) Log.v(TAG, "No session for " + sessionId); + // Just ignore, as the session could have finished + return false; + } + if (rightUid != uid) { + Log.e(TAG, "invalid call from UID " + uid + ": session " + sessionId + " belongs to " + + rightUid); + //TODO(b/111276913): log metrics as this could be a malicious app forging a sessionId + return false; + } + return true; + + } + + /** + * Sends the state of the {@link ContentCaptureManager} in the cleint app. + * + * @param clientReceiver receiver in the client app. + * @param binder handle to the {@code IContentCaptureDirectManager} object that resides in the + * service. + * @hide + */ + public static void setClientState(@NonNull IResultReceiver clientReceiver, + int sessionStatus, @Nullable IBinder binder) { + try { + final Bundle extras; + if (binder != null) { + extras = new Bundle(); + extras.putBinder(ContentCaptureSession.EXTRA_BINDER, binder); + } else { + extras = null; + } + clientReceiver.send(sessionStatus, extras); + } catch (RemoteException e) { + Slog.w(TAG, "Error async reporting result to client: " + e); + } + } } diff --git a/core/java/android/service/contentcapture/IContentCaptureService.aidl b/core/java/android/service/contentcapture/IContentCaptureService.aidl index 8167be9e6838..20e8e99d4ce1 100644 --- a/core/java/android/service/contentcapture/IContentCaptureService.aidl +++ b/core/java/android/service/contentcapture/IContentCaptureService.aidl @@ -16,10 +16,11 @@ package android.service.contentcapture; -import android.service.contentcapture.ContentCaptureEventsRequest; import android.service.contentcapture.SnapshotData; import android.view.contentcapture.ContentCaptureContext; +import com.android.internal.os.IResultReceiver; + import java.util.List; /** @@ -28,11 +29,8 @@ import java.util.List; * @hide */ oneway interface IContentCaptureService { - - // Called when session is created (context not null) or destroyed (context null) - void onSessionLifecycle(in ContentCaptureContext context, String sessionId); - - void onContentCaptureEventsRequest(String sessionId, in ContentCaptureEventsRequest request); - + void onSessionStarted(in ContentCaptureContext context, String sessionId, int uid, + in IResultReceiver clientReceiver); + void onSessionFinished(String sessionId); void onActivitySnapshot(String sessionId, in SnapshotData snapshotData); } diff --git a/core/java/android/service/quicksettings/Tile.java b/core/java/android/service/quicksettings/Tile.java index 4b81a7260852..6b569cfa9722 100644 --- a/core/java/android/service/quicksettings/Tile.java +++ b/core/java/android/service/quicksettings/Tile.java @@ -15,6 +15,7 @@ */ package android.service.quicksettings; +import android.annotation.Nullable; import android.graphics.drawable.Icon; import android.os.IBinder; import android.os.Parcel; @@ -62,6 +63,7 @@ public final class Tile implements Parcelable { private IBinder mToken; private Icon mIcon; private CharSequence mLabel; + private CharSequence mSubtitle; private CharSequence mContentDescription; // Default to active until clients of the new API can update. private int mState = STATE_ACTIVE; @@ -152,6 +154,22 @@ public final class Tile implements Parcelable { } /** + * Gets the current subtitle for the tile. + */ + @Nullable + public CharSequence getSubtitle() { + return mSubtitle; + } + + /** + * Set the subtitle for the tile. Will be displayed as the secondary label. + * @param subtitle the subtitle to show. + */ + public void setSubtitle(@Nullable CharSequence subtitle) { + this.mSubtitle = subtitle; + } + + /** * Gets the current content description for the tile. */ public CharSequence getContentDescription() { @@ -195,6 +213,7 @@ public final class Tile implements Parcelable { } dest.writeInt(mState); TextUtils.writeToParcel(mLabel, dest, flags); + TextUtils.writeToParcel(mSubtitle, dest, flags); TextUtils.writeToParcel(mContentDescription, dest, flags); } @@ -206,6 +225,7 @@ public final class Tile implements Parcelable { } mState = source.readInt(); mLabel = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source); + mSubtitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source); mContentDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source); } diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java index a095b0d8b239..f295b705e4e6 100644 --- a/core/java/android/service/wallpaper/WallpaperService.java +++ b/core/java/android/service/wallpaper/WallpaperService.java @@ -25,6 +25,7 @@ import android.app.Service; import android.app.WallpaperColors; import android.app.WallpaperInfo; import android.app.WallpaperManager; +import android.content.Context; import android.content.Intent; import android.content.res.TypedArray; import android.graphics.Bitmap; @@ -52,12 +53,12 @@ import android.view.InputChannel; import android.view.InputDevice; import android.view.InputEvent; import android.view.InputEventReceiver; +import android.view.InsetsState; import android.view.MotionEvent; import android.view.SurfaceHolder; import android.view.View; import android.view.ViewGroup; import android.view.WindowInsets; -import android.view.InsetsState; import android.view.WindowManager; import android.view.WindowManagerGlobal; @@ -211,7 +212,8 @@ public abstract class WallpaperService extends Service { private final Supplier<Long> mClockFunction; private final Handler mHandler; - Display mDisplay; + private Display mDisplay; + private Context mDisplayContext; private int mDisplayState; final BaseSurfaceHolder mSurfaceHolder = new BaseSurfaceHolder() { @@ -1038,6 +1040,7 @@ public abstract class WallpaperService extends Service { mIWallpaperEngine.mDisplayManager.registerDisplayListener(mDisplayListener, mCaller.getHandler()); mDisplay = mIWallpaperEngine.mDisplay; + mDisplayContext = createDisplayContext(mDisplay); mDisplayState = mDisplay.getState(); if (DEBUG) Log.v(TAG, "onCreate(): " + this); @@ -1050,6 +1053,18 @@ public abstract class WallpaperService extends Service { } /** + * The {@link Context} with resources that match the current display the wallpaper is on. + * For multiple display environment, multiple engines can be created to render on each + * display, but these displays may have different densities. Use this context to get the + * corresponding resources for currently display, avoiding the context of the service. + * + * @return A {@link Context} for current display. + */ + public Context getDisplayContext() { + return mDisplayContext; + } + + /** * Executes life cycle event and updates internal ambient mode state based on * message sent from handler. * diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java index ade7577dc666..8b97e0e4a107 100644 --- a/core/java/android/util/FeatureFlagUtils.java +++ b/core/java/android/util/FeatureFlagUtils.java @@ -46,6 +46,7 @@ public class FeatureFlagUtils { DEFAULT_FLAGS.put("settings_mobile_network_v2", "true"); DEFAULT_FLAGS.put("settings_network_and_internet_v2", "false"); DEFAULT_FLAGS.put("settings_seamless_transfer", "false"); + DEFAULT_FLAGS.put("settings_slice_injection", "false"); DEFAULT_FLAGS.put("settings_systemui_theme", "true"); DEFAULT_FLAGS.put("settings_wifi_dpp", "false"); DEFAULT_FLAGS.put("settings_wifi_mac_randomization", "false"); diff --git a/core/java/android/view/NotificationHeaderView.java b/core/java/android/view/NotificationHeaderView.java index ada78532d63f..60eeeeaa77ff 100644 --- a/core/java/android/view/NotificationHeaderView.java +++ b/core/java/android/view/NotificationHeaderView.java @@ -201,22 +201,16 @@ public class NotificationHeaderView extends ViewGroup { int bottom = top + childHeight; int layoutLeft = left; int layoutRight = right; - if (child == mExpandButton && mShowExpandButtonAtEnd) { - layoutRight = end - mContentEndMargin; - end = layoutLeft = layoutRight - child.getMeasuredWidth(); - } - if (child == mProfileBadge) { - int paddingEnd = getPaddingEnd(); - if (mShowWorkBadgeAtEnd) { - paddingEnd = mContentEndMargin; + if ((child == mExpandButton && mShowExpandButtonAtEnd) + || child == mProfileBadge + || child == mAppOps) { + if (end == getMeasuredWidth()) { + layoutRight = end - mContentEndMargin; + } else { + layoutRight = end - params.getMarginEnd(); } - layoutRight = end - paddingEnd; - end = layoutLeft = layoutRight - child.getMeasuredWidth(); - } - if (child == mAppOps) { - int paddingEnd = mContentEndMargin; - layoutRight = end - paddingEnd; - end = layoutLeft = layoutRight - child.getMeasuredWidth(); + layoutLeft = layoutRight - child.getMeasuredWidth(); + end = layoutLeft - params.getMarginStart(); } if (getLayoutDirection() == LAYOUT_DIRECTION_RTL) { int ltrLeft = layoutLeft; diff --git a/core/java/android/view/TouchDelegate.java b/core/java/android/view/TouchDelegate.java index bef9f07afb5f..2ea95e90a8bf 100644 --- a/core/java/android/view/TouchDelegate.java +++ b/core/java/android/view/TouchDelegate.java @@ -157,6 +157,11 @@ public class TouchDelegate { * Forward hover events to the delegate view if the event is within the bounds * specified in the constructor and touch exploration is enabled. * + * <p>This method is provided for accessibility purposes so touch exploration, which is + * commonly used by screen readers, can properly place accessibility focus on views that + * use touch delegates. Therefore, touch exploration must be enabled for hover events + * to be dispatched through the delegate.</p> + * * @param event The hover event to forward * @return True if the event was consumed by the delegate, false otherwise. * diff --git a/core/java/android/view/contentcapture/ContentCaptureSession.java b/core/java/android/view/contentcapture/ContentCaptureSession.java index 632955d335cf..f411cf751b47 100644 --- a/core/java/android/view/contentcapture/ContentCaptureSession.java +++ b/core/java/android/view/contentcapture/ContentCaptureSession.java @@ -27,9 +27,11 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.content.ComponentName; import android.content.Context; +import android.content.pm.ParceledListSlice; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; +import android.os.IBinder.DeathRecipient; import android.os.RemoteException; import android.os.SystemClock; import android.util.Log; @@ -45,6 +47,8 @@ import dalvik.system.CloseGuard; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.Collections; +import java.util.List; import java.util.UUID; import java.util.concurrent.atomic.AtomicBoolean; @@ -100,6 +104,13 @@ public final class ContentCaptureSession implements AutoCloseable { public static final int STATE_DISABLED = 3; /** + * Session is disabled because its id already existed on server. + * + * @hide + */ + public static final int STATE_DISABLED_DUPLICATED_ID = 4; + + /** * Handler message used to flush the buffer. */ private static final int MSG_FLUSH = 1; @@ -116,6 +127,13 @@ public final class ContentCaptureSession implements AutoCloseable { // TODO(b/121044064): use settings private static final int FLUSHING_FREQUENCY_MS = 5_000; + + /** + * Name of the {@link IResultReceiver} extra used to pass the binder interface to the service. + * @hide + */ + public static final String EXTRA_BINDER = "binder"; + private final CloseGuard mCloseGuard = CloseGuard.get(); @NonNull @@ -127,8 +145,21 @@ public final class ContentCaptureSession implements AutoCloseable { @NonNull private final Handler mHandler; + /** + * Interface to the system_server binder object - it's only used to start the session (and + * notify when the session is finished). + */ @Nullable - private final IContentCaptureManager mService; + private final IContentCaptureManager mSystemServerInterface; + + /** + * Direct interface to the service binder object - it's used to send the events, including the + * last ones (when the session is finished) + */ + @Nullable + private IContentCaptureDirectManager mDirectServiceInterface; + @Nullable + private DeathRecipient mDirectServiceVulture; @Nullable private final String mId = UUID.randomUUID().toString(); @@ -165,11 +196,11 @@ public final class ContentCaptureSession implements AutoCloseable { /** @hide */ protected ContentCaptureSession(@NonNull Context context, @NonNull Handler handler, - @Nullable IContentCaptureManager service, @NonNull AtomicBoolean disabled, + @Nullable IContentCaptureManager systemServerInterface, @NonNull AtomicBoolean disabled, @Nullable ContentCaptureContext clientContext) { mContext = context; mHandler = handler; - mService = service; + mSystemServerInterface = systemServerInterface; mDisabled = disabled; mClientContext = clientContext; mCloseGuard.open("destroy"); @@ -227,6 +258,8 @@ public final class ContentCaptureSession implements AutoCloseable { Log.v(TAG, "destroy(): state=" + getStateAsString(mState) + ", mId=" + mId); } + flush(); + mHandler.sendMessage(obtainMessage(ContentCaptureSession::handleDestroySession, this)); mCloseGuard.close(); } @@ -267,11 +300,20 @@ public final class ContentCaptureSession implements AutoCloseable { final int flags = 0; // TODO(b/111276913): get proper flags try { - mService.startSession(mContext.getUserId(), mApplicationToken, componentName, - mId, mClientContext, flags, new IResultReceiver.Stub() { + mSystemServerInterface.startSession(mContext.getUserId(), mApplicationToken, + componentName, mId, mClientContext, flags, new IResultReceiver.Stub() { @Override public void send(int resultCode, Bundle resultData) { - handleSessionStarted(resultCode); + IBinder binder = null; + if (resultData != null) { + binder = resultData.getBinder(EXTRA_BINDER); + if (binder == null) { + Log.wtf(TAG, "No " + EXTRA_BINDER + " extra result"); + handleResetState(); + return; + } + } + handleSessionStarted(resultCode, binder); } }); } catch (RemoteException e) { @@ -280,12 +322,38 @@ public final class ContentCaptureSession implements AutoCloseable { } } - private void handleSessionStarted(int resultCode) { + /** + * Callback from {@code system_server} after call to + * {@link IContentCaptureManager#startSession(int, IBinder, ComponentName, String, + * ContentCaptureContext, int, IResultReceiver)}. + * + * @param resultCode session state + * @param binder handle to {@link IContentCaptureDirectManager} + */ + private void handleSessionStarted(int resultCode, @Nullable IBinder binder) { mState = resultCode; - mDisabled.set(mState == STATE_DISABLED); + if (binder != null) { + mDirectServiceInterface = IContentCaptureDirectManager.Stub.asInterface(binder); + mDirectServiceVulture = () -> { + Log.w(TAG, "Destroying session " + mId + " because service died"); + destroy(); + }; + try { + binder.linkToDeath(mDirectServiceVulture, 0); + } catch (RemoteException e) { + Log.w(TAG, "Failed to link to death on " + binder + ": " + e); + } + } + if (resultCode == STATE_DISABLED || resultCode == STATE_DISABLED_DUPLICATED_ID) { + mDisabled.set(true); + handleResetSession(/* resetState= */ false); + } else { + mDisabled.set(false); + } if (VERBOSE) { Log.v(TAG, "handleSessionStarted() result: code=" + resultCode + ", id=" + mId - + ", state=" + getStateAsString(mState) + ", disabled=" + mDisabled.get()); + + ", state=" + getStateAsString(mState) + ", disabled=" + mDisabled.get() + + ", binder=" + binder); } } @@ -307,7 +375,7 @@ public final class ContentCaptureSession implements AutoCloseable { final boolean bufferEvent = numberEvents < MAX_BUFFER_SIZE; if (bufferEvent && !forceFlush) { - handleScheduleFlush(); + handleScheduleFlush(/* checkExisting= */ true); return; } @@ -331,8 +399,8 @@ public final class ContentCaptureSession implements AutoCloseable { handleForceFlush(); } - private void handleScheduleFlush() { - if (mHandler.hasMessages(MSG_FLUSH)) { + private void handleScheduleFlush(boolean checkExisting) { + if (checkExisting && mHandler.hasMessages(MSG_FLUSH)) { // "Renew" the flush message by removing the previous one mHandler.removeMessages(MSG_FLUSH); } @@ -356,52 +424,80 @@ public final class ContentCaptureSession implements AutoCloseable { private void handleForceFlush() { if (mEvents == null) return; + if (mDirectServiceInterface == null) { + Log.w(TAG, "handleForceFlush(): client not available yet"); + if (!mHandler.hasMessages(MSG_FLUSH)) { + handleScheduleFlush(/* checkExisting= */ false); + } + return; + } + final int numberEvents = mEvents.size(); try { if (DEBUG) { Log.d(TAG, "Flushing " + numberEvents + " event(s) for " + getActivityDebugName()); } mHandler.removeMessages(MSG_FLUSH); - mService.sendEvents(mContext.getUserId(), mId, mEvents); - // TODO(b/111276913): decide whether we should clear or set it to null, as each has - // its own advantages: clearing will save extra allocations while the session is - // active, while setting to null would save memory if there's no more event coming. - mEvents.clear(); + + final ParceledListSlice<ContentCaptureEvent> events = handleClearEvents(); + mDirectServiceInterface.sendEvents(mId, events); } catch (RemoteException e) { Log.w(TAG, "Error sending " + numberEvents + " for " + getActivityDebugName() + ": " + e); } } + /** + * Resets the buffer and return a {@link ParceledListSlice} with the previous events. + */ + @NonNull + private ParceledListSlice<ContentCaptureEvent> handleClearEvents() { + // NOTE: we must save a reference to the current mEvents and then set it to to null, + // otherwise clearing it would clear it in the receiving side if the service is also local. + final List<ContentCaptureEvent> events = mEvents == null + ? Collections.emptyList() + : mEvents; + mEvents = null; + return new ParceledListSlice<>(events); + } + private void handleDestroySession() { //TODO(b/111276913): right now both the ContentEvents and lifecycle sessions are sent // to system_server, so it's ok to call both in sequence here. But once we split // them so the events are sent directly to the service, we need to make sure they're // sent in order. - try { - if (DEBUG) { - Log.d(TAG, "Destroying session (ctx=" + mContext + ", id=" + mId + ") with " - + (mEvents == null ? 0 : mEvents.size()) + " event(s) for " - + getActivityDebugName()); - } + if (DEBUG) { + Log.d(TAG, "Destroying session (ctx=" + mContext + ", id=" + mId + ") with " + + (mEvents == null ? 0 : mEvents.size()) + " event(s) for " + + getActivityDebugName()); + } - mService.finishSession(mContext.getUserId(), mId, mEvents); + try { + mSystemServerInterface.finishSession(mContext.getUserId(), mId); } catch (RemoteException e) { - Log.e(TAG, "Error destroying session " + mId + " for " + getActivityDebugName() - + ": " + e); - } finally { - handleResetState(); + Log.e(TAG, "Error destroying system-service session " + mId + " for " + + getActivityDebugName() + ": " + e); } } + private void handleResetState() { + handleResetSession(/* resetState= */ true); + } + // TODO(b/111276913): once we support multiple sessions, we might need to move some of these // clearings out. - private void handleResetState() { - mState = STATE_UNKNOWN; + private void handleResetSession(boolean resetState) { + if (resetState) { + mState = STATE_UNKNOWN; + } mContentCaptureSessionId = null; mApplicationToken = null; mComponentName = null; mEvents = null; + if (mDirectServiceInterface != null) { + mDirectServiceInterface.asBinder().unlinkToDeath(mDirectServiceVulture, 0); + } + mDirectServiceInterface = null; mHandler.removeMessages(MSG_FLUSH); } @@ -492,15 +588,20 @@ public final class ContentCaptureSession implements AutoCloseable { } private boolean isContentCaptureEnabled() { - return mService != null && !mDisabled.get(); + return mSystemServerInterface != null && !mDisabled.get(); } void dump(@NonNull String prefix, @NonNull PrintWriter pw) { pw.print(prefix); pw.print("id: "); pw.println(mId); pw.print(prefix); pw.print("mContext: "); pw.println(mContext); pw.print(prefix); pw.print("user: "); pw.println(mContext.getUserId()); - if (mService != null) { - pw.print(prefix); pw.print("mService: "); pw.println(mService); + if (mSystemServerInterface != null) { + pw.print(prefix); pw.print("mSystemServerInterface: "); + pw.println(mSystemServerInterface); + } + if (mDirectServiceInterface != null) { + pw.print(prefix); pw.print("mDirectServiceInterface: "); + pw.println(mDirectServiceInterface); } if (mClientContext != null) { // NOTE: we don't dump clientContent because it could have PII @@ -563,6 +664,8 @@ public final class ContentCaptureSession implements AutoCloseable { return "ACTIVE"; case STATE_DISABLED: return "DISABLED"; + case STATE_DISABLED_DUPLICATED_ID: + return "DISABLED_DUPLICATED_ID"; default: return "INVALID:" + state; } diff --git a/core/java/android/view/contentcapture/IContentCaptureDirectManager.aidl b/core/java/android/view/contentcapture/IContentCaptureDirectManager.aidl new file mode 100644 index 000000000000..145fc16707a1 --- /dev/null +++ b/core/java/android/view/contentcapture/IContentCaptureDirectManager.aidl @@ -0,0 +1,30 @@ +/* + * 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.view.contentcapture; + +import android.content.pm.ParceledListSlice; +import android.view.contentcapture.ContentCaptureEvent; + +/** + * Interface between an app (ContentCaptureManager / ContentCaptureSession) and the app providing + * the ContentCaptureService implementation. + * + * @hide + */ +oneway interface IContentCaptureDirectManager { + void sendEvents(in String sessionId, in ParceledListSlice events); +} diff --git a/core/java/android/view/contentcapture/IContentCaptureManager.aidl b/core/java/android/view/contentcapture/IContentCaptureManager.aidl index 2002c5c1fc9a..cbd37017038e 100644 --- a/core/java/android/view/contentcapture/IContentCaptureManager.aidl +++ b/core/java/android/view/contentcapture/IContentCaptureManager.aidl @@ -26,12 +26,14 @@ import com.android.internal.os.IResultReceiver; import java.util.List; /** - * {@hide} - */ + * Interface between an app (ContentCaptureManager / ContentCaptureSession) and the system-server + * implementation service (ContentCaptureManagerService). + * + * @hide + */ oneway interface IContentCaptureManager { void startSession(int userId, IBinder activityToken, in ComponentName componentName, String sessionId, in ContentCaptureContext clientContext, int flags, in IResultReceiver result); - void finishSession(int userId, String sessionId, in List<ContentCaptureEvent> events); - void sendEvents(int userId, in String sessionId, in List<ContentCaptureEvent> events); + void finishSession(int userId, String sessionId); } diff --git a/core/java/com/android/internal/app/IAppOpsNotedCallback.aidl b/core/java/com/android/internal/app/IAppOpsNotedCallback.aidl new file mode 100644 index 000000000000..fa5c30a03e78 --- /dev/null +++ b/core/java/com/android/internal/app/IAppOpsNotedCallback.aidl @@ -0,0 +1,22 @@ +/* + * 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.app; + +// Iterface to observe op note/checks of ops +oneway interface IAppOpsNotedCallback { + void opNoted(int op, int uid, String packageName, int mode); +} diff --git a/core/java/com/android/internal/app/IAppOpsService.aidl b/core/java/com/android/internal/app/IAppOpsService.aidl index 049103bfebb2..e571656b9135 100644 --- a/core/java/com/android/internal/app/IAppOpsService.aidl +++ b/core/java/com/android/internal/app/IAppOpsService.aidl @@ -21,6 +21,7 @@ import android.content.pm.ParceledListSlice; import android.os.Bundle; import com.android.internal.app.IAppOpsCallback; import com.android.internal.app.IAppOpsActiveCallback; +import com.android.internal.app.IAppOpsNotedCallback; interface IAppOpsService { // These first methods are also called by native code, so must @@ -61,4 +62,7 @@ interface IAppOpsService { boolean isOperationActive(int code, int uid, String packageName); void startWatchingModeWithFlags(int op, String packageName, int flags, IAppOpsCallback callback); + + void startWatchingNoted(in int[] ops, IAppOpsNotedCallback callback); + void stopWatchingNoted(IAppOpsNotedCallback callback); } diff --git a/core/java/com/android/internal/app/procstats/AssociationState.java b/core/java/com/android/internal/app/procstats/AssociationState.java index 4da339165655..2df515835026 100644 --- a/core/java/com/android/internal/app/procstats/AssociationState.java +++ b/core/java/com/android/internal/app/procstats/AssociationState.java @@ -17,6 +17,7 @@ package com.android.internal.app.procstats; +import android.annotation.Nullable; import android.os.Parcel; import android.os.SystemClock; import android.os.UserHandle; @@ -192,9 +193,16 @@ public final class AssociationState { */ String mProcess; - SourceKey(int uid, String process) { + /** + * Optional package name, or null; consider this final. Not final just to avoid a + * temporary object during lookup. + */ + @Nullable String mPackage; + + SourceKey(int uid, String process, String pkg) { mUid = uid; mProcess = process; + mPackage = pkg; } public boolean equals(Object o) { @@ -202,12 +210,14 @@ public final class AssociationState { return false; } SourceKey s = (SourceKey) o; - return s.mUid == mUid && Objects.equals(s.mProcess, mProcess); + return s.mUid == mUid && Objects.equals(s.mProcess, mProcess) + && Objects.equals(s.mPackage, mPackage); } @Override public int hashCode() { - return Integer.hashCode(mUid) ^ (mProcess == null ? 0 : mProcess.hashCode()); + return Integer.hashCode(mUid) ^ (mProcess == null ? 0 : mProcess.hashCode()) + ^ (mPackage == null ? 0 : (mPackage.hashCode() * 33)); } @Override @@ -217,6 +227,8 @@ public final class AssociationState { UserHandle.formatUid(sb, mUid); sb.append(' '); sb.append(mProcess); + sb.append(' '); + sb.append(mPackage); sb.append('}'); return sb.toString(); } @@ -227,7 +239,7 @@ public final class AssociationState { */ private final ArrayMap<SourceKey, SourceState> mSources = new ArrayMap<>(); - private final SourceKey mTmpSourceKey = new SourceKey(0, null); + private final SourceKey mTmpSourceKey = new SourceKey(0, null, null); private ProcessState mProc; @@ -266,12 +278,13 @@ public final class AssociationState { mProc = proc; } - public SourceState startSource(int uid, String processName) { + public SourceState startSource(int uid, String processName, String packageName) { mTmpSourceKey.mUid = uid; mTmpSourceKey.mProcess = processName; + mTmpSourceKey.mPackage = packageName; SourceState src = mSources.get(mTmpSourceKey); if (src == null) { - SourceKey key = new SourceKey(uid, processName); + SourceKey key = new SourceKey(uid, processName, packageName); src = new SourceState(key); mSources.put(key, src); } @@ -379,6 +392,7 @@ public final class AssociationState { final SourceState src = mSources.valueAt(isrc); out.writeInt(key.mUid); stats.writeCommonString(out, key.mProcess); + stats.writeCommonString(out, key.mPackage); out.writeInt(src.mCount); out.writeLong(src.mDuration); out.writeInt(src.mActiveCount); @@ -405,7 +419,8 @@ public final class AssociationState { for (int isrc = 0; isrc < NSRC; isrc++) { final int uid = in.readInt(); final String procName = stats.readCommonString(in, parcelVersion); - final SourceKey key = new SourceKey(uid, procName); + final String pkgName = stats.readCommonString(in, parcelVersion); + final SourceKey key = new SourceKey(uid, procName, pkgName); final SourceState src = new SourceState(key); src.mCount = in.readInt(); src.mDuration = in.readLong(); @@ -445,10 +460,11 @@ public final class AssociationState { } } - public boolean hasProcess(String procName) { + public boolean hasProcessOrPackage(String procName) { final int NSRC = mSources.size(); for (int isrc = 0; isrc < NSRC; isrc++) { - if (mSources.keyAt(isrc).mProcess.equals(procName)) { + final SourceKey key = mSources.keyAt(isrc); + if (procName.equals(key.mProcess) || procName.equals(key.mPackage)) { return true; } } @@ -466,14 +482,20 @@ public final class AssociationState { for (int isrc = 0; isrc < NSRC; isrc++) { final SourceKey key = mSources.keyAt(isrc); final SourceState src = mSources.valueAt(isrc); - if (reqPackage != null && !reqPackage.equals(key.mProcess)) { + if (reqPackage != null && !reqPackage.equals(key.mProcess) + && !reqPackage.equals(key.mPackage)) { continue; } pw.print(prefixInner); pw.print("<- "); pw.print(key.mProcess); - pw.print(" / "); + pw.print("/"); UserHandle.formatUid(pw, key.mUid); + if (key.mPackage != null) { + pw.print(" ("); + pw.print(key.mPackage); + pw.print(")"); + } pw.println(":"); pw.print(prefixInner); pw.print(" Total count "); @@ -683,6 +705,7 @@ public final class AssociationState { final SourceState src = mSources.valueAt(isrc); final long sourceToken = proto.start(PackageAssociationProcessStatsProto.SOURCES); proto.write(PackageAssociationSourceProcessStatsProto.PROCESS_NAME, key.mProcess); + proto.write(PackageAssociationSourceProcessStatsProto.PACKAGE_NAME, key.mPackage); proto.write(PackageAssociationSourceProcessStatsProto.PROCESS_UID, key.mUid); proto.write(PackageAssociationSourceProcessStatsProto.TOTAL_COUNT, src.mCount); long duration = src.mDuration; diff --git a/core/java/com/android/internal/app/procstats/ProcessStats.java b/core/java/com/android/internal/app/procstats/ProcessStats.java index 9ee583a97b8d..9b9b77196a53 100644 --- a/core/java/com/android/internal/app/procstats/ProcessStats.java +++ b/core/java/com/android/internal/app/procstats/ProcessStats.java @@ -178,7 +178,7 @@ public final class ProcessStats implements Parcelable { {"proc", "pkg-proc", "pkg-svc", "pkg-asc", "pkg-all", "all"}; // Current version of the parcel format. - private static final int PARCEL_VERSION = 34; + private static final int PARCEL_VERSION = 35; // In-memory Parcel magic number, used to detect attempts to unmarshall bad data private static final int MAGIC = 0x50535454; @@ -1490,7 +1490,7 @@ public final class ProcessStats implements Parcelable { // package, so that if so we print those. for (int iasc = 0; iasc < NASCS; iasc++) { AssociationState asc = pkgState.mAssociations.valueAt(iasc); - if (asc.hasProcess(reqPackage)) { + if (asc.hasProcessOrPackage(reqPackage)) { onlyAssociations = true; break; } @@ -1591,7 +1591,7 @@ public final class ProcessStats implements Parcelable { for (int iasc = 0; iasc < NASCS; iasc++) { AssociationState asc = pkgState.mAssociations.valueAt(iasc); if (!pkgMatch && !reqPackage.equals(asc.getProcessName())) { - if (!onlyAssociations || !asc.hasProcess(reqPackage)) { + if (!onlyAssociations || !asc.hasProcessOrPackage(reqPackage)) { continue; } } diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java index 8bdb000aad0e..c2c6ae6712ab 100644 --- a/core/java/com/android/internal/os/ZygoteInit.java +++ b/core/java/com/android/internal/os/ZygoteInit.java @@ -536,9 +536,11 @@ public class ZygoteInit { static ClassLoader createPathClassLoader(String classPath, int targetSdkVersion) { String libraryPath = System.getProperty("java.library.path"); + // We use the boot class loader, that's what the runtime expects at AOT. + ClassLoader parent = ClassLoader.getSystemClassLoader().getParent(); + return ClassLoaderFactory.createClassLoader(classPath, libraryPath, libraryPath, - ClassLoader.getSystemClassLoader(), targetSdkVersion, true /* isNamespaceShared */, - null /* classLoaderName */); + parent, targetSdkVersion, true /* isNamespaceShared */, null /* classLoaderName */); } /** diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl index 5118e5f0473e..9087dd219d97 100644 --- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl @@ -67,7 +67,8 @@ interface IStatusBarService in NotificationVisibility[] noLongerVisibleKeys); void onNotificationExpansionChanged(in String key, in boolean userAction, in boolean expanded); void onNotificationDirectReplied(String key); - void onNotificationSmartRepliesAdded(in String key, in int replyCount); + void onNotificationSmartSuggestionsAdded(String key, int smartReplyCount, int smartActionCount, + boolean generatedByAsssistant); void onNotificationSmartReplySent(in String key, in int replyIndex, in CharSequence reply, boolean generatedByAssistant); void onNotificationSettingsViewed(String key); void setSystemUiVisibility(int displayId, int vis, int mask, String cause); diff --git a/core/java/com/android/internal/usb/DumpUtils.java b/core/java/com/android/internal/usb/DumpUtils.java index cac22652ebd7..240c2e757faf 100644 --- a/core/java/com/android/internal/usb/DumpUtils.java +++ b/core/java/com/android/internal/usb/DumpUtils.java @@ -16,12 +16,12 @@ package com.android.internal.usb; -import static android.hardware.usb.UsbPort.MODE_AUDIO_ACCESSORY; -import static android.hardware.usb.UsbPort.MODE_DEBUG_ACCESSORY; -import static android.hardware.usb.UsbPort.MODE_DFP; -import static android.hardware.usb.UsbPort.MODE_DUAL; -import static android.hardware.usb.UsbPort.MODE_NONE; -import static android.hardware.usb.UsbPort.MODE_UFP; +import static android.hardware.usb.UsbPortStatus.MODE_AUDIO_ACCESSORY; +import static android.hardware.usb.UsbPortStatus.MODE_DEBUG_ACCESSORY; +import static android.hardware.usb.UsbPortStatus.MODE_DFP; +import static android.hardware.usb.UsbPortStatus.MODE_DUAL; +import static android.hardware.usb.UsbPortStatus.MODE_NONE; +import static android.hardware.usb.UsbPortStatus.MODE_UFP; import static com.android.internal.util.dump.DumpUtils.writeStringIfNotNull; diff --git a/core/java/com/android/server/net/BaseNetdEventCallback.java b/core/java/com/android/server/net/BaseNetdEventCallback.java index 97247aada49d..a65214a1edca 100644 --- a/core/java/com/android/server/net/BaseNetdEventCallback.java +++ b/core/java/com/android/server/net/BaseNetdEventCallback.java @@ -32,6 +32,12 @@ public class BaseNetdEventCallback extends INetdEventCallback.Stub { } @Override + public void onNat64PrefixEvent(int netId, boolean added, String prefixString, + int prefixLength) { + // default no-op + } + + @Override public void onPrivateDnsValidationEvent(int netId, String ipAddress, String hostname, boolean validated) { // default no-op diff --git a/core/jni/Android.bp b/core/jni/Android.bp index 43f8d00bb5f1..21fa75eb35e0 100644 --- a/core/jni/Android.bp +++ b/core/jni/Android.bp @@ -274,6 +274,7 @@ cc_library_shared { "libicuuc", "libmedia", "libmediametrics", + "libmeminfo", "libaudioclient", "libjpeg", "libusbhost", diff --git a/core/jni/android/graphics/Bitmap.cpp b/core/jni/android/graphics/Bitmap.cpp index c32de0a5737e..12ca78a7ce92 100755 --- a/core/jni/android/graphics/Bitmap.cpp +++ b/core/jni/android/graphics/Bitmap.cpp @@ -725,9 +725,10 @@ static jobject Bitmap_createFromParcel(JNIEnv* env, jobject, jobject parcel) { return NULL; } - // Map the pixels in place and take ownership of the ashmem region. - nativeBitmap = sk_sp<Bitmap>(GraphicsJNI::mapAshmemBitmap(env, bitmap.get(), - dupFd, const_cast<void*>(blob.data()), size, !isMutable)); + // Map the pixels in place and take ownership of the ashmem region. We must also respect the + // rowBytes value already set on the bitmap instead of attempting to compute our own. + nativeBitmap = Bitmap::createFrom(bitmap->info(), bitmap->rowBytes(), dupFd, + const_cast<void*>(blob.data()), size, !isMutable); if (!nativeBitmap) { close(dupFd); blob.release(); @@ -1097,21 +1098,20 @@ static jobject Bitmap_copyPreserveInternalConfig(JNIEnv* env, jobject, jlong bit SkBitmap src; hwuiBitmap.getSkBitmap(&src); - SkBitmap result; - HeapAllocator allocator; - if (!bitmapCopyTo(&result, hwuiBitmap.info().colorType(), src, &allocator)) { + if (src.pixelRef() == nullptr) { doThrowRE(env, "Could not copy a hardware bitmap."); return NULL; } - return createBitmap(env, allocator.getStorageObjAndReset(), getPremulBitmapCreateFlags(false)); + + sk_sp<Bitmap> bitmap = Bitmap::createFrom(src.info(), *src.pixelRef()); + return createBitmap(env, bitmap.release(), getPremulBitmapCreateFlags(false)); } static jobject Bitmap_createHardwareBitmap(JNIEnv* env, jobject, jobject graphicBuffer) { sp<GraphicBuffer> buffer(graphicBufferForJavaObject(env, graphicBuffer)); - // Bitmap::createFrom currently assumes SRGB color space for RGBA images. // To support any color space, we need to pass an additional ColorSpace argument to // java Bitmap.createHardwareBitmap. - sk_sp<Bitmap> bitmap = Bitmap::createFrom(buffer); + sk_sp<Bitmap> bitmap = Bitmap::createFrom(buffer, SkColorSpace::MakeSRGB()); if (!bitmap.get()) { ALOGW("failed to create hardware bitmap from graphic buffer"); return NULL; diff --git a/core/jni/android/graphics/Graphics.cpp b/core/jni/android/graphics/Graphics.cpp index 26af15e79e2d..67d0c8aced61 100644 --- a/core/jni/android/graphics/Graphics.cpp +++ b/core/jni/android/graphics/Graphics.cpp @@ -424,36 +424,6 @@ jobject GraphicsJNI::createRegion(JNIEnv* env, SkRegion* region) /////////////////////////////////////////////////////////////////////////////// -android::Bitmap* GraphicsJNI::mapAshmemBitmap(JNIEnv* env, SkBitmap* bitmap, - int fd, void* addr, size_t size, bool readOnly) { - const SkImageInfo& info = bitmap->info(); - if (info.colorType() == kUnknown_SkColorType) { - doThrowIAE(env, "unknown bitmap configuration"); - return nullptr; - } - - if (!addr) { - // Map existing ashmem region if not already mapped. - int flags = readOnly ? (PROT_READ) : (PROT_READ | PROT_WRITE); - size = ashmem_get_size_region(fd); - addr = mmap(NULL, size, flags, MAP_SHARED, fd, 0); - if (addr == MAP_FAILED) { - return nullptr; - } - } - - // we must respect the rowBytes value already set on the bitmap instead of - // attempting to compute our own. - const size_t rowBytes = bitmap->rowBytes(); - - auto wrapper = new android::Bitmap(addr, fd, size, info, rowBytes); - wrapper->getSkBitmap(bitmap); - if (readOnly) { - bitmap->pixelRef()->setImmutable(); - } - return wrapper; -} - SkColorSpaceTransferFn GraphicsJNI::getNativeTransferParameters(JNIEnv* env, jobject transferParams) { SkColorSpaceTransferFn p; p.fA = (float) env->GetDoubleField(transferParams, gTransferParams_aFieldID); diff --git a/core/jni/android/graphics/GraphicsJNI.h b/core/jni/android/graphics/GraphicsJNI.h index cee3c46dd67f..b0bd68336e08 100644 --- a/core/jni/android/graphics/GraphicsJNI.h +++ b/core/jni/android/graphics/GraphicsJNI.h @@ -85,9 +85,6 @@ public: static jobject createBitmapRegionDecoder(JNIEnv* env, SkBitmapRegionDecoder* bitmap); - static android::Bitmap* mapAshmemBitmap(JNIEnv* env, SkBitmap* bitmap, - int fd, void* addr, size_t size, bool readOnly); - /** * Given a bitmap we natively allocate a memory block to store the contents * of that bitmap. The memory is then attached to the bitmap via an diff --git a/core/jni/android_os_Debug.cpp b/core/jni/android_os_Debug.cpp index fa1da4bfbf3a..888dab19c247 100644 --- a/core/jni/android_os_Debug.cpp +++ b/core/jni/android_os_Debug.cpp @@ -32,6 +32,7 @@ #include <atomic> #include <iomanip> #include <string> +#include <vector> #include <debuggerd/client.h> #include <log/log.h> @@ -41,6 +42,7 @@ #include <nativehelper/JNIHelp.h> #include <nativehelper/ScopedUtfChars.h> #include "jni.h" +#include <meminfo/sysmeminfo.h> #include <memtrack/memtrack.h> #include <memunreachable/memunreachable.h> #include "android_os_Debug.h" @@ -712,6 +714,8 @@ static long get_allocated_vmalloc_memory() { return vmalloc_allocated_size; } +// The 1:1 mapping of MEMINFO_* enums here must match with the constants from +// Debug.java. enum { MEMINFO_TOTAL, MEMINFO_FREE, @@ -731,138 +735,43 @@ enum { MEMINFO_COUNT }; -static long long get_zram_mem_used() -{ -#define ZRAM_SYSFS "/sys/block/zram0/" - UniqueFile mm_stat_file = MakeUniqueFile(ZRAM_SYSFS "mm_stat", "re"); - if (mm_stat_file) { - long long mem_used_total = 0; - - int matched = fscanf(mm_stat_file.get(), "%*d %*d %lld %*d %*d %*d %*d", &mem_used_total); - if (matched != 1) - ALOGW("failed to parse " ZRAM_SYSFS "mm_stat"); - - return mem_used_total; - } - - UniqueFile mem_used_total_file = MakeUniqueFile(ZRAM_SYSFS "mem_used_total", "re"); - if (mem_used_total_file) { - long long mem_used_total = 0; - - int matched = fscanf(mem_used_total_file.get(), "%lld", &mem_used_total); - if (matched != 1) - ALOGW("failed to parse " ZRAM_SYSFS "mem_used_total"); - - return mem_used_total; - } - - return 0; -} - static void android_os_Debug_getMemInfo(JNIEnv *env, jobject clazz, jlongArray out) { - char buffer[4096]; - size_t numFound = 0; - if (out == NULL) { jniThrowNullPointerException(env, "out == null"); return; } - int fd = open("/proc/meminfo", O_RDONLY | O_CLOEXEC); - - if (fd < 0) { - ALOGW("Unable to open /proc/meminfo: %s\n", strerror(errno)); + int outLen = env->GetArrayLength(out); + if (outLen < MEMINFO_COUNT) { + jniThrowRuntimeException(env, "outLen < MEMINFO_COUNT"); return; } - int len = read(fd, buffer, sizeof(buffer)-1); - close(fd); - - if (len < 0) { - ALOGW("Empty /proc/meminfo"); + // Read system memory info including ZRAM. The values are stored in the vector + // in the same order as MEMINFO_* enum + std::vector<uint64_t> mem(MEMINFO_COUNT); + std::vector<std::string> tags(::android::meminfo::SysMemInfo::kDefaultSysMemInfoTags); + tags.insert(tags.begin() + MEMINFO_ZRAM_TOTAL, "Zram:"); + ::android::meminfo::SysMemInfo smi; + if (!smi.ReadMemInfo(tags, &mem)) { + jniThrowRuntimeException(env, "SysMemInfo read failed"); return; } - buffer[len] = 0; - - static const char* const tags[] = { - "MemTotal:", - "MemFree:", - "Buffers:", - "Cached:", - "Shmem:", - "Slab:", - "SReclaimable:", - "SUnreclaim:", - "SwapTotal:", - "SwapFree:", - "ZRam:", - "Mapped:", - "VmallocUsed:", - "PageTables:", - "KernelStack:", - NULL - }; - static const int tagsLen[] = { - 9, - 8, - 8, - 7, - 6, - 5, - 13, - 11, - 10, - 9, - 5, - 7, - 12, - 11, - 12, - 0 - }; - long mem[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; - - char* p = buffer; - while (*p && numFound < (sizeof(tagsLen) / sizeof(tagsLen[0]))) { - int i = 0; - while (tags[i]) { - if (strncmp(p, tags[i], tagsLen[i]) == 0) { - p += tagsLen[i]; - while (*p == ' ') p++; - char* num = p; - while (*p >= '0' && *p <= '9') p++; - if (*p != 0) { - *p = 0; - p++; - } - mem[i] = atoll(num); - numFound++; - break; - } - i++; - } - while (*p && *p != '\n') { - p++; - } - if (*p) p++; - } - - mem[MEMINFO_ZRAM_TOTAL] = get_zram_mem_used() / 1024; - // Recompute Vmalloc Used since the value in meminfo - // doesn't account for I/O remapping which doesn't use RAM. - mem[MEMINFO_VMALLOC_USED] = get_allocated_vmalloc_memory() / 1024; - int maxNum = env->GetArrayLength(out); - if (maxNum > MEMINFO_COUNT) { - maxNum = MEMINFO_COUNT; - } jlong* outArray = env->GetLongArrayElements(out, 0); if (outArray != NULL) { - for (int i=0; i<maxNum; i++) { + outLen = MEMINFO_COUNT; + for (int i = 0; i < outLen; i++) { + // TODO: move get_allocated_vmalloc_memory() to libmeminfo + if (i == MEMINFO_VMALLOC_USED) { + outArray[i] = get_allocated_vmalloc_memory() / 1024; + continue; + } outArray[i] = mem[i]; } } + env->ReleaseLongArrayElements(out, outArray, 0); } diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp index 102a0b7b8957..0c1a8aa18370 100644 --- a/core/jni/android_util_Process.cpp +++ b/core/jni/android_util_Process.cpp @@ -25,8 +25,12 @@ #include <cutils/sched_policy.h> #include <utils/String8.h> #include <utils/Vector.h> +#include <meminfo/sysmeminfo.h> #include <processgroup/processgroup.h> +#include <string> +#include <vector> + #include "core_jni_helpers.h" #include "android_util_Binder.h" @@ -39,9 +43,11 @@ #include <inttypes.h> #include <pwd.h> #include <signal.h> +#include <string.h> #include <sys/errno.h> #include <sys/resource.h> #include <sys/stat.h> +#include <sys/sysinfo.h> #include <sys/types.h> #include <unistd.h> @@ -603,66 +609,34 @@ static int pid_compare(const void* v1, const void* v2) return *((const jint*)v1) - *((const jint*)v2); } -static jlong getFreeMemoryImpl(const char* const sums[], const size_t sumsLen[], size_t num) +static jlong android_os_Process_getFreeMemory(JNIEnv* env, jobject clazz) { - int fd = open("/proc/meminfo", O_RDONLY | O_CLOEXEC); + static const std::vector<std::string> memFreeTags = { + ::android::meminfo::SysMemInfo::kMemFree, + ::android::meminfo::SysMemInfo::kMemCached, + }; + std::vector<uint64_t> mem(memFreeTags.size()); + ::android::meminfo::SysMemInfo smi; - if (fd < 0) { - ALOGW("Unable to open /proc/meminfo"); - return -1; + if (!smi.ReadMemInfo(memFreeTags, &mem)) { + jniThrowRuntimeException(env, "SysMemInfo read failed to get Free Memory"); + return -1L; } - char buffer[2048]; - const int len = read(fd, buffer, sizeof(buffer)-1); - close(fd); - - if (len < 0) { - ALOGW("Unable to read /proc/meminfo"); - return -1; - } - buffer[len] = 0; - - size_t numFound = 0; - jlong mem = 0; - - char* p = buffer; - while (*p && numFound < num) { - int i = 0; - while (sums[i]) { - if (strncmp(p, sums[i], sumsLen[i]) == 0) { - p += sumsLen[i]; - while (*p == ' ') p++; - char* num = p; - while (*p >= '0' && *p <= '9') p++; - if (*p != 0) { - *p = 0; - p++; - if (*p == 0) p--; - } - mem += atoll(num) * 1024; - numFound++; - break; - } - i++; - } - p++; - } - - return numFound > 0 ? mem : -1; -} - -static jlong android_os_Process_getFreeMemory(JNIEnv* env, jobject clazz) -{ - static const char* const sums[] = { "MemFree:", "Cached:", NULL }; - static const size_t sumsLen[] = { strlen("MemFree:"), strlen("Cached:"), 0 }; - return getFreeMemoryImpl(sums, sumsLen, 2); + jlong sum = 0; + std::for_each(mem.begin(), mem.end(), [&](uint64_t val) { sum += val; }); + return sum * 1024; } static jlong android_os_Process_getTotalMemory(JNIEnv* env, jobject clazz) { - static const char* const sums[] = { "MemTotal:", NULL }; - static const size_t sumsLen[] = { strlen("MemTotal:"), 0 }; - return getFreeMemoryImpl(sums, sumsLen, 1); + struct sysinfo si; + if (sysinfo(&si) == -1) { + ALOGE("sysinfo failed: %s", strerror(errno)); + return -1; + } + + return si.totalram; } void android_os_Process_readProcLines(JNIEnv* env, jobject clazz, jstring fileStr, diff --git a/core/jni/android_view_DisplayListCanvas.cpp b/core/jni/android_view_DisplayListCanvas.cpp index e0fd1a634557..40a133b9383b 100644 --- a/core/jni/android_view_DisplayListCanvas.cpp +++ b/core/jni/android_view_DisplayListCanvas.cpp @@ -168,6 +168,11 @@ static void android_view_DisplayListCanvas_drawCircleProps(jlong canvasPtr, canvas->drawCircle(xProp, yProp, radiusProp, paintProp); } +static void android_view_DisplayListCanvas_drawWebViewFunctor(jlong canvasPtr, jint functor) { + Canvas* canvas = reinterpret_cast<Canvas*>(canvasPtr); + canvas->drawWebViewFunctor(functor); +} + // ---------------------------------------------------------------------------- // JNI Glue // ---------------------------------------------------------------------------- @@ -192,6 +197,7 @@ static JNINativeMethod gMethods[] = { { "nDrawTextureLayer", "(JJ)V", (void*) android_view_DisplayListCanvas_drawTextureLayer }, { "nDrawCircle", "(JJJJJ)V", (void*) android_view_DisplayListCanvas_drawCircleProps }, { "nDrawRoundRect", "(JJJJJJJJ)V",(void*) android_view_DisplayListCanvas_drawRoundRectProps }, + { "nDrawWebViewFunctor", "(JI)V", (void*) android_view_DisplayListCanvas_drawWebViewFunctor }, }; int register_android_view_DisplayListCanvas(JNIEnv* env) { diff --git a/core/jni/android_view_RenderNode.cpp b/core/jni/android_view_RenderNode.cpp index 752624b0a0be..0d75de9ee95f 100644 --- a/core/jni/android_view_RenderNode.cpp +++ b/core/jni/android_view_RenderNode.cpp @@ -338,6 +338,11 @@ static jboolean android_view_RenderNode_hasOverlappingRendering(jlong renderNode return renderNode->stagingProperties().hasOverlappingRendering(); } +static jboolean android_view_RenderNode_getClipToBounds(jlong renderNodePtr) { + RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); + return renderNode->stagingProperties().getClipToBounds(); +} + static jboolean android_view_RenderNode_getClipToOutline(jlong renderNodePtr) { RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); return renderNode->stagingProperties().getOutline().getShouldClip(); @@ -409,6 +414,11 @@ static jboolean android_view_RenderNode_hasIdentityMatrix(jlong renderNodePtr) { return !renderNode->stagingProperties().hasTransformMatrix(); } +static jint android_view_RenderNode_getLayerType(jlong renderNodePtr) { + RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); + return static_cast<int>(renderNode->stagingProperties().layerProperties().type()); +} + // ---------------------------------------------------------------------------- // RenderProperties - computed getters // ---------------------------------------------------------------------------- @@ -623,10 +633,12 @@ static const JNINativeMethod gMethods[] = { // ---------------------------------------------------------------------------- { "nIsValid", "(J)Z", (void*) android_view_RenderNode_isValid }, { "nSetLayerType", "(JI)Z", (void*) android_view_RenderNode_setLayerType }, + { "nGetLayerType", "(J)I", (void*) android_view_RenderNode_getLayerType }, { "nSetLayerPaint", "(JJ)Z", (void*) android_view_RenderNode_setLayerPaint }, { "nSetStaticMatrix", "(JJ)Z", (void*) android_view_RenderNode_setStaticMatrix }, { "nSetAnimationMatrix", "(JJ)Z", (void*) android_view_RenderNode_setAnimationMatrix }, { "nSetClipToBounds", "(JZ)Z", (void*) android_view_RenderNode_setClipToBounds }, + { "nGetClipToBounds", "(J)Z", (void*) android_view_RenderNode_getClipToBounds }, { "nSetClipBounds", "(JIIII)Z", (void*) android_view_RenderNode_setClipBounds }, { "nSetClipBoundsEmpty", "(J)Z", (void*) android_view_RenderNode_setClipBoundsEmpty }, { "nSetProjectBackwards", "(JZ)Z", (void*) android_view_RenderNode_setProjectBackwards }, diff --git a/core/jni/android_view_ThreadedRenderer.cpp b/core/jni/android_view_ThreadedRenderer.cpp index 702741eb813c..5a8ab3c1bdc4 100644 --- a/core/jni/android_view_ThreadedRenderer.cpp +++ b/core/jni/android_view_ThreadedRenderer.cpp @@ -31,8 +31,6 @@ #include <gui/BufferQueue.h> #include <gui/Surface.h> -#include <EGL/egl.h> -#include <EGL/eglext.h> #include <private/EGL/cache.h> #include <utils/Looper.h> @@ -58,6 +56,7 @@ #include <renderthread/RenderTask.h> #include <renderthread/RenderThread.h> #include <pipeline/skia/ShaderCache.h> +#include <utils/Color.h> namespace android { @@ -1011,10 +1010,9 @@ static jobject android_view_ThreadedRenderer_createHardwareBitmapFromRenderNode( buffer->getWidth(), buffer->getHeight(), width, height); // Continue I guess? } - sk_sp<Bitmap> bitmap = Bitmap::createFrom(buffer); - // Bitmap::createFrom currently can only attach to a GraphicBuffer with PIXEL_FORMAT_RGBA_8888 - // format and SRGB color space. - // To support any color space, we could extract it from BufferItem and pass it to Bitmap. + + sk_sp<SkColorSpace> cs = uirenderer::DataSpaceToColorSpace(bufferItem.mDataSpace); + sk_sp<Bitmap> bitmap = Bitmap::createFrom(buffer, cs); return bitmap::createBitmap(env, bitmap.release(), android::bitmap::kBitmapCreateFlag_Premultiplied); } diff --git a/core/proto/android/service/procstats.proto b/core/proto/android/service/procstats.proto index ce19ce3519b2..71ebcc1e3659 100644 --- a/core/proto/android/service/procstats.proto +++ b/core/proto/android/service/procstats.proto @@ -186,7 +186,7 @@ message PackageServiceStatsProto { repeated PackageServiceOperationStatsProto operation_stats = 2; } -// Next Tag: 7 +// Next Tag: 8 message PackageAssociationSourceProcessStatsProto { option (android.msg_privacy).dest = DEST_AUTOMATIC; @@ -194,6 +194,8 @@ message PackageAssociationSourceProcessStatsProto { optional int32 process_uid = 1; // Process name. optional string process_name = 2; + // Package name. + optional string package_name = 7; // Total count of the times this association appeared. optional int32 total_count = 3; diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 7fa3e66bae25..cc8927fdf730 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -1642,6 +1642,12 @@ <permission android:name="android.permission.NETWORK_BYPASS_PRIVATE_DNS" android:protectionLevel="signature" /> + <!-- #SystemApi @hide Allows device mobility state to be set so that Wifi scan interval can be increased + when the device is stationary in order to save power. + <p>Not for use by third-party applications. --> + <permission android:name="android.permission.WIFI_SET_DEVICE_MOBILITY_STATE" + android:protectionLevel="signature|privileged" /> + <!-- ======================================= --> <!-- Permissions for short range, peripheral networks --> <!-- ======================================= --> diff --git a/core/res/res/layout/notification_template_header.xml b/core/res/res/layout/notification_template_header.xml index 5ba1cf259551..0f53549a966c 100644 --- a/core/res/res/layout/notification_template_header.xml +++ b/core/res/res/layout/notification_template_header.xml @@ -111,6 +111,7 @@ android:background="@null" android:layout_width="@dimen/notification_header_expand_icon_size" android:layout_height="@dimen/notification_header_expand_icon_size" + android:layout_marginStart="4dp" android:paddingTop="@dimen/notification_expand_button_padding_top" android:visibility="gone" android:contentDescription="@string/expand_button_content_description_collapsed" diff --git a/core/res/res/values-night/themes_permission_controller.xml b/core/res/res/values-night/themes_permission_controller.xml index 0ad2bdcbb52d..a071927f46b2 100644 --- a/core/res/res/values-night/themes_permission_controller.xml +++ b/core/res/res/values-night/themes_permission_controller.xml @@ -28,8 +28,5 @@ <style name="Theme.DeviceDefault.PermissionGrant" parent="@style/Theme.DeviceDefault.Dialog"> <item name="titleTextStyle">@style/PermissionGrantTitleMessage</item> - <item name="radioButtonStyle">@style/PermissionGrantRadioButton</item> - <item name="checkboxStyle">@style/PermissionGrantCheckbox</item> - <item name="buttonBarStyle">@style/PermissionGrantButtonBar</item> </style> </resources> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 9277daec7027..3f1f9cd062a6 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -1165,6 +1165,10 @@ <!-- The default suggested battery % at which we enable battery saver automatically. --> <integer name="config_lowBatteryAutoTriggerDefaultLevel">15</integer> + <!-- The app which will handle routine based automatic battery saver, if empty the UI for + routine based battery saver will be hidden --> + <string name="config_batterySaverScheduleProvider"></string> + <!-- Close low battery warning when battery level reaches the lowBatteryWarningLevel plus this --> <integer name="config_lowBatteryCloseWarningBump">5</integer> @@ -3488,8 +3492,6 @@ <!-- Name of a font family to use for headlines. If empty, falls back to platform default --> <string name="config_headlineFontFamily" translatable="false"></string> - <!-- Name of a font family to use for headlines. Defaults to sans-serif-light --> - <string name="config_headlineFontFamilyLight" translatable="false">sans-serif-light</string> <!-- Allows setting custom fontFeatureSettings on specific text. --> <string name="config_headlineFontFeatureSettings" translatable="false"></string> @@ -3556,8 +3558,6 @@ <string name="config_headlineFontFamilyMedium" translateable="false">@string/font_family_button_material</string> <!-- Name of a font family to use for body text. --> <string name="config_bodyFontFamily" translatable="false">sans-serif</string> - <!-- Name of a font family to use for light body text. --> - <string name="config_bodyFontFamilyLight" translatable="false">sans-serif-light</string> <!-- Name of a font family to use for medium body text. --> <string name="config_bodyFontFamilyMedium" translatable="false">sans-serif-medium</string> diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index 6f75d90c46c4..799d9d858b59 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -2978,6 +2978,11 @@ <public name="config_mediaMetadataBitmapMaxSize" /> </public-group> + <public-group type="color" first-id="0x0106001c"> + <!-- @hide @SystemApi --> + <public name="system_notification_accent_color" /> + </public-group> + <!-- =============================================================== DO NOT ADD UN-GROUPED ITEMS HERE diff --git a/core/res/res/values/styles_permission_controller.xml b/core/res/res/values/styles_permission_controller.xml index e6e0de3a6f30..5a9d3e6c2889 100644 --- a/core/res/res/values/styles_permission_controller.xml +++ b/core/res/res/values/styles_permission_controller.xml @@ -25,15 +25,16 @@ </style> <style name="PermissionGrantTitleIcon"> - <item name="layout_width">36dp</item> - <item name="layout_height">36dp</item> + <item name="layout_width">24dp</item> + <item name="layout_height">24dp</item> + <item name="layout_marginBottom">12dp</item> <item name="tint">?attr/colorAccent</item> <item name="scaleType">fitCenter</item> </style> <style name="PermissionGrantTitleMessage" parent="@style/TextAppearance.DeviceDefault"> - <item name="paddingStart">22dp</item> + <item name="gravity">center</item> <item name="textSize">20sp</item> <item name="textColor">?attr/textColorPrimary</item> </style> @@ -46,56 +47,22 @@ </style> <style name="PermissionGrantDescription"> - <item name="layout_marginTop">20dp</item> <item name="layout_marginStart">24dp</item> - <item name="layout_marginBottom">16dp</item> <item name="layout_marginEnd">24dp</item> </style> <style name="PermissionGrantContent"> - <item name="layout_marginStart">16dp</item> + <item name="layout_marginStart">24dp</item> <item name="layout_marginEnd">24dp</item> </style> - <style name="PermissionGrantRadioGroup"> - <item name="layout_marginStart">8dp</item> - <item name="layout_marginTop">-4dp</item> - </style> - - <style name="PermissionGrantRadioButton" - parent="@style/Widget.DeviceDefault.CompoundButton.RadioButton"> - <item name="paddingStart">16dp</item> - <item name="paddingTop">8dp</item> - <item name="paddingBottom">8dp</item> - <item name="textSize">16sp</item> - </style> - <style name="PermissionGrantDetailMessage" parent="@style/TextAppearance.DeviceDefault"> - <item name="layout_marginStart">8dp</item> - <item name="layout_marginBottom">4dp</item> + <item name="layout_marginTop">18dp</item> <item name="textColor">?attr/textColorPrimary</item> <item name="textSize">16sp</item> </style> - <style name="PermissionGrantDetailMessageSpace"> - <item name="layout_height">8dp</item> - </style> - - <style name="PermissionGrantCheckbox" - parent="@style/Widget.DeviceDefault.CompoundButton.CheckBox"> - <item name="paddingStart">16dp</item> - <item name="textColor">?attr/textColorSecondary</item> - <item name="buttonTint">?attr/textColorSecondary</item> - <item name="textSize">16sp</item> - </style> - - <style name="PermissionGrantButtonBar"> - <item name="layout_marginStart">24dp</item> - <item name="layout_marginEnd">16dp</item> - <item name="layout_marginBottom">4dp</item> - </style> - <!-- styles for the permission review screen. --> <style name="PermissionReviewDescription"> <item name="layout_marginTop">20dp</item> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index d68681d8c51d..ed9f3b11b67b 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -1,4 +1,3 @@ -<?xml version="1.0" encoding="utf-8"?> <!-- /* Copyright 2012, The Android Open Source Project ** @@ -3328,7 +3327,6 @@ <java-symbol type="bool" name="config_displayBrightnessBucketsInDoze" /> <java-symbol type="integer" name="config_storageManagerDaystoRetainDefault" /> <java-symbol type="string" name="config_headlineFontFamily" /> - <java-symbol type="string" name="config_headlineFontFamilyLight" /> <java-symbol type="string" name="config_headlineFontFamilyMedium" /> <java-symbol type="drawable" name="stat_sys_vitals" /> @@ -3463,6 +3461,7 @@ <java-symbol type="integer" name="config_lowBatteryAutoTriggerDefaultLevel" /> <java-symbol type="bool" name="config_batterySaverStickyBehaviourDisabled" /> <java-symbol type="integer" name="config_dynamicPowerSavingsDefaultDisableThreshold" /> + <java-symbol type="string" name="config_batterySaverScheduleProvider" /> <!-- For car devices --> <java-symbol type="string" name="car_loading_profile" /> diff --git a/core/res/res/values/themes_permission_controller.xml b/core/res/res/values/themes_permission_controller.xml index 369cee3d98f1..205c4eb2d9a2 100644 --- a/core/res/res/values/themes_permission_controller.xml +++ b/core/res/res/values/themes_permission_controller.xml @@ -28,9 +28,6 @@ <style name="Theme.DeviceDefault.PermissionGrant" parent="@style/Theme.DeviceDefault.Light.Dialog"> <item name="titleTextStyle">@style/PermissionGrantTitleMessage</item> - <item name="radioButtonStyle">@style/PermissionGrantRadioButton</item> - <item name="checkboxStyle">@style/PermissionGrantCheckbox</item> - <item name="buttonBarStyle">@style/PermissionGrantButtonBar</item> </style> <!-- themes for the permission review dialog. --> @@ -39,6 +36,5 @@ <item name="windowActionBar">false</item> <item name="windowNoTitle">true</item> <item name="titleTextStyle">@style/PermissionReviewTitleMessage</item> - <item name="buttonBarStyle">@style/PermissionReviewButtonBar</item> </style> </resources> diff --git a/core/tests/coretests/src/android/os/FileUtilsTest.java b/core/tests/coretests/src/android/os/FileUtilsTest.java index 55e21a76f170..514ea0cc013e 100644 --- a/core/tests/coretests/src/android/os/FileUtilsTest.java +++ b/core/tests/coretests/src/android/os/FileUtilsTest.java @@ -517,6 +517,28 @@ public class FileUtilsTest { } @Test + public void testMalformedTransate_int() throws Exception { + try { + // The non-standard Linux access mode 3 should throw + // an IllegalArgumentException. + translateModePosixToPfd(O_RDWR | O_WRONLY); + fail(); + } catch (IllegalArgumentException expected) { + } + } + + @Test + public void testMalformedTransate_string() throws Exception { + try { + // The non-standard Linux access mode 3 should throw + // an IllegalArgumentException. + translateModePosixToString(O_RDWR | O_WRONLY); + fail(); + } catch (IllegalArgumentException expected) { + } + } + + @Test public void testTranslateMode_Invalid() throws Exception { try { translateModeStringToPosix("rwx"); diff --git a/core/tests/overlaytests/device/src/com/android/overlaytest/OverlayBaseTest.java b/core/tests/overlaytests/device/src/com/android/overlaytest/OverlayBaseTest.java index 96ab977b799c..5a86885459f2 100644 --- a/core/tests/overlaytests/device/src/com/android/overlaytest/OverlayBaseTest.java +++ b/core/tests/overlaytests/device/src/com/android/overlaytest/OverlayBaseTest.java @@ -278,13 +278,12 @@ public abstract class OverlayBaseTest { } } - final String no = "Lorem ipsum dolor sit amet, consectetur adipisicing elit, " + - "sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. " + - "Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip " + - "ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit " + - "esse cillum dolore eu fugiat nulla pariatur. " + - "Excepteur sint occaecat cupidatat non proident, " + - "sunt in culpa qui officia deserunt mollit anim id est laborum."; + final String no = "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do " + + "eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim " + + "veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo " + + "consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse " + + "cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non " + + "proident, sunt in culpa qui officia deserunt mollit anim id est laborum."; final String so = "Lorem ipsum: single overlay."; final String mo = "Lorem ipsum: multiple overlays."; diff --git a/core/tests/overlaytests/host/src/com/android/server/om/hosttest/InstallOverlayTests.java b/core/tests/overlaytests/host/src/com/android/server/om/hosttest/InstallOverlayTests.java index 6d5276f2423e..27986cce0835 100644 --- a/core/tests/overlaytests/host/src/com/android/server/om/hosttest/InstallOverlayTests.java +++ b/core/tests/overlaytests/host/src/com/android/server/om/hosttest/InstallOverlayTests.java @@ -1,17 +1,17 @@ /* * 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 + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, 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. + * 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.om.hosttest; diff --git a/core/tests/overlaytests/host/test-apps/UpdateOverlay/Android.mk b/core/tests/overlaytests/host/test-apps/UpdateOverlay/Android.mk index 8656781e31e5..c7b2dd1ac8f9 100644 --- a/core/tests/overlaytests/host/test-apps/UpdateOverlay/Android.mk +++ b/core/tests/overlaytests/host/test-apps/UpdateOverlay/Android.mk @@ -20,7 +20,7 @@ LOCAL_SRC_FILES := $(call all-java-files-under,src) LOCAL_PACKAGE_NAME := OverlayHostTests_UpdateOverlay LOCAL_SDK_VERSION := current LOCAL_COMPATIBILITY_SUITE := general-tests -LOCAL_STATIC_JAVA_LIBRARIES := android-support-test +LOCAL_STATIC_JAVA_LIBRARIES := androidx.test.rules LOCAL_USE_AAPT2 := true LOCAL_AAPT_FLAGS := --no-resource-removal include $(BUILD_PACKAGE) diff --git a/core/tests/overlaytests/host/test-apps/UpdateOverlay/AndroidManifest.xml b/core/tests/overlaytests/host/test-apps/UpdateOverlay/AndroidManifest.xml index 06077a79d009..8b5fe99c2e74 100644 --- a/core/tests/overlaytests/host/test-apps/UpdateOverlay/AndroidManifest.xml +++ b/core/tests/overlaytests/host/test-apps/UpdateOverlay/AndroidManifest.xml @@ -21,7 +21,7 @@ <uses-library android:name="android.test.runner" /> </application> - <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner" + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" android:targetPackage="com.android.server.om.hosttest.update_overlay_test" android:label="Update Overlay Test"/> </manifest> diff --git a/core/tests/overlaytests/host/test-apps/UpdateOverlay/src/com/android/server/om/hosttest/update_overlay_test/UpdateOverlayTest.java b/core/tests/overlaytests/host/test-apps/UpdateOverlay/src/com/android/server/om/hosttest/update_overlay_test/UpdateOverlayTest.java index a174d774b80e..fef63208b2d3 100644 --- a/core/tests/overlaytests/host/test-apps/UpdateOverlay/src/com/android/server/om/hosttest/update_overlay_test/UpdateOverlayTest.java +++ b/core/tests/overlaytests/host/test-apps/UpdateOverlay/src/com/android/server/om/hosttest/update_overlay_test/UpdateOverlayTest.java @@ -1,17 +1,17 @@ /* * 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 + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, 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. + * 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.om.hosttest.update_overlay_test; @@ -19,9 +19,10 @@ import static org.junit.Assert.assertEquals; import android.content.res.Configuration; import android.content.res.Resources; -import android.support.test.InstrumentationRegistry; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; + +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; import org.junit.Before; import org.junit.Test; diff --git a/core/tests/packagemanagertests/Android.mk b/core/tests/packagemanagertests/Android.mk index f95231f1d397..8c00d14ce856 100644 --- a/core/tests/packagemanagertests/Android.mk +++ b/core/tests/packagemanagertests/Android.mk @@ -9,7 +9,7 @@ LOCAL_SRC_FILES := \ $(call all-java-files-under, src) LOCAL_STATIC_JAVA_LIBRARIES := \ - android-support-test \ + androidx.test.rules \ frameworks-base-testutils \ mockito-target-minus-junit4 diff --git a/core/tests/packagemanagertests/AndroidManifest.xml b/core/tests/packagemanagertests/AndroidManifest.xml index 8f490083b16e..ee4a775ac97f 100644 --- a/core/tests/packagemanagertests/AndroidManifest.xml +++ b/core/tests/packagemanagertests/AndroidManifest.xml @@ -24,7 +24,7 @@ </application> <instrumentation - android:name="android.support.test.runner.AndroidJUnitRunner" + android:name="androidx.test.runner.AndroidJUnitRunner" android:targetPackage="com.android.frameworks.coretests.packagemanager" android:label="Frameworks PackageManager Core Tests" /> diff --git a/core/tests/packagemanagertests/src/android/content/pm/KernelPackageMappingTests.java b/core/tests/packagemanagertests/src/android/content/pm/KernelPackageMappingTests.java index 4e0f2a8fe060..c5c9700e2316 100644 --- a/core/tests/packagemanagertests/src/android/content/pm/KernelPackageMappingTests.java +++ b/core/tests/packagemanagertests/src/android/content/pm/KernelPackageMappingTests.java @@ -22,10 +22,11 @@ import android.content.Context; import android.os.FileUtils; import android.os.ServiceManager; import android.os.UserManager; -import android.support.test.InstrumentationRegistry; -import android.support.test.runner.AndroidJUnit4; import android.util.Log; +import androidx.test.InstrumentationRegistry; +import androidx.test.runner.AndroidJUnit4; + import org.junit.After; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/core/tests/privacytests/Android.mk b/core/tests/privacytests/Android.mk index 3c1526b7dc48..7765977c03e8 100644 --- a/core/tests/privacytests/Android.mk +++ b/core/tests/privacytests/Android.mk @@ -8,7 +8,7 @@ LOCAL_MODULE_TAGS := tests LOCAL_SRC_FILES := \ $(call all-java-files-under, src) -LOCAL_STATIC_JAVA_LIBRARIES := junit rappor-tests android-support-test truth-prebuilt +LOCAL_STATIC_JAVA_LIBRARIES := junit rappor-tests androidx.test.rules truth-prebuilt LOCAL_JAVA_LIBRARIES := android.test.runner LOCAL_PACKAGE_NAME := FrameworksPrivacyLibraryTests diff --git a/core/tests/privacytests/AndroidManifest.xml b/core/tests/privacytests/AndroidManifest.xml index a0e52814bdcd..3c826144aa57 100644 --- a/core/tests/privacytests/AndroidManifest.xml +++ b/core/tests/privacytests/AndroidManifest.xml @@ -22,7 +22,7 @@ </application> <instrumentation - android:name="android.support.test.runner.AndroidJUnitRunner" + android:name="androidx.test.runner.AndroidJUnitRunner" android:targetPackage="com.android.frameworks.coretests.privacy" android:label="Frameworks Privacy Library Tests" /> diff --git a/core/tests/privacytests/src/android/privacy/LongitudinalReportingEncoderTest.java b/core/tests/privacytests/src/android/privacy/LongitudinalReportingEncoderTest.java index c88a722c026b..18928ebd6461 100644 --- a/core/tests/privacytests/src/android/privacy/LongitudinalReportingEncoderTest.java +++ b/core/tests/privacytests/src/android/privacy/LongitudinalReportingEncoderTest.java @@ -17,21 +17,20 @@ package android.privacy; import static com.google.common.truth.Truth.assertThat; + import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import android.privacy.internal.longitudinalreporting.LongitudinalReportingConfig; import android.privacy.internal.longitudinalreporting.LongitudinalReportingEncoder; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; import java.nio.ByteBuffer; -import java.nio.ByteOrder; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; diff --git a/core/tests/privacytests/src/android/privacy/RapporEncoderTest.java b/core/tests/privacytests/src/android/privacy/RapporEncoderTest.java index 71bd8f1c7bf0..4a353505f1da 100644 --- a/core/tests/privacytests/src/android/privacy/RapporEncoderTest.java +++ b/core/tests/privacytests/src/android/privacy/RapporEncoderTest.java @@ -22,8 +22,9 @@ import static org.junit.Assert.assertTrue; import android.privacy.internal.rappor.RapporConfig; import android.privacy.internal.rappor.RapporEncoder; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/core/tests/utiltests/Android.mk b/core/tests/utiltests/Android.mk index 5c60c8184753..343c07af51df 100644 --- a/core/tests/utiltests/Android.mk +++ b/core/tests/utiltests/Android.mk @@ -15,7 +15,7 @@ LOCAL_SRC_FILES += src/android/util/IRemoteMemoryIntArray.aidl LOCAL_JNI_SHARED_LIBRARIES := libmemoryintarraytest libcutils libc++ LOCAL_STATIC_JAVA_LIBRARIES := \ - android-support-test \ + androidx.test.rules \ frameworks-base-testutils \ mockito-target-minus-junit4 \ diff --git a/core/tests/utiltests/AndroidManifest.xml b/core/tests/utiltests/AndroidManifest.xml index 8db81ca4b3b6..4ef4b1fe9120 100644 --- a/core/tests/utiltests/AndroidManifest.xml +++ b/core/tests/utiltests/AndroidManifest.xml @@ -51,7 +51,7 @@ </application> <instrumentation - android:name="android.support.test.runner.AndroidJUnitRunner" + android:name="androidx.test.runner.AndroidJUnitRunner" android:targetPackage="com.android.frameworks.utiltests" android:label="Frameworks Utility Tests" /> diff --git a/core/tests/utiltests/runtests.sh b/core/tests/utiltests/runtests.sh index 853119f62334..3b46cbbef4e9 100755 --- a/core/tests/utiltests/runtests.sh +++ b/core/tests/utiltests/runtests.sh @@ -21,4 +21,4 @@ adb wait-for-device adb install -r -g "$OUT/data/app/FrameworksUtilTests/FrameworksUtilTests.apk" -adb shell am instrument -w "$@" 'com.android.frameworks.utiltests/android.support.test.runner.AndroidJUnitRunner' +adb shell am instrument -w "$@" 'com.android.frameworks.utiltests/androidx.test.runner.AndroidJUnitRunner' diff --git a/core/tests/utiltests/src/android/util/IntArrayTest.java b/core/tests/utiltests/src/android/util/IntArrayTest.java index a6120a1e8142..a76c640db74a 100644 --- a/core/tests/utiltests/src/android/util/IntArrayTest.java +++ b/core/tests/utiltests/src/android/util/IntArrayTest.java @@ -19,8 +19,9 @@ package android.util; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + import org.junit.Test; import org.junit.runner.RunWith; diff --git a/core/tests/utiltests/src/android/util/LongArrayTest.java b/core/tests/utiltests/src/android/util/LongArrayTest.java index a7afcbd81fdf..a9a168b1bf6b 100644 --- a/core/tests/utiltests/src/android/util/LongArrayTest.java +++ b/core/tests/utiltests/src/android/util/LongArrayTest.java @@ -19,8 +19,9 @@ package android.util; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + import org.junit.Test; import org.junit.runner.RunWith; diff --git a/core/tests/utiltests/src/android/util/MemoryIntArrayTest.java b/core/tests/utiltests/src/android/util/MemoryIntArrayTest.java index 24b33effdb71..2daefe74eb12 100644 --- a/core/tests/utiltests/src/android/util/MemoryIntArrayTest.java +++ b/core/tests/utiltests/src/android/util/MemoryIntArrayTest.java @@ -24,8 +24,11 @@ import static org.junit.Assert.fail; import android.os.Parcel; import android.os.ParcelFileDescriptor; -import android.support.test.runner.AndroidJUnit4; + +import androidx.test.runner.AndroidJUnit4; + import libcore.io.IoUtils; + import org.junit.Test; import org.junit.runner.RunWith; diff --git a/core/tests/utiltests/src/android/util/RemoteIntArray.java b/core/tests/utiltests/src/android/util/RemoteIntArray.java index 11d0888179d1..4a3a01e61b7d 100644 --- a/core/tests/utiltests/src/android/util/RemoteIntArray.java +++ b/core/tests/utiltests/src/android/util/RemoteIntArray.java @@ -24,7 +24,8 @@ import android.os.Build; import android.os.IBinder; import android.os.RemoteException; import android.os.SystemClock; -import android.support.test.InstrumentationRegistry; + +import androidx.test.InstrumentationRegistry; import java.io.Closeable; import java.io.IOException; diff --git a/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java b/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java index b18ee171eb0c..03cf3eb6a2b9 100644 --- a/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java +++ b/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java @@ -29,11 +29,12 @@ import android.content.ContextWrapper; import android.content.pm.UserInfo; import android.os.UserManager; import android.provider.Settings; -import android.support.test.InstrumentationRegistry; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; - import android.test.mock.MockContentResolver; + +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + import com.android.internal.util.test.FakeSettingsProvider; import com.android.internal.widget.ILockSettings; import com.android.internal.widget.LockPatternUtils; diff --git a/data/etc/Android.bp b/data/etc/Android.bp new file mode 100644 index 000000000000..25dabad6d34c --- /dev/null +++ b/data/etc/Android.bp @@ -0,0 +1,64 @@ +// 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. + + +// Sysconfig files + +prebuilt_etc { + name: "framework-sysconfig.xml", + sub_dir: "sysconfig", + src: "framework-sysconfig.xml", +} + +prebuilt_etc { + name: "hiddenapi-package-whitelist.xml", + sub_dir: "sysconfig", + src: "hiddenapi-package-whitelist.xml", +} + +// Privapp permission whitelist files + +prebuilt_etc { + name: "platform.xml", + sub_dir: "permissions", + src: "platform.xml", +} + +prebuilt_etc { + name: "privapp-permissions-platform.xml", + sub_dir: "permissions", + src: "privapp-permissions-platform.xml", +} + +prebuilt_etc { + name: "privapp_whitelist_com.android.settings", + product_specific: true, + sub_dir: "permissions", + src: "com.android.settings.xml", + filename_from_src: true, +} + +prebuilt_etc { + name: "privapp_whitelist_com.android.systemui", + product_specific: true, + sub_dir: "permissions", + src: "com.android.systemui.xml", + filename_from_src: true, +} + +prebuilt_etc { + name: "com.android.timezone.updater.xml", + sub_dir: "permissions", + src: "com.android.timezone.updater.xml", +} diff --git a/data/etc/Android.mk b/data/etc/Android.mk deleted file mode 100644 index 61ef426f510a..000000000000 --- a/data/etc/Android.mk +++ /dev/null @@ -1,76 +0,0 @@ -# -# Copyright (C) 2008 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 := $(my-dir) - -######################## -include $(CLEAR_VARS) -LOCAL_MODULE := framework-sysconfig.xml -LOCAL_MODULE_CLASS := ETC -LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)/sysconfig -LOCAL_SRC_FILES := $(LOCAL_MODULE) -include $(BUILD_PREBUILT) - -######################## -include $(CLEAR_VARS) -LOCAL_MODULE := platform.xml -LOCAL_MODULE_CLASS := ETC -LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)/permissions -LOCAL_SRC_FILES := $(LOCAL_MODULE) -include $(BUILD_PREBUILT) - -######################## -include $(CLEAR_VARS) -LOCAL_MODULE := privapp-permissions-platform.xml -LOCAL_MODULE_CLASS := ETC -LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)/permissions -LOCAL_SRC_FILES := $(LOCAL_MODULE) -include $(BUILD_PREBUILT) - -######################## -include $(CLEAR_VARS) -LOCAL_MODULE := hiddenapi-package-whitelist.xml -LOCAL_MODULE_CLASS := ETC -LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)/sysconfig -LOCAL_SRC_FILES := $(LOCAL_MODULE) -include $(BUILD_PREBUILT) - -######################## -include $(CLEAR_VARS) -LOCAL_MODULE := privapp_whitelist_com.android.settings -LOCAL_MODULE_CLASS := ETC -LOCAL_MODULE_RELATIVE_PATH := permissions -LOCAL_MODULE_STEM := com.android.settings.xml -LOCAL_SRC_FILES := com.android.settings.xml -include $(BUILD_PREBUILT) - -######################## -include $(CLEAR_VARS) -LOCAL_MODULE := privapp_whitelist_com.android.systemui -LOCAL_MODULE_CLASS := ETC -LOCAL_MODULE_RELATIVE_PATH := permissions -LOCAL_MODULE_STEM := com.android.systemui.xml -LOCAL_SRC_FILES := com.android.systemui.xml -include $(BUILD_PREBUILT) - - -######################## -include $(CLEAR_VARS) -LOCAL_MODULE := com.android.timezone.updater.xml -LOCAL_MODULE_CLASS := ETC -LOCAL_MODULE_RELATIVE_PATH := permissions -LOCAL_SRC_FILES := $(LOCAL_MODULE) -include $(BUILD_PREBUILT) diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index af570b39925b..c21642559bb9 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -297,6 +297,8 @@ applications that come with the platform <permission name="android.permission.POWER_SAVER" /> <permission name="android.permission.READ_FRAME_BUFFER"/> <permission name="android.permission.READ_LOWPAN_CREDENTIAL"/> + <!-- Needed for test only --> + <permission name="android.permission.READ_PRECISE_PHONE_STATE" /> <permission name="android.permission.READ_PRIVILEGED_PHONE_STATE"/> <permission name="android.permission.REAL_GET_TASKS"/> <permission name="android.permission.READ_PRIVILEGED_PHONE_STATE"/> diff --git a/graphics/java/android/graphics/RecordingCanvas.java b/graphics/java/android/graphics/RecordingCanvas.java index 67ad4045868e..515532ffda52 100644 --- a/graphics/java/android/graphics/RecordingCanvas.java +++ b/graphics/java/android/graphics/RecordingCanvas.java @@ -189,6 +189,14 @@ public final class RecordingCanvas extends DisplayListCanvas { nCallDrawGLFunction(mNativeCanvasWrapper, drawGLFunctor, releasedCallback); } + /** + * Calls the provided functor that was created via WebViewFunctor_create() + * @hide + */ + public void drawWebViewFunctor(int functor) { + nDrawWebViewFunctor(mNativeCanvasWrapper, functor); + } + /////////////////////////////////////////////////////////////////////////// // Display list /////////////////////////////////////////////////////////////////////////// @@ -303,4 +311,6 @@ public final class RecordingCanvas extends DisplayListCanvas { @CriticalNative private static native void nDrawRoundRect(long renderer, long propLeft, long propTop, long propRight, long propBottom, long propRx, long propRy, long propPaint); + @CriticalNative + private static native void nDrawWebViewFunctor(long canvas, int functor); } diff --git a/graphics/java/android/graphics/RenderNode.java b/graphics/java/android/graphics/RenderNode.java index d6f08b92a648..3b1d44b44ed4 100644 --- a/graphics/java/android/graphics/RenderNode.java +++ b/graphics/java/android/graphics/RenderNode.java @@ -446,7 +446,21 @@ public final class RenderNode { } /** - * Sets the clip bounds of the RenderNode. + * Gets whether or not a compositing layer is forced to be used. The default & recommended + * is false, as it is typically faster to avoid using compositing layers. + * See {@link #setUseCompositingLayer(boolean, Paint)}. + * + * @return true if a compositing layer is forced, false otherwise + */ + public boolean getUseCompositingLayer() { + return nGetLayerType(mNativeRenderNode) != 0; + } + + /** + * Sets the clip bounds of the RenderNode. If null, the clip bounds is removed from the + * RenderNode. If non-null, the RenderNode will be clipped to this rect. If + * {@link #setClipToBounds(boolean)} is true, then the RenderNode will be clipped to the + * intersection of this rectangle and the bounds of the render node. * * @param rect the bounds to clip to. If null, the clip bounds are reset * @return True if the clip bounds changed, false otherwise @@ -460,16 +474,30 @@ public final class RenderNode { } /** - * Set whether the Render node should clip itself to its bounds. This property is controlled by - * the view's parent. + * Set whether the Render node should clip itself to its bounds. This defaults to true, + * and is useful to the renderer in enable quick-rejection of chunks of the tree as well as + * better partial invalidation support. Clipping can be further restricted or controlled + * through the combination of this property as well as {@link #setClipBounds(Rect)}, which + * allows for a different clipping rectangle to be used in addition to or instead of the + * {@link #setLeftTopRightBottom(int, int, int, int)} or the RenderNode. * - * @param clipToBounds true if the display list should clip to its bounds + * @param clipToBounds true if the display list should clip to its bounds, false otherwise. */ public boolean setClipToBounds(boolean clipToBounds) { return nSetClipToBounds(mNativeRenderNode, clipToBounds); } /** + * Returns whether or not the RenderNode is clipping to its bounds. See + * {@link #setClipToBounds(boolean)} and {@link #setLeftTopRightBottom(int, int, int, int)} + * + * @return true if the render node clips to its bounds, false otherwise. + */ + public boolean getClipToBounds() { + return nGetClipToBounds(mNativeRenderNode); + } + + /** * Sets whether the RenderNode should be drawn immediately after the * closest ancestor RenderNode containing a projection receiver. * @@ -1339,12 +1367,18 @@ public final class RenderNode { private static native boolean nSetLayerType(long renderNode, int layerType); @CriticalNative + private static native int nGetLayerType(long renderNode); + + @CriticalNative private static native boolean nSetLayerPaint(long renderNode, long paint); @CriticalNative private static native boolean nSetClipToBounds(long renderNode, boolean clipToBounds); @CriticalNative + private static native boolean nGetClipToBounds(long renderNode); + + @CriticalNative private static native boolean nSetClipBounds(long renderNode, int left, int top, int right, int bottom); diff --git a/libs/androidfw/Android.bp b/libs/androidfw/Android.bp index 98af3eb05391..eeaefc5b157c 100644 --- a/libs/androidfw/Android.bp +++ b/libs/androidfw/Android.bp @@ -167,6 +167,7 @@ cc_test { }, }, data: ["tests/data/**/*.apk"], + test_suites: ["device-tests"], } cc_benchmark { diff --git a/libs/androidfw/LoadedArsc.cpp b/libs/androidfw/LoadedArsc.cpp index c20c720eadbb..5a267804ddf1 100644 --- a/libs/androidfw/LoadedArsc.cpp +++ b/libs/androidfw/LoadedArsc.cpp @@ -623,7 +623,7 @@ std::unique_ptr<const LoadedPackage> LoadedPackage::Load(const Chunk& chunk, } // Add the pairing of overlayable properties to resource ids to the package - OverlayableInfo overlayable_info; + OverlayableInfo overlayable_info{}; overlayable_info.policy_flags = policy_header->policy_flags; loaded_package->overlayable_infos_.push_back(std::make_pair(overlayable_info, ids)); break; diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp index 6585bfc929ba..7e69e3a8a73f 100644 --- a/libs/hwui/Android.bp +++ b/libs/hwui/Android.bp @@ -9,6 +9,8 @@ cc_defaults { "hwui_lto", ], + cpp_std: "experimental", + cflags: [ "-DEGL_EGLEXT_PROTOTYPES", "-DGL_GLEXT_PROTOTYPES", @@ -226,6 +228,7 @@ cc_defaults { "RenderProperties.cpp", "SkiaCanvas.cpp", "TreeInfo.cpp", + "WebViewFunctorManager.cpp", "VectorDrawable.cpp", "protos/graphicsstats.proto", ], @@ -330,6 +333,7 @@ cc_test { "tests/unit/TypefaceTests.cpp", "tests/unit/VectorDrawableTests.cpp", "tests/unit/VectorDrawableAtlasTests.cpp", + "tests/unit/WebViewFunctorManagerTests.cpp", ], } diff --git a/libs/hwui/HardwareBitmapUploader.cpp b/libs/hwui/HardwareBitmapUploader.cpp index a97c12cad9fd..b9860ada18fc 100644 --- a/libs/hwui/HardwareBitmapUploader.cpp +++ b/libs/hwui/HardwareBitmapUploader.cpp @@ -253,7 +253,8 @@ sk_sp<Bitmap> HardwareBitmapUploader::allocateHardwareBitmap(const SkBitmap& sou eglDestroySyncKHR(display, fence); } - return sk_sp<Bitmap>(new Bitmap(buffer.get(), bitmap.info(), Bitmap::computePalette(bitmap))); + return Bitmap::createFrom(buffer.get(), bitmap.refColorSpace(), bitmap.alphaType(), + Bitmap::computePalette(bitmap)); } } // namespace android::uirenderer diff --git a/libs/hwui/RecordingCanvas.h b/libs/hwui/RecordingCanvas.h index 35cf707665cb..de8777b4e79a 100644 --- a/libs/hwui/RecordingCanvas.h +++ b/libs/hwui/RecordingCanvas.h @@ -65,6 +65,7 @@ public: void applyColorTransform(ColorTransform transform); bool hasText() const { return mHasText; } + size_t usedSize() const { return fUsed; } private: friend class RecordingCanvas; diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp index 00ce28ad196f..1ff7ffe6bf87 100644 --- a/libs/hwui/RenderNode.cpp +++ b/libs/hwui/RenderNode.cpp @@ -288,7 +288,10 @@ void RenderNode::syncDisplayList(TreeObserver& observer, TreeInfo* info) { mDisplayList = mStagingDisplayList; mStagingDisplayList = nullptr; if (mDisplayList) { - mDisplayList->syncContents(); + WebViewSyncData syncData { + .applyForceDark = info && !info->disableForceDark + }; + mDisplayList->syncContents(syncData); handleForceDark(info); } } diff --git a/libs/hwui/RenderProperties.h b/libs/hwui/RenderProperties.h index 04379ae68a0d..ddb7e4e4ce74 100644 --- a/libs/hwui/RenderProperties.h +++ b/libs/hwui/RenderProperties.h @@ -98,15 +98,15 @@ public: LayerProperties& operator=(const LayerProperties& other); + // Strongly recommend using effectiveLayerType instead + LayerType type() const { return mType; } + private: LayerProperties(); ~LayerProperties(); void reset(); bool setColorFilter(SkColorFilter* filter); - // Private since external users should go through properties().effectiveLayerType() - LayerType type() const { return mType; } - friend class RenderProperties; LayerType mType = LayerType::None; diff --git a/libs/hwui/WebViewFunctorManager.cpp b/libs/hwui/WebViewFunctorManager.cpp new file mode 100644 index 000000000000..20e77b453706 --- /dev/null +++ b/libs/hwui/WebViewFunctorManager.cpp @@ -0,0 +1,167 @@ +/* + * 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 "WebViewFunctorManager.h" + +#include <private/hwui/WebViewFunctor.h> +#include "Properties.h" + +#include <log/log.h> +#include <utils/Trace.h> +#include <atomic> + +namespace android::uirenderer { + +RenderMode WebViewFunctor_queryPlatformRenderMode() { + auto pipelineType = Properties::getRenderPipelineType(); + switch (pipelineType) { + case RenderPipelineType::SkiaGL: + return RenderMode::OpenGL_ES; + case RenderPipelineType::SkiaVulkan: + return RenderMode::Vulkan; + default: + LOG_ALWAYS_FATAL("Unknown render pipeline type: %d", (int)pipelineType); + } +} + +int WebViewFunctor_create(const WebViewFunctorCallbacks& prototype, RenderMode functorMode) { + if (functorMode != RenderMode::OpenGL_ES && functorMode != RenderMode::Vulkan) { + ALOGW("Unknown rendermode %d", (int)functorMode); + return -1; + } + if (functorMode == RenderMode::Vulkan && + WebViewFunctor_queryPlatformRenderMode() != RenderMode::Vulkan) { + ALOGW("Unable to map from GLES platform to a vulkan functor"); + return -1; + } + return WebViewFunctorManager::instance().createFunctor(prototype, functorMode); +} + +void WebViewFunctor_release(int functor) { + WebViewFunctorManager::instance().releaseFunctor(functor); +} + +static std::atomic_int sNextId{1}; + +WebViewFunctor::WebViewFunctor(const WebViewFunctorCallbacks& callbacks, RenderMode functorMode) { + mFunctor = sNextId++; + mCallbacks = callbacks; + mMode = functorMode; +} + +WebViewFunctor::~WebViewFunctor() { + destroyContext(); + + ATRACE_NAME("WebViewFunctor::onDestroy"); + mCallbacks.onDestroyed(mFunctor); +} + +void WebViewFunctor::sync(const WebViewSyncData& syncData) const { + ATRACE_NAME("WebViewFunctor::sync"); + mCallbacks.onSync(mFunctor, syncData); +} + +void WebViewFunctor::drawGl(const DrawGlInfo& drawInfo) { + ATRACE_NAME("WebViewFunctor::drawGl"); + if (!mHasContext) { + mHasContext = true; + } + mCallbacks.gles.draw(mFunctor, drawInfo); +} + +void WebViewFunctor::destroyContext() { + if (mHasContext) { + mHasContext = false; + ATRACE_NAME("WebViewFunctor::onContextDestroyed"); + mCallbacks.onContextDestroyed(mFunctor); + } +} + +WebViewFunctorManager& WebViewFunctorManager::instance() { + static WebViewFunctorManager sInstance; + return sInstance; +} + +int WebViewFunctorManager::createFunctor(const WebViewFunctorCallbacks& callbacks, + RenderMode functorMode) { + auto object = std::make_unique<WebViewFunctor>(callbacks, functorMode); + int id = object->id(); + auto handle = object->createHandle(); + { + std::lock_guard _lock{mLock}; + mActiveFunctors.push_back(std::move(handle)); + mFunctors.push_back(std::move(object)); + } + return id; +} + +void WebViewFunctorManager::releaseFunctor(int functor) { + sp<WebViewFunctor::Handle> toRelease; + { + std::lock_guard _lock{mLock}; + for (auto iter = mActiveFunctors.begin(); iter != mActiveFunctors.end(); iter++) { + if ((*iter)->id() == functor) { + toRelease = std::move(*iter); + mActiveFunctors.erase(iter); + break; + } + } + } +} + +void WebViewFunctorManager::onContextDestroyed() { + // WARNING: SKETCHY + // Because we know that we always remove from mFunctors on RenderThread, the same + // thread that always invokes onContextDestroyed, we know that the functor pointers + // will remain valid without the lock held. + // However, we won't block new functors from being added in the meantime. + mLock.lock(); + const size_t size = mFunctors.size(); + WebViewFunctor* toDestroyContext[size]; + for (size_t i = 0; i < size; i++) { + toDestroyContext[i] = mFunctors[i].get(); + } + mLock.unlock(); + for (size_t i = 0; i < size; i++) { + toDestroyContext[i]->destroyContext(); + } +} + +void WebViewFunctorManager::destroyFunctor(int functor) { + std::unique_ptr<WebViewFunctor> toRelease; + { + std::lock_guard _lock{mLock}; + for (auto iter = mFunctors.begin(); iter != mFunctors.end(); iter++) { + if ((*iter)->id() == functor) { + toRelease = std::move(*iter); + mFunctors.erase(iter); + break; + } + } + } +} + +sp<WebViewFunctor::Handle> WebViewFunctorManager::handleFor(int functor) { + std::lock_guard _lock{mLock}; + for (auto& iter : mActiveFunctors) { + if (iter->id() == functor) { + return iter; + } + } + return nullptr; +} + +} // namespace android::uirenderer
\ No newline at end of file diff --git a/libs/hwui/WebViewFunctorManager.h b/libs/hwui/WebViewFunctorManager.h new file mode 100644 index 000000000000..2a621dd411e3 --- /dev/null +++ b/libs/hwui/WebViewFunctorManager.h @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include <private/hwui/WebViewFunctor.h> +#include <renderthread/RenderProxy.h> + +#include <utils/LightRefBase.h> +#include <mutex> +#include <vector> + +namespace android::uirenderer { + +class WebViewFunctorManager; + +class WebViewFunctor { +public: + WebViewFunctor(const WebViewFunctorCallbacks& callbacks, RenderMode functorMode); + ~WebViewFunctor(); + + class Handle : public LightRefBase<Handle> { + public: + ~Handle() { renderthread::RenderProxy::destroyFunctor(id()); } + + int id() const { return mReference.id(); } + + void sync(const WebViewSyncData& syncData) const { mReference.sync(syncData); } + + void drawGl(const DrawGlInfo& drawInfo) const { mReference.drawGl(drawInfo); } + + private: + friend class WebViewFunctor; + + Handle(WebViewFunctor& ref) : mReference(ref) {} + + WebViewFunctor& mReference; + }; + + int id() const { return mFunctor; } + void sync(const WebViewSyncData& syncData) const; + void drawGl(const DrawGlInfo& drawInfo); + void destroyContext(); + + sp<Handle> createHandle() { + LOG_ALWAYS_FATAL_IF(mCreatedHandle); + mCreatedHandle = true; + return sp<Handle>{new Handle(*this)}; + } + +private: + WebViewFunctorCallbacks mCallbacks; + int mFunctor; + RenderMode mMode; + bool mHasContext = false; + bool mCreatedHandle = false; +}; + +class WebViewFunctorManager { +public: + static WebViewFunctorManager& instance(); + + int createFunctor(const WebViewFunctorCallbacks& callbacks, RenderMode functorMode); + void releaseFunctor(int functor); + void onContextDestroyed(); + void destroyFunctor(int functor); + + sp<WebViewFunctor::Handle> handleFor(int functor); + +private: + WebViewFunctorManager() = default; + ~WebViewFunctorManager() = default; + + std::mutex mLock; + std::vector<std::unique_ptr<WebViewFunctor>> mFunctors; + std::vector<sp<WebViewFunctor::Handle>> mActiveFunctors; +}; + +} // namespace android::uirenderer diff --git a/libs/hwui/hwui/Bitmap.cpp b/libs/hwui/hwui/Bitmap.cpp index 6c77f9ee1845..6e0258c9ecb2 100644 --- a/libs/hwui/hwui/Bitmap.cpp +++ b/libs/hwui/hwui/Bitmap.cpp @@ -75,20 +75,33 @@ sk_sp<Bitmap> Bitmap::allocateAshmemBitmap(SkBitmap* bitmap) { return allocateBitmap(bitmap, &Bitmap::allocateAshmemBitmap); } -static sk_sp<Bitmap> allocateHeapBitmap(size_t size, const SkImageInfo& info, size_t rowBytes) { - void* addr = calloc(size, 1); - if (!addr) { +sk_sp<Bitmap> Bitmap::allocateAshmemBitmap(size_t size, const SkImageInfo& info, size_t rowBytes) { + // Create new ashmem region with read/write priv + int fd = ashmem_create_region("bitmap", size); + if (fd < 0) { return nullptr; } - return sk_sp<Bitmap>(new Bitmap(addr, size, info, rowBytes)); + + void* addr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (addr == MAP_FAILED) { + close(fd); + return nullptr; + } + + if (ashmem_set_prot_region(fd, PROT_READ) < 0) { + munmap(addr, size); + close(fd); + return nullptr; + } + return sk_sp<Bitmap>(new Bitmap(addr, fd, size, info, rowBytes)); } -sk_sp<Bitmap> Bitmap::allocateHardwareBitmap(SkBitmap& bitmap) { +sk_sp<Bitmap> Bitmap::allocateHardwareBitmap(const SkBitmap& bitmap) { return uirenderer::HardwareBitmapUploader::allocateHardwareBitmap(bitmap); } sk_sp<Bitmap> Bitmap::allocateHeapBitmap(SkBitmap* bitmap) { - return allocateBitmap(bitmap, &android::allocateHeapBitmap); + return allocateBitmap(bitmap, &Bitmap::allocateHeapBitmap); } sk_sp<Bitmap> Bitmap::allocateHeapBitmap(const SkImageInfo& info) { @@ -97,28 +110,15 @@ sk_sp<Bitmap> Bitmap::allocateHeapBitmap(const SkImageInfo& info) { LOG_ALWAYS_FATAL("trying to allocate too large bitmap"); return nullptr; } - return android::allocateHeapBitmap(size, info, info.minRowBytes()); + return allocateHeapBitmap(size, info, info.minRowBytes()); } -sk_sp<Bitmap> Bitmap::allocateAshmemBitmap(size_t size, const SkImageInfo& info, size_t rowBytes) { - // Create new ashmem region with read/write priv - int fd = ashmem_create_region("bitmap", size); - if (fd < 0) { - return nullptr; - } - - void* addr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); - if (addr == MAP_FAILED) { - close(fd); - return nullptr; - } - - if (ashmem_set_prot_region(fd, PROT_READ) < 0) { - munmap(addr, size); - close(fd); +sk_sp<Bitmap> Bitmap::allocateHeapBitmap(size_t size, const SkImageInfo& info, size_t rowBytes) { + void* addr = calloc(size, 1); + if (!addr) { return nullptr; } - return sk_sp<Bitmap>(new Bitmap(addr, fd, size, info, rowBytes)); + return sk_sp<Bitmap>(new Bitmap(addr, size, info, rowBytes)); } void FreePixelRef(void* addr, void* context) { @@ -132,17 +132,38 @@ sk_sp<Bitmap> Bitmap::createFrom(const SkImageInfo& info, SkPixelRef& pixelRef) pixelRef.rowBytes())); } -sk_sp<Bitmap> Bitmap::createFrom(sp<GraphicBuffer> graphicBuffer) { - return createFrom(graphicBuffer, SkColorSpace::MakeSRGB()); -} -sk_sp<Bitmap> Bitmap::createFrom(sp<GraphicBuffer> graphicBuffer, sk_sp<SkColorSpace> colorSpace) { +sk_sp<Bitmap> Bitmap::createFrom(sp<GraphicBuffer> graphicBuffer, sk_sp<SkColorSpace> colorSpace, + SkAlphaType alphaType, BitmapPalette palette) { // As we will be effectively texture-sampling the buffer (using either EGL or Vulkan), we can - // view the colorspace as RGBA8888. + // view the format as RGBA8888. SkImageInfo info = SkImageInfo::Make(graphicBuffer->getWidth(), graphicBuffer->getHeight(), - kRGBA_8888_SkColorType, kPremul_SkAlphaType, - colorSpace); - return sk_sp<Bitmap>(new Bitmap(graphicBuffer.get(), info)); + kRGBA_8888_SkColorType, alphaType, colorSpace); + return sk_sp<Bitmap>(new Bitmap(graphicBuffer.get(), info, palette)); +} + +sk_sp<Bitmap> Bitmap::createFrom(const SkImageInfo& info, size_t rowBytes, int fd, void* addr, + size_t size, bool readOnly) { + if (info.colorType() == kUnknown_SkColorType) { + LOG_ALWAYS_FATAL("unknown bitmap configuration"); + return nullptr; + } + + if (!addr) { + // Map existing ashmem region if not already mapped. + int flags = readOnly ? (PROT_READ) : (PROT_READ | PROT_WRITE); + size = ashmem_get_size_region(fd); + addr = mmap(NULL, size, flags, MAP_SHARED, fd, 0); + if (addr == MAP_FAILED) { + return nullptr; + } + } + + sk_sp<Bitmap> bitmap(new Bitmap(addr, fd, size, info, rowBytes)); + if (readOnly) { + bitmap->setImmutable(); + } + return bitmap; } void Bitmap::setColorSpace(sk_sp<SkColorSpace> colorSpace) { diff --git a/libs/hwui/hwui/Bitmap.h b/libs/hwui/hwui/Bitmap.h index d446377ec1d9..2138040d9690 100644 --- a/libs/hwui/hwui/Bitmap.h +++ b/libs/hwui/hwui/Bitmap.h @@ -54,28 +54,31 @@ typedef void (*FreeFunc)(void* addr, void* context); class ANDROID_API Bitmap : public SkPixelRef { public: + /* The allocate factories not only construct the Bitmap object but also allocate the + * backing store whose type is determined by the specific method that is called. + * + * The factories that accept SkBitmap* as a param will modify those params by + * installing the returned bitmap as their SkPixelRef. + * + * The factories that accept const SkBitmap& as a param will copy the contents of the + * provided bitmap into the newly allocated buffer. + */ + static sk_sp<Bitmap> allocateAshmemBitmap(SkBitmap* bitmap); + static sk_sp<Bitmap> allocateHardwareBitmap(const SkBitmap& bitmap); static sk_sp<Bitmap> allocateHeapBitmap(SkBitmap* bitmap); static sk_sp<Bitmap> allocateHeapBitmap(const SkImageInfo& info); - static sk_sp<Bitmap> allocateHardwareBitmap(SkBitmap& bitmap); - - static sk_sp<Bitmap> allocateAshmemBitmap(SkBitmap* bitmap); - static sk_sp<Bitmap> allocateAshmemBitmap(size_t allocSize, const SkImageInfo& info, - size_t rowBytes); - - static sk_sp<Bitmap> createFrom(sp<GraphicBuffer> graphicBuffer); + /* The createFrom factories construct a new Bitmap object by wrapping the already allocated + * memory that is provided as an input param. + */ static sk_sp<Bitmap> createFrom(sp<GraphicBuffer> graphicBuffer, - sk_sp<SkColorSpace> colorSpace); - + sk_sp<SkColorSpace> colorSpace, + SkAlphaType alphaType = kPremul_SkAlphaType, + BitmapPalette palette = BitmapPalette::Unknown); + static sk_sp<Bitmap> createFrom(const SkImageInfo& info, size_t rowBytes, int fd, void* addr, + size_t size, bool readOnly); static sk_sp<Bitmap> createFrom(const SkImageInfo&, SkPixelRef&); - Bitmap(void* address, size_t allocSize, const SkImageInfo& info, size_t rowBytes); - Bitmap(void* address, void* context, FreeFunc freeFunc, const SkImageInfo& info, - size_t rowBytes); - Bitmap(void* address, int fd, size_t mappedSize, const SkImageInfo& info, size_t rowBytes); - Bitmap(GraphicBuffer* buffer, const SkImageInfo& info, - BitmapPalette palette = BitmapPalette::Unknown); - int rowBytesAsPixels() const { return rowBytes() >> mInfo.shiftPerPixel(); } void reconfigure(const SkImageInfo& info, size_t rowBytes); @@ -123,6 +126,15 @@ public: } private: + static sk_sp<Bitmap> allocateAshmemBitmap(size_t size, const SkImageInfo& i, size_t rowBytes); + static sk_sp<Bitmap> allocateHeapBitmap(size_t size, const SkImageInfo& i, size_t rowBytes); + + Bitmap(void* address, size_t allocSize, const SkImageInfo& info, size_t rowBytes); + Bitmap(void* address, void* context, FreeFunc freeFunc, const SkImageInfo& info, + size_t rowBytes); + Bitmap(void* address, int fd, size_t mappedSize, const SkImageInfo& info, size_t rowBytes); + Bitmap(GraphicBuffer* buffer, const SkImageInfo& info, BitmapPalette palette); + virtual ~Bitmap(); void* getStorage() const; diff --git a/libs/hwui/hwui/Canvas.h b/libs/hwui/hwui/Canvas.h index a5f21d8e6d73..71814c365c31 100644 --- a/libs/hwui/hwui/Canvas.h +++ b/libs/hwui/hwui/Canvas.h @@ -178,6 +178,9 @@ public: virtual void drawRenderNode(uirenderer::RenderNode* renderNode) = 0; virtual void callDrawGLFunction(Functor* functor, uirenderer::GlFunctorLifecycleListener* listener) = 0; + virtual void drawWebViewFunctor(int /*functor*/) { + LOG_ALWAYS_FATAL("Not supported"); + } // ---------------------------------------------------------------------------- // Canvas state operations diff --git a/libs/hwui/pipeline/skia/FunctorDrawable.h b/libs/hwui/pipeline/skia/FunctorDrawable.h index af3a056864a7..cf2f93b95e71 100644 --- a/libs/hwui/pipeline/skia/FunctorDrawable.h +++ b/libs/hwui/pipeline/skia/FunctorDrawable.h @@ -21,7 +21,9 @@ #include <SkCanvas.h> #include <SkDrawable.h> +#include <WebViewFunctorManager.h> #include <utils/Functor.h> +#include <variant> namespace android { namespace uirenderer { @@ -35,17 +37,43 @@ namespace skiapipeline { class FunctorDrawable : public SkDrawable { public: FunctorDrawable(Functor* functor, GlFunctorLifecycleListener* listener, SkCanvas* canvas) - : mFunctor(functor), mListener(listener), mBounds(canvas->getLocalClipBounds()) {} + : mBounds(canvas->getLocalClipBounds()) + , mAnyFunctor(std::in_place_type<LegacyFunctor>, functor, listener) {} + + FunctorDrawable(int functor, SkCanvas* canvas) + : mBounds(canvas->getLocalClipBounds()) + , mAnyFunctor(std::in_place_type<NewFunctor>, functor) {} + virtual ~FunctorDrawable() {} - virtual void syncFunctor() const = 0; + virtual void syncFunctor(const WebViewSyncData& data) const { + if (mAnyFunctor.index() == 0) { + std::get<0>(mAnyFunctor).handle->sync(data); + } else { + (*(std::get<1>(mAnyFunctor).functor))(DrawGlInfo::kModeSync, nullptr); + } + } protected: virtual SkRect onGetBounds() override { return mBounds; } - Functor* mFunctor; - sp<GlFunctorLifecycleListener> mListener; const SkRect mBounds; + + struct LegacyFunctor { + explicit LegacyFunctor(Functor* functor, GlFunctorLifecycleListener* listener) + : functor(functor), listener(listener) {} + Functor* functor; + sp<GlFunctorLifecycleListener> listener; + }; + + struct NewFunctor { + explicit NewFunctor(int functor) { + handle = WebViewFunctorManager::instance().handleFor(functor); + } + sp<WebViewFunctor::Handle> handle; + }; + + std::variant<NewFunctor, LegacyFunctor> mAnyFunctor; }; } // namespace skiapipeline diff --git a/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp b/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp index 4a87e7502c6f..240efb41285c 100644 --- a/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp +++ b/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp @@ -17,29 +17,28 @@ #include "GLFunctorDrawable.h" #include <GrContext.h> #include <private/hwui/DrawGlInfo.h> +#include "FunctorDrawable.h" #include "GlFunctorLifecycleListener.h" +#include "GrBackendSurface.h" +#include "GrRenderTarget.h" +#include "GrRenderTargetContext.h" #include "RenderNode.h" #include "SkAndroidFrameworkUtils.h" #include "SkClipStack.h" #include "SkRect.h" -#include "GrBackendSurface.h" -#include "GrRenderTarget.h" -#include "GrRenderTargetContext.h" namespace android { namespace uirenderer { namespace skiapipeline { GLFunctorDrawable::~GLFunctorDrawable() { - if (mListener.get() != nullptr) { - mListener->onGlFunctorReleased(mFunctor); + if (auto lp = std::get_if<LegacyFunctor>(&mAnyFunctor)) { + if (lp->listener) { + lp->listener->onGlFunctorReleased(lp->functor); + } } } -void GLFunctorDrawable::syncFunctor() const { - (*mFunctor)(DrawGlInfo::kModeSync, nullptr); -} - static void setScissor(int viewportHeight, const SkIRect& clip) { SkASSERT(!clip.isEmpty()); // transform to Y-flipped GL space, and prevent negatives @@ -49,14 +48,14 @@ static void setScissor(int viewportHeight, const SkIRect& clip) { } static bool GetFboDetails(SkCanvas* canvas, GLuint* outFboID, SkISize* outFboSize) { - GrRenderTargetContext *renderTargetContext = + GrRenderTargetContext* renderTargetContext = canvas->internal_private_accessTopLayerRenderTargetContext(); if (!renderTargetContext) { ALOGW("Unable to extract renderTarget info from canvas; aborting GLFunctor draw"); return false; } - GrRenderTarget *renderTarget = renderTargetContext->accessRenderTarget(); + GrRenderTarget* renderTarget = renderTargetContext->accessRenderTarget(); if (!renderTarget) { ALOGW("Unable to extract renderTarget info from canvas; aborting GLFunctor draw"); return false; @@ -94,16 +93,16 @@ void GLFunctorDrawable::onDraw(SkCanvas* canvas) { sk_sp<SkSurface> tmpSurface; // we are in a state where there is an unclipped saveLayer if (fboID != 0 && !surfaceBounds.contains(clipBounds)) { - // create an offscreen layer and clear it - SkImageInfo surfaceInfo = canvas->imageInfo().makeWH(clipBounds.width(), clipBounds.height()); - tmpSurface = SkSurface::MakeRenderTarget(canvas->getGrContext(), SkBudgeted::kYes, - surfaceInfo); + SkImageInfo surfaceInfo = + canvas->imageInfo().makeWH(clipBounds.width(), clipBounds.height()); + tmpSurface = + SkSurface::MakeRenderTarget(canvas->getGrContext(), SkBudgeted::kYes, surfaceInfo); tmpSurface->getCanvas()->clear(SK_ColorTRANSPARENT); GrGLFramebufferInfo fboInfo; if (!tmpSurface->getBackendRenderTarget(SkSurface::kFlushWrite_BackendHandleAccess) - .getGLFramebufferInfo(&fboInfo)) { + .getGLFramebufferInfo(&fboInfo)) { ALOGW("Unable to extract renderTarget info from offscreen canvas; aborting GLFunctor"); return; } @@ -144,7 +143,7 @@ void GLFunctorDrawable::onDraw(SkCanvas* canvas) { bool clearStencilAfterFunctor = false; if (CC_UNLIKELY(clipRegion.isComplex())) { // clear the stencil - //TODO: move stencil clear and canvas flush to SkAndroidFrameworkUtils::clipWithStencil + // TODO: move stencil clear and canvas flush to SkAndroidFrameworkUtils::clipWithStencil glDisable(GL_SCISSOR_TEST); glStencilMask(0x1); glClearStencil(0); @@ -163,7 +162,7 @@ void GLFunctorDrawable::onDraw(SkCanvas* canvas) { // GL ops get inserted here if previous flush is missing, which could dirty the stencil bool stencilWritten = SkAndroidFrameworkUtils::clipWithStencil(tmpCanvas); - tmpCanvas->flush(); //need this flush for the single op that draws into the stencil + tmpCanvas->flush(); // need this flush for the single op that draws into the stencil // ensure that the framebuffer that the webview will render into is bound before after we // draw into the stencil @@ -188,7 +187,11 @@ void GLFunctorDrawable::onDraw(SkCanvas* canvas) { setScissor(info.height, clipRegion.getBounds()); } - (*mFunctor)(DrawGlInfo::kModeDraw, &info); + if (mAnyFunctor.index() == 0) { + std::get<0>(mAnyFunctor).handle->drawGl(info); + } else { + (*(std::get<1>(mAnyFunctor).functor))(DrawGlInfo::kModeDraw, &info); + } if (clearStencilAfterFunctor) { // clear stencil buffer as it may be used by Skia diff --git a/libs/hwui/pipeline/skia/GLFunctorDrawable.h b/libs/hwui/pipeline/skia/GLFunctorDrawable.h index 215979cba2e3..2ea4f67428bc 100644 --- a/libs/hwui/pipeline/skia/GLFunctorDrawable.h +++ b/libs/hwui/pipeline/skia/GLFunctorDrawable.h @@ -31,11 +31,9 @@ namespace skiapipeline { */ class GLFunctorDrawable : public FunctorDrawable { public: - GLFunctorDrawable(Functor* functor, GlFunctorLifecycleListener* listener, SkCanvas* canvas) - : FunctorDrawable(functor, listener, canvas) {} - virtual ~GLFunctorDrawable(); + using FunctorDrawable::FunctorDrawable; - void syncFunctor() const override; + virtual ~GLFunctorDrawable(); protected: void onDraw(SkCanvas* canvas) override; diff --git a/libs/hwui/pipeline/skia/LayerDrawable.cpp b/libs/hwui/pipeline/skia/LayerDrawable.cpp index f08ac17e4082..eed19420a78a 100644 --- a/libs/hwui/pipeline/skia/LayerDrawable.cpp +++ b/libs/hwui/pipeline/skia/LayerDrawable.cpp @@ -14,8 +14,8 @@ * limitations under the License. */ -#include <utils/MathUtils.h> #include "LayerDrawable.h" +#include <utils/MathUtils.h> #include "GrBackendSurface.h" #include "SkColorFilter.h" @@ -44,10 +44,9 @@ static bool shouldFilter(const SkMatrix& matrix) { if (!matrix.isScaleTranslate()) return true; // We only care about meaningful scale here - bool noScale = MathUtils::isOne(matrix.getScaleX()) - && MathUtils::isOne(matrix.getScaleY()); - bool pixelAligned = SkScalarIsInt(matrix.getTranslateX()) - && SkScalarIsInt(matrix.getTranslateY()); + bool noScale = MathUtils::isOne(matrix.getScaleX()) && MathUtils::isOne(matrix.getScaleY()); + bool pixelAligned = + SkScalarIsInt(matrix.getTranslateX()) && SkScalarIsInt(matrix.getTranslateY()); return !(noScale && pixelAligned); } @@ -120,11 +119,12 @@ bool LayerDrawable::DrawLayer(GrContext* context, SkCanvas* canvas, Layer* layer // Integer translation is defined as when src rect and dst rect align fractionally. // Skia TextureOp has the above logic build-in, but not NonAAFillRectOp. TextureOp works // only for SrcOver blending and without color filter (readback uses Src blending). - bool isIntegerTranslate = isBasicallyTranslate(totalMatrix) - && SkScalarFraction(skiaDestRect.fLeft + totalMatrix[SkMatrix::kMTransX]) - == SkScalarFraction(skiaSrcRect.fLeft) - && SkScalarFraction(skiaDestRect.fTop + totalMatrix[SkMatrix::kMTransY]) - == SkScalarFraction(skiaSrcRect.fTop); + bool isIntegerTranslate = + isBasicallyTranslate(totalMatrix) && + SkScalarFraction(skiaDestRect.fLeft + totalMatrix[SkMatrix::kMTransX]) == + SkScalarFraction(skiaSrcRect.fLeft) && + SkScalarFraction(skiaDestRect.fTop + totalMatrix[SkMatrix::kMTransY]) == + SkScalarFraction(skiaSrcRect.fTop); if (layer->getForceFilter() || !isIntegerTranslate) { paint.setFilterQuality(kLow_SkFilterQuality); } diff --git a/libs/hwui/pipeline/skia/LayerDrawable.h b/libs/hwui/pipeline/skia/LayerDrawable.h index 95dc6d0cf096..7cd515ae9fcb 100644 --- a/libs/hwui/pipeline/skia/LayerDrawable.h +++ b/libs/hwui/pipeline/skia/LayerDrawable.h @@ -32,8 +32,8 @@ class LayerDrawable : public SkDrawable { public: explicit LayerDrawable(DeferredLayerUpdater* layerUpdater) : mLayerUpdater(layerUpdater) {} - static bool DrawLayer(GrContext* context, SkCanvas* canvas, Layer* layer, - const SkRect* srcRect, const SkRect* dstRect, bool useLayerTransform); + static bool DrawLayer(GrContext* context, SkCanvas* canvas, Layer* layer, const SkRect* srcRect, + const SkRect* dstRect, bool useLayerTransform); protected: virtual SkRect onGetBounds() override { diff --git a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp index 4494cb05df10..df1537e2d824 100644 --- a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp +++ b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp @@ -127,6 +127,7 @@ public: mNode.markDrawEnd(mCanvas); } } + private: SkCanvas& mCanvas; RenderNode& mNode; @@ -140,7 +141,7 @@ void RenderNodeDrawable::forceDraw(SkCanvas* canvas) { // ensures that we paint the layer even if it is not currently visible in the // event that the properties change and it becomes visible. if ((mProjectedDisplayList == nullptr && !renderNode->isRenderable()) || - (renderNode->nothingToDraw() && mComposeLayer)) { + (renderNode->nothingToDraw() && mComposeLayer)) { return; } @@ -234,8 +235,8 @@ void RenderNodeDrawable::drawContent(SkCanvas* canvas) const { // we need to restrict the portion of the surface drawn to the size of the renderNode. SkASSERT(renderNode->getLayerSurface()->width() >= bounds.width()); SkASSERT(renderNode->getLayerSurface()->height() >= bounds.height()); - canvas->drawImageRect(renderNode->getLayerSurface()->makeImageSnapshot().get(), - bounds, bounds, &paint); + canvas->drawImageRect(renderNode->getLayerSurface()->makeImageSnapshot().get(), bounds, + bounds, &paint); if (!renderNode->getSkiaLayer()->hasRenderedSinceRepaint) { renderNode->getSkiaLayer()->hasRenderedSinceRepaint = true; diff --git a/libs/hwui/pipeline/skia/ShaderCache.cpp b/libs/hwui/pipeline/skia/ShaderCache.cpp index 073b4814305e..562a3b225e36 100644 --- a/libs/hwui/pipeline/skia/ShaderCache.cpp +++ b/libs/hwui/pipeline/skia/ShaderCache.cpp @@ -15,11 +15,11 @@ */ #include "ShaderCache.h" -#include <algorithm> #include <log/log.h> -#include <thread> -#include <array> #include <openssl/sha.h> +#include <algorithm> +#include <array> +#include <thread> #include "FileBlobCache.h" #include "Properties.h" #include "utils/TraceUtils.h" @@ -44,8 +44,7 @@ ShaderCache& ShaderCache::get() { } bool ShaderCache::validateCache(const void* identity, ssize_t size) { - if (nullptr == identity && size == 0) - return true; + if (nullptr == identity && size == 0) return true; if (nullptr == identity || size < 0) { if (CC_UNLIKELY(Properties::debugLevel & kDebugCaches)) { @@ -66,8 +65,7 @@ bool ShaderCache::validateCache(const void* identity, ssize_t size) { auto key = sIDKey; auto loaded = mBlobCache->get(&key, sizeof(key), hash.data(), hash.size()); - if (loaded && std::equal(hash.begin(), hash.end(), mIDHash.begin())) - return true; + if (loaded && std::equal(hash.begin(), hash.end(), mIDHash.begin())) return true; if (CC_UNLIKELY(Properties::debugLevel & kDebugCaches)) { ALOGW("ShaderCache::validateCache cache validation fails"); @@ -119,7 +117,7 @@ sk_sp<SkData> ShaderCache::load(const SkData& key) { int maxTries = 3; while (valueSize > mObservedBlobValueSize && maxTries > 0) { mObservedBlobValueSize = std::min(valueSize, maxValueSize); - void *newValueBuffer = realloc(valueBuffer, mObservedBlobValueSize); + void* newValueBuffer = realloc(valueBuffer, mObservedBlobValueSize); if (!newValueBuffer) { free(valueBuffer); return nullptr; @@ -133,7 +131,7 @@ sk_sp<SkData> ShaderCache::load(const SkData& key) { return nullptr; } if (valueSize > mObservedBlobValueSize) { - ALOGE("ShaderCache::load value size is too big %d", (int) valueSize); + ALOGE("ShaderCache::load value size is too big %d", (int)valueSize); free(valueBuffer); return nullptr; } diff --git a/libs/hwui/pipeline/skia/ShaderCache.h b/libs/hwui/pipeline/skia/ShaderCache.h index 82804cf93785..d41aadb269dd 100644 --- a/libs/hwui/pipeline/skia/ShaderCache.h +++ b/libs/hwui/pipeline/skia/ShaderCache.h @@ -16,12 +16,12 @@ #pragma once +#include <GrContextOptions.h> #include <cutils/compiler.h> #include <memory> #include <mutex> #include <string> #include <vector> -#include <GrContextOptions.h> namespace android { @@ -52,7 +52,7 @@ public: * the initialized state the load and store methods will return without * performing any cache operations. */ - virtual void initShaderDiskCache(const void *identity, ssize_t size); + virtual void initShaderDiskCache(const void* identity, ssize_t size); virtual void initShaderDiskCache() { initShaderDiskCache(nullptr, 0); } @@ -153,7 +153,7 @@ private: /** * "mObservedBlobValueSize" is the maximum value size observed by the cache reading function. */ - size_t mObservedBlobValueSize = 20*1024; + size_t mObservedBlobValueSize = 20 * 1024; /** * The time in seconds to wait before saving newly inserted cache entries. @@ -176,7 +176,7 @@ private: */ static constexpr uint8_t sIDKey = 0; - friend class ShaderCacheTestUtils; //used for unit testing + friend class ShaderCacheTestUtils; // used for unit testing }; } /* namespace skiapipeline */ diff --git a/libs/hwui/pipeline/skia/SkiaDisplayList.cpp b/libs/hwui/pipeline/skia/SkiaDisplayList.cpp index ac6f6a3f776d..230065c222a9 100644 --- a/libs/hwui/pipeline/skia/SkiaDisplayList.cpp +++ b/libs/hwui/pipeline/skia/SkiaDisplayList.cpp @@ -27,9 +27,9 @@ namespace android { namespace uirenderer { namespace skiapipeline { -void SkiaDisplayList::syncContents() { +void SkiaDisplayList::syncContents(const WebViewSyncData& data) { for (auto& functor : mChildFunctors) { - functor->syncFunctor(); + functor->syncFunctor(data); } for (auto& animatedImage : mAnimatedImages) { animatedImage->syncProperties(); diff --git a/libs/hwui/pipeline/skia/SkiaDisplayList.h b/libs/hwui/pipeline/skia/SkiaDisplayList.h index d7879e722a29..3219ad1deeff 100644 --- a/libs/hwui/pipeline/skia/SkiaDisplayList.h +++ b/libs/hwui/pipeline/skia/SkiaDisplayList.h @@ -16,11 +16,11 @@ #pragma once -#include "hwui/AnimatedImageDrawable.h" #include "FunctorDrawable.h" #include "RecordingCanvas.h" #include "RenderNodeDrawable.h" #include "TreeInfo.h" +#include "hwui/AnimatedImageDrawable.h" #include "utils/LinearAllocator.h" #include <deque> @@ -49,7 +49,7 @@ namespace skiapipeline { */ class SkiaDisplayList { public: - size_t getUsedSize() { return allocator.usedSize(); } + size_t getUsedSize() { return allocator.usedSize() + mDisplayList.usedSize(); } ~SkiaDisplayList() { /* Given that we are using a LinearStdAllocator to store some of the @@ -109,7 +109,7 @@ public: * NOTE: This function can be folded into RenderNode when we no longer need * to subclass from DisplayList */ - void syncContents(); + void syncContents(const WebViewSyncData& data); /** * ONLY to be called by RenderNode::prepareTree in order to prepare this diff --git a/libs/hwui/pipeline/skia/SkiaMemoryTracer.cpp b/libs/hwui/pipeline/skia/SkiaMemoryTracer.cpp index ea578cb3ec05..e48ecf490c56 100644 --- a/libs/hwui/pipeline/skia/SkiaMemoryTracer.cpp +++ b/libs/hwui/pipeline/skia/SkiaMemoryTracer.cpp @@ -21,16 +21,16 @@ namespace uirenderer { namespace skiapipeline { SkiaMemoryTracer::SkiaMemoryTracer(std::vector<ResourcePair> resourceMap, bool itemizeType) - : mResourceMap(resourceMap) - , mItemizeType(itemizeType) - , mTotalSize("bytes", 0) - , mPurgeableSize("bytes", 0) {} + : mResourceMap(resourceMap) + , mItemizeType(itemizeType) + , mTotalSize("bytes", 0) + , mPurgeableSize("bytes", 0) {} SkiaMemoryTracer::SkiaMemoryTracer(const char* categoryKey, bool itemizeType) - : mCategoryKey(categoryKey) - , mItemizeType(itemizeType) - , mTotalSize("bytes", 0) - , mPurgeableSize("bytes", 0) {} + : mCategoryKey(categoryKey) + , mItemizeType(itemizeType) + , mTotalSize("bytes", 0) + , mPurgeableSize("bytes", 0) {} const char* SkiaMemoryTracer::mapName(const char* resourceName) { for (auto& resource : mResourceMap) { @@ -42,7 +42,7 @@ const char* SkiaMemoryTracer::mapName(const char* resourceName) { } void SkiaMemoryTracer::processElement() { - if(!mCurrentElement.empty()) { + if (!mCurrentElement.empty()) { // Only count elements that contain "size", other values just provide metadata. auto sizeResult = mCurrentValues.find("size"); if (sizeResult != mCurrentValues.end()) { @@ -136,8 +136,8 @@ void SkiaMemoryTracer::logOutput(String8& log) { for (const auto& typedValue : namedItem.second) { TraceValue traceValue = convertUnits(typedValue.second); const char* entry = (traceValue.count > 1) ? "entries" : "entry"; - log.appendFormat(" %s: %.2f %s (%d %s)\n", typedValue.first, - traceValue.value, traceValue.units, traceValue.count, entry); + log.appendFormat(" %s: %.2f %s (%d %s)\n", typedValue.first, traceValue.value, + traceValue.units, traceValue.count, entry); } } else { auto result = namedItem.second.find("size"); diff --git a/libs/hwui/pipeline/skia/SkiaMemoryTracer.h b/libs/hwui/pipeline/skia/SkiaMemoryTracer.h index abf1f4b052ce..e9a7981ef028 100644 --- a/libs/hwui/pipeline/skia/SkiaMemoryTracer.h +++ b/libs/hwui/pipeline/skia/SkiaMemoryTracer.h @@ -50,8 +50,8 @@ public: } bool shouldDumpWrappedObjects() const override { return true; } - void setMemoryBacking(const char*, const char*, const char*) override { } - void setDiscardableMemoryBacking(const char*, const SkDiscardableMemory&) override { } + void setMemoryBacking(const char*, const char*, const char*) override {} + void setDiscardableMemoryBacking(const char*, const SkDiscardableMemory&) override {} private: struct TraceValue { diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp index 7f62ab5abb7d..2e7850d48e54 100644 --- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp +++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp @@ -184,15 +184,15 @@ bool SkiaPipeline::createOrUpdateLayer(RenderNode* node, const DamageAccumulator } else { String8 cachesOutput; mRenderThread.cacheManager().dumpMemoryUsage(cachesOutput, - &mRenderThread.renderState()); + &mRenderThread.renderState()); ALOGE("%s", cachesOutput.string()); if (errorHandler) { std::ostringstream err; err << "Unable to create layer for " << node->getName(); const int maxTextureSize = DeviceInfo::get()->maxTextureSize(); err << ", size " << info.width() << "x" << info.height() << " max size " - << maxTextureSize << " color type " << (int)info.colorType() - << " has context " << (int)(mRenderThread.getGrContext() != nullptr); + << maxTextureSize << " color type " << (int)info.colorType() << " has context " + << (int)(mRenderThread.getGrContext() != nullptr); errorHandler->onError(err.str()); } } @@ -301,8 +301,7 @@ void SkiaPipeline::endCapture(SkSurface* surface) { mSavePictureProcessor->savePicture(data, mCapturedFile); } else { mSavePictureProcessor->savePicture( - data, - mCapturedFile + "_" + std::to_string(mCaptureSequence)); + data, mCapturedFile + "_" + std::to_string(mCaptureSequence)); } mCaptureSequence--; } diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.h b/libs/hwui/pipeline/skia/SkiaPipeline.h index af58f634ecd0..f2906de4d5a0 100644 --- a/libs/hwui/pipeline/skia/SkiaPipeline.h +++ b/libs/hwui/pipeline/skia/SkiaPipeline.h @@ -97,8 +97,7 @@ public: return mLightCenter; } - static void updateLighting(const LightGeometry& lightGeometry, - const LightInfo& lightInfo) { + static void updateLighting(const LightGeometry& lightGeometry, const LightInfo& lightInfo) { mLightRadius = lightGeometry.radius; mAmbientShadowAlpha = lightInfo.ambientShadowAlpha; mSpotShadowAlpha = lightInfo.spotShadowAlpha; diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp index b56c3ef94791..6eefed959913 100644 --- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp +++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp @@ -24,8 +24,8 @@ #include "RenderNode.h" #include "pipeline/skia/AnimatedDrawables.h" #include "pipeline/skia/GLFunctorDrawable.h" -#include "pipeline/skia/VkInteropFunctorDrawable.h" #include "pipeline/skia/VkFunctorDrawable.h" +#include "pipeline/skia/VkInteropFunctorDrawable.h" namespace android { namespace uirenderer { @@ -95,8 +95,8 @@ void SkiaRecordingCanvas::insertReorderBarrier(bool enableReorder) { drawDrawable(drawable); } if (enableReorder) { - mCurrentBarrier = mDisplayList->allocateDrawable<StartReorderBarrierDrawable>( - mDisplayList.get()); + mCurrentBarrier = + mDisplayList->allocateDrawable<StartReorderBarrierDrawable>(mDisplayList.get()); drawDrawable(mCurrentBarrier); } } @@ -127,11 +127,25 @@ void SkiaRecordingCanvas::callDrawGLFunction(Functor* functor, if (Properties::getRenderPipelineType() == RenderPipelineType::SkiaVulkan) { // TODO(cblume) use VkFunctorDrawable instead of VkInteropFunctorDrawable here when the // interop is disabled/moved. - functorDrawable = mDisplayList->allocateDrawable<VkInteropFunctorDrawable>(functor, - listener, asSkCanvas()); + functorDrawable = mDisplayList->allocateDrawable<VkInteropFunctorDrawable>( + functor, listener, asSkCanvas()); + } else { + functorDrawable = + mDisplayList->allocateDrawable<GLFunctorDrawable>(functor, listener, asSkCanvas()); + } + mDisplayList->mChildFunctors.push_back(functorDrawable); + drawDrawable(functorDrawable); +} + +void SkiaRecordingCanvas::drawWebViewFunctor(int functor) { + FunctorDrawable* functorDrawable; + if (Properties::getRenderPipelineType() == RenderPipelineType::SkiaVulkan) { + // TODO(cblume) use VkFunctorDrawable instead of VkInteropFunctorDrawable here when the + // interop is disabled. + functorDrawable = + mDisplayList->allocateDrawable<VkInteropFunctorDrawable>(functor, asSkCanvas()); } else { - functorDrawable = mDisplayList->allocateDrawable<GLFunctorDrawable>(functor, listener, - asSkCanvas()); + functorDrawable = mDisplayList->allocateDrawable<GLFunctorDrawable>(functor, asSkCanvas()); } mDisplayList->mChildFunctors.push_back(functorDrawable); drawDrawable(functorDrawable); @@ -167,7 +181,7 @@ SkiaCanvas::PaintCoW&& SkiaRecordingCanvas::filterBitmap(PaintCoW&& paint, if (colorSpaceFilter) { if (tmpPaint.getColorFilter()) { tmpPaint.setColorFilter(SkColorFilter::MakeComposeFilter( - tmpPaint.refColorFilter(), std::move(colorSpaceFilter))); + tmpPaint.refColorFilter(), std::move(colorSpaceFilter))); } else { tmpPaint.setColorFilter(std::move(colorSpaceFilter)); } @@ -248,8 +262,7 @@ void SkiaRecordingCanvas::drawNinePatch(Bitmap& bitmap, const Res_png_9patch& ch filteredPaint.writeable().setFilterQuality(kLow_SkFilterQuality); } sk_sp<SkImage> image = bitmap.makeImage(); - mRecorder.drawImageLattice(image, lattice, dst, - filterPaint(std::move(filteredPaint)), + mRecorder.drawImageLattice(image, lattice, dst, filterPaint(std::move(filteredPaint)), bitmap.palette()); if (!bitmap.isImmutable() && image.get() && !image->unique() && !dst.isEmpty()) { mDisplayList->mMutableImages.push_back(image.get()); diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h index d6107a9d9969..afeccea3fb70 100644 --- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h +++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h @@ -74,6 +74,7 @@ public: virtual void drawRenderNode(uirenderer::RenderNode* renderNode) override; virtual void callDrawGLFunction(Functor* functor, uirenderer::GlFunctorLifecycleListener* listener) override; + void drawWebViewFunctor(int functor) override; private: RecordingCanvas mRecorder; diff --git a/libs/hwui/pipeline/skia/SkiaUtils.h b/libs/hwui/pipeline/skia/SkiaUtils.h index 834446905216..fa7f1fe2f746 100644 --- a/libs/hwui/pipeline/skia/SkiaUtils.h +++ b/libs/hwui/pipeline/skia/SkiaUtils.h @@ -22,7 +22,7 @@ namespace android { static inline SkRect SkRectMakeLargest() { const SkScalar v = SK_ScalarMax; - return { -v, -v, v, v }; + return {-v, -v, v, v}; }; } /* namespace android */ diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp index 65ae0ddeccf3..1d3a24463057 100644 --- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp +++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp @@ -20,9 +20,9 @@ #include "Readback.h" #include "SkiaPipeline.h" #include "SkiaProfileRenderer.h" +#include "VkInteropFunctorDrawable.h" #include "renderstate/RenderState.h" #include "renderthread/Frame.h" -#include "VkInteropFunctorDrawable.h" #include <SkSurface.h> #include <SkTypes.h> @@ -158,7 +158,7 @@ sk_sp<Bitmap> SkiaVulkanPipeline::allocateHardwareBitmap(renderthread::RenderThr ALOGW("SkiaVulkanPipeline::allocateHardwareBitmap() failed in GraphicBuffer.create()"); return nullptr; } - return sk_sp<Bitmap>(new Bitmap(buffer.get(), skBitmap.info())); + return Bitmap::createFrom(buffer, skBitmap.refColorSpace()); } } /* namespace skiapipeline */ diff --git a/libs/hwui/pipeline/skia/VkFunctorDrawable.cpp b/libs/hwui/pipeline/skia/VkFunctorDrawable.cpp index 71ad5e17301a..156f74a611a7 100644 --- a/libs/hwui/pipeline/skia/VkFunctorDrawable.cpp +++ b/libs/hwui/pipeline/skia/VkFunctorDrawable.cpp @@ -17,23 +17,21 @@ #include "VkFunctorDrawable.h" #include <private/hwui/DrawVkInfo.h> -#include "thread/ThreadBase.h" -#include "utils/TimeUtils.h" #include <GrBackendDrawableInfo.h> -#include <thread> +#include <SkImage.h> #include <utils/Color.h> #include <utils/Trace.h> #include <utils/TraceUtils.h> -#include <SkImage.h> #include <vk/GrVkTypes.h> +#include <thread> +#include "thread/ThreadBase.h" +#include "utils/TimeUtils.h" namespace android { namespace uirenderer { namespace skiapipeline { -VkFunctorDrawHandler::VkFunctorDrawHandler(Functor *functor) - : INHERITED() - , mFunctor(functor) {} +VkFunctorDrawHandler::VkFunctorDrawHandler(Functor* functor) : INHERITED(), mFunctor(functor) {} VkFunctorDrawHandler::~VkFunctorDrawHandler() { // TODO(cblume) Fill in the DrawVkInfo parameters. @@ -55,14 +53,12 @@ void VkFunctorDrawHandler::draw(const GrBackendDrawableInfo& info) { (*mFunctor)(DrawVkInfo::kModeComposite, &draw_vk_info); } -VkFunctorDrawable::VkFunctorDrawable(Functor* functor, GlFunctorLifecycleListener* listener, - SkCanvas* canvas) - : FunctorDrawable(functor, listener, canvas) {} - -VkFunctorDrawable::~VkFunctorDrawable() = default; - -void VkFunctorDrawable::syncFunctor() const { - (*mFunctor)(DrawVkInfo::kModeSync, nullptr); +VkFunctorDrawable::~VkFunctorDrawable() { + if (auto lp = std::get_if<LegacyFunctor>(&mAnyFunctor)) { + if (lp->listener) { + lp->listener->onGlFunctorReleased(lp->functor); + } + } } void VkFunctorDrawable::onDraw(SkCanvas* /*canvas*/) { @@ -71,12 +67,17 @@ void VkFunctorDrawable::onDraw(SkCanvas* /*canvas*/) { } std::unique_ptr<FunctorDrawable::GpuDrawHandler> VkFunctorDrawable::onSnapGpuDrawHandler( - GrBackendApi backendApi, const SkMatrix& matrix) { + GrBackendApi backendApi, const SkMatrix& matrix) { if (backendApi != GrBackendApi::kVulkan) { return nullptr; } - std::unique_ptr<VkFunctorDrawHandler> draw(new VkFunctorDrawHandler(mFunctor)); - return std::move(draw); + std::unique_ptr<VkFunctorDrawHandler> draw; + if (mAnyFunctor.index() == 0) { + LOG_ALWAYS_FATAL("Not implemented"); + return nullptr; + } else { + return std::make_unique<VkFunctorDrawHandler>(std::get<1>(mAnyFunctor).functor); + } } } // namespace skiapipeline diff --git a/libs/hwui/pipeline/skia/VkFunctorDrawable.h b/libs/hwui/pipeline/skia/VkFunctorDrawable.h index 5cd131405d15..d6fefc1fca06 100644 --- a/libs/hwui/pipeline/skia/VkFunctorDrawable.h +++ b/libs/hwui/pipeline/skia/VkFunctorDrawable.h @@ -18,9 +18,9 @@ #include "FunctorDrawable.h" -#include <utils/RefBase.h> -#include <ui/GraphicBuffer.h> #include <SkImageInfo.h> +#include <ui/GraphicBuffer.h> +#include <utils/RefBase.h> namespace android { namespace uirenderer { @@ -36,6 +36,7 @@ public: ~VkFunctorDrawHandler() override; void draw(const GrBackendDrawableInfo& info) override; + private: typedef GpuDrawHandler INHERITED; @@ -48,17 +49,15 @@ private: */ class VkFunctorDrawable : public FunctorDrawable { public: - VkFunctorDrawable(Functor* functor, GlFunctorLifecycleListener* listener, - SkCanvas* canvas); - ~VkFunctorDrawable() override; + using FunctorDrawable::FunctorDrawable; - void syncFunctor() const override; + ~VkFunctorDrawable() override; protected: // SkDrawable functions: void onDraw(SkCanvas* canvas) override; - std::unique_ptr<FunctorDrawable::GpuDrawHandler> onSnapGpuDrawHandler(GrBackendApi backendApi, - const SkMatrix& matrix) override; + std::unique_ptr<FunctorDrawable::GpuDrawHandler> onSnapGpuDrawHandler( + GrBackendApi backendApi, const SkMatrix& matrix) override; }; } // namespace skiapipeline diff --git a/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp b/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp index 82285501cb63..a5faae7d5068 100644 --- a/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp +++ b/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp @@ -17,13 +17,13 @@ #include "VkInteropFunctorDrawable.h" #include <private/hwui/DrawGlInfo.h> -#include "renderthread/EglManager.h" -#include "thread/ThreadBase.h" -#include "utils/TimeUtils.h" -#include <thread> #include <utils/Color.h> #include <utils/Trace.h> #include <utils/TraceUtils.h> +#include <thread> +#include "renderthread/EglManager.h" +#include "thread/ThreadBase.h" +#include "utils/TimeUtils.h" #include <EGL/eglext.h> #include <GLES2/gl2.h> @@ -44,6 +44,7 @@ static renderthread::EglManager sEglManager; class ScopedDrawRequest { public: ScopedDrawRequest() { beginDraw(); } + private: void beginDraw() { std::lock_guard _lock{sLock}; @@ -57,9 +58,7 @@ private: } if (!sEglManager.hasEglContext()) { - sGLDrawThread->queue().runSync([]() { - sEglManager.initialize(); - }); + sGLDrawThread->queue().runSync([]() { sEglManager.initialize(); }); } } }; @@ -93,14 +92,14 @@ void VkInteropFunctorDrawable::onDraw(SkCanvas* canvas) { if (!mFrameBuffer.get() || mFBInfo != surfaceInfo) { // Buffer will be used as an OpenGL ES render target. mFrameBuffer = new GraphicBuffer( - //TODO: try to reduce the size of the buffer: possibly by using clip bounds. - static_cast<uint32_t>(surfaceInfo.width()), - static_cast<uint32_t>(surfaceInfo.height()), - ColorTypeToPixelFormat(surfaceInfo.colorType()), - GraphicBuffer::USAGE_HW_TEXTURE | GraphicBuffer::USAGE_SW_WRITE_NEVER | - GraphicBuffer::USAGE_SW_READ_NEVER | GraphicBuffer::USAGE_HW_RENDER, - std::string("VkInteropFunctorDrawable::onDraw pid [") + std::to_string(getpid()) + - "]"); + // TODO: try to reduce the size of the buffer: possibly by using clip bounds. + static_cast<uint32_t>(surfaceInfo.width()), + static_cast<uint32_t>(surfaceInfo.height()), + ColorTypeToPixelFormat(surfaceInfo.colorType()), + GraphicBuffer::USAGE_HW_TEXTURE | GraphicBuffer::USAGE_SW_WRITE_NEVER | + GraphicBuffer::USAGE_SW_READ_NEVER | GraphicBuffer::USAGE_HW_RENDER, + std::string("VkInteropFunctorDrawable::onDraw pid [") + std::to_string(getpid()) + + "]"); status_t error = mFrameBuffer->initCheck(); if (error < 0) { ALOGW("VkInteropFunctorDrawable::onDraw() failed in GraphicBuffer.create()"); @@ -110,16 +109,15 @@ void VkInteropFunctorDrawable::onDraw(SkCanvas* canvas) { mFBInfo = surfaceInfo; } - //TODO: Synchronization is needed on mFrameBuffer to guarantee that the previous Vulkan - //TODO: draw command has completed. - //TODO: A simple but inefficient way is to flush and issue a QueueWaitIdle call. See - //TODO: GrVkGpu::destroyResources() for example. + // TODO: Synchronization is needed on mFrameBuffer to guarantee that the previous Vulkan + // TODO: draw command has completed. + // TODO: A simple but inefficient way is to flush and issue a QueueWaitIdle call. See + // TODO: GrVkGpu::destroyResources() for example. bool success = sGLDrawThread->queue().runSync([&]() -> bool { ATRACE_FORMAT("WebViewDraw_%dx%d", mFBInfo.width(), mFBInfo.height()); EGLDisplay display = sEglManager.eglDisplay(); - LOG_ALWAYS_FATAL_IF(display == EGL_NO_DISPLAY, - "Failed to get EGL_DEFAULT_DISPLAY! err=%s", - uirenderer::renderthread::EglManager::eglErrorString()); + LOG_ALWAYS_FATAL_IF(display == EGL_NO_DISPLAY, "Failed to get EGL_DEFAULT_DISPLAY! err=%s", + uirenderer::renderthread::EglManager::eglErrorString()); // We use an EGLImage to access the content of the GraphicBuffer // The EGL image is later bound to a 2D texture EGLClientBuffer clientBuffer = (EGLClientBuffer)mFrameBuffer->getNativeBuffer(); @@ -154,10 +152,10 @@ void VkInteropFunctorDrawable::onDraw(SkCanvas* canvas) { AutoGLFramebuffer glFb; // Bind texture to the frame buffer. glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, - glTexture.mTexture, 0); + glTexture.mTexture, 0); if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { ALOGE("Failed framebuffer check for created target buffer: %s", - GLUtils::getGLFramebufferError()); + GLUtils::getGLFramebufferError()); return false; } @@ -166,19 +164,22 @@ void VkInteropFunctorDrawable::onDraw(SkCanvas* canvas) { glClearColor(0.0f, 0.0f, 0.0f, 0.0f); glClear(GL_COLOR_BUFFER_BIT); - (*mFunctor)(DrawGlInfo::kModeDraw, &info); + if (mAnyFunctor.index() == 0) { + std::get<0>(mAnyFunctor).handle->drawGl(info); + } else { + (*(std::get<1>(mAnyFunctor).functor))(DrawGlInfo::kModeDraw, &info); + } EGLSyncKHR glDrawFinishedFence = eglCreateSyncKHR(eglGetCurrentDisplay(), EGL_SYNC_FENCE_KHR, NULL); LOG_ALWAYS_FATAL_IF(glDrawFinishedFence == EGL_NO_SYNC_KHR, - "Could not create sync fence %#x", eglGetError()); + "Could not create sync fence %#x", eglGetError()); glFlush(); // TODO: export EGLSyncKHR in file descr // TODO: import file desc in Vulkan Semaphore // TODO: instead block the GPU: probably by using external Vulkan semaphore. // Block the CPU until the glFlush finish. - EGLint waitStatus = eglClientWaitSyncKHR(display, glDrawFinishedFence, 0, - FENCE_TIMEOUT); + EGLint waitStatus = eglClientWaitSyncKHR(display, glDrawFinishedFence, 0, FENCE_TIMEOUT); LOG_ALWAYS_FATAL_IF(waitStatus != EGL_CONDITION_SATISFIED_KHR, "Failed to wait for the fence %#x", eglGetError()); eglDestroySyncKHR(display, glDrawFinishedFence); @@ -197,26 +198,25 @@ void VkInteropFunctorDrawable::onDraw(SkCanvas* canvas) { canvas->resetMatrix(); auto functorImage = SkImage::MakeFromAHardwareBuffer( - reinterpret_cast<AHardwareBuffer*>(mFrameBuffer.get()), kPremul_SkAlphaType, - nullptr, kBottomLeft_GrSurfaceOrigin); + reinterpret_cast<AHardwareBuffer*>(mFrameBuffer.get()), kPremul_SkAlphaType, nullptr, + kBottomLeft_GrSurfaceOrigin); canvas->drawImage(functorImage, 0, 0, &paint); canvas->restore(); } VkInteropFunctorDrawable::~VkInteropFunctorDrawable() { - if (mListener.get() != nullptr) { - ScopedDrawRequest _drawRequest{}; - sGLDrawThread->queue().runSync([&]() { - mListener->onGlFunctorReleased(mFunctor); - }); + if (auto lp = std::get_if<LegacyFunctor>(&mAnyFunctor)) { + if (lp->listener) { + ScopedDrawRequest _drawRequest{}; + sGLDrawThread->queue().runSync( + [&]() { lp->listener->onGlFunctorReleased(lp->functor); }); + } } } -void VkInteropFunctorDrawable::syncFunctor() const { +void VkInteropFunctorDrawable::syncFunctor(const WebViewSyncData& data) const { ScopedDrawRequest _drawRequest{}; - sGLDrawThread->queue().runSync([&]() { - (*mFunctor)(DrawGlInfo::kModeSync, nullptr); - }); + sGLDrawThread->queue().runSync([&]() { FunctorDrawable::syncFunctor(data); }); } } // namespace skiapipeline diff --git a/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.h b/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.h index 8fe52c5ef700..c47ee114263f 100644 --- a/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.h +++ b/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.h @@ -18,8 +18,8 @@ #include "FunctorDrawable.h" -#include <utils/RefBase.h> #include <ui/GraphicBuffer.h> +#include <utils/RefBase.h> namespace android { namespace uirenderer { @@ -32,20 +32,18 @@ namespace skiapipeline { */ class VkInteropFunctorDrawable : public FunctorDrawable { public: - VkInteropFunctorDrawable(Functor* functor, GlFunctorLifecycleListener* listener, - SkCanvas* canvas) - : FunctorDrawable(functor, listener, canvas) {} - virtual ~VkInteropFunctorDrawable(); + using FunctorDrawable::FunctorDrawable; - void syncFunctor() const override; + virtual ~VkInteropFunctorDrawable(); static void vkInvokeFunctor(Functor* functor); + void syncFunctor(const WebViewSyncData& data) const override; + protected: virtual void onDraw(SkCanvas* canvas) override; private: - // Variables below describe/store temporary offscreen buffer used for Vulkan pipeline. sp<GraphicBuffer> mFrameBuffer; SkImageInfo mFBInfo; diff --git a/libs/hwui/private/hwui/WebViewFunctor.h b/libs/hwui/private/hwui/WebViewFunctor.h new file mode 100644 index 000000000000..e5346aabaee3 --- /dev/null +++ b/libs/hwui/private/hwui/WebViewFunctor.h @@ -0,0 +1,82 @@ +/* + * 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 FRAMEWORKS_BASE_WEBVIEWFUNCTOR_H +#define FRAMEWORKS_BASE_WEBVIEWFUNCTOR_H + +#include <private/hwui/DrawGlInfo.h> + +namespace android::uirenderer { + +enum class RenderMode { + OpenGL_ES, + Vulkan, +}; + +// Static for the lifetime of the process +RenderMode WebViewFunctor_queryPlatformRenderMode(); + +struct WebViewSyncData { + bool applyForceDark; +}; + +struct WebViewFunctorCallbacks { + // kModeSync, called on RenderThread + void (*onSync)(int functor, const WebViewSyncData& syncData); + + // Called when either the context is destroyed _or_ when the functor's last reference goes + // away. Will always be called with an active context and always on renderthread. + void (*onContextDestroyed)(int functor); + + // Called when the last reference to the handle goes away and the handle is considered + // irrevocably destroyed. Will always be proceeded by a call to onContextDestroyed if + // this functor had ever been drawn. + void (*onDestroyed)(int functor); + + union { + struct { + // Called on RenderThread. initialize is guaranteed to happen before this call + void (*draw)(int functor, const DrawGlInfo& params); + } gles; + // TODO: VK support. The current DrawVkInfo is monolithic and needs to be split up for + // what params are valid on what callbacks + struct { + // Called either the first time the functor is used or the first time it's used after + // a call to onContextDestroyed. + // void (*initialize)(int functor, const InitParams& params); + // void (*frameStart)(int functor, /* todo: what params are actually needed for this to + // be useful? Is this useful? */) + // void (*draw)(int functor, const CompositeParams& params /* todo: rename - composite + // almost always means something else, and we aren't compositing */); + // void (*frameEnd)(int functor, const PostCompositeParams& params /* todo: same as + // CompositeParams - rename */); + } vk; + }; +}; + +// Creates a new WebViewFunctor from the given prototype. The prototype is copied after +// this function returns. Caller retains full ownership of it. +// Returns -1 if the creation fails (such as an unsupported functorMode + platform mode combination) +int WebViewFunctor_create(const WebViewFunctorCallbacks& prototype, RenderMode functorMode); + +// May be called on any thread to signal that the functor should be destroyed. +// The functor will receive an onDestroyed when the last usage of it is released, +// and it should be considered alive & active until that point. +void WebViewFunctor_release(int functor); + +} // namespace android::uirenderer + +#endif // FRAMEWORKS_BASE_WEBVIEWFUNCTOR_H diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp index 4e4262c5c0a7..8e57a3a119e3 100644 --- a/libs/hwui/renderthread/CanvasContext.cpp +++ b/libs/hwui/renderthread/CanvasContext.cpp @@ -576,8 +576,7 @@ void CanvasContext::trimMemory(RenderThread& thread, int level) { ATRACE_CALL(); if (level >= TRIM_MEMORY_COMPLETE) { thread.cacheManager().trimMemory(CacheManager::TrimMemoryMode::Complete); - thread.destroyGlContext(); - thread.vulkanManager().destroy(); + thread.destroyRenderingContext(); } else if (level >= TRIM_MEMORY_UI_HIDDEN) { thread.cacheManager().trimMemory(CacheManager::TrimMemoryMode::UiHidden); } diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp index 085812a00f71..aa6af23d8ed3 100644 --- a/libs/hwui/renderthread/RenderProxy.cpp +++ b/libs/hwui/renderthread/RenderProxy.cpp @@ -30,6 +30,7 @@ #include "renderthread/RenderThread.h" #include "utils/Macros.h" #include "utils/TimeUtils.h" +#include "WebViewFunctorManager.h" #include <ui/GraphicBuffer.h> @@ -143,6 +144,14 @@ void RenderProxy::invokeFunctor(Functor* functor, bool waitForCompletion) { } } +void RenderProxy::destroyFunctor(int functor) { + ATRACE_CALL(); + RenderThread& thread = RenderThread::getInstance(); + thread.queue().post([=]() { + WebViewFunctorManager::instance().destroyFunctor(functor); + }); +} + DeferredLayerUpdater* RenderProxy::createTextureLayer() { return mRenderThread.queue().runSync([this]() -> auto { return mContext->createTextureLayer(); diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h index d9b789f28f8d..9dc918121be6 100644 --- a/libs/hwui/renderthread/RenderProxy.h +++ b/libs/hwui/renderthread/RenderProxy.h @@ -82,6 +82,7 @@ public: ANDROID_API void destroy(); ANDROID_API static void invokeFunctor(Functor* functor, bool waitForCompletion); + static void destroyFunctor(int functor); ANDROID_API DeferredLayerUpdater* createTextureLayer(); ANDROID_API void buildLayer(RenderNode* node); diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp index 207673c1c8dd..c06faddf7fa6 100644 --- a/libs/hwui/renderthread/RenderThread.cpp +++ b/libs/hwui/renderthread/RenderThread.cpp @@ -132,6 +132,7 @@ RenderThread::RenderThread() , mFrameCallbackTaskPending(false) , mRenderState(nullptr) , mEglManager(nullptr) + , mFunctorManager(WebViewFunctorManager::instance()) , mVkManager(nullptr) { Properties::load(); start("RenderThread"); @@ -197,11 +198,13 @@ void RenderThread::requireGlContext() { setGrContext(grContext); } -void RenderThread::destroyGlContext() { +void RenderThread::destroyRenderingContext() { + mFunctorManager.onContextDestroyed(); if (mEglManager->hasEglContext()) { setGrContext(nullptr); mEglManager->destroy(); } + vulkanManager().destroy(); } void RenderThread::dumpGraphicsMemory(int fd) { diff --git a/libs/hwui/renderthread/RenderThread.h b/libs/hwui/renderthread/RenderThread.h index 2384f9541ec0..12666b323d11 100644 --- a/libs/hwui/renderthread/RenderThread.h +++ b/libs/hwui/renderthread/RenderThread.h @@ -23,6 +23,7 @@ #include "CacheManager.h" #include "TimeLord.h" #include "thread/ThreadBase.h" +#include "WebViewFunctorManager.h" #include <GrContext.h> #include <SkBitmap.h> @@ -104,7 +105,7 @@ public: void dumpGraphicsMemory(int fd); void requireGlContext(); - void destroyGlContext(); + void destroyRenderingContext(); /** * isCurrent provides a way to query, if the caller is running on @@ -151,6 +152,7 @@ private: TimeLord mTimeLord; RenderState* mRenderState; EglManager* mEglManager; + WebViewFunctorManager& mFunctorManager; ProfileDataContainer mGlobalProfileData; Readback* mReadback = nullptr; diff --git a/libs/hwui/surfacetexture/ImageConsumer.cpp b/libs/hwui/surfacetexture/ImageConsumer.cpp index 15aec9f291a4..4a2f57e344c4 100644 --- a/libs/hwui/surfacetexture/ImageConsumer.cpp +++ b/libs/hwui/surfacetexture/ImageConsumer.cpp @@ -70,7 +70,8 @@ sk_sp<SkImage> ImageConsumer::dequeueImage(bool* queueEmpty, SurfaceTexture& st, int slot = st.mCurrentTexture; if (slot != BufferItem::INVALID_BUFFER_SLOT) { *queueEmpty = true; - mImageSlots[slot].createIfNeeded(st.mSlots[slot].mGraphicBuffer, item.mDataSpace); + mImageSlots[slot].createIfNeeded(st.mSlots[slot].mGraphicBuffer, + st.mCurrentDataSpace); return mImageSlots[slot].mImage; } } diff --git a/libs/hwui/tests/common/TestUtils.cpp b/libs/hwui/tests/common/TestUtils.cpp index f81202292a49..7aa9b82e3583 100644 --- a/libs/hwui/tests/common/TestUtils.cpp +++ b/libs/hwui/tests/common/TestUtils.cpp @@ -32,6 +32,8 @@ namespace android { namespace uirenderer { +std::unordered_map<int, TestUtils::CallCounts> TestUtils::sMockFunctorCounts{}; + SkColor TestUtils::interpolateColor(float fraction, SkColor start, SkColor end) { int startA = (start >> 24) & 0xff; int startR = (start >> 16) & 0xff; @@ -82,12 +84,10 @@ void TestUtils::drawUtf8ToCanvas(Canvas* canvas, const char* text, const SkPaint uint32_t length = strlen(text); SkPaint glyphPaint(paint); glyphPaint.setTextEncoding(kGlyphID_SkTextEncoding); - canvas->drawText( - utf16.get(), length, // text buffer - 0, length, // draw range - 0, length, // context range - x, y, minikin::Bidi::LTR, - glyphPaint, nullptr, nullptr /* measured text */); + canvas->drawText(utf16.get(), length, // text buffer + 0, length, // draw range + 0, length, // context range + x, y, minikin::Bidi::LTR, glyphPaint, nullptr, nullptr /* measured text */); } void TestUtils::drawUtf8ToCanvas(Canvas* canvas, const char* text, const SkPaint& paint, @@ -96,7 +96,7 @@ void TestUtils::drawUtf8ToCanvas(Canvas* canvas, const char* text, const SkPaint SkPaint glyphPaint(paint); glyphPaint.setTextEncoding(kGlyphID_SkTextEncoding); canvas->drawTextOnPath(utf16.get(), strlen(text), minikin::Bidi::LTR, path, 0, 0, glyphPaint, - nullptr); + nullptr); } void TestUtils::TestTask::run() { @@ -110,11 +110,7 @@ void TestUtils::TestTask::run() { rtCallback(renderThread); - if (Properties::getRenderPipelineType() == RenderPipelineType::SkiaVulkan) { - renderThread.vulkanManager().destroy(); - } else { - renderThread.destroyGlContext(); - } + renderThread.destroyRenderingContext(); } std::unique_ptr<uint16_t[]> TestUtils::asciiToUtf16(const char* str) { diff --git a/libs/hwui/tests/common/TestUtils.h b/libs/hwui/tests/common/TestUtils.h index c5db861d4f48..5ff8993e6779 100644 --- a/libs/hwui/tests/common/TestUtils.h +++ b/libs/hwui/tests/common/TestUtils.h @@ -27,6 +27,7 @@ #include <renderstate/RenderState.h> #include <renderthread/RenderThread.h> +#include <gtest/gtest.h> #include <memory> namespace android { @@ -201,8 +202,7 @@ public: static void recordNode(RenderNode& node, std::function<void(Canvas&)> contentCallback) { std::unique_ptr<Canvas> canvas(Canvas::create_recording_canvas( - node.stagingProperties().getWidth(), node.stagingProperties().getHeight(), - &node)); + node.stagingProperties().getWidth(), node.stagingProperties().getHeight(), &node)); contentCallback(*canvas.get()); node.setStagingDisplayList(canvas->finishRecording()); } @@ -267,7 +267,14 @@ public: renderthread::RenderThread::getInstance().queue().runSync([&]() { task.run(); }); } + static void runOnRenderThreadUnmanaged(RtCallback rtCallback) { + auto& rt = renderthread::RenderThread::getInstance(); + rt.queue().runSync([&]() { rtCallback(rt); }); + } + + static bool isRenderThreadRunning() { return renderthread::RenderThread::hasInstance(); } + static pid_t getRenderThreadTid() { return renderthread::RenderThread::getInstance().getTid(); } static SkColor interpolateColor(float fraction, SkColor start, SkColor end); @@ -296,7 +303,52 @@ public: static SkRect getClipBounds(const SkCanvas* canvas); static SkRect getLocalClipBounds(const SkCanvas* canvas); + struct CallCounts { + int sync = 0; + int contextDestroyed = 0; + int destroyed = 0; + int glesDraw = 0; + }; + + static void expectOnRenderThread() { EXPECT_EQ(gettid(), TestUtils::getRenderThreadTid()); } + + static WebViewFunctorCallbacks createMockFunctor(RenderMode mode) { + auto callbacks = WebViewFunctorCallbacks{ + .onSync = + [](int functor, const WebViewSyncData& data) { + expectOnRenderThread(); + sMockFunctorCounts[functor].sync++; + }, + .onContextDestroyed = + [](int functor) { + expectOnRenderThread(); + sMockFunctorCounts[functor].contextDestroyed++; + }, + .onDestroyed = + [](int functor) { + expectOnRenderThread(); + sMockFunctorCounts[functor].destroyed++; + }, + }; + switch (mode) { + case RenderMode::OpenGL_ES: + callbacks.gles.draw = [](int functor, const DrawGlInfo& params) { + expectOnRenderThread(); + sMockFunctorCounts[functor].glesDraw++; + }; + break; + default: + ADD_FAILURE(); + return WebViewFunctorCallbacks{}; + } + return callbacks; + } + + static CallCounts& countsForFunctor(int functor) { return sMockFunctorCounts[functor]; } + private: + static std::unordered_map<int, CallCounts> sMockFunctorCounts; + static void syncHierarchyPropertiesAndDisplayListImpl(RenderNode* node) { MarkAndSweepRemoved observer(nullptr); node->syncProperties(); @@ -306,9 +358,9 @@ private: } auto displayList = node->getDisplayList(); if (displayList) { - for (auto&& childDr : static_cast<skiapipeline::SkiaDisplayList*>( - const_cast<DisplayList*>(displayList)) - ->mChildNodes) { + for (auto&& childDr : + static_cast<skiapipeline::SkiaDisplayList*>(const_cast<DisplayList*>(displayList)) + ->mChildNodes) { syncHierarchyPropertiesAndDisplayListImpl(childDr.getRenderNode()); } } diff --git a/libs/hwui/tests/common/scenes/HwBitmapInCompositeShader.cpp b/libs/hwui/tests/common/scenes/HwBitmapInCompositeShader.cpp index 448408d19eb1..ec81f629ee45 100644 --- a/libs/hwui/tests/common/scenes/HwBitmapInCompositeShader.cpp +++ b/libs/hwui/tests/common/scenes/HwBitmapInCompositeShader.cpp @@ -50,7 +50,7 @@ public: pixels[4000 + 4 * i + 3] = 255; } buffer->unlock(); - sk_sp<Bitmap> hardwareBitmap(Bitmap::createFrom(buffer)); + sk_sp<Bitmap> hardwareBitmap(Bitmap::createFrom(buffer, SkColorSpace::MakeSRGB())); sk_sp<SkShader> hardwareShader(createBitmapShader(*hardwareBitmap)); SkPoint center; diff --git a/libs/hwui/tests/unit/DeferredLayerUpdaterTests.cpp b/libs/hwui/tests/unit/DeferredLayerUpdaterTests.cpp index a6869791a915..f4c3e13b0ea6 100644 --- a/libs/hwui/tests/unit/DeferredLayerUpdaterTests.cpp +++ b/libs/hwui/tests/unit/DeferredLayerUpdaterTests.cpp @@ -19,9 +19,9 @@ #include "tests/common/TestUtils.h" -#include <gtest/gtest.h> #include <SkBitmap.h> #include <SkImage.h> +#include <gtest/gtest.h> using namespace android; using namespace android::uirenderer; diff --git a/libs/hwui/tests/unit/GpuMemoryTrackerTests.cpp b/libs/hwui/tests/unit/GpuMemoryTrackerTests.cpp index 08b967964c59..dac888cd79ca 100644 --- a/libs/hwui/tests/unit/GpuMemoryTrackerTests.cpp +++ b/libs/hwui/tests/unit/GpuMemoryTrackerTests.cpp @@ -39,7 +39,7 @@ public: // current thread can spoof being a GPU thread static void destroyEglContext() { if (TestUtils::isRenderThreadRunning()) { - TestUtils::runOnRenderThread([](RenderThread& thread) { thread.destroyGlContext(); }); + TestUtils::runOnRenderThread([](RenderThread& thread) { thread.destroyRenderingContext(); }); } } diff --git a/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp index 0331581799b7..c813cd945905 100644 --- a/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp +++ b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp @@ -355,9 +355,7 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(RenderNodeDrawable, emptyReceiver) { class ProjectionTestCanvas : public SkCanvas { public: ProjectionTestCanvas(int width, int height) : SkCanvas(width, height) {} - void onDrawRect(const SkRect& rect, const SkPaint& paint) override { - mDrawCounter++; - } + void onDrawRect(const SkRect& rect, const SkPaint& paint) override { mDrawCounter++; } int getDrawCounter() { return mDrawCounter; } @@ -370,7 +368,7 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(RenderNodeDrawable, emptyReceiver) { [](RenderProperties& properties, SkiaRecordingCanvas& canvas) { properties.setProjectionReceiver(true); }, - "B"); // a receiver with an empty display list + "B"); // a receiver with an empty display list auto projectingRipple = TestUtils::createSkiaNode( 0, 0, 100, 100, @@ -389,14 +387,14 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(RenderNodeDrawable, emptyReceiver) { canvas.drawRenderNode(projectingRipple.get()); }, "C"); - auto parent = TestUtils::createSkiaNode( - 0, 0, 100, 100, - [&receiverBackground, &child](RenderProperties& properties, - SkiaRecordingCanvas& canvas) { - canvas.drawRenderNode(receiverBackground.get()); - canvas.drawRenderNode(child.get()); - }, - "A"); + auto parent = + TestUtils::createSkiaNode(0, 0, 100, 100, + [&receiverBackground, &child](RenderProperties& properties, + SkiaRecordingCanvas& canvas) { + canvas.drawRenderNode(receiverBackground.get()); + canvas.drawRenderNode(child.get()); + }, + "A"); ContextFactory contextFactory; std::unique_ptr<CanvasContext> canvasContext( CanvasContext::create(renderThread, false, parent.get(), &contextFactory)); @@ -1058,7 +1056,7 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(RenderNodeDrawable, layerComposeQuality) { public: FrameTestCanvas() : TestCanvasBase(CANVAS_WIDTH, CANVAS_HEIGHT) {} void onDrawImageRect(const SkImage* image, const SkRect* src, const SkRect& dst, - const SkPaint* paint, SrcRectConstraint constraint) override { + const SkPaint* paint, SrcRectConstraint constraint) override { mDrawCounter++; EXPECT_EQ(kLow_SkFilterQuality, paint->getFilterQuality()); } @@ -1076,7 +1074,7 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(RenderNodeDrawable, layerComposeQuality) { FrameTestCanvas canvas; RenderNodeDrawable drawable(layerNode.get(), &canvas, true); canvas.drawDrawable(&drawable); - EXPECT_EQ(1, canvas.mDrawCounter); //make sure the layer was composed + EXPECT_EQ(1, canvas.mDrawCounter); // make sure the layer was composed // clean up layer pointer, so we can safely destruct RenderNode layerNode->setLayerSurface(nullptr); @@ -1129,15 +1127,14 @@ TEST(ReorderBarrierDrawable, testShadowMatrix) { getTotalMatrix()); } else { // Second invocation is preparing the matrix for an elevated RenderNodeDrawable. - EXPECT_EQ(SkMatrix::MakeTrans(TRANSLATE_X, TRANSLATE_Y), - matrix); - EXPECT_EQ(SkMatrix::MakeTrans(TRANSLATE_X, TRANSLATE_Y), - getTotalMatrix()); + EXPECT_EQ(SkMatrix::MakeTrans(TRANSLATE_X, TRANSLATE_Y), matrix); + EXPECT_EQ(SkMatrix::MakeTrans(TRANSLATE_X, TRANSLATE_Y), getTotalMatrix()); } } protected: int mDrawCounter = 0; + private: bool mFirstDidConcat = true; }; @@ -1174,14 +1171,14 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaRecordingCanvas, drawVectorDrawable) { public: VectorDrawableTestCanvas() : TestCanvasBase(CANVAS_WIDTH, CANVAS_HEIGHT) {} void onDrawBitmapRect(const SkBitmap& bitmap, const SkRect* src, const SkRect& dst, - const SkPaint* paint, SrcRectConstraint constraint) override { + const SkPaint* paint, SrcRectConstraint constraint) override { const int index = mDrawCounter++; switch (index) { case 0: EXPECT_EQ(dst, SkRect::MakeWH(CANVAS_WIDTH, CANVAS_HEIGHT)); break; case 1: - EXPECT_EQ(dst, SkRect::MakeWH(CANVAS_WIDTH/2, CANVAS_HEIGHT)); + EXPECT_EQ(dst, SkRect::MakeWH(CANVAS_WIDTH / 2, CANVAS_HEIGHT)); break; default: ADD_FAILURE(); @@ -1191,17 +1188,18 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaRecordingCanvas, drawVectorDrawable) { VectorDrawable::Group* group = new VectorDrawable::Group(); sp<VectorDrawableRoot> vectorDrawable(new VectorDrawableRoot(group)); - vectorDrawable->mutateStagingProperties()->setScaledSize(CANVAS_WIDTH/10, CANVAS_HEIGHT/10); + vectorDrawable->mutateStagingProperties()->setScaledSize(CANVAS_WIDTH / 10, CANVAS_HEIGHT / 10); - auto node = TestUtils::createSkiaNode(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT, - [&](RenderProperties& props, SkiaRecordingCanvas& canvas) { - vectorDrawable->mutateStagingProperties()->setBounds(SkRect::MakeWH(CANVAS_WIDTH, - CANVAS_HEIGHT)); - canvas.drawVectorDrawable(vectorDrawable.get()); - vectorDrawable->mutateStagingProperties()->setBounds(SkRect::MakeWH(CANVAS_WIDTH/2, - CANVAS_HEIGHT)); - canvas.drawVectorDrawable(vectorDrawable.get()); - }); + auto node = + TestUtils::createSkiaNode(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT, + [&](RenderProperties& props, SkiaRecordingCanvas& canvas) { + vectorDrawable->mutateStagingProperties()->setBounds( + SkRect::MakeWH(CANVAS_WIDTH, CANVAS_HEIGHT)); + canvas.drawVectorDrawable(vectorDrawable.get()); + vectorDrawable->mutateStagingProperties()->setBounds( + SkRect::MakeWH(CANVAS_WIDTH / 2, CANVAS_HEIGHT)); + canvas.drawVectorDrawable(vectorDrawable.get()); + }); VectorDrawableTestCanvas canvas; RenderNodeDrawable drawable(node.get(), &canvas, true); diff --git a/libs/hwui/tests/unit/RenderNodeTests.cpp b/libs/hwui/tests/unit/RenderNodeTests.cpp index 75fb0ef0acc1..1cd9bd8ee9d9 100644 --- a/libs/hwui/tests/unit/RenderNodeTests.cpp +++ b/libs/hwui/tests/unit/RenderNodeTests.cpp @@ -295,7 +295,8 @@ RENDERTHREAD_TEST(RenderNode, prepareTree_nullableDisplayList) { canvasContext->destroy(); } -RENDERTHREAD_TEST(RenderNode, prepareTree_HwLayer_AVD_enqueueDamage) { +// TODO: Is this supposed to work in SkiaGL/SkiaVK? +RENDERTHREAD_TEST(DISABLED_RenderNode, prepareTree_HwLayer_AVD_enqueueDamage) { VectorDrawable::Group* group = new VectorDrawable::Group(); sp<VectorDrawableRoot> vectorDrawable(new VectorDrawableRoot(group)); diff --git a/libs/hwui/tests/unit/ShaderCacheTests.cpp b/libs/hwui/tests/unit/ShaderCacheTests.cpp index 1433aa0349f4..87981f115763 100644 --- a/libs/hwui/tests/unit/ShaderCacheTests.cpp +++ b/libs/hwui/tests/unit/ShaderCacheTests.cpp @@ -14,17 +14,17 @@ * limitations under the License. */ -#include <gtest/gtest.h> -#include <dirent.h> #include <cutils/properties.h> -#include <cstdint> +#include <dirent.h> #include <errno.h> +#include <gtest/gtest.h> #include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <utils/Log.h> -#include "pipeline/skia/ShaderCache.h" +#include <cstdint> #include "FileBlobCache.h" +#include "pipeline/skia/ShaderCache.h" using namespace android::uirenderer::skiapipeline; @@ -66,7 +66,6 @@ public: } /* namespace uirenderer */ } /* namespace android */ - namespace { std::string getExternalStorageFolder() { @@ -82,14 +81,12 @@ bool folderExist(const std::string& folderName) { return false; } -inline bool -checkShader(const sk_sp<SkData>& shader1, const sk_sp<SkData>& shader2) { - return nullptr != shader1 && nullptr != shader2 && shader1->size() == shader2->size() - && 0 == memcmp(shader1->data(), shader2->data(), shader1->size()); +inline bool checkShader(const sk_sp<SkData>& shader1, const sk_sp<SkData>& shader2) { + return nullptr != shader1 && nullptr != shader2 && shader1->size() == shader2->size() && + 0 == memcmp(shader1->data(), shader2->data(), shader1->size()); } -inline bool -checkShader(const sk_sp<SkData>& shader, const char* program) { +inline bool checkShader(const sk_sp<SkData>& shader, const char* program) { sk_sp<SkData> shader2 = SkData::MakeWithCString(program); return checkShader(shader, shader2); } @@ -116,32 +113,31 @@ void genRandomData(std::vector<T>& buffer) { } } - #define GrProgramDescTest(a) (*SkData::MakeWithCString(#a).get()) TEST(ShaderCacheTest, testWriteAndRead) { if (!folderExist(getExternalStorageFolder())) { - //don't run the test if external storage folder is not available + // don't run the test if external storage folder is not available return; } - std::string cacheFile1 = getExternalStorageFolder() + "/shaderCacheTest1"; - std::string cacheFile2 = getExternalStorageFolder() + "/shaderCacheTest2"; + std::string cacheFile1 = getExternalStorageFolder() + "/shaderCacheTest1"; + std::string cacheFile2 = getExternalStorageFolder() + "/shaderCacheTest2"; - //remove any test files from previous test run + // remove any test files from previous test run int deleteFile = remove(cacheFile1.c_str()); ASSERT_TRUE(0 == deleteFile || ENOENT == errno); std::srand(0); - //read the cache from a file that does not exist + // read the cache from a file that does not exist ShaderCache::get().setFilename(cacheFile1.c_str()); - ShaderCacheTestUtils::setSaveDelay(ShaderCache::get(), 0); //disable deferred save + ShaderCacheTestUtils::setSaveDelay(ShaderCache::get(), 0); // disable deferred save ShaderCache::get().initShaderDiskCache(); - //read a key - should not be found since the cache is empty + // read a key - should not be found since the cache is empty sk_sp<SkData> outVS; ASSERT_EQ(ShaderCache::get().load(GrProgramDescTest(432)), sk_sp<SkData>()); - //write to the in-memory cache without storing on disk and verify we read the same values + // write to the in-memory cache without storing on disk and verify we read the same values sk_sp<SkData> inVS; setShader(inVS, "sassas"); ShaderCache::get().store(GrProgramDescTest(100), *inVS.get()); @@ -152,23 +148,23 @@ TEST(ShaderCacheTest, testWriteAndRead) { ASSERT_NE((outVS = ShaderCache::get().load(GrProgramDescTest(432))), sk_sp<SkData>()); ASSERT_TRUE(checkShader(outVS, "someVS")); - //store content to disk and release in-memory cache + // store content to disk and release in-memory cache ShaderCacheTestUtils::terminate(ShaderCache::get(), true); - //change to a file that does not exist and verify load fails + // change to a file that does not exist and verify load fails ShaderCache::get().setFilename(cacheFile2.c_str()); ShaderCache::get().initShaderDiskCache(); ASSERT_EQ(ShaderCache::get().load(GrProgramDescTest(432)), sk_sp<SkData>()); ShaderCacheTestUtils::terminate(ShaderCache::get(), false); - //load again content from disk from an existing file and check the data is read correctly + // load again content from disk from an existing file and check the data is read correctly ShaderCache::get().setFilename(cacheFile1.c_str()); ShaderCache::get().initShaderDiskCache(); sk_sp<SkData> outVS2; ASSERT_NE((outVS2 = ShaderCache::get().load(GrProgramDescTest(432))), sk_sp<SkData>()); ASSERT_TRUE(checkShader(outVS2, "someVS")); - //change data, store to disk, read back again and verify data has been changed + // change data, store to disk, read back again and verify data has been changed setShader(inVS, "ewData1"); ShaderCache::get().store(GrProgramDescTest(432), *inVS.get()); ShaderCacheTestUtils::terminate(ShaderCache::get(), true); @@ -176,9 +172,8 @@ TEST(ShaderCacheTest, testWriteAndRead) { ASSERT_NE((outVS2 = ShaderCache::get().load(GrProgramDescTest(432))), sk_sp<SkData>()); ASSERT_TRUE(checkShader(outVS2, "ewData1")); - - //write and read big data chunk (50K) - size_t dataSize = 50*1024; + // write and read big data chunk (50K) + size_t dataSize = 50 * 1024; std::vector<uint8_t> dataBuffer(dataSize); genRandomData(dataBuffer); setShader(inVS, dataBuffer); @@ -194,31 +189,31 @@ TEST(ShaderCacheTest, testWriteAndRead) { TEST(ShaderCacheTest, testCacheValidation) { if (!folderExist(getExternalStorageFolder())) { - //don't run the test if external storage folder is not available + // don't run the test if external storage folder is not available return; } - std::string cacheFile1 = getExternalStorageFolder() + "/shaderCacheTest1"; - std::string cacheFile2 = getExternalStorageFolder() + "/shaderCacheTest2"; + std::string cacheFile1 = getExternalStorageFolder() + "/shaderCacheTest1"; + std::string cacheFile2 = getExternalStorageFolder() + "/shaderCacheTest2"; - //remove any test files from previous test run + // remove any test files from previous test run int deleteFile = remove(cacheFile1.c_str()); ASSERT_TRUE(0 == deleteFile || ENOENT == errno); std::srand(0); - //generate identity and read the cache from a file that does not exist + // generate identity and read the cache from a file that does not exist ShaderCache::get().setFilename(cacheFile1.c_str()); - ShaderCacheTestUtils::setSaveDelay(ShaderCache::get(), 0); //disable deferred save + ShaderCacheTestUtils::setSaveDelay(ShaderCache::get(), 0); // disable deferred save std::vector<uint8_t> identity(1024); genRandomData(identity); - ShaderCache::get().initShaderDiskCache(identity.data(), identity.size() * - sizeof(decltype(identity)::value_type)); + ShaderCache::get().initShaderDiskCache( + identity.data(), identity.size() * sizeof(decltype(identity)::value_type)); // generate random content in cache and store to disk constexpr size_t numBlob(10); constexpr size_t keySize(1024); constexpr size_t dataSize(50 * 1024); - std::vector< std::pair<sk_sp<SkData>, sk_sp<SkData>> > blobVec(numBlob); + std::vector<std::pair<sk_sp<SkData>, sk_sp<SkData>>> blobVec(numBlob); for (auto& blob : blobVec) { std::vector<uint8_t> keyBuffer(keySize); std::vector<uint8_t> dataBuffer(dataSize); @@ -237,47 +232,47 @@ TEST(ShaderCacheTest, testCacheValidation) { // change to a file that does not exist and verify validation fails ShaderCache::get().setFilename(cacheFile2.c_str()); ShaderCache::get().initShaderDiskCache(); - ASSERT_FALSE( ShaderCacheTestUtils::validateCache(ShaderCache::get(), identity) ); + ASSERT_FALSE(ShaderCacheTestUtils::validateCache(ShaderCache::get(), identity)); ShaderCacheTestUtils::terminate(ShaderCache::get(), false); // restore the original file and verify validation succeeds ShaderCache::get().setFilename(cacheFile1.c_str()); - ShaderCache::get().initShaderDiskCache(identity.data(), identity.size() * - sizeof(decltype(identity)::value_type)); - ASSERT_TRUE( ShaderCacheTestUtils::validateCache(ShaderCache::get(), identity) ); + ShaderCache::get().initShaderDiskCache( + identity.data(), identity.size() * sizeof(decltype(identity)::value_type)); + ASSERT_TRUE(ShaderCacheTestUtils::validateCache(ShaderCache::get(), identity)); for (const auto& blob : blobVec) { auto outVS = ShaderCache::get().load(*blob.first.get()); - ASSERT_TRUE( checkShader(outVS, blob.second) ); + ASSERT_TRUE(checkShader(outVS, blob.second)); } // generate error identity and verify load fails ShaderCache::get().initShaderDiskCache(identity.data(), -1); for (const auto& blob : blobVec) { - ASSERT_EQ( ShaderCache::get().load(*blob.first.get()), sk_sp<SkData>() ); + ASSERT_EQ(ShaderCache::get().load(*blob.first.get()), sk_sp<SkData>()); } - ShaderCache::get().initShaderDiskCache(nullptr, identity.size() * - sizeof(decltype(identity)::value_type)); + ShaderCache::get().initShaderDiskCache( + nullptr, identity.size() * sizeof(decltype(identity)::value_type)); for (const auto& blob : blobVec) { - ASSERT_EQ( ShaderCache::get().load(*blob.first.get()), sk_sp<SkData>() ); + ASSERT_EQ(ShaderCache::get().load(*blob.first.get()), sk_sp<SkData>()); } // verify the cache validation again after load fails - ShaderCache::get().initShaderDiskCache(identity.data(), identity.size() * - sizeof(decltype(identity)::value_type)); - ASSERT_TRUE( ShaderCacheTestUtils::validateCache(ShaderCache::get(), identity) ); + ShaderCache::get().initShaderDiskCache( + identity.data(), identity.size() * sizeof(decltype(identity)::value_type)); + ASSERT_TRUE(ShaderCacheTestUtils::validateCache(ShaderCache::get(), identity)); for (const auto& blob : blobVec) { auto outVS = ShaderCache::get().load(*blob.first.get()); - ASSERT_TRUE( checkShader(outVS, blob.second) ); + ASSERT_TRUE(checkShader(outVS, blob.second)); } // generate another identity and verify load fails for (auto& data : identity) { data += std::rand(); } - ShaderCache::get().initShaderDiskCache(identity.data(), identity.size() * - sizeof(decltype(identity)::value_type)); + ShaderCache::get().initShaderDiskCache( + identity.data(), identity.size() * sizeof(decltype(identity)::value_type)); for (const auto& blob : blobVec) { - ASSERT_EQ( ShaderCache::get().load(*blob.first.get()), sk_sp<SkData>() ); + ASSERT_EQ(ShaderCache::get().load(*blob.first.get()), sk_sp<SkData>()); } ShaderCacheTestUtils::terminate(ShaderCache::get(), false); diff --git a/libs/hwui/tests/unit/SkiaDisplayListTests.cpp b/libs/hwui/tests/unit/SkiaDisplayListTests.cpp index 415f9e8517ff..53bf84f13fd6 100644 --- a/libs/hwui/tests/unit/SkiaDisplayListTests.cpp +++ b/libs/hwui/tests/unit/SkiaDisplayListTests.cpp @@ -100,16 +100,35 @@ TEST(SkiaDisplayList, syncContexts) { GLFunctorDrawable functorDrawable(&functor, nullptr, &dummyCanvas); skiaDL.mChildFunctors.push_back(&functorDrawable); + int functor2 = WebViewFunctor_create(TestUtils::createMockFunctor(RenderMode::OpenGL_ES), + RenderMode::OpenGL_ES); + auto& counts = TestUtils::countsForFunctor(functor2); + skiaDL.mChildFunctors.push_back( + skiaDL.allocateDrawable<GLFunctorDrawable>(functor2, &dummyCanvas)); + WebViewFunctor_release(functor2); + SkRect bounds = SkRect::MakeWH(200, 200); VectorDrawableRoot vectorDrawable(new VectorDrawable::Group()); vectorDrawable.mutateStagingProperties()->setBounds(bounds); skiaDL.mVectorDrawables.push_back(&vectorDrawable); // ensure that the functor and vectorDrawable are properly synced - skiaDL.syncContents(); - - ASSERT_EQ(functor.getLastMode(), DrawGlInfo::kModeSync); - ASSERT_EQ(vectorDrawable.mutateProperties()->getBounds(), bounds); + TestUtils::runOnRenderThread([&](auto&) { + skiaDL.syncContents(WebViewSyncData{ + .applyForceDark = false, + }); + }); + + EXPECT_EQ(functor.getLastMode(), DrawGlInfo::kModeSync); + EXPECT_EQ(counts.sync, 1); + EXPECT_EQ(counts.destroyed, 0); + EXPECT_EQ(vectorDrawable.mutateProperties()->getBounds(), bounds); + + skiaDL.reset(); + TestUtils::runOnRenderThread([](auto&) { + // Fence + }); + EXPECT_EQ(counts.destroyed, 1); } class ContextFactory : public IContextFactory { diff --git a/libs/hwui/tests/unit/SkiaPipelineTests.cpp b/libs/hwui/tests/unit/SkiaPipelineTests.cpp index d16b8be89e20..3c06dab36fe4 100644 --- a/libs/hwui/tests/unit/SkiaPipelineTests.cpp +++ b/libs/hwui/tests/unit/SkiaPipelineTests.cpp @@ -24,10 +24,10 @@ #include "DamageAccumulator.h" #include "IContextFactory.h" #include "SkiaCanvas.h" -#include "pipeline/skia/SkiaUtils.h" #include "pipeline/skia/SkiaDisplayList.h" #include "pipeline/skia/SkiaOpenGLPipeline.h" #include "pipeline/skia/SkiaRecordingCanvas.h" +#include "pipeline/skia/SkiaUtils.h" #include "renderthread/CanvasContext.h" #include "tests/common/TestUtils.h" @@ -51,8 +51,7 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, renderFrame) { auto surface = SkSurface::MakeRasterN32Premul(1, 1); surface->getCanvas()->drawColor(SK_ColorBLUE, SkBlendMode::kSrcOver); ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE); - pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, - surface); + pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface); ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorRED); } @@ -84,8 +83,7 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, testOnPrepareTree) { ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE); // drawFrame will crash if "SkiaPipeline::onPrepareTree" did not clean invalid VD pointer - pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, - surface); + pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface); ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorRED); } @@ -106,12 +104,10 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, renderFrameCheckOpaque) { auto surface = SkSurface::MakeRasterN32Premul(2, 2); surface->getCanvas()->drawColor(SK_ColorBLUE, SkBlendMode::kSrcOver); ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE); - pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, true, contentDrawBounds, - surface); + pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, true, contentDrawBounds, surface); ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE); ASSERT_EQ(TestUtils::getColor(surface, 0, 1), SK_ColorGREEN); - pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, false, contentDrawBounds, - surface); + pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, false, contentDrawBounds, surface); ASSERT_EQ(TestUtils::getColor(surface, 0, 0), (unsigned int)SK_ColorTRANSPARENT); ASSERT_EQ(TestUtils::getColor(surface, 0, 1), SK_ColorGREEN); } @@ -130,8 +126,7 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, renderFrameCheckDirtyRect) { auto surface = SkSurface::MakeRasterN32Premul(2, 2); surface->getCanvas()->drawColor(SK_ColorBLUE, SkBlendMode::kSrcOver); ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE); - pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, true, contentDrawBounds, - surface); + pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, true, contentDrawBounds, surface); ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE); ASSERT_EQ(TestUtils::getColor(surface, 1, 0), SK_ColorBLUE); ASSERT_EQ(TestUtils::getColor(surface, 0, 1), SK_ColorRED); @@ -203,38 +198,32 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, renderOverdraw) { ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE); // Single draw, should be white. - pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, - surface); + pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface); ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorWHITE); // 1 Overdraw, should be blue blended onto white. renderNodes.push_back(whiteNode); - pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, - surface); + pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface); ASSERT_EQ(TestUtils::getColor(surface, 0, 0), (unsigned)0xffd0d0ff); // 2 Overdraw, should be green blended onto white renderNodes.push_back(whiteNode); - pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, - surface); + pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface); ASSERT_EQ(TestUtils::getColor(surface, 0, 0), (unsigned)0xffd0ffd0); // 3 Overdraw, should be pink blended onto white. renderNodes.push_back(whiteNode); - pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, - surface); + pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface); ASSERT_EQ(TestUtils::getColor(surface, 0, 0), (unsigned)0xffffc0c0); // 4 Overdraw, should be red blended onto white. renderNodes.push_back(whiteNode); - pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, - surface); + pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface); ASSERT_EQ(TestUtils::getColor(surface, 0, 0), (unsigned)0xffff8080); // 5 Overdraw, should be red blended onto white. renderNodes.push_back(whiteNode); - pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, - surface); + pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface); ASSERT_EQ(TestUtils::getColor(surface, 0, 0), (unsigned)0xffff8080); } @@ -389,7 +378,6 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, context_lost) { EXPECT_FALSE(pipeline->isSurfaceReady()); EXPECT_TRUE(pipeline->setSurface((Surface*)0x01, SwapBehavior::kSwap_default, ColorMode::SRGB)); EXPECT_TRUE(pipeline->isSurfaceReady()); - renderThread.destroyGlContext(); + renderThread.destroyRenderingContext(); EXPECT_FALSE(pipeline->isSurfaceReady()); } - diff --git a/libs/hwui/tests/unit/TypefaceTests.cpp b/libs/hwui/tests/unit/TypefaceTests.cpp index b645aeb55074..1a09b1c52d8a 100644 --- a/libs/hwui/tests/unit/TypefaceTests.cpp +++ b/libs/hwui/tests/unit/TypefaceTests.cpp @@ -54,9 +54,9 @@ std::shared_ptr<minikin::FontFamily> buildFamily(const char* fileName) { sk_sp<SkFontMgr> fm(SkFontMgr::RefDefault()); sk_sp<SkTypeface> typeface(fm->makeFromStream(std::move(fontData))); LOG_ALWAYS_FATAL_IF(typeface == nullptr, "Failed to make typeface from %s", fileName); - std::shared_ptr<minikin::MinikinFont> font = std::make_shared<MinikinFontSkia>( - std::move(typeface), data, st.st_size, fileName, 0, - std::vector<minikin::FontVariation>()); + std::shared_ptr<minikin::MinikinFont> font = + std::make_shared<MinikinFontSkia>(std::move(typeface), data, st.st_size, fileName, 0, + std::vector<minikin::FontVariation>()); std::vector<minikin::Font> fonts; fonts.push_back(minikin::Font::Builder(font).build()); return std::make_shared<minikin::FontFamily>(std::move(fonts)); diff --git a/libs/hwui/tests/unit/VectorDrawableTests.cpp b/libs/hwui/tests/unit/VectorDrawableTests.cpp index b11eaa9d6a2f..5db002862fcd 100644 --- a/libs/hwui/tests/unit/VectorDrawableTests.cpp +++ b/libs/hwui/tests/unit/VectorDrawableTests.cpp @@ -85,8 +85,10 @@ const static TestData sTestDataSet[] = { outPath->rCubicTo(8.0, 8.0, 8.0, 8.0, 8.0, 8.0); outPath->cubicTo(16.0, 16.0, 9.0, 9.0, 9.0, 9.0); outPath->rCubicTo(0.0, 0.0, 9.0, 9.0, 9.0, 9.0); - outPath->arcTo(10.0, 10.0, 0.0, SkPath::kLarge_ArcSize, SkPath::kCW_Direction, 10.0, 10.0); - outPath->arcTo(10.0, 10.0, 0.0, SkPath::kLarge_ArcSize, SkPath::kCW_Direction, 20.0, 20.0); + outPath->arcTo(10.0, 10.0, 0.0, SkPath::kLarge_ArcSize, SkPath::kCW_Direction, 10.0, + 10.0); + outPath->arcTo(10.0, 10.0, 0.0, SkPath::kLarge_ArcSize, SkPath::kCW_Direction, 20.0, + 20.0); }}, // Check box VectorDrawable path data @@ -157,7 +159,8 @@ const static TestData sTestDataSet[] = { }, [](SkPath* outPath) { outPath->moveTo(300.0, 70.0); - outPath->arcTo(230.0, 230.0, 0.0, SkPath::kLarge_ArcSize, SkPath::kCCW_Direction, 301.0, 70.0); + outPath->arcTo(230.0, 230.0, 0.0, SkPath::kLarge_ArcSize, SkPath::kCCW_Direction, + 301.0, 70.0); outPath->close(); outPath->moveTo(300.0, 70.0); }}, @@ -236,14 +239,14 @@ struct StringPath { }; const StringPath sStringPaths[] = { - {"3e...3", false}, // Not starting with a verb and ill-formatted float - {"L.M.F.A.O", false}, // No floats following verbs - {"m 1 1", true}, // Valid path data - {"\n \t z", true}, // Valid path data with leading spaces - {"1-2e34567", false}, // Not starting with a verb and ill-formatted float - {"f 4 5", false}, // Invalid verb - {"\r ", false}, // Empty string - {"L1,0 L1,1 L0,1 z M1000", false} // Not enough floats following verb M. + {"3e...3", false}, // Not starting with a verb and ill-formatted float + {"L.M.F.A.O", false}, // No floats following verbs + {"m 1 1", true}, // Valid path data + {"\n \t z", true}, // Valid path data with leading spaces + {"1-2e34567", false}, // Not starting with a verb and ill-formatted float + {"f 4 5", false}, // Invalid verb + {"\r ", false}, // Empty string + {"L1,0 L1,1 L0,1 z M1000", false} // Not enough floats following verb M. }; static bool hasSameVerbs(const PathData& from, const PathData& to) { diff --git a/libs/hwui/tests/unit/WebViewFunctorManagerTests.cpp b/libs/hwui/tests/unit/WebViewFunctorManagerTests.cpp new file mode 100644 index 000000000000..c8169aff1c5e --- /dev/null +++ b/libs/hwui/tests/unit/WebViewFunctorManagerTests.cpp @@ -0,0 +1,154 @@ +/* + * 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 "WebViewFunctorManager.h" +#include "private/hwui/WebViewFunctor.h" +#include "renderthread/RenderProxy.h" +#include "tests/common/TestUtils.h" + +#include <unordered_map> + +using namespace android; +using namespace android::uirenderer; + +TEST(WebViewFunctor, createDestroyGLES) { + int functor = WebViewFunctor_create(TestUtils::createMockFunctor(RenderMode::OpenGL_ES), + RenderMode::OpenGL_ES); + ASSERT_NE(-1, functor); + WebViewFunctor_release(functor); + TestUtils::runOnRenderThreadUnmanaged([](renderthread::RenderThread&) { + // Empty, don't care + }); + auto& counts = TestUtils::countsForFunctor(functor); + // We never initialized, so contextDestroyed == 0 + EXPECT_EQ(0, counts.contextDestroyed); + EXPECT_EQ(1, counts.destroyed); +} + +TEST(WebViewFunctor, createSyncHandleGLES) { + int functor = WebViewFunctor_create(TestUtils::createMockFunctor(RenderMode::OpenGL_ES), + RenderMode::OpenGL_ES); + ASSERT_NE(-1, functor); + auto handle = WebViewFunctorManager::instance().handleFor(functor); + ASSERT_TRUE(handle); + WebViewFunctor_release(functor); + EXPECT_FALSE(WebViewFunctorManager::instance().handleFor(functor)); + TestUtils::runOnRenderThreadUnmanaged([](renderthread::RenderThread&) { + // fence + }); + auto& counts = TestUtils::countsForFunctor(functor); + EXPECT_EQ(0, counts.sync); + EXPECT_EQ(0, counts.contextDestroyed); + EXPECT_EQ(0, counts.destroyed); + + TestUtils::runOnRenderThreadUnmanaged([&](auto&) { + WebViewSyncData syncData; + handle->sync(syncData); + }); + + EXPECT_EQ(1, counts.sync); + + TestUtils::runOnRenderThreadUnmanaged([&](auto&) { + WebViewSyncData syncData; + handle->sync(syncData); + }); + + EXPECT_EQ(2, counts.sync); + + handle.clear(); + + TestUtils::runOnRenderThreadUnmanaged([](renderthread::RenderThread&) { + // fence + }); + + EXPECT_EQ(2, counts.sync); + EXPECT_EQ(0, counts.contextDestroyed); + EXPECT_EQ(1, counts.destroyed); +} + +TEST(WebViewFunctor, createSyncDrawGLES) { + int functor = WebViewFunctor_create(TestUtils::createMockFunctor(RenderMode::OpenGL_ES), + RenderMode::OpenGL_ES); + ASSERT_NE(-1, functor); + auto handle = WebViewFunctorManager::instance().handleFor(functor); + ASSERT_TRUE(handle); + WebViewFunctor_release(functor); + auto& counts = TestUtils::countsForFunctor(functor); + for (int i = 0; i < 5; i++) { + TestUtils::runOnRenderThreadUnmanaged([&](auto&) { + WebViewSyncData syncData; + handle->sync(syncData); + DrawGlInfo drawInfo; + handle->drawGl(drawInfo); + handle->drawGl(drawInfo); + }); + } + handle.clear(); + TestUtils::runOnRenderThreadUnmanaged([](renderthread::RenderThread&) { + // fence + }); + EXPECT_EQ(5, counts.sync); + EXPECT_EQ(10, counts.glesDraw); + EXPECT_EQ(1, counts.contextDestroyed); + EXPECT_EQ(1, counts.destroyed); +} + +TEST(WebViewFunctor, contextDestroyed) { + int functor = WebViewFunctor_create(TestUtils::createMockFunctor(RenderMode::OpenGL_ES), + RenderMode::OpenGL_ES); + ASSERT_NE(-1, functor); + auto handle = WebViewFunctorManager::instance().handleFor(functor); + ASSERT_TRUE(handle); + WebViewFunctor_release(functor); + auto& counts = TestUtils::countsForFunctor(functor); + TestUtils::runOnRenderThreadUnmanaged([&](auto&) { + WebViewSyncData syncData; + handle->sync(syncData); + DrawGlInfo drawInfo; + handle->drawGl(drawInfo); + }); + EXPECT_EQ(1, counts.sync); + EXPECT_EQ(1, counts.glesDraw); + EXPECT_EQ(0, counts.contextDestroyed); + EXPECT_EQ(0, counts.destroyed); + TestUtils::runOnRenderThreadUnmanaged([](auto& rt) { + rt.destroyRenderingContext(); + }); + EXPECT_EQ(1, counts.sync); + EXPECT_EQ(1, counts.glesDraw); + EXPECT_EQ(1, counts.contextDestroyed); + EXPECT_EQ(0, counts.destroyed); + TestUtils::runOnRenderThreadUnmanaged([&](auto&) { + WebViewSyncData syncData; + handle->sync(syncData); + DrawGlInfo drawInfo; + handle->drawGl(drawInfo); + }); + EXPECT_EQ(2, counts.sync); + EXPECT_EQ(2, counts.glesDraw); + EXPECT_EQ(1, counts.contextDestroyed); + EXPECT_EQ(0, counts.destroyed); + handle.clear(); + TestUtils::runOnRenderThreadUnmanaged([](renderthread::RenderThread&) { + // fence + }); + EXPECT_EQ(2, counts.sync); + EXPECT_EQ(2, counts.glesDraw); + EXPECT_EQ(2, counts.contextDestroyed); + EXPECT_EQ(1, counts.destroyed); +}
\ No newline at end of file diff --git a/libs/hwui/tests/unit/main.cpp b/libs/hwui/tests/unit/main.cpp index aecceb3609f5..63d15403b607 100644 --- a/libs/hwui/tests/unit/main.cpp +++ b/libs/hwui/tests/unit/main.cpp @@ -17,14 +17,14 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" +#include "Properties.h" #include "debug/GlesDriver.h" #include "debug/NullGlesDriver.h" #include "hwui/Typeface.h" -#include "Properties.h" #include "tests/common/LeakChecker.h" -#include "thread/TaskProcessor.h" #include "thread/Task.h" #include "thread/TaskManager.h" +#include "thread/TaskProcessor.h" #include <signal.h> diff --git a/libs/hwui/utils/Color.h b/libs/hwui/utils/Color.h index 4daccda78e23..4473ce632b1b 100644 --- a/libs/hwui/utils/Color.h +++ b/libs/hwui/utils/Color.h @@ -17,6 +17,7 @@ #define COLOR_H #include <math.h> +#include <cutils/compiler.h> #include <system/graphics.h> #include <ui/PixelFormat.h> @@ -117,7 +118,7 @@ bool transferFunctionCloseToSRGB(const SkColorSpace* colorSpace); android::PixelFormat ColorTypeToPixelFormat(SkColorType colorType); -sk_sp<SkColorSpace> DataSpaceToColorSpace(android_dataspace dataspace); +ANDROID_API sk_sp<SkColorSpace> DataSpaceToColorSpace(android_dataspace dataspace); struct Lab { float L; diff --git a/libs/input/PointerController.cpp b/libs/input/PointerController.cpp index 80d8e72a87e2..0a90f85cda0e 100644 --- a/libs/input/PointerController.cpp +++ b/libs/input/PointerController.cpp @@ -89,6 +89,10 @@ PointerController::PointerController(const sp<PointerControllerPolicyInterface>& mLocked.animationPending = false; + mLocked.displayWidth = -1; + mLocked.displayHeight = -1; + mLocked.displayOrientation = DISPLAY_ORIENTATION_0; + mLocked.presentation = PRESENTATION_POINTER; mLocked.presentationChanged = false; @@ -106,6 +110,15 @@ PointerController::PointerController(const sp<PointerControllerPolicyInterface>& mLocked.lastFrameUpdatedTime = 0; mLocked.buttonState = 0; + + mPolicy->loadPointerIcon(&mLocked.pointerIcon); + + loadResources(); + + if (mLocked.pointerIcon.isValid()) { + mLocked.pointerIconChanged = true; + updatePointerLocked(); + } } PointerController::~PointerController() { @@ -131,15 +144,23 @@ bool PointerController::getBounds(float* outMinX, float* outMinY, bool PointerController::getBoundsLocked(float* outMinX, float* outMinY, float* outMaxX, float* outMaxY) const { - - if (!mLocked.viewport.isValid()) { + if (mLocked.displayWidth <= 0 || mLocked.displayHeight <= 0) { return false; } - *outMinX = mLocked.viewport.logicalLeft; - *outMinY = mLocked.viewport.logicalTop; - *outMaxX = mLocked.viewport.logicalRight - 1; - *outMaxY = mLocked.viewport.logicalBottom - 1; + *outMinX = 0; + *outMinY = 0; + switch (mLocked.displayOrientation) { + case DISPLAY_ORIENTATION_90: + case DISPLAY_ORIENTATION_270: + *outMaxX = mLocked.displayHeight - 1; + *outMaxY = mLocked.displayWidth - 1; + break; + default: + *outMaxX = mLocked.displayWidth - 1; + *outMaxY = mLocked.displayHeight - 1; + break; + } return true; } @@ -210,12 +231,6 @@ void PointerController::getPosition(float* outX, float* outY) const { *outY = mLocked.pointerY; } -int32_t PointerController::getDisplayId() const { - AutoMutex _l(mLock); - - return mLocked.viewport.displayId; -} - void PointerController::fade(Transition transition) { AutoMutex _l(mLock); @@ -340,57 +355,48 @@ void PointerController::setInactivityTimeout(InactivityTimeout inactivityTimeout void PointerController::reloadPointerResources() { AutoMutex _l(mLock); - loadResourcesLocked(); - updatePointerLocked(); -} + loadResources(); -/** - * The viewport values for deviceHeight and deviceWidth have already been adjusted for rotation, - * so here we are getting the dimensions in the original, unrotated orientation (orientation 0). - */ -static void getNonRotatedSize(const DisplayViewport& viewport, int32_t& width, int32_t& height) { - if (viewport.orientation == DISPLAY_ORIENTATION_90 - || viewport.orientation == DISPLAY_ORIENTATION_270) { - width = viewport.deviceHeight; - height = viewport.deviceWidth; - } else { - width = viewport.deviceWidth; - height = viewport.deviceHeight; + if (mLocked.presentation == PRESENTATION_POINTER) { + mLocked.additionalMouseResources.clear(); + mLocked.animationResources.clear(); + mPolicy->loadPointerIcon(&mLocked.pointerIcon); + mPolicy->loadAdditionalMouseResources(&mLocked.additionalMouseResources, + &mLocked.animationResources); } + + mLocked.presentationChanged = true; + updatePointerLocked(); } -void PointerController::setDisplayViewport(const DisplayViewport& viewport) { +void PointerController::setDisplayViewport(int32_t width, int32_t height, int32_t orientation) { AutoMutex _l(mLock); - if (viewport == mLocked.viewport) { - return; - } - const DisplayViewport oldViewport = mLocked.viewport; - mLocked.viewport = viewport; - - int32_t oldDisplayWidth, oldDisplayHeight; - getNonRotatedSize(oldViewport, oldDisplayWidth, oldDisplayHeight); - int32_t newDisplayWidth, newDisplayHeight; - getNonRotatedSize(viewport, newDisplayWidth, newDisplayHeight); + // Adjust to use the display's unrotated coordinate frame. + if (orientation == DISPLAY_ORIENTATION_90 + || orientation == DISPLAY_ORIENTATION_270) { + int32_t temp = height; + height = width; + width = temp; + } - // Reset cursor position to center if size or display changed. - if (oldViewport.displayId != viewport.displayId - || oldDisplayWidth != newDisplayWidth - || oldDisplayHeight != newDisplayHeight) { + if (mLocked.displayWidth != width || mLocked.displayHeight != height) { + mLocked.displayWidth = width; + mLocked.displayHeight = height; float minX, minY, maxX, maxY; if (getBoundsLocked(&minX, &minY, &maxX, &maxY)) { mLocked.pointerX = (minX + maxX) * 0.5f; mLocked.pointerY = (minY + maxY) * 0.5f; - // Reload icon resources for density may be changed. - loadResourcesLocked(); } else { mLocked.pointerX = 0; mLocked.pointerY = 0; } fadeOutAndReleaseAllSpotsLocked(); - } else if (oldViewport.orientation != viewport.orientation) { + } + + if (mLocked.displayOrientation != orientation) { // Apply offsets to convert from the pixel top-left corner position to the pixel center. // This creates an invariant frame of reference that we can easily rotate when // taking into account that the pointer may be located at fractional pixel offsets. @@ -399,37 +405,37 @@ void PointerController::setDisplayViewport(const DisplayViewport& viewport) { float temp; // Undo the previous rotation. - switch (oldViewport.orientation) { + switch (mLocked.displayOrientation) { case DISPLAY_ORIENTATION_90: temp = x; - x = oldViewport.deviceHeight - y; + x = mLocked.displayWidth - y; y = temp; break; case DISPLAY_ORIENTATION_180: - x = oldViewport.deviceWidth - x; - y = oldViewport.deviceHeight - y; + x = mLocked.displayWidth - x; + y = mLocked.displayHeight - y; break; case DISPLAY_ORIENTATION_270: temp = x; x = y; - y = oldViewport.deviceWidth - temp; + y = mLocked.displayHeight - temp; break; } // Perform the new rotation. - switch (viewport.orientation) { + switch (orientation) { case DISPLAY_ORIENTATION_90: temp = x; x = y; - y = viewport.deviceHeight - temp; + y = mLocked.displayWidth - temp; break; case DISPLAY_ORIENTATION_180: - x = viewport.deviceWidth - x; - y = viewport.deviceHeight - y; + x = mLocked.displayWidth - x; + y = mLocked.displayHeight - y; break; case DISPLAY_ORIENTATION_270: temp = x; - x = viewport.deviceWidth - y; + x = mLocked.displayHeight - y; y = temp; break; } @@ -438,6 +444,7 @@ void PointerController::setDisplayViewport(const DisplayViewport& viewport) { // and save the results. mLocked.pointerX = x - 0.5f; mLocked.pointerY = y - 0.5f; + mLocked.displayOrientation = orientation; } updatePointerLocked(); @@ -607,16 +614,11 @@ void PointerController::removeInactivityTimeoutLocked() { mLooper->removeMessages(mHandler, MSG_INACTIVITY_TIMEOUT); } -void PointerController::updatePointerLocked() REQUIRES(mLock) { - if (!mLocked.viewport.isValid()) { - return; - } - +void PointerController::updatePointerLocked() { mSpriteController->openTransaction(); mLocked.pointerSprite->setLayer(Sprite::BASE_LAYER_POINTER); mLocked.pointerSprite->setPosition(mLocked.pointerX, mLocked.pointerY); - mLocked.pointerSprite->setDisplayId(mLocked.viewport.displayId); if (mLocked.pointerAlpha > 0) { mLocked.pointerSprite->setAlpha(mLocked.pointerAlpha); @@ -727,18 +729,8 @@ void PointerController::fadeOutAndReleaseAllSpotsLocked() { } } -void PointerController::loadResourcesLocked() REQUIRES(mLock) { +void PointerController::loadResources() { mPolicy->loadPointerResources(&mResources); - - if (mLocked.presentation == PRESENTATION_POINTER) { - mLocked.additionalMouseResources.clear(); - mLocked.animationResources.clear(); - mPolicy->loadPointerIcon(&mLocked.pointerIcon); - mPolicy->loadAdditionalMouseResources(&mLocked.additionalMouseResources, - &mLocked.animationResources); - } - - mLocked.pointerIconChanged = true; } diff --git a/libs/input/PointerController.h b/libs/input/PointerController.h index a32cc42a3342..7f4e5a59c9b6 100644 --- a/libs/input/PointerController.h +++ b/libs/input/PointerController.h @@ -23,7 +23,6 @@ #include <vector> #include <ui/DisplayInfo.h> -#include <input/DisplayViewport.h> #include <input/Input.h> #include <PointerControllerInterface.h> #include <utils/BitSet.h> @@ -97,7 +96,6 @@ public: virtual int32_t getButtonState() const; virtual void setPosition(float x, float y); virtual void getPosition(float* outX, float* outY) const; - virtual int32_t getDisplayId() const; virtual void fade(Transition transition); virtual void unfade(Transition transition); @@ -108,7 +106,7 @@ public: void updatePointerIcon(int32_t iconId); void setCustomPointerIcon(const SpriteIcon& icon); - void setDisplayViewport(const DisplayViewport& viewport); + void setDisplayViewport(int32_t width, int32_t height, int32_t orientation); void setInactivityTimeout(InactivityTimeout inactivityTimeout); void reloadPointerResources(); @@ -158,7 +156,9 @@ private: size_t animationFrameIndex; nsecs_t lastFrameUpdatedTime; - DisplayViewport viewport; + int32_t displayWidth; + int32_t displayHeight; + int32_t displayOrientation; InactivityTimeout inactivityTimeout; @@ -182,7 +182,7 @@ private: Vector<Spot*> spots; Vector<sp<Sprite> > recycledSprites; - } mLocked GUARDED_BY(mLock); + } mLocked; bool getBoundsLocked(float* outMinX, float* outMinY, float* outMaxX, float* outMaxY) const; void setPositionLocked(float x, float y); @@ -207,7 +207,7 @@ private: void fadeOutAndReleaseSpotLocked(Spot* spot); void fadeOutAndReleaseAllSpotsLocked(); - void loadResourcesLocked(); + void loadResources(); }; } // namespace android diff --git a/libs/input/SpriteController.cpp b/libs/input/SpriteController.cpp index c1868d3a94d6..eb2bc98ec9e9 100644 --- a/libs/input/SpriteController.cpp +++ b/libs/input/SpriteController.cpp @@ -144,16 +144,13 @@ void SpriteController::doUpdateSprites() { } } - // Resize and/or reparent sprites if needed. + // Resize sprites if needed. SurfaceComposerClient::Transaction t; bool needApplyTransaction = false; for (size_t i = 0; i < numSprites; i++) { SpriteUpdate& update = updates.editItemAt(i); - if (update.state.surfaceControl == nullptr) { - continue; - } - if (update.state.wantSurfaceVisible()) { + if (update.state.surfaceControl != NULL && update.state.wantSurfaceVisible()) { int32_t desiredWidth = update.state.icon.bitmap.width(); int32_t desiredHeight = update.state.icon.bitmap.height(); if (update.state.surfaceWidth < desiredWidth @@ -173,12 +170,6 @@ void SpriteController::doUpdateSprites() { } } } - - // If surface is a new one, we have to set right layer stack. - if (update.surfaceChanged || update.state.dirty & DIRTY_DISPLAY_ID) { - t.setLayerStack(update.state.surfaceControl, update.state.displayId); - needApplyTransaction = true; - } } if (needApplyTransaction) { t.apply(); @@ -245,7 +236,7 @@ void SpriteController::doUpdateSprites() { if (update.state.surfaceControl != NULL && (becomingVisible || becomingHidden || (wantSurfaceVisibleAndDrawn && (update.state.dirty & (DIRTY_ALPHA | DIRTY_POSITION | DIRTY_TRANSFORMATION_MATRIX | DIRTY_LAYER - | DIRTY_VISIBILITY | DIRTY_HOTSPOT | DIRTY_DISPLAY_ID))))) { + | DIRTY_VISIBILITY | DIRTY_HOTSPOT))))) { needApplyTransaction = true; if (wantSurfaceVisibleAndDrawn @@ -454,15 +445,6 @@ void SpriteController::SpriteImpl::setTransformationMatrix( } } -void SpriteController::SpriteImpl::setDisplayId(int32_t displayId) { - AutoMutex _l(mController->mLock); - - if (mLocked.state.displayId != displayId) { - mLocked.state.displayId = displayId; - invalidateLocked(DIRTY_DISPLAY_ID); - } -} - void SpriteController::SpriteImpl::invalidateLocked(uint32_t dirty) { bool wasDirty = mLocked.state.dirty; mLocked.state.dirty |= dirty; diff --git a/libs/input/SpriteController.h b/libs/input/SpriteController.h index 5b216f50d113..31e43e9b99e5 100644 --- a/libs/input/SpriteController.h +++ b/libs/input/SpriteController.h @@ -125,9 +125,6 @@ public: /* Sets the sprite transformation matrix. */ virtual void setTransformationMatrix(const SpriteTransformationMatrix& matrix) = 0; - - /* Sets the id of the display where the sprite should be shown. */ - virtual void setDisplayId(int32_t displayId) = 0; }; /* @@ -173,7 +170,6 @@ private: DIRTY_LAYER = 1 << 4, DIRTY_VISIBILITY = 1 << 5, DIRTY_HOTSPOT = 1 << 6, - DIRTY_DISPLAY_ID = 1 << 7, }; /* Describes the state of a sprite. @@ -184,7 +180,7 @@ private: struct SpriteState { inline SpriteState() : dirty(0), visible(false), - positionX(0), positionY(0), layer(0), alpha(1.0f), displayId(ADISPLAY_ID_DEFAULT), + positionX(0), positionY(0), layer(0), alpha(1.0f), surfaceWidth(0), surfaceHeight(0), surfaceDrawn(false), surfaceVisible(false) { } @@ -197,7 +193,6 @@ private: int32_t layer; float alpha; SpriteTransformationMatrix transformationMatrix; - int32_t displayId; sp<SurfaceControl> surfaceControl; int32_t surfaceWidth; @@ -230,7 +225,6 @@ private: virtual void setLayer(int32_t layer); virtual void setAlpha(float alpha); virtual void setTransformationMatrix(const SpriteTransformationMatrix& matrix); - virtual void setDisplayId(int32_t displayId); inline const SpriteState& getStateLocked() const { return mLocked.state; diff --git a/location/tests/locationtests/Android.mk b/location/tests/locationtests/Android.mk index b2fd8ecef734..3dcf69426362 100644 --- a/location/tests/locationtests/Android.mk +++ b/location/tests/locationtests/Android.mk @@ -12,7 +12,7 @@ LOCAL_PACKAGE_NAME := FrameworksLocationTests LOCAL_PRIVATE_PLATFORM_APIS := true LOCAL_STATIC_JAVA_LIBRARIES := \ - android-support-test \ + androidx.test.rules \ core-test-rules \ guava \ mockito-target-minus-junit4 \ diff --git a/location/tests/locationtests/AndroidManifest.xml b/location/tests/locationtests/AndroidManifest.xml index ddb8ea6aa53a..5010d3d56a50 100644 --- a/location/tests/locationtests/AndroidManifest.xml +++ b/location/tests/locationtests/AndroidManifest.xml @@ -29,7 +29,7 @@ </application> <instrumentation - android:name="android.support.test.runner.AndroidJUnitRunner" + android:name="androidx.test.runner.AndroidJUnitRunner" android:targetPackage="com.android.frameworks.locationtests" android:label="Frameworks Location Tests" /> </manifest> diff --git a/location/tests/locationtests/AndroidTest.xml b/location/tests/locationtests/AndroidTest.xml index bb6547bec0f7..7bddb58f2cf2 100644 --- a/location/tests/locationtests/AndroidTest.xml +++ b/location/tests/locationtests/AndroidTest.xml @@ -22,7 +22,7 @@ <option name="test-tag" value="FrameworksLocationTests" /> <test class="com.android.tradefed.testtype.AndroidJUnitTest" > <option name="package" value="com.android.frameworks.locationtests" /> - <option name="runner" value="android.support.test.runner.AndroidJUnitRunner" /> + <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" /> <option name="hidden-api-checks" value="false"/> </test> </configuration> diff --git a/media/java/android/media/MediaItem2.java b/media/java/android/media/MediaItem2.java new file mode 100644 index 000000000000..aa2a937956c5 --- /dev/null +++ b/media/java/android/media/MediaItem2.java @@ -0,0 +1,324 @@ +/* + * 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.media; + +import static android.media.MediaMetadata.METADATA_KEY_MEDIA_ID; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Parcel; +import android.os.Parcelable; +import android.text.TextUtils; +import android.util.Log; +import android.util.Pair; + +import com.android.internal.annotations.GuardedBy; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Executor; + +/** + * A class with information on a single media item with the metadata information. Here are use + * cases. + * <ul> + * <li>Specify media items to {@link SessionPlayer2} for playback. + * <li>Share media items across the processes. + * </ul> + * <p> + * Subclasses of the session player may only accept certain subclasses of the media items. Check + * the player documentation that you're interested in. + * <p> + * When it's shared across the processes, we cannot guarantee that they contain the right values + * because media items are application dependent especially for the metadata. + * <p> + * This object is thread safe. + * <p> + * This API is not generally intended for third party application developers. + * Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a> + * {@link androidx.media2.MediaItem} for consistent behavior across all devices. + * </p> + * @hide + */ +public class MediaItem2 implements Parcelable { + private static final String TAG = "MediaItem2"; + + // intentionally less than long.MAX_VALUE. + // Declare this first to avoid 'illegal forward reference'. + static final long LONG_MAX = 0x7ffffffffffffffL; + + /** + * Used when a position is unknown. + * + * @see #getEndPosition() + */ + public static final long POSITION_UNKNOWN = LONG_MAX; + + public static final Parcelable.Creator<MediaItem2> CREATOR = + new Parcelable.Creator<MediaItem2>() { + @Override + public MediaItem2 createFromParcel(Parcel in) { + return new MediaItem2(in); + } + + @Override + public MediaItem2[] newArray(int size) { + return new MediaItem2[size]; + } + }; + + // TODO: Use SessionPlayer2.UNKNOWN_TIME instead + private static final long UNKNOWN_TIME = -1; + + private final long mStartPositionMs; + private final long mEndPositionMs; + + private final Object mLock = new Object(); + + @GuardedBy("mLock") + private MediaMetadata mMetadata; + @GuardedBy("mLock") + private final List<Pair<OnMetadataChangedListener, Executor>> mListeners = new ArrayList<>(); + + /** + * Used by {@link MediaItem2.Builder}. + */ + // Note: Needs to be protected when we want to allow 3rd party player to define customized + // MediaItem2. + @SuppressWarnings("WeakerAccess") /* synthetic access */ + MediaItem2(Builder builder) { + this(builder.mMetadata, builder.mStartPositionMs, builder.mEndPositionMs); + } + + /** + * Used by Parcelable.Creator. + */ + // Note: Needs to be protected when we want to allow 3rd party player to define customized + // MediaItem2. + @SuppressWarnings("WeakerAccess") /* synthetic access */ + MediaItem2(Parcel in) { + this(in.readParcelable(MediaItem2.class.getClassLoader()), in.readLong(), in.readLong()); + } + + @SuppressWarnings("WeakerAccess") /* synthetic access */ + MediaItem2(MediaItem2 item) { + this(item.mMetadata, item.mStartPositionMs, item.mEndPositionMs); + } + + @SuppressWarnings("WeakerAccess") /* synthetic access */ + MediaItem2(@Nullable MediaMetadata metadata, long startPositionMs, long endPositionMs) { + if (startPositionMs > endPositionMs) { + throw new IllegalArgumentException("Illegal start/end position: " + + startPositionMs + " : " + endPositionMs); + } + if (metadata != null && metadata.containsKey(MediaMetadata.METADATA_KEY_DURATION)) { + long durationMs = metadata.getLong(MediaMetadata.METADATA_KEY_DURATION); + if (durationMs != UNKNOWN_TIME && endPositionMs != POSITION_UNKNOWN + && endPositionMs > durationMs) { + throw new IllegalArgumentException("endPositionMs shouldn't be greater than" + + " duration in the metdata, endPositionMs=" + endPositionMs + + ", durationMs=" + durationMs); + } + } + mMetadata = metadata; + mStartPositionMs = startPositionMs; + mEndPositionMs = endPositionMs; + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder(getClass().getSimpleName()); + synchronized (mLock) { + sb.append("{mMetadata=").append(mMetadata); + sb.append(", mStartPositionMs=").append(mStartPositionMs); + sb.append(", mEndPositionMs=").append(mEndPositionMs); + sb.append('}'); + } + return sb.toString(); + } + + /** + * Sets metadata. If the metadata is not {@code null}, its id should be matched with this + * instance's media id. + * + * @param metadata metadata to update + * @see MediaMetadata#METADATA_KEY_MEDIA_ID + */ + public void setMetadata(@Nullable MediaMetadata metadata) { + List<Pair<OnMetadataChangedListener, Executor>> listeners = new ArrayList<>(); + synchronized (mLock) { + if (mMetadata != null && metadata != null + && !TextUtils.equals(getMediaId(), metadata.getString(METADATA_KEY_MEDIA_ID))) { + Log.d(TAG, "MediaItem2's media ID shouldn't be changed"); + return; + } + mMetadata = metadata; + listeners.addAll(mListeners); + } + + for (Pair<OnMetadataChangedListener, Executor> pair : listeners) { + final OnMetadataChangedListener listener = pair.first; + pair.second.execute(new Runnable() { + @Override + public void run() { + listener.onMetadataChanged(MediaItem2.this); + } + }); + } + } + + /** + * Gets the metadata of the media. + * + * @return metadata from the session + */ + public @Nullable MediaMetadata getMetadata() { + synchronized (mLock) { + return mMetadata; + } + } + + /** + * Return the position in milliseconds at which the playback will start. + * @return the position in milliseconds at which the playback will start + */ + public long getStartPosition() { + return mStartPositionMs; + } + + /** + * Return the position in milliseconds at which the playback will end. + * {@link #POSITION_UNKNOWN} means ending at the end of source content. + * @return the position in milliseconds at which the playback will end + */ + public long getEndPosition() { + return mEndPositionMs; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeParcelable(mMetadata, 0); + dest.writeLong(mStartPositionMs); + dest.writeLong(mEndPositionMs); + } + + /** + * Gets the media id for this item. If it's not {@code null}, it's a persistent unique key + * for the underlying media content. + * + * @return media Id from the session + */ + @Nullable String getMediaId() { + synchronized (mLock) { + return mMetadata != null + ? mMetadata.getString(METADATA_KEY_MEDIA_ID) : null; + } + } + + void addOnMetadataChangedListener(Executor executor, OnMetadataChangedListener listener) { + synchronized (mLock) { + for (Pair<OnMetadataChangedListener, Executor> pair : mListeners) { + if (pair.first == listener) { + return; + } + } + mListeners.add(new Pair<>(listener, executor)); + } + } + + void removeOnMetadataChangedListener(OnMetadataChangedListener listener) { + synchronized (mLock) { + for (int i = mListeners.size() - 1; i >= 0; i--) { + if (mListeners.get(i).first == listener) { + mListeners.remove(i); + return; + } + } + } + } + + /** + * Builder for {@link MediaItem2}. + */ + public static class Builder { + @SuppressWarnings("WeakerAccess") /* synthetic access */ + MediaMetadata mMetadata; + @SuppressWarnings("WeakerAccess") /* synthetic access */ + long mStartPositionMs = 0; + @SuppressWarnings("WeakerAccess") /* synthetic access */ + long mEndPositionMs = POSITION_UNKNOWN; + + /** + * Set the metadata of this instance. {@code null} for unset. + * + * @param metadata metadata + * @return this instance for chaining + */ + public @NonNull Builder setMetadata(@Nullable MediaMetadata metadata) { + mMetadata = metadata; + return this; + } + + /** + * Sets the start position in milliseconds at which the playback will start. + * Any negative number is treated as 0. + * + * @param position the start position in milliseconds at which the playback will start + * @return the same Builder instance. + */ + public @NonNull Builder setStartPosition(long position) { + if (position < 0) { + position = 0; + } + mStartPositionMs = position; + return this; + } + + /** + * Sets the end position in milliseconds at which the playback will end. + * Any negative number is treated as maximum length of the media item. + * + * @param position the end position in milliseconds at which the playback will end + * @return the same Builder instance. + */ + public @NonNull Builder setEndPosition(long position) { + if (position < 0) { + position = POSITION_UNKNOWN; + } + mEndPositionMs = position; + return this; + } + + /** + * Build {@link MediaItem2}. + * + * @return a new {@link MediaItem2}. + */ + public @NonNull MediaItem2 build() { + return new MediaItem2(this); + } + } + + interface OnMetadataChangedListener { + void onMetadataChanged(MediaItem2 item); + } +} diff --git a/media/java/android/media/MediaMetadataRetriever.java b/media/java/android/media/MediaMetadataRetriever.java index 00a393a902b4..d656fa359826 100644 --- a/media/java/android/media/MediaMetadataRetriever.java +++ b/media/java/android/media/MediaMetadataRetriever.java @@ -40,8 +40,7 @@ import java.util.Map; * MediaMetadataRetriever class provides a unified interface for retrieving * frame and meta data from an input media file. */ -public class MediaMetadataRetriever -{ +public class MediaMetadataRetriever implements AutoCloseable { static { System.loadLibrary("media_jni"); native_init(); @@ -672,6 +671,11 @@ public class MediaMetadataRetriever @UnsupportedAppUsage private native byte[] getEmbeddedPicture(int pictureType); + @Override + public void close() { + release(); + } + /** * Call it when one is done with the object. This method releases the memory * allocated internally. diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java index 18d36eb1f753..0057875ec3f4 100644 --- a/media/java/android/media/MediaPlayer.java +++ b/media/java/android/media/MediaPlayer.java @@ -19,7 +19,6 @@ package android.media; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; -import android.annotation.TestApi; import android.annotation.UnsupportedAppUsage; import android.app.ActivityThread; import android.content.ContentProvider; @@ -1680,37 +1679,6 @@ public class MediaPlayer extends PlayerBase public native boolean isPlaying(); /** - * Gets the current buffering management params used by the source component. - * Calling it only after {@code setDataSource} has been called. - * Each type of data source might have different set of default params. - * - * @return the current buffering management params used by the source component. - * @throws IllegalStateException if the internal player engine has not been - * initialized, or {@code setDataSource} has not been called. - * @hide - */ - @NonNull - @TestApi - public native BufferingParams getBufferingParams(); - - /** - * Sets buffering management params. - * The object sets its internal BufferingParams to the input, except that the input is - * invalid or not supported. - * Call it only after {@code setDataSource} has been called. - * The input is a hint to MediaPlayer. - * - * @param params the buffering management params. - * - * @throws IllegalStateException if the internal player engine has not been - * initialized or has been released, or {@code setDataSource} has not been called. - * @throws IllegalArgumentException if params is invalid or not supported. - * @hide - */ - @TestApi - public native void setBufferingParams(@NonNull BufferingParams params); - - /** * Change playback speed of audio by resampling the audio. * <p> * Specifies resampling as audio mode for variable rate playback, i.e., diff --git a/media/java/android/media/MediaPlayer2.java b/media/java/android/media/MediaPlayer2.java index d4b1c7f868cb..b137ce2cda0f 100644 --- a/media/java/android/media/MediaPlayer2.java +++ b/media/java/android/media/MediaPlayer2.java @@ -544,32 +544,55 @@ public class MediaPlayer2 implements AutoCloseable public native long getCurrentPosition(); /** - * Gets the duration of the file. + * Gets the duration of the dsd. * + * @param dsd the descriptor of data source of which you want to get duration * @return the duration in milliseconds, if no duration is available * (for example, if streaming live content), -1 is returned. + * @throws NullPointerException if dsd is null */ - public native long getDuration(); + public long getDuration(@NonNull DataSourceDesc dsd) { + if (dsd == null) { + throw new NullPointerException("non-null dsd is expected"); + } + SourceInfo sourceInfo = getSourceInfo(dsd); + if (sourceInfo == null) { + return -1; + } + + return native_getDuration(sourceInfo.mId); + } + + private native long native_getDuration(long srcId); /** - * Gets the current buffered media source position received through progressive downloading. + * Gets the buffered media source position of given dsd. * For example a buffering update of 8000 milliseconds when 5000 milliseconds of the content * has already been played indicates that the next 3000 milliseconds of the * content to play has been buffered. * + * @param dsd the descriptor of data source of which you want to get buffered position * @return the current buffered media source position in milliseconds + * @throws NullPointerException if dsd is null */ - public long getBufferedPosition() { + public long getBufferedPosition(@NonNull DataSourceDesc dsd) { + if (dsd == null) { + throw new NullPointerException("non-null dsd is expected"); + } + SourceInfo sourceInfo = getSourceInfo(dsd); + if (sourceInfo == null) { + return 0; + } + // Use cached buffered percent for now. - int bufferedPercentage; - synchronized (mSrcLock) { - if (mCurrentSourceInfo == null) { - bufferedPercentage = 0; - } else { - bufferedPercentage = mCurrentSourceInfo.mBufferedPercentage.get(); - } + int bufferedPercentage = sourceInfo.mBufferedPercentage.get(); + + long duration = getDuration(dsd); + if (duration < 0) { + duration = 0; } - return getDuration() * bufferedPercentage / 100; + + return duration * bufferedPercentage / 100; } /** @@ -1467,7 +1490,6 @@ public class MediaPlayer2 implements AutoCloseable private native PersistableBundle native_getMetrics(); - /** * Gets the current buffering management params used by the source component. * Calling it only after {@code setDataSource} has been called. @@ -1505,7 +1527,6 @@ public class MediaPlayer2 implements AutoCloseable private native void native_setBufferingParams(@NonNull BufferingParams params); - /** * Sets playback rate using {@link PlaybackParams}. The object sets its internal * PlaybackParams to the input. This allows the object to resume at previous speed @@ -1969,19 +1990,31 @@ public class MediaPlayer2 implements AutoCloseable /** * Returns a List of track information. * + * @param dsd the descriptor of data source of which you want to get track info * @return List of track info. The total number of tracks is the array length. * Must be called again if an external timed text source has been added after * addTimedTextSource method is called. * @throws IllegalStateException if it is called in an invalid state. + * @throws NullPointerException if dsd is null */ - public @NonNull List<TrackInfo> getTrackInfo() { - TrackInfo[] trackInfo = getInbandTrackInfo(); + + public @NonNull List<TrackInfo> getTrackInfo(@NonNull DataSourceDesc dsd) { + if (dsd == null) { + throw new NullPointerException("non-null dsd is expected"); + } + SourceInfo sourceInfo = getSourceInfo(dsd); + if (sourceInfo == null) { + return new ArrayList<TrackInfo>(0); + } + + TrackInfo[] trackInfo = getInbandTrackInfo(sourceInfo); return (trackInfo != null ? Arrays.asList(trackInfo) : new ArrayList<TrackInfo>(0)); } - private TrackInfo[] getInbandTrackInfo() throws IllegalStateException { + private TrackInfo[] getInbandTrackInfo(SourceInfo sourceInfo) throws IllegalStateException { PlayerMessage request = PlayerMessage.newBuilder() .addValues(Value.newBuilder().setInt32Value(INVOKE_ID_GET_TRACK_INFO)) + .addValues(Value.newBuilder().setInt64Value(sourceInfo.mId)) .build(); PlayerMessage response = invoke(request); if (response == null) { @@ -2001,9 +2034,10 @@ public class MediaPlayer2 implements AutoCloseable /** * Returns the index of the audio, video, or subtitle track currently selected for playback, - * The return value is an index into the array returned by {@link #getTrackInfo()}, and can - * be used in calls to {@link #selectTrack(int)} or {@link #deselectTrack(int)}. + * The return value is an index into the array returned by {@link #getTrackInfo}, and can + * be used in calls to {@link #selectTrack} or {@link #deselectTrack}. * + * @param dsd the descriptor of data source of which you want to get selected track * @param trackType should be one of {@link TrackInfo#MEDIA_TRACK_TYPE_VIDEO}, * {@link TrackInfo#MEDIA_TRACK_TYPE_AUDIO}, or * {@link TrackInfo#MEDIA_TRACK_TYPE_SUBTITLE} @@ -2011,14 +2045,24 @@ public class MediaPlayer2 implements AutoCloseable * a negative integer is returned when there is no selected track for {@code trackType} or * when {@code trackType} is not one of audio, video, or subtitle. * @throws IllegalStateException if called after {@link #close()} + * @throws NullPointerException if dsd is null * - * @see #getTrackInfo() - * @see #selectTrack(int) - * @see #deselectTrack(int) + * @see #getTrackInfo + * @see #selectTrack + * @see #deselectTrack */ - public int getSelectedTrack(int trackType) { + public int getSelectedTrack(@NonNull DataSourceDesc dsd, int trackType) { + if (dsd == null) { + throw new NullPointerException("non-null dsd is expected"); + } + SourceInfo sourceInfo = getSourceInfo(dsd); + if (sourceInfo == null) { + return -1; + } + PlayerMessage request = PlayerMessage.newBuilder() .addValues(Value.newBuilder().setInt32Value(INVOKE_ID_GET_SELECTED_TRACK)) + .addValues(Value.newBuilder().setInt64Value(sourceInfo.mId)) .addValues(Value.newBuilder().setInt32Value(trackType)) .build(); PlayerMessage response = invoke(request); @@ -2049,19 +2093,20 @@ public class MediaPlayer2 implements AutoCloseable * In addition, the support for selecting an audio track at runtime is pretty limited * in that an audio track can only be selected in the <em>Prepared</em> state. * </p> + * @param dsd the descriptor of data source of which you want to select track * @param index the index of the track to be selected. The valid range of the index * is 0..total number of track - 1. The total number of tracks as well as the type of - * each individual track can be found by calling {@link #getTrackInfo()} method. + * each individual track can be found by calling {@link #getTrackInfo} method. * @return a token which can be used to cancel the operation later with {@link #cancelCommand}. * * @see MediaPlayer2#getTrackInfo */ // This is an asynchronous call. - public Object selectTrack(int index) { + public Object selectTrack(@NonNull DataSourceDesc dsd, int index) { return addTask(new Task(CALL_COMPLETED_SELECT_TRACK, false) { @Override void process() { - selectOrDeselectTrack(index, true /* select */); + selectOrDeselectTrack(dsd, index, true /* select */); } }); } @@ -2073,28 +2118,37 @@ public class MediaPlayer2 implements AutoCloseable * deselected. If the timed text track identified by index has not been * selected before, it throws an exception. * </p> + * @param dsd the descriptor of data source of which you want to deselect track * @param index the index of the track to be deselected. The valid range of the index * is 0..total number of tracks - 1. The total number of tracks as well as the type of - * each individual track can be found by calling {@link #getTrackInfo()} method. + * each individual track can be found by calling {@link #getTrackInfo} method. * @return a token which can be used to cancel the operation later with {@link #cancelCommand}. * * @see MediaPlayer2#getTrackInfo */ // This is an asynchronous call. - public Object deselectTrack(int index) { + public Object deselectTrack(@NonNull DataSourceDesc dsd, int index) { return addTask(new Task(CALL_COMPLETED_DESELECT_TRACK, false) { @Override void process() { - selectOrDeselectTrack(index, false /* select */); + selectOrDeselectTrack(dsd, index, false /* select */); } }); } - private void selectOrDeselectTrack(int index, boolean select) - throws IllegalStateException { + private void selectOrDeselectTrack(@NonNull DataSourceDesc dsd, int index, boolean select) { + if (dsd == null) { + throw new IllegalArgumentException("non-null dsd is expected"); + } + SourceInfo sourceInfo = getSourceInfo(dsd); + if (sourceInfo == null) { + return; + } + PlayerMessage request = PlayerMessage.newBuilder() .addValues(Value.newBuilder().setInt32Value( select ? INVOKE_ID_SELECT_TRACK : INVOKE_ID_DESELECT_TRACK)) + .addValues(Value.newBuilder().setInt64Value(sourceInfo.mId)) .addValues(Value.newBuilder().setInt32Value(index)) .build(); invoke(request); @@ -2568,7 +2622,7 @@ public class MediaPlayer2 implements AutoCloseable * Currently only HTTP live streaming data URI's embedded with timed ID3 tags generates * {@link TimedMetaData}. * - * @see MediaPlayer2#selectTrack(int) + * @see MediaPlayer2#selectTrack * @see MediaPlayer2.OnTimedMetaDataAvailableListener * @see TimedMetaData * diff --git a/media/java/android/media/MediaPlayerBase.java b/media/java/android/media/MediaPlayerBase.java deleted file mode 100644 index a4265525fb6b..000000000000 --- a/media/java/android/media/MediaPlayerBase.java +++ /dev/null @@ -1,331 +0,0 @@ -/* - * 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.media; - -import android.annotation.IntDef; -import android.annotation.NonNull; -import android.annotation.Nullable; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.util.List; -import java.util.concurrent.Executor; - -/** - * @hide - * Base class for all media players that want media session. - */ -public abstract class MediaPlayerBase implements AutoCloseable { - /** - * @hide - */ - @IntDef({ - PLAYER_STATE_IDLE, - PLAYER_STATE_PAUSED, - PLAYER_STATE_PLAYING, - PLAYER_STATE_ERROR }) - @Retention(RetentionPolicy.SOURCE) - public @interface PlayerState {} - - /** - * @hide - */ - @IntDef({ - BUFFERING_STATE_UNKNOWN, - BUFFERING_STATE_BUFFERING_AND_PLAYABLE, - BUFFERING_STATE_BUFFERING_AND_STARVED, - BUFFERING_STATE_BUFFERING_COMPLETE }) - @Retention(RetentionPolicy.SOURCE) - public @interface BuffState {} - - /** - * State when the player is idle, and needs configuration to start playback. - */ - public static final int PLAYER_STATE_IDLE = 0; - - /** - * State when the player's playback is paused - */ - public static final int PLAYER_STATE_PAUSED = 1; - - /** - * State when the player's playback is ongoing - */ - public static final int PLAYER_STATE_PLAYING = 2; - - /** - * State when the player is in error state and cannot be recovered self. - */ - public static final int PLAYER_STATE_ERROR = 3; - - /** - * Buffering state is unknown. - */ - public static final int BUFFERING_STATE_UNKNOWN = 0; - - /** - * Buffering state indicating the player is buffering but enough has been buffered - * for this player to be able to play the content. - * See {@link #getBufferedPosition()} for how far is buffered already. - */ - public static final int BUFFERING_STATE_BUFFERING_AND_PLAYABLE = 1; - - /** - * Buffering state indicating the player is buffering, but the player is currently starved - * for data, and cannot play. - */ - public static final int BUFFERING_STATE_BUFFERING_AND_STARVED = 2; - - /** - * Buffering state indicating the player is done buffering, and the remainder of the content is - * available for playback. - */ - public static final int BUFFERING_STATE_BUFFERING_COMPLETE = 3; - - /** - * Starts or resumes playback. - */ - public abstract void play(); - - /** - * Prepares the player for playback. - * See {@link PlayerEventCallback#onMediaPrepared(MediaPlayerBase, DataSourceDesc)} for being - * notified when the preparation phase completed. During this time, the player may allocate - * resources required to play, such as audio and video decoders. - */ - public abstract void prepare(); - - /** - * Pauses playback. - */ - public abstract void pause(); - - /** - * Resets the MediaPlayerBase to its uninitialized state. - */ - public abstract void reset(); - - /** - * - */ - public abstract void skipToNext(); - - /** - * Moves the playback head to the specified position - * @param pos the new playback position expressed in ms. - */ - public abstract void seekTo(long pos); - - public static final long UNKNOWN_TIME = -1; - - /** - * Gets the current playback head position. - * @return the current playback position in ms, or {@link #UNKNOWN_TIME} if unknown. - */ - public long getCurrentPosition() { return UNKNOWN_TIME; } - - /** - * Returns the duration of the current data source, or {@link #UNKNOWN_TIME} if unknown. - * @return the duration in ms, or {@link #UNKNOWN_TIME}. - */ - public long getDuration() { return UNKNOWN_TIME; } - - /** - * Gets the buffered position of current playback, or {@link #UNKNOWN_TIME} if unknown. - * @return the buffered position in ms, or {@link #UNKNOWN_TIME}. - */ - public long getBufferedPosition() { return UNKNOWN_TIME; } - - /** - * Returns the current player state. - * See also {@link PlayerEventCallback#onPlayerStateChanged(MediaPlayerBase, int)} for - * notification of changes. - * @return the current player state - */ - public abstract @PlayerState int getPlayerState(); - - /** - * Returns the current buffering state of the player. - * During buffering, see {@link #getBufferedPosition()} for the quantifying the amount already - * buffered. - * @return the buffering state. - */ - public abstract @BuffState int getBufferingState(); - - /** - * Sets the {@link AudioAttributes} to be used during the playback of the media. - * - * @param attributes non-null <code>AudioAttributes</code>. - */ - public abstract void setAudioAttributes(@NonNull AudioAttributes attributes); - - /** - * Returns AudioAttributes that media player has. - */ - public abstract @Nullable AudioAttributes getAudioAttributes(); - - /** - * Sets the data source to be played. - * @param dsd - */ - public abstract void setDataSource(@NonNull DataSourceDesc dsd); - - /** - * Sets the data source that will be played immediately after the current one is done playing. - * @param dsd - */ - public abstract void setNextDataSource(@NonNull DataSourceDesc dsd); - - /** - * Sets the list of data sources that will be sequentially played after the current one. Each - * data source is played immediately after the previous one is done playing. - * @param dsds - */ - public abstract void setNextDataSources(@NonNull List<DataSourceDesc> dsds); - - /** - * Returns the current data source. - * @return the current data source, or null if none is set, or none available to play. - */ - public abstract @Nullable DataSourceDesc getCurrentDataSource(); - - /** - * Configures the player to loop on the current data source. - * @param loop true if the current data source is meant to loop. - */ - public abstract void loopCurrent(boolean loop); - - /** - * Sets the playback speed. - * A value of 1.0f is the default playback value. - * A negative value indicates reverse playback, check {@link #isReversePlaybackSupported()} - * before using negative values.<br> - * After changing the playback speed, it is recommended to query the actual speed supported - * by the player, see {@link #getPlaybackSpeed()}. - * @param speed - */ - public abstract void setPlaybackSpeed(float speed); - - /** - * Returns the actual playback speed to be used by the player when playing. - * Note that it may differ from the speed set in {@link #setPlaybackSpeed(float)}. - * @return the actual playback speed - */ - public float getPlaybackSpeed() { return 1.0f; } - - /** - * Indicates whether reverse playback is supported. - * Reverse playback is indicated by negative playback speeds, see - * {@link #setPlaybackSpeed(float)}. - * @return true if reverse playback is supported. - */ - public boolean isReversePlaybackSupported() { return false; } - - /** - * Sets the volume of the audio of the media to play, expressed as a linear multiplier - * on the audio samples. - * Note that this volume is specific to the player, and is separate from stream volume - * used across the platform.<br> - * A value of 0.0f indicates muting, a value of 1.0f is the nominal unattenuated and unamplified - * gain. See {@link #getMaxPlayerVolume()} for the volume range supported by this player. - * @param volume a value between 0.0f and {@link #getMaxPlayerVolume()}. - */ - public abstract void setPlayerVolume(float volume); - - /** - * Returns the current volume of this player to this player. - * Note that it does not take into account the associated stream volume. - * @return the player volume. - */ - public abstract float getPlayerVolume(); - - /** - * @return the maximum volume that can be used in {@link #setPlayerVolume(float)}. - */ - public float getMaxPlayerVolume() { return 1.0f; } - - /** - * Adds a callback to be notified of events for this player. - * @param e the {@link Executor} to be used for the events. - * @param cb the callback to receive the events. - */ - public abstract void registerPlayerEventCallback(@NonNull Executor e, - @NonNull PlayerEventCallback cb); - - /** - * Removes a previously registered callback for player events - * @param cb the callback to remove - */ - public abstract void unregisterPlayerEventCallback(@NonNull PlayerEventCallback cb); - - /** - * A callback class to receive notifications for events on the media player. - * See {@link MediaPlayerBase#registerPlayerEventCallback(Executor, PlayerEventCallback)} to - * register this callback. - */ - public static abstract class PlayerEventCallback { - /** - * Called when the player's current data source has changed. - * - * @param mpb the player whose data source changed. - * @param dsd the new current data source. null, if no more data sources available. - */ - public void onCurrentDataSourceChanged(@NonNull MediaPlayerBase mpb, - @Nullable DataSourceDesc dsd) { } - /** - * Called when the player is <i>prepared</i>, i.e. it is ready to play the content - * referenced by the given data source. - * @param mpb the player that is prepared. - * @param dsd the data source that the player is prepared to play. - */ - public void onMediaPrepared(@NonNull MediaPlayerBase mpb, @NonNull DataSourceDesc dsd) { } - - /** - * Called to indicate that the state of the player has changed. - * See {@link MediaPlayerBase#getPlayerState()} for polling the player state. - * @param mpb the player whose state has changed. - * @param state the new state of the player. - */ - public void onPlayerStateChanged(@NonNull MediaPlayerBase mpb, @PlayerState int state) { } - - /** - * Called to report buffering events for a data source. - * @param mpb the player that is buffering - * @param dsd the data source for which buffering is happening. - * @param state the new buffering state. - */ - public void onBufferingStateChanged(@NonNull MediaPlayerBase mpb, - @NonNull DataSourceDesc dsd, @BuffState int state) { } - - /** - * Called to indicate that the playback speed has changed. - * @param mpb the player that has changed the playback speed. - * @param speed the new playback speed. - */ - public void onPlaybackSpeedChanged(@NonNull MediaPlayerBase mpb, float speed) { } - - /** - * Called to indicate that {@link #seekTo(long)} is completed. - * - * @param mpb the player that has completed seeking. - * @param position the previous seeking request. - * @see #seekTo(long) - */ - public void onSeekCompleted(@NonNull MediaPlayerBase mpb, long position) { } - } - -} diff --git a/media/java/android/media/ThumbnailUtils.java b/media/java/android/media/ThumbnailUtils.java index fd1406078e7a..f07076ad14aa 100644 --- a/media/java/android/media/ThumbnailUtils.java +++ b/media/java/android/media/ThumbnailUtils.java @@ -16,33 +16,53 @@ package android.media; +import static android.media.MediaMetadataRetriever.METADATA_KEY_DURATION; +import static android.media.MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT; +import static android.media.MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH; +import static android.media.MediaMetadataRetriever.OPTION_CLOSEST_SYNC; +import static android.os.Environment.MEDIA_UNKNOWN; + +import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.UnsupportedAppUsage; import android.content.ContentResolver; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; +import android.graphics.ImageDecoder; +import android.graphics.ImageDecoder.ImageInfo; +import android.graphics.ImageDecoder.Source; import android.graphics.Matrix; +import android.graphics.Point; import android.graphics.Rect; import android.net.Uri; +import android.os.CancellationSignal; +import android.os.Environment; import android.os.ParcelFileDescriptor; -import android.provider.MediaStore.Images; +import android.provider.MediaStore.ThumbnailConstants; import android.util.Log; +import android.util.Size; -import java.io.FileDescriptor; -import java.io.FileInputStream; +import com.android.internal.util.ArrayUtils; + +import libcore.io.IoUtils; + +import java.io.File; import java.io.IOException; +import java.util.Arrays; +import java.util.Comparator; +import java.util.function.ToIntFunction; /** - * Thumbnail generation routines for media provider. + * Utilities for generating visual thumbnails from files. */ - public class ThumbnailUtils { private static final String TAG = "ThumbnailUtils"; - /* Maximum pixels size for created bitmap. */ - private static final int MAX_NUM_PIXELS_THUMBNAIL = 512 * 384; - private static final int MAX_NUM_PIXELS_MICRO_THUMBNAIL = 160 * 120; - private static final int UNCONSTRAINED = -1; + /** @hide */ + @Deprecated + @UnsupportedAppUsage + public static final int TARGET_SIZE_MICRO_THUMBNAIL = 96; /* Options used internally. */ private static final int OPTIONS_NONE = 0x0; @@ -54,153 +74,252 @@ public class ThumbnailUtils { */ public static final int OPTIONS_RECYCLE_INPUT = 0x2; + private static Size convertKind(int kind) { + if (kind == ThumbnailConstants.MICRO_KIND) { + return Point.convert(ThumbnailConstants.MICRO_SIZE); + } else if (kind == ThumbnailConstants.FULL_SCREEN_KIND) { + return Point.convert(ThumbnailConstants.FULL_SCREEN_SIZE); + } else if (kind == ThumbnailConstants.MINI_KIND) { + return Point.convert(ThumbnailConstants.MINI_SIZE); + } else { + throw new IllegalArgumentException("Unsupported kind: " + kind); + } + } + + private static class Resizer implements ImageDecoder.OnHeaderDecodedListener { + private final Size size; + private final CancellationSignal signal; + + public Resizer(Size size, CancellationSignal signal) { + this.size = size; + this.signal = signal; + } + + @Override + public void onHeaderDecoded(ImageDecoder decoder, ImageInfo info, Source source) { + // One last-ditch check to see if we've been canceled. + if (signal != null) signal.throwIfCanceled(); + + // We don't know how clients will use the decoded data, so we have + // to default to the more flexible "software" option. + decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE); + + // We requested a rough thumbnail size, but the remote size may have + // returned something giant, so defensively scale down as needed. + final int widthSample = info.getSize().getWidth() / size.getWidth(); + final int heightSample = info.getSize().getHeight() / size.getHeight(); + final int sample = Math.max(widthSample, heightSample); + if (sample > 1) { + decoder.setTargetSampleSize(sample); + } + } + } + /** - * Constant used to indicate the dimension of mini thumbnail. - * @hide Only used by media framework and media provider internally. + * Create a thumbnail for given audio file. + * + * @param filePath The audio file. + * @param kind The desired thumbnail kind, such as + * {@link android.provider.MediaStore.Images.Thumbnails#MINI_KIND}. */ - public static final int TARGET_SIZE_MINI_THUMBNAIL = 320; + @Deprecated + public static @Nullable Bitmap createAudioThumbnail(@NonNull String filePath, int kind) { + try { + return createAudioThumbnail(new File(filePath), convertKind(kind), null); + } catch (IOException e) { + Log.w(TAG, e); + return null; + } + } /** - * Constant used to indicate the dimension of micro thumbnail. - * @hide Only used by media framework and media provider internally. + * Create a thumbnail for given audio file. + * + * @param file The audio file. + * @param size The desired thumbnail size. + * @throws IOException If any trouble was encountered while generating or + * loading the thumbnail, or if + * {@link CancellationSignal#cancel()} was invoked. */ - @UnsupportedAppUsage - public static final int TARGET_SIZE_MICRO_THUMBNAIL = 96; + public static @NonNull Bitmap createAudioThumbnail(@NonNull File file, @NonNull Size size, + @Nullable CancellationSignal signal) throws IOException { + // Checkpoint before going deeper + if (signal != null) signal.throwIfCanceled(); + + final Resizer resizer = new Resizer(size, signal); + try (MediaMetadataRetriever retriever = new MediaMetadataRetriever()) { + retriever.setDataSource(file.getAbsolutePath()); + final byte[] raw = retriever.getEmbeddedPicture(); + if (raw != null) { + return ImageDecoder.decodeBitmap(ImageDecoder.createSource(raw), resizer); + } + } catch (RuntimeException e) { + throw new IOException("Failed to create thumbnail", e); + } + + // Only poke around for files on external storage + if (MEDIA_UNKNOWN.equals(Environment.getExternalStorageState(file))) { + throw new IOException("No embedded album art found"); + } + + // Ignore "Downloads" or top-level directories + final File parent = file.getParentFile(); + final File grandParent = parent != null ? parent.getParentFile() : null; + if (parent != null + && parent.getName().equals(Environment.DIRECTORY_DOWNLOADS)) { + throw new IOException("No thumbnails in Downloads directories"); + } + if (grandParent != null + && MEDIA_UNKNOWN.equals(Environment.getExternalStorageState(grandParent))) { + throw new IOException("No thumbnails in top-level directories"); + } + + // If no embedded image found, look around for best standalone file + final File[] found = ArrayUtils + .defeatNullable(file.getParentFile().listFiles((dir, name) -> { + final String lower = name.toLowerCase(); + return (lower.endsWith(".jpg") || lower.endsWith(".png")); + })); + + final ToIntFunction<File> score = (f) -> { + final String lower = f.getName().toLowerCase(); + if (lower.equals("albumart.jpg")) return 4; + if (lower.startsWith("albumart") && lower.endsWith(".jpg")) return 3; + if (lower.contains("albumart") && lower.endsWith(".jpg")) return 2; + if (lower.endsWith(".jpg")) return 1; + return 0; + }; + final Comparator<File> bestScore = (a, b) -> { + return score.applyAsInt(a) - score.applyAsInt(b); + }; + + final File bestFile = Arrays.asList(found).stream().max(bestScore).orElse(null); + if (bestFile == null) { + throw new IOException("No album art found"); + } + + // Checkpoint before going deeper + if (signal != null) signal.throwIfCanceled(); + + return ImageDecoder.decodeBitmap(ImageDecoder.createSource(bestFile), resizer); + } /** - * This method first examines if the thumbnail embedded in EXIF is bigger than our target - * size. If not, then it'll create a thumbnail from original image. Due to efficiency - * consideration, we want to let MediaThumbRequest avoid calling this method twice for - * both kinds, so it only requests for MICRO_KIND and set saveImage to true. - * - * This method always returns a "square thumbnail" for MICRO_KIND thumbnail. + * Create a thumbnail for given image file. * - * @param filePath the path of image file - * @param kind could be MINI_KIND or MICRO_KIND - * @return Bitmap, or null on failures + * @param filePath The image file. + * @param kind The desired thumbnail kind, such as + * {@link android.provider.MediaStore.Images.Thumbnails#MINI_KIND}. + */ + @Deprecated + public static @Nullable Bitmap createImageThumbnail(@NonNull String filePath, int kind) { + try { + return createImageThumbnail(new File(filePath), convertKind(kind), null); + } catch (IOException e) { + Log.w(TAG, e); + return null; + } + } + + /** + * Create a thumbnail for given image file. * - * @hide This method is only used by media framework and media provider internally. + * @param file The audio file. + * @param size The desired thumbnail size. + * @throws IOException If any trouble was encountered while generating or + * loading the thumbnail, or if + * {@link CancellationSignal#cancel()} was invoked. */ - @UnsupportedAppUsage - public static Bitmap createImageThumbnail(String filePath, int kind) { - boolean wantMini = (kind == Images.Thumbnails.MINI_KIND); - int targetSize = wantMini - ? TARGET_SIZE_MINI_THUMBNAIL - : TARGET_SIZE_MICRO_THUMBNAIL; - int maxPixels = wantMini - ? MAX_NUM_PIXELS_THUMBNAIL - : MAX_NUM_PIXELS_MICRO_THUMBNAIL; - SizedThumbnailBitmap sizedThumbnailBitmap = new SizedThumbnailBitmap(); - Bitmap bitmap = null; - String mimeType = MediaFile.getMimeTypeForFile(filePath); + public static @NonNull Bitmap createImageThumbnail(@NonNull File file, @NonNull Size size, + @Nullable CancellationSignal signal) throws IOException { + // Checkpoint before going deeper + if (signal != null) signal.throwIfCanceled(); + + final Resizer resizer = new Resizer(size, signal); + final String mimeType = MediaFile.getMimeTypeForFile(file.getName()); if (mimeType.equals("image/heif") || mimeType.equals("image/heif-sequence") || mimeType.equals("image/heic") || mimeType.equals("image/heic-sequence")) { - bitmap = createThumbnailFromMetadataRetriever(filePath, targetSize, maxPixels); + try (MediaMetadataRetriever retriever = new MediaMetadataRetriever()) { + retriever.setDataSource(file.getAbsolutePath()); + return retriever.getThumbnailImageAtIndex(-1, + new MediaMetadataRetriever.BitmapParams(), size.getWidth(), + size.getWidth() * size.getHeight()); + } catch (RuntimeException e) { + throw new IOException("Failed to create thumbnail", e); + } } else if (MediaFile.isExifMimeType(mimeType)) { - createThumbnailFromEXIF(filePath, targetSize, maxPixels, sizedThumbnailBitmap); - bitmap = sizedThumbnailBitmap.mBitmap; + final ExifInterface exif = new ExifInterface(file); + final byte[] raw = exif.getThumbnailBytes(); + if (raw != null) { + return ImageDecoder.decodeBitmap(ImageDecoder.createSource(raw), resizer); + } } - if (bitmap == null) { - FileInputStream stream = null; - try { - stream = new FileInputStream(filePath); - FileDescriptor fd = stream.getFD(); - BitmapFactory.Options options = new BitmapFactory.Options(); - options.inSampleSize = 1; - options.inJustDecodeBounds = true; - BitmapFactory.decodeFileDescriptor(fd, null, options); - if (options.mCancel || options.outWidth == -1 - || options.outHeight == -1) { - return null; - } - options.inSampleSize = computeSampleSize( - options, targetSize, maxPixels); - options.inJustDecodeBounds = false; - - options.inDither = false; - options.inPreferredConfig = Bitmap.Config.ARGB_8888; - bitmap = BitmapFactory.decodeFileDescriptor(fd, null, options); - } catch (IOException ex) { - Log.e(TAG, "", ex); - } catch (OutOfMemoryError oom) { - Log.e(TAG, "Unable to decode file " + filePath + ". OutOfMemoryError.", oom); - } finally { - try { - if (stream != null) { - stream.close(); - } - } catch (IOException ex) { - Log.e(TAG, "", ex); - } - } + // Checkpoint before going deeper + if (signal != null) signal.throwIfCanceled(); - } + return ImageDecoder.decodeBitmap(ImageDecoder.createSource(file), resizer); + } - if (kind == Images.Thumbnails.MICRO_KIND) { - // now we make it a "square thumbnail" for MICRO_KIND thumbnail - bitmap = extractThumbnail(bitmap, - TARGET_SIZE_MICRO_THUMBNAIL, - TARGET_SIZE_MICRO_THUMBNAIL, OPTIONS_RECYCLE_INPUT); + /** + * Create a thumbnail for given video file. + * + * @param filePath The video file. + * @param kind The desired thumbnail kind, such as + * {@link android.provider.MediaStore.Images.Thumbnails#MINI_KIND}. + */ + @Deprecated + public static @Nullable Bitmap createVideoThumbnail(@NonNull String filePath, int kind) { + try { + return createVideoThumbnail(new File(filePath), convertKind(kind), null); + } catch (IOException e) { + Log.w(TAG, e); + return null; } - return bitmap; } /** - * Create a video thumbnail for a video. May return null if the video is - * corrupt or the format is not supported. + * Create a thumbnail for given video file. * - * @param filePath the path of video file - * @param kind could be MINI_KIND or MICRO_KIND + * @param file The video file. + * @param size The desired thumbnail size. + * @throws IOException If any trouble was encountered while generating or + * loading the thumbnail, or if + * {@link CancellationSignal#cancel()} was invoked. */ - public static Bitmap createVideoThumbnail(String filePath, int kind) { - Bitmap bitmap = null; - MediaMetadataRetriever retriever = new MediaMetadataRetriever(); - try { - retriever.setDataSource(filePath); - // First retrieve album art in metadata if set. - byte[] embeddedPicture = retriever.getEmbeddedPicture(); - if (embeddedPicture != null && embeddedPicture.length > 0) { - bitmap = BitmapFactory.decodeByteArray(embeddedPicture, 0, embeddedPicture.length); + public static @NonNull Bitmap createVideoThumbnail(@NonNull File file, @NonNull Size size, + @Nullable CancellationSignal signal) throws IOException { + // Checkpoint before going deeper + if (signal != null) signal.throwIfCanceled(); + + final Resizer resizer = new Resizer(size, signal); + try (MediaMetadataRetriever mmr = new MediaMetadataRetriever()) { + mmr.setDataSource(file.getAbsolutePath()); + + // Try to retrieve thumbnail from metadata + final byte[] raw = mmr.getEmbeddedPicture(); + if (raw != null) { + return ImageDecoder.decodeBitmap(ImageDecoder.createSource(raw), resizer); } - // Fall back to first frame of the video. - if (bitmap == null) { - bitmap = retriever.getFrameAtTime(-1); - } - } catch (IllegalArgumentException ex) { - // Assume this is a corrupt video file - } catch (RuntimeException ex) { - // Assume this is a corrupt video file. - } finally { - try { - retriever.release(); - } catch (RuntimeException ex) { - // Ignore failures while cleaning up. - } - } - if (bitmap == null) return null; - - if (kind == Images.Thumbnails.MINI_KIND) { - // Scale down the bitmap if it's too large. - int width = bitmap.getWidth(); - int height = bitmap.getHeight(); - int max = Math.max(width, height); - if (max > 512) { - float scale = 512f / max; - int w = Math.round(scale * width); - int h = Math.round(scale * height); - bitmap = Bitmap.createScaledBitmap(bitmap, w, h, true); + // Fall back to middle of video + final int width = Integer.parseInt(mmr.extractMetadata(METADATA_KEY_VIDEO_WIDTH)); + final int height = Integer.parseInt(mmr.extractMetadata(METADATA_KEY_VIDEO_HEIGHT)); + final long duration = Long.parseLong(mmr.extractMetadata(METADATA_KEY_DURATION)); + + // If we're okay with something larger than native format, just + // return a frame without up-scaling it + if (size.getWidth() > width && size.getHeight() > height) { + return mmr.getFrameAtTime(duration / 2, OPTION_CLOSEST_SYNC); + } else { + return mmr.getScaledFrameAtTime(duration / 2, OPTION_CLOSEST_SYNC, + size.getWidth(), size.getHeight()); } - } else if (kind == Images.Thumbnails.MICRO_KIND) { - bitmap = extractThumbnail(bitmap, - TARGET_SIZE_MICRO_THUMBNAIL, - TARGET_SIZE_MICRO_THUMBNAIL, - OPTIONS_RECYCLE_INPUT); + } catch (RuntimeException e) { + throw new IOException("Failed to create thumbnail", e); } - return bitmap; } /** @@ -242,122 +361,27 @@ public class ThumbnailUtils { return thumbnail; } - /* - * Compute the sample size as a function of minSideLength - * and maxNumOfPixels. - * minSideLength is used to specify that minimal width or height of a - * bitmap. - * maxNumOfPixels is used to specify the maximal size in pixels that is - * tolerable in terms of memory usage. - * - * The function returns a sample size based on the constraints. - * Both size and minSideLength can be passed in as IImage.UNCONSTRAINED, - * which indicates no care of the corresponding constraint. - * The functions prefers returning a sample size that - * generates a smaller bitmap, unless minSideLength = IImage.UNCONSTRAINED. - * - * Also, the function rounds up the sample size to a power of 2 or multiple - * of 8 because BitmapFactory only honors sample size this way. - * For example, BitmapFactory downsamples an image by 2 even though the - * request is 3. So we round up the sample size to avoid OOM. - */ + @Deprecated @UnsupportedAppUsage private static int computeSampleSize(BitmapFactory.Options options, int minSideLength, int maxNumOfPixels) { - int initialSize = computeInitialSampleSize(options, minSideLength, - maxNumOfPixels); - - int roundedSize; - if (initialSize <= 8 ) { - roundedSize = 1; - while (roundedSize < initialSize) { - roundedSize <<= 1; - } - } else { - roundedSize = (initialSize + 7) / 8 * 8; - } - - return roundedSize; + return 1; } + @Deprecated @UnsupportedAppUsage private static int computeInitialSampleSize(BitmapFactory.Options options, int minSideLength, int maxNumOfPixels) { - double w = options.outWidth; - double h = options.outHeight; - - int lowerBound = (maxNumOfPixels == UNCONSTRAINED) ? 1 : - (int) Math.ceil(Math.sqrt(w * h / maxNumOfPixels)); - int upperBound = (minSideLength == UNCONSTRAINED) ? 128 : - (int) Math.min(Math.floor(w / minSideLength), - Math.floor(h / minSideLength)); - - if (upperBound < lowerBound) { - // return the larger one when there is no overlapping zone. - return lowerBound; - } - - if ((maxNumOfPixels == UNCONSTRAINED) && - (minSideLength == UNCONSTRAINED)) { - return 1; - } else if (minSideLength == UNCONSTRAINED) { - return lowerBound; - } else { - return upperBound; - } - } - - /** - * Make a bitmap from a given Uri, minimal side length, and maximum number of pixels. - * The image data will be read from specified pfd if it's not null, otherwise - * a new input stream will be created using specified ContentResolver. - * - * Clients are allowed to pass their own BitmapFactory.Options used for bitmap decoding. A - * new BitmapFactory.Options will be created if options is null. - */ - private static Bitmap makeBitmap(int minSideLength, int maxNumOfPixels, - Uri uri, ContentResolver cr, ParcelFileDescriptor pfd, - BitmapFactory.Options options) { - Bitmap b = null; - try { - if (pfd == null) pfd = makeInputStream(uri, cr); - if (pfd == null) return null; - if (options == null) options = new BitmapFactory.Options(); - - FileDescriptor fd = pfd.getFileDescriptor(); - options.inSampleSize = 1; - options.inJustDecodeBounds = true; - BitmapFactory.decodeFileDescriptor(fd, null, options); - if (options.mCancel || options.outWidth == -1 - || options.outHeight == -1) { - return null; - } - options.inSampleSize = computeSampleSize( - options, minSideLength, maxNumOfPixels); - options.inJustDecodeBounds = false; - - options.inDither = false; - options.inPreferredConfig = Bitmap.Config.ARGB_8888; - b = BitmapFactory.decodeFileDescriptor(fd, null, options); - } catch (OutOfMemoryError ex) { - Log.e(TAG, "Got oom exception ", ex); - return null; - } finally { - closeSilently(pfd); - } - return b; + return 1; } + @Deprecated @UnsupportedAppUsage private static void closeSilently(ParcelFileDescriptor c) { - if (c == null) return; - try { - c.close(); - } catch (Throwable t) { - // do nothing - } + IoUtils.closeQuietly(c); } + @Deprecated @UnsupportedAppUsage private static ParcelFileDescriptor makeInputStream( Uri uri, ContentResolver cr) { @@ -371,6 +395,7 @@ public class ThumbnailUtils { /** * Transform source Bitmap to targeted width and height. */ + @Deprecated @UnsupportedAppUsage private static Bitmap transform(Matrix scaler, Bitmap source, @@ -468,14 +493,7 @@ public class ThumbnailUtils { return b2; } - /** - * SizedThumbnailBitmap contains the bitmap, which is downsampled either from - * the thumbnail in exif or the full image. - * mThumbnailData, mThumbnailWidth and mThumbnailHeight are set together only if mThumbnail - * is not null. - * - * The width/height of the sized bitmap may be different from mThumbnailWidth/mThumbnailHeight. - */ + @Deprecated private static class SizedThumbnailBitmap { public byte[] mThumbnailData; public Bitmap mBitmap; @@ -483,81 +501,9 @@ public class ThumbnailUtils { public int mThumbnailHeight; } - /** - * Creates a bitmap by either downsampling from the thumbnail in EXIF or the full image. - * The functions returns a SizedThumbnailBitmap, - * which contains a downsampled bitmap and the thumbnail data in EXIF if exists. - */ + @Deprecated @UnsupportedAppUsage private static void createThumbnailFromEXIF(String filePath, int targetSize, int maxPixels, SizedThumbnailBitmap sizedThumbBitmap) { - if (filePath == null) return; - - ExifInterface exif = null; - byte [] thumbData = null; - try { - exif = new ExifInterface(filePath); - thumbData = exif.getThumbnail(); - } catch (IOException ex) { - Log.w(TAG, ex); - } - - BitmapFactory.Options fullOptions = new BitmapFactory.Options(); - BitmapFactory.Options exifOptions = new BitmapFactory.Options(); - int exifThumbWidth = 0; - int fullThumbWidth = 0; - - // Compute exifThumbWidth. - if (thumbData != null) { - exifOptions.inJustDecodeBounds = true; - BitmapFactory.decodeByteArray(thumbData, 0, thumbData.length, exifOptions); - exifOptions.inSampleSize = computeSampleSize(exifOptions, targetSize, maxPixels); - exifThumbWidth = exifOptions.outWidth / exifOptions.inSampleSize; - } - - // Compute fullThumbWidth. - fullOptions.inJustDecodeBounds = true; - BitmapFactory.decodeFile(filePath, fullOptions); - fullOptions.inSampleSize = computeSampleSize(fullOptions, targetSize, maxPixels); - fullThumbWidth = fullOptions.outWidth / fullOptions.inSampleSize; - - // Choose the larger thumbnail as the returning sizedThumbBitmap. - if (thumbData != null && exifThumbWidth >= fullThumbWidth) { - int width = exifOptions.outWidth; - int height = exifOptions.outHeight; - exifOptions.inJustDecodeBounds = false; - sizedThumbBitmap.mBitmap = BitmapFactory.decodeByteArray(thumbData, 0, - thumbData.length, exifOptions); - if (sizedThumbBitmap.mBitmap != null) { - sizedThumbBitmap.mThumbnailData = thumbData; - sizedThumbBitmap.mThumbnailWidth = width; - sizedThumbBitmap.mThumbnailHeight = height; - } - } else { - fullOptions.inJustDecodeBounds = false; - sizedThumbBitmap.mBitmap = BitmapFactory.decodeFile(filePath, fullOptions); - } - } - - private static Bitmap createThumbnailFromMetadataRetriever( - String filePath, int targetSize, int maxPixels) { - if (filePath == null) { - return null; - } - Bitmap thumbnail = null; - MediaMetadataRetriever retriever = new MediaMetadataRetriever(); - try { - retriever.setDataSource(filePath); - MediaMetadataRetriever.BitmapParams params = new MediaMetadataRetriever.BitmapParams(); - params.setPreferredConfig(Bitmap.Config.ARGB_8888); - thumbnail = retriever.getThumbnailImageAtIndex(-1, params, targetSize, maxPixels); - } catch (RuntimeException ex) { - // Assume this is a corrupt video file. - } finally { - if (retriever != null) { - retriever.release(); - } - } - return thumbnail; } } diff --git a/media/jni/Android.bp b/media/jni/Android.bp index e25e6a5e735f..7481fff74765 100644 --- a/media/jni/Android.bp +++ b/media/jni/Android.bp @@ -97,9 +97,10 @@ cc_library_shared { shared_libs: [ "android.hardware.cas@1.0", // for CasManager. VNDK??? "android.hardware.cas.native@1.0", // CasManager. VNDK??? + "android.hidl.allocator@1.0", + "libhidlmemory", "libbinder", "libgui", // for VideoFrameScheduler - "libhidlallocatorutils", "libhidlbase", // VNDK??? "libpowermanager", // for JWakeLock. to be removed diff --git a/media/jni/android_media_MediaPlayer.cpp b/media/jni/android_media_MediaPlayer.cpp index 5dd01b03274a..76bbce7f0d87 100644 --- a/media/jni/android_media_MediaPlayer.cpp +++ b/media/jni/android_media_MediaPlayer.cpp @@ -39,7 +39,6 @@ #include "utils/Errors.h" // for status_t #include "utils/KeyedVector.h" #include "utils/String8.h" -#include "android_media_BufferingParams.h" #include "android_media_MediaDataSource.h" #include "android_media_MediaMetricsJNI.h" #include "android_media_PlaybackParams.h" @@ -94,7 +93,6 @@ struct fields_t { }; static fields_t fields; -static BufferingParams::fields_t gBufferingParamsFields; static PlaybackParams::fields_t gPlaybackParamsFields; static SyncParams::fields_t gSyncParamsFields; static VolumeShaperHelper::fields_t gVolumeShaperFields; @@ -370,50 +368,6 @@ android_media_MediaPlayer_setVideoSurface(JNIEnv *env, jobject thiz, jobject jsu setVideoSurface(env, thiz, jsurface, true /* mediaPlayerMustBeAlive */); } -static jobject -android_media_MediaPlayer_getBufferingParams(JNIEnv *env, jobject thiz) -{ - sp<MediaPlayer> mp = getMediaPlayer(env, thiz); - if (mp == NULL) { - jniThrowException(env, "java/lang/IllegalStateException", NULL); - return NULL; - } - - BufferingParams bp; - BufferingSettings &settings = bp.settings; - process_media_player_call( - env, thiz, mp->getBufferingSettings(&settings), - "java/lang/IllegalStateException", "unexpected error"); - if (env->ExceptionCheck()) { - return nullptr; - } - ALOGV("getBufferingSettings:{%s}", settings.toString().string()); - - return bp.asJobject(env, gBufferingParamsFields); -} - -static void -android_media_MediaPlayer_setBufferingParams(JNIEnv *env, jobject thiz, jobject params) -{ - if (params == NULL) { - return; - } - - sp<MediaPlayer> mp = getMediaPlayer(env, thiz); - if (mp == NULL) { - jniThrowException(env, "java/lang/IllegalStateException", NULL); - return; - } - - BufferingParams bp; - bp.fillFromJobject(env, gBufferingParamsFields, params); - ALOGV("setBufferingParams:{%s}", bp.settings.toString().string()); - - process_media_player_call( - env, thiz, mp->setBufferingSettings(bp.settings), - "java/lang/IllegalStateException", "unexpected error"); -} - static void android_media_MediaPlayer_prepare(JNIEnv *env, jobject thiz) { @@ -976,8 +930,6 @@ android_media_MediaPlayer_native_init(JNIEnv *env) env->DeleteLocalRef(clazz); - gBufferingParamsFields.init(env); - // Modular DRM FIND_CLASS(clazz, "android/media/MediaDrm$MediaDrmStateException"); if (clazz) { @@ -1426,8 +1378,6 @@ static const JNINativeMethod gMethods[] = { {"_setDataSource", "(Ljava/io/FileDescriptor;JJ)V", (void *)android_media_MediaPlayer_setDataSourceFD}, {"_setDataSource", "(Landroid/media/MediaDataSource;)V",(void *)android_media_MediaPlayer_setDataSourceCallback }, {"_setVideoSurface", "(Landroid/view/Surface;)V", (void *)android_media_MediaPlayer_setVideoSurface}, - {"getBufferingParams", "()Landroid/media/BufferingParams;", (void *)android_media_MediaPlayer_getBufferingParams}, - {"setBufferingParams", "(Landroid/media/BufferingParams;)V", (void *)android_media_MediaPlayer_setBufferingParams}, {"_prepare", "()V", (void *)android_media_MediaPlayer_prepare}, {"prepareAsync", "()V", (void *)android_media_MediaPlayer_prepareAsync}, {"_start", "()V", (void *)android_media_MediaPlayer_start}, diff --git a/media/jni/android_media_MediaPlayer2.cpp b/media/jni/android_media_MediaPlayer2.cpp index 8b6009e749ce..7e6a8abbce90 100644 --- a/media/jni/android_media_MediaPlayer2.cpp +++ b/media/jni/android_media_MediaPlayer2.cpp @@ -820,7 +820,7 @@ android_media_MediaPlayer2_getCurrentPosition(JNIEnv *env, jobject thiz) } static jlong -android_media_MediaPlayer2_getDuration(JNIEnv *env, jobject thiz) +android_media_MediaPlayer2_getDuration(JNIEnv *env, jobject thiz, jlong srcId) { sp<MediaPlayer2> mp = getMediaPlayer(env, thiz); if (mp == NULL ) { @@ -828,7 +828,7 @@ android_media_MediaPlayer2_getDuration(JNIEnv *env, jobject thiz) return 0; } int64_t msec; - process_media_player_call( env, thiz, mp->getDuration(&msec), NULL, NULL ); + process_media_player_call( env, thiz, mp->getDuration(srcId, &msec), NULL, NULL ); ALOGV("getDuration: %lld (msec)", (long long)msec); return (jlong) msec; } @@ -1408,7 +1408,7 @@ static const JNINativeMethod gMethods[] = { {"native_seekTo", "(JI)V", (void *)android_media_MediaPlayer2_seekTo}, {"native_pause", "()V", (void *)android_media_MediaPlayer2_pause}, {"getCurrentPosition", "()J", (void *)android_media_MediaPlayer2_getCurrentPosition}, - {"getDuration", "()J", (void *)android_media_MediaPlayer2_getDuration}, + {"native_getDuration", "(J)J", (void *)android_media_MediaPlayer2_getDuration}, {"native_release", "()V", (void *)android_media_MediaPlayer2_release}, {"native_reset", "()V", (void *)android_media_MediaPlayer2_reset}, {"native_setAudioAttributes", "(Landroid/media/AudioAttributes;)Z", (void *)android_media_MediaPlayer2_setAudioAttributes}, diff --git a/native/android/net.c b/native/android/net.c index e32b7875b4e7..4cac371f313b 100644 --- a/native/android/net.c +++ b/native/android/net.c @@ -84,8 +84,7 @@ int android_getaddrinfofornetwork(net_handle_t network, return android_getaddrinfofornet(node, service, hints, netid, 0, res); } -int android_res_nquery(net_handle_t network, - const char *dname, int ns_class, int ns_type) { +int android_res_nquery(net_handle_t network, const char *dname, int ns_class, int ns_type) { unsigned netid; if (!getnetidfromhandle(network, &netid)) { return -ENONET; @@ -94,12 +93,11 @@ int android_res_nquery(net_handle_t network, return resNetworkQuery(netid, dname, ns_class, ns_type); } -int android_res_nresult(int fd, int *rcode, unsigned char *answer, int anslen) { +int android_res_nresult(int fd, int *rcode, uint8_t *answer, size_t anslen) { return resNetworkResult(fd, rcode, answer, anslen); } -int android_res_nsend(net_handle_t network, - const unsigned char *msg, int msglen) { +int android_res_nsend(net_handle_t network, const uint8_t *msg, size_t msglen) { unsigned netid; if (!getnetidfromhandle(network, &netid)) { return -ENONET; diff --git a/packages/CarSystemUI/Android.bp b/packages/CarSystemUI/Android.bp index 74d6605a1ffb..293fa9a7f134 100644 --- a/packages/CarSystemUI/Android.bp +++ b/packages/CarSystemUI/Android.bp @@ -80,4 +80,5 @@ android_app { "com.android.keyguard", ], + annotation_processors: ["dagger2-compiler-2.19"], } diff --git a/packages/CarSystemUI/res/layout/car_fullscreen_user_pod.xml b/packages/CarSystemUI/res/layout/car_fullscreen_user_pod.xml index 1d6728689933..a2a628d7319e 100644 --- a/packages/CarSystemUI/res/layout/car_fullscreen_user_pod.xml +++ b/packages/CarSystemUI/res/layout/car_fullscreen_user_pod.xml @@ -25,13 +25,15 @@ android:orientation="vertical" android:gravity="center"> - <ImageView android:id="@+id/user_avatar" + <ImageView + android:id="@+id/user_avatar" android:layout_width="@dimen/car_user_switcher_image_avatar_size" android:layout_height="@dimen/car_user_switcher_image_avatar_size" - android:background="@drawable/car_button_ripple_background_light" + android:background="?android:attr/selectableItemBackground" android:gravity="center"/> - <TextView android:id="@+id/user_name" + <TextView + android:id="@+id/user_name" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="@dimen/car_user_switcher_vertical_spacing_between_name_and_avatar" diff --git a/packages/CarSystemUI/res/layout/car_fullscreen_user_switcher.xml b/packages/CarSystemUI/res/layout/car_fullscreen_user_switcher.xml index 6cd70d62b4f7..e8c5134cd180 100644 --- a/packages/CarSystemUI/res/layout/car_fullscreen_user_switcher.xml +++ b/packages/CarSystemUI/res/layout/car_fullscreen_user_switcher.xml @@ -19,12 +19,10 @@ android:fitsSystemWindows="true" android:layout_width="match_parent" android:layout_height="match_parent" - android:background="@color/car_user_switcher_background_color" android:visibility="gone"> <LinearLayout android:id="@+id/container" - android:background="@color/car_user_switcher_background_color" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> @@ -38,7 +36,7 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:layout_marginTop="@dimen/car_user_switcher_margin_top" - android:theme="@style/Theme.Car.Light.List" + android:theme="@style/PagedListTheme" app:verticallyCenterListContent="true" app:showPagedListViewDivider="false" app:gutter="both" diff --git a/packages/CarSystemUI/res/layout/car_left_navigation_bar.xml b/packages/CarSystemUI/res/layout/car_left_navigation_bar.xml index 141b28a9ae28..72ec8d86368d 100644 --- a/packages/CarSystemUI/res/layout/car_left_navigation_bar.xml +++ b/packages/CarSystemUI/res/layout/car_left_navigation_bar.xml @@ -68,7 +68,7 @@ android:orientation="vertical"> <com.android.keyguard.AlphaOptimizedImageButton - android:id="@+id/notifications" + android:id="@+id/note" android:layout_height="wrap_content" android:layout_width="match_parent" android:src="@drawable/car_ic_notification" diff --git a/packages/CarSystemUI/res/layout/car_navigation_bar.xml b/packages/CarSystemUI/res/layout/car_navigation_bar.xml index b67ce15f80b0..052566d67c1b 100644 --- a/packages/CarSystemUI/res/layout/car_navigation_bar.xml +++ b/packages/CarSystemUI/res/layout/car_navigation_bar.xml @@ -20,7 +20,7 @@ xmlns:systemui="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" - android:background="@android:color/black" + android:background="@drawable/system_bar_background" android:orientation="vertical"> <LinearLayout android:id="@id/nav_buttons" diff --git a/packages/CarSystemUI/res/layout/car_navigation_bar_unprovisioned.xml b/packages/CarSystemUI/res/layout/car_navigation_bar_unprovisioned.xml index 46e60db0ba4b..4fa877ff37dc 100644 --- a/packages/CarSystemUI/res/layout/car_navigation_bar_unprovisioned.xml +++ b/packages/CarSystemUI/res/layout/car_navigation_bar_unprovisioned.xml @@ -20,7 +20,7 @@ xmlns:systemui="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" - android:background="@android:color/black" + android:background="@drawable/system_bar_background" android:orientation="vertical"> <LinearLayout diff --git a/packages/CarSystemUI/res/layout/car_qs_footer.xml b/packages/CarSystemUI/res/layout/car_qs_footer.xml index 6f19cfcfa345..bf96c00e3f0d 100644 --- a/packages/CarSystemUI/res/layout/car_qs_footer.xml +++ b/packages/CarSystemUI/res/layout/car_qs_footer.xml @@ -35,7 +35,7 @@ android:layout_centerVertical="true" android:layout_width="@dimen/car_qs_footer_icon_width" android:layout_height="@dimen/car_qs_footer_icon_height" - android:background="@drawable/ripple_drawable" + android:background="?android:attr/selectableItemBackground" android:focusable="true"> <ImageView diff --git a/packages/CarSystemUI/res/layout/car_qs_panel.xml b/packages/CarSystemUI/res/layout/car_qs_panel.xml index dfa48c30b0c8..d923e0fbb20b 100644 --- a/packages/CarSystemUI/res/layout/car_qs_panel.xml +++ b/packages/CarSystemUI/res/layout/car_qs_panel.xml @@ -21,8 +21,7 @@ android:layout_height="wrap_content" android:background="@color/car_qs_background_primary" android:orientation="vertical" - android:elevation="4dp" - android:theme="@android:style/Theme"> + android:elevation="4dp"> <include layout="@layout/car_status_bar_header"/> <include layout="@layout/car_qs_footer"/> @@ -39,7 +38,7 @@ android:id="@+id/user_grid" android:layout_width="match_parent" android:layout_height="match_parent" - android:theme="@style/Theme.Car.Light.List" + android:theme="@style/PagedListTheme" app:showPagedListViewDivider="false" app:gutter="both" app:itemSpacing="@dimen/car_user_switcher_vertical_spacing_between_users"/> diff --git a/packages/CarSystemUI/res/layout/car_right_navigation_bar.xml b/packages/CarSystemUI/res/layout/car_right_navigation_bar.xml index 141b28a9ae28..72ec8d86368d 100644 --- a/packages/CarSystemUI/res/layout/car_right_navigation_bar.xml +++ b/packages/CarSystemUI/res/layout/car_right_navigation_bar.xml @@ -68,7 +68,7 @@ android:orientation="vertical"> <com.android.keyguard.AlphaOptimizedImageButton - android:id="@+id/notifications" + android:id="@+id/note" android:layout_height="wrap_content" android:layout_width="match_parent" android:src="@drawable/car_ic_notification" diff --git a/packages/CarSystemUI/res/layout/car_top_navigation_bar.xml b/packages/CarSystemUI/res/layout/car_top_navigation_bar.xml index 7b3333e63b7a..1dca10a04c43 100644 --- a/packages/CarSystemUI/res/layout/car_top_navigation_bar.xml +++ b/packages/CarSystemUI/res/layout/car_top_navigation_bar.xml @@ -21,7 +21,7 @@ android:id="@+id/car_top_bar" android:layout_width="match_parent" android:layout_height="match_parent" - android:background="@android:color/black" + android:background="@drawable/system_bar_background" android:orientation="vertical"> <RelativeLayout diff --git a/packages/CarSystemUI/res/values/config.xml b/packages/CarSystemUI/res/values/config.xml index 572737f92370..c527711f563f 100644 --- a/packages/CarSystemUI/res/values/config.xml +++ b/packages/CarSystemUI/res/values/config.xml @@ -51,7 +51,6 @@ <item>com.android.systemui.LatencyTester</item> <item>com.android.systemui.globalactions.GlobalActionsComponent</item> <item>com.android.systemui.ScreenDecorations</item> - <item>com.android.systemui.fingerprint.FingerprintDialogImpl</item> <item>com.android.systemui.SliceBroadcastRelayHandler</item> <item>com.android.systemui.notifications.NotificationsUI</item> </string-array> diff --git a/packages/CarSystemUI/res/values/themes.xml b/packages/CarSystemUI/res/values/themes.xml new file mode 100644 index 000000000000..8a5961ed051b --- /dev/null +++ b/packages/CarSystemUI/res/values/themes.xml @@ -0,0 +1,24 @@ +<?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. + */ +--> + +<resources> + <!--This Theme contains attributes required for components from the car support lib --> + <style name="PagedListTheme" parent="Theme.CarSupportWrapper.NoActionBar"> + </style> +</resources> diff --git a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIFactory.java b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIFactory.java index f57f26db118c..7039a2c0a957 100644 --- a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIFactory.java +++ b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIFactory.java @@ -17,25 +17,42 @@ package com.android.systemui; import android.content.Context; -import android.util.ArrayMap; import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.ViewMediatorCallback; -import com.android.systemui.Dependency.DependencyProvider; import com.android.systemui.car.CarNotificationEntryManager; import com.android.systemui.statusbar.car.CarFacetButtonController; import com.android.systemui.statusbar.car.CarStatusBarKeyguardViewManager; -import com.android.systemui.statusbar.car.hvac.HvacController; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; import com.android.systemui.volume.CarVolumeDialogComponent; import com.android.systemui.volume.VolumeDialogComponent; +import javax.inject.Singleton; + +import dagger.Component; +import dagger.Module; +import dagger.Provides; + /** * Class factory to provide car specific SystemUI components. */ public class CarSystemUIFactory extends SystemUIFactory { + private CarDependencyComponent mCarDependencyComponent; + + @Override + protected void init(Context context) { + super.init(context); + mCarDependencyComponent = DaggerCarSystemUIFactory_CarDependencyComponent.builder() + .contextHolder(new ContextHolder(context)) + .build(); + } + + public CarDependencyComponent getCarDependencyComponent() { + return mCarDependencyComponent; + } + public StatusBarKeyguardViewManager createStatusBarKeyguardViewManager(Context context, ViewMediatorCallback viewMediatorCallback, LockPatternUtils lockPatternUtils) { return new CarStatusBarKeyguardViewManager(context, viewMediatorCallback, lockPatternUtils); @@ -46,12 +63,27 @@ public class CarSystemUIFactory extends SystemUIFactory { } @Override - public void injectDependencies(ArrayMap<Object, DependencyProvider> providers, - Context context) { - super.injectDependencies(providers, context); - providers.put(NotificationEntryManager.class, - () -> new CarNotificationEntryManager(context)); - providers.put(CarFacetButtonController.class, () -> new CarFacetButtonController(context)); - providers.put(HvacController.class, () -> new HvacController(context)); + public NotificationEntryManager provideNotificationEntryManager(Context context) { + return new CarNotificationEntryManager(context); + } + + @Module + protected static class ContextHolder { + private Context mContext; + + public ContextHolder(Context context) { + mContext = context; + } + + @Provides + public Context provideContext() { + return mContext; + } + } + + @Singleton + @Component(modules = ContextHolder.class) + public interface CarDependencyComponent { + CarFacetButtonController getCarFacetButtonController(); } } diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarFacetButton.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarFacetButton.java index cea4ab0e4992..0a20eaa0b888 100644 --- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarFacetButton.java +++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarFacetButton.java @@ -26,8 +26,9 @@ import android.widget.ImageView; import android.widget.LinearLayout; import com.android.keyguard.AlphaOptimizedImageButton; -import com.android.systemui.Dependency; +import com.android.systemui.CarSystemUIFactory; import com.android.systemui.R; +import com.android.systemui.SystemUIFactory; /** * CarFacetButton is a ui component designed to be used as a shortcut for an app of a defined @@ -76,8 +77,9 @@ public class CarFacetButton extends LinearLayout { TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CarFacetButton); setupIntents(typedArray); setupIcons(typedArray); - CarFacetButtonController carFacetButtonController = Dependency.get( - CarFacetButtonController.class); + CarSystemUIFactory factory = SystemUIFactory.getInstance(); + CarFacetButtonController carFacetButtonController = factory.getCarDependencyComponent() + .getCarFacetButtonController(); carFacetButtonController.addFacetButton(this); } diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarFacetButtonController.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarFacetButtonController.java index 56db242f1eb9..7811a1caeb88 100644 --- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarFacetButtonController.java +++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarFacetButtonController.java @@ -29,11 +29,15 @@ import java.util.HashMap; import java.util.List; import java.util.Set; +import javax.inject.Inject; +import javax.inject.Singleton; + /** * CarFacetButtons placed on the nav bar are designed to have visual indication that the active * application on screen is associated with it. This is basically a similar concept to a radio * button group. */ +@Singleton public class CarFacetButtonController { protected HashMap<String, CarFacetButton> mButtonsByCategory = new HashMap<>(); @@ -42,6 +46,7 @@ public class CarFacetButtonController { protected CarFacetButton mSelectedFacetButton; protected Context mContext; + @Inject public CarFacetButtonController(Context context) { mContext = context; } diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java index 5da236ceb211..7028999c159c 100644 --- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java +++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java @@ -29,9 +29,11 @@ import android.view.WindowManager; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.BatteryMeterView; +import com.android.systemui.CarSystemUIFactory; import com.android.systemui.Dependency; import com.android.systemui.Prefs; import com.android.systemui.R; +import com.android.systemui.SystemUIFactory; import com.android.systemui.classifier.FalsingLog; import com.android.systemui.classifier.FalsingManager; import com.android.systemui.fragments.FragmentHostManager; @@ -102,7 +104,9 @@ public class CarStatusBar extends StatusBar implements mHvacController.connectToCarService(); - mCarFacetButtonController = Dependency.get(CarFacetButtonController.class); + CarSystemUIFactory factory = SystemUIFactory.getInstance(); + mCarFacetButtonController = factory.getCarDependencyComponent() + .getCarFacetButtonController(); mDeviceProvisionedController = Dependency.get(DeviceProvisionedController.class); mDeviceIsProvisioned = mDeviceProvisionedController.isDeviceProvisioned(); if (!mDeviceIsProvisioned) { @@ -239,7 +243,7 @@ public class CarStatusBar extends StatusBar implements @Override protected void makeStatusBarView() { super.makeStatusBarView(); - mHvacController = Dependency.get(HvacController.class); + mHvacController = new HvacController(mContext); mNotificationPanelBackground = getDefaultWallpaper(); mScrimController.setScrimBehindDrawable(mNotificationPanelBackground); diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/DrivingStateHelper.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/DrivingStateHelper.java index 730c3e3440ea..a4424260fef5 100644 --- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/DrivingStateHelper.java +++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/DrivingStateHelper.java @@ -52,6 +52,9 @@ public class DrivingStateHelper { * is idling or moving, {@code false} otherwise. */ public boolean isCurrentlyDriving() { + if (mDrivingStateManager == null) { + return false; + } try { CarDrivingStateEvent currentState = mDrivingStateManager.getCurrentCarDrivingState(); if (currentState != null) { diff --git a/packages/CompanionDeviceManager/AndroidManifest.xml b/packages/CompanionDeviceManager/AndroidManifest.xml index 34bc4ebcd0aa..0be71e6d17b9 100644 --- a/packages/CompanionDeviceManager/AndroidManifest.xml +++ b/packages/CompanionDeviceManager/AndroidManifest.xml @@ -26,6 +26,7 @@ <uses-permission android:name="android.permission.BLUETOOTH"/> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/> + <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/> <uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/> diff --git a/packages/ExtServices/src/android/ext/services/sms/FinancialSmsServiceImpl.java b/packages/ExtServices/src/android/ext/services/sms/FinancialSmsServiceImpl.java index ab71802102ae..81a63dd80a63 100644 --- a/packages/ExtServices/src/android/ext/services/sms/FinancialSmsServiceImpl.java +++ b/packages/ExtServices/src/android/ext/services/sms/FinancialSmsServiceImpl.java @@ -17,14 +17,10 @@ package android.ext.services.sms; import android.annotation.NonNull; import android.annotation.Nullable; -import android.database.Cursor; import android.database.CursorWindow; -import android.net.Uri; import android.os.Bundle; import android.service.sms.FinancialSmsService; -import android.util.Log; -import java.util.ArrayList; /** * Service to provide financial apps access to sms messages. */ @@ -36,45 +32,6 @@ public class FinancialSmsServiceImpl extends FinancialSmsService { @Nullable @Override public CursorWindow onGetSmsMessages(@NonNull Bundle params) { - ArrayList<String> columnNames = params.getStringArrayList(KEY_COLUMN_NAMES); - if (columnNames == null || columnNames.size() <= 0) { - return null; - } - - Uri inbox = Uri.parse("content://sms/inbox"); - - try (Cursor cursor = getContentResolver().query(inbox, null, null, null, null); - CursorWindow window = new CursorWindow("FinancialSmsMessages")) { - int messageCount = cursor.getCount(); - if (messageCount > 0 && cursor.moveToFirst()) { - window.setNumColumns(columnNames.size()); - for (int row = 0; row < messageCount; row++) { - if (!window.allocRow()) { - Log.e(TAG, "CursorWindow ran out of memory."); - return null; - } - for (int col = 0; col < columnNames.size(); col++) { - String columnName = columnNames.get(col); - int inboxColumnIndex = cursor.getColumnIndexOrThrow(columnName); - String inboxColumnValue = cursor.getString(inboxColumnIndex); - boolean addedToCursorWindow = window.putString(inboxColumnValue, row, col); - if (!addedToCursorWindow) { - Log.e(TAG, "Failed to add:" - + inboxColumnValue - + ";column:" - + columnName); - return null; - } - } - cursor.moveToNext(); - } - } else { - Log.w(TAG, "No sms messages."); - } - return window; - } catch (Exception e) { - Log.e(TAG, "Failed to get sms messages."); - return null; - } + return null; } } diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp index 042808a07f8f..2321790fa960 100644 --- a/packages/SettingsLib/Android.bp +++ b/packages/SettingsLib/Android.bp @@ -19,6 +19,7 @@ android_library { "SettingsLibLayoutPreference", "SettingsLibActionButtonsPreference", "SettingsLibEntityHeaderWidgets", + "SettingsLibBarChartPreference" ], // ANDROIDMK TRANSLATION ERROR: unsupported assignment to LOCAL_SHARED_JAVA_LIBRARIES diff --git a/packages/SettingsLib/BarChartPreference/Android.bp b/packages/SettingsLib/BarChartPreference/Android.bp new file mode 100644 index 000000000000..477e8979b03b --- /dev/null +++ b/packages/SettingsLib/BarChartPreference/Android.bp @@ -0,0 +1,13 @@ +android_library { + name: "SettingsLibBarChartPreference", + + srcs: ["src/**/*.java"], + resource_dirs: ["res"], + + static_libs: [ + "androidx.preference_preference", + ], + + sdk_version: "system_current", + min_sdk_version: "21", +} diff --git a/packages/SettingsLib/BarChartPreference/AndroidManifest.xml b/packages/SettingsLib/BarChartPreference/AndroidManifest.xml new file mode 100644 index 000000000000..4b9f1ab8d6cc --- /dev/null +++ b/packages/SettingsLib/BarChartPreference/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.widget"> + + <uses-sdk android:minSdkVersion="21" /> + +</manifest> diff --git a/packages/SettingsLib/BarChartPreference/res/layout/settings_bar_chart.xml b/packages/SettingsLib/BarChartPreference/res/layout/settings_bar_chart.xml new file mode 100644 index 000000000000..1a4d7b7dee14 --- /dev/null +++ b/packages/SettingsLib/BarChartPreference/res/layout/settings_bar_chart.xml @@ -0,0 +1,65 @@ +<?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. + --> + +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:settings="http://schemas.android.com/apk/res-auto" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginStart="16dp" + android:layout_marginEnd="16dp" + android:gravity="center" + android:orientation="vertical"> + + <TextView + android:id="@+id/bar_chart_title" + android:layout_width="wrap_content" + android:layout_height="48dp" + android:gravity="center" + android:textAppearance="@style/BarChart.Text.HeaderTitle"/> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:gravity="center|bottom"> + + <com.android.settingslib.widget.BarView + android:id="@+id/bar_view1" + style="@style/BarViewStyle" + settings:barColor="#FA7B17"/> + <com.android.settingslib.widget.BarView + android:id="@+id/bar_view2" + style="@style/BarViewStyle" + settings:barColor="#F439A0"/> + <com.android.settingslib.widget.BarView + android:id="@+id/bar_view3" + style="@style/BarViewStyle" + settings:barColor="#A142F4"/> + <com.android.settingslib.widget.BarView + android:id="@+id/bar_view4" + style="@style/BarViewStyle" + settings:barColor="#24C1E0"/> + </LinearLayout> + + <Button + android:id="@+id/bar_chart_details" + style="@android:style/Widget.DeviceDefault.Button.Borderless.Colored" + android:layout_width="wrap_content" + android:layout_height="48dp" + android:gravity="center"/> + +</LinearLayout> diff --git a/packages/SettingsLib/BarChartPreference/res/layout/settings_bar_view.xml b/packages/SettingsLib/BarChartPreference/res/layout/settings_bar_view.xml new file mode 100644 index 000000000000..b053317b9de1 --- /dev/null +++ b/packages/SettingsLib/BarChartPreference/res/layout/settings_bar_view.xml @@ -0,0 +1,58 @@ +<?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. + --> + +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:gravity="center" + android:orientation="vertical"> + + <View + android:id="@+id/bar_view" + android:layout_width="8dp" + android:layout_height="wrap_content" + android:background="?android:attr/colorAccent"/> + + <ImageView + android:id="@+id/icon_view" + android:layout_width="@dimen/settings_bar_view_icon_size" + android:layout_height="@dimen/settings_bar_view_icon_size" + android:scaleType="fitCenter" + android:layout_marginTop="12dp" + android:layout_marginBottom="12dp"/> + + <TextView + android:id="@+id/bar_title" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="10dp" + android:layout_marginBottom="2dp" + android:singleLine="true" + android:ellipsize="marquee" + android:textAppearance="@style/BarChart.Text.Title"/> + + <TextView + android:id="@+id/bar_summary" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginBottom="12dp" + android:singleLine="true" + android:ellipsize="marquee" + android:textAppearance="@style/BarChart.Text.Summary"/> + +</LinearLayout>
\ No newline at end of file diff --git a/packages/SettingsLib/BarChartPreference/res/values/attrs.xml b/packages/SettingsLib/BarChartPreference/res/values/attrs.xml new file mode 100644 index 000000000000..df3eb0a36919 --- /dev/null +++ b/packages/SettingsLib/BarChartPreference/res/values/attrs.xml @@ -0,0 +1,25 @@ +<?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. + --> + +<resources> + + <declare-styleable name="SettingsBarView"> + <!-- The color of bar view --> + <attr name="barColor" format="color" /> + </declare-styleable> + +</resources> diff --git a/packages/SettingsLib/BarChartPreference/res/values/dimens.xml b/packages/SettingsLib/BarChartPreference/res/values/dimens.xml new file mode 100644 index 000000000000..7148afa2d62f --- /dev/null +++ b/packages/SettingsLib/BarChartPreference/res/values/dimens.xml @@ -0,0 +1,21 @@ +<?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. + --> + +<resources> + <dimen name="settings_bar_view_max_height">106dp</dimen> + <dimen name="settings_bar_view_icon_size">24dp</dimen> +</resources>
\ No newline at end of file diff --git a/packages/SettingsLib/BarChartPreference/res/values/styles.xml b/packages/SettingsLib/BarChartPreference/res/values/styles.xml new file mode 100644 index 000000000000..647d0800fe82 --- /dev/null +++ b/packages/SettingsLib/BarChartPreference/res/values/styles.xml @@ -0,0 +1,46 @@ +<?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. + --> + +<resources> + <style name="BarViewStyle"> + <item name="android:layout_width">0dp</item> + <item name="android:layout_height">wrap_content</item> + <item name="android:layout_weight">1</item> + <item name="android:layout_marginStart">8dp</item> + <item name="android:layout_marginEnd">8dp</item> + </style> + + <style name="BarChart.Text" + parent="@android:style/TextAppearance.Material.Subhead"> + <item name="android:fontFamily">@*android:string/config_headlineFontFamilyMedium</item> + <item name="android:textColor">?android:attr/textColorPrimary</item> + </style> + + <style name="BarChart.Text.HeaderTitle"> + <item name="android:textSize">14sp</item> + </style> + + <style name="BarChart.Text.Title"> + <item name="android:textSize">22sp</item> + </style> + + <style name="BarChart.Text.Summary" + parent="@android:style/TextAppearance.Material.Body1"> + <item name="android:textColor">?android:attr/textColorSecondary</item> + <item name="android:textSize">14sp</item> + </style> +</resources>
\ No newline at end of file diff --git a/packages/SettingsLib/BarChartPreference/src/com/android/settingslib/widget/BarChartPreference.java b/packages/SettingsLib/BarChartPreference/src/com/android/settingslib/widget/BarChartPreference.java new file mode 100644 index 000000000000..89ebf4d1300a --- /dev/null +++ b/packages/SettingsLib/BarChartPreference/src/com/android/settingslib/widget/BarChartPreference.java @@ -0,0 +1,242 @@ +/* + * 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.widget; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.View; +import android.widget.Button; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.StringRes; +import androidx.preference.Preference; +import androidx.preference.PreferenceViewHolder; + +import java.util.Arrays; + +/** + * This BarChartPreference shows four bar views in this preference at most. + * + * <p>The following code sample shows a typical use, with an XML layout and code to initialize the + * contents of the BarChartPreference: + * + * <pre> + * <com.android.settingslib.widget.BarChartPreference + * android:key="bar_chart"/> + * </pre> + * + * <p>This code sample demonstrates how to initialize the contents of the BarChartPreference defined + * in the previous XML layout: + * + * <pre> + * BarViewInfo[] viewsInfo = new BarViewInfo [] { + * new BarViewInfo(icon, 18, res of summary), + * new BarViewInfo(icon, 25, res of summary), + * new BarViewInfo(icon, 10, res of summary), + * new BarViewInfo(icon, 3, res of summary), + * }; + * </pre> + * + * <pre> + * BarChartPreference preference = ((BarChartPreference) findPreference("bar_chart")); + * + * preference.setBarChartTitleRes(R.string.title_res); + * preference.setBarChartDetailsRes(R.string.details_res); + * preference.setBarChartDetailsClickListener(v -> doSomething()); + * preference.setAllBarViewsData(viewsInfo); + * </pre> + */ +public class BarChartPreference extends Preference { + + private static final String TAG = "BarChartPreference"; + private static final int MAXIMUM_BAR_VIEWS = 4; + private static final int[] BAR_VIEWS = { + R.id.bar_view1, + R.id.bar_view2, + R.id.bar_view3, + R.id.bar_view4 + }; + + private int mMaxBarHeight; + private @StringRes int mTitleId; + private @StringRes int mDetailsId; + private BarViewInfo[] mBarViewsInfo; + private View.OnClickListener mDetailsOnClickListener; + + /** + * Constructs a new BarChartPreference with the given context's theme. + * It sets a layout with settings bar chart style + * + * @param context The Context the view is running in, through which it can + * access the current theme, resources, etc. + */ + public BarChartPreference(Context context) { + super(context); + init(); + } + + /** + * Constructs a new BarChartPreference with the given context's theme and the supplied + * attribute set. + * It sets a layout with settings bar chart style + * + * @param context the Context the view is running in + * @param attrs the attributes of the XML tag that is inflating the view. + */ + public BarChartPreference(Context context, AttributeSet attrs) { + super(context, attrs); + init(); + } + + /** + * Constructs a new BarChartPreference with the given context's theme, the supplied + * attribute set, and default style attribute. + * It sets a layout with settings bar chart style + * + * @param context The Context the view is running in, through which it can + * access the current theme, resources, etc. + * @param attrs The attributes of the XML tag that is inflating the view. + * @param defStyleAttr An attribute in the current theme that contains a + * reference to a style resource that supplies default + * values for the view. Can be 0 to not look for + * defaults. + */ + public BarChartPreference(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(); + } + + /** + * Constructs a new BarChartPreference with the given context's theme, the supplied + * attribute set, and default styles. + * It sets a layout with settings bar chart style + * + * @param context The Context the view is running in, through which it can + * access the current theme, resources, etc. + * @param attrs The attributes of the XML tag that is inflating the view. + * @param defStyleAttr An attribute in the current theme that contains a + * reference to a style resource that supplies default + * values for the view. Can be 0 to not look for + * defaults. + * @param defStyleRes A resource identifier of a style resource that + * supplies default values for the view, used only if + * defStyleAttr is 0 or can not be found in the theme. + * Can be 0 to not look for defaults. + */ + public BarChartPreference(Context context, AttributeSet attrs, int defStyleAttr, + int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + init(); + } + + /** + * Set the text resource for bar chart title. + */ + public void setBarChartTitle(@StringRes int resId) { + mTitleId = resId; + notifyChanged(); + } + + /** + * Set the text resource for bar chart details. + */ + public void setBarChartDetails(@StringRes int resId) { + mDetailsId = resId; + notifyChanged(); + } + + /** + * Register a callback to be invoked when bar chart details view is clicked. + */ + public void setBarChartDetailsClickListener(@Nullable View.OnClickListener clickListener) { + mDetailsOnClickListener = clickListener; + notifyChanged(); + } + + /** + * Set all bar view information which you'd like to show in preference. + * + * <p>This method helps you do a sort by {@linkBarViewInfo#mBarNumber} in descending order. + * + * @param barViewsInfo the barViewsInfo contain at least one {@link BarViewInfo}. + */ + public void setAllBarViewsInfo(@NonNull BarViewInfo[] barViewsInfo) { + mBarViewsInfo = barViewsInfo; + // Do a sort in descending order, the first element would have max {@link + // BarViewInfo#mBarNumber} + Arrays.sort(mBarViewsInfo); + caculateAllBarViewsHeight(); + notifyChanged(); + } + + @Override + public void onBindViewHolder(PreferenceViewHolder holder) { + super.onBindViewHolder(holder); + holder.setDividerAllowedAbove(true); + holder.setDividerAllowedBelow(true); + + bindChartTitleView(holder); + bindChartDetailsView(holder); + updateBarChart(holder); + } + + private void init() { + setSelectable(false); + setLayoutResource(R.layout.settings_bar_chart); + mMaxBarHeight = getContext().getResources().getDimensionPixelSize( + R.dimen.settings_bar_view_max_height); + } + + private void bindChartTitleView(PreferenceViewHolder holder) { + final TextView titleView = (TextView) holder.findViewById(R.id.bar_chart_title); + titleView.setText(mTitleId); + } + + private void bindChartDetailsView(PreferenceViewHolder holder) { + final Button detailsView = (Button) holder.findViewById(R.id.bar_chart_details); + detailsView.setText(mDetailsId); + detailsView.setOnClickListener(mDetailsOnClickListener); + } + + private void updateBarChart(PreferenceViewHolder holder) { + for (int index = 0; index < MAXIMUM_BAR_VIEWS; index++) { + final BarView barView = (BarView) holder.findViewById(BAR_VIEWS[index]); + + // If there is no bar views data can be shown. + if (mBarViewsInfo == null || index >= mBarViewsInfo.length) { + barView.setVisibility(View.GONE); + continue; + } + barView.setVisibility(View.VISIBLE); + barView.updateBarViewUI(mBarViewsInfo[index]); + } + } + + private void caculateAllBarViewsHeight() { + // Since we sorted this array in advance, the first element must have the max {@link + // BarViewInfo#mBarNumber}. + final int maxBarViewNumber = mBarViewsInfo[0].getBarNumber(); + // If the max number of bar view is zero, then we don't caculate the unit for bar height. + final int unit = maxBarViewNumber == 0 ? 0 : mMaxBarHeight / maxBarViewNumber; + + for (BarViewInfo barView : mBarViewsInfo) { + barView.setBarHeight(barView.getBarNumber() * unit); + } + } +} diff --git a/packages/SettingsLib/BarChartPreference/src/com/android/settingslib/widget/BarView.java b/packages/SettingsLib/BarChartPreference/src/com/android/settingslib/widget/BarView.java new file mode 100644 index 000000000000..6243a2de387a --- /dev/null +++ b/packages/SettingsLib/BarChartPreference/src/com/android/settingslib/widget/BarView.java @@ -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.android.settingslib.widget; + +import android.content.Context; +import android.content.res.TypedArray; +import android.util.AttributeSet; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import androidx.annotation.ColorInt; +import androidx.annotation.VisibleForTesting; + +/** + * A extension view for bar chart. + */ +public class BarView extends LinearLayout { + + private static final String TAG = "BarView"; + + private View mBarView; + private ImageView mIcon; + private TextView mBarTitle; + private TextView mBarSummary; + + /** + * Constructs a new BarView with the given context's theme. + * + * @param context The Context the view is running in, through which it can + * access the current theme, resources, etc. + */ + public BarView(Context context) { + super(context); + init(); + } + + /** + * Constructs a new BarView with the given context's theme and the supplied + * attribute set. + * + * @param context the Context the view is running in + * @param attrs the attributes of the XML tag that is inflating the view. + */ + public BarView(Context context, AttributeSet attrs) { + super(context, attrs); + init(); + + // Get accent color + TypedArray a = context.obtainStyledAttributes(new int[]{android.R.attr.colorAccent}); + @ColorInt final int colorAccent = a.getColor(0, 0); + + // Get bar color from layout XML + a = context.obtainStyledAttributes(attrs, R.styleable.SettingsBarView); + @ColorInt final int barColor = a.getColor(R.styleable.SettingsBarView_barColor, + colorAccent); + a.recycle(); + + mBarView.setBackgroundColor(barColor); + } + + /** + * This helps update the bar view UI with a {@link BarViewInfo}. + * + * @param barViewInfo A {@link BarViewInfo} saves bar view status. + */ + public void updateBarViewUI(BarViewInfo barViewInfo) { + //Set height of bar view + mBarView.getLayoutParams().height = barViewInfo.getBarHeight(); + mIcon.setImageDrawable(barViewInfo.getIcon()); + // For now, we use the bar number as title. + mBarTitle.setText(Integer.toString(barViewInfo.getBarNumber())); + mBarSummary.setText(barViewInfo.getSummaryRes()); + } + + @VisibleForTesting + CharSequence getTitle() { + return mBarTitle.getText(); + } + + @VisibleForTesting + CharSequence getSummary() { + return mBarSummary.getText(); + } + + private void init() { + LayoutInflater.from(getContext()).inflate(R.layout.settings_bar_view, this); + setOrientation(LinearLayout.VERTICAL); + setGravity(Gravity.CENTER); + + mBarView = findViewById(R.id.bar_view); + mIcon = (ImageView) findViewById(R.id.icon_view); + mBarTitle = (TextView) findViewById(R.id.bar_title); + mBarSummary = (TextView) findViewById(R.id.bar_summary); + } + + private void setOnClickListner(View.OnClickListener listener) { + mBarView.setOnClickListener(listener); + } +} diff --git a/packages/SettingsLib/BarChartPreference/src/com/android/settingslib/widget/BarViewInfo.java b/packages/SettingsLib/BarChartPreference/src/com/android/settingslib/widget/BarViewInfo.java new file mode 100644 index 000000000000..aa83ce99d200 --- /dev/null +++ b/packages/SettingsLib/BarChartPreference/src/com/android/settingslib/widget/BarViewInfo.java @@ -0,0 +1,146 @@ +/* + * 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.widget; + +import android.graphics.drawable.Drawable; +import android.view.View; + +import androidx.annotation.IntRange; +import androidx.annotation.Nullable; +import androidx.annotation.StringRes; + +import java.util.Comparator; + +/** + * A class responsible for saving bar view information. + */ +public class BarViewInfo implements Comparable<BarViewInfo> { + + private final Drawable mIcon; + private View.OnClickListener mListener; + private @StringRes int mSummaryRes; + // A number indicates this bar's height. The larger number shows a higher bar view. + private int mBarNumber; + // A real height of bar view. + private int mBarHeight; + + /** + * Construct a BarViewInfo instance. + * + * @param icon the icon of bar view. + * @param barNumber the number of bar view. The larger number show a more height of bar view. + * @param summaryRes the resource identifier of the string resource to be displayed + * @return BarViewInfo object. + */ + public BarViewInfo(Drawable icon, @IntRange(from = 0) int barNumber, + @StringRes int summaryRes) { + mIcon = icon; + mBarNumber = barNumber; + mSummaryRes = summaryRes; + } + + /** + * Set number for bar view. + * + * @param barNumber the number of bar view. The larger number shows a higher bar view. + */ + public void setBarNumber(@IntRange(from = 0) int barNumber) { + mBarNumber = barNumber; + } + + /** + * Set summary resource for bar view + * + * @param resId the resource identifier of the string resource to be displayed + */ + public void setSummary(@StringRes int resId) { + mSummaryRes = resId; + } + + /** + * Set a click listner for bar view. + * + * @param listener the click listner is attached on bar view. + */ + public void setClickListener(@Nullable View.OnClickListener listener) { + mListener = listener; + } + + /** + * Get the icon of bar view. + * + * @return Drawable the icon of bar view. + */ + public Drawable getIcon() { + return mIcon; + } + + /** + * Get the OnClickListener of bar view. + * + * @return View.OnClickListener the click listner of bar view. + */ + public View.OnClickListener getListener() { + return mListener; + } + + /** + * Get the real height of bar view. + * + * @return the real height of bar view. + */ + public int getBarHeight() { + return mBarHeight; + } + + /** + * Get summary resource of bar view. + * + * @return summary resource of bar view. + */ + public int getSummaryRes() { + return mSummaryRes; + } + + /** + * Get the number of app uses this permisssion. + * + * @return the number of app uses this permission. + */ + public int getBarNumber() { + return mBarNumber; + } + + @Override + public int compareTo(BarViewInfo other) { + // Descending order + return Comparator.comparingInt((BarViewInfo barViewInfo) -> barViewInfo.mBarNumber) + .compare(other, this); + } + + /** + * Set a real height for bar view. + * + * <p>This method should not be called by outside. It usually should be called by + * {@link BarChartPreference#caculateAllBarViewsHeight} + * + * @param barHeight the real bar height for bar view. + */ + void setBarHeight(@IntRange(from = 0) int barHeight) { + mBarHeight = barHeight; + } +} diff --git a/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractWifiMacAddressPreferenceController.java b/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractWifiMacAddressPreferenceController.java index 9699294df587..71778215e079 100644 --- a/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractWifiMacAddressPreferenceController.java +++ b/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractWifiMacAddressPreferenceController.java @@ -19,9 +19,7 @@ package com.android.settingslib.deviceinfo; import android.annotation.SuppressLint; import android.content.Context; import android.net.ConnectivityManager; -import android.net.wifi.WifiInfo; import android.net.wifi.WifiManager; -import android.provider.Settings; import android.text.TextUtils; import androidx.annotation.VisibleForTesting; @@ -83,15 +81,13 @@ public abstract class AbstractWifiMacAddressPreferenceController @SuppressLint("HardwareIds") @Override protected void updateConnectivity() { - WifiInfo wifiInfo = mWifiManager.getConnectionInfo(); - final int macRandomizationMode = Settings.Global.getInt(mContext.getContentResolver(), - Settings.Global.WIFI_CONNECTED_MAC_RANDOMIZATION_ENABLED, OFF); - final String macAddress = wifiInfo == null ? null : wifiInfo.getMacAddress(); + final String[] macAddresses = mWifiManager.getFactoryMacAddresses(); + String macAddress = null; + if (macAddresses != null && macAddresses.length > 0) { + macAddress = macAddresses[0]; + } - if (macRandomizationMode == ON && WifiInfo.DEFAULT_MAC_ADDRESS.equals(macAddress)) { - mWifiMacAddress.setSummary(R.string.wifi_status_mac_randomized); - } else if (TextUtils.isEmpty(macAddress) - || WifiInfo.DEFAULT_MAC_ADDRESS.equals(macAddress)) { + if (TextUtils.isEmpty(macAddress)) { mWifiMacAddress.setSummary(R.string.status_unavailable); } else { mWifiMacAddress.setSummary(macAddress); diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java index f7b16f8b18db..c8c05a0f0bb6 100644 --- a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java +++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java @@ -22,6 +22,7 @@ import android.content.Intent; import android.os.PowerManager; import android.provider.Settings.Global; import android.provider.Settings.Secure; +import android.text.TextUtils; import android.util.KeyValueListParser; import android.util.Log; import android.util.Slog; @@ -176,4 +177,22 @@ public class BatterySaverUtils { setAutoBatterySaverTriggerLevel(context, level); } } + + /** + * Reverts battery saver schedule mode to none if we are in a bad state where routine mode + * is selected but no app is configured to actually provide the signal. + * @param context a valid context + */ + public static void revertScheduleToNoneIfNeeded(Context context) { + ContentResolver resolver = context.getContentResolver(); + final int currentMode = Global.getInt(resolver, Global.AUTOMATIC_POWER_SAVER_MODE, + PowerManager.POWER_SAVER_MODE_PERCENTAGE); + boolean providerConfigured = !TextUtils.isEmpty(context.getString( + com.android.internal.R.string.config_batterySaverScheduleProvider)); + if (currentMode == PowerManager.POWER_SAVER_MODE_DYNAMIC && !providerConfigured) { + Global.putInt(resolver, Global.LOW_POWER_MODE_TRIGGER_LEVEL, 0); + Global.putInt(resolver, Global.AUTOMATIC_POWER_SAVER_MODE, + PowerManager.POWER_SAVER_MODE_PERCENTAGE); + } + } } diff --git a/packages/SettingsLib/src/com/android/settingslib/location/RecentLocationAccesses.java b/packages/SettingsLib/src/com/android/settingslib/location/RecentLocationAccesses.java new file mode 100644 index 000000000000..9af06702a696 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/location/RecentLocationAccesses.java @@ -0,0 +1,189 @@ +/* + * 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.location; + +import android.app.AppOpsManager; +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.graphics.drawable.Drawable; +import android.os.Process; +import android.os.UserHandle; +import android.os.UserManager; +import android.text.format.DateUtils; +import android.util.IconDrawableFactory; +import android.util.Log; + +import androidx.annotation.VisibleForTesting; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +/** + * Retrieves the information of applications which accessed location recently. + */ +public class RecentLocationAccesses { + private static final String TAG = RecentLocationAccesses.class.getSimpleName(); + @VisibleForTesting + static final String ANDROID_SYSTEM_PACKAGE_NAME = "android"; + + // Keep last 24 hours of location app information. + private static final long RECENT_TIME_INTERVAL_MILLIS = DateUtils.DAY_IN_MILLIS; + + @VisibleForTesting + static final int[] LOCATION_OPS = new int[]{ + AppOpsManager.OP_FINE_LOCATION, + AppOpsManager.OP_COARSE_LOCATION, + }; + + private final PackageManager mPackageManager; + private final Context mContext; + private final IconDrawableFactory mDrawableFactory; + + public RecentLocationAccesses(Context context) { + mContext = context; + mPackageManager = context.getPackageManager(); + mDrawableFactory = IconDrawableFactory.newInstance(context); + } + + /** + * Fills a list of applications which queried location recently within specified time. + * Apps are sorted by recency. Apps with more recent location accesses are in the front. + */ + public List<Access> getAppList() { + // Retrieve a location usage list from AppOps + AppOpsManager aoManager = + (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE); + List<AppOpsManager.PackageOps> appOps = aoManager.getPackagesForOps(LOCATION_OPS); + + final int appOpsCount = appOps != null ? appOps.size() : 0; + + // Process the AppOps list and generate a preference list. + ArrayList<Access> accesses = new ArrayList<>(appOpsCount); + final long now = System.currentTimeMillis(); + final UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE); + final List<UserHandle> profiles = um.getUserProfiles(); + + for (int i = 0; i < appOpsCount; ++i) { + AppOpsManager.PackageOps ops = appOps.get(i); + // Don't show the Android System in the list - it's not actionable for the user. + // Also don't show apps belonging to background users except managed users. + String packageName = ops.getPackageName(); + int uid = ops.getUid(); + int userId = UserHandle.getUserId(uid); + boolean isAndroidOs = + (uid == Process.SYSTEM_UID) && ANDROID_SYSTEM_PACKAGE_NAME.equals(packageName); + if (isAndroidOs || !profiles.contains(new UserHandle(userId))) { + continue; + } + Access access = getAccessFromOps(now, ops); + if (access != null) { + accesses.add(access); + } + } + return accesses; + } + + public List<Access> getAppListSorted() { + List<Access> accesses = getAppList(); + // Sort the list of Access by recency. Most recent accesses first. + Collections.sort(accesses, Collections.reverseOrder(new Comparator<Access>() { + @Override + public int compare(Access access1, Access access2) { + return Long.compare(access1.accessFinishTime, access2.accessFinishTime); + } + })); + return accesses; + } + + /** + * Creates a Access entry for the given PackageOps. + * + * This method examines the time interval of the PackageOps first. If the PackageOps is older + * than the designated interval, this method ignores the PackageOps object and returns null. + * When the PackageOps is fresh enough, this method returns a Access object for the package + */ + private Access getAccessFromOps(long now, + AppOpsManager.PackageOps ops) { + String packageName = ops.getPackageName(); + List<AppOpsManager.OpEntry> entries = ops.getOps(); + long locationAccessFinishTime = 0L; + // Earliest time for a location access to end and still be shown in list. + long recentLocationCutoffTime = now - RECENT_TIME_INTERVAL_MILLIS; + for (AppOpsManager.OpEntry entry : entries) { + locationAccessFinishTime = Math.max(entry.getLastAccessBackgroundTime(), + entry.getLastAccessForegroundTime()); + } + // Bail out if the entry is out of date. + if (locationAccessFinishTime < recentLocationCutoffTime) { + return null; + } + + // The package is fresh enough, continue. + int uid = ops.getUid(); + int userId = UserHandle.getUserId(uid); + + Access access = null; + try { + ApplicationInfo appInfo = mPackageManager.getApplicationInfoAsUser( + packageName, PackageManager.GET_META_DATA, userId); + if (appInfo == null) { + Log.w(TAG, "Null application info retrieved for package " + packageName + + ", userId " + userId); + return null; + } + + final UserHandle userHandle = new UserHandle(userId); + Drawable icon = mDrawableFactory.getBadgedIcon(appInfo, userId); + CharSequence appLabel = mPackageManager.getApplicationLabel(appInfo); + CharSequence badgedAppLabel = mPackageManager.getUserBadgedLabel(appLabel, userHandle); + if (appLabel.toString().contentEquals(badgedAppLabel)) { + // If badged label is not different from original then no need for it as + // a separate content description. + badgedAppLabel = null; + } + access = new Access(packageName, userHandle, icon, appLabel, badgedAppLabel, + locationAccessFinishTime); + } catch (NameNotFoundException e) { + Log.w(TAG, "package name not found for " + packageName + ", userId " + userId); + } + return access; + } + + public static class Access { + public final String packageName; + public final UserHandle userHandle; + public final Drawable icon; + public final CharSequence label; + public final CharSequence contentDescription; + public final long accessFinishTime; + + private Access(String packageName, UserHandle userHandle, Drawable icon, + CharSequence label, CharSequence contentDescription, + long accessFinishTime) { + this.packageName = packageName; + this.userHandle = userHandle; + this.icon = icon; + this.label = label; + this.contentDescription = contentDescription; + this.accessFinishTime = accessFinishTime; + } + } +} diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/WifiMacAddressPreferenceControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/WifiMacAddressPreferenceControllerTest.java index 74e5bf5a8034..1f7f4bc1f6b6 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/WifiMacAddressPreferenceControllerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/WifiMacAddressPreferenceControllerTest.java @@ -27,7 +27,6 @@ import android.content.Context; import android.net.ConnectivityManager; import android.net.wifi.WifiInfo; import android.net.wifi.WifiManager; -import android.provider.Settings; import androidx.preference.Preference; import androidx.preference.PreferenceScreen; @@ -93,105 +92,23 @@ public class WifiMacAddressPreferenceControllerTest { } @Test - public void updateConnectivity_nullWifiInfoWithMacRandomizationOff_setMacUnavailable() { - Settings.Global.putInt(mContext.getContentResolver(), - Settings.Global.WIFI_CONNECTED_MAC_RANDOMIZATION_ENABLED, - AbstractWifiMacAddressPreferenceController.OFF); - doReturn(null).when(mWifiManager).getConnectionInfo(); - - mController.displayPreference(mScreen); - - assertThat(mPreference.getSummary()) - .isEqualTo(mContext.getString(R.string.status_unavailable)); - } - - @Test - public void updateConnectivity_nullMacWithMacRandomizationOff_setMacUnavailable() { - Settings.Global.putInt(mContext.getContentResolver(), - Settings.Global.WIFI_CONNECTED_MAC_RANDOMIZATION_ENABLED, - AbstractWifiMacAddressPreferenceController.OFF); - doReturn(null).when(mWifiInfo).getMacAddress(); - + public void updateConnectivity_null_setMacUnavailable() { + doReturn(null).when(mWifiManager).getFactoryMacAddresses(); mController.displayPreference(mScreen); - assertThat(mPreference.getSummary()) .isEqualTo(mContext.getString(R.string.status_unavailable)); } @Test - public void updateConnectivity_defaultMacWithMacRandomizationOff_setMacUnavailable() { - Settings.Global.putInt(mContext.getContentResolver(), - Settings.Global.WIFI_CONNECTED_MAC_RANDOMIZATION_ENABLED, - AbstractWifiMacAddressPreferenceController.OFF); - doReturn(WifiInfo.DEFAULT_MAC_ADDRESS).when(mWifiInfo).getMacAddress(); - + public void updateConnectivity_validMac_setValidMac() { + final String[] macAddresses = new String[]{TEST_MAC_ADDRESS}; + doReturn(macAddresses).when(mWifiManager).getFactoryMacAddresses(); mController.displayPreference(mScreen); - - assertThat(mPreference.getSummary()) - .isEqualTo(mContext.getString(R.string.status_unavailable)); - } - - @Test - public void updateConnectivity_validMacWithMacRandomizationOff_setValidMac() { - Settings.Global.putInt(mContext.getContentResolver(), - Settings.Global.WIFI_CONNECTED_MAC_RANDOMIZATION_ENABLED, - AbstractWifiMacAddressPreferenceController.OFF); - doReturn(TEST_MAC_ADDRESS).when(mWifiInfo).getMacAddress(); - - mController.displayPreference(mScreen); - assertThat(mPreference.getSummary()).isEqualTo(TEST_MAC_ADDRESS); - } - - @Test - public void updateConnectivity_nullWifiInfoWithMacRandomizationOn_setMacUnavailable() { - Settings.Global.putInt(mContext.getContentResolver(), - Settings.Global.WIFI_CONNECTED_MAC_RANDOMIZATION_ENABLED, - AbstractWifiMacAddressPreferenceController.ON); - doReturn(null).when(mWifiManager).getConnectionInfo(); - mController.displayPreference(mScreen); - - assertThat(mPreference.getSummary()) - .isEqualTo(mContext.getString(R.string.status_unavailable)); - } - - @Test - public void updateConnectivity_nullMacWithMacRandomizationOn_setMacUnavailable() { - Settings.Global.putInt(mContext.getContentResolver(), - Settings.Global.WIFI_CONNECTED_MAC_RANDOMIZATION_ENABLED, - AbstractWifiMacAddressPreferenceController.ON); - doReturn(null).when(mWifiInfo).getMacAddress(); - mController.displayPreference(mScreen); - assertThat(mPreference.getSummary()) - .isEqualTo(mContext.getString(R.string.status_unavailable)); - } - @Test - public void updateConnectivity_defaultMacWithMacRandomizationOn_setMacRandomized() { - Settings.Global.putInt(mContext.getContentResolver(), - Settings.Global.WIFI_CONNECTED_MAC_RANDOMIZATION_ENABLED, - AbstractWifiMacAddressPreferenceController.ON); - doReturn(WifiInfo.DEFAULT_MAC_ADDRESS).when(mWifiInfo).getMacAddress(); - - mController.displayPreference(mScreen); - - assertThat(mPreference.getSummary()) - .isEqualTo(mContext.getString(R.string.wifi_status_mac_randomized)); - } - - @Test - public void updateConnectivity_validMacWithMacRandomizationOn_setValidMac() { - Settings.Global.putInt(mContext.getContentResolver(), - Settings.Global.WIFI_CONNECTED_MAC_RANDOMIZATION_ENABLED, - AbstractWifiMacAddressPreferenceController.ON); - doReturn(TEST_MAC_ADDRESS).when(mWifiInfo).getMacAddress(); - - mController.displayPreference(mScreen); - - assertThat(mPreference.getSummary()).isEqualTo(TEST_MAC_ADDRESS); } private static class ConcreteWifiMacAddressPreferenceController diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/BarChartPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/BarChartPreferenceTest.java new file mode 100644 index 000000000000..371c3d46dfb4 --- /dev/null +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/BarChartPreferenceTest.java @@ -0,0 +1,211 @@ +/* + * 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.widget; + +import static com.google.common.truth.Truth.assertThat; + +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.view.View; +import android.widget.TextView; + +import androidx.preference.PreferenceViewHolder; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; + +@RunWith(RobolectricTestRunner.class) +public class BarChartPreferenceTest { + + private Context mContext; + private View mBarChartView; + private Drawable mIcon; + private BarView mBarView1; + private BarView mBarView2; + private BarView mBarView3; + private BarView mBarView4; + private PreferenceViewHolder mHolder; + private BarChartPreference mPreference; + + @Before + public void setUp() { + mContext = RuntimeEnvironment.application; + mBarChartView = View.inflate(mContext, R.layout.settings_bar_chart, null /* parent */); + mHolder = PreferenceViewHolder.createInstanceForTests(mBarChartView); + mPreference = new BarChartPreference(mContext, null /* attrs */); + mPreference.setBarChartTitle(R.string.debug_app); + mPreference.setBarChartDetails(R.string.debug_app); + + + mIcon = mContext.getDrawable(R.drawable.ic_menu); + mBarView1 = (BarView) mBarChartView.findViewById(R.id.bar_view1); + mBarView2 = (BarView) mBarChartView.findViewById(R.id.bar_view2); + mBarView3 = (BarView) mBarChartView.findViewById(R.id.bar_view3); + mBarView4 = (BarView) mBarChartView.findViewById(R.id.bar_view4); + } + + @Test + public void setBarChartTitleRes_setTitleRes_showInBarChartTitle() { + final TextView titleView = (TextView) mBarChartView.findViewById(R.id.bar_chart_title); + + mPreference.setBarChartTitle(R.string.debug_app); + mPreference.onBindViewHolder(mHolder); + + assertThat(titleView.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(titleView.getText()).isEqualTo(mContext.getText(R.string.debug_app)); + } + + @Test + public void setBarChartDetailsRes_setDetailsRes_showInBarChartDetails() { + final TextView detailsView = (TextView) mBarChartView.findViewById(R.id.bar_chart_details); + + mPreference.setBarChartDetails(R.string.debug_app); + mPreference.onBindViewHolder(mHolder); + + assertThat(detailsView.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(detailsView.getText()).isEqualTo(mContext.getText(R.string.debug_app)); + } + + @Test + public void setBarChartDetailsClickListener_setClickListener_detailsViewAttachClickListener() { + final TextView detailsView = (TextView) mBarChartView.findViewById(R.id.bar_chart_details); + + mPreference.setBarChartDetailsClickListener(v -> { + }); + mPreference.onBindViewHolder(mHolder); + + assertThat(detailsView.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(detailsView.hasOnClickListeners()).isTrue(); + } + + @Test + public void setAllBarViewsInfo_setOneBarViewInfo_showOneBarView() { + final BarViewInfo[] barViewsInfo = new BarViewInfo[]{ + new BarViewInfo(mIcon, 10 /* barNumber */, R.string.debug_app) + }; + + mPreference.setAllBarViewsInfo(barViewsInfo); + mPreference.onBindViewHolder(mHolder); + + assertThat(mBarView1.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(mBarView1.getTitle()).isEqualTo("10"); + + assertThat(mBarView2.getVisibility()).isEqualTo(View.GONE); + assertThat(mBarView3.getVisibility()).isEqualTo(View.GONE); + assertThat(mBarView4.getVisibility()).isEqualTo(View.GONE); + } + + @Test + public void setAllBarViewsInfo_setTwoBarViewsInfo_showTwoBarViews() { + final BarViewInfo[] barViewsInfo = new BarViewInfo[]{ + new BarViewInfo(mIcon, 20 /* barNumber */, R.string.debug_app), + new BarViewInfo(mIcon, 10 /* barNumber */, R.string.debug_app) + }; + + mPreference.setAllBarViewsInfo(barViewsInfo); + mPreference.onBindViewHolder(mHolder); + + assertThat(mBarView1.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(mBarView1.getTitle()).isEqualTo("20"); + assertThat(mBarView2.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(mBarView2.getTitle()).isEqualTo("10"); + + assertThat(mBarView3.getVisibility()).isEqualTo(View.GONE); + assertThat(mBarView4.getVisibility()).isEqualTo(View.GONE); + } + + @Test + public void setAllBarViewsInfo_setThreeBarViewsInfo_showThreeBarViews() { + final BarViewInfo[] barViewsInfo = new BarViewInfo[]{ + new BarViewInfo(mIcon, 20 /* barNumber */, R.string.debug_app), + new BarViewInfo(mIcon, 10 /* barNumber */, R.string.debug_app), + new BarViewInfo(mIcon, 5 /* barNumber */, R.string.debug_app) + }; + + mPreference.setAllBarViewsInfo(barViewsInfo); + mPreference.onBindViewHolder(mHolder); + + assertThat(mBarView1.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(mBarView1.getTitle()).isEqualTo("20"); + assertThat(mBarView2.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(mBarView2.getTitle()).isEqualTo("10"); + assertThat(mBarView3.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(mBarView3.getTitle()).isEqualTo("5"); + + assertThat(mBarView4.getVisibility()).isEqualTo(View.GONE); + } + + @Test + public void setAllBarViewsInfo_setFourBarViewsInfo_showFourBarViews() { + final BarViewInfo[] barViewsInfo = new BarViewInfo[]{ + new BarViewInfo(mIcon, 20 /* barNumber */, R.string.debug_app), + new BarViewInfo(mIcon, 10 /* barNumber */, R.string.debug_app), + new BarViewInfo(mIcon, 5 /* barNumber */, R.string.debug_app), + new BarViewInfo(mIcon, 2 /* barNumber */, R.string.debug_app), + }; + + mPreference.setAllBarViewsInfo(barViewsInfo); + mPreference.onBindViewHolder(mHolder); + + assertThat(mBarView1.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(mBarView1.getTitle()).isEqualTo("20"); + assertThat(mBarView2.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(mBarView2.getTitle()).isEqualTo("10"); + assertThat(mBarView3.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(mBarView3.getTitle()).isEqualTo("5"); + assertThat(mBarView4.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(mBarView4.getTitle()).isEqualTo("2"); + } + + @Test + public void setAllBarViewsInfo_setFourBarViewsInfo_barViewWasSortedInDescending() { + final BarViewInfo[] barViewsInfo = new BarViewInfo[]{ + new BarViewInfo(mIcon, 30 /* barNumber */, R.string.debug_app), + new BarViewInfo(mIcon, 50 /* barNumber */, R.string.debug_app), + new BarViewInfo(mIcon, 5 /* barNumber */, R.string.debug_app), + new BarViewInfo(mIcon, 10 /* barNumber */, R.string.debug_app), + }; + + mPreference.setAllBarViewsInfo(barViewsInfo); + mPreference.onBindViewHolder(mHolder); + + assertThat(mBarView1.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(mBarView1.getTitle()).isEqualTo("50"); + assertThat(mBarView2.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(mBarView2.getTitle()).isEqualTo("30"); + assertThat(mBarView3.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(mBarView3.getTitle()).isEqualTo("10"); + assertThat(mBarView4.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(mBarView4.getTitle()).isEqualTo("5"); + } + + @Test + public void setAllBarViewsInfo_setValidSummaryRes_barViewShouldShowSummary() { + final BarViewInfo[] barViewsInfo = new BarViewInfo[]{ + new BarViewInfo(mIcon, 10 /* barNumber */, R.string.debug_app), + }; + + mPreference.setAllBarViewsInfo(barViewsInfo); + mPreference.onBindViewHolder(mHolder); + + assertThat(mBarView1.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(mBarView1.getSummary()).isEqualTo(mContext.getText(R.string.debug_app)); + } +} diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index 5fe08aab93cc..8cfc2a6b158d 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -27,6 +27,7 @@ <uses-permission android:name="android.permission.READ_SMS" /> <uses-permission android:name="android.permission.CALL_PHONE" /> <uses-permission android:name="android.permission.READ_PHONE_STATE" /> + <uses-permission android:name="android.permission.READ_PRECISE_PHONE_STATE" /> <uses-permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE" /> <uses-permission android:name="android.permission.READ_CONTACTS" /> <uses-permission android:name="android.permission.WRITE_CONTACTS" /> diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp index c2e107a06692..8be67d9a7a51 100644 --- a/packages/SystemUI/Android.bp +++ b/packages/SystemUI/Android.bp @@ -115,6 +115,8 @@ android_library { "mockito-target-inline-minus-junit4", "testables", "truth-prebuilt", + "dagger2-2.19", + "jsr330" ], libs: [ "android.test.runner", @@ -125,6 +127,7 @@ android_library { "--extra-packages", "com.android.keyguard:com.android.systemui", ], + annotation_processors: ["dagger2-compiler-2.19"], } android_app { @@ -134,6 +137,7 @@ android_app { ], platform_apis: true, + product_specific: true, certificate: "platform", privileged: true, diff --git a/packages/SystemUI/docs/dagger.md b/packages/SystemUI/docs/dagger.md new file mode 100644 index 000000000000..565d765f5556 --- /dev/null +++ b/packages/SystemUI/docs/dagger.md @@ -0,0 +1,136 @@ +# Dagger 2 in SystemUI +*Dagger 2 is a dependency injection framework that compiles annotations to code +to create dependencies without reflection* + +## Recommended reading + +Go read about Dagger 2. + +TODO: Add some links. + +## State of the world + +Dagger 2 has been turned on for SystemUI and a early first pass has been taken +for converting everything in Dependency.java to use Dagger. Since a lot of +SystemUI depends on Dependency, stubs have been added to Dependency to proxy +any gets through to the instances provided by dagger, this will allow migration +of SystemUI through a number of CLs. + +### How it works in SystemUI + +For the classes that we're using in Dependency and are switching to dagger, the +equivalent dagger version is using @Singleton and only having one instance. +To have the single instance span all of SystemUI and be easily accessible for +other components, there is a single root Component that exists that generates +these. The component lives in SystemUIFactory and is called SystemUIRootComponent. + +```java +@Singleton +@Component(modules = {SystemUIFactory.class, DependencyProvider.class, ContextHolder.class}) +public interface SystemUIRootComponent { + @Singleton + Dependency.DependencyInjector createDependency(); +} +``` + +The root modules are what provides the global singleton dependencies across +SystemUI. ContextHolder is just a wrapper that provides a context. +SystemUIFactory @Provide dependencies that need to be overridden by SystemUI +variants (like other form factors). DependencyProvider provides or binds any +remaining depedencies required. + +### Adding injection to a new SystemUI object + +Anything that depends on any @Singleton provider from SystemUIRootComponent +should be declared as a Subcomponent of the root component, this requires +declaring your own interface for generating your own modules or just the +object you need injected. The subcomponent also needs to be added to +SystemUIRootComponent in SystemUIFactory so it can be acquired. + +```java +public interface SystemUIRootComponent { ++ @Singleton ++ Dependency.DependencyInjector createDependency(); +} + +public class Dependency extends SystemUI { + ... ++ @Subcomponent ++ public interface DependencyInjector { ++ Dependency createSystemUI(); ++ } +} +``` + +For objects that extend SystemUI and require injection, you can define an +injector that creates the injected object for you. This other class should +be referenced in @string/config_systemUIServiceComponents. + +```java +public static class DependencyCreator implements Injector { + @Override + public SystemUI apply(Context context) { + return SystemUIFactory.getInstance().getRootComponent() + .createDependency() + .createSystemUI(); + } +} +``` + +### Adding a new injectable object + +First tag the constructor with @Inject. Also tag it with @Singleton if only one +instance should be created. + +```java +@Singleton +public class SomethingController { + @Inject + public SomethingController(Context context, + @Named(MAIN_HANDLER_NAME) Handler mainHandler) { + // context and mainHandler will be automatically populated. + } +} +``` + +If you have an interface class and an implementation class, dagger needs to know +how to map it. The simplest way to do this is to add a provides method to +DependencyProvider. + +```java +public class DependencyProvider { + ... + @Singleton + @Provide + public SomethingController provideSomethingController(Context context, + @Named(MAIN_HANDLER_NAME) Handler mainHandler) { + return new SomethingControllerImpl(context, mainHandler); + } +} +``` + +If you need to access this from Dependency#get, then add an adapter to Dependency +that maps to the instance provided by Dagger. The changes should be similar +to the following diff. + +```java +public class Dependency { + ... + @Inject Lazy<SomethingController> mSomethingController; + ... + public void start() { + ... + mProviders.put(SomethingController.class, mSomethingController::get); + } +} +``` + +## TODO List + + - Eliminate usages of Depndency#get + - Add support for Fragments to handle injection automatically + - (this could be through dagger2-android or something custom) + - Reduce number of things with @Provide in DependencyProvider (many can be + @Inject instead) + - Migrate as many remaining DependencyProvider instances to @Bind + - Add links in above TODO diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockPlugin.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockPlugin.java index 1a18f6096e25..6135aebb0587 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockPlugin.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockPlugin.java @@ -62,4 +62,12 @@ public interface ClockPlugin extends Plugin { * Notifies that the time zone has changed. */ default void onTimeZoneChanged(TimeZone timeZone) {} + + /** + * Indicates whether the keyguard status area (date) should be shown below + * the clock. + */ + default boolean shouldShowStatusArea() { + return true; + } } diff --git a/packages/SystemUI/proguard.flags b/packages/SystemUI/proguard.flags index ee94aedfe399..cc6848f323e9 100644 --- a/packages/SystemUI/proguard.flags +++ b/packages/SystemUI/proguard.flags @@ -17,6 +17,7 @@ -keep class com.android.systemui.car.CarSystemUIFactory -keep class com.android.systemui.SystemUIFactory -keep class * extends com.android.systemui.SystemUI +-keep class * implements com.android.systemui.SystemUI$Injector -keepclasseswithmembers class * { public <init>(android.content.Context, android.util.AttributeSet); diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml index 367a9ae28f97..d52866fbd444 100644 --- a/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml +++ b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml @@ -20,19 +20,31 @@ <!-- This is a view that shows clock information in Keyguard. --> <com.android.keyguard.KeyguardClockSwitch xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/keyguard_clock_container" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_gravity="center_horizontal" - android:layout_alignParentTop="true"> - <TextClock - android:id="@+id/default_clock_view" - android:layout_width="wrap_content" + android:layout_gravity="center_horizontal|top"> + <FrameLayout + android:id="@+id/clock_view" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_gravity="center_horizontal" + android:layout_alignParentTop="true"> + <TextClock + android:id="@+id/default_clock_view" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_horizontal" + android:letterSpacing="0.03" + android:textColor="?attr/wallpaperTextColor" + android:singleLine="true" + style="@style/widget_big" + android:format12Hour="@string/keyguard_widget_12_hours_format" + android:format24Hour="@string/keyguard_widget_24_hours_format" /> + </FrameLayout> + <include layout="@layout/keyguard_status_area" + android:id="@+id/keyguard_status_area" + android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_gravity="center_horizontal" - android:letterSpacing="0.03" - android:textColor="?attr/wallpaperTextColor" - android:singleLine="true" - style="@style/widget_big" - android:format12Hour="@string/keyguard_widget_12_hours_format" - android:format24Hour="@string/keyguard_widget_24_hours_format" /> + android:layout_below="@id/clock_view" /> </com.android.keyguard.KeyguardClockSwitch> diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_presentation.xml b/packages/SystemUI/res-keyguard/layout/keyguard_presentation.xml index 7d8a1f5bbbc7..a9ba19d2d393 100644 --- a/packages/SystemUI/res-keyguard/layout/keyguard_presentation.xml +++ b/packages/SystemUI/res-keyguard/layout/keyguard_presentation.xml @@ -33,21 +33,11 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> - <RelativeLayout + <include + layout="@layout/keyguard_clock_switch" android:id="@+id/keyguard_clock_container" android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_gravity="center_horizontal|top"> - <include layout="@layout/keyguard_clock_switch" - android:id="@+id/clock_view" - android:layout_width="match_parent" - android:layout_height="wrap_content" /> - <include layout="@layout/keyguard_status_area" - android:id="@+id/keyguard_status_area" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_below="@id/clock_view" /> - </RelativeLayout> + android:layout_height="wrap_content" /> <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_status_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_status_view.xml index 67ecf6f7b21a..10fea9d50112 100644 --- a/packages/SystemUI/res-keyguard/layout/keyguard_status_view.xml +++ b/packages/SystemUI/res-keyguard/layout/keyguard_status_view.xml @@ -50,21 +50,11 @@ android:textSize="13sp" android:text="@*android:string/global_action_logout" /> - <RelativeLayout + <include + layout="@layout/keyguard_clock_switch" android:id="@+id/keyguard_clock_container" android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_gravity="center_horizontal|top"> - <include layout="@layout/keyguard_clock_switch" - android:id="@+id/clock_view" - android:layout_width="match_parent" - android:layout_height="wrap_content" /> - <include layout="@layout/keyguard_status_area" - android:id="@+id/keyguard_status_area" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_below="@id/clock_view" /> - </RelativeLayout> + android:layout_height="wrap_content" /> <TextView android:id="@+id/owner_info" diff --git a/packages/SystemUI/res-keyguard/values/styles.xml b/packages/SystemUI/res-keyguard/values/styles.xml index ffc7b3cca357..b9966cf33646 100644 --- a/packages/SystemUI/res-keyguard/values/styles.xml +++ b/packages/SystemUI/res-keyguard/values/styles.xml @@ -33,7 +33,7 @@ <item name="android:gravity">center_horizontal|center_vertical</item> <item name="android:background">@null</item> <item name="android:textSize">32sp</item> - <item name="android:fontFamily">@*android:string/config_headlineFontFamilyLight</item> + <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item> <item name="android:textColor">?attr/wallpaperTextColor</item> <item name="android:paddingBottom">-16dp</item> </style> diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index 61efbd5c2248..889db66526b8 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -290,7 +290,7 @@ <!-- SystemUI Services: The classes of the stuff to start. --> <string-array name="config_systemUIServiceComponents" translatable="false"> - <item>com.android.systemui.Dependency</item> + <item>com.android.systemui.Dependency$DependencyCreator</item> <item>com.android.systemui.util.NotificationChannels</item> <item>com.android.systemui.statusbar.CommandQueue$CommandQueueStart</item> <item>com.android.systemui.keyguard.KeyguardViewMediator</item> @@ -318,7 +318,7 @@ <!-- SystemUI Services (per user): The classes of the stuff to start for each user. This is a subset of the config_systemUIServiceComponents --> <string-array name="config_systemUIServiceComponentsPerUser" translatable="false"> - <item>com.android.systemui.Dependency</item> + <item>com.android.systemui.Dependency$DependencyCreator</item> <item>com.android.systemui.util.NotificationChannels</item> </string-array> diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginEnabler.java b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginEnabler.java index 74fd13f9564e..01b012d1fc84 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginEnabler.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginEnabler.java @@ -14,12 +14,40 @@ package com.android.systemui.shared.plugins; +import android.annotation.IntDef; import android.content.ComponentName; /** * Enables and disables plugins. */ public interface PluginEnabler { - void setEnabled(ComponentName component, boolean enabled); + + int ENABLED = 0; + int DISABLED_MANUALLY = 1; + int DISABLED_INVALID_VERSION = 1; + int DISABLED_FROM_EXPLICIT_CRASH = 2; + int DISABLED_FROM_SYSTEM_CRASH = 3; + + @IntDef({ENABLED, DISABLED_MANUALLY, DISABLED_INVALID_VERSION, DISABLED_FROM_EXPLICIT_CRASH, + DISABLED_FROM_SYSTEM_CRASH}) + @interface DisableReason { + } + + /** Enables plugin via the PackageManager. */ + void setEnabled(ComponentName component); + + /** Disables a plugin via the PackageManager and records the reason for disabling. */ + void setDisabled(ComponentName component, @DisableReason int reason); + + /** Returns true if the plugin is enabled in the PackageManager. */ boolean isEnabled(ComponentName component); + + /** + * Returns the reason that a plugin is disabled, (if it is). + * + * It should return {@link #ENABLED} if the plugin is turned on. + * It should return {@link #DISABLED_MANUALLY} if the plugin is off but the reason is unknown. + */ + @DisableReason + int getDisableReason(ComponentName componentName); } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstanceManager.java b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstanceManager.java index 8e7fadb5c7cb..523720d54eec 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstanceManager.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstanceManager.java @@ -136,7 +136,7 @@ public class PluginInstanceManager<T extends Plugin> { ArrayList<PluginInfo> plugins = new ArrayList<PluginInfo>(mPluginHandler.mPlugins); for (PluginInfo info : plugins) { if (className.startsWith(info.mPackage)) { - disable(info); + disable(info, PluginEnabler.DISABLED_FROM_EXPLICIT_CRASH); disableAny = true; } } @@ -146,12 +146,13 @@ public class PluginInstanceManager<T extends Plugin> { public boolean disableAll() { ArrayList<PluginInfo> plugins = new ArrayList<PluginInfo>(mPluginHandler.mPlugins); for (int i = 0; i < plugins.size(); i++) { - disable(plugins.get(i)); + disable(plugins.get(i), PluginEnabler.DISABLED_FROM_SYSTEM_CRASH); } return plugins.size() != 0; } - private void disable(PluginInfo info) { + private void disable(PluginInfo info, + @PluginEnabler.DisableReason int reason) { // Live by the sword, die by the sword. // Misbehaving plugins get disabled and won't come back until uninstall/reinstall. @@ -162,9 +163,9 @@ public class PluginInstanceManager<T extends Plugin> { // Don't disable whitelisted plugins as they are a part of the OS. return; } - Log.w(TAG, "Disabling plugin " + info.mPackage + "/" + info.mClass); - mManager.getPluginEnabler().setEnabled(new ComponentName(info.mPackage, info.mClass), - false); + ComponentName pluginComponent = new ComponentName(info.mPackage, info.mClass); + Log.w(TAG, "Disabling plugin " + pluginComponent.flattenToShortString()); + mManager.getPluginEnabler().setDisabled(pluginComponent, reason); } public <T> boolean dependsOn(Plugin p, Class<T> cls) { diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginManagerImpl.java b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginManagerImpl.java index dc2a9bd5105b..10b5f1c64d85 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginManagerImpl.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginManagerImpl.java @@ -184,6 +184,7 @@ public class PluginManagerImpl extends BroadcastReceiver implements PluginManage mListening = true; IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED); filter.addAction(Intent.ACTION_PACKAGE_CHANGED); + filter.addAction(Intent.ACTION_PACKAGE_REPLACED); filter.addAction(Intent.ACTION_PACKAGE_REMOVED); filter.addAction(PLUGIN_CHANGED); filter.addAction(DISABLE_PLUGIN); @@ -214,12 +215,13 @@ public class PluginManagerImpl extends BroadcastReceiver implements PluginManage // Don't disable whitelisted plugins as they are a part of the OS. return; } - getPluginEnabler().setEnabled(component, false); + getPluginEnabler().setDisabled(component, PluginEnabler.DISABLED_INVALID_VERSION); mContext.getSystemService(NotificationManager.class).cancel(component.getClassName(), SystemMessage.NOTE_PLUGIN); } else { Uri data = intent.getData(); String pkg = data.getEncodedSchemeSpecificPart(); + ComponentName componentName = ComponentName.unflattenFromString(pkg); if (mOneShotPackages.contains(pkg)) { int icon = mContext.getResources().getIdentifier("tuner", "drawable", mContext.getPackageName()); @@ -256,6 +258,17 @@ public class PluginManagerImpl extends BroadcastReceiver implements PluginManage Log.v(TAG, "Reloading " + pkg); } } + if (Intent.ACTION_PACKAGE_REPLACED.equals(intent.getAction())) { + @PluginEnabler.DisableReason int disableReason = + getPluginEnabler().getDisableReason(componentName); + if (disableReason == PluginEnabler.DISABLED_FROM_EXPLICIT_CRASH + || disableReason == PluginEnabler.DISABLED_FROM_SYSTEM_CRASH + || disableReason == PluginEnabler.DISABLED_INVALID_VERSION) { + Log.i(TAG, "Re-enabling previously disabled plugin that has been " + + "updated: " + componentName.flattenToShortString()); + getPluginEnabler().setEnabled(componentName); + } + } if (!Intent.ACTION_PACKAGE_REMOVED.equals(intent.getAction())) { for (PluginInstanceManager manager : mPluginMap.values()) { manager.onPackageChange(pkg); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java index 0ec90148c350..22a23a8f8a21 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java @@ -7,6 +7,7 @@ import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; import android.widget.FrameLayout; +import android.widget.RelativeLayout; import android.widget.TextClock; import androidx.annotation.VisibleForTesting; @@ -22,7 +23,7 @@ import java.util.TimeZone; /** * Switch to show plugin clock when plugin is connected, otherwise it will show default clock. */ -public class KeyguardClockSwitch extends FrameLayout { +public class KeyguardClockSwitch extends RelativeLayout { /** * Optional/alternative clock injected via plugin. */ @@ -31,6 +32,15 @@ public class KeyguardClockSwitch extends FrameLayout { * Default clock. */ private TextClock mClockView; + /** + * Frame for default and custom clock. + */ + private FrameLayout mClockFrame; + /** + * Status area (date and other stuff) shown below the clock. Plugin can decide whether + * or not to show it below the alternate clock. + */ + private View mKeyguardStatusArea; private final PluginListener<ClockPlugin> mClockPluginListener = new PluginListener<ClockPlugin>() { @@ -43,11 +53,14 @@ public class KeyguardClockSwitch extends FrameLayout { // selected clock face. In the future, the user should be able to // pick a clock face from the available plugins. mClockPlugin = plugin; - addView(view, -1, + mClockFrame.addView(view, -1, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); initPluginParams(); mClockView.setVisibility(View.GONE); + if (!plugin.shouldShowStatusArea()) { + mKeyguardStatusArea.setVisibility(View.GONE); + } } } @@ -56,6 +69,7 @@ public class KeyguardClockSwitch extends FrameLayout { if (Objects.equals(plugin, mClockPlugin)) { disconnectPlugin(); mClockView.setVisibility(View.VISIBLE); + mKeyguardStatusArea.setVisibility(View.VISIBLE); } } }; @@ -72,6 +86,8 @@ public class KeyguardClockSwitch extends FrameLayout { protected void onFinishInflate() { super.onFinishInflate(); mClockView = findViewById(R.id.default_clock_view); + mClockFrame = findViewById(R.id.clock_view); + mKeyguardStatusArea = findViewById(R.id.keyguard_status_area); } @Override @@ -185,7 +201,7 @@ public class KeyguardClockSwitch extends FrameLayout { if (mClockPlugin != null) { View view = mClockPlugin.getView(); if (view != null) { - removeView(view); + mClockFrame.removeView(view); } mClockPlugin = null; } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java index 1e9d288bc605..c6f172684686 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java @@ -37,7 +37,7 @@ import android.util.Slog; import android.util.TypedValue; import android.view.View; import android.widget.GridLayout; -import android.widget.RelativeLayout; +import android.widget.LinearLayout; import android.widget.TextView; import androidx.core.graphics.ColorUtils; @@ -173,7 +173,7 @@ public class KeyguardStatusView extends GridLayout implements mLogoutView.setOnClickListener(this::onLogoutClicked); } - mClockView = findViewById(R.id.clock_view); + mClockView = findViewById(R.id.keyguard_clock_container); mClockView.setShowCurrentUserTime(true); if (KeyguardClockAccessibilityDelegate.isNeeded(mContext)) { mClockView.setAccessibilityDelegate(new KeyguardClockAccessibilityDelegate(mContext)); @@ -205,8 +205,8 @@ public class KeyguardStatusView extends GridLayout implements * Moves clock, adjusting margins when slice content changes. */ private void onSliceContentChanged() { - RelativeLayout.LayoutParams layoutParams = - (RelativeLayout.LayoutParams) mClockView.getLayoutParams(); + LinearLayout.LayoutParams layoutParams = + (LinearLayout.LayoutParams) mClockView.getLayoutParams(); layoutParams.bottomMargin = mPulsing ? mSmallClockPadding : 0; mClockView.setLayoutParams(layoutParams); } diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java index 327ffcd24762..4e5af15dbfed 100644 --- a/packages/SystemUI/src/com/android/systemui/Dependency.java +++ b/packages/SystemUI/src/com/android/systemui/Dependency.java @@ -14,20 +14,15 @@ package com.android.systemui; +import android.annotation.Nullable; import android.content.Context; import android.content.res.Configuration; -import android.hardware.SensorManager; import android.hardware.SensorPrivacyManager; import android.os.Handler; -import android.os.HandlerThread; import android.os.Looper; -import android.os.Process; -import android.os.ServiceManager; -import android.os.UserHandle; import android.util.ArrayMap; import android.util.DisplayMetrics; import android.view.IWindowManager; -import android.view.WindowManagerGlobal; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.ColorDisplayController; @@ -36,92 +31,86 @@ import com.android.internal.statusbar.IStatusBarService; import com.android.internal.util.Preconditions; import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.systemui.appops.AppOpsController; -import com.android.systemui.appops.AppOpsControllerImpl; import com.android.systemui.assist.AssistManager; +import com.android.systemui.bubbles.BubbleController; import com.android.systemui.colorextraction.SysuiColorExtractor; 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.PluginDependencyProvider; -import com.android.systemui.plugins.PluginInitializerImpl; import com.android.systemui.plugins.VolumeDialogController; import com.android.systemui.power.EnhancedEstimates; -import com.android.systemui.power.EnhancedEstimatesImpl; -import com.android.systemui.power.PowerNotificationWarnings; import com.android.systemui.power.PowerUI; import com.android.systemui.recents.OverviewProxyService; import com.android.systemui.shared.plugins.PluginManager; -import com.android.systemui.shared.plugins.PluginManagerImpl; +import com.android.systemui.statusbar.AmbientPulseManager; import com.android.systemui.statusbar.DisplayNavigationBarController; +import com.android.systemui.statusbar.NotificationListener; +import com.android.systemui.statusbar.NotificationLockscreenUserManager; +import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.NotificationRemoteInputManager; +import com.android.systemui.statusbar.NotificationViewHierarchyManager; +import com.android.systemui.statusbar.SmartReplyController; +import com.android.systemui.statusbar.StatusBarStateController; import com.android.systemui.statusbar.VibratorHelper; import com.android.systemui.statusbar.notification.NotificationData.KeyguardEnvironment; -import com.android.systemui.statusbar.phone.ConfigurationControllerImpl; -import com.android.systemui.statusbar.phone.DarkIconDispatcherImpl; -import com.android.systemui.statusbar.phone.KeyguardEnvironmentImpl; +import com.android.systemui.statusbar.notification.NotificationEntryManager; +import com.android.systemui.statusbar.notification.VisualStabilityManager; +import com.android.systemui.statusbar.notification.logging.NotificationLogger; +import com.android.systemui.statusbar.notification.row.NotificationBlockingHelperManager; +import com.android.systemui.statusbar.notification.row.NotificationGutsManager; +import com.android.systemui.statusbar.phone.KeyguardDismissUtil; import com.android.systemui.statusbar.phone.LightBarController; import com.android.systemui.statusbar.phone.LockscreenGestureLogger; import com.android.systemui.statusbar.phone.ManagedProfileController; -import com.android.systemui.statusbar.phone.ManagedProfileControllerImpl; +import com.android.systemui.statusbar.phone.NotificationGroupAlertTransferHelper; +import com.android.systemui.statusbar.phone.NotificationGroupManager; import com.android.systemui.statusbar.phone.ShadeController; -import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.phone.StatusBarIconController; -import com.android.systemui.statusbar.phone.StatusBarIconControllerImpl; -import com.android.systemui.statusbar.phone.StatusBarRemoteInputCallback; import com.android.systemui.statusbar.phone.StatusBarWindowController; import com.android.systemui.statusbar.policy.AccessibilityController; import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper; import com.android.systemui.statusbar.policy.BatteryController; -import com.android.systemui.statusbar.policy.BatteryControllerImpl; import com.android.systemui.statusbar.policy.BluetoothController; -import com.android.systemui.statusbar.policy.BluetoothControllerImpl; import com.android.systemui.statusbar.policy.CastController; -import com.android.systemui.statusbar.policy.CastControllerImpl; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.DarkIconDispatcher; import com.android.systemui.statusbar.policy.DataSaverController; import com.android.systemui.statusbar.policy.DeviceProvisionedController; -import com.android.systemui.statusbar.policy.DeviceProvisionedControllerImpl; import com.android.systemui.statusbar.policy.ExtensionController; -import com.android.systemui.statusbar.policy.ExtensionControllerImpl; import com.android.systemui.statusbar.policy.FlashlightController; -import com.android.systemui.statusbar.policy.FlashlightControllerImpl; import com.android.systemui.statusbar.policy.HotspotController; -import com.android.systemui.statusbar.policy.HotspotControllerImpl; import com.android.systemui.statusbar.policy.IconLogger; -import com.android.systemui.statusbar.policy.IconLoggerImpl; import com.android.systemui.statusbar.policy.KeyguardMonitor; -import com.android.systemui.statusbar.policy.KeyguardMonitorImpl; import com.android.systemui.statusbar.policy.LocationController; -import com.android.systemui.statusbar.policy.LocationControllerImpl; import com.android.systemui.statusbar.policy.NetworkController; -import com.android.systemui.statusbar.policy.NetworkControllerImpl; import com.android.systemui.statusbar.policy.NextAlarmController; -import com.android.systemui.statusbar.policy.NextAlarmControllerImpl; +import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler; import com.android.systemui.statusbar.policy.RotationLockController; -import com.android.systemui.statusbar.policy.RotationLockControllerImpl; import com.android.systemui.statusbar.policy.SecurityController; -import com.android.systemui.statusbar.policy.SecurityControllerImpl; +import com.android.systemui.statusbar.policy.SmartReplyConstants; import com.android.systemui.statusbar.policy.UserInfoController; -import com.android.systemui.statusbar.policy.UserInfoControllerImpl; import com.android.systemui.statusbar.policy.UserSwitcherController; import com.android.systemui.statusbar.policy.ZenModeController; -import com.android.systemui.statusbar.policy.ZenModeControllerImpl; import com.android.systemui.tuner.TunablePadding.TunablePaddingService; import com.android.systemui.tuner.TunerService; -import com.android.systemui.tuner.TunerServiceImpl; import com.android.systemui.util.AsyncSensorManager; import com.android.systemui.util.leak.GarbageMonitor; import com.android.systemui.util.leak.LeakDetector; import com.android.systemui.util.leak.LeakReporter; -import com.android.systemui.volume.VolumeDialogControllerImpl; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.HashMap; import java.util.function.Consumer; +import javax.inject.Inject; +import javax.inject.Named; + +import dagger.Lazy; +import dagger.Subcomponent; + /** * Class to handle ugly dependencies throughout sysui until we determine the * long-term dependency injection solution. @@ -143,235 +132,302 @@ public class Dependency extends SystemUI { /** * Key for getting a background Looper for background work. */ - public static final DependencyKey<Looper> BG_LOOPER = new DependencyKey<>("background_looper"); + public static final String BG_LOOPER_NAME = "background_looper"; + /** + * Key for getting a background Handler for background work. + */ + public static final String BG_HANDLER_NAME = "background_handler"; + /** + * Key for getting a Handler for receiving time tick broadcasts on. + */ + public static final String TIME_TICK_HANDLER_NAME = "time_tick_handler"; + /** + * Generic handler on the main thread. + */ + public static final String MAIN_HANDLER_NAME = "main_handler"; + + /** + * An email address to send memory leak reports to by default. + */ + public static final String LEAK_REPORT_EMAIL_NAME = "leak_report_email"; + + /** + * Key for getting a background Looper for background work. + */ + public static final DependencyKey<Looper> BG_LOOPER = new DependencyKey<>(BG_LOOPER_NAME); /** * Key for getting a background Handler for background work. */ - public static final DependencyKey<Handler> BG_HANDLER = new DependencyKey<>("background_handler"); + public static final DependencyKey<Handler> BG_HANDLER = new DependencyKey<>(BG_HANDLER_NAME); /** * Key for getting a Handler for receiving time tick broadcasts on. */ public static final DependencyKey<Handler> TIME_TICK_HANDLER = - new DependencyKey<>("time_tick_handler"); + new DependencyKey<>(TIME_TICK_HANDLER_NAME); /** * Generic handler on the main thread. */ - public static final DependencyKey<Handler> MAIN_HANDLER = new DependencyKey<>("main_handler"); + public static final DependencyKey<Handler> MAIN_HANDLER = + new DependencyKey<>(MAIN_HANDLER_NAME); /** * An email address to send memory leak reports to by default. */ - public static final DependencyKey<String> LEAK_REPORT_EMAIL - = new DependencyKey<>("leak_report_email"); + public static final DependencyKey<String> LEAK_REPORT_EMAIL = + new DependencyKey<>(LEAK_REPORT_EMAIL_NAME); private final ArrayMap<Object, Object> mDependencies = new ArrayMap<>(); private final ArrayMap<Object, DependencyProvider> mProviders = new ArrayMap<>(); + @Inject Lazy<ActivityStarter> mActivityStarter; + @Inject Lazy<ActivityStarterDelegate> mActivityStarterDelegate; + @Inject Lazy<AsyncSensorManager> mAsyncSensorManager; + @Inject Lazy<BluetoothController> mBluetoothController; + @Inject Lazy<LocationController> mLocationController; + @Inject Lazy<RotationLockController> mRotationLockController; + @Inject Lazy<NetworkController> mNetworkController; + @Inject Lazy<ZenModeController> mZenModeController; + @Inject Lazy<HotspotController> mHotspotController; + @Inject Lazy<CastController> mCastController; + @Inject Lazy<FlashlightController> mFlashlightController; + @Inject Lazy<UserSwitcherController> mUserSwitcherController; + @Inject Lazy<UserInfoController> mUserInfoController; + @Inject Lazy<KeyguardMonitor> mKeyguardMonitor; + @Inject Lazy<BatteryController> mBatteryController; + @Inject Lazy<ColorDisplayController> mColorDisplayController; + @Inject Lazy<ManagedProfileController> mManagedProfileController; + @Inject Lazy<NextAlarmController> mNextAlarmController; + @Inject Lazy<DataSaverController> mDataSaverController; + @Inject Lazy<AccessibilityController> mAccessibilityController; + @Inject Lazy<DeviceProvisionedController> mDeviceProvisionedController; + @Inject Lazy<PluginManager> mPluginManager; + @Inject Lazy<AssistManager> mAssistManager; + @Inject Lazy<SecurityController> mSecurityController; + @Inject Lazy<LeakDetector> mLeakDetector; + @Inject Lazy<LeakReporter> mLeakReporter; + @Inject Lazy<GarbageMonitor> mGarbageMonitor; + @Inject Lazy<IconLogger> mIconLogger; + @Inject Lazy<TunerService> mTunerService; + @Inject Lazy<StatusBarWindowController> mStatusBarWindowController; + @Inject Lazy<DarkIconDispatcher> mDarkIconDispatcher; + @Inject Lazy<ConfigurationController> mConfigurationController; + @Inject Lazy<StatusBarIconController> mStatusBarIconController; + @Inject Lazy<ScreenLifecycle> mScreenLifecycle; + @Inject Lazy<WakefulnessLifecycle> mWakefulnessLifecycle; + @Inject Lazy<FragmentService> mFragmentService; + @Inject Lazy<ExtensionController> mExtensionController; + @Inject Lazy<PluginDependencyProvider> mPluginDependencyProvider; + @Inject Lazy<LocalBluetoothManager> mLocalBluetoothManager; + @Inject Lazy<VolumeDialogController> mVolumeDialogController; + @Inject Lazy<MetricsLogger> mMetricsLogger; + @Inject Lazy<AccessibilityManagerWrapper> mAccessibilityManagerWrapper; + @Inject Lazy<SysuiColorExtractor> mSysuiColorExtractor; + @Inject Lazy<TunablePaddingService> mTunablePaddingService; + @Inject Lazy<ForegroundServiceController> mForegroundServiceController; + @Inject Lazy<UiOffloadThread> mUiOffloadThread; + @Inject Lazy<PowerUI.WarningsUI> mWarningsUI; + @Inject Lazy<LightBarController> mLightBarController; + @Inject Lazy<IWindowManager> mIWindowManager; + @Inject Lazy<OverviewProxyService> mOverviewProxyService; + @Inject Lazy<EnhancedEstimates> mEnhancedEstimates; + @Inject Lazy<VibratorHelper> mVibratorHelper; + @Inject Lazy<IStatusBarService> mIStatusBarService; + @Inject Lazy<DisplayMetrics> mDisplayMetrics; + @Inject Lazy<LockscreenGestureLogger> mLockscreenGestureLogger; + @Inject Lazy<KeyguardEnvironment> mKeyguardEnvironment; + @Inject Lazy<ShadeController> mShadeController; + @Inject Lazy<NotificationRemoteInputManager.Callback> mNotificationRemoteInputManagerCallback; + @Inject Lazy<InitController> mInitController; + @Inject Lazy<AppOpsController> mAppOpsController; + @Inject Lazy<DisplayNavigationBarController> mDisplayNavigationBarController; + @Inject Lazy<StatusBarStateController> mStatusBarStateController; + @Inject Lazy<NotificationLockscreenUserManager> mNotificationLockscreenUserManager; + @Inject Lazy<NotificationGroupAlertTransferHelper> mNotificationGroupAlertTransferHelper; + @Inject Lazy<NotificationGroupManager> mNotificationGroupManager; + @Inject Lazy<VisualStabilityManager> mVisualStabilityManager; + @Inject Lazy<NotificationGutsManager> mNotificationGutsManager; + @Inject Lazy<NotificationMediaManager> mNotificationMediaManager; + @Inject Lazy<AmbientPulseManager> mAmbientPulseManager; + @Inject Lazy<NotificationBlockingHelperManager> mNotificationBlockingHelperManager; + @Inject Lazy<NotificationRemoteInputManager> mNotificationRemoteInputManager; + @Inject Lazy<SmartReplyConstants> mSmartReplyConstants; + @Inject Lazy<NotificationListener> mNotificationListener; + @Inject Lazy<NotificationLogger> mNotificationLogger; + @Inject Lazy<NotificationViewHierarchyManager> mNotificationViewHierarchyManager; + @Inject Lazy<KeyguardDismissUtil> mKeyguardDismissUtil; + @Inject Lazy<SmartReplyController> mSmartReplyController; + @Inject Lazy<RemoteInputQuickSettingsDisabler> mRemoteInputQuickSettingsDisabler; + @Inject Lazy<BubbleController> mBubbleController; + @Inject Lazy<NotificationEntryManager> mNotificationEntryManager; + @Inject Lazy<SensorPrivacyManager> mSensorPrivacyManager; + @Inject @Named(BG_LOOPER_NAME) Lazy<Looper> mBgLooper; + @Inject @Named(BG_HANDLER_NAME) Lazy<Handler> mBgHandler; + @Inject @Named(MAIN_HANDLER_NAME) Lazy<Handler> mMainHandler; + @Inject @Named(TIME_TICK_HANDLER_NAME) Lazy<Handler> mTimeTickHandler; + @Nullable + @Inject @Named(LEAK_REPORT_EMAIL_NAME) Lazy<String> mLeakReportEmail; + + @Inject + public Dependency() { + } + @Override public void start() { // TODO: Think about ways to push these creation rules out of Dependency to cut down // on imports. - mProviders.put(TIME_TICK_HANDLER, () -> { - HandlerThread thread = new HandlerThread("TimeTick"); - thread.start(); - return new Handler(thread.getLooper()); - }); - mProviders.put(BG_LOOPER, () -> { - HandlerThread thread = new HandlerThread("SysUiBg", - Process.THREAD_PRIORITY_BACKGROUND); - thread.start(); - return thread.getLooper(); - }); - mProviders.put(BG_HANDLER, () -> new Handler(getDependency(BG_LOOPER))); - mProviders.put(MAIN_HANDLER, () -> new Handler(Looper.getMainLooper())); - mProviders.put(ActivityStarter.class, () -> new ActivityStarterDelegate()); - mProviders.put(ActivityStarterDelegate.class, () -> - getDependency(ActivityStarter.class)); - - mProviders.put(AsyncSensorManager.class, () -> - new AsyncSensorManager(mContext.getSystemService(SensorManager.class), - getDependency(PluginManager.class))); + mProviders.put(TIME_TICK_HANDLER, mTimeTickHandler::get); + mProviders.put(BG_LOOPER, mBgLooper::get); + mProviders.put(BG_HANDLER, mBgHandler::get); + mProviders.put(MAIN_HANDLER, mMainHandler::get); + mProviders.put(ActivityStarter.class, mActivityStarter::get); + mProviders.put(ActivityStarterDelegate.class, mActivityStarterDelegate::get); - mProviders.put(SensorPrivacyManager.class, () -> - mContext.getSystemService(SensorPrivacyManager.class)); + mProviders.put(AsyncSensorManager.class, mAsyncSensorManager::get); - mProviders.put(BluetoothController.class, () -> - new BluetoothControllerImpl(mContext, getDependency(BG_LOOPER))); + mProviders.put(BluetoothController.class, mBluetoothController::get); + mProviders.put(SensorPrivacyManager.class, mSensorPrivacyManager::get); - mProviders.put(LocationController.class, () -> - new LocationControllerImpl(mContext, getDependency(BG_LOOPER))); + mProviders.put(LocationController.class, mLocationController::get); - mProviders.put(RotationLockController.class, () -> - new RotationLockControllerImpl(mContext)); + mProviders.put(RotationLockController.class, mRotationLockController::get); - mProviders.put(NetworkController.class, () -> - new NetworkControllerImpl(mContext, getDependency(BG_LOOPER), - getDependency(DeviceProvisionedController.class))); + mProviders.put(NetworkController.class, mNetworkController::get); - mProviders.put(ZenModeController.class, () -> - new ZenModeControllerImpl(mContext, getDependency(MAIN_HANDLER))); + mProviders.put(ZenModeController.class, mZenModeController::get); - mProviders.put(HotspotController.class, () -> - new HotspotControllerImpl(mContext)); + mProviders.put(HotspotController.class, mHotspotController::get); - mProviders.put(CastController.class, () -> - new CastControllerImpl(mContext)); + mProviders.put(CastController.class, mCastController::get); - mProviders.put(FlashlightController.class, () -> - new FlashlightControllerImpl(mContext)); + mProviders.put(FlashlightController.class, mFlashlightController::get); - mProviders.put(KeyguardMonitor.class, () -> - new KeyguardMonitorImpl(mContext)); + mProviders.put(KeyguardMonitor.class, mKeyguardMonitor::get); - mProviders.put(UserSwitcherController.class, () -> - new UserSwitcherController(mContext, getDependency(KeyguardMonitor.class), - getDependency(MAIN_HANDLER), getDependency(ActivityStarter.class))); + mProviders.put(UserSwitcherController.class, mUserSwitcherController::get); - mProviders.put(UserInfoController.class, () -> - new UserInfoControllerImpl(mContext)); + mProviders.put(UserInfoController.class, mUserInfoController::get); - mProviders.put(BatteryController.class, () -> - new BatteryControllerImpl(mContext)); + mProviders.put(BatteryController.class, mBatteryController::get); - mProviders.put(ColorDisplayController.class, () -> - new ColorDisplayController(mContext)); + mProviders.put(ColorDisplayController.class, mColorDisplayController::get); - mProviders.put(ManagedProfileController.class, () -> - new ManagedProfileControllerImpl(mContext)); + mProviders.put(ManagedProfileController.class, mManagedProfileController::get); - mProviders.put(NextAlarmController.class, () -> - new NextAlarmControllerImpl(mContext)); + mProviders.put(NextAlarmController.class, mNextAlarmController::get); - mProviders.put(DataSaverController.class, () -> - get(NetworkController.class).getDataSaverController()); + mProviders.put(DataSaverController.class, mDataSaverController::get); - mProviders.put(AccessibilityController.class, () -> - new AccessibilityController(mContext)); + mProviders.put(AccessibilityController.class, mAccessibilityController::get); - mProviders.put(DeviceProvisionedController.class, () -> - new DeviceProvisionedControllerImpl(mContext)); + mProviders.put(DeviceProvisionedController.class, mDeviceProvisionedController::get); - mProviders.put(PluginManager.class, () -> - new PluginManagerImpl(mContext, new PluginInitializerImpl())); + mProviders.put(PluginManager.class, mPluginManager::get); - mProviders.put(AssistManager.class, () -> - new AssistManager(getDependency(DeviceProvisionedController.class), mContext)); + mProviders.put(AssistManager.class, mAssistManager::get); - mProviders.put(SecurityController.class, () -> - new SecurityControllerImpl(mContext)); + mProviders.put(SecurityController.class, mSecurityController::get); - mProviders.put(LeakDetector.class, LeakDetector::create); + mProviders.put(LeakDetector.class, mLeakDetector::get); - mProviders.put(LEAK_REPORT_EMAIL, () -> null); + mProviders.put(LEAK_REPORT_EMAIL, mLeakReportEmail::get); - mProviders.put(LeakReporter.class, () -> new LeakReporter( - mContext, - getDependency(LeakDetector.class), - getDependency(LEAK_REPORT_EMAIL))); + mProviders.put(LeakReporter.class, mLeakReporter::get); - mProviders.put( - GarbageMonitor.class, - () -> - new GarbageMonitor( - mContext, - getDependency(BG_LOOPER), - getDependency(LeakDetector.class), - getDependency(LeakReporter.class))); + mProviders.put(GarbageMonitor.class, mGarbageMonitor::get); - mProviders.put(TunerService.class, () -> - new TunerServiceImpl(mContext)); + mProviders.put(TunerService.class, mTunerService::get); - mProviders.put(StatusBarWindowController.class, () -> - new StatusBarWindowController(mContext)); + mProviders.put(StatusBarWindowController.class, mStatusBarWindowController::get); - mProviders.put(DarkIconDispatcher.class, () -> - new DarkIconDispatcherImpl(mContext)); + mProviders.put(DarkIconDispatcher.class, mDarkIconDispatcher::get); - mProviders.put(ConfigurationController.class, () -> - new ConfigurationControllerImpl(mContext)); + mProviders.put(ConfigurationController.class, mConfigurationController::get); - mProviders.put(StatusBarIconController.class, () -> - new StatusBarIconControllerImpl(mContext)); + mProviders.put(StatusBarIconController.class, mStatusBarIconController::get); - mProviders.put(ScreenLifecycle.class, () -> - new ScreenLifecycle()); + mProviders.put(ScreenLifecycle.class, mScreenLifecycle::get); - mProviders.put(WakefulnessLifecycle.class, () -> - new WakefulnessLifecycle()); + mProviders.put(WakefulnessLifecycle.class, mWakefulnessLifecycle::get); - mProviders.put(FragmentService.class, () -> - new FragmentService()); + mProviders.put(FragmentService.class, mFragmentService::get); - mProviders.put(ExtensionController.class, () -> - new ExtensionControllerImpl(mContext)); + mProviders.put(ExtensionController.class, mExtensionController::get); - mProviders.put(PluginDependencyProvider.class, () -> - new PluginDependencyProvider(get(PluginManager.class))); + mProviders.put(PluginDependencyProvider.class, mPluginDependencyProvider::get); - mProviders.put(LocalBluetoothManager.class, () -> - LocalBluetoothManager.create(mContext, getDependency(BG_HANDLER), - UserHandle.ALL)); + mProviders.put(LocalBluetoothManager.class, mLocalBluetoothManager::get); - mProviders.put(VolumeDialogController.class, () -> - new VolumeDialogControllerImpl(mContext)); + mProviders.put(VolumeDialogController.class, mVolumeDialogController::get); - mProviders.put(MetricsLogger.class, () -> new MetricsLogger()); + mProviders.put(MetricsLogger.class, mMetricsLogger::get); - mProviders.put(AccessibilityManagerWrapper.class, - () -> new AccessibilityManagerWrapper(mContext)); + mProviders.put(AccessibilityManagerWrapper.class, mAccessibilityManagerWrapper::get); - // Creating a new instance will trigger color extraction. - // Thankfully this only happens once - during boot - and WallpaperManagerService - // loads colors from cache. - mProviders.put(SysuiColorExtractor.class, () -> new SysuiColorExtractor(mContext)); + mProviders.put(SysuiColorExtractor.class, mSysuiColorExtractor::get); - mProviders.put(TunablePaddingService.class, () -> new TunablePaddingService()); + mProviders.put(TunablePaddingService.class, mTunablePaddingService::get); - mProviders.put(ForegroundServiceController.class, - () -> new ForegroundServiceControllerImpl(mContext)); + mProviders.put(ForegroundServiceController.class, mForegroundServiceController::get); - mProviders.put(UiOffloadThread.class, UiOffloadThread::new); + mProviders.put(UiOffloadThread.class, mUiOffloadThread::get); - mProviders.put(PowerUI.WarningsUI.class, () -> new PowerNotificationWarnings(mContext)); + mProviders.put(PowerUI.WarningsUI.class, mWarningsUI::get); - mProviders.put(IconLogger.class, () -> new IconLoggerImpl(mContext, - getDependency(BG_LOOPER), getDependency(MetricsLogger.class))); + mProviders.put(IconLogger.class, mIconLogger::get); - mProviders.put(LightBarController.class, () -> new LightBarController(mContext)); + mProviders.put(LightBarController.class, mLightBarController::get); - mProviders.put(IWindowManager.class, () -> WindowManagerGlobal.getWindowManagerService()); + mProviders.put(IWindowManager.class, mIWindowManager::get); - mProviders.put(OverviewProxyService.class, () -> new OverviewProxyService(mContext)); + mProviders.put(OverviewProxyService.class, mOverviewProxyService::get); - mProviders.put(EnhancedEstimates.class, () -> new EnhancedEstimatesImpl()); + mProviders.put(EnhancedEstimates.class, mEnhancedEstimates::get); - mProviders.put(VibratorHelper.class, () -> new VibratorHelper(mContext)); + mProviders.put(VibratorHelper.class, mVibratorHelper::get); - mProviders.put(IStatusBarService.class, () -> IStatusBarService.Stub.asInterface( - ServiceManager.getService(Context.STATUS_BAR_SERVICE))); + mProviders.put(IStatusBarService.class, mIStatusBarService::get); - // Single instance of DisplayMetrics, gets updated by StatusBar, but can be used - // anywhere it is needed. - mProviders.put(DisplayMetrics.class, () -> new DisplayMetrics()); + mProviders.put(DisplayMetrics.class, mDisplayMetrics::get); - mProviders.put(LockscreenGestureLogger.class, () -> new LockscreenGestureLogger()); + mProviders.put(LockscreenGestureLogger.class, mLockscreenGestureLogger::get); - mProviders.put(KeyguardEnvironment.class, () -> new KeyguardEnvironmentImpl()); - mProviders.put(ShadeController.class, () -> - SysUiServiceProvider.getComponent(mContext, StatusBar.class)); + mProviders.put(KeyguardEnvironment.class, mKeyguardEnvironment::get); + mProviders.put(ShadeController.class, mShadeController::get); mProviders.put(NotificationRemoteInputManager.Callback.class, - () -> new StatusBarRemoteInputCallback(mContext)); - - mProviders.put(InitController.class, InitController::new); - - mProviders.put(AppOpsController.class, () -> - new AppOpsControllerImpl(mContext, getDependency(BG_LOOPER))); - - mProviders.put(DisplayNavigationBarController.class, () -> - new DisplayNavigationBarController(mContext, getDependency(MAIN_HANDLER))); - - // Put all dependencies above here so the factory can override them if it wants. - SystemUIFactory.getInstance().injectDependencies(mProviders, mContext); + mNotificationRemoteInputManagerCallback::get); + + mProviders.put(InitController.class, mInitController::get); + + mProviders.put(AppOpsController.class, mAppOpsController::get); + + mProviders.put(DisplayNavigationBarController.class, + mDisplayNavigationBarController::get); + + mProviders.put(StatusBarStateController.class, mStatusBarStateController::get); + mProviders.put(NotificationLockscreenUserManager.class, + mNotificationLockscreenUserManager::get); + mProviders.put(VisualStabilityManager.class, mVisualStabilityManager::get); + mProviders.put(NotificationGroupManager.class, mNotificationGroupManager::get); + mProviders.put(NotificationGroupAlertTransferHelper.class, + mNotificationGroupAlertTransferHelper::get); + mProviders.put(NotificationMediaManager.class, mNotificationMediaManager::get); + mProviders.put(NotificationGutsManager.class, mNotificationGutsManager::get); + mProviders.put(AmbientPulseManager.class, mAmbientPulseManager::get); + mProviders.put(NotificationBlockingHelperManager.class, + mNotificationBlockingHelperManager::get); + mProviders.put(NotificationRemoteInputManager.class, + mNotificationRemoteInputManager::get); + mProviders.put(SmartReplyConstants.class, mSmartReplyConstants::get); + mProviders.put(NotificationListener.class, mNotificationListener::get); + mProviders.put(NotificationLogger.class, mNotificationLogger::get); + mProviders.put(NotificationViewHierarchyManager.class, + mNotificationViewHierarchyManager::get); + mProviders.put(KeyguardDismissUtil.class, mKeyguardDismissUtil::get); + mProviders.put(SmartReplyController.class, mSmartReplyController::get); + mProviders.put(RemoteInputQuickSettingsDisabler.class, + mRemoteInputQuickSettingsDisabler::get); + mProviders.put(BubbleController.class, mBubbleController::get); + mProviders.put(NotificationEntryManager.class, mNotificationEntryManager::get); sDependency = this; } @@ -484,4 +540,20 @@ public class Dependency extends SystemUI { return mDisplayName; } } + + @Subcomponent + public interface DependencyInjector { + void createSystemUI(Dependency dependency); + } + + public static class DependencyCreator implements Injector { + @Override + public SystemUI apply(Context context) { + Dependency dependency = new Dependency(); + SystemUIFactory.getInstance().getRootComponent() + .createDependency() + .createSystemUI(dependency); + return dependency; + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/DependencyProvider.java b/packages/SystemUI/src/com/android/systemui/DependencyProvider.java new file mode 100644 index 000000000000..e828b2346b81 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/DependencyProvider.java @@ -0,0 +1,578 @@ +/* + * 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; + +import static com.android.systemui.Dependency.BG_HANDLER_NAME; +import static com.android.systemui.Dependency.BG_LOOPER_NAME; +import static com.android.systemui.Dependency.LEAK_REPORT_EMAIL_NAME; +import static com.android.systemui.Dependency.MAIN_HANDLER_NAME; +import static com.android.systemui.Dependency.TIME_TICK_HANDLER_NAME; + +import android.annotation.Nullable; +import android.content.Context; +import android.hardware.SensorManager; +import android.hardware.SensorPrivacyManager; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; +import android.os.Process; +import android.os.ServiceManager; +import android.os.UserHandle; +import android.util.DisplayMetrics; +import android.util.Log; +import android.view.IWindowManager; +import android.view.WindowManagerGlobal; + +import com.android.internal.app.ColorDisplayController; +import com.android.internal.logging.MetricsLogger; +import com.android.internal.statusbar.IStatusBarService; +import com.android.settingslib.bluetooth.LocalBluetoothManager; +import com.android.systemui.appops.AppOpsController; +import com.android.systemui.appops.AppOpsControllerImpl; +import com.android.systemui.colorextraction.SysuiColorExtractor; +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.PluginDependencyProvider; +import com.android.systemui.plugins.PluginInitializerImpl; +import com.android.systemui.plugins.VolumeDialogController; +import com.android.systemui.power.PowerNotificationWarnings; +import com.android.systemui.power.PowerUI; +import com.android.systemui.recents.OverviewProxyService; +import com.android.systemui.shared.plugins.PluginManager; +import com.android.systemui.shared.plugins.PluginManagerImpl; +import com.android.systemui.statusbar.DisplayNavigationBarController; +import com.android.systemui.statusbar.NotificationRemoteInputManager; +import com.android.systemui.statusbar.VibratorHelper; +import com.android.systemui.statusbar.phone.ConfigurationControllerImpl; +import com.android.systemui.statusbar.phone.DarkIconDispatcherImpl; +import com.android.systemui.statusbar.phone.LightBarController; +import com.android.systemui.statusbar.phone.LockscreenGestureLogger; +import com.android.systemui.statusbar.phone.ManagedProfileController; +import com.android.systemui.statusbar.phone.ManagedProfileControllerImpl; +import com.android.systemui.statusbar.phone.ShadeController; +import com.android.systemui.statusbar.phone.StatusBar; +import com.android.systemui.statusbar.phone.StatusBarIconController; +import com.android.systemui.statusbar.phone.StatusBarIconControllerImpl; +import com.android.systemui.statusbar.phone.StatusBarRemoteInputCallback; +import com.android.systemui.statusbar.phone.StatusBarWindowController; +import com.android.systemui.statusbar.policy.AccessibilityController; +import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper; +import com.android.systemui.statusbar.policy.BatteryController; +import com.android.systemui.statusbar.policy.BatteryControllerImpl; +import com.android.systemui.statusbar.policy.BluetoothController; +import com.android.systemui.statusbar.policy.BluetoothControllerImpl; +import com.android.systemui.statusbar.policy.CastController; +import com.android.systemui.statusbar.policy.CastControllerImpl; +import com.android.systemui.statusbar.policy.ConfigurationController; +import com.android.systemui.statusbar.policy.DarkIconDispatcher; +import com.android.systemui.statusbar.policy.DataSaverController; +import com.android.systemui.statusbar.policy.DeviceProvisionedController; +import com.android.systemui.statusbar.policy.DeviceProvisionedControllerImpl; +import com.android.systemui.statusbar.policy.ExtensionController; +import com.android.systemui.statusbar.policy.ExtensionControllerImpl; +import com.android.systemui.statusbar.policy.FlashlightController; +import com.android.systemui.statusbar.policy.FlashlightControllerImpl; +import com.android.systemui.statusbar.policy.HotspotController; +import com.android.systemui.statusbar.policy.HotspotControllerImpl; +import com.android.systemui.statusbar.policy.IconLogger; +import com.android.systemui.statusbar.policy.IconLoggerImpl; +import com.android.systemui.statusbar.policy.KeyguardMonitor; +import com.android.systemui.statusbar.policy.KeyguardMonitorImpl; +import com.android.systemui.statusbar.policy.LocationController; +import com.android.systemui.statusbar.policy.LocationControllerImpl; +import com.android.systemui.statusbar.policy.NetworkController; +import com.android.systemui.statusbar.policy.NetworkControllerImpl; +import com.android.systemui.statusbar.policy.NextAlarmController; +import com.android.systemui.statusbar.policy.NextAlarmControllerImpl; +import com.android.systemui.statusbar.policy.RotationLockController; +import com.android.systemui.statusbar.policy.RotationLockControllerImpl; +import com.android.systemui.statusbar.policy.SecurityController; +import com.android.systemui.statusbar.policy.SecurityControllerImpl; +import com.android.systemui.statusbar.policy.UserInfoController; +import com.android.systemui.statusbar.policy.UserInfoControllerImpl; +import com.android.systemui.statusbar.policy.UserSwitcherController; +import com.android.systemui.statusbar.policy.ZenModeController; +import com.android.systemui.statusbar.policy.ZenModeControllerImpl; +import com.android.systemui.tuner.TunablePadding; +import com.android.systemui.tuner.TunablePadding.TunablePaddingService; +import com.android.systemui.tuner.TunerService; +import com.android.systemui.tuner.TunerServiceImpl; +import com.android.systemui.util.AsyncSensorManager; +import com.android.systemui.util.leak.GarbageMonitor; +import com.android.systemui.util.leak.LeakDetector; +import com.android.systemui.util.leak.LeakReporter; +import com.android.systemui.volume.VolumeDialogControllerImpl; + +import javax.inject.Named; +import javax.inject.Singleton; + +import dagger.Module; +import dagger.Provides; + +/** + * Provides dependencies for the root component of sysui injection. + * See SystemUI/docs/dagger.md + */ +@Module +public class DependencyProvider { + + @Singleton + @Provides + @Named(TIME_TICK_HANDLER_NAME) + public Handler provideHandler() { + HandlerThread thread = new HandlerThread("TimeTick"); + thread.start(); + return new Handler(thread.getLooper()); + } + + @Singleton + @Provides + @Named(BG_LOOPER_NAME) + public Looper provideBgLooper() { + HandlerThread thread = new HandlerThread("SysUiBg", + Process.THREAD_PRIORITY_BACKGROUND); + thread.start(); + return thread.getLooper(); + } + + @Singleton + @Provides + @Named(BG_HANDLER_NAME) + public Handler provideBgHandler(@Named(BG_LOOPER_NAME) Looper bgLooper) { + return new Handler(bgLooper); + } + + @Singleton + @Provides + @Named(MAIN_HANDLER_NAME) + public Handler provideMainHandler() { + return new Handler(Looper.getMainLooper()); + } + + @Singleton + @Provides + public ActivityStarter provideActivityStarter() { + return new ActivityStarterDelegate(); + } + + @Singleton + @Provides + public InitController provideInitController() { + return new InitController(); + } + + @Singleton + @Provides + public ActivityStarterDelegate provideActivityStarterDelegate(ActivityStarter starter) { + return (ActivityStarterDelegate) starter; + } + + @Singleton + @Provides + public AsyncSensorManager provideAsyncSensorManager(Context context, PluginManager manager) { + return new AsyncSensorManager(context.getSystemService(SensorManager.class), + manager); + + } + + @Singleton + @Provides + public BluetoothController provideBluetoothController(Context context, + @Named(BG_LOOPER_NAME) Looper looper) { + return new BluetoothControllerImpl(context, looper); + + } + + @Singleton + @Provides + public LocationController provideLocationController(Context context, + @Named(BG_LOOPER_NAME) Looper bgLooper) { + return new LocationControllerImpl(context, bgLooper); + + } + + @Singleton + @Provides + public RotationLockController provideRotationLockController(Context context) { + return new RotationLockControllerImpl(context); + + } + + @Singleton + @Provides + public NetworkController provideNetworkController(Context context, + @Named(BG_LOOPER_NAME) Looper bgLooper, DeviceProvisionedController controller) { + return new NetworkControllerImpl(context, bgLooper, + controller); + + } + + @Singleton + @Provides + public ZenModeController provideZenModeController(Context context, + @Named(MAIN_HANDLER_NAME) Handler mainHandler) { + return new ZenModeControllerImpl(context, mainHandler); + + } + + @Singleton + @Provides + public HotspotController provideHotspotController(Context context) { + return new HotspotControllerImpl(context); + + } + + @Singleton + @Provides + public CastController provideCastController(Context context) { + return new CastControllerImpl(context); + + } + + @Singleton + @Provides + public FlashlightController provideFlashlightController(Context context) { + return new FlashlightControllerImpl(context); + + } + + @Singleton + @Provides + public KeyguardMonitor provideKeyguardMonitor(Context context) { + return new KeyguardMonitorImpl(context); + + } + + @Singleton + @Provides + public UserSwitcherController provideUserSwitcherController(Context context, + KeyguardMonitor keyguardMonitor, @Named(MAIN_HANDLER_NAME) Handler mainHandler, + ActivityStarter activityStarter) { + return new UserSwitcherController(context, keyguardMonitor, mainHandler, activityStarter); + } + + @Singleton + @Provides + public UserInfoController provideUserInfoContrller(Context context) { + return new UserInfoControllerImpl(context); + + } + + @Singleton + @Provides + public BatteryController provideBatteryController(Context context) { + return new BatteryControllerImpl(context); + + } + + @Singleton + @Provides + public ColorDisplayController provideColorDisplayController(Context context) { + return new ColorDisplayController(context); + + } + + @Singleton + @Provides + public ManagedProfileController provideManagedProfileController(Context context) { + return new ManagedProfileControllerImpl(context); + + } + + @Singleton + @Provides + public NextAlarmController provideNextAlarmController(Context context) { + return new NextAlarmControllerImpl(context); + + } + + @Singleton + @Provides + public DataSaverController provideDataSaverController(NetworkController networkController) { + return networkController.getDataSaverController(); + } + + @Singleton + @Provides + public AccessibilityController provideAccessibilityController(Context context) { + return new AccessibilityController(context); + + } + + @Singleton + @Provides + public DeviceProvisionedController provideDeviceProvisionedController(Context context) { + return new DeviceProvisionedControllerImpl(context); + + } + + @Singleton + @Provides + public PluginManager providePluginManager(Context context) { + return new PluginManagerImpl(context, new PluginInitializerImpl()); + + } + + @Singleton + @Provides + public SecurityController provideSecurityController(Context context) { + return new SecurityControllerImpl(context); + + } + + @Singleton + @Provides + public LeakDetector provideLeakDetector() { + return LeakDetector.create(); + + } + + @Singleton + @Provides + public LeakReporter provideLeakReporter(Context context, LeakDetector detector, + @Nullable @Named(LEAK_REPORT_EMAIL_NAME) String email) { + return new LeakReporter(context, detector, email); + } + + @Singleton + @Provides + public GarbageMonitor provideGarbageMonitor(Context context, + @Named(BG_LOOPER_NAME) Looper bgLooper, LeakDetector detector, LeakReporter reporter) { + return new GarbageMonitor(context, bgLooper, detector, reporter); + } + + @Singleton + @Provides + public TunerService provideTunerService(Context context) { + return new TunerServiceImpl(context); + + } + + @Singleton + @Provides + public StatusBarWindowController provideStatusBarWindowController(Context context) { + return new StatusBarWindowController(context); + + } + + @Singleton + @Provides + public DarkIconDispatcher provideDarkIconDispatcher(Context context) { + return new DarkIconDispatcherImpl(context); + } + + @Singleton + @Provides + public ConfigurationController provideConfigurationController(Context context) { + return new ConfigurationControllerImpl(context); + + } + + @Singleton + @Provides + public StatusBarIconController provideStatusBarIconController(Context context) { + return new StatusBarIconControllerImpl(context); + + } + + @Singleton + @Provides + public ScreenLifecycle provideScreenLifecycle() { + return new ScreenLifecycle(); + } + + @Singleton + @Provides + public WakefulnessLifecycle provideWakefulnessLifecycle() { + return new WakefulnessLifecycle(); + } + + @Singleton + @Provides + public FragmentService provideFragmentService() { + return new FragmentService(); + } + + @Singleton + @Provides + public ExtensionController provideExtensionController(Context context) { + return new ExtensionControllerImpl(context); + } + + @Singleton + @Provides + public PluginDependencyProvider providePluginDependency(PluginManager pluginManager) { + return new PluginDependencyProvider(pluginManager); + } + + @Singleton + @Provides + public LocalBluetoothManager provideLocalBluetoothController(Context context, + @Named(BG_HANDLER_NAME) Handler bgHandler) { + return LocalBluetoothManager.create(context, bgHandler, + UserHandle.ALL); + } + + @Singleton + @Provides + public VolumeDialogController provideVolumeDialogController(Context context) { + return new VolumeDialogControllerImpl(context); + + } + + @Singleton + @Provides + public MetricsLogger provideMetricsLogger() { + return new MetricsLogger(); + + } + + @Singleton + @Provides + public AccessibilityManagerWrapper provideAccessibilityManagerWrapper(Context context) { + return new AccessibilityManagerWrapper(context); + + } + + @Singleton + @Provides + // Creating a new instance will trigger color extraction. + // Thankfully this only happens once - during boot - and WallpaperManagerService + // loads colors from cache. + public SysuiColorExtractor provideSysuiColorExtractor(Context context) { + return new SysuiColorExtractor(context); + + } + + @Singleton + @Provides + public TunablePadding.TunablePaddingService provideTunablePaddingService() { + return new TunablePaddingService(); + + } + + @Singleton + @Provides + public ForegroundServiceController provideForegroundService(Context context) { + return new ForegroundServiceControllerImpl(context); + + } + + @Singleton + @Provides + public UiOffloadThread provideUiOffloadThread() { + Log.d("TestTest", "provideUiOffloadThread"); + return new UiOffloadThread(); + } + + @Singleton + @Provides + public PowerUI.WarningsUI provideWarningsUi(Context context) { + return new PowerNotificationWarnings(context); + } + + @Singleton + @Provides + public IconLogger provideIconLogger(Context context, @Named(BG_LOOPER_NAME) Looper bgLooper, + MetricsLogger logger) { + return new IconLoggerImpl(context, bgLooper, logger); + } + + @Singleton + @Provides + public LightBarController provideLightBarController(Context context) { + return new LightBarController(context); + } + + @Singleton + @Provides + public IWindowManager provideIWindowManager() { + return WindowManagerGlobal.getWindowManagerService(); + } + + @Singleton + @Provides + public OverviewProxyService provideOverviewProxyService(Context context) { + return new OverviewProxyService(context); + } + + @Singleton + @Provides + public VibratorHelper provideVibratorHelper(Context context) { + return new VibratorHelper(context); + + } + + @Singleton + @Provides + public IStatusBarService provideIStatusBarService() { + return IStatusBarService.Stub.asInterface( + ServiceManager.getService(Context.STATUS_BAR_SERVICE)); + } + + @Singleton + @Provides + // Single instance of DisplayMetrics, gets updated by StatusBar, but can be used +// anywhere it is needed. + public DisplayMetrics provideDisplayMetrics() { + return new DisplayMetrics(); + + } + + @Singleton + @Provides + public LockscreenGestureLogger provideLockscreenGestureLogger() { + return new LockscreenGestureLogger(); + } + + @Singleton + @Provides + public ShadeController provideShadeController(Context context) { + return SysUiServiceProvider.getComponent(context, StatusBar.class); + } + + @Singleton + @Provides + public NotificationRemoteInputManager.Callback provideNotificationRemoteInputManager( + Context context) { + return new StatusBarRemoteInputCallback(context); + + } + + @Singleton + @Provides + public AppOpsController provideAppOpsController(Context context, + @Named(BG_LOOPER_NAME) Looper bgLooper) { + return new AppOpsControllerImpl(context, bgLooper); + + } + + @Singleton + @Provides + public DisplayNavigationBarController provideDisplayNavigationBarController(Context context, + @Named(MAIN_HANDLER_NAME) Handler mainHandler) { + return new DisplayNavigationBarController(context, mainHandler); + } + + @Singleton + @Provides + public SensorPrivacyManager provideSensorPrivacyManager(Context context) { + return context.getSystemService(SensorPrivacyManager.class); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/SystemUI.java b/packages/SystemUI/src/com/android/systemui/SystemUI.java index 30fbef6cbefb..78a12467cd1c 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUI.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUI.java @@ -24,6 +24,7 @@ import android.os.Bundle; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.Map; +import java.util.function.Function; public abstract class SystemUI implements SysUiServiceProvider { public Context mContext; @@ -61,4 +62,7 @@ public abstract class SystemUI implements SysUiServiceProvider { n.addExtras(extras); } + + public interface Injector extends Function<Context, SystemUI> { + } } diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java index 92aa652131ba..1d8a21d13fa2 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java @@ -170,7 +170,11 @@ public class SystemUIApplication extends Application implements SysUiServiceProv Class cls; try { cls = Class.forName(clsName); - mServices[i] = (SystemUI) cls.newInstance(); + Object o = cls.newInstance(); + if (o instanceof SystemUI.Injector) { + o = ((SystemUI.Injector) o).apply(this); + } + mServices[i] = (SystemUI) o; } catch(ClassNotFoundException ex){ throw new RuntimeException(ex); } catch (IllegalAccessException ex) { diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java index 867c9175d308..9bc91eef52d0 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java @@ -16,9 +16,11 @@ package com.android.systemui; +import static com.android.systemui.Dependency.LEAK_REPORT_EMAIL_NAME; + +import android.annotation.Nullable; import android.app.AlarmManager; import android.content.Context; -import android.util.ArrayMap; import android.util.Log; import android.view.ViewGroup; @@ -26,56 +28,54 @@ import com.android.internal.colorextraction.ColorExtractor.GradientColors; import com.android.internal.util.function.TriConsumer; import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.ViewMediatorCallback; -import com.android.systemui.Dependency.DependencyProvider; -import com.android.systemui.bubbles.BubbleController; +import com.android.systemui.assist.AssistManager; import com.android.systemui.classifier.FalsingManager; import com.android.systemui.keyguard.DismissCallbackRegistry; +import com.android.systemui.power.EnhancedEstimates; +import com.android.systemui.power.EnhancedEstimatesImpl; import com.android.systemui.qs.QSTileHost; -import com.android.systemui.statusbar.AmbientPulseManager; import com.android.systemui.statusbar.KeyguardIndicationController; import com.android.systemui.statusbar.NotificationListener; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationLockscreenUserManagerImpl; -import com.android.systemui.statusbar.NotificationMediaManager; -import com.android.systemui.statusbar.NotificationRemoteInputManager; -import com.android.systemui.statusbar.NotificationViewHierarchyManager; import com.android.systemui.statusbar.ScrimView; -import com.android.systemui.statusbar.SmartReplyController; -import com.android.systemui.statusbar.StatusBarStateController; +import com.android.systemui.statusbar.notification.NotificationData; import com.android.systemui.statusbar.notification.NotificationEntryManager; -import com.android.systemui.statusbar.notification.VisualStabilityManager; -import com.android.systemui.statusbar.notification.logging.NotificationLogger; -import com.android.systemui.statusbar.notification.row.NotificationBlockingHelperManager; -import com.android.systemui.statusbar.notification.row.NotificationGutsManager; import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.statusbar.phone.KeyguardBouncer; -import com.android.systemui.statusbar.phone.KeyguardDismissUtil; +import com.android.systemui.statusbar.phone.KeyguardEnvironmentImpl; import com.android.systemui.statusbar.phone.LockIcon; import com.android.systemui.statusbar.phone.LockscreenWallpaper; -import com.android.systemui.statusbar.phone.NotificationGroupAlertTransferHelper; -import com.android.systemui.statusbar.phone.NotificationGroupManager; import com.android.systemui.statusbar.phone.NotificationIconAreaController; import com.android.systemui.statusbar.phone.ScrimController; import com.android.systemui.statusbar.phone.ScrimState; import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.phone.StatusBarIconController; import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; -import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler; -import com.android.systemui.statusbar.policy.SmartReplyConstants; +import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.volume.VolumeDialogComponent; import java.util.function.Consumer; +import javax.inject.Named; +import javax.inject.Singleton; + +import dagger.Component; +import dagger.Module; +import dagger.Provides; + /** * Class factory to provide customizable SystemUI components. */ +@Module public class SystemUIFactory { private static final String TAG = "SystemUIFactory"; static SystemUIFactory mFactory; + private SystemUIRootComponent mRootComponent; - public static SystemUIFactory getInstance() { - return mFactory; + public static <T extends SystemUIFactory> T getInstance() { + return (T) mFactory; } public static void createFromConfig(Context context) { @@ -88,6 +88,7 @@ public class SystemUIFactory { Class<?> cls = null; cls = context.getClassLoader().loadClass(clsName); mFactory = (SystemUIFactory) cls.newInstance(); + mFactory.init(context); } catch (Throwable t) { Log.w(TAG, "Error creating SystemUIFactory component: " + clsName, t); throw new RuntimeException(t); @@ -96,6 +97,18 @@ public class SystemUIFactory { public SystemUIFactory() {} + protected void init(Context context) { + mRootComponent = DaggerSystemUIFactory_SystemUIRootComponent.builder() + .systemUIFactory(this) + .dependencyProvider(new com.android.systemui.DependencyProvider()) + .contextHolder(new ContextHolder(context)) + .build(); + } + + public SystemUIRootComponent getRootComponent() { + return mRootComponent; + } + public StatusBarKeyguardViewManager createStatusBarKeyguardViewManager(Context context, ViewMediatorCallback viewMediatorCallback, LockPatternUtils lockPatternUtils) { return new StatusBarKeyguardViewManager(context, viewMediatorCallback, lockPatternUtils); @@ -137,33 +150,70 @@ public class SystemUIFactory { return new VolumeDialogComponent(systemUi, context); } - public void injectDependencies(ArrayMap<Object, DependencyProvider> providers, + @Singleton + @Provides + public NotificationData.KeyguardEnvironment provideKeyguardEnvironment(Context context) { + return new KeyguardEnvironmentImpl(); + } + + @Singleton + @Provides + public NotificationLockscreenUserManager provideNotificationLockscreenUserManager( Context context) { - providers.put(StatusBarStateController.class, StatusBarStateController::new); - providers.put(NotificationLockscreenUserManager.class, - () -> new NotificationLockscreenUserManagerImpl(context)); - providers.put(VisualStabilityManager.class, VisualStabilityManager::new); - providers.put(NotificationGroupManager.class, NotificationGroupManager::new); - providers.put(NotificationGroupAlertTransferHelper.class, - NotificationGroupAlertTransferHelper::new); - providers.put(NotificationMediaManager.class, () -> new NotificationMediaManager(context)); - providers.put(NotificationGutsManager.class, () -> new NotificationGutsManager(context)); - providers.put(AmbientPulseManager.class, () -> new AmbientPulseManager(context)); - providers.put(NotificationBlockingHelperManager.class, - () -> new NotificationBlockingHelperManager(context)); - providers.put(NotificationRemoteInputManager.class, - () -> new NotificationRemoteInputManager(context)); - providers.put(SmartReplyConstants.class, - () -> new SmartReplyConstants(Dependency.get(Dependency.MAIN_HANDLER), context)); - providers.put(NotificationListener.class, () -> new NotificationListener(context)); - providers.put(NotificationLogger.class, NotificationLogger::new); - providers.put(NotificationViewHierarchyManager.class, - () -> new NotificationViewHierarchyManager(context)); - providers.put(NotificationEntryManager.class, () -> new NotificationEntryManager(context)); - providers.put(KeyguardDismissUtil.class, KeyguardDismissUtil::new); - providers.put(SmartReplyController.class, () -> new SmartReplyController()); - providers.put(RemoteInputQuickSettingsDisabler.class, - () -> new RemoteInputQuickSettingsDisabler(context)); - providers.put(BubbleController.class, () -> new BubbleController(context)); + return new NotificationLockscreenUserManagerImpl(context); + } + + @Singleton + @Provides + public AssistManager provideAssistManager(DeviceProvisionedController controller, + Context context) { + return new AssistManager(controller, context); + } + + @Singleton + @Provides + public NotificationEntryManager provideNotificationEntryManager(Context context) { + return new NotificationEntryManager(context); + } + + @Singleton + @Provides + public EnhancedEstimates provideEnhancedEstimates(Context context) { + return new EnhancedEstimatesImpl(); + } + + @Singleton + @Provides + @Named(LEAK_REPORT_EMAIL_NAME) + @Nullable + public String provideLeakReportEmail() { + return null; + } + + @Singleton + @Provides + public NotificationListener provideNotificationListener(Context context) { + return new NotificationListener(context); + } + + @Module + protected static class ContextHolder { + private Context mContext; + + public ContextHolder(Context context) { + mContext = context; + } + + @Provides + public Context provideContext() { + return mContext; + } + } + + @Singleton + @Component(modules = {SystemUIFactory.class, DependencyProvider.class, ContextHolder.class}) + public interface SystemUIRootComponent { + @Singleton + Dependency.DependencyInjector createDependency(); } } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java index 1e91ef3f10c7..c8595ebb5fca 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java @@ -42,12 +42,16 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.Map; +import javax.inject.Inject; +import javax.inject.Singleton; + /** * Bubbles are a special type of content that can "float" on top of other apps or System UI. * Bubbles can be expanded to show more content. * * The controller manages addition, removal, and visible state of bubbles on screen. */ +@Singleton public class BubbleController { private static final int MAX_BUBBLES = 5; // TODO: actually enforce this @@ -117,6 +121,7 @@ public class BubbleController { void onBubbleExpandChanged(boolean isExpanding, float amount); } + @Inject public BubbleController(Context context) { mContext = context; WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java index 4fb1bc59a23f..974cd8804841 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java @@ -35,7 +35,7 @@ public class DozeLog { private static final int SIZE = Build.IS_DEBUGGABLE ? 400 : 50; static final SimpleDateFormat FORMAT = new SimpleDateFormat("MM-dd HH:mm:ss.SSS"); - private static final int REASONS = 8; + private static final int REASONS = 7; public static final int PULSE_REASON_NONE = -1; public static final int PULSE_REASON_INTENT = 0; @@ -182,7 +182,7 @@ public class DozeLog { */ public static void traceLockScreenWakeUp(boolean wake) { if (!ENABLED) return; - log("wakeLockScreenWakeUp " + wake); + log("wakeLockScreen " + wake); } /** @@ -191,7 +191,7 @@ public class DozeLog { */ public static void traceWakeDisplay(boolean wake) { if (!ENABLED) return; - log("wakeLockScreenWakeUp " + wake); + log("wakeDisplay " + wake); } public static void traceProximityResult(Context context, boolean near, long millis, diff --git a/packages/SystemUI/src/com/android/systemui/plugins/PluginEnablerImpl.java b/packages/SystemUI/src/com/android/systemui/plugins/PluginEnablerImpl.java index e2417f7b6397..63374150adaa 100644 --- a/packages/SystemUI/src/com/android/systemui/plugins/PluginEnablerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/plugins/PluginEnablerImpl.java @@ -16,28 +16,44 @@ package com.android.systemui.plugins; import android.content.ComponentName; import android.content.Context; +import android.content.SharedPreferences; import android.content.pm.PackageManager; import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.shared.plugins.PluginEnabler; public class PluginEnablerImpl implements PluginEnabler { + private static final String CRASH_DISABLED_PLUGINS_PREF_FILE = "auto_disabled_plugins_prefs"; - final private PackageManager mPm; + private PackageManager mPm; + private final SharedPreferences mAutoDisabledPrefs; public PluginEnablerImpl(Context context) { - this(context.getPackageManager()); + this(context, context.getPackageManager()); } - @VisibleForTesting public PluginEnablerImpl(PackageManager pm) { + @VisibleForTesting public PluginEnablerImpl(Context context, PackageManager pm) { + mAutoDisabledPrefs = context.getSharedPreferences( + CRASH_DISABLED_PLUGINS_PREF_FILE, Context.MODE_PRIVATE); mPm = pm; } @Override - public void setEnabled(ComponentName component, boolean enabled) { + public void setEnabled(ComponentName component) { + setDisabled(component, ENABLED); + } + + @Override + public void setDisabled(ComponentName component, @DisableReason int reason) { + boolean enabled = reason == ENABLED; final int desiredState = enabled ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED : PackageManager.COMPONENT_ENABLED_STATE_DISABLED; mPm.setComponentEnabledSetting(component, desiredState, PackageManager.DONT_KILL_APP); + if (enabled) { + mAutoDisabledPrefs.edit().remove(component.flattenToString()).apply(); + } else { + mAutoDisabledPrefs.edit().putInt(component.flattenToString(), reason).apply(); + } } @Override @@ -45,4 +61,12 @@ public class PluginEnablerImpl implements PluginEnabler { return mPm.getComponentEnabledSetting(component) != PackageManager.COMPONENT_ENABLED_STATE_DISABLED; } + + @Override + public @DisableReason int getDisableReason(ComponentName componentName) { + if (isEnabled(componentName)) { + return ENABLED; + } + return mAutoDisabledPrefs.getInt(componentName.flattenToString(), DISABLED_MANUALLY); + } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java index 89066651084d..466c8082f0b9 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java @@ -183,6 +183,7 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener public void updateState(Tile tile) { mTile.setIcon(tile.getIcon()); mTile.setLabel(tile.getLabel()); + mTile.setSubtitle(tile.getSubtitle()); mTile.setContentDescription(tile.getContentDescription()); mTile.setState(tile.getState()); } @@ -322,6 +323,14 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener return null; }; state.label = mTile.getLabel(); + + CharSequence subtitle = mTile.getSubtitle(); + if (subtitle != null && subtitle.length() > 0) { + state.secondaryLabel = subtitle; + } else { + state.secondaryLabel = null; + } + if (mTile.getContentDescription() != null) { state.contentDescription = mTile.getContentDescription(); } else { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/AmbientPulseManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/AmbientPulseManager.java index 8821679aadf1..9bfd4ee24ff0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/AmbientPulseManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/AmbientPulseManager.java @@ -28,17 +28,22 @@ import com.android.systemui.R; import com.android.systemui.statusbar.notification.NotificationData; import com.android.systemui.statusbar.notification.row.NotificationInflater.InflationFlag; +import javax.inject.Inject; +import javax.inject.Singleton; + /** * Manager which handles high priority notifications that should "pulse" in when the device is * dozing and/or in AOD. The pulse uses the notification's ambient view and pops in briefly * before automatically dismissing the alert. */ +@Singleton public class AmbientPulseManager extends AlertingNotificationManager { protected final ArraySet<OnAmbientChangedListener> mListeners = new ArraySet<>(); @VisibleForTesting protected long mExtensionTime; + @Inject public AmbientPulseManager(@NonNull final Context context) { Resources resources = context.getResources(); mAutoDismissNotificationDecay = resources.getInteger(R.integer.ambient_notification_decay); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java index f0455481b353..7d80860fca85 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java @@ -61,10 +61,14 @@ import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; +import javax.inject.Inject; +import javax.inject.Singleton; + /** * Handles tasks and state related to media notifications. For example, there is a 'current' media * notification, which this class keeps track of. */ +@Singleton public class NotificationMediaManager implements Dumpable { private static final String TAG = "NotificationMediaManager"; public static final boolean DEBUG_MEDIA = false; @@ -157,6 +161,7 @@ public class NotificationMediaManager implements Dumpable { return mEntryManager; } + @Inject public NotificationMediaManager(Context context) { mContext = context; mMediaSessionManager diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java index 9391737fe23c..d1556fb33f92 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java @@ -59,12 +59,16 @@ import java.io.PrintWriter; import java.util.ArrayList; import java.util.Set; +import javax.inject.Inject; +import javax.inject.Singleton; + /** * Class for handling remote input state over a set of notifications. This class handles things * like keeping notifications temporarily that were cancelled as a response to a remote input * interaction, keeping track of notifications to remove when NotificationPresenter is collapsed, * and handling clicks on remote views. */ +@Singleton public class NotificationRemoteInputManager implements Dumpable { public static final boolean ENABLE_REMOTE_INPUT = SystemProperties.getBoolean("debug.enable_remote_input", true); @@ -229,6 +233,7 @@ public class NotificationRemoteInputManager implements Dumpable { return mShadeController; } + @Inject public NotificationRemoteInputManager(Context context) { mContext = context; mBarService = IStatusBarService.Stub.asInterface( diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java index 0702f1b9f1fe..25247470bd88 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java @@ -41,6 +41,9 @@ import java.util.HashMap; import java.util.List; import java.util.Stack; +import javax.inject.Inject; +import javax.inject.Singleton; + /** * NotificationViewHierarchyManager manages updating the view hierarchy of notification views based * on their group structure. For example, if a notification becomes bundled with another, @@ -48,6 +51,7 @@ import java.util.Stack; * tell NotificationListContainer which notifications to display, and inform it of changes to those * notifications that might affect their display. */ +@Singleton public class NotificationViewHierarchyManager { private static final String TAG = "NotificationViewHierarchyManager"; @@ -123,6 +127,7 @@ public class NotificationViewHierarchyManager { return mShadeController; } + @Inject public NotificationViewHierarchyManager(Context context) { Resources res = context.getResources(); mAlwaysExpandNonGroupedNotification = diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SmartReplyController.java b/packages/SystemUI/src/com/android/systemui/statusbar/SmartReplyController.java index f5d6904a1543..6f1548d2fb5f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/SmartReplyController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/SmartReplyController.java @@ -27,10 +27,14 @@ import com.android.systemui.statusbar.notification.NotificationEntryManager; import java.util.Set; +import javax.inject.Inject; +import javax.inject.Singleton; + /** * Handles when smart replies are added to a notification * and clicked upon. */ +@Singleton public class SmartReplyController { private IStatusBarService mBarService; private Set<String> mSendingKeys = new ArraySet<>(); @@ -38,7 +42,7 @@ public class SmartReplyController { private final NotificationEntryManager mEntryManager = Dependency.get(NotificationEntryManager.class); - + @Inject public SmartReplyController() { mBarService = Dependency.get(IStatusBarService.class); } @@ -88,10 +92,14 @@ public class SmartReplyController { return mSendingKeys.contains(key); } - public void smartRepliesAdded(final NotificationData.Entry entry, int replyCount) { + /** + * Smart Replies and Actions have been added to the UI. + */ + public void smartSuggestionsAdded(final NotificationData.Entry entry, int replyCount, + int actionCount, boolean generatedByAssistant) { try { - mBarService.onNotificationSmartRepliesAdded(entry.notification.getKey(), - replyCount); + mBarService.onNotificationSmartSuggestionsAdded( + entry.notification.getKey(), replyCount, actionCount, generatedByAssistant); } catch (RemoteException e) { // Nothing to do, system going down } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateController.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateController.java index 3f84416ad575..087b65592b7a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateController.java @@ -35,9 +35,13 @@ import java.lang.annotation.Retention; import java.util.ArrayList; import java.util.Comparator; +import javax.inject.Inject; +import javax.inject.Singleton; + /** * Tracks and reports on {@link StatusBarState}. */ +@Singleton public class StatusBarStateController implements CallbackController<StateListener> { private static final String TAG = "SbStateController"; @@ -101,6 +105,10 @@ public class StatusBarStateController implements CallbackController<StateListene public static final int RANK_STACK_SCROLLER = 2; public static final int RANK_SHELF = 3; + @Inject + public StatusBarStateController() { + } + public int getState() { return mState; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java new file mode 100644 index 000000000000..49f1a8d6f248 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.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.android.systemui.statusbar.notification; + +import android.app.Notification; +import android.os.SystemClock; +import android.service.notification.StatusBarNotification; +import android.util.Log; +import android.view.View; + +import com.android.systemui.DejankUtils; +import com.android.systemui.bubbles.BubbleController; +import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; +import com.android.systemui.statusbar.phone.ShadeController; + +/** + * Click handler for generic clicks on notifications. Clicks on specific areas (expansion caret, + * app ops icon, etc) are handled elsewhere. + */ +public final class NotificationClicker implements View.OnClickListener { + private static final String TAG = "NotificationClicker"; + + private final ShadeController mShadeController; + private final BubbleController mBubbleController; + private final NotificationActivityStarter mNotificationActivityStarter; + + public NotificationClicker(ShadeController shadeController, + BubbleController bubbleController, + NotificationActivityStarter notificationActivityStarter) { + mShadeController = shadeController; + mBubbleController = bubbleController; + mNotificationActivityStarter = notificationActivityStarter; + } + + @Override + public void onClick(final View v) { + if (!(v instanceof ExpandableNotificationRow)) { + Log.e(TAG, "NotificationClicker called on a view that is not a notification row."); + return; + } + + mShadeController.wakeUpIfDozing(SystemClock.uptimeMillis(), v); + + final ExpandableNotificationRow row = (ExpandableNotificationRow) v; + final StatusBarNotification sbn = row.getStatusBarNotification(); + if (sbn == null) { + Log.e(TAG, "NotificationClicker called on an unclickable notification,"); + return; + } + + // Check if the notification is displaying the menu, if so slide notification back + if (isMenuVisible(row)) { + row.animateTranslateNotification(0); + return; + } else if (row.isChildInGroup() && isMenuVisible(row.getNotificationParent())) { + row.getNotificationParent().animateTranslateNotification(0); + return; + } else if (row.isSummaryWithChildren() && row.areChildrenExpanded()) { + // We never want to open the app directly if the user clicks in between + // the notifications. + return; + } + + // Mark notification for one frame. + row.setJustClicked(true); + DejankUtils.postAfterTraversal(() -> row.setJustClicked(false)); + + // If it was a bubble we should close it + if (row.getEntry().isBubble()) { + mBubbleController.collapseStack(); + } + + mNotificationActivityStarter.onNotificationClicked(sbn, row); + } + + private boolean isMenuVisible(ExpandableNotificationRow row) { + return row.getProvider() != null && row.getProvider().isMenuVisible(); + } + + /** + * Attaches the click listener to the row if appropriate. + */ + public void register(ExpandableNotificationRow row, StatusBarNotification sbn) { + Notification notification = sbn.getNotification(); + if (notification.contentIntent != null || notification.fullScreenIntent != null) { + row.setOnClickListener(this); + } else { + row.setOnClickListener(null); + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java index aab3fc4c7a0e..d2a5864f9f33 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java @@ -15,6 +15,7 @@ */ package com.android.systemui.statusbar.notification; +import static com.android.internal.util.Preconditions.checkNotNull; import static com.android.systemui.bubbles.BubbleController.DEBUG_DEMOTE_TO_NOTIF; import static com.android.systemui.statusbar.NotificationRemoteInputManager.ENABLE_REMOTE_INPUT; import static com.android.systemui.statusbar.NotificationRemoteInputManager.FORCE_REMOTE_INPUT_HISTORY; @@ -36,7 +37,6 @@ import android.os.Handler; import android.os.PowerManager; import android.os.RemoteException; import android.os.ServiceManager; -import android.os.SystemClock; import android.os.UserHandle; import android.provider.Settings; import android.service.dreams.DreamService; @@ -49,7 +49,6 @@ import android.util.ArrayMap; import android.util.ArraySet; import android.util.EventLog; import android.util.Log; -import android.view.View; import android.view.ViewGroup; import com.android.internal.annotations.VisibleForTesting; @@ -57,7 +56,6 @@ import com.android.internal.logging.MetricsLogger; import com.android.internal.statusbar.IStatusBarService; import com.android.internal.statusbar.NotificationVisibility; import com.android.internal.util.NotificationMessagingUtil; -import com.android.systemui.DejankUtils; import com.android.systemui.Dependency; import com.android.systemui.Dumpable; import com.android.systemui.EventLogTags; @@ -115,7 +113,6 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater. private final NotificationMessagingUtil mMessagingUtil; protected final Context mContext; protected final HashMap<String, NotificationData.Entry> mPendingNotifications = new HashMap<>(); - private final NotificationClicker mNotificationClicker = new NotificationClicker(); private final NotificationGroupManager mGroupManager = Dependency.get(NotificationGroupManager.class); @@ -146,7 +143,6 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater. protected IStatusBarService mBarService; private NotificationPresenter mPresenter; private Callback mCallback; - private NotificationActivityStarter mNotificationActivityStarter; protected PowerManager mPowerManager; private NotificationListenerService.RankingMap mLatestRankingMap; protected HeadsUpManager mHeadsUpManager; @@ -161,63 +157,7 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater. private ExpandableNotificationRow.OnAppOpsClickListener mOnAppOpsClickListener; private NotificationViewHierarchyManager.StatusBarStateListener mStatusBarStateListener; @Nullable private AlertTransferListener mAlertTransferListener; - - private final class NotificationClicker implements View.OnClickListener { - - @Override - public void onClick(final View v) { - if (!(v instanceof ExpandableNotificationRow)) { - Log.e(TAG, "NotificationClicker called on a view that is not a notification row."); - return; - } - - getShadeController().wakeUpIfDozing(SystemClock.uptimeMillis(), v); - - final ExpandableNotificationRow row = (ExpandableNotificationRow) v; - final StatusBarNotification sbn = row.getStatusBarNotification(); - if (sbn == null) { - Log.e(TAG, "NotificationClicker called on an unclickable notification,"); - return; - } - - // Check if the notification is displaying the menu, if so slide notification back - if (isMenuVisible(row)) { - row.animateTranslateNotification(0); - return; - } else if (row.isChildInGroup() && isMenuVisible(row.getNotificationParent())) { - row.getNotificationParent().animateTranslateNotification(0); - return; - } else if (row.isSummaryWithChildren() && row.areChildrenExpanded()) { - // We never want to open the app directly if the user clicks in between - // the notifications. - return; - } - - // Mark notification for one frame. - row.setJustClicked(true); - DejankUtils.postAfterTraversal(() -> row.setJustClicked(false)); - - // If it was a bubble we should close it - if (row.getEntry().isBubble()) { - mBubbleController.collapseStack(); - } - - mNotificationActivityStarter.onNotificationClicked(sbn, row); - } - - private boolean isMenuVisible(ExpandableNotificationRow row) { - return row.getProvider() != null && row.getProvider().isMenuVisible(); - } - - public void register(ExpandableNotificationRow row, StatusBarNotification sbn) { - Notification notification = sbn.getNotification(); - if (notification.contentIntent != null || notification.fullScreenIntent != null) { - row.setOnClickListener(this); - } else { - row.setOnClickListener(null); - } - } - } + @Nullable private NotificationClicker mNotificationClicker; private final DeviceProvisionedController.DeviceProvisionedListener mDeviceProvisionedListener = @@ -269,6 +209,10 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater. mAlertTransferListener = listener; } + public void setNotificationClicker(NotificationClicker clicker) { + mNotificationClicker = clicker; + } + /** * Our dependencies can have cyclic references, so some need to be lazy */ @@ -355,11 +299,6 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater. mOnAppOpsClickListener = mGutsManager::openGuts; } - public void setNotificationActivityStarter( - NotificationActivityStarter notificationActivityStarter) { - mNotificationActivityStarter = notificationActivityStarter; - } - public NotificationData getNotificationData() { return mNotificationData; } @@ -757,7 +696,7 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater. row.setIsLowPriority(isLowPriority); row.setLowPriorityStateUpdated(isUpdate && (wasLowPriority != isLowPriority)); // bind the click event to the content area - mNotificationClicker.register(row, sbn); + checkNotNull(mNotificationClicker).register(row, sbn); // Extract target SDK version. try { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/VisualStabilityManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/VisualStabilityManager.java index fce79800193d..abb7b4161984 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/VisualStabilityManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/VisualStabilityManager.java @@ -25,10 +25,14 @@ import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener; import java.util.ArrayList; +import javax.inject.Inject; +import javax.inject.Singleton; + /** * A manager that ensures that notifications are visually stable. It will suppress reorderings * and reorder at the right time when they are out of view. */ +@Singleton public class VisualStabilityManager implements OnHeadsUpChangedListener { private final ArrayList<Callback> mCallbacks = new ArrayList<>(); @@ -42,6 +46,10 @@ public class VisualStabilityManager implements OnHeadsUpChangedListener { private ArraySet<View> mAddedChildren = new ArraySet<>(); private boolean mPulsing; + @Inject + public VisualStabilityManager() { + } + /** * Add a callback to invoke when reordering is allowed again. * @param callback diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java index 9f02e543b6e3..eb1fc30843b3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java @@ -40,10 +40,14 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import javax.inject.Inject; +import javax.inject.Singleton; + /** * Handles notification logging, in particular, logging which notifications are visible and which * are not. */ +@Singleton public class NotificationLogger implements StateListener { private static final String TAG = "NotificationLogger"; @@ -145,6 +149,7 @@ public class NotificationLogger implements StateListener { } }; + @Inject public NotificationLogger() { mBarService = IStatusBarService.Stub.asInterface( ServiceManager.getService(Context.STATUS_BAR_SERVICE)); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java index 1d79152bb1cc..8b0a6828205d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java @@ -800,7 +800,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView } @Override - public void performRemoveAnimation(long duration, long delay, + public long performRemoveAnimation(long duration, long delay, float translationDirection, boolean isHeadsUpAnimation, float endLocation, Runnable onFinishedRunnable, AnimatorListenerAdapter animationListener) { enableAppearDrawing(true); @@ -812,6 +812,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView } else if (onFinishedRunnable != null) { onFinishedRunnable.run(); } + return 0; } @Override 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 91d08fffa642..a58c7cde32a7 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 @@ -2612,6 +2612,29 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } @Override + public long performRemoveAnimation(long duration, long delay, float translationDirection, + boolean isHeadsUpAnimation, float endLocation, Runnable onFinishedRunnable, + AnimatorListenerAdapter animationListener) { + if (mMenuRow.isMenuVisible()) { + Animator anim = getTranslateViewAnimator(0f, null /* listener */); + if (anim != null) { + anim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + ExpandableNotificationRow.super.performRemoveAnimation( + duration, delay, translationDirection, isHeadsUpAnimation, + endLocation, onFinishedRunnable, animationListener); + } + }); + anim.start(); + return anim.getDuration(); + } + } + return super.performRemoveAnimation(duration, delay, translationDirection, + isHeadsUpAnimation, endLocation, onFinishedRunnable, animationListener); + } + + @Override protected void onAppearAnimationFinished(boolean wasAppearing) { super.onAppearAnimationFinished(wasAppearing); if (wasAppearing) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java index b1fa6a531438..d1a89b4e493f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java @@ -312,16 +312,19 @@ public abstract class ExpandableView extends FrameLayout implements Dumpable { * @param duration The duration of the remove animation. * @param delay The delay of the animation * @param translationDirection The direction value from [-1 ... 1] indicating in which the - * animation should be performed. A value of -1 means that The - * remove animation should be performed upwards, - * such that the child appears to be going away to the top. 1 - * Should mean the opposite. + * animation should be performed. A value of -1 means that The + * remove animation should be performed upwards, + * such that the child appears to be going away to the top. 1 + * Should mean the opposite. * @param isHeadsUpAnimation Is this a headsUp animation. * @param endLocation The location where the horizonal heads up disappear animation should end. * @param onFinishedRunnable A runnable which should be run when the animation is finished. * @param animationListener An animation listener to add to the animation. + * + * @return The additional delay, in milliseconds, that this view needs to add before the + * animation starts. */ - public abstract void performRemoveAnimation(long duration, + public abstract long performRemoveAnimation(long duration, long delay, float translationDirection, boolean isHeadsUpAnimation, float endLocation, Runnable onFinishedRunnable, AnimatorListenerAdapter animationListener); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManager.java index 16796dd86e18..607d96dcbd3f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManager.java @@ -34,10 +34,14 @@ import java.util.Collections; import java.util.HashSet; import java.util.Set; +import javax.inject.Inject; +import javax.inject.Singleton; + /** * Manager for the notification blocking helper - tracks and helps create the blocking helper * affordance. */ +@Singleton public class NotificationBlockingHelperManager { /** Enables debug logging and always makes the blocking helper show up after a dismiss. */ private static final boolean DEBUG = false; @@ -54,6 +58,7 @@ public class NotificationBlockingHelperManager { */ private boolean mIsShadeExpanded; + @Inject public NotificationBlockingHelperManager(Context context) { mContext = context; mNonBlockablePkgs = new HashSet<>(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java index 6bc39c82f8bf..02a310c29379 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java @@ -1474,9 +1474,19 @@ public class NotificationContentView extends FrameLayout { if (mExpandedChild != null) { mExpandedSmartReplyView = applySmartReplyView(mExpandedChild, smartRepliesAndActions, entry); - if (mExpandedSmartReplyView != null && smartRepliesAndActions.smartReplies != null) { - mSmartReplyController.smartRepliesAdded( - entry, smartRepliesAndActions.smartReplies.choices.length); + if (mExpandedSmartReplyView != null) { + if (smartRepliesAndActions.smartReplies != null + || smartRepliesAndActions.smartActions != null) { + int numSmartReplies = smartRepliesAndActions.smartReplies == null + ? 0 : smartRepliesAndActions.smartReplies.choices.length; + int numSmartActions = smartRepliesAndActions.smartActions == null + ? 0 : smartRepliesAndActions.smartActions.actions.size(); + boolean fromAssistant = smartRepliesAndActions.smartReplies == null + ? smartRepliesAndActions.smartActions.fromAssistant + : smartRepliesAndActions.smartReplies.fromAssistant; + mSmartReplyController.smartSuggestionsAdded(entry, numSmartReplies, + numSmartActions, fromAssistant); + } } } if (mHeadsUpChild != null) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java index 2e4552701456..ac4e5830d76b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java @@ -57,10 +57,14 @@ import com.android.systemui.statusbar.policy.DeviceProvisionedController; import java.io.FileDescriptor; import java.io.PrintWriter; +import javax.inject.Inject; +import javax.inject.Singleton; + /** * Handles various NotificationGuts related tasks, such as binding guts to a row, opening and * closing guts, and keeping track of the currently exposed notification guts. */ +@Singleton public class NotificationGutsManager implements Dumpable, NotificationLifetimeExtender { private static final String TAG = "NotificationGutsManager"; @@ -91,6 +95,7 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx @VisibleForTesting protected String mKeyToRemoveOnGutsClosed; + @Inject public NotificationGutsManager(Context context) { mContext = context; mAccessibilityManager = (AccessibilityManager) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java index 1b40c06a5867..eaa2eaf21927 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java @@ -190,12 +190,13 @@ public abstract class StackScrollerDecorView extends ExpandableView { } @Override - public void performRemoveAnimation(long duration, long delay, + public long performRemoveAnimation(long duration, long delay, float translationDirection, boolean isHeadsUpAnimation, float endLocation, Runnable onFinishedRunnable, AnimatorListenerAdapter animationListener) { // TODO: Use duration setContentVisible(false); + return 0; } @Override 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 dbe6e8ec764d..67a5cd934a6b 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 @@ -16,10 +16,8 @@ package com.android.systemui.statusbar.notification.stack; -import static com.android.systemui.statusbar.notification.ActivityLaunchAnimator - .ExpandAnimationParameters; -import static com.android.systemui.statusbar.notification.stack.StackStateAnimator - .ANIMATION_DURATION_SWIPE; +import static com.android.systemui.statusbar.notification.ActivityLaunchAnimator.ExpandAnimationParameters; +import static com.android.systemui.statusbar.notification.stack.StackStateAnimator.ANIMATION_DURATION_SWIPE; import static com.android.systemui.statusbar.phone.NotificationIconAreaController.LOW_PRIORITY; import android.animation.Animator; @@ -651,6 +649,8 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd < mSections[NUM_SECTIONS - 1].getCurrentBounds().bottom || mAmbientState.isDark())) { drawBackground(canvas); + } else if (mInHeadsUpPinnedMode || mHeadsUpAnimatingAway) { + drawHeadsUpBackground(canvas); } if (DEBUG) { @@ -749,6 +749,32 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd mCornerRadius, mCornerRadius, mBackgroundPaint); } + private void drawHeadsUpBackground(Canvas canvas) { + int left = mSidePaddings; + int right = getWidth() - mSidePaddings; + + float top = getHeight(); + float bottom = 0; + int childCount = getChildCount(); + for (int i = 0; i < childCount; i++) { + View child = getChildAt(i); + if (child.getVisibility() != View.GONE + && child instanceof ExpandableNotificationRow) { + ExpandableNotificationRow row = (ExpandableNotificationRow) child; + if ((row.isPinned() || row.isHeadsUpAnimatingAway()) && row.getTranslation() < 0) { + top = Math.min(top, row.getTranslationY()); + bottom = Math.max(bottom, row.getTranslationY() + row.getActualHeight()); + } + } + } + + if (top < bottom) { + canvas.drawRoundRect( + left, top, right, bottom, + mCornerRadius, mCornerRadius, mBackgroundPaint); + } + } + @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) private void updateBackgroundDimming() { // No need to update the background color if it's not being drawn. @@ -2157,19 +2183,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd } return; } - NotificationSection firstSection = getFirstVisibleSection(); - int top = 0; - if (firstSection != null) { - ActivatableNotificationView firstView = firstSection.getFirstVisibleChild(); - // Round Y up to avoid seeing the background during animation - int finalTranslationY = (int) Math.ceil(ViewState.getFinalTranslationY(firstView)); - if (mAnimateNextBackgroundTop || firstSection.isTargetTop(finalTranslationY)) { - // we're ending up at the same location as we are now, lets just skip the animation - top = finalTranslationY; - } else { - top = (int) Math.ceil(firstView.getTranslationY()); - } - } + int top = getSectionTopOrFinalTop(getFirstVisibleSection(), mAnimateNextBackgroundTop); NotificationSection lastSection = getLastVisibleSection(); ActivatableNotificationView lastView = mShelf.hasItemsInStableShelf() && mShelf.getVisibility() != GONE @@ -2177,21 +2191,8 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd : lastSection == null ? null : lastSection.getLastVisibleChild(); int bottom; if (lastView != null) { - int finalTranslationY; - if (lastView == mShelf) { - finalTranslationY = (int) mShelf.getTranslationY(); - } else { - finalTranslationY = (int) ViewState.getFinalTranslationY(lastView); - } - int finalHeight = ExpandableViewState.getFinalActualHeight(lastView); - int finalBottom = finalTranslationY + finalHeight - lastView.getClipBottomAmount(); - if (mAnimateNextBackgroundBottom || lastSection.isTargetBottom(finalBottom)) { - // we're ending up at the same location as we are now, lets just skip the animation - bottom = finalBottom; - } else { - bottom = (int) (lastView.getTranslationY() + lastView.getActualHeight() - - lastView.getClipBottomAmount()); - } + bottom = getSectionBottomOrFinalBottom( + lastSection, lastView, mAnimateNextBackgroundBottom); } else { top = mTopPadding; bottom = top; @@ -2207,6 +2208,57 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd setSectionBoundsByPriority(left, right, top, bottom, mSections[0], mSections[1]); } + private int getSectionTopOrFinalTop( + @Nullable NotificationSection section, boolean alreadyAnimating) { + int top = 0; + if (section != null) { + ActivatableNotificationView firstView = section.getFirstVisibleChild(); + // Round Y up to avoid seeing the background during animation + int finalTranslationY = (int) Math.ceil(ViewState.getFinalTranslationY(firstView)); + if (alreadyAnimating || section.isTargetTop(finalTranslationY)) { + // we're ending up at the same location as we are now, let's just skip the animation + top = finalTranslationY; + } else { + top = (int) Math.ceil(firstView.getTranslationY()); + } + } + return top; + } + + private int getSectionBottomOrFinalBottom( + @Nullable NotificationSection section, boolean alreadyAnimating) { + return section == null ? 0 + : getSectionBottomOrFinalBottom( + section, section.getLastVisibleChild(), alreadyAnimating); + } + + private int getSectionBottomOrFinalBottom( + NotificationSection section, + ActivatableNotificationView lastView, + boolean alreadyAnimating) { + int bottom = 0; + if (lastView != null) { + float finalTranslationY; + if (lastView == mShelf) { + finalTranslationY = mShelf.getTranslationY(); + } else { + finalTranslationY = ViewState.getFinalTranslationY(lastView); + } + int finalHeight = ExpandableViewState.getFinalActualHeight(lastView); + // Round Y down to avoid seeing the background during animation + int finalBottom = (int) Math.floor( + finalTranslationY + finalHeight - lastView.getClipBottomAmount()); + if (alreadyAnimating || section.isTargetBottom(finalBottom)) { + // we're ending up at the same location as we are now, lets just skip the animation + bottom = finalBottom; + } else { + bottom = (int) (lastView.getTranslationY() + lastView.getActualHeight() + - lastView.getClipBottomAmount()); + } + } + return bottom; + } + private void setSectionBoundsByPriority(int left, int right, int top, int bottom, NotificationSection highPrioritySection, NotificationSection lowPrioritySection) { if (NotificationUtils.useNewInterruptionModel(mContext)) { @@ -2214,13 +2266,14 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd ActivatableNotificationView lastChildAboveGap = getLastHighPriorityChild(); ActivatableNotificationView firstChildBelowGap = getFirstLowPriorityChild(); if (lastChildAboveGap != null && firstChildBelowGap != null) { - int gapTop = - (int) Math.max(top, - Math.min(lastChildAboveGap.getTranslationY() - + lastChildAboveGap.getActualHeight(), - bottom)); - int gapBottom = (int) Math.max(top, - Math.min(firstChildBelowGap.getTranslationY(), bottom)); + int gapTop = getSectionBottomOrFinalBottom( + highPrioritySection, mAnimateNextSectionBoundsChange); + gapTop = Math.max(top, Math.min(gapTop, bottom)); + + int gapBottom = getSectionTopOrFinalTop( + lowPrioritySection, mAnimateNextSectionBoundsChange); + gapBottom = Math.max(top, Math.min(gapBottom, bottom)); + highPrioritySection.getBounds().set(left, top, right, gapTop); lowPrioritySection.getBounds().set(left, gapBottom, right, bottom); } else if (lastChildAboveGap != null) { @@ -5574,15 +5627,21 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd if (translatingParentView != null && row == translatingParentView) { mSwipeHelper.clearExposedMenuView(); mSwipeHelper.clearTranslatingParentView(); + if (row instanceof ExpandableNotificationRow) { + mHeadsUpManager.setMenuShown( + ((ExpandableNotificationRow) row).getEntry(), false); + + } } } @Override public void onMenuShown(View row) { if (row instanceof ExpandableNotificationRow) { + ExpandableNotificationRow notificationRow = (ExpandableNotificationRow) row; MetricsLogger.action(mContext, MetricsEvent.ACTION_REVEAL_GEAR, - ((ExpandableNotificationRow) row).getStatusBarNotification() - .getPackageName()); + notificationRow.getStatusBarNotification().getPackageName()); + mHeadsUpManager.setMenuShown(notificationRow.getEntry(), true); } mSwipeHelper.onMenuShown(row); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java index d6905478a043..19fce485b0c7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java @@ -406,13 +406,8 @@ public class StackStateAnimator { } changingView.performRemoveAnimation(ANIMATION_DURATION_APPEAR_DISAPPEAR, - 0 /* delay */, translationDirection, false /* isHeadsUpAppear */, - 0, new Runnable() { - @Override - public void run() { - removeTransientView(changingView); - } - }, null); + 0 /* delay */, translationDirection, false /* isHeadsUpAppear */, + 0, () -> removeTransientView(changingView), null); } else if (event.animationType == NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT) { if (Math.abs(changingView.getTranslation()) == changingView.getWidth() @@ -507,10 +502,11 @@ public class StackStateAnimator { // We need to add the global animation listener, since once no animations are // running anymore, the panel will instantly hide itself. We need to wait until // the animation is fully finished for this though. - changingView.performRemoveAnimation(ANIMATION_DURATION_HEADS_UP_DISAPPEAR - + ANIMATION_DELAY_HEADS_UP, extraDelay, 0.0f, - true /* isHeadsUpAppear */, targetLocation, endRunnable, - getGlobalAnimationFinishedListener()); + long removeAnimationDelay = changingView.performRemoveAnimation( + ANIMATION_DURATION_HEADS_UP_DISAPPEAR + ANIMATION_DELAY_HEADS_UP, + extraDelay, 0.0f, true /* isHeadsUpAppear */, targetLocation, + endRunnable, getGlobalAnimationFinishedListener()); + mAnimationProperties.delay += removeAnimationDelay; } else if (endRunnable != null) { endRunnable.run(); } 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 aa0b7b656e1c..f4cfd4197637 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java @@ -264,6 +264,17 @@ public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable, } } + /** + * Sets whether an entry's menu row is exposed and therefore it should stick in the heads up + * area if it's pinned until it's hidden again. + */ + public void setMenuShown(@NonNull NotificationData.Entry entry, boolean menuShown) { + HeadsUpEntry headsUpEntry = getHeadsUpEntry(entry.key); + if (headsUpEntry instanceof HeadsUpEntryPhone && entry.isRowPinned()) { + ((HeadsUpEntryPhone) headsUpEntry).setMenuShownPinned(menuShown); + } + } + /////////////////////////////////////////////////////////////////////////////////////////////// // HeadsUpManager public methods overrides: @@ -469,6 +480,14 @@ public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable, // HeadsUpEntryPhone: protected class HeadsUpEntryPhone extends HeadsUpManager.HeadsUpEntry { + + private boolean mMenuShownPinned; + + @Override + protected boolean isSticky() { + return super.isSticky() || mMenuShownPinned; + } + public void setEntry(@NonNull final NotificationData.Entry entry) { Runnable removeHeadsUpRunnable = () -> { if (!mVisualStabilityManager.isReorderingAllowed()) { @@ -510,6 +529,25 @@ public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable, updateEntry(false /* updatePostTime */); } } + + public void setMenuShownPinned(boolean menuShownPinned) { + if (mMenuShownPinned == menuShownPinned) { + return; + } + + mMenuShownPinned = menuShownPinned; + if (menuShownPinned) { + removeAutoRemovalCallbacks(); + } else { + updateEntry(false /* updatePostTime */); + } + } + + @Override + public void reset() { + super.reset(); + mMenuShownPinned = false; + } } public interface AnimationStateHandler { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardDismissUtil.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardDismissUtil.java index b3d0bf8abf62..e541e14b3d91 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardDismissUtil.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardDismissUtil.java @@ -20,15 +20,23 @@ import android.util.Log; import com.android.systemui.plugins.ActivityStarter.OnDismissAction; +import javax.inject.Inject; +import javax.inject.Singleton; + /** * Executes actions that require the screen to be unlocked. Delegates the actual handling to an * implementation passed via {@link #setDismissHandler}. */ +@Singleton public class KeyguardDismissUtil implements KeyguardDismissHandler { private static final String TAG = "KeyguardDismissUtil"; private volatile KeyguardDismissHandler mDismissHandler; + @Inject + public KeyguardDismissUtil() { + } + /** Sets the actual {@link DismissHandler} implementation. */ public void setDismissHandler(KeyguardDismissHandler dismissHandler) { mDismissHandler = dismissHandler; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java index 67b077e729df..7c30e48f171a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java @@ -76,11 +76,14 @@ public class LightBarController implements BatteryController.BatteryStateChangeC private final Rect mLastDockedBounds = new Rect(); private boolean mQsCustomizing; + private final Context mContext; + public LightBarController(Context ctx) { mDarkModeColor = Color.valueOf(ctx.getColor(R.color.dark_mode_icon_color_single_tone)); mStatusBarIconController = Dependency.get(DarkIconDispatcher.class); mBatteryController = Dependency.get(BatteryController.class); mBatteryController.addCallback(this); + mContext = ctx; } public void setNavigationBar(LightBarTransitionsController navigationBar) { @@ -217,8 +220,9 @@ public class LightBarController implements BatteryController.BatteryStateChangeC private void updateNavigation() { if (mNavigationBarController != null) { - mNavigationBarController.setIconsDark( - mNavigationLight, animateChange()); + if (!NavBarTintController.isEnabled(mContext)) { + mNavigationBarController.setIconsDark(mNavigationLight, animateChange()); + } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java index 57cc7d6c1ecb..7876aa5d89d0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java @@ -16,12 +16,16 @@ package com.android.systemui.statusbar.phone; +import static com.android.systemui.statusbar.phone.NavBarTintController.MIN_COLOR_ADAPT_TRANSITION_TIME; +import static com.android.systemui.statusbar.phone.NavBarTintController.NAV_COLOR_TRANSITION_TIME_SETTING; + import android.animation.ValueAnimator; import android.content.Context; import android.os.Bundle; import android.os.Handler; import android.os.SystemClock; import android.util.MathUtils; +import android.provider.Settings; import android.util.TimeUtils; import com.android.systemui.Dependency; @@ -42,13 +46,14 @@ import java.io.PrintWriter; public class LightBarTransitionsController implements Dumpable, Callbacks, StatusBarStateController.StateListener { - public static final long DEFAULT_TINT_ANIMATION_DURATION = 120; + public static final int DEFAULT_TINT_ANIMATION_DURATION = 120; private static final String EXTRA_DARK_INTENSITY = "dark_intensity"; private final Handler mHandler; private final DarkIntensityApplier mApplier; private final KeyguardMonitor mKeyguardMonitor; private final StatusBarStateController mStatusBarStateController; + private NavBarTintController mColorAdaptionController; private boolean mTransitionDeferring; private long mTransitionDeferringStartTime; @@ -67,6 +72,8 @@ public class LightBarTransitionsController implements Dumpable, Callbacks, } }; + private final Context mContext; + public LightBarTransitionsController(Context context, DarkIntensityApplier applier) { mApplier = applier; mHandler = new Handler(); @@ -76,6 +83,7 @@ public class LightBarTransitionsController implements Dumpable, Callbacks, .addCallback(this); mStatusBarStateController.addCallback(this); mDozeAmount = mStatusBarStateController.getDozeAmount(); + mContext = context; } public void destroy(Context context) { @@ -106,7 +114,7 @@ public class LightBarTransitionsController implements Dumpable, Callbacks, public void appTransitionCancelled() { if (mTransitionPending && mTintChangePending) { mTintChangePending = false; - animateIconTint(mPendingDarkIntensity, 0 /* delay */, DEFAULT_TINT_ANIMATION_DURATION); + animateIconTint(mPendingDarkIntensity, 0 /* delay */, getTintAnimationDuration()); } mTransitionPending = false; } @@ -146,8 +154,17 @@ public class LightBarTransitionsController implements Dumpable, Callbacks, Math.max(0, mTransitionDeferringStartTime - SystemClock.uptimeMillis()), mTransitionDeferringDuration); } else { - animateIconTint(dark ? 1.0f : 0.0f, 0 /* delay */, DEFAULT_TINT_ANIMATION_DURATION); + animateIconTint(dark ? 1.0f : 0.0f, 0 /* delay */, getTintAnimationDuration()); + } + } + + public long getTintAnimationDuration() { + if (NavBarTintController.isEnabled(mContext)) { + return Math.max(Settings.Global.getInt(mContext.getContentResolver(), + NAV_COLOR_TRANSITION_TIME_SETTING, DEFAULT_TINT_ANIMATION_DURATION), + MIN_COLOR_ADAPT_TRANSITION_TIME); } + return DEFAULT_TINT_ANIMATION_DURATION; } public float getCurrentDarkIntensity() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavBarTintController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavBarTintController.java new file mode 100644 index 000000000000..9ecee1825f07 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavBarTintController.java @@ -0,0 +1,208 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.phone; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Bitmap.Config; +import android.graphics.Color; +import android.graphics.Rect; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; +import android.provider.Settings; +import android.util.DisplayMetrics; +import android.view.SurfaceControl; + +public class NavBarTintController { + public static final String NAV_COLOR_TRANSITION_TIME_SETTING = "navbar_color_adapt_transition"; + public static final int MIN_COLOR_ADAPT_TRANSITION_TIME = 400; + + private final HandlerThread mColorAdaptHandlerThread = new HandlerThread("ColorExtractThread"); + private Handler mColorAdaptionHandler; + + // Poll time for each iteration to color sample + private static final int COLOR_ADAPTION_TIMEOUT = 300; + + // Passing the threshold of this luminance value will make the button black otherwise white + private static final float LUMINANCE_THRESHOLD = 0.3f; + + // The home button's icon is actually smaller than the button's size, the percentage will + // cut into the button's size to determine the icon size + private static final float PERCENTAGE_BUTTON_PADDING = 0.3f; + + // The distance from the home button to color sample around + private static final int COLOR_SAMPLE_MARGIN = 20; + + private boolean mRunning; + + private final NavigationBarView mNavigationBarView; + private final LightBarTransitionsController mLightBarController; + private final Handler mMainHandler = new Handler(Looper.getMainLooper()); + + public NavBarTintController(NavigationBarView navigationBarView, + LightBarTransitionsController lightBarController) { + mNavigationBarView = navigationBarView; + mLightBarController = lightBarController; + } + + public void start() { + if (!isEnabled(mNavigationBarView.getContext())) { + return; + } + if (mColorAdaptionHandler == null) { + mColorAdaptHandlerThread.start(); + mColorAdaptionHandler = new Handler(mColorAdaptHandlerThread.getLooper()); + } + mColorAdaptionHandler.removeCallbacksAndMessages(null); + mColorAdaptionHandler.post(this::updateTint); + mRunning = true; + } + + public void end() { + if (mColorAdaptionHandler != null) { + mColorAdaptionHandler.removeCallbacksAndMessages(null); + } + mRunning = false; + } + + public void stop() { + end(); + if (mColorAdaptionHandler != null) { + mColorAdaptHandlerThread.quitSafely(); + } + } + + private void updateTint() { + int[] navPos = new int[2]; + int[] butPos = new int[2]; + if (mNavigationBarView.getHomeButton().getCurrentView() == null) { + return; + } + + // Determine the area of the home icon in the larger home button + mNavigationBarView.getHomeButton().getCurrentView().getLocationInSurface(butPos); + final int navWidth = mNavigationBarView.getHomeButton().getCurrentView().getWidth(); + final int navHeight = mNavigationBarView.getHomeButton().getCurrentView().getHeight(); + final int xPadding = (int) (PERCENTAGE_BUTTON_PADDING * navWidth); + final int yPadding = (int) (PERCENTAGE_BUTTON_PADDING * navHeight); + final Rect homeButtonRect = new Rect(butPos[0] + xPadding, butPos[1] + yPadding, + navWidth + butPos[0] - xPadding, navHeight + butPos[1] - yPadding); + if (mNavigationBarView.getCurrentView() == null || homeButtonRect.isEmpty()) { + scheduleColorAdaption(); + return; + } + mNavigationBarView.getCurrentView().getLocationOnScreen(navPos); + homeButtonRect.offset(navPos[0], navPos[1]); + + // Apply a margin area around the button region to sample the colors, crop from screenshot + final Rect cropRect = new Rect(homeButtonRect); + cropRect.inset(-COLOR_SAMPLE_MARGIN, -COLOR_SAMPLE_MARGIN); + if (cropRect.isEmpty()) { + scheduleColorAdaption(); + return; + } + + // Determine the size of the home area + Rect homeArea = new Rect(COLOR_SAMPLE_MARGIN, COLOR_SAMPLE_MARGIN, + homeButtonRect.width() + COLOR_SAMPLE_MARGIN, + homeButtonRect.height() + COLOR_SAMPLE_MARGIN); + + // Get the screenshot around the home button icon to determine the color + DisplayMetrics mDisplayMetrics = new DisplayMetrics(); + mNavigationBarView.getContext().getDisplay().getRealMetrics(mDisplayMetrics); + final Bitmap hardBitmap = SurfaceControl + .screenshot(new Rect(), mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels, + mNavigationBarView.getContext().getDisplay().getRotation()); + if (hardBitmap != null && cropRect.bottom <= hardBitmap.getHeight()) { + final Bitmap cropBitmap = Bitmap.createBitmap(hardBitmap, cropRect.left, cropRect.top, + cropRect.width(), cropRect.height()); + final Bitmap softBitmap = cropBitmap.copy(Config.ARGB_8888, false); + + // Get the luminance value to determine if the home button should be black or white + final int[] pixels = new int[softBitmap.getByteCount() / 4]; + softBitmap.getPixels(pixels, 0, softBitmap.getWidth(), 0, 0, softBitmap.getWidth(), + softBitmap.getHeight()); + float r = 0, g = 0, blue = 0; + + int width = cropRect.width(); + int total = 0; + for (int i = 0; i < pixels.length; i += 4) { + int x = i % width; + int y = i / width; + if (!homeArea.contains(x, y)) { + r += Color.red(pixels[i]); + g += Color.green(pixels[i]); + blue += Color.blue(pixels[i]); + total++; + } + } + + r /= total; + g /= total; + blue /= total; + + r = Math.max(Math.min(r / 255f, 1), 0); + g = Math.max(Math.min(g / 255f, 1), 0); + blue = Math.max(Math.min(blue / 255f, 1), 0); + + if (r <= 0.03928) { + r /= 12.92; + } else { + r = (float) Math.pow((r + 0.055) / 1.055, 2.4); + } + if (g <= 0.03928) { + g /= 12.92; + } else { + g = (float) Math.pow((g + 0.055) / 1.055, 2.4); + } + if (blue <= 0.03928) { + blue /= 12.92; + } else { + blue = (float) Math.pow((blue + 0.055) / 1.055, 2.4); + } + + if (r * 0.2126 + g * 0.7152 + blue * 0.0722 > LUMINANCE_THRESHOLD) { + // Black + mMainHandler.post( + () -> mLightBarController + .setIconsDark(true /* dark */, true /* animate */)); + } else { + // White + mMainHandler.post( + () -> mLightBarController + .setIconsDark(false /* dark */, true /* animate */)); + } + cropBitmap.recycle(); + hardBitmap.recycle(); + } + scheduleColorAdaption(); + } + + private void scheduleColorAdaption() { + mColorAdaptionHandler.removeCallbacksAndMessages(null); + if (!mRunning || !isEnabled(mNavigationBarView.getContext())) { + return; + } + mColorAdaptionHandler.postDelayed(this::updateTint, COLOR_ADAPTION_TIMEOUT); + } + + public static boolean isEnabled(Context context) { + return Settings.Global.getInt(context.getContentResolver(), + NavigationPrototypeController.NAV_COLOR_ADAPT_ENABLE_SETTING, 0) == 1; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java index ae0a1452905d..55655d5b6240 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java @@ -851,6 +851,16 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback if (Intent.ACTION_SCREEN_OFF.equals(action) || Intent.ACTION_SCREEN_ON.equals(action)) { notifyNavigationBarScreenOn(); + + if (Intent.ACTION_SCREEN_ON.equals(action)) { + // Enabled and screen is on, start it again if enabled + if (NavBarTintController.isEnabled(getContext())) { + mNavigationBarView.getColorAdaptionController().start(); + } + } else { + // Screen off disable it + mNavigationBarView.getColorAdaptionController().end(); + } } if (Intent.ACTION_USER_SWITCHED.equals(action)) { // The accessibility settings may be different for the new user 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 30e840926698..6a7983af862d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java @@ -149,6 +149,7 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav private RecentsOnboarding mRecentsOnboarding; private NotificationPanelView mPanelView; + private NavBarTintController mColorAdaptionController; private NavigationPrototypeController mPrototypeController; private NavigationGestureAction[] mDefaultGestureMap; private QuickScrubAction mQuickScrubAction; @@ -277,6 +278,15 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav public void onBackButtonVisibilityChanged(boolean visible) { getBackButton().setVisibility(visible ? VISIBLE : GONE); } + + @Override + public void onColorAdaptChanged(boolean enabled) { + if (enabled) { + mColorAdaptionController.start(); + } else { + mColorAdaptionController.end(); + } + } }; public NavigationBarView(Context context, AttributeSet attrs) { @@ -334,6 +344,11 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav mPrototypeController = new NavigationPrototypeController(mHandler, mContext); mPrototypeController.register(); mPrototypeController.setOnPrototypeChangedListener(mPrototypeListener); + mColorAdaptionController = new NavBarTintController(this, getLightTransitionsController()); + } + + public NavBarTintController getColorAdaptionController() { + return mColorAdaptionController; } public BarTransitions getBarTransitions() { @@ -1097,6 +1112,7 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav Dependency.get(PluginManager.class).addPluginListener(this, NavGesture.class, false /* Only one */); setUpSwipeUpOnboarding(isQuickStepSwipeUpEnabled()); + mColorAdaptionController.start(); } @Override @@ -1107,6 +1123,7 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav mGestureHelper.destroy(); } mPrototypeController.unregister(); + mColorAdaptionController.stop(); setUpSwipeUpOnboarding(false); for (int i = 0; i < mButtonDispatchers.size(); ++i) { mButtonDispatchers.valueAt(i).onDestroy(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationPrototypeController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationPrototypeController.java index b11b6d472713..40ac79376b06 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationPrototypeController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationPrototypeController.java @@ -37,6 +37,7 @@ public class NavigationPrototypeController extends ContentObserver { static final String NAVBAR_EXPERIMENTS_DISABLED = "navbarexperiments_disabled"; private final String GESTURE_MATCH_SETTING = "quickstepcontroller_gesture_match_map"; + public static final String NAV_COLOR_ADAPT_ENABLE_SETTING = "navbar_color_adapt_enable"; @Retention(RetentionPolicy.SOURCE) @IntDef({ACTION_DEFAULT, ACTION_QUICKSTEP, ACTION_QUICKSCRUB, ACTION_BACK}) @@ -73,6 +74,7 @@ public class NavigationPrototypeController extends ContentObserver { public void register() { registerObserver(HIDE_BACK_BUTTON_SETTING); registerObserver(GESTURE_MATCH_SETTING); + registerObserver(NAV_COLOR_ADAPT_ENABLE_SETTING); } /** @@ -96,6 +98,9 @@ public class NavigationPrototypeController extends ContentObserver { } else if (path.endsWith(HIDE_BACK_BUTTON_SETTING)) { mListener.onBackButtonVisibilityChanged( !getGlobalBool(HIDE_BACK_BUTTON_SETTING)); + } else if (path.endsWith(NAV_COLOR_ADAPT_ENABLE_SETTING)) { + mListener.onColorAdaptChanged( + NavBarTintController.isEnabled(mContext)); } } catch (SettingNotFoundException e) { e.printStackTrace(); @@ -138,5 +143,6 @@ public class NavigationPrototypeController extends ContentObserver { public interface OnPrototypeChangedListener { void onGestureRemap(@GestureAction int[] actions); void onBackButtonVisibilityChanged(boolean visible); + void onColorAdaptChanged(boolean enabled); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java index dd81c4e20d6d..6732bbeba493 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java @@ -42,11 +42,15 @@ import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener; import java.util.ArrayList; import java.util.Objects; +import javax.inject.Inject; +import javax.inject.Singleton; + /** * A helper class dealing with the alert interactions between {@link NotificationGroupManager}, * {@link HeadsUpManager}, {@link AmbientPulseManager}. In particular, this class deals with keeping * the correct notification in a group alerting based off the group suppression. */ +@Singleton public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedListener, OnAmbientChangedListener, StateListener { @@ -73,6 +77,7 @@ public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedLis private boolean mIsDozing; + @Inject public NotificationGroupAlertTransferHelper() { Dependency.get(StatusBarStateController.class).addCallback(this); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java index 8f4369a98b17..3c1c0765a3b8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java @@ -39,9 +39,13 @@ import java.util.HashMap; import java.util.Map; import java.util.Objects; +import javax.inject.Inject; +import javax.inject.Singleton; + /** * A class to handle notifications and their corresponding groups. */ +@Singleton public class NotificationGroupManager implements OnHeadsUpChangedListener, OnAmbientChangedListener, StateListener { @@ -54,6 +58,7 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener, private AmbientPulseManager mAmbientPulseManager = Dependency.get(AmbientPulseManager.class); private boolean mIsUpdatingUnchangedGroup; + @Inject public NotificationGroupManager() { Dependency.get(StatusBarStateController.class).addCallback(this); } 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 6caea1d7a8c7..1e709125c7ba 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -187,6 +187,7 @@ import com.android.systemui.statusbar.StatusBarStateController; import com.android.systemui.statusbar.VibratorHelper; import com.android.systemui.statusbar.notification.ActivityLaunchAnimator; import com.android.systemui.statusbar.notification.NotificationActivityStarter; +import com.android.systemui.statusbar.notification.NotificationClicker; import com.android.systemui.statusbar.notification.NotificationData; import com.android.systemui.statusbar.notification.NotificationData.Entry; import com.android.systemui.statusbar.notification.NotificationEntryManager; @@ -630,8 +631,6 @@ public class StatusBar extends SystemUI implements DemoMode, mBubbleController = Dependency.get(BubbleController.class); mBubbleController.setExpandListener(mBubbleExpandListener); - mGroupAlertTransferHelper.bind(mEntryManager, mGroupManager); - mColorExtractor.addOnColorsChangedListener(this); mStatusBarStateController.addCallback(this, StatusBarStateController.RANK_STATUS_BAR); @@ -1018,7 +1017,7 @@ public class StatusBar extends SystemUI implements DemoMode, return new QSFragment(); } - protected void setUpPresenter() { + private void setUpPresenter() { // Set up the initial notification state. mActivityLaunchAnimator = new ActivityLaunchAnimator( mStatusBarWindow, this, mNotificationPanel, @@ -1036,7 +1035,10 @@ public class StatusBar extends SystemUI implements DemoMode, mNotificationActivityStarter = new StatusBarNotificationActivityStarter( mContext, mNotificationPanel, mPresenter, mHeadsUpManager, mActivityLaunchAnimator); mGutsManager.setNotificationActivityStarter(mNotificationActivityStarter); - mEntryManager.setNotificationActivityStarter(mNotificationActivityStarter); + + mGroupAlertTransferHelper.bind(mEntryManager, mGroupManager); + mEntryManager.setNotificationClicker(new NotificationClicker( + this, Dependency.get(BubbleController.class), mNotificationActivityStarter)); } /** @@ -2230,6 +2232,11 @@ public class StatusBar extends SystemUI implements DemoMode, mNavigationBar.getBarTransitions().setAutoDim(false); } mHandler.removeCallbacks(mAutoDim); + + // Do not dim the navigation buttons if the its tint is controlled by the bar's background + if (NavBarTintController.isEnabled(mContext)) { + return; + } if (mState != StatusBarState.KEYGUARD && mState != StatusBarState.SHADE_LOCKED) { mHandler.postDelayed(mAutoDim, AUTOHIDE_TIMEOUT_MS); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java index e72806438f54..a02c9d51fecf 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java @@ -16,8 +16,7 @@ package com.android.systemui.statusbar.policy; -import static com.android.systemui.statusbar.notification.row.NotificationInflater - .FLAG_CONTENT_VIEW_HEADS_UP; +import static com.android.systemui.statusbar.notification.row.NotificationInflater.FLAG_CONTENT_VIEW_HEADS_UP; import android.annotation.NonNull; import android.annotation.Nullable; @@ -32,7 +31,6 @@ import com.android.internal.logging.MetricsLogger; import com.android.systemui.R; import com.android.systemui.statusbar.AlertingNotificationManager; import com.android.systemui.statusbar.notification.NotificationData; -import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.NotificationInflater.InflationFlag; import java.io.FileDescriptor; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisabler.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisabler.java index c2933e1516be..2a10db620096 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisabler.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisabler.java @@ -27,9 +27,13 @@ import com.android.systemui.qs.QSFragment; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.phone.StatusBar; +import javax.inject.Inject; +import javax.inject.Singleton; + /** * Let {@link RemoteInputView} to control the visibility of QuickSetting. */ +@Singleton public class RemoteInputQuickSettingsDisabler implements ConfigurationController.ConfigurationListener { @@ -39,6 +43,7 @@ public class RemoteInputQuickSettingsDisabler private int mLastOrientation; @VisibleForTesting CommandQueue mCommandQueue; + @Inject public RemoteInputQuickSettingsDisabler(Context context) { mContext = context; mCommandQueue = SysUiServiceProvider.getComponent(context, CommandQueue.class); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyConstants.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyConstants.java index 71d6e5421b75..6193159ec0a5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyConstants.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyConstants.java @@ -16,6 +16,8 @@ package com.android.systemui.statusbar.policy; +import static com.android.systemui.Dependency.MAIN_HANDLER_NAME; + import android.content.Context; import android.content.res.Resources; import android.database.ContentObserver; @@ -27,6 +29,11 @@ import android.util.Log; import com.android.systemui.R; +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Singleton; + +@Singleton public final class SmartReplyConstants extends ContentObserver { private static final String TAG = "SmartReplyConstants"; @@ -47,7 +54,8 @@ public final class SmartReplyConstants extends ContentObserver { private final Context mContext; private final KeyValueListParser mParser = new KeyValueListParser(','); - public SmartReplyConstants(Handler handler, Context context) { + @Inject + public SmartReplyConstants(@Named(MAIN_HANDLER_NAME) Handler handler, Context context) { super(handler); mContext = context; diff --git a/packages/SystemUI/src/com/android/systemui/tuner/PluginFragment.java b/packages/SystemUI/src/com/android/systemui/tuner/PluginFragment.java index dae1472c42af..0a29e04ce20f 100644 --- a/packages/SystemUI/src/com/android/systemui/tuner/PluginFragment.java +++ b/packages/SystemUI/src/com/android/systemui/tuner/PluginFragment.java @@ -184,7 +184,11 @@ public class PluginFragment extends PreferenceFragment { mInfo.services[i].name); if (mPluginEnabler.isEnabled(componentName) != isEnabled) { - mPluginEnabler.setEnabled(componentName, isEnabled); + if (isEnabled) { + mPluginEnabler.setEnabled(componentName); + } else { + mPluginEnabler.setDisabled(componentName, PluginEnabler.DISABLED_MANUALLY); + } shouldSendBroadcast = true; } } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java index 7ca54231fe7b..fb2ceac4b810 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java @@ -35,6 +35,7 @@ import android.testing.AndroidTestingRunner; import android.testing.TestableLooper.RunWithLooper; import android.text.TextPaint; import android.view.LayoutInflater; +import android.widget.FrameLayout; import android.widget.TextClock; import com.android.systemui.SysuiTestCase; @@ -51,10 +52,14 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; @SmallTest -@RunWithLooper @RunWith(AndroidTestingRunner.class) +// Need to run on the main thread because KeyguardSliceView$Row init checks for +// the main thread before acquiring a wake lock. This class is constructed when +// the keyguard_clcok_switch layout is inflated. +@RunWithLooper(setAsMainLooper = true) public class KeyguardClockSwitchTest extends SysuiTestCase { private PluginManager mPluginManager; + private FrameLayout mClockContainer; @Mock TextClock mClockView; @@ -67,6 +72,7 @@ public class KeyguardClockSwitchTest extends SysuiTestCase { LayoutInflater layoutInflater = LayoutInflater.from(getContext()); mKeyguardClockSwitch = (KeyguardClockSwitch) layoutInflater.inflate(R.layout.keyguard_clock_switch, null); + mClockContainer = mKeyguardClockSwitch.findViewById(R.id.clock_view); MockitoAnnotations.initMocks(this); when(mClockView.getPaint()).thenReturn(mock(TextPaint.class)); } @@ -97,7 +103,7 @@ public class KeyguardClockSwitchTest extends SysuiTestCase { listener.onPluginConnected(plugin, null); verify(mClockView).setVisibility(GONE); - assertThat(plugin.getView().getParent()).isEqualTo(mKeyguardClockSwitch); + assertThat(plugin.getView().getParent()).isEqualTo(mClockContainer); } @Test @@ -120,7 +126,7 @@ public class KeyguardClockSwitchTest extends SysuiTestCase { when(plugin2.getView()).thenReturn(new TextClock(getContext())); listener.onPluginConnected(plugin2, null); // THEN only the view from the second plugin should be a child of KeyguardClockSwitch. - assertThat(plugin2.getView().getParent()).isEqualTo(mKeyguardClockSwitch); + assertThat(plugin2.getView().getParent()).isEqualTo(mClockContainer); assertThat(plugin1.getView().getParent()).isNull(); } @@ -161,7 +167,7 @@ public class KeyguardClockSwitchTest extends SysuiTestCase { // WHEN the first plugin is disconnected listener.onPluginDisconnected(plugin1); // THEN the view from the second plugin is still a child of KeyguardClockSwitch. - assertThat(plugin2.getView().getParent()).isEqualTo(mKeyguardClockSwitch); + assertThat(plugin2.getView().getParent()).isEqualTo(mClockContainer); assertThat(plugin1.getView().getParent()).isNull(); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/TestableDependency.java b/packages/SystemUI/tests/src/com/android/systemui/TestableDependency.java index 0c8d137569f8..18bf75e3cfb1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/TestableDependency.java +++ b/packages/SystemUI/tests/src/com/android/systemui/TestableDependency.java @@ -29,9 +29,10 @@ public class TestableDependency extends Dependency { mComponents = ((SysuiTestableContext) context).getComponents(); } mContext = context; - if (SystemUIFactory.getInstance() == null) { - SystemUIFactory.createFromConfig(context); - } + SystemUIFactory.createFromConfig(context); + SystemUIFactory.getInstance().getRootComponent() + .createDependency() + .createSystemUI(this); start(); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceManagerTest.java index 5cc3b3c7823b..458377017fb9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceManagerTest.java @@ -17,6 +17,7 @@ package com.android.systemui.shared.plugins; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertNotNull; import static junit.framework.Assert.assertTrue; + import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.eq; @@ -26,22 +27,6 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; -import com.android.systemui.SysuiTestCase; -import com.android.systemui.plugins.Plugin; -import com.android.systemui.plugins.PluginEnablerImpl; -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; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; -import org.mockito.Mockito; - import android.app.Activity; import android.app.NotificationManager; import android.content.BroadcastReceiver; @@ -60,6 +45,21 @@ import android.support.test.annotation.UiThreadTest; import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.SmallTest; +import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; +import com.android.systemui.SysuiTestCase; +import com.android.systemui.plugins.Plugin; +import com.android.systemui.plugins.PluginListener; +import com.android.systemui.plugins.annotations.Requires; +import com.android.systemui.shared.plugins.PluginInstanceManager.PluginInfo; +import com.android.systemui.shared.plugins.VersionInfo.InvalidVersionException; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mockito; + import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -79,6 +79,9 @@ public class PluginInstanceManagerTest extends SysuiTestCase { private PluginInstanceManager mPluginInstanceManager; private PluginManagerImpl mMockManager; private VersionInfo mMockVersionInfo; + private PluginEnabler mMockEnabler; + ComponentName mTestPluginComponentName = + new ComponentName(WHITELISTED_PACKAGE, TestPlugin.class.getName()); @Before public void setup() throws Exception { @@ -88,9 +91,9 @@ public class PluginInstanceManagerTest extends SysuiTestCase { mMockPm = mock(PackageManager.class); mMockListener = mock(PluginListener.class); mMockManager = mock(PluginManagerImpl.class); - when(mMockManager.getClassLoader(any(), any())) - .thenReturn(getClass().getClassLoader()); - when(mMockManager.getPluginEnabler()).thenReturn(new PluginEnablerImpl(mMockPm)); + when(mMockManager.getClassLoader(any(), any())).thenReturn(getClass().getClassLoader()); + mMockEnabler = mock(PluginEnabler.class); + when(mMockManager.getPluginEnabler()).thenReturn(mMockEnabler); mMockVersionInfo = mock(VersionInfo.class); mPluginInstanceManager = new PluginInstanceManager(mContextWrapper, mMockPm, "myAction", mMockListener, true, mHandlerThread.getLooper(), mMockVersionInfo, @@ -230,18 +233,13 @@ public class PluginInstanceManagerTest extends SysuiTestCase { // Start with an unrelated class. boolean result = mPluginInstanceManager.checkAndDisable(Activity.class.getName()); assertFalse(result); - verify(mMockPm, never()).setComponentEnabledSetting( - ArgumentCaptor.forClass(ComponentName.class).capture(), - ArgumentCaptor.forClass(int.class).capture(), - ArgumentCaptor.forClass(int.class).capture()); + verify(mMockEnabler, never()).setDisabled(any(ComponentName.class), anyInt()); // Now hand it a real class and make sure it disables the plugin. result = mPluginInstanceManager.checkAndDisable(TestPlugin.class.getName()); assertTrue(result); - verify(mMockPm).setComponentEnabledSetting( - ArgumentCaptor.forClass(ComponentName.class).capture(), - ArgumentCaptor.forClass(int.class).capture(), - ArgumentCaptor.forClass(int.class).capture()); + verify(mMockEnabler).setDisabled( + mTestPluginComponentName, PluginEnabler.DISABLED_FROM_EXPLICIT_CRASH); } @Test @@ -250,10 +248,8 @@ public class PluginInstanceManagerTest extends SysuiTestCase { mPluginInstanceManager.disableAll(); - verify(mMockPm).setComponentEnabledSetting( - ArgumentCaptor.forClass(ComponentName.class).capture(), - ArgumentCaptor.forClass(int.class).capture(), - ArgumentCaptor.forClass(int.class).capture()); + verify(mMockEnabler).setDisabled( + mTestPluginComponentName, PluginEnabler.DISABLED_FROM_SYSTEM_CRASH); } @Test @@ -275,8 +271,8 @@ public class PluginInstanceManagerTest extends SysuiTestCase { List<ResolveInfo> list = new ArrayList<>(); ResolveInfo info = new ResolveInfo(); info.serviceInfo = mock(ServiceInfo.class); - info.serviceInfo.packageName = "com.android.systemui"; - info.serviceInfo.name = TestPlugin.class.getName(); + info.serviceInfo.packageName = mTestPluginComponentName.getPackageName(); + info.serviceInfo.name = mTestPluginComponentName.getClassName(); when(info.serviceInfo.loadLabel(any())).thenReturn("Test Plugin"); list.add(info); when(mMockPm.queryIntentServices(any(), Mockito.anyInt())).thenReturn(list); @@ -288,6 +284,7 @@ public class PluginInstanceManagerTest extends SysuiTestCase { ApplicationInfo appInfo = getContext().getApplicationInfo(); when(mMockPm.getApplicationInfo(Mockito.anyString(), Mockito.anyInt())).thenReturn( appInfo); + when(mMockEnabler.isEnabled(mTestPluginComponentName)).thenReturn(true); } private void createPlugin() throws Exception { diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginManagerTest.java index ff1bc8abf5d1..76e68f1df724 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginManagerTest.java @@ -27,7 +27,6 @@ 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; @@ -82,17 +81,18 @@ public class PluginManagerTest extends SysuiTestCase { .thenReturn(mMockPluginInstance); mMockPackageManager = mock(PackageManager.class); - mPluginManager = new PluginManagerImpl(getContext(), mMockFactory, true, + mPluginManager = new PluginManagerImpl( + getContext(), mMockFactory, true, mMockExceptionHandler, new PluginInitializerImpl() { - @Override - public String[] getWhitelistedPlugins(Context context) { - return new String[0]; - } - - @Override - public PluginEnabler getPluginEnabler(Context context) { - return new PluginEnablerImpl(mMockPackageManager); - } + @Override + public String[] getWhitelistedPlugins(Context context) { + return new String[0]; + } + + @Override + public PluginEnabler getPluginEnabler(Context context) { + return new PluginEnablerImpl(context, mMockPackageManager); + } }); resetExceptionHandler(); mMockListener = mock(PluginListener.class); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/SmartReplyControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/SmartReplyControllerTest.java index 8d52ccd71808..14e611a26b4c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/SmartReplyControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/SmartReplyControllerTest.java @@ -51,6 +51,7 @@ public class SmartReplyControllerTest extends SysuiTestCase { private static final String TEST_CHOICE_TEXT = "A Reply"; private static final int TEST_CHOICE_INDEX = 2; private static final int TEST_CHOICE_COUNT = 4; + private static final int TEST_ACTION_COUNT = 3; private Notification mNotification; private NotificationData.Entry mEntry; @@ -117,12 +118,14 @@ public class SmartReplyControllerTest extends SysuiTestCase { } @Test - public void testShowSmartReply_logsToStatusBar() throws RemoteException { - mSmartReplyController.smartRepliesAdded(mEntry, TEST_CHOICE_COUNT); + public void testShowSmartSuggestions_logsToStatusBar() throws RemoteException { + final boolean generatedByAsssistant = true; + mSmartReplyController.smartSuggestionsAdded(mEntry, TEST_CHOICE_COUNT, TEST_ACTION_COUNT, + generatedByAsssistant); // Check we log the result to the status bar service. - verify(mIStatusBarService).onNotificationSmartRepliesAdded(mSbn.getKey(), - TEST_CHOICE_COUNT); + verify(mIStatusBarService).onNotificationSmartSuggestionsAdded(mSbn.getKey(), + TEST_CHOICE_COUNT, TEST_ACTION_COUNT, generatedByAsssistant); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java index 904e5b99b0ab..7f0e435eb7da 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java @@ -231,6 +231,7 @@ public class NotificationEntryManagerTest extends SysuiTestCase { mEntryManager = new TestableNotificationEntryManager(mContext, mBarService); Dependency.get(InitController.class).executePostInitTasks(); mEntryManager.setUpWithPresenter(mPresenter, mListContainer, mCallback, mHeadsUpManager); + mEntryManager.setNotificationClicker(mock(NotificationClicker.class)); setUserSentiment(mEntry.key, NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java index c207feff26f3..e5620a5520d5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java @@ -176,6 +176,7 @@ public class StatusBarTest extends SysuiTestCase { mDependency.injectTestDependency(MetricsLogger.class, mMetricsLogger); mDependency.injectTestDependency(NotificationLogger.class, mNotificationLogger); mNotificationLogger = new NotificationLogger(); + DozeLog.traceDozing(mContext, false /* dozing */); IPowerManager powerManagerService = mock(IPowerManager.class); mPowerManager = new PowerManager(mContext, powerManagerService, diff --git a/packages/overlays/AccentColorBlackOverlay/Android.mk b/packages/overlays/AccentColorBlackOverlay/Android.mk index d316fbd8907c..b81ae5bbe3fa 100644 --- a/packages/overlays/AccentColorBlackOverlay/Android.mk +++ b/packages/overlays/AccentColorBlackOverlay/Android.mk @@ -19,6 +19,7 @@ include $(CLEAR_VARS) LOCAL_RRO_THEME := AccentColorBlack LOCAL_CERTIFICATE := platform +LOCAL_PRODUCT_MODULE := true LOCAL_SRC_FILES := $(call all-subdir-java-files) diff --git a/packages/overlays/AccentColorGreenOverlay/Android.mk b/packages/overlays/AccentColorGreenOverlay/Android.mk index afc42873a4d6..db92157c8fdf 100644 --- a/packages/overlays/AccentColorGreenOverlay/Android.mk +++ b/packages/overlays/AccentColorGreenOverlay/Android.mk @@ -19,6 +19,7 @@ include $(CLEAR_VARS) LOCAL_RRO_THEME := AccentColorGreen LOCAL_CERTIFICATE := platform +LOCAL_PRODUCT_MODULE := true LOCAL_SRC_FILES := $(call all-subdir-java-files) diff --git a/packages/overlays/AccentColorPurpleOverlay/Android.mk b/packages/overlays/AccentColorPurpleOverlay/Android.mk index 336616921d71..d7dc4978e2ca 100644 --- a/packages/overlays/AccentColorPurpleOverlay/Android.mk +++ b/packages/overlays/AccentColorPurpleOverlay/Android.mk @@ -19,6 +19,7 @@ include $(CLEAR_VARS) LOCAL_RRO_THEME := AccentColorPurple LOCAL_CERTIFICATE := platform +LOCAL_PRODUCT_MODULE := true LOCAL_SRC_FILES := $(call all-subdir-java-files) diff --git a/packages/overlays/CleanSpec.mk b/packages/overlays/CleanSpec.mk new file mode 100644 index 000000000000..16fbaa202aa1 --- /dev/null +++ b/packages/overlays/CleanSpec.mk @@ -0,0 +1,53 @@ +# 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. +# + +# If you don't need to do a full clean build but would like to touch +# a file or delete some intermediate files, add a clean step to the end +# of the list. These steps will only be run once, if they haven't been +# run before. +# +# E.g.: +# $(call add-clean-step, touch -c external/sqlite/sqlite3.h) +# $(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/STATIC_LIBRARIES/libz_intermediates) +# +# Always use "touch -c" and "rm -f" or "rm -rf" to gracefully deal with +# files that are missing or have been moved. +# +# Use $(PRODUCT_OUT) to get to the "out/target/product/blah/" directory. +# Use $(OUT_DIR) to refer to the "out" directory. +# +# If you need to re-do something that's already mentioned, just copy +# the command and add it to the bottom of the list. E.g., if a change +# that you made last week required touching a file and a change you +# made today requires touching the same file, just copy the old +# touch step and add it to the end of the list. +# +# ************************************************ +# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST +# ************************************************ + +# For example: +#$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/APPS/AndroidTests_intermediates) +#$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/core_intermediates) +#$(call add-clean-step, find $(OUT_DIR) -type f -name "IGTalkSession*" -print0 | xargs -0 rm -f) +#$(call add-clean-step, rm -rf $(PRODUCT_OUT)/data/*) + +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/vendor/overlay/AccentColor*) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/vendor/overlay/DisplayCutout*) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/vendor/overlay/IconShape*) + +# ************************************************ +# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST +# ************************************************ diff --git a/packages/overlays/DisplayCutoutEmulationCornerOverlay/Android.mk b/packages/overlays/DisplayCutoutEmulationCornerOverlay/Android.mk index 74c43b40616f..bf2b6312d68f 100644 --- a/packages/overlays/DisplayCutoutEmulationCornerOverlay/Android.mk +++ b/packages/overlays/DisplayCutoutEmulationCornerOverlay/Android.mk @@ -4,6 +4,8 @@ include $(CLEAR_VARS) LOCAL_RRO_THEME := DisplayCutoutEmulationCorner LOCAL_CERTIFICATE := platform +LOCAL_PRODUCT_MODULE := true + LOCAL_SRC_FILES := $(call all-subdir-java-files) LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res diff --git a/packages/overlays/DisplayCutoutEmulationDoubleOverlay/Android.mk b/packages/overlays/DisplayCutoutEmulationDoubleOverlay/Android.mk index d83b30a8785a..70429064ec2b 100644 --- a/packages/overlays/DisplayCutoutEmulationDoubleOverlay/Android.mk +++ b/packages/overlays/DisplayCutoutEmulationDoubleOverlay/Android.mk @@ -4,6 +4,8 @@ include $(CLEAR_VARS) LOCAL_RRO_THEME := DisplayCutoutEmulationDouble LOCAL_CERTIFICATE := platform +LOCAL_PRODUCT_MODULE := true + LOCAL_SRC_FILES := $(call all-subdir-java-files) LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res diff --git a/packages/overlays/DisplayCutoutEmulationNarrowOverlay/Android.mk b/packages/overlays/DisplayCutoutEmulationNarrowOverlay/Android.mk index f5afad24676f..ae69e1137e60 100644 --- a/packages/overlays/DisplayCutoutEmulationNarrowOverlay/Android.mk +++ b/packages/overlays/DisplayCutoutEmulationNarrowOverlay/Android.mk @@ -4,6 +4,8 @@ include $(CLEAR_VARS) LOCAL_RRO_THEME := DisplayCutoutEmulationNarrow LOCAL_CERTIFICATE := platform +LOCAL_PRODUCT_MODULE := true + LOCAL_SRC_FILES := $(call all-subdir-java-files) LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res diff --git a/packages/overlays/DisplayCutoutEmulationTallOverlay/Android.mk b/packages/overlays/DisplayCutoutEmulationTallOverlay/Android.mk index f1f8c27d94f1..7dcadfbd4708 100644 --- a/packages/overlays/DisplayCutoutEmulationTallOverlay/Android.mk +++ b/packages/overlays/DisplayCutoutEmulationTallOverlay/Android.mk @@ -4,6 +4,8 @@ include $(CLEAR_VARS) LOCAL_RRO_THEME := DisplayCutoutEmulationTall LOCAL_CERTIFICATE := platform +LOCAL_PRODUCT_MODULE := true + LOCAL_SRC_FILES := $(call all-subdir-java-files) LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res diff --git a/packages/overlays/DisplayCutoutEmulationWideOverlay/Android.mk b/packages/overlays/DisplayCutoutEmulationWideOverlay/Android.mk index d149d8ecf4df..3f7be73aa5fa 100644 --- a/packages/overlays/DisplayCutoutEmulationWideOverlay/Android.mk +++ b/packages/overlays/DisplayCutoutEmulationWideOverlay/Android.mk @@ -4,6 +4,8 @@ include $(CLEAR_VARS) LOCAL_RRO_THEME := DisplayCutoutEmulationWide LOCAL_CERTIFICATE := platform +LOCAL_PRODUCT_MODULE := true + LOCAL_SRC_FILES := $(call all-subdir-java-files) LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res diff --git a/packages/overlays/IconShapeRoundedRectOverlay/Android.mk b/packages/overlays/IconShapeRoundedRectOverlay/Android.mk index a734a6b46947..08428d192fae 100644 --- a/packages/overlays/IconShapeRoundedRectOverlay/Android.mk +++ b/packages/overlays/IconShapeRoundedRectOverlay/Android.mk @@ -19,6 +19,7 @@ include $(CLEAR_VARS) LOCAL_RRO_THEME := IconShapeRoundedRect LOCAL_CERTIFICATE := platform +LOCAL_PRODUCT_MODULE := true LOCAL_SRC_FILES := $(call all-subdir-java-files) diff --git a/packages/overlays/IconShapeSquareOverlay/Android.mk b/packages/overlays/IconShapeSquareOverlay/Android.mk index 217da9feb534..ceb745ae1429 100644 --- a/packages/overlays/IconShapeSquareOverlay/Android.mk +++ b/packages/overlays/IconShapeSquareOverlay/Android.mk @@ -19,6 +19,7 @@ include $(CLEAR_VARS) LOCAL_RRO_THEME := IconShapeSquare LOCAL_CERTIFICATE := platform +LOCAL_PRODUCT_MODULE := true LOCAL_SRC_FILES := $(call all-subdir-java-files) diff --git a/packages/overlays/IconShapeSquircleOverlay/Android.mk b/packages/overlays/IconShapeSquircleOverlay/Android.mk index fd3bfa06de83..34edc3b78b09 100644 --- a/packages/overlays/IconShapeSquircleOverlay/Android.mk +++ b/packages/overlays/IconShapeSquircleOverlay/Android.mk @@ -19,6 +19,7 @@ include $(CLEAR_VARS) LOCAL_RRO_THEME := IconShapeSquircle LOCAL_CERTIFICATE := platform +LOCAL_PRODUCT_MODULE := true LOCAL_SRC_FILES := $(call all-subdir-java-files) diff --git a/packages/overlays/IconShapeTeardropOverlay/Android.mk b/packages/overlays/IconShapeTeardropOverlay/Android.mk index ea43423f93ba..834a1c357c61 100644 --- a/packages/overlays/IconShapeTeardropOverlay/Android.mk +++ b/packages/overlays/IconShapeTeardropOverlay/Android.mk @@ -19,6 +19,7 @@ include $(CLEAR_VARS) LOCAL_RRO_THEME := IconShapeTeardrop LOCAL_CERTIFICATE := platform +LOCAL_PRODUCT_MODULE := true LOCAL_SRC_FILES := $(call all-subdir-java-files) diff --git a/proto/src/metrics_constants/metrics_constants.proto b/proto/src/metrics_constants/metrics_constants.proto index 66d64b1a5f7a..529d78f577ed 100644 --- a/proto/src/metrics_constants/metrics_constants.proto +++ b/proto/src/metrics_constants/metrics_constants.proto @@ -6649,6 +6649,21 @@ message MetricsEvent { // OS: Q QS_SENSOR_PRIVACY = 1598; + // Tagged data for SMART_REPLY_VISIBLE. Count of number of smart actions. + // OS: Q + NOTIFICATION_SMART_ACTION_COUNT = 1599; + + // Tagged data for SMART_REPLY_VISIBLE and NOTIFICATION_ITEM_ACTION. + // Whether the notification has notification-assistant generated + // actions/replies. + // OS: Q + NOTIFICATION_SMART_SUGGESTION_ASSISTANT_GENERATED = 1600; + + // Tagged data for NOTIFICATION_ITEM_ACTION. Whether the action is a smart + // action. + // OS: Q + NOTIFICATION_ACTION_IS_SMART = 1601; + // ---- End Q Constants, all Q constants go above this line ---- // Add new aosp constants above this line. diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java index 2cbab492a605..fd2043769dd1 100644 --- a/services/backup/java/com/android/server/backup/BackupManagerService.java +++ b/services/backup/java/com/android/server/backup/BackupManagerService.java @@ -16,10 +16,13 @@ package com.android.server.backup; +import static com.android.internal.util.Preconditions.checkNotNull; + import android.Manifest; import android.annotation.Nullable; import android.annotation.UserIdInt; import android.app.ActivityManager; +import android.app.backup.BackupManager; import android.app.backup.IBackupManagerMonitor; import android.app.backup.IBackupObserver; import android.app.backup.IFullBackupRestoreObserver; @@ -41,6 +44,7 @@ import android.os.RemoteException; import android.os.Trace; import android.os.UserHandle; import android.util.Slog; +import android.util.SparseArray; import com.android.internal.annotations.VisibleForTesting; import com.android.server.SystemConfig; @@ -83,22 +87,27 @@ public class BackupManagerService { } private final Context mContext; - private UserBackupManagerService mUserBackupManagerService; + private final Trampoline mTrampoline; + private final HandlerThread mBackupThread; + + // Keeps track of all unlocked users registered with this service. Indexed by user id. + private final SparseArray<UserBackupManagerService> mServiceUsers = new SparseArray<>(); + + private Set<ComponentName> mTransportWhitelist; /** Instantiate a new instance of {@link BackupManagerService}. */ public BackupManagerService( Context context, Trampoline trampoline, HandlerThread backupThread) { - // Set up our transport options and initialize the default transport + mContext = checkNotNull(context); + mTrampoline = checkNotNull(trampoline); + mBackupThread = checkNotNull(backupThread); + + // Set up our transport options. SystemConfig systemConfig = SystemConfig.getInstance(); - Set<ComponentName> transportWhitelist = systemConfig.getBackupTransportWhitelist(); - if (transportWhitelist == null) { - transportWhitelist = Collections.emptySet(); + mTransportWhitelist = systemConfig.getBackupTransportWhitelist(); + if (mTransportWhitelist == null) { + mTransportWhitelist = Collections.emptySet(); } - - mContext = context; - mUserBackupManagerService = - UserBackupManagerService.createAndInitializeService( - context, trampoline, backupThread, transportWhitelist); } /** @@ -115,12 +124,6 @@ public class BackupManagerService { } } - // TODO(b/118520567): Remove when tests are modified to use per-user instance. - @VisibleForTesting - void setUserBackupManagerService(UserBackupManagerService userBackupManagerService) { - mUserBackupManagerService = userBackupManagerService; - } - /** * Called through Trampoline from {@link Lifecycle#onUnlockUser(int)}. We run the heavy work on * a background thread to keep the unlock time down. @@ -139,9 +142,42 @@ public class BackupManagerService { * Starts the backup service for user {@code userId} by creating a new instance of {@link * UserBackupManagerService} and registering it with this service. */ - // TODO(b/120212806): Add UserBackupManagerService initialization logic. - void startServiceForUser(int userId) { - // Intentionally empty. + @VisibleForTesting + protected void startServiceForUser(int userId) { + UserBackupManagerService userBackupManagerService = + UserBackupManagerService.createAndInitializeService( + userId, mContext, mTrampoline, mBackupThread, mTransportWhitelist); + startServiceForUser(userId, userBackupManagerService); + } + + /** + * Starts the backup service for user {@code userId} by registering its instance of {@link + * UserBackupManagerService} with this service. + */ + void startServiceForUser(int userId, UserBackupManagerService userBackupManagerService) { + mServiceUsers.put(userId, userBackupManagerService); + } + + SparseArray<UserBackupManagerService> getServiceUsers() { + return mServiceUsers; + } + + /** + * Returns the {@link UserBackupManagerService} instance for the specified user {@code userId}. + * If the user is not registered with the service (either the user is locked or not eligible for + * the backup service) then return {@code null}. + * + * @param userId The id of the user to retrieve its instance of {@link + * UserBackupManagerService}. + * @param caller A {@link String} identifying the caller for logging purposes. + */ + @Nullable + private UserBackupManagerService getServiceForUser(@UserIdInt int userId, String caller) { + UserBackupManagerService userBackupManagerService = mServiceUsers.get(userId); + if (userBackupManagerService == null) { + Slog.w(TAG, "Called " + caller + " for unknown user: " + userId); + } + return userBackupManagerService; } /* @@ -149,7 +185,7 @@ public class BackupManagerService { * They delegate to the appropriate per-user instance of UserBackupManagerService to perform the * action on the passed in user. Currently this is a straight redirection (see TODO). */ - // TODO (b/118520567): Take in user id and call per-user instance of UserBackupManagerService. + // TODO (b/118520567): Stop hardcoding system user when we pass in user id as a parameter // --------------------------------------------- // BACKUP AGENT OPERATIONS @@ -161,7 +197,12 @@ public class BackupManagerService { * backup. */ public void dataChanged(String packageName) { - mUserBackupManagerService.dataChanged(packageName); + UserBackupManagerService userBackupManagerService = + getServiceForUser(UserHandle.USER_SYSTEM, "dataChanged()"); + + if (userBackupManagerService != null) { + userBackupManagerService.dataChanged(packageName); + } } /** @@ -169,7 +210,12 @@ public class BackupManagerService { * {@link ActivityManager}. */ public void agentConnected(String packageName, IBinder agentBinder) { - mUserBackupManagerService.agentConnected(packageName, agentBinder); + UserBackupManagerService userBackupManagerService = + getServiceForUser(UserHandle.USER_SYSTEM, "agentConnected()"); + + if (userBackupManagerService != null) { + userBackupManagerService.agentConnected(packageName, agentBinder); + } } /** @@ -177,7 +223,12 @@ public class BackupManagerService { * called from the {@link ActivityManager}. */ public void agentDisconnected(String packageName) { - mUserBackupManagerService.agentDisconnected(packageName); + UserBackupManagerService userBackupManagerService = + getServiceForUser(UserHandle.USER_SYSTEM, "agentDisconnected()"); + + if (userBackupManagerService != null) { + userBackupManagerService.agentDisconnected(packageName); + } } /** @@ -185,7 +236,12 @@ public class BackupManagerService { * outstanding asynchronous backup/restore operation. */ public void opComplete(int token, long result) { - mUserBackupManagerService.opComplete(token, result); + UserBackupManagerService userBackupManagerService = + getServiceForUser(UserHandle.USER_SYSTEM, "opComplete()"); + + if (userBackupManagerService != null) { + userBackupManagerService.opComplete(token, result); + } } // --------------------------------------------- @@ -194,7 +250,12 @@ public class BackupManagerService { /** Run an initialize operation for the given transports {@code transportNames}. */ public void initializeTransports(String[] transportNames, IBackupObserver observer) { - mUserBackupManagerService.initializeTransports(transportNames, observer); + UserBackupManagerService userBackupManagerService = + getServiceForUser(UserHandle.USER_SYSTEM, "initializeTransports()"); + + if (userBackupManagerService != null) { + userBackupManagerService.initializeTransports(transportNames, observer); + } } /** @@ -202,35 +263,70 @@ public class BackupManagerService { * transportName}. */ public void clearBackupData(String transportName, String packageName) { - mUserBackupManagerService.clearBackupData(transportName, packageName); + UserBackupManagerService userBackupManagerService = + getServiceForUser(UserHandle.USER_SYSTEM, "clearBackupData()"); + + if (userBackupManagerService != null) { + userBackupManagerService.clearBackupData(transportName, packageName); + } } /** Return the name of the currently active transport. */ + @Nullable public String getCurrentTransport() { - return mUserBackupManagerService.getCurrentTransport(); + UserBackupManagerService userBackupManagerService = + getServiceForUser(UserHandle.USER_SYSTEM, "getCurrentTransport()"); + + return userBackupManagerService == null + ? null + : userBackupManagerService.getCurrentTransport(); } /** * Returns the {@link ComponentName} of the host service of the selected transport or {@code * null} if no transport selected or if the transport selected is not registered. */ + @Nullable public ComponentName getCurrentTransportComponent() { - return mUserBackupManagerService.getCurrentTransportComponent(); + UserBackupManagerService userBackupManagerService = + getServiceForUser(UserHandle.USER_SYSTEM, "getCurrentTransportComponent()"); + + return userBackupManagerService == null + ? null + : userBackupManagerService.getCurrentTransportComponent(); } /** Report all known, available backup transports by name. */ + @Nullable public String[] listAllTransports() { - return mUserBackupManagerService.listAllTransports(); + UserBackupManagerService userBackupManagerService = + getServiceForUser(UserHandle.USER_SYSTEM, "listAllTransports()"); + + return userBackupManagerService == null + ? null + : userBackupManagerService.listAllTransports(); } /** Report all known, available backup transports by {@link ComponentName}. */ + @Nullable public ComponentName[] listAllTransportComponents() { - return mUserBackupManagerService.listAllTransportComponents(); + UserBackupManagerService userBackupManagerService = + getServiceForUser(UserHandle.USER_SYSTEM, "listAllTransportComponents()"); + + return userBackupManagerService == null + ? null + : userBackupManagerService.listAllTransportComponents(); } /** Report all system whitelisted transports. */ + @Nullable public String[] getTransportWhitelist() { - return mUserBackupManagerService.getTransportWhitelist(); + UserBackupManagerService userBackupManagerService = + getServiceForUser(UserHandle.USER_SYSTEM, "getTransportWhitelist()"); + + return userBackupManagerService == null + ? null + : userBackupManagerService.getTransportWhitelist(); } /** @@ -263,13 +359,18 @@ public class BackupManagerService { String currentDestinationString, @Nullable Intent dataManagementIntent, String dataManagementLabel) { - mUserBackupManagerService.updateTransportAttributes( - transportComponent, - name, - configurationIntent, - currentDestinationString, - dataManagementIntent, - dataManagementLabel); + UserBackupManagerService userBackupManagerService = + getServiceForUser(UserHandle.USER_SYSTEM, "updateTransportAttributes()"); + + if (userBackupManagerService != null) { + userBackupManagerService.updateTransportAttributes( + transportComponent, + name, + configurationIntent, + currentDestinationString, + dataManagementIntent, + dataManagementLabel); + } } /** @@ -281,7 +382,12 @@ public class BackupManagerService { @Deprecated @Nullable public String selectBackupTransport(String transportName) { - return mUserBackupManagerService.selectBackupTransport(transportName); + UserBackupManagerService userBackupManagerService = + getServiceForUser(UserHandle.USER_SYSTEM, "selectBackupTransport()"); + + return userBackupManagerService == null + ? null + : userBackupManagerService.selectBackupTransport(transportName); } /** @@ -290,7 +396,12 @@ public class BackupManagerService { */ public void selectBackupTransportAsync( ComponentName transportComponent, ISelectBackupTransportCallback listener) { - mUserBackupManagerService.selectBackupTransportAsync(transportComponent, listener); + UserBackupManagerService userBackupManagerService = + getServiceForUser(UserHandle.USER_SYSTEM, "selectBackupTransportAsync()"); + + if (userBackupManagerService != null) { + userBackupManagerService.selectBackupTransportAsync(transportComponent, listener); + } } /** @@ -298,8 +409,14 @@ public class BackupManagerService { * available transports, or if the transport does not supply any configuration UI, the method * returns {@code null}. */ + @Nullable public Intent getConfigurationIntent(String transportName) { - return mUserBackupManagerService.getConfigurationIntent(transportName); + UserBackupManagerService userBackupManagerService = + getServiceForUser(UserHandle.USER_SYSTEM, "getConfigurationIntent()"); + + return userBackupManagerService == null + ? null + : userBackupManagerService.getConfigurationIntent(transportName); } /** @@ -311,21 +428,39 @@ public class BackupManagerService { * @param transportName The name of the registered transport. * @return The current destination string or null if the transport is not registered. */ + @Nullable public String getDestinationString(String transportName) { - return mUserBackupManagerService.getDestinationString(transportName); + UserBackupManagerService userBackupManagerService = + getServiceForUser(UserHandle.USER_SYSTEM, "getDestinationString()"); + + return userBackupManagerService == null + ? null + : userBackupManagerService.getDestinationString(transportName); } /** Supply the manage-data intent for the given transport. */ + @Nullable public Intent getDataManagementIntent(String transportName) { - return mUserBackupManagerService.getDataManagementIntent(transportName); + UserBackupManagerService userBackupManagerService = + getServiceForUser(UserHandle.USER_SYSTEM, "getDataManagementIntent()"); + + return userBackupManagerService == null + ? null + : userBackupManagerService.getDataManagementIntent(transportName); } /** * Supply the menu label for affordances that fire the manage-data intent for the given * transport. */ + @Nullable public String getDataManagementLabel(String transportName) { - return mUserBackupManagerService.getDataManagementLabel(transportName); + UserBackupManagerService userBackupManagerService = + getServiceForUser(UserHandle.USER_SYSTEM, "getDataManagementLabel()"); + + return userBackupManagerService == null + ? null + : userBackupManagerService.getDataManagementLabel(transportName); } // --------------------------------------------- @@ -335,17 +470,32 @@ public class BackupManagerService { /** Enable/disable the backup service. This is user-configurable via backup settings. */ public void setBackupEnabled(@UserIdInt int userId, boolean enable) { enforceCallingPermissionOnUserId(userId, "setBackupEnabled"); - mUserBackupManagerService.setBackupEnabled(enable); + UserBackupManagerService userBackupManagerService = + getServiceForUser(userId, "setBackupEnabled()"); + + if (userBackupManagerService != null) { + userBackupManagerService.setBackupEnabled(enable); + } } /** Enable/disable automatic restore of app data at install time. */ public void setAutoRestore(boolean autoRestore) { - mUserBackupManagerService.setAutoRestore(autoRestore); + UserBackupManagerService userBackupManagerService = + getServiceForUser(UserHandle.USER_SYSTEM, "setAutoRestore()"); + + if (userBackupManagerService != null) { + userBackupManagerService.setAutoRestore(autoRestore); + } } /** Mark the backup service as having been provisioned (device has gone through SUW). */ public void setBackupProvisioned(boolean provisioned) { - mUserBackupManagerService.setBackupProvisioned(provisioned); + UserBackupManagerService userBackupManagerService = + getServiceForUser(UserHandle.USER_SYSTEM, "setBackupProvisioned()"); + + if (userBackupManagerService != null) { + userBackupManagerService.setBackupProvisioned(provisioned); + } } /** @@ -353,7 +503,10 @@ public class BackupManagerService { */ public boolean isBackupEnabled(@UserIdInt int userId) { enforceCallingPermissionOnUserId(userId, "isBackupEnabled"); - return mUserBackupManagerService.isBackupEnabled(); + UserBackupManagerService userBackupManagerService = + getServiceForUser(userId, "isBackupEnabled()"); + + return userBackupManagerService != null && userBackupManagerService.isBackupEnabled(); } // --------------------------------------------- @@ -362,14 +515,24 @@ public class BackupManagerService { /** Checks if the given package {@code packageName} is eligible for backup. */ public boolean isAppEligibleForBackup(String packageName) { - return mUserBackupManagerService.isAppEligibleForBackup(packageName); + UserBackupManagerService userBackupManagerService = + getServiceForUser(UserHandle.USER_SYSTEM, "isAppEligibleForBackup()"); + + return userBackupManagerService != null + && userBackupManagerService.isAppEligibleForBackup(packageName); } /** * Returns from the inputted packages {@code packages}, the ones that are eligible for backup. */ + @Nullable public String[] filterAppsEligibleForBackup(String[] packages) { - return mUserBackupManagerService.filterAppsEligibleForBackup(packages); + UserBackupManagerService userBackupManagerService = + getServiceForUser(UserHandle.USER_SYSTEM, "filterAppsEligibleForBackup()"); + + return userBackupManagerService == null + ? null + : userBackupManagerService.filterAppsEligibleForBackup(packages); } /** @@ -378,7 +541,12 @@ public class BackupManagerService { */ public void backupNow(@UserIdInt int userId) { enforceCallingPermissionOnUserId(userId, "backupNow"); - mUserBackupManagerService.backupNow(); + UserBackupManagerService userBackupManagerService = + getServiceForUser(userId, "backupNow()"); + + if (userBackupManagerService != null) { + userBackupManagerService.backupNow(); + } } /** @@ -392,13 +560,23 @@ public class BackupManagerService { IBackupManagerMonitor monitor, int flags) { enforceCallingPermissionOnUserId(userId, "requestBackup"); - return mUserBackupManagerService.requestBackup(packages, observer, monitor, flags); + UserBackupManagerService userBackupManagerService = + getServiceForUser(userId, "requestBackup()"); + + return userBackupManagerService == null + ? BackupManager.ERROR_BACKUP_NOT_ALLOWED + : userBackupManagerService.requestBackup(packages, observer, monitor, flags); } /** Cancel all running backup operations. */ public void cancelBackups(@UserIdInt int userId) { enforceCallingPermissionOnUserId(userId, "cancelBackups"); - mUserBackupManagerService.cancelBackups(); + UserBackupManagerService userBackupManagerService = + getServiceForUser(userId, "cancelBackups()"); + + if (userBackupManagerService != null) { + userBackupManagerService.cancelBackups(); + } } /** @@ -410,7 +588,11 @@ public class BackupManagerService { * return value to the callback {@link JobService#onStartJob(JobParameters)}. */ public boolean beginFullBackup(FullBackupJob scheduledJob) { - return mUserBackupManagerService.beginFullBackup(scheduledJob); + UserBackupManagerService userBackupManagerService = + getServiceForUser(UserHandle.USER_SYSTEM, "beginFullBackup()"); + + return userBackupManagerService != null + && userBackupManagerService.beginFullBackup(scheduledJob); } /** @@ -418,14 +600,24 @@ public class BackupManagerService { * longer met for running the full backup job. */ public void endFullBackup() { - mUserBackupManagerService.endFullBackup(); + UserBackupManagerService userBackupManagerService = + getServiceForUser(UserHandle.USER_SYSTEM, "endFullBackup()"); + + if (userBackupManagerService != null) { + userBackupManagerService.endFullBackup(); + } } /** * Run a full backup pass for the given packages {@code packageNames}. Used by 'adb shell bmgr'. */ public void fullTransportBackup(String[] packageNames) { - mUserBackupManagerService.fullTransportBackup(packageNames); + UserBackupManagerService userBackupManagerService = + getServiceForUser(UserHandle.USER_SYSTEM, "fullTransportBackup()"); + + if (userBackupManagerService != null) { + userBackupManagerService.fullTransportBackup(packageNames); + } } // --------------------------------------------- @@ -437,15 +629,26 @@ public class BackupManagerService { * called from the {@link PackageManager}. */ public void restoreAtInstall(String packageName, int token) { - mUserBackupManagerService.restoreAtInstall(packageName, token); + UserBackupManagerService userBackupManagerService = + getServiceForUser(UserHandle.USER_SYSTEM, "restoreAtInstall()"); + + if (userBackupManagerService != null) { + userBackupManagerService.restoreAtInstall(packageName, token); + } } /** * Begin a restore for the specified package {@code packageName} using the specified transport * {@code transportName}. */ + @Nullable public IRestoreSession beginRestoreSession(String packageName, String transportName) { - return mUserBackupManagerService.beginRestoreSession(packageName, transportName); + UserBackupManagerService userBackupManagerService = + getServiceForUser(UserHandle.USER_SYSTEM, "beginRestoreSession()"); + + return userBackupManagerService == null + ? null + : userBackupManagerService.beginRestoreSession(packageName, transportName); } /** @@ -453,7 +656,12 @@ public class BackupManagerService { * the active set if possible, else the ancestral one. Returns zero if none available. */ public long getAvailableRestoreToken(String packageName) { - return mUserBackupManagerService.getAvailableRestoreToken(packageName); + UserBackupManagerService userBackupManagerService = + getServiceForUser(UserHandle.USER_SYSTEM, "getAvailableRestoreToken()"); + + return userBackupManagerService == null + ? 0 + : userBackupManagerService.getAvailableRestoreToken(packageName); } // --------------------------------------------- @@ -462,12 +670,19 @@ public class BackupManagerService { /** Sets the backup password used when running adb backup. */ public boolean setBackupPassword(String currentPassword, String newPassword) { - return mUserBackupManagerService.setBackupPassword(currentPassword, newPassword); + UserBackupManagerService userBackupManagerService = + getServiceForUser(UserHandle.USER_SYSTEM, "setBackupPassword()"); + + return userBackupManagerService != null + && userBackupManagerService.setBackupPassword(currentPassword, newPassword); } /** Returns {@code true} if adb backup was run with a password, else returns {@code false}. */ public boolean hasBackupPassword() { - return mUserBackupManagerService.hasBackupPassword(); + UserBackupManagerService userBackupManagerService = + getServiceForUser(UserHandle.USER_SYSTEM, "hasBackupPassword()"); + + return userBackupManagerService != null && userBackupManagerService.hasBackupPassword(); } /** @@ -489,18 +704,22 @@ public class BackupManagerService { boolean doKeyValue, String[] packageNames) { enforceCallingPermissionOnUserId(userId, "adbBackup"); - - mUserBackupManagerService.adbBackup( - fd, - includeApks, - includeObbs, - includeShared, - doWidgets, - doAllApps, - includeSystem, - doCompress, - doKeyValue, - packageNames); + UserBackupManagerService userBackupManagerService = + getServiceForUser(userId, "adbBackup()"); + + if (userBackupManagerService != null) { + userBackupManagerService.adbBackup( + fd, + includeApks, + includeObbs, + includeShared, + doWidgets, + doAllApps, + includeSystem, + doCompress, + doKeyValue, + packageNames); + } } /** @@ -510,8 +729,12 @@ public class BackupManagerService { */ public void adbRestore(@UserIdInt int userId, ParcelFileDescriptor fd) { enforceCallingPermissionOnUserId(userId, "setBackupEnabled"); + UserBackupManagerService userBackupManagerService = + getServiceForUser(userId, "adbRestore()"); - mUserBackupManagerService.adbRestore(fd); + if (userBackupManagerService != null) { + userBackupManagerService.adbRestore(fd); + } } /** @@ -524,8 +747,13 @@ public class BackupManagerService { String currentPassword, String encryptionPassword, IFullBackupRestoreObserver observer) { - mUserBackupManagerService.acknowledgeAdbBackupOrRestore( - token, allow, currentPassword, encryptionPassword, observer); + UserBackupManagerService userBackupManagerService = + getServiceForUser(UserHandle.USER_SYSTEM, "acknowledgeAdbBackupOrRestore()"); + + if (userBackupManagerService != null) { + userBackupManagerService.acknowledgeAdbBackupOrRestore( + token, allow, currentPassword, encryptionPassword, observer); + } } // --------------------------------------------- @@ -534,7 +762,12 @@ public class BackupManagerService { /** Prints service state for 'dumpsys backup'. */ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - mUserBackupManagerService.dump(fd, pw, args); + UserBackupManagerService userBackupManagerService = + getServiceForUser(UserHandle.USER_SYSTEM, "dump()"); + + if (userBackupManagerService != null) { + userBackupManagerService.dump(fd, pw, args); + } } private static boolean readBackupEnableState(int userId) { @@ -592,7 +825,7 @@ public class BackupManagerService { if (userId == UserHandle.USER_SYSTEM) { sInstance.initializeServiceAndUnlockSystemUser(); } else { - sInstance.startServiceForUser(userId); + sInstance.unlockUser(userId); } } } diff --git a/services/backup/java/com/android/server/backup/Trampoline.java b/services/backup/java/com/android/server/backup/Trampoline.java index 4acd5c494ac7..eb10a04586bf 100644 --- a/services/backup/java/com/android/server/backup/Trampoline.java +++ b/services/backup/java/com/android/server/backup/Trampoline.java @@ -116,7 +116,7 @@ public class Trampoline extends IBackupManager.Stub { return SystemProperties.getBoolean(BACKUP_DISABLE_PROPERTY, false); } - protected boolean isMultiUserEnabled() { + private boolean isMultiUserEnabled() { return Settings.Global.getInt( mContext.getContentResolver(), Settings.Global.BACKUP_MULTI_USER_ENABLED, @@ -145,6 +145,10 @@ public class Trampoline extends IBackupManager.Stub { return new BackupManagerService(mContext, this, mHandlerThread); } + protected void postToHandler(Runnable runnable) { + mHandler.post(runnable); + } + /** * Initialize {@link BackupManagerService} if the backup service is not disabled. Only the * system user can initialize the service. @@ -174,14 +178,18 @@ public class Trampoline extends IBackupManager.Stub { * to initialize {@link BackupManagerService} and set backup state for the system user. */ void initializeServiceAndUnlockSystemUser() { - mHandler.post( + postToHandler( () -> { + // Initialize the backup service. Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "backup init"); initializeService(UserHandle.USER_SYSTEM); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); + // Start the service for the system user. BackupManagerService service = mService; if (service != null) { + Slog.i(TAG, "Starting service for system user"); + service.startServiceForUser(UserHandle.USER_SYSTEM); Slog.i(TAG, "Unlocking system user"); service.unlockSystemUser(); } @@ -195,20 +203,21 @@ public class Trampoline extends IBackupManager.Stub { */ // TODO(b/120212806): Consolidate service start for system and non-system users when system // user-only logic is removed. - void startServiceForUser(int userId) { + void unlockUser(int userId) { if (!isMultiUserEnabled()) { Slog.i(TAG, "Multi-user disabled, cannot start service for user: " + userId); return; } - mHandler.post( - () -> { - BackupManagerService service = mService; - if (service != null) { - Slog.i(TAG, "Starting service for user: " + userId); - service.startServiceForUser(userId); - } - }); + postToHandler(() -> startServiceForUser(userId)); + } + + private void startServiceForUser(int userId) { + BackupManagerService service = mService; + if (service != null) { + Slog.i(TAG, "Starting service for user: " + userId); + service.startServiceForUser(userId); + } } /** @@ -242,6 +251,7 @@ public class Trampoline extends IBackupManager.Stub { if (makeActive) { mService = createBackupManagerService(); mSuppressFile.delete(); + startServiceForUser(userId); } else { mService = null; try { @@ -274,7 +284,7 @@ public class Trampoline extends IBackupManager.Stub { } @Override - public void dataChanged(String packageName) throws RemoteException { + public void dataChangedForUser(int userId, String packageName) throws RemoteException { BackupManagerService svc = mService; if (svc != null) { svc.dataChanged(packageName); @@ -282,8 +292,13 @@ public class Trampoline extends IBackupManager.Stub { } @Override - public void initializeTransports(String[] transportNames, IBackupObserver observer) - throws RemoteException { + public void dataChanged(String packageName) throws RemoteException { + dataChangedForUser(binderGetCallingUserId(), packageName); + } + + @Override + public void initializeTransportsForUser( + int userId, String[] transportNames, IBackupObserver observer) throws RemoteException { BackupManagerService svc = mService; if (svc != null) { svc.initializeTransports(transportNames, observer); @@ -291,7 +306,7 @@ public class Trampoline extends IBackupManager.Stub { } @Override - public void clearBackupData(String transportName, String packageName) + public void clearBackupDataForUser(int userId, String transportName, String packageName) throws RemoteException { BackupManagerService svc = mService; if (svc != null) { @@ -300,7 +315,14 @@ public class Trampoline extends IBackupManager.Stub { } @Override - public void agentConnected(String packageName, IBinder agent) throws RemoteException { + public void clearBackupData(String transportName, String packageName) + throws RemoteException { + clearBackupDataForUser(binderGetCallingUserId(), transportName, packageName); + } + + @Override + public void agentConnectedForUser(int userId, String packageName, IBinder agent) + throws RemoteException { BackupManagerService svc = mService; if (svc != null) { svc.agentConnected(packageName, agent); @@ -308,7 +330,12 @@ public class Trampoline extends IBackupManager.Stub { } @Override - public void agentDisconnected(String packageName) throws RemoteException { + public void agentConnected(String packageName, IBinder agent) throws RemoteException { + agentConnectedForUser(binderGetCallingUserId(), packageName, agent); + } + + @Override + public void agentDisconnectedForUser(int userId, String packageName) throws RemoteException { BackupManagerService svc = mService; if (svc != null) { svc.agentDisconnected(packageName); @@ -316,7 +343,13 @@ public class Trampoline extends IBackupManager.Stub { } @Override - public void restoreAtInstall(String packageName, int token) throws RemoteException { + public void agentDisconnected(String packageName) throws RemoteException { + agentDisconnectedForUser(binderGetCallingUserId(), packageName); + } + + @Override + public void restoreAtInstallForUser(int userId, String packageName, int token) + throws RemoteException { BackupManagerService svc = mService; if (svc != null) { svc.restoreAtInstall(packageName, token); @@ -324,6 +357,11 @@ public class Trampoline extends IBackupManager.Stub { } @Override + public void restoreAtInstall(String packageName, int token) throws RemoteException { + restoreAtInstallForUser(binderGetCallingUserId(), packageName, token); + } + + @Override public void setBackupEnabledForUser(@UserIdInt int userId, boolean isEnabled) throws RemoteException { BackupManagerService svc = mService; @@ -338,7 +376,7 @@ public class Trampoline extends IBackupManager.Stub { } @Override - public void setAutoRestore(boolean doAutoRestore) throws RemoteException { + public void setAutoRestoreForUser(int userId, boolean doAutoRestore) throws RemoteException { BackupManagerService svc = mService; if (svc != null) { svc.setAutoRestore(doAutoRestore); @@ -346,6 +384,11 @@ public class Trampoline extends IBackupManager.Stub { } @Override + public void setAutoRestore(boolean doAutoRestore) throws RemoteException { + setAutoRestoreForUser(binderGetCallingUserId(), doAutoRestore); + } + + @Override public void setBackupProvisioned(boolean isProvisioned) throws RemoteException { BackupManagerService svc = mService; if (svc != null) { @@ -401,7 +444,8 @@ public class Trampoline extends IBackupManager.Stub { } @Override - public void fullTransportBackup(String[] packageNames) throws RemoteException { + public void fullTransportBackupForUser(int userId, String[] packageNames) + throws RemoteException { BackupManagerService svc = mService; if (svc != null) { svc.fullTransportBackup(packageNames); @@ -417,9 +461,14 @@ public class Trampoline extends IBackupManager.Stub { } @Override - public void acknowledgeFullBackupOrRestore(int token, boolean allow, String curPassword, - String encryptionPassword, IFullBackupRestoreObserver observer) - throws RemoteException { + public void acknowledgeFullBackupOrRestoreForUser( + int userId, + int token, + boolean allow, + String curPassword, + String encryptionPassword, + IFullBackupRestoreObserver observer) + throws RemoteException { BackupManagerService svc = mService; if (svc != null) { svc.acknowledgeAdbBackupOrRestore(token, allow, @@ -428,30 +477,50 @@ public class Trampoline extends IBackupManager.Stub { } @Override - public String getCurrentTransport() throws RemoteException { + public void acknowledgeFullBackupOrRestore(int token, boolean allow, String curPassword, + String encryptionPassword, IFullBackupRestoreObserver observer) + throws RemoteException { + BackupManagerService svc = mService; + acknowledgeFullBackupOrRestoreForUser( + binderGetCallingUserId(), token, allow, curPassword, encryptionPassword, observer); + } + + + @Override + public String getCurrentTransportForUser(int userId) throws RemoteException { BackupManagerService svc = mService; return (svc != null) ? svc.getCurrentTransport() : null; } + @Override + public String getCurrentTransport() throws RemoteException { + return getCurrentTransportForUser(binderGetCallingUserId()); + } + /** * Returns the {@link ComponentName} of the host service of the selected transport or * {@code null} if no transport selected or if the transport selected is not registered. */ @Override @Nullable - public ComponentName getCurrentTransportComponent() { + public ComponentName getCurrentTransportComponentForUser(int userId) { BackupManagerService svc = mService; return (svc != null) ? svc.getCurrentTransportComponent() : null; } @Override - public String[] listAllTransports() throws RemoteException { + public String[] listAllTransportsForUser(int userId) throws RemoteException { BackupManagerService svc = mService; return (svc != null) ? svc.listAllTransports() : null; } @Override - public ComponentName[] listAllTransportComponents() throws RemoteException { + public String[] listAllTransports() throws RemoteException { + return listAllTransportsForUser(binderGetCallingUserId()); + } + + @Override + public ComponentName[] listAllTransportComponentsForUser(int userId) throws RemoteException { BackupManagerService svc = mService; return (svc != null) ? svc.listAllTransportComponents() : null; } @@ -463,7 +532,8 @@ public class Trampoline extends IBackupManager.Stub { } @Override - public void updateTransportAttributes( + public void updateTransportAttributesForUser( + int userId, ComponentName transportComponent, String name, @Nullable Intent configurationIntent, @@ -483,13 +553,19 @@ public class Trampoline extends IBackupManager.Stub { } @Override - public String selectBackupTransport(String transport) throws RemoteException { + public String selectBackupTransportForUser(int userId, String transport) + throws RemoteException { BackupManagerService svc = mService; return (svc != null) ? svc.selectBackupTransport(transport) : null; } @Override - public void selectBackupTransportAsync(ComponentName transport, + public String selectBackupTransport(String transport) throws RemoteException { + return selectBackupTransportForUser(binderGetCallingUserId(), transport); + } + + @Override + public void selectBackupTransportAsyncForUser(int userId, ComponentName transport, ISelectBackupTransportCallback listener) throws RemoteException { BackupManagerService svc = mService; if (svc != null) { @@ -506,32 +582,58 @@ public class Trampoline extends IBackupManager.Stub { } @Override - public Intent getConfigurationIntent(String transport) throws RemoteException { + public Intent getConfigurationIntentForUser(int userId, String transport) + throws RemoteException { BackupManagerService svc = mService; return (svc != null) ? svc.getConfigurationIntent(transport) : null; } @Override - public String getDestinationString(String transport) throws RemoteException { + public Intent getConfigurationIntent(String transport) + throws RemoteException { + return getConfigurationIntentForUser(binderGetCallingUserId(), transport); + } + + @Override + public String getDestinationStringForUser(int userId, String transport) throws RemoteException { BackupManagerService svc = mService; return (svc != null) ? svc.getDestinationString(transport) : null; } @Override - public Intent getDataManagementIntent(String transport) throws RemoteException { + public String getDestinationString(String transport) throws RemoteException { + return getDestinationStringForUser(binderGetCallingUserId(), transport); + } + + @Override + public Intent getDataManagementIntentForUser(int userId, String transport) + throws RemoteException { BackupManagerService svc = mService; return (svc != null) ? svc.getDataManagementIntent(transport) : null; } @Override - public String getDataManagementLabel(String transport) throws RemoteException { + public Intent getDataManagementIntent(String transport) + throws RemoteException { + return getDataManagementIntentForUser(binderGetCallingUserId(), transport); + } + + @Override + public String getDataManagementLabelForUser(int userId, String transport) + throws RemoteException { BackupManagerService svc = mService; return (svc != null) ? svc.getDataManagementLabel(transport) : null; } @Override - public IRestoreSession beginRestoreSession(String packageName, String transportID) + public String getDataManagementLabel(String transport) throws RemoteException { + return getDataManagementLabelForUser(binderGetCallingUserId(), transport); + } + + @Override + public IRestoreSession beginRestoreSessionForUser( + int userId, String packageName, String transportID) throws RemoteException { BackupManagerService svc = mService; return (svc != null) ? svc.beginRestoreSession(packageName, transportID) : null; } @@ -545,19 +647,19 @@ public class Trampoline extends IBackupManager.Stub { } @Override - public long getAvailableRestoreToken(String packageName) { + public long getAvailableRestoreTokenForUser(int userId, String packageName) { BackupManagerService svc = mService; return (svc != null) ? svc.getAvailableRestoreToken(packageName) : 0; } @Override - public boolean isAppEligibleForBackup(String packageName) { + public boolean isAppEligibleForBackupForUser(int userId, String packageName) { BackupManagerService svc = mService; return (svc != null) ? svc.isAppEligibleForBackup(packageName) : false; } @Override - public String[] filterAppsEligibleForBackup(String[] packages) { + public String[] filterAppsEligibleForBackupForUser(int userId, String[] packages) { BackupManagerService svc = mService; return (svc != null) ? svc.filterAppsEligibleForBackup(packages) : null; } diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java index 796ef406d333..d35740482059 100644 --- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java +++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java @@ -251,6 +251,7 @@ public class UserBackupManagerService { private static final long BUSY_BACKOFF_MIN_MILLIS = 1000 * 60 * 60; // one hour private static final int BUSY_BACKOFF_FUZZ = 1000 * 60 * 60 * 2; // two hours + private final @UserIdInt int mUserId; private final BackupAgentTimeoutParameters mAgentTimeoutParameters; private final TransportManager mTransportManager; @@ -372,10 +373,11 @@ public class UserBackupManagerService { * Creates an instance of {@link UserBackupManagerService} and initializes state for it. This * includes setting up the directories where we keep our bookkeeping and transport management. * - * @see #createAndInitializeService(Context, Trampoline, HandlerThread, File, File, + * @see #createAndInitializeService(int, Context, Trampoline, HandlerThread, File, File, * TransportManager) */ static UserBackupManagerService createAndInitializeService( + @UserIdInt int userId, Context context, Trampoline trampoline, HandlerThread backupThread, @@ -399,12 +401,13 @@ public class UserBackupManagerService { File dataDir = new File(Environment.getDownloadCacheDirectory(), BACKUP_STAGING_DIR); return createAndInitializeService( - context, trampoline, backupThread, baseStateDir, dataDir, transportManager); + userId, context, trampoline, backupThread, baseStateDir, dataDir, transportManager); } /** * Creates an instance of {@link UserBackupManagerService}. * + * @param userId The user which this service is for. * @param context The system server context. * @param trampoline A reference to the proxy to {@link BackupManagerService}. * @param backupThread The thread running backup/restore operations for the user. @@ -415,6 +418,7 @@ public class UserBackupManagerService { */ @VisibleForTesting public static UserBackupManagerService createAndInitializeService( + @UserIdInt int userId, Context context, Trampoline trampoline, HandlerThread backupThread, @@ -422,16 +426,18 @@ public class UserBackupManagerService { File dataDir, TransportManager transportManager) { return new UserBackupManagerService( - context, trampoline, backupThread, baseStateDir, dataDir, transportManager); + userId, context, trampoline, backupThread, baseStateDir, dataDir, transportManager); } private UserBackupManagerService( + @UserIdInt int userId, Context context, Trampoline parent, HandlerThread backupThread, File baseStateDir, File dataDir, TransportManager transportManager) { + mUserId = userId; mContext = checkNotNull(context, "context cannot be null"); mPackageManager = context.getPackageManager(); mPackageManagerBinder = AppGlobals.getPackageManager(); diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java index 10e713d9dabe..e8820ae4e32a 100644 --- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java +++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java @@ -25,6 +25,7 @@ import android.annotation.UserIdInt; import android.app.ActivityManagerInternal; import android.content.ComponentName; import android.content.Context; +import android.os.Binder; import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; @@ -34,7 +35,6 @@ import android.os.UserHandle; import android.os.UserManager; import android.util.Slog; import android.view.contentcapture.ContentCaptureContext; -import android.view.contentcapture.ContentCaptureEvent; import android.view.contentcapture.IContentCaptureManager; import com.android.internal.annotations.GuardedBy; @@ -47,7 +47,6 @@ import com.android.server.infra.AbstractMasterSystemService; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; -import java.util.List; /** * A service used to observe the contents of the screen. @@ -182,30 +181,18 @@ public final class ContentCaptureManagerService extends synchronized (mLock) { final ContentCapturePerUserService service = getServiceForUserLocked(userId); service.startSessionLocked(activityToken, componentName, taskId, displayId, - sessionId, clientContext, flags, mAllowInstantService, result); + sessionId, Binder.getCallingUid(), clientContext, flags, + mAllowInstantService, result); } } @Override - public void sendEvents(@UserIdInt int userId, @NonNull String sessionId, - @NonNull List<ContentCaptureEvent> events) { + public void finishSession(@UserIdInt int userId, @NonNull String sessionId) { Preconditions.checkNotNull(sessionId); - Preconditions.checkNotNull(events); synchronized (mLock) { final ContentCapturePerUserService service = getServiceForUserLocked(userId); - service.sendEventsLocked(sessionId, events); - } - } - - @Override - public void finishSession(@UserIdInt int userId, @NonNull String sessionId, - @Nullable List<ContentCaptureEvent> events) { - Preconditions.checkNotNull(sessionId); - - synchronized (mLock) { - final ContentCapturePerUserService service = getServiceForUserLocked(userId); - service.finishSessionLocked(sessionId, events); + service.finishSessionLocked(sessionId); } } diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java index 81309122d0ca..f21b0d810411 100644 --- a/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java +++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java @@ -16,6 +16,8 @@ package com.android.server.contentcapture; +import static android.service.contentcapture.ContentCaptureService.setClientState; + import static com.android.server.wm.ActivityTaskManagerInternal.ASSIST_KEY_CONTENT; import static com.android.server.wm.ActivityTaskManagerInternal.ASSIST_KEY_DATA; import static com.android.server.wm.ActivityTaskManagerInternal.ASSIST_KEY_STRUCTURE; @@ -38,7 +40,6 @@ import android.service.contentcapture.SnapshotData; import android.util.ArrayMap; import android.util.Slog; import android.view.contentcapture.ContentCaptureContext; -import android.view.contentcapture.ContentCaptureEvent; import android.view.contentcapture.ContentCaptureSession; import com.android.internal.annotations.GuardedBy; @@ -48,7 +49,6 @@ import com.android.server.infra.FrameworkResourcesServiceNameResolver; import java.io.PrintWriter; import java.util.ArrayList; -import java.util.List; /** * Per-user instance of {@link ContentCaptureManagerService}. @@ -114,10 +114,10 @@ final class ContentCapturePerUserService @GuardedBy("mLock") public void startSessionLocked(@NonNull IBinder activityToken, @NonNull ComponentName componentName, int taskId, int displayId, - @NonNull String sessionId, @Nullable ContentCaptureContext clientContext, - int flags, boolean bindInstantServiceAllowed, @NonNull IResultReceiver resultReceiver) { + @NonNull String sessionId, int uid, @Nullable ContentCaptureContext clientContext, + int flags, boolean bindInstantServiceAllowed, @NonNull IResultReceiver clientReceiver) { if (!isEnabledLocked()) { - sendToClient(resultReceiver, ContentCaptureSession.STATE_DISABLED); + setClientState(clientReceiver, ContentCaptureSession.STATE_DISABLED, /* binder=*/ null); return; } final ComponentName serviceComponentName = getServiceComponentName(); @@ -131,35 +131,30 @@ final class ContentCapturePerUserService return; } - ContentCaptureServerSession session = mSessions.get(sessionId); - if (session != null) { - if (mMaster.debug) { - Slog.d(TAG, "startSession(): reusing session " + sessionId + " for " - + componentName); - } - // TODO(b/111276913): check if local ids match and decide what to do if they don't - // TODO(b/111276913): should we call session.notifySessionStartedLocked() again?? - // if not, move notifySessionStartedLocked() into session constructor - sendToClient(resultReceiver, ContentCaptureSession.STATE_ACTIVE); + final ContentCaptureServerSession existingSession = mSessions.get(sessionId); + if (existingSession != null) { + Slog.w(TAG, "startSession(id=" + existingSession + ", token=" + activityToken + + ": ignoring because it already exists for " + existingSession.mActivityToken); + setClientState(clientReceiver, ContentCaptureSession.STATE_DISABLED_DUPLICATED_ID, + /* binder=*/ null); return; } - session = new ContentCaptureServerSession(getContext(), mUserId, mLock, activityToken, - this, serviceComponentName, componentName, taskId, displayId, sessionId, - clientContext, flags, bindInstantServiceAllowed, mMaster.verbose); + final ContentCaptureServerSession newSession = new ContentCaptureServerSession(getContext(), + mUserId, mLock, activityToken, this, serviceComponentName, componentName, taskId, + displayId, sessionId, uid, clientContext, flags, bindInstantServiceAllowed, + mMaster.verbose); if (mMaster.verbose) { - Slog.v(TAG, "startSession(): new session for " + componentName + " and id " - + sessionId); + Slog.v(TAG, "startSession(): new session for " + + ComponentName.flattenToShortString(componentName) + " and id " + sessionId); } - mSessions.put(sessionId, session); - session.notifySessionStartedLocked(); - sendToClient(resultReceiver, ContentCaptureSession.STATE_ACTIVE); + mSessions.put(sessionId, newSession); + newSession.notifySessionStartedLocked(clientReceiver); } // TODO(b/111276913): log metrics @GuardedBy("mLock") - public void finishSessionLocked(@NonNull String sessionId, - @Nullable List<ContentCaptureEvent> events) { + public void finishSessionLocked(@NonNull String sessionId) { if (!isEnabledLocked()) { return; } @@ -171,41 +166,8 @@ final class ContentCapturePerUserService } return; } - if (events != null && !events.isEmpty()) { - // TODO(b/111276913): for now we're sending the events and the onDestroy() in 2 separate - // calls because it's not clear yet whether we'll change the manager to send events - // to the service directly (i.e., without passing through system server). Once we - // decide, we might need to split IContentCaptureManager.onSessionLifecycle() in 2 - // methods, one for start and another for finish (and passing the events to finish), - // otherwise the service might receive the 2 calls out of order. - session.sendEventsLocked(events); - } - if (mMaster.verbose) { - Slog.v(TAG, "finishSession(" + (events == null ? 0 : events.size()) + " events): " - + session); - } - session.removeSelfLocked(true); - } - - // TODO(b/111276913): need to figure out why some events are sent before session is started; - // probably because ContentCaptureManager is not buffering them until it gets the session back - @GuardedBy("mLock") - public void sendEventsLocked(@NonNull String sessionId, - @NonNull List<ContentCaptureEvent> events) { - if (!isEnabledLocked()) { - return; - } - final ContentCaptureServerSession session = mSessions.get(sessionId); - if (session == null) { - if (mMaster.verbose) { - Slog.v(TAG, "sendEvents(): no session for " + sessionId); - } - return; - } - if (mMaster.verbose) { - Slog.v(TAG, "sendEvents(): id=" + sessionId + ", events=" + events.size()); - } - session.sendEventsLocked(events); + if (mMaster.verbose) Slog.v(TAG, "finishSession(): id=" + sessionId); + session.removeSelfLocked(/* notifyRemoteService= */ true); } @GuardedBy("mLock") @@ -308,12 +270,4 @@ final class ContentCapturePerUserService } return null; } - - private static void sendToClient(@NonNull IResultReceiver resultReceiver, int value) { - try { - resultReceiver.send(value, null); - } catch (RemoteException e) { - Slog.w(TAG, "Error async reporting result to client: " + e); - } - } } diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureServerSession.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureServerSession.java index 181a2daa2467..ba98b95082f7 100644 --- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureServerSession.java +++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureServerSession.java @@ -24,15 +24,14 @@ import android.service.contentcapture.ContentCaptureService; import android.service.contentcapture.SnapshotData; import android.util.Slog; import android.view.contentcapture.ContentCaptureContext; -import android.view.contentcapture.ContentCaptureEvent; import android.view.contentcapture.ContentCaptureSessionId; import com.android.internal.annotations.GuardedBy; +import com.android.internal.os.IResultReceiver; import com.android.internal.util.Preconditions; import com.android.server.contentcapture.RemoteContentCaptureService.ContentCaptureServiceCallbacks; import java.io.PrintWriter; -import java.util.List; final class ContentCaptureServerSession implements ContentCaptureServiceCallbacks { @@ -43,18 +42,28 @@ final class ContentCaptureServerSession implements ContentCaptureServiceCallback private final ContentCapturePerUserService mService; private final RemoteContentCaptureService mRemoteService; private final ContentCaptureContext mContentCaptureContext; + + /** + * Canonical session id. + */ private final String mId; + /** + * UID of the app whose contents is being captured. + */ + private final int mUid; + ContentCaptureServerSession(@NonNull Context context, int userId, @NonNull Object lock, @NonNull IBinder activityToken, @NonNull ContentCapturePerUserService service, @NonNull ComponentName serviceComponentName, @NonNull ComponentName appComponentName, - int taskId, int displayId, @NonNull String sessionId, + int taskId, int displayId, @NonNull String sessionId, int uid, @Nullable ContentCaptureContext clientContext, int flags, boolean bindInstantServiceAllowed, boolean verbose) { mLock = lock; mActivityToken = activityToken; mService = service; mId = Preconditions.checkNotNull(sessionId); + mUid = uid; mRemoteService = new RemoteContentCaptureService(context, ContentCaptureService.SERVICE_INTERFACE, serviceComponentName, userId, this, bindInstantServiceAllowed, verbose); @@ -73,15 +82,8 @@ final class ContentCaptureServerSession implements ContentCaptureServiceCallback * Notifies the {@link ContentCaptureService} that the service started. */ @GuardedBy("mLock") - public void notifySessionStartedLocked() { - mRemoteService.onSessionLifecycleRequest(mContentCaptureContext, mId); - } - - /** - * Notifies the {@link ContentCaptureService} of a batch of events. - */ - public void sendEventsLocked(@NonNull List<ContentCaptureEvent> events) { - mRemoteService.onContentCaptureEventsRequest(mId, events); + public void notifySessionStartedLocked(@NonNull IResultReceiver clientReceiver) { + mRemoteService.onSessionStarted(mContentCaptureContext, mId, mUid, clientReceiver); } /** @@ -118,11 +120,11 @@ final class ContentCaptureServerSession implements ContentCaptureServiceCallback @GuardedBy("mLock") public void destroyLocked(boolean notifyRemoteService) { if (mService.isVerbose()) { - Slog.v(TAG, "destroyLocked(notifyRemoteService=" + notifyRemoteService + ")"); + Slog.v(TAG, "destroy(notifyRemoteService=" + notifyRemoteService + ")"); } // TODO(b/111276913): must call client to set session as FINISHED_BY_SERVER if (notifyRemoteService) { - mRemoteService.onSessionLifecycleRequest(/* context= */ null, mId); + mRemoteService.onSessionFinished(mId); } } @@ -133,13 +135,14 @@ final class ContentCaptureServerSession implements ContentCaptureServiceCallback Slog.d(TAG, "onServiceDied() for " + mId); } synchronized (mLock) { - removeSelfLocked(/* notifyRemoteService= */ false); + removeSelfLocked(/* notifyRemoteService= */ true); } } @GuardedBy("mLock") public void dumpLocked(@NonNull String prefix, @NonNull PrintWriter pw) { pw.print(prefix); pw.print("id: "); pw.print(mId); pw.println(); + pw.print(prefix); pw.print("uid: "); pw.print(mUid); pw.println(); pw.print(prefix); pw.print("context: "); mContentCaptureContext.dump(pw); pw.println(); pw.print(prefix); pw.print("activity token: "); pw.println(mActivityToken); pw.print(prefix); pw.print("has autofill callback: "); diff --git a/services/contentcapture/java/com/android/server/contentcapture/RemoteContentCaptureService.java b/services/contentcapture/java/com/android/server/contentcapture/RemoteContentCaptureService.java index b4edf7e60bc4..b9b19435408f 100644 --- a/services/contentcapture/java/com/android/server/contentcapture/RemoteContentCaptureService.java +++ b/services/contentcapture/java/com/android/server/contentcapture/RemoteContentCaptureService.java @@ -20,17 +20,14 @@ import android.annotation.Nullable; import android.content.ComponentName; import android.content.Context; import android.os.IBinder; -import android.service.contentcapture.ContentCaptureEventsRequest; import android.service.contentcapture.IContentCaptureService; import android.service.contentcapture.SnapshotData; import android.text.format.DateUtils; import android.view.contentcapture.ContentCaptureContext; -import android.view.contentcapture.ContentCaptureEvent; +import com.android.internal.os.IResultReceiver; import com.android.server.infra.AbstractMultiplePendingRequestsRemoteService; -import java.util.List; - final class RemoteContentCaptureService extends AbstractMultiplePendingRequestsRemoteService<RemoteContentCaptureService, IContentCaptureService> { @@ -67,21 +64,19 @@ final class RemoteContentCaptureService /** * Called by {@link ContentCaptureServerSession} to generate a call to the - * {@link RemoteContentCaptureService} to indicate the session was created (when {@code context} - * is not {@code null} or destroyed (when {@code context} is {@code null}). + * {@link RemoteContentCaptureService} to indicate the session was created. */ - public void onSessionLifecycleRequest(@Nullable ContentCaptureContext context, - @NonNull String sessionId) { - scheduleAsyncRequest((s) -> s.onSessionLifecycle(context, sessionId)); + public void onSessionStarted(@Nullable ContentCaptureContext context, + @NonNull String sessionId, int uid, @NonNull IResultReceiver clientReceiver) { + scheduleAsyncRequest((s) -> s.onSessionStarted(context, sessionId, uid, clientReceiver)); } /** - * Called by {@link ContentCaptureServerSession} to send a batch of events to the service. + * Called by {@link ContentCaptureServerSession} to generate a call to the + * {@link RemoteContentCaptureService} to indicate the session was finished. */ - public void onContentCaptureEventsRequest(@NonNull String sessionId, - @NonNull List<ContentCaptureEvent> events) { - scheduleAsyncRequest((s) -> s.onContentCaptureEventsRequest(sessionId, - new ContentCaptureEventsRequest(events))); + public void onSessionFinished(@NonNull String sessionId) { + scheduleAsyncRequest((s) -> s.onSessionFinished(sessionId)); } /** diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java index 08034f734bea..0fa996ed7657 100644 --- a/services/core/java/com/android/server/AlarmManagerService.java +++ b/services/core/java/com/android/server/AlarmManagerService.java @@ -1123,8 +1123,6 @@ class AlarmManagerService extends SystemService { rescheduleKernelAlarmsLocked(); updateNextAlarmClockLocked(); - // And send a TIME_TICK right now, since it is important to get the UI updated. - mHandler.post(() -> getContext().sendBroadcastAsUser(mTimeTickIntent, UserHandle.ALL)); } static final class InFlight { @@ -1298,7 +1296,7 @@ class AlarmManagerService extends SystemService { mInjector.init(); synchronized (mLock) { - mHandler = new AlarmHandler(Looper.myLooper()); + mHandler = new AlarmHandler(); mConstants = new Constants(mHandler); mNextWakeup = mNextNonWakeup = 0; @@ -3050,6 +3048,9 @@ class AlarmManagerService extends SystemService { mNonInteractiveTime = dur; } } + // And send a TIME_TICK right now, since it is important to get the UI updated. + mHandler.post(() -> + getContext().sendBroadcastAsUser(mTimeTickIntent, UserHandle.ALL)); } else { mNonInteractiveStartTime = nowELAPSED; } @@ -3838,7 +3839,8 @@ class AlarmManagerService extends SystemService { mWakeLock.setWorkSource(null); } - private class AlarmHandler extends Handler { + @VisibleForTesting + class AlarmHandler extends Handler { public static final int ALARM_EVENT = 1; public static final int SEND_NEXT_ALARM_CLOCK_CHANGED = 2; public static final int LISTENER_TIMEOUT = 3; @@ -3847,8 +3849,8 @@ class AlarmManagerService extends SystemService { public static final int APP_STANDBY_PAROLE_CHANGED = 6; public static final int REMOVE_FOR_STOPPED = 7; - AlarmHandler(Looper looper) { - super(looper); + AlarmHandler() { + super(Looper.myLooper()); } public void postRemoveForStopped(int uid) { @@ -3961,8 +3963,8 @@ class AlarmManagerService extends SystemService { final WorkSource workSource = null; // Let system take blame for time tick events. setImpl(ELAPSED_REALTIME, mInjector.getElapsedRealtime() + tickEventDelay, 0, - 0, null, mTimeTickTrigger, null, AlarmManager.FLAG_STANDALONE, workSource, - null, Process.myUid(), "android"); + 0, null, mTimeTickTrigger, "TIME_TICK", AlarmManager.FLAG_STANDALONE, + workSource, null, Process.myUid(), "android"); // Finally, remember when we set the tick alarm synchronized (mLock) { diff --git a/services/core/java/com/android/server/AppOpsService.java b/services/core/java/com/android/server/AppOpsService.java index 8d912fadf6d1..f0ec69f488b1 100644 --- a/services/core/java/com/android/server/AppOpsService.java +++ b/services/core/java/com/android/server/AppOpsService.java @@ -86,6 +86,7 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.IAppOpsActiveCallback; import com.android.internal.app.IAppOpsCallback; +import com.android.internal.app.IAppOpsNotedCallback; import com.android.internal.app.IAppOpsService; import com.android.internal.os.Zygote; import com.android.internal.util.ArrayUtils; @@ -456,6 +457,7 @@ public class AppOpsService extends IAppOpsService.Stub { final ArrayMap<String, ArraySet<ModeCallback>> mPackageModeWatchers = new ArrayMap<>(); final ArrayMap<IBinder, ModeCallback> mModeWatchers = new ArrayMap<>(); final ArrayMap<IBinder, SparseArray<ActiveCallback>> mActiveWatchers = new ArrayMap<>(); + final ArrayMap<IBinder, SparseArray<NotedCallback>> mNotedWatchers = new ArrayMap<>(); final SparseArray<SparseArray<Restriction>> mAudioRestrictions = new SparseArray<>(); final class ModeCallback implements DeathRecipient { @@ -475,6 +477,7 @@ public class AppOpsService extends IAppOpsService.Stub { try { mCallback.asBinder().linkToDeath(this, 0); } catch (RemoteException e) { + /*ignored*/ } } @@ -524,6 +527,7 @@ public class AppOpsService extends IAppOpsService.Stub { try { mCallback.asBinder().linkToDeath(this, 0); } catch (RemoteException e) { + /*ignored*/ } } @@ -552,6 +556,50 @@ public class AppOpsService extends IAppOpsService.Stub { } } + final class NotedCallback implements DeathRecipient { + final IAppOpsNotedCallback mCallback; + final int mWatchingUid; + final int mCallingUid; + final int mCallingPid; + + NotedCallback(IAppOpsNotedCallback callback, int watchingUid, int callingUid, + int callingPid) { + mCallback = callback; + mWatchingUid = watchingUid; + mCallingUid = callingUid; + mCallingPid = callingPid; + try { + mCallback.asBinder().linkToDeath(this, 0); + } catch (RemoteException e) { + /*ignored*/ + } + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(128); + sb.append("NotedCallback{"); + sb.append(Integer.toHexString(System.identityHashCode(this))); + sb.append(" watchinguid="); + UserHandle.formatUid(sb, mWatchingUid); + sb.append(" from uid="); + UserHandle.formatUid(sb, mCallingUid); + sb.append(" pid="); + sb.append(mCallingPid); + sb.append('}'); + return sb.toString(); + } + + void destroy() { + mCallback.asBinder().unlinkToDeath(this, 0); + } + + @Override + public void binderDied() { + stopWatchingNoted(mCallback); + } + } + final ArrayMap<IBinder, ClientState> mClients = new ArrayMap<>(); final class ClientState extends Binder implements DeathRecipient { @@ -1629,7 +1677,7 @@ public class AppOpsService extends IAppOpsService.Stub { UidState uidState = getUidStateLocked(uid, false); if (uidState != null && uidState.opModes != null && uidState.opModes.indexOfKey(code) >= 0) { - return uidState.opModes.get(code); + return uidState.evalMode(uidState.opModes.get(code)); } Op op = getOpLocked(code, uid, packageName, false, true, false); if (op == null) { @@ -1795,12 +1843,16 @@ public class AppOpsService extends IAppOpsService.Stub { final Ops ops = getOpsRawLocked(uid, packageName, true /* edit */, false /* uidMismatchExpected */); if (ops == null) { + scheduleOpNotedIfNeededLocked(code, uid, packageName, + AppOpsManager.MODE_IGNORED); if (DEBUG) Slog.d(TAG, "noteOperation: no op for code " + code + " uid " + uid + " package " + packageName); return AppOpsManager.MODE_ERRORED; } final Op op = getOpLocked(ops, code, true); if (isOpRestrictedLocked(uid, code, packageName)) { + scheduleOpNotedIfNeededLocked(code, uid, packageName, + AppOpsManager.MODE_IGNORED); return AppOpsManager.MODE_IGNORED; } final UidState uidState = ops.uidState; @@ -1820,6 +1872,7 @@ public class AppOpsService extends IAppOpsService.Stub { + switchCode + " (" + code + ") uid " + uid + " package " + packageName); op.rejectTime[uidState.state] = System.currentTimeMillis(); + scheduleOpNotedIfNeededLocked(code, uid, packageName, uidMode); return uidMode; } } else { @@ -1830,6 +1883,7 @@ public class AppOpsService extends IAppOpsService.Stub { + switchCode + " (" + code + ") uid " + uid + " package " + packageName); op.rejectTime[uidState.state] = System.currentTimeMillis(); + scheduleOpNotedIfNeededLocked(op.op, uid, packageName, mode); return mode; } } @@ -1839,6 +1893,8 @@ public class AppOpsService extends IAppOpsService.Stub { op.rejectTime[uidState.state] = 0; op.proxyUid = proxyUid; op.proxyPackageName = proxyPackageName; + scheduleOpNotedIfNeededLocked(code, uid, packageName, + AppOpsManager.MODE_ALLOWED); return AppOpsManager.MODE_ALLOWED; } } @@ -1886,10 +1942,50 @@ public class AppOpsService extends IAppOpsService.Stub { } final int callbackCount = activeCallbacks.size(); for (int i = 0; i < callbackCount; i++) { - // Apps ops are mapped to a singleton - if (i == 0) { - activeCallbacks.valueAt(i).destroy(); - } + activeCallbacks.valueAt(i).destroy(); + } + } + } + + @Override + public void startWatchingNoted(@NonNull int[] ops, @NonNull IAppOpsNotedCallback callback) { + int watchedUid = Process.INVALID_UID; + final int callingUid = Binder.getCallingUid(); + final int callingPid = Binder.getCallingPid(); + if (mContext.checkCallingOrSelfPermission(Manifest.permission.WATCH_APPOPS) + != PackageManager.PERMISSION_GRANTED) { + watchedUid = callingUid; + } + Preconditions.checkArgument(!ArrayUtils.isEmpty(ops), "Ops cannot be null or empty"); + Preconditions.checkArrayElementsInRange(ops, 0, AppOpsManager._NUM_OP - 1, + "Invalid op code in: " + Arrays.toString(ops)); + Preconditions.checkNotNull(callback, "Callback cannot be null"); + synchronized (this) { + SparseArray<NotedCallback> callbacks = mNotedWatchers.get(callback.asBinder()); + if (callbacks == null) { + callbacks = new SparseArray<>(); + mNotedWatchers.put(callback.asBinder(), callbacks); + } + final NotedCallback notedCallback = new NotedCallback(callback, watchedUid, + callingUid, callingPid); + for (int op : ops) { + callbacks.put(op, notedCallback); + } + } + } + + @Override + public void stopWatchingNoted(IAppOpsNotedCallback callback) { + Preconditions.checkNotNull(callback, "Callback cannot be null"); + synchronized (this) { + final SparseArray<NotedCallback> notedCallbacks = + mNotedWatchers.remove(callback.asBinder()); + if (notedCallbacks == null) { + return; + } + final int callbackCount = notedCallbacks.size(); + for (int i = 0; i < callbackCount; i++) { + notedCallbacks.valueAt(i).destroy(); } } } @@ -2052,6 +2148,51 @@ public class AppOpsService extends IAppOpsService.Stub { } } + private void scheduleOpNotedIfNeededLocked(int code, int uid, String packageName, + int result) { + ArraySet<NotedCallback> dispatchedCallbacks = null; + final int callbackListCount = mNotedWatchers.size(); + for (int i = 0; i < callbackListCount; i++) { + final SparseArray<NotedCallback> callbacks = mNotedWatchers.valueAt(i); + final NotedCallback callback = callbacks.get(code); + if (callback != null) { + if (callback.mWatchingUid >= 0 && callback.mWatchingUid != uid) { + continue; + } + if (dispatchedCallbacks == null) { + dispatchedCallbacks = new ArraySet<>(); + } + dispatchedCallbacks.add(callback); + } + } + if (dispatchedCallbacks == null) { + return; + } + mHandler.sendMessage(PooledLambda.obtainMessage( + AppOpsService::notifyOpChecked, + this, dispatchedCallbacks, code, uid, packageName, result)); + } + + private void notifyOpChecked(ArraySet<NotedCallback> callbacks, + int code, int uid, String packageName, int result) { + // There are components watching for checks in our process. The callbacks in + // these components may require permissions our remote caller does not have. + final long identity = Binder.clearCallingIdentity(); + try { + final int callbackCount = callbacks.size(); + for (int i = 0; i < callbackCount; i++) { + final NotedCallback callback = callbacks.valueAt(i); + try { + callback.mCallback.opNoted(code, uid, packageName, result); + } catch (RemoteException e) { + /* do nothing */ + } + } + } finally { + Binder.restoreCallingIdentity(identity); + } + } + @Override public int permissionToOpCode(String permission) { if (permission == null) { @@ -3463,6 +3604,46 @@ public class AppOpsService extends IAppOpsService.Stub { pw.println(cb); } } + if (mNotedWatchers.size() > 0 && dumpMode < 0) { + needSep = true; + boolean printedHeader = false; + for (int i = 0; i < mNotedWatchers.size(); i++) { + final SparseArray<NotedCallback> notedWatchers = mNotedWatchers.valueAt(i); + if (notedWatchers.size() <= 0) { + continue; + } + final NotedCallback cb = notedWatchers.valueAt(0); + if (dumpOp >= 0 && notedWatchers.indexOfKey(dumpOp) < 0) { + continue; + } + if (dumpPackage != null && cb.mWatchingUid >= 0 + && dumpUid != UserHandle.getAppId(cb.mWatchingUid)) { + continue; + } + if (!printedHeader) { + pw.println(" All op noted watchers:"); + printedHeader = true; + } + pw.print(" "); + pw.print(Integer.toHexString(System.identityHashCode( + mNotedWatchers.keyAt(i)))); + pw.println(" ->"); + pw.print(" ["); + final int opCount = notedWatchers.size(); + for (i = 0; i < opCount; i++) { + if (i > 0) { + pw.print(' '); + } + pw.print(AppOpsManager.opToName(notedWatchers.keyAt(i))); + if (i < opCount - 1) { + pw.print(','); + } + } + pw.println("]"); + pw.print(" "); + pw.println(cb); + } + } if (mClients.size() > 0 && dumpMode < 0) { needSep = true; boolean printedHeader = false; diff --git a/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java index a07939e86325..7bbc543a6aab 100644 --- a/services/core/java/com/android/server/BluetoothManagerService.java +++ b/services/core/java/com/android/server/BluetoothManagerService.java @@ -76,6 +76,7 @@ import java.util.HashMap; import java.util.LinkedList; import java.util.Locale; import java.util.Map; +import java.util.NoSuchElementException; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.locks.ReentrantReadWriteLock; @@ -1276,7 +1277,11 @@ class BluetoothManagerService extends IBluetoothManager.Stub { if (mService == null) { return; } - mService.unlinkToDeath(this, 0); + try { + mService.unlinkToDeath(this, 0); + } catch (NoSuchElementException e) { + Log.e(TAG, "error unlinking to death", e); + } mService = null; mClassName = null; diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 89194e43bf95..66ceae432c6e 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -5714,7 +5714,6 @@ public class ConnectivityService extends IConnectivityManager.Stub // This should never fail. Specifying an already in use NetID will cause failure. if (networkAgent.isVPN()) { mNMS.createVirtualNetwork(networkAgent.network.netId, - !networkAgent.linkProperties.getDnsServers().isEmpty(), (networkAgent.networkMisc == null || !networkAgent.networkMisc.allowBypass)); } else { diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java index 4e8ef54a6465..d33b61757141 100644 --- a/services/core/java/com/android/server/LocationManagerService.java +++ b/services/core/java/com/android/server/LocationManagerService.java @@ -302,12 +302,14 @@ public class LocationManagerService extends ILocationManager.Stub { AppOpsManager.OnOpChangedListener callback = new AppOpsManager.OnOpChangedInternalListener() { public void onOpChanged(int op, String packageName) { - synchronized (mLock) { - for (Receiver receiver : mReceivers.values()) { - receiver.updateMonitoring(true); - } - applyAllProviderRequirementsLocked(); - } + mLocationHandler.post(() -> { + synchronized (mLock) { + for (Receiver receiver : mReceivers.values()) { + receiver.updateMonitoring(true); + } + applyAllProviderRequirementsLocked(); + } + }); } }; mAppOps.startWatchingMode(AppOpsManager.OP_COARSE_LOCATION, null, diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java index 8869af4e334a..b0ca2df20f1f 100644 --- a/services/core/java/com/android/server/NetworkManagementService.java +++ b/services/core/java/com/android/server/NetworkManagementService.java @@ -2314,11 +2314,11 @@ public class NetworkManagementService extends INetworkManagementService.Stub } @Override - public void createVirtualNetwork(int netId, boolean hasDNS, boolean secure) { + public void createVirtualNetwork(int netId, boolean secure) { mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); try { - mNetdService.networkCreateVpn(netId, hasDNS, secure); + mNetdService.networkCreateVpn(netId, secure); } 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 6c62725bb5bd..7adcabac26ee 100644 --- a/services/core/java/com/android/server/StorageManagerService.java +++ b/services/core/java/com/android/server/StorageManagerService.java @@ -1504,6 +1504,10 @@ class StorageManagerService extends IStorageManager.Stub public StorageManagerService(Context context) { sSelf = this; + // Snapshot feature flag used for this boot + SystemProperties.set(StorageManager.PROP_ISOLATED_STORAGE_SNAPSHOT, Boolean.toString( + SystemProperties.getBoolean(StorageManager.PROP_ISOLATED_STORAGE, false))); + mContext = context; mCallbacks = new Callbacks(FgThread.get().getLooper()); mLockPatternUtils = new LockPatternUtils(mContext); diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java index 1ef3e942ca23..d07cf78e47ed 100644 --- a/services/core/java/com/android/server/TelephonyRegistry.java +++ b/services/core/java/com/android/server/TelephonyRegistry.java @@ -214,6 +214,10 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { private PreciseCallState mPreciseCallState = new PreciseCallState(); + private int mCallDisconnectCause = DisconnectCause.NOT_VALID; + + private int mCallPreciseDisconnectCause = PreciseDisconnectCause.NOT_VALID; + private boolean mCarrierNetworkChangeState = false; private PhoneCapability mPhoneCapability = null; @@ -714,6 +718,14 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { remove(r.binder); } } + if ((events & PhoneStateListener.LISTEN_CALL_DISCONNECT_CAUSES) != 0) { + try { + r.callback.onCallDisconnectCauseChanged(mCallDisconnectCause, + mCallPreciseDisconnectCause); + } catch (RemoteException ex) { + remove(r.binder); + } + } if ((events & PhoneStateListener.LISTEN_PRECISE_DATA_CONNECTION_STATE) != 0) { try { r.callback.onPreciseDataConnectionStateChanged( @@ -1491,9 +1503,8 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { } handleRemoveListLocked(); } - broadcastPreciseCallStateChanged(ringingCallState, foregroundCallState, backgroundCallState, - DisconnectCause.NOT_VALID, - PreciseDisconnectCause.NOT_VALID); + broadcastPreciseCallStateChanged(ringingCallState, foregroundCallState, + backgroundCallState); } public void notifyDisconnectCause(int disconnectCause, int preciseDisconnectCause) { @@ -1501,12 +1512,14 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { return; } synchronized (mRecords) { - mPreciseCallState = new PreciseCallState(mRingingCallState, mForegroundCallState, - mBackgroundCallState, disconnectCause, preciseDisconnectCause); + mCallDisconnectCause = disconnectCause; + mCallPreciseDisconnectCause = preciseDisconnectCause; for (Record r : mRecords) { - if (r.matchPhoneStateListenerEvent(PhoneStateListener.LISTEN_PRECISE_CALL_STATE)) { + if (r.matchPhoneStateListenerEvent(PhoneStateListener + .LISTEN_CALL_DISCONNECT_CAUSES)) { try { - r.callback.onPreciseCallStateChanged(mPreciseCallState); + r.callback.onCallDisconnectCauseChanged(mCallDisconnectCause, + mCallPreciseDisconnectCause); } catch (RemoteException ex) { mRemoveList.add(r.binder); } @@ -1514,8 +1527,6 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { } handleRemoveListLocked(); } - broadcastPreciseCallStateChanged(mRingingCallState, mForegroundCallState, - mBackgroundCallState, disconnectCause, preciseDisconnectCause); } public void notifyPreciseDataConnectionFailed(String reason, String apnType, @@ -1737,6 +1748,8 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { } pw.println("mPreciseDataConnectionState=" + mPreciseDataConnectionState); pw.println("mPreciseCallState=" + mPreciseCallState); + pw.println("mCallDisconnectCause=" + mCallDisconnectCause); + pw.println("mCallPreciseDisconnectCause=" + mCallPreciseDisconnectCause); pw.println("mCarrierNetworkChangeState=" + mCarrierNetworkChangeState); pw.println("mRingingCallState=" + mRingingCallState); pw.println("mForegroundCallState=" + mForegroundCallState); @@ -1912,13 +1925,11 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { } private void broadcastPreciseCallStateChanged(int ringingCallState, int foregroundCallState, - int backgroundCallState, int disconnectCause, int preciseDisconnectCause) { + int backgroundCallState) { Intent intent = new Intent(TelephonyManager.ACTION_PRECISE_CALL_STATE_CHANGED); intent.putExtra(TelephonyManager.EXTRA_RINGING_CALL_STATE, ringingCallState); intent.putExtra(TelephonyManager.EXTRA_FOREGROUND_CALL_STATE, foregroundCallState); intent.putExtra(TelephonyManager.EXTRA_BACKGROUND_CALL_STATE, backgroundCallState); - intent.putExtra(TelephonyManager.EXTRA_DISCONNECT_CAUSE, disconnectCause); - intent.putExtra(TelephonyManager.EXTRA_PRECISE_DISCONNECT_CAUSE, preciseDisconnectCause); mContext.sendBroadcastAsUser(intent, UserHandle.ALL, android.Manifest.permission.READ_PRECISE_PHONE_STATE); } @@ -2006,6 +2017,11 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { + "LISTEN_PREFERRED_DATA_SUBID_CHANGE"); } + if ((events & PhoneStateListener.LISTEN_CALL_DISCONNECT_CAUSES) != 0) { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.READ_PRECISE_PHONE_STATE, null); + } + return true; } diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index 5afb90d681af..fe632e52cae6 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -1665,7 +1665,7 @@ public final class ActiveServices { AppBindRecord b = s.retrieveAppBindingLocked(service, callerApp); ConnectionRecord c = new ConnectionRecord(b, activity, connection, flags, clientLabel, clientIntent, - callerApp.uid, callerApp.processName); + callerApp.uid, callerApp.processName, callingPackage); IBinder binder = connection.asBinder(); ArrayList<ConnectionRecord> clist = s.connections.get(binder); diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index cb7fd8020a40..3bfd363de836 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -2330,9 +2330,16 @@ public class ActivityManagerService extends IActivityManager.Stub fos.close(); long[] rssAfter = Process.getRss(pid); long end = SystemClock.uptimeMillis(); + long time = end - start; EventLog.writeEvent(EventLogTags.AM_COMPACT, pid, name, action, rssBefore[0], rssBefore[1], rssBefore[2], rssBefore[3], - rssAfter[0], rssAfter[1], rssAfter[2], rssAfter[3], end-start); + rssAfter[0], rssAfter[1], rssAfter[2], rssAfter[3], time, + lastCompactAction, lastCompactTime, msg.arg1, msg.arg2); + StatsLog.write(StatsLog.APP_COMPACTED, pid, name, pendingAction, + rssBefore[0], rssBefore[1], rssBefore[2], rssBefore[3], + rssAfter[0], rssAfter[1], rssAfter[2], rssAfter[3], time, + lastCompactAction, lastCompactTime, msg.arg1, + ActivityManager.processStateAmToProto(msg.arg2)); synchronized(ActivityManagerService.this) { proc.lastCompactTime = end; proc.lastCompactAction = pendingAction; @@ -6285,7 +6292,7 @@ public class ActivityManagerService extends IActivityManager.Stub ContentProviderConnection incProviderCountLocked(ProcessRecord r, final ContentProviderRecord cpr, IBinder externalProcessToken, int callingUid, - String callingTag, boolean stable) { + String callingPackage, String callingTag, boolean stable) { if (r != null) { for (int i=0; i<r.conProviders.size(); i++) { ContentProviderConnection conn = r.conProviders.get(i); @@ -6305,7 +6312,7 @@ public class ActivityManagerService extends IActivityManager.Stub return conn; } } - ContentProviderConnection conn = new ContentProviderConnection(cpr, r); + ContentProviderConnection conn = new ContentProviderConnection(cpr, r, callingPackage); conn.startAssociationIfNeeded(); if (stable) { conn.stableCount = 1; @@ -6412,8 +6419,8 @@ public class ActivityManagerService extends IActivityManager.Stub } private ContentProviderHolder getContentProviderImpl(IApplicationThread caller, - String name, IBinder token, int callingUid, String callingTag, boolean stable, - int userId) { + String name, IBinder token, int callingUid, String callingPackage, String callingTag, + boolean stable, int userId) { ContentProviderRecord cpr; ContentProviderConnection conn = null; ProviderInfo cpi = null; @@ -6536,7 +6543,8 @@ public class ActivityManagerService extends IActivityManager.Stub // In this case the provider instance already exists, so we can // return it right away. - conn = incProviderCountLocked(r, cpr, token, callingUid, callingTag, stable); + conn = incProviderCountLocked(r, cpr, token, callingUid, callingPackage, callingTag, + stable); if (conn != null && (conn.stableCount+conn.unstableCount) == 1) { if (cpr.proc != null && r.setAdj <= ProcessList.PERCEPTIBLE_APP_ADJ) { // If this is a perceptible app accessing the provider, @@ -6783,7 +6791,8 @@ public class ActivityManagerService extends IActivityManager.Stub } mProviderMap.putProviderByName(name, cpr); - conn = incProviderCountLocked(r, cpr, token, callingUid, callingTag, stable); + conn = incProviderCountLocked(r, cpr, token, callingUid, callingPackage, callingTag, + stable); if (conn != null) { conn.waiting = true; } @@ -6925,7 +6934,8 @@ public class ActivityManagerService extends IActivityManager.Stub @Override public final ContentProviderHolder getContentProvider( - IApplicationThread caller, String name, int userId, boolean stable) { + IApplicationThread caller, String callingPackage, String name, int userId, + boolean stable) { enforceNotIsolatedCaller("getContentProvider"); if (caller == null) { String msg = "null IApplicationThread when getting content provider " @@ -6935,8 +6945,14 @@ public class ActivityManagerService extends IActivityManager.Stub } // The incoming user check is now handled in checkContentProviderPermissionLocked() to deal // with cross-user grant. - return getContentProviderImpl(caller, name, null, Binder.getCallingUid(), null, stable, - userId); + final int callingUid = Binder.getCallingUid(); + if (callingPackage != null && mAppOpsService.checkPackage(callingUid, callingPackage) + != AppOpsManager.MODE_ALLOWED) { + throw new SecurityException("Given calling package " + callingPackage + + " does not match caller's uid " + callingUid); + } + return getContentProviderImpl(caller, name, null, callingUid, callingPackage, + null, stable, userId); } public ContentProviderHolder getContentProviderExternal( @@ -6951,7 +6967,8 @@ public class ActivityManagerService extends IActivityManager.Stub private ContentProviderHolder getContentProviderExternalUnchecked(String name, IBinder token, int callingUid, String callingTag, int userId) { - return getContentProviderImpl(null, name, token, callingUid, callingTag, true, userId); + return getContentProviderImpl(null, name, token, callingUid, null, callingTag, + true, userId); } /** @@ -17069,12 +17086,16 @@ public class ActivityManagerService extends IActivityManager.Stub app.curAdj == ProcessList.HOME_APP_ADJ)) { app.reqCompactAction = COMPACT_PROCESS_SOME; mPendingCompactionProcesses.add(app); - mCompactionHandler.sendEmptyMessage(COMPACT_PROCESS_MSG); + mCompactionHandler.sendMessage( + mCompactionHandler.obtainMessage( + COMPACT_PROCESS_MSG, app.curAdj, app.setProcState)); } else if (app.setAdj < ProcessList.CACHED_APP_MIN_ADJ && app.curAdj >= ProcessList.CACHED_APP_MIN_ADJ) { app.reqCompactAction = COMPACT_PROCESS_FULL; mPendingCompactionProcesses.add(app); - mCompactionHandler.sendEmptyMessage(COMPACT_PROCESS_MSG); + mCompactionHandler.sendMessage( + mCompactionHandler.obtainMessage( + COMPACT_PROCESS_MSG, app.curAdj, app.setProcState)); } } ProcessList.setOomAdj(app.pid, app.uid, app.curAdj); diff --git a/services/core/java/com/android/server/am/ConnectionRecord.java b/services/core/java/com/android/server/am/ConnectionRecord.java index aa76b3d6b856..af1031e5b887 100644 --- a/services/core/java/com/android/server/am/ConnectionRecord.java +++ b/services/core/java/com/android/server/am/ConnectionRecord.java @@ -43,6 +43,7 @@ final class ConnectionRecord { final PendingIntent clientIntent; // How to launch the client. final int clientUid; // The identity of this connection's client final String clientProcessName; // The source process of this connection's client + final String clientPackageName; // The source package of this connection's client public AssociationState.SourceState association; // Association tracking String stringName; // Caching of toString. boolean serviceDead; // Well is it? @@ -96,7 +97,7 @@ final class ConnectionRecord { ActivityServiceConnectionsHolder<ConnectionRecord> _activity, IServiceConnection _conn, int _flags, int _clientLabel, PendingIntent _clientIntent, - int _clientUid, String _clientProcessName) { + int _clientUid, String _clientProcessName, String _clientPackageName) { binding = _binding; activity = _activity; conn = _conn; @@ -105,6 +106,7 @@ final class ConnectionRecord { clientIntent = _clientIntent; clientUid = _clientUid; clientProcessName = _clientProcessName; + clientPackageName = _clientPackageName; } public void startAssociationIfNeeded() { @@ -125,7 +127,7 @@ final class ConnectionRecord { } else { association = holder.pkg.getAssociationStateLocked(holder.state, binding.service.instanceName.getClassName()).startSource(clientUid, - clientProcessName); + clientProcessName, clientPackageName); } } diff --git a/services/core/java/com/android/server/am/ContentProviderConnection.java b/services/core/java/com/android/server/am/ContentProviderConnection.java index f2d4f739c8bf..5f184c2dabee 100644 --- a/services/core/java/com/android/server/am/ContentProviderConnection.java +++ b/services/core/java/com/android/server/am/ContentProviderConnection.java @@ -32,6 +32,7 @@ import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; public final class ContentProviderConnection extends Binder { public final ContentProviderRecord provider; public final ProcessRecord client; + public final String clientPackage; public AssociationState.SourceState association; public final long createTime; public int stableCount; @@ -46,9 +47,11 @@ public final class ContentProviderConnection extends Binder { public int numStableIncs; public int numUnstableIncs; - public ContentProviderConnection(ContentProviderRecord _provider, ProcessRecord _client) { + public ContentProviderConnection(ContentProviderRecord _provider, ProcessRecord _client, + String _clientPackage) { provider = _provider; client = _client; + clientPackage = _clientPackage; createTime = SystemClock.elapsedRealtime(); } @@ -69,7 +72,8 @@ public final class ContentProviderConnection extends Binder { + provider.name.toShortString() + ": proc=" + provider.proc); } else { association = holder.pkg.getAssociationStateLocked(holder.state, - provider.name.getClassName()).startSource(client.uid, client.processName); + provider.name.getClassName()).startSource(client.uid, client.processName, + clientPackage); } } diff --git a/services/core/java/com/android/server/am/ContentProviderRecord.java b/services/core/java/com/android/server/am/ContentProviderRecord.java index 2fc4adc2dc6c..46dfc7c60fd9 100644 --- a/services/core/java/com/android/server/am/ContentProviderRecord.java +++ b/services/core/java/com/android/server/am/ContentProviderRecord.java @@ -305,7 +305,7 @@ final class ContentProviderRecord implements ComponentName.WithComponentName { } else { mAssociation = holder.pkg.getAssociationStateLocked(holder.state, provider.name.getClassName()).startSource(mOwningUid, - mOwningProcessName); + mOwningProcessName, null); } } diff --git a/services/core/java/com/android/server/am/EventLogTags.logtags b/services/core/java/com/android/server/am/EventLogTags.logtags index fa7a4c532f42..a71f6af80bec 100644 --- a/services/core/java/com/android/server/am/EventLogTags.logtags +++ b/services/core/java/com/android/server/am/EventLogTags.logtags @@ -138,4 +138,4 @@ option java_package com.android.server.am 30061 am_remove_task (Task ID|1|5), (Stack ID|1|5) # The task is being compacted -30063 am_compact (Pid|1|5),(Process Name|3),(Action|3),(BeforeRssTotal|2|2),(BeforeRssFile|2|2),(BeforeRssAnon|2|2),(BeforeRssSwap|2|2),(AfterRssTotal|2|2),(AfterRssFile|2|2),(AfterRssAnon|2|2),(AfterRssSwap|2|2),(Time|2|3)
\ No newline at end of file +30063 am_compact (Pid|1|5),(Process Name|3),(Action|3),(BeforeRssTotal|2|2),(BeforeRssFile|2|2),(BeforeRssAnon|2|2),(BeforeRssSwap|2|2),(AfterRssTotal|2|2),(AfterRssFile|2|2),(AfterRssAnon|2|2),(AfterRssSwap|2|2),(Time|2|3),(LastAction|1|2),(LastActionTimestamp|2|3),(setAdj|1|2),(procState|1|2) diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java index 4c4a09060706..d5ede5b63edc 100644 --- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java +++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java @@ -19,8 +19,10 @@ package com.android.server.am; import android.content.ContentResolver; import android.database.ContentObserver; import android.net.Uri; +import android.os.AsyncTask; import android.os.Build; import android.os.SystemProperties; +import android.provider.DeviceConfig; import android.provider.Settings; import android.text.TextUtils; import android.util.Slog; @@ -61,13 +63,18 @@ class SettingsToPropertiesMapper { // Add the global setting you want to push to native level as experiment flag into this list. // // NOTE: please grant write permission system property prefix - // with format persist.experiment.[experiment_category_name]. in system_server.te and grant read - // permission in the corresponding .te file your feature belongs to. + // with format persist.device_config.global_settings.[flag_name] in system_server.te and grant + // read permission in the corresponding .te file your feature belongs to. @VisibleForTesting static final String[] sGlobalSettings = new String[] { Settings.Global.NATIVE_FLAGS_HEALTH_CHECK_ENABLED, }; + // All the flags under the listed DeviceConfig scopes will be synced to native level. + // + // NOTE: please grant write permission system property prefix + // with format persist.device_config.[device_config_scope]. in system_server.te and grant read + // permission in the corresponding .te file your feature belongs to. @VisibleForTesting static final String[] sDeviceConfigScopes = new String[] { }; @@ -104,19 +111,31 @@ class SettingsToPropertiesMapper { ContentObserver co = new ContentObserver(null) { @Override public void onChange(boolean selfChange) { - updatePropertyFromSetting(globalSetting, propName, true); + updatePropertyFromSetting(globalSetting, propName); } }; // only updating on starting up when no native flags reset is performed during current // booting. if (!isNativeFlagsResetPerformed()) { - updatePropertyFromSetting(globalSetting, propName, true); + updatePropertyFromSetting(globalSetting, propName); } mContentResolver.registerContentObserver(settingUri, false, co); } - // TODO: address sDeviceConfigScopes after DeviceConfig APIs are available. + for (String deviceConfigScope : mDeviceConfigScopes) { + DeviceConfig.addOnPropertyChangedListener( + deviceConfigScope, + AsyncTask.THREAD_POOL_EXECUTOR, + (String scope, String name, String value) -> { + String propertyName = makePropertyName(scope, name); + if (propertyName == null) { + log("unable to construct system property for " + scope + "/" + name); + return; + } + setProperty(propertyName, value); + }); + } } public static SettingsToPropertiesMapper start(ContentResolver contentResolver) { @@ -184,15 +203,6 @@ class SettingsToPropertiesMapper { return propertyName; } - private String getSetting(String name, boolean isGlobalSetting) { - if (isGlobalSetting) { - return Settings.Global.getString(mContentResolver, name); - } else { - // TODO: complete the code after DeviceConfig APIs implemented. - return null; - } - } - private void setProperty(String key, String value) { // Check if need to clear the property if (value == null) { @@ -259,8 +269,8 @@ class SettingsToPropertiesMapper { } @VisibleForTesting - void updatePropertyFromSetting(String settingName, String propName, boolean isGlobalSetting) { - String settingValue = getSetting(settingName, isGlobalSetting); + void updatePropertyFromSetting(String settingName, String propName) { + String settingValue = Settings.Global.getString(mContentResolver, settingName); setProperty(propName, settingValue); } } diff --git a/services/core/java/com/android/server/connectivity/NetdEventListenerService.java b/services/core/java/com/android/server/connectivity/NetdEventListenerService.java index 422f5566eee1..e40949b785d9 100644 --- a/services/core/java/com/android/server/connectivity/NetdEventListenerService.java +++ b/services/core/java/com/android/server/connectivity/NetdEventListenerService.java @@ -217,6 +217,19 @@ public class NetdEventListenerService extends INetdEventListener.Stub { @Override // Called concurrently by multiple binder threads. // This method must not block or perform long-running operations. + public synchronized void onNat64PrefixEvent(int netId, + boolean added, String prefixString, int prefixLength) + throws RemoteException { + for (INetdEventCallback callback : mNetdEventCallbackList) { + if (callback != null) { + callback.onNat64PrefixEvent(netId, added, prefixString, prefixLength); + } + } + } + + @Override + // Called concurrently by multiple binder threads. + // This method must not block or perform long-running operations. public synchronized void onPrivateDnsValidationEvent(int netId, String ipAddress, String hostname, boolean validated) throws RemoteException { diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java index 78b3c15500ea..52ecccaa7367 100644 --- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java +++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java @@ -17,6 +17,13 @@ package com.android.server.display; import android.annotation.Nullable; +import android.app.ActivityManager; +import android.app.ActivityManager.StackInfo; +import android.app.ActivityTaskManager; +import android.app.IActivityTaskManager; +import android.app.TaskStackListener; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManagerInternal; import android.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorEventListener; @@ -27,6 +34,8 @@ import android.os.Handler; import android.os.Looper; import android.os.Message; import android.os.PowerManager; +import android.os.Process; +import android.os.RemoteException; import android.os.SystemClock; import android.os.Trace; import android.util.EventLog; @@ -34,14 +43,15 @@ import android.util.MathUtils; import android.util.Slog; import android.util.TimeUtils; +import com.android.internal.os.BackgroundThread; import com.android.server.EventLogTags; +import com.android.server.LocalServices; import java.io.PrintWriter; class AutomaticBrightnessController { private static final String TAG = "AutomaticBrightnessController"; - private static final boolean DEBUG = false; private static final boolean DEBUG_PRETEND_LIGHT_SENSOR_ABSENT = false; // If true, enables the use of the screen auto-brightness adjustment setting. @@ -66,6 +76,8 @@ class AutomaticBrightnessController { private static final int MSG_UPDATE_AMBIENT_LUX = 1; private static final int MSG_BRIGHTNESS_ADJUSTMENT_SAMPLE = 2; private static final int MSG_INVALIDATE_SHORT_TERM_MODEL = 3; + private static final int MSG_UPDATE_FOREGROUND_APP = 4; + private static final int MSG_UPDATE_FOREGROUND_APP_SYNC = 5; // Length of the ambient light horizon used to calculate the long term estimate of ambient // light. @@ -126,6 +138,8 @@ class AutomaticBrightnessController { private final HysteresisLevels mAmbientBrightnessThresholds; private final HysteresisLevels mScreenBrightnessThresholds; + private boolean mLoggingEnabled; + // Amount of time to delay auto-brightness after screen on while waiting for // the light sensor to warm-up in milliseconds. // May be 0 if no warm-up is required. @@ -192,6 +206,19 @@ class AutomaticBrightnessController { private float mShortTermModelAnchor; private float SHORT_TERM_MODEL_THRESHOLD_RATIO = 0.6f; + // Context-sensitive brightness configurations require keeping track of the foreground app's + // package name and category, which is done by registering a TaskStackListener to call back to + // us onTaskStackChanged, and then using the ActivityTaskManager to get the foreground app's + // package namd and PackageManager to get its category (so might as well cache them). + private int mUserId; + private String mForegroundAppPackageName; + private String mPendingForegroundAppPackageName; + private @ApplicationInfo.Category int mForegroundAppCategory; + private @ApplicationInfo.Category int mPendingForegroundAppCategory; + private TaskStackListenerImpl mTaskStackListener; + private IActivityTaskManager mActivityTaskManager; + private PackageManagerInternal mPackageManagerInternal; + public AutomaticBrightnessController(Callbacks callbacks, Looper looper, SensorManager sensorManager, BrightnessMappingStrategy mapper, int lightSensorWarmUpTime, int brightnessMin, int brightnessMax, float dozeScaleFactor, @@ -226,6 +253,42 @@ class AutomaticBrightnessController { if (!DEBUG_PRETEND_LIGHT_SENSOR_ABSENT) { mLightSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_LIGHT); } + + mUserId = ActivityManager.getCurrentUser(); + mActivityTaskManager = ActivityTaskManager.getService(); + mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class); + mTaskStackListener = new TaskStackListenerImpl(); + mForegroundAppPackageName = null; + mPendingForegroundAppPackageName = null; + mForegroundAppCategory = ApplicationInfo.CATEGORY_UNDEFINED; + mPendingForegroundAppCategory = ApplicationInfo.CATEGORY_UNDEFINED; + } + + /** + * Enable/disable logging. + * + * @param loggingEnabled + * Whether logging should be on/off. + * + * @return Whether the method succeeded or not. + */ + public boolean setLoggingEnabled(boolean loggingEnabled) { + if (mLoggingEnabled == loggingEnabled) { + return false; + } + mBrightnessMapper.setLoggingEnabled(loggingEnabled); + mLoggingEnabled = loggingEnabled; + return true; + } + + /** + * Update the current user's ID. + * + * @param userId + * The current user's ID. + */ + public void onSwitchUser(int userId) { + mUserId = userId; } public int getAutomaticScreenBrightness() { @@ -290,7 +353,7 @@ class AutomaticBrightnessController { } final int oldPolicy = mDisplayPolicy; mDisplayPolicy = policy; - if (DEBUG) { + if (mLoggingEnabled) { Slog.d(TAG, "Display policy transitioning from " + oldPolicy + " to " + policy); } if (!isInteractivePolicy(policy) && isInteractivePolicy(oldPolicy)) { @@ -317,7 +380,7 @@ class AutomaticBrightnessController { mBrightnessMapper.addUserDataPoint(mAmbientLux, brightness); mShortTermModelValid = true; mShortTermModelAnchor = mAmbientLux; - if (DEBUG) { + if (mLoggingEnabled) { Slog.d(TAG, "ShortTermModel: anchor=" + mShortTermModelAnchor); } return true; @@ -330,7 +393,7 @@ class AutomaticBrightnessController { } private void invalidateShortTermModel() { - if (DEBUG) { + if (mLoggingEnabled) { Slog.d(TAG, "ShortTermModel: invalidate user data"); } mShortTermModelValid = false; @@ -383,7 +446,11 @@ class AutomaticBrightnessController { pw.println(" mBrightnessAdjustmentSampleOldLux=" + mBrightnessAdjustmentSampleOldLux); pw.println(" mBrightnessAdjustmentSampleOldBrightness=" + mBrightnessAdjustmentSampleOldBrightness); - pw.println(" mShortTermModelValid=" + mShortTermModelValid); + pw.println(" mUserId=" + mUserId); + pw.println(" mForegroundAppPackageName=" + mForegroundAppPackageName); + pw.println(" mPendingForegroundAppPackageName=" + mPendingForegroundAppPackageName); + pw.println(" mForegroundAppCategory=" + mForegroundAppCategory); + pw.println(" mPendingForegroundAppCategory=" + mPendingForegroundAppCategory); pw.println(); mBrightnessMapper.dump(pw); @@ -399,6 +466,7 @@ class AutomaticBrightnessController { mLightSensorEnabled = true; mLightSensorEnableTime = SystemClock.uptimeMillis(); mCurrentLightSensorRate = mInitialLightSensorRate; + registerForegroundAppUpdater(); mSensorManager.registerListener(mLightSensorListener, mLightSensor, mCurrentLightSensorRate * 1000, mHandler); return true; @@ -411,6 +479,7 @@ class AutomaticBrightnessController { mAmbientLightRingBuffer.clear(); mCurrentLightSensorRate = -1; mHandler.removeMessages(MSG_UPDATE_AMBIENT_LUX); + unregisterForegroundAppUpdater(); mSensorManager.unregisterListener(mLightSensorListener); } return false; @@ -441,7 +510,7 @@ class AutomaticBrightnessController { private void adjustLightSensorRate(int lightSensorRate) { // if the light sensor rate changed, update the sensor listener if (lightSensorRate != mCurrentLightSensorRate) { - if (DEBUG) { + if (mLoggingEnabled) { Slog.d(TAG, "adjustLightSensorRate: " + "previousRate=" + mCurrentLightSensorRate + ", " + "currentRate=" + lightSensorRate); @@ -458,7 +527,7 @@ class AutomaticBrightnessController { } private void setAmbientLux(float lux) { - if (DEBUG) { + if (mLoggingEnabled) { Slog.d(TAG, "setAmbientLux(" + lux + ")"); } if (lux < 0) { @@ -476,7 +545,7 @@ class AutomaticBrightnessController { final float maxAmbientLux = mShortTermModelAnchor + mShortTermModelAnchor * SHORT_TERM_MODEL_THRESHOLD_RATIO; if (minAmbientLux < mAmbientLux && mAmbientLux < maxAmbientLux) { - if (DEBUG) { + if (mLoggingEnabled) { Slog.d(TAG, "ShortTermModel: re-validate user data, ambient lux is " + minAmbientLux + " < " + mAmbientLux + " < " + maxAmbientLux); } @@ -490,7 +559,7 @@ class AutomaticBrightnessController { } private float calculateAmbientLux(long now, long horizon) { - if (DEBUG) { + if (mLoggingEnabled) { Slog.d(TAG, "calculateAmbientLux(" + now + ", " + horizon + ")"); } final int N = mAmbientLightRingBuffer.size(); @@ -509,7 +578,7 @@ class AutomaticBrightnessController { break; } } - if (DEBUG) { + if (mLoggingEnabled) { Slog.d(TAG, "calculateAmbientLux: selected endIndex=" + endIndex + ", point=(" + mAmbientLightRingBuffer.getTime(endIndex) + ", " + mAmbientLightRingBuffer.getLux(endIndex) + ")"); @@ -527,7 +596,7 @@ class AutomaticBrightnessController { final long startTime = eventTime - now; float weight = calculateWeight(startTime, endTime); float lux = mAmbientLightRingBuffer.getLux(i); - if (DEBUG) { + if (mLoggingEnabled) { Slog.d(TAG, "calculateAmbientLux: [" + startTime + ", " + endTime + "]: " + "lux=" + lux + ", " + "weight=" + weight); @@ -536,7 +605,7 @@ class AutomaticBrightnessController { sum += lux * weight; endTime = startTime; } - if (DEBUG) { + if (mLoggingEnabled) { Slog.d(TAG, "calculateAmbientLux: " + "totalWeight=" + totalWeight + ", " + "newAmbientLux=" + (sum / totalWeight)); @@ -591,7 +660,7 @@ class AutomaticBrightnessController { final long timeWhenSensorWarmedUp = mLightSensorWarmUpTimeConfig + mLightSensorEnableTime; if (time < timeWhenSensorWarmedUp) { - if (DEBUG) { + if (mLoggingEnabled) { Slog.d(TAG, "updateAmbientLux: Sensor not ready yet: " + "time=" + time + ", " + "timeWhenSensorWarmedUp=" + timeWhenSensorWarmedUp); @@ -602,7 +671,7 @@ class AutomaticBrightnessController { } setAmbientLux(calculateAmbientLux(time, AMBIENT_LIGHT_SHORT_HORIZON_MILLIS)); mAmbientLuxValid = true; - if (DEBUG) { + if (mLoggingEnabled) { Slog.d(TAG, "updateAmbientLux: Initializing: " + "mAmbientLightRingBuffer=" + mAmbientLightRingBuffer + ", " + "mAmbientLux=" + mAmbientLux); @@ -630,10 +699,10 @@ class AutomaticBrightnessController { && fastAmbientLux <= mAmbientDarkeningThreshold && nextDarkenTransition <= time)) { setAmbientLux(fastAmbientLux); - if (DEBUG) { + if (mLoggingEnabled) { Slog.d(TAG, "updateAmbientLux: " + ((fastAmbientLux > mAmbientLux) ? "Brightened" : "Darkened") + ": " - + "mAmbientBrighteningThreshold=" + mAmbientBrighteningThreshold + ", " + + "mBrighteningLuxThreshold=" + mAmbientBrighteningThreshold + ", " + "mAmbientLightRingBuffer=" + mAmbientLightRingBuffer + ", " + "mAmbientLux=" + mAmbientLux); } @@ -650,7 +719,7 @@ class AutomaticBrightnessController { // weighted ambient lux or not. nextTransitionTime = nextTransitionTime > time ? nextTransitionTime : time + mNormalLightSensorRate; - if (DEBUG) { + if (mLoggingEnabled) { Slog.d(TAG, "updateAmbientLux: Scheduling ambient lux update for " + nextTransitionTime + TimeUtils.formatUptime(nextTransitionTime)); } @@ -662,7 +731,8 @@ class AutomaticBrightnessController { return; } - float value = mBrightnessMapper.getBrightness(mAmbientLux); + float value = mBrightnessMapper.getBrightness(mAmbientLux, mForegroundAppPackageName, + mForegroundAppCategory); int newScreenAutoBrightness = clampScreenBrightness(Math.round(value * PowerManager.BRIGHTNESS_ON)); @@ -673,7 +743,7 @@ class AutomaticBrightnessController { if (mScreenAutoBrightness != -1 && newScreenAutoBrightness > mScreenDarkeningThreshold && newScreenAutoBrightness < mScreenBrighteningThreshold) { - if (DEBUG) { + if (mLoggingEnabled) { Slog.d(TAG, "ignoring newScreenAutoBrightness: " + mScreenDarkeningThreshold + " < " + newScreenAutoBrightness + " < " + mScreenBrighteningThreshold); } @@ -681,8 +751,7 @@ class AutomaticBrightnessController { } if (mScreenAutoBrightness != newScreenAutoBrightness) { - - if (DEBUG) { + if (mLoggingEnabled) { Slog.d(TAG, "updateAutoBrightness: " + "mScreenAutoBrightness=" + mScreenAutoBrightness + ", " + "newScreenAutoBrightness=" + newScreenAutoBrightness); @@ -718,18 +787,11 @@ class AutomaticBrightnessController { BRIGHTNESS_ADJUSTMENT_SAMPLE_DEBOUNCE_MILLIS); } - private void cancelBrightnessAdjustmentSample() { - if (mBrightnessAdjustmentSamplePending) { - mBrightnessAdjustmentSamplePending = false; - mHandler.removeMessages(MSG_BRIGHTNESS_ADJUSTMENT_SAMPLE); - } - } - private void collectBrightnessAdjustmentSample() { if (mBrightnessAdjustmentSamplePending) { mBrightnessAdjustmentSamplePending = false; if (mAmbientLuxValid && mScreenAutoBrightness >= 0) { - if (DEBUG) { + if (mLoggingEnabled) { Slog.d(TAG, "Auto-brightness adjustment changed by user: " + "lux=" + mAmbientLux + ", " + "brightness=" + mScreenAutoBrightness + ", " + @@ -745,6 +807,68 @@ class AutomaticBrightnessController { } } + // Register a TaskStackListener to call back to us onTaskStackChanged, so we can update the + // foreground app's package name and category and correct the brightness accordingly. + private void registerForegroundAppUpdater() { + try { + mActivityTaskManager.registerTaskStackListener(mTaskStackListener); + // This will not get called until the foreground app changes for the first time, so + // call it explicitly to get the current foreground app's info. + updateForegroundApp(); + } catch (RemoteException e) { + // Nothing to do. + } + } + + private void unregisterForegroundAppUpdater() { + try { + mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener); + } catch (RemoteException e) { + // Nothing to do. + } + mForegroundAppPackageName = null; + mForegroundAppCategory = ApplicationInfo.CATEGORY_UNDEFINED; + } + + // Set the foreground app's package name and category, so brightness can be corrected per app. + private void updateForegroundApp() { + // The ActivityTaskManager's lock tends to get contended, so this is done in a background + // thread and applied via this thread's handler synchronously. + BackgroundThread.getHandler().post(new Runnable() { + public void run() { + try { + // The foreground app is the top activity of the focused tasks stack. + final StackInfo info = mActivityTaskManager.getFocusedStackInfo(); + if (info == null || info.topActivity == null) { + return; + } + final String packageName = info.topActivity.getPackageName(); + // If the app didn't change, there's nothing to do. Otherwise, we have to + // update the category and re-apply the brightness correction. + if (mForegroundAppPackageName != null + && mForegroundAppPackageName.equals(packageName)) { + return; + } + mPendingForegroundAppPackageName = packageName; + ApplicationInfo app = mPackageManagerInternal.getApplicationInfo(packageName, + 0 /* flags */, Process.SYSTEM_UID /* filterCallingUid */, mUserId); + mPendingForegroundAppCategory = app.category; + mHandler.sendEmptyMessage(MSG_UPDATE_FOREGROUND_APP_SYNC); + } catch (RemoteException e) { + // Nothing to do. + } + } + }); + } + + private void updateForegroundAppSync() { + mForegroundAppPackageName = mPendingForegroundAppPackageName; + mPendingForegroundAppPackageName = null; + mForegroundAppCategory = mPendingForegroundAppCategory; + mPendingForegroundAppCategory = ApplicationInfo.CATEGORY_UNDEFINED; + updateAutoBrightness(true /* sendUpdate */); + } + private final class AutomaticBrightnessHandler extends Handler { public AutomaticBrightnessHandler(Looper looper) { super(looper, null, true /*async*/); @@ -764,6 +888,14 @@ class AutomaticBrightnessController { case MSG_INVALIDATE_SHORT_TERM_MODEL: invalidateShortTermModel(); break; + + case MSG_UPDATE_FOREGROUND_APP: + updateForegroundApp(); + break; + + case MSG_UPDATE_FOREGROUND_APP_SYNC: + updateForegroundAppSync(); + break; } } } @@ -784,6 +916,15 @@ class AutomaticBrightnessController { } }; + // Call back whenever the tasks stack changes, which includes tasks being created, removed, and + // moving to top. + class TaskStackListenerImpl extends TaskStackListener { + @Override + public void onTaskStackChanged() { + mHandler.sendEmptyMessage(MSG_UPDATE_FOREGROUND_APP); + } + } + /** Callbacks to request updates to the display's power state. */ interface Callbacks { void updateBrightness(); diff --git a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java index 76c191d5e9ea..9fce644d6c4b 100644 --- a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java +++ b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java @@ -17,9 +17,11 @@ package com.android.server.display; import android.annotation.Nullable; +import android.content.pm.ApplicationInfo; import android.content.res.Resources; import android.content.res.TypedArray; import android.hardware.display.BrightnessConfiguration; +import android.hardware.display.BrightnessCorrection; import android.os.PowerManager; import android.util.MathUtils; import android.util.Pair; @@ -42,11 +44,12 @@ import java.util.Arrays; */ public abstract class BrightnessMappingStrategy { private static final String TAG = "BrightnessMappingStrategy"; - private static final boolean DEBUG = false; private static final float LUX_GRAD_SMOOTHING = 0.25f; private static final float MAX_GRAD = 1.0f; + protected boolean mLoggingEnabled; + private static final Plog PLOG = Plog.createSystemPlog(TAG); @Nullable @@ -161,6 +164,22 @@ public abstract class BrightnessMappingStrategy { } /** + * Enable/disable logging. + * + * @param loggingEnabled + * Whether logging should be on/off. + * + * @return Whether the method succeeded or not. + */ + public boolean setLoggingEnabled(boolean loggingEnabled) { + if (mLoggingEnabled == loggingEnabled) { + return false; + } + mLoggingEnabled = loggingEnabled; + return true; + } + + /** * Sets the {@link BrightnessConfiguration}. * * @param config The new configuration. If {@code null} is passed, the default configuration is @@ -170,15 +189,33 @@ public abstract class BrightnessMappingStrategy { public abstract boolean setBrightnessConfiguration(@Nullable BrightnessConfiguration config); /** - * Returns the desired brightness of the display based on the current ambient lux. + * Returns the desired brightness of the display based on the current ambient lux, including + * any context-related corrections. * * The returned brightness will be in the range [0, 1.0], where 1.0 is the display at max * brightness and 0 is the display at minimum brightness. * * @param lux The current ambient brightness in lux. + * @param packageName the foreground app package name. + * @param category the foreground app package category. * @return The desired brightness of the display normalized to the range [0, 1.0]. */ - public abstract float getBrightness(float lux); + public abstract float getBrightness(float lux, String packageName, + @ApplicationInfo.Category int category); + + /** + * Returns the desired brightness of the display based on the current ambient lux. + * + * The returned brightness wil be in the range [0, 1.0], where 1.0 is the display at max + * brightness and 0 is the display at minimum brightness. + * + * @param lux The current ambient brightness in lux. + * + * @return The desired brightness of the display normalized to the range [0, 1.0]. + */ + public float getBrightness(float lux) { + return getBrightness(lux, null /* packageName */, ApplicationInfo.CATEGORY_UNDEFINED); + } /** * Returns the current auto-brightness adjustment. @@ -239,13 +276,13 @@ public abstract class BrightnessMappingStrategy { public abstract void dump(PrintWriter pw); - private static float normalizeAbsoluteBrightness(int brightness) { + protected float normalizeAbsoluteBrightness(int brightness) { brightness = MathUtils.constrain(brightness, PowerManager.BRIGHTNESS_OFF, PowerManager.BRIGHTNESS_ON); return (float) brightness / PowerManager.BRIGHTNESS_ON; } - private static Pair<float[], float[]> insertControlPoint( + private Pair<float[], float[]> insertControlPoint( float[] luxLevels, float[] brightnessLevels, float lux, float brightness) { final int idx = findInsertionPoint(luxLevels, lux); final float[] newLuxLevels; @@ -278,7 +315,7 @@ public abstract class BrightnessMappingStrategy { * This assumes that {@code arr} is sorted. If all values in {@code arr} are greater * than val, then it will return the length of arr as the insertion point. */ - private static int findInsertionPoint(float[] arr, float val) { + private int findInsertionPoint(float[] arr, float val) { for (int i = 0; i < arr.length; i++) { if (val <= arr[i]) { return i; @@ -287,8 +324,8 @@ public abstract class BrightnessMappingStrategy { return arr.length; } - private static void smoothCurve(float[] lux, float[] brightness, int idx) { - if (DEBUG) { + private void smoothCurve(float[] lux, float[] brightness, int idx) { + if (mLoggingEnabled) { PLOG.logCurve("unsmoothed curve", lux, brightness); } float prevLux = lux[idx]; @@ -323,19 +360,19 @@ public abstract class BrightnessMappingStrategy { prevBrightness = newBrightness; brightness[i] = newBrightness; } - if (DEBUG) { + if (mLoggingEnabled) { PLOG.logCurve("smoothed curve", lux, brightness); } } - private static float permissibleRatio(float currLux, float prevLux) { + private float permissibleRatio(float currLux, float prevLux) { return MathUtils.exp(MAX_GRAD * (MathUtils.log(currLux + LUX_GRAD_SMOOTHING) - MathUtils.log(prevLux + LUX_GRAD_SMOOTHING))); } - private static float inferAutoBrightnessAdjustment(float maxGamma, - float desiredBrightness, float currentBrightness) { + protected float inferAutoBrightnessAdjustment(float maxGamma, float desiredBrightness, + float currentBrightness) { float adjustment = 0; float gamma = Float.NaN; // Extreme edge cases: use a simpler heuristic, as proper gamma correction around the edges @@ -355,7 +392,7 @@ public abstract class BrightnessMappingStrategy { adjustment = -MathUtils.log(gamma) / MathUtils.log(maxGamma); } adjustment = MathUtils.constrain(adjustment, -1, +1); - if (DEBUG) { + if (mLoggingEnabled) { Slog.d(TAG, "inferAutoBrightnessAdjustment: " + maxGamma + "^" + -adjustment + "=" + MathUtils.pow(maxGamma, -adjustment) + " == " + gamma); Slog.d(TAG, "inferAutoBrightnessAdjustment: " + currentBrightness + "^" + gamma + "=" + @@ -364,16 +401,16 @@ public abstract class BrightnessMappingStrategy { return adjustment; } - private static Pair<float[], float[]> getAdjustedCurve(float[] lux, float[] brightness, + protected Pair<float[], float[]> getAdjustedCurve(float[] lux, float[] brightness, float userLux, float userBrightness, float adjustment, float maxGamma) { float[] newLux = lux; float[] newBrightness = Arrays.copyOf(brightness, brightness.length); - if (DEBUG) { + if (mLoggingEnabled) { PLOG.logCurve("unadjusted curve", newLux, newBrightness); } adjustment = MathUtils.constrain(adjustment, -1, 1); float gamma = MathUtils.pow(maxGamma, -adjustment); - if (DEBUG) { + if (mLoggingEnabled) { Slog.d(TAG, "getAdjustedCurve: " + maxGamma + "^" + -adjustment + "=" + MathUtils.pow(maxGamma, -adjustment) + " == " + gamma); } @@ -382,7 +419,7 @@ public abstract class BrightnessMappingStrategy { newBrightness[i] = MathUtils.pow(newBrightness[i], gamma); } } - if (DEBUG) { + if (mLoggingEnabled) { PLOG.logCurve("gamma adjusted curve", newLux, newBrightness); } if (userLux != -1) { @@ -390,7 +427,7 @@ public abstract class BrightnessMappingStrategy { userBrightness); newLux = curve.first; newBrightness = curve.second; - if (DEBUG) { + if (mLoggingEnabled) { PLOG.logCurve("gamma and user adjusted curve", newLux, newBrightness); // This is done for comparison. curve = insertControlPoint(lux, brightness, userLux, userBrightness); @@ -440,7 +477,7 @@ public abstract class BrightnessMappingStrategy { mAutoBrightnessAdjustment = 0; mUserLux = -1; mUserBrightness = -1; - if (DEBUG) { + if (mLoggingEnabled) { PLOG.start("simple mapping strategy"); } computeSpline(); @@ -452,7 +489,8 @@ public abstract class BrightnessMappingStrategy { } @Override - public float getBrightness(float lux) { + public float getBrightness(float lux, String packageName, + @ApplicationInfo.Category int category) { return mSpline.interpolate(lux); } @@ -467,7 +505,7 @@ public abstract class BrightnessMappingStrategy { if (adjustment == mAutoBrightnessAdjustment) { return false; } - if (DEBUG) { + if (mLoggingEnabled) { Slog.d(TAG, "setAutoBrightnessAdjustment: " + mAutoBrightnessAdjustment + " => " + adjustment); PLOG.start("auto-brightness adjustment"); @@ -485,7 +523,7 @@ public abstract class BrightnessMappingStrategy { @Override public void addUserDataPoint(float lux, float brightness) { float unadjustedBrightness = getUnadjustedBrightness(lux); - if (DEBUG){ + if (mLoggingEnabled) { Slog.d(TAG, "addUserDataPoint: (" + lux + "," + brightness + ")"); PLOG.start("add user data point") .logPoint("user data point", lux, brightness) @@ -494,7 +532,7 @@ public abstract class BrightnessMappingStrategy { float adjustment = inferAutoBrightnessAdjustment(mMaxGamma, brightness /* desiredBrightness */, unadjustedBrightness /* currentBrightness */); - if (DEBUG) { + if (mLoggingEnabled) { Slog.d(TAG, "addUserDataPoint: " + mAutoBrightnessAdjustment + " => " + adjustment); } @@ -507,7 +545,7 @@ public abstract class BrightnessMappingStrategy { @Override public void clearUserDataPoints() { if (mUserLux != -1) { - if (DEBUG) { + if (mLoggingEnabled) { Slog.d(TAG, "clearUserDataPoints: " + mAutoBrightnessAdjustment + " => 0"); PLOG.start("clear user data points") .logPoint("user data point", mUserLux, mUserBrightness); @@ -614,7 +652,7 @@ public abstract class BrightnessMappingStrategy { mBacklightToNitsSpline = Spline.createSpline(normalizedBacklight, nits); mDefaultConfig = config; - if (DEBUG) { + if (mLoggingEnabled) { PLOG.start("physical mapping strategy"); } mConfig = config; @@ -629,7 +667,7 @@ public abstract class BrightnessMappingStrategy { if (config.equals(mConfig)) { return false; } - if (DEBUG) { + if (mLoggingEnabled) { PLOG.start("brightness configuration"); } mConfig = config; @@ -638,9 +676,17 @@ public abstract class BrightnessMappingStrategy { } @Override - public float getBrightness(float lux) { + public float getBrightness(float lux, String packageName, + @ApplicationInfo.Category int category) { float nits = mBrightnessSpline.interpolate(lux); float backlight = mNitsToBacklightSpline.interpolate(nits); + // Correct the brightness according to the current application and its category, but + // only if no user data point is set (as this will oevrride the user setting). + if (mUserLux == -1) { + backlight = correctBrightness(backlight, packageName, category); + } else if (mLoggingEnabled) { + Slog.d(TAG, "user point set, correction not applied"); + } return backlight; } @@ -655,7 +701,7 @@ public abstract class BrightnessMappingStrategy { if (adjustment == mAutoBrightnessAdjustment) { return false; } - if (DEBUG) { + if (mLoggingEnabled) { Slog.d(TAG, "setAutoBrightnessAdjustment: " + mAutoBrightnessAdjustment + " => " + adjustment); PLOG.start("auto-brightness adjustment"); @@ -673,7 +719,7 @@ public abstract class BrightnessMappingStrategy { @Override public void addUserDataPoint(float lux, float brightness) { float unadjustedBrightness = getUnadjustedBrightness(lux); - if (DEBUG){ + if (mLoggingEnabled) { Slog.d(TAG, "addUserDataPoint: (" + lux + "," + brightness + ")"); PLOG.start("add user data point") .logPoint("user data point", lux, brightness) @@ -682,7 +728,7 @@ public abstract class BrightnessMappingStrategy { float adjustment = inferAutoBrightnessAdjustment(mMaxGamma, brightness /* desiredBrightness */, unadjustedBrightness /* currentBrightness */); - if (DEBUG) { + if (mLoggingEnabled) { Slog.d(TAG, "addUserDataPoint: " + mAutoBrightnessAdjustment + " => " + adjustment); } @@ -695,7 +741,7 @@ public abstract class BrightnessMappingStrategy { @Override public void clearUserDataPoints() { if (mUserLux != -1) { - if (DEBUG) { + if (mLoggingEnabled) { Slog.d(TAG, "clearUserDataPoints: " + mAutoBrightnessAdjustment + " => 0"); PLOG.start("clear user data points") .logPoint("user data point", mUserLux, mUserBrightness); @@ -758,5 +804,21 @@ public abstract class BrightnessMappingStrategy { Spline spline = Spline.createSpline(curve.first, curve.second); return mNitsToBacklightSpline.interpolate(spline.interpolate(lux)); } + + private float correctBrightness(float brightness, String packageName, int category) { + if (packageName != null) { + BrightnessCorrection correction = mConfig.getCorrectionByPackageName(packageName); + if (correction != null) { + return correction.apply(brightness); + } + } + if (category != ApplicationInfo.CATEGORY_UNDEFINED) { + BrightnessCorrection correction = mConfig.getCorrectionByCategory(category); + if (correction != null) { + return correction.apply(brightness); + } + } + return brightness; + } } } diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index cf8d21b66417..b1ba05c035b3 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -1296,58 +1296,21 @@ public final class DisplayManagerService extends SystemService { return; } display.configureDisplayLocked(t, device, info.state == Display.STATE_OFF); - + final int viewportType; // Update the corresponding viewport. - DisplayViewport internalViewport = getInternalViewportLocked(); if ((info.flags & DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY) != 0) { - populateViewportLocked(internalViewport, 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 = getVirtualViewportLocked(info.uniqueId); - populateViewportLocked(viewport, display, device); - } - } - - /** 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 = mViewports.size(); - for (int i = 0; i < count; 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; - } + viewportType = VIEWPORT_INTERNAL; + } else if (info.touch == DisplayDeviceInfo.TOUCH_EXTERNAL) { + viewportType = VIEWPORT_EXTERNAL; + } else if (info.touch == DisplayDeviceInfo.TOUCH_VIRTUAL + && !TextUtils.isEmpty(info.uniqueId)) { + viewportType = VIEWPORT_VIRTUAL; + } else { + Slog.wtf(TAG, "Unable to populate viewport for display device: " + info); + return; } - viewport = new DisplayViewport(); - viewport.uniqueId = uniqueId; - viewport.type = VIEWPORT_VIRTUAL; - mViewports.add(viewport); - return viewport; - } - - private DisplayViewport getInternalViewportLocked() { - return getViewportByTypeLocked(VIEWPORT_INTERNAL); - } - - private DisplayViewport getExternalViewportLocked() { - return getViewportByTypeLocked(VIEWPORT_EXTERNAL); + populateViewportLocked(viewportType, display.getDisplayIdLocked(), device, info.uniqueId); } /** @@ -1355,35 +1318,44 @@ public final class DisplayManagerService extends SystemService { * @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) { + private DisplayViewport getViewportLocked(int viewportType, String uniqueId) { + if (viewportType != VIEWPORT_INTERNAL && viewportType != VIEWPORT_EXTERNAL + && viewportType != VIEWPORT_VIRTUAL) { Slog.wtf(TAG, "Cannot call getViewportByTypeLocked for type " + DisplayViewport.typeToString(viewportType)); return null; } + + // Only allow a single INTERNAL or EXTERNAL viewport by forcing their uniqueIds + // to be identical (in particular, empty). + // TODO (b/116824030) allow multiple EXTERNAL viewports and remove this function. + if (viewportType != VIEWPORT_VIRTUAL) { + uniqueId = ""; + } + DisplayViewport viewport; final int count = mViewports.size(); for (int i = 0; i < count; i++) { viewport = mViewports.get(i); - if (viewport.type == viewportType) { + if (viewport.type == viewportType && uniqueId.equals(viewport.uniqueId)) { return viewport; } } + // Creates the viewport if none exists. viewport = new DisplayViewport(); viewport.type = viewportType; + viewport.uniqueId = uniqueId; mViewports.add(viewport); return viewport; } - private static void populateViewportLocked(DisplayViewport viewport, - LogicalDisplay display, DisplayDevice device) { - viewport.valid = true; - viewport.displayId = display.getDisplayIdLocked(); + private void populateViewportLocked(int viewportType, + int displayId, DisplayDevice device, String uniqueId) { + final DisplayViewport viewport = getViewportLocked(viewportType, uniqueId); device.populateViewportLocked(viewport); + viewport.valid = true; + viewport.displayId = displayId; } private LogicalDisplay findLogicalDisplayForDeviceLocked(DisplayDevice device) { @@ -2172,6 +2144,14 @@ public final class DisplayManagerService extends SystemService { mContext.getPackageName()); } + void setAutoBrightnessLoggingEnabled(boolean enabled) { + if (mDisplayPowerController != null) { + synchronized (mSyncRoot) { + mDisplayPowerController.setAutoBrightnessLoggingEnabled(enabled); + } + } + } + private boolean validatePackageName(int uid, String packageName) { if (packageName != null) { String[] packageNames = mContext.getPackageManager().getPackagesForUid(uid); diff --git a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java index 27cad1eece09..abbfc7b18f94 100644 --- a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java +++ b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java @@ -17,14 +17,9 @@ package com.android.server.display; import android.content.Intent; -import android.os.RemoteException; -import android.os.ResultReceiver; -import android.os.ShellCallback; import android.os.ShellCommand; -import android.util.Slog; import java.io.PrintWriter; -import java.lang.NumberFormatException; class DisplayManagerShellCommand extends ShellCommand { private static final String TAG = "DisplayManagerShellCommand"; @@ -46,6 +41,10 @@ class DisplayManagerShellCommand extends ShellCommand { return setBrightness(); case "reset-brightness-configuration": return resetBrightnessConfiguration(); + case "ab-logging-enable": + return setAutoBrightnessLoggingEnabled(true); + case "ab-logging-disable": + return setAutoBrightnessLoggingEnabled(false); default: return handleDefaultCommands(cmd); } @@ -62,6 +61,10 @@ class DisplayManagerShellCommand extends ShellCommand { pw.println(" Sets the current brightness to BRIGHTNESS (a number between 0 and 1)."); pw.println(" reset-brightness-configuration"); pw.println(" Reset the brightness to its default configuration."); + pw.println(" ab-logging-enable"); + pw.println(" Enable auto-brightness logging."); + pw.println(" ab-logging-disable"); + pw.println(" Disable auto-brightness logging."); pw.println(); Intent.printIntentArgsHelp(pw , ""); } @@ -89,4 +92,9 @@ class DisplayManagerShellCommand extends ShellCommand { mService.resetBrightnessConfiguration(); return 0; } + + private int setAutoBrightnessLoggingEnabled(boolean enabled) { + mService.setAutoBrightnessLoggingEnabled(enabled); + return 0; + } } diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index 249270bfda7e..c9ed9f7cea43 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -523,6 +523,9 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call public void onSwitchUser(@UserIdInt int newUserId) { handleSettingsChange(true /* userSwitch */); mBrightnessTracker.onSwitchUser(newUserId); + if (mAutomaticBrightnessController != null) { + mAutomaticBrightnessController.onSwitchUser(newUserId); + } } public ParceledListSlice<AmbientBrightnessDayStats> getAmbientBrightnessStats( @@ -1836,4 +1839,10 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mHandler.sendMessage(msg); } } + + void setAutoBrightnessLoggingEnabled(boolean enabled) { + if (mAutomaticBrightnessController != null) { + mAutomaticBrightnessController.setLoggingEnabled(enabled); + } + } } diff --git a/services/core/java/com/android/server/display/PersistentDataStore.java b/services/core/java/com/android/server/display/PersistentDataStore.java index 89cef621a55d..9aec43b6eaed 100644 --- a/services/core/java/com/android/server/display/PersistentDataStore.java +++ b/services/core/java/com/android/server/display/PersistentDataStore.java @@ -16,13 +16,6 @@ package com.android.server.display; -import com.android.internal.util.FastXmlSerializer; -import com.android.internal.util.XmlUtils; - -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; -import org.xmlpull.v1.XmlSerializer; - import android.annotation.Nullable; import android.graphics.Point; import android.hardware.display.BrightnessConfiguration; @@ -30,13 +23,20 @@ import android.hardware.display.WifiDisplay; import android.util.AtomicFile; import android.util.Slog; import android.util.SparseArray; -import android.util.Pair; import android.util.SparseLongArray; import android.util.TimeUtils; import android.util.Xml; import android.view.Display; 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; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; @@ -50,12 +50,9 @@ import java.io.PrintWriter; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.HashMap; -import java.util.List; import java.util.Map; import java.util.Objects; -import libcore.io.IoUtils; - /** * Manages persistent state recorded by the display manager service as an XML file. * Caller must acquire lock on the data store before accessing it. @@ -110,14 +107,9 @@ final class PersistentDataStore { private static final String TAG_BRIGHTNESS_CONFIGURATIONS = "brightness-configurations"; private static final String TAG_BRIGHTNESS_CONFIGURATION = "brightness-configuration"; - private static final String TAG_BRIGHTNESS_CURVE = "brightness-curve"; - private static final String TAG_BRIGHTNESS_POINT = "brightness-point"; private static final String ATTR_USER_SERIAL = "user-serial"; private static final String ATTR_PACKAGE_NAME = "package-name"; private static final String ATTR_TIME_STAMP = "timestamp"; - private static final String ATTR_LUX = "lux"; - private static final String ATTR_NITS = "nits"; - private static final String ATTR_DESCRIPTION = "description"; // Remembered Wifi display devices. private ArrayList<WifiDisplay> mRememberedWifiDisplays = new ArrayList<WifiDisplay>(); @@ -646,7 +638,8 @@ final class PersistentDataStore { } try { - BrightnessConfiguration config = loadConfigurationFromXml(parser); + BrightnessConfiguration config = + BrightnessConfiguration.loadFromXml(parser); if (userSerial >= 0 && config != null) { mConfigurations.put(userSerial, config); if (timeStamp != -1) { @@ -663,56 +656,6 @@ final class PersistentDataStore { } } - private static BrightnessConfiguration loadConfigurationFromXml(XmlPullParser parser) - throws IOException, XmlPullParserException { - final int outerDepth = parser.getDepth(); - String description = null; - Pair<float[], float[]> curve = null; - while (XmlUtils.nextElementWithin(parser, outerDepth)) { - if (TAG_BRIGHTNESS_CURVE.equals(parser.getName())) { - description = parser.getAttributeValue(null, ATTR_DESCRIPTION); - curve = loadCurveFromXml(parser); - } - } - if (curve == null) { - return null; - } - final BrightnessConfiguration.Builder builder = new BrightnessConfiguration.Builder( - curve.first, curve.second); - builder.setDescription(description); - return builder.build(); - } - - private static Pair<float[], float[]> loadCurveFromXml(XmlPullParser parser) - throws IOException, XmlPullParserException { - final int outerDepth = parser.getDepth(); - List<Float> luxLevels = new ArrayList<>(); - List<Float> nitLevels = new ArrayList<>(); - while (XmlUtils.nextElementWithin(parser, outerDepth)) { - if (TAG_BRIGHTNESS_POINT.equals(parser.getName())) { - luxLevels.add(loadFloat(parser.getAttributeValue(null, ATTR_LUX))); - nitLevels.add(loadFloat(parser.getAttributeValue(null, ATTR_NITS))); - } - } - final int N = luxLevels.size(); - float[] lux = new float[N]; - float[] nits = new float[N]; - for (int i = 0; i < N; i++) { - lux[i] = luxLevels.get(i); - nits[i] = nitLevels.get(i); - } - return Pair.create(lux, nits); - } - - private static float loadFloat(String val) { - try { - return Float.parseFloat(val); - } catch (NullPointerException | NumberFormatException e) { - Slog.e(TAG, "Failed to parse float loading brightness config", e); - return Float.NEGATIVE_INFINITY; - } - } - public void saveToXml(XmlSerializer serializer) throws IOException { for (int i = 0; i < mConfigurations.size(); i++) { final int userSerial = mConfigurations.keyAt(i); @@ -728,27 +671,11 @@ final class PersistentDataStore { if (timestamp != -1) { serializer.attribute(null, ATTR_TIME_STAMP, Long.toString(timestamp)); } - saveConfigurationToXml(serializer, config); + config.saveToXml(serializer); serializer.endTag(null, TAG_BRIGHTNESS_CONFIGURATION); } } - private static void saveConfigurationToXml(XmlSerializer serializer, - BrightnessConfiguration config) throws IOException { - serializer.startTag(null, TAG_BRIGHTNESS_CURVE); - if (config.getDescription() != null) { - serializer.attribute(null, ATTR_DESCRIPTION, config.getDescription()); - } - final Pair<float[], float[]> curve = config.getCurve(); - for (int i = 0; i < curve.first.length; i++) { - serializer.startTag(null, TAG_BRIGHTNESS_POINT); - serializer.attribute(null, ATTR_LUX, Float.toString(curve.first[i])); - serializer.attribute(null, ATTR_NITS, Float.toString(curve.second[i])); - serializer.endTag(null, TAG_BRIGHTNESS_POINT); - } - serializer.endTag(null, TAG_BRIGHTNESS_CURVE); - } - public void dump(final PrintWriter pw, final String prefix) { for (int i = 0; i < mConfigurations.size(); i++) { final int userSerial = mConfigurations.keyAt(i); diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java index c0d3fdfb8f91..f468c0bf6652 100644 --- a/services/core/java/com/android/server/hdmi/HdmiControlService.java +++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java @@ -81,8 +81,10 @@ import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Locale; +import java.util.Map; import libcore.util.EmptyArray; /** @@ -94,6 +96,31 @@ public class HdmiControlService extends SystemService { private final Locale HONG_KONG = new Locale("zh", "HK"); private final Locale MACAU = new Locale("zh", "MO"); + private static final Map<String, String> mTerminologyToBibliographicMap; + static { + mTerminologyToBibliographicMap = new HashMap<>(); + // NOTE: (TERMINOLOGY_CODE, BIBLIOGRAPHIC_CODE) + mTerminologyToBibliographicMap.put("sqi", "alb"); // Albanian + mTerminologyToBibliographicMap.put("hye", "arm"); // Armenian + mTerminologyToBibliographicMap.put("eus", "baq"); // Basque + mTerminologyToBibliographicMap.put("mya", "bur"); // Burmese + mTerminologyToBibliographicMap.put("ces", "cze"); // Czech + mTerminologyToBibliographicMap.put("nld", "dut"); // Dutch + mTerminologyToBibliographicMap.put("kat", "geo"); // Georgian + mTerminologyToBibliographicMap.put("deu", "ger"); // German + mTerminologyToBibliographicMap.put("ell", "gre"); // Greek + mTerminologyToBibliographicMap.put("fra", "fre"); // French + mTerminologyToBibliographicMap.put("isl", "ice"); // Icelandic + mTerminologyToBibliographicMap.put("mkd", "mac"); // Macedonian + mTerminologyToBibliographicMap.put("mri", "mao"); // Maori + mTerminologyToBibliographicMap.put("msa", "may"); // Malay + mTerminologyToBibliographicMap.put("fas", "per"); // Persian + mTerminologyToBibliographicMap.put("ron", "rum"); // Romanian + mTerminologyToBibliographicMap.put("slk", "slo"); // Slovak + mTerminologyToBibliographicMap.put("bod", "tib"); // Tibetan + mTerminologyToBibliographicMap.put("cym", "wel"); // Welsh + } + static final String PERMISSION = "android.permission.HDMI_CEC"; // The reason code to initiate initializeCec(). @@ -177,7 +204,18 @@ public class HdmiControlService extends SystemService { // Chinese used in Taiwan/Hong Kong/Macau. return "chi"; } else { - return locale.getISO3Language(); + String language = locale.getISO3Language(); + + // locale.getISO3Language() returns terminology code and need to + // send it as bibliographic code instead since the Bibliographic + // codes of ISO/FDIS 639-2 shall be used. + // NOTE: Chinese also has terminology/bibliographic code "zho" and "chi" + // But, as it depends on the locale, is not handled here. + if (mTerminologyToBibliographicMap.containsKey(language)) { + language = mTerminologyToBibliographicMap.get(language); + } + + return language; } } } diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java index e7c3c7bbe21b..d96b6cba119b 100644 --- a/services/core/java/com/android/server/input/InputManagerService.java +++ b/services/core/java/com/android/server/input/InputManagerService.java @@ -1951,11 +1951,6 @@ public class InputManagerService extends IInputManager.Stub } // Native callback. - private int getPointerDisplayId() { - return mWindowManagerCallbacks.getPointerDisplayId(); - } - - // Native callback. private String[] getKeyboardLayoutOverlay(InputDeviceIdentifier identifier) { if (!mSystemReady) { return null; @@ -2022,8 +2017,6 @@ public class InputManagerService extends IInputManager.Stub KeyEvent event, int policyFlags); public int getPointerLayer(); - - public int getPointerDisplayId(); } /** diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index 28a6ba4ceb1d..3552e6635738 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -675,13 +675,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub private final int mHardKeyboardBehavior; /** - * Whether we temporarily allow IMEs implemented in instant apps to run for testing. - * - * <p>Note: This is quite dangerous. Don't forget to reset after you finish testing.</p> - */ - private boolean mBindInstantServiceAllowed = false; - - /** * Internal state snapshot when {@link #MSG_START_INPUT} message is about to be posted to the * internal message queue. Any subsequent state change inside {@link InputMethodManagerService} * will not affect those tasks that are already posted. @@ -1135,8 +1128,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub final PackageManager pm = mContext.getPackageManager(); final List<ResolveInfo> services = pm.queryIntentServicesAsUser( new Intent(InputMethod.SERVICE_INTERFACE).setPackage(packageName), - getComponentMatchingFlags(PackageManager.MATCH_DISABLED_COMPONENTS), - getChangingUserId()); + PackageManager.MATCH_DISABLED_COMPONENTS, getChangingUserId()); // No need to lock this because we access it only on getRegisteredHandler(). if (!services.isEmpty()) { mImePackageAppeared = true; @@ -1684,9 +1676,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub Slog.e(TAG, "--- bind failed: service = " + service + ", conn = " + conn); return false; } - if (mBindInstantServiceAllowed) { - flags |= Context.BIND_ALLOW_INSTANT; - } return mContext.bindServiceAsUser(service, conn, flags, new UserHandle(mSettings.getCurrentUserId())); } @@ -1696,6 +1685,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub return getInputMethodList(false /* isVrOnly */); } + @Override public List<InputMethodInfo> getVrInputMethodList() { return getInputMethodList(true /* isVrOnly */); } @@ -3631,16 +3621,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub return false; } - @PackageManager.ResolveInfoFlags - private int getComponentMatchingFlags(@PackageManager.ResolveInfoFlags int baseFlags) { - synchronized (mMethodMap) { - if (mBindInstantServiceAllowed) { - baseFlags |= PackageManager.MATCH_INSTANT; - } - return baseFlags; - } - } - @GuardedBy("mMethodMap") void buildInputMethodListLocked(boolean resetDefaultEnabledIme) { if (DEBUG) { @@ -3664,8 +3644,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub // services depending on the unlock state for the specified user. final List<ResolveInfo> services = pm.queryIntentServicesAsUser( new Intent(InputMethod.SERVICE_INTERFACE), - getComponentMatchingFlags(PackageManager.GET_META_DATA - | PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS), + PackageManager.GET_META_DATA | PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS, mSettings.getCurrentUserId()); final ArrayMap<String, List<InputMethodSubtype>> additionalSubtypeMap = @@ -3707,8 +3686,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub // conservative, but it seems we cannot use it for now (Issue 35176630). final List<ResolveInfo> allInputMethodServices = pm.queryIntentServicesAsUser( new Intent(InputMethod.SERVICE_INTERFACE), - getComponentMatchingFlags(PackageManager.MATCH_DISABLED_COMPONENTS), - mSettings.getCurrentUserId()); + PackageManager.MATCH_DISABLED_COMPONENTS, mSettings.getCurrentUserId()); final int N = allInputMethodServices.size(); for (int i = 0; i < N; ++i) { final ServiceInfo si = allInputMethodServices.get(i).serviceInfo; @@ -4606,8 +4584,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub synchronized (mMethodMap) { p.println("Current Input Method Manager state:"); int N = mMethodList.size(); - p.println(" Input Methods: mMethodMapUpdateCount=" + mMethodMapUpdateCount - + " mBindInstantServiceAllowed=" + mBindInstantServiceAllowed); + p.println(" Input Methods: mMethodMapUpdateCount=" + mMethodMapUpdateCount); for (int i=0; i<N; i++) { InputMethodInfo info = mMethodList.get(i); p.println(" InputMethod #" + i + ":"); @@ -4719,9 +4696,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub if ("refresh_debug_properties".equals(cmd)) { return refreshDebugProperties(); } - if ("set-bind-instant-service-allowed".equals(cmd)) { - return setBindInstantServiceAllowed(); - } // For existing "adb shell ime <command>". if ("ime".equals(cmd)) { @@ -4752,12 +4726,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub @BinderThread @ShellCommandResult - private int setBindInstantServiceAllowed() { - return mService.handleSetBindInstantServiceAllowed(this); - } - - @BinderThread - @ShellCommandResult private int refreshDebugProperties() { DebugFlags.FLAG_OPTIMIZE_START_INPUT.refresh(); return ShellCommandResult.SUCCESS; @@ -4774,9 +4742,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub pw.println(" Synonym of dumpsys."); pw.println(" ime <command> [options]"); pw.println(" Manipulate IMEs. Run \"ime help\" for details."); - pw.println(" set-bind-instant-service-allowed true|false "); - pw.println(" Set whether binding to services provided by instant apps is " - + "allowed."); } } @@ -4825,53 +4790,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub // Shell command handlers: /** - * Handles {@code adb shell cmd input_method set-bind-instant-service-allowed}. - * - * @param shellCommand {@link ShellCommand} object that is handling this command. - * @return Exit code of the command. - */ - @BinderThread - @RequiresPermission(android.Manifest.permission.MANAGE_BIND_INSTANT_SERVICE) - @ShellCommandResult - private int handleSetBindInstantServiceAllowed(@NonNull ShellCommand shellCommand) { - final String allowedString = shellCommand.getNextArgRequired(); - if (allowedString == null) { - shellCommand.getErrPrintWriter().println("Error: no true/false specified"); - return ShellCommandResult.FAILURE; - } - final boolean allowed = Boolean.parseBoolean(allowedString); - synchronized (mMethodMap) { - if (mContext.checkCallingOrSelfPermission( - android.Manifest.permission.MANAGE_BIND_INSTANT_SERVICE) - != PackageManager.PERMISSION_GRANTED) { - shellCommand.getErrPrintWriter().print( - "Caller must have MANAGE_BIND_INSTANT_SERVICE permission"); - return ShellCommandResult.FAILURE; - } - - if (mBindInstantServiceAllowed == allowed) { - // Nothing to do. - return ShellCommandResult.SUCCESS; - } - mBindInstantServiceAllowed = allowed; - - // Rebuild everything. - final long ident = Binder.clearCallingIdentity(); - try { - // Reset the current IME - resetSelectedInputMethodAndSubtypeLocked(null); - // Also reset the settings of the current IME - mSettings.putSelectedInputMethod(null); - buildInputMethodListLocked(false /* resetDefaultEnabledIme */); - updateInputMethodsFromSettingsLocked(true /* enabledMayChange */); - } finally { - Binder.restoreCallingIdentity(ident); - } - } - return ShellCommandResult.SUCCESS; - } - - /** * Handles {@code adb shell ime list}. * @param shellCommand {@link ShellCommand} object that is handling this command. * @return Exit code of the command. diff --git a/services/core/java/com/android/server/job/JobConcurrencyManager.java b/services/core/java/com/android/server/job/JobConcurrencyManager.java index ee1d8476c471..827c0f1df71a 100644 --- a/services/core/java/com/android/server/job/JobConcurrencyManager.java +++ b/services/core/java/com/android/server/job/JobConcurrencyManager.java @@ -44,17 +44,11 @@ class JobConcurrencyManager { * We manipulate this array until we arrive at what jobs should be running on * what JobServiceContext. */ - JobStatus[] mTmpAssignContextIdToJobMap = new JobStatus[MAX_JOB_CONTEXTS_COUNT]; + JobStatus[] mRecycledAssignContextIdToJobMap = new JobStatus[MAX_JOB_CONTEXTS_COUNT]; - /** - * Indicates whether we need to act on this jobContext id - */ - boolean[] mTmpAssignAct = new boolean[MAX_JOB_CONTEXTS_COUNT]; + boolean[] mRecycledSlotChanged = new boolean[MAX_JOB_CONTEXTS_COUNT]; - /** - * The uid whose jobs we would like to assign to a context. - */ - int[] mTmpAssignPreferredUidForContext = new int[MAX_JOB_CONTEXTS_COUNT]; + int[] mRecycledPreferredUidForContext = new int[MAX_JOB_CONTEXTS_COUNT]; JobConcurrencyManager(JobSchedulerService service) { mService = service; @@ -99,28 +93,30 @@ class JobConcurrencyManager { break; } - JobStatus[] contextIdToJobMap = mTmpAssignContextIdToJobMap; - boolean[] act = mTmpAssignAct; - int[] preferredUidForContext = mTmpAssignPreferredUidForContext; - int numActive = 0; - int numForeground = 0; + // To avoid GC churn, we recycle the arrays. + JobStatus[] contextIdToJobMap = mRecycledAssignContextIdToJobMap; + boolean[] slotChanged = mRecycledSlotChanged; + int[] preferredUidForContext = mRecycledPreferredUidForContext; + + int numTotalRunningJobs = 0; + int numForegroundJobs = 0; for (int i=0; i<MAX_JOB_CONTEXTS_COUNT; i++) { final JobServiceContext js = mService.mActiveServices.get(i); final JobStatus status = js.getRunningJobLocked(); if ((contextIdToJobMap[i] = status) != null) { - numActive++; + numTotalRunningJobs++; if (status.lastEvaluatedPriority >= JobInfo.PRIORITY_TOP_APP) { - numForeground++; + numForegroundJobs++; } } - act[i] = false; + slotChanged[i] = false; preferredUidForContext[i] = js.getPreferredUid(); } if (DEBUG) { Slog.d(TAG, printContextIdToJobMap(contextIdToJobMap, "running jobs initial")); } for (int i=0; i<pendingJobs.size(); i++) { - JobStatus nextPending = pendingJobs.get(i); + final JobStatus nextPending = pendingJobs.get(i); // If job is already running, go to next job. int jobRunningContext = findJobContextIdFromMap(nextPending, contextIdToJobMap); @@ -131,23 +127,32 @@ class JobConcurrencyManager { final int priority = mService.evaluateJobPriorityLocked(nextPending); nextPending.lastEvaluatedPriority = priority; - // Find a context for nextPending. The context should be available OR + // Find an available slot for nextPending. The context should be available OR // it should have lowest priority among all running jobs // (sharing the same Uid as nextPending) - int minPriority = Integer.MAX_VALUE; - int minPriorityContextId = -1; + int minPriorityForPreemption = Integer.MAX_VALUE; + int selectedContextId = -1; + boolean startingJob = false; for (int j=0; j<MAX_JOB_CONTEXTS_COUNT; j++) { JobStatus job = contextIdToJobMap[j]; int preferredUid = preferredUidForContext[j]; if (job == null) { - if ((numActive < mService.mMaxActiveJobs || - (priority >= JobInfo.PRIORITY_TOP_APP && - numForeground < mConstants.FG_JOB_COUNT)) && - (preferredUid == nextPending.getUid() || - preferredUid == JobServiceContext.NO_PREFERRED_UID)) { + final boolean totalCountOk = numTotalRunningJobs < mService.mMaxActiveJobs; + final boolean fgCountOk = (priority >= JobInfo.PRIORITY_TOP_APP) + && (numForegroundJobs < mConstants.FG_JOB_COUNT); + final boolean preferredUidOkay = (preferredUid == nextPending.getUid()) + || (preferredUid == JobServiceContext.NO_PREFERRED_UID); + + // TODO: The following check is slightly wrong. + // Depending on how the pending jobs are sorted, we sometimes cap the total + // job count at mMaxActiveJobs (when all jobs are FG jobs), or + // at [mMaxActiveJobs + FG_JOB_COUNT] (when there are mMaxActiveJobs BG jobs + // and then FG_JOB_COUNT FG jobs.) + if ((totalCountOk || fgCountOk) && preferredUidOkay) { // This slot is free, and we haven't yet hit the limit on // concurrent jobs... we can just throw the job in to here. - minPriorityContextId = j; + selectedContextId = j; + startingJob = true; break; } // No job on this context, but nextPending can't run here because @@ -158,30 +163,39 @@ class JobConcurrencyManager { if (job.getUid() != nextPending.getUid()) { continue; } - if (mService.evaluateJobPriorityLocked(job) >= nextPending.lastEvaluatedPriority) { + + final int jobPriority = mService.evaluateJobPriorityLocked(job); + if (jobPriority >= nextPending.lastEvaluatedPriority) { continue; } - if (minPriority > nextPending.lastEvaluatedPriority) { - minPriority = nextPending.lastEvaluatedPriority; - minPriorityContextId = j; + + // TODO lastEvaluatedPriority should be evaluateJobPriorityLocked. (double check it) + if (minPriorityForPreemption > nextPending.lastEvaluatedPriority) { + minPriorityForPreemption = nextPending.lastEvaluatedPriority; + selectedContextId = j; + // In this case, we're just going to preempt a low priority job, we're not + // actually starting a job, so don't set startingJob. } } - if (minPriorityContextId != -1) { - contextIdToJobMap[minPriorityContextId] = nextPending; - act[minPriorityContextId] = true; - numActive++; + if (selectedContextId != -1) { + contextIdToJobMap[selectedContextId] = nextPending; + slotChanged[selectedContextId] = true; + } + if (startingJob) { + // Increase the counters when we're going to start a job. + numTotalRunningJobs++; if (priority >= JobInfo.PRIORITY_TOP_APP) { - numForeground++; + numForegroundJobs++; } } } if (DEBUG) { Slog.d(TAG, printContextIdToJobMap(contextIdToJobMap, "running jobs final")); } - tracker.noteConcurrency(numActive, numForeground); + tracker.noteConcurrency(numTotalRunningJobs, numForegroundJobs); for (int i=0; i<MAX_JOB_CONTEXTS_COUNT; i++) { boolean preservePreferredUid = false; - if (act[i]) { + if (slotChanged[i]) { JobStatus js = activeServices.get(i).getRunningJobLocked(); if (js != null) { if (DEBUG) { @@ -195,7 +209,7 @@ class JobConcurrencyManager { final JobStatus pendingJob = contextIdToJobMap[i]; if (DEBUG) { Slog.d(TAG, "About to run job on context " - + String.valueOf(i) + ", job: " + pendingJob); + + i + ", job: " + pendingJob); } for (int ic=0; ic<controllers.size(); ic++) { controllers.get(ic).prepareForExecutionLocked(pendingJob); diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java index 10dc156fbc01..3f9d928e1986 100644 --- a/services/core/java/com/android/server/job/JobSchedulerService.java +++ b/services/core/java/com/android/server/job/JobSchedulerService.java @@ -1261,6 +1261,9 @@ public class JobSchedulerService extends com.android.server.SystemService @Override public void onDeviceIdleStateChanged(boolean deviceIdle) { synchronized (mLock) { + if (DEBUG) { + Slog.d(TAG, "Doze state changed: " + deviceIdle); + } if (deviceIdle) { // When becoming idle, make sure no jobs are actively running, // except those using the idle exemption flag. @@ -1829,6 +1832,9 @@ public class JobSchedulerService extends com.android.server.SystemService } } break; case MSG_CHECK_JOB: + if (DEBUG) { + Slog.d(TAG, "MSG_CHECK_JOB"); + } if (mReportedActive) { // if jobs are currently being run, queue all ready jobs for execution. queueReadyJobsForExecutionLocked(); @@ -1838,6 +1844,9 @@ public class JobSchedulerService extends com.android.server.SystemService } break; case MSG_CHECK_JOB_GREEDY: + if (DEBUG) { + Slog.d(TAG, "MSG_CHECK_JOB_GREEDY"); + } queueReadyJobsForExecutionLocked(); break; case MSG_STOP_JOB: diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java index 93b66208e752..b7bb2c610e62 100644 --- a/services/core/java/com/android/server/media/MediaSessionService.java +++ b/services/core/java/com/android/server/media/MediaSessionService.java @@ -27,8 +27,10 @@ import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; +import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.ServiceInfo; import android.content.pm.UserInfo; import android.database.ContentObserver; import android.media.AudioManager; @@ -634,11 +636,16 @@ public class MediaSessionService extends SystemService implements Monitor { * <p>The contents of this object is guarded by {@link #mLock}. */ final class FullUserRecord implements MediaSessionStack.OnMediaButtonSessionChangedListener { + public static final int COMPONENT_TYPE_BROADCAST = 0; + public static final int COMPONENT_TYPE_ACTIVITY = 1; + public static final int COMPONENT_TYPE_SERVICE = 2; private static final String COMPONENT_NAME_USER_ID_DELIM = ","; + private final int mFullUserId; private final MediaSessionStack mPriorityStack; private PendingIntent mLastMediaButtonReceiver; private ComponentName mRestoredMediaButtonReceiver; + private int mRestoredMediaButtonReceiverComponentType; private int mRestoredMediaButtonReceiverUserId; private IOnVolumeKeyLongPressListener mOnVolumeKeyLongPressListener; @@ -655,17 +662,23 @@ public class MediaSessionService extends SystemService implements Monitor { mFullUserId = fullUserId; mPriorityStack = new MediaSessionStack(mAudioPlayerStateMonitor, this); // Restore the remembered media button receiver before the boot. - String mediaButtonReceiver = Settings.Secure.getStringForUser(mContentResolver, + String mediaButtonReceiverInfo = Settings.Secure.getStringForUser(mContentResolver, Settings.System.MEDIA_BUTTON_RECEIVER, mFullUserId); - if (mediaButtonReceiver == null) { + if (mediaButtonReceiverInfo == null) { return; } - String[] tokens = mediaButtonReceiver.split(COMPONENT_NAME_USER_ID_DELIM); - if (tokens == null || tokens.length != 2) { + String[] tokens = mediaButtonReceiverInfo.split(COMPONENT_NAME_USER_ID_DELIM); + if (tokens == null || (tokens.length != 2 && tokens.length != 3)) { return; } mRestoredMediaButtonReceiver = ComponentName.unflattenFromString(tokens[0]); mRestoredMediaButtonReceiverUserId = Integer.parseInt(tokens[1]); + if (tokens.length == 3) { + mRestoredMediaButtonReceiverComponentType = Integer.parseInt(tokens[2]); + } else { + mRestoredMediaButtonReceiverComponentType = + getComponentType(mRestoredMediaButtonReceiver); + } } public void destroySessionsForUserLocked(int userId) { @@ -696,6 +709,8 @@ public class MediaSessionService extends SystemService implements Monitor { pw.println(indent + "Callback: " + mCallback); pw.println(indent + "Last MediaButtonReceiver: " + mLastMediaButtonReceiver); pw.println(indent + "Restored MediaButtonReceiver: " + mRestoredMediaButtonReceiver); + pw.println(indent + "Restored MediaButtonReceiverComponentType: " + + mRestoredMediaButtonReceiverComponentType); mPriorityStack.dump(pw, indent); } @@ -722,17 +737,21 @@ public class MediaSessionService extends SystemService implements Monitor { PendingIntent receiver = record.getMediaButtonReceiver(); mLastMediaButtonReceiver = receiver; mRestoredMediaButtonReceiver = null; - String componentName = ""; + + String mediaButtonReceiverInfo = ""; if (receiver != null) { ComponentName component = receiver.getIntent().getComponent(); if (component != null && record.getPackageName().equals(component.getPackageName())) { - componentName = component.flattenToString(); + String componentName = component.flattenToString(); + int componentType = getComponentType(component); + mediaButtonReceiverInfo = String.join(COMPONENT_NAME_USER_ID_DELIM, + componentName, String.valueOf(record.getUserId()), + String.valueOf(componentType)); } } Settings.Secure.putStringForUser(mContentResolver, - Settings.System.MEDIA_BUTTON_RECEIVER, - componentName + COMPONENT_NAME_USER_ID_DELIM + record.getUserId(), + Settings.System.MEDIA_BUTTON_RECEIVER, mediaButtonReceiverInfo, mFullUserId); } @@ -762,6 +781,32 @@ public class MediaSessionService extends SystemService implements Monitor { return isGlobalPriorityActiveLocked() ? mGlobalPrioritySession : mPriorityStack.getMediaButtonSession(); } + + private int getComponentType(ComponentName componentName) { + PackageManager pm = getContext().getPackageManager(); + try { + ActivityInfo activityInfo = pm.getActivityInfo(componentName, + PackageManager.MATCH_DIRECT_BOOT_AWARE + | PackageManager.MATCH_DIRECT_BOOT_UNAWARE + | PackageManager.GET_ACTIVITIES); + if (activityInfo != null) { + return COMPONENT_TYPE_ACTIVITY; + } + } catch (NameNotFoundException e) { + } + try { + ServiceInfo serviceInfo = pm.getServiceInfo(componentName, + PackageManager.MATCH_DIRECT_BOOT_AWARE + | PackageManager.MATCH_DIRECT_BOOT_UNAWARE + | PackageManager.GET_SERVICES); + if (serviceInfo != null) { + return COMPONENT_TYPE_SERVICE; + } + } catch (NameNotFoundException e) { + } + // Pick legacy behavior for BroadcastReceiver or unknown. + return COMPONENT_TYPE_BROADCAST; + } } final class SessionsListenerRecord implements IBinder.DeathRecipient { @@ -1580,14 +1625,32 @@ public class MediaSessionService extends SystemService implements Monitor { } else { ComponentName receiver = mCurrentFullUserRecord.mRestoredMediaButtonReceiver; + int componentType = mCurrentFullUserRecord + .mRestoredMediaButtonReceiverComponentType; + UserHandle userHandle = UserHandle.of(mCurrentFullUserRecord + .mRestoredMediaButtonReceiverUserId); if (DEBUG_KEY_EVENT) { Log.d(TAG, "Sending " + keyEvent + " to the restored intent " - + receiver); + + receiver + ", type=" + componentType); } mediaButtonIntent.setComponent(receiver); - getContext().sendBroadcastAsUser(mediaButtonIntent, - UserHandle.of(mCurrentFullUserRecord - .mRestoredMediaButtonReceiverUserId)); + try { + switch (componentType) { + case FullUserRecord.COMPONENT_TYPE_ACTIVITY: + getContext().startActivityAsUser(mediaButtonIntent, userHandle); + break; + case FullUserRecord.COMPONENT_TYPE_SERVICE: + getContext().startForegroundServiceAsUser(mediaButtonIntent, + userHandle); + break; + default: + // Legacy behavior for other cases. + getContext().sendBroadcastAsUser(mediaButtonIntent, userHandle); + } + } catch (Exception e) { + Log.w(TAG, "Error sending media button to the restored intent " + + receiver + ", type=" + componentType, e); + } if (mCurrentFullUserRecord.mCallback != null) { mCurrentFullUserRecord.mCallback .onMediaKeyEventDispatchedToMediaButtonReceiver( diff --git a/services/core/java/com/android/server/notification/NotificationDelegate.java b/services/core/java/com/android/server/notification/NotificationDelegate.java index 84bb13ec92d3..ee60daa20837 100644 --- a/services/core/java/com/android/server/notification/NotificationDelegate.java +++ b/services/core/java/com/android/server/notification/NotificationDelegate.java @@ -45,7 +45,12 @@ public interface NotificationDelegate { void onNotificationExpansionChanged(String key, boolean userAction, boolean expanded); void onNotificationDirectReplied(String key); void onNotificationSettingsViewed(String key); - void onNotificationSmartRepliesAdded(String key, int replyCount); + + /** + * Notifies that smart replies and actions have been added to the UI. + */ + void onNotificationSmartSuggestionsAdded(String key, int smartReplyCount, int smartActionCount, + boolean generatedByAssistant); /** * Notifies a smart reply is sent. diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index bad259bafeb8..8fa3c460529c 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -762,7 +762,13 @@ public class NotificationManagerService extends SystemService { .setType(MetricsEvent.TYPE_ACTION) .setSubtype(actionIndex) .addTaggedData(MetricsEvent.NOTIFICATION_SHADE_INDEX, nv.rank) - .addTaggedData(MetricsEvent.NOTIFICATION_SHADE_COUNT, nv.count)); + .addTaggedData(MetricsEvent.NOTIFICATION_SHADE_COUNT, nv.count) + .addTaggedData(MetricsEvent.NOTIFICATION_ACTION_IS_SMART, + (Notification.Action.SEMANTIC_ACTION_CONTEXTUAL_SUGGESTION + == action.getSemanticAction()) ? 1 : 0) + .addTaggedData( + MetricsEvent.NOTIFICATION_SMART_SUGGESTION_ASSISTANT_GENERATED, + generatedByAssistant ? 1 : 0)); EventLogTags.writeNotificationActionClicked(key, actionIndex, r.getLifespanMs(now), r.getFreshnessMs(now), r.getExposureMs(now), nv.rank, nv.count); @@ -837,15 +843,20 @@ public class NotificationManagerService extends SystemService { if (DBG) Slog.d(TAG, "Marking notification as visible " + nv.key); reportSeen(r); - // If the newly visible notification has smart replies + // If the newly visible notification has smart suggestions // then log that the user has seen them. - if (r.getNumSmartRepliesAdded() > 0 + if ((r.getNumSmartRepliesAdded() > 0 || r.getNumSmartActionsAdded() > 0) && !r.hasSeenSmartReplies()) { r.setSeenSmartReplies(true); LogMaker logMaker = r.getLogMaker() .setCategory(MetricsEvent.SMART_REPLY_VISIBLE) .addTaggedData(MetricsEvent.NOTIFICATION_SMART_REPLY_COUNT, - r.getNumSmartRepliesAdded()); + r.getNumSmartRepliesAdded()) + .addTaggedData(MetricsEvent.NOTIFICATION_SMART_ACTION_COUNT, + r.getNumSmartActionsAdded()) + .addTaggedData( + MetricsEvent.NOTIFICATION_SMART_SUGGESTION_ASSISTANT_GENERATED, + r.getSuggestionsGeneratedByAssistant()); mMetricsLogger.write(logMaker); } } @@ -908,11 +919,14 @@ public class NotificationManagerService extends SystemService { } @Override - public void onNotificationSmartRepliesAdded(String key, int replyCount) { + public void onNotificationSmartSuggestionsAdded(String key, int smartReplyCount, + int smartActionCount, boolean generatedByAssistant) { synchronized (mNotificationLock) { NotificationRecord r = mNotificationsByKey.get(key); if (r != null) { - r.setNumSmartRepliesAdded(replyCount); + r.setNumSmartRepliesAdded(smartReplyCount); + r.setNumSmartActionsAdded(smartActionCount); + r.setSuggestionsGeneratedByAssistant(generatedByAssistant); } } } @@ -920,6 +934,7 @@ public class NotificationManagerService extends SystemService { @Override public void onNotificationSmartReplySent(String key, int replyIndex, CharSequence reply, boolean generatedByAssistant) { + synchronized (mNotificationLock) { NotificationRecord r = mNotificationsByKey.get(key); if (r != null) { @@ -2632,6 +2647,10 @@ public class NotificationManagerService extends SystemService { * Note that since notification posting is done asynchronously, this will not return * notifications that are in the process of being posted. * + * From {@link Build.VERSION_CODES#Q}, will also return notifications you've posted as + * an app's notification delegate via + * {@link NotificationManager#notifyAsPackage(String, String, int, Notification)}. + * * @returns A list of all the package's notifications, in natural order. */ @Override @@ -2674,16 +2693,18 @@ public class NotificationManagerService extends SystemService { private StatusBarNotification sanitizeSbn(String pkg, int userId, StatusBarNotification sbn) { - if (sbn.getPackageName().equals(pkg) && sbn.getUserId() == userId) { - // We could pass back a cloneLight() but clients might get confused and - // try to send this thing back to notify() again, which would not work - // very well. - return new StatusBarNotification( - sbn.getPackageName(), - sbn.getOpPkg(), - sbn.getId(), sbn.getTag(), sbn.getUid(), sbn.getInitialPid(), - sbn.getNotification().clone(), - sbn.getUser(), sbn.getOverrideGroupKey(), sbn.getPostTime()); + if (sbn.getUserId() == userId) { + if (sbn.getPackageName().equals(pkg) || sbn.getOpPkg().equals(pkg)) { + // We could pass back a cloneLight() but clients might get confused and + // try to send this thing back to notify() again, which would not work + // very well. + return new StatusBarNotification( + sbn.getPackageName(), + sbn.getOpPkg(), + sbn.getId(), sbn.getTag(), sbn.getUid(), sbn.getInitialPid(), + sbn.getNotification().clone(), + sbn.getUser(), sbn.getOverrideGroupKey(), sbn.getPostTime()); + } } return null; } diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java index 39451d40a97e..89ec38db99e5 100644 --- a/services/core/java/com/android/server/notification/NotificationRecord.java +++ b/services/core/java/com/android/server/notification/NotificationRecord.java @@ -178,6 +178,8 @@ public final class NotificationRecord { private boolean mTextChanged; private boolean mRecordedInterruption; private int mNumberOfSmartRepliesAdded; + private int mNumberOfSmartActionsAdded; + private boolean mSuggestionsGeneratedByAssistant; private boolean mHasSeenSmartReplies; /** * Whether this notification (and its channels) should be considered user locked. Used in @@ -1140,6 +1142,22 @@ public final class NotificationRecord { return mNumberOfSmartRepliesAdded; } + public void setNumSmartActionsAdded(int noActions) { + mNumberOfSmartActionsAdded = noActions; + } + + public int getNumSmartActionsAdded() { + return mNumberOfSmartActionsAdded; + } + + public void setSuggestionsGeneratedByAssistant(boolean generatedByAssistant) { + mSuggestionsGeneratedByAssistant = generatedByAssistant; + } + + public boolean getSuggestionsGeneratedByAssistant() { + return mSuggestionsGeneratedByAssistant; + } + public boolean hasSeenSmartReplies() { return mHasSeenSmartReplies; } diff --git a/services/core/java/com/android/server/om/IdmapManager.java b/services/core/java/com/android/server/om/IdmapManager.java index 6d5982708f4c..16143d3ae9e0 100644 --- a/services/core/java/com/android/server/om/IdmapManager.java +++ b/services/core/java/com/android/server/om/IdmapManager.java @@ -60,7 +60,6 @@ class IdmapManager { boolean createIdmap(@NonNull final PackageInfo targetPackage, @NonNull final PackageInfo overlayPackage, int userId) { - // unused userId: see comment in OverlayManagerServiceImpl.removeIdmapIfPossible if (DEBUG) { Slog.d(TAG, "create idmap for " + targetPackage.packageName + " and " + overlayPackage.packageName); @@ -70,16 +69,19 @@ class IdmapManager { final String overlayPath = overlayPackage.applicationInfo.getBaseCodePath(); try { if (FEATURE_FLAG_IDMAP2) { - mIdmap2Service.createIdmap(targetPath, overlayPath, userId); + if (mIdmap2Service.verifyIdmap(overlayPath, userId)) { + return true; + } + return mIdmap2Service.createIdmap(targetPath, overlayPath, userId) != null; } else { mInstaller.idmap(targetPath, overlayPath, sharedGid); + return true; } } catch (Exception e) { Slog.w(TAG, "failed to generate idmap for " + targetPath + " and " + overlayPath + ": " + e.getMessage()); return false; } - return true; } boolean removeIdmap(@NonNull final OverlayInfo oi, final int userId) { @@ -88,15 +90,15 @@ class IdmapManager { } try { if (FEATURE_FLAG_IDMAP2) { - mIdmap2Service.removeIdmap(oi.baseCodePath, userId); + return mIdmap2Service.removeIdmap(oi.baseCodePath, userId); } else { mInstaller.removeIdmap(oi.baseCodePath); + return true; } } catch (Exception e) { Slog.w(TAG, "failed to remove idmap for " + oi.baseCodePath + ": " + e.getMessage()); return false; } - return true; } boolean idmapExists(@NonNull final OverlayInfo oi) { diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java index d4719049e426..a7f114655dcb 100644 --- a/services/core/java/com/android/server/om/OverlayManagerService.java +++ b/services/core/java/com/android/server/om/OverlayManagerService.java @@ -223,8 +223,8 @@ public final class OverlayManagerService extends SystemService { public OverlayManagerService(@NonNull final Context context, @NonNull final Installer installer) { super(context); - mSettingsFile = - new AtomicFile(new File(Environment.getDataSystemDirectory(), "overlays.xml"), "overlays"); + mSettingsFile = new AtomicFile( + new File(Environment.getDataSystemDirectory(), "overlays.xml"), "overlays"); mPackageManager = new PackageManagerHelper(); mUserManager = UserManagerService.getInstance(); IdmapManager im = new IdmapManager(installer); @@ -717,9 +717,9 @@ public final class OverlayManagerService extends SystemService { final Map<String, List<String>> pendingChanges = new ArrayMap<>(targetPackageNames.size()); synchronized (mLock) { final List<String> frameworkOverlays = - mImpl.getEnabledOverlayPackageNames("android", userId); - final int N = targetPackageNames.size(); - for (int i = 0; i < N; i++) { + mImpl.getEnabledOverlayPackageNames("android", userId); + final int n = targetPackageNames.size(); + for (int i = 0; i < n; i++) { final String targetPackageName = targetPackageNames.get(i); List<String> list = new ArrayList<>(); if (!"android".equals(targetPackageName)) { @@ -730,8 +730,8 @@ public final class OverlayManagerService extends SystemService { } } - final int N = targetPackageNames.size(); - for (int i = 0; i < N; i++) { + final int n = targetPackageNames.size(); + for (int i = 0; i < n; i++) { final String targetPackageName = targetPackageNames.get(i); if (DEBUG) { Slog.d(TAG, "-> Updating overlay: target=" + targetPackageName + " overlays=[" @@ -789,7 +789,7 @@ public final class OverlayManagerService extends SystemService { if (!mSettingsFile.getBaseFile().exists()) { return; } - try (final FileInputStream stream = mSettingsFile.openRead()) { + try (FileInputStream stream = mSettingsFile.openRead()) { mSettings.restore(stream); // We might have data for dying users if the device was @@ -915,8 +915,8 @@ public final class OverlayManagerService extends SystemService { if (!verbose) { int count = 0; - final int N = mCache.size(); - for (int i = 0; i < N; i++) { + final int n = mCache.size(); + for (int i = 0; i < n; i++) { final int userId = mCache.keyAt(i); count += mCache.get(userId).size(); } @@ -929,8 +929,8 @@ public final class OverlayManagerService extends SystemService { return; } - final int N = mCache.size(); - for (int i = 0; i < N; i++) { + final int n = mCache.size(); + for (int i = 0; i < n; i++) { final int userId = mCache.keyAt(i); pw.println(TAB1 + "User " + userId); final HashMap<String, PackageInfo> map = mCache.get(userId); diff --git a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java index 112059daf95e..b0d2704196a6 100644 --- a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java +++ b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java @@ -55,8 +55,8 @@ import java.util.Set; */ final class OverlayManagerServiceImpl { // Flags to use in conjunction with updateState. - private static final int FLAG_TARGET_IS_UPGRADING = 1<<0; - private static final int FLAG_OVERLAY_IS_UPGRADING = 1<<1; + private static final int FLAG_TARGET_IS_UPGRADING = 1 << 0; + private static final int FLAG_OVERLAY_IS_UPGRADING = 1 << 1; private final PackageManagerHelper mPackageManager; private final IdmapManager mIdmapManager; @@ -89,8 +89,8 @@ final class OverlayManagerServiceImpl { // a change in priority is only relevant for static RROs: specifically, // a regular RRO should not have its state reset only because a change // in priority - if (theTruth.isStaticOverlayPackage() && - theTruth.overlayPriority != oldSettings.priority) { + if (theTruth.isStaticOverlayPackage() + && theTruth.overlayPriority != oldSettings.priority) { return true; } return false; @@ -294,8 +294,8 @@ final class OverlayManagerServiceImpl { final int userId, final int flags) { boolean modified = false; final List<OverlayInfo> ois = mSettings.getOverlaysForTarget(targetPackageName, userId); - final int N = ois.size(); - for (int i = 0; i < N; i++) { + final int n = ois.size(); + for (int i = 0; i < n; i++) { final OverlayInfo oi = ois.get(i); final PackageInfo overlayPackage = mPackageManager.getPackageInfo(oi.packageName, userId); @@ -610,8 +610,8 @@ final class OverlayManagerServiceImpl { final List<OverlayInfo> overlays = mSettings.getOverlaysForTarget(targetPackageName, userId); final List<String> paths = new ArrayList<>(overlays.size()); - final int N = overlays.size(); - for (int i = 0; i < N; i++) { + final int n = overlays.size(); + for (int i = 0; i < n; i++) { final OverlayInfo oi = overlays.get(i); if (oi.isEnabled()) { paths.add(oi.packageName); @@ -632,8 +632,8 @@ final class OverlayManagerServiceImpl { userId); // Static RROs targeting to "android", ie framework-res.apk, are handled by native layers. - if (targetPackage != null && overlayPackage != null && - !("android".equals(targetPackageName) + if (targetPackage != null && overlayPackage != null + && !("android".equals(targetPackageName) && overlayPackage.isStaticOverlayPackage())) { mIdmapManager.createIdmap(targetPackage, overlayPackage, userId); } @@ -663,7 +663,7 @@ final class OverlayManagerServiceImpl { private @OverlayInfo.State int calculateNewState(@Nullable final PackageInfo targetPackage, @Nullable final PackageInfo overlayPackage, final int userId, final int flags) - throws OverlayManagerSettings.BadKeyException { + throws OverlayManagerSettings.BadKeyException { if ((flags & FLAG_TARGET_IS_UPGRADING) != 0) { return STATE_TARGET_UPGRADING; diff --git a/services/core/java/com/android/server/om/OverlayManagerSettings.java b/services/core/java/com/android/server/om/OverlayManagerSettings.java index 572d36897040..ee067460cc45 100644 --- a/services/core/java/com/android/server/om/OverlayManagerSettings.java +++ b/services/core/java/com/android/server/om/OverlayManagerSettings.java @@ -290,21 +290,22 @@ final class OverlayManagerSettings { return; } - final int N = mItems.size(); - for (int i = 0; i < N; i++) { + final int n = mItems.size(); + for (int i = 0; i < n; i++) { final SettingsItem item = mItems.get(i); pw.println(item.mPackageName + ":" + item.getUserId() + " {"); pw.increaseIndent(); - pw.print("mPackageName.......: "); pw.println(item.mPackageName); - pw.print("mUserId............: "); pw.println(item.getUserId()); - pw.print("mTargetPackageName.: "); pw.println(item.getTargetPackageName()); - pw.print("mBaseCodePath......: "); pw.println(item.getBaseCodePath()); - pw.print("mState.............: "); pw.println(OverlayInfo.stateToString(item.getState())); - pw.print("mIsEnabled.........: "); pw.println(item.isEnabled()); - pw.print("mIsStatic..........: "); pw.println(item.isStatic()); - pw.print("mPriority..........: "); pw.println(item.mPriority); - pw.print("mCategory..........: "); pw.println(item.mCategory); + pw.println("mPackageName.......: " + item.mPackageName); + pw.println("mUserId............: " + item.getUserId()); + pw.println("mTargetPackageName.: " + item.getTargetPackageName()); + pw.println("mBaseCodePath......: " + item.getBaseCodePath()); + pw.println("mState.............: " + OverlayInfo.stateToString(item.getState())); + pw.println("mState.............: " + OverlayInfo.stateToString(item.getState())); + pw.println("mIsEnabled.........: " + item.isEnabled()); + pw.println("mIsStatic..........: " + item.isStatic()); + pw.println("mPriority..........: " + item.mPriority); + pw.println("mCategory..........: " + item.mCategory); pw.decreaseIndent(); pw.println("}"); @@ -400,8 +401,8 @@ final class OverlayManagerSettings { xml.startTag(null, TAG_OVERLAYS); XmlUtils.writeIntAttribute(xml, ATTR_VERSION, CURRENT_VERSION); - final int N = table.size(); - for (int i = 0; i < N; i++) { + final int n = table.size(); + for (int i = 0; i < n; i++) { final SettingsItem item = table.get(i); persistRow(xml, item); } @@ -542,8 +543,8 @@ final class OverlayManagerSettings { } private int select(@NonNull final String packageName, final int userId) { - final int N = mItems.size(); - for (int i = 0; i < N; i++) { + final int n = mItems.size(); + for (int i = 0; i < n; i++) { final SettingsItem item = mItems.get(i); if (item.mUserId == userId && item.mPackageName.equals(packageName)) { return i; diff --git a/services/core/java/com/android/server/om/OverlayManagerShellCommand.java b/services/core/java/com/android/server/om/OverlayManagerShellCommand.java index d576d330c4a8..e2b1cba3819f 100644 --- a/services/core/java/com/android/server/om/OverlayManagerShellCommand.java +++ b/services/core/java/com/android/server/om/OverlayManagerShellCommand.java @@ -121,8 +121,8 @@ final class OverlayManagerShellCommand extends ShellCommand { for (final String targetPackageName : allOverlays.keySet()) { out.println(targetPackageName); List<OverlayInfo> overlaysForTarget = allOverlays.get(targetPackageName); - final int N = overlaysForTarget.size(); - for (int i = 0; i < N; i++) { + final int n = overlaysForTarget.size(); + for (int i = 0; i < n; i++) { final OverlayInfo oi = overlaysForTarget.get(i); String status; switch (oi.state) { diff --git a/services/core/java/com/android/server/pm/dex/PackageDexUsage.java b/services/core/java/com/android/server/pm/dex/PackageDexUsage.java index 519a20d51ddb..33a9650a9994 100644 --- a/services/core/java/com/android/server/pm/dex/PackageDexUsage.java +++ b/services/core/java/com/android/server/pm/dex/PackageDexUsage.java @@ -16,9 +16,9 @@ package com.android.server.pm.dex; +import android.os.Build; import android.util.AtomicFile; import android.util.Slog; -import android.os.Build; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; @@ -26,26 +26,27 @@ import com.android.internal.util.FastPrintWriter; import com.android.server.pm.AbstractStatsBase; import com.android.server.pm.PackageManagerServiceUtils; +import dalvik.system.VMRuntime; + +import libcore.io.IoUtils; + import java.io.BufferedReader; import java.io.FileNotFoundException; import java.io.FileOutputStream; -import java.io.InputStreamReader; import java.io.IOException; +import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.Reader; import java.io.StringWriter; import java.io.Writer; import java.util.Collections; -import java.util.Iterator; import java.util.HashMap; import java.util.HashSet; +import java.util.Iterator; import java.util.Map; import java.util.Objects; import java.util.Set; -import dalvik.system.VMRuntime; -import libcore.io.IoUtils; - /** * Stat file which store usage information about dex files. */ @@ -86,6 +87,13 @@ public class PackageDexUsage extends AbstractStatsBase<Void> { private static final String UNSUPPORTED_CLASS_LOADER_CONTEXT = "=UnsupportedClassLoaderContext="; + /** + * Limit on how many secondary DEX paths we store for a single owner, to avoid one app causing + * unbounded memory consumption. + */ + @VisibleForTesting + /* package */ static final int MAX_SECONDARY_FILES_PER_OWNER = 100; + // Map which structures the information we have on a package. // Maps package name to package data (which stores info about UsedByOtherApps and // secondary dex files.). @@ -164,8 +172,12 @@ public class PackageDexUsage extends AbstractStatsBase<Void> { DexUseInfo existingData = packageUseInfo.mDexUseInfoMap.get(dexPath); if (existingData == null) { // It's the first time we see this dex file. - packageUseInfo.mDexUseInfoMap.put(dexPath, newData); - return true; + if (packageUseInfo.mDexUseInfoMap.size() < MAX_SECONDARY_FILES_PER_OWNER) { + packageUseInfo.mDexUseInfoMap.put(dexPath, newData); + return true; + } else { + return updateLoadingPackages; + } } else { if (ownerUserId != existingData.mOwnerUserId) { // Oups, this should never happen, the DexManager who calls this should diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java index 51619cf940a8..164af38be5a6 100644 --- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java +++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java @@ -143,6 +143,13 @@ public final class DefaultPermissionGrantPolicy { LOCATION_PERMISSIONS.add(Manifest.permission.ACCESS_COARSE_LOCATION); } + private static final Set<String> ALWAYS_LOCATION_PERMISSIONS = new ArraySet<>(); + static { + ALWAYS_LOCATION_PERMISSIONS.add(Manifest.permission.ACCESS_FINE_LOCATION); + ALWAYS_LOCATION_PERMISSIONS.add(Manifest.permission.ACCESS_COARSE_LOCATION); + ALWAYS_LOCATION_PERMISSIONS.add(Manifest.permission.ACCESS_BACKGROUND_LOCATION); + } + private static final Set<String> ACTIVITY_RECOGNITION_PERMISSIONS = new ArraySet<>(); static { ACTIVITY_RECOGNITION_PERMISSIONS.add(Manifest.permission.ACTIVITY_RECOGNITION); @@ -690,7 +697,7 @@ public final class DefaultPermissionGrantPolicy { // Companion devices grantSystemFixedPermissionsToSystemPackage( CompanionDeviceManager.COMPANION_DEVICE_DISCOVERY_PACKAGE_NAME, userId, - LOCATION_PERMISSIONS); + ALWAYS_LOCATION_PERMISSIONS); // Ringtone Picker grantSystemFixedPermissionsToSystemPackage( diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java index 6e4c00eed181..02d8c0bcb584 100644 --- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java +++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java @@ -1254,12 +1254,13 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D } @Override - public void onNotificationSmartRepliesAdded(String key, int replyCount) - throws RemoteException { + public void onNotificationSmartSuggestionsAdded(String key, int smartReplyCount, + int smartActionCount, boolean generatedByAssistant) { enforceStatusBarService(); long identity = Binder.clearCallingIdentity(); try { - mNotificationDelegate.onNotificationSmartRepliesAdded(key, replyCount); + mNotificationDelegate.onNotificationSmartSuggestionsAdded(key, smartReplyCount, + smartActionCount, generatedByAssistant); } finally { Binder.restoreCallingIdentity(identity); } diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java index 64ff9cf21ba0..7c61e373c53b 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java @@ -60,6 +60,7 @@ import android.graphics.Rect; import android.hardware.display.DisplayManager; import android.os.Binder; import android.os.Bundle; +import android.os.Debug; import android.os.Environment; import android.os.FileObserver; import android.os.FileUtils; @@ -85,6 +86,7 @@ import android.system.Os; import android.util.EventLog; import android.util.Slog; import android.util.SparseArray; +import android.util.SparseBooleanArray; import android.util.Xml; import android.view.Display; import android.view.IWindowManager; @@ -120,12 +122,13 @@ import java.util.Arrays; import java.util.List; import java.util.Objects; import java.util.function.Consumer; +import java.util.function.Predicate; public class WallpaperManagerService extends IWallpaperManager.Stub implements IWallpaperManagerService { - static final String TAG = "WallpaperManagerService"; - static final boolean DEBUG = false; - static final boolean DEBUG_LIVE = DEBUG || true; + private static final String TAG = "WallpaperManagerService"; + private static final boolean DEBUG = false; + private static final boolean DEBUG_LIVE = true; public static class Lifecycle extends SystemService { private IWallpaperManagerService mService; @@ -163,14 +166,14 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } } - final Object mLock = new Object(); + private final Object mLock = new Object(); /** * Minimum time between crashes of a wallpaper service for us to consider * restarting it vs. just reverting to the static wallpaper. */ - static final long MIN_WALLPAPER_CRASH_TIME = 10000; - static final int MAX_WALLPAPER_COMPONENT_LOG_LENGTH = 128; + private static final long MIN_WALLPAPER_CRASH_TIME = 10000; + private static final int MAX_WALLPAPER_COMPONENT_LOG_LENGTH = 128; static final String WALLPAPER = "wallpaper_orig"; static final String WALLPAPER_CROP = "wallpaper"; static final String WALLPAPER_LOCK_ORIG = "wallpaper_lock_orig"; @@ -178,7 +181,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub static final String WALLPAPER_INFO = "wallpaper_info.xml"; // All the various per-user state files we need to be aware of - static final String[] sPerUserFiles = new String[] { + private static final String[] sPerUserFiles = new String[] { WALLPAPER, WALLPAPER_CROP, WALLPAPER_LOCK_ORIG, WALLPAPER_LOCK_CROP, WALLPAPER_INFO @@ -335,7 +338,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } } - void notifyLockWallpaperChanged() { + private void notifyLockWallpaperChanged() { final IWallpaperManagerCallback cb = mKeyguardListener; if (cb != null) { try { @@ -487,7 +490,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub boolean success = false; // Only generate crop for default display. - final WallpaperData.DisplayData wpData = getDisplayDataOrCreate(wallpaper, DEFAULT_DISPLAY); + final DisplayData wpData = getDisplayDataOrCreate(DEFAULT_DISPLAY); Rect cropHint = new Rect(wallpaper.cropHint); if (DEBUG) { @@ -640,11 +643,11 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } } - final Context mContext; - final IWindowManager mIWindowManager; - final IPackageManager mIPackageManager; - final MyPackageMonitor mMonitor; - final AppOpsManager mAppOpsManager; + private final Context mContext; + private final IWindowManager mIWindowManager; + private final IPackageManager mIPackageManager; + private final MyPackageMonitor mMonitor; + private final AppOpsManager mAppOpsManager; private final DisplayManager mDisplayManager; private final DisplayManager.DisplayListener mDisplayListener = @@ -654,11 +657,23 @@ public class WallpaperManagerService extends IWallpaperManager.Stub public void onDisplayAdded(int displayId) { synchronized (mLock) { if (mLastWallpaper != null) { - final WallpaperConnection.DisplayConnector connector = - mLastWallpaper.connection.getDisplayConnectorOrCreate(displayId); - if (connector == null) return; - - connector.connectLocked(mLastWallpaper.connection, mLastWallpaper); + if (supportsMultiDisplay(mLastWallpaper.connection)) { + final WallpaperConnection.DisplayConnector connector = + mLastWallpaper.connection.getDisplayConnectorOrCreate(displayId); + if (connector == null) return; + connector.connectLocked(mLastWallpaper.connection, mLastWallpaper); + return; + } + // System wallpaper does not support multiple displays, attach this display to + // the fallback wallpaper. + if (mFallbackWallpaper != null) { + final WallpaperConnection.DisplayConnector connector = mFallbackWallpaper + .connection.getDisplayConnectorOrCreate(displayId); + if (connector == null) return; + connector.connectLocked(mFallbackWallpaper.connection, mFallbackWallpaper); + } else { + Slog.w(TAG, "No wallpaper can be added to the new display"); + } } } } @@ -667,12 +682,19 @@ public class WallpaperManagerService extends IWallpaperManager.Stub public void onDisplayRemoved(int displayId) { synchronized (mLock) { if (mLastWallpaper != null) { - final WallpaperConnection.DisplayConnector connector = - mLastWallpaper.connection.getDisplayConnectorOrCreate(displayId); + WallpaperData targetWallpaper = null; + if (mLastWallpaper.connection.containsDisplay(displayId)) { + targetWallpaper = mLastWallpaper; + } else if (mFallbackWallpaper.connection.containsDisplay(displayId)) { + targetWallpaper = mFallbackWallpaper; + } + if (targetWallpaper == null) return; + WallpaperConnection.DisplayConnector connector = + targetWallpaper.connection.getDisplayConnectorOrCreate(displayId); if (connector == null) return; connector.disconnectLocked(); - mLastWallpaper.connection.removeDisplayConnector(displayId); - mLastWallpaper.removeDisplayData(displayId); + targetWallpaper.connection.removeDisplayConnector(displayId); + removeDisplayData(displayId); } } } @@ -686,35 +708,40 @@ public class WallpaperManagerService extends IWallpaperManager.Stub * Map of color listeners per user id. * The key will be the id of a user or UserHandle.USER_ALL - for wildcard listeners. */ - final SparseArray<RemoteCallbackList<IWallpaperManagerCallback>> mColorsChangedListeners; - WallpaperData mLastWallpaper; - IWallpaperManagerCallback mKeyguardListener; - boolean mWaitingForUnlock; - boolean mShuttingDown; + private final SparseArray<RemoteCallbackList<IWallpaperManagerCallback>> + mColorsChangedListeners; + private WallpaperData mLastWallpaper; + private IWallpaperManagerCallback mKeyguardListener; + private boolean mWaitingForUnlock; + private boolean mShuttingDown; /** * ID of the current wallpaper, changed every time anything sets a wallpaper. * This is used for external detection of wallpaper update activity. */ - int mWallpaperId; + private int mWallpaperId; /** * Name of the component used to display bitmap wallpapers from either the gallery or * built-in wallpapers. */ - final ComponentName mImageWallpaper; + private final ComponentName mImageWallpaper; /** * Name of the default wallpaper component; might be different from mImageWallpaper */ - final ComponentName mDefaultWallpaperComponent; + private final ComponentName mDefaultWallpaperComponent; - final SparseArray<WallpaperData> mWallpaperMap = new SparseArray<WallpaperData>(); - final SparseArray<WallpaperData> mLockWallpaperMap = new SparseArray<WallpaperData>(); + private final SparseArray<WallpaperData> mWallpaperMap = new SparseArray<WallpaperData>(); + private final SparseArray<WallpaperData> mLockWallpaperMap = new SparseArray<WallpaperData>(); - final SparseArray<Boolean> mUserRestorecon = new SparseArray<Boolean>(); - int mCurrentUserId = UserHandle.USER_NULL; - boolean mInAmbientMode; + private SparseArray<DisplayData> mDisplayDatas = new SparseArray<>(); + + private WallpaperData mFallbackWallpaper; + + private final SparseBooleanArray mUserRestorecon = new SparseBooleanArray(); + private int mCurrentUserId = UserHandle.USER_NULL; + private boolean mInAmbientMode; static class WallpaperData { @@ -780,18 +807,6 @@ public class WallpaperManagerService extends IWallpaperManager.Stub private RemoteCallbackList<IWallpaperManagerCallback> callbacks = new RemoteCallbackList<IWallpaperManagerCallback>(); - private static final class DisplayData { - int mWidth = -1; - int mHeight = -1; - final Rect mPadding = new Rect(0, 0, 0, 0); - final int mDisplayId; - - DisplayData(int displayId) { - mDisplayId = displayId; - } - } - private SparseArray<DisplayData> mDisplayDatas = new SparseArray<>(); - /** * The crop hint supplied for displaying a subset of the source image */ @@ -812,24 +827,34 @@ public class WallpaperManagerService extends IWallpaperManager.Stub boolean sourceExists() { return wallpaperFile.exists(); } + } - void removeDisplayData(int displayId) { - mDisplayDatas.remove(displayId); + private static final class DisplayData { + int mWidth = -1; + int mHeight = -1; + final Rect mPadding = new Rect(0, 0, 0, 0); + final int mDisplayId; + + DisplayData(int displayId) { + mDisplayId = displayId; } } - private WallpaperData.DisplayData getDisplayDataOrCreate(WallpaperData data, int displayId) { - WallpaperData.DisplayData wpdData = data.mDisplayDatas.get(displayId); + private void removeDisplayData(int displayId) { + mDisplayDatas.remove(displayId); + } + + private DisplayData getDisplayDataOrCreate(int displayId) { + DisplayData wpdData = mDisplayDatas.get(displayId); if (wpdData == null) { - wpdData = new WallpaperData.DisplayData(displayId); + wpdData = new DisplayData(displayId); ensureSaneWallpaperDisplaySize(wpdData, displayId); - data.mDisplayDatas.append(displayId, wpdData); + mDisplayDatas.append(displayId, wpdData); } return wpdData; } - private void ensureSaneWallpaperDisplaySize(WallpaperData.DisplayData wpdData, - int displayId) { + private void ensureSaneWallpaperDisplaySize(DisplayData wpdData, int displayId) { // We always want to have some reasonable width hint. final int baseSize = getMaximumSizeDimension(displayId); if (wpdData.mWidth < baseSize) { @@ -842,12 +867,16 @@ public class WallpaperManagerService extends IWallpaperManager.Stub private int getMaximumSizeDimension(int displayId) { Display display = mDisplayManager.getDisplay(displayId); + if (display == null) { + Slog.w(TAG, "Invalid displayId=" + displayId + " " + Debug.getCallers(4)); + display = mDisplayManager.getDisplay(DEFAULT_DISPLAY); + } return display.getMaximumSizeDimension(); } - void forEachDisplayData(WallpaperData data, Consumer<WallpaperData.DisplayData> action) { - for (int i = data.mDisplayDatas.size() - 1; i >= 0; i--) { - final WallpaperData.DisplayData wpdData = data.mDisplayDatas.valueAt(i); + void forEachDisplayData(Consumer<DisplayData> action) { + for (int i = mDisplayDatas.size() - 1; i >= 0; i--) { + final DisplayData wpdData = mDisplayDatas.valueAt(i); action.accept(wpdData); } } @@ -859,6 +888,45 @@ public class WallpaperManagerService extends IWallpaperManager.Stub return mWallpaperId; } + private boolean supportsMultiDisplay(WallpaperConnection connection) { + if (connection != null) { + return connection.mInfo == null // This is image wallpaper + || connection.mInfo.supportsMultipleDisplays(); + } + return false; + } + + private void updateFallbackConnection() { + if (mLastWallpaper == null || mFallbackWallpaper == null) return; + final WallpaperConnection systemConnection = mLastWallpaper.connection; + final WallpaperConnection fallbackConnection = mFallbackWallpaper.connection; + if (fallbackConnection == null) { + Slog.w(TAG, "Fallback wallpaper connection has not been created yet!!"); + return; + } + if (supportsMultiDisplay(systemConnection) + && fallbackConnection.getConnectedEngineSize() != 0) { + fallbackConnection.forEachDisplayConnector( + WallpaperConnection.DisplayConnector::disconnectLocked); + fallbackConnection.mDisplayConnector.clear(); + } else { + // TODO(b/121181553) Handle wallpaper service disconnect case. + if (fallbackConnection.mService == null) { + Slog.w(TAG, "There is no fallback wallpaper service"); + return; + } + fallbackConnection.appendConnectorWithCondition(display -> + fallbackConnection.isUsableDisplay(display) + && display.getDisplayId() != DEFAULT_DISPLAY + && !fallbackConnection.containsDisplay(display.getDisplayId())); + fallbackConnection.forEachDisplayConnector(connector -> { + if (connector.mEngine == null) { + connector.connectLocked(fallbackConnection, mFallbackWallpaper); + } + }); + } + } + class WallpaperConnection extends IWallpaperConnection.Stub implements ServiceConnection { @@ -877,8 +945,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } void ensureStatusHandled() { - final WallpaperData.DisplayData wpdData = getDisplayDataOrCreate(mWallpaper, - mDisplayId); + final DisplayData wpdData = getDisplayDataOrCreate(mDisplayId); if (mDimensionsChanged) { try { mEngine.setDesiredSize(wpdData.mWidth, wpdData.mHeight); @@ -906,8 +973,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub return; } - final WallpaperData.DisplayData wpdData = getDisplayDataOrCreate(wallpaper, - mDisplayId); + final DisplayData wpdData = getDisplayDataOrCreate(mDisplayId); try { connection.mService.attach(connection, mToken, TYPE_WALLPAPER, false, wpdData.mWidth, wpdData.mHeight, @@ -982,19 +1048,33 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } private void initDisplayState() { + // Do not initialize fallback wallpaper + if (!mWallpaper.equals(mFallbackWallpaper)) { + if (supportsMultiDisplay(this)) { + // The system wallpaper is image wallpaper or it can supports multiple displays. + appendConnectorWithCondition(this::isUsableDisplay); + } else { + // The system wallpaper does not support multiple displays, so just attach it on + // default display. + mDisplayConnector.append(DEFAULT_DISPLAY, + new DisplayConnector(DEFAULT_DISPLAY)); + } + } + } + + private void appendConnectorWithCondition(Predicate<Display> tester) { final Display[] displays = mDisplayManager.getDisplays(); for (Display display : displays) { - if (isUsableDisplay(display)) { + if (tester.test(display)) { final int displayId = display.getDisplayId(); - mDisplayConnector.append(displayId, new DisplayConnector(displayId)); + mDisplayConnector.append(displayId, + new DisplayConnector(displayId)); } } } - // TODO(b/115486823) Support the system decorations change at runtime. private boolean isUsableDisplay(Display display) { return display != null && display.hasAccess(mClientUid) - // TODO(b/114338689) Use WindowManager.supportsSystemDecorations when ready && (display.supportsSystemDecorations() || display.getDisplayId() == DEFAULT_DISPLAY); } @@ -1027,6 +1107,10 @@ public class WallpaperManagerService extends IWallpaperManager.Stub return connector; } + boolean containsDisplay(int displayId) { + return mDisplayConnector.get(displayId) != null; + } + void removeDisplayConnector(int displayId) { final DisplayConnector connector = mDisplayConnector.get(displayId); if (connector != null) { @@ -1044,7 +1128,9 @@ public class WallpaperManagerService extends IWallpaperManager.Stub // when we have an engine, but I'm not sure about // locking there and anyway we always need to be able to // recover if there is something wrong. - saveSettingsLocked(mWallpaper.userId); + if (!mWallpaper.equals(mFallbackWallpaper)) { + saveSettingsLocked(mWallpaper.userId); + } FgThread.getHandler().removeCallbacks(mResetRunnable); } } @@ -1533,8 +1619,8 @@ public class WallpaperManagerService extends IWallpaperManager.Stub // This corrects for mislabeling bugs that might have arisen from move-to // operations involving the wallpaper files. This isn't timing-critical, // so we do it in the background to avoid holding up the user unlock operation. - if (mUserRestorecon.get(userId) != Boolean.TRUE) { - mUserRestorecon.put(userId, Boolean.TRUE); + if (!mUserRestorecon.get(userId)) { + mUserRestorecon.put(userId, true); Runnable relabeler = new Runnable() { @Override public void run() { @@ -1562,7 +1648,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub for (String filename : sPerUserFiles) { new File(wallpaperDir, filename).delete(); } - mUserRestorecon.remove(userId); + mUserRestorecon.delete(userId); } } @@ -1789,7 +1875,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub throw new IllegalArgumentException("Cannot find display with id=" + displayId); } - final WallpaperData.DisplayData wpdData = getDisplayDataOrCreate(wallpaper, displayId); + final DisplayData wpdData = getDisplayDataOrCreate(displayId); if (width != wpdData.mWidth || height != wpdData.mHeight) { wpdData.mWidth = width; wpdData.mHeight = height; @@ -1826,8 +1912,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } WallpaperData wallpaper = mWallpaperMap.get(UserHandle.getCallingUserId()); if (wallpaper != null) { - final WallpaperData.DisplayData wpdData = getDisplayDataOrCreate(wallpaper, - displayId); + final DisplayData wpdData = getDisplayDataOrCreate(displayId); return wpdData.mWidth; } else { return 0; @@ -1845,8 +1930,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } WallpaperData wallpaper = mWallpaperMap.get(UserHandle.getCallingUserId()); if (wallpaper != null) { - final WallpaperData.DisplayData wpdData = getDisplayDataOrCreate(wallpaper, - displayId); + final DisplayData wpdData = getDisplayDataOrCreate(displayId); return wpdData.mHeight; } else { return 0; @@ -1872,7 +1956,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub throw new IllegalArgumentException("padding must be positive: " + padding); } - final WallpaperData.DisplayData wpdData = getDisplayDataOrCreate(wallpaper, displayId); + final DisplayData wpdData = getDisplayDataOrCreate(displayId); if (!padding.equals(wpdData.mPadding)) { wpdData.mPadding.set(padding); if (displayId == DEFAULT_DISPLAY) saveSettingsLocked(userId); @@ -1940,8 +2024,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub return null; } // Only for default display. - final WallpaperData.DisplayData wpdData = getDisplayDataOrCreate(wallpaper, - DEFAULT_DISPLAY); + final DisplayData wpdData = getDisplayDataOrCreate(DEFAULT_DISPLAY); try { if (outParams != null) { outParams.putInt("width", wpdData.mWidth); @@ -2173,14 +2256,8 @@ public class WallpaperManagerService extends IWallpaperManager.Stub // We know a-priori that there is no lock-only wallpaper currently WallpaperData lockWP = new WallpaperData(userId, WALLPAPER_LOCK_ORIG, WALLPAPER_LOCK_CROP); - final WallpaperData.DisplayData lockWPDData = getDisplayDataOrCreate(lockWP, - DEFAULT_DISPLAY); - final WallpaperData.DisplayData sysWPDData = getDisplayDataOrCreate(sysWP, - DEFAULT_DISPLAY); lockWP.wallpaperId = sysWP.wallpaperId; lockWP.cropHint.set(sysWP.cropHint); - lockWPDData.mWidth = sysWPDData.mWidth; - lockWPDData.mHeight = sysWPDData.mHeight; lockWP.allowBackup = sysWP.allowBackup; lockWP.primaryColors = sysWP.primaryColors; @@ -2320,7 +2397,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub return false; } - boolean bindWallpaperComponentLocked(ComponentName componentName, boolean force, + private boolean bindWallpaperComponentLocked(ComponentName componentName, boolean force, boolean fromUser, WallpaperData wallpaper, IRemoteCallback reply) { if (DEBUG_LIVE) { Slog.v(TAG, "bindWallpaperComponentLocked: componentName=" + componentName); @@ -2443,15 +2520,17 @@ public class WallpaperManagerService extends IWallpaperManager.Stub Slog.w(TAG, msg); return false; } - if (wallpaper.userId == mCurrentUserId && mLastWallpaper != null) { + if (wallpaper.userId == mCurrentUserId && mLastWallpaper != null + && !wallpaper.equals(mFallbackWallpaper)) { detachWallpaperLocked(mLastWallpaper); } wallpaper.wallpaperComponent = componentName; wallpaper.connection = newConn; newConn.mReply = reply; - if (wallpaper.userId == mCurrentUserId) { + if (wallpaper.userId == mCurrentUserId && !wallpaper.equals(mFallbackWallpaper)) { mLastWallpaper = wallpaper; } + updateFallbackConnection(); } catch (RemoteException e) { String msg = "Remote exception for " + componentName + "\n" + e; if (fromUser) { @@ -2463,7 +2542,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub return true; } - void detachWallpaperLocked(WallpaperData wallpaper) { + private void detachWallpaperLocked(WallpaperData wallpaper) { if (wallpaper.connection != null) { if (wallpaper.connection.mReply != null) { try { @@ -2473,7 +2552,8 @@ public class WallpaperManagerService extends IWallpaperManager.Stub wallpaper.connection.mReply = null; } mContext.unbindService(wallpaper.connection); - wallpaper.connection.forEachDisplayConnector(connector -> connector.disconnectLocked()); + wallpaper.connection.forEachDisplayConnector( + WallpaperConnection.DisplayConnector::disconnectLocked); wallpaper.connection.mService = null; wallpaper.connection.mDisplayConnector.clear(); wallpaper.connection = null; @@ -2481,12 +2561,12 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } } - void clearWallpaperComponentLocked(WallpaperData wallpaper) { + private void clearWallpaperComponentLocked(WallpaperData wallpaper) { wallpaper.wallpaperComponent = null; detachWallpaperLocked(wallpaper); } - void attachServiceLocked(WallpaperConnection conn, WallpaperData wallpaper) { + private void attachServiceLocked(WallpaperConnection conn, WallpaperData wallpaper) { conn.forEachDisplayConnector(connector-> connector.connectLocked(conn, wallpaper)); } @@ -2596,8 +2676,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub if (DEBUG) { Slog.v(TAG, "writeWallpaperAttributes id=" + wallpaper.wallpaperId); } - final WallpaperData.DisplayData wpdData = getDisplayDataOrCreate(wallpaper, - DEFAULT_DISPLAY); + final DisplayData wpdData = getDisplayDataOrCreate(DEFAULT_DISPLAY); out.startTag(null, tag); out.attribute(null, "id", Integer.toString(wallpaper.wallpaperId)); out.attribute(null, "width", Integer.toString(wpdData.mWidth)); @@ -2755,10 +2834,10 @@ public class WallpaperManagerService extends IWallpaperManager.Stub Slog.i(TAG, "No static wallpaper imagery; defaults will be shown"); } } + initializeFallbackWallpaper(); } boolean success = false; - final WallpaperData.DisplayData wpdData = getDisplayDataOrCreate(wallpaper, - DEFAULT_DISPLAY); + final DisplayData wpdData = getDisplayDataOrCreate(DEFAULT_DISPLAY); try { stream = new FileInputStream(file); XmlPullParser parser = Xml.newPullParser(); @@ -2845,8 +2924,19 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } } + private void initializeFallbackWallpaper() { + if (mFallbackWallpaper == null) { + if (DEBUG) Slog.d(TAG, "Initialize fallback wallpaper"); + mFallbackWallpaper = new WallpaperData( + UserHandle.USER_SYSTEM, WALLPAPER, WALLPAPER_CROP); + mFallbackWallpaper.allowBackup = false; + mFallbackWallpaper.wallpaperId = makeWallpaperIdLocked(); + bindWallpaperComponentLocked(mImageWallpaper, true, false, mFallbackWallpaper, null); + } + } + private void ensureSaneWallpaperData(WallpaperData wallpaper, int displayId) { - final WallpaperData.DisplayData size = getDisplayDataOrCreate(wallpaper, displayId); + final DisplayData size = getDisplayDataOrCreate(displayId); if (displayId == DEFAULT_DISPLAY) { // crop, if not previously specified @@ -2869,7 +2959,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub wallpaper.wallpaperId = makeWallpaperIdLocked(); } - final WallpaperData.DisplayData wpData = getDisplayDataOrCreate(wallpaper, DEFAULT_DISPLAY); + final DisplayData wpData = getDisplayDataOrCreate(DEFAULT_DISPLAY); if (!keepDimensionHints) { wpData.mWidth = Integer.parseInt(parser.getAttributeValue(null, "width")); @@ -2963,7 +3053,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } // Restore the named resource bitmap to both source + crop files - boolean restoreNamedResourceLocked(WallpaperData wallpaper) { + private boolean restoreNamedResourceLocked(WallpaperData wallpaper) { if (wallpaper.name.length() > 4 && "res:".equals(wallpaper.name.substring(0, 4))) { String resName = wallpaper.name.substring(4); @@ -3048,8 +3138,9 @@ public class WallpaperManagerService extends IWallpaperManager.Stub for (int i = 0; i < mWallpaperMap.size(); i++) { WallpaperData wallpaper = mWallpaperMap.valueAt(i); pw.print(" User "); pw.print(wallpaper.userId); - pw.print(": id="); pw.println(wallpaper.wallpaperId); - forEachDisplayData(wallpaper, wpSize -> { + pw.print(": id="); pw.println(wallpaper.wallpaperId); + pw.println(" Display state:"); + forEachDisplayData(wpSize -> { pw.print(" displayId="); pw.println(wpSize.mDisplayId); pw.print(" mWidth="); @@ -3072,11 +3163,11 @@ public class WallpaperManagerService extends IWallpaperManager.Stub pw.println(conn.mInfo.getComponent()); } conn.forEachDisplayConnector(connector -> { - pw.print(" mDisplayId="); + pw.print(" mDisplayId="); pw.println(connector.mDisplayId); - pw.print(" mToken="); + pw.print(" mToken="); pw.println(connector.mToken); - pw.print(" mEngine="); + pw.print(" mEngine="); pw.println(connector.mEngine); }); pw.print(" mService="); @@ -3090,18 +3181,38 @@ public class WallpaperManagerService extends IWallpaperManager.Stub WallpaperData wallpaper = mLockWallpaperMap.valueAt(i); pw.print(" User "); pw.print(wallpaper.userId); pw.print(": id="); pw.println(wallpaper.wallpaperId); - forEachDisplayData(wallpaper, wpSize -> { - pw.print(" displayId="); - pw.println(wpSize.mDisplayId); - pw.print(" mWidth="); pw.print(wpSize.mWidth); - pw.print(" mHeight="); pw.println(wpSize.mHeight); - pw.print(" mPadding="); pw.println(wpSize.mPadding); - }); pw.print(" mCropHint="); pw.println(wallpaper.cropHint); pw.print(" mName="); pw.println(wallpaper.name); pw.print(" mAllowBackup="); pw.println(wallpaper.allowBackup); } - + pw.println("Fallback wallpaper state:"); + pw.print(" User "); pw.print(mFallbackWallpaper.userId); + pw.print(": id="); pw.println(mFallbackWallpaper.wallpaperId); + pw.print(" mCropHint="); pw.println(mFallbackWallpaper.cropHint); + pw.print(" mName="); pw.println(mFallbackWallpaper.name); + pw.print(" mAllowBackup="); pw.println(mFallbackWallpaper.allowBackup); + if (mFallbackWallpaper.connection != null) { + WallpaperConnection conn = mFallbackWallpaper.connection; + pw.print(" Fallback Wallpaper connection "); + pw.print(conn); + pw.println(":"); + if (conn.mInfo != null) { + pw.print(" mInfo.component="); + pw.println(conn.mInfo.getComponent()); + } + conn.forEachDisplayConnector(connector -> { + pw.print(" mDisplayId="); + pw.println(connector.mDisplayId); + pw.print(" mToken="); + pw.println(connector.mToken); + pw.print(" mEngine="); + pw.println(connector.mEngine); + }); + pw.print(" mService="); + pw.println(conn.mService); + pw.print(" mLastDiedTime="); + pw.println(mFallbackWallpaper.lastDiedTime - SystemClock.uptimeMillis()); + } } } } diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java index 2663d997162c..6755c7363ede 100644 --- a/services/core/java/com/android/server/wm/ActivityStack.java +++ b/services/core/java/com/android/server/wm/ActivityStack.java @@ -4914,7 +4914,6 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai // Update override configurations of all tasks in the stack. final Rect taskBounds = tempTaskBounds != null ? tempTaskBounds : bounds; - final Rect insetBounds = tempTaskInsetBounds != null ? tempTaskInsetBounds : taskBounds; mTmpBounds.clear(); mTmpInsetBounds.clear(); @@ -4922,7 +4921,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai for (int i = mTaskHistory.size() - 1; i >= 0; i--) { final TaskRecord task = mTaskHistory.get(i); if (task.isResizeable()) { - task.updateOverrideConfiguration(taskBounds, insetBounds); + task.updateOverrideConfiguration(taskBounds, tempTaskInsetBounds); } if (task.hasDisplayedBounds()) { diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index 8b8cadc7a4a6..6d3c69385a09 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -505,23 +505,6 @@ public class DisplayPolicy { // TODO: Make it can take screenshot on external display mScreenshotHelper = displayContent.isDefaultDisplay ? new ScreenshotHelper(mContext) : null; - } - - void systemReady() { - mSystemGestures.systemReady(); - } - - private int getDisplayId() { - return mDisplayContent.getDisplayId(); - } - - void onDisplayRemoved() { - mDisplayContent.unregisterPointerEventListener(mSystemGestures); - } - - void configure(int width, int height, int shortSizeDp) { - // Allow the navigation bar to move on non-square small devices (phones). - mNavigationBarCanMove = width != height && shortSizeDp < 600; if (mDisplayContent.isDefaultDisplay) { mHasStatusBar = true; @@ -541,6 +524,23 @@ public class DisplayPolicy { } } + void systemReady() { + mSystemGestures.systemReady(); + } + + private int getDisplayId() { + return mDisplayContent.getDisplayId(); + } + + void onDisplayRemoved() { + mDisplayContent.unregisterPointerEventListener(mSystemGestures); + } + + void configure(int width, int height, int shortSizeDp) { + // Allow the navigation bar to move on non-square small devices (phones). + mNavigationBarCanMove = width != height && shortSizeDp < 600; + } + void updateConfigurationDependentBehaviors() { mNavBarOpacityMode = mContext.getResources().getInteger(R.integer.config_navBarOpacityMode); } diff --git a/services/core/java/com/android/server/wm/InputManagerCallback.java b/services/core/java/com/android/server/wm/InputManagerCallback.java index f9c9d33c561a..639ed02a1e48 100644 --- a/services/core/java/com/android/server/wm/InputManagerCallback.java +++ b/services/core/java/com/android/server/wm/InputManagerCallback.java @@ -1,6 +1,5 @@ package com.android.server.wm; -import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; @@ -10,6 +9,7 @@ import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import android.os.Debug; import android.os.IBinder; import android.util.Slog; +import android.view.InputApplicationHandle; import android.view.KeyEvent; import android.view.WindowManager; @@ -204,37 +204,6 @@ final class InputManagerCallback implements InputManagerService.WindowManagerCal + WindowManagerService.TYPE_LAYER_OFFSET; } - /** Callback to get pointer display id. */ - @Override - public int getPointerDisplayId() { - synchronized (mService.mGlobalLock) { - // If desktop mode is not enabled, show on the default display. - if (!mService.mForceDesktopModeOnExternalDisplays) { - return DEFAULT_DISPLAY; - } - - // Look for the topmost freeform display. - int firstExternalDisplayId = DEFAULT_DISPLAY; - for (int i = mService.mRoot.mChildren.size() - 1; i >= 0; --i) { - final DisplayContent displayContent = mService.mRoot.mChildren.get(i); - // Heuristic solution here. Currently when "Freeform windows" developer option is - // enabled we automatically put secondary displays in freeform mode and emulating - // "desktop mode". It also makes sense to show the pointer on the same display. - if (displayContent.getWindowingMode() == WINDOWING_MODE_FREEFORM) { - return displayContent.getDisplayId(); - } - - if (firstExternalDisplayId == DEFAULT_DISPLAY - && displayContent.getDisplayId() != DEFAULT_DISPLAY) { - firstExternalDisplayId = displayContent.getDisplayId(); - } - } - - // Look for the topmost non-default display - return firstExternalDisplayId; - } - } - /** Waits until the built-in input devices have been configured. */ public boolean waitForInputDevicesReady(long timeoutMillis) { synchronized (mInputDevicesReadyMonitor) { diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index 801e5f2038ad..dcade2f012db 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -794,8 +794,9 @@ class RootWindowContainer extends WindowContainer<DisplayContent> private void handleResizingWindows() { for (int i = mWmService.mResizingWindows.size() - 1; i >= 0; i--) { WindowState win = mWmService.mResizingWindows.get(i); - if (win.mAppFreezing) { - // Don't remove this window until rotation has completed. + if (win.mAppFreezing || win.getDisplayContent().mWaitingForConfig) { + // Don't remove this window until rotation has completed and is not waiting for the + // complete configuration. continue; } win.reportResized(); diff --git a/services/core/java/com/android/server/wm/TaskRecord.java b/services/core/java/com/android/server/wm/TaskRecord.java index 5bb64407a28d..8c800097e49d 100644 --- a/services/core/java/com/android/server/wm/TaskRecord.java +++ b/services/core/java/com/android/server/wm/TaskRecord.java @@ -1716,7 +1716,7 @@ class TaskRecord extends ConfigurationContainer { updateTaskDescription(); } - private void adjustForMinimalTaskDimensions(Rect bounds) { + private void adjustForMinimalTaskDimensions(Rect bounds, Rect previousBounds) { if (bounds == null) { return; } @@ -1747,9 +1747,8 @@ class TaskRecord extends ConfigurationContainer { return; } - final Rect configBounds = getRequestedOverrideBounds(); if (adjustWidth) { - if (!configBounds.isEmpty() && bounds.right == configBounds.right) { + if (!previousBounds.isEmpty() && bounds.right == previousBounds.right) { bounds.left = bounds.right - minWidth; } else { // Either left bounds match, or neither match, or the previous bounds were @@ -1758,7 +1757,7 @@ class TaskRecord extends ConfigurationContainer { } } if (adjustHeight) { - if (!configBounds.isEmpty() && bounds.bottom == configBounds.bottom) { + if (!previousBounds.isEmpty() && bounds.bottom == previousBounds.bottom) { bounds.top = bounds.bottom - minHeight; } else { // Either top bounds match, or neither match, or the previous bounds were @@ -2113,11 +2112,15 @@ class TaskRecord extends ConfigurationContainer { // TODO(b/113900640): remove this once ActivityRecord is changed to not need it anymore. void computeResolvedOverrideConfiguration(Configuration inOutConfig, Configuration parentConfig, Configuration overrideConfig) { + // Save previous bounds because adjustForMinimalTaskDimensions uses that to determine if it + // changes left bound vs. right bound, or top bound vs. bottom bound. + mTmpBounds.set(inOutConfig.windowConfiguration.getBounds()); + inOutConfig.setTo(overrideConfig); Rect outOverrideBounds = inOutConfig.windowConfiguration.getBounds(); if (outOverrideBounds != null && !outOverrideBounds.isEmpty()) { - adjustForMinimalTaskDimensions(outOverrideBounds); + adjustForMinimalTaskDimensions(outOverrideBounds, mTmpBounds); int windowingMode = overrideConfig.windowConfiguration.getWindowingMode(); if (windowingMode == WINDOWING_MODE_UNDEFINED) { diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp index bf83ca912eed..43d2dcf7e0d1 100644 --- a/services/core/jni/com_android_server_input_InputManagerService.cpp +++ b/services/core/jni/com_android_server_input_InputManagerService.cpp @@ -107,7 +107,6 @@ static struct { jmethodID getLongPressTimeout; jmethodID getPointerLayer; jmethodID getPointerIcon; - jmethodID getPointerDisplayId; jmethodID getKeyboardLayoutOverlay; jmethodID getDeviceAlias; jmethodID getTouchCalibrationForInputDevice; @@ -175,6 +174,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, }; @@ -234,7 +242,6 @@ public: jfloatArray matrixArr); virtual TouchAffineTransformation getTouchAffineTransformation( const std::string& inputDeviceDescriptor, int32_t surfaceRotation); - virtual void updatePointerDisplay(); /* --- InputDispatcherPolicyInterface implementation --- */ @@ -307,11 +314,10 @@ private: std::atomic<bool> mInteractive; - void updateInactivityTimeoutLocked(); + void updateInactivityTimeoutLocked(const sp<PointerController>& controller); void handleInterceptActions(jint wmActions, nsecs_t when, uint32_t& policyFlags); void ensureSpriteControllerLocked(); - const DisplayViewport* findDisplayViewportLocked(int32_t displayId); - int32_t getPointerDisplayId(); + static bool checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodName); static inline JNIEnv* jniEnv() { @@ -385,10 +391,9 @@ bool NativeInputManager::checkAndClearExceptionFromCallback(JNIEnv* env, const c return false; } -const DisplayViewport* NativeInputManager::findDisplayViewportLocked(int32_t displayId) - REQUIRES(mLock) { - for (const DisplayViewport& v : mLocked.viewports) { - if (v.displayId == displayId) { +static const DisplayViewport* findInternalViewport(const std::vector<DisplayViewport>& viewports) { + for (const DisplayViewport& v : viewports) { + if (v.type == ViewportType::VIEWPORT_INTERNAL) { return &v; } } @@ -415,10 +420,20 @@ void NativeInputManager::setDisplayViewports(JNIEnv* env, jobjectArray viewportO } } - { // acquire lock + const DisplayViewport* newInternalViewport = findInternalViewport(viewports); + { AutoMutex _l(mLock); + 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; - } // release lock + } mInputManager->getReader()->requestRefreshConfiguration( InputReaderConfiguration::CHANGE_DISPLAY_INFO); @@ -541,41 +556,13 @@ sp<PointerControllerInterface> NativeInputManager::obtainPointerController(int32 controller = new PointerController(this, mLooper, mLocked.spriteController); mLocked.pointerController = controller; - updateInactivityTimeoutLocked(); - } - return controller; -} + const DisplayViewport* internalViewport = findInternalViewport(mLocked.viewports); + updatePointerControllerFromViewport(controller, internalViewport); -int32_t NativeInputManager::getPointerDisplayId() { - JNIEnv* env = jniEnv(); - jint pointerDisplayId = env->CallIntMethod(mServiceObj, - gServiceClassInfo.getPointerDisplayId); - if (checkAndClearExceptionFromCallback(env, "getPointerDisplayId")) { - pointerDisplayId = ADISPLAY_ID_DEFAULT; - } - - return pointerDisplayId; -} - -void NativeInputManager::updatePointerDisplay() { - ATRACE_CALL(); - - jint pointerDisplayId = getPointerDisplayId(); - - AutoMutex _l(mLock); - sp<PointerController> controller = mLocked.pointerController.promote(); - if (controller != nullptr) { - const DisplayViewport* viewport = findDisplayViewportLocked(pointerDisplayId); - if (viewport == nullptr) { - ALOGW("Can't find pointer display viewport, fallback to default display."); - viewport = findDisplayViewportLocked(ADISPLAY_ID_DEFAULT); - } - - if (viewport != nullptr) { - controller->setDisplayViewport(*viewport); - } + updateInactivityTimeoutLocked(controller); } + return controller; } void NativeInputManager::ensureSpriteControllerLocked() REQUIRES(mLock) { @@ -834,16 +821,16 @@ void NativeInputManager::setSystemUiVisibility(int32_t visibility) { if (mLocked.systemUiVisibility != visibility) { mLocked.systemUiVisibility = visibility; - updateInactivityTimeoutLocked(); - } -} -void NativeInputManager::updateInactivityTimeoutLocked() REQUIRES(mLock) { - sp<PointerController> controller = mLocked.pointerController.promote(); - if (controller == nullptr) { - return; + sp<PointerController> controller = mLocked.pointerController.promote(); + if (controller != nullptr) { + updateInactivityTimeoutLocked(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 @@ -1837,9 +1824,6 @@ int register_android_server_InputManager(JNIEnv* env) { GET_METHOD_ID(gServiceClassInfo.getPointerIcon, clazz, "getPointerIcon", "()Landroid/view/PointerIcon;"); - GET_METHOD_ID(gServiceClassInfo.getPointerDisplayId, clazz, - "getPointerDisplayId", "()I"); - GET_METHOD_ID(gServiceClassInfo.getKeyboardLayoutOverlay, clazz, "getKeyboardLayoutOverlay", "(Landroid/hardware/input/InputDeviceIdentifier;)[Ljava/lang/String;"); diff --git a/services/net/java/android/net/dhcp/DhcpServer.java b/services/net/java/android/net/dhcp/DhcpServer.java index cee6fa96bbc5..35d29e75c0e4 100644 --- a/services/net/java/android/net/dhcp/DhcpServer.java +++ b/services/net/java/android/net/dhcp/DhcpServer.java @@ -39,7 +39,6 @@ import android.annotation.Nullable; import android.net.MacAddress; import android.net.NetworkUtils; import android.net.TrafficStats; -import android.net.util.InterfaceParams; import android.net.util.SharedLog; import android.os.Handler; import android.os.Looper; @@ -85,7 +84,7 @@ public class DhcpServer { @NonNull private final ServerHandler mHandler; @NonNull - private final InterfaceParams mIface; + private final String mIfName; @NonNull private final DhcpLeaseRepository mLeaseRepo; @NonNull @@ -161,20 +160,20 @@ public class DhcpServer { } } - public DhcpServer(@NonNull Looper looper, @NonNull InterfaceParams iface, + public DhcpServer(@NonNull Looper looper, @NonNull String ifName, @NonNull DhcpServingParams params, @NonNull SharedLog log) { - this(looper, iface, params, log, null); + this(looper, ifName, params, log, null); } @VisibleForTesting - DhcpServer(@NonNull Looper looper, @NonNull InterfaceParams iface, + DhcpServer(@NonNull Looper looper, @NonNull String ifName, @NonNull DhcpServingParams params, @NonNull SharedLog log, @Nullable Dependencies deps) { if (deps == null) { deps = new DependenciesImpl(); } mHandler = new ServerHandler(looper); - mIface = iface; + mIfName = ifName; mServingParams = params; mLog = log; mDeps = deps; @@ -444,7 +443,7 @@ public class DhcpServer { private boolean addArpEntry(@NonNull MacAddress macAddr, @NonNull Inet4Address inetAddr) { try { - mDeps.addArpEntry(inetAddr, macAddr, mIface.name, mSocket); + mDeps.addArpEntry(inetAddr, macAddr, mIfName, mSocket); return true; } catch (IOException e) { mLog.e("Error adding client to ARP table", e); @@ -526,7 +525,7 @@ public class DhcpServer { // SO_BINDTODEVICE actually takes a string. This works because the first member // of struct ifreq is a NULL-terminated interface name. // TODO: add a setsockoptString() - Os.setsockoptIfreq(mSocket, SOL_SOCKET, SO_BINDTODEVICE, mIface.name); + Os.setsockoptIfreq(mSocket, SOL_SOCKET, SO_BINDTODEVICE, mIfName); Os.setsockoptInt(mSocket, SOL_SOCKET, SO_BROADCAST, 1); Os.bind(mSocket, Inet4Address.ANY, DHCP_SERVER); NetworkUtils.protectFromVpn(mSocket); diff --git a/services/net/java/android/net/ip/IpServer.java b/services/net/java/android/net/ip/IpServer.java index 823c0a1ac7b0..493350d776f3 100644 --- a/services/net/java/android/net/ip/IpServer.java +++ b/services/net/java/android/net/ip/IpServer.java @@ -138,9 +138,9 @@ public class IpServer extends StateMachine { return NetdService.getInstance(); } - public DhcpServer makeDhcpServer(Looper looper, InterfaceParams iface, + public DhcpServer makeDhcpServer(Looper looper, String ifName, DhcpServingParams params, SharedLog log) { - return new DhcpServer(looper, iface, params, log); + return new DhcpServer(looper, ifName, params, log); } } @@ -256,12 +256,6 @@ public class IpServer extends StateMachine { if (mUsingLegacyDhcp) { return true; } - - final InterfaceParams ifaceParams = mDeps.getInterfaceParams(mIfaceName); - if (ifaceParams == null) { - Log.e(TAG, "Failed to find interface params for DHCPv4"); - return false; - } final DhcpServingParams params; try { params = new DhcpServingParams.Builder() @@ -277,7 +271,7 @@ public class IpServer extends StateMachine { return false; } - mDhcpServer = mDeps.makeDhcpServer(getHandler().getLooper(), ifaceParams, params, + mDhcpServer = mDeps.makeDhcpServer(getHandler().getLooper(), mIfaceName, params, mLog.forSubComponent("DHCP")); mDhcpServer.start(); return true; diff --git a/services/robotests/Android.mk b/services/robotests/Android.mk index 0c9c85acf6e2..9159f0d47621 100644 --- a/services/robotests/Android.mk +++ b/services/robotests/Android.mk @@ -41,7 +41,8 @@ include $(CLEAR_VARS) LOCAL_MODULE := FrameworksServicesRoboTests -LOCAL_SRC_FILES := $(call all-java-files-under, src) +LOCAL_SRC_FILES := $(call all-java-files-under, src) \ + $(call all-java-files-under, backup/src) LOCAL_RESOURCE_DIR := \ $(LOCAL_PATH)/res @@ -81,4 +82,13 @@ LOCAL_JAVA_LIBRARIES := \ LOCAL_TEST_PACKAGE := FrameworksServicesLib -include external/robolectric-shadows/run_robotests.mk
\ No newline at end of file +LOCAL_ROBOTEST_FILES := $(call find-files-in-subdirs,$(LOCAL_PATH)/src,*Test.java,.) \ + $(call find-files-in-subdirs,$(LOCAL_PATH)/backup/src,*Test.java,.) + +include external/robolectric-shadows/run_robotests.mk + +################################################################### +# include subdir Android.mk files +################################################################### +include $(CLEAR_VARS) +include $(call all-makefiles-under,$(LOCAL_PATH)) diff --git a/services/robotests/backup/Android.mk b/services/robotests/backup/Android.mk new file mode 100644 index 000000000000..cc59b0c9bb16 --- /dev/null +++ b/services/robotests/backup/Android.mk @@ -0,0 +1,84 @@ +# 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. + +LOCAL_PATH := $(call my-dir) + +################################################################### +# BackupFrameworksServicesLib app just for Robolectric test target # +################################################################### +include $(CLEAR_VARS) + +LOCAL_PACKAGE_NAME := BackupFrameworksServicesLib +LOCAL_PRIVATE_PLATFORM_APIS := true +LOCAL_MODULE_TAGS := optional + +LOCAL_PRIVILEGED_MODULE := true + +LOCAL_STATIC_JAVA_LIBRARIES := \ + bmgrlib \ + bu \ + services.backup \ + services.core \ + services.net + +include $(BUILD_PACKAGE) + +################################################################### +# BackupFrameworksServicesLib Robolectric test target. # +################################################################### +include $(CLEAR_VARS) + +LOCAL_MODULE := BackupFrameworksServicesRoboTests + +LOCAL_SRC_FILES := $(call all-java-files-under, src) \ + $(call all-java-files-under, ../src/com/android/server/testing/shadows) + +LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res + +LOCAL_JAVA_RESOURCE_DIRS := config + +# Include the testing libraries +LOCAL_JAVA_LIBRARIES := \ + platform-test-annotations \ + robolectric_android-all-stub \ + Robolectric_all-target \ + mockito-robolectric-prebuilt \ + truth-prebuilt \ + testng + +LOCAL_INSTRUMENTATION_FOR := BackupFrameworksServicesLib + +LOCAL_MODULE_TAGS := optional + +include $(BUILD_STATIC_JAVA_LIBRARY) + +################################################################### +# BackupFrameworksServicesLib runner target to run the previous target. # +################################################################### +include $(CLEAR_VARS) + +LOCAL_MODULE := RunBackupFrameworksServicesRoboTests + +LOCAL_JAVA_LIBRARIES := \ + BackupFrameworksServicesRoboTests \ + platform-test-annotations \ + robolectric_android-all-stub \ + Robolectric_all-target \ + mockito-robolectric-prebuilt \ + truth-prebuilt \ + testng + +LOCAL_TEST_PACKAGE := BackupFrameworksServicesLib + +include external/robolectric-shadows/run_robotests.mk diff --git a/services/robotests/backup/config/robolectric.properties b/services/robotests/backup/config/robolectric.properties new file mode 100644 index 000000000000..850557a9b693 --- /dev/null +++ b/services/robotests/backup/config/robolectric.properties @@ -0,0 +1 @@ +sdk=NEWEST_SDK
\ No newline at end of file diff --git a/services/robotests/src/android/app/backup/BackupUtilsTest.java b/services/robotests/backup/src/android/app/backup/BackupUtilsTest.java index 099cde04bd5e..099cde04bd5e 100644 --- a/services/robotests/src/android/app/backup/BackupUtilsTest.java +++ b/services/robotests/backup/src/android/app/backup/BackupUtilsTest.java diff --git a/services/robotests/src/android/app/backup/ForwardingBackupAgent.java b/services/robotests/backup/src/android/app/backup/ForwardingBackupAgent.java index 4ff5b7c03a8f..4ff5b7c03a8f 100644 --- a/services/robotests/src/android/app/backup/ForwardingBackupAgent.java +++ b/services/robotests/backup/src/android/app/backup/ForwardingBackupAgent.java diff --git a/services/robotests/src/com/android/commands/bmgr/BmgrTest.java b/services/robotests/backup/src/com/android/commands/bmgr/BmgrTest.java index 1705f5b8c704..1705f5b8c704 100644 --- a/services/robotests/src/com/android/commands/bmgr/BmgrTest.java +++ b/services/robotests/backup/src/com/android/commands/bmgr/BmgrTest.java diff --git a/services/robotests/src/com/android/commands/bu/AdbBackupTest.java b/services/robotests/backup/src/com/android/commands/bu/AdbBackupTest.java index 6869f5612c1d..6869f5612c1d 100644 --- a/services/robotests/src/com/android/commands/bu/AdbBackupTest.java +++ b/services/robotests/backup/src/com/android/commands/bu/AdbBackupTest.java diff --git a/services/robotests/src/com/android/server/backup/BackupAgentTimeoutParametersTest.java b/services/robotests/backup/src/com/android/server/backup/BackupAgentTimeoutParametersTest.java index 5b226f36d565..5b226f36d565 100644 --- a/services/robotests/src/com/android/server/backup/BackupAgentTimeoutParametersTest.java +++ b/services/robotests/backup/src/com/android/server/backup/BackupAgentTimeoutParametersTest.java diff --git a/services/robotests/src/com/android/server/backup/BackupManagerConstantsTest.java b/services/robotests/backup/src/com/android/server/backup/BackupManagerConstantsTest.java index affa1f3f97f3..affa1f3f97f3 100644 --- a/services/robotests/src/com/android/server/backup/BackupManagerConstantsTest.java +++ b/services/robotests/backup/src/com/android/server/backup/BackupManagerConstantsTest.java diff --git a/services/robotests/backup/src/com/android/server/backup/BackupManagerServiceTest.java b/services/robotests/backup/src/com/android/server/backup/BackupManagerServiceTest.java new file mode 100644 index 000000000000..83f66c5258b2 --- /dev/null +++ b/services/robotests/backup/src/com/android/server/backup/BackupManagerServiceTest.java @@ -0,0 +1,1480 @@ +/* + * 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.backup; + +import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL; + +import static com.android.server.backup.testing.BackupManagerServiceTestUtils.startBackupThread; +import static com.android.server.backup.testing.TransportData.backupTransport; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.robolectric.Shadows.shadowOf; +import static org.testng.Assert.expectThrows; + +import android.annotation.UserIdInt; +import android.app.Application; +import android.app.backup.IBackupManagerMonitor; +import android.app.backup.IBackupObserver; +import android.app.backup.IFullBackupRestoreObserver; +import android.app.backup.ISelectBackupTransportCallback; +import android.content.Context; +import android.content.Intent; +import android.os.IBinder; +import android.os.ParcelFileDescriptor; +import android.os.UserHandle; +import android.platform.test.annotations.Presubmit; +import android.util.SparseArray; + +import com.android.server.backup.testing.TransportData; +import com.android.server.testing.shadows.ShadowBinder; + +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 org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; +import org.robolectric.shadows.ShadowContextWrapper; + +import java.io.File; +import java.io.FileDescriptor; +import java.io.PrintWriter; + +/** Tests for the user-aware backup/restore system service {@link BackupManagerService}. */ +@RunWith(RobolectricTestRunner.class) +@Config(shadows = {ShadowBinder.class}) +@Presubmit +public class BackupManagerServiceTest { + private static final String TEST_PACKAGE = "package"; + private static final String TEST_TRANSPORT = "transport"; + private static final String[] ADB_TEST_PACKAGES = {TEST_PACKAGE}; + + private ShadowContextWrapper mShadowContext; + private Context mContext; + @UserIdInt private int mUserOneId; + @UserIdInt private int mUserTwoId; + @Mock private UserBackupManagerService mUserOneService; + @Mock private UserBackupManagerService mUserTwoService; + + /** Initialize {@link BackupManagerService}. */ + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + Application application = RuntimeEnvironment.application; + mContext = application; + mShadowContext = shadowOf(application); + + // TODO(b/120212806): Hardcoding system user for now since most methods in BMS don't yet + // take an user parameter (and instead hardcode the system user). + mUserOneId = UserHandle.USER_SYSTEM; + mUserTwoId = mUserOneId + 1; + } + + /** + * Clean up and reset state that was created for testing {@link BackupManagerService} + * operations. + */ + @After + public void tearDown() throws Exception { + ShadowBinder.reset(); + } + + /** + * Test verifying that {@link BackupManagerService#MORE_DEBUG} is set to {@code false}. This is + * specifically to prevent overloading the logs in production. + */ + @Test + public void testMoreDebug_isFalse() throws Exception { + boolean moreDebug = BackupManagerService.MORE_DEBUG; + + assertThat(moreDebug).isFalse(); + } + + /** Test that the constructor does not create {@link UserBackupManagerService} instances. */ + @Test + public void testConstructor_doesNotRegisterUsers() throws Exception { + BackupManagerService backupManagerService = createService(); + + assertThat(backupManagerService.getServiceUsers().size()).isEqualTo(0); + } + + /** Test that the constructor handles {@code null} parameters. */ + @Test + public void testConstructor_withNullContext_throws() throws Exception { + expectThrows( + NullPointerException.class, + () -> + new BackupManagerService( + /* context */ null, + new Trampoline(mContext), + startBackupThread(null))); + } + + /** Test that the constructor handles {@code null} parameters. */ + @Test + public void testConstructor_withNullTrampoline_throws() throws Exception { + expectThrows( + NullPointerException.class, + () -> + new BackupManagerService( + mContext, /* trampoline */ null, startBackupThread(null))); + } + + /** Test that the constructor handles {@code null} parameters. */ + @Test + public void testConstructor_withNullBackupThread_throws() throws Exception { + expectThrows( + NullPointerException.class, + () -> + new BackupManagerService( + mContext, new Trampoline(mContext), /* backupThread */ null)); + } + + /** Test that the service registers users. */ + @Test + public void testStartServiceForUser_registersUser() throws Exception { + BackupManagerService backupManagerService = createService(); + + backupManagerService.startServiceForUser(mUserOneId); + + SparseArray<UserBackupManagerService> serviceUsers = backupManagerService.getServiceUsers(); + assertThat(serviceUsers.size()).isEqualTo(1); + assertThat(serviceUsers.get(mUserOneId)).isNotNull(); + } + + /** Test that the service registers users. */ + @Test + public void testStartServiceForUser_withServiceInstance_registersUser() throws Exception { + BackupManagerService backupManagerService = createService(); + + backupManagerService.startServiceForUser(mUserOneId, mUserOneService); + + SparseArray<UserBackupManagerService> serviceUsers = backupManagerService.getServiceUsers(); + assertThat(serviceUsers.size()).isEqualTo(1); + assertThat(serviceUsers.get(mUserOneId)).isEqualTo(mUserOneService); + } + + // TODO(b/120212806): When BMS methods take in a user parameter, modify unknown user tests to + // check that that we don't call the method on another registered user. Currently these tests + // have no registered users since we hardcode the system user in BMS. + + // --------------------------------------------- + // Backup agent tests + // --------------------------------------------- + + /** Test that the backup service routes methods correctly to the user that requests it. */ + @Test + public void testDataChanged_onRegisteredUser_callsMethodForUser() throws Exception { + BackupManagerService backupManagerService = + createServiceAndRegisterUser(mUserOneId, mUserOneService); + + backupManagerService.dataChanged(TEST_PACKAGE); + + verify(mUserOneService).dataChanged(TEST_PACKAGE); + } + + /** Test that the backup service does not route methods for non-registered users. */ + @Test + public void testDataChanged_onUnknownUser_doesNotPropagateCall() throws Exception { + BackupManagerService backupManagerService = createService(); + + backupManagerService.dataChanged(TEST_PACKAGE); + + verify(mUserOneService, never()).dataChanged(TEST_PACKAGE); + } + + /** Test that the backup service routes methods correctly to the user that requests it. */ + @Test + public void testAgentConnected_onRegisteredUser_callsMethodForUser() throws Exception { + BackupManagerService backupManagerService = + createServiceAndRegisterUser(mUserOneId, mUserOneService); + IBinder agentBinder = mock(IBinder.class); + + backupManagerService.agentConnected(TEST_PACKAGE, agentBinder); + + verify(mUserOneService).agentConnected(TEST_PACKAGE, agentBinder); + } + + /** Test that the backup service does not route methods for non-registered users. */ + @Test + public void testAgentConnected_onUnknownUser_doesNotPropagateCall() throws Exception { + BackupManagerService backupManagerService = createService(); + IBinder agentBinder = mock(IBinder.class); + + backupManagerService.agentConnected(TEST_PACKAGE, agentBinder); + + verify(mUserOneService, never()).agentConnected(TEST_PACKAGE, agentBinder); + } + + /** Test that the backup service routes methods correctly to the user that requests it. */ + @Test + public void testAgentDisconnected_onRegisteredUser_callsMethodForUser() throws Exception { + BackupManagerService backupManagerService = + createServiceAndRegisterUser(mUserOneId, mUserOneService); + + backupManagerService.agentDisconnected(TEST_PACKAGE); + + verify(mUserOneService).agentDisconnected(TEST_PACKAGE); + } + + /** Test that the backup service does not route methods for non-registered users. */ + @Test + public void testAgentDisconnected_onUnknownUser_doesNotPropagateCall() throws Exception { + BackupManagerService backupManagerService = createService(); + + backupManagerService.agentDisconnected(TEST_PACKAGE); + + verify(mUserOneService, never()).agentDisconnected(TEST_PACKAGE); + } + + /** Test that the backup service routes methods correctly to the user that requests it. */ + @Test + public void testOpComplete_onRegisteredUser_callsMethodForUser() throws Exception { + BackupManagerService backupManagerService = + createServiceAndRegisterUser(mUserOneId, mUserOneService); + + backupManagerService.opComplete(/* token */ 0, /* result */ 0L); + + verify(mUserOneService).opComplete(/* token */ 0, /* result */ 0L); + } + + /** Test that the backup service does not route methods for non-registered users. */ + @Test + public void testOpComplete_onUnknownUser_doesNotPropagateCall() throws Exception { + BackupManagerService backupManagerService = createService(); + + backupManagerService.opComplete(/* token */ 0, /* result */ 0L); + + verify(mUserOneService, never()).opComplete(/* token */ 0, /* result */ 0L); + } + + // --------------------------------------------- + // Transport tests + // --------------------------------------------- + + /** Test that the backup service routes methods correctly to the user that requests it. */ + @Test + public void testInitializeTransports_onRegisteredUser_callsMethodForUser() throws Exception { + BackupManagerService backupManagerService = + createServiceAndRegisterUser(mUserOneId, mUserOneService); + String[] transports = {TEST_TRANSPORT}; + + backupManagerService.initializeTransports(transports, /* observer */ null); + + verify(mUserOneService).initializeTransports(transports, /* observer */ null); + } + + /** Test that the backup service does not route methods for non-registered users. */ + @Test + public void testInitializeTransports_onUnknownUser_doesNotPropagateCall() throws Exception { + BackupManagerService backupManagerService = createService(); + String[] transports = {TEST_TRANSPORT}; + + backupManagerService.initializeTransports(transports, /* observer */ null); + + verify(mUserOneService, never()).initializeTransports(transports, /* observer */ null); + } + + /** Test that the backup service routes methods correctly to the user that requests it. */ + @Test + public void testClearBackupData_onRegisteredUser_callsMethodForUser() throws Exception { + BackupManagerService backupManagerService = + createServiceAndRegisterUser(mUserOneId, mUserOneService); + + backupManagerService.clearBackupData(TEST_TRANSPORT, TEST_PACKAGE); + + verify(mUserOneService).clearBackupData(TEST_TRANSPORT, TEST_PACKAGE); + } + + /** Test that the backup service does not route methods for non-registered users. */ + @Test + public void testClearBackupData_onUnknownUser_doesNotPropagateCall() throws Exception { + BackupManagerService backupManagerService = createService(); + + backupManagerService.clearBackupData(TEST_TRANSPORT, TEST_PACKAGE); + + verify(mUserOneService, never()).clearBackupData(TEST_TRANSPORT, TEST_PACKAGE); + } + + /** Test that the backup service routes methods correctly to the user that requests it. */ + @Test + public void testGetCurrentTransport_onRegisteredUser_callsMethodForUser() throws Exception { + BackupManagerService backupManagerService = + createServiceAndRegisterUser(mUserOneId, mUserOneService); + + backupManagerService.getCurrentTransport(); + + verify(mUserOneService).getCurrentTransport(); + } + + /** Test that the backup service does not route methods for non-registered users. */ + @Test + public void testGetCurrentTransport_onUnknownUser_doesNotPropagateCall() throws Exception { + BackupManagerService backupManagerService = createService(); + + backupManagerService.getCurrentTransport(); + + verify(mUserOneService, never()).getCurrentTransport(); + } + + /** Test that the backup service routes methods correctly to the user that requests it. */ + @Test + public void testGetCurrentTransportComponent_onRegisteredUser_callsMethodForUser() + throws Exception { + BackupManagerService backupManagerService = + createServiceAndRegisterUser(mUserOneId, mUserOneService); + + backupManagerService.getCurrentTransportComponent(); + + verify(mUserOneService).getCurrentTransportComponent(); + } + + /** Test that the backup service does not route methods for non-registered users. */ + @Test + public void testGetCurrentTransportComponent_onUnknownUser_doesNotPropagateCall() + throws Exception { + BackupManagerService backupManagerService = createService(); + + backupManagerService.getCurrentTransportComponent(); + + verify(mUserOneService, never()).getCurrentTransportComponent(); + } + + /** Test that the backup service routes methods correctly to the user that requests it. */ + @Test + public void testListAllTransports_onRegisteredUser_callsMethodForUser() throws Exception { + BackupManagerService backupManagerService = + createServiceAndRegisterUser(mUserOneId, mUserOneService); + + backupManagerService.listAllTransports(); + + verify(mUserOneService).listAllTransports(); + } + + /** Test that the backup service does not route methods for non-registered users. */ + @Test + public void testListAllTransports_onUnknownUser_doesNotPropagateCall() throws Exception { + BackupManagerService backupManagerService = createService(); + + backupManagerService.listAllTransports(); + + verify(mUserOneService, never()).listAllTransports(); + } + + /** Test that the backup service routes methods correctly to the user that requests it. */ + @Test + public void testListAllTransportComponents_onRegisteredUser_callsMethodForUser() + throws Exception { + BackupManagerService backupManagerService = + createServiceAndRegisterUser(mUserOneId, mUserOneService); + + backupManagerService.listAllTransportComponents(); + + verify(mUserOneService).listAllTransportComponents(); + } + + /** Test that the backup service does not route methods for non-registered users. */ + @Test + public void testListAllTransportComponents_onUnknownUser_doesNotPropagateCall() + throws Exception { + BackupManagerService backupManagerService = createService(); + + backupManagerService.listAllTransportComponents(); + + verify(mUserOneService, never()).listAllTransportComponents(); + } + + /** Test that the backup service routes methods correctly to the user that requests it. */ + @Test + public void testGetTransportWhitelist_onRegisteredUser_callsMethodForUser() throws Exception { + BackupManagerService backupManagerService = + createServiceAndRegisterUser(mUserOneId, mUserOneService); + + backupManagerService.getTransportWhitelist(); + + verify(mUserOneService).getTransportWhitelist(); + } + + /** Test that the backup service does not route methods for non-registered users. */ + @Test + public void testGetTransportWhitelist_onUnknownUser_doesNotPropagateCall() throws Exception { + BackupManagerService backupManagerService = createService(); + + backupManagerService.getTransportWhitelist(); + + verify(mUserOneService, never()).getTransportWhitelist(); + } + + /** Test that the backup service routes methods correctly to the user that requests it. */ + @Test + public void testUpdateTransportAttributes_onRegisteredUser_callsMethodForUser() + throws Exception { + BackupManagerService backupManagerService = + createServiceAndRegisterUser(mUserOneId, mUserOneService); + TransportData transport = backupTransport(); + Intent configurationIntent = new Intent(); + Intent dataManagementIntent = new Intent(); + + backupManagerService.updateTransportAttributes( + transport.getTransportComponent(), + transport.transportName, + configurationIntent, + "currentDestinationString", + dataManagementIntent, + "dataManagementLabel"); + + verify(mUserOneService) + .updateTransportAttributes( + transport.getTransportComponent(), + transport.transportName, + configurationIntent, + "currentDestinationString", + dataManagementIntent, + "dataManagementLabel"); + } + + /** Test that the backup service does not route methods for non-registered users. */ + @Test + public void testUpdateTransportAttributes_onUnknownUser_doesNotPropagateCall() + throws Exception { + BackupManagerService backupManagerService = createService(); + TransportData transport = backupTransport(); + Intent configurationIntent = new Intent(); + Intent dataManagementIntent = new Intent(); + + backupManagerService.updateTransportAttributes( + transport.getTransportComponent(), + transport.transportName, + configurationIntent, + "currentDestinationString", + dataManagementIntent, + "dataManagementLabel"); + + verify(mUserOneService, never()) + .updateTransportAttributes( + transport.getTransportComponent(), + transport.transportName, + configurationIntent, + "currentDestinationString", + dataManagementIntent, + "dataManagementLabel"); + } + + /** Test that the backup service routes methods correctly to the user that requests it. */ + @Test + public void testSelectBackupTransport_onRegisteredUser_callsMethodForUser() throws Exception { + BackupManagerService backupManagerService = + createServiceAndRegisterUser(mUserOneId, mUserOneService); + + backupManagerService.selectBackupTransport(TEST_TRANSPORT); + + verify(mUserOneService).selectBackupTransport(TEST_TRANSPORT); + } + + /** Test that the backup service does not route methods for non-registered users. */ + @Test + public void testSelectBackupTransport_onUnknownUser_doesNotPropagateCall() throws Exception { + BackupManagerService backupManagerService = createService(); + + backupManagerService.selectBackupTransport(TEST_TRANSPORT); + + verify(mUserOneService, never()).selectBackupTransport(TEST_TRANSPORT); + } + + /** Test that the backup service routes methods correctly to the user that requests it. */ + @Test + public void testSelectTransportAsync_onRegisteredUser_callsMethodForUser() throws Exception { + BackupManagerService backupManagerService = + createServiceAndRegisterUser(mUserOneId, mUserOneService); + TransportData transport = backupTransport(); + ISelectBackupTransportCallback callback = mock(ISelectBackupTransportCallback.class); + + backupManagerService.selectBackupTransportAsync( + transport.getTransportComponent(), callback); + + verify(mUserOneService) + .selectBackupTransportAsync(transport.getTransportComponent(), callback); + } + + /** Test that the backup service does not route methods for non-registered users. */ + @Test + public void testSelectBackupTransportAsync_onUnknownUser_doesNotPropagateCall() + throws Exception { + BackupManagerService backupManagerService = createService(); + TransportData transport = backupTransport(); + ISelectBackupTransportCallback callback = mock(ISelectBackupTransportCallback.class); + + backupManagerService.selectBackupTransportAsync( + transport.getTransportComponent(), callback); + + verify(mUserOneService, never()) + .selectBackupTransportAsync(transport.getTransportComponent(), callback); + } + + /** Test that the backup service routes methods correctly to the user that requests it. */ + @Test + public void testGetConfigurationIntent_onRegisteredUser_callsMethodForUser() throws Exception { + BackupManagerService backupManagerService = + createServiceAndRegisterUser(mUserOneId, mUserOneService); + + backupManagerService.getConfigurationIntent(TEST_TRANSPORT); + + verify(mUserOneService).getConfigurationIntent(TEST_TRANSPORT); + } + + /** Test that the backup service does not route methods for non-registered users. */ + @Test + public void testGetConfigurationIntent_onUnknownUser_doesNotPropagateCall() throws Exception { + BackupManagerService backupManagerService = createService(); + + backupManagerService.getConfigurationIntent(TEST_TRANSPORT); + + verify(mUserOneService, never()).getConfigurationIntent(TEST_TRANSPORT); + } + + /** Test that the backup service routes methods correctly to the user that requests it. */ + @Test + public void testGetDestinationString_onRegisteredUser_callsMethodForUser() throws Exception { + BackupManagerService backupManagerService = + createServiceAndRegisterUser(mUserOneId, mUserOneService); + + backupManagerService.getDestinationString(TEST_TRANSPORT); + + verify(mUserOneService).getDestinationString(TEST_TRANSPORT); + } + + /** Test that the backup service does not route methods for non-registered users. */ + @Test + public void testGetDestinationString_onUnknownUser_doesNotPropagateCall() throws Exception { + BackupManagerService backupManagerService = createService(); + + backupManagerService.getDestinationString(TEST_TRANSPORT); + + verify(mUserOneService, never()).getDestinationString(TEST_TRANSPORT); + } + + /** Test that the backup service routes methods correctly to the user that requests it. */ + @Test + public void testGetDataManagementIntent_onRegisteredUser_callsMethodForUser() throws Exception { + BackupManagerService backupManagerService = + createServiceAndRegisterUser(mUserOneId, mUserOneService); + + backupManagerService.getDataManagementIntent(TEST_TRANSPORT); + + verify(mUserOneService).getDataManagementIntent(TEST_TRANSPORT); + } + + /** Test that the backup service does not route methods for non-registered users. */ + @Test + public void testGetDataManagementIntent_onUnknownUser_doesNotPropagateCall() throws Exception { + BackupManagerService backupManagerService = createService(); + + backupManagerService.getDataManagementIntent(TEST_TRANSPORT); + + verify(mUserOneService, never()).getDataManagementIntent(TEST_TRANSPORT); + } + + /** Test that the backup service routes methods correctly to the user that requests it. */ + @Test + public void testGetDataManagementLabel_onRegisteredUser_callsMethodForUser() throws Exception { + BackupManagerService backupManagerService = + createServiceAndRegisterUser(mUserOneId, mUserOneService); + + backupManagerService.getDataManagementLabel(TEST_TRANSPORT); + + verify(mUserOneService).getDataManagementLabel(TEST_TRANSPORT); + } + + /** Test that the backup service does not route methods for non-registered users. */ + @Test + public void testGetDataManagementLabel_onUnknownUser_doesNotPropagateCall() throws Exception { + BackupManagerService backupManagerService = createService(); + + backupManagerService.getDataManagementLabel(TEST_TRANSPORT); + + verify(mUserOneService, never()).getDataManagementLabel(TEST_TRANSPORT); + } + + // --------------------------------------------- + // Settings tests + // --------------------------------------------- + + /** + * Test that the backup services throws a {@link SecurityException} if the caller does not have + * INTERACT_ACROSS_USERS_FULL permission and passes a different user id. + */ + @Test + public void testSetBackupEnabled_withoutPermission_throwsSecurityExceptionForNonCallingUser() { + BackupManagerService backupManagerService = + createServiceAndRegisterUser(mUserOneId, mUserOneService); + setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false); + + expectThrows( + SecurityException.class, + () -> backupManagerService.setBackupEnabled(mUserTwoId, true)); + } + + /** + * Test that the backup service does not throw a {@link SecurityException} if the caller has + * INTERACT_ACROSS_USERS_FULL permission and passes a different user id. + */ + @Test + public void testSetBackupEnabled_withPermission_propagatesForNonCallingUser() { + BackupManagerService backupManagerService = + createServiceAndRegisterUser(mUserOneId, mUserOneService); + backupManagerService.startServiceForUser(mUserTwoId, mUserTwoService); + setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ true); + + backupManagerService.setBackupEnabled(mUserTwoId, true); + + verify(mUserTwoService).setBackupEnabled(true); + } + + /** Test that the backup service routes methods correctly to the user that requests it. */ + @Test + public void testSetBackupEnabled_onRegisteredUser_callsMethodForUser() throws Exception { + BackupManagerService backupManagerService = + createServiceAndRegisterUser(mUserOneId, mUserOneService); + setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false); + + backupManagerService.setBackupEnabled(mUserOneId, true); + + verify(mUserOneService).setBackupEnabled(true); + } + + /** Test that the backup service does not route methods for non-registered users. */ + @Test + public void testSetBackupEnabled_onUnknownUser_doesNotPropagateCall() throws Exception { + BackupManagerService backupManagerService = + createServiceAndRegisterUser(mUserOneId, mUserOneService); + setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ false); + + backupManagerService.setBackupEnabled(mUserTwoId, true); + + verify(mUserOneService, never()).setBackupEnabled(true); + } + + /** Test that the backup service routes methods correctly to the user that requests it. */ + @Test + public void testSetAutoRestore_onRegisteredUser_callsMethodForUser() throws Exception { + BackupManagerService backupManagerService = + createServiceAndRegisterUser(mUserOneId, mUserOneService); + + backupManagerService.setAutoRestore(true); + + verify(mUserOneService).setAutoRestore(true); + } + + /** Test that the backup service does not route methods for non-registered users. */ + @Test + public void testSetAutoRestore_onUnknownUser_doesNotPropagateCall() throws Exception { + BackupManagerService backupManagerService = createService(); + + backupManagerService.setAutoRestore(true); + + verify(mUserOneService, never()).setAutoRestore(true); + } + + /** Test that the backup service routes methods correctly to the user that requests it. */ + @Test + public void testSetBackupProvisioned_onRegisteredUser_callsMethodForUser() throws Exception { + BackupManagerService backupManagerService = + createServiceAndRegisterUser(mUserOneId, mUserOneService); + + backupManagerService.setBackupProvisioned(true); + + verify(mUserOneService).setBackupProvisioned(true); + } + + /** Test that the backup service does not route methods for non-registered users. */ + @Test + public void testSetBackupProvisioned_onUnknownUser_doesNotPropagateCall() throws Exception { + BackupManagerService backupManagerService = createService(); + + backupManagerService.setBackupProvisioned(true); + + verify(mUserOneService, never()).setBackupProvisioned(true); + } + + /** + * Test that the backup services throws a {@link SecurityException} if the caller does not have + * INTERACT_ACROSS_USERS_FULL permission and passes a different user id. + */ + @Test + public void testIsBackupEnabled_withoutPermission_throwsSecurityExceptionForNonCallingUser() { + BackupManagerService backupManagerService = + createServiceAndRegisterUser(mUserOneId, mUserOneService); + setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false); + + expectThrows( + SecurityException.class, () -> backupManagerService.isBackupEnabled(mUserTwoId)); + } + + /** + * Test that the backup service does not throw a {@link SecurityException} if the caller has + * INTERACT_ACROSS_USERS_FULL permission and passes a different user id. + */ + @Test + public void testIsBackupEnabled_withPermission_propagatesForNonCallingUser() { + BackupManagerService backupManagerService = + createServiceAndRegisterUser(mUserOneId, mUserOneService); + backupManagerService.startServiceForUser(mUserTwoId, mUserTwoService); + setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ true); + + backupManagerService.isBackupEnabled(mUserTwoId); + + verify(mUserTwoService).isBackupEnabled(); + } + + /** Test that the backup service routes methods correctly to the user that requests it. */ + @Test + public void testIsBackupEnabled_onRegisteredUser_callsMethodForUser() throws Exception { + BackupManagerService backupManagerService = + createServiceAndRegisterUser(mUserOneId, mUserOneService); + setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false); + + backupManagerService.isBackupEnabled(mUserOneId); + + verify(mUserOneService).isBackupEnabled(); + } + + /** Test that the backup service does not route methods for non-registered users. */ + @Test + public void testIsBackupEnabled_onUnknownUser_doesNotPropagateCall() throws Exception { + BackupManagerService backupManagerService = + createServiceAndRegisterUser(mUserOneId, mUserOneService); + setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ false); + + backupManagerService.isBackupEnabled(mUserTwoId); + + verify(mUserOneService, never()).isBackupEnabled(); + } + + // --------------------------------------------- + // Backup tests + // --------------------------------------------- + + /** Test that the backup service routes methods correctly to the user that requests it. */ + @Test + public void testIsAppEligibleForBackup_onRegisteredUser_callsMethodForUser() throws Exception { + BackupManagerService backupManagerService = + createServiceAndRegisterUser(mUserOneId, mUserOneService); + + backupManagerService.isAppEligibleForBackup(TEST_PACKAGE); + + verify(mUserOneService).isAppEligibleForBackup(TEST_PACKAGE); + } + + /** Test that the backup service does not route methods for non-registered users. */ + @Test + public void testIsAppEligibleForBackup_onUnknownUser_doesNotPropagateCall() throws Exception { + BackupManagerService backupManagerService = createService(); + + backupManagerService.isAppEligibleForBackup(TEST_PACKAGE); + + verify(mUserOneService, never()).isAppEligibleForBackup(TEST_PACKAGE); + } + + /** Test that the backup service routes methods correctly to the user that requests it. */ + @Test + public void testFilterAppsEligibleForBackup_onRegisteredUser_callsMethodForUser() + throws Exception { + BackupManagerService backupManagerService = + createServiceAndRegisterUser(mUserOneId, mUserOneService); + String[] packages = {TEST_PACKAGE}; + + backupManagerService.filterAppsEligibleForBackup(packages); + + verify(mUserOneService).filterAppsEligibleForBackup(packages); + } + + /** Test that the backup service does not route methods for non-registered users. */ + @Test + public void testFilterAppsEligibleForBackup_onUnknownUser_doesNotPropagateCall() + throws Exception { + BackupManagerService backupManagerService = createService(); + String[] packages = {TEST_PACKAGE}; + + backupManagerService.filterAppsEligibleForBackup(packages); + + verify(mUserOneService, never()).filterAppsEligibleForBackup(packages); + } + + /** + * Test verifying that {@link BackupManagerService#backupNow(int)} throws a {@link + * SecurityException} if the caller does not have INTERACT_ACROSS_USERS_FULL permission. + */ + @Test + public void testBackupNow_withoutPermission_throwsSecurityExceptionForNonCallingUser() { + BackupManagerService backupManagerService = + createServiceAndRegisterUser(mUserOneId, mUserOneService); + setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false); + + expectThrows(SecurityException.class, () -> backupManagerService.backupNow(mUserTwoId)); + } + + /** + * Test that the backup service does not throw a {@link SecurityException} if the caller has + * INTERACT_ACROSS_USERS_FULL permission and passes a different user id. + */ + @Test + public void testBackupNow_withPermission_propagatesForNonCallingUser() { + BackupManagerService backupManagerService = + createServiceAndRegisterUser(mUserOneId, mUserOneService); + backupManagerService.startServiceForUser(mUserTwoId, mUserTwoService); + setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ true); + + backupManagerService.backupNow(mUserTwoId); + + verify(mUserTwoService).backupNow(); + } + + /** Test that the backup service routes methods correctly to the user that requests it. */ + @Test + public void testBackupNow_onRegisteredUser_callsMethodForUser() throws Exception { + BackupManagerService backupManagerService = + createServiceAndRegisterUser(mUserOneId, mUserOneService); + setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false); + + backupManagerService.backupNow(mUserOneId); + + verify(mUserOneService).backupNow(); + } + + /** Test that the backup service does not route methods for non-registered users. */ + @Test + public void testBackupNow_onUnknownUser_doesNotPropagateCall() throws Exception { + BackupManagerService backupManagerService = + createServiceAndRegisterUser(mUserOneId, mUserOneService); + setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ false); + + backupManagerService.backupNow(mUserTwoId); + + verify(mUserOneService, never()).backupNow(); + } + + /** + * Test that the backup services throws a {@link SecurityException} if the caller does not have + * INTERACT_ACROSS_USERS_FULL permission and passes a different user id. + */ + @Test + public void testRequestBackup_withoutPermission_throwsSecurityExceptionForNonCallingUser() { + BackupManagerService backupManagerService = + createServiceAndRegisterUser(mUserOneId, mUserOneService); + setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false); + String[] packages = {TEST_PACKAGE}; + IBackupObserver observer = mock(IBackupObserver.class); + IBackupManagerMonitor monitor = mock(IBackupManagerMonitor.class); + + expectThrows( + SecurityException.class, + () -> + backupManagerService.requestBackup( + mUserTwoId, packages, observer, monitor, 0)); + } + + /** + * Test that the backup service does not throw a {@link SecurityException} if the caller has + * INTERACT_ACROSS_USERS_FULL permission and passes a different user id. + */ + @Test + public void testRequestBackup_withPermission_propagatesForNonCallingUser() { + BackupManagerService backupManagerService = + createServiceAndRegisterUser(mUserOneId, mUserOneService); + backupManagerService.startServiceForUser(mUserTwoId, mUserTwoService); + String[] packages = {TEST_PACKAGE}; + IBackupObserver observer = mock(IBackupObserver.class); + IBackupManagerMonitor monitor = mock(IBackupManagerMonitor.class); + setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ true); + + backupManagerService.requestBackup(mUserTwoId, packages, observer, monitor, /* flags */ 0); + + verify(mUserTwoService).requestBackup(packages, observer, monitor, /* flags */ 0); + } + + /** Test that the backup service routes methods correctly to the user that requests it. */ + @Test + public void testRequestBackup_onRegisteredUser_callsMethodForUser() throws Exception { + BackupManagerService backupManagerService = + createServiceAndRegisterUser(mUserOneId, mUserOneService); + String[] packages = {TEST_PACKAGE}; + IBackupObserver observer = mock(IBackupObserver.class); + IBackupManagerMonitor monitor = mock(IBackupManagerMonitor.class); + setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false); + + backupManagerService.requestBackup(mUserOneId, packages, observer, monitor, /* flags */ 0); + + verify(mUserOneService).requestBackup(packages, observer, monitor, /* flags */ 0); + } + + /** Test that the backup service routes methods correctly to the user that requests it. */ + @Test + public void testRequestBackup_onUnknownUser_doesNotPropagateCall() throws Exception { + BackupManagerService backupManagerService = + createServiceAndRegisterUser(mUserOneId, mUserOneService); + String[] packages = {TEST_PACKAGE}; + IBackupObserver observer = mock(IBackupObserver.class); + IBackupManagerMonitor monitor = mock(IBackupManagerMonitor.class); + setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ false); + + backupManagerService.requestBackup(mUserTwoId, packages, observer, monitor, /* flags */ 0); + + verify(mUserOneService, never()).requestBackup(packages, observer, monitor, /* flags */ 0); + } + + /** + * Test verifying that {@link BackupManagerService#cancelBackups(int)} throws a {@link + * SecurityException} if the caller does not have INTERACT_ACROSS_USERS_FULL permission. + */ + @Test + public void testCancelBackups_withoutPermission_throwsSecurityExceptionForNonCallingUser() { + BackupManagerService backupManagerService = + createServiceAndRegisterUser(mUserOneId, mUserOneService); + setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false); + + expectThrows(SecurityException.class, () -> backupManagerService.cancelBackups(mUserTwoId)); + } + + /** + * Test that the backup service does not throw a {@link SecurityException} if the caller has + * INTERACT_ACROSS_USERS_FULL permission and passes a different user id. + */ + @Test + public void testCancelBackups_withPermission_propagatesForNonCallingUser() { + BackupManagerService backupManagerService = + createServiceAndRegisterUser(mUserOneId, mUserOneService); + backupManagerService.startServiceForUser(mUserTwoId, mUserTwoService); + setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ true); + + backupManagerService.cancelBackups(mUserTwoId); + + verify(mUserTwoService).cancelBackups(); + } + + /** Test that the backup service routes methods correctly to the user that requests it. */ + @Test + public void testCancelBackups_onRegisteredUser_callsMethodForUser() throws Exception { + BackupManagerService backupManagerService = + createServiceAndRegisterUser(mUserOneId, mUserOneService); + setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false); + + backupManagerService.cancelBackups(mUserOneId); + + verify(mUserOneService).cancelBackups(); + } + + /** Test that the backup service does not route methods for non-registered users. */ + @Test + public void testCancelBackups_onUnknownUser_doesNotPropagateCall() throws Exception { + BackupManagerService backupManagerService = + createServiceAndRegisterUser(mUserOneId, mUserOneService); + setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ false); + + backupManagerService.cancelBackups(mUserTwoId); + + verify(mUserOneService, never()).cancelBackups(); + } + + /** Test that the backup service routes methods correctly to the user that requests it. */ + @Test + public void testBeginFullBackup_onRegisteredUser_callsMethodForUser() throws Exception { + BackupManagerService backupManagerService = + createServiceAndRegisterUser(mUserOneId, mUserOneService); + FullBackupJob job = new FullBackupJob(); + + backupManagerService.beginFullBackup(job); + + verify(mUserOneService).beginFullBackup(job); + } + + /** Test that the backup service does not route methods for non-registered users. */ + @Test + public void testBeginFullBackup_onUnknownUser_doesNotPropagateCall() throws Exception { + BackupManagerService backupManagerService = createService(); + FullBackupJob job = new FullBackupJob(); + + backupManagerService.beginFullBackup(job); + + verify(mUserOneService, never()).beginFullBackup(job); + } + + /** Test that the backup service routes methods correctly to the user that requests it. */ + @Test + public void testEndFullBackup_onRegisteredUser_callsMethodForUser() throws Exception { + BackupManagerService backupManagerService = + createServiceAndRegisterUser(mUserOneId, mUserOneService); + + backupManagerService.endFullBackup(); + + verify(mUserOneService).endFullBackup(); + } + + /** Test that the backup service does not route methods for non-registered users. */ + @Test + public void testEndFullBackup_onUnknownUser_doesNotPropagateCall() throws Exception { + BackupManagerService backupManagerService = createService(); + + backupManagerService.endFullBackup(); + + verify(mUserOneService, never()).endFullBackup(); + } + + /** Test that the backup service routes methods correctly to the user that requests it. */ + @Test + public void testFullTransportBackup_onRegisteredUser_callsMethodForUser() throws Exception { + BackupManagerService backupManagerService = + createServiceAndRegisterUser(mUserOneId, mUserOneService); + String[] packages = {TEST_PACKAGE}; + + backupManagerService.fullTransportBackup(packages); + + verify(mUserOneService).fullTransportBackup(packages); + } + + /** Test that the backup service does not route methods for non-registered users. */ + @Test + public void testFullTransportBackup_onUnknownUser_doesNotPropagateCall() throws Exception { + BackupManagerService backupManagerService = createService(); + String[] packages = {TEST_PACKAGE}; + + backupManagerService.fullTransportBackup(packages); + + verify(mUserOneService, never()).fullTransportBackup(packages); + } + + // --------------------------------------------- + // Restore tests + // --------------------------------------------- + + /** Test that the backup service routes methods correctly to the user that requests it. */ + @Test + public void testRestoreAtInstall_onRegisteredUser_callsMethodForUser() throws Exception { + BackupManagerService backupManagerService = + createServiceAndRegisterUser(mUserOneId, mUserOneService); + + backupManagerService.restoreAtInstall(TEST_PACKAGE, /* token */ 0); + + verify(mUserOneService).restoreAtInstall(TEST_PACKAGE, /* token */ 0); + } + + /** Test that the backup service does not route methods for non-registered users. */ + @Test + public void testRestoreAtInstall_onUnknownUser_doesNotPropagateCall() throws Exception { + BackupManagerService backupManagerService = createService(); + + backupManagerService.restoreAtInstall(TEST_PACKAGE, /* token */ 0); + + verify(mUserOneService, never()).restoreAtInstall(TEST_PACKAGE, /* token */ 0); + } + + /** Test that the backup service routes methods correctly to the user that requests it. */ + @Test + public void testBeginRestoreSession_onRegisteredUser_callsMethodForUser() throws Exception { + BackupManagerService backupManagerService = + createServiceAndRegisterUser(mUserOneId, mUserOneService); + + backupManagerService.beginRestoreSession(TEST_PACKAGE, TEST_TRANSPORT); + + verify(mUserOneService).beginRestoreSession(TEST_PACKAGE, TEST_TRANSPORT); + } + + /** Test that the backup service does not route methods for non-registered users. */ + @Test + public void testBeginRestoreSession_onUnknownUser_doesNotPropagateCall() throws Exception { + BackupManagerService backupManagerService = createService(); + + backupManagerService.beginRestoreSession(TEST_PACKAGE, TEST_TRANSPORT); + + verify(mUserOneService, never()).beginRestoreSession(TEST_PACKAGE, TEST_TRANSPORT); + } + + /** Test that the backup service routes methods correctly to the user that requests it. */ + @Test + public void testGetAvailableRestoreToken_onRegisteredUser_callsMethodForUser() + throws Exception { + BackupManagerService backupManagerService = + createServiceAndRegisterUser(mUserOneId, mUserOneService); + + backupManagerService.getAvailableRestoreToken(TEST_PACKAGE); + + verify(mUserOneService).getAvailableRestoreToken(TEST_PACKAGE); + } + + /** Test that the backup service does not route methods for non-registered users. */ + @Test + public void testGetAvailableRestoreToken_onUnknownUser_doesNotPropagateCall() throws Exception { + BackupManagerService backupManagerService = createService(); + + backupManagerService.getAvailableRestoreToken(TEST_PACKAGE); + + verify(mUserOneService, never()).getAvailableRestoreToken(TEST_PACKAGE); + } + + // --------------------------------------------- + // Adb backup/restore tests + // --------------------------------------------- + + /** Test that the backup service routes methods correctly to the user that requests it. */ + @Test + public void testSetBackupPassword_onRegisteredUser_callsMethodForUser() throws Exception { + BackupManagerService backupManagerService = + createServiceAndRegisterUser(mUserOneId, mUserOneService); + + backupManagerService.setBackupPassword("currentPassword", "newPassword"); + + verify(mUserOneService).setBackupPassword("currentPassword", "newPassword"); + } + + /** Test that the backup service does not route methods for non-registered users. */ + @Test + public void testSetBackupPassword_onUnknownUser_doesNotPropagateCall() throws Exception { + BackupManagerService backupManagerService = createService(); + + backupManagerService.setBackupPassword("currentPassword", "newPassword"); + + verify(mUserOneService, never()).setBackupPassword("currentPassword", "newPassword"); + } + + /** Test that the backup service routes methods correctly to the user that requests it. */ + @Test + public void testHasBackupPassword_onRegisteredUser_callsMethodForUser() throws Exception { + BackupManagerService backupManagerService = + createServiceAndRegisterUser(mUserOneId, mUserOneService); + + backupManagerService.hasBackupPassword(); + + verify(mUserOneService).hasBackupPassword(); + } + + /** Test that the backup service does not route methods for non-registered users. */ + @Test + public void testHasBackupPassword_onUnknownUser_doesNotPropagateCall() throws Exception { + BackupManagerService backupManagerService = createService(); + + backupManagerService.hasBackupPassword(); + + verify(mUserOneService, never()).hasBackupPassword(); + } + + /** + * Test that the backup services throws a {@link SecurityException} if the caller does not have + * INTERACT_ACROSS_USERS_FULL permission and passes a different user id. + */ + @Test + public void testAdbBackup_withoutPermission_throwsSecurityExceptionForNonCallingUser() { + BackupManagerService backupManagerService = + createServiceAndRegisterUser(mUserOneId, mUserOneService); + setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false); + + expectThrows( + SecurityException.class, + () -> + backupManagerService.adbBackup( + mUserTwoId, + /* parcelFileDescriptor*/ null, + /* includeApks */ true, + /* includeObbs */ true, + /* includeShared */ true, + /* doWidgets */ true, + /* doAllApps */ true, + /* includeSystem */ true, + /* doCompress */ true, + /* doKeyValue */ true, + null)); + } + + /** + * Test that the backup service does not throw a {@link SecurityException} if the caller has + * INTERACT_ACROSS_USERS_FULL permission and passes a different user id. + */ + @Test + public void testAdbBackup_withPermission_propagatesForNonCallingUser() throws Exception { + BackupManagerService backupManagerService = + createServiceAndRegisterUser(mUserOneId, mUserOneService); + backupManagerService.startServiceForUser(mUserTwoId, mUserTwoService); + ParcelFileDescriptor parcelFileDescriptor = getFileDescriptorForAdbTest(); + setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ true); + + backupManagerService.adbBackup( + mUserTwoId, + parcelFileDescriptor, + /* includeApks */ true, + /* includeObbs */ true, + /* includeShared */ true, + /* doWidgets */ true, + /* doAllApps */ true, + /* includeSystem */ true, + /* doCompress */ true, + /* doKeyValue */ true, + ADB_TEST_PACKAGES); + + verify(mUserTwoService) + .adbBackup( + parcelFileDescriptor, + /* includeApks */ true, + /* includeObbs */ true, + /* includeShared */ true, + /* doWidgets */ true, + /* doAllApps */ true, + /* includeSystem */ true, + /* doCompress */ true, + /* doKeyValue */ true, + ADB_TEST_PACKAGES); + } + + /** Test that the backup service routes methods correctly to the user that requests it. */ + @Test + public void testAdbBackup_onRegisteredUser_callsMethodForUser() throws Exception { + BackupManagerService backupManagerService = + createServiceAndRegisterUser(mUserOneId, mUserOneService); + ParcelFileDescriptor parcelFileDescriptor = getFileDescriptorForAdbTest(); + setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false); + + backupManagerService.adbBackup( + mUserOneId, + parcelFileDescriptor, + /* includeApks */ true, + /* includeObbs */ true, + /* includeShared */ true, + /* doWidgets */ true, + /* doAllApps */ true, + /* includeSystem */ true, + /* doCompress */ true, + /* doKeyValue */ true, + ADB_TEST_PACKAGES); + + verify(mUserOneService) + .adbBackup( + parcelFileDescriptor, + /* includeApks */ true, + /* includeObbs */ true, + /* includeShared */ true, + /* doWidgets */ true, + /* doAllApps */ true, + /* includeSystem */ true, + /* doCompress */ true, + /* doKeyValue */ true, + ADB_TEST_PACKAGES); + } + + /** Test that the backup service does not route methods for non-registered users. */ + @Test + public void testAdbBackup_onUnknownUser_doesNotPropagateCall() throws Exception { + BackupManagerService backupManagerService = + createServiceAndRegisterUser(mUserOneId, mUserOneService); + ParcelFileDescriptor parcelFileDescriptor = getFileDescriptorForAdbTest(); + setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ false); + + backupManagerService.adbBackup( + mUserTwoId, + parcelFileDescriptor, + /* includeApks */ true, + /* includeObbs */ true, + /* includeShared */ true, + /* doWidgets */ true, + /* doAllApps */ true, + /* includeSystem */ true, + /* doCompress */ true, + /* doKeyValue */ true, + ADB_TEST_PACKAGES); + + verify(mUserOneService, never()) + .adbBackup( + parcelFileDescriptor, + /* includeApks */ true, + /* includeObbs */ true, + /* includeShared */ true, + /* doWidgets */ true, + /* doAllApps */ true, + /* includeSystem */ true, + /* doCompress */ true, + /* doKeyValue */ true, + ADB_TEST_PACKAGES); + } + + /** + * Test that the backup services throws a {@link SecurityException} if the caller does not have + * INTERACT_ACROSS_USERS_FULL permission and passes a different user id. + */ + @Test + public void testAdbRestore_withoutPermission_throwsSecurityExceptionForNonCallingUser() { + BackupManagerService backupManagerService = + createServiceAndRegisterUser(mUserOneId, mUserOneService); + setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false); + + expectThrows( + SecurityException.class, () -> backupManagerService.adbRestore(mUserTwoId, null)); + } + + /** + * Test that the backup service does not throw a {@link SecurityException} if the caller has + * INTERACT_ACROSS_USERS_FULL permission and passes a different user id. + */ + @Test + public void testAdbRestore_withPermission_propagatesForNonCallingUser() throws Exception { + BackupManagerService backupManagerService = + createServiceAndRegisterUser(mUserOneId, mUserOneService); + backupManagerService.startServiceForUser(mUserTwoId, mUserTwoService); + ParcelFileDescriptor parcelFileDescriptor = getFileDescriptorForAdbTest(); + setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ true); + + backupManagerService.adbRestore(mUserTwoId, parcelFileDescriptor); + + verify(mUserTwoService).adbRestore(parcelFileDescriptor); + } + + /** Test that the backup service routes methods correctly to the user that requests it. */ + @Test + public void testAdbRestore_onRegisteredUser_callsMethodForUser() throws Exception { + BackupManagerService backupManagerService = + createServiceAndRegisterUser(mUserOneId, mUserOneService); + ParcelFileDescriptor parcelFileDescriptor = getFileDescriptorForAdbTest(); + setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false); + + backupManagerService.adbRestore(mUserOneId, parcelFileDescriptor); + + verify(mUserOneService).adbRestore(parcelFileDescriptor); + } + + /** Test that the backup service does not route methods for non-registered users. */ + @Test + public void testAdbRestore_onUnknownUser_doesNotPropagateCall() throws Exception { + BackupManagerService backupManagerService = + createServiceAndRegisterUser(mUserOneId, mUserOneService); + ParcelFileDescriptor parcelFileDescriptor = getFileDescriptorForAdbTest(); + setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ false); + + backupManagerService.adbRestore(mUserTwoId, parcelFileDescriptor); + + verify(mUserOneService, never()).adbRestore(parcelFileDescriptor); + } + + /** Test that the backup service routes methods correctly to the user that requests it. */ + @Test + public void testAcknowledgeAdbBackupOrRestore_onRegisteredUser_callsMethodForUser() + throws Exception { + BackupManagerService backupManagerService = + createServiceAndRegisterUser(mUserOneId, mUserOneService); + IFullBackupRestoreObserver observer = mock(IFullBackupRestoreObserver.class); + + backupManagerService.acknowledgeAdbBackupOrRestore( + /* token */ 0, /* allow */ true, "currentPassword", "encryptionPassword", observer); + + verify(mUserOneService) + .acknowledgeAdbBackupOrRestore( + /* token */ 0, + /* allow */ true, + "currentPassword", + "encryptionPassword", + observer); + } + + /** Test that the backup service does not route methods for non-registered users. */ + @Test + public void testAcknowledgeAdbBackupOrRestore_onUnknownUser_doesNotPropagateCall() + throws Exception { + BackupManagerService backupManagerService = createService(); + IFullBackupRestoreObserver observer = mock(IFullBackupRestoreObserver.class); + + backupManagerService.acknowledgeAdbBackupOrRestore( + /* token */ 0, /* allow */ true, "currentPassword", "encryptionPassword", observer); + + verify(mUserOneService, never()) + .acknowledgeAdbBackupOrRestore( + /* token */ 0, + /* allow */ true, + "currentPassword", + "encryptionPassword", + observer); + } + + // --------------------------------------------- + // Service tests + // --------------------------------------------- + + /** Test that the backup service routes methods correctly to the user that requests it. */ + @Test + public void testDump_onRegisteredUser_callsMethodForUser() throws Exception { + BackupManagerService backupManagerService = + createServiceAndRegisterUser(mUserOneId, mUserOneService); + File testFile = new File(mContext.getFilesDir(), "test"); + testFile.createNewFile(); + FileDescriptor fileDescriptor = new FileDescriptor(); + PrintWriter printWriter = new PrintWriter(testFile); + String[] args = {"1", "2"}; + + backupManagerService.dump(fileDescriptor, printWriter, args); + + verify(mUserOneService).dump(fileDescriptor, printWriter, args); + } + + /** Test that the backup service does not route methods for non-registered users. */ + @Test + public void testDump_onUnknownUser_doesNotPropagateCall() throws Exception { + BackupManagerService backupManagerService = createService(); + File testFile = new File(mContext.getFilesDir(), "test"); + testFile.createNewFile(); + FileDescriptor fileDescriptor = new FileDescriptor(); + PrintWriter printWriter = new PrintWriter(testFile); + String[] args = {"1", "2"}; + + backupManagerService.dump(fileDescriptor, printWriter, args); + + verify(mUserOneService, never()).dump(fileDescriptor, printWriter, args); + } + + private BackupManagerService createService() { + return new BackupManagerService( + mContext, new Trampoline(mContext), startBackupThread(null)); + } + + private BackupManagerService createServiceAndRegisterUser( + int userId, UserBackupManagerService userBackupManagerService) { + BackupManagerService backupManagerService = createService(); + backupManagerService.startServiceForUser(userId, userBackupManagerService); + return backupManagerService; + } + + /** + * Sets the calling user to {@code userId} and grants the permission INTERACT_ACROSS_USERS_FULL + * to the caller if {@code shouldGrantPermission} is {@code true}, else it denies the + * permission. + */ + private void setCallerAndGrantInteractUserPermission( + @UserIdInt int userId, boolean shouldGrantPermission) { + ShadowBinder.setCallingUserHandle(UserHandle.of(userId)); + if (shouldGrantPermission) { + mShadowContext.grantPermissions(INTERACT_ACROSS_USERS_FULL); + } else { + mShadowContext.denyPermissions(INTERACT_ACROSS_USERS_FULL); + } + } + + private ParcelFileDescriptor getFileDescriptorForAdbTest() throws Exception { + File testFile = new File(mContext.getFilesDir(), "test"); + testFile.createNewFile(); + return ParcelFileDescriptor.open(testFile, ParcelFileDescriptor.MODE_READ_WRITE); + } +} diff --git a/services/robotests/src/com/android/server/backup/KeyValueBackupJobTest.java b/services/robotests/backup/src/com/android/server/backup/KeyValueBackupJobTest.java index 8e17209e1f50..8e17209e1f50 100644 --- a/services/robotests/src/com/android/server/backup/KeyValueBackupJobTest.java +++ b/services/robotests/backup/src/com/android/server/backup/KeyValueBackupJobTest.java diff --git a/services/robotests/src/com/android/server/backup/TransportManagerTest.java b/services/robotests/backup/src/com/android/server/backup/TransportManagerTest.java index 693092da0334..693092da0334 100644 --- a/services/robotests/src/com/android/server/backup/TransportManagerTest.java +++ b/services/robotests/backup/src/com/android/server/backup/TransportManagerTest.java diff --git a/services/robotests/src/com/android/server/backup/UserBackupManagerServiceTest.java b/services/robotests/backup/src/com/android/server/backup/UserBackupManagerServiceTest.java index efbcb960c1e9..1e468d44b8c7 100644 --- a/services/robotests/src/com/android/server/backup/UserBackupManagerServiceTest.java +++ b/services/robotests/backup/src/com/android/server/backup/UserBackupManagerServiceTest.java @@ -87,6 +87,7 @@ public class UserBackupManagerServiceTest { private static final String TAG = "BMSTest"; private static final String PACKAGE_1 = "some.package.1"; private static final String PACKAGE_2 = "some.package.2"; + private static final int USER_ID = 10; @Mock private TransportManager mTransportManager; private HandlerThread mBackupThread; @@ -979,6 +980,7 @@ public class UserBackupManagerServiceTest { mShadowContext.grantPermissions(android.Manifest.permission.BACKUP); UserBackupManagerService.createAndInitializeService( + USER_ID, mContext, new Trampoline(mContext), mBackupThread, @@ -1000,6 +1002,7 @@ public class UserBackupManagerServiceTest { mShadowContext.grantPermissions(android.Manifest.permission.BACKUP); UserBackupManagerService.createAndInitializeService( + USER_ID, mContext, new Trampoline(mContext), mBackupThread, @@ -1022,6 +1025,7 @@ public class UserBackupManagerServiceTest { NullPointerException.class, () -> UserBackupManagerService.createAndInitializeService( + USER_ID, /* context */ null, new Trampoline(mContext), mBackupThread, @@ -1041,6 +1045,7 @@ public class UserBackupManagerServiceTest { NullPointerException.class, () -> UserBackupManagerService.createAndInitializeService( + USER_ID, mContext, /* trampoline */ null, mBackupThread, @@ -1060,6 +1065,7 @@ public class UserBackupManagerServiceTest { NullPointerException.class, () -> UserBackupManagerService.createAndInitializeService( + USER_ID, mContext, new Trampoline(mContext), /* backupThread */ null, @@ -1079,6 +1085,7 @@ public class UserBackupManagerServiceTest { NullPointerException.class, () -> UserBackupManagerService.createAndInitializeService( + USER_ID, mContext, new Trampoline(mContext), mBackupThread, @@ -1089,8 +1096,8 @@ public class UserBackupManagerServiceTest { /** * Test checking non-null argument on {@link - * UserBackupManagerService#createAndInitializeService(Context, Trampoline, HandlerThread, File, - * File, TransportManager)}. + * UserBackupManagerService#createAndInitializeService(int, Context, Trampoline, HandlerThread, + * File, File, TransportManager)}. */ @Test public void testCreateAndInitializeService_withNullDataDir_throws() { @@ -1098,6 +1105,7 @@ public class UserBackupManagerServiceTest { NullPointerException.class, () -> UserBackupManagerService.createAndInitializeService( + USER_ID, mContext, new Trampoline(mContext), mBackupThread, @@ -1108,8 +1116,8 @@ public class UserBackupManagerServiceTest { /** * Test checking non-null argument on {@link - * UserBackupManagerService#createAndInitializeService(Context, Trampoline, HandlerThread, File, - * File, TransportManager)}. + * UserBackupManagerService#createAndInitializeService(int, Context, Trampoline, HandlerThread, + * File, File, TransportManager)}. */ @Test public void testCreateAndInitializeService_withNullTransportManager_throws() { @@ -1117,6 +1125,7 @@ public class UserBackupManagerServiceTest { NullPointerException.class, () -> UserBackupManagerService.createAndInitializeService( + USER_ID, mContext, new Trampoline(mContext), mBackupThread, @@ -1127,7 +1136,7 @@ public class UserBackupManagerServiceTest { private UserBackupManagerService createUserBackupManagerServiceAndRunTasks() { return BackupManagerServiceTestUtils.createUserBackupManagerServiceAndRunTasks( - mContext, mBackupThread, mBaseStateDir, mDataDir, mTransportManager); + USER_ID, mContext, mBackupThread, mBaseStateDir, mDataDir, mTransportManager); } private void setUpPowerManager(UserBackupManagerService backupManagerService) { diff --git a/services/robotests/src/com/android/server/backup/encryption/chunk/ChunkHashTest.java b/services/robotests/backup/src/com/android/server/backup/encryption/chunk/ChunkHashTest.java index 3f57240bc0e9..3f57240bc0e9 100644 --- a/services/robotests/src/com/android/server/backup/encryption/chunk/ChunkHashTest.java +++ b/services/robotests/backup/src/com/android/server/backup/encryption/chunk/ChunkHashTest.java diff --git a/services/robotests/src/com/android/server/backup/encryption/chunk/ChunkListingTest.java b/services/robotests/backup/src/com/android/server/backup/encryption/chunk/ChunkListingTest.java index 4354db72554a..4354db72554a 100644 --- a/services/robotests/src/com/android/server/backup/encryption/chunk/ChunkListingTest.java +++ b/services/robotests/backup/src/com/android/server/backup/encryption/chunk/ChunkListingTest.java diff --git a/services/robotests/src/com/android/server/backup/encryption/chunk/ChunkTest.java b/services/robotests/backup/src/com/android/server/backup/encryption/chunk/ChunkTest.java index 17c9a86169be..17c9a86169be 100644 --- a/services/robotests/src/com/android/server/backup/encryption/chunk/ChunkTest.java +++ b/services/robotests/backup/src/com/android/server/backup/encryption/chunk/ChunkTest.java diff --git a/services/robotests/src/com/android/server/backup/encryption/chunk/EncryptedChunkOrderingTest.java b/services/robotests/backup/src/com/android/server/backup/encryption/chunk/EncryptedChunkOrderingTest.java index 0bf14174e5c3..0bf14174e5c3 100644 --- a/services/robotests/src/com/android/server/backup/encryption/chunk/EncryptedChunkOrderingTest.java +++ b/services/robotests/backup/src/com/android/server/backup/encryption/chunk/EncryptedChunkOrderingTest.java diff --git a/services/robotests/src/com/android/server/backup/encryption/chunking/ChunkEncryptorTest.java b/services/robotests/backup/src/com/android/server/backup/encryption/chunking/ChunkEncryptorTest.java index d0e5fb335da9..d0e5fb335da9 100644 --- a/services/robotests/src/com/android/server/backup/encryption/chunking/ChunkEncryptorTest.java +++ b/services/robotests/backup/src/com/android/server/backup/encryption/chunking/ChunkEncryptorTest.java diff --git a/services/robotests/src/com/android/server/backup/encryption/chunking/ChunkHasherTest.java b/services/robotests/backup/src/com/android/server/backup/encryption/chunking/ChunkHasherTest.java index 2bbbf2857146..2bbbf2857146 100644 --- a/services/robotests/src/com/android/server/backup/encryption/chunking/ChunkHasherTest.java +++ b/services/robotests/backup/src/com/android/server/backup/encryption/chunking/ChunkHasherTest.java diff --git a/services/robotests/src/com/android/server/backup/encryption/chunking/EncryptedChunkTest.java b/services/robotests/backup/src/com/android/server/backup/encryption/chunking/EncryptedChunkTest.java index 8e801a133909..8e801a133909 100644 --- a/services/robotests/src/com/android/server/backup/encryption/chunking/EncryptedChunkTest.java +++ b/services/robotests/backup/src/com/android/server/backup/encryption/chunking/EncryptedChunkTest.java diff --git a/services/robotests/src/com/android/server/backup/encryption/chunking/InlineLengthsEncryptedChunkEncoderTest.java b/services/robotests/backup/src/com/android/server/backup/encryption/chunking/InlineLengthsEncryptedChunkEncoderTest.java index 2f872beacd17..2f872beacd17 100644 --- a/services/robotests/src/com/android/server/backup/encryption/chunking/InlineLengthsEncryptedChunkEncoderTest.java +++ b/services/robotests/backup/src/com/android/server/backup/encryption/chunking/InlineLengthsEncryptedChunkEncoderTest.java diff --git a/services/robotests/src/com/android/server/backup/encryption/chunking/LengthlessEncryptedChunkEncoderTest.java b/services/robotests/backup/src/com/android/server/backup/encryption/chunking/LengthlessEncryptedChunkEncoderTest.java index 978bddb7301a..978bddb7301a 100644 --- a/services/robotests/src/com/android/server/backup/encryption/chunking/LengthlessEncryptedChunkEncoderTest.java +++ b/services/robotests/backup/src/com/android/server/backup/encryption/chunking/LengthlessEncryptedChunkEncoderTest.java diff --git a/services/robotests/src/com/android/server/backup/encryption/chunking/RawBackupWriterTest.java b/services/robotests/backup/src/com/android/server/backup/encryption/chunking/RawBackupWriterTest.java index 19ef8fb339ba..19ef8fb339ba 100644 --- a/services/robotests/src/com/android/server/backup/encryption/chunking/RawBackupWriterTest.java +++ b/services/robotests/backup/src/com/android/server/backup/encryption/chunking/RawBackupWriterTest.java diff --git a/services/robotests/src/com/android/server/backup/encryption/chunking/cdc/ContentDefinedChunkerTest.java b/services/robotests/backup/src/com/android/server/backup/encryption/chunking/cdc/ContentDefinedChunkerTest.java index 77b734785424..77b734785424 100644 --- a/services/robotests/src/com/android/server/backup/encryption/chunking/cdc/ContentDefinedChunkerTest.java +++ b/services/robotests/backup/src/com/android/server/backup/encryption/chunking/cdc/ContentDefinedChunkerTest.java diff --git a/services/robotests/src/com/android/server/backup/encryption/chunking/cdc/FingerprintMixerTest.java b/services/robotests/backup/src/com/android/server/backup/encryption/chunking/cdc/FingerprintMixerTest.java index 936b5dca033d..936b5dca033d 100644 --- a/services/robotests/src/com/android/server/backup/encryption/chunking/cdc/FingerprintMixerTest.java +++ b/services/robotests/backup/src/com/android/server/backup/encryption/chunking/cdc/FingerprintMixerTest.java diff --git a/services/robotests/src/com/android/server/backup/encryption/chunking/cdc/HkdfTest.java b/services/robotests/backup/src/com/android/server/backup/encryption/chunking/cdc/HkdfTest.java index 549437454e9c..549437454e9c 100644 --- a/services/robotests/src/com/android/server/backup/encryption/chunking/cdc/HkdfTest.java +++ b/services/robotests/backup/src/com/android/server/backup/encryption/chunking/cdc/HkdfTest.java diff --git a/services/robotests/src/com/android/server/backup/encryption/chunking/cdc/IsChunkBreakpointTest.java b/services/robotests/backup/src/com/android/server/backup/encryption/chunking/cdc/IsChunkBreakpointTest.java index 277dc372e73c..277dc372e73c 100644 --- a/services/robotests/src/com/android/server/backup/encryption/chunking/cdc/IsChunkBreakpointTest.java +++ b/services/robotests/backup/src/com/android/server/backup/encryption/chunking/cdc/IsChunkBreakpointTest.java diff --git a/services/robotests/src/com/android/server/backup/encryption/chunking/cdc/RabinFingerprint64Test.java b/services/robotests/backup/src/com/android/server/backup/encryption/chunking/cdc/RabinFingerprint64Test.java index 729580cf5101..729580cf5101 100644 --- a/services/robotests/src/com/android/server/backup/encryption/chunking/cdc/RabinFingerprint64Test.java +++ b/services/robotests/backup/src/com/android/server/backup/encryption/chunking/cdc/RabinFingerprint64Test.java diff --git a/services/robotests/src/com/android/server/backup/encryption/keys/RecoverableKeyStoreSecondaryKeyManagerTest.java b/services/robotests/backup/src/com/android/server/backup/encryption/keys/RecoverableKeyStoreSecondaryKeyManagerTest.java index 5342efa18a97..5342efa18a97 100644 --- a/services/robotests/src/com/android/server/backup/encryption/keys/RecoverableKeyStoreSecondaryKeyManagerTest.java +++ b/services/robotests/backup/src/com/android/server/backup/encryption/keys/RecoverableKeyStoreSecondaryKeyManagerTest.java diff --git a/services/robotests/src/com/android/server/backup/encryption/keys/RecoverableKeyStoreSecondaryKeyTest.java b/services/robotests/backup/src/com/android/server/backup/encryption/keys/RecoverableKeyStoreSecondaryKeyTest.java index 89977f82c145..89977f82c145 100644 --- a/services/robotests/src/com/android/server/backup/encryption/keys/RecoverableKeyStoreSecondaryKeyTest.java +++ b/services/robotests/backup/src/com/android/server/backup/encryption/keys/RecoverableKeyStoreSecondaryKeyTest.java diff --git a/services/robotests/src/com/android/server/backup/encryption/keys/TertiaryKeyGeneratorTest.java b/services/robotests/backup/src/com/android/server/backup/encryption/keys/TertiaryKeyGeneratorTest.java index 48216f8d7aca..48216f8d7aca 100644 --- a/services/robotests/src/com/android/server/backup/encryption/keys/TertiaryKeyGeneratorTest.java +++ b/services/robotests/backup/src/com/android/server/backup/encryption/keys/TertiaryKeyGeneratorTest.java diff --git a/services/robotests/src/com/android/server/backup/encryption/keys/TertiaryKeyRotationTrackerTest.java b/services/robotests/backup/src/com/android/server/backup/encryption/keys/TertiaryKeyRotationTrackerTest.java index 49bb410ceb65..49bb410ceb65 100644 --- a/services/robotests/src/com/android/server/backup/encryption/keys/TertiaryKeyRotationTrackerTest.java +++ b/services/robotests/backup/src/com/android/server/backup/encryption/keys/TertiaryKeyRotationTrackerTest.java diff --git a/services/robotests/src/com/android/server/backup/encryption/storage/BackupEncryptionDbTest.java b/services/robotests/backup/src/com/android/server/backup/encryption/storage/BackupEncryptionDbTest.java index 87f21bfa59c2..87f21bfa59c2 100644 --- a/services/robotests/src/com/android/server/backup/encryption/storage/BackupEncryptionDbTest.java +++ b/services/robotests/backup/src/com/android/server/backup/encryption/storage/BackupEncryptionDbTest.java diff --git a/services/robotests/src/com/android/server/backup/encryption/storage/TertiaryKeysTableTest.java b/services/robotests/backup/src/com/android/server/backup/encryption/storage/TertiaryKeysTableTest.java index 319ec89f445e..319ec89f445e 100644 --- a/services/robotests/src/com/android/server/backup/encryption/storage/TertiaryKeysTableTest.java +++ b/services/robotests/backup/src/com/android/server/backup/encryption/storage/TertiaryKeysTableTest.java diff --git a/services/robotests/src/com/android/server/backup/fullbackup/AppMetadataBackupWriterTest.java b/services/robotests/backup/src/com/android/server/backup/fullbackup/AppMetadataBackupWriterTest.java index 423512ce4b70..423512ce4b70 100644 --- a/services/robotests/src/com/android/server/backup/fullbackup/AppMetadataBackupWriterTest.java +++ b/services/robotests/backup/src/com/android/server/backup/fullbackup/AppMetadataBackupWriterTest.java diff --git a/services/robotests/src/com/android/server/backup/internal/PerformInitializeTaskTest.java b/services/robotests/backup/src/com/android/server/backup/internal/PerformInitializeTaskTest.java index a14cc51a3ab6..a14cc51a3ab6 100644 --- a/services/robotests/src/com/android/server/backup/internal/PerformInitializeTaskTest.java +++ b/services/robotests/backup/src/com/android/server/backup/internal/PerformInitializeTaskTest.java diff --git a/services/robotests/src/com/android/server/backup/keyvalue/AgentExceptionTest.java b/services/robotests/backup/src/com/android/server/backup/keyvalue/AgentExceptionTest.java index b00b922ed3d0..b00b922ed3d0 100644 --- a/services/robotests/src/com/android/server/backup/keyvalue/AgentExceptionTest.java +++ b/services/robotests/backup/src/com/android/server/backup/keyvalue/AgentExceptionTest.java diff --git a/services/robotests/src/com/android/server/backup/keyvalue/BackupExceptionTest.java b/services/robotests/backup/src/com/android/server/backup/keyvalue/BackupExceptionTest.java index d5603d64687a..d5603d64687a 100644 --- a/services/robotests/src/com/android/server/backup/keyvalue/BackupExceptionTest.java +++ b/services/robotests/backup/src/com/android/server/backup/keyvalue/BackupExceptionTest.java diff --git a/services/robotests/src/com/android/server/backup/keyvalue/KeyValueBackupReporterTest.java b/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupReporterTest.java index a1b8a9520524..a1b8a9520524 100644 --- a/services/robotests/src/com/android/server/backup/keyvalue/KeyValueBackupReporterTest.java +++ b/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupReporterTest.java diff --git a/services/robotests/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java b/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java index 099127cbeb4b..cf51e19edb00 100644 --- a/services/robotests/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java +++ b/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java @@ -169,6 +169,7 @@ public class KeyValueBackupTaskTest { private static final PackageData PACKAGE_2 = keyValuePackage(2); private static final String BACKUP_AGENT_SHARED_PREFS_SYNCHRONIZER_CLASS = "android.app.backup.BackupAgent$SharedPrefsSynchronizer"; + private static final int USER_ID = 10; @Mock private TransportManager mTransportManager; @Mock private DataChangedJournal mOldJournal; @@ -224,7 +225,7 @@ public class KeyValueBackupTaskTest { setUpBinderCallerAndApplicationAsSystem(mApplication); mBackupManagerService = spy(createUserBackupManagerServiceAndRunTasks( - mContext, mBaseStateDir, mDataDir, mTransportManager)); + USER_ID, mContext, mBaseStateDir, mDataDir, mTransportManager)); setUpBackupManagerServiceBasics( mBackupManagerService, mApplication, diff --git a/services/robotests/src/com/android/server/backup/keyvalue/TaskExceptionTest.java b/services/robotests/backup/src/com/android/server/backup/keyvalue/TaskExceptionTest.java index 3698b79872b1..3698b79872b1 100644 --- a/services/robotests/src/com/android/server/backup/keyvalue/TaskExceptionTest.java +++ b/services/robotests/backup/src/com/android/server/backup/keyvalue/TaskExceptionTest.java diff --git a/services/robotests/src/com/android/server/backup/remote/FutureBackupCallbackTest.java b/services/robotests/backup/src/com/android/server/backup/remote/FutureBackupCallbackTest.java index 5ac26f49abdc..5ac26f49abdc 100644 --- a/services/robotests/src/com/android/server/backup/remote/FutureBackupCallbackTest.java +++ b/services/robotests/backup/src/com/android/server/backup/remote/FutureBackupCallbackTest.java diff --git a/services/robotests/src/com/android/server/backup/remote/RemoteCallTest.java b/services/robotests/backup/src/com/android/server/backup/remote/RemoteCallTest.java index 7ec2a4e66903..7ec2a4e66903 100644 --- a/services/robotests/src/com/android/server/backup/remote/RemoteCallTest.java +++ b/services/robotests/backup/src/com/android/server/backup/remote/RemoteCallTest.java diff --git a/services/robotests/src/com/android/server/backup/remote/RemoteResultTest.java b/services/robotests/backup/src/com/android/server/backup/remote/RemoteResultTest.java index b9a77fbafe61..b9a77fbafe61 100644 --- a/services/robotests/src/com/android/server/backup/remote/RemoteResultTest.java +++ b/services/robotests/backup/src/com/android/server/backup/remote/RemoteResultTest.java diff --git a/services/robotests/src/com/android/server/backup/remote/ServiceBackupCallbackTest.java b/services/robotests/backup/src/com/android/server/backup/remote/ServiceBackupCallbackTest.java index 38a54dab3fb8..38a54dab3fb8 100644 --- a/services/robotests/src/com/android/server/backup/remote/ServiceBackupCallbackTest.java +++ b/services/robotests/backup/src/com/android/server/backup/remote/ServiceBackupCallbackTest.java diff --git a/services/robotests/src/com/android/server/backup/restore/ActiveRestoreSessionTest.java b/services/robotests/backup/src/com/android/server/backup/restore/ActiveRestoreSessionTest.java index 859392d80d36..859392d80d36 100644 --- a/services/robotests/src/com/android/server/backup/restore/ActiveRestoreSessionTest.java +++ b/services/robotests/backup/src/com/android/server/backup/restore/ActiveRestoreSessionTest.java diff --git a/services/robotests/src/com/android/server/backup/testing/BackupManagerServiceTestUtils.java b/services/robotests/backup/src/com/android/server/backup/testing/BackupManagerServiceTestUtils.java index 06f6d21b9ca9..b9785707fe96 100644 --- a/services/robotests/src/com/android/server/backup/testing/BackupManagerServiceTestUtils.java +++ b/services/robotests/backup/src/com/android/server/backup/testing/BackupManagerServiceTestUtils.java @@ -58,13 +58,17 @@ public class BackupManagerServiceTestUtils { * <p>If the class-under-test is going to execute methods as the system, it's a good idea to * also call {@link #setUpBinderCallerAndApplicationAsSystem(Application)} before this method. * - * @see #createUserBackupManagerServiceAndRunTasks(Context, HandlerThread, File, File, + * @see #createUserBackupManagerServiceAndRunTasks(int, Context, HandlerThread, File, File, * TransportManager) */ public static UserBackupManagerService createUserBackupManagerServiceAndRunTasks( - Context context, File baseStateDir, File dataDir, TransportManager transportManager) { + int userId, + Context context, + File baseStateDir, + File dataDir, + TransportManager transportManager) { return createUserBackupManagerServiceAndRunTasks( - context, startBackupThread(null), baseStateDir, dataDir, transportManager); + userId, context, startBackupThread(null), baseStateDir, dataDir, transportManager); } /** @@ -75,6 +79,7 @@ public class BackupManagerServiceTestUtils { * also call {@link #setUpBinderCallerAndApplicationAsSystem(Application)} before this method. */ public static UserBackupManagerService createUserBackupManagerServiceAndRunTasks( + int userId, Context context, HandlerThread backupThread, File baseStateDir, @@ -82,6 +87,7 @@ public class BackupManagerServiceTestUtils { TransportManager transportManager) { UserBackupManagerService backupManagerService = UserBackupManagerService.createAndInitializeService( + userId, context, new Trampoline(context), backupThread, diff --git a/services/robotests/src/com/android/server/backup/testing/CryptoTestUtils.java b/services/robotests/backup/src/com/android/server/backup/testing/CryptoTestUtils.java index 0428796f51fa..0428796f51fa 100644 --- a/services/robotests/src/com/android/server/backup/testing/CryptoTestUtils.java +++ b/services/robotests/backup/src/com/android/server/backup/testing/CryptoTestUtils.java diff --git a/services/robotests/src/com/android/server/backup/testing/PackageData.java b/services/robotests/backup/src/com/android/server/backup/testing/PackageData.java index f9177a80fbfe..f9177a80fbfe 100644 --- a/services/robotests/src/com/android/server/backup/testing/PackageData.java +++ b/services/robotests/backup/src/com/android/server/backup/testing/PackageData.java diff --git a/services/robotests/src/com/android/server/backup/testing/TestUtils.java b/services/robotests/backup/src/com/android/server/backup/testing/TestUtils.java index 3fe1f3f90f2f..3fe1f3f90f2f 100644 --- a/services/robotests/src/com/android/server/backup/testing/TestUtils.java +++ b/services/robotests/backup/src/com/android/server/backup/testing/TestUtils.java diff --git a/services/robotests/src/com/android/server/backup/testing/TransportData.java b/services/robotests/backup/src/com/android/server/backup/testing/TransportData.java index 77f5d9a48c18..77f5d9a48c18 100644 --- a/services/robotests/src/com/android/server/backup/testing/TransportData.java +++ b/services/robotests/backup/src/com/android/server/backup/testing/TransportData.java diff --git a/services/robotests/src/com/android/server/backup/testing/TransportTestUtils.java b/services/robotests/backup/src/com/android/server/backup/testing/TransportTestUtils.java index f6ed6307c82f..f6ed6307c82f 100644 --- a/services/robotests/src/com/android/server/backup/testing/TransportTestUtils.java +++ b/services/robotests/backup/src/com/android/server/backup/testing/TransportTestUtils.java diff --git a/services/robotests/src/com/android/server/backup/testing/Utils.java b/services/robotests/backup/src/com/android/server/backup/testing/Utils.java index b0e00a2656d0..b0e00a2656d0 100644 --- a/services/robotests/src/com/android/server/backup/testing/Utils.java +++ b/services/robotests/backup/src/com/android/server/backup/testing/Utils.java diff --git a/services/robotests/src/com/android/server/backup/transport/TransportClientManagerTest.java b/services/robotests/backup/src/com/android/server/backup/transport/TransportClientManagerTest.java index 7dd0d927edd0..7dd0d927edd0 100644 --- a/services/robotests/src/com/android/server/backup/transport/TransportClientManagerTest.java +++ b/services/robotests/backup/src/com/android/server/backup/transport/TransportClientManagerTest.java diff --git a/services/robotests/src/com/android/server/backup/transport/TransportClientTest.java b/services/robotests/backup/src/com/android/server/backup/transport/TransportClientTest.java index 7281a3c87a29..7281a3c87a29 100644 --- a/services/robotests/src/com/android/server/backup/transport/TransportClientTest.java +++ b/services/robotests/backup/src/com/android/server/backup/transport/TransportClientTest.java diff --git a/services/robotests/src/com/android/server/backup/transport/TransportStatsTest.java b/services/robotests/backup/src/com/android/server/backup/transport/TransportStatsTest.java index f01a6b067c8a..f01a6b067c8a 100644 --- a/services/robotests/src/com/android/server/backup/transport/TransportStatsTest.java +++ b/services/robotests/backup/src/com/android/server/backup/transport/TransportStatsTest.java diff --git a/services/robotests/src/com/android/server/backup/BackupManagerServiceTest.java b/services/robotests/src/com/android/server/backup/BackupManagerServiceTest.java deleted file mode 100644 index 58bce1cdfbf1..000000000000 --- a/services/robotests/src/com/android/server/backup/BackupManagerServiceTest.java +++ /dev/null @@ -1,744 +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.backup; - -import static com.android.server.backup.testing.TransportData.backupTransport; - -import static com.google.common.truth.Truth.assertThat; - -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.robolectric.Shadows.shadowOf; -import static org.testng.Assert.expectThrows; - -import android.annotation.UserIdInt; -import android.app.Application; -import android.app.backup.IBackupManagerMonitor; -import android.app.backup.IBackupObserver; -import android.app.backup.IFullBackupRestoreObserver; -import android.app.backup.ISelectBackupTransportCallback; -import android.content.Context; -import android.content.Intent; -import android.os.IBinder; -import android.os.ParcelFileDescriptor; -import android.os.UserHandle; -import android.platform.test.annotations.Presubmit; - -import com.android.server.backup.testing.BackupManagerServiceTestUtils; -import com.android.server.backup.testing.TransportData; -import com.android.server.testing.shadows.ShadowBinder; - -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 org.robolectric.RobolectricTestRunner; -import org.robolectric.RuntimeEnvironment; -import org.robolectric.annotation.Config; -import org.robolectric.shadows.ShadowContextWrapper; - -import java.io.File; -import java.io.FileDescriptor; -import java.io.PrintWriter; - -/** Tests for the user-aware backup/restore system service {@link BackupManagerService}. */ -@RunWith(RobolectricTestRunner.class) -@Config(shadows = {ShadowBinder.class}) -@Presubmit -public class BackupManagerServiceTest { - private static final String TEST_PACKAGE = "package"; - private static final String TEST_TRANSPORT = "transport"; - - private static final String[] ADB_TEST_PACKAGES = {TEST_PACKAGE}; - - private static final int NON_USER_SYSTEM = UserHandle.USER_SYSTEM + 1; - - private ShadowContextWrapper mShadowContext; - @Mock private UserBackupManagerService mUserBackupManagerService; - private BackupManagerService mBackupManagerService; - private Context mContext; - @UserIdInt private int mUserId; - - /** Initialize {@link BackupManagerService}. */ - @Before - public void setUp() throws Exception { - MockitoAnnotations.initMocks(this); - - Application application = RuntimeEnvironment.application; - mContext = application; - mShadowContext = shadowOf(application); - mUserId = NON_USER_SYSTEM; - mBackupManagerService = - new BackupManagerService( - application, - new Trampoline(application), - BackupManagerServiceTestUtils.startBackupThread(null)); - mBackupManagerService.setUserBackupManagerService(mUserBackupManagerService); - } - - /** - * Clean up and reset state that was created for testing {@link BackupManagerService} - * operations. - */ - @After - public void tearDown() throws Exception { - ShadowBinder.reset(); - } - - /** - * Test verifying that {@link BackupManagerService#MORE_DEBUG} is set to {@code false}. - * This is specifically to prevent overloading the logs in production. - */ - @Test - public void testMoreDebug_isFalse() throws Exception { - boolean moreDebug = BackupManagerService.MORE_DEBUG; - - assertThat(moreDebug).isFalse(); - } - - // TODO(b/118520567): Change the following tests to use the per-user instance of - // UserBackupManagerService once it's implemented. Currently these tests only test the straight - // forward redirection. - - // --------------------------------------------- - // Backup agent tests - // --------------------------------------------- - - /** Test that the backup service routes methods correctly to the user that requests it. */ - @Test - public void testDataChanged_callsDataChangedForUser() throws Exception { - mBackupManagerService.dataChanged(TEST_PACKAGE); - - verify(mUserBackupManagerService).dataChanged(TEST_PACKAGE); - } - - /** Test that the backup service routes methods correctly to the user that requests it. */ - @Test - public void testAgentConnected_callsAgentConnectedForUser() throws Exception { - IBinder agentBinder = mock(IBinder.class); - - mBackupManagerService.agentConnected(TEST_PACKAGE, agentBinder); - - verify(mUserBackupManagerService).agentConnected(TEST_PACKAGE, agentBinder); - } - - /** Test that the backup service routes methods correctly to the user that requests it. */ - @Test - public void testAgentDisconnected_callsAgentDisconnectedForUser() throws Exception { - mBackupManagerService.agentDisconnected(TEST_PACKAGE); - - verify(mUserBackupManagerService).agentDisconnected(TEST_PACKAGE); - } - - /** Test that the backup service routes methods correctly to the user that requests it. */ - @Test - public void testOpComplete_callsOpCompleteForUser() throws Exception { - mBackupManagerService.opComplete(/* token */ 0, /* result */ 0L); - - verify(mUserBackupManagerService).opComplete(/* token */ 0, /* result */ 0L); - } - - // --------------------------------------------- - // Transport tests - // --------------------------------------------- - - /** Test that the backup service routes methods correctly to the user that requests it. */ - @Test - public void testInitializeTransports_callsInitializeTransportsForUser() throws Exception { - String[] transports = {TEST_TRANSPORT}; - - mBackupManagerService.initializeTransports(transports, /* observer */ null); - - verify(mUserBackupManagerService).initializeTransports(transports, /* observer */ null); - } - - /** Test that the backup service routes methods correctly to the user that requests it. */ - @Test - public void testClearBackupData_callsClearBackupDataForUser() throws Exception { - mBackupManagerService.clearBackupData(TEST_TRANSPORT, TEST_PACKAGE); - - verify(mUserBackupManagerService).clearBackupData(TEST_TRANSPORT, TEST_PACKAGE); - } - - /** Test that the backup service routes methods correctly to the user that requests it. */ - @Test - public void testGetCurrentTransport_callsGetCurrentTransportForUser() throws Exception { - mBackupManagerService.getCurrentTransport(); - - verify(mUserBackupManagerService).getCurrentTransport(); - } - - /** Test that the backup service routes methods correctly to the user that requests it. */ - @Test - public void testGetCurrentTransportComponent_callsGetCurrentTransportComponentForUser() - throws Exception { - mBackupManagerService.getCurrentTransportComponent(); - - verify(mUserBackupManagerService).getCurrentTransportComponent(); - } - - /** Test that the backup service routes methods correctly to the user that requests it. */ - @Test - public void testListAllTransports_callsListAllTransportsForUser() throws Exception { - mBackupManagerService.listAllTransports(); - - verify(mUserBackupManagerService).listAllTransports(); - } - - /** Test that the backup service routes methods correctly to the user that requests it. */ - @Test - public void testListAllTransportComponents_callsListAllTransportComponentsForUser() - throws Exception { - mBackupManagerService.listAllTransportComponents(); - - verify(mUserBackupManagerService).listAllTransportComponents(); - } - - /** Test that the backup service routes methods correctly to the user that requests it. */ - @Test - public void testGetTransportWhitelist_callsGetTransportWhitelistForUser() throws Exception { - mBackupManagerService.getTransportWhitelist(); - - verify(mUserBackupManagerService).getTransportWhitelist(); - } - - /** Test that the backup service routes methods correctly to the user that requests it. */ - @Test - public void testUpdateTransportAttributes_callsUpdateTransportAttributesForUser() - throws Exception { - TransportData transport = backupTransport(); - Intent configurationIntent = new Intent(); - Intent dataManagementIntent = new Intent(); - - mBackupManagerService.updateTransportAttributes( - transport.getTransportComponent(), - transport.transportName, - configurationIntent, - "currentDestinationString", - dataManagementIntent, - "dataManagementLabel"); - - verify(mUserBackupManagerService) - .updateTransportAttributes( - transport.getTransportComponent(), - transport.transportName, - configurationIntent, - "currentDestinationString", - dataManagementIntent, - "dataManagementLabel"); - } - - /** Test that the backup service routes methods correctly to the user that requests it. */ - @Test - public void testSelectBackupTransport_callsSelectBackupTransportForUser() throws Exception { - mBackupManagerService.selectBackupTransport(TEST_TRANSPORT); - - verify(mUserBackupManagerService).selectBackupTransport(TEST_TRANSPORT); - } - - /** Test that the backup service routes methods correctly to the user that requests it. */ - @Test - public void testSelectTransportAsync_callsSelectTransportAsyncForUser() throws Exception { - TransportData transport = backupTransport(); - ISelectBackupTransportCallback callback = mock(ISelectBackupTransportCallback.class); - - mBackupManagerService.selectBackupTransportAsync( - transport.getTransportComponent(), callback); - - verify(mUserBackupManagerService) - .selectBackupTransportAsync(transport.getTransportComponent(), callback); - } - - /** Test that the backup service routes methods correctly to the user that requests it. */ - @Test - public void testGetConfigurationIntent_callsGetConfigurationIntentForUser() throws Exception { - mBackupManagerService.getConfigurationIntent(TEST_TRANSPORT); - - verify(mUserBackupManagerService).getConfigurationIntent(TEST_TRANSPORT); - } - - /** Test that the backup service routes methods correctly to the user that requests it. */ - @Test - public void testGetDestinationString_callsGetDestinationStringForUser() throws Exception { - mBackupManagerService.getDestinationString(TEST_TRANSPORT); - - verify(mUserBackupManagerService).getDestinationString(TEST_TRANSPORT); - } - - /** Test that the backup service routes methods correctly to the user that requests it. */ - @Test - public void testGetDataManagementIntent_callsGetDataManagementIntentForUser() throws Exception { - mBackupManagerService.getDataManagementIntent(TEST_TRANSPORT); - - verify(mUserBackupManagerService).getDataManagementIntent(TEST_TRANSPORT); - } - - /** Test that the backup service routes methods correctly to the user that requests it. */ - @Test - public void testGetDataManagementLabel_callsGetDataManagementLabelForUser() throws Exception { - mBackupManagerService.getDataManagementLabel(TEST_TRANSPORT); - - verify(mUserBackupManagerService).getDataManagementLabel(TEST_TRANSPORT); - } - - // --------------------------------------------- - // Settings tests - // --------------------------------------------- - /** - * Test verifying that {@link BackupManagerService#setBackupEnabled(int, boolean)} throws a - * {@link SecurityException} if the caller does not have INTERACT_ACROSS_USERS_FULL permission. - */ - @Test - public void setBackupEnabled_withoutPermission_throwsSecurityException() { - mShadowContext.denyPermissions(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL); - - expectThrows( - SecurityException.class, - () -> mBackupManagerService.setBackupEnabled(mUserId, true)); - } - - /** - * Test verifying that {@link BackupManagerService#setBackupEnabled(int, boolean)} does not - * require the caller to have INTERACT_ACROSS_USERS_FULL permission when the calling user id is - * the same as the target user id. - */ - @Test - public void setBackupEnabled_whenCallingUserIsTargetUser_doesntNeedPermission() { - ShadowBinder.setCallingUserHandle(UserHandle.of(mUserId)); - mShadowContext.denyPermissions(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL); - - mBackupManagerService.setBackupEnabled(mUserId, true); - - verify(mUserBackupManagerService).setBackupEnabled(true); - } - - - /** Test that the backup service routes methods correctly to the user that requests it. */ - @Test - public void setBackupEnabled_callsSetBackupEnabledForUser() throws Exception { - mShadowContext.grantPermissions(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL); - - mBackupManagerService.setBackupEnabled(mUserId, true); - - verify(mUserBackupManagerService).setBackupEnabled(true); - } - - /** Test that the backup service routes methods correctly to the user that requests it. */ - @Test - public void setAutoRestore_callsSetAutoRestoreForUser() throws Exception { - mBackupManagerService.setAutoRestore(true); - - verify(mUserBackupManagerService).setAutoRestore(true); - } - - /** Test that the backup service routes methods correctly to the user that requests it. */ - @Test - public void testSetBackupProvisioned_callsSetBackupProvisionedForUser() throws Exception { - mBackupManagerService.setBackupProvisioned(true); - - verify(mUserBackupManagerService).setBackupProvisioned(true); - } - - /** - * Test verifying that {@link BackupManagerService#isBackupEnabled(int)} throws a - * {@link SecurityException} if the caller does not have INTERACT_ACROSS_USERS_FULL permission. - */ - @Test - public void testIsBackupEnabled_withoutPermission_throwsSecurityException() { - mShadowContext.denyPermissions(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL); - - expectThrows( - SecurityException.class, - () -> mBackupManagerService.isBackupEnabled(mUserId)); - } - - /** Test that the backup service routes methods correctly to the user that requests it. */ - @Test - public void testIsBackupEnabled_callsIsBackupEnabledForUser() throws Exception { - mShadowContext.grantPermissions(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL); - - mBackupManagerService.isBackupEnabled(mUserId); - - verify(mUserBackupManagerService).isBackupEnabled(); - } - - // --------------------------------------------- - // Backup tests - // --------------------------------------------- - - /** Test that the backup service routes methods correctly to the user that requests it. */ - @Test - public void testIsAppEligibleForBackup_callsIsAppEligibleForBackupForUser() throws Exception { - mBackupManagerService.isAppEligibleForBackup(TEST_PACKAGE); - - verify(mUserBackupManagerService).isAppEligibleForBackup(TEST_PACKAGE); - } - - /** Test that the backup service routes methods correctly to the user that requests it. */ - @Test - public void testFilterAppsEligibleForBackup_callsFilterAppsEligibleForBackupForUser() - throws Exception { - String[] packages = {TEST_PACKAGE}; - - mBackupManagerService.filterAppsEligibleForBackup(packages); - - verify(mUserBackupManagerService).filterAppsEligibleForBackup(packages); - } - - /** - * Test verifying that {@link BackupManagerService#backupNow(int)} throws a - * {@link SecurityException} if the caller does not have INTERACT_ACROSS_USERS_FULL permission. - */ - @Test - public void testBackupNow_withoutPermission_throwsSecurityException() { - mShadowContext.denyPermissions(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL); - - expectThrows( - SecurityException.class, - () -> mBackupManagerService.backupNow(mUserId)); - } - - /** Test that the backup service routes methods correctly to the user that requests it. */ - @Test - public void testBackupNow_callsBackupNowForUser() throws Exception { - mShadowContext.grantPermissions(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL); - - mBackupManagerService.backupNow(mUserId); - - verify(mUserBackupManagerService).backupNow(); - } - - /** - * Test verifying that {@link BackupManagerService#requestBackup(int, String[], IBackupObserver, - * IBackupManagerMonitor, int)} throws a {@link SecurityException} if the caller does not have - * INTERACT_ACROSS_USERS_FULL permission. - */ - @Test - public void testRequestBackup_withoutPermission_throwsSecurityException() { - mShadowContext.denyPermissions(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL); - String[] packages = {TEST_PACKAGE}; - IBackupObserver observer = mock(IBackupObserver.class); - IBackupManagerMonitor monitor = mock(IBackupManagerMonitor.class); - - expectThrows( - SecurityException.class, - () -> mBackupManagerService.requestBackup(mUserId, packages, observer, monitor, 0)); - } - - - /** Test that the backup service routes methods correctly to the user that requests it. */ - @Test - public void testRequestBackup_callsRequestBackupForUser() throws Exception { - mShadowContext.grantPermissions(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL); - String[] packages = {TEST_PACKAGE}; - IBackupObserver observer = mock(IBackupObserver.class); - IBackupManagerMonitor monitor = mock(IBackupManagerMonitor.class); - - mBackupManagerService.requestBackup(mUserId, packages, observer, monitor, - /* flags */ 0); - - verify(mUserBackupManagerService).requestBackup(packages, observer, monitor, /* flags */ 0); - } - - /** - * Test verifying that {@link BackupManagerService#cancelBackups(int)} throws a - * {@link SecurityException} if the caller does not have INTERACT_ACROSS_USERS_FULL permission. - */ - @Test - public void testCancelBackups_withoutPermission_throwsSecurityException() { - mShadowContext.denyPermissions(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL); - - expectThrows( - SecurityException.class, - () -> mBackupManagerService.cancelBackups(mUserId)); - } - - - /** Test that the backup service routes methods correctly to the user that requests it. */ - @Test - public void testCancelBackups_callsCancelBackupsForUser() throws Exception { - mShadowContext.grantPermissions(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL); - - mBackupManagerService.cancelBackups(mUserId); - - verify(mUserBackupManagerService).cancelBackups(); - } - - /** Test that the backup service routes methods correctly to the user that requests it. */ - @Test - public void testBeginFullBackup_callsBeginFullBackupForUser() throws Exception { - FullBackupJob job = new FullBackupJob(); - - mBackupManagerService.beginFullBackup(job); - - verify(mUserBackupManagerService).beginFullBackup(job); - } - - /** Test that the backup service routes methods correctly to the user that requests it. */ - @Test - public void testEndFullBackup_callsEndFullBackupForUser() throws Exception { - mBackupManagerService.endFullBackup(); - - verify(mUserBackupManagerService).endFullBackup(); - } - - /** Test that the backup service routes methods correctly to the user that requests it. */ - @Test - public void testFullTransportBackup_callsFullTransportBackupForUser() throws Exception { - String[] packages = {TEST_PACKAGE}; - - mBackupManagerService.fullTransportBackup(packages); - - verify(mUserBackupManagerService).fullTransportBackup(packages); - } - - // --------------------------------------------- - // Restore tests - // --------------------------------------------- - - /** Test that the backup service routes methods correctly to the user that requests it. */ - @Test - public void testRestoreAtInstall_callsRestoreAtInstallForUser() throws Exception { - mBackupManagerService.restoreAtInstall(TEST_PACKAGE, /* token */ 0); - - verify(mUserBackupManagerService).restoreAtInstall(TEST_PACKAGE, /* token */ 0); - } - - /** Test that the backup service routes methods correctly to the user that requests it. */ - @Test - public void testBeginRestoreSession_callsBeginRestoreSessionForUser() throws Exception { - mBackupManagerService.beginRestoreSession(TEST_PACKAGE, TEST_TRANSPORT); - - verify(mUserBackupManagerService).beginRestoreSession(TEST_PACKAGE, TEST_TRANSPORT); - } - - /** Test that the backup service routes methods correctly to the user that requests it. */ - @Test - public void testGetAvailableRestoreToken_callsGetAvailableRestoreTokenForUser() - throws Exception { - mBackupManagerService.getAvailableRestoreToken(TEST_PACKAGE); - - verify(mUserBackupManagerService).getAvailableRestoreToken(TEST_PACKAGE); - } - - // --------------------------------------------- - // Adb backup/restore tests - // --------------------------------------------- - - /** Test that the backup service routes methods correctly to the user that requests it. */ - @Test - public void testSetBackupPassword_callsSetBackupPasswordForUser() throws Exception { - mBackupManagerService.setBackupPassword("currentPassword", "newPassword"); - - verify(mUserBackupManagerService).setBackupPassword("currentPassword", "newPassword"); - } - - /** Test that the backup service routes methods correctly to the user that requests it. */ - @Test - public void testHasBackupPassword_callsHasBackupPasswordForUser() throws Exception { - mBackupManagerService.hasBackupPassword(); - - verify(mUserBackupManagerService).hasBackupPassword(); - } - - /** - * Test verifying that {@link BackupManagerService#adbBackup(ParcelFileDescriptor, int, boolean, - * boolean, boolean, boolean, boolean, boolean, boolean, boolean, String[])} throws a - * {@link SecurityException} if the caller does not have INTERACT_ACROSS_USERS_FULL permission. - */ - @Test - public void testAdbBackup_withoutPermission_throwsSecurityException() { - mShadowContext.denyPermissions(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL); - - expectThrows(SecurityException.class, - () -> - mBackupManagerService.adbBackup( - /* userId */ mUserId, - /* parcelFileDescriptor*/ null, - /* includeApks */ true, - /* includeObbs */ true, - /* includeShared */ true, - /* doWidgets */ true, - /* doAllApps */ true, - /* includeSystem */ true, - /* doCompress */ true, - /* doKeyValue */ true, - null)); - - } - - /** - * Test verifying that {@link BackupManagerService#adbBackup(ParcelFileDescriptor, int, boolean, - * boolean, boolean, boolean, boolean, boolean, boolean, boolean, String[])} does not require - * the caller to have INTERACT_ACROSS_USERS_FULL permission when the calling user id is the - * same as the target user id. - */ - @Test - public void testAdbBackup_whenCallingUserIsTargetUser_doesntNeedPermission() throws Exception { - ShadowBinder.setCallingUserHandle(UserHandle.of(mUserId)); - mShadowContext.denyPermissions(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL); - - ParcelFileDescriptor parcelFileDescriptor = getFileDescriptorForAdbTest(); - - mBackupManagerService.adbBackup( - /* userId */ mUserId, - parcelFileDescriptor, - /* includeApks */ true, - /* includeObbs */ true, - /* includeShared */ true, - /* doWidgets */ true, - /* doAllApps */ true, - /* includeSystem */ true, - /* doCompress */ true, - /* doKeyValue */ true, - ADB_TEST_PACKAGES); - - verify(mUserBackupManagerService) - .adbBackup( - parcelFileDescriptor, - /* includeApks */ true, - /* includeObbs */ true, - /* includeShared */ true, - /* doWidgets */ true, - /* doAllApps */ true, - /* includeSystem */ true, - /* doCompress */ true, - /* doKeyValue */ true, - ADB_TEST_PACKAGES); - } - - /** Test that the backup service routes methods correctly to the user that requests it. */ - @Test - public void testAdbBackup_callsAdbBackupForUser() throws Exception { - mShadowContext.grantPermissions(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL); - - ParcelFileDescriptor parcelFileDescriptor = getFileDescriptorForAdbTest(); - - mBackupManagerService.adbBackup( - /* userId */ mUserId, - parcelFileDescriptor, - /* includeApks */ true, - /* includeObbs */ true, - /* includeShared */ true, - /* doWidgets */ true, - /* doAllApps */ true, - /* includeSystem */ true, - /* doCompress */ true, - /* doKeyValue */ true, - ADB_TEST_PACKAGES); - - verify(mUserBackupManagerService) - .adbBackup( - parcelFileDescriptor, - /* includeApks */ true, - /* includeObbs */ true, - /* includeShared */ true, - /* doWidgets */ true, - /* doAllApps */ true, - /* includeSystem */ true, - /* doCompress */ true, - /* doKeyValue */ true, - ADB_TEST_PACKAGES); - } - - /** - * Test verifying that {@link BackupManagerService#adbRestore(ParcelFileDescriptor, int)} throws - * a {@link SecurityException} if the caller does not have INTERACT_ACROSS_USERS_FULL - * permission. - */ - @Test - public void testAdbRestore_withoutPermission_throwsSecurityException() { - mShadowContext.denyPermissions(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL); - - expectThrows(SecurityException.class, - () -> mBackupManagerService.adbRestore(mUserId, null)); - - } - - /** - * Test verifying that {@link BackupManagerService#adbRestore(ParcelFileDescriptor, int)} does - * not require the caller to have INTERACT_ACROSS_USERS_FULL permission when the calling user id - * is the same as the target user id. - */ - @Test - public void testAdbRestore_whenCallingUserIsTargetUser_doesntNeedPermission() throws Exception { - ShadowBinder.setCallingUserHandle(UserHandle.of(mUserId)); - mShadowContext.denyPermissions(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL); - - ParcelFileDescriptor parcelFileDescriptor = getFileDescriptorForAdbTest(); - - mBackupManagerService.adbRestore(mUserId, parcelFileDescriptor); - - verify(mUserBackupManagerService).adbRestore(parcelFileDescriptor); - } - - /** Test that the backup service routes methods correctly to the user that requests it. */ - @Test - public void testAdbRestore_callsAdbRestoreForUser() throws Exception { - mShadowContext.grantPermissions(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL); - - ParcelFileDescriptor parcelFileDescriptor = getFileDescriptorForAdbTest(); - - mBackupManagerService.adbRestore(mUserId, parcelFileDescriptor); - - verify(mUserBackupManagerService).adbRestore(parcelFileDescriptor); - } - - /** Test that the backup service routes methods correctly to the user that requests it. */ - @Test - public void testAcknowledgeAdbBackupOrRestore_callsAcknowledgeAdbBackupOrRestoreForUser() - throws Exception { - IFullBackupRestoreObserver observer = mock(IFullBackupRestoreObserver.class); - - mBackupManagerService.acknowledgeAdbBackupOrRestore( - /* token */ 0, /* allow */ true, "currentPassword", "encryptionPassword", observer); - - verify(mUserBackupManagerService) - .acknowledgeAdbBackupOrRestore( - /* token */ 0, - /* allow */ true, - "currentPassword", - "encryptionPassword", - observer); - } - - // --------------------------------------------- - // Service tests - // --------------------------------------------- - - /** Test that the backup service routes methods correctly to the user that requests it. */ - @Test - public void testDump_callsDumpForUser() throws Exception { - File testFile = new File(mContext.getFilesDir(), "test"); - testFile.createNewFile(); - FileDescriptor fileDescriptor = new FileDescriptor(); - PrintWriter printWriter = new PrintWriter(testFile); - String[] args = {"1", "2"}; - - mBackupManagerService.dump(fileDescriptor, printWriter, args); - - verify(mUserBackupManagerService).dump(fileDescriptor, printWriter, args); - } - - private ParcelFileDescriptor getFileDescriptorForAdbTest() throws Exception { - File testFile = new File(mContext.getFilesDir(), "test"); - testFile.createNewFile(); - return ParcelFileDescriptor.open(testFile, ParcelFileDescriptor.MODE_READ_WRITE); - } -} diff --git a/services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java index 3979a8e762d3..148faada6381 100644 --- a/services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java @@ -25,7 +25,6 @@ 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.spy; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; @@ -42,7 +41,6 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; @@ -66,7 +64,6 @@ import android.provider.Settings; import android.util.Log; import android.util.SparseArray; -import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; @@ -96,6 +93,8 @@ public class AlarmManagerServiceTest { @Mock private ContentResolver mMockResolver; @Mock + private Context mMockContext; + @Mock private IActivityManager mIActivityManager; @Mock private UsageStatsManagerInternal mUsageStatsManagerInternal; @@ -221,17 +220,16 @@ public class AlarmManagerServiceTest { .thenReturn(STANDBY_BUCKET_ACTIVE); doReturn(Looper.getMainLooper()).when(Looper::myLooper); - final Context context = spy(InstrumentationRegistry.getTargetContext()); - when(context.getContentResolver()).thenReturn(mMockResolver); - doNothing().when(mMockResolver).registerContentObserver(any(), anyBoolean(), any()); + when(mMockContext.getContentResolver()).thenReturn(mMockResolver); doReturn("min_futurity=0").when(() -> Settings.Global.getString(mMockResolver, Settings.Global.ALARM_MANAGER_CONSTANTS)); - mInjector = new Injector(context); - mService = new AlarmManagerService(context, mInjector); + mInjector = new Injector(mMockContext); + mService = new AlarmManagerService(mMockContext, mInjector); spyOn(mService); doNothing().when(mService).publishBinderService(any(), any()); mService.onStart(); mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY); + spyOn(mService.mHandler); assertEquals(0, mService.mConstants.MIN_FUTURITY); assertEquals(mService.mSystemUiUid, SYSTEM_UI_UID); @@ -273,7 +271,7 @@ public class AlarmManagerServiceTest { final ArgumentCaptor<PendingIntent.OnFinished> onFinishedCaptor = ArgumentCaptor.forClass(PendingIntent.OnFinished.class); - verify(alarmPi).send(any(Context.class), eq(0), any(Intent.class), + verify(alarmPi).send(eq(mMockContext), eq(0), any(Intent.class), onFinishedCaptor.capture(), any(Handler.class), isNull(), any()); verify(mWakeLock).acquire(); onFinishedCaptor.getValue().onSendFinished(alarmPi, null, 0, null, null); @@ -423,11 +421,23 @@ public class AlarmManagerServiceTest { assertNotNull(restrictedAlarms.get(TEST_CALLING_UID)); listenerArgumentCaptor.getValue().unblockAlarmsForUid(TEST_CALLING_UID); - verify(alarmPi).send(any(Context.class), eq(0), any(Intent.class), any(), + verify(alarmPi).send(eq(mMockContext), eq(0), any(Intent.class), any(), any(Handler.class), isNull(), any()); assertNull(restrictedAlarms.get(TEST_CALLING_UID)); } + @Test + public void sendsTimeTickOnInteractive() { + final ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class); + // Stubbing so the handler doesn't actually run the runnable. + doReturn(true).when(mService.mHandler).post(runnableCaptor.capture()); + // change interactive state: false -> true + mService.interactiveStateChangedLocked(false); + mService.interactiveStateChangedLocked(true); + runnableCaptor.getValue().run(); + verify(mMockContext).sendBroadcastAsUser(mService.mTimeTickIntent, UserHandle.ALL); + } + @After public void tearDown() { if (mMockingSession != null) { diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml index cf4d3a8070f9..1b5ba263e6dd 100644 --- a/services/tests/servicestests/AndroidManifest.xml +++ b/services/tests/servicestests/AndroidManifest.xml @@ -25,7 +25,6 @@ <uses-permission android:name="android.permission.READ_PHONE_STATE" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.BROADCAST_STICKY" /> - <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" /> <uses-permission android:name="android.permission.UPDATE_DEVICE_STATS" /> <uses-permission android:name="android.permission.MANAGE_APP_TOKENS" /> <uses-permission android:name="android.permission.WAKE_LOCK" /> diff --git a/services/tests/servicestests/src/com/android/server/am/SettingsToPropertiesMapperTest.java b/services/tests/servicestests/src/com/android/server/am/SettingsToPropertiesMapperTest.java index d965f8a34fa4..0fd59216fa21 100644 --- a/services/tests/servicestests/src/com/android/server/am/SettingsToPropertiesMapperTest.java +++ b/services/tests/servicestests/src/com/android/server/am/SettingsToPropertiesMapperTest.java @@ -41,11 +41,14 @@ import java.util.Map; /** * Tests for {@link SettingsToPropertiesMapper} + * + * Build/Install/Run: + * atest FrameworksServicesTests:SettingsToPropertiesMapperTest */ @RunWith(AndroidJUnit4.class) @SmallTest public class SettingsToPropertiesMapperTest { - private static final String NAME_VALID_CHARACTERS_REGEX = "^[\\w\\.\\-@:]*$"; + private static final String NAME_VALID_CHARACTERS_REGEX = "^[\\w\\-@:]*$"; private static final String[] TEST_MAPPING = new String[] { Settings.Global.SQLITE_COMPATIBILITY_WAL_FLAGS }; @@ -77,7 +80,28 @@ public class SettingsToPropertiesMapperTest { } if (!globalSetting.matches(NAME_VALID_CHARACTERS_REGEX)) { Assert.fail(globalSetting + " contains invalid characters. " - + "Only alphanumeric characters, '.', '-', '@', ':' and '_' are valid."); + + "Only alphanumeric characters, '-', '@', ':' and '_' are valid."); + } + } + } + + @Test + public void validateRegisteredDeviceConfigScopes() { + HashSet<String> hashSet = new HashSet<>(); + for (String deviceConfigScope : SettingsToPropertiesMapper.sDeviceConfigScopes) { + if (hashSet.contains(deviceConfigScope)) { + Assert.fail("deviceConfigScope " + + deviceConfigScope + + " is registered more than once in " + + "SettingsToPropertiesMapper.sDeviceConfigScopes."); + } + hashSet.add(deviceConfigScope); + if (TextUtils.isEmpty(deviceConfigScope)) { + Assert.fail("empty deviceConfigScope registered."); + } + if (!deviceConfigScope.matches(NAME_VALID_CHARACTERS_REGEX)) { + Assert.fail(deviceConfigScope + " contains invalid characters. " + + "Only alphanumeric characters, '-', '@', ':' and '_' are valid."); } } } @@ -98,8 +122,7 @@ public class SettingsToPropertiesMapperTest { Settings.Global.SQLITE_COMPATIBILITY_WAL_FLAGS, "testValue2"); mTestMapper.updatePropertyFromSetting( Settings.Global.SQLITE_COMPATIBILITY_WAL_FLAGS, - systemPropertyName, - true); + systemPropertyName); propValue = mTestMapper.systemPropertiesGet(systemPropertyName); Assert.assertEquals("testValue2", propValue); @@ -107,8 +130,7 @@ public class SettingsToPropertiesMapperTest { Settings.Global.SQLITE_COMPATIBILITY_WAL_FLAGS, null); mTestMapper.updatePropertyFromSetting( Settings.Global.SQLITE_COMPATIBILITY_WAL_FLAGS, - systemPropertyName, - true); + systemPropertyName); propValue = mTestMapper.systemPropertiesGet(systemPropertyName); Assert.assertEquals("", propValue); } diff --git a/services/tests/servicestests/src/com/android/server/appops/AppOpsNotedWatcherTest.java b/services/tests/servicestests/src/com/android/server/appops/AppOpsNotedWatcherTest.java new file mode 100644 index 000000000000..52f434db3be3 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/appops/AppOpsNotedWatcherTest.java @@ -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. + */ + +package com.android.server.appops; + +import android.Manifest; +import android.app.AppOpsManager; +import android.app.AppOpsManager.OnOpNotedListener; +import android.content.Context; +import android.os.Process; +import androidx.test.InstrumentationRegistry; +import androidx.test.runner.AndroidJUnit4; +import androidx.test.filters.SmallTest; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InOrder; + + +import static org.junit.Assert.fail; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.timeout; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +/** + * Tests watching noted ops. + */ +@SmallTest +@RunWith(AndroidJUnit4.class) +public class AppOpsNotedWatcherTest { + + private static final long NOTIFICATION_TIMEOUT_MILLIS = 5000; + + public void testWatchNotedOpsRequiresPermission() { + // Create a mock listener + final OnOpNotedListener listener = mock(OnOpNotedListener.class); + + // Try to start watching noted ops + final AppOpsManager appOpsManager = getContext().getSystemService(AppOpsManager.class); + try { + appOpsManager.startWatchingNoted(new String[]{AppOpsManager.OPSTR_FINE_LOCATION, + AppOpsManager.OPSTR_RECORD_AUDIO}, listener); + fail("Watching noted ops shoudl require " + Manifest.permission.WATCH_APPOPS); + } catch (SecurityException expected) { + /*ignored*/ + } + } + + @Test + public void testWatchNotedOps() { + // Create a mock listener + final OnOpNotedListener listener = mock(OnOpNotedListener.class); + + // Start watching noted ops + final AppOpsManager appOpsManager = getContext().getSystemService(AppOpsManager.class); + appOpsManager.startWatchingNoted(new String[]{AppOpsManager.OPSTR_FINE_LOCATION, + AppOpsManager.OPSTR_CAMERA}, listener); + + // Note some ops + appOpsManager.noteOp(AppOpsManager.OPSTR_FINE_LOCATION, Process.myUid(), + getContext().getPackageName()); + appOpsManager.noteOp(AppOpsManager.OPSTR_CAMERA, Process.myUid(), + getContext().getPackageName()); + + // Verify that we got called for the ops being noted + final InOrder inOrder = inOrder(listener); + inOrder.verify(listener, timeout(NOTIFICATION_TIMEOUT_MILLIS) + .times(1)).onOpNoted(eq(AppOpsManager.OPSTR_FINE_LOCATION), + eq(Process.myUid()), eq(getContext().getPackageName()), + eq(AppOpsManager.MODE_ALLOWED)); + inOrder.verify(listener, timeout(NOTIFICATION_TIMEOUT_MILLIS) + .times(1)).onOpNoted(eq(AppOpsManager.OPSTR_CAMERA), + eq(Process.myUid()), eq(getContext().getPackageName()), + eq(AppOpsManager.MODE_ALLOWED)); + + // Stop watching + appOpsManager.stopWatchingNoted(listener); + + // This should be the only two callbacks we got + verifyNoMoreInteractions(listener); + } + + private static Context getContext() { + return InstrumentationRegistry.getContext(); + } +}
\ No newline at end of file diff --git a/services/tests/servicestests/src/com/android/server/backup/TrampolineTest.java b/services/tests/servicestests/src/com/android/server/backup/TrampolineTest.java index d7a398e50a66..5615dff6c607 100644 --- a/services/tests/servicestests/src/com/android/server/backup/TrampolineTest.java +++ b/services/tests/servicestests/src/com/android/server/backup/TrampolineTest.java @@ -132,19 +132,21 @@ public class TrampolineTest { } @Test - public void startServiceForUser_whenMultiUserSettingDisabled_isIgnored() { + public void unlockUser_whenMultiUserSettingDisabled_isIgnoredForNonSystemUser() { Settings.Global.putInt(mContentResolver, Settings.Global.BACKUP_MULTI_USER_ENABLED, 0); + mTrampoline.initializeService(UserHandle.USER_SYSTEM); - mTrampoline.startServiceForUser(10); + mTrampoline.unlockUser(10); verify(mBackupManagerServiceMock, never()).startServiceForUser(10); } @Test - public void startServiceForUser_whenMultiUserSettingEnabled_callsBackupManagerService() { + public void unlockUser_whenMultiUserSettingEnabled_callsBackupManagerServiceForNonSystemUser() { Settings.Global.putInt(mContentResolver, Settings.Global.BACKUP_MULTI_USER_ENABLED, 1); + mTrampoline.initializeService(UserHandle.USER_SYSTEM); - mTrampoline.startServiceForUser(10); + mTrampoline.unlockUser(10); verify(mBackupManagerServiceMock).startServiceForUser(10); } @@ -300,6 +302,15 @@ public class TrampolineTest { } @Test + public void dataChangedForUser_forwarded() throws RemoteException { + mTrampoline.initializeService(UserHandle.USER_SYSTEM); + + mTrampoline.dataChangedForUser(mUserId, PACKAGE_NAME); + + verify(mBackupManagerServiceMock).dataChanged(PACKAGE_NAME); + } + + @Test public void dataChanged_forwarded() throws RemoteException { mTrampoline.initializeService(UserHandle.USER_SYSTEM); mTrampoline.dataChanged(PACKAGE_NAME); @@ -313,6 +324,15 @@ public class TrampolineTest { } @Test + public void clearBackupDataForUser_forwarded() throws RemoteException { + mTrampoline.initializeService(UserHandle.USER_SYSTEM); + + mTrampoline.clearBackupDataForUser(mUserId, TRANSPORT_NAME, PACKAGE_NAME); + + verify(mBackupManagerServiceMock).clearBackupData(TRANSPORT_NAME, PACKAGE_NAME); + } + + @Test public void clearBackupData_forwarded() throws RemoteException { mTrampoline.initializeService(UserHandle.USER_SYSTEM); mTrampoline.clearBackupData(TRANSPORT_NAME, PACKAGE_NAME); @@ -326,6 +346,15 @@ public class TrampolineTest { } @Test + public void agentConnectedForUser_forwarded() throws RemoteException { + mTrampoline.initializeService(UserHandle.USER_SYSTEM); + + mTrampoline.agentConnectedForUser(mUserId, PACKAGE_NAME, mAgentMock); + + verify(mBackupManagerServiceMock).agentConnected(PACKAGE_NAME, mAgentMock); + } + + @Test public void agentConnected_forwarded() throws RemoteException { mTrampoline.initializeService(UserHandle.USER_SYSTEM); mTrampoline.agentConnected(PACKAGE_NAME, mAgentMock); @@ -339,6 +368,15 @@ public class TrampolineTest { } @Test + public void agentDisconnectedForUser_forwarded() throws RemoteException { + mTrampoline.initializeService(UserHandle.USER_SYSTEM); + + mTrampoline.agentDisconnectedForUser(mUserId, PACKAGE_NAME); + + verify(mBackupManagerServiceMock).agentDisconnected(PACKAGE_NAME); + } + + @Test public void agentDisconnected_forwarded() throws RemoteException { mTrampoline.initializeService(UserHandle.USER_SYSTEM); mTrampoline.agentDisconnected(PACKAGE_NAME); @@ -352,6 +390,15 @@ public class TrampolineTest { } @Test + public void restoreAtInstallForUser_forwarded() throws RemoteException { + mTrampoline.initializeService(UserHandle.USER_SYSTEM); + + mTrampoline.restoreAtInstallForUser(mUserId, PACKAGE_NAME, 123); + + verify(mBackupManagerServiceMock).restoreAtInstall(PACKAGE_NAME, 123); + } + + @Test public void restoreAtInstall_forwarded() throws RemoteException { mTrampoline.initializeService(UserHandle.USER_SYSTEM); mTrampoline.restoreAtInstall(PACKAGE_NAME, 123); @@ -390,6 +437,15 @@ public class TrampolineTest { } @Test + public void setAutoRestoreForUser_forwarded() throws RemoteException { + mTrampoline.initializeService(UserHandle.USER_SYSTEM); + + mTrampoline.setAutoRestoreForUser(mUserId, true); + + verify(mBackupManagerServiceMock).setAutoRestore(true); + } + + @Test public void setAutoRestore_forwarded() throws RemoteException { mTrampoline.initializeService(UserHandle.USER_SYSTEM); mTrampoline.setAutoRestore(true); @@ -505,14 +561,14 @@ public class TrampolineTest { @Test public void fullTransportBackup_calledBeforeInitialize_ignored() throws RemoteException { - mTrampoline.fullTransportBackup(PACKAGE_NAMES); + mTrampoline.fullTransportBackupForUser(mUserId, PACKAGE_NAMES); verifyNoMoreInteractions(mBackupManagerServiceMock); } @Test - public void fullTransportBackup_forwarded() throws RemoteException { + public void fullTransportBackupForUser_forwarded() throws RemoteException { mTrampoline.initializeService(UserHandle.USER_SYSTEM); - mTrampoline.fullTransportBackup(PACKAGE_NAMES); + mTrampoline.fullTransportBackupForUser(mUserId, PACKAGE_NAMES); verify(mBackupManagerServiceMock).fullTransportBackup(PACKAGE_NAMES); } @@ -538,6 +594,22 @@ public class TrampolineTest { } @Test + public void acknowledgeFullBackupOrRestoreForUser_forwarded() throws RemoteException { + mTrampoline.initializeService(UserHandle.USER_SYSTEM); + + mTrampoline.acknowledgeFullBackupOrRestoreForUser( + mUserId, + 123, + true, + CURRENT_PASSWORD, + ENCRYPTION_PASSWORD, + mFullBackupRestoreObserverMock); + + verify(mBackupManagerServiceMock).acknowledgeAdbBackupOrRestore(123, true, CURRENT_PASSWORD, + ENCRYPTION_PASSWORD, mFullBackupRestoreObserverMock); + } + + @Test public void acknowledgeFullBackupOrRestore_forwarded() throws RemoteException { mTrampoline.initializeService(UserHandle.USER_SYSTEM); mTrampoline.acknowledgeFullBackupOrRestore(123, true, CURRENT_PASSWORD, ENCRYPTION_PASSWORD, @@ -553,6 +625,16 @@ public class TrampolineTest { } @Test + public void getCurrentTransportForUser_forwarded() throws RemoteException { + when(mBackupManagerServiceMock.getCurrentTransport()).thenReturn(TRANSPORT_NAME); + mTrampoline.initializeService(UserHandle.USER_SYSTEM); + + assertEquals(TRANSPORT_NAME, mTrampoline.getCurrentTransportForUser(mUserId)); + + verify(mBackupManagerServiceMock).getCurrentTransport(); + } + + @Test public void getCurrentTransport_forwarded() throws RemoteException { when(mBackupManagerServiceMock.getCurrentTransport()).thenReturn(TRANSPORT_NAME); @@ -569,6 +651,16 @@ public class TrampolineTest { } @Test + public void listAllTransportsForUser_forwarded() throws RemoteException { + when(mBackupManagerServiceMock.listAllTransports()).thenReturn(TRANSPORTS); + mTrampoline.initializeService(UserHandle.USER_SYSTEM); + + assertEquals(TRANSPORTS, mTrampoline.listAllTransportsForUser(mUserId)); + verify(mBackupManagerServiceMock).listAllTransports(); + } + + + @Test public void listAllTransports_forwarded() throws RemoteException { when(mBackupManagerServiceMock.listAllTransports()).thenReturn(TRANSPORTS); @@ -578,18 +670,19 @@ public class TrampolineTest { } @Test - public void listAllTransportComponents_calledBeforeInitialize_ignored() throws RemoteException { - assertNull(mTrampoline.listAllTransportComponents()); + public void listAllTransportComponentsForUser_calledBeforeInitialize_ignored() + throws RemoteException { + assertNull(mTrampoline.listAllTransportComponentsForUser(mUserId)); verifyNoMoreInteractions(mBackupManagerServiceMock); } @Test - public void listAllTransportComponents_forwarded() throws RemoteException { + public void listAllTransportComponentsForUser_forwarded() throws RemoteException { when(mBackupManagerServiceMock.listAllTransportComponents()).thenReturn( TRANSPORT_COMPONENTS); - mTrampoline.initializeService(UserHandle.USER_SYSTEM); - assertEquals(TRANSPORT_COMPONENTS, mTrampoline.listAllTransportComponents()); + + assertEquals(TRANSPORT_COMPONENTS, mTrampoline.listAllTransportComponentsForUser(mUserId)); verify(mBackupManagerServiceMock).listAllTransportComponents(); } @@ -609,19 +702,34 @@ public class TrampolineTest { } @Test - public void describeTransport_calledBeforeInitialize_ignored() throws RemoteException { - mTrampoline.updateTransportAttributes(TRANSPORT_COMPONENT_NAME, TRANSPORT_NAME, null, - "Transport Destination", null, "Data Management"); + public void updateTransportAttributesForUser_calledBeforeInitialize_ignored() + throws RemoteException { + mTrampoline.updateTransportAttributesForUser( + mUserId, + TRANSPORT_COMPONENT_NAME, + TRANSPORT_NAME, + null, + "Transport Destination", + null, + "Data Management"); + verifyNoMoreInteractions(mBackupManagerServiceMock); } @Test - public void describeTransport_forwarded() throws RemoteException { + public void updateTransportAttributesForUser_forwarded() throws RemoteException { when(mBackupManagerServiceMock.getTransportWhitelist()).thenReturn(TRANSPORTS); - mTrampoline.initializeService(UserHandle.USER_SYSTEM); - mTrampoline.updateTransportAttributes(TRANSPORT_COMPONENT_NAME, TRANSPORT_NAME, null, - "Transport Destination", null, "Data Management"); + + mTrampoline.updateTransportAttributesForUser( + mUserId, + TRANSPORT_COMPONENT_NAME, + TRANSPORT_NAME, + null, + "Transport Destination", + null, + "Data Management"); + verify(mBackupManagerServiceMock).updateTransportAttributes(TRANSPORT_COMPONENT_NAME, TRANSPORT_NAME, null, "Transport Destination", null, "Data Management"); } @@ -633,6 +741,15 @@ public class TrampolineTest { } @Test + public void selectBackupTransportForUser_forwarded() throws RemoteException { + mTrampoline.initializeService(UserHandle.USER_SYSTEM); + + mTrampoline.selectBackupTransportForUser(mUserId, TRANSPORT_NAME); + + verify(mBackupManagerServiceMock).selectBackupTransport(TRANSPORT_NAME); + } + + @Test public void selectBackupTransport_forwarded() throws RemoteException { mTrampoline.initializeService(UserHandle.USER_SYSTEM); mTrampoline.selectBackupTransport(TRANSPORT_NAME); @@ -640,9 +757,12 @@ public class TrampolineTest { } @Test - public void selectBackupTransportAsync_calledBeforeInitialize_ignored() throws Exception { + public void selectBackupTransportAsyncForUser_calledBeforeInitialize_ignored() + throws Exception { LinkedBlockingQueue<Integer> q = new LinkedBlockingQueue(); - mTrampoline.selectBackupTransportAsync( + + mTrampoline.selectBackupTransportAsyncForUser( + mUserId, TRANSPORT_COMPONENT_NAME, new ISelectBackupTransportCallback() { @Override @@ -660,6 +780,7 @@ public class TrampolineTest { return null; } }); + verifyNoMoreInteractions(mBackupManagerServiceMock); Integer errorCode = q.poll(5, TimeUnit.SECONDS); assertNotNull(errorCode); @@ -667,17 +788,19 @@ public class TrampolineTest { } @Test - public void selectBackupTransportAsync_calledBeforeInitialize_ignored_nullListener() + public void selectBackupTransportAsyncForUser_calledBeforeInitialize_ignored_nullListener() throws Exception { - mTrampoline.selectBackupTransportAsync(TRANSPORT_COMPONENT_NAME, null); + mTrampoline.selectBackupTransportAsyncForUser(mUserId, TRANSPORT_COMPONENT_NAME, null); + verifyNoMoreInteractions(mBackupManagerServiceMock); // No crash. } @Test - public void selectBackupTransportAsync_calledBeforeInitialize_ignored_listenerThrowException() + public void selectBackupTransportAsyncForUser_calledBeforeInitialize_ignored_listenerThrows() throws Exception { - mTrampoline.selectBackupTransportAsync( + mTrampoline.selectBackupTransportAsyncForUser( + mUserId, TRANSPORT_COMPONENT_NAME, new ISelectBackupTransportCallback() { @Override @@ -695,14 +818,17 @@ public class TrampolineTest { return null; } }); + verifyNoMoreInteractions(mBackupManagerServiceMock); // No crash. } @Test - public void selectBackupTransportAsync_forwarded() throws RemoteException { + public void selectBackupTransportAsyncForUser_forwarded() throws RemoteException { mTrampoline.initializeService(UserHandle.USER_SYSTEM); - mTrampoline.selectBackupTransportAsync(TRANSPORT_COMPONENT_NAME, null); + + mTrampoline.selectBackupTransportAsyncForUser(mUserId, TRANSPORT_COMPONENT_NAME, null); + verify(mBackupManagerServiceMock).selectBackupTransportAsync(TRANSPORT_COMPONENT_NAME, null); } @@ -714,6 +840,19 @@ public class TrampolineTest { } @Test + public void getConfigurationIntentForUser_forwarded() throws RemoteException { + Intent configurationIntentStub = new Intent(); + when(mBackupManagerServiceMock.getConfigurationIntent(TRANSPORT_NAME)).thenReturn( + configurationIntentStub); + mTrampoline.initializeService(UserHandle.USER_SYSTEM); + + assertEquals( + configurationIntentStub, + mTrampoline.getConfigurationIntentForUser(mUserId, TRANSPORT_NAME)); + verify(mBackupManagerServiceMock).getConfigurationIntent(TRANSPORT_NAME); + } + + @Test public void getConfigurationIntent_forwarded() throws RemoteException { Intent configurationIntentStub = new Intent(); when(mBackupManagerServiceMock.getConfigurationIntent(TRANSPORT_NAME)).thenReturn( @@ -731,6 +870,18 @@ public class TrampolineTest { } @Test + public void getDestinationStringForUser_forwarded() throws RemoteException { + when(mBackupManagerServiceMock.getDestinationString(TRANSPORT_NAME)).thenReturn( + DESTINATION_STRING); + mTrampoline.initializeService(UserHandle.USER_SYSTEM); + + assertEquals( + DESTINATION_STRING, + mTrampoline.getDestinationStringForUser(mUserId, TRANSPORT_NAME)); + verify(mBackupManagerServiceMock).getDestinationString(TRANSPORT_NAME); + } + + @Test public void getDestinationString_forwarded() throws RemoteException { when(mBackupManagerServiceMock.getDestinationString(TRANSPORT_NAME)).thenReturn( DESTINATION_STRING); @@ -747,6 +898,19 @@ public class TrampolineTest { } @Test + public void getDataManagementIntentForUser_forwarded() throws RemoteException { + Intent dataManagementIntent = new Intent(); + when(mBackupManagerServiceMock.getDataManagementIntent(TRANSPORT_NAME)).thenReturn( + dataManagementIntent); + mTrampoline.initializeService(UserHandle.USER_SYSTEM); + + assertEquals( + dataManagementIntent, + mTrampoline.getDataManagementIntentForUser(mUserId, TRANSPORT_NAME)); + verify(mBackupManagerServiceMock).getDataManagementIntent(TRANSPORT_NAME); + } + + @Test public void getDataManagementIntent_forwarded() throws RemoteException { Intent dataManagementIntent = new Intent(); when(mBackupManagerServiceMock.getDataManagementIntent(TRANSPORT_NAME)).thenReturn( @@ -764,6 +928,18 @@ public class TrampolineTest { } @Test + public void getDataManagementLabelForUser_forwarded() throws RemoteException { + when(mBackupManagerServiceMock.getDataManagementLabel(TRANSPORT_NAME)).thenReturn( + DATA_MANAGEMENT_LABEL); + mTrampoline.initializeService(UserHandle.USER_SYSTEM); + + assertEquals( + DATA_MANAGEMENT_LABEL, + mTrampoline.getDataManagementLabelForUser(mUserId, TRANSPORT_NAME)); + verify(mBackupManagerServiceMock).getDataManagementLabel(TRANSPORT_NAME); + } + + @Test public void getDataManagementLabel_forwarded() throws RemoteException { when(mBackupManagerServiceMock.getDataManagementLabel(TRANSPORT_NAME)).thenReturn( DATA_MANAGEMENT_LABEL); @@ -775,14 +951,16 @@ public class TrampolineTest { @Test public void beginRestoreSession_calledBeforeInitialize_ignored() throws RemoteException { - mTrampoline.beginRestoreSession(PACKAGE_NAME, TRANSPORT_NAME); + mTrampoline.beginRestoreSessionForUser(mUserId, PACKAGE_NAME, TRANSPORT_NAME); verifyNoMoreInteractions(mBackupManagerServiceMock); } @Test - public void beginRestoreSession_forwarded() throws RemoteException { + public void beginRestoreSessionForUser_forwarded() throws RemoteException { mTrampoline.initializeService(UserHandle.USER_SYSTEM); - mTrampoline.beginRestoreSession(PACKAGE_NAME, TRANSPORT_NAME); + + mTrampoline.beginRestoreSessionForUser(mUserId, PACKAGE_NAME, TRANSPORT_NAME); + verify(mBackupManagerServiceMock).beginRestoreSession(PACKAGE_NAME, TRANSPORT_NAME); } @@ -800,32 +978,34 @@ public class TrampolineTest { } @Test - public void getAvailableRestoreToken_calledBeforeInitialize_ignored() throws RemoteException { - assertEquals(0, mTrampoline.getAvailableRestoreToken(PACKAGE_NAME)); + public void getAvailableRestoreTokenForUser_calledBeforeInitialize_ignored() + throws RemoteException { + assertEquals(0, mTrampoline.getAvailableRestoreTokenForUser(mUserId, PACKAGE_NAME)); verifyNoMoreInteractions(mBackupManagerServiceMock); } @Test - public void getAvailableRestoreToken_forwarded() throws RemoteException { + public void getAvailableRestoreTokenForUser_forwarded() throws RemoteException { when(mBackupManagerServiceMock.getAvailableRestoreToken(PACKAGE_NAME)).thenReturn(123L); - mTrampoline.initializeService(UserHandle.USER_SYSTEM); - assertEquals(123, mTrampoline.getAvailableRestoreToken(PACKAGE_NAME)); + + assertEquals(123, mTrampoline.getAvailableRestoreTokenForUser(mUserId, PACKAGE_NAME)); verify(mBackupManagerServiceMock).getAvailableRestoreToken(PACKAGE_NAME); } @Test - public void isAppEligibleForBackup_calledBeforeInitialize_ignored() throws RemoteException { - assertFalse(mTrampoline.isAppEligibleForBackup(PACKAGE_NAME)); + public void isAppEligibleForBackupForUser_calledBeforeInitialize_ignored() + throws RemoteException { + assertFalse(mTrampoline.isAppEligibleForBackupForUser(mUserId, PACKAGE_NAME)); verifyNoMoreInteractions(mBackupManagerServiceMock); } @Test - public void isAppEligibleForBackup_forwarded() throws RemoteException { + public void isAppEligibleForBackupForUser_forwarded() throws RemoteException { when(mBackupManagerServiceMock.isAppEligibleForBackup(PACKAGE_NAME)).thenReturn(true); - mTrampoline.initializeService(UserHandle.USER_SYSTEM); - assertTrue(mTrampoline.isAppEligibleForBackup(PACKAGE_NAME)); + + assertTrue(mTrampoline.isAppEligibleForBackupForUser(mUserId, PACKAGE_NAME)); verify(mBackupManagerServiceMock).isAppEligibleForBackup(PACKAGE_NAME); } @@ -989,6 +1169,11 @@ public class TrampolineTest { return sBackupManagerServiceMock; } + @Override + protected void postToHandler(Runnable runnable) { + runnable.run(); + } + int getCreateServiceCallsCount() { return mCreateServiceCallsCount; } 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 e9bfa8f4e0c8..abf90402250c 100644 --- a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java @@ -149,12 +149,11 @@ public class DisplayManagerServiceTest { verify(mMockInputManagerInternal).setDisplayViewports(viewportCaptor.capture()); List<DisplayViewport> viewports = viewportCaptor.getValue(); - // Expect to receive 3 viewports: internal, external, and virtual - assertEquals(3, viewports.size()); + // Expect to receive 2 viewports: internal, and virtual + assertEquals(2, 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) { @@ -163,7 +162,7 @@ public class DisplayManagerServiceTest { break; } case DisplayViewport.VIEWPORT_EXTERNAL: { - externalViewport = v; + fail("EXTERNAL viewport should not exist."); break; } case DisplayViewport.VIEWPORT_VIRTUAL: { @@ -172,14 +171,12 @@ public class DisplayManagerServiceTest { } } } - // INTERNAL and EXTERNAL viewports get created upon access + // INTERNAL viewport gets created upon access. assertNotNull(internalViewport); - assertNotNull(externalViewport); assertNotNull(virtualViewport); - // INTERNAL and EXTERNAL + // INTERNAL assertTrue(internalViewport.valid); - assertTrue(externalViewport.valid); // VIRTUAL assertEquals(height, virtualViewport.deviceHeight); @@ -216,39 +213,16 @@ public class DisplayManagerServiceTest { verify(mMockInputManagerInternal).setDisplayViewports(viewportCaptor.capture()); List<DisplayViewport> viewports = viewportCaptor.getValue(); - // Expect to receive 2 viewports: 1 internal, 1 external - assertEquals(2, viewports.size()); + // Expect to receive actual viewports: 1 internal + assertEquals(1, 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 + DisplayViewport internalViewport = viewports.get(0); + + // INTERNAL is the only one actual display. assertNotNull(internalViewport); - assertNotNull(externalViewport); + assertEquals(DisplayViewport.VIEWPORT_INTERNAL, internalViewport.type); 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 diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/PackageDexUsageTests.java b/services/tests/servicestests/src/com/android/server/pm/dex/PackageDexUsageTests.java index 7755e94369af..5df4509af885 100644 --- a/services/tests/servicestests/src/com/android/server/pm/dex/PackageDexUsageTests.java +++ b/services/tests/servicestests/src/com/android/server/pm/dex/PackageDexUsageTests.java @@ -17,6 +17,7 @@ package com.android.server.pm.dex; import static com.android.server.pm.dex.PackageDexUsage.DexUseInfo; +import static com.android.server.pm.dex.PackageDexUsage.MAX_SECONDARY_FILES_PER_OWNER; import static com.android.server.pm.dex.PackageDexUsage.PackageUseInfo; import static org.junit.Assert.assertEquals; @@ -31,24 +32,28 @@ import android.os.Build; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import dalvik.system.VMRuntime; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import dalvik.system.VMRuntime; - import java.io.IOException; import java.io.StringReader; import java.io.StringWriter; +import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Set; @RunWith(AndroidJUnit4.class) @SmallTest public class PackageDexUsageTests { + private static final String ISA = VMRuntime.getInstructionSet(Build.SUPPORTED_ABIS[0]); + private PackageDexUsage mPackageDexUsage; private TestData mFooBaseUser0; @@ -71,25 +76,23 @@ public class PackageDexUsageTests { String fooCodeDir = "/data/app/com.google.foo/"; String fooDataDir = "/data/user/0/com.google.foo/"; - String isa = VMRuntime.getInstructionSet(Build.SUPPORTED_ABIS[0]); - mFooBaseUser0 = new TestData(fooPackageName, - fooCodeDir + "base.apk", 0, isa, false, true, fooPackageName); + fooCodeDir + "base.apk", 0, ISA, false, true, fooPackageName); mFooSplit1User0 = new TestData(fooPackageName, - fooCodeDir + "split-1.apk", 0, isa, false, true, fooPackageName); + fooCodeDir + "split-1.apk", 0, ISA, false, true, fooPackageName); mFooSplit2UsedByOtherApps0 = new TestData(fooPackageName, - fooCodeDir + "split-2.apk", 0, isa, true, true, "used.by.other.com"); + fooCodeDir + "split-2.apk", 0, ISA, true, true, "used.by.other.com"); mFooSecondary1User0 = new TestData(fooPackageName, - fooDataDir + "sec-1.dex", 0, isa, false, false, fooPackageName); + fooDataDir + "sec-1.dex", 0, ISA, false, false, fooPackageName); mFooSecondary1User1 = new TestData(fooPackageName, - fooDataDir + "sec-1.dex", 1, isa, false, false, fooPackageName); + fooDataDir + "sec-1.dex", 1, ISA, false, false, fooPackageName); mFooSecondary2UsedByOtherApps0 = new TestData(fooPackageName, - fooDataDir + "sec-2.dex", 0, isa, true, false, "used.by.other.com"); + fooDataDir + "sec-2.dex", 0, ISA, true, false, "used.by.other.com"); mInvalidIsa = new TestData(fooPackageName, fooCodeDir + "base.apk", 0, "INVALID_ISA", false, true, "INALID_USER"); @@ -100,11 +103,11 @@ public class PackageDexUsageTests { String barDataDir1 = "/data/user/1/com.google.bar/"; mBarBaseUser0 = new TestData(barPackageName, - barCodeDir + "base.apk", 0, isa, false, true, barPackageName); + barCodeDir + "base.apk", 0, ISA, false, true, barPackageName); mBarSecondary1User0 = new TestData(barPackageName, - barDataDir + "sec-1.dex", 0, isa, false, false, barPackageName); + barDataDir + "sec-1.dex", 0, ISA, false, false, barPackageName); mBarSecondary2User1 = new TestData(barPackageName, - barDataDir1 + "sec-2.dex", 1, isa, false, false, barPackageName); + barDataDir1 + "sec-2.dex", 1, ISA, false, false, barPackageName); } @Test @@ -183,6 +186,25 @@ public class PackageDexUsageTests { } @Test + public void testRecordTooManySecondaries() { + int tooManyFiles = MAX_SECONDARY_FILES_PER_OWNER + 1; + List<TestData> expectedSecondaries = new ArrayList<>(); + for (int i = 1; i <= tooManyFiles; i++) { + String fooPackageName = "com.google.foo"; + TestData testData = new TestData(fooPackageName, + "/data/user/0/" + fooPackageName + "/sec-" + i + "1.dex", 0, ISA, false, false, + fooPackageName); + if (i < tooManyFiles) { + assertTrue("Adding " + testData.mDexFile, record(testData)); + expectedSecondaries.add(testData); + } else { + assertFalse("Adding " + testData.mDexFile, record(testData)); + } + assertPackageDexUsage(mPackageDexUsage, null, null, expectedSecondaries); + } + } + + @Test public void testMultiplePackages() { assertTrue(record(mFooBaseUser0)); assertTrue(record(mFooSecondary1User0)); @@ -540,7 +562,14 @@ public class PackageDexUsageTests { private void assertPackageDexUsage(PackageDexUsage packageDexUsage, Set<String> users, TestData primary, TestData... secondaries) { - String packageName = primary == null ? secondaries[0].mPackageName : primary.mPackageName; + assertPackageDexUsage(packageDexUsage, users, primary, Arrays.asList(secondaries)); + } + + private void assertPackageDexUsage(PackageDexUsage packageDexUsage, Set<String> users, + TestData primary, List<TestData> secondaries) { + String packageName = primary == null + ? secondaries.get(0).mPackageName + : primary.mPackageName; boolean primaryUsedByOtherApps = primary != null && primary.mUsedByOtherApps; PackageUseInfo pInfo = packageDexUsage.getPackageUseInfo(packageName); @@ -554,7 +583,7 @@ public class PackageDexUsageTests { } Map<String, DexUseInfo> dexUseInfoMap = pInfo.getDexUseInfoMap(); - assertEquals(secondaries.length, dexUseInfoMap.size()); + assertEquals(secondaries.size(), dexUseInfoMap.size()); // Check dex use info for (TestData testData : secondaries) { diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java index e9889948c341..bf4b52eb72aa 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java @@ -52,7 +52,6 @@ import android.platform.test.annotations.Presubmit; import android.provider.Settings; import android.view.Surface; -import androidx.test.filters.FlakyTest; import androidx.test.filters.SmallTest; import com.android.internal.util.test.FakeSettingsProvider; @@ -81,7 +80,6 @@ import java.util.concurrent.TimeUnit; */ @SmallTest @Presubmit -@FlakyTest(detail = "Confirm stable in post-submit before removing") public class DisplayRotationTests { private static final long UI_HANDLER_WAIT_TIMEOUT_MS = 50; diff --git a/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java b/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java index f3a125bf79e4..86353643c128 100644 --- a/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java @@ -40,7 +40,6 @@ import android.platform.test.annotations.Presubmit; import android.view.DisplayInfo; import androidx.test.InstrumentationRegistry; -import androidx.test.filters.FlakyTest; import androidx.test.filters.MediumTest; import com.android.server.LocalServices; @@ -65,7 +64,6 @@ import java.util.function.Predicate; */ @MediumTest @Presubmit -@FlakyTest(detail = "Confirm stable in post-submit before removing") public class LaunchParamsPersisterTests extends ActivityTestsBase { private static final int TEST_USER_ID = 3; private static final int ALTERNATIVE_USER_ID = 0; diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java index 0bd681bd69a2..7186e22cefef 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java @@ -47,7 +47,6 @@ import android.platform.test.annotations.Presubmit; import android.view.Display; import android.view.Gravity; -import androidx.test.filters.FlakyTest; import androidx.test.filters.SmallTest; import com.android.server.wm.LaunchParamsController.LaunchParams; @@ -64,7 +63,6 @@ import java.util.Locale; * atest WmTests:TaskLaunchParamsModifierTests */ @SmallTest -@FlakyTest(detail = "Confirm stable in post-submit before removing") @Presubmit public class TaskLaunchParamsModifierTests extends ActivityTestsBase { diff --git a/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java b/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java index ec475bf26bb3..eddf8f9b7254 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java +++ b/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java @@ -98,14 +98,14 @@ final class UsageStatsXmlV1 { stats.mLastTimeVisible = statsOut.beginTime + XmlUtils.readLongAttribute( parser, LAST_TIME_VISIBLE_ATTR); } catch (IOException e) { - Log.e(TAG, "Failed to parse mLastTimeVisible", e); + Log.i(TAG, "Failed to parse mLastTimeVisible"); } try { stats.mLastTimeForegroundServiceUsed = statsOut.beginTime + XmlUtils.readLongAttribute( parser, LAST_TIME_SERVICE_USED_ATTR); } catch (IOException e) { - Log.e(TAG, "Failed to parse mLastTimeForegroundServiceUsed", e); + Log.i(TAG, "Failed to parse mLastTimeForegroundServiceUsed"); } stats.mTotalTimeInForeground = XmlUtils.readLongAttribute(parser, TOTAL_TIME_ACTIVE_ATTR); @@ -113,14 +113,14 @@ final class UsageStatsXmlV1 { try { stats.mTotalTimeVisible = XmlUtils.readLongAttribute(parser, TOTAL_TIME_VISIBLE_ATTR); } catch (IOException e) { - Log.e(TAG, "Failed to parse mTotalTimeVisible", e); + Log.i(TAG, "Failed to parse mTotalTimeVisible"); } try { stats.mTotalTimeForegroundServiceUsed = XmlUtils.readLongAttribute(parser, TOTAL_TIME_SERVICE_USED_ATTR); } catch (IOException e) { - Log.e(TAG, "Failed to parse mTotalTimeForegroundServiceUsed", e); + Log.i(TAG, "Failed to parse mTotalTimeForegroundServiceUsed"); } stats.mLastEvent = XmlUtils.readIntAttribute(parser, LAST_EVENT_ATTR); diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java index 60cb08f00f89..294b7509698b 100644 --- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java +++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java @@ -16,6 +16,12 @@ package com.android.server.usb; +import static android.hardware.usb.UsbPortStatus.DATA_ROLE_DEVICE; +import static android.hardware.usb.UsbPortStatus.DATA_ROLE_HOST; +import static android.hardware.usb.UsbPortStatus.MODE_AUDIO_ACCESSORY; +import static android.hardware.usb.UsbPortStatus.POWER_ROLE_SINK; +import static android.hardware.usb.UsbPortStatus.POWER_ROLE_SOURCE; + import static com.android.internal.usb.DumpUtils.writeAccessory; import static com.android.internal.util.dump.DumpUtils.writeStringIfNotNull; @@ -36,6 +42,7 @@ import android.content.pm.PackageManager; import android.content.res.Resources; import android.debug.AdbManagerInternal; import android.debug.IAdbTransport; +import android.hardware.usb.ParcelableUsbPort; import android.hardware.usb.UsbAccessory; import android.hardware.usb.UsbConfiguration; import android.hardware.usb.UsbConstants; @@ -294,9 +301,10 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser BroadcastReceiver portReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { - UsbPort port = intent.getParcelableExtra(UsbManager.EXTRA_PORT); + ParcelableUsbPort port = intent.getParcelableExtra(UsbManager.EXTRA_PORT); UsbPortStatus status = intent.getParcelableExtra(UsbManager.EXTRA_PORT_STATUS); - mHandler.updateHostState(port, status); + mHandler.updateHostState( + port.getUsbPort(context.getSystemService(UsbManager.class)), status); } }; @@ -821,23 +829,20 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser boolean prevHostConnected = mHostConnected; UsbPort port = (UsbPort) args.arg1; UsbPortStatus status = (UsbPortStatus) args.arg2; - mHostConnected = status.getCurrentDataRole() == UsbPort.DATA_ROLE_HOST; - mSourcePower = status.getCurrentPowerRole() == UsbPort.POWER_ROLE_SOURCE; - mSinkPower = status.getCurrentPowerRole() == UsbPort.POWER_ROLE_SINK; - mAudioAccessoryConnected = - (status.getCurrentMode() == UsbPort.MODE_AUDIO_ACCESSORY); - mAudioAccessorySupported = port.isModeSupported(UsbPort.MODE_AUDIO_ACCESSORY); + mHostConnected = status.getCurrentDataRole() == DATA_ROLE_HOST; + mSourcePower = status.getCurrentPowerRole() == POWER_ROLE_SOURCE; + mSinkPower = status.getCurrentPowerRole() == POWER_ROLE_SINK; + mAudioAccessoryConnected = (status.getCurrentMode() == MODE_AUDIO_ACCESSORY); + mAudioAccessorySupported = port.isModeSupported(MODE_AUDIO_ACCESSORY); // Ideally we want to see if PR_SWAP and DR_SWAP is supported. // But, this should be suffice, since, all four combinations are only supported // when PR_SWAP and DR_SWAP are supported. mSupportsAllCombinations = status.isRoleCombinationSupported( - UsbPort.POWER_ROLE_SOURCE, UsbPort.DATA_ROLE_HOST) - && status.isRoleCombinationSupported(UsbPort.POWER_ROLE_SINK, - UsbPort.DATA_ROLE_HOST) - && status.isRoleCombinationSupported(UsbPort.POWER_ROLE_SOURCE, - UsbPort.DATA_ROLE_DEVICE) - && status.isRoleCombinationSupported(UsbPort.POWER_ROLE_SINK, - UsbPort.DATA_ROLE_HOST); + POWER_ROLE_SOURCE, DATA_ROLE_HOST) + && status.isRoleCombinationSupported(POWER_ROLE_SINK, DATA_ROLE_HOST) + && status.isRoleCombinationSupported(POWER_ROLE_SOURCE, + DATA_ROLE_DEVICE) + && status.isRoleCombinationSupported(POWER_ROLE_SINK, DATA_ROLE_HOST); args.recycle(); updateUsbNotification(false); diff --git a/services/usb/java/com/android/server/usb/UsbPortManager.java b/services/usb/java/com/android/server/usb/UsbPortManager.java index 96618f569928..6f210e37d6d1 100644 --- a/services/usb/java/com/android/server/usb/UsbPortManager.java +++ b/services/usb/java/com/android/server/usb/UsbPortManager.java @@ -16,12 +16,22 @@ package com.android.server.usb; +import static android.hardware.usb.UsbPortStatus.DATA_ROLE_DEVICE; +import static android.hardware.usb.UsbPortStatus.DATA_ROLE_HOST; +import static android.hardware.usb.UsbPortStatus.MODE_DFP; +import static android.hardware.usb.UsbPortStatus.MODE_DUAL; +import static android.hardware.usb.UsbPortStatus.MODE_UFP; +import static android.hardware.usb.UsbPortStatus.POWER_ROLE_SINK; +import static android.hardware.usb.UsbPortStatus.POWER_ROLE_SOURCE; + import static com.android.internal.usb.DumpUtils.writePort; import static com.android.internal.usb.DumpUtils.writePortStatus; +import android.Manifest; import android.annotation.NonNull; import android.content.Context; import android.content.Intent; +import android.hardware.usb.ParcelableUsbPort; import android.hardware.usb.UsbManager; import android.hardware.usb.UsbPort; import android.hardware.usb.UsbPortStatus; @@ -78,13 +88,13 @@ public class UsbPortManager { // All non-trivial role combinations. private static final int COMBO_SOURCE_HOST = - UsbPort.combineRolesAsBit(UsbPort.POWER_ROLE_SOURCE, UsbPort.DATA_ROLE_HOST); - private static final int COMBO_SOURCE_DEVICE = - UsbPort.combineRolesAsBit(UsbPort.POWER_ROLE_SOURCE, UsbPort.DATA_ROLE_DEVICE); + UsbPort.combineRolesAsBit(POWER_ROLE_SOURCE, DATA_ROLE_HOST); + private static final int COMBO_SOURCE_DEVICE = UsbPort.combineRolesAsBit( + POWER_ROLE_SOURCE, DATA_ROLE_DEVICE); private static final int COMBO_SINK_HOST = - UsbPort.combineRolesAsBit(UsbPort.POWER_ROLE_SINK, UsbPort.DATA_ROLE_HOST); - private static final int COMBO_SINK_DEVICE = - UsbPort.combineRolesAsBit(UsbPort.POWER_ROLE_SINK, UsbPort.DATA_ROLE_DEVICE); + UsbPort.combineRolesAsBit(POWER_ROLE_SINK, DATA_ROLE_HOST); + private static final int COMBO_SINK_DEVICE = UsbPort.combineRolesAsBit( + POWER_ROLE_SINK, DATA_ROLE_DEVICE); // The system context. private final Context mContext; @@ -217,12 +227,12 @@ public class UsbPortManager { final int newMode; if ((!canChangePowerRole && currentPowerRole != newPowerRole) || (!canChangeDataRole && currentDataRole != newDataRole)) { - if (canChangeMode && newPowerRole == UsbPort.POWER_ROLE_SOURCE - && newDataRole == UsbPort.DATA_ROLE_HOST) { - newMode = UsbPort.MODE_DFP; - } else if (canChangeMode && newPowerRole == UsbPort.POWER_ROLE_SINK - && newDataRole == UsbPort.DATA_ROLE_DEVICE) { - newMode = UsbPort.MODE_UFP; + if (canChangeMode && newPowerRole == POWER_ROLE_SOURCE + && newDataRole == DATA_ROLE_HOST) { + newMode = MODE_DFP; + } else if (canChangeMode && newPowerRole == POWER_ROLE_SINK + && newDataRole == DATA_ROLE_DEVICE) { + newMode = MODE_UFP; } else { logAndPrint(Log.ERROR, pw, "Found mismatch in supported USB role combinations " + "while attempting to change role: " + portInfo @@ -607,7 +617,7 @@ public class UsbPortManager { IndentingPrintWriter pw) { // Only allow mode switch capability for dual role ports. // Validate that the current mode matches the supported modes we expect. - if ((supportedModes & UsbPort.MODE_DUAL) != UsbPort.MODE_DUAL) { + if ((supportedModes & MODE_DUAL) != MODE_DUAL) { canChangeMode = false; if (currentMode != 0 && currentMode != supportedModes) { logAndPrint(Log.WARN, pw, "Ignoring inconsistent current mode from USB " @@ -633,16 +643,16 @@ public class UsbPortManager { // Can only change power role. // Assume data role must remain at its current value. supportedRoleCombinations |= UsbPort.combineRolesAsBit( - UsbPort.POWER_ROLE_SOURCE, currentDataRole); + POWER_ROLE_SOURCE, currentDataRole); supportedRoleCombinations |= UsbPort.combineRolesAsBit( - UsbPort.POWER_ROLE_SINK, currentDataRole); + POWER_ROLE_SINK, currentDataRole); } else if (canChangeDataRole) { // Can only change data role. // Assume power role must remain at its current value. supportedRoleCombinations |= UsbPort.combineRolesAsBit( - currentPowerRole, UsbPort.DATA_ROLE_HOST); + currentPowerRole, DATA_ROLE_HOST); supportedRoleCombinations |= UsbPort.combineRolesAsBit( - currentPowerRole, UsbPort.DATA_ROLE_DEVICE); + currentPowerRole, DATA_ROLE_DEVICE); } else if (canChangeMode) { // Can only change the mode. // Assume both standard UFP and DFP configurations will become available @@ -654,7 +664,8 @@ public class UsbPortManager { // Update the port data structures. PortInfo portInfo = mPorts.get(portId); if (portInfo == null) { - portInfo = new PortInfo(portId, supportedModes); + portInfo = new PortInfo(mContext.getSystemService(UsbManager.class), portId, + supportedModes); portInfo.setStatus(currentMode, canChangeMode, currentPowerRole, canChangePowerRole, currentDataRole, canChangeDataRole, @@ -701,12 +712,13 @@ public class UsbPortManager { intent.addFlags( Intent.FLAG_RECEIVER_FOREGROUND | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); - intent.putExtra(UsbManager.EXTRA_PORT, portInfo.mUsbPort); + intent.putExtra(UsbManager.EXTRA_PORT, ParcelableUsbPort.of(portInfo.mUsbPort)); intent.putExtra(UsbManager.EXTRA_PORT_STATUS, portInfo.mUsbPortStatus); // Guard against possible reentrance by posting the broadcast from the handler // instead of from within the critical section. - mHandler.post(() -> mContext.sendBroadcastAsUser(intent, UserHandle.ALL)); + mHandler.post(() -> mContext.sendBroadcastAsUser(intent, UserHandle.ALL, + Manifest.permission.MANAGE_USB)); // Log to statsd if (!mConnected.containsKey(portInfo.mUsbPort.getId()) @@ -772,8 +784,8 @@ public class UsbPortManager { // 0 when port is connected. Else reports the last connected duration public long mLastConnectDurationMillis; - public PortInfo(String portId, int supportedModes) { - mUsbPort = new UsbPort(portId, supportedModes); + PortInfo(@NonNull UsbManager usbManager, @NonNull String portId, int supportedModes) { + mUsbPort = new UsbPort(usbManager, portId, supportedModes); } public boolean setStatus(int currentMode, boolean canChangeMode, diff --git a/services/usb/java/com/android/server/usb/UsbService.java b/services/usb/java/com/android/server/usb/UsbService.java index f9abedfbf586..911547720a8f 100644 --- a/services/usb/java/com/android/server/usb/UsbService.java +++ b/services/usb/java/com/android/server/usb/UsbService.java @@ -16,6 +16,14 @@ package com.android.server.usb; +import static android.hardware.usb.UsbPortStatus.DATA_ROLE_DEVICE; +import static android.hardware.usb.UsbPortStatus.DATA_ROLE_HOST; +import static android.hardware.usb.UsbPortStatus.MODE_DFP; +import static android.hardware.usb.UsbPortStatus.MODE_DUAL; +import static android.hardware.usb.UsbPortStatus.MODE_UFP; +import static android.hardware.usb.UsbPortStatus.POWER_ROLE_SINK; +import static android.hardware.usb.UsbPortStatus.POWER_ROLE_SOURCE; + import android.annotation.NonNull; import android.annotation.UserIdInt; import android.app.PendingIntent; @@ -27,6 +35,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; import android.hardware.usb.IUsbManager; +import android.hardware.usb.ParcelableUsbPort; import android.hardware.usb.UsbAccessory; import android.hardware.usb.UsbDevice; import android.hardware.usb.UsbManager; @@ -52,7 +61,9 @@ import com.android.server.SystemService; import java.io.File; import java.io.FileDescriptor; import java.io.PrintWriter; +import java.util.ArrayList; import java.util.Collections; +import java.util.List; /** * UsbService manages all USB related state, including both host and device support. @@ -489,12 +500,25 @@ public class UsbService extends IUsbManager.Stub { } @Override - public UsbPort[] getPorts() { + public List<ParcelableUsbPort> getPorts() { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null); final long ident = Binder.clearCallingIdentity(); try { - return mPortManager != null ? mPortManager.getPorts() : null; + if (mPortManager == null) { + return null; + } else { + final UsbPort[] ports = mPortManager.getPorts(); + + final int numPorts = ports.length; + ArrayList<ParcelableUsbPort> parcelablePorts = new ArrayList<>(); + for (int i = 0; i < numPorts; i++) { + parcelablePorts.add(ParcelableUsbPort.of(ports[i])); + } + + return parcelablePorts; + } + } finally { Binder.restoreCallingIdentity(ident); } @@ -588,10 +612,10 @@ public class UsbService extends IUsbManager.Stub { final int powerRole; switch (args[2]) { case "source": - powerRole = UsbPort.POWER_ROLE_SOURCE; + powerRole = POWER_ROLE_SOURCE; break; case "sink": - powerRole = UsbPort.POWER_ROLE_SINK; + powerRole = POWER_ROLE_SINK; break; case "no-power": powerRole = 0; @@ -603,10 +627,10 @@ public class UsbService extends IUsbManager.Stub { final int dataRole; switch (args[3]) { case "host": - dataRole = UsbPort.DATA_ROLE_HOST; + dataRole = DATA_ROLE_HOST; break; case "device": - dataRole = UsbPort.DATA_ROLE_DEVICE; + dataRole = DATA_ROLE_DEVICE; break; case "no-data": dataRole = 0; @@ -631,13 +655,13 @@ public class UsbService extends IUsbManager.Stub { final int supportedModes; switch (args[2]) { case "ufp": - supportedModes = UsbPort.MODE_UFP; + supportedModes = MODE_UFP; break; case "dfp": - supportedModes = UsbPort.MODE_DFP; + supportedModes = MODE_DFP; break; case "dual": - supportedModes = UsbPort.MODE_DUAL; + supportedModes = MODE_DUAL; break; case "none": supportedModes = 0; @@ -658,10 +682,10 @@ public class UsbService extends IUsbManager.Stub { final boolean canChangeMode = args[2].endsWith("?"); switch (canChangeMode ? removeLastChar(args[2]) : args[2]) { case "ufp": - mode = UsbPort.MODE_UFP; + mode = MODE_UFP; break; case "dfp": - mode = UsbPort.MODE_DFP; + mode = MODE_DFP; break; default: pw.println("Invalid mode: " + args[2]); @@ -671,10 +695,10 @@ public class UsbService extends IUsbManager.Stub { final boolean canChangePowerRole = args[3].endsWith("?"); switch (canChangePowerRole ? removeLastChar(args[3]) : args[3]) { case "source": - powerRole = UsbPort.POWER_ROLE_SOURCE; + powerRole = POWER_ROLE_SOURCE; break; case "sink": - powerRole = UsbPort.POWER_ROLE_SINK; + powerRole = POWER_ROLE_SINK; break; default: pw.println("Invalid power role: " + args[3]); @@ -684,10 +708,10 @@ public class UsbService extends IUsbManager.Stub { final boolean canChangeDataRole = args[4].endsWith("?"); switch (canChangeDataRole ? removeLastChar(args[4]) : args[4]) { case "host": - dataRole = UsbPort.DATA_ROLE_HOST; + dataRole = DATA_ROLE_HOST; break; case "device": - dataRole = UsbPort.DATA_ROLE_DEVICE; + dataRole = DATA_ROLE_DEVICE; break; default: pw.println("Invalid data role: " + args[4]); diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java index 99ad1f4d6b50..bbf3d45d7c99 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java @@ -356,7 +356,12 @@ public class VoiceInteractionManagerService extends SystemService { } // No voice interactor, we'll just set up a simple recognizer. - curRecognizer = findAvailRecognizer(null, userHandle); + initSimpleRecognizer(curInteractorInfo, userHandle); + } + + public void initSimpleRecognizer(VoiceInteractionServiceInfo curInteractorInfo, + int userHandle) { + ComponentName curRecognizer = findAvailRecognizer(null, userHandle); if (curRecognizer != null) { if (curInteractorInfo == null) { setCurInteractor(null, userHandle); @@ -1236,34 +1241,46 @@ public class VoiceInteractionManagerService extends SystemService { int userHandle = UserHandle.getUserId(uid); ComponentName curInteractor = getCurInteractor(userHandle); ComponentName curRecognizer = getCurRecognizer(userHandle); - boolean hit = false; + boolean hitInt = false; + boolean hitRec = false; for (String pkg : packages) { if (curInteractor != null && pkg.equals(curInteractor.getPackageName())) { - hit = true; + hitInt = true; break; } else if (curRecognizer != null && pkg.equals(curRecognizer.getPackageName())) { - hit = true; + hitRec = true; break; } } - if (hit && doit) { - // The user is force stopping our current interactor/recognizer. + if (hitInt && doit) { + // The user is force stopping our current interactor. // Clear the current settings and restore default state. synchronized (VoiceInteractionManagerServiceStub.this) { + Slog.i(TAG, "Force stopping current voice interactor: " + + getCurInteractor(userHandle)); unloadAllKeyphraseModels(); if (mImpl != null) { mImpl.shutdownLocked(); setImplLocked(null); } + setCurInteractor(null, userHandle); setCurRecognizer(null, userHandle); resetCurAssistant(userHandle); initForUser(userHandle); switchImplementationIfNeededLocked(true); } + } else if (hitRec && doit) { + // We are just force-stopping the current recognizer, which is not + // also the current interactor. + synchronized (VoiceInteractionManagerServiceStub.this) { + Slog.i(TAG, "Force stopping current voice recognizer: " + + getCurRecognizer(userHandle)); + initSimpleRecognizer(null, userHandle); + } } - return hit; + return hitInt || hitRec; } @Override diff --git a/startop/view_compiler/dex_builder.cc b/startop/view_compiler/dex_builder.cc index 94879d0bbcc4..a78f7d53d135 100644 --- a/startop/view_compiler/dex_builder.cc +++ b/startop/view_compiler/dex_builder.cc @@ -79,6 +79,9 @@ std::ostream& operator<<(std::ostream& out, const Instruction::Op& opcode) { case Instruction::Op::kNew: out << "kNew"; return out; + case Instruction::Op::kCheckCast: + out << "kCheckCast"; + return out; } } @@ -329,6 +332,8 @@ void MethodBuilder::EncodeInstruction(const Instruction& instruction) { return EncodeBranch(art::Instruction::IF_NEZ, instruction); case Instruction::Op::kNew: return EncodeNew(instruction); + case Instruction::Op::kCheckCast: + return EncodeCast(instruction); } } @@ -422,6 +427,18 @@ void MethodBuilder::EncodeNew(const Instruction& instruction) { Encode21c(::art::Instruction::NEW_INSTANCE, RegisterValue(*instruction.dest()), type.value()); } +void MethodBuilder::EncodeCast(const Instruction& instruction) { + DCHECK_EQ(Instruction::Op::kCheckCast, instruction.opcode()); + DCHECK(instruction.dest().has_value()); + DCHECK(instruction.dest()->is_variable()); + DCHECK_EQ(1, instruction.args().size()); + + const Value& type = instruction.args()[0]; + DCHECK_LT(RegisterValue(*instruction.dest()), 256); + DCHECK(type.is_type()); + Encode21c(::art::Instruction::CHECK_CAST, RegisterValue(*instruction.dest()), type.value()); +} + size_t MethodBuilder::RegisterValue(const Value& value) const { if (value.is_register()) { return value.value(); diff --git a/startop/view_compiler/dex_builder.h b/startop/view_compiler/dex_builder.h index 45596acfdc44..06059c8c6e56 100644 --- a/startop/view_compiler/dex_builder.h +++ b/startop/view_compiler/dex_builder.h @@ -142,17 +142,18 @@ class Instruction { // The operation performed by this instruction. These are virtual instructions that do not // correspond exactly to DEX instructions. enum class Op { - kReturn, - kReturnObject, - kMove, - kInvokeVirtual, - kInvokeDirect, - kInvokeStatic, - kInvokeInterface, kBindLabel, kBranchEqz, kBranchNEqz, - kNew + kCheckCast, + kInvokeDirect, + kInvokeInterface, + kInvokeStatic, + kInvokeVirtual, + kMove, + kNew, + kReturn, + kReturnObject, }; //////////////////////// @@ -168,6 +169,13 @@ class Instruction { static inline Instruction OpWithArgs(Op opcode, std::optional<const Value> dest, T... args) { return Instruction{opcode, /*method_id=*/0, /*result_is_object=*/false, dest, args...}; } + + // A cast instruction. Basically, `(type)val` + static inline Instruction Cast(Value val, Value type) { + DCHECK(type.is_type()); + return OpWithArgs(Op::kCheckCast, val, type); + } + // For method calls. template <typename... T> static inline Instruction InvokeVirtual(size_t method_id, std::optional<const Value> dest, @@ -302,6 +310,7 @@ class MethodBuilder { void EncodeInvoke(const Instruction& instruction, ::art::Instruction::Code opcode); void EncodeBranch(art::Instruction::Code op, const Instruction& instruction); void EncodeNew(const Instruction& instruction); + void EncodeCast(const Instruction& instruction); // Low-level instruction format encoding. See // https://source.android.com/devices/tech/dalvik/instruction-formats for documentation of diff --git a/startop/view_compiler/dex_builder_test/src/android/startop/test/DexBuilderTest.java b/startop/view_compiler/dex_builder_test/src/android/startop/test/DexBuilderTest.java index 1508ad9ee56b..42d4161ee81e 100644 --- a/startop/view_compiler/dex_builder_test/src/android/startop/test/DexBuilderTest.java +++ b/startop/view_compiler/dex_builder_test/src/android/startop/test/DexBuilderTest.java @@ -20,6 +20,7 @@ import com.google.common.io.ByteStreams; import dalvik.system.InMemoryDexClassLoader; import dalvik.system.PathClassLoader; import java.io.InputStream; +import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.nio.ByteBuffer; import org.junit.Assert; @@ -151,4 +152,23 @@ public class DexBuilderTest { Method method = clazz.getMethod("invokeVirtualReturnObject", String.class, int.class); Assert.assertEquals("bc", method.invoke(null, "abc", 1)); } + + @Test + public void castObjectToString() throws Exception { + ClassLoader loader = loadDexFile("simple.dex"); + Class clazz = loader.loadClass("android.startop.test.testcases.SimpleTests"); + Method method = clazz.getMethod("castObjectToString", Object.class); + Assert.assertEquals("abc", method.invoke(null, "abc")); + boolean castFailed = false; + try { + method.invoke(null, 5); + } catch (InvocationTargetException e) { + if (e.getCause() instanceof ClassCastException) { + castFailed = true; + } else { + throw e; + } + } + Assert.assertTrue(castFailed); + } } diff --git a/startop/view_compiler/dex_testcase_generator.cc b/startop/view_compiler/dex_testcase_generator.cc index 2781aa55d1df..f62ec5dde85e 100644 --- a/startop/view_compiler/dex_testcase_generator.cc +++ b/startop/view_compiler/dex_testcase_generator.cc @@ -269,6 +269,19 @@ void GenerateSimpleTestCases(const string& outdir) { method.Encode(); }(invokeVirtualReturnObject); + // Make sure we can cast objects + // String castObjectToString(Object o) { return (String)o; } + MethodBuilder castObjectToString{cbuilder.CreateMethod( + "castObjectToString", + Prototype{string_type, TypeDescriptor::FromClassname("java.lang.Object")})}; + [&](MethodBuilder& method) { + const ir::Type* type_def = dex_file.GetOrAddType(string_type.descriptor()); + method.AddInstruction( + Instruction::Cast(Value::Parameter(0), Value::Type(type_def->orig_index))); + method.BuildReturn(Value::Parameter(0), /*is_object=*/true); + method.Encode(); + }(castObjectToString); + slicer::MemView image{dex_file.CreateImage()}; std::ofstream out_file(outdir + "/simple.dex"); out_file.write(image.ptr<const char>(), image.size()); diff --git a/telephony/java/android/telephony/DisconnectCause.java b/telephony/java/android/telephony/DisconnectCause.java index c6887ab93109..f53cb8224706 100644 --- a/telephony/java/android/telephony/DisconnectCause.java +++ b/telephony/java/android/telephony/DisconnectCause.java @@ -16,12 +16,16 @@ package android.telephony; +import android.annotation.SystemApi; import android.annotation.UnsupportedAppUsage; /** - * Contains disconnect call causes generated by the framework and the RIL. + * Describes the cause of a disconnected call. Those disconnect causes can be converted into a more + * generic {@link android.telecom.DisconnectCause} object. + * * @hide */ +@SystemApi public class DisconnectCause { /** The disconnect cause is not valid (Not received a disconnect cause) */ @@ -101,8 +105,8 @@ public class DisconnectCause { /** Unknown error or not specified */ public static final int ERROR_UNSPECIFIED = 36; /** - * Only emergency numbers are allowed, but we tried to dial - * a non-emergency number. + * Only emergency numbers are allowed, but we tried to dial a non-emergency number. + * @hide */ // TODO: This should be the same as NOT_EMERGENCY public static final int EMERGENCY_ONLY = 37; @@ -115,8 +119,7 @@ public class DisconnectCause { */ public static final int DIALED_MMI = 39; /** - * We tried to call a voicemail: URI but the device has no - * voicemail number configured. + * We tried to call a voicemail: URI but the device has no voicemail number configured. */ public static final int VOICEMAIL_NUMBER_MISSING = 40; /** @@ -129,6 +132,8 @@ public class DisconnectCause { * needs to be triggered by a *disconnect* event, rather than when * the InCallScreen first comes to the foreground. For now we use * the needToShowCallLostDialog field for this (see below.) + * + * @hide */ public static final int CDMA_CALL_LOST = 41; /** @@ -169,62 +174,52 @@ public class DisconnectCause { /** * Stk Call Control modified DIAL request to USSD request. - * {@hide} */ public static final int DIAL_MODIFIED_TO_USSD = 46; /** * Stk Call Control modified DIAL request to SS request. - * {@hide} */ public static final int DIAL_MODIFIED_TO_SS = 47; /** * Stk Call Control modified DIAL request to DIAL with modified data. - * {@hide} */ public static final int DIAL_MODIFIED_TO_DIAL = 48; /** * The call was terminated because CDMA phone service and roaming have already been activated. - * {@hide} */ public static final int CDMA_ALREADY_ACTIVATED = 49; /** * The call was terminated because it is not possible to place a video call while TTY is * enabled. - * {@hide} */ public static final int VIDEO_CALL_NOT_ALLOWED_WHILE_TTY_ENABLED = 50; /** * The call was terminated because it was pulled to another device. - * {@hide} */ public static final int CALL_PULLED = 51; /** * The call was terminated because it was answered on another device. - * {@hide} */ public static final int ANSWERED_ELSEWHERE = 52; /** * The call was terminated because the maximum allowable number of calls has been reached. - * {@hide} */ public static final int MAXIMUM_NUMBER_OF_CALLS_REACHED = 53; /** * The call was terminated because cellular data has been disabled. * Used when in a video call and the user disables cellular data via the settings. - * {@hide} */ public static final int DATA_DISABLED = 54; /** * The call was terminated because the data policy has disabled cellular data. * Used when in a video call and the user has exceeded the device data limit. - * {@hide} */ public static final int DATA_LIMIT_REACHED = 55; @@ -237,7 +232,6 @@ public class DisconnectCause { /** * The network does not accept the emergency call request because IMEI was used as * identification and this cability is not supported by the network. - * {@hide} */ public static final int IMEI_NOT_ACCEPTED = 58; @@ -249,7 +243,6 @@ public class DisconnectCause { /** * The call has failed because of access class barring. - * {@hide} */ public static final int IMS_ACCESS_BLOCKED = 60; @@ -265,51 +258,43 @@ public class DisconnectCause { /** * Emergency call failed with a temporary fail cause and can be redialed on this slot. - * {@hide} */ public static final int EMERGENCY_TEMP_FAILURE = 63; /** * Emergency call failed with a permanent fail cause and should not be redialed on this - * slot. - * {@hide} + * slot. */ public static final int EMERGENCY_PERM_FAILURE = 64; /** * This cause is used to report a normal event only when no other cause in the normal class * applies. - * {@hide} */ public static final int NORMAL_UNSPECIFIED = 65; /** * Stk Call Control modified DIAL request to video DIAL request. - * {@hide} */ public static final int DIAL_MODIFIED_TO_DIAL_VIDEO = 66; /** * Stk Call Control modified Video DIAL request to SS request. - * {@hide} */ public static final int DIAL_VIDEO_MODIFIED_TO_SS = 67; /** * Stk Call Control modified Video DIAL request to USSD request. - * {@hide} */ public static final int DIAL_VIDEO_MODIFIED_TO_USSD = 68; /** * Stk Call Control modified Video DIAL request to DIAL request. - * {@hide} */ public static final int DIAL_VIDEO_MODIFIED_TO_DIAL = 69; /** * Stk Call Control modified Video DIAL request to Video DIAL request. - * {@hide} */ public static final int DIAL_VIDEO_MODIFIED_TO_DIAL_VIDEO = 70; @@ -359,7 +344,10 @@ public class DisconnectCause { // Do nothing. } - /** Returns descriptive string for the specified disconnect cause. */ + /** + * Returns descriptive string for the specified disconnect cause. + * @hide + */ @UnsupportedAppUsage public static String toString(int cause) { switch (cause) { diff --git a/telephony/java/android/telephony/PhoneStateListener.java b/telephony/java/android/telephony/PhoneStateListener.java index 0df0dafbe1dd..9317aa73ffc2 100644 --- a/telephony/java/android/telephony/PhoneStateListener.java +++ b/telephony/java/android/telephony/PhoneStateListener.java @@ -173,14 +173,14 @@ public class PhoneStateListener { public static final int LISTEN_CELL_INFO = 0x00000400; /** - * Listen for precise changes and fails to the device calls (cellular). + * Listen for {@link PreciseCallState.State} of ringing, background and foreground calls. * {@more} * Requires Permission: {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE * READ_PRECISE_PHONE_STATE} * * @hide */ - @UnsupportedAppUsage + @SystemApi public static final int LISTEN_PRECISE_CALL_STATE = 0x00000800; /** @@ -320,6 +320,18 @@ public class PhoneStateListener { */ public static final int LISTEN_EMERGENCY_NUMBER_LIST = 0x01000000; + /** + * Listen for call disconnect causes which contains {@link DisconnectCause} and + * {@link PreciseDisconnectCause}. + * {@more} + * Requires Permission: {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE + * READ_PRECISE_PHONE_STATE} + * + * @hide + */ + @SystemApi + public static final int LISTEN_CALL_DISCONNECT_CAUSES = 0x02000000; + /* * Subscription used to listen to the phone state changes * @hide @@ -530,15 +542,27 @@ public class PhoneStateListener { /** * Callback invoked when precise device call state changes. - * + * @param callState {@link PreciseCallState} * @hide */ - @UnsupportedAppUsage + @SystemApi public void onPreciseCallStateChanged(PreciseCallState callState) { // default implementation empty } /** + * Callback invoked when call disconnect cause changes. + * @param disconnectCause {@link DisconnectCause}. + * @param preciseDisconnectCause {@link PreciseDisconnectCause}. + * + * @hide + */ + @SystemApi + public void onCallDisconnectCauseChanged(int disconnectCause, int preciseDisconnectCause) { + // default implementation empty + } + + /** * Callback invoked when data connection state changes with precise information. * * @hide @@ -799,6 +823,15 @@ public class PhoneStateListener { () -> mExecutor.execute(() -> psl.onPreciseCallStateChanged(callState))); } + public void onCallDisconnectCauseChanged(int disconnectCause, int preciseDisconnectCause) { + PhoneStateListener psl = mPhoneStateListenerWeakRef.get(); + if (psl == null) return; + + Binder.withCleanCallingIdentity( + () -> mExecutor.execute(() -> psl.onCallDisconnectCauseChanged( + disconnectCause, preciseDisconnectCause))); + } + public void onPreciseDataConnectionStateChanged( PreciseDataConnectionState dataConnectionState) { PhoneStateListener psl = mPhoneStateListenerWeakRef.get(); diff --git a/telephony/java/android/telephony/PreciseCallState.java b/telephony/java/android/telephony/PreciseCallState.java index ed5c26ac5cf2..59f3e1f0e7f7 100644 --- a/telephony/java/android/telephony/PreciseCallState.java +++ b/telephony/java/android/telephony/PreciseCallState.java @@ -16,29 +16,51 @@ package android.telephony; +import android.annotation.IntDef; +import android.annotation.SystemApi; import android.annotation.UnsupportedAppUsage; import android.os.Parcel; import android.os.Parcelable; import android.telephony.DisconnectCause; import android.telephony.PreciseDisconnectCause; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Objects; + /** - * Contains precise call state and call fail causes generated by the - * framework and the RIL. + * Contains precise call states. * * The following call information is included in returned PreciseCallState: * * <ul> - * <li>Ringing call state. - * <li>Foreground call state. - * <li>Background call state. - * <li>Disconnect cause; generated by the framework. - * <li>Precise disconnect cause; generated by the RIL. + * <li>Precise ringing call state. + * <li>Precise foreground call state. + * <li>Precise background call state. * </ul> * + * @see android.telephony.TelephonyManager.CallState which contains generic call states. + * * @hide */ -public class PreciseCallState implements Parcelable { +@SystemApi +public final class PreciseCallState implements Parcelable { + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = {"PRECISE_CALL_STATE_"}, + value = { + PRECISE_CALL_STATE_NOT_VALID, + PRECISE_CALL_STATE_IDLE, + PRECISE_CALL_STATE_ACTIVE, + PRECISE_CALL_STATE_HOLDING, + PRECISE_CALL_STATE_DIALING, + PRECISE_CALL_STATE_ALERTING, + PRECISE_CALL_STATE_INCOMING, + PRECISE_CALL_STATE_WAITING, + PRECISE_CALL_STATE_DISCONNECTED, + PRECISE_CALL_STATE_DISCONNECTING}) + public @interface State {} /** Call state is not valid (Not received a call state). */ public static final int PRECISE_CALL_STATE_NOT_VALID = -1; @@ -61,9 +83,9 @@ public class PreciseCallState implements Parcelable { /** Call state: Disconnecting. */ public static final int PRECISE_CALL_STATE_DISCONNECTING = 8; - private int mRingingCallState = PRECISE_CALL_STATE_NOT_VALID; - private int mForegroundCallState = PRECISE_CALL_STATE_NOT_VALID; - private int mBackgroundCallState = PRECISE_CALL_STATE_NOT_VALID; + private @State int mRingingCallState = PRECISE_CALL_STATE_NOT_VALID; + private @State int mForegroundCallState = PRECISE_CALL_STATE_NOT_VALID; + private @State int mBackgroundCallState = PRECISE_CALL_STATE_NOT_VALID; private int mDisconnectCause = DisconnectCause.NOT_VALID; private int mPreciseDisconnectCause = PreciseDisconnectCause.NOT_VALID; @@ -73,8 +95,9 @@ public class PreciseCallState implements Parcelable { * @hide */ @UnsupportedAppUsage - public PreciseCallState(int ringingCall, int foregroundCall, int backgroundCall, - int disconnectCause, int preciseDisconnectCause) { + public PreciseCallState(@State int ringingCall, @State int foregroundCall, + @State int backgroundCall, int disconnectCause, + int preciseDisconnectCause) { mRingingCallState = ringingCall; mForegroundCallState = foregroundCall; mBackgroundCallState = backgroundCall; @@ -92,6 +115,8 @@ public class PreciseCallState implements Parcelable { /** * Construct a PreciseCallState object from the given parcel. + * + * @hide */ private PreciseCallState(Parcel in) { mRingingCallState = in.readInt(); @@ -102,59 +127,23 @@ public class PreciseCallState implements Parcelable { } /** - * Get precise ringing call state - * - * @see PreciseCallState#PRECISE_CALL_STATE_NOT_VALID - * @see PreciseCallState#PRECISE_CALL_STATE_IDLE - * @see PreciseCallState#PRECISE_CALL_STATE_ACTIVE - * @see PreciseCallState#PRECISE_CALL_STATE_HOLDING - * @see PreciseCallState#PRECISE_CALL_STATE_DIALING - * @see PreciseCallState#PRECISE_CALL_STATE_ALERTING - * @see PreciseCallState#PRECISE_CALL_STATE_INCOMING - * @see PreciseCallState#PRECISE_CALL_STATE_WAITING - * @see PreciseCallState#PRECISE_CALL_STATE_DISCONNECTED - * @see PreciseCallState#PRECISE_CALL_STATE_DISCONNECTING + * Returns the precise ringing call state. */ - @UnsupportedAppUsage - public int getRingingCallState() { + public @State int getRingingCallState() { return mRingingCallState; } /** - * Get precise foreground call state - * - * @see PreciseCallState#PRECISE_CALL_STATE_NOT_VALID - * @see PreciseCallState#PRECISE_CALL_STATE_IDLE - * @see PreciseCallState#PRECISE_CALL_STATE_ACTIVE - * @see PreciseCallState#PRECISE_CALL_STATE_HOLDING - * @see PreciseCallState#PRECISE_CALL_STATE_DIALING - * @see PreciseCallState#PRECISE_CALL_STATE_ALERTING - * @see PreciseCallState#PRECISE_CALL_STATE_INCOMING - * @see PreciseCallState#PRECISE_CALL_STATE_WAITING - * @see PreciseCallState#PRECISE_CALL_STATE_DISCONNECTED - * @see PreciseCallState#PRECISE_CALL_STATE_DISCONNECTING + * Returns the precise foreground call state. */ - @UnsupportedAppUsage - public int getForegroundCallState() { + public @State int getForegroundCallState() { return mForegroundCallState; } /** - * Get precise background call state - * - * @see PreciseCallState#PRECISE_CALL_STATE_NOT_VALID - * @see PreciseCallState#PRECISE_CALL_STATE_IDLE - * @see PreciseCallState#PRECISE_CALL_STATE_ACTIVE - * @see PreciseCallState#PRECISE_CALL_STATE_HOLDING - * @see PreciseCallState#PRECISE_CALL_STATE_DIALING - * @see PreciseCallState#PRECISE_CALL_STATE_ALERTING - * @see PreciseCallState#PRECISE_CALL_STATE_INCOMING - * @see PreciseCallState#PRECISE_CALL_STATE_WAITING - * @see PreciseCallState#PRECISE_CALL_STATE_DISCONNECTED - * @see PreciseCallState#PRECISE_CALL_STATE_DISCONNECTING + * Returns the precise background call state. */ - @UnsupportedAppUsage - public int getBackgroundCallState() { + public @State int getBackgroundCallState() { return mBackgroundCallState; } @@ -199,6 +188,11 @@ public class PreciseCallState implements Parcelable { * @see DisconnectCause#CDMA_NOT_EMERGENCY * @see DisconnectCause#CDMA_ACCESS_BLOCKED * @see DisconnectCause#ERROR_UNSPECIFIED + * + * TODO: remove disconnect cause from preciseCallState as there is no link between random + * connection disconnect cause with foreground, background or ringing call. + * + * @hide */ @UnsupportedAppUsage public int getDisconnectCause() { @@ -238,6 +232,11 @@ public class PreciseCallState implements Parcelable { * @see PreciseDisconnectCause#CDMA_NOT_EMERGENCY * @see PreciseDisconnectCause#CDMA_ACCESS_BLOCKED * @see PreciseDisconnectCause#ERROR_UNSPECIFIED + * + * TODO: remove precise disconnect cause from preciseCallState as there is no link between + * random connection disconnect cause with foreground, background or ringing call. + * + * @hide */ @UnsupportedAppUsage public int getPreciseDisconnectCause() { @@ -272,14 +271,8 @@ public class PreciseCallState implements Parcelable { @Override public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + mRingingCallState; - result = prime * result + mForegroundCallState; - result = prime * result + mBackgroundCallState; - result = prime * result + mDisconnectCause; - result = prime * result + mPreciseDisconnectCause; - return result; + return Objects.hash(mRingingCallState, mForegroundCallState, mForegroundCallState, + mDisconnectCause, mPreciseDisconnectCause); } @Override diff --git a/telephony/java/android/telephony/PreciseDisconnectCause.java b/telephony/java/android/telephony/PreciseDisconnectCause.java index 2acaf34dbb30..af88748af9e6 100644 --- a/telephony/java/android/telephony/PreciseDisconnectCause.java +++ b/telephony/java/android/telephony/PreciseDisconnectCause.java @@ -16,279 +16,329 @@ package android.telephony; +import android.annotation.SystemApi; + /** - * Contains precise disconnect call causes generated by the - * framework and the RIL. - * + * Contains precise disconnect call causes generated by the framework and the RIL. * @hide */ +@SystemApi public class PreciseDisconnectCause { - /** The disconnect cause is not valid (Not received a disconnect cause)*/ + /** The disconnect cause is not valid (Not received a disconnect cause).*/ public static final int NOT_VALID = -1; - /** No disconnect cause provided. Generally a local disconnect or an incoming missed call */ + /** No disconnect cause provided. Generally a local disconnect or an incoming missed call. */ public static final int NO_DISCONNECT_CAUSE_AVAILABLE = 0; /** * The destination cannot be reached because the number, although valid, - * is not currently assigned + * is not currently assigned. */ public static final int UNOBTAINABLE_NUMBER = 1; - /** The user cannot be reached because the network through which the call has been - * routed does not serve the destination desired + /** + * The user cannot be reached because the network through which the call has been routed does + * not serve the destination desired. */ public static final int NO_ROUTE_TO_DESTINATION = 3; - /** The channel most recently identified is not acceptable to the sending entity for - * use in this call + /** + * The channel most recently identified is not acceptable to the sending entity for use in this + * call. */ public static final int CHANNEL_UNACCEPTABLE = 6; - /** The MS has tried to access a service that the MS's network operator or service - * provider is not prepared to allow + /** + * The mobile station (MS) has tried to access a service that the MS's network operator or + * service provider is not prepared to allow. */ public static final int OPERATOR_DETERMINED_BARRING = 8; - /** One of the users involved in the call has requested that the call is cleared */ + /** One of the users involved in the call has requested that the call is cleared. */ public static final int NORMAL = 16; - /** The called user is unable to accept another call */ + /** The called user is unable to accept another call. */ public static final int BUSY = 17; - /** The user does not respond to a call establishment message with either an alerting - * or connect indication within the prescribed period of time allocated + /** + * The user does not respond to a call establishment message with either an alerting or connect + * indication within the prescribed period of time allocated. */ public static final int NO_USER_RESPONDING = 18; - /** The user has provided an alerting indication but has not provided a connect - * indication within a prescribed period of time + /** + * The user has provided an alerting indication but has not provided a connect indication + * within a prescribed period of time. */ public static final int NO_ANSWER_FROM_USER = 19; - /** The equipment sending this cause does not wish to accept this call */ + /** The equipment sending this cause does not wish to accept this call. */ public static final int CALL_REJECTED = 21; - /** The called number is no longer assigned */ + /** The called number is no longer assigned. */ public static final int NUMBER_CHANGED = 22; - /** This cause is returned to the network when a mobile station clears an active - * call which is being pre-empted by another call with higher precedence + /** + * This cause is returned to the network when a mobile station clears an active call which is + * being pre-empted by another call with higher precedence. */ public static final int PREEMPTION = 25; - /** The destination indicated by the mobile station cannot be reached because - * the interface to the destination is not functioning correctly + /** + * The destination indicated by the mobile station cannot be reached because the interface to + * the destination is not functioning correctly. */ public static final int DESTINATION_OUT_OF_ORDER = 27; - /** The called party number is not a valid format or is not complete */ + /** The called party number is not a valid format or is not complete. */ public static final int INVALID_NUMBER_FORMAT = 28; - /** The facility requested by user can not be provided by the network */ + /** The facility requested by user can not be provided by the network. */ public static final int FACILITY_REJECTED = 29; - /** Provided in response to a STATUS ENQUIRY message */ + /** Provided in response to a STATUS ENQUIRY message. */ public static final int STATUS_ENQUIRY = 30; - /** Reports a normal disconnect only when no other normal cause applies */ + /** Reports a normal disconnect only when no other normal cause applies. */ public static final int NORMAL_UNSPECIFIED = 31; - /** There is no channel presently available to handle the call */ + /** There is no channel presently available to handle the call. */ public static final int NO_CIRCUIT_AVAIL = 34; - /** The network is not functioning correctly and that the condition is likely - * to last a relatively long period of time + /** + * The network is not functioning correctly and that the condition is likely to last a + * relatively long period of time. */ public static final int NETWORK_OUT_OF_ORDER = 38; /** - * The network is not functioning correctly and the condition is not likely to last - * a long period of time + * The network is not functioning correctly and the condition is not likely to last a long + * period of time. */ public static final int TEMPORARY_FAILURE = 41; - /** The switching equipment is experiencing a period of high traffic */ + /** The switching equipment is experiencing a period of high traffic. */ public static final int SWITCHING_CONGESTION = 42; - /** The network could not deliver access information to the remote user as requested */ + /** The network could not deliver access information to the remote user as requested. */ public static final int ACCESS_INFORMATION_DISCARDED = 43; - /** The channel cannot be provided */ + /** The channel cannot be provided. */ public static final int CHANNEL_NOT_AVAIL = 44; - /** This cause is used to report a resource unavailable event only when no other - * cause in the resource unavailable class applies + /** + * This cause is used to report a resource unavailable event only when no other cause in the + * resource unavailable class applies. */ public static final int RESOURCES_UNAVAILABLE_OR_UNSPECIFIED = 47; - /** The requested quality of service (ITU-T X.213) cannot be provided */ + /** The requested quality of service (ITU-T X.213) cannot be provided. */ public static final int QOS_NOT_AVAIL = 49; - /** The facility could not be provided by the network because the user has no - * complete subscription + /** + * The facility could not be provided by the network because the user has no complete + * subscription. */ public static final int REQUESTED_FACILITY_NOT_SUBSCRIBED = 50; - /** Incoming calls are not allowed within this CUG */ + /** Incoming calls are not allowed within this calling user group (CUG). */ public static final int INCOMING_CALLS_BARRED_WITHIN_CUG = 55; - /** The mobile station is not authorized to use bearer capability requested */ + /** The mobile station is not authorized to use bearer capability requested. */ public static final int BEARER_CAPABILITY_NOT_AUTHORIZED = 57; - /** The requested bearer capability is not available at this time */ + /** The requested bearer capability is not available at this time. */ public static final int BEARER_NOT_AVAIL = 58; - /** The service option is not availble at this time */ + /** The service option is not availble at this time. */ public static final int SERVICE_OPTION_NOT_AVAILABLE = 63; - /** The equipment sending this cause does not support the bearer capability requested */ + /** The equipment sending this cause does not support the bearer capability requested. */ public static final int BEARER_SERVICE_NOT_IMPLEMENTED = 65; - /** The call clearing is due to ACM being greater than or equal to ACMmax */ + /** The call clearing is due to ACM being greater than or equal to ACMmax. */ public static final int ACM_LIMIT_EXCEEDED = 68; - /** The equipment sending this cause does not support the requested facility */ + /** The equipment sending this cause does not support the requested facility. */ public static final int REQUESTED_FACILITY_NOT_IMPLEMENTED = 69; - /** The equipment sending this cause only supports the restricted version of - * the requested bearer capability + /** + * The equipment sending this cause only supports the restricted version of the requested bearer + * capability. */ public static final int ONLY_DIGITAL_INFORMATION_BEARER_AVAILABLE = 70; - /** The service requested is not implemented at network */ + /** The service requested is not implemented at network. */ public static final int SERVICE_OR_OPTION_NOT_IMPLEMENTED = 79; - /** The equipment sending this cause has received a message with a transaction identifier - * which is not currently in use on the MS-network interface + /** + * The equipment sending this cause has received a message with a transaction identifier + * which is not currently in use on the mobile station network interface. */ public static final int INVALID_TRANSACTION_IDENTIFIER = 81; - /** The called user for the incoming CUG call is not a member of the specified CUG */ + /** + * The called user for the incoming CUG call is not a member of the specified calling user + * group (CUG). + */ public static final int USER_NOT_MEMBER_OF_CUG = 87; - /** The equipment sending this cause has received a request which can't be accomodated */ + /** The equipment sending this cause has received a request which can't be accomodated. */ public static final int INCOMPATIBLE_DESTINATION = 88; - /** This cause is used to report receipt of a message with semantically incorrect contents */ + /** This cause is used to report receipt of a message with semantically incorrect contents. */ public static final int SEMANTICALLY_INCORRECT_MESSAGE = 95; - /** The equipment sending this cause has received a message with a non-semantical - * mandatory IE error + /** + * The equipment sending this cause has received a message with a non-semantical mandatory + * information element (IE) error. */ public static final int INVALID_MANDATORY_INFORMATION = 96; - /** This is sent in response to a message which is not defined, or defined but not - * implemented by the equipment sending this cause + /** + * This is sent in response to a message which is not defined, or defined but not implemented + * by the equipment sending this cause. */ public static final int MESSAGE_TYPE_NON_IMPLEMENTED = 97; - /** The equipment sending this cause has received a message not compatible with the - * protocol state + /** + * The equipment sending this cause has received a message not compatible with the protocol + * state. */ public static final int MESSAGE_TYPE_NOT_COMPATIBLE_WITH_PROTOCOL_STATE = 98; - /** The equipment sending this cause has received a message which includes information - * elements not recognized because its identifier is not defined or it is defined but not - * implemented by the equipment sending the cause + /** + * The equipment sending this cause has received a message which includes information + * elements not recognized because its identifier is not defined or it is defined but not + * implemented by the equipment sending the cause. */ public static final int INFORMATION_ELEMENT_NON_EXISTENT = 99; - /** The equipment sending this cause has received a message with conditional IE errors */ + /** The equipment sending this cause has received a message with conditional IE errors. */ public static final int CONDITIONAL_IE_ERROR = 100; - /** The message has been received which is incompatible with the protocol state */ + /** The message has been received which is incompatible with the protocol state. */ public static final int MESSAGE_NOT_COMPATIBLE_WITH_PROTOCOL_STATE = 101; - /** The procedure has been initiated by the expiry of a timer in association with - * 3GPP TS 24.008 error handling procedures + /** + * The procedure has been initiated by the expiry of a timer in association with + * 3GPP TS 24.008 error handling procedures. */ public static final int RECOVERY_ON_TIMER_EXPIRED = 102; - /** This protocol error event is reported only when no other cause in the protocol - * error class applies + /** + * This protocol error event is reported only when no other cause in the protocol error class + * applies. */ public static final int PROTOCOL_ERROR_UNSPECIFIED = 111; - /** interworking with a network which does not provide causes for actions it takes - * thus, the precise cause for a message which is being sent cannot be ascertained + /** + * Interworking with a network which does not provide causes for actions it takes thus, the + * precise cause for a message which is being sent cannot be ascertained. */ public static final int INTERWORKING_UNSPECIFIED = 127; - /** The call is restricted */ + /** The call is restricted. */ public static final int CALL_BARRED = 240; - /** The call is blocked by the Fixed Dialing Number list */ + /** The call is blocked by the Fixed Dialing Number list. */ public static final int FDN_BLOCKED = 241; - /** The given IMSI is not known at the VLR */ - /** TS 24.008 cause 4 */ + /** The given IMSI is not known at the Visitor Location Register (VLR) TS 24.008 cause . */ public static final int IMSI_UNKNOWN_IN_VLR = 242; /** * The network does not accept emergency call establishment using an IMEI or not accept attach - * procedure for emergency services using an IMEI + * procedure for emergency services using an IMEI. */ public static final int IMEI_NOT_ACCEPTED = 243; - /** The call cannot be established because RADIO is OFF */ + /** The call cannot be established because RADIO is OFF. */ public static final int RADIO_OFF = 247; - /** The call cannot be established because of no cell coverage */ + /** The call cannot be established because of no cell coverage. */ public static final int OUT_OF_SRV = 248; - /** The call cannot be established because of no valid SIM */ + /** The call cannot be established because of no valid SIM. */ public static final int NO_VALID_SIM = 249; - /** The call is dropped or failed internally by modem */ + /** The call is dropped or failed internally by modem. */ public static final int RADIO_INTERNAL_ERROR = 250; - /** Call failed because of UE timer expired while waiting for a response from network */ + /** Call failed because of UE timer expired while waiting for a response from network. */ public static final int NETWORK_RESP_TIMEOUT = 251; - /** Call failed because of a network reject */ + /** Call failed because of a network reject. */ public static final int NETWORK_REJECT = 252; - /** Call failed because of radio access failure. ex. RACH failure */ + /** Call failed because of radio access failure. ex. RACH failure. */ public static final int RADIO_ACCESS_FAILURE = 253; - /** Call failed/dropped because of a RLF */ + /** Call failed/dropped because of a Radio Link Failure (RLF). */ public static final int RADIO_LINK_FAILURE = 254; - /** Call failed/dropped because of radio link lost */ + /** Call failed/dropped because of radio link lost. */ public static final int RADIO_LINK_LOST = 255; - /** Call failed because of a radio uplink issue */ + /** Call failed because of a radio uplink issue. */ public static final int RADIO_UPLINK_FAILURE = 256; - /** Call failed because of a RRC connection setup failure */ + /** Call failed because of a RRC (Radio Resource Control) connection setup failure. */ public static final int RADIO_SETUP_FAILURE = 257; - /** Call failed/dropped because of RRC connection release from NW */ + /** Call failed/dropped because of RRC (Radio Resource Control) connection release from NW. */ public static final int RADIO_RELEASE_NORMAL = 258; - /** Call failed/dropped because of RRC abnormally released by modem/network */ + /** + * Call failed/dropped because of RRC (Radio Resource Control) abnormally released by + * modem/network. + */ public static final int RADIO_RELEASE_ABNORMAL = 259; - /** Call setup failed because of access class barring */ + /** Call setup failed because of access class barring. */ public static final int ACCESS_CLASS_BLOCKED = 260; - /** Call failed/dropped because of a network detach */ + /** Call failed/dropped because of a network detach. */ public static final int NETWORK_DETACH = 261; - /** MS is locked until next power cycle */ + /** Mobile station (MS) is locked until next power cycle. */ public static final int CDMA_LOCKED_UNTIL_POWER_CYCLE = 1000; - /** Drop call*/ + /** Drop call. */ public static final int CDMA_DROP = 1001; - /** INTERCEPT order received, MS state idle entered */ + /** INTERCEPT order received, Mobile station (MS) state idle entered. */ public static final int CDMA_INTERCEPT = 1002; - /** MS has been redirected, call is cancelled */ + /** Mobile station (MS) has been redirected, call is cancelled. */ public static final int CDMA_REORDER = 1003; - /** Service option rejection */ + /** Service option rejection. */ public static final int CDMA_SO_REJECT = 1004; - /** Requested service is rejected, retry delay is set */ + /** Requested service is rejected, retry delay is set. */ public static final int CDMA_RETRY_ORDER = 1005; - /** Unable to obtain access to the CDMA system */ + /** Unable to obtain access to the CDMA system. */ public static final int CDMA_ACCESS_FAILURE = 1006; - /** Not a preempted call */ + /** Not a preempted call. */ public static final int CDMA_PREEMPTED = 1007; - /** Not an emergency call */ + /** Not an emergency call. */ public static final int CDMA_NOT_EMERGENCY = 1008; - /** Access Blocked by CDMA network */ + /** Access Blocked by CDMA network. */ public static final int CDMA_ACCESS_BLOCKED = 1009; /** Mapped from ImsReasonInfo */ + // TODO: remove ImsReasonInfo from preciseDisconnectCause /* The passed argument is an invalid */ + /** @hide */ public static final int LOCAL_ILLEGAL_ARGUMENT = 1200; // The operation is invoked in invalid call state + /** @hide */ public static final int LOCAL_ILLEGAL_STATE = 1201; // IMS service internal error + /** @hide */ public static final int LOCAL_INTERNAL_ERROR = 1202; // IMS service goes down (service connection is lost) + /** @hide */ public static final int LOCAL_IMS_SERVICE_DOWN = 1203; // No pending incoming call exists + /** @hide */ public static final int LOCAL_NO_PENDING_CALL = 1204; // Service unavailable; by power off + /** @hide */ public static final int LOCAL_POWER_OFF = 1205; // Service unavailable; by low battery + /** @hide */ public static final int LOCAL_LOW_BATTERY = 1206; // Service unavailable; by out of service (data service state) + /** @hide */ public static final int LOCAL_NETWORK_NO_SERVICE = 1207; /* Service unavailable; by no LTE coverage * (VoLTE is not supported even though IMS is registered) */ + /** @hide */ public static final int LOCAL_NETWORK_NO_LTE_COVERAGE = 1208; /** Service unavailable; by located in roaming area */ + /** @hide */ public static final int LOCAL_NETWORK_ROAMING = 1209; /** Service unavailable; by IP changed */ + /** @hide */ public static final int LOCAL_NETWORK_IP_CHANGED = 1210; /** Service unavailable; other */ + /** @hide */ public static final int LOCAL_SERVICE_UNAVAILABLE = 1211; /* Service unavailable; IMS connection is lost (IMS is not registered) */ + /** @hide */ public static final int LOCAL_NOT_REGISTERED = 1212; /** Max call exceeded */ + /** @hide */ public static final int LOCAL_MAX_CALL_EXCEEDED = 1213; /** Call decline */ + /** @hide */ public static final int LOCAL_CALL_DECLINE = 1214; /** SRVCC is in progress */ + /** @hide */ public static final int LOCAL_CALL_VCC_ON_PROGRESSING = 1215; /** Resource reservation is failed (QoS precondition) */ + /** @hide */ public static final int LOCAL_CALL_RESOURCE_RESERVATION_FAILED = 1216; /** Retry CS call; VoLTE service can't be provided by the network or remote end * Resolve the extra code(EXTRA_CODE_CALL_RETRY_*) if the below code is set + * @hide */ public static final int LOCAL_CALL_CS_RETRY_REQUIRED = 1217; /** Retry VoLTE call; VoLTE service can't be provided by the network temporarily */ + /** @hide */ public static final int LOCAL_CALL_VOLTE_RETRY_REQUIRED = 1218; /** IMS call is already terminated (in TERMINATED state) */ + /** @hide */ public static final int LOCAL_CALL_TERMINATED = 1219; /** Handover not feasible */ + /** @hide */ public static final int LOCAL_HO_NOT_FEASIBLE = 1220; /** 1xx waiting timer is expired after sending INVITE request (MO only) */ + /** @hide */ public static final int TIMEOUT_1XX_WAITING = 1221; /** User no answer during call setup operation (MO/MT) * MO : 200 OK to INVITE request is not received, * MT : No action from user after alerting the call + * @hide */ public static final int TIMEOUT_NO_ANSWER = 1222; /** User no answer during call update operation (MO/MT) * MO : 200 OK to re-INVITE request is not received, * MT : No action from user after alerting the call + * @hide */ public static final int TIMEOUT_NO_ANSWER_CALL_UPDATE = 1223; @@ -296,102 +346,142 @@ public class PreciseDisconnectCause { * STATUSCODE (SIP response code) (IMS -> Telephony) */ /** SIP request is redirected */ + /** @hide */ public static final int SIP_REDIRECTED = 1300; /** 4xx responses */ /** 400 : Bad Request */ + /** @hide */ public static final int SIP_BAD_REQUEST = 1310; /** 403 : Forbidden */ + /** @hide */ public static final int SIP_FORBIDDEN = 1311; /** 404 : Not Found */ + /** @hide */ public static final int SIP_NOT_FOUND = 1312; /** 415 : Unsupported Media Type * 416 : Unsupported URI Scheme * 420 : Bad Extension */ + /** @hide */ public static final int SIP_NOT_SUPPORTED = 1313; /** 408 : Request Timeout */ + /** @hide */ public static final int SIP_REQUEST_TIMEOUT = 1314; /** 480 : Temporarily Unavailable */ + /** @hide */ public static final int SIP_TEMPRARILY_UNAVAILABLE = 1315; /** 484 : Address Incomplete */ + /** @hide */ public static final int SIP_BAD_ADDRESS = 1316; /** 486 : Busy Here * 600 : Busy Everywhere */ + /** @hide */ public static final int SIP_BUSY = 1317; /** 487 : Request Terminated */ + /** @hide */ public static final int SIP_REQUEST_CANCELLED = 1318; /** 406 : Not Acceptable * 488 : Not Acceptable Here * 606 : Not Acceptable */ + /** @hide */ public static final int SIP_NOT_ACCEPTABLE = 1319; /** 410 : Gone * 604 : Does Not Exist Anywhere */ + /** @hide */ public static final int SIP_NOT_REACHABLE = 1320; /** Others */ + /** @hide */ public static final int SIP_CLIENT_ERROR = 1321; /** 481 : Transaction Does Not Exist */ + /** @hide */ public static final int SIP_TRANSACTION_DOES_NOT_EXIST = 1322; /** 5xx responses * 501 : Server Internal Error */ + /** @hide */ public static final int SIP_SERVER_INTERNAL_ERROR = 1330; /** 503 : Service Unavailable */ + /** @hide */ public static final int SIP_SERVICE_UNAVAILABLE = 1331; /** 504 : Server Time-out */ + /** @hide */ public static final int SIP_SERVER_TIMEOUT = 1332; /** Others */ + /** @hide */ public static final int SIP_SERVER_ERROR = 1333; /** 6xx responses * 603 : Decline */ + /** @hide */ public static final int SIP_USER_REJECTED = 1340; /** Others */ + /** @hide */ public static final int SIP_GLOBAL_ERROR = 1341; /** Emergency failure */ + /** @hide */ public static final int EMERGENCY_TEMP_FAILURE = 1342; + /** @hide */ public static final int EMERGENCY_PERM_FAILURE = 1343; /** Media resource initialization failed */ + /** @hide */ public static final int MEDIA_INIT_FAILED = 1400; /** RTP timeout (no audio / video traffic in the session) */ + /** @hide */ public static final int MEDIA_NO_DATA = 1401; /** Media is not supported; so dropped the call */ + /** @hide */ public static final int MEDIA_NOT_ACCEPTABLE = 1402; /** Unknown media related errors */ + /** @hide */ public static final int MEDIA_UNSPECIFIED = 1403; /** User triggers the call end */ + /** @hide */ public static final int USER_TERMINATED = 1500; /** No action while an incoming call is ringing */ + /** @hide */ public static final int USER_NOANSWER = 1501; /** User ignores an incoming call */ + /** @hide */ public static final int USER_IGNORE = 1502; /** User declines an incoming call */ + /** @hide */ public static final int USER_DECLINE = 1503; /** Device declines/ends a call due to low battery */ + /** @hide */ public static final int LOW_BATTERY = 1504; /** Device declines call due to blacklisted call ID */ + /** @hide */ public static final int BLACKLISTED_CALL_ID = 1505; /** The call is terminated by the network or remote user */ + /** @hide */ public static final int USER_TERMINATED_BY_REMOTE = 1510; /** * UT */ + /** @hide */ public static final int UT_NOT_SUPPORTED = 1800; + /** @hide */ public static final int UT_SERVICE_UNAVAILABLE = 1801; + /** @hide */ public static final int UT_OPERATION_NOT_ALLOWED = 1802; + /** @hide */ public static final int UT_NETWORK_ERROR = 1803; + /** @hide */ public static final int UT_CB_PASSWORD_MISMATCH = 1804; /** * ECBM + * @hide */ public static final int ECBM_NOT_SUPPORTED = 1900; /** * Fail code used to indicate that Multi-endpoint is not supported by the Ims framework. + * @hide */ public static final int MULTIENDPOINT_NOT_SUPPORTED = 1901; @@ -405,56 +495,68 @@ public class PreciseDisconnectCause { * active wifi call and at the edge of coverage and there is no qualified LTE network available * to handover the call to. We get a handover NOT_TRIGERRED message from the modem. This error * code is received as part of the handover message. + * @hide */ public static final int CALL_DROP_IWLAN_TO_LTE_UNAVAILABLE = 2000; /** * MT call has ended due to a release from the network * because the call was answered elsewhere + * @hide */ public static final int ANSWERED_ELSEWHERE = 2100; /** * For MultiEndpoint - Call Pull request has failed + * @hide */ public static final int CALL_PULL_OUT_OF_SYNC = 2101; /** * For MultiEndpoint - Call has been pulled from primary to secondary + * @hide */ public static final int CALL_PULLED = 2102; /** * Supplementary services (HOLD/RESUME) failure error codes. * Values for Supplemetary services failure - Failed, Cancelled and Re-Invite collision. + * @hide */ public static final int SUPP_SVC_FAILED = 2300; + /** @hide */ public static final int SUPP_SVC_CANCELLED = 2301; + /** @hide */ public static final int SUPP_SVC_REINVITE_COLLISION = 2302; /** * DPD Procedure received no response or send failed + * @hide */ public static final int IWLAN_DPD_FAILURE = 2400; /** * Establishment of the ePDG Tunnel Failed + * @hide */ public static final int EPDG_TUNNEL_ESTABLISH_FAILURE = 2500; /** * Re-keying of the ePDG Tunnel Failed; may not always result in teardown + * @hide */ public static final int EPDG_TUNNEL_REKEY_FAILURE = 2501; /** * Connection to the packet gateway is lost + * @hide */ public static final int EPDG_TUNNEL_LOST_CONNECTION = 2502; /** * The maximum number of calls allowed has been reached. Used in a multi-endpoint scenario * where the number of calls across all connected devices has reached the maximum. + * @hide */ public static final int MAXIMUM_NUMBER_OF_CALLS_REACHED = 2503; @@ -462,21 +564,25 @@ public class PreciseDisconnectCause { * Similar to {@link #CODE_LOCAL_CALL_DECLINE}, except indicates that a remote device has * declined the call. Used in a multi-endpoint scenario where a remote device declined an * incoming call. + * @hide */ public static final int REMOTE_CALL_DECLINE = 2504; /** * Indicates the call was disconnected due to the user reaching their data limit. + * @hide */ public static final int DATA_LIMIT_REACHED = 2505; /** * Indicates the call was disconnected due to the user disabling cellular data. + * @hide */ public static final int DATA_DISABLED = 2506; /** * Indicates a call was disconnected due to loss of wifi signal. + * @hide */ public static final int WIFI_LOST = 2507; @@ -499,7 +605,7 @@ public class PreciseDisconnectCause { public static final int OEM_CAUSE_14 = 0xf00e; public static final int OEM_CAUSE_15 = 0xf00f; - /** Disconnected due to unspecified reasons */ + /** Disconnected due to unspecified reasons. */ public static final int ERROR_UNSPECIFIED = 0xffff; /** Private constructor to avoid class instantiation. */ diff --git a/telephony/java/android/telephony/SubscriptionInfo.java b/telephony/java/android/telephony/SubscriptionInfo.java index a7e8e8a95ea2..a1e8b199d2a0 100644 --- a/telephony/java/android/telephony/SubscriptionInfo.java +++ b/telephony/java/android/telephony/SubscriptionInfo.java @@ -138,10 +138,15 @@ public class SubscriptionInfo implements Parcelable { private UiccAccessRule[] mAccessRules; /** - * The ID of the SIM card. It is the ICCID of the active profile for a UICC card and the EID - * for an eUICC card. + * The string ID of the SIM card. It is the ICCID of the active profile for a UICC card and the + * EID for an eUICC card. */ - private String mCardId; + private String mCardString; + + /** + * The card ID of the SIM card. This maps uniquely to the card string. + */ + private int mCardId; /** * Whether the subscription is opportunistic. @@ -174,9 +179,9 @@ public class SubscriptionInfo implements Parcelable { public SubscriptionInfo(int id, String iccId, int simSlotIndex, CharSequence displayName, CharSequence carrierName, int nameSource, int iconTint, String number, int roaming, Bitmap icon, String mcc, String mnc, String countryIso, boolean isEmbedded, - @Nullable UiccAccessRule[] accessRules, String cardId) { + @Nullable UiccAccessRule[] accessRules, String cardString) { this(id, iccId, simSlotIndex, displayName, carrierName, nameSource, iconTint, number, - roaming, icon, mcc, mnc, countryIso, isEmbedded, accessRules, cardId, + roaming, icon, mcc, mnc, countryIso, isEmbedded, accessRules, cardString, false, null, true, TelephonyManager.UNKNOWN_CARRIER_ID); } @@ -186,20 +191,22 @@ public class SubscriptionInfo implements Parcelable { public SubscriptionInfo(int id, String iccId, int simSlotIndex, CharSequence displayName, CharSequence carrierName, int nameSource, int iconTint, String number, int roaming, Bitmap icon, String mcc, String mnc, String countryIso, boolean isEmbedded, - @Nullable UiccAccessRule[] accessRules, String cardId, boolean isOpportunistic, + @Nullable UiccAccessRule[] accessRules, String cardString, boolean isOpportunistic, @Nullable String groupUUID, boolean isMetered, int carrierId) { this(id, iccId, simSlotIndex, displayName, carrierName, nameSource, iconTint, number, - roaming, icon, mcc, mnc, countryIso, isEmbedded, accessRules, cardId, + roaming, icon, mcc, mnc, countryIso, isEmbedded, accessRules, cardString, -1, isOpportunistic, groupUUID, isMetered, false, carrierId); } + /** * @hide */ public SubscriptionInfo(int id, String iccId, int simSlotIndex, CharSequence displayName, CharSequence carrierName, int nameSource, int iconTint, String number, int roaming, Bitmap icon, String mcc, String mnc, String countryIso, boolean isEmbedded, - @Nullable UiccAccessRule[] accessRules, String cardId, boolean isOpportunistic, - @Nullable String groupUUID, boolean isMetered, boolean isGroupDisabled, int carrierid) { + @Nullable UiccAccessRule[] accessRules, String cardString, int cardId, + boolean isOpportunistic, @Nullable String groupUUID, boolean isMetered, + boolean isGroupDisabled, int carrierid) { this.mId = id; this.mIccId = iccId; this.mSimSlotIndex = simSlotIndex; @@ -215,6 +222,7 @@ public class SubscriptionInfo implements Parcelable { this.mCountryIso = countryIso; this.mIsEmbedded = isEmbedded; this.mAccessRules = accessRules; + this.mCardString = cardString; this.mCardId = cardId; this.mIsOpportunistic = isOpportunistic; this.mGroupUUID = groupUUID; @@ -523,10 +531,21 @@ public class SubscriptionInfo implements Parcelable { } /** - * @return the ID of the SIM card which contains the subscription. + * @return the card string of the SIM card which contains the subscription. The card string is + * the ICCID for UICCs or the EID for eUICCs. + * @hide + * //TODO rename usages in LPA: UiccSlotUtil.java, UiccSlotsManager.java, UiccSlotInfoTest.java + */ + public String getCardString() { + return this.mCardString; + } + + /** + * @return the cardId of the SIM card which contains the subscription. * @hide */ - public String getCardId() { + @SystemApi + public int getCardId() { return this.mCardId; } @@ -564,7 +583,8 @@ public class SubscriptionInfo implements Parcelable { Bitmap iconBitmap = Bitmap.CREATOR.createFromParcel(source); boolean isEmbedded = source.readBoolean(); UiccAccessRule[] accessRules = source.createTypedArray(UiccAccessRule.CREATOR); - String cardId = source.readString(); + String cardString = source.readString(); + int cardId = source.readInt(); boolean isOpportunistic = source.readBoolean(); String groupUUID = source.readString(); boolean isMetered = source.readBoolean(); @@ -573,8 +593,8 @@ public class SubscriptionInfo implements Parcelable { return new SubscriptionInfo(id, iccId, simSlotIndex, displayName, carrierName, nameSource, iconTint, number, dataRoaming, iconBitmap, mcc, mnc, countryIso, - isEmbedded, accessRules, cardId, isOpportunistic, groupUUID, isMetered, - isGroupDisabled, carrierid); + isEmbedded, accessRules, cardString, cardId, isOpportunistic, groupUUID, + isMetered, isGroupDisabled, carrierid); } @Override @@ -600,7 +620,8 @@ public class SubscriptionInfo implements Parcelable { mIconBitmap.writeToParcel(dest, flags); dest.writeBoolean(mIsEmbedded); dest.writeTypedArray(mAccessRules, flags); - dest.writeString(mCardId); + dest.writeString(mCardString); + dest.writeInt(mCardId); dest.writeBoolean(mIsOpportunistic); dest.writeString(mGroupUUID); dest.writeBoolean(mIsMetered); @@ -631,7 +652,7 @@ public class SubscriptionInfo implements Parcelable { @Override public String toString() { String iccIdToPrint = givePrintableIccid(mIccId); - String cardIdToPrint = givePrintableIccid(mCardId); + String cardStringToPrint = givePrintableIccid(mCardString); return "{id=" + mId + ", iccId=" + iccIdToPrint + " simSlotIndex=" + mSimSlotIndex + " carrierId=" + mCarrierId + " displayName=" + mDisplayName + " carrierName=" + mCarrierName + " nameSource=" + mNameSource @@ -639,17 +660,17 @@ public class SubscriptionInfo implements Parcelable { + " dataRoaming=" + mDataRoaming + " iconBitmap=" + mIconBitmap + " mcc " + mMcc + " mnc " + mMnc + "mCountryIso=" + mCountryIso + " isEmbedded " + mIsEmbedded + " accessRules " + Arrays.toString(mAccessRules) - + " cardId=" + cardIdToPrint + " isOpportunistic " + mIsOpportunistic - + " mGroupUUID=" + mGroupUUID + " isMetered=" + mIsMetered - + " mIsGroupDisabled=" + mIsGroupDisabled + "}"; + + " cardString=" + cardStringToPrint + " cardId=" + mCardId + + " isOpportunistic " + mIsOpportunistic + " mGroupUUID=" + mGroupUUID + + " isMetered=" + mIsMetered + " mIsGroupDisabled=" + mIsGroupDisabled + "}"; } @Override public int hashCode() { return Objects.hash(mId, mSimSlotIndex, mNameSource, mIconTint, mDataRoaming, mIsEmbedded, mIsOpportunistic, mGroupUUID, mIsMetered, mIccId, mNumber, mMcc, mMnc, - mCountryIso, mCardId, mDisplayName, mCarrierName, mAccessRules, mIsGroupDisabled, - mCarrierId); + mCountryIso, mCardString, mCardId, mDisplayName, mCarrierName, mAccessRules, + mIsGroupDisabled, mCarrierId); } @Override @@ -680,6 +701,7 @@ public class SubscriptionInfo implements Parcelable { && Objects.equals(mMcc, toCompare.mMcc) && Objects.equals(mMnc, toCompare.mMnc) && Objects.equals(mCountryIso, toCompare.mCountryIso) + && Objects.equals(mCardString, toCompare.mCardString) && Objects.equals(mCardId, toCompare.mCardId) && TextUtils.equals(mDisplayName, toCompare.mDisplayName) && TextUtils.equals(mCarrierName, toCompare.mCarrierName) diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java index eaff50a522ea..b61e99bc6f0d 100644 --- a/telephony/java/android/telephony/SubscriptionManager.java +++ b/telephony/java/android/telephony/SubscriptionManager.java @@ -1198,7 +1198,8 @@ public class SubscriptionManager { } /** - * Request a refresh of the platform cache of profile information. + * Request a refresh of the platform cache of profile information for the eUICC which + * corresponds to the card ID returned by {@link TelephonyManager#getCardIdForDefaultEuicc()}. * * <p>Should be called by the EuiccService implementation whenever this information changes due * to an operation done outside the scope of a request initiated by the platform to the @@ -1206,17 +1207,50 @@ public class SubscriptionManager { * were made through the EuiccService. * * <p>Requires the {@link android.Manifest.permission#WRITE_EMBEDDED_SUBSCRIPTIONS} permission. + * + * @see {@link TelephonyManager#getCardIdForDefaultEuicc()} for more information on the card ID. + * * @hide */ @SystemApi public void requestEmbeddedSubscriptionInfoListRefresh() { + int cardId = TelephonyManager.from(mContext).getCardIdForDefaultEuicc(); try { ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); if (iSub != null) { - iSub.requestEmbeddedSubscriptionInfoListRefresh(); + iSub.requestEmbeddedSubscriptionInfoListRefresh(cardId); } } catch (RemoteException ex) { - // ignore it + logd("requestEmbeddedSubscriptionInfoListFresh for card = " + cardId + " failed."); + } + } + + /** + * Request a refresh of the platform cache of profile information for the eUICC with the given + * {@code cardId}. + * + * <p>Should be called by the EuiccService implementation whenever this information changes due + * to an operation done outside the scope of a request initiated by the platform to the + * EuiccService. There is no need to refresh for downloads, deletes, or other operations that + * were made through the EuiccService. + * + * <p>Requires the {@link android.Manifest.permission#WRITE_EMBEDDED_SUBSCRIPTIONS} permission. + * + * @param cardId the card ID of the eUICC. + * + * @see {@link TelephonyManager#getCardIdForDefaultEuicc()} for more information on the card ID. + * + * @hide + */ + @SystemApi + public void requestEmbeddedSubscriptionInfoListRefresh(int cardId) { + try { + ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); + if (iSub != null) { + iSub.requestEmbeddedSubscriptionInfoListRefresh(cardId); + } + } catch (RemoteException ex) { + logd("requestEmbeddedSubscriptionInfoListFresh for card = " + cardId + " failed."); } } diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index e0632b1e0392..29d32e946b2c 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -625,8 +625,6 @@ public class TelephonyManager { * The {@link #EXTRA_RINGING_CALL_STATE} extra indicates the ringing call state. * The {@link #EXTRA_FOREGROUND_CALL_STATE} extra indicates the foreground call state. * The {@link #EXTRA_BACKGROUND_CALL_STATE} extra indicates the background call state. - * The {@link #EXTRA_DISCONNECT_CAUSE} extra indicates the disconnect cause. - * The {@link #EXTRA_PRECISE_DISCONNECT_CAUSE} extra indicates the precise disconnect cause. * * <p class="note"> * Requires the READ_PRECISE_PHONE_STATE permission. @@ -634,12 +632,10 @@ public class TelephonyManager { * @see #EXTRA_RINGING_CALL_STATE * @see #EXTRA_FOREGROUND_CALL_STATE * @see #EXTRA_BACKGROUND_CALL_STATE - * @see #EXTRA_DISCONNECT_CAUSE - * @see #EXTRA_PRECISE_DISCONNECT_CAUSE * * <p class="note"> * Requires the READ_PRECISE_PHONE_STATE permission. - * + * @deprecated use {@link PhoneStateListener#LISTEN_PRECISE_CALL_STATE} instead * @hide */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) @@ -647,8 +643,28 @@ public class TelephonyManager { "android.intent.action.PRECISE_CALL_STATE"; /** - * The lookup key used with the {@link #ACTION_PRECISE_CALL_STATE_CHANGED} broadcast - * for an integer containing the state of the current ringing call. + * Broadcast intent action indicating that call disconnect cause has changed. + * + * <p> + * The {@link #EXTRA_DISCONNECT_CAUSE} extra indicates the disconnect cause. + * The {@link #EXTRA_PRECISE_DISCONNECT_CAUSE} extra indicates the precise disconnect cause. + * + * <p class="note"> + * Requires the READ_PRECISE_PHONE_STATE permission. + * + * @see #EXTRA_DISCONNECT_CAUSE + * @see #EXTRA_PRECISE_DISCONNECT_CAUSE + * + * @hide + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_CALL_DISCONNECT_CAUSE_CHANGED = + "android.intent.action.CALL_DISCONNECT_CAUSE"; + + /** + * The lookup key used with the {@link #ACTION_PRECISE_CALL_STATE_CHANGED} broadcast and + * {@link PhoneStateListener#onPreciseCallStateChanged(PreciseCallState)} for an integer + * containing the state of the current ringing call. * * @see PreciseCallState#PRECISE_CALL_STATE_NOT_VALID * @see PreciseCallState#PRECISE_CALL_STATE_IDLE @@ -670,8 +686,9 @@ public class TelephonyManager { public static final String EXTRA_RINGING_CALL_STATE = "ringing_state"; /** - * The lookup key used with the {@link #ACTION_PRECISE_CALL_STATE_CHANGED} broadcast - * for an integer containing the state of the current foreground call. + * The lookup key used with the {@link #ACTION_PRECISE_CALL_STATE_CHANGED} broadcast and + * {@link PhoneStateListener#onPreciseCallStateChanged(PreciseCallState)} for an integer + * containing the state of the current foreground call. * * @see PreciseCallState#PRECISE_CALL_STATE_NOT_VALID * @see PreciseCallState#PRECISE_CALL_STATE_IDLE @@ -693,8 +710,9 @@ public class TelephonyManager { public static final String EXTRA_FOREGROUND_CALL_STATE = "foreground_state"; /** - * The lookup key used with the {@link #ACTION_PRECISE_CALL_STATE_CHANGED} broadcast - * for an integer containing the state of the current background call. + * The lookup key used with the {@link #ACTION_PRECISE_CALL_STATE_CHANGED} broadcast and + * {@link PhoneStateListener#onPreciseCallStateChanged(PreciseCallState)} for an integer + * containing the state of the current background call. * * @see PreciseCallState#PRECISE_CALL_STATE_NOT_VALID * @see PreciseCallState#PRECISE_CALL_STATE_IDLE @@ -716,8 +734,9 @@ public class TelephonyManager { public static final String EXTRA_BACKGROUND_CALL_STATE = "background_state"; /** - * The lookup key used with the {@link #ACTION_PRECISE_CALL_STATE_CHANGED} broadcast - * for an integer containing the disconnect cause. + * The lookup key used with the {@link #ACTION_PRECISE_CALL_STATE_CHANGED} broadcast and + * {@link PhoneStateListener#onPreciseCallStateChanged(PreciseCallState)} for an integer + * containing the disconnect cause. * * @see DisconnectCause * @@ -730,8 +749,9 @@ public class TelephonyManager { public static final String EXTRA_DISCONNECT_CAUSE = "disconnect_cause"; /** - * The lookup key used with the {@link #ACTION_PRECISE_CALL_STATE_CHANGED} broadcast - * for an integer containing the disconnect cause provided by the RIL. + * The lookup key used with the {@link #ACTION_PRECISE_CALL_STATE_CHANGED} broadcast and + * {@link PhoneStateListener#onPreciseCallStateChanged(PreciseCallState)} for an integer + * containing the disconnect cause provided by the RIL. * * @see PreciseDisconnectCause * @@ -4888,7 +4908,7 @@ public class TelephonyManager { */ @RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION) public void requestCellInfoUpdate( - @NonNull Executor executor, @NonNull CellInfoCallback callback) { + @NonNull @CallbackExecutor Executor executor, @NonNull CellInfoCallback callback) { try { ITelephony telephony = getITelephony(); if (telephony == null) return; diff --git a/telephony/java/com/android/internal/telephony/IPhoneStateListener.aidl b/telephony/java/com/android/internal/telephony/IPhoneStateListener.aidl index 78fc0bc487bf..00cf9c3577ec 100644 --- a/telephony/java/com/android/internal/telephony/IPhoneStateListener.aidl +++ b/telephony/java/com/android/internal/telephony/IPhoneStateListener.aidl @@ -55,5 +55,6 @@ oneway interface IPhoneStateListener { void onPreferredDataSubIdChanged(in int subId); void onRadioPowerStateChanged(in int state); void onEmergencyNumberListChanged(in Map emergencyNumberList); + void onCallDisconnectCauseChanged(in int disconnectCause, in int preciseDisconnectCause); } diff --git a/telephony/java/com/android/internal/telephony/ISub.aidl b/telephony/java/com/android/internal/telephony/ISub.aidl index 65eedb83c4cc..d169b7d04f5c 100755 --- a/telephony/java/com/android/internal/telephony/ISub.aidl +++ b/telephony/java/com/android/internal/telephony/ISub.aidl @@ -104,7 +104,7 @@ interface ISub { /** * @see android.telephony.SubscriptionManager#requestEmbeddedSubscriptionInfoListRefresh */ - oneway void requestEmbeddedSubscriptionInfoListRefresh(); + oneway void requestEmbeddedSubscriptionInfoListRefresh(int cardId); /** * Add a new SubscriptionInfo to subinfo database if needed diff --git a/tests/net/java/android/net/dhcp/DhcpServerTest.java b/tests/net/java/android/net/dhcp/DhcpServerTest.java index df34c7310b63..ab9bd84b05cb 100644 --- a/tests/net/java/android/net/dhcp/DhcpServerTest.java +++ b/tests/net/java/android/net/dhcp/DhcpServerTest.java @@ -25,7 +25,6 @@ import static android.net.dhcp.DhcpPacket.INADDR_BROADCAST; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertNotNull; -import static junit.framework.Assert.assertNull; import static junit.framework.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; @@ -48,7 +47,6 @@ import android.net.dhcp.DhcpLeaseRepository.InvalidAddressException; import android.net.dhcp.DhcpLeaseRepository.OutOfAddressesException; import android.net.dhcp.DhcpServer.Clock; import android.net.dhcp.DhcpServer.Dependencies; -import android.net.util.InterfaceParams; import android.net.util.SharedLog; import android.os.test.TestLooper; import android.support.test.filters.SmallTest; @@ -74,9 +72,6 @@ import java.util.Set; public class DhcpServerTest { private static final String PROP_DEXMAKER_SHARE_CLASSLOADER = "dexmaker.share_classloader"; private static final String TEST_IFACE = "testiface"; - private static final MacAddress TEST_IFACE_MAC = MacAddress.fromString("11:22:33:44:55:66"); - private static final InterfaceParams TEST_IFACEPARAMS = - new InterfaceParams(TEST_IFACE, 1, TEST_IFACE_MAC); private static final Inet4Address TEST_SERVER_ADDR = parseAddr("192.168.0.2"); private static final LinkAddress TEST_SERVER_LINKADDR = new LinkAddress(TEST_SERVER_ADDR, 20); @@ -149,7 +144,7 @@ public class DhcpServerTest { .build(); mLooper = new TestLooper(); - mServer = new DhcpServer(mLooper.getLooper(), TEST_IFACEPARAMS, servingParams, + mServer = new DhcpServer(mLooper.getLooper(), TEST_IFACE, servingParams, new SharedLog(DhcpServerTest.class.getSimpleName()), mDeps); mServer.start(); diff --git a/tests/net/java/android/net/ip/IpServerTest.java b/tests/net/java/android/net/ip/IpServerTest.java index cff0b5469d47..2c675c68a076 100644 --- a/tests/net/java/android/net/ip/IpServerTest.java +++ b/tests/net/java/android/net/ip/IpServerTest.java @@ -404,7 +404,7 @@ public class IpServerTest { private void assertDhcpStarted(IpPrefix expectedPrefix) { verify(mDependencies, times(1)).makeDhcpServer( - eq(mLooper.getLooper()), eq(TEST_IFACE_PARAMS), any(), eq(mSharedLog)); + eq(mLooper.getLooper()), eq(IFACE_NAME), any(), eq(mSharedLog)); verify(mDhcpServer, times(1)).start(); final DhcpServingParams params = mDhcpParamsCaptor.getValue(); // Last address byte is random diff --git a/tests/net/java/com/android/server/connectivity/TetheringTest.java b/tests/net/java/com/android/server/connectivity/TetheringTest.java index bca9be772704..e6b43d286a3d 100644 --- a/tests/net/java/com/android/server/connectivity/TetheringTest.java +++ b/tests/net/java/com/android/server/connectivity/TetheringTest.java @@ -240,7 +240,7 @@ public class TetheringTest { } @Override - public DhcpServer makeDhcpServer(Looper looper, InterfaceParams iface, + public DhcpServer makeDhcpServer(Looper looper, String ifName, DhcpServingParams params, SharedLog log) { return mDhcpServer; } diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp index c91134c167ed..58702dc465cc 100644 --- a/tools/aapt2/ResourceParser.cpp +++ b/tools/aapt2/ResourceParser.cpp @@ -99,7 +99,7 @@ struct ParsedResource { ResourceId id; Visibility::Level visibility_level = Visibility::Level::kUndefined; bool allow_new = false; - Maybe<Overlayable> overlayable; + Maybe<OverlayableItem> overlayable_item; std::string comment; std::unique_ptr<Value> value; @@ -133,8 +133,8 @@ static bool AddResourcesToTable(ResourceTable* table, IDiagnostics* diag, Parsed } } - if (res->overlayable) { - if (!table->SetOverlayable(res->name, res->overlayable.value(), diag)) { + if (res->overlayable_item) { + if (!table->SetOverlayable(res->name, res->overlayable_item.value(), diag)) { return false; } } @@ -1059,92 +1059,119 @@ bool ResourceParser::ParseSymbol(xml::XmlPullParser* parser, ParsedResource* out bool ResourceParser::ParseOverlayable(xml::XmlPullParser* parser, ParsedResource* out_resource) { if (out_resource->config != ConfigDescription::DefaultConfig()) { diag_->Warn(DiagMessage(out_resource->source) - << "ignoring configuration '" << out_resource->config - << "' for <overlayable> tag"); + << "ignoring configuration '" << out_resource->config + << "' for <overlayable> tag"); + } + + Maybe<StringPiece> overlayable_name = xml::FindNonEmptyAttribute(parser, "name"); + if (!overlayable_name) { + diag_->Error(DiagMessage(out_resource->source) + << "<overlayable> tag must have a 'name' attribute"); + return false; } + const std::string kActorUriScheme = + android::base::StringPrintf("%s://", Overlayable::kActorScheme); + Maybe<StringPiece> overlayable_actor = xml::FindNonEmptyAttribute(parser, "actor"); + if (overlayable_actor && !util::StartsWith(overlayable_actor.value(), kActorUriScheme)) { + diag_->Error(DiagMessage(out_resource->source) + << "specified <overlayable> tag 'actor' attribute must use the scheme '" + << Overlayable::kActorScheme << "'"); + return false; + } + + // Create a overlayable entry grouping that represents this <overlayable> + auto overlayable = std::make_shared<Overlayable>( + overlayable_name.value(), (overlayable_actor) ? overlayable_actor.value() : "", + out_resource->source); + bool error = false; std::string comment; - Overlayable::PolicyFlags current_policies = Overlayable::Policy::kNone; + OverlayableItem::PolicyFlags current_policies = OverlayableItem::Policy::kNone; const size_t start_depth = parser->depth(); while (xml::XmlPullParser::IsGoodEvent(parser->Next())) { xml::XmlPullParser::Event event = parser->event(); if (event == xml::XmlPullParser::Event::kEndElement && parser->depth() == start_depth) { - // Break the loop when exiting the overlayable element + // Break the loop when exiting the <overlayable> break; } else if (event == xml::XmlPullParser::Event::kEndElement && parser->depth() == start_depth + 1) { - // Clear the current policies when exiting the policy element - current_policies = Overlayable::Policy::kNone; + // Clear the current policies when exiting the <policy> tags + current_policies = OverlayableItem::Policy::kNone; continue; } else if (event == xml::XmlPullParser::Event::kComment) { - // Get the comment of individual item elements + // Retrieve the comment of individual <item> tags comment = parser->comment(); continue; } else if (event != xml::XmlPullParser::Event::kStartElement) { - // Skip to the next element + // Skip to the start of the next element continue; } - const Source item_source = source_.WithLine(parser->line_number()); + const Source element_source = source_.WithLine(parser->line_number()); const std::string& element_name = parser->element_name(); const std::string& element_namespace = parser->element_namespace(); if (element_namespace.empty() && element_name == "item") { // Items specify the name and type of resource that should be overlayable - Maybe<StringPiece> maybe_name = xml::FindNonEmptyAttribute(parser, "name"); - if (!maybe_name) { - diag_->Error(DiagMessage(item_source) - << "<item> within an <overlayable> tag must have a 'name' attribute"); + Maybe<StringPiece> item_name = xml::FindNonEmptyAttribute(parser, "name"); + if (!item_name) { + diag_->Error(DiagMessage(element_source) + << "<item> within an <overlayable> must have a 'name' attribute"); error = true; continue; } - Maybe<StringPiece> maybe_type = xml::FindNonEmptyAttribute(parser, "type"); - if (!maybe_type) { - diag_->Error(DiagMessage(item_source) - << "<item> within an <overlayable> tag must have a 'type' attribute"); + Maybe<StringPiece> item_type = xml::FindNonEmptyAttribute(parser, "type"); + if (!item_type) { + diag_->Error(DiagMessage(element_source) + << "<item> within an <overlayable> must have a 'type' attribute"); error = true; continue; } - const ResourceType* type = ParseResourceType(maybe_type.value()); + const ResourceType* type = ParseResourceType(item_type.value()); if (type == nullptr) { - diag_->Error(DiagMessage(item_source) - << "invalid resource type '" << maybe_type.value() + diag_->Error(DiagMessage(element_source) + << "invalid resource type '" << item_type.value() << "' in <item> within an <overlayable>"); error = true; continue; } - ParsedResource child_resource; + OverlayableItem overlayable_item(overlayable); + overlayable_item.policies = current_policies; + overlayable_item.comment = comment; + overlayable_item.source = element_source; + + ParsedResource child_resource{}; child_resource.name.type = *type; - child_resource.name.entry = maybe_name.value().to_string(); - child_resource.overlayable = Overlayable{current_policies, item_source, comment}; + child_resource.name.entry = item_name.value().to_string(); + child_resource.overlayable_item = overlayable_item; out_resource->child_resources.push_back(std::move(child_resource)); } else if (element_namespace.empty() && element_name == "policy") { - if (current_policies != Overlayable::Policy::kNone) { + if (current_policies != OverlayableItem::Policy::kNone) { // If the policy list is not empty, then we are currently inside a policy element - diag_->Error(DiagMessage(item_source) << "<policy> blocks cannot be recursively nested"); + diag_->Error(DiagMessage(element_source) << "<policy> blocks cannot be recursively nested"); error = true; break; } else if (Maybe<StringPiece> maybe_type = xml::FindNonEmptyAttribute(parser, "type")) { // Parse the polices separated by vertical bar characters to allow for specifying multiple - // policies + // policies. Items within the policy tag will have the specified policy. for (StringPiece part : util::Tokenize(maybe_type.value(), '|')) { StringPiece trimmed_part = util::TrimWhitespace(part); if (trimmed_part == "public") { - current_policies |= Overlayable::Policy::kPublic; + current_policies |= OverlayableItem::Policy::kPublic; } else if (trimmed_part == "product") { - current_policies |= Overlayable::Policy::kProduct; + current_policies |= OverlayableItem::Policy::kProduct; } else if (trimmed_part == "product_services") { - current_policies |= Overlayable::Policy::kProductServices; + current_policies |= OverlayableItem::Policy::kProductServices; } else if (trimmed_part == "system") { - current_policies |= Overlayable::Policy::kSystem; + current_policies |= OverlayableItem::Policy::kSystem; } else if (trimmed_part == "vendor") { - current_policies |= Overlayable::Policy::kVendor; + current_policies |= OverlayableItem::Policy::kVendor; } else { - diag_->Error(DiagMessage(item_source) + diag_->Error(DiagMessage(element_source) << "<policy> has unsupported type '" << trimmed_part << "'"); error = true; continue; @@ -1152,11 +1179,13 @@ bool ResourceParser::ParseOverlayable(xml::XmlPullParser* parser, ParsedResource } } } else if (!ShouldIgnoreElement(element_namespace, element_name)) { - diag_->Error(DiagMessage(item_source) << "invalid element <" << element_name << "> " + diag_->Error(DiagMessage(element_source) << "invalid element <" << element_name << "> " << " in <overlayable>"); error = true; break; } + + comment.clear(); } return !error; diff --git a/tools/aapt2/ResourceParser_test.cpp b/tools/aapt2/ResourceParser_test.cpp index 03e6197027cb..debca9c1e1ba 100644 --- a/tools/aapt2/ResourceParser_test.cpp +++ b/tools/aapt2/ResourceParser_test.cpp @@ -892,11 +892,8 @@ TEST_F(ResourceParserTest, ParsePlatformIndependentNewline) { } TEST_F(ResourceParserTest, ParseOverlayable) { - std::string input = R"(<overlayable />)"; - EXPECT_TRUE(TestParse(input)); - - input = R"( - <overlayable> + std::string input = R"( + <overlayable name="Name" actor="overlay://theme"> <item type="string" name="foo" /> <item type="drawable" name="bar" /> </overlayable>)"; @@ -905,24 +902,35 @@ TEST_F(ResourceParserTest, ParseOverlayable) { auto search_result = table_.FindResource(test::ParseNameOrDie("string/foo")); ASSERT_TRUE(search_result); ASSERT_THAT(search_result.value().entry, NotNull()); - ASSERT_TRUE(search_result.value().entry->overlayable); - EXPECT_THAT(search_result.value().entry->overlayable.value().policies, - Eq(Overlayable::Policy::kNone)); + ASSERT_TRUE(search_result.value().entry->overlayable_item); + OverlayableItem& result_overlayable_item = search_result.value().entry->overlayable_item.value(); + EXPECT_THAT(result_overlayable_item.overlayable->name, Eq("Name")); + EXPECT_THAT(result_overlayable_item.overlayable->actor, Eq("overlay://theme")); + EXPECT_THAT(result_overlayable_item.policies, Eq(OverlayableItem::Policy::kNone)); search_result = table_.FindResource(test::ParseNameOrDie("drawable/bar")); ASSERT_TRUE(search_result); ASSERT_THAT(search_result.value().entry, NotNull()); - ASSERT_TRUE(search_result.value().entry->overlayable); - EXPECT_THAT(search_result.value().entry->overlayable.value().policies, - Eq(Overlayable::Policy::kNone)); + ASSERT_TRUE(search_result.value().entry->overlayable_item); + result_overlayable_item = search_result.value().entry->overlayable_item.value(); + EXPECT_THAT(result_overlayable_item.overlayable->name, Eq("Name")); + EXPECT_THAT(result_overlayable_item.overlayable->actor, Eq("overlay://theme")); + EXPECT_THAT(result_overlayable_item.policies, Eq(OverlayableItem::Policy::kNone)); } -TEST_F(ResourceParserTest, ParseOverlayablePolicy) { - std::string input = R"(<overlayable />)"; - EXPECT_TRUE(TestParse(input)); +TEST_F(ResourceParserTest, ParseOverlayableRequiresName) { + EXPECT_FALSE(TestParse(R"(<overlayable actor="overlay://theme" />)")); + EXPECT_TRUE(TestParse(R"(<overlayable name="Name" />)")); + EXPECT_TRUE(TestParse(R"(<overlayable name="Name" actor="overlay://theme" />)")); +} - input = R"( - <overlayable> +TEST_F(ResourceParserTest, ParseOverlayableBadActorFail) { + EXPECT_FALSE(TestParse(R"(<overlayable name="Name" actor="overley://theme" />)")); +} + +TEST_F(ResourceParserTest, ParseOverlayablePolicy) { + std::string input = R"( + <overlayable name="Name"> <item type="string" name="foo" /> <policy type="product"> <item type="string" name="bar" /> @@ -945,49 +953,55 @@ TEST_F(ResourceParserTest, ParseOverlayablePolicy) { auto search_result = table_.FindResource(test::ParseNameOrDie("string/foo")); ASSERT_TRUE(search_result); ASSERT_THAT(search_result.value().entry, NotNull()); - ASSERT_TRUE(search_result.value().entry->overlayable); - Overlayable& overlayable = search_result.value().entry->overlayable.value(); - EXPECT_THAT(overlayable.policies, Eq(Overlayable::Policy::kNone)); + ASSERT_TRUE(search_result.value().entry->overlayable_item); + OverlayableItem result_overlayable_item = search_result.value().entry->overlayable_item.value(); + EXPECT_THAT(result_overlayable_item.overlayable->name, Eq("Name")); + EXPECT_THAT(result_overlayable_item.policies, Eq(OverlayableItem::Policy::kNone)); search_result = table_.FindResource(test::ParseNameOrDie("string/bar")); ASSERT_TRUE(search_result); ASSERT_THAT(search_result.value().entry, NotNull()); - ASSERT_TRUE(search_result.value().entry->overlayable); - overlayable = search_result.value().entry->overlayable.value(); - EXPECT_THAT(overlayable.policies, Eq(Overlayable::Policy::kProduct)); + ASSERT_TRUE(search_result.value().entry->overlayable_item); + result_overlayable_item = search_result.value().entry->overlayable_item.value(); + EXPECT_THAT(result_overlayable_item.overlayable->name, Eq("Name")); + EXPECT_THAT(result_overlayable_item.policies, Eq(OverlayableItem::Policy::kProduct)); search_result = table_.FindResource(test::ParseNameOrDie("string/baz")); ASSERT_TRUE(search_result); ASSERT_THAT(search_result.value().entry, NotNull()); - ASSERT_TRUE(search_result.value().entry->overlayable); - overlayable = search_result.value().entry->overlayable.value(); - EXPECT_THAT(overlayable.policies, Eq(Overlayable::Policy::kProductServices)); + ASSERT_TRUE(search_result.value().entry->overlayable_item); + result_overlayable_item = search_result.value().entry->overlayable_item.value(); + EXPECT_THAT(result_overlayable_item.overlayable->name, Eq("Name")); + EXPECT_THAT(result_overlayable_item.policies, Eq(OverlayableItem::Policy::kProductServices)); search_result = table_.FindResource(test::ParseNameOrDie("string/fiz")); ASSERT_TRUE(search_result); ASSERT_THAT(search_result.value().entry, NotNull()); - ASSERT_TRUE(search_result.value().entry->overlayable); - overlayable = search_result.value().entry->overlayable.value(); - EXPECT_THAT(overlayable.policies, Eq(Overlayable::Policy::kSystem)); + ASSERT_TRUE(search_result.value().entry->overlayable_item); + result_overlayable_item = search_result.value().entry->overlayable_item.value(); + EXPECT_THAT(result_overlayable_item.overlayable->name, Eq("Name")); + EXPECT_THAT(result_overlayable_item.policies, Eq(OverlayableItem::Policy::kSystem)); search_result = table_.FindResource(test::ParseNameOrDie("string/fuz")); ASSERT_TRUE(search_result); ASSERT_THAT(search_result.value().entry, NotNull()); - ASSERT_TRUE(search_result.value().entry->overlayable); - overlayable = search_result.value().entry->overlayable.value(); - EXPECT_THAT(overlayable.policies, Eq(Overlayable::Policy::kVendor)); + ASSERT_TRUE(search_result.value().entry->overlayable_item); + result_overlayable_item = search_result.value().entry->overlayable_item.value(); + EXPECT_THAT(result_overlayable_item.overlayable->name, Eq("Name")); + EXPECT_THAT(result_overlayable_item.policies, Eq(OverlayableItem::Policy::kVendor)); search_result = table_.FindResource(test::ParseNameOrDie("string/faz")); ASSERT_TRUE(search_result); ASSERT_THAT(search_result.value().entry, NotNull()); - ASSERT_TRUE(search_result.value().entry->overlayable); - overlayable = search_result.value().entry->overlayable.value(); - EXPECT_THAT(overlayable.policies, Eq(Overlayable::Policy::kPublic)); + ASSERT_TRUE(search_result.value().entry->overlayable_item); + result_overlayable_item = search_result.value().entry->overlayable_item.value(); + EXPECT_THAT(result_overlayable_item.overlayable->name, Eq("Name")); + EXPECT_THAT(result_overlayable_item.policies, Eq(OverlayableItem::Policy::kPublic)); } TEST_F(ResourceParserTest, ParseOverlayableBadPolicyError) { std::string input = R"( - <overlayable> + <overlayable name="Name"> <policy type="illegal_policy"> <item type="string" name="foo" /> </policy> @@ -995,7 +1009,7 @@ TEST_F(ResourceParserTest, ParseOverlayableBadPolicyError) { EXPECT_FALSE(TestParse(input)); input = R"( - <overlayable> + <overlayable name="Name"> <policy type="product"> <item name="foo" /> </policy> @@ -1003,7 +1017,7 @@ TEST_F(ResourceParserTest, ParseOverlayableBadPolicyError) { EXPECT_FALSE(TestParse(input)); input = R"( - <overlayable> + <overlayable name="Name"> <policy type="vendor"> <item type="string" /> </policy> @@ -1013,7 +1027,7 @@ TEST_F(ResourceParserTest, ParseOverlayableBadPolicyError) { TEST_F(ResourceParserTest, ParseOverlayableMultiplePolicy) { std::string input = R"( - <overlayable> + <overlayable name="Name"> <policy type="vendor|product_services"> <item type="string" name="foo" /> </policy> @@ -1026,39 +1040,59 @@ TEST_F(ResourceParserTest, ParseOverlayableMultiplePolicy) { auto search_result = table_.FindResource(test::ParseNameOrDie("string/foo")); ASSERT_TRUE(search_result); ASSERT_THAT(search_result.value().entry, NotNull()); - ASSERT_TRUE(search_result.value().entry->overlayable); - Overlayable& overlayable = search_result.value().entry->overlayable.value(); - EXPECT_THAT(overlayable.policies, Eq(Overlayable::Policy::kVendor - | Overlayable::Policy::kProductServices)); + ASSERT_TRUE(search_result.value().entry->overlayable_item); + OverlayableItem result_overlayable_item = search_result.value().entry->overlayable_item.value(); + EXPECT_THAT(result_overlayable_item.overlayable->name, Eq("Name")); + EXPECT_THAT(result_overlayable_item.policies, Eq(OverlayableItem::Policy::kVendor + | OverlayableItem::Policy::kProductServices)); search_result = table_.FindResource(test::ParseNameOrDie("string/bar")); ASSERT_TRUE(search_result); ASSERT_THAT(search_result.value().entry, NotNull()); - ASSERT_TRUE(search_result.value().entry->overlayable); - overlayable = search_result.value().entry->overlayable.value(); - EXPECT_THAT(overlayable.policies, Eq(Overlayable::Policy::kProduct - | Overlayable::Policy::kSystem)); + ASSERT_TRUE(search_result.value().entry->overlayable_item); + result_overlayable_item = search_result.value().entry->overlayable_item.value(); + EXPECT_THAT(result_overlayable_item.overlayable->name, Eq("Name")); + EXPECT_THAT(result_overlayable_item.policies, Eq(OverlayableItem::Policy::kProduct + | OverlayableItem::Policy::kSystem)); } TEST_F(ResourceParserTest, DuplicateOverlayableIsError) { std::string input = R"( - <overlayable> + <overlayable name="Name"> + <item type="string" name="foo" /> + <item type="string" name="foo" /> + </overlayable>)"; + EXPECT_FALSE(TestParse(input)); + + input = R"( + <overlayable name="Name"> + <item type="string" name="foo" /> + </overlayable> + <overlayable name="Name"> <item type="string" name="foo" /> + </overlayable>)"; + EXPECT_FALSE(TestParse(input)); + + input = R"( + <overlayable name="Name"> + <item type="string" name="foo" /> + </overlayable> + <overlayable name="Other"> <item type="string" name="foo" /> </overlayable>)"; EXPECT_FALSE(TestParse(input)); input = R"( - <overlayable> + <overlayable name="Name" actor="overlay://my.actor.one"> <item type="string" name="foo" /> </overlayable> - <overlayable> + <overlayable name="Other" actor="overlay://my.actor.two"> <item type="string" name="foo" /> </overlayable>)"; EXPECT_FALSE(TestParse(input)); input = R"( - <overlayable> + <overlayable name="Name"> <policy type="product"> <item type="string" name="foo" /> <item type="string" name="foo" /> @@ -1067,7 +1101,7 @@ TEST_F(ResourceParserTest, DuplicateOverlayableIsError) { EXPECT_FALSE(TestParse(input)); input = R"( - <overlayable> + <overlayable name="Name"> <policy type="product"> <item type="string" name="foo" /> </policy> @@ -1076,7 +1110,7 @@ TEST_F(ResourceParserTest, DuplicateOverlayableIsError) { EXPECT_FALSE(TestParse(input)); input = R"( - <overlayable> + <overlayable name="Name"> <policy type="product"> <item type="string" name="foo" /> </policy> @@ -1087,13 +1121,13 @@ TEST_F(ResourceParserTest, DuplicateOverlayableIsError) { EXPECT_FALSE(TestParse(input)); input = R"( - <overlayable> + <overlayable name="Name"> <policy type="product"> <item type="string" name="foo" /> </policy> </overlayable> - <overlayable> + <overlayable name="Name"> <policy type="product"> <item type="string" name="foo" /> </policy> @@ -1103,7 +1137,7 @@ TEST_F(ResourceParserTest, DuplicateOverlayableIsError) { TEST_F(ResourceParserTest, NestPolicyInOverlayableError) { std::string input = R"( - <overlayable> + <overlayable name="Name"> <policy type="vendor|product"> <policy type="product_services"> <item type="string" name="foo" /> diff --git a/tools/aapt2/ResourceTable.cpp b/tools/aapt2/ResourceTable.cpp index 54633ad5c5e3..dbd0a0ca1799 100644 --- a/tools/aapt2/ResourceTable.cpp +++ b/tools/aapt2/ResourceTable.cpp @@ -40,6 +40,8 @@ using ::android::base::StringPrintf; namespace aapt { +const char* Overlayable::kActorScheme = "overlay"; + static bool less_than_type_and_id(const std::unique_ptr<ResourceTableType>& lhs, const std::pair<ResourceType, Maybe<uint8_t>>& rhs) { return lhs->type < rhs.first || (lhs->type == rhs.first && rhs.second && lhs->id < rhs.second); @@ -625,17 +627,18 @@ bool ResourceTable::SetAllowNewImpl(const ResourceNameRef& name, const AllowNew& return true; } -bool ResourceTable::SetOverlayable(const ResourceNameRef& name, const Overlayable& overlayable, +bool ResourceTable::SetOverlayable(const ResourceNameRef& name, const OverlayableItem& overlayable, IDiagnostics* diag) { return SetOverlayableImpl(name, overlayable, ResourceNameValidator, diag); } bool ResourceTable::SetOverlayableMangled(const ResourceNameRef& name, - const Overlayable& overlayable, IDiagnostics* diag) { + const OverlayableItem& overlayable, IDiagnostics* diag) { return SetOverlayableImpl(name, overlayable, SkipNameValidator, diag); } -bool ResourceTable::SetOverlayableImpl(const ResourceNameRef& name, const Overlayable& overlayable, +bool ResourceTable::SetOverlayableImpl(const ResourceNameRef& name, + const OverlayableItem& overlayable, NameValidator name_validator, IDiagnostics *diag) { CHECK(diag != nullptr); @@ -647,14 +650,15 @@ bool ResourceTable::SetOverlayableImpl(const ResourceNameRef& name, const Overla ResourceTableType* type = package->FindOrCreateType(name.type); ResourceEntry* entry = type->FindOrCreateEntry(name.entry); - if (entry->overlayable) { + if (entry->overlayable_item) { diag->Error(DiagMessage(overlayable.source) - << "duplicate overlayable declaration for resource '" << name << "'"); - diag->Error(DiagMessage(entry->overlayable.value().source) << "previous declaration here"); + << "duplicate overlayable declaration for resource '" << name << "'"); + diag->Error(DiagMessage(entry->overlayable_item.value().source) + << "previous declaration here"); return false; } - entry->overlayable = overlayable; + entry->overlayable_item = overlayable; return true; } @@ -690,7 +694,7 @@ std::unique_ptr<ResourceTable> ResourceTable::Clone() const { new_entry->id = entry->id; new_entry->visibility = entry->visibility; new_entry->allow_new = entry->allow_new; - new_entry->overlayable = entry->overlayable; + new_entry->overlayable_item = entry->overlayable_item; for (const auto& config_value : entry->values) { ResourceConfigValue* new_value = diff --git a/tools/aapt2/ResourceTable.h b/tools/aapt2/ResourceTable.h index e646f5be43c7..eaf6a47a15fd 100644 --- a/tools/aapt2/ResourceTable.h +++ b/tools/aapt2/ResourceTable.h @@ -57,10 +57,27 @@ struct AllowNew { std::string comment; }; -// Represents a declaration that a resource is overlayable at runtime. struct Overlayable { + Overlayable() = default; + Overlayable(const android::StringPiece& name, const android::StringPiece& actor) + : name(name.to_string()), actor(actor.to_string()) {} + Overlayable(const android::StringPiece& name, const android::StringPiece& actor, + const Source& source) + : name(name.to_string()), actor(actor.to_string()), source(source ){} + + static const char* kActorScheme; + std::string name; + std::string actor; + Source source; +}; + +// Represents a declaration that a resource is overlayable at runtime. +struct OverlayableItem { + explicit OverlayableItem(const std::shared_ptr<Overlayable>& overlayable) + : overlayable(overlayable) {} // Represents the types overlays that are allowed to overlay the resource. + typedef uint32_t PolicyFlags; enum Policy : uint32_t { kNone = 0x00, @@ -80,11 +97,10 @@ struct Overlayable { kProductServices = 0x10 }; - typedef uint32_t PolicyFlags; + std::shared_ptr<Overlayable> overlayable; PolicyFlags policies = Policy::kNone; - - Source source; std::string comment; + Source source; }; class ResourceConfigValue { @@ -121,7 +137,7 @@ class ResourceEntry { Maybe<AllowNew> allow_new; // The declarations of this resource as overlayable for RROs - Maybe<Overlayable> overlayable; + Maybe<OverlayableItem> overlayable_item; // The resource's values for each configuration. std::vector<std::unique_ptr<ResourceConfigValue>> values; @@ -251,9 +267,9 @@ class ResourceTable { bool SetVisibilityWithIdMangled(const ResourceNameRef& name, const Visibility& visibility, const ResourceId& res_id, IDiagnostics* diag); - bool SetOverlayable(const ResourceNameRef& name, const Overlayable& overlayable, + bool SetOverlayable(const ResourceNameRef& name, const OverlayableItem& overlayable, IDiagnostics *diag); - bool SetOverlayableMangled(const ResourceNameRef& name, const Overlayable& overlayable, + bool SetOverlayableMangled(const ResourceNameRef& name, const OverlayableItem& overlayable, IDiagnostics* diag); bool SetAllowNew(const ResourceNameRef& name, const AllowNew& allow_new, IDiagnostics* diag); @@ -328,7 +344,7 @@ class ResourceTable { bool SetAllowNewImpl(const ResourceNameRef& name, const AllowNew& allow_new, NameValidator name_validator, IDiagnostics* diag); - bool SetOverlayableImpl(const ResourceNameRef &name, const Overlayable &overlayable, + bool SetOverlayableImpl(const ResourceNameRef &name, const OverlayableItem& overlayable, NameValidator name_validator, IDiagnostics *diag); bool SetSymbolStateImpl(const ResourceNameRef& name, const ResourceId& res_id, diff --git a/tools/aapt2/ResourceTable_test.cpp b/tools/aapt2/ResourceTable_test.cpp index 31095c4d88c8..a733134f123c 100644 --- a/tools/aapt2/ResourceTable_test.cpp +++ b/tools/aapt2/ResourceTable_test.cpp @@ -244,48 +244,90 @@ TEST(ResourceTableTest, SetAllowNew) { TEST(ResourceTableTest, SetOverlayable) { ResourceTable table; - Overlayable overlayable{}; - overlayable.policies |= Overlayable::Policy::kProduct; - overlayable.policies |= Overlayable::Policy::kProductServices; - overlayable.comment = "comment"; + auto overlayable = std::make_shared<Overlayable>("Name", "overlay://theme", + Source("res/values/overlayable.xml", 40)); + OverlayableItem overlayable_item(overlayable); + overlayable_item.policies |= OverlayableItem::Policy::kProduct; + overlayable_item.policies |= OverlayableItem::Policy::kProductServices; + overlayable_item.comment = "comment"; + overlayable_item.source = Source("res/values/overlayable.xml", 42); const ResourceName name = test::ParseNameOrDie("android:string/foo"); - ASSERT_TRUE(table.SetOverlayable(name, overlayable, test::GetDiagnostics())); + ASSERT_TRUE(table.SetOverlayable(name, overlayable_item, test::GetDiagnostics())); Maybe<ResourceTable::SearchResult> search_result = table.FindResource(name); ASSERT_TRUE(search_result); - ASSERT_TRUE(search_result.value().entry->overlayable); + ASSERT_TRUE(search_result.value().entry->overlayable_item); + + OverlayableItem& result_overlayable_item = search_result.value().entry->overlayable_item.value(); + EXPECT_THAT(result_overlayable_item.overlayable->name, Eq("Name")); + EXPECT_THAT(result_overlayable_item.overlayable->actor, Eq("overlay://theme")); + EXPECT_THAT(result_overlayable_item.overlayable->source.path, Eq("res/values/overlayable.xml")); + EXPECT_THAT(result_overlayable_item.overlayable->source.line, 40); + EXPECT_THAT(result_overlayable_item.policies, Eq(OverlayableItem::Policy::kProduct + | OverlayableItem::Policy::kProductServices)); + ASSERT_THAT(result_overlayable_item.comment, StrEq("comment")); + EXPECT_THAT(result_overlayable_item.source.path, Eq("res/values/overlayable.xml")); + EXPECT_THAT(result_overlayable_item.source.line, 42); +} + +TEST(ResourceTableTest, SetMultipleOverlayableResources) { + ResourceTable table; + + const ResourceName foo = test::ParseNameOrDie("android:string/foo"); + auto group = std::make_shared<Overlayable>("Name", "overlay://theme"); + OverlayableItem overlayable(group); + overlayable.policies = OverlayableItem::Policy::kProduct; + ASSERT_TRUE(table.SetOverlayable(foo, overlayable, test::GetDiagnostics())); + + const ResourceName bar = test::ParseNameOrDie("android:string/bar"); + OverlayableItem overlayable2(group); + overlayable2.policies = OverlayableItem::Policy::kProduct; + ASSERT_TRUE(table.SetOverlayable(bar, overlayable2, test::GetDiagnostics())); + + const ResourceName baz = test::ParseNameOrDie("android:string/baz"); + OverlayableItem overlayable3(group); + overlayable3.policies = OverlayableItem::Policy::kVendor; + ASSERT_TRUE(table.SetOverlayable(baz, overlayable3, test::GetDiagnostics())); +} + +TEST(ResourceTableTest, SetOverlayableDifferentResourcesDifferentName) { + ResourceTable table; + + const ResourceName foo = test::ParseNameOrDie("android:string/foo"); + OverlayableItem overlayable_item(std::make_shared<Overlayable>("Name", "overlay://theme")); + overlayable_item.policies = OverlayableItem::Policy::kProduct; + ASSERT_TRUE(table.SetOverlayable(foo, overlayable_item, test::GetDiagnostics())); - Overlayable& result_overlayable = search_result.value().entry->overlayable.value(); - ASSERT_THAT(result_overlayable.comment, StrEq("comment")); - EXPECT_THAT(result_overlayable.policies, Eq(Overlayable::Policy::kProduct - | Overlayable::Policy::kProductServices)); + const ResourceName bar = test::ParseNameOrDie("android:string/bar"); + OverlayableItem overlayable_item2(std::make_shared<Overlayable>("Name2", "overlay://theme")); + overlayable_item2.policies = OverlayableItem::Policy::kProduct; + ASSERT_TRUE(table.SetOverlayable(bar, overlayable_item2, test::GetDiagnostics())); } -TEST(ResourceTableTest, AddDuplicateOverlayableSamePolicyFail) { +TEST(ResourceTableTest, SetOverlayableSameResourcesFail) { ResourceTable table; const ResourceName name = test::ParseNameOrDie("android:string/foo"); - Overlayable overlayable{}; - overlayable.policies = Overlayable::Policy::kProduct; - ASSERT_TRUE(table.SetOverlayable(name, overlayable, test::GetDiagnostics())); + auto overlayable = std::make_shared<Overlayable>("Name", "overlay://theme"); + OverlayableItem overlayable_item(overlayable); + ASSERT_TRUE(table.SetOverlayable(name, overlayable_item, test::GetDiagnostics())); - Overlayable overlayable2{}; - overlayable2.policies = Overlayable::Policy::kProduct; - ASSERT_FALSE(table.SetOverlayable(name, overlayable2, test::GetDiagnostics())); + OverlayableItem overlayable_item2(overlayable); + ASSERT_FALSE(table.SetOverlayable(name, overlayable_item2, test::GetDiagnostics())); } -TEST(ResourceTableTest, AddDuplicateOverlayableDifferentPolicyFail) { +TEST(ResourceTableTest, SetOverlayableSameResourcesDifferentNameFail) { ResourceTable table; const ResourceName name = test::ParseNameOrDie("android:string/foo"); - Overlayable overlayable{}; - overlayable.policies = Overlayable::Policy::kProduct; - ASSERT_TRUE(table.SetOverlayable(name, overlayable, test::GetDiagnostics())); + auto overlayable = std::make_shared<Overlayable>("Name", "overlay://theme"); + OverlayableItem overlayable_item(overlayable); + ASSERT_TRUE(table.SetOverlayable(name, overlayable_item, test::GetDiagnostics())); - Overlayable overlayable2{}; - overlayable2.policies = Overlayable::Policy::kVendor; - ASSERT_FALSE(table.SetOverlayable(name, overlayable2, test::GetDiagnostics())); + auto overlayable2 = std::make_shared<Overlayable>("Other", "overlay://theme"); + OverlayableItem overlayable_item2(overlayable2); + ASSERT_FALSE(table.SetOverlayable(name, overlayable_item2, test::GetDiagnostics())); } TEST(ResourceTableTest, AllowDuplictaeResourcesNames) { diff --git a/tools/aapt2/Resources.proto b/tools/aapt2/Resources.proto index 81a2c2e5cc02..da541be9502b 100644 --- a/tools/aapt2/Resources.proto +++ b/tools/aapt2/Resources.proto @@ -49,6 +49,9 @@ message ResourceTable { // Resource definitions corresponding to an Android package. repeated Package package = 2; + + // The <overlayable> declarations within the resource table. + repeated Overlayable overlayable = 3; } // A package ID in the range [0x00, 0xff]. @@ -133,8 +136,20 @@ message AllowNew { string comment = 2; } -// Represents a declaration that a resource is overayable at runtime. +// Represents a set of overlayable resources. message Overlayable { + // The name of the <overlyabale>. + string name = 1; + + // The location of the <overlyabale> declaration in the source. + Source source = 2; + + // The component responsible for enabling and disabling overlays targeting this <overlayable>. + string actor = 3; +} + +// Represents an overlayable <item> declaration within an <overlayable> tag. +message OverlayableItem { enum Policy { PUBLIC = 0; SYSTEM = 1; @@ -143,14 +158,18 @@ message Overlayable { PRODUCT_SERVICES = 4; } - // Where this declaration was defined in source. + // The location of the <item> declaration in source. Source source = 1; // Any comment associated with the declaration. string comment = 2; - // The policy defined in the overlayable declaration. + // The policy defined by the enclosing <policy> tag of this <item>. repeated Policy policy = 3; + + // The index into overlayable list that points to the <overlayable> tag that contains + // this <item>. + uint32 overlayable_idx = 4; } // An entry ID in the range [0x0000, 0xffff]. @@ -180,7 +199,7 @@ message Entry { AllowNew allow_new = 4; // Whether this resource can be overlaid by a runtime resource overlay (RRO). - Overlayable overlayable = 5; + OverlayableItem overlayable_item = 5; // The set of values defined for this entry, each corresponding to a different // configuration/variant. diff --git a/tools/aapt2/dump/DumpManifest.cpp b/tools/aapt2/dump/DumpManifest.cpp index 11a4074cd3cd..e17fb4783a45 100644 --- a/tools/aapt2/dump/DumpManifest.cpp +++ b/tools/aapt2/dump/DumpManifest.cpp @@ -43,8 +43,10 @@ enum { PERMISSION_ATTR = 0x01010006, EXPORTED_ATTR = 0x01010010, GRANT_URI_PERMISSIONS_ATTR = 0x0101001b, + PRIORITY_ATTR = 0x0101001c, RESOURCE_ATTR = 0x01010025, DEBUGGABLE_ATTR = 0x0101000f, + TARGET_PACKAGE_ATTR = 0x01010021, VALUE_ATTR = 0x01010024, VERSION_CODE_ATTR = 0x0101021b, VERSION_NAME_ATTR = 0x0101021c, @@ -77,8 +79,11 @@ enum { ISGAME_ATTR = 0x10103f4, VERSION_ATTR = 0x01010519, CERT_DIGEST_ATTR = 0x01010548, - REQUIRED_FEATURE_ATTR = 0x1010557, - REQUIRED_NOT_FEATURE_ATTR = 0x1010558, + REQUIRED_FEATURE_ATTR = 0x01010557, + REQUIRED_NOT_FEATURE_ATTR = 0x01010558, + IS_STATIC_ATTR = 0x0101055a, + REQUIRED_SYSTEM_PROPERTY_NAME_ATTR = 0x01010565, + REQUIRED_SYSTEM_PROPERTY_VALUE_ATTR = 0x01010566, COMPILE_SDK_VERSION_ATTR = 0x01010572, COMPILE_SDK_VERSION_CODENAME_ATTR = 0x01010573, VERSION_MAJOR_ATTR = 0x01010577, @@ -1586,6 +1591,44 @@ class OriginalPackage : public ManifestExtractor::Element { } }; + +/** Represents <overlay> elements. **/ +class Overlay : public ManifestExtractor::Element { + public: + Overlay() = default; + const std::string* target_package = nullptr; + int priority; + bool is_static; + const std::string* required_property_name = nullptr; + const std::string* required_property_value = nullptr; + + void Extract(xml::Element* element) override { + target_package = GetAttributeString(FindAttribute(element, TARGET_PACKAGE_ATTR)); + priority = GetAttributeIntegerDefault(FindAttribute(element, PRIORITY_ATTR), 0); + is_static = GetAttributeIntegerDefault(FindAttribute(element, IS_STATIC_ATTR), false) != 0; + required_property_name = GetAttributeString( + FindAttribute(element, REQUIRED_SYSTEM_PROPERTY_NAME_ATTR)); + required_property_value = GetAttributeString( + FindAttribute(element, REQUIRED_SYSTEM_PROPERTY_VALUE_ATTR)); + } + + void Print(text::Printer* printer) override { + printer->Print(StringPrintf("overlay:")); + if (target_package) { + printer->Print(StringPrintf(" targetPackage='%s'", target_package->c_str())); + } + printer->Print(StringPrintf(" priority='%d'", priority)); + printer->Print(StringPrintf(" isStatic='%s'", is_static ? "true" : "false")); + if (required_property_name) { + printer->Print(StringPrintf(" requiredPropertyName='%s'", required_property_name->c_str())); + } + if (required_property_value) { + printer->Print(StringPrintf(" requiredPropertyValue='%s'", required_property_value->c_str())); + } + printer->Print("\n"); + } +}; + /** * Represents <package-verifier> elements. **/ class PackageVerifier : public ManifestExtractor::Element { public: @@ -2166,6 +2209,7 @@ T* ElementCast(ManifestExtractor::Element* element) { {"meta-data", std::is_base_of<MetaData, T>::value}, {"manifest", std::is_base_of<Manifest, T>::value}, {"original-package", std::is_base_of<OriginalPackage, T>::value}, + {"overlay", std::is_base_of<Overlay, T>::value}, {"package-verifier", std::is_base_of<PackageVerifier, T>::value}, {"permission", std::is_base_of<Permission, T>::value}, {"provider", std::is_base_of<Provider, T>::value}, @@ -2215,6 +2259,7 @@ std::unique_ptr<ManifestExtractor::Element> ManifestExtractor::Element::Inflate( {"manifest", &CreateType<Manifest>}, {"meta-data", &CreateType<MetaData>}, {"original-package", &CreateType<OriginalPackage>}, + {"overlay", &CreateType<Overlay>}, {"package-verifier", &CreateType<PackageVerifier>}, {"permission", &CreateType<Permission>}, {"provider", &CreateType<Provider>}, diff --git a/tools/aapt2/format/binary/BinaryResourceParser.cpp b/tools/aapt2/format/binary/BinaryResourceParser.cpp index 61ebd4ee26ca..c496ff0e159b 100644 --- a/tools/aapt2/format/binary/BinaryResourceParser.cpp +++ b/tools/aapt2/format/binary/BinaryResourceParser.cpp @@ -434,6 +434,8 @@ bool BinaryResourceParser::ParseOverlayable(const ResChunk_header* chunk) { return false; } + auto overlayable = std::make_shared<Overlayable>(); + ResChunkPullParser parser(GetChunkData(chunk), GetChunkDataLen(chunk)); while (ResChunkPullParser::IsGoodEvent(parser.Next())) { @@ -441,25 +443,25 @@ bool BinaryResourceParser::ParseOverlayable(const ResChunk_header* chunk) { const ResTable_overlayable_policy_header* policy_header = ConvertTo<ResTable_overlayable_policy_header>(parser.chunk()); - Overlayable::PolicyFlags policies = Overlayable::Policy::kNone; + OverlayableItem::PolicyFlags policies = OverlayableItem::Policy::kNone; if (policy_header->policy_flags & ResTable_overlayable_policy_header::POLICY_PUBLIC) { - policies |= Overlayable::Policy::kPublic; + policies |= OverlayableItem::Policy::kPublic; } if (policy_header->policy_flags & ResTable_overlayable_policy_header::POLICY_SYSTEM_PARTITION) { - policies |= Overlayable::Policy::kSystem; + policies |= OverlayableItem::Policy::kSystem; } if (policy_header->policy_flags & ResTable_overlayable_policy_header::POLICY_VENDOR_PARTITION) { - policies |= Overlayable::Policy::kVendor; + policies |= OverlayableItem::Policy::kVendor; } if (policy_header->policy_flags & ResTable_overlayable_policy_header::POLICY_PRODUCT_PARTITION) { - policies |= Overlayable::Policy::kProduct; + policies |= OverlayableItem::Policy::kProduct; } if (policy_header->policy_flags & ResTable_overlayable_policy_header::POLICY_PRODUCT_SERVICES_PARTITION) { - policies |= Overlayable::Policy::kProductServices; + policies |= OverlayableItem::Policy::kProductServices; } const ResTable_ref* const ref_begin = reinterpret_cast<const ResTable_ref*>( @@ -478,10 +480,10 @@ bool BinaryResourceParser::ParseOverlayable(const ResChunk_header* chunk) { return false; } - Overlayable overlayable{}; - overlayable.source = source_.WithLine(0); - overlayable.policies = policies; - if (!table_->SetOverlayable(iter->second, overlayable, diag_)) { + OverlayableItem overlayable_item(overlayable); + overlayable_item.source = source_.WithLine(0); + overlayable_item.policies = policies; + if (!table_->SetOverlayable(iter->second, overlayable_item, diag_)) { return false; } } diff --git a/tools/aapt2/format/binary/TableFlattener.cpp b/tools/aapt2/format/binary/TableFlattener.cpp index 200e2d468500..931d57b1c08a 100644 --- a/tools/aapt2/format/binary/TableFlattener.cpp +++ b/tools/aapt2/format/binary/TableFlattener.cpp @@ -429,29 +429,29 @@ class PackageFlattener { CHECK(bool(type->id)) << "type must have an ID set when flattening <overlayable>"; for (auto& entry : type->entries) { CHECK(bool(type->id)) << "entry must have an ID set when flattening <overlayable>"; - if (!entry->overlayable) { + if (!entry->overlayable_item) { continue; } - Overlayable overlayable = entry->overlayable.value(); - uint32_t policy_flags = Overlayable::Policy::kNone; - if (overlayable.policies & Overlayable::Policy::kPublic) { + OverlayableItem& overlayable = entry->overlayable_item.value(); + uint32_t policy_flags = OverlayableItem::Policy::kNone; + if (overlayable.policies & OverlayableItem::Policy::kPublic) { policy_flags |= ResTable_overlayable_policy_header::POLICY_PUBLIC; } - if (overlayable.policies & Overlayable::Policy::kSystem) { + if (overlayable.policies & OverlayableItem::Policy::kSystem) { policy_flags |= ResTable_overlayable_policy_header::POLICY_SYSTEM_PARTITION; } - if (overlayable.policies & Overlayable::Policy::kVendor) { + if (overlayable.policies & OverlayableItem::Policy::kVendor) { policy_flags |= ResTable_overlayable_policy_header::POLICY_VENDOR_PARTITION; } - if (overlayable.policies & Overlayable::Policy::kProduct) { + if (overlayable.policies & OverlayableItem::Policy::kProduct) { policy_flags |= ResTable_overlayable_policy_header::POLICY_PRODUCT_PARTITION; } - if (overlayable.policies & Overlayable::Policy::kProductServices) { + if (overlayable.policies & OverlayableItem::Policy::kProductServices) { policy_flags |= ResTable_overlayable_policy_header::POLICY_PRODUCT_SERVICES_PARTITION; } - if (overlayable.policies == Overlayable::Policy::kNone) { + if (overlayable.policies == OverlayableItem::Policy::kNone) { // Encode overlayable entries defined without a policy as publicly overlayable policy_flags |= ResTable_overlayable_policy_header::POLICY_PUBLIC; } diff --git a/tools/aapt2/format/binary/TableFlattener_test.cpp b/tools/aapt2/format/binary/TableFlattener_test.cpp index e99ab1f37761..a5fb6fd6d7aa 100644 --- a/tools/aapt2/format/binary/TableFlattener_test.cpp +++ b/tools/aapt2/format/binary/TableFlattener_test.cpp @@ -628,17 +628,17 @@ TEST_F(TableFlattenerTest, ObfuscatingResourceNamesWithWhitelistSucceeds) { } TEST_F(TableFlattenerTest, FlattenOverlayable) { - Overlayable overlayable{}; - overlayable.policies |= Overlayable::Policy::kProduct; - overlayable.policies |= Overlayable::Policy::kSystem; - overlayable.policies |= Overlayable::Policy::kVendor; + OverlayableItem overlayable_item(std::make_shared<Overlayable>("TestName", "overlay://theme")); + overlayable_item.policies |= OverlayableItem::Policy::kProduct; + overlayable_item.policies |= OverlayableItem::Policy::kSystem; + overlayable_item.policies |= OverlayableItem::Policy::kVendor; std::string name = "com.app.test:integer/overlayable"; std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() .SetPackageId("com.app.test", 0x7f) .AddSimple(name, ResourceId(0x7f020000)) - .SetOverlayable(name, overlayable) + .SetOverlayable(name, overlayable_item) .Build(); ResourceTable output_table; @@ -647,45 +647,46 @@ TEST_F(TableFlattenerTest, FlattenOverlayable) { auto search_result = output_table.FindResource(test::ParseNameOrDie(name)); ASSERT_TRUE(search_result); ASSERT_THAT(search_result.value().entry, NotNull()); - ASSERT_TRUE(search_result.value().entry->overlayable); - Overlayable& result_overlayable = search_result.value().entry->overlayable.value(); - EXPECT_EQ(result_overlayable.policies, Overlayable::Policy::kSystem - | Overlayable::Policy::kVendor - | Overlayable::Policy::kProduct); + ASSERT_TRUE(search_result.value().entry->overlayable_item); + OverlayableItem& result_overlayable_item = search_result.value().entry->overlayable_item.value(); + EXPECT_EQ(result_overlayable_item.policies, OverlayableItem::Policy::kSystem + | OverlayableItem::Policy::kVendor + | OverlayableItem::Policy::kProduct); } TEST_F(TableFlattenerTest, FlattenMultipleOverlayablePolicies) { - std::string name_zero = "com.app.test:integer/overlayable_zero"; - Overlayable overlayable_zero{}; - overlayable_zero.policies |= Overlayable::Policy::kProduct; - overlayable_zero.policies |= Overlayable::Policy::kSystem; - overlayable_zero.policies |= Overlayable::Policy::kProductServices; - - std::string name_one = "com.app.test:integer/overlayable_one"; - Overlayable overlayable_one{}; - overlayable_one.policies |= Overlayable::Policy::kPublic; - overlayable_one.policies |= Overlayable::Policy::kProductServices; - - std::string name_two = "com.app.test:integer/overlayable_two"; - Overlayable overlayable_two{}; - overlayable_two.policies |= Overlayable::Policy::kProduct; - overlayable_two.policies |= Overlayable::Policy::kSystem; - overlayable_two.policies |= Overlayable::Policy::kVendor; - - std::string name_three = "com.app.test:integer/overlayable_three"; - Overlayable overlayable_three{}; + auto overlayable = std::make_shared<Overlayable>("TestName", "overlay://theme"); + std::string name_zero = "com.app.test:integer/overlayable_zero_item"; + OverlayableItem overlayable_zero_item(overlayable); + overlayable_zero_item.policies |= OverlayableItem::Policy::kProduct; + overlayable_zero_item.policies |= OverlayableItem::Policy::kSystem; + overlayable_zero_item.policies |= OverlayableItem::Policy::kProductServices; + + std::string name_one = "com.app.test:integer/overlayable_one_item"; + OverlayableItem overlayable_one_item(overlayable); + overlayable_one_item.policies |= OverlayableItem::Policy::kPublic; + overlayable_one_item.policies |= OverlayableItem::Policy::kProductServices; + + std::string name_two = "com.app.test:integer/overlayable_two_item"; + OverlayableItem overlayable_two_item(overlayable); + overlayable_two_item.policies |= OverlayableItem::Policy::kProduct; + overlayable_two_item.policies |= OverlayableItem::Policy::kSystem; + overlayable_two_item.policies |= OverlayableItem::Policy::kVendor; + + std::string name_three = "com.app.test:integer/overlayable_three_item"; + OverlayableItem overlayable_three_item(overlayable); std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() .SetPackageId("com.app.test", 0x7f) .AddSimple(name_zero, ResourceId(0x7f020000)) - .SetOverlayable(name_zero, overlayable_zero) + .SetOverlayable(name_zero, overlayable_zero_item) .AddSimple(name_one, ResourceId(0x7f020001)) - .SetOverlayable(name_one, overlayable_one) + .SetOverlayable(name_one, overlayable_one_item) .AddSimple(name_two, ResourceId(0x7f020002)) - .SetOverlayable(name_two, overlayable_two) + .SetOverlayable(name_two, overlayable_two_item) .AddSimple(name_three, ResourceId(0x7f020003)) - .SetOverlayable(name_three, overlayable_three) + .SetOverlayable(name_three, overlayable_three_item) .Build(); ResourceTable output_table; @@ -694,35 +695,35 @@ TEST_F(TableFlattenerTest, FlattenMultipleOverlayablePolicies) { auto search_result = output_table.FindResource(test::ParseNameOrDie(name_zero)); ASSERT_TRUE(search_result); ASSERT_THAT(search_result.value().entry, NotNull()); - ASSERT_TRUE(search_result.value().entry->overlayable); - Overlayable& result_overlayable = search_result.value().entry->overlayable.value(); - EXPECT_EQ(result_overlayable.policies, Overlayable::Policy::kSystem - | Overlayable::Policy::kProduct - | Overlayable::Policy::kProductServices); + ASSERT_TRUE(search_result.value().entry->overlayable_item); + OverlayableItem& overlayable_item = search_result.value().entry->overlayable_item.value(); + EXPECT_EQ(overlayable_item.policies, OverlayableItem::Policy::kSystem + | OverlayableItem::Policy::kProduct + | OverlayableItem::Policy::kProductServices); search_result = output_table.FindResource(test::ParseNameOrDie(name_one)); ASSERT_TRUE(search_result); ASSERT_THAT(search_result.value().entry, NotNull()); - ASSERT_TRUE(search_result.value().entry->overlayable); - result_overlayable = search_result.value().entry->overlayable.value(); - EXPECT_EQ(result_overlayable.policies, Overlayable::Policy::kPublic - | Overlayable::Policy::kProductServices); + ASSERT_TRUE(search_result.value().entry->overlayable_item); + overlayable_item = search_result.value().entry->overlayable_item.value(); + EXPECT_EQ(overlayable_item.policies, OverlayableItem::Policy::kPublic + | OverlayableItem::Policy::kProductServices); search_result = output_table.FindResource(test::ParseNameOrDie(name_two)); ASSERT_TRUE(search_result); ASSERT_THAT(search_result.value().entry, NotNull()); - ASSERT_TRUE(search_result.value().entry->overlayable); - result_overlayable = search_result.value().entry->overlayable.value(); - EXPECT_EQ(result_overlayable.policies, Overlayable::Policy::kSystem - | Overlayable::Policy::kProduct - | Overlayable::Policy::kVendor); + ASSERT_TRUE(search_result.value().entry->overlayable_item); + overlayable_item = search_result.value().entry->overlayable_item.value(); + EXPECT_EQ(overlayable_item.policies, OverlayableItem::Policy::kSystem + | OverlayableItem::Policy::kProduct + | OverlayableItem::Policy::kVendor); search_result = output_table.FindResource(test::ParseNameOrDie(name_three)); ASSERT_TRUE(search_result); ASSERT_THAT(search_result.value().entry, NotNull()); - ASSERT_TRUE(search_result.value().entry->overlayable); - result_overlayable = search_result.value().entry->overlayable.value(); - EXPECT_EQ(result_overlayable.policies, Overlayable::Policy::kPublic); + ASSERT_TRUE(search_result.value().entry->overlayable_item); + overlayable_item = search_result.value().entry->overlayable_item.value(); + EXPECT_EQ(overlayable_item.policies, OverlayableItem::Policy::kPublic); } } // namespace aapt diff --git a/tools/aapt2/format/proto/ProtoDeserialize.cpp b/tools/aapt2/format/proto/ProtoDeserialize.cpp index cf2ab0f45ad6..6b5746d63bf8 100644 --- a/tools/aapt2/format/proto/ProtoDeserialize.cpp +++ b/tools/aapt2/format/proto/ProtoDeserialize.cpp @@ -373,9 +373,44 @@ static Visibility::Level DeserializeVisibilityFromPb(const pb::Visibility::Level return Visibility::Level::kUndefined; } +bool DeserializeOverlayableItemFromPb(const pb::OverlayableItem& pb_overlayable, + const android::ResStringPool& src_pool, + OverlayableItem* out_overlayable, std::string* out_error) { + for (const int policy : pb_overlayable.policy()) { + switch (policy) { + case pb::OverlayableItem::PUBLIC: + out_overlayable->policies |= OverlayableItem::Policy::kPublic; + break; + case pb::OverlayableItem::SYSTEM: + out_overlayable->policies |= OverlayableItem::Policy::kSystem; + break; + case pb::OverlayableItem::VENDOR: + out_overlayable->policies |= OverlayableItem::Policy::kVendor; + break; + case pb::OverlayableItem::PRODUCT: + out_overlayable->policies |= OverlayableItem::Policy::kProduct; + break; + case pb::OverlayableItem::PRODUCT_SERVICES: + out_overlayable->policies |= OverlayableItem::Policy::kProductServices; + break; + default: + *out_error = "unknown overlayable policy"; + return false; + } + } + + if (pb_overlayable.has_source()) { + DeserializeSourceFromPb(pb_overlayable.source(), src_pool, &out_overlayable->source); + } + + out_overlayable->comment = pb_overlayable.comment(); + return true; +} + static bool DeserializePackageFromPb(const pb::Package& pb_package, const ResStringPool& src_pool, - io::IFileCollection* files, ResourceTable* out_table, - std::string* out_error) { + io::IFileCollection* files, + const std::vector<std::shared_ptr<Overlayable>>& overlayables, + ResourceTable* out_table, std::string* out_error) { Maybe<uint8_t> id; if (pb_package.has_package_id()) { id = static_cast<uint8_t>(pb_package.package_id().id()); @@ -437,39 +472,22 @@ static bool DeserializePackageFromPb(const pb::Package& pb_package, const ResStr entry->allow_new = std::move(allow_new); } - if (pb_entry.has_overlayable()) { - Overlayable overlayable{}; - - const pb::Overlayable& pb_overlayable = pb_entry.overlayable(); - for (const int policy : pb_overlayable.policy()) { - switch (policy) { - case pb::Overlayable::PUBLIC: - overlayable.policies |= Overlayable::Policy::kPublic; - break; - case pb::Overlayable::SYSTEM: - overlayable.policies |= Overlayable::Policy::kSystem; - break; - case pb::Overlayable::VENDOR: - overlayable.policies |= Overlayable::Policy::kVendor; - break; - case pb::Overlayable::PRODUCT: - overlayable.policies |= Overlayable::Policy::kProduct; - break; - case pb::Overlayable::PRODUCT_SERVICES: - overlayable.policies |= Overlayable::Policy::kProductServices; - break; - default: - *out_error = "unknown overlayable policy"; - return false; - } + if (pb_entry.has_overlayable_item()) { + // Find the overlayable to which this item belongs + pb::OverlayableItem pb_overlayable_item = pb_entry.overlayable_item(); + if (pb_overlayable_item.overlayable_idx() >= overlayables.size()) { + *out_error = android::base::StringPrintf("invalid overlayable_idx value %d", + pb_overlayable_item.overlayable_idx()); + return false; } - if (pb_overlayable.has_source()) { - DeserializeSourceFromPb(pb_overlayable.source(), src_pool, &overlayable.source); + OverlayableItem overlayable_item(overlayables[pb_overlayable_item.overlayable_idx()]); + if (!DeserializeOverlayableItemFromPb(pb_overlayable_item, src_pool, &overlayable_item, + out_error)) { + return false; } - overlayable.comment = pb_overlayable.comment(); - entry->overlayable = overlayable; + entry->overlayable_item = std::move(overlayable_item); } ResourceId resid(pb_package.package_id().id(), pb_type.type_id().id(), @@ -522,8 +540,19 @@ bool DeserializeTableFromPb(const pb::ResourceTable& pb_table, io::IFileCollecti } } + // Deserialize the overlayable groups of the table + std::vector<std::shared_ptr<Overlayable>> overlayables; + for (const pb::Overlayable& pb_overlayable : pb_table.overlayable()) { + auto group = std::make_shared<Overlayable>(pb_overlayable.name(), pb_overlayable.actor()); + if (pb_overlayable.has_source()) { + DeserializeSourceFromPb(pb_overlayable.source(), source_pool, &group->source); + } + overlayables.push_back(group); + } + for (const pb::Package& pb_package : pb_table.package()) { - if (!DeserializePackageFromPb(pb_package, source_pool, files, out_table, out_error)) { + if (!DeserializePackageFromPb(pb_package, source_pool, files, overlayables, out_table, + out_error)) { return false; } } diff --git a/tools/aapt2/format/proto/ProtoSerialize.cpp b/tools/aapt2/format/proto/ProtoSerialize.cpp index 70bf8684f8a8..76fbb464b62a 100644 --- a/tools/aapt2/format/proto/ProtoSerialize.cpp +++ b/tools/aapt2/format/proto/ProtoSerialize.cpp @@ -272,9 +272,57 @@ void SerializeConfig(const ConfigDescription& config, pb::Configuration* out_pb_ out_pb_config->set_sdk_version(config.sdkVersion); } +static void SerializeOverlayableItemToPb(const OverlayableItem& overlayable_item, + std::vector<Overlayable*>& serialized_overlayables, + StringPool* source_pool, pb::Entry* pb_entry, + pb::ResourceTable* pb_table) { + // Retrieve the index of the overlayable in the list of groups that have already been serialized. + size_t i; + for (i = 0 ; i < serialized_overlayables.size(); i++) { + if (overlayable_item.overlayable.get() == serialized_overlayables[i]) { + break; + } + } + + // Serialize the overlayable if it has not been serialized already. + if (i == serialized_overlayables.size()) { + serialized_overlayables.push_back(overlayable_item.overlayable.get()); + pb::Overlayable* pb_overlayable = pb_table->add_overlayable(); + pb_overlayable->set_name(overlayable_item.overlayable->name); + pb_overlayable->set_actor(overlayable_item.overlayable->actor); + SerializeSourceToPb(overlayable_item.overlayable->source, source_pool, + pb_overlayable->mutable_source()); + } + + pb::OverlayableItem* pb_overlayable_item = pb_entry->mutable_overlayable_item(); + pb_overlayable_item->set_overlayable_idx(i); + + if (overlayable_item.policies & OverlayableItem::Policy::kPublic) { + pb_overlayable_item->add_policy(pb::OverlayableItem::PUBLIC); + } + if (overlayable_item.policies & OverlayableItem::Policy::kProduct) { + pb_overlayable_item->add_policy(pb::OverlayableItem::PRODUCT); + } + if (overlayable_item.policies & OverlayableItem::Policy::kProductServices) { + pb_overlayable_item->add_policy(pb::OverlayableItem::PRODUCT_SERVICES); + } + if (overlayable_item.policies & OverlayableItem::Policy::kSystem) { + pb_overlayable_item->add_policy(pb::OverlayableItem::SYSTEM); + } + if (overlayable_item.policies & OverlayableItem::Policy::kVendor) { + pb_overlayable_item->add_policy(pb::OverlayableItem::VENDOR); + } + + SerializeSourceToPb(overlayable_item.source, source_pool, + pb_overlayable_item->mutable_source()); + pb_overlayable_item->set_comment(overlayable_item.comment); +} + void SerializeTableToPb(const ResourceTable& table, pb::ResourceTable* out_table, IDiagnostics* diag) { StringPool source_pool; + + std::vector<Overlayable*> overlayables; for (const std::unique_ptr<ResourceTablePackage>& package : table.packages) { pb::Package* pb_package = out_table->add_package(); if (package->id) { @@ -310,29 +358,9 @@ void SerializeTableToPb(const ResourceTable& table, pb::ResourceTable* out_table pb_allow_new->set_comment(entry->allow_new.value().comment); } - if (entry->overlayable) { - pb::Overlayable* pb_overlayable = pb_entry->mutable_overlayable(); - - Overlayable overlayable = entry->overlayable.value(); - if (overlayable.policies & Overlayable::Policy::kPublic) { - pb_overlayable->add_policy(pb::Overlayable::PUBLIC); - } - if (overlayable.policies & Overlayable::Policy::kProduct) { - pb_overlayable->add_policy(pb::Overlayable::PRODUCT); - } - if (overlayable.policies & Overlayable::Policy::kProductServices) { - pb_overlayable->add_policy(pb::Overlayable::PRODUCT_SERVICES); - } - if (overlayable.policies & Overlayable::Policy::kSystem) { - pb_overlayable->add_policy(pb::Overlayable::SYSTEM); - } - if (overlayable.policies & Overlayable::Policy::kVendor) { - pb_overlayable->add_policy(pb::Overlayable::VENDOR); - } - - SerializeSourceToPb(overlayable.source, &source_pool, - pb_overlayable->mutable_source()); - pb_overlayable->set_comment(overlayable.comment); + if (entry->overlayable_item) { + SerializeOverlayableItemToPb(entry->overlayable_item.value(), overlayables, &source_pool, + pb_entry, out_table); } for (const std::unique_ptr<ResourceConfigValue>& config_value : entry->values) { diff --git a/tools/aapt2/format/proto/ProtoSerialize_test.cpp b/tools/aapt2/format/proto/ProtoSerialize_test.cpp index fb913f409f52..4a3c1b86236e 100644 --- a/tools/aapt2/format/proto/ProtoSerialize_test.cpp +++ b/tools/aapt2/format/proto/ProtoSerialize_test.cpp @@ -93,8 +93,11 @@ TEST(ProtoSerializeTest, SerializeSinglePackage) { util::make_unique<Reference>(expected_ref), context->GetDiagnostics())); // Make an overlayable resource. + OverlayableItem overlayable_item(std::make_shared<Overlayable>( + "OverlayableName", "overlay://theme", Source("res/values/overlayable.xml", 40))); + overlayable_item.source = Source("res/values/overlayable.xml", 42); ASSERT_TRUE(table->SetOverlayable(test::ParseNameOrDie("com.app.a:integer/overlayable"), - Overlayable{}, test::GetDiagnostics())); + overlayable_item, test::GetDiagnostics())); pb::ResourceTable pb_table; SerializeTableToPb(*table, &pb_table, context->GetDiagnostics()); @@ -160,9 +163,15 @@ TEST(ProtoSerializeTest, SerializeSinglePackage) { new_table.FindResource(test::ParseNameOrDie("com.app.a:integer/overlayable")); ASSERT_TRUE(search_result); ASSERT_THAT(search_result.value().entry, NotNull()); - ASSERT_TRUE(search_result.value().entry->overlayable); - EXPECT_THAT(search_result.value().entry->overlayable.value().policies, - Eq(Overlayable::Policy::kNone)); + ASSERT_TRUE(search_result.value().entry->overlayable_item); + OverlayableItem& result_overlayable_item = search_result.value().entry->overlayable_item.value(); + EXPECT_THAT(result_overlayable_item.overlayable->name, Eq("OverlayableName")); + EXPECT_THAT(result_overlayable_item.overlayable->actor, Eq("overlay://theme")); + EXPECT_THAT(result_overlayable_item.overlayable->source.path, Eq("res/values/overlayable.xml")); + EXPECT_THAT(result_overlayable_item.overlayable->source.line, Eq(40)); + EXPECT_THAT(result_overlayable_item.policies, Eq(OverlayableItem::Policy::kNone)); + EXPECT_THAT(result_overlayable_item.source.path, Eq("res/values/overlayable.xml")); + EXPECT_THAT(result_overlayable_item.source.line, Eq(42)); } TEST(ProtoSerializeTest, SerializeAndDeserializeXml) { @@ -503,26 +512,31 @@ TEST(ProtoSerializeTest, SerializeDeserializeConfiguration) { } TEST(ProtoSerializeTest, SerializeAndDeserializeOverlayable) { - Overlayable overlayable_foo{}; - overlayable_foo.policies |= Overlayable::Policy::kSystem; - overlayable_foo.policies |= Overlayable::Policy::kProduct; + OverlayableItem overlayable_item_foo(std::make_shared<Overlayable>( + "CustomizableResources", "overlay://customization")); + overlayable_item_foo.policies |= OverlayableItem::Policy::kSystem; + overlayable_item_foo.policies |= OverlayableItem::Policy::kProduct; - Overlayable overlayable_bar{}; - overlayable_bar.policies |= Overlayable::Policy::kProductServices; - overlayable_bar.policies |= Overlayable::Policy::kVendor; + OverlayableItem overlayable_item_bar(std::make_shared<Overlayable>( + "TaskBar", "overlay://theme")); + overlayable_item_bar.policies |= OverlayableItem::Policy::kProductServices; + overlayable_item_bar.policies |= OverlayableItem::Policy::kVendor; - Overlayable overlayable_baz{}; - overlayable_baz.policies |= Overlayable::Policy::kPublic; + OverlayableItem overlayable_item_baz(std::make_shared<Overlayable>( + "FontPack", "overlay://theme")); + overlayable_item_baz.policies |= OverlayableItem::Policy::kPublic; - Overlayable overlayable_biz{}; + OverlayableItem overlayable_item_biz(std::make_shared<Overlayable>( + "Other", "overlay://customization")); + overlayable_item_biz.comment ="comment"; std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() - .SetOverlayable("com.app.a:bool/foo", overlayable_foo) - .SetOverlayable("com.app.a:bool/bar", overlayable_bar) - .SetOverlayable("com.app.a:bool/baz", overlayable_baz) - .SetOverlayable("com.app.a:bool/biz", overlayable_biz) + .SetOverlayable("com.app.a:bool/foo", overlayable_item_foo) + .SetOverlayable("com.app.a:bool/bar", overlayable_item_bar) + .SetOverlayable("com.app.a:bool/baz", overlayable_item_baz) + .SetOverlayable("com.app.a:bool/biz", overlayable_item_biz) .AddValue("com.app.a:bool/fiz", ResourceUtils::TryParseBool("true")) .Build(); @@ -538,33 +552,41 @@ TEST(ProtoSerializeTest, SerializeAndDeserializeOverlayable) { Maybe<ResourceTable::SearchResult> search_result = new_table.FindResource(test::ParseNameOrDie("com.app.a:bool/foo")); ASSERT_TRUE(search_result); - ASSERT_TRUE(search_result.value().entry->overlayable); - Overlayable result_overlayable = search_result.value().entry->overlayable.value(); - EXPECT_THAT(result_overlayable.policies, Eq(Overlayable::Policy::kSystem - | Overlayable::Policy::kProduct)); + ASSERT_TRUE(search_result.value().entry->overlayable_item); + OverlayableItem& overlayable_item = search_result.value().entry->overlayable_item.value(); + EXPECT_THAT(overlayable_item.overlayable->name, Eq("CustomizableResources")); + EXPECT_THAT(overlayable_item.overlayable->actor, Eq("overlay://customization")); + EXPECT_THAT(overlayable_item.policies, Eq(OverlayableItem::Policy::kSystem + | OverlayableItem::Policy::kProduct)); search_result = new_table.FindResource(test::ParseNameOrDie("com.app.a:bool/bar")); ASSERT_TRUE(search_result); - ASSERT_TRUE(search_result.value().entry->overlayable); - result_overlayable = search_result.value().entry->overlayable.value(); - EXPECT_THAT(result_overlayable.policies, Eq(Overlayable::Policy::kProductServices - | Overlayable::Policy::kVendor)); + ASSERT_TRUE(search_result.value().entry->overlayable_item); + overlayable_item = search_result.value().entry->overlayable_item.value(); + EXPECT_THAT(overlayable_item.overlayable->name, Eq("TaskBar")); + EXPECT_THAT(overlayable_item.overlayable->actor, Eq("overlay://theme")); + EXPECT_THAT(overlayable_item.policies, Eq(OverlayableItem::Policy::kProductServices + | OverlayableItem::Policy::kVendor)); search_result = new_table.FindResource(test::ParseNameOrDie("com.app.a:bool/baz")); ASSERT_TRUE(search_result); - ASSERT_TRUE(search_result.value().entry->overlayable); - result_overlayable = search_result.value().entry->overlayable.value(); - EXPECT_THAT(result_overlayable.policies, Overlayable::Policy::kPublic); + ASSERT_TRUE(search_result.value().entry->overlayable_item); + overlayable_item = search_result.value().entry->overlayable_item.value(); + EXPECT_THAT(overlayable_item.overlayable->name, Eq("FontPack")); + EXPECT_THAT(overlayable_item.overlayable->actor, Eq("overlay://theme")); + EXPECT_THAT(overlayable_item.policies, Eq(OverlayableItem::Policy::kPublic)); search_result = new_table.FindResource(test::ParseNameOrDie("com.app.a:bool/biz")); ASSERT_TRUE(search_result); - ASSERT_TRUE(search_result.value().entry->overlayable); - result_overlayable = search_result.value().entry->overlayable.value(); - EXPECT_THAT(result_overlayable.policies, Overlayable::Policy::kNone); + ASSERT_TRUE(search_result.value().entry->overlayable_item); + overlayable_item = search_result.value().entry->overlayable_item.value(); + EXPECT_THAT(overlayable_item.overlayable->name, Eq("Other")); + EXPECT_THAT(overlayable_item.policies, Eq(OverlayableItem::Policy::kNone)); + EXPECT_THAT(overlayable_item.comment, Eq("comment")); search_result = new_table.FindResource(test::ParseNameOrDie("com.app.a:bool/fiz")); ASSERT_TRUE(search_result); - ASSERT_FALSE(search_result.value().entry->overlayable); + ASSERT_FALSE(search_result.value().entry->overlayable_item); } } // namespace aapt diff --git a/tools/aapt2/link/ReferenceLinker.cpp b/tools/aapt2/link/ReferenceLinker.cpp index 8cbc03738677..c2340ba65e38 100644 --- a/tools/aapt2/link/ReferenceLinker.cpp +++ b/tools/aapt2/link/ReferenceLinker.cpp @@ -374,8 +374,8 @@ bool ReferenceLinker::Consume(IAaptContext* context, ResourceTable* table) { } // Ensure that definitions for values declared as overlayable exist - if (entry->overlayable && entry->values.empty()) { - context->GetDiagnostics()->Error(DiagMessage(entry->overlayable.value().source) + if (entry->overlayable_item && entry->values.empty()) { + context->GetDiagnostics()->Error(DiagMessage(entry->overlayable_item.value().source) << "no definition for overlayable symbol '" << name << "'"); error = true; diff --git a/tools/aapt2/link/TableMerger.cpp b/tools/aapt2/link/TableMerger.cpp index 22e1723591a8..cc9fed554350 100644 --- a/tools/aapt2/link/TableMerger.cpp +++ b/tools/aapt2/link/TableMerger.cpp @@ -134,18 +134,18 @@ static bool MergeEntry(IAaptContext* context, const Source& src, dst_entry->allow_new = std::move(src_entry->allow_new); } - if (src_entry->overlayable) { - if (dst_entry->overlayable) { + if (src_entry->overlayable_item) { + if (dst_entry->overlayable_item) { // Do not allow a resource with an overlayable declaration to have that overlayable // declaration redefined - context->GetDiagnostics()->Error(DiagMessage(src_entry->overlayable.value().source) + context->GetDiagnostics()->Error(DiagMessage(src_entry->overlayable_item.value().source) << "duplicate overlayable declaration for resource '" << src_entry->name << "'"); - context->GetDiagnostics()->Error(DiagMessage(dst_entry->overlayable.value().source) + context->GetDiagnostics()->Error(DiagMessage(dst_entry->overlayable_item.value().source) << "previous declaration here"); return false; } else { - dst_entry->overlayable = std::move(src_entry->overlayable); + dst_entry->overlayable_item = std::move(src_entry->overlayable_item); } } diff --git a/tools/aapt2/link/TableMerger_test.cpp b/tools/aapt2/link/TableMerger_test.cpp index 17b2a83bad04..921d634e583e 100644 --- a/tools/aapt2/link/TableMerger_test.cpp +++ b/tools/aapt2/link/TableMerger_test.cpp @@ -437,14 +437,16 @@ TEST_F(TableMergerTest, OverlaidStyleablesAndStylesShouldBeMerged) { } TEST_F(TableMergerTest, SetOverlayable) { - Overlayable overlayable{}; - overlayable.policies |= Overlayable::Policy::kProduct; - overlayable.policies |= Overlayable::Policy::kVendor; + auto overlayable = std::make_shared<Overlayable>("CustomizableResources", + "overlay://customization"); + OverlayableItem overlayable_item(overlayable); + overlayable_item.policies |= OverlayableItem::Policy::kProduct; + overlayable_item.policies |= OverlayableItem::Policy::kVendor; std::unique_ptr<ResourceTable> table_a = test::ResourceTableBuilder() .SetPackageId("com.app.a", 0x7f) - .SetOverlayable("bool/foo", overlayable) + .SetOverlayable("bool/foo", overlayable_item) .Build(); std::unique_ptr<ResourceTable> table_b = @@ -463,26 +465,30 @@ TEST_F(TableMergerTest, SetOverlayable) { const ResourceName name = test::ParseNameOrDie("com.app.a:bool/foo"); Maybe<ResourceTable::SearchResult> search_result = final_table.FindResource(name); ASSERT_TRUE(search_result); - ASSERT_TRUE(search_result.value().entry->overlayable); - Overlayable& result_overlayable = search_result.value().entry->overlayable.value(); - EXPECT_THAT(result_overlayable.policies, Eq(Overlayable::Policy::kProduct - | Overlayable::Policy::kVendor)); + ASSERT_TRUE(search_result.value().entry->overlayable_item); + OverlayableItem& result_overlayable_item = search_result.value().entry->overlayable_item.value(); + EXPECT_THAT(result_overlayable_item.overlayable->name, Eq("CustomizableResources")); + EXPECT_THAT(result_overlayable_item.overlayable->actor, Eq("overlay://customization")); + EXPECT_THAT(result_overlayable_item.policies, Eq(OverlayableItem::Policy::kProduct + | OverlayableItem::Policy::kVendor)); } TEST_F(TableMergerTest, SetOverlayableLater) { + auto overlayable = std::make_shared<Overlayable>("CustomizableResources", + "overlay://customization"); std::unique_ptr<ResourceTable> table_a = test::ResourceTableBuilder() .SetPackageId("com.app.a", 0x7f) .AddSimple("bool/foo") .Build(); - Overlayable overlayable{}; - overlayable.policies |= Overlayable::Policy::kPublic; - overlayable.policies |= Overlayable::Policy::kProductServices; + OverlayableItem overlayable_item(overlayable); + overlayable_item.policies |= OverlayableItem::Policy::kPublic; + overlayable_item.policies |= OverlayableItem::Policy::kProductServices; std::unique_ptr<ResourceTable> table_b = test::ResourceTableBuilder() .SetPackageId("com.app.a", 0x7f) - .SetOverlayable("bool/foo", overlayable) + .SetOverlayable("bool/foo", overlayable_item) .Build(); ResourceTable final_table; @@ -495,27 +501,33 @@ TEST_F(TableMergerTest, SetOverlayableLater) { const ResourceName name = test::ParseNameOrDie("com.app.a:bool/foo"); Maybe<ResourceTable::SearchResult> search_result = final_table.FindResource(name); ASSERT_TRUE(search_result); - ASSERT_TRUE(search_result.value().entry->overlayable); - Overlayable& result_overlayable = search_result.value().entry->overlayable.value(); - EXPECT_THAT(result_overlayable.policies, Eq(Overlayable::Policy::kPublic - | Overlayable::Policy::kProductServices)); + ASSERT_TRUE(search_result.value().entry->overlayable_item); + OverlayableItem& result_overlayable_item = search_result.value().entry->overlayable_item.value(); + EXPECT_THAT(result_overlayable_item.overlayable->name, Eq("CustomizableResources")); + EXPECT_THAT(result_overlayable_item.overlayable->actor, Eq("overlay://customization")); + EXPECT_THAT(result_overlayable_item.policies, Eq(OverlayableItem::Policy::kPublic + | OverlayableItem::Policy::kProductServices)); } -TEST_F(TableMergerTest, SetOverlayableSamePolicesFail) { - Overlayable overlayable_first{}; - overlayable_first.policies |= Overlayable::Policy::kProduct; +TEST_F(TableMergerTest, SameResourceDifferentNameFail) { + auto overlayable_first = std::make_shared<Overlayable>("CustomizableResources", + "overlay://customization"); + OverlayableItem overlayable_item_first(overlayable_first); + overlayable_item_first.policies |= OverlayableItem::Policy::kProduct; std::unique_ptr<ResourceTable> table_a = test::ResourceTableBuilder() .SetPackageId("com.app.a", 0x7f) - .SetOverlayable("bool/foo", overlayable_first) + .SetOverlayable("bool/foo", overlayable_item_first) .Build(); - Overlayable overlayable_second{}; - overlayable_second.policies |= Overlayable::Policy::kProduct; + auto overlayable_second = std::make_shared<Overlayable>("ThemeResources", + "overlay://theme"); + OverlayableItem overlayable_item_second(overlayable_second); + overlayable_item_second.policies |= OverlayableItem::Policy::kProduct; std::unique_ptr<ResourceTable> table_b = test::ResourceTableBuilder() .SetPackageId("com.app.a", 0x7f) - .SetOverlayable("bool/foo", overlayable_second) + .SetOverlayable("bool/foo", overlayable_item_second) .Build(); ResourceTable final_table; @@ -526,21 +538,24 @@ TEST_F(TableMergerTest, SetOverlayableSamePolicesFail) { ASSERT_FALSE(merger.Merge({}, table_b.get(), false /*overlay*/)); } -TEST_F(TableMergerTest, SetOverlayableDifferentPolicesFail) { - Overlayable overlayable_first{}; - overlayable_first.policies |= Overlayable::Policy::kVendor; +TEST_F(TableMergerTest, SameResourceSameNameFail) { + auto overlayable = std::make_shared<Overlayable>("CustomizableResources", + "overlay://customization"); + + OverlayableItem overlayable_item_first(overlayable); + overlayable_item_first.policies |= OverlayableItem::Policy::kProduct; std::unique_ptr<ResourceTable> table_a = test::ResourceTableBuilder() .SetPackageId("com.app.a", 0x7f) - .SetOverlayable("bool/foo",overlayable_first) + .SetOverlayable("bool/foo", overlayable_item_first) .Build(); - Overlayable overlayable_second{}; - overlayable_second.policies |= Overlayable::Policy::kProduct; + OverlayableItem overlayable_item_second(overlayable); + overlayable_item_second.policies |= OverlayableItem::Policy::kSystem; std::unique_ptr<ResourceTable> table_b = test::ResourceTableBuilder() .SetPackageId("com.app.a", 0x7f) - .SetOverlayable("bool/foo", overlayable_second) + .SetOverlayable("bool/foo", overlayable_item_second) .Build(); ResourceTable final_table; diff --git a/tools/aapt2/split/TableSplitter.cpp b/tools/aapt2/split/TableSplitter.cpp index 9c5b5d36b798..24cd5ba302ea 100644 --- a/tools/aapt2/split/TableSplitter.cpp +++ b/tools/aapt2/split/TableSplitter.cpp @@ -248,7 +248,7 @@ void TableSplitter::SplitTable(ResourceTable* original_table) { if (!split_entry->id) { split_entry->id = entry->id; split_entry->visibility = entry->visibility; - split_entry->overlayable = entry->overlayable; + split_entry->overlayable_item = entry->overlayable_item; } // Copy the selected values into the new Split Entry. diff --git a/tools/aapt2/test/Builders.cpp b/tools/aapt2/test/Builders.cpp index 884ec38290f8..9a93f2a7476c 100644 --- a/tools/aapt2/test/Builders.cpp +++ b/tools/aapt2/test/Builders.cpp @@ -136,7 +136,7 @@ ResourceTableBuilder& ResourceTableBuilder::SetSymbolState(const StringPiece& na } ResourceTableBuilder& ResourceTableBuilder::SetOverlayable(const StringPiece& name, - const Overlayable& overlayable) { + const OverlayableItem& overlayable) { ResourceName res_name = ParseNameOrDie(name); CHECK(table_->SetOverlayable(res_name, overlayable, GetDiagnostics())); diff --git a/tools/aapt2/test/Builders.h b/tools/aapt2/test/Builders.h index a12048436e38..c971a1b47fd5 100644 --- a/tools/aapt2/test/Builders.h +++ b/tools/aapt2/test/Builders.h @@ -74,7 +74,7 @@ class ResourceTableBuilder { ResourceTableBuilder& SetSymbolState(const android::StringPiece& name, const ResourceId& id, Visibility::Level level, bool allow_new = false); ResourceTableBuilder& SetOverlayable(const android::StringPiece& name, - const Overlayable& overlayable); + const OverlayableItem& overlayable); StringPool* string_pool(); std::unique_ptr<ResourceTable> Build(); diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl index a8e82b3d1bc6..0362a1b491ea 100644 --- a/wifi/java/android/net/wifi/IWifiManager.aidl +++ b/wifi/java/android/net/wifi/IWifiManager.aidl @@ -197,5 +197,7 @@ interface IWifiManager int removeNetworkSuggestions(in List<WifiNetworkSuggestion> networkSuggestions, in String packageName); String[] getFactoryMacAddresses(); + + void setDeviceMobilityState(int state); } diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java index 8cb20638dd42..a7c2ff0f875c 100644 --- a/wifi/java/android/net/wifi/WifiManager.java +++ b/wifi/java/android/net/wifi/WifiManager.java @@ -4476,4 +4476,69 @@ public class WifiManager { throw e.rethrowFromSystemServer(); } } + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = {"DEVICE_MOBILITY_STATE_"}, value = { + DEVICE_MOBILITY_STATE_UNKNOWN, + DEVICE_MOBILITY_STATE_HIGH_MVMT, + DEVICE_MOBILITY_STATE_LOW_MVMT, + DEVICE_MOBILITY_STATE_STATIONARY}) + public @interface DeviceMobilityState {} + + /** + * Unknown device mobility state + * + * @see #setDeviceMobilityState(int) + * + * @hide + */ + @SystemApi + public static final int DEVICE_MOBILITY_STATE_UNKNOWN = 0; + + /** + * High movement device mobility state + * + * @see #setDeviceMobilityState(int) + * + * @hide + */ + @SystemApi + public static final int DEVICE_MOBILITY_STATE_HIGH_MVMT = 1; + + /** + * Low movement device mobility state + * + * @see #setDeviceMobilityState(int) + * + * @hide + */ + @SystemApi + public static final int DEVICE_MOBILITY_STATE_LOW_MVMT = 2; + + /** + * Stationary device mobility state + * + * @see #setDeviceMobilityState(int) + * + * @hide + */ + @SystemApi + public static final int DEVICE_MOBILITY_STATE_STATIONARY = 3; + + /** + * Updates the device mobility state. Wifi uses this information to adjust the interval between + * Wifi scans in order to balance power consumption with scan accuracy. + * @param state the updated device mobility state + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.WIFI_SET_DEVICE_MOBILITY_STATE) + public void setDeviceMobilityState(@DeviceMobilityState int state) { + try { + mService.setDeviceMobilityState(state); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } } |