diff options
264 files changed, 7577 insertions, 4916 deletions
diff --git a/Android.mk b/Android.mk index e27aa307e1e6..915f103fca0c 100644 --- a/Android.mk +++ b/Android.mk @@ -224,6 +224,7 @@ LOCAL_SRC_FILES += \ core/java/android/net/IEthernetManager.aidl \ core/java/android/net/IEthernetServiceListener.aidl \ core/java/android/net/INetdEventCallback.aidl \ + core/java/android/net/IIpSecService.aidl \ core/java/android/net/INetworkManagementEventObserver.aidl \ core/java/android/net/INetworkPolicyListener.aidl \ core/java/android/net/INetworkPolicyManager.aidl \ diff --git a/api/current.txt b/api/current.txt index c5f4816474dc..d93c268d574b 100644 --- a/api/current.txt +++ b/api/current.txt @@ -1609,6 +1609,7 @@ package android { field public static final int alert_light_frame = 17301505; // 0x1080001 field public static final int arrow_down_float = 17301506; // 0x1080002 field public static final int arrow_up_float = 17301507; // 0x1080003 + field public static final int autofilled_highlight = 17301684; // 0x10800b4 field public static final int bottom_bar = 17301658; // 0x108009a field public static final int btn_default = 17301508; // 0x1080004 field public static final int btn_default_small = 17301509; // 0x1080005 @@ -4771,7 +4772,7 @@ package android.app { method public abstract android.app.FragmentManager.BackStackEntry getBackStackEntryAt(int); method public abstract int getBackStackEntryCount(); method public abstract android.app.Fragment getFragment(android.os.Bundle, java.lang.String); - method public abstract java.util.Collection<android.app.Fragment> getFragments(); + method public abstract java.util.List<android.app.Fragment> getFragments(); method public abstract android.app.Fragment getPrimaryNavigationFragment(); method public void invalidateOptionsMenu(); method public abstract boolean isDestroyed(); @@ -5116,6 +5117,7 @@ package android.app { method public java.lang.String getChannel(); method public java.lang.String getGroup(); method public android.graphics.drawable.Icon getLargeIcon(); + method public java.lang.CharSequence getSettingsText(); method public java.lang.String getShortcutId(); method public android.graphics.drawable.Icon getSmallIcon(); method public java.lang.String getSortKey(); @@ -5160,6 +5162,8 @@ package android.app { field public static final java.lang.String EXTRA_LARGE_ICON_BIG = "android.largeIcon.big"; field public static final java.lang.String EXTRA_MEDIA_SESSION = "android.mediaSession"; field public static final java.lang.String EXTRA_MESSAGES = "android.messages"; + field public static final java.lang.String EXTRA_NOTIFICATION_ID = "android.intent.extra.NOTIFICATION_ID"; + field public static final java.lang.String EXTRA_NOTIFICATION_TAG = "android.intent.extra.NOTIFICATION_TAG"; field public static final java.lang.String EXTRA_PEOPLE = "android.people"; field public static final java.lang.String EXTRA_PICTURE = "android.picture"; field public static final java.lang.String EXTRA_PROGRESS = "android.progress"; @@ -5345,6 +5349,7 @@ package android.app { method public android.app.Notification.Builder setProgress(int, int, boolean); method public android.app.Notification.Builder setPublicVersion(android.app.Notification); method public android.app.Notification.Builder setRemoteInputHistory(java.lang.CharSequence[]); + method public android.app.Notification.Builder setSettingsText(java.lang.CharSequence); method public android.app.Notification.Builder setShortcutId(java.lang.String); method public android.app.Notification.Builder setShowWhen(boolean); method public android.app.Notification.Builder setSmallIcon(int); @@ -10802,6 +10807,7 @@ package android.content.pm { field public static final int PROTECTION_FLAG_PRE23 = 128; // 0x80 field public static final int PROTECTION_FLAG_PREINSTALLED = 1024; // 0x400 field public static final int PROTECTION_FLAG_PRIVILEGED = 16; // 0x10 + field public static final int PROTECTION_FLAG_RUNTIME_ONLY = 8192; // 0x2000 field public static final int PROTECTION_FLAG_SETUP = 2048; // 0x800 field public static final deprecated int PROTECTION_FLAG_SYSTEM = 16; // 0x10 field public static final int PROTECTION_FLAG_VERIFIER = 512; // 0x200 @@ -12850,7 +12856,7 @@ package android.graphics { public static class ColorSpace.Connector { method public android.graphics.ColorSpace getDestination(); - method public android.graphics.ColorSpace.RenderIntent getIntent(); + method public android.graphics.ColorSpace.RenderIntent getRenderIntent(); method public android.graphics.ColorSpace getSource(); method public float[] transform(float, float, float); method public float[] transform(float[]); @@ -15944,7 +15950,7 @@ package android.hardware.usb { method public java.lang.String getSerial(); method public boolean releaseInterface(android.hardware.usb.UsbInterface); method public android.hardware.usb.UsbRequest requestWait(); - method public android.hardware.usb.UsbRequest requestWait(int); + method public android.hardware.usb.UsbRequest requestWait(long) throws java.util.concurrent.TimeoutException; method public boolean setConfiguration(android.hardware.usb.UsbConfiguration); method public boolean setInterface(android.hardware.usb.UsbInterface); } @@ -24546,6 +24552,7 @@ package android.media.tv { method public static boolean isChannelUriForPassthroughInput(android.net.Uri); method public static boolean isChannelUriForTunerInput(android.net.Uri); method public static boolean isProgramUri(android.net.Uri); + field public static final java.lang.String ACTION_INITIALIZE_PROGRAMS = "android.media.tv.action.INITIALIZE_PROGRAMS"; field public static final java.lang.String ACTION_MAKE_CHANNEL_BROWSABLE = "android.media.tv.action.MAKE_CHANNEL_BROWSABLE"; field public static final java.lang.String ACTION_PREVIEW_PROGRAM_ADDED_TO_WATCH_NEXT = "android.media.tv.action.PREVIEW_PROGRAM_ADDED_TO_WATCH_NEXT"; field public static final java.lang.String ACTION_PREVIEW_PROGRAM_BROWSABLE_DISABLED = "android.media.tv.action.PREVIEW_PROGRAM_BROWSABLE_DISABLED"; @@ -25557,7 +25564,7 @@ package android.net { method public android.net.IpSecManager.UdpEncapsulationSocket openUdpEncapsulationSocket() throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException; method public void removeTransportModeTransform(java.net.Socket, android.net.IpSecTransform); method public void removeTransportModeTransform(java.net.DatagramSocket, android.net.IpSecTransform); - method public android.net.IpSecManager.SecurityParameterIndex reserveSecurityParameterIndex(java.net.InetAddress, int) throws android.net.IpSecManager.ResourceUnavailableException, android.net.IpSecManager.SpiUnavailableException; + method public android.net.IpSecManager.SecurityParameterIndex reserveSecurityParameterIndex(int, java.net.InetAddress, int) throws android.net.IpSecManager.ResourceUnavailableException, android.net.IpSecManager.SpiUnavailableException; field public static final int INVALID_SECURITY_PARAMETER_INDEX = 0; // 0x0 } @@ -25591,7 +25598,6 @@ package android.net { method public android.net.IpSecTransform.Builder setAuthentication(int, android.net.IpSecAlgorithm); method public android.net.IpSecTransform.Builder setEncryption(int, android.net.IpSecAlgorithm); method public android.net.IpSecTransform.Builder setIpv4Encapsulation(android.net.IpSecManager.UdpEncapsulationSocket, int); - method public android.net.IpSecTransform.Builder setSpi(int, int); method public android.net.IpSecTransform.Builder setSpi(int, android.net.IpSecManager.SecurityParameterIndex); } @@ -37016,7 +37022,7 @@ package android.service.autofill { method public final android.os.IBinder onBind(android.content.Intent); method public void onConnected(); method public void onDisconnected(); - method public void onFillRequest(android.app.assist.AssistStructure, android.os.Bundle, int, android.os.CancellationSignal, android.service.autofill.FillCallback); + method public abstract void onFillRequest(android.app.assist.AssistStructure, android.os.Bundle, int, android.os.CancellationSignal, android.service.autofill.FillCallback); method public abstract void onSaveRequest(android.app.assist.AssistStructure, android.os.Bundle, android.service.autofill.SaveCallback); field public static final java.lang.String SERVICE_INTERFACE = "android.service.autofill.AutofillService"; field public static final java.lang.String SERVICE_META_DATA = "android.autofill"; @@ -37052,7 +37058,7 @@ package android.service.autofill { ctor public FillResponse.Builder(); method public android.service.autofill.FillResponse.Builder addDataset(android.service.autofill.Dataset); method public android.service.autofill.FillResponse build(); - method public android.service.autofill.FillResponse.Builder setAuthentication(android.content.IntentSender, android.widget.RemoteViews); + method public android.service.autofill.FillResponse.Builder setAuthentication(android.view.autofill.AutofillId[], android.content.IntentSender, android.widget.RemoteViews); method public android.service.autofill.FillResponse.Builder setExtras(android.os.Bundle); method public android.service.autofill.FillResponse.Builder setSaveInfo(android.service.autofill.SaveInfo); } @@ -37068,8 +37074,10 @@ package android.service.autofill { field public static final android.os.Parcelable.Creator<android.service.autofill.SaveInfo> CREATOR; field public static final int SAVE_DATA_TYPE_ADDRESS = 2; // 0x2 field public static final int SAVE_DATA_TYPE_CREDIT_CARD = 3; // 0x3 + field public static final int SAVE_DATA_TYPE_EMAIL_ADDRESS = 5; // 0x5 field public static final int SAVE_DATA_TYPE_GENERIC = 0; // 0x0 field public static final int SAVE_DATA_TYPE_PASSWORD = 1; // 0x1 + field public static final int SAVE_DATA_TYPE_USERNAME = 4; // 0x4 } public static final class SaveInfo.Builder { @@ -40145,7 +40153,6 @@ package android.telephony { method public boolean isVoicemailVibrationEnabled(android.telecom.PhoneAccountHandle); method public boolean isWorldPhone(); method public void listen(android.telephony.PhoneStateListener, int); - method public deprecated boolean sendDialerCode(java.lang.String); method public void sendDialerSpecialCode(java.lang.String); method public java.lang.String sendEnvelopeWithStatus(java.lang.String); method public void sendUssdRequest(java.lang.String, android.telephony.TelephonyManager.UssdResponseCallback, android.os.Handler); diff --git a/api/system-current.txt b/api/system-current.txt index 9187c94ca90e..b7400c6c53d7 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -1728,6 +1728,7 @@ package android { field public static final int alert_light_frame = 17301505; // 0x1080001 field public static final int arrow_down_float = 17301506; // 0x1080002 field public static final int arrow_up_float = 17301507; // 0x1080003 + field public static final int autofilled_highlight = 17301684; // 0x10800b4 field public static final int bottom_bar = 17301658; // 0x108009a field public static final int btn_default = 17301508; // 0x1080004 field public static final int btn_default_small = 17301509; // 0x1080005 @@ -4932,7 +4933,7 @@ package android.app { method public abstract android.app.FragmentManager.BackStackEntry getBackStackEntryAt(int); method public abstract int getBackStackEntryCount(); method public abstract android.app.Fragment getFragment(android.os.Bundle, java.lang.String); - method public abstract java.util.Collection<android.app.Fragment> getFragments(); + method public abstract java.util.List<android.app.Fragment> getFragments(); method public abstract android.app.Fragment getPrimaryNavigationFragment(); method public void invalidateOptionsMenu(); method public abstract boolean isDestroyed(); @@ -5290,6 +5291,7 @@ package android.app { method public java.lang.String getGroup(); method public android.graphics.drawable.Icon getLargeIcon(); method public static java.lang.Class<? extends android.app.Notification.Style> getNotificationStyleClass(java.lang.String); + method public java.lang.CharSequence getSettingsText(); method public java.lang.String getShortcutId(); method public android.graphics.drawable.Icon getSmallIcon(); method public java.lang.String getSortKey(); @@ -5335,6 +5337,8 @@ package android.app { field public static final java.lang.String EXTRA_LARGE_ICON_BIG = "android.largeIcon.big"; field public static final java.lang.String EXTRA_MEDIA_SESSION = "android.mediaSession"; field public static final java.lang.String EXTRA_MESSAGES = "android.messages"; + field public static final java.lang.String EXTRA_NOTIFICATION_ID = "android.intent.extra.NOTIFICATION_ID"; + field public static final java.lang.String EXTRA_NOTIFICATION_TAG = "android.intent.extra.NOTIFICATION_TAG"; field public static final java.lang.String EXTRA_PEOPLE = "android.people"; field public static final java.lang.String EXTRA_PICTURE = "android.picture"; field public static final java.lang.String EXTRA_PROGRESS = "android.progress"; @@ -5522,6 +5526,7 @@ package android.app { method public android.app.Notification.Builder setProgress(int, int, boolean); method public android.app.Notification.Builder setPublicVersion(android.app.Notification); method public android.app.Notification.Builder setRemoteInputHistory(java.lang.CharSequence[]); + method public android.app.Notification.Builder setSettingsText(java.lang.CharSequence); method public android.app.Notification.Builder setShortcutId(java.lang.String); method public android.app.Notification.Builder setShowWhen(boolean); method public android.app.Notification.Builder setSmallIcon(int); @@ -11551,6 +11556,7 @@ package android.content.pm { field public static final int PROTECTION_FLAG_PRE23 = 128; // 0x80 field public static final int PROTECTION_FLAG_PREINSTALLED = 1024; // 0x400 field public static final int PROTECTION_FLAG_PRIVILEGED = 16; // 0x10 + field public static final int PROTECTION_FLAG_RUNTIME_ONLY = 8192; // 0x2000 field public static final int PROTECTION_FLAG_SETUP = 2048; // 0x800 field public static final deprecated int PROTECTION_FLAG_SYSTEM = 16; // 0x10 field public static final int PROTECTION_FLAG_VERIFIER = 512; // 0x200 @@ -13613,7 +13619,7 @@ package android.graphics { public static class ColorSpace.Connector { method public android.graphics.ColorSpace getDestination(); - method public android.graphics.ColorSpace.RenderIntent getIntent(); + method public android.graphics.ColorSpace.RenderIntent getRenderIntent(); method public android.graphics.ColorSpace getSource(); method public float[] transform(float, float, float); method public float[] transform(float[]); @@ -17434,7 +17440,7 @@ package android.hardware.usb { method public java.lang.String getSerial(); method public boolean releaseInterface(android.hardware.usb.UsbInterface); method public android.hardware.usb.UsbRequest requestWait(); - method public android.hardware.usb.UsbRequest requestWait(int); + method public android.hardware.usb.UsbRequest requestWait(long) throws java.util.concurrent.TimeoutException; method public boolean resetDevice(); method public boolean setConfiguration(android.hardware.usb.UsbConfiguration); method public boolean setInterface(android.hardware.usb.UsbInterface); @@ -26516,6 +26522,7 @@ package android.media.tv { method public static boolean isChannelUriForPassthroughInput(android.net.Uri); method public static boolean isChannelUriForTunerInput(android.net.Uri); method public static boolean isProgramUri(android.net.Uri); + field public static final java.lang.String ACTION_INITIALIZE_PROGRAMS = "android.media.tv.action.INITIALIZE_PROGRAMS"; field public static final java.lang.String ACTION_MAKE_CHANNEL_BROWSABLE = "android.media.tv.action.MAKE_CHANNEL_BROWSABLE"; field public static final java.lang.String ACTION_PREVIEW_PROGRAM_ADDED_TO_WATCH_NEXT = "android.media.tv.action.PREVIEW_PROGRAM_ADDED_TO_WATCH_NEXT"; field public static final java.lang.String ACTION_PREVIEW_PROGRAM_BROWSABLE_DISABLED = "android.media.tv.action.PREVIEW_PROGRAM_BROWSABLE_DISABLED"; @@ -27719,13 +27726,11 @@ package android.net { public final class IpSecManager { method public void applyTransportModeTransform(java.net.Socket, android.net.IpSecTransform) throws java.io.IOException; method public void applyTransportModeTransform(java.net.DatagramSocket, android.net.IpSecTransform) throws java.io.IOException; - method public void applyTunnelModeTransform(android.net.Network, android.net.IpSecTransform); method public android.net.IpSecManager.UdpEncapsulationSocket openUdpEncapsulationSocket(int) throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException; method public android.net.IpSecManager.UdpEncapsulationSocket openUdpEncapsulationSocket() throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException; method public void removeTransportModeTransform(java.net.Socket, android.net.IpSecTransform); method public void removeTransportModeTransform(java.net.DatagramSocket, android.net.IpSecTransform); - method public void removeTunnelModeTransform(android.net.Network, android.net.IpSecTransform); - method public android.net.IpSecManager.SecurityParameterIndex reserveSecurityParameterIndex(java.net.InetAddress, int) throws android.net.IpSecManager.ResourceUnavailableException, android.net.IpSecManager.SpiUnavailableException; + method public android.net.IpSecManager.SecurityParameterIndex reserveSecurityParameterIndex(int, java.net.InetAddress, int) throws android.net.IpSecManager.ResourceUnavailableException, android.net.IpSecManager.SpiUnavailableException; field public static final int INVALID_SECURITY_PARAMETER_INDEX = 0; // 0x0 } @@ -27756,12 +27761,10 @@ package android.net { public static class IpSecTransform.Builder { ctor public IpSecTransform.Builder(android.content.Context); method public android.net.IpSecTransform buildTransportModeTransform(java.net.InetAddress) throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException, android.net.IpSecManager.SpiUnavailableException; - method public android.net.IpSecTransform buildTunnelModeTransform(java.net.InetAddress, java.net.InetAddress); method public android.net.IpSecTransform.Builder setAuthentication(int, android.net.IpSecAlgorithm); method public android.net.IpSecTransform.Builder setEncryption(int, android.net.IpSecAlgorithm); method public android.net.IpSecTransform.Builder setIpv4Encapsulation(android.net.IpSecManager.UdpEncapsulationSocket, int); method public android.net.IpSecTransform.Builder setNattKeepalive(int); - method public android.net.IpSecTransform.Builder setSpi(int, int); method public android.net.IpSecTransform.Builder setSpi(int, android.net.IpSecManager.SecurityParameterIndex); method public android.net.IpSecTransform.Builder setUnderlyingNetwork(android.net.Network); } @@ -33586,6 +33589,15 @@ package android.os { method public void open(); } + public final class ConfigUpdate { + field public static final java.lang.String ACTION_UPDATE_CARRIER_PROVISIONING_URLS = "android.intent.action.UPDATE_CARRIER_PROVISIONING_URLS"; + field public static final java.lang.String ACTION_UPDATE_CT_LOGS = "android.intent.action.UPDATE_CT_LOGS"; + field public static final java.lang.String ACTION_UPDATE_INTENT_FIREWALL = "android.intent.action.UPDATE_INTENT_FIREWALL"; + field public static final java.lang.String ACTION_UPDATE_PINS = "android.intent.action.UPDATE_PINS"; + field public static final java.lang.String ACTION_UPDATE_SMS_SHORT_CODES = "android.intent.action.UPDATE_SMS_SHORT_CODES"; + field public static final java.lang.String ACTION_UPDATE_TZDATA = "android.intent.action.UPDATE_TZDATA"; + } + public abstract class CountDownTimer { ctor public CountDownTimer(long, long); method public final synchronized void cancel(); @@ -35598,8 +35610,23 @@ package android.print { } public final class PrintManager { + method public void addPrintServiceRecommendationsChangeListener(android.print.PrintManager.PrintServiceRecommendationsChangeListener, android.os.Handler); + method public void addPrintServicesChangeListener(android.print.PrintManager.PrintServicesChangeListener, android.os.Handler); method public java.util.List<android.print.PrintJob> getPrintJobs(); + method public java.util.List<android.printservice.recommendation.RecommendationInfo> getPrintServiceRecommendations(); + method public java.util.List<android.printservice.PrintServiceInfo> getPrintServices(int); method public android.print.PrintJob print(java.lang.String, android.print.PrintDocumentAdapter, android.print.PrintAttributes); + method public void removePrintServiceRecommendationsChangeListener(android.print.PrintManager.PrintServiceRecommendationsChangeListener); + method public void removePrintServicesChangeListener(android.print.PrintManager.PrintServicesChangeListener); + field public static final int ENABLED_SERVICES = 1; // 0x1 + } + + public static abstract interface PrintManager.PrintServiceRecommendationsChangeListener { + method public abstract void onPrintServiceRecommendationsChanged(); + } + + public static abstract interface PrintManager.PrintServicesChangeListener { + method public abstract void onPrintServicesChanged(); } public final class PrinterCapabilitiesInfo implements android.os.Parcelable { @@ -35728,6 +35755,13 @@ package android.printservice { field public static final java.lang.String SERVICE_META_DATA = "android.printservice"; } + public final class PrintServiceInfo implements android.os.Parcelable { + method public int describeContents(); + method public android.content.ComponentName getComponentName(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.printservice.PrintServiceInfo> CREATOR; + } + public abstract class PrinterDiscoverySession { ctor public PrinterDiscoverySession(); method public final void addPrinters(java.util.List<android.print.PrinterInfo>); @@ -35750,8 +35784,10 @@ package android.printservice { package android.printservice.recommendation { public final class RecommendationInfo implements android.os.Parcelable { - ctor public RecommendationInfo(java.lang.CharSequence, java.lang.CharSequence, int, boolean); + ctor public RecommendationInfo(java.lang.CharSequence, java.lang.CharSequence, java.util.List<java.net.InetAddress>, boolean); + ctor public deprecated RecommendationInfo(java.lang.CharSequence, java.lang.CharSequence, int, boolean); method public int describeContents(); + method public java.util.List<java.net.InetAddress> getDiscoveredPrinters(); method public java.lang.CharSequence getName(); method public int getNumDiscoveredPrinters(); method public java.lang.CharSequence getPackageName(); @@ -40082,7 +40118,7 @@ package android.service.autofill { method public final android.os.IBinder onBind(android.content.Intent); method public void onConnected(); method public void onDisconnected(); - method public void onFillRequest(android.app.assist.AssistStructure, android.os.Bundle, int, android.os.CancellationSignal, android.service.autofill.FillCallback); + method public abstract void onFillRequest(android.app.assist.AssistStructure, android.os.Bundle, int, android.os.CancellationSignal, android.service.autofill.FillCallback); method public abstract void onSaveRequest(android.app.assist.AssistStructure, android.os.Bundle, android.service.autofill.SaveCallback); field public static final java.lang.String SERVICE_INTERFACE = "android.service.autofill.AutofillService"; field public static final java.lang.String SERVICE_META_DATA = "android.autofill"; @@ -40118,7 +40154,7 @@ package android.service.autofill { ctor public FillResponse.Builder(); method public android.service.autofill.FillResponse.Builder addDataset(android.service.autofill.Dataset); method public android.service.autofill.FillResponse build(); - method public android.service.autofill.FillResponse.Builder setAuthentication(android.content.IntentSender, android.widget.RemoteViews); + method public android.service.autofill.FillResponse.Builder setAuthentication(android.view.autofill.AutofillId[], android.content.IntentSender, android.widget.RemoteViews); method public android.service.autofill.FillResponse.Builder setExtras(android.os.Bundle); method public android.service.autofill.FillResponse.Builder setSaveInfo(android.service.autofill.SaveInfo); } @@ -40134,8 +40170,10 @@ package android.service.autofill { field public static final android.os.Parcelable.Creator<android.service.autofill.SaveInfo> CREATOR; field public static final int SAVE_DATA_TYPE_ADDRESS = 2; // 0x2 field public static final int SAVE_DATA_TYPE_CREDIT_CARD = 3; // 0x3 + field public static final int SAVE_DATA_TYPE_EMAIL_ADDRESS = 5; // 0x5 field public static final int SAVE_DATA_TYPE_GENERIC = 0; // 0x0 field public static final int SAVE_DATA_TYPE_PASSWORD = 1; // 0x1 + field public static final int SAVE_DATA_TYPE_USERNAME = 4; // 0x4 } public static final class SaveInfo.Builder { @@ -40361,7 +40399,6 @@ package android.service.notification { method public int getUser(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.service.notification.Adjustment> CREATOR; - field public static final java.lang.String KEY_CHANNEL_ID = "key_channel_id"; field public static final java.lang.String KEY_PEOPLE = "key_people"; field public static final java.lang.String KEY_SNOOZE_CRITERIA = "key_snooze_criteria"; } @@ -40416,14 +40453,10 @@ package android.service.notification { ctor public NotificationAssistantService(); method public final void adjustNotification(android.service.notification.Adjustment); method public final void adjustNotifications(java.util.List<android.service.notification.Adjustment>); - method public void createNotificationChannel(java.lang.String, android.app.NotificationChannel); - method public void deleteNotificationChannel(java.lang.String, java.lang.String); - method public java.util.List<android.app.NotificationChannel> getNotificationChannels(java.lang.String); method public final android.os.IBinder onBind(android.content.Intent); method public abstract android.service.notification.Adjustment onNotificationEnqueued(android.service.notification.StatusBarNotification); method public abstract void onNotificationSnoozedUntilContext(android.service.notification.StatusBarNotification, java.lang.String); method public final void unsnoozeNotification(java.lang.String); - method public void updateNotificationChannel(java.lang.String, android.app.NotificationChannel); field public static final java.lang.String SERVICE_INTERFACE = "android.service.notification.NotificationAssistantService"; } @@ -43620,7 +43653,6 @@ package android.telephony { method public boolean isWorldPhone(); method public void listen(android.telephony.PhoneStateListener, int); method public boolean needsOtaServiceProvisioning(); - method public deprecated boolean sendDialerCode(java.lang.String); method public void sendDialerSpecialCode(java.lang.String); method public java.lang.String sendEnvelopeWithStatus(java.lang.String); method public void sendUssdRequest(java.lang.String, android.telephony.TelephonyManager.UssdResponseCallback, android.os.Handler); @@ -52936,6 +52968,7 @@ package android.webkit { method public abstract boolean onKeyMultiple(int, int, android.view.KeyEvent); method public abstract boolean onKeyUp(int, android.view.KeyEvent); method public abstract void onMeasure(int, int); + method public default void onMovedToDisplay(int, android.content.res.Configuration); method public abstract void onOverScrolled(int, int, boolean, boolean); method public default void onProvideAutofillVirtualStructure(android.view.ViewStructure, int); method public abstract void onProvideVirtualStructure(android.view.ViewStructure); diff --git a/api/test-current.txt b/api/test-current.txt index 6db91be96ca6..1024e285225d 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -1609,6 +1609,7 @@ package android { field public static final int alert_light_frame = 17301505; // 0x1080001 field public static final int arrow_down_float = 17301506; // 0x1080002 field public static final int arrow_up_float = 17301507; // 0x1080003 + field public static final int autofilled_highlight = 17301684; // 0x10800b4 field public static final int bottom_bar = 17301658; // 0x108009a field public static final int btn_default = 17301508; // 0x1080004 field public static final int btn_default_small = 17301509; // 0x1080005 @@ -4783,7 +4784,7 @@ package android.app { method public abstract android.app.FragmentManager.BackStackEntry getBackStackEntryAt(int); method public abstract int getBackStackEntryCount(); method public abstract android.app.Fragment getFragment(android.os.Bundle, java.lang.String); - method public abstract java.util.Collection<android.app.Fragment> getFragments(); + method public abstract java.util.List<android.app.Fragment> getFragments(); method public abstract android.app.Fragment getPrimaryNavigationFragment(); method public void invalidateOptionsMenu(); method public abstract boolean isDestroyed(); @@ -5128,6 +5129,7 @@ package android.app { method public java.lang.String getChannel(); method public java.lang.String getGroup(); method public android.graphics.drawable.Icon getLargeIcon(); + method public java.lang.CharSequence getSettingsText(); method public java.lang.String getShortcutId(); method public android.graphics.drawable.Icon getSmallIcon(); method public java.lang.String getSortKey(); @@ -5172,6 +5174,8 @@ package android.app { field public static final java.lang.String EXTRA_LARGE_ICON_BIG = "android.largeIcon.big"; field public static final java.lang.String EXTRA_MEDIA_SESSION = "android.mediaSession"; field public static final java.lang.String EXTRA_MESSAGES = "android.messages"; + field public static final java.lang.String EXTRA_NOTIFICATION_ID = "android.intent.extra.NOTIFICATION_ID"; + field public static final java.lang.String EXTRA_NOTIFICATION_TAG = "android.intent.extra.NOTIFICATION_TAG"; field public static final java.lang.String EXTRA_PEOPLE = "android.people"; field public static final java.lang.String EXTRA_PICTURE = "android.picture"; field public static final java.lang.String EXTRA_PROGRESS = "android.progress"; @@ -5357,6 +5361,7 @@ package android.app { method public android.app.Notification.Builder setProgress(int, int, boolean); method public android.app.Notification.Builder setPublicVersion(android.app.Notification); method public android.app.Notification.Builder setRemoteInputHistory(java.lang.CharSequence[]); + method public android.app.Notification.Builder setSettingsText(java.lang.CharSequence); method public android.app.Notification.Builder setShortcutId(java.lang.String); method public android.app.Notification.Builder setShowWhen(boolean); method public android.app.Notification.Builder setSmallIcon(int); @@ -10842,6 +10847,7 @@ package android.content.pm { field public static final int PROTECTION_FLAG_PRE23 = 128; // 0x80 field public static final int PROTECTION_FLAG_PREINSTALLED = 1024; // 0x400 field public static final int PROTECTION_FLAG_PRIVILEGED = 16; // 0x10 + field public static final int PROTECTION_FLAG_RUNTIME_ONLY = 8192; // 0x2000 field public static final int PROTECTION_FLAG_SETUP = 2048; // 0x800 field public static final deprecated int PROTECTION_FLAG_SYSTEM = 16; // 0x10 field public static final int PROTECTION_FLAG_VERIFIER = 512; // 0x200 @@ -12900,7 +12906,7 @@ package android.graphics { public static class ColorSpace.Connector { method public android.graphics.ColorSpace getDestination(); - method public android.graphics.ColorSpace.RenderIntent getIntent(); + method public android.graphics.ColorSpace.RenderIntent getRenderIntent(); method public android.graphics.ColorSpace getSource(); method public float[] transform(float, float, float); method public float[] transform(float[]); @@ -15999,7 +16005,7 @@ package android.hardware.usb { method public java.lang.String getSerial(); method public boolean releaseInterface(android.hardware.usb.UsbInterface); method public android.hardware.usb.UsbRequest requestWait(); - method public android.hardware.usb.UsbRequest requestWait(int); + method public android.hardware.usb.UsbRequest requestWait(long) throws java.util.concurrent.TimeoutException; method public boolean setConfiguration(android.hardware.usb.UsbConfiguration); method public boolean setInterface(android.hardware.usb.UsbInterface); } @@ -24659,6 +24665,7 @@ package android.media.tv { method public static boolean isChannelUriForPassthroughInput(android.net.Uri); method public static boolean isChannelUriForTunerInput(android.net.Uri); method public static boolean isProgramUri(android.net.Uri); + field public static final java.lang.String ACTION_INITIALIZE_PROGRAMS = "android.media.tv.action.INITIALIZE_PROGRAMS"; field public static final java.lang.String ACTION_MAKE_CHANNEL_BROWSABLE = "android.media.tv.action.MAKE_CHANNEL_BROWSABLE"; field public static final java.lang.String ACTION_PREVIEW_PROGRAM_ADDED_TO_WATCH_NEXT = "android.media.tv.action.PREVIEW_PROGRAM_ADDED_TO_WATCH_NEXT"; field public static final java.lang.String ACTION_PREVIEW_PROGRAM_BROWSABLE_DISABLED = "android.media.tv.action.PREVIEW_PROGRAM_BROWSABLE_DISABLED"; @@ -25670,7 +25677,7 @@ package android.net { method public android.net.IpSecManager.UdpEncapsulationSocket openUdpEncapsulationSocket() throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException; method public void removeTransportModeTransform(java.net.Socket, android.net.IpSecTransform); method public void removeTransportModeTransform(java.net.DatagramSocket, android.net.IpSecTransform); - method public android.net.IpSecManager.SecurityParameterIndex reserveSecurityParameterIndex(java.net.InetAddress, int) throws android.net.IpSecManager.ResourceUnavailableException, android.net.IpSecManager.SpiUnavailableException; + method public android.net.IpSecManager.SecurityParameterIndex reserveSecurityParameterIndex(int, java.net.InetAddress, int) throws android.net.IpSecManager.ResourceUnavailableException, android.net.IpSecManager.SpiUnavailableException; field public static final int INVALID_SECURITY_PARAMETER_INDEX = 0; // 0x0 } @@ -25704,7 +25711,6 @@ package android.net { method public android.net.IpSecTransform.Builder setAuthentication(int, android.net.IpSecAlgorithm); method public android.net.IpSecTransform.Builder setEncryption(int, android.net.IpSecAlgorithm); method public android.net.IpSecTransform.Builder setIpv4Encapsulation(android.net.IpSecManager.UdpEncapsulationSocket, int); - method public android.net.IpSecTransform.Builder setSpi(int, int); method public android.net.IpSecTransform.Builder setSpi(int, android.net.IpSecManager.SecurityParameterIndex); } @@ -37174,7 +37180,7 @@ package android.service.autofill { method public final android.os.IBinder onBind(android.content.Intent); method public void onConnected(); method public void onDisconnected(); - method public void onFillRequest(android.app.assist.AssistStructure, android.os.Bundle, int, android.os.CancellationSignal, android.service.autofill.FillCallback); + method public abstract void onFillRequest(android.app.assist.AssistStructure, android.os.Bundle, int, android.os.CancellationSignal, android.service.autofill.FillCallback); method public abstract void onSaveRequest(android.app.assist.AssistStructure, android.os.Bundle, android.service.autofill.SaveCallback); field public static final java.lang.String SERVICE_INTERFACE = "android.service.autofill.AutofillService"; field public static final java.lang.String SERVICE_META_DATA = "android.autofill"; @@ -37210,7 +37216,7 @@ package android.service.autofill { ctor public FillResponse.Builder(); method public android.service.autofill.FillResponse.Builder addDataset(android.service.autofill.Dataset); method public android.service.autofill.FillResponse build(); - method public android.service.autofill.FillResponse.Builder setAuthentication(android.content.IntentSender, android.widget.RemoteViews); + method public android.service.autofill.FillResponse.Builder setAuthentication(android.view.autofill.AutofillId[], android.content.IntentSender, android.widget.RemoteViews); method public android.service.autofill.FillResponse.Builder setExtras(android.os.Bundle); method public android.service.autofill.FillResponse.Builder setSaveInfo(android.service.autofill.SaveInfo); } @@ -37226,8 +37232,10 @@ package android.service.autofill { field public static final android.os.Parcelable.Creator<android.service.autofill.SaveInfo> CREATOR; field public static final int SAVE_DATA_TYPE_ADDRESS = 2; // 0x2 field public static final int SAVE_DATA_TYPE_CREDIT_CARD = 3; // 0x3 + field public static final int SAVE_DATA_TYPE_EMAIL_ADDRESS = 5; // 0x5 field public static final int SAVE_DATA_TYPE_GENERIC = 0; // 0x0 field public static final int SAVE_DATA_TYPE_PASSWORD = 1; // 0x1 + field public static final int SAVE_DATA_TYPE_USERNAME = 4; // 0x4 } public static final class SaveInfo.Builder { @@ -37453,7 +37461,6 @@ package android.service.notification { method public int getUser(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.service.notification.Adjustment> CREATOR; - field public static final java.lang.String KEY_CHANNEL_ID = "key_channel_id"; field public static final java.lang.String KEY_PEOPLE = "key_people"; field public static final java.lang.String KEY_SNOOZE_CRITERIA = "key_snooze_criteria"; } @@ -37508,14 +37515,10 @@ package android.service.notification { ctor public NotificationAssistantService(); method public final void adjustNotification(android.service.notification.Adjustment); method public final void adjustNotifications(java.util.List<android.service.notification.Adjustment>); - method public void createNotificationChannel(java.lang.String, android.app.NotificationChannel); - method public void deleteNotificationChannel(java.lang.String, java.lang.String); - method public java.util.List<android.app.NotificationChannel> getNotificationChannels(java.lang.String); method public final android.os.IBinder onBind(android.content.Intent); method public abstract android.service.notification.Adjustment onNotificationEnqueued(android.service.notification.StatusBarNotification); method public abstract void onNotificationSnoozedUntilContext(android.service.notification.StatusBarNotification, java.lang.String); method public final void unsnoozeNotification(java.lang.String); - method public void updateNotificationChannel(java.lang.String, android.app.NotificationChannel); field public static final java.lang.String SERVICE_INTERFACE = "android.service.notification.NotificationAssistantService"; } @@ -40349,7 +40352,6 @@ package android.telephony { method public boolean isVoicemailVibrationEnabled(android.telecom.PhoneAccountHandle); method public boolean isWorldPhone(); method public void listen(android.telephony.PhoneStateListener, int); - method public deprecated boolean sendDialerCode(java.lang.String); method public void sendDialerSpecialCode(java.lang.String); method public java.lang.String sendEnvelopeWithStatus(java.lang.String); method public void sendUssdRequest(java.lang.String, android.telephony.TelephonyManager.UssdResponseCallback, android.os.Handler); @@ -46038,6 +46040,7 @@ package android.view { method public void setAnimation(android.view.animation.Animation); method public void setAutofillHints(java.lang.String...); method public void setAutofillMode(int); + method public void setAutofilled(boolean); method public void setBackground(android.graphics.drawable.Drawable); method public void setBackgroundColor(int); method public deprecated void setBackgroundDrawable(android.graphics.drawable.Drawable); @@ -47309,6 +47312,7 @@ package android.view { field public static final deprecated int MEMORY_TYPE_HARDWARE = 1; // 0x1 field public static final deprecated int MEMORY_TYPE_NORMAL = 0; // 0x0 field public static final deprecated int MEMORY_TYPE_PUSH_BUFFERS = 3; // 0x3 + field public static final int PRIVATE_FLAG_NO_MOVE_ANIMATION = 64; // 0x40 field public static final int ROTATION_ANIMATION_CHANGED = 4096; // 0x1000 field public static final int ROTATION_ANIMATION_CROSSFADE = 1; // 0x1 field public static final int ROTATION_ANIMATION_JUMPCUT = 2; // 0x2 @@ -47369,6 +47373,7 @@ package android.view { field public java.lang.String packageName; field public int preferredDisplayModeId; field public deprecated float preferredRefreshRate; + field public int privateFlags; field public int rotationAnimation; field public float screenBrightness; field public int screenOrientation; diff --git a/cmds/bu/src/com/android/commands/bu/Backup.java b/cmds/bu/src/com/android/commands/bu/Backup.java index db17b28b8182..ce114fd02475 100644 --- a/cmds/bu/src/com/android/commands/bu/Backup.java +++ b/cmds/bu/src/com/android/commands/bu/Backup.java @@ -101,8 +101,10 @@ public final class Backup { doCompress = true; } else if ("-nocompress".equals(arg)) { doCompress = false; - } else if ("-includekeyvalue".equals(arg)) { + } else if ("-keyvalue".equals(arg)) { doKeyValue = true; + } else if ("-nokeyvalue".equals(arg)) { + doKeyValue = false; } else { Log.w(TAG, "Unknown backup flag " + arg); continue; diff --git a/core/java/android/accounts/ChooseTypeAndAccountActivity.java b/core/java/android/accounts/ChooseTypeAndAccountActivity.java index 35011b5f3ec3..844258515561 100644 --- a/core/java/android/accounts/ChooseTypeAndAccountActivity.java +++ b/core/java/android/accounts/ChooseTypeAndAccountActivity.java @@ -40,8 +40,8 @@ import com.android.internal.R; import java.io.IOException; import java.util.ArrayList; -import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; @@ -98,11 +98,10 @@ public class ChooseTypeAndAccountActivity extends Activity "alwaysPromptForAccount"; /** - * If set then this string willb e used as the description rather than + * If set then this string will be used as the description rather than * the default. */ - public static final String EXTRA_DESCRIPTION_TEXT_OVERRIDE = - "descriptionTextOverride"; + public static final String EXTRA_DESCRIPTION_TEXT_OVERRIDE = "descriptionTextOverride"; public static final int REQUEST_NULL = 0; public static final int REQUEST_CHOOSE_TYPE = 1; @@ -112,7 +111,8 @@ public class ChooseTypeAndAccountActivity extends Activity private static final String KEY_INSTANCE_STATE_EXISTING_ACCOUNTS = "existingAccounts"; private static final String KEY_INSTANCE_STATE_SELECTED_ACCOUNT_NAME = "selectedAccountName"; private static final String KEY_INSTANCE_STATE_SELECTED_ADD_ACCOUNT = "selectedAddAccount"; - private static final String KEY_INSTANCE_STATE_ACCOUNT_LIST = "accountAndVisibilityList"; + private static final String KEY_INSTANCE_STATE_ACCOUNTS_LIST = "accountsList"; + private static final String KEY_INSTANCE_STATE_VISIBILITY_LIST = "visibilityList"; private static final int SELECTED_ITEM_NONE = -1; @@ -122,7 +122,7 @@ public class ChooseTypeAndAccountActivity extends Activity private boolean mSelectedAddNewAccount = false; private String mDescriptionOverride; - private Map<Account, Integer> mAccounts; + private LinkedHashMap<Account, Integer> mAccounts; // TODO Redesign flow to show NOT_VISIBLE accounts // and display a warning if they are selected. // Currently NOT_VISBILE accounts are not shown at all. @@ -164,6 +164,10 @@ public class ChooseTypeAndAccountActivity extends Activity // save some items we use frequently final Intent intent = getIntent(); + mSetOfAllowableAccounts = getAllowableAccountSet(intent); + mSetOfRelevantAccountTypes = getReleventAccountTypes(intent); + mDescriptionOverride = intent.getStringExtra(EXTRA_DESCRIPTION_TEXT_OVERRIDE); + if (savedInstanceState != null) { mPendingRequest = savedInstanceState.getInt(KEY_INSTANCE_STATE_PENDING_REQUEST); mExistingAccounts = @@ -174,8 +178,15 @@ public class ChooseTypeAndAccountActivity extends Activity savedInstanceState.getString(KEY_INSTANCE_STATE_SELECTED_ACCOUNT_NAME); mSelectedAddNewAccount = savedInstanceState.getBoolean(KEY_INSTANCE_STATE_SELECTED_ADD_ACCOUNT, false); - mAccounts = (Map<Account, Integer>) savedInstanceState - .getSerializable(KEY_INSTANCE_STATE_ACCOUNT_LIST); + // restore mAccounts + Parcelable[] accounts = + savedInstanceState.getParcelableArray(KEY_INSTANCE_STATE_ACCOUNTS_LIST); + ArrayList<Integer> visibility = + savedInstanceState.getIntegerArrayList(KEY_INSTANCE_STATE_VISIBILITY_LIST); + mAccounts = new LinkedHashMap<>(); + for (int i = 0; i < accounts.length; i++) { + mAccounts.put((Account) accounts[i], visibility.get(i)); + } } else { mPendingRequest = REQUEST_NULL; mExistingAccounts = null; @@ -185,20 +196,21 @@ public class ChooseTypeAndAccountActivity extends Activity if (selectedAccount != null) { mSelectedAccountName = selectedAccount.name; } + mAccounts = getAcceptableAccountChoices(AccountManager.get(this)); } if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "selected account name is " + mSelectedAccountName); } + mPossiblyVisibleAccounts = new ArrayList<>(mAccounts.size()); + for (Map.Entry<Account, Integer> entry : mAccounts.entrySet()) { + if (AccountManager.VISIBILITY_NOT_VISIBLE != entry.getValue()) { + mPossiblyVisibleAccounts.add(entry.getKey()); + } + } - mSetOfAllowableAccounts = getAllowableAccountSet(intent); - mSetOfRelevantAccountTypes = getReleventAccountTypes(intent); - mDescriptionOverride = intent.getStringExtra(EXTRA_DESCRIPTION_TEXT_OVERRIDE); - - mAccounts = getAcceptableAccountChoices(AccountManager.get(this)); - if (mAccounts.isEmpty() - && mDisallowAddAccounts) { + if (mPossiblyVisibleAccounts.isEmpty() && mDisallowAddAccounts) { requestWindowFeature(Window.FEATURE_NO_TITLE); setContentView(R.layout.app_not_authorized); mDontShowPicker = true; @@ -216,7 +228,7 @@ public class ChooseTypeAndAccountActivity extends Activity if (mPendingRequest == REQUEST_NULL) { // If there are no relevant accounts and only one relevant account type go directly to // add account. Otherwise let the user choose. - if (mAccounts.isEmpty()) { + if (mPossiblyVisibleAccounts.isEmpty()) { setNonLabelThemeAndCallSuperCreate(savedInstanceState); if (mSetOfRelevantAccountTypes.size() == 1) { runAddAccountForAuthenticator(mSetOfRelevantAccountTypes.iterator().next()); @@ -226,12 +238,6 @@ public class ChooseTypeAndAccountActivity extends Activity } } - mPossiblyVisibleAccounts = new ArrayList<>(mAccounts.size()); - for (Map.Entry<Account, Integer> entry : mAccounts.entrySet()) { - if (AccountManager.VISIBILITY_NOT_VISIBLE != entry.getValue()) { - mPossiblyVisibleAccounts.add(entry.getKey()); - } - } String[] listItems = getListOfDisplayableOptions(mPossiblyVisibleAccounts); mSelectedItemIndex = getItemIndexToSelect(mPossiblyVisibleAccounts, mSelectedAccountName, mSelectedAddNewAccount); @@ -270,10 +276,16 @@ public class ChooseTypeAndAccountActivity extends Activity mPossiblyVisibleAccounts.get(mSelectedItemIndex).name); } } - // should be HashMap by default. - HashMap<Account, Integer> accountsHashMap = (mAccounts instanceof HashMap) - ? (HashMap) mAccounts : new HashMap<Account, Integer>(mAccounts); - outState.putSerializable(KEY_INSTANCE_STATE_ACCOUNT_LIST, accountsHashMap); + // save mAccounts + Parcelable[] accounts = new Parcelable[mAccounts.size()]; + ArrayList<Integer> visibility = new ArrayList<>(mAccounts.size()); + int i = 0; + for (Map.Entry<Account, Integer> e : mAccounts.entrySet()) { + accounts[i++] = e.getKey(); + visibility.add(e.getValue()); + } + outState.putParcelableArray(KEY_INSTANCE_STATE_ACCOUNTS_LIST, accounts); + outState.putIntegerArrayList(KEY_INSTANCE_STATE_VISIBILITY_LIST, visibility); } public void onCancelButtonClicked(View view) { @@ -308,7 +320,7 @@ public class ChooseTypeAndAccountActivity extends Activity if (resultCode == RESULT_CANCELED) { // if canceling out of addAccount and the original state caused us to skip this, // finish this activity - if (mAccounts.isEmpty()) { + if (mPossiblyVisibleAccounts.isEmpty()) { setResult(Activity.RESULT_CANCELED); finish(); } @@ -428,18 +440,20 @@ public class ChooseTypeAndAccountActivity extends Activity private void setResultAndFinish(final String accountName, final String accountType) { // Mark account as visible since user chose it. Account account = new Account(accountName, accountType); - Integer oldVisibility = mAccounts.get(account); - // oldVisibility is null if new account was added - if (oldVisibility == null) { - Map<Account, Integer> accountsAndVisibility = AccountManager.get(this) - .getAccountsAndVisibilityForPackage(mCallingPackage, null /* type */); - oldVisibility = accountsAndVisibility.get(account); - } + Integer oldVisibility = + AccountManager.get(this).getAccountVisibility(account, mCallingPackage); if (oldVisibility != null && oldVisibility == AccountManager.VISIBILITY_USER_MANAGED_NOT_VISIBLE) { AccountManager.get(this).setAccountVisibility(account, mCallingPackage, AccountManager.VISIBILITY_USER_MANAGED_VISIBLE); } + + if (oldVisibility != null && oldVisibility == AccountManager.VISIBILITY_NOT_VISIBLE) { + // Added account is not visible to caller. + setResult(Activity.RESULT_CANCELED); + finish(); + return; + } Bundle bundle = new Bundle(); bundle.putString(AccountManager.KEY_ACCOUNT_NAME, accountName); bundle.putString(AccountManager.KEY_ACCOUNT_TYPE, accountType); @@ -448,6 +462,7 @@ public class ChooseTypeAndAccountActivity extends Activity Log.v(TAG, "ChooseTypeAndAccountActivity.setResultAndFinish: selected account " + accountName + ", " + accountType); } + finish(); } @@ -509,22 +524,24 @@ public class ChooseTypeAndAccountActivity extends Activity * that don't match the allowable types, if provided, or that don't match the allowable * accounts, if provided. */ - private Map<Account, Integer> getAcceptableAccountChoices(AccountManager accountManager) { - Map<Account, Integer> accountsAndVisibility = - accountManager.getAccountsAndVisibilityForPackage(mCallingPackage, null /* type */); - - Map<Account, Integer> accountsToPopulate = - new HashMap<Account, Integer>(accountsAndVisibility.size()); - for (Map.Entry<Account, Integer> entry : accountsAndVisibility.entrySet()) { + private LinkedHashMap<Account, Integer> getAcceptableAccountChoices(AccountManager accountManager) { + Map<Account, Integer> accountsAndVisibilityForCaller = + accountManager.getAccountsAndVisibilityForPackage(mCallingPackage, null); + Account[] allAccounts = accountManager.getAccounts(); + LinkedHashMap<Account, Integer> accountsToPopulate = + new LinkedHashMap<>(accountsAndVisibilityForCaller.size()); + for (Account account : allAccounts) { if (mSetOfAllowableAccounts != null - && !mSetOfAllowableAccounts.contains(entry.getKey())) { + && !mSetOfAllowableAccounts.contains(account)) { continue; } if (mSetOfRelevantAccountTypes != null - && !mSetOfRelevantAccountTypes.contains(entry.getKey().type)) { + && !mSetOfRelevantAccountTypes.contains(account.type)) { continue; } - accountsToPopulate.put(entry.getKey(), entry.getValue()); + if (accountsAndVisibilityForCaller.get(account) != null) { + accountsToPopulate.put(account, accountsAndVisibilityForCaller.get(account)); + } } return accountsToPopulate; } diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index c0505eb1d2c8..af3bf2aee049 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -724,6 +724,7 @@ public class Activity extends ContextThemeWrapper public static final int FINISH_TASK_WITH_ACTIVITY = 2; static final String FRAGMENTS_TAG = "android:fragments"; + static final String AUTOFILL_RESET_NEEDED_TAG = "android:autofillResetNeeded"; private static final String WINDOW_HIERARCHY_TAG = "android:viewHierarchyState"; private static final String SAVED_DIALOG_IDS_KEY = "android:savedDialogIds"; @@ -1057,6 +1058,12 @@ public class Activity extends ContextThemeWrapper * @see #onSaveInstanceState */ protected void onRestoreInstanceState(Bundle savedInstanceState) { + mAutoFillResetNeeded = savedInstanceState.getBoolean(AUTOFILL_RESET_NEEDED_TAG, false); + + if (mAutoFillResetNeeded) { + getSystemService(AutofillManager.class).onRestoreInstanceState(savedInstanceState); + } + if (mWindow != null) { Bundle windowState = savedInstanceState.getBundle(WINDOW_HIERARCHY_TAG); if (windowState != null) { @@ -1502,6 +1509,10 @@ public class Activity extends ContextThemeWrapper if (p != null) { outState.putParcelable(FRAGMENTS_TAG, p); } + if (mAutoFillResetNeeded) { + outState.putBoolean(AUTOFILL_RESET_NEEDED_TAG, mAutoFillResetNeeded); + getSystemService(AutofillManager.class).onSaveInstanceState(outState); + } getApplication().dispatchActivitySaveInstanceState(this, outState); } @@ -4166,25 +4177,14 @@ public class Activity extends ContextThemeWrapper mTaskDescription.setPrimaryColor(colorPrimary); } } - - int colorBackground = a.getColor( - com.android.internal.R.styleable.ActivityTaskDescription_colorBackground, 0); - if (colorBackground != 0 && Color.alpha(colorBackground) == 0xFF) { - mTaskDescription.setBackgroundColor(colorBackground); - } - - final int statusBarColor = a.getColor( - com.android.internal.R.styleable.ActivityTaskDescription_statusBarColor, 0); - if (statusBarColor != 0) { - mTaskDescription.setStatusBarColor(statusBarColor); - } - - final int navigationBarColor = a.getColor( - com.android.internal.R.styleable.ActivityTaskDescription_navigationBarColor, 0); - if (navigationBarColor != 0) { - mTaskDescription.setNavigationBarColor(navigationBarColor); + // For dev-preview only. + if (mTaskDescription.getBackgroundColor() == 0) { + int colorBackground = a.getColor( + com.android.internal.R.styleable.ActivityTaskDescription_colorBackground, 0); + if (colorBackground != 0 && Color.alpha(colorBackground) == 0xFF) { + mTaskDescription.setBackgroundColor(colorBackground); + } } - a.recycle(); setTaskDescription(mTaskDescription); } diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index aede1bb67f80..4004bd6686b1 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -1145,8 +1145,6 @@ public class ActivityManager { private String mIconFilename; private int mColorPrimary; private int mColorBackground; - private int mStatusBarColor; - private int mNavigationBarColor; /** * Creates the TaskDescription to the specified values. @@ -1157,7 +1155,7 @@ public class ActivityManager { * opaque. */ public TaskDescription(String label, Bitmap icon, int colorPrimary) { - this(label, icon, null, colorPrimary, 0, 0, 0); + this(label, icon, null, colorPrimary, 0); if ((colorPrimary != 0) && (Color.alpha(colorPrimary) != 255)) { throw new RuntimeException("A TaskDescription's primary color should be opaque"); } @@ -1170,7 +1168,7 @@ public class ActivityManager { * @param icon An icon that represents the current state of this activity. */ public TaskDescription(String label, Bitmap icon) { - this(label, icon, null, 0, 0, 0, 0); + this(label, icon, null, 0, 0); } /** @@ -1179,26 +1177,24 @@ public class ActivityManager { * @param label A label and description of the current state of this activity. */ public TaskDescription(String label) { - this(label, null, null, 0, 0, 0, 0); + this(label, null, null, 0, 0); } /** * Creates an empty TaskDescription. */ public TaskDescription() { - this(null, null, null, 0, 0, 0, 0); + this(null, null, null, 0, 0); } /** @hide */ public TaskDescription(String label, Bitmap icon, String iconFilename, int colorPrimary, - int colorBackground, int statusBarColor, int navigationBarColor) { + int colorBackground) { mLabel = label; mIcon = icon; mIconFilename = iconFilename; mColorPrimary = colorPrimary; mColorBackground = colorBackground; - mStatusBarColor = statusBarColor; - mNavigationBarColor = navigationBarColor; } /** @@ -1218,8 +1214,6 @@ public class ActivityManager { mIconFilename = other.mIconFilename; mColorPrimary = other.mColorPrimary; mColorBackground = other.mColorBackground; - mStatusBarColor = other.mStatusBarColor; - mNavigationBarColor = other.mNavigationBarColor; } private TaskDescription(Parcel source) { @@ -1259,20 +1253,6 @@ public class ActivityManager { } /** - * @hide - */ - public void setStatusBarColor(int statusBarColor) { - mStatusBarColor = statusBarColor; - } - - /** - * @hide - */ - public void setNavigationBarColor(int navigationBarColor) { - mNavigationBarColor = navigationBarColor; - } - - /** * Sets the icon for this task description. * @hide */ @@ -1345,20 +1325,6 @@ public class ActivityManager { return mColorBackground; } - /** - * @hide - */ - public int getStatusBarColor() { - return mStatusBarColor; - } - - /** - * @hide - */ - public int getNavigationBarColor() { - return mNavigationBarColor; - } - /** @hide */ public void saveToXml(XmlSerializer out) throws IOException { if (mLabel != null) { @@ -1411,8 +1377,6 @@ public class ActivityManager { } dest.writeInt(mColorPrimary); dest.writeInt(mColorBackground); - dest.writeInt(mStatusBarColor); - dest.writeInt(mNavigationBarColor); if (mIconFilename == null) { dest.writeInt(0); } else { @@ -1426,8 +1390,6 @@ public class ActivityManager { mIcon = source.readInt() > 0 ? Bitmap.CREATOR.createFromParcel(source) : null; mColorPrimary = source.readInt(); mColorBackground = source.readInt(); - mStatusBarColor = source.readInt(); - mNavigationBarColor = source.readInt(); mIconFilename = source.readInt() > 0 ? source.readString() : null; } @@ -1445,9 +1407,7 @@ public class ActivityManager { public String toString() { return "TaskDescription Label: " + mLabel + " Icon: " + mIcon + " IconFilename: " + mIconFilename + " colorPrimary: " + mColorPrimary + - " colorBackground: " + mColorBackground + - " statusBarColor: " + mColorBackground + - " navigationBarColor: " + mNavigationBarColor; + " colorBackground: " + mColorBackground; } } diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index d1d462c2fbe8..7299d6bd0ed7 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -38,6 +38,7 @@ import android.content.pm.InstrumentationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.ParceledListSlice; import android.content.pm.ProviderInfo; import android.content.pm.ServiceInfo; import android.content.res.AssetManager; @@ -869,16 +870,20 @@ public final class ActivityThread { sendMessage(H.UNBIND_SERVICE, s); } - public final void scheduleServiceArgs(IBinder token, boolean taskRemoved, int startId, - int flags ,Intent args) { - ServiceArgsData s = new ServiceArgsData(); - s.token = token; - s.taskRemoved = taskRemoved; - s.startId = startId; - s.flags = flags; - s.args = args; + public final void scheduleServiceArgs(IBinder token, ParceledListSlice args) { + List<ServiceStartArgs> list = args.getList(); + + for (int i = 0; i < list.size(); i++) { + ServiceStartArgs ssa = list.get(i); + ServiceArgsData s = new ServiceArgsData(); + s.token = token; + s.taskRemoved = ssa.taskRemoved; + s.startId = ssa.startId; + s.flags = ssa.flags; + s.args = ssa.args; - sendMessage(H.SERVICE_ARGS, s); + sendMessage(H.SERVICE_ARGS, s); + } } public final void scheduleStopService(IBinder token) { diff --git a/core/java/android/app/Fragment.java b/core/java/android/app/Fragment.java index 3102a93790b2..a3c123f74cc4 100644 --- a/core/java/android/app/Fragment.java +++ b/core/java/android/app/Fragment.java @@ -2608,6 +2608,12 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene } } + void noteStateNotSaved() { + if (mChildFragmentManager != null) { + mChildFragmentManager.noteStateNotSaved(); + } + } + @Deprecated void performMultiWindowModeChanged(boolean isInMultiWindowMode) { onMultiWindowModeChanged(isInMultiWindowMode); diff --git a/core/java/android/app/FragmentManager.java b/core/java/android/app/FragmentManager.java index 399987fe1c41..75d62956671e 100644 --- a/core/java/android/app/FragmentManager.java +++ b/core/java/android/app/FragmentManager.java @@ -314,14 +314,17 @@ public abstract class FragmentManager { public abstract Fragment getFragment(Bundle bundle, String key); /** - * Get a collection of all fragments that are currently added to the FragmentManager. + * Get a list of all fragments that are currently added to the FragmentManager. * This may include those that are hidden as well as those that are shown. * This will not include any fragments only in the back stack, or fragments that * are detached or removed. + * <p> + * The order of the fragments in the list is the order in which they were + * added or attached. * - * @return A collection of all fragments that are added to the FragmentManager. + * @return A list of all fragments that are added to the FragmentManager. */ - public abstract Collection<Fragment> getFragments(); + public abstract List<Fragment> getFragments(); /** * Save the current instance state of the given Fragment. This can be @@ -695,6 +698,9 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate // This is dangerous, but we want to keep from breaking old applications. boolean mAllowOldReentrantBehavior; + // Saved FragmentManagerNonConfig during saveAllState() and cleared in noteStateNotSaved() + FragmentManagerNonConfig mSavedNonConfig; + Runnable mExecCommit = new Runnable() { @Override public void run() { @@ -907,12 +913,12 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate } @Override - public Collection<Fragment> getFragments() { + public List<Fragment> getFragments() { if (mAdded == null) { return Collections.EMPTY_LIST; } synchronized (mAdded) { - return (Collection<Fragment>) mAdded.clone(); + return (List<Fragment>) mAdded.clone(); } } @@ -2518,6 +2524,35 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate } FragmentManagerNonConfig retainNonConfig() { + setRetaining(mSavedNonConfig); + return mSavedNonConfig; + } + + /** + * Recurse the FragmentManagerNonConfig fragments and set the mRetaining to true. This + * was previously done while saving the non-config state, but that has been moved to + * {@link #saveNonConfig()} called from {@link #saveAllState()}. If mRetaining is set too + * early, the fragment won't be destroyed when the FragmentManager is destroyed. + */ + private static void setRetaining(FragmentManagerNonConfig nonConfig) { + if (nonConfig == null) { + return; + } + List<Fragment> fragments = nonConfig.getFragments(); + if (fragments != null) { + for (Fragment fragment : fragments) { + fragment.mRetaining = true; + } + } + List<FragmentManagerNonConfig> children = nonConfig.getChildNonConfigs(); + if (children != null) { + for (FragmentManagerNonConfig child : children) { + setRetaining(child); + } + } + } + + void saveNonConfig() { ArrayList<Fragment> fragments = null; ArrayList<FragmentManagerNonConfig> childFragments = null; if (mActive != null) { @@ -2529,13 +2564,13 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate fragments = new ArrayList<>(); } fragments.add(f); - f.mRetaining = true; f.mTargetIndex = f.mTarget != null ? f.mTarget.mIndex : -1; if (DEBUG) Log.v(TAG, "retainNonConfig: keeping retained " + f); } boolean addedChild = false; if (f.mChildFragmentManager != null) { - FragmentManagerNonConfig child = f.mChildFragmentManager.retainNonConfig(); + f.mChildFragmentManager.saveNonConfig(); + FragmentManagerNonConfig child = f.mChildFragmentManager.mSavedNonConfig; if (child != null) { if (childFragments == null) { childFragments = new ArrayList<>(); @@ -2554,9 +2589,10 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate } } if (fragments == null && childFragments == null) { - return null; + mSavedNonConfig = null; + } else { + mSavedNonConfig = new FragmentManagerNonConfig(fragments, childFragments); } - return new FragmentManagerNonConfig(fragments, childFragments); } void saveFragmentViewState(Fragment f) { @@ -2617,6 +2653,7 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate execPendingActions(); mStateSaved = true; + mSavedNonConfig = null; if (mActive == null || mActive.size() <= 0) { return null; @@ -2717,6 +2754,7 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate if (mPrimaryNav != null) { fms.mPrimaryNavActiveIndex = mPrimaryNav.mIndex; } + saveNonConfig(); return fms; } @@ -2892,9 +2930,17 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate } public void noteStateNotSaved() { + mSavedNonConfig = null; mStateSaved = false; + final int addedCount = mAdded == null ? 0 : mAdded.size(); + for (int i = 0; i < addedCount; i++) { + Fragment fragment = mAdded.get(i); + if (fragment != null) { + fragment.noteStateNotSaved(); + } + } } - + public void dispatchCreate() { mStateSaved = false; dispatchMoveToState(Fragment.CREATED); diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl index f4d26fd7d9d8..079bbcdd4951 100644 --- a/core/java/android/app/IActivityManager.aidl +++ b/core/java/android/app/IActivityManager.aidl @@ -635,6 +635,11 @@ interface IActivityManager { */ int getLastResumedActivityUserId(); + /** + * Add a bare uid to the background restrictions whitelist. Only the system uid may call this. + */ + void backgroundWhitelistUid(int uid); + // WARNING: when these transactions are updated, check if they are any callers on the native // side. If so, make sure they are using the correct transaction ids and arguments. // If a transaction which will also be used on the native side is being inserted, add it diff --git a/core/java/android/app/IApplicationThread.aidl b/core/java/android/app/IApplicationThread.aidl index 6c43fe3beca1..1b3c00b61539 100644 --- a/core/java/android/app/IApplicationThread.aidl +++ b/core/java/android/app/IApplicationThread.aidl @@ -25,6 +25,7 @@ import android.content.IIntentReceiver; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; +import android.content.pm.ParceledListSlice; import android.content.pm.ProviderInfo; import android.content.pm.ServiceInfo; import android.content.res.CompatibilityInfo; @@ -86,8 +87,7 @@ oneway interface IApplicationThread { in Bundle coreSettings, in String buildSerial); void scheduleExit(); void scheduleConfigurationChanged(in Configuration config); - void scheduleServiceArgs(IBinder token, boolean taskRemoved, int startId, - int flags, in Intent args); + void scheduleServiceArgs(IBinder token, in ParceledListSlice args); void updateTimeZone(); void processInBackground(); void scheduleBindService(IBinder token, diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl index 43cad5b2f729..61dacefd3f51 100644 --- a/core/java/android/app/INotificationManager.aidl +++ b/core/java/android/app/INotificationManager.aidl @@ -104,10 +104,6 @@ interface INotificationManager void applyEnqueuedAdjustmentFromAssistant(in INotificationListener token, in Adjustment adjustment); void applyAdjustmentFromAssistant(in INotificationListener token, in Adjustment adjustment); void applyAdjustmentsFromAssistant(in INotificationListener token, in List<Adjustment> adjustments); - void createNotificationChannelFromAssistant(in INotificationListener token, String pkg, in NotificationChannel channel); - void updateNotificationChannelFromAssistant(in INotificationListener token, String pkg, in NotificationChannel channel); - void deleteNotificationChannelFromAssistant(in INotificationListener token, String pkg, String channelId); - ParceledListSlice getNotificationChannelsFromAssistant(in INotificationListener token, String pkg); void unsnoozeNotificationFromAssistant(in INotificationListener token, String key); ComponentName getEffectsSuppressor(); diff --git a/core/java/android/app/InstantAppResolverService.java b/core/java/android/app/InstantAppResolverService.java index 2bdfa99fee47..88399e5d6695 100644 --- a/core/java/android/app/InstantAppResolverService.java +++ b/core/java/android/app/InstantAppResolverService.java @@ -21,6 +21,7 @@ import android.app.Service; import android.content.Context; import android.content.Intent; import android.content.pm.InstantAppResolveInfo; +import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; @@ -28,9 +29,12 @@ import android.os.IRemoteCallback; import android.os.Looper; import android.os.Message; import android.os.RemoteException; +import android.util.Log; +import android.util.Slog; import com.android.internal.os.SomeArgs; +import java.util.Arrays; import java.util.List; /** @@ -39,6 +43,9 @@ import java.util.List; */ @SystemApi public abstract class InstantAppResolverService extends Service { + private static final boolean DEBUG_EPHEMERAL = Build.IS_DEBUGGABLE; + private static final String TAG = "PackageManager"; + /** @hide */ public static final String EXTRA_RESOLVE_INFO = "android.app.extra.RESOLVE_INFO"; /** @hide */ @@ -132,11 +139,19 @@ public abstract class InstantAppResolverService extends Service { @Deprecated void _onGetInstantAppResolveInfo(int[] digestPrefix, String token, InstantAppResolutionCallback callback) { + if (DEBUG_EPHEMERAL) { + Slog.d(TAG, "Instant resolver; getInstantAppResolveInfo;" + + " prefix: " + Arrays.toString(digestPrefix)); + } onGetInstantAppResolveInfo(digestPrefix, token, callback); } @Deprecated void _onGetInstantAppIntentFilter(int digestPrefix[], String token, String hostName, InstantAppResolutionCallback callback) { + if (DEBUG_EPHEMERAL) { + Slog.d(TAG, "Instant resolver; getInstantAppIntentFilter;" + + " prefix: " + Arrays.toString(digestPrefix)); + } onGetInstantAppIntentFilter(digestPrefix, token, callback); } diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 9887d34cbb8f..7f26f4fd7050 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -103,8 +103,7 @@ public class Notification implements Parcelable /** * An activity that provides a user interface for adjusting notification preferences for its - * containing application. Optional but recommended for apps that post - * {@link android.app.Notification Notifications}. + * containing application. */ @SdkConstant(SdkConstantType.INTENT_CATEGORY) public static final String INTENT_CATEGORY_NOTIFICATION_PREFERENCES @@ -113,11 +112,25 @@ public class Notification implements Parcelable /** * Optional extra for {@link #INTENT_CATEGORY_NOTIFICATION_PREFERENCES}. If provided, will * contain a {@link NotificationChannel#getId() channel id} that can be used to narrow down - * what in app notifications settings should be shown. + * what settings should be shown in the target app. */ public static final String EXTRA_CHANNEL_ID = "android.intent.extra.CHANNEL_ID"; /** + * Optional extra for {@link #INTENT_CATEGORY_NOTIFICATION_PREFERENCES}. If provided, will + * contain the tag provided to {@link NotificationManager#notify(String, int, Notification)} + * that can be used to narrow down what settings should be shown in the target app. + */ + public static final String EXTRA_NOTIFICATION_TAG = "android.intent.extra.NOTIFICATION_TAG"; + + /** + * Optional extra for {@link #INTENT_CATEGORY_NOTIFICATION_PREFERENCES}. If provided, will + * contain the id provided to {@link NotificationManager#notify(String, int, Notification)} + * that can be used to narrow down what settings should be shown in the target app. + */ + public static final String EXTRA_NOTIFICATION_ID = "android.intent.extra.NOTIFICATION_ID"; + + /** * Use all default values (where applicable). */ public static final int DEFAULT_ALL = ~0; @@ -1082,6 +1095,7 @@ public class Notification implements Parcelable private long mTimeout; private String mShortcutId; + private CharSequence mSettingsText; /** * If this notification is being shown as a badge, always show as a number. @@ -1851,6 +1865,10 @@ public class Notification implements Parcelable } mBadgeIcon = parcel.readInt(); + + if (parcel.readInt() != 0) { + mSettingsText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel); + } } @Override @@ -1960,6 +1978,9 @@ public class Notification implements Parcelable that.mChannelId = this.mChannelId; that.mTimeout = this.mTimeout; + that.mShortcutId = this.mShortcutId; + that.mBadgeIcon = this.mBadgeIcon; + that.mSettingsText = this.mSettingsText; if (!heavy) { that.lightenPayload(); // will clean out extras @@ -2229,6 +2250,13 @@ public class Notification implements Parcelable } parcel.writeInt(mBadgeIcon); + + if (mSettingsText != null) { + parcel.writeInt(1); + TextUtils.writeToParcel(mSettingsText, parcel, flags); + } else { + parcel.writeInt(0); + } } /** @@ -2458,6 +2486,14 @@ public class Notification implements Parcelable return mShortcutId; } + + /** + * Returns the settings text provided to {@link Builder#setSettingsText(CharSequence)}. + */ + public CharSequence getSettingsText() { + return mSettingsText; + } + /** * The small icon representing this notification in the status bar and content view. * @@ -2887,6 +2923,24 @@ public class Notification implements Parcelable } /** + * Provides text that will appear as a link to your application's settings. + * + * <p>This text does not appear within notification {@link Style templates} but may + * appear when the user uses an affordance to learn more about the notification. + * Additionally, this text will not appear unless you provide a valid link target by + * handling {@link #INTENT_CATEGORY_NOTIFICATION_PREFERENCES}. + * + * <p>This text is meant to be concise description about what the user can customize + * when they click on this link. The recommended maximum length is 40 characters. + * @param text + * @return + */ + public Builder setSettingsText(CharSequence text) { + mN.mSettingsText = safeCharSequence(text); + return this; + } + + /** * Set the remote input history. * * This should be set to the most recent inputs that have been sent diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java index 489a0f0975ae..3191eec03ffa 100644 --- a/core/java/android/app/ResourcesManager.java +++ b/core/java/android/app/ResourcesManager.java @@ -44,6 +44,8 @@ import com.android.internal.util.ArrayUtils; import java.lang.ref.WeakReference; import java.util.ArrayList; +import java.util.Iterator; +import java.util.Map; import java.util.Objects; import java.util.WeakHashMap; import java.util.function.Predicate; @@ -114,7 +116,7 @@ public class ResourcesManager { * A cache of DisplayId, Resources to Display. These display adjustments associated with these * {@link Display}s will change as the resources change. */ - private final ArrayMap<Pair<Integer, Resources>, WeakReference<Display>> mResourceDisplays = + private final ArrayMap<Pair<Integer, ResourcesKey>, WeakReference<Display>> mResourceDisplays = new ArrayMap<>(); public static ResourcesManager getInstance() { @@ -137,10 +139,7 @@ public class ResourcesManager { for (int i = 0; i < mResourceImpls.size();) { final ResourcesKey key = mResourceImpls.keyAt(i); if (key.isPathReferenced(path)) { - final ResourcesImpl res = mResourceImpls.removeAt(i).get(); - if (res != null) { - res.flushLayoutCache(); - } + cleanupResourceImpl(key); count++; } else { i++; @@ -251,8 +250,14 @@ public class ResourcesManager { * @param resources The {@link Resources} backing the display adjustments. */ public Display getAdjustedDisplay(final int displayId, Resources resources) { - final Pair<Integer, Resources> key = Pair.create(displayId, resources); synchronized (this) { + // Note that the ResourcesKey might be {@code null} in the case that the + // {@link Resources} is actually from {@link Resources#getSystem}. In this case, it is + // not managed by {@link ResourcesManager}, but we still want to cache the display + // object. + final Pair<Integer, ResourcesKey> key = Pair.create(displayId, + findKeyForResourceImplLocked(resources.getImpl())); + WeakReference<Display> wd = mResourceDisplays.get(key); if (wd != null) { final Display display = wd.get(); @@ -273,6 +278,32 @@ public class ResourcesManager { } } + private void cleanupResourceImpl(ResourcesKey removedKey) { + // Remove any resource to display mapping based on this key. + final Iterator<Map.Entry<Pair<Integer, ResourcesKey>, WeakReference<Display>>> iter = + mResourceDisplays.entrySet().iterator(); + while (iter.hasNext()) { + final Map.Entry<Pair<Integer, ResourcesKey>, WeakReference<Display>> entry = + iter.next(); + final ResourcesKey key = entry.getKey().second; + + // Do not touch system resource displays (indicated by a {@code null} key) or + // non-matching keys. + if (key == null || !key.equals(removedKey)) { + continue; + } + + iter.remove(); + } + + // Remove resource key to resource impl mapping and flush cache + final ResourcesImpl res = mResourceImpls.remove(removedKey).get(); + + if (res != null) { + res.flushLayoutCache(); + } + } + /** * Creates an AssetManager from the paths within the ResourcesKey. * @@ -945,7 +976,7 @@ public class ResourcesManager { final ResourcesKey key = mResourceImpls.keyAt(i); final WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.valueAt(i); final ResourcesImpl impl = weakImplRef != null ? weakImplRef.get() : null; - if (impl != null && key.mResDir.equals(assetPath)) { + if (impl != null && Objects.equals(key.mResDir, assetPath)) { if (!ArrayUtils.contains(key.mLibDirs, libAsset)) { final int newLibAssetCount = 1 + (key.mLibDirs != null ? key.mLibDirs.length : 0); diff --git a/core/java/android/view/autofill/AutoFillId.aidl b/core/java/android/app/ServiceStartArgs.aidl index fc57ce7acfc1..fd2cf0f93dc6 100644 --- a/core/java/android/view/autofill/AutoFillId.aidl +++ b/core/java/android/app/ServiceStartArgs.aidl @@ -1,11 +1,11 @@ -/** - * Copyright (c) 2017, The Android Open Source Project +/* + * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.view.autofill; +package android.app; -// @deprecated TODO(b/35956626): remove once clients use AutofillId -parcelable AutoFillId;
\ No newline at end of file +/** @hide */ +parcelable ServiceStartArgs; diff --git a/core/java/android/app/ServiceStartArgs.java b/core/java/android/app/ServiceStartArgs.java new file mode 100644 index 000000000000..f030cbaaecaf --- /dev/null +++ b/core/java/android/app/ServiceStartArgs.java @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app; + +import android.content.Intent; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Describes a Service.onStartCommand() request from the system. + * @hide + */ +public class ServiceStartArgs implements Parcelable { + final public boolean taskRemoved; + final public int startId; + final public int flags; + final public Intent args; + + public ServiceStartArgs(boolean _taskRemoved, int _startId, int _flags, Intent _args) { + taskRemoved = _taskRemoved; + startId = _startId; + flags = _flags; + args = _args; + } + + public String toString() { + return "ServiceStartArgs{taskRemoved=" + taskRemoved + ", startId=" + startId + + ", flags=0x" + Integer.toHexString(flags) + ", args=" + args + "}"; + } + + public int describeContents() { + return 0; + } + + public void writeToParcel(Parcel out, int flags) { + out.writeInt(taskRemoved ? 1 : 0); + out.writeInt(startId); + out.writeInt(flags); + if (args != null) { + out.writeInt(1); + args.writeToParcel(out, 0); + } else { + out.writeInt(0); + } + } + + public static final Parcelable.Creator<ServiceStartArgs> CREATOR + = new Parcelable.Creator<ServiceStartArgs>() { + public ServiceStartArgs createFromParcel(Parcel in) { + return new ServiceStartArgs(in); + } + + public ServiceStartArgs[] newArray(int size) { + return new ServiceStartArgs[size]; + } + }; + + public ServiceStartArgs(Parcel in) { + taskRemoved = in.readInt() != 0; + startId = in.readInt(); + flags = in.readInt(); + if (in.readInt() != 0) { + args = Intent.CREATOR.createFromParcel(in); + } else { + args = null; + } + } +} diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index 6b05bdff5d1e..4572578ea608 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -74,7 +74,9 @@ import android.net.ConnectivityThread; import android.net.EthernetManager; import android.net.IConnectivityManager; import android.net.IEthernetManager; +import android.net.IIpSecService; import android.net.INetworkPolicyManager; +import android.net.IpSecManager; import android.net.NetworkPolicyManager; import android.net.NetworkScoreManager; import android.net.nsd.INsdManager; @@ -261,6 +263,15 @@ final class SystemServiceRegistry { return new ConnectivityManager(context, service); }}); + registerService(Context.IPSEC_SERVICE, IpSecManager.class, + new StaticServiceFetcher<IpSecManager>() { + @Override + public IpSecManager createService() { + IBinder b = ServiceManager.getService(Context.IPSEC_SERVICE); + IIpSecService service = IIpSecService.Stub.asInterface(b); + return new IpSecManager(service); + }}); + registerService(Context.COUNTRY_DETECTOR, CountryDetector.class, new StaticServiceFetcher<CountryDetector>() { @Override diff --git a/core/java/android/app/WaitResult.java b/core/java/android/app/WaitResult.java index af196cfe4db4..898d0cabee3e 100644 --- a/core/java/android/app/WaitResult.java +++ b/core/java/android/app/WaitResult.java @@ -20,6 +20,8 @@ import android.content.ComponentName; import android.os.Parcel; import android.os.Parcelable; +import java.io.PrintWriter; + /** * Information returned after waiting for an activity start. * @@ -69,4 +71,13 @@ public class WaitResult implements Parcelable { thisTime = source.readLong(); totalTime = source.readLong(); } + + public void dump(PrintWriter pw, String prefix) { + pw.println(prefix + "WaitResult:"); + pw.println(prefix + " result=" + result); + pw.println(prefix + " timeout=" + timeout); + pw.println(prefix + " who=" + who); + pw.println(prefix + " thisTime=" + thisTime); + pw.println(prefix + " totalTime=" + totalTime); + } }
\ No newline at end of file diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index bb5d830820aa..82ad8252c5a9 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -2768,9 +2768,11 @@ public class DevicePolicyManager { * or clears the lockscreen password. * <p> * <em>This token is highly sensitive and should be treated at the same level as user - * credentials. In particular, NEVER store this token on device in plaintext, especially in - * Device-Encrypted storage if the token will be used to reset password on FBE devices before - * user unlocks. + * credentials. In particular, NEVER store this token on device in plaintext. Do not store + * the plaintext token in device-encrypted storage if it will be needed to reset password on + * file-based encryption devices before user unlocks. Consider carefully how any password token + * will be stored on your server and who will need access to them. Tokens may be the subject of + * legal access requests. * </em> * * @param admin Which {@link DeviceAdminReceiver} this request is associated with. diff --git a/core/java/android/app/assist/AssistStructure.java b/core/java/android/app/assist/AssistStructure.java index 7d2db5c9f384..545aef5224b7 100644 --- a/core/java/android/app/assist/AssistStructure.java +++ b/core/java/android/app/assist/AssistStructure.java @@ -27,9 +27,6 @@ import android.view.ViewStructure.HtmlInfo; import android.view.ViewStructure.HtmlInfo.Builder; import android.view.WindowManager; import android.view.WindowManagerGlobal; -import android.view.autofill.AutoFillId; -import android.view.autofill.AutoFillType; -import android.view.autofill.AutoFillValue; import android.view.autofill.AutofillId; import android.view.autofill.AutofillValue; @@ -602,6 +599,10 @@ public class AssistStructure implements Parcelable { boolean mSanitized; HtmlInfo mHtmlInfo; + // POJO used to override some autofill-related values when the node is parcelized. + // Not written to parcel. + AutofillOverlay mAutofillOverlay; + int mX; int mY; int mScrollX; @@ -759,6 +760,7 @@ public class AssistStructure implements Parcelable { boolean writeSensitive = true; int flags = mFlags & ~FLAGS_ALL_CONTROL; + if (mId != View.NO_ID) { flags |= FLAGS_HAS_ID; } @@ -813,6 +815,13 @@ public class AssistStructure implements Parcelable { // Remove 'checked' from sanitized autofill request. writtenFlags = flags & ~FLAGS_CHECKED; } + if (mAutofillOverlay != null) { + if (mAutofillOverlay.focused) { + writtenFlags |= ViewNode.FLAGS_FOCUSED; + } else { + writtenFlags &= ~ViewNode.FLAGS_FOCUSED; + } + } out.writeInt(writtenFlags); if ((flags&FLAGS_HAS_ID) != 0) { @@ -832,7 +841,14 @@ public class AssistStructure implements Parcelable { out.writeParcelable(mAutofillId, 0); out.writeInt(mAutofillType); out.writeStringArray(mAutofillHints); - final AutofillValue sanitizedValue = writeSensitive ? mAutofillValue : null; + final AutofillValue sanitizedValue; + if (mAutofillOverlay != null && mAutofillOverlay.value != null) { + sanitizedValue = mAutofillOverlay.value; + } else if (writeSensitive) { + sanitizedValue = mAutofillValue; + } else { + sanitizedValue = null; + } out.writeParcelable(sanitizedValue, 0); out.writeStringArray(mAutofillOptions); if (mHtmlInfo instanceof Parcelable) { @@ -920,15 +936,6 @@ public class AssistStructure implements Parcelable { } /** - * @hide - * @deprecated TODO(b/35956626): remove once clients use getAutoFilltype - */ - @Deprecated - public AutoFillId getAutoFillId() { - return AutoFillId.forDaRealId(mAutofillId); - } - - /** * Gets the id that can be used to autofill the view contents. * * <p>It's only set when the {@link AssistStructure} is used for autofilling purposes, not @@ -939,26 +946,6 @@ public class AssistStructure implements Parcelable { } /** - * @hide - * @deprecated TODO(b/35956626): remove once clients use getAutoFilltype() - */ - @Deprecated - public AutoFillType getAutoFillType() { - switch (getAutofillType()) { - case View.AUTOFILL_TYPE_TEXT: - return AutoFillType.forText(); - case View.AUTOFILL_TYPE_TOGGLE: - return AutoFillType.forToggle(); - case View.AUTOFILL_TYPE_LIST: - return AutoFillType.forList(); - case View.AUTOFILL_TYPE_DATE: - return AutoFillType.forDate(); - default: - return null; - } - } - - /** * Gets the the type of value that can be used to autofill the view contents. * * <p>It's only set when the {@link AssistStructure} is used for autofilling purposes, not @@ -982,15 +969,6 @@ public class AssistStructure implements Parcelable { } /** - * @hide - * @deprecated TODO(b/35956626): remove once clients use getAutoFilltype - */ - @Deprecated - public AutoFillValue getAutoFillValue() { - return AutoFillValue.forDaRealValue(mAutofillValue); - } - - /** * Gets the the value of this view. * * <p>It's only set when the {@link AssistStructure} is used for autofilling purposes, not @@ -1000,6 +978,11 @@ public class AssistStructure implements Parcelable { return mAutofillValue; } + /** @hide **/ + public void setAutofillOverlay(AutofillOverlay overlay) { + mAutofillOverlay = overlay; + } + /** * Gets the options that can be used to autofill this structure. * @@ -1381,6 +1364,16 @@ public class AssistStructure implements Parcelable { } } + /** + * POJO used to override some autofill-related values when the node is parcelized. + * + * @hide + */ + static public class AutofillOverlay { + public boolean focused; + public AutofillValue value; + } + static class ViewNodeBuilder extends ViewStructure { final AssistStructure mAssist; final ViewNode mNode; diff --git a/core/java/android/content/pm/BaseParceledListSlice.java b/core/java/android/content/pm/BaseParceledListSlice.java index c4e4e06be749..aaa5f19c3fca 100644 --- a/core/java/android/content/pm/BaseParceledListSlice.java +++ b/core/java/android/content/pm/BaseParceledListSlice.java @@ -50,6 +50,8 @@ abstract class BaseParceledListSlice<T> implements Parcelable { private final List<T> mList; + private int mInlineCountLimit = Integer.MAX_VALUE; + public BaseParceledListSlice(List<T> list) { mList = list; } @@ -135,6 +137,14 @@ abstract class BaseParceledListSlice<T> implements Parcelable { } /** + * Set a limit on the maximum number of entries in the array that will be included + * inline in the initial parcelling of this object. + */ + public void setInlineCountLimit(int maxCount) { + mInlineCountLimit = maxCount; + } + + /** * Write this to another Parcel. Note that this discards the internal Parcel * and should not be used anymore. This is so we can pass this to a Binder * where we won't have a chance to call recycle on this. @@ -149,7 +159,7 @@ abstract class BaseParceledListSlice<T> implements Parcelable { final Class<?> listElementClass = mList.get(0).getClass(); writeParcelableCreator(mList.get(0), dest); int i = 0; - while (i < N && dest.dataSize() < MAX_IPC_SIZE) { + while (i < N && i < mInlineCountLimit && dest.dataSize() < MAX_IPC_SIZE) { dest.writeInt(1); final T parcelable = mList.get(i); diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index 9457d158b139..940447ca5f08 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -3114,6 +3114,7 @@ public class PackageParser { if ((perm.info.protectionLevel&PermissionInfo.PROTECTION_MASK_FLAGS) != 0) { if ( (perm.info.protectionLevel&PermissionInfo.PROTECTION_FLAG_EPHEMERAL) == 0 + && (perm.info.protectionLevel&PermissionInfo.PROTECTION_FLAG_RUNTIME_ONLY) == 0 && (perm.info.protectionLevel&PermissionInfo.PROTECTION_MASK_BASE) != PermissionInfo.PROTECTION_SIGNATURE) { outError[0] = "<permission> protectionLevel specifies a non-ephemeral flag but is " @@ -6172,6 +6173,7 @@ public class PackageParser { cpuAbiOverride = dest.readString(); use32bitAbi = (dest.readInt() == 1); restrictUpdateHash = dest.createByteArray(); + visibleToInstantApps = dest.readInt() == 1; } private static void internStringArrayList(List<String> list) { @@ -6286,6 +6288,7 @@ public class PackageParser { dest.writeString(cpuAbiOverride); dest.writeInt(use32bitAbi ? 1 : 0); dest.writeByteArray(restrictUpdateHash); + dest.writeInt(visibleToInstantApps ? 1 : 0); } diff --git a/core/java/android/content/pm/PermissionInfo.java b/core/java/android/content/pm/PermissionInfo.java index 0703138287ba..694e60781546 100644 --- a/core/java/android/content/pm/PermissionInfo.java +++ b/core/java/android/content/pm/PermissionInfo.java @@ -131,6 +131,13 @@ public class PermissionInfo extends PackageItemInfo implements Parcelable { public static final int PROTECTION_FLAG_EPHEMERAL = 0x1000; /** + * Additional flag for {@link #protectionLevel}, corresponding + * to the <code>runtime</code> value of + * {@link android.R.attr#protectionLevel}. + */ + public static final int PROTECTION_FLAG_RUNTIME_ONLY = 0x2000; + + /** * Mask for {@link #protectionLevel}: the basic protection type. */ public static final int PROTECTION_MASK_BASE = 0xf; @@ -250,6 +257,9 @@ public class PermissionInfo extends PackageItemInfo implements Parcelable { if ((level&PermissionInfo.PROTECTION_FLAG_EPHEMERAL) != 0) { protLevel += "|ephemeral"; } + if ((level&PermissionInfo.PROTECTION_FLAG_RUNTIME_ONLY) != 0) { + protLevel += "|runtime"; + } return protLevel; } diff --git a/core/java/android/hardware/SensorAdditionalInfo.java b/core/java/android/hardware/SensorAdditionalInfo.java index 572a287f53d8..ea1d01bb7738 100644 --- a/core/java/android/hardware/SensorAdditionalInfo.java +++ b/core/java/android/hardware/SensorAdditionalInfo.java @@ -131,6 +131,64 @@ public class SensorAdditionalInfo { */ public static final int TYPE_SAMPLING = 0x10004; + /** + * Local geo-magnetic Field. + * + * Additional into to sensor hardware. Local geomagnetic field information based on + * device geo location. This type is primarily for for magnetic field calibration and rotation + * vector sensor fusion. + * + * float[3]: strength (uT), declination and inclination angle (rad). + * @hide + */ + public static final int TYPE_LOCAL_GEOMAGNETIC_FIELD = 0x30000; + + /** + * Local gravity acceleration strength. + * + * Additional info to sensor hardware for accelerometer calibration. + * + * float: gravitational acceleration norm in m/s^2. + * @hide + */ + public static final int TYPE_LOCAL_GRAVITY = 0x30001; + + /** + * Device dock state. + * + * Additional info to sensor hardware indicating dock states of device. + * + * int32_t: dock state following definition of {@link android.content.Intent#EXTRA_DOCK_STATE}. + * Undefined values are ignored. + * @hide + */ + public static final int TYPE_DOCK_STATE = 0x30002; + + /** + * High performance mode. + * + * Additional info to sensor hardware. Device is able to use up more power and take more + * resources to improve throughput and latency in high performance mode. One possible use case + * is virtual reality, when sensor latency need to be carefully controlled. + * + * int32_t: 1 or 0, denoting device is in or out of high performance mode, respectively. + * Other values are ignored. + * @hide + */ + public static final int TYPE_HIGH_PERFORMANCE_MODE = 0x30003; + + /** + * Magnetic field calibration hint. + * + * Additional info to sensor hardware. Device is notified when manually triggered magnetic field + * calibration procedure is started or stopped. The calibration procedure is assumed timed out + * after 1 minute from start, even if an explicit stop is not received. + * + * int32_t: 1 for calibration start, 0 for stop, other values are ignored. + * @hide + */ + public static final int TYPE_MAGNETIC_FIELD_CALIBRATION = 0x30004; + SensorAdditionalInfo( Sensor aSensor, int aType, int aSerial, int [] aIntValues, float [] aFloatValues) { sensor = aSensor; @@ -139,4 +197,18 @@ public class SensorAdditionalInfo { intValues = aIntValues; floatValues = aFloatValues; } + + /** @hide */ + public static SensorAdditionalInfo createLocalGeomagneticField( + float strength, float declination, float inclination) { + if (strength < 10 || strength > 100 // much beyond extreme values on earth + || declination < 0 || declination > Math.PI + || inclination < -Math.PI / 2 || inclination > Math.PI / 2) { + throw new IllegalArgumentException("Geomagnetic field info out of range"); + } + + return new SensorAdditionalInfo( + null, TYPE_LOCAL_GEOMAGNETIC_FIELD, 0, + null, new float[] { strength, declination, inclination}); + } } diff --git a/core/java/android/hardware/SensorManager.java b/core/java/android/hardware/SensorManager.java index a6930b035853..1dc64784c837 100644 --- a/core/java/android/hardware/SensorManager.java +++ b/core/java/android/hardware/SensorManager.java @@ -1927,4 +1927,12 @@ public abstract class SensorManager { } return delay; } + + /** @hide */ + public boolean setOperationParameter(SensorAdditionalInfo parameter) { + return setOperationParameterImpl(parameter); + } + + /** @hide */ + protected abstract boolean setOperationParameterImpl(SensorAdditionalInfo parameter); } diff --git a/core/java/android/hardware/SystemSensorManager.java b/core/java/android/hardware/SystemSensorManager.java index 70298475043d..067717905518 100644 --- a/core/java/android/hardware/SystemSensorManager.java +++ b/core/java/android/hardware/SystemSensorManager.java @@ -67,6 +67,9 @@ public class SystemSensorManager extends SensorManager { private static native int nativeConfigDirectChannel( long nativeInstance, int channelHandle, int sensorHandle, int rate); + private static native int nativeSetOperationParameter( + long nativeInstance, int type, float[] floatValues, int[] intValues); + private static final Object sLock = new Object(); @GuardedBy("sLock") private static boolean sNativeClassInited = false; @@ -928,4 +931,9 @@ public class SystemSensorManager extends SensorManager { } } + + protected boolean setOperationParameterImpl(SensorAdditionalInfo parameter) { + return nativeSetOperationParameter( + mNativeInstance, parameter.type, parameter.floatValues, parameter.intValues) == 0; + } } diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java index 2aa6af6f9e3c..46ad3f0eccc9 100644 --- a/core/java/android/hardware/camera2/CameraCharacteristics.java +++ b/core/java/android/hardware/camera2/CameraCharacteristics.java @@ -67,6 +67,15 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * * @hide */ + public Key(String name, Class<T> type, long vendorId) { + mKey = new CameraMetadataNative.Key<T>(name, type, vendorId); + } + + /** + * Visible for testing and vendor extensions only. + * + * @hide + */ public Key(String name, Class<T> type) { mKey = new CameraMetadataNative.Key<T>(name, type); } @@ -99,6 +108,15 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri } /** + * Return vendor tag id. + * + * @hide + */ + public long getVendorId() { + return mKey.getVendorId(); + } + + /** * {@inheritDoc} */ @Override @@ -159,6 +177,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri */ public CameraCharacteristics(CameraMetadataNative properties) { mProperties = CameraMetadataNative.move(properties); + setNativeInstance(mProperties); } /** @@ -227,7 +246,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri } mKeys = Collections.unmodifiableList( - getKeysStatic(getClass(), getKeyClass(), this, filterTags)); + getKeys(getClass(), getKeyClass(), this, filterTags)); return mKeys; } @@ -320,7 +339,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri "metadataClass must be a subclass of CameraMetadata"); } - List<TKey> staticKeyList = CameraCharacteristics.<TKey>getKeysStatic( + List<TKey> staticKeyList = getKeys( metadataClass, keyClass, /*instance*/null, filterTags); return Collections.unmodifiableList(staticKeyList); } diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java index e2896271424b..8c8c49fa6373 100644 --- a/core/java/android/hardware/camera2/CameraMetadata.java +++ b/core/java/android/hardware/camera2/CameraMetadata.java @@ -52,6 +52,7 @@ public abstract class CameraMetadata<TKey> { private static final String TAG = "CameraMetadataAb"; private static final boolean DEBUG = false; + private CameraMetadataNative mNativeInstance = null; /** * Set a camera metadata field to a value. The field definitions can be @@ -89,6 +90,13 @@ public abstract class CameraMetadata<TKey> { /** * @hide */ + protected void setNativeInstance(CameraMetadataNative nativeInstance) { + mNativeInstance = nativeInstance; + } + + /** + * @hide + */ protected abstract Class<TKey> getKeyClass(); /** @@ -108,7 +116,7 @@ public abstract class CameraMetadata<TKey> { public List<TKey> getKeys() { Class<CameraMetadata<TKey>> thisClass = (Class<CameraMetadata<TKey>>) getClass(); return Collections.unmodifiableList( - getKeysStatic(thisClass, getKeyClass(), this, /*filterTags*/null)); + getKeys(thisClass, getKeyClass(), this, /*filterTags*/null)); } /** @@ -126,7 +134,7 @@ public abstract class CameraMetadata<TKey> { * </p> */ /*package*/ @SuppressWarnings("unchecked") - static <TKey> ArrayList<TKey> getKeysStatic( + <TKey> ArrayList<TKey> getKeys( Class<?> type, Class<TKey> keyClass, CameraMetadata<TKey> instance, int[] filterTags) { @@ -173,23 +181,31 @@ public abstract class CameraMetadata<TKey> { } } - ArrayList<TKey> vendorKeys = CameraMetadataNative.getAllVendorKeys(keyClass); + if (null == mNativeInstance) { + return keyList; + } + + ArrayList<TKey> vendorKeys = mNativeInstance.getAllVendorKeys(keyClass); if (vendorKeys != null) { for (TKey k : vendorKeys) { String keyName; + long vendorId; if (k instanceof CaptureRequest.Key<?>) { keyName = ((CaptureRequest.Key<?>) k).getName(); + vendorId = ((CaptureRequest.Key<?>) k).getVendorId(); } else if (k instanceof CaptureResult.Key<?>) { keyName = ((CaptureResult.Key<?>) k).getName(); + vendorId = ((CaptureResult.Key<?>) k).getVendorId(); } else if (k instanceof CameraCharacteristics.Key<?>) { keyName = ((CameraCharacteristics.Key<?>) k).getName(); + vendorId = ((CameraCharacteristics.Key<?>) k).getVendorId(); } else { continue; } if (filterTags == null || Arrays.binarySearch(filterTags, - CameraMetadataNative.getTag(keyName)) >= 0) { + CameraMetadataNative.getTag(keyName, vendorId)) >= 0) { keyList.add(k); } } diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java index 12b46c1bf693..1cf8f0314980 100644 --- a/core/java/android/hardware/camera2/CaptureRequest.java +++ b/core/java/android/hardware/camera2/CaptureRequest.java @@ -101,6 +101,15 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * * @hide */ + public Key(String name, Class<T> type, long vendorId) { + mKey = new CameraMetadataNative.Key<T>(name, type, vendorId); + } + + /** + * Visible for testing and vendor extensions only. + * + * @hide + */ public Key(String name, Class<T> type) { mKey = new CameraMetadataNative.Key<T>(name, type); } @@ -133,6 +142,15 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> } /** + * Return vendor tag id. + * + * @hide + */ + public long getVendorId() { + return mKey.getVendorId(); + } + + /** * {@inheritDoc} */ @Override @@ -199,6 +217,7 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> */ private CaptureRequest() { mSettings = new CameraMetadataNative(); + setNativeInstance(mSettings); mSurfaceSet = new HashSet<Surface>(); mIsReprocess = false; mReprocessableSessionId = CameraCaptureSession.SESSION_ID_NONE; @@ -212,6 +231,7 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> @SuppressWarnings("unchecked") private CaptureRequest(CaptureRequest source) { mSettings = new CameraMetadataNative(source.mSettings); + setNativeInstance(mSettings); mSurfaceSet = (HashSet<Surface>) source.mSurfaceSet.clone(); mIsReprocess = source.mIsReprocess; mIsPartOfCHSRequestList = source.mIsPartOfCHSRequestList; @@ -242,6 +262,7 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> private CaptureRequest(CameraMetadataNative settings, boolean isReprocess, int reprocessableSessionId) { mSettings = CameraMetadataNative.move(settings); + setNativeInstance(mSettings); mSurfaceSet = new HashSet<Surface>(); mIsReprocess = isReprocess; if (isReprocess) { @@ -441,6 +462,7 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> */ private void readFromParcel(Parcel in) { mSettings.readFromParcel(in); + setNativeInstance(mSettings); mSurfaceSet.clear(); diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java index 3f8b57a3498f..419e3e28d36d 100644 --- a/core/java/android/hardware/camera2/CaptureResult.java +++ b/core/java/android/hardware/camera2/CaptureResult.java @@ -78,6 +78,15 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * * @hide */ + public Key(String name, Class<T> type, long vendorId) { + mKey = new CameraMetadataNative.Key<T>(name, type, vendorId); + } + + /** + * Visible for testing and vendor extensions only. + * + * @hide + */ public Key(String name, Class<T> type) { mKey = new CameraMetadataNative.Key<T>(name, type); } @@ -110,6 +119,15 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { } /** + * Return vendor tag id. + * + * @hide + */ + public long getVendorId() { + return mKey.getVendorId(); + } + + /** * {@inheritDoc} */ @Override @@ -186,6 +204,7 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { if (mResults.isEmpty()) { throw new AssertionError("Results must not be empty"); } + setNativeInstance(mResults); mRequest = parent; mSequenceId = extras.getRequestId(); mFrameNumber = extras.getFrameNumber(); @@ -215,6 +234,7 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { throw new AssertionError("Results must not be empty"); } + setNativeInstance(mResults); mRequest = null; mSequenceId = sequenceId; mFrameNumber = -1; diff --git a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java index 4d92ab1ccbb9..ebe2fa17a7c2 100644 --- a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java +++ b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java @@ -79,10 +79,28 @@ public class CameraMetadataNative implements Parcelable { public static class Key<T> { private boolean mHasTag; private int mTag; + private long mVendorId = Long.MAX_VALUE; private final Class<T> mType; private final TypeReference<T> mTypeReference; private final String mName; private final int mHash; + + /** + * @hide + */ + public Key(String name, Class<T> type, long vendorId) { + if (name == null) { + throw new NullPointerException("Key needs a valid name"); + } else if (type == null) { + throw new NullPointerException("Type needs to be non-null"); + } + mName = name; + mType = type; + mVendorId = vendorId; + mTypeReference = TypeReference.createSpecializedTypeReference(type); + mHash = mName.hashCode() ^ mTypeReference.hashCode(); + } + /** * Visible for testing only. * @@ -194,7 +212,7 @@ public class CameraMetadataNative implements Parcelable { */ public final int getTag() { if (!mHasTag) { - mTag = CameraMetadataNative.getTag(mName); + mTag = CameraMetadataNative.getTag(mName, mVendorId); mHasTag = true; } return mTag; @@ -212,6 +230,15 @@ public class CameraMetadataNative implements Parcelable { } /** + * Get the vendor tag provider id. + * + * @hide + */ + public final long getVendorId() { + return mVendorId; + } + + /** * Get the type reference backing the type {@code T} for this key. * * <p>The distinction is only important if {@code T} is a generic, e.g. @@ -463,13 +490,14 @@ public class CameraMetadataNative implements Parcelable { } private <T> T getBase(Key<T> key) { - int tag = key.getTag(); + int tag = nativeGetTagFromKeyLocal(key.getName()); byte[] values = readValues(tag); if (values == null) { return null; } - Marshaler<T> marshaler = getMarshalerForKey(key); + int nativeType = nativeGetTypeFromTagLocal(tag); + Marshaler<T> marshaler = getMarshalerForKey(key, nativeType); ByteBuffer buffer = ByteBuffer.wrap(values).order(ByteOrder.nativeOrder()); return marshaler.unmarshal(buffer); } @@ -947,15 +975,15 @@ public class CameraMetadataNative implements Parcelable { } private <T> void setBase(Key<T> key, T value) { - int tag = key.getTag(); - + int tag = nativeGetTagFromKeyLocal(key.getName()); if (value == null) { // Erase the entry writeValues(tag, /*src*/null); return; } // else update the entry to a new value - Marshaler<T> marshaler = getMarshalerForKey(key); + int nativeType = nativeGetTypeFromTagLocal(tag); + Marshaler<T> marshaler = getMarshalerForKey(key, nativeType); int size = marshaler.calculateMarshalSize(value); // TODO: Optimization. Cache the byte[] and reuse if the size is big enough. @@ -1092,10 +1120,14 @@ public class CameraMetadataNative implements Parcelable { private native synchronized void nativeWriteValues(int tag, byte[] src); private native synchronized void nativeDump() throws IOException; // dump to ALOGD - private static native ArrayList nativeGetAllVendorKeys(Class keyClass); - private static native int nativeGetTagFromKey(String keyName) + private native synchronized ArrayList nativeGetAllVendorKeys(Class keyClass); + private native synchronized int nativeGetTagFromKeyLocal(String keyName) throws IllegalArgumentException; - private static native int nativeGetTypeFromTag(int tag) + private native synchronized int nativeGetTypeFromTagLocal(int tag) + throws IllegalArgumentException; + private static native int nativeGetTagFromKey(String keyName, long vendorId) + throws IllegalArgumentException; + private static native int nativeGetTypeFromTag(int tag, long vendorId) throws IllegalArgumentException; /** @@ -1133,7 +1165,7 @@ public class CameraMetadataNative implements Parcelable { * * @hide */ - public static <K> ArrayList<K> getAllVendorKeys(Class<K> keyClass) { + public <K> ArrayList<K> getAllVendorKeys(Class<K> keyClass) { if (keyClass == null) { throw new NullPointerException(); } @@ -1149,19 +1181,32 @@ public class CameraMetadataNative implements Parcelable { * @hide */ public static int getTag(String key) { - return nativeGetTagFromKey(key); + return nativeGetTagFromKey(key, Long.MAX_VALUE); + } + + /** + * Convert a key string into the equivalent native tag. + * + * @throws IllegalArgumentException if the key was not recognized + * @throws NullPointerException if the key was null + * + * @hide + */ + public static int getTag(String key, long vendorId) { + return nativeGetTagFromKey(key, vendorId); } /** * Get the underlying native type for a tag. * * @param tag An integer tag, see e.g. {@link #getTag} + * @param vendorId A vendor tag provider id * @return An int enum for the metadata type, see e.g. {@link #TYPE_BYTE} * * @hide */ - public static int getNativeType(int tag) { - return nativeGetTypeFromTag(tag); + public static int getNativeType(int tag, long vendorId) { + return nativeGetTypeFromTag(tag, vendorId); } /** @@ -1226,9 +1271,9 @@ public class CameraMetadataNative implements Parcelable { * @throws UnsupportedOperationException * if the native/managed type combination for {@code key} is not supported */ - private static <T> Marshaler<T> getMarshalerForKey(Key<T> key) { + private static <T> Marshaler<T> getMarshalerForKey(Key<T> key, int nativeType) { return MarshalRegistry.getMarshaler(key.getTypeReference(), - getNativeType(key.getTag())); + nativeType); } @SuppressWarnings({ "unchecked", "rawtypes" }) diff --git a/core/java/android/hardware/camera2/params/VendorTagDescriptorCache.java b/core/java/android/hardware/camera2/params/VendorTagDescriptorCache.java new file mode 100644 index 000000000000..1f92f6d9ebf1 --- /dev/null +++ b/core/java/android/hardware/camera2/params/VendorTagDescriptorCache.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.camera2.params; + +import android.os.Parcel; +import android.os.Parcelable; +import android.util.Log; + +/** + * A class for describing the vendor tag cache declared by a camera HAL module. + * Generally only used by the native side of + * android.hardware.camera2.impl.CameraMetadataNative + * + * @hide + */ +public final class VendorTagDescriptorCache implements Parcelable { + + private VendorTagDescriptorCache(Parcel source) { + } + + public static final Parcelable.Creator<VendorTagDescriptorCache> CREATOR = + new Parcelable.Creator<VendorTagDescriptorCache>() { + @Override + public VendorTagDescriptorCache createFromParcel(Parcel source) { + try { + VendorTagDescriptorCache vendorDescriptorCache = new VendorTagDescriptorCache(source); + return vendorDescriptorCache; + } catch (Exception e) { + Log.e(TAG, "Exception creating VendorTagDescriptorCache from parcel", e); + return null; + } + } + + @Override + public VendorTagDescriptorCache[] newArray(int size) { + return new VendorTagDescriptorCache[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + if (dest == null) { + throw new IllegalArgumentException("dest must not be null"); + } + } + + private static final String TAG = "VendorTagDescriptorCache"; +} diff --git a/core/java/android/hardware/usb/UsbDeviceConnection.java b/core/java/android/hardware/usb/UsbDeviceConnection.java index ffd6ec32fccc..7a0272df40d9 100644 --- a/core/java/android/hardware/usb/UsbDeviceConnection.java +++ b/core/java/android/hardware/usb/UsbDeviceConnection.java @@ -20,6 +20,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; import android.content.Context; +import android.os.Build; import android.os.ParcelFileDescriptor; import com.android.internal.util.Preconditions; @@ -27,7 +28,9 @@ import com.android.internal.util.Preconditions; import dalvik.system.CloseGuard; import java.io.FileDescriptor; +import java.nio.BufferOverflowException; import java.nio.ByteBuffer; +import java.util.concurrent.TimeoutException; /** * This class is used for sending and receiving data and control messages to a USB device. @@ -268,16 +271,29 @@ public class UsbDeviceConnection { * * @return a completed USB request, or null if an error occurred * - * @throws IllegalArgumentException if the number of bytes read or written is more than the - * limit of the request's buffer. The number of bytes is - * determined by the {@code length} parameter of + * @throws IllegalArgumentException Before API {@value Build.VERSION_CODES#O}: if the number of + * bytes read or written is more than the limit of the + * request's buffer. The number of bytes is determined by the + * {@code length} parameter of * {@link UsbRequest#queue(ByteBuffer, int)} + * @throws BufferOverflowException In API {@value Build.VERSION_CODES#O} and after: if the + * number of bytes read or written is more than the limit of the + * request's buffer. The number of bytes is determined by the + * {@code length} parameter of + * {@link UsbRequest#queue(ByteBuffer, int)} */ public UsbRequest requestWait() { - // -1 is special value indicating infinite wait - UsbRequest request = native_request_wait(-1); + UsbRequest request = null; + try { + // -1 is special value indicating infinite wait + request = native_request_wait(-1); + } catch (TimeoutException e) { + // Does not happen, infinite timeout + } + if (request != null) { - request.dequeue(); + request.dequeue( + mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.O); } return request; } @@ -290,24 +306,25 @@ public class UsbDeviceConnection { * android.hardware.usb.UsbRequest#getClientData} can be useful in determining how to process * the result of this function.</p> * <p>Android processes {@link UsbRequest UsbRequests} asynchronously. Hence it is not - * guaranteed that {@link #requestWait(int) requestWait(0)} returns a request that has been + * guaranteed that {@link #requestWait(long) requestWait(0)} returns a request that has been * queued right before even if the request could have been processed immediately.</p> * * @param timeout timeout in milliseconds. If 0 this method does not wait. * - * @return a completed USB request, or {@code null} if an error or time out occurred + * @return a completed USB request, or {@code null} if an error occurred * - * @throws IllegalArgumentException if the number of bytes read or written is more than the - * limit of the request's buffer. The number of bytes is - * determined by the {@code length} parameter of - * {@link UsbRequest#queue(ByteBuffer, int)} + * @throws BufferOverflowException if the number of bytes read or written is more than the + * limit of the request's buffer. The number of bytes is + * determined by the {@code length} parameter of + * {@link UsbRequest#queue(ByteBuffer, int)} + * @throws TimeoutException if no request was received in {@code timeout} milliseconds. */ - public UsbRequest requestWait(int timeout) { + public UsbRequest requestWait(long timeout) throws TimeoutException { timeout = Preconditions.checkArgumentNonnegative(timeout, "timeout"); UsbRequest request = native_request_wait(timeout); if (request != null) { - request.dequeue(); + request.dequeue(true); } return request; } @@ -350,7 +367,7 @@ public class UsbDeviceConnection { int index, byte[] buffer, int offset, int length, int timeout); private native int native_bulk_request(int endpoint, byte[] buffer, int offset, int length, int timeout); - private native UsbRequest native_request_wait(int timeout); + private native UsbRequest native_request_wait(long timeout) throws TimeoutException; private native String native_get_serial(); private native boolean native_reset_device(); } diff --git a/core/java/android/hardware/usb/UsbRequest.java b/core/java/android/hardware/usb/UsbRequest.java index 239a2df5e1c3..90990b798d3e 100644 --- a/core/java/android/hardware/usb/UsbRequest.java +++ b/core/java/android/hardware/usb/UsbRequest.java @@ -17,11 +17,13 @@ package android.hardware.usb; import android.annotation.Nullable; +import android.util.Log; import com.android.internal.util.Preconditions; import dalvik.system.CloseGuard; +import java.nio.BufferOverflowException; import java.nio.ByteBuffer; /** @@ -276,7 +278,7 @@ public class UsbRequest { return wasQueued; } - /* package */ void dequeue() { + /* package */ void dequeue(boolean useBufferOverflowInsteadOfIllegalArg) { boolean isSend = (mEndpoint.getDirection() == UsbConstants.USB_DIR_OUT); int bytesTransferred; @@ -313,7 +315,18 @@ public class UsbRequest { bytesTransferred = native_dequeue_array(mBuffer.array(), mLength, isSend); } if (bytesTransferred >= 0) { - mBuffer.position(Math.min(bytesTransferred, mLength)); + int bytesToStore = Math.min(bytesTransferred, mLength); + try { + mBuffer.position(bytesToStore); + } catch (IllegalArgumentException e) { + if (useBufferOverflowInsteadOfIllegalArg) { + Log.e(TAG, "Buffer " + mBuffer + " does not have enough space to read " + + bytesToStore + " bytes", e); + throw new BufferOverflowException(); + } else { + throw e; + } + } } } diff --git a/core/java/android/net/IIpSecService.aidl b/core/java/android/net/IIpSecService.aidl new file mode 100644 index 000000000000..0aa3ce66eb29 --- /dev/null +++ b/core/java/android/net/IIpSecService.aidl @@ -0,0 +1,46 @@ +/* +** Copyright 2017, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +package android.net; + +import android.net.Network; +import android.net.IpSecConfig; +import android.os.Bundle; +import android.os.IBinder; +import android.os.ParcelFileDescriptor; + +/** + * @hide + */ +interface IIpSecService +{ + Bundle reserveSecurityParameterIndex( + int direction, in String remoteAddress, int requestedSpi, in IBinder binder); + + void releaseSecurityParameterIndex(int resourceId); + + Bundle openUdpEncapsulationSocket(int port, in IBinder binder); + + void closeUdpEncapsulationSocket(in ParcelFileDescriptor socket); + + Bundle createTransportModeTransform(in IpSecConfig c, in IBinder binder); + + void deleteTransportModeTransform(int transformId); + + void applyTransportModeTransform(in ParcelFileDescriptor socket, int transformId); + + void removeTransportModeTransform(in ParcelFileDescriptor socket, int transformId); +} diff --git a/core/java/android/net/IpSecAlgorithm.java b/core/java/android/net/IpSecAlgorithm.java index da5cb37961ab..7fea4a25cab7 100644 --- a/core/java/android/net/IpSecAlgorithm.java +++ b/core/java/android/net/IpSecAlgorithm.java @@ -164,6 +164,8 @@ public final class IpSecAlgorithm implements Parcelable { private static boolean isTruncationLengthValid(String algo, int truncLenBits) { switch (algo) { + case ALGO_CRYPT_AES_CBC: + return (truncLenBits == 128 || truncLenBits == 192 || truncLenBits == 256); case ALGO_AUTH_HMAC_MD5: return (truncLenBits >= 96 && truncLenBits <= 128); case ALGO_AUTH_HMAC_SHA1: diff --git a/core/java/android/net/IpSecConfig.java b/core/java/android/net/IpSecConfig.java index b58bf421a86a..13dc19f68577 100644 --- a/core/java/android/net/IpSecConfig.java +++ b/core/java/android/net/IpSecConfig.java @@ -23,7 +23,7 @@ import java.net.UnknownHostException; /** @hide */ public final class IpSecConfig implements Parcelable { - private static final String TAG = IpSecConfig.class.getSimpleName(); + private static final String TAG = "IpSecConfig"; //MODE_TRANSPORT or MODE_TUNNEL int mode; @@ -43,13 +43,13 @@ public final class IpSecConfig implements Parcelable { int spi; // Encryption Algorithm - IpSecAlgorithm encryptionAlgo; + IpSecAlgorithm encryption; // Authentication Algorithm - IpSecAlgorithm authenticationAlgo; + IpSecAlgorithm authentication; } - Flow[] flow = new Flow[2]; + Flow[] flow = new Flow[] {new Flow(), new Flow()}; // For tunnel mode IPv4 UDP Encapsulation // IpSecTransform#ENCAP_ESP_*, such as ENCAP_ESP_OVER_UDP_IKE @@ -57,17 +57,15 @@ public final class IpSecConfig implements Parcelable { int encapLocalPort; int encapRemotePort; - // An optional protocol to match with the selector - int selectorProto; - - // A bitmask of FEATURE_* indicating which of the fields - // of this class are valid. - long features; - // An interval, in seconds between the NattKeepalive packets int nattKeepaliveInterval; - public InetAddress getLocalIp() { + // Transport or Tunnel + public int getMode() { + return mode; + } + + public InetAddress getLocalAddress() { return localAddress; } @@ -75,19 +73,19 @@ public final class IpSecConfig implements Parcelable { return flow[direction].spi; } - public InetAddress getRemoteIp() { + public InetAddress getRemoteAddress() { return remoteAddress; } - public IpSecAlgorithm getEncryptionAlgo(int direction) { - return flow[direction].encryptionAlgo; + public IpSecAlgorithm getEncryption(int direction) { + return flow[direction].encryption; } - public IpSecAlgorithm getAuthenticationAlgo(int direction) { - return flow[direction].authenticationAlgo; + public IpSecAlgorithm getAuthentication(int direction) { + return flow[direction].authentication; } - Network getNetwork() { + public Network getNetwork() { return network; } @@ -103,18 +101,10 @@ public final class IpSecConfig implements Parcelable { return encapRemotePort; } - public int getSelectorProto() { - return selectorProto; - } - - int getNattKeepaliveInterval() { + public int getNattKeepaliveInterval() { return nattKeepaliveInterval; } - public boolean hasProperty(int featureBits) { - return (features & featureBits) == featureBits; - } - // Parcelable Methods @Override @@ -124,31 +114,25 @@ public final class IpSecConfig implements Parcelable { @Override public void writeToParcel(Parcel out, int flags) { - out.writeLong(features); // TODO: Use a byte array or other better method for storing IPs that can also include scope out.writeString((localAddress != null) ? localAddress.getHostAddress() : null); // TODO: Use a byte array or other better method for storing IPs that can also include scope out.writeString((remoteAddress != null) ? remoteAddress.getHostAddress() : null); out.writeParcelable(network, flags); out.writeInt(flow[IpSecTransform.DIRECTION_IN].spi); - out.writeParcelable(flow[IpSecTransform.DIRECTION_IN].encryptionAlgo, flags); - out.writeParcelable(flow[IpSecTransform.DIRECTION_IN].authenticationAlgo, flags); + out.writeParcelable(flow[IpSecTransform.DIRECTION_IN].encryption, flags); + out.writeParcelable(flow[IpSecTransform.DIRECTION_IN].authentication, flags); out.writeInt(flow[IpSecTransform.DIRECTION_OUT].spi); - out.writeParcelable(flow[IpSecTransform.DIRECTION_OUT].encryptionAlgo, flags); - out.writeParcelable(flow[IpSecTransform.DIRECTION_OUT].authenticationAlgo, flags); + out.writeParcelable(flow[IpSecTransform.DIRECTION_OUT].encryption, flags); + out.writeParcelable(flow[IpSecTransform.DIRECTION_OUT].authentication, flags); out.writeInt(encapType); out.writeInt(encapLocalPort); out.writeInt(encapRemotePort); - out.writeInt(selectorProto); } // Package Private: Used by the IpSecTransform.Builder; // there should be no public constructor for this object - IpSecConfig() { - flow[IpSecTransform.DIRECTION_IN].spi = 0; - flow[IpSecTransform.DIRECTION_OUT].spi = 0; - nattKeepaliveInterval = 0; //FIXME constant - } + IpSecConfig() {} private static InetAddress readInetAddressFromParcel(Parcel in) { String addrString = in.readString(); @@ -164,24 +148,22 @@ public final class IpSecConfig implements Parcelable { } private IpSecConfig(Parcel in) { - features = in.readLong(); localAddress = readInetAddressFromParcel(in); remoteAddress = readInetAddressFromParcel(in); network = (Network) in.readParcelable(Network.class.getClassLoader()); flow[IpSecTransform.DIRECTION_IN].spi = in.readInt(); - flow[IpSecTransform.DIRECTION_IN].encryptionAlgo = + flow[IpSecTransform.DIRECTION_IN].encryption = (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader()); - flow[IpSecTransform.DIRECTION_IN].authenticationAlgo = + flow[IpSecTransform.DIRECTION_IN].authentication = (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader()); flow[IpSecTransform.DIRECTION_OUT].spi = in.readInt(); - flow[IpSecTransform.DIRECTION_OUT].encryptionAlgo = + flow[IpSecTransform.DIRECTION_OUT].encryption = (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader()); - flow[IpSecTransform.DIRECTION_OUT].authenticationAlgo = + flow[IpSecTransform.DIRECTION_OUT].authentication = (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader()); encapType = in.readInt(); encapLocalPort = in.readInt(); encapRemotePort = in.readInt(); - selectorProto = in.readInt(); } public static final Parcelable.Creator<IpSecConfig> CREATOR = diff --git a/core/java/android/net/IpSecManager.java b/core/java/android/net/IpSecManager.java index 2c544e9b9bfe..6852beb06529 100644 --- a/core/java/android/net/IpSecManager.java +++ b/core/java/android/net/IpSecManager.java @@ -17,10 +17,11 @@ package android.net; import static com.android.internal.util.Preconditions.checkNotNull; -import android.annotation.SystemApi; -import android.content.Context; -import android.os.INetworkManagementService; +import android.annotation.NonNull; +import android.os.Binder; +import android.os.Bundle; import android.os.ParcelFileDescriptor; +import android.os.RemoteException; import android.util.AndroidException; import dalvik.system.CloseGuard; import java.io.FileDescriptor; @@ -41,6 +42,29 @@ public final class IpSecManager { private static final String TAG = "IpSecManager"; /** + * The Security Parameter Index, SPI, 0 indicates an unknown or invalid index. + * + * <p>No IPsec packet may contain an SPI of 0. + */ + public static final int INVALID_SECURITY_PARAMETER_INDEX = 0; + + /** @hide */ + public interface Status { + public static final int OK = 0; + public static final int RESOURCE_UNAVAILABLE = 1; + public static final int SPI_UNAVAILABLE = 2; + } + + /** @hide */ + public static final String KEY_STATUS = "status"; + /** @hide */ + public static final String KEY_RESOURCE_ID = "resourceId"; + /** @hide */ + public static final String KEY_SPI = "spi"; + /** @hide */ + public static final int INVALID_RESOURCE_ID = 0; + + /** * Indicates that the combination of remote InetAddress and SPI was non-unique for a given * request. If encountered, selection of a new SPI is required before a transform may be * created. Note, this should happen very rarely if the SPI is chosen to be sufficiently random @@ -79,42 +103,30 @@ public final class IpSecManager { } } - private final Context mContext; - private final INetworkManagementService mService; + private final IIpSecService mService; public static final class SecurityParameterIndex implements AutoCloseable { - private final Context mContext; - private final InetAddress mDestinationAddress; + private final IIpSecService mService; + private final InetAddress mRemoteAddress; private final CloseGuard mCloseGuard = CloseGuard.get(); - private int mSpi; + private int mSpi = INVALID_SECURITY_PARAMETER_INDEX; + private int mResourceId; /** Return the underlying SPI held by this object */ public int getSpi() { return mSpi; } - private SecurityParameterIndex(Context context, InetAddress destinationAddress, int spi) - throws ResourceUnavailableException, SpiUnavailableException { - mContext = context; - mDestinationAddress = destinationAddress; - mSpi = spi; - mCloseGuard.open("open"); - } - /** * Release an SPI that was previously reserved. * - * <p>Release an SPI for use by other users in the system. This will fail if the SPI is - * currently in use by an IpSecTransform. - * - * @param destinationAddress SPIs must be unique for each combination of SPI and destination - * address. Thus, the destinationAddress to which the SPI will communicate must be - * supplied. - * @param spi the previously reserved SPI to be freed. + * <p>Release an SPI for use by other users in the system. If a SecurityParameterIndex is + * applied to an IpSecTransform, it will become unusable for future transforms but should + * still be closed to ensure system resources are released. */ @Override public void close() { - mSpi = INVALID_SECURITY_PARAMETER_INDEX; // TODO: Invalid SPI + mSpi = INVALID_SECURITY_PARAMETER_INDEX; mCloseGuard.close(); } @@ -126,23 +138,61 @@ public final class IpSecManager { close(); } - } - /** - * The Security Parameter Index, SPI, 0 indicates an unknown or invalid index. - * - * <p>No IPsec packet may contain an SPI of 0. - */ - public static final int INVALID_SECURITY_PARAMETER_INDEX = 0; + private SecurityParameterIndex( + @NonNull IIpSecService service, int direction, InetAddress remoteAddress, int spi) + throws ResourceUnavailableException, SpiUnavailableException { + mService = service; + mRemoteAddress = remoteAddress; + try { + Bundle result = + mService.reserveSecurityParameterIndex( + direction, remoteAddress.getHostAddress(), spi, new Binder()); + + if (result == null) { + throw new NullPointerException("Received null response from IpSecService"); + } + + int status = result.getInt(KEY_STATUS); + switch (status) { + case Status.OK: + break; + case Status.RESOURCE_UNAVAILABLE: + throw new ResourceUnavailableException( + "No more SPIs may be allocated by this requester."); + case Status.SPI_UNAVAILABLE: + throw new SpiUnavailableException("Requested SPI is unavailable", spi); + default: + throw new RuntimeException( + "Unknown status returned by IpSecService: " + status); + } + mSpi = result.getInt(KEY_SPI); + mResourceId = result.getInt(KEY_RESOURCE_ID); + + if (mSpi == INVALID_SECURITY_PARAMETER_INDEX) { + throw new RuntimeException("Invalid SPI returned by IpSecService: " + status); + } + + if (mResourceId == INVALID_RESOURCE_ID) { + throw new RuntimeException( + "Invalid Resource ID returned by IpSecService: " + status); + } + + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + mCloseGuard.open("open"); + } + } /** - * Reserve an SPI for traffic bound towards the specified destination address. + * Reserve an SPI for traffic bound towards the specified remote address. * * <p>If successful, this SPI is guaranteed available until released by a call to {@link * SecurityParameterIndex#close()}. * - * @param destinationAddress SPIs must be unique for each combination of SPI and destination - * address. + * @param direction {@link IpSecTransform#DIRECTION_IN} or {@link IpSecTransform#DIRECTION_OUT} + * @param remoteAddress address of the remote. SPIs must be unique for each remoteAddress. * @param requestedSpi the requested SPI, or '0' to allocate a random SPI. * @return the reserved SecurityParameterIndex * @throws ResourceUnavailableException indicating that too many SPIs are currently allocated @@ -150,9 +200,9 @@ public final class IpSecManager { * @throws SpiUnavailableException indicating that a particular SPI cannot be reserved */ public SecurityParameterIndex reserveSecurityParameterIndex( - InetAddress destinationAddress, int requestedSpi) + int direction, InetAddress remoteAddress, int requestedSpi) throws SpiUnavailableException, ResourceUnavailableException { - return new SecurityParameterIndex(mContext, destinationAddress, requestedSpi); + return new SecurityParameterIndex(mService, direction, remoteAddress, requestedSpi); } /** @@ -190,7 +240,13 @@ public final class IpSecManager { } /* Call down to activate a transform */ - private void applyTransportModeTransform(ParcelFileDescriptor pfd, IpSecTransform transform) {} + private void applyTransportModeTransform(ParcelFileDescriptor pfd, IpSecTransform transform) { + try { + mService.applyTransportModeTransform(pfd, transform.getResourceId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } /** * Apply an active Tunnel Mode IPsec Transform to a network, which will tunnel all traffic to @@ -203,7 +259,6 @@ public final class IpSecManager { * @param transform an {@link IpSecTransform}, which must be an active Tunnel Mode transform. * @hide */ - @SystemApi public void applyTunnelModeTransform(Network net, IpSecTransform transform) {} /** @@ -235,7 +290,13 @@ public final class IpSecManager { } /* Call down to activate a transform */ - private void removeTransportModeTransform(ParcelFileDescriptor pfd, IpSecTransform transform) {} + private void removeTransportModeTransform(ParcelFileDescriptor pfd, IpSecTransform transform) { + try { + mService.removeTransportModeTransform(pfd, transform.getResourceId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } /** * Remove a Tunnel Mode IPsec Transform from a {@link Network}. This must be used as part of @@ -248,7 +309,6 @@ public final class IpSecManager { * network * @hide */ - @SystemApi public void removeTunnelModeTransform(Network net, IpSecTransform transform) {} /** @@ -260,19 +320,19 @@ public final class IpSecManager { */ public static final class UdpEncapsulationSocket implements AutoCloseable { private final FileDescriptor mFd; - private final Context mContext; + private final IIpSecService mService; private final CloseGuard mCloseGuard = CloseGuard.get(); - private UdpEncapsulationSocket(Context context, int port) + private UdpEncapsulationSocket(@NonNull IIpSecService service, int port) throws ResourceUnavailableException { - mContext = context; + mService = service; mCloseGuard.open("constructor"); // TODO: go down to the kernel and get a socket on the specified mFd = new FileDescriptor(); } - private UdpEncapsulationSocket(Context context) throws ResourceUnavailableException { - mContext = context; + private UdpEncapsulationSocket(IIpSecService service) throws ResourceUnavailableException { + mService = service; mCloseGuard.open("constructor"); // TODO: go get a random socket on a random port mFd = new FileDescriptor(); @@ -339,7 +399,7 @@ public final class IpSecManager { public UdpEncapsulationSocket openUdpEncapsulationSocket(int port) throws IOException, ResourceUnavailableException { // Temporary code - return new UdpEncapsulationSocket(mContext, port); + return new UdpEncapsulationSocket(mService, port); } /** @@ -363,7 +423,7 @@ public final class IpSecManager { public UdpEncapsulationSocket openUdpEncapsulationSocket() throws IOException, ResourceUnavailableException { // Temporary code - return new UdpEncapsulationSocket(mContext); + return new UdpEncapsulationSocket(mService); } /** @@ -372,8 +432,7 @@ public final class IpSecManager { * @param context the application context for this manager * @hide */ - public IpSecManager(Context context, INetworkManagementService service) { - mContext = checkNotNull(context, "missing context"); + public IpSecManager(IIpSecService service) { mService = checkNotNull(service, "missing service"); } } diff --git a/core/java/android/net/IpSecTransform.java b/core/java/android/net/IpSecTransform.java index d6dd28bec390..801e98c7b138 100644 --- a/core/java/android/net/IpSecTransform.java +++ b/core/java/android/net/IpSecTransform.java @@ -15,11 +15,21 @@ */ package android.net; +import static android.net.IpSecManager.INVALID_RESOURCE_ID; +import static android.net.IpSecManager.KEY_RESOURCE_ID; +import static android.net.IpSecManager.KEY_STATUS; + import android.annotation.IntDef; +import android.annotation.NonNull; import android.annotation.SystemApi; import android.content.Context; -import android.system.ErrnoException; +import android.os.Binder; +import android.os.Bundle; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.ServiceManager; import android.util.Log; +import com.android.internal.util.Preconditions; import dalvik.system.CloseGuard; import java.io.IOException; import java.lang.annotation.Retention; @@ -86,39 +96,64 @@ public final class IpSecTransform implements AutoCloseable { @Retention(RetentionPolicy.SOURCE) public @interface EncapType {} - /** - * Sentinel for an invalid transform (means that this transform is inactive). - * - * @hide - */ - public static final int INVALID_TRANSFORM_ID = -1; - private IpSecTransform(Context context, IpSecConfig config) { mContext = context; mConfig = config; - mTransformId = INVALID_TRANSFORM_ID; + mResourceId = INVALID_RESOURCE_ID; + } + + private IIpSecService getIpSecService() { + IBinder b = ServiceManager.getService(android.content.Context.IPSEC_SERVICE); + if (b == null) { + throw new RemoteException("Failed to connect to IpSecService") + .rethrowAsRuntimeException(); + } + + return IIpSecService.Stub.asInterface(b); + } + + private void checkResultStatusAndThrow(int status) + throws IOException, IpSecManager.ResourceUnavailableException, + IpSecManager.SpiUnavailableException { + switch (status) { + case IpSecManager.Status.OK: + return; + // TODO: Pass Error string back from bundle so that errors can be more specific + case IpSecManager.Status.RESOURCE_UNAVAILABLE: + throw new IpSecManager.ResourceUnavailableException( + "Failed to allocate a new IpSecTransform"); + case IpSecManager.Status.SPI_UNAVAILABLE: + Log.wtf(TAG, "Attempting to use an SPI that was somehow not reserved"); + // Fall through + default: + throw new IllegalStateException( + "Failed to Create a Transform with status code " + status); + } } private IpSecTransform activate() throws IOException, IpSecManager.ResourceUnavailableException, IpSecManager.SpiUnavailableException { - int transformId; synchronized (this) { - //try { - transformId = INVALID_TRANSFORM_ID; - //} catch (RemoteException e) { - // throw e.rethrowFromSystemServer(); - //} - - if (transformId < 0) { - throw new ErrnoException("addTransform", -transformId).rethrowAsIOException(); + try { + IIpSecService svc = getIpSecService(); + Bundle result = svc.createTransportModeTransform(mConfig, new Binder()); + int status = result.getInt(KEY_STATUS); + checkResultStatusAndThrow(status); + mResourceId = result.getInt(KEY_RESOURCE_ID, INVALID_RESOURCE_ID); + + /* Keepalive will silently fail if not needed by the config; but, if needed and + * it fails to start, we need to bail because a transform will not be reliable + * to use if keepalive is expected to offload and fails. + */ + // FIXME: if keepalive fails, we need to fail spectacularly + startKeepalive(mContext); + Log.d(TAG, "Added Transform with Id " + mResourceId); + mCloseGuard.open("build"); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); } - - startKeepalive(mContext); // Will silently fail if not required - mTransformId = transformId; - Log.d(TAG, "Added Transform with Id " + transformId); } - mCloseGuard.open("build"); return this; } @@ -133,21 +168,27 @@ public final class IpSecTransform implements AutoCloseable { * transform is no longer needed. */ public void close() { - Log.d(TAG, "Removing Transform with Id " + mTransformId); + Log.d(TAG, "Removing Transform with Id " + mResourceId); // Always safe to attempt cleanup - if (mTransformId == INVALID_TRANSFORM_ID) { + if (mResourceId == INVALID_RESOURCE_ID) { + mCloseGuard.close(); return; } - //try { - stopKeepalive(); - //} catch (RemoteException e) { - // transform.setTransformId(transformId); - // throw e.rethrowFromSystemServer(); - //} finally { - mTransformId = INVALID_TRANSFORM_ID; - //} - mCloseGuard.close(); + try { + /* Order matters here because the keepalive is best-effort but could fail in some + * horrible way to be removed if the wifi (or cell) subsystem has crashed, and we + * still want to clear out the transform. + */ + IIpSecService svc = getIpSecService(); + svc.deleteTransportModeTransform(mResourceId); + stopKeepalive(); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } finally { + mResourceId = INVALID_RESOURCE_ID; + mCloseGuard.close(); + } } @Override @@ -164,7 +205,7 @@ public final class IpSecTransform implements AutoCloseable { } private final IpSecConfig mConfig; - private int mTransformId; + private int mResourceId; private final Context mContext; private final CloseGuard mCloseGuard = CloseGuard.get(); private ConnectivityManager.PacketKeepalive mKeepalive; @@ -200,6 +241,7 @@ public final class IpSecTransform implements AutoCloseable { /* Package */ void startKeepalive(Context c) { + // FIXME: NO_KEEPALIVE needs to be a constant if (mConfig.getNattKeepaliveInterval() == 0) { return; } @@ -208,7 +250,7 @@ public final class IpSecTransform implements AutoCloseable { (ConnectivityManager) c.getSystemService(Context.CONNECTIVITY_SERVICE); if (mKeepalive != null) { - Log.e(TAG, "Keepalive already started for this IpSecTransform."); + Log.wtf(TAG, "Keepalive already started for this IpSecTransform."); return; } @@ -218,10 +260,11 @@ public final class IpSecTransform implements AutoCloseable { mConfig.getNetwork(), mConfig.getNattKeepaliveInterval(), mKeepaliveCallback, - mConfig.getLocalIp(), + mConfig.getLocalAddress(), mConfig.getEncapLocalPort(), - mConfig.getRemoteIp()); + mConfig.getRemoteAddress()); try { + // FIXME: this is still a horrible way to fudge the synchronous callback mKeepaliveSyncLock.wait(2000); } catch (InterruptedException e) { } @@ -232,6 +275,11 @@ public final class IpSecTransform implements AutoCloseable { } /* Package */ + int getResourceId() { + return mResourceId; + } + + /* Package */ void stopKeepalive() { if (mKeepalive == null) { return; @@ -247,16 +295,6 @@ public final class IpSecTransform implements AutoCloseable { } } - /* Package */ - void setTransformId(int transformId) { - mTransformId = transformId; - } - - /* Package */ - int getTransformId() { - return mTransformId; - } - /** * Builder object to facilitate the creation of IpSecTransform objects. * @@ -280,7 +318,7 @@ public final class IpSecTransform implements AutoCloseable { */ public IpSecTransform.Builder setEncryption( @TransformDirection int direction, IpSecAlgorithm algo) { - mConfig.flow[direction].encryptionAlgo = algo; + mConfig.flow[direction].encryption = algo; return this; } @@ -295,7 +333,7 @@ public final class IpSecTransform implements AutoCloseable { */ public IpSecTransform.Builder setAuthentication( @TransformDirection int direction, IpSecAlgorithm algo) { - mConfig.flow[direction].authenticationAlgo = algo; + mConfig.flow[direction].authentication = algo; return this; } @@ -305,32 +343,9 @@ public final class IpSecTransform implements AutoCloseable { * given destination address. * * <p>Care should be chosen when selecting an SPI to ensure that is is as unique as - * possible. Random number generation is a reasonable approach to selecting an SPI. For - * outbound SPIs, they must be reserved by calling {@link - * IpSecManager#reserveSecurityParameterIndex(InetAddress, int)}. Otherwise, Transforms will - * fail to build. - * - * <p>Unless an SPI is set for a given direction, traffic in that direction will be - * sent/received without any IPsec applied. - * - * @param direction either {@link #DIRECTION_IN or #DIRECTION_OUT} - * @param spi a unique 32-bit integer to identify transformed traffic - */ - public IpSecTransform.Builder setSpi(@TransformDirection int direction, int spi) { - mConfig.flow[direction].spi = spi; - return this; - } - - /** - * Set the SPI, which uniquely identifies a particular IPsec session from others. Because - * IPsec operates at the IP layer, this 32-bit identifier uniquely identifies packets to a - * given destination address. - * - * <p>Care should be chosen when selecting an SPI to ensure that is is as unique as - * possible. Random number generation is a reasonable approach to selecting an SPI. For - * outbound SPIs, they must be reserved by calling {@link - * IpSecManager#reserveSecurityParameterIndex(InetAddress, int)}. Otherwise, Transforms will - * fail to activate. + * possible. To reserve a value call {@link IpSecManager#reserveSecurityParameterIndex(int, + * InetAddress, int)}. Otherwise, SPI collisions would prevent a transform from being + * activated. IpSecManager#reserveSecurityParameterIndex(int, InetAddres$s, int)}. * * <p>Unless an SPI is set for a given direction, traffic in that direction will be * sent/received without any IPsec applied. @@ -341,6 +356,8 @@ public final class IpSecTransform implements AutoCloseable { */ public IpSecTransform.Builder setSpi( @TransformDirection int direction, IpSecManager.SecurityParameterIndex spi) { + // TODO: convert to using the resource Id of the SPI. Then build() can validate + // the owner in the IpSecService mConfig.flow[direction].spi = spi.getSpi(); return this; } @@ -447,7 +464,6 @@ public final class IpSecTransform implements AutoCloseable { * properties is invalid. * @hide */ - @SystemApi public IpSecTransform buildTunnelModeTransform( InetAddress localAddress, InetAddress remoteAddress) { //FIXME: argument validation here @@ -463,7 +479,8 @@ public final class IpSecTransform implements AutoCloseable { * * @param context current Context */ - public Builder(Context context) { + public Builder(@NonNull Context context) { + Preconditions.checkNotNull(context); mContext = context; mConfig = new IpSecConfig(); } diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java index dd11f68ffe50..4c6d22ae1c51 100644 --- a/core/java/android/os/BatteryStats.java +++ b/core/java/android/os/BatteryStats.java @@ -182,7 +182,7 @@ public abstract class BatteryStats implements Parcelable { * New in version 19: * - Wakelock data (wl) gets current and max times. * New in version 20: - * - Sensor, BluetoothScan, WifiScan get background timers and counter. + * - Background timers and counters for: Sensor, BluetoothScan, WifiScan, Jobs. */ static final String CHECKIN_VERSION = "20"; @@ -392,6 +392,16 @@ public abstract class BatteryStats implements Parcelable { } /** + * Returns the secondary Timer held by the Timer, if one exists. This secondary timer may be + * used, for example, for tracking background usage. Secondary timers are never pooled. + * + * Not all Timer subclasses have a secondary timer; those that don't return null. + */ + public Timer getSubTimer() { + return null; + } + + /** * Returns whether the timer is currently running. Some types of timers * (e.g. BatchTimers) don't know whether the event is currently active, * and report false. @@ -3303,16 +3313,17 @@ public abstract class BatteryStats implements Parcelable { final int wifiScanCount = u.getWifiScanCount(which); final int wifiScanCountBg = u.getWifiScanBackgroundCount(which); // Note that 'ActualTime' are unpooled and always since reset (regardless of 'which') - final long wifiScanActualTime = u.getWifiScanActualTime(rawRealtime); - final long wifiScanActualTimeBg = u.getWifiScanBackgroundTime(rawRealtime); + final long wifiScanActualTimeMs = (u.getWifiScanActualTime(rawRealtime) + 500) / 1000; + final long wifiScanActualTimeMsBg = (u.getWifiScanBackgroundTime(rawRealtime) + 500) + / 1000; final long uidWifiRunningTime = u.getWifiRunningTime(rawRealtime, which); if (fullWifiLockOnTime != 0 || wifiScanTime != 0 || wifiScanCount != 0 - || wifiScanCountBg != 0 || wifiScanActualTime != 0 || wifiScanActualTimeBg != 0 - || uidWifiRunningTime != 0) { + || wifiScanCountBg != 0 || wifiScanActualTimeMs != 0 + || wifiScanActualTimeMsBg != 0 || uidWifiRunningTime != 0) { dumpLine(pw, uid, category, WIFI_DATA, fullWifiLockOnTime, wifiScanTime, uidWifiRunningTime, wifiScanCount, /* legacy fields follow, keep at 0 */ 0, 0, 0, - wifiScanCountBg, wifiScanActualTime, wifiScanActualTimeBg); + wifiScanCountBg, wifiScanActualTimeMs, wifiScanActualTimeMsBg); } dumpControllerActivityLine(pw, uid, category, WIFI_CONTROLLER_DATA, @@ -3393,9 +3404,13 @@ public abstract class BatteryStats implements Parcelable { // Convert from microseconds to milliseconds with rounding final long totalTime = (timer.getTotalTimeLocked(rawRealtime, which) + 500) / 1000; final int count = timer.getCountLocked(which); + final Timer bgTimer = timer.getSubTimer(); + final long bgTime = bgTimer != null ? + (bgTimer.getTotalTimeLocked(rawRealtime, which) + 500) / 1000 : -1; + final int bgCount = bgTimer != null ? bgTimer.getCountLocked(which) : -1; if (totalTime != 0) { dumpLine(pw, uid, category, JOB_DATA, "\"" + jobs.keyAt(ij) + "\"", - totalTime, count); + totalTime, count, bgTime, bgCount); } } @@ -4616,6 +4631,10 @@ public abstract class BatteryStats implements Parcelable { // Convert from microseconds to milliseconds with rounding final long totalTime = (timer.getTotalTimeLocked(rawRealtime, which) + 500) / 1000; final int count = timer.getCountLocked(which); + final Timer bgTimer = timer.getSubTimer(); + final long bgTime = bgTimer != null ? + (bgTimer.getTotalTimeLocked(rawRealtime, which) + 500) / 1000 : -1; + final int bgCount = bgTimer != null ? bgTimer.getCountLocked(which) : -1; sb.setLength(0); sb.append(prefix); sb.append(" Job "); @@ -4626,6 +4645,13 @@ public abstract class BatteryStats implements Parcelable { sb.append("realtime ("); sb.append(count); sb.append(" times)"); + if (bgTime > 0) { + sb.append(", "); + formatTimeMs(sb, bgTime); + sb.append("background ("); + sb.append(bgCount); + sb.append(" times)"); + } } else { sb.append("(not used)"); } diff --git a/core/java/android/os/ConfigUpdate.java b/core/java/android/os/ConfigUpdate.java new file mode 100644 index 000000000000..793a90e1ece7 --- /dev/null +++ b/core/java/android/os/ConfigUpdate.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.os; + +import android.annotation.SystemApi; + +/** + * Intents used to provide unbundled updates of system data. + * All require the UPDATE_CONFIG permission. + * + * @see com.android.server.updates + * @hide + */ +@SystemApi +public final class ConfigUpdate { + + /** + * Update system wide certificate pins for TLS connections. + * @hide + */ + @SystemApi + public static final String ACTION_UPDATE_PINS = "android.intent.action.UPDATE_PINS"; + + /** + * Update system wide Intent firewall. + * @hide + */ + @SystemApi + public static final String ACTION_UPDATE_INTENT_FIREWALL + = "android.intent.action.UPDATE_INTENT_FIREWALL"; + + /** + * Update list of permium SMS short codes. + * @hide + */ + @SystemApi + public static final String ACTION_UPDATE_SMS_SHORT_CODES + = "android.intent.action.UPDATE_SMS_SHORT_CODES"; + + /** + * Update list of carrier provisioning URLs. + * @hide + */ + @SystemApi + public static final String ACTION_UPDATE_CARRIER_PROVISIONING_URLS + = "android.intent.action.UPDATE_CARRIER_PROVISIONING_URLS"; + + /** + * Update set of trusted logs used for Certificate Transparency support for TLS connections. + * @hide + */ + @SystemApi + public static final String ACTION_UPDATE_CT_LOGS + = "android.intent.action.UPDATE_CT_LOGS"; + + /** + * Update system wide timezone data. + * @hide + */ + @SystemApi + public static final String ACTION_UPDATE_TZDATA = "android.intent.action.UPDATE_TZDATA"; + + private ConfigUpdate() { + } +} diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java index 31376587e144..7a709ed59b15 100644 --- a/core/java/android/os/Process.java +++ b/core/java/android/os/Process.java @@ -660,7 +660,7 @@ public class Process { * @hide * @param tid The identifier of the thread to change. * @param group The target group for this thread from THREAD_GROUP_*. - * + * * @throws IllegalArgumentException Throws IllegalArgumentException if * <var>tid</var> does not exist. * @throws SecurityException Throws SecurityException if your process does @@ -676,6 +676,21 @@ public class Process { throws IllegalArgumentException, SecurityException; /** + * Sets the scheduling group and the corresponding cpuset group + * @hide + * @param tid The identifier of the thread to change. + * @param group The target group for this thread from THREAD_GROUP_*. + * + * @throws IllegalArgumentException Throws IllegalArgumentException if + * <var>tid</var> does not exist. + * @throws SecurityException Throws SecurityException if your process does + * not have permission to modify the given thread, or to use the given + * priority. + */ + public static final native void setThreadGroupAndCpuset(int tid, int group) + throws IllegalArgumentException, SecurityException; + + /** * Sets the scheduling group for a process and all child threads * @hide * @param pid The identifier of the process to change. diff --git a/core/java/android/os/RemoteCallbackList.java b/core/java/android/os/RemoteCallbackList.java index 6656b00d457c..2281fb6d5cc2 100644 --- a/core/java/android/os/RemoteCallbackList.java +++ b/core/java/android/os/RemoteCallbackList.java @@ -17,6 +17,7 @@ package android.os; import android.util.ArrayMap; +import android.util.Slog; import java.util.function.Consumer; @@ -49,16 +50,19 @@ import java.util.function.Consumer; * implements the {@link #onCallbackDied} method. */ public class RemoteCallbackList<E extends IInterface> { + private static final String TAG = "RemoteCallbackList"; + /*package*/ ArrayMap<IBinder, Callback> mCallbacks = new ArrayMap<IBinder, Callback>(); private Object[] mActiveBroadcast; private int mBroadcastCount = -1; private boolean mKilled = false; + private StringBuilder mRecentCallers; private final class Callback implements IBinder.DeathRecipient { final E mCallback; final Object mCookie; - + Callback(E callback, Object cookie) { mCallback = callback; mCookie = cookie; @@ -111,6 +115,8 @@ public class RemoteCallbackList<E extends IInterface> { if (mKilled) { return false; } + // Flag unusual case that could be caused by a leak. b/36778087 + logExcessiveCallbacks(); IBinder binder = callback.asBinder(); try { Callback cb = new Callback(callback, cookie); @@ -392,4 +398,25 @@ public class RemoteCallbackList<E extends IInterface> { return mCallbacks.valueAt(index).mCookie; } } + + private void logExcessiveCallbacks() { + final long size = mCallbacks.size(); + final long TOO_MANY = 3000; + final long MAX_CHARS = 1000; + if (size >= TOO_MANY) { + if (size == TOO_MANY && mRecentCallers == null) { + mRecentCallers = new StringBuilder(); + } + if (mRecentCallers != null && mRecentCallers.length() < MAX_CHARS) { + mRecentCallers.append(Debug.getCallers(5)); + mRecentCallers.append('\n'); + if (mRecentCallers.length() >= MAX_CHARS) { + Slog.wtf(TAG, "More than " + + TOO_MANY + " remote callbacks registered. Recent callers:\n" + + mRecentCallers.toString()); + mRecentCallers = null; + } + } + } + } } diff --git a/core/java/android/print/PrintManager.java b/core/java/android/print/PrintManager.java index 5d0ad5568173..8ee05177f186 100644 --- a/core/java/android/print/PrintManager.java +++ b/core/java/android/print/PrintManager.java @@ -18,6 +18,7 @@ package android.print; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SystemApi; import android.app.Activity; import android.app.Application.ActivityLifecycleCallbacks; import android.content.ComponentName; @@ -43,8 +44,8 @@ import android.util.ArrayMap; import android.util.Log; import com.android.internal.os.SomeArgs; - import com.android.internal.util.Preconditions; + import libcore.io.IoUtils; import java.lang.ref.WeakReference; @@ -115,8 +116,6 @@ public final class PrintManager { private static final boolean DEBUG = false; private static final int MSG_NOTIFY_PRINT_JOB_STATE_CHANGED = 1; - private static final int MSG_NOTIFY_PRINT_SERVICES_CHANGED = 2; - private static final int MSG_NOTIFY_PRINT_SERVICE_RECOMMENDATIONS_CHANGED = 3; /** * Package name of print spooler. @@ -131,6 +130,7 @@ public final class PrintManager { * @see #getPrintServices * @hide */ + @SystemApi public static final int ENABLED_SERVICES = 1 << 0; /** @@ -221,16 +221,26 @@ public final class PrintManager { public void onPrintJobStateChanged(PrintJobId printJobId); } - /** @hide */ + /** + * Listen for changes to {@link #getPrintServices(int)}. + * + * @hide + */ + @SystemApi public interface PrintServicesChangeListener { /** * Callback notifying that the print services changed. */ - public void onPrintServicesChanged(); + void onPrintServicesChanged(); } - /** @hide */ + /** + * Listen for changes to {@link #getPrintServiceRecommendations()}. + * + * @hide + */ + @SystemApi public interface PrintServiceRecommendationsChangeListener { /** @@ -268,22 +278,6 @@ public final class PrintManager { } args.recycle(); } break; - case MSG_NOTIFY_PRINT_SERVICES_CHANGED: { - PrintServicesChangeListenerWrapper wrapper = - (PrintServicesChangeListenerWrapper) message.obj; - PrintServicesChangeListener listener = wrapper.getListener(); - if (listener != null) { - listener.onPrintServicesChanged(); - } - } break; - case MSG_NOTIFY_PRINT_SERVICE_RECOMMENDATIONS_CHANGED: { - PrintServiceRecommendationsChangeListenerWrapper wrapper = - (PrintServiceRecommendationsChangeListenerWrapper) message.obj; - PrintServiceRecommendationsChangeListener listener = wrapper.getListener(); - if (listener != null) { - listener.onPrintServiceRecommendationsChanged(); - } - } break; } } }; @@ -325,8 +319,7 @@ public final class PrintManager { return; } if (mPrintJobStateChangeListeners == null) { - mPrintJobStateChangeListeners = new ArrayMap<PrintJobStateChangeListener, - PrintJobStateChangeListenerWrapper>(); + mPrintJobStateChangeListeners = new ArrayMap<>(); } PrintJobStateChangeListenerWrapper wrappedListener = new PrintJobStateChangeListenerWrapper(listener, mHandler); @@ -399,7 +392,7 @@ public final class PrintManager { * @param printerId the id of the printer the icon should be loaded for * @return the custom icon to be used for the printer or null if the icon is * not yet available - * @see android.print.PrinterInfo.Builder#setHasCustomPrinterIcon() + * @see android.print.PrinterInfo.Builder#setHasCustomPrinterIcon(boolean) * @hide */ public Icon getCustomPrinterIcon(PrinterId printerId) { @@ -558,12 +551,21 @@ public final class PrintManager { * Listen for changes to the installed and enabled print services. * * @param listener the listener to add + * @param handler the handler the listener is called back on * * @see android.print.PrintManager#getPrintServices + * + * @hide */ - void addPrintServicesChangeListener(@NonNull PrintServicesChangeListener listener) { + @SystemApi + public void addPrintServicesChangeListener(@NonNull PrintServicesChangeListener listener, + @Nullable Handler handler) { Preconditions.checkNotNull(listener); + if (handler == null) { + handler = mHandler; + } + if (mService == null) { Log.w(LOG_TAG, "Feature android.software.print not available"); return; @@ -572,7 +574,7 @@ public final class PrintManager { mPrintServicesChangeListeners = new ArrayMap<>(); } PrintServicesChangeListenerWrapper wrappedListener = - new PrintServicesChangeListenerWrapper(listener, mHandler); + new PrintServicesChangeListenerWrapper(listener, handler); try { mService.addPrintServicesChangeListener(wrappedListener, mUserId); mPrintServicesChangeListeners.put(listener, wrappedListener); @@ -587,8 +589,11 @@ public final class PrintManager { * @param listener the listener to remove * * @see android.print.PrintManager#getPrintServices + * + * @hide */ - void removePrintServicesChangeListener(@NonNull PrintServicesChangeListener listener) { + @SystemApi + public void removePrintServicesChangeListener(@NonNull PrintServicesChangeListener listener) { Preconditions.checkNotNull(listener); if (mService == null) { @@ -623,11 +628,12 @@ public final class PrintManager { * * @return The print service list or an empty list. * - * @see #addPrintServicesChangeListener(PrintServicesChangeListener) + * @see #addPrintServicesChangeListener(PrintServicesChangeListener, Handler) * @see #removePrintServicesChangeListener(PrintServicesChangeListener) * * @hide */ + @SystemApi public @NonNull List<PrintServiceInfo> getPrintServices(int selectionFlags) { Preconditions.checkFlagsArgument(selectionFlags, ALL_SERVICES); @@ -646,13 +652,22 @@ public final class PrintManager { * Listen for changes to the print service recommendations. * * @param listener the listener to add + * @param handler the handler the listener is called back on * * @see android.print.PrintManager#getPrintServiceRecommendations + * + * @hide */ - void addPrintServiceRecommendationsChangeListener( - @NonNull PrintServiceRecommendationsChangeListener listener) { + @SystemApi + public void addPrintServiceRecommendationsChangeListener( + @NonNull PrintServiceRecommendationsChangeListener listener, + @Nullable Handler handler) { Preconditions.checkNotNull(listener); + if (handler == null) { + handler = mHandler; + } + if (mService == null) { Log.w(LOG_TAG, "Feature android.software.print not available"); return; @@ -661,7 +676,7 @@ public final class PrintManager { mPrintServiceRecommendationsChangeListeners = new ArrayMap<>(); } PrintServiceRecommendationsChangeListenerWrapper wrappedListener = - new PrintServiceRecommendationsChangeListenerWrapper(listener, mHandler); + new PrintServiceRecommendationsChangeListenerWrapper(listener, handler); try { mService.addPrintServiceRecommendationsChangeListener(wrappedListener, mUserId); mPrintServiceRecommendationsChangeListeners.put(listener, wrappedListener); @@ -676,8 +691,11 @@ public final class PrintManager { * @param listener the listener to remove * * @see android.print.PrintManager#getPrintServiceRecommendations + * + * @hide */ - void removePrintServiceRecommendationsChangeListener( + @SystemApi + public void removePrintServiceRecommendationsChangeListener( @NonNull PrintServiceRecommendationsChangeListener listener) { Preconditions.checkNotNull(listener); @@ -715,6 +733,7 @@ public final class PrintManager { * * @hide */ + @SystemApi public @NonNull List<RecommendationInfo> getPrintServiceRecommendations() { try { List<RecommendationInfo> recommendations = @@ -1349,17 +1368,13 @@ public final class PrintManager { Handler handler = mWeakHandler.get(); PrintServicesChangeListener listener = mWeakListener.get(); if (handler != null && listener != null) { - handler.obtainMessage(MSG_NOTIFY_PRINT_SERVICES_CHANGED, this).sendToTarget(); + handler.post(listener::onPrintServicesChanged); } } public void destroy() { mWeakListener.clear(); } - - public PrintServicesChangeListener getListener() { - return mWeakListener.get(); - } } /** @@ -1381,17 +1396,12 @@ public final class PrintManager { Handler handler = mWeakHandler.get(); PrintServiceRecommendationsChangeListener listener = mWeakListener.get(); if (handler != null && listener != null) { - handler.obtainMessage(MSG_NOTIFY_PRINT_SERVICE_RECOMMENDATIONS_CHANGED, - this).sendToTarget(); + handler.post(listener::onPrintServiceRecommendationsChanged); } } public void destroy() { mWeakListener.clear(); } - - public PrintServiceRecommendationsChangeListener getListener() { - return mWeakListener.get(); - } } } diff --git a/core/java/android/print/PrintServiceRecommendationsLoader.java b/core/java/android/print/PrintServiceRecommendationsLoader.java index c6a4d5103a47..dbd219750758 100644 --- a/core/java/android/print/PrintServiceRecommendationsLoader.java +++ b/core/java/android/print/PrintServiceRecommendationsLoader.java @@ -22,6 +22,7 @@ import android.content.Loader; import android.os.Handler; import android.os.Message; import android.printservice.recommendation.RecommendationInfo; + import com.android.internal.util.Preconditions; import java.util.List; @@ -77,7 +78,7 @@ public class PrintServiceRecommendationsLoader extends Loader<List<Recommendatio } }; - mPrintManager.addPrintServiceRecommendationsChangeListener(mListener); + mPrintManager.addPrintServiceRecommendationsChangeListener(mListener, null); // Immediately deliver a result deliverResult(mPrintManager.getPrintServiceRecommendations()); diff --git a/core/java/android/print/PrintServicesLoader.java b/core/java/android/print/PrintServicesLoader.java index 4c9a69ab5e97..f686a6d8ef5a 100644 --- a/core/java/android/print/PrintServicesLoader.java +++ b/core/java/android/print/PrintServicesLoader.java @@ -22,6 +22,7 @@ import android.content.Loader; import android.os.Handler; import android.os.Message; import android.printservice.PrintServiceInfo; + import com.android.internal.util.Preconditions; import java.util.List; @@ -82,7 +83,7 @@ public class PrintServicesLoader extends Loader<List<PrintServiceInfo>> { } }; - mPrintManager.addPrintServicesChangeListener(mListener); + mPrintManager.addPrintServicesChangeListener(mListener, null); // Immediately deliver a result deliverResult(mPrintManager.getPrintServices(mSelectionFlags)); diff --git a/core/java/android/printservice/PrintServiceInfo.java b/core/java/android/printservice/PrintServiceInfo.java index 45e3d47c39de..57f122923c69 100644 --- a/core/java/android/printservice/PrintServiceInfo.java +++ b/core/java/android/printservice/PrintServiceInfo.java @@ -17,6 +17,7 @@ package android.printservice; import android.annotation.NonNull; +import android.annotation.SystemApi; import android.content.ComponentName; import android.content.Context; import android.content.pm.PackageManager; @@ -47,6 +48,7 @@ import java.io.IOException; * * @hide */ +@SystemApi public final class PrintServiceInfo implements Parcelable { private static final String LOG_TAG = PrintServiceInfo.class.getSimpleName(); @@ -86,6 +88,8 @@ public final class PrintServiceInfo implements Parcelable { * @param settingsActivityName Optional settings activity name. * @param addPrintersActivityName Optional add printers activity name. * @param advancedPrintOptionsActivityName Optional advanced print options activity. + * + * @hide */ public PrintServiceInfo(ResolveInfo resolveInfo, String settingsActivityName, String addPrintersActivityName, String advancedPrintOptionsActivityName) { @@ -110,11 +114,13 @@ public final class PrintServiceInfo implements Parcelable { /** * Creates a new instance. * - * @param resolveInfo The service resolve info. * @param context Context for accessing resources. + * @param resolveInfo The service resolve info. * @return The created instance. + * + * @hide */ - public static PrintServiceInfo create(ResolveInfo resolveInfo, Context context) { + public static PrintServiceInfo create(Context context, ResolveInfo resolveInfo) { String settingsActivityName = null; String addPrintersActivityName = null; String advancedPrintOptionsActivityName = null; @@ -177,6 +183,8 @@ public final class PrintServiceInfo implements Parcelable { * </p> * * @return The id. + * + * @hide */ public String getId() { return mId; @@ -186,6 +194,8 @@ public final class PrintServiceInfo implements Parcelable { * If the service was enabled when it was read from the system. * * @return The id. + * + * @hide */ public boolean isEnabled() { return mIsEnabled; @@ -195,6 +205,8 @@ public final class PrintServiceInfo implements Parcelable { * Mark a service as enabled or not * * @param isEnabled If the service should be marked as enabled. + * + * @hide */ public void setIsEnabled(boolean isEnabled) { mIsEnabled = isEnabled; @@ -204,6 +216,8 @@ public final class PrintServiceInfo implements Parcelable { * The service {@link ResolveInfo}. * * @return The info. + * + * @hide */ public ResolveInfo getResolveInfo() { return mResolveInfo; @@ -217,6 +231,8 @@ public final class PrintServiceInfo implements Parcelable { * </p> * * @return The settings activity name. + * + * @hide */ public String getSettingsActivityName() { return mSettingsActivityName; @@ -230,6 +246,8 @@ public final class PrintServiceInfo implements Parcelable { * </p> * * @return The add printers activity name. + * + * @hide */ public String getAddPrintersActivityName() { return mAddPrintersActivityName; @@ -243,6 +261,8 @@ public final class PrintServiceInfo implements Parcelable { * </p> * * @return The advanced print options activity name. + * + * @hide */ public String getAdvancedOptionsActivityName() { return mAdvancedPrintOptionsActivityName; diff --git a/core/java/android/printservice/recommendation/RecommendationInfo.java b/core/java/android/printservice/recommendation/RecommendationInfo.java index 65d534e45e1c..a32795631227 100644 --- a/core/java/android/printservice/recommendation/RecommendationInfo.java +++ b/core/java/android/printservice/recommendation/RecommendationInfo.java @@ -22,8 +22,14 @@ import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; import android.printservice.PrintService; + import com.android.internal.util.Preconditions; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.List; + /** * A recommendation to install a {@link PrintService print service}. * @@ -37,8 +43,8 @@ public final class RecommendationInfo implements Parcelable { /** Display name of the print service. */ private @NonNull final CharSequence mName; - /** Number of printers the print service would discover if installed. */ - private @IntRange(from = 0) final int mNumDiscoveredPrinters; + /** Printers the print service would discover if installed. */ + @NonNull private final List<InetAddress> mDiscoveredPrinters; /** If the service detects printer from multiple vendors. */ private final boolean mRecommendsMultiVendorService; @@ -48,19 +54,63 @@ public final class RecommendationInfo implements Parcelable { * * @param packageName Package name of the print service * @param name Display name of the print service - * @param numDiscoveredPrinters Number of printers the print service would discover if - * installed + * @param discoveredPrinters The {@link InetAddress addresses} of the discovered + * printers. Cannot be null or empty. * @param recommendsMultiVendorService If the service detects printer from multiple vendor */ public RecommendationInfo(@NonNull CharSequence packageName, @NonNull CharSequence name, - @IntRange(from = 0) int numDiscoveredPrinters, boolean recommendsMultiVendorService) { + @NonNull List<InetAddress> discoveredPrinters, boolean recommendsMultiVendorService) { mPackageName = Preconditions.checkStringNotEmpty(packageName); mName = Preconditions.checkStringNotEmpty(name); - mNumDiscoveredPrinters = Preconditions.checkArgumentNonnegative(numDiscoveredPrinters); + mDiscoveredPrinters = Preconditions.checkCollectionElementsNotNull(discoveredPrinters, + "discoveredPrinters"); mRecommendsMultiVendorService = recommendsMultiVendorService; } /** + * Create a new recommendation. + * + * @param packageName Package name of the print service + * @param name Display name of the print service + * @param numDiscoveredPrinters Number of printers the print service would discover if + * installed + * @param recommendsMultiVendorService If the service detects printer from multiple vendor + * + * @deprecated Use {@link RecommendationInfo(String, String, List<InetAddress>, boolean)} + * instead + */ + @Deprecated + public RecommendationInfo(@NonNull CharSequence packageName, @NonNull CharSequence name, + @IntRange(from = 0) int numDiscoveredPrinters, boolean recommendsMultiVendorService) { + throw new IllegalArgumentException("This constructor has been deprecated"); + } + + /** + * Read a list of blobs from the parcel and return it as a list of {@link InetAddress + * addresses}. + * + * @param parcel the parcel to read the blobs from + * + * @return The list of {@link InetAddress addresses} or null if no printers were found. + * + * @see #writeToParcel(Parcel, int) + */ + @NonNull private static ArrayList<InetAddress> readDiscoveredPrinters(@NonNull Parcel parcel) { + int numDiscoveredPrinters = parcel.readInt(); + ArrayList<InetAddress> discoveredPrinters = new ArrayList<>(numDiscoveredPrinters); + + for (int i = 0; i < numDiscoveredPrinters; i++) { + try { + discoveredPrinters.add(InetAddress.getByAddress(parcel.readBlob())); + } catch (UnknownHostException e) { + throw new IllegalArgumentException(e); + } + } + + return discoveredPrinters; + } + + /** * Create a new recommendation from a parcel. * * @param parcel The parcel containing the data @@ -68,7 +118,7 @@ public final class RecommendationInfo implements Parcelable { * @see #CREATOR */ private RecommendationInfo(@NonNull Parcel parcel) { - this(parcel.readCharSequence(), parcel.readCharSequence(), parcel.readInt(), + this(parcel.readCharSequence(), parcel.readCharSequence(), readDiscoveredPrinters(parcel), parcel.readByte() != 0); } @@ -87,10 +137,17 @@ public final class RecommendationInfo implements Parcelable { } /** + * @return The {@link InetAddress address} of the printers the print service would detect. + */ + @NonNull public List<InetAddress> getDiscoveredPrinters() { + return mDiscoveredPrinters; + } + + /** * @return The number of printer the print service would detect. */ public int getNumDiscoveredPrinters() { - return mNumDiscoveredPrinters; + return mDiscoveredPrinters.size(); } /** @@ -109,7 +166,14 @@ public final class RecommendationInfo implements Parcelable { public void writeToParcel(Parcel dest, int flags) { dest.writeCharSequence(mPackageName); dest.writeCharSequence(mName); - dest.writeInt(mNumDiscoveredPrinters); + + int numDiscoveredPrinters = mDiscoveredPrinters.size(); + dest.writeInt(numDiscoveredPrinters); + + for (InetAddress printer : mDiscoveredPrinters) { + dest.writeBlob(printer.getAddress()); + } + dest.writeByte((byte) (mRecommendsMultiVendorService ? 1 : 0)); } diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 660d53a91b99..cac643fc8b5f 100755 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -7091,6 +7091,8 @@ public final class Settings { INSTANT_APP_SETTINGS.add(ENABLED_INPUT_METHODS); INSTANT_APP_SETTINGS.add(ANDROID_ID); + + INSTANT_APP_SETTINGS.add(PACKAGE_VERIFIER_USER_CONSENT); } /** diff --git a/core/java/android/service/autofill/AutofillService.java b/core/java/android/service/autofill/AutofillService.java index 6f17d0e5ec77..aae22c1bb750 100644 --- a/core/java/android/service/autofill/AutofillService.java +++ b/core/java/android/service/autofill/AutofillService.java @@ -71,7 +71,7 @@ public abstract class AutofillService extends Service { * Name under which a AutoFillService component publishes information about itself. * This meta-data should reference an XML resource containing a * <code><{@link - * android.R.styleable#AutoFillService autofill-service}></code> tag. + * android.R.styleable#AutofillService autofill-service}></code> tag. * This is a a sample XML file configuring an AutoFillService: * <pre> <autofill-service * android:settingsActivity="foo.bar.SettingsActivity" @@ -204,35 +204,21 @@ public abstract class AutofillService extends Service { * to notify the result of the request. * * @param structure {@link Activity}'s view structure. - * @param data bundle containing data passed by the service on previous calls to fill. - * This bundle allows your service to keep state between fill and save requests - * as well as when filling different sections of the UI as the system will try to - * aggressively unbind from the service to conserve resources. See {@link - * FillResponse} Javadoc for examples of multiple-sections requests. + * @param data bundle containing data passed by the service in a last call to + * {@link FillResponse.Builder#setExtras(Bundle)}, if any. This bundle allows your + * service to keep state between fill and save requests as well as when filling different + * sections of the UI as the system will try to aggressively unbind from the service to + * conserve resources. + * See {@link FillResponse} for examples of multiple-sections requests. * @param flags either {@code 0} or {@link AutofillManager#FLAG_MANUAL_REQUEST}. * @param cancellationSignal signal for observing cancellation requests. The system will use * this to notify you that the fill result is no longer needed and you should stop * handling this fill request in order to save resources. * @param callback object used to notify the result of the request. */ - public void onFillRequest(@NonNull AssistStructure structure, @Nullable Bundle data, int flags, - @NonNull CancellationSignal cancellationSignal, @NonNull FillCallback callback) { - //TODO(b/33197203): make non-abstract once older method is removed - onFillRequest(structure, data, cancellationSignal, callback); - } - - /** - * @hide - * @deprecated - use {@link #onFillRequest(AssistStructure, Bundle, int, - * CancellationSignal, FillCallback)} instead - */ - //TODO(b/33197203): remove once clients are not using anymore - @Deprecated - public void onFillRequest(@NonNull AssistStructure structure, @Nullable Bundle data, - @NonNull CancellationSignal cancellationSignal, @NonNull FillCallback callback) { - // Should never be called because it was abstract before. - throw new UnsupportedOperationException("deprecated"); - } + public abstract void onFillRequest(@NonNull AssistStructure structure, @Nullable Bundle data, + int flags, @NonNull CancellationSignal cancellationSignal, + @NonNull FillCallback callback); /** * Called when user requests service to save the fields of an {@link Activity}. @@ -242,11 +228,12 @@ public abstract class AutofillService extends Service { * to notify the result of the request. * * @param structure {@link Activity}'s view structure. - * @param data bundle containing data passed by the service on previous calls to fill. - * This bundle allows your service to keep state between fill and save requests - * as well as when filling different sections of the UI as the system will try to - * aggressively unbind from the service to conserve resources. See {@link - * FillResponse} Javadoc for examples of multiple-sections requests. + * @param data bundle containing data passed by the service in a last call to + * {@link FillResponse.Builder#setExtras(Bundle)}, if any. This bundle allows your + * service to keep state between fill and save requests as well as when filling different + * sections of the UI as the system will try to aggressively unbind from the service to + * conserve resources. + * See {@link FillResponse} for examples of multiple-sections requests. * @param callback object used to notify the result of the request. */ public abstract void onSaveRequest(@NonNull AssistStructure structure, @Nullable Bundle data, diff --git a/core/java/android/service/autofill/AutofillServiceInfo.java b/core/java/android/service/autofill/AutofillServiceInfo.java index f6d40dbf3414..0f4824e47fa9 100644 --- a/core/java/android/service/autofill/AutofillServiceInfo.java +++ b/core/java/android/service/autofill/AutofillServiceInfo.java @@ -36,7 +36,6 @@ import com.android.internal.R; import java.io.IOException; -// TODO(b/33197203 , b/33802548): add CTS tests /** * {@link ServiceInfo} and meta-data about an {@link AutofillService}. * @@ -75,15 +74,8 @@ public final class AutofillServiceInfo { mServiceInfo = si; final TypedArray metaDataArray = getMetaDataArray(pm, si); if (metaDataArray != null) { - // TODO(b/35956626): inline newSettingsActivity once clients migrate - final String newSettingsActivity = - metaDataArray.getString(R.styleable.AutofillService_settingsActivity); - if (newSettingsActivity != null) { - mSettingsActivity = newSettingsActivity; - } else { - mSettingsActivity = - metaDataArray.getString(R.styleable.AutoFillService_settingsActivity); - } + mSettingsActivity = metaDataArray + .getString(R.styleable.AutofillService_settingsActivity); metaDataArray.recycle(); } else { mSettingsActivity = null; @@ -96,16 +88,11 @@ public final class AutofillServiceInfo { @Nullable private static TypedArray getMetaDataArray(PackageManager pm, ServiceInfo si) { // Check for permissions. - // TODO(b/35956626): remove check for BIND_AUTO_FILL once clients migrate - if (!Manifest.permission.BIND_AUTOFILL.equals(si.permission) - && !Manifest.permission.BIND_AUTO_FILL.equals(si.permission)) { + if (!Manifest.permission.BIND_AUTOFILL.equals(si.permission)) { Log.e(TAG, "Service does not require permission " + Manifest.permission.BIND_AUTOFILL); return null; } - // TODO(b/35956626): remove once clients migrate - final boolean oldStyle = !Manifest.permission.BIND_AUTOFILL.equals(si.permission); - // Get the AutoFill metadata, if declared. XmlResourceParser parser = si.loadXmlMetaData(pm, AutofillService.SERVICE_META_DATA); if (parser == null) { @@ -141,8 +128,7 @@ public final class AutofillServiceInfo { return null; } - return oldStyle ? res.obtainAttributes(attrs, R.styleable.AutoFillService) - : res.obtainAttributes(attrs, R.styleable.AutofillService); + return res.obtainAttributes(attrs, R.styleable.AutofillService); } finally { parser.close(); } diff --git a/core/java/android/service/autofill/Dataset.java b/core/java/android/service/autofill/Dataset.java index b7a04206a4c6..e77bd0d753ac 100644 --- a/core/java/android/service/autofill/Dataset.java +++ b/core/java/android/service/autofill/Dataset.java @@ -23,8 +23,6 @@ import android.annotation.Nullable; import android.content.IntentSender; import android.os.Parcel; import android.os.Parcelable; -import android.view.autofill.AutoFillId; -import android.view.autofill.AutoFillValue; import android.view.autofill.AutofillId; import android.view.autofill.AutofillValue; import android.widget.RemoteViews; @@ -175,15 +173,6 @@ public final class Dataset implements Parcelable { } /** - * @hide - * @deprecated TODO(b/35956626): remove once clients use other setValue() - */ - @Deprecated - public @NonNull Builder setValue(@NonNull AutoFillId id, @NonNull AutoFillValue value) { - return setValue(id.getDaRealId(), value.getDaRealValue()); - } - - /** * Sets the value of a field. * * @param id id returned by {@link diff --git a/core/java/android/service/autofill/FillResponse.java b/core/java/android/service/autofill/FillResponse.java index c43019dc297d..3117f9842b64 100644 --- a/core/java/android/service/autofill/FillResponse.java +++ b/core/java/android/service/autofill/FillResponse.java @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package android.service.autofill; import static android.view.autofill.Helper.DEBUG; @@ -23,6 +24,7 @@ import android.content.IntentSender; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; +import android.view.autofill.AutofillId; import android.view.autofill.AutofillManager; import android.widget.RemoteViews; @@ -112,7 +114,7 @@ import java.util.ArrayList; * * <p>The service could require user authentication at the {@link FillResponse} or the * {@link Dataset} level, prior to autofilling an activity - see - * {@link FillResponse.Builder#setAuthentication(IntentSender, RemoteViews)} and + * {@link FillResponse.Builder#setAuthentication(AutofillId[], IntentSender, RemoteViews)} and * {@link Dataset.Builder#setAuthentication(IntentSender)}. * * <p>It is recommended that you encrypt only the sensitive data but leave the labels unencrypted @@ -126,7 +128,7 @@ import java.util.ArrayList; * possible options) which will start your auth flow and after successfully authenticating * the user will be presented with the Home and Work options to pick one. Hence, you have * flexibility how to implement your auth while storing labels non-encrypted and data - * encrypted provides a better user experience.</p> + * encrypted provides a better user experience. */ public final class FillResponse implements Parcelable { @@ -135,6 +137,7 @@ public final class FillResponse implements Parcelable { private final Bundle mExtras; private final RemoteViews mPresentation; private final IntentSender mAuthentication; + private AutofillId[] mAuthenticationIds; private FillResponse(@NonNull Builder builder) { mDatasets = builder.mDatasets; @@ -142,6 +145,7 @@ public final class FillResponse implements Parcelable { mExtras = builder.mExtras; mPresentation = builder.mPresentation; mAuthentication = builder.mAuthentication; + mAuthenticationIds = builder.mAuthenticationIds; } /** @hide */ @@ -169,6 +173,11 @@ public final class FillResponse implements Parcelable { return mAuthentication; } + /** @hide */ + public @Nullable AutofillId[] getAuthenticationIds() { + return mAuthenticationIds; + } + /** * Builder for {@link FillResponse} objects. You must to provide at least * one dataset or set an authentication intent with a presentation view. @@ -179,6 +188,7 @@ public final class FillResponse implements Parcelable { private Bundle mExtras; private RemoteViews mPresentation; private IntentSender mAuthentication; + private AutofillId[] mAuthenticationIds; private boolean mDestroyed; /** @@ -193,7 +203,7 @@ public final class FillResponse implements Parcelable { * be encrypted. The provided {@link android.app.PendingIntent intent} must be an * activity which implements your authentication flow. Also if you provide an auth * intent you also need to specify the presentation view to be shown in the fill UI - * for the user to trigger your authentication flow.</p> + * for the user to trigger your authentication flow. * * <p>When a user triggers autofill, the system launches the provided intent * whose extras will have the {@link AutofillManager#EXTRA_ASSIST_STRUCTURE screen @@ -205,40 +215,54 @@ public final class FillResponse implements Parcelable { * user's data was locked and marked that the response needs an authentication then * in the response returned if authentication succeeds you need to provide all * available data sets some of which may need to be further authenticated, for - * example a credit card whose CVV needs to be entered.</p> + * example a credit card whose CVV needs to be entered. * * <p>If you provide an authentication intent you must also provide a presentation * which is used to visualize visualize the response for triggering the authentication - * flow.</p> + * flow. * * <p></><strong>Note:</strong> Do not make the provided pending intent * immutable by using {@link android.app.PendingIntent#FLAG_IMMUTABLE} as the - * platform needs to fill in the authentication arguments.</p> + * platform needs to fill in the authentication arguments. * * @param authentication Intent to an activity with your authentication flow. * @param presentation The presentation to visualize the response. - * @return This builder. + * @param ids id of Views that when focused will display the authentication UI affordance. * + * @return This builder. * @see android.app.PendingIntent#getIntentSender() */ - public @NonNull Builder setAuthentication(@Nullable IntentSender authentication, - @Nullable RemoteViews presentation) { + public @NonNull Builder setAuthentication(@NonNull AutofillId[] ids, + @Nullable IntentSender authentication, @Nullable RemoteViews presentation) { throwIfDestroyed(); + // TODO(b/33197203): assert ids is not null nor empty once old version is removed if (authentication == null ^ presentation == null) { throw new IllegalArgumentException("authentication and presentation" + " must be both non-null or null"); } mAuthentication = authentication; mPresentation = presentation; + mAuthenticationIds = ids; return this; } /** + * TODO(b/33197203): will be removed once clients use the version that takes ids + * @hide + * @deprecated + */ + @Deprecated + public @NonNull Builder setAuthentication(@Nullable IntentSender authentication, + @Nullable RemoteViews presentation) { + return setAuthentication(null, authentication, presentation); + } + + /** * Adds a new {@link Dataset} to this response. * * @return This builder. */ - public@NonNull Builder addDataset(@Nullable Dataset dataset) { + public @NonNull Builder addDataset(@Nullable Dataset dataset) { throwIfDestroyed(); if (dataset == null) { return this; @@ -273,6 +297,9 @@ public final class FillResponse implements Parcelable { * android.os.CancellationSignal, FillCallback)} and {@link AutofillService#onSaveRequest( * android.app.assist.AssistStructure, Bundle, SaveCallback)}. * + * <p>If this method is called on multiple {@link FillResponse} objects for the same + * activity, just the latest bundle is passed back to the service. + * * @param extras The response extras. * @return This builder. */ @@ -282,6 +309,7 @@ public final class FillResponse implements Parcelable { return this; } + /** * Builds a new {@link FillResponse} instance. You must provide at least * one dataset or some savable ids or an authentication with a presentation @@ -308,7 +336,7 @@ public final class FillResponse implements Parcelable { } ///////////////////////////////////// - // Object "contract" methods. // + // Object "contract" methods. // ///////////////////////////////////// @Override public String toString() { @@ -320,11 +348,13 @@ public final class FillResponse implements Parcelable { .append(", hasExtras=").append(mExtras != null) .append(", hasPresentation=").append(mPresentation != null) .append(", hasAuthentication=").append(mAuthentication != null) + .append(", authenticationSize=").append(mAuthenticationIds != null + ? mAuthenticationIds.length : "N/A") .toString(); } ///////////////////////////////////// - // Parcelable "contract" methods. // + // Parcelable "contract" methods. // ///////////////////////////////////// @Override @@ -337,6 +367,7 @@ public final class FillResponse implements Parcelable { parcel.writeTypedArrayList(mDatasets, flags); parcel.writeParcelable(mSaveInfo, flags); parcel.writeParcelable(mExtras, flags); + parcel.writeParcelableArray(mAuthenticationIds, flags); parcel.writeParcelable(mAuthentication, flags); parcel.writeParcelable(mPresentation, flags); } @@ -356,8 +387,8 @@ public final class FillResponse implements Parcelable { } builder.setSaveInfo(parcel.readParcelable(null)); builder.setExtras(parcel.readParcelable(null)); - builder.setAuthentication(parcel.readParcelable(null), - parcel.readParcelable(null)); + builder.setAuthentication(parcel.readParcelableArray(null, AutofillId.class), + parcel.readParcelable(null), parcel.readParcelable(null)); return builder.build(); } diff --git a/core/java/android/service/autofill/SaveInfo.java b/core/java/android/service/autofill/SaveInfo.java index 6213d27bfc70..f75b7afe4c46 100644 --- a/core/java/android/service/autofill/SaveInfo.java +++ b/core/java/android/service/autofill/SaveInfo.java @@ -25,7 +25,6 @@ import android.content.IntentSender; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; -import android.view.autofill.AutoFillId; import android.view.autofill.AutofillId; import android.view.autofill.AutofillManager; import android.view.autofill.AutofillValue; @@ -130,6 +129,16 @@ public final class SaveInfo implements Parcelable { */ public static final int SAVE_DATA_TYPE_CREDIT_CARD = 3; + /** + * Type used when the {@link FillResponse} represents just an username, without a password. + */ + public static final int SAVE_DATA_TYPE_USERNAME = 4; + + /** + * Type used when the {@link FillResponse} represents just an email address, without a password. + */ + public static final int SAVE_DATA_TYPE_EMAIL_ADDRESS = 5; + private final @SaveDataType int mType; private final CharSequence mNegativeActionTitle; private final IntentSender mNegativeActionListener; @@ -223,6 +232,8 @@ public final class SaveInfo implements Parcelable { case SAVE_DATA_TYPE_PASSWORD: case SAVE_DATA_TYPE_ADDRESS: case SAVE_DATA_TYPE_CREDIT_CARD: + case SAVE_DATA_TYPE_USERNAME: + case SAVE_DATA_TYPE_EMAIL_ADDRESS: mType = type; break; default: @@ -269,26 +280,6 @@ public final class SaveInfo implements Parcelable { return this; } - - /** - * @hide - */ - // TODO(b/33197203): temporary fix to runtime crash - public @NonNull Builder addSavableIds(@Nullable AutoFillId... ids) { - throwIfDestroyed(); - - if (ids == null || ids.length == 0) { - return this; - } - if (mRequiredIds == null) { - mRequiredIds = new AutofillId[ids.length]; - } - for (int i = 0; i < ids.length; i++) { - mRequiredIds[i] = ids[i].getDaRealId(); - } - return this; - } - /** * Sets an optional description to be shown in the UI when the user is asked to save. * diff --git a/core/java/android/service/notification/Adjustment.java b/core/java/android/service/notification/Adjustment.java index e39d53fddaad..137cf577ab7c 100644 --- a/core/java/android/service/notification/Adjustment.java +++ b/core/java/android/service/notification/Adjustment.java @@ -36,10 +36,6 @@ public final class Adjustment implements Parcelable { private final int mUser; /** - * Data type: {@code String}. See {@link NotificationChannel#getId()}. - */ - public static final String KEY_CHANNEL_ID = "key_channel_id"; - /** * Data type: ArrayList of {@code String}, where each is a representation of a * {@link android.provider.ContactsContract.Contacts#CONTENT_LOOKUP_URI}. * See {@link android.app.Notification.Builder#addPerson(String)}. diff --git a/core/java/android/service/notification/NotificationAssistantService.java b/core/java/android/service/notification/NotificationAssistantService.java index 46609df6d3f2..6ec9d69583e3 100644 --- a/core/java/android/service/notification/NotificationAssistantService.java +++ b/core/java/android/service/notification/NotificationAssistantService.java @@ -138,69 +138,6 @@ public abstract class NotificationAssistantService extends NotificationListenerS } } - /** - * Creates a notification channel that notifications can be posted to for a given package. - * - * @param pkg The package to create a channel for. - * @param channel the channel to attempt to create. - */ - public void createNotificationChannel(@NonNull String pkg, - @NonNull NotificationChannel channel) { - if (!isBound()) return; - try { - getNotificationInterface().createNotificationChannelFromAssistant( - mWrapper, pkg, channel); - } catch (RemoteException e) { - Log.v(TAG, "Unable to contact notification manager", e); - throw e.rethrowFromSystemServer(); - } - } - - /** - * Updates a notification channel for a given package. - * - * @param pkg The package to the channel belongs to. - * @param channel the channel to attempt to update. - */ - public void updateNotificationChannel(@NonNull String pkg, - @NonNull NotificationChannel channel) { - if (!isBound()) return; - try { - getNotificationInterface().updateNotificationChannelFromAssistant( - mWrapper, pkg, channel); - } catch (RemoteException e) { - Log.v(TAG, "Unable to contact notification manager", e); - throw e.rethrowFromSystemServer(); - } - } - - /** - * Returns all notification channels belonging to the given package. - */ - public List<NotificationChannel> getNotificationChannels(@NonNull String pkg) { - if (!isBound()) return null; - try { - return getNotificationInterface().getNotificationChannelsFromAssistant( - mWrapper, pkg).getList(); - } catch (RemoteException e) { - Log.v(TAG, "Unable to contact notification manager", e); - throw e.rethrowFromSystemServer(); - } - } - - /** - * Deletes the given notification channel. - */ - public void deleteNotificationChannel(@NonNull String pkg, @NonNull String channelId) { - if (!isBound()) return; - try { - getNotificationInterface().deleteNotificationChannelFromAssistant( - mWrapper, pkg, channelId); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - private class NotificationAssistantServiceWrapper extends NotificationListenerWrapper { @Override diff --git a/core/java/android/text/method/DateKeyListener.java b/core/java/android/text/method/DateKeyListener.java index e14cd2cd725d..0accbf6c74e5 100644 --- a/core/java/android/text/method/DateKeyListener.java +++ b/core/java/android/text/method/DateKeyListener.java @@ -22,6 +22,7 @@ import android.text.InputType; import android.view.KeyEvent; import com.android.internal.annotations.GuardedBy; +import com.android.internal.util.ArrayUtils; import java.util.HashMap; import java.util.LinkedHashSet; @@ -37,8 +38,11 @@ import java.util.Locale; public class DateKeyListener extends NumberKeyListener { public int getInputType() { - return InputType.TYPE_CLASS_DATETIME - | InputType.TYPE_DATETIME_VARIATION_DATE; + if (mNeedsAdvancedInput) { + return InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_NORMAL; + } else { + return InputType.TYPE_CLASS_DATETIME | InputType.TYPE_DATETIME_VARIATION_DATE; + } } @Override @@ -65,7 +69,13 @@ public class DateKeyListener extends NumberKeyListener final boolean success = NumberKeyListener.addDigits(chars, locale) && NumberKeyListener.addFormatCharsFromSkeletons( chars, locale, SKELETONS, SYMBOLS_TO_IGNORE); - mCharacters = success ? NumberKeyListener.collectionToArray(chars) : CHARACTERS; + if (success) { + mCharacters = NumberKeyListener.collectionToArray(chars); + mNeedsAdvancedInput = !ArrayUtils.containsAll(CHARACTERS, mCharacters); + } else { + mCharacters = CHARACTERS; + mNeedsAdvancedInput = false; + } } /** @@ -110,6 +120,7 @@ public class DateKeyListener extends NumberKeyListener }; private final char[] mCharacters; + private final boolean mNeedsAdvancedInput; private static final Object sLock = new Object(); @GuardedBy("sLock") diff --git a/core/java/android/text/method/DateTimeKeyListener.java b/core/java/android/text/method/DateTimeKeyListener.java index 62e3adea9b7a..551db5560128 100644 --- a/core/java/android/text/method/DateTimeKeyListener.java +++ b/core/java/android/text/method/DateTimeKeyListener.java @@ -22,6 +22,7 @@ import android.text.InputType; import android.view.KeyEvent; import com.android.internal.annotations.GuardedBy; +import com.android.internal.util.ArrayUtils; import java.util.HashMap; import java.util.LinkedHashSet; @@ -37,10 +38,13 @@ import java.util.Locale; public class DateTimeKeyListener extends NumberKeyListener { public int getInputType() { - return InputType.TYPE_CLASS_DATETIME - | InputType.TYPE_DATETIME_VARIATION_NORMAL; + if (mNeedsAdvancedInput) { + return InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_NORMAL; + } else { + return InputType.TYPE_CLASS_DATETIME | InputType.TYPE_DATETIME_VARIATION_NORMAL; + } } - + @Override @NonNull protected char[] getAcceptedChars() @@ -70,7 +74,13 @@ public class DateTimeKeyListener extends NumberKeyListener chars, locale, SKELETON_12HOUR, SYMBOLS_TO_IGNORE) && NumberKeyListener.addFormatCharsFromSkeleton( chars, locale, SKELETON_24HOUR, SYMBOLS_TO_IGNORE); - mCharacters = success ? NumberKeyListener.collectionToArray(chars) : CHARACTERS; + if (success) { + mCharacters = NumberKeyListener.collectionToArray(chars); + mNeedsAdvancedInput = !ArrayUtils.containsAll(CHARACTERS, mCharacters); + } else { + mCharacters = CHARACTERS; + mNeedsAdvancedInput = false; + } } /** @@ -114,6 +124,7 @@ public class DateTimeKeyListener extends NumberKeyListener }; private final char[] mCharacters; + private final boolean mNeedsAdvancedInput; private static final Object sLock = new Object(); @GuardedBy("sLock") diff --git a/core/java/android/text/method/DigitsKeyListener.java b/core/java/android/text/method/DigitsKeyListener.java index 26c69ab01da0..d9f2dcf2e896 100644 --- a/core/java/android/text/method/DigitsKeyListener.java +++ b/core/java/android/text/method/DigitsKeyListener.java @@ -27,6 +27,7 @@ import android.text.Spanned; import android.view.KeyEvent; import com.android.internal.annotations.GuardedBy; +import com.android.internal.util.ArrayUtils; import java.util.HashMap; import java.util.LinkedHashSet; @@ -42,8 +43,12 @@ import java.util.Locale; public class DigitsKeyListener extends NumberKeyListener { private char[] mAccepted; + private boolean mNeedsAdvancedInput; private final boolean mSign; private final boolean mDecimal; + private final boolean mStringMode; + @Nullable + private final Locale mLocale; private static final String DEFAULT_DECIMAL_POINT_CHARS = "."; private static final String DEFAULT_SIGN_CHARS = "-+"; @@ -112,11 +117,17 @@ public class DigitsKeyListener extends NumberKeyListener this(locale, false, false); } - private void setToCompat(boolean sign, boolean decimal) { + private void setToCompat() { mDecimalPointChars = DEFAULT_DECIMAL_POINT_CHARS; mSignChars = DEFAULT_SIGN_CHARS; - final int kind = (sign ? SIGN : 0) | (decimal ? DECIMAL : 0); + final int kind = (mSign ? SIGN : 0) | (mDecimal ? DECIMAL : 0); mAccepted = COMPATIBILITY_CHARACTERS[kind]; + mNeedsAdvancedInput = false; + } + + private void calculateNeedForAdvancedInput() { + final int kind = (mSign ? SIGN : 0) | (mDecimal ? DECIMAL : 0); + mNeedsAdvancedInput = !ArrayUtils.containsAll(COMPATIBILITY_CHARACTERS[kind], mAccepted); } // Takes a sign string and strips off its bidi controls, if any. @@ -144,14 +155,16 @@ public class DigitsKeyListener extends NumberKeyListener public DigitsKeyListener(@Nullable Locale locale, boolean sign, boolean decimal) { mSign = sign; mDecimal = decimal; + mStringMode = false; + mLocale = locale; if (locale == null) { - setToCompat(sign, decimal); + setToCompat(); return; } LinkedHashSet<Character> chars = new LinkedHashSet<>(); final boolean success = NumberKeyListener.addDigits(chars, locale); if (!success) { - setToCompat(sign, decimal); + setToCompat(); return; } if (sign || decimal) { @@ -161,7 +174,7 @@ public class DigitsKeyListener extends NumberKeyListener final String plusString = stripBidiControls(symbols.getPlusSignString()); if (minusString.length() > 1 || plusString.length() > 1) { // non-BMP and multi-character signs are not supported. - setToCompat(sign, decimal); + setToCompat(); return; } final char minus = minusString.charAt(0); @@ -181,7 +194,7 @@ public class DigitsKeyListener extends NumberKeyListener final String separatorString = symbols.getDecimalSeparatorString(); if (separatorString.length() > 1) { // non-BMP and multi-character decimal separators are not supported. - setToCompat(sign, decimal); + setToCompat(); return; } final Character separatorChar = Character.valueOf(separatorString.charAt(0)); @@ -190,13 +203,19 @@ public class DigitsKeyListener extends NumberKeyListener } } mAccepted = NumberKeyListener.collectionToArray(chars); + calculateNeedForAdvancedInput(); } private DigitsKeyListener(@NonNull final String accepted) { mSign = false; mDecimal = false; + mStringMode = true; + mLocale = null; mAccepted = new char[accepted.length()]; accepted.getChars(0, accepted.length(), mAccepted, 0); + // Theoretically we may need advanced input, but for backward compatibility, we don't change + // the input type. + mNeedsAdvancedInput = false; } /** @@ -280,13 +299,38 @@ public class DigitsKeyListener extends NumberKeyListener return result; } - public int getInputType() { - int contentType = InputType.TYPE_CLASS_NUMBER; - if (mSign) { - contentType |= InputType.TYPE_NUMBER_FLAG_SIGNED; + /** + * Returns a DigitsKeyListener based on an the settings of a existing DigitsKeyListener, with + * the locale modified. + * + * @hide + */ + @NonNull + public static DigitsKeyListener getInstance( + @Nullable Locale locale, + @NonNull DigitsKeyListener listener) { + if (listener.mStringMode) { + return listener; // string-mode DigitsKeyListeners have no locale. + } else { + return getInstance(locale, listener.mSign, listener.mDecimal); } - if (mDecimal) { - contentType |= InputType.TYPE_NUMBER_FLAG_DECIMAL; + } + + /** + * Returns the input type for the listener. + */ + public int getInputType() { + int contentType; + if (mNeedsAdvancedInput) { + contentType = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_NORMAL; + } else { + contentType = InputType.TYPE_CLASS_NUMBER; + if (mSign) { + contentType |= InputType.TYPE_NUMBER_FLAG_SIGNED; + } + if (mDecimal) { + contentType |= InputType.TYPE_NUMBER_FLAG_DECIMAL; + } } return contentType; } diff --git a/core/java/android/text/method/TimeKeyListener.java b/core/java/android/text/method/TimeKeyListener.java index c9f9f9fc5dde..5b1db11777dd 100644 --- a/core/java/android/text/method/TimeKeyListener.java +++ b/core/java/android/text/method/TimeKeyListener.java @@ -22,6 +22,7 @@ import android.text.InputType; import android.view.KeyEvent; import com.android.internal.annotations.GuardedBy; +import com.android.internal.util.ArrayUtils; import java.util.HashMap; import java.util.LinkedHashSet; @@ -37,8 +38,11 @@ import java.util.Locale; public class TimeKeyListener extends NumberKeyListener { public int getInputType() { - return InputType.TYPE_CLASS_DATETIME - | InputType.TYPE_DATETIME_VARIATION_TIME; + if (mNeedsAdvancedInput) { + return InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_NORMAL; + } else { + return InputType.TYPE_CLASS_DATETIME | InputType.TYPE_DATETIME_VARIATION_TIME; + } } @Override @@ -70,7 +74,13 @@ public class TimeKeyListener extends NumberKeyListener chars, locale, SKELETON_12HOUR, SYMBOLS_TO_IGNORE) && NumberKeyListener.addFormatCharsFromSkeleton( chars, locale, SKELETON_24HOUR, SYMBOLS_TO_IGNORE); - mCharacters = success ? NumberKeyListener.collectionToArray(chars) : CHARACTERS; + if (success) { + mCharacters = NumberKeyListener.collectionToArray(chars); + mNeedsAdvancedInput = !ArrayUtils.containsAll(CHARACTERS, mCharacters); + } else { + mCharacters = CHARACTERS; + mNeedsAdvancedInput = false; + } } /** @@ -114,6 +124,7 @@ public class TimeKeyListener extends NumberKeyListener }; private final char[] mCharacters; + private final boolean mNeedsAdvancedInput; private static final Object sLock = new Object(); @GuardedBy("sLock") diff --git a/core/java/android/view/KeyEvent.java b/core/java/android/view/KeyEvent.java index 5d8f336ef193..829b2b74ac9f 100644 --- a/core/java/android/view/KeyEvent.java +++ b/core/java/android/view/KeyEvent.java @@ -494,7 +494,8 @@ public class KeyEvent extends InputEvent implements Parcelable { * On TV remotes, switches to viewing live TV. */ public static final int KEYCODE_TV = 170; /** Key code constant: Window key. - * On TV remotes, toggles picture-in-picture mode or other windowing functions. */ + * On TV remotes, toggles picture-in-picture mode or other windowing functions. + * On Android Wear devices, triggers a display offset. */ public static final int KEYCODE_WINDOW = 171; /** Key code constant: Guide key. * On TV remotes, shows a programming guide. */ diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 4ffcd957d913..b4100123a5b8 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -2750,7 +2750,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * 1 PFLAG3_SCROLL_INDICATOR_END * 1 PFLAG3_ASSIST_BLOCKED * 1 PFLAG3_CLUSTER - * x * NO LONGER NEEDED, SHOULD BE REUSED * + * 1 PFLAG3_IS_AUTOFILLED * 1 PFLAG3_FINGER_DOWN * 1 PFLAG3_FOCUSED_BY_DEFAULT * 11 PFLAG3_AUTO_FILL_MODE_MASK @@ -2961,6 +2961,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback, private static final int PFLAG3_CLUSTER = 0x8000; /** + * Flag indicating that the view is autofilled + * + * @see #isAutofilled() + * @see #setAutofilled(boolean) + */ + private static final int PFLAG3_IS_AUTOFILLED = 0x10000; + + /** * Indicates that the user is currently touching the screen. * Currently used for the tooltip positioning only. */ @@ -7423,6 +7431,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityNodeInfo info = createAccessibilityNodeInfo(); structure.setChildCount(1); ViewStructure root = structure.newChild(0); + if (forAutofill) { + setAutofillId(root); + } populateVirtualStructure(root, provider, info, forAutofill); info.recycle(); } @@ -7440,10 +7451,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * <ol> * <li>Calling the proper getter method on {@link AutofillValue} to fetch the actual value. * <li>Passing the actual value to the equivalent setter in the view. - * <ol> + * </ol> * * <p>For example, a text-field view would call: - * * <pre class="prettyprint"> * CharSequence text = value.getTextValue(); * if (text != null) { @@ -7451,6 +7461,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * } * </pre> * + * <p>If the value is updated asyncronously the next call to + * {@link AutofillManager#notifyValueChanged(View)} must happen <u>after</u> the value was + * changed to the autofilled value. If not, the view will not be considered autofilled. + * * @param value value to be autofilled. */ public void autofill(@SuppressWarnings("unused") AutofillValue value) { @@ -7461,6 +7475,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * * <p>See {@link #autofill(AutofillValue)} and * {@link #onProvideAutofillVirtualStructure(ViewStructure, int)} for more info. + * <p>To indicate that a virtual view was autofilled + * <code>@android:drawable/autofilled_highlight</code> should be drawn over it until the data + * changes. * * @param values map of values to be autofilled, keyed by virtual child id. */ @@ -7491,6 +7508,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** + * @hide + */ + public boolean isAutofilled() { + return (mPrivateFlags3 & PFLAG3_IS_AUTOFILLED) != 0; + } + + /** * Gets the {@link View}'s current autofill value. * * <p>By default returns {@code null}, but views should override it (and @@ -9131,6 +9155,24 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** + * @hide + */ + @TestApi + public void setAutofilled(boolean isAutofilled) { + boolean wasChanged = isAutofilled != isAutofilled(); + + if (wasChanged) { + if (isAutofilled) { + mPrivateFlags3 |= PFLAG3_IS_AUTOFILLED; + } else { + mPrivateFlags3 &= ~PFLAG3_IS_AUTOFILLED; + } + + invalidate(); + } + } + + /** * Set whether this view should have sound effects enabled for events such as * clicking and touching. * @@ -17117,9 +17159,19 @@ public class View implements Drawable.Callback, KeyEvent.Callback, @CallSuper protected Parcelable onSaveInstanceState() { mPrivateFlags |= PFLAG_SAVE_STATE_CALLED; - if (mStartActivityRequestWho != null) { + if (mStartActivityRequestWho != null || isAutofilled()) { BaseSavedState state = new BaseSavedState(AbsSavedState.EMPTY_STATE); + + if (mStartActivityRequestWho != null) { + state.mSavedData |= BaseSavedState.START_ACTIVITY_REQUESTED_WHO_SAVED; + } + + if (isAutofilled()) { + state.mSavedData |= BaseSavedState.IS_AUTOFILLED; + } + state.mStartActivityRequestWhoSaved = mStartActivityRequestWho; + state.mIsAutofilled = isAutofilled(); return state; } return BaseSavedState.EMPTY_STATE; @@ -17189,7 +17241,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback, + "other views do not use the same id."); } if (state != null && state instanceof BaseSavedState) { - mStartActivityRequestWho = ((BaseSavedState) state).mStartActivityRequestWhoSaved; + BaseSavedState baseState = (BaseSavedState) state; + + if ((baseState.mSavedData & BaseSavedState.START_ACTIVITY_REQUESTED_WHO_SAVED) != 0) { + mStartActivityRequestWho = baseState.mStartActivityRequestWhoSaved; + } + if ((baseState.mSavedData & BaseSavedState.IS_AUTOFILLED) != 0) { + setAutofilled(baseState.mIsAutofilled); + } } } @@ -17570,6 +17629,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, // Fast path for layouts with no backgrounds if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) { dispatchDraw(canvas); + drawAutofilledHighlight(canvas); if (mOverlay != null && !mOverlay.isEmpty()) { mOverlay.getOverlayView().draw(canvas); } @@ -17870,6 +17930,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) { mPrivateFlags &= ~PFLAG_DIRTY_MASK; dispatchDraw(canvas); + drawAutofilledHighlight(canvas); if (mOverlay != null && !mOverlay.isEmpty()) { mOverlay.getOverlayView().draw(canvas); } @@ -17946,6 +18007,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, // Fast path for layouts with no backgrounds if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) { dispatchDraw(canvas); + drawAutofilledHighlight(canvas); if (mOverlay != null && !mOverlay.isEmpty()) { mOverlay.getOverlayView().draw(canvas); } @@ -18630,6 +18692,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, // Step 4, draw the children dispatchDraw(canvas); + drawAutofilledHighlight(canvas); + // Overlay is part of the content and draws beneath Foreground if (mOverlay != null && !mOverlay.isEmpty()) { mOverlay.getOverlayView().dispatchDraw(canvas); @@ -18783,6 +18847,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, canvas.restoreToCount(saveCount); + drawAutofilledHighlight(canvas); + // Overlay is part of the content and draws beneath Foreground if (mOverlay != null && !mOverlay.isEmpty()) { mOverlay.getOverlayView().dispatchDraw(canvas); @@ -20141,6 +20207,41 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** + * Get the drawable to be overlayed when a view is autofilled + * + * @return The drawable + * + * @throws IllegalStateException if the drawable could not be found. + */ + @NonNull private Drawable getAutofilledDrawable() { + // Lazily load the isAutofilled drawable. + if (mAttachInfo.mAutofilledDrawable == null) { + mAttachInfo.mAutofilledDrawable = mContext.getDrawable(R.drawable.autofilled_highlight); + + if (mAttachInfo.mAutofilledDrawable == null) { + throw new IllegalStateException( + "Could not find android:drawable/autofilled_highlight"); + } + } + + return mAttachInfo.mAutofilledDrawable; + } + + /** + * Draw {@link View#isAutofilled()} highlight over view if the view is autofilled. + * + * @param canvas The canvas to draw on + */ + private void drawAutofilledHighlight(@NonNull Canvas canvas) { + if (isAutofilled()) { + Drawable autofilledHighlight = getAutofilledDrawable(); + + autofilledHighlight.setBounds(0, 0, getWidth(), getHeight()); + autofilledHighlight.draw(canvas); + } + } + + /** * Draw any foreground content for this view. * * <p>Foreground content may consist of scroll bars, a {@link #setForeground foreground} @@ -24305,7 +24406,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * state in {@link android.view.View#onSaveInstanceState()}. */ public static class BaseSavedState extends AbsSavedState { + static final int START_ACTIVITY_REQUESTED_WHO_SAVED = 0b1; + static final int IS_AUTOFILLED = 0b10; + + // Flags that describe what data in this state is valid + int mSavedData; String mStartActivityRequestWhoSaved; + boolean mIsAutofilled; /** * Constructor used when reading from a parcel. Reads the state of the superclass. @@ -24325,7 +24432,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ public BaseSavedState(Parcel source, ClassLoader loader) { super(source, loader); + mSavedData = source.readInt(); mStartActivityRequestWhoSaved = source.readString(); + mIsAutofilled = source.readBoolean(); } /** @@ -24340,7 +24449,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, @Override public void writeToParcel(Parcel out, int flags) { super.writeToParcel(out, flags); + + out.writeInt(mSavedData); out.writeString(mStartActivityRequestWhoSaved); + out.writeBoolean(mIsAutofilled); } public static final Parcelable.Creator<BaseSavedState> CREATOR @@ -24741,6 +24853,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Drawable mAccessibilityFocusDrawable; /** + * The drawable for highlighting autofilled views. + * + * @see #isAutofilled() + */ + Drawable mAutofilledDrawable; + + /** * Show where the margins, bounds and layout bounds are for each view. */ boolean mDebugLayout = SystemProperties.getBoolean(DEBUG_LAYOUT_PROPERTY, false); diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index 6b8aab6fd5fe..666ccf486941 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -19,6 +19,7 @@ package android.view; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.SystemApi; +import android.annotation.TestApi; import android.app.KeyguardManager; import android.app.Presentation; import android.content.Context; @@ -1278,7 +1279,9 @@ public interface WindowManager extends ViewManager { /** * Never animate position changes of the window. * - * {@hide} */ + * {@hide} + */ + @TestApi public static final int PRIVATE_FLAG_NO_MOVE_ANIMATION = 0x00000040; /** Window flag: special flag to limit the size of the window to be @@ -1387,6 +1390,7 @@ public interface WindowManager extends ViewManager { * Control flags that are private to the platform. * @hide */ + @TestApi public int privateFlags; /** diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java index 35276ccd5172..41f1df719de2 100644 --- a/core/java/android/view/accessibility/AccessibilityManager.java +++ b/core/java/android/view/accessibility/AccessibilityManager.java @@ -41,6 +41,7 @@ import android.util.Log; import android.view.IWindow; import android.view.View; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.IntPair; import java.util.ArrayList; @@ -112,7 +113,7 @@ public final class AccessibilityManager { */ @SdkConstant(SdkConstant.SdkConstantType.ACTIVITY_INTENT_ACTION) public static final String ACTION_CHOOSE_ACCESSIBILITY_BUTTON = - "android.intent.action.CHOOSE_ACCESSIBILITY_BUTTON"; + "com.android.internal.intent.action.CHOOSE_ACCESSIBILITY_BUTTON"; static final Object sInstanceSync = new Object(); @@ -126,6 +127,8 @@ public final class AccessibilityManager { final Handler mHandler; + final Handler.Callback mCallback; + boolean mIsEnabled; int mRelevantEventTypes = AccessibilityEvent.TYPES_ALL_MASK; @@ -217,12 +220,12 @@ public final class AccessibilityManager { // is now off an exception will be thrown. We want to have the exception // enforcement to guard against apps that fire unnecessary accessibility // events when accessibility is off. - mHandler.obtainMessage(MyHandler.MSG_SET_STATE, state, 0).sendToTarget(); + mHandler.obtainMessage(MyCallback.MSG_SET_STATE, state, 0).sendToTarget(); } @Override public void notifyServicesStateChanged() { - mHandler.obtainMessage(MyHandler.MSG_NOTIFY_SERVICES_STATE_CHANGED).sendToTarget(); + mHandler.obtainMessage(MyCallback.MSG_NOTIFY_SERVICES_STATE_CHANGED).sendToTarget(); } @Override @@ -271,7 +274,8 @@ public final class AccessibilityManager { public AccessibilityManager(Context context, IAccessibilityManager service, int userId) { // Constructor can't be chained because we can't create an instance of an inner class // before calling another constructor. - mHandler = new MyHandler(context.getMainLooper()); + mCallback = new MyCallback(); + mHandler = new Handler(context.getMainLooper(), mCallback); mUserId = userId; synchronized (mLock) { tryConnectToServiceLocked(service); @@ -288,6 +292,7 @@ public final class AccessibilityManager { * @hide */ public AccessibilityManager(Handler handler, IAccessibilityManager service, int userId) { + mCallback = new MyCallback(); mHandler = handler; mUserId = userId; synchronized (mLock) { @@ -303,6 +308,14 @@ public final class AccessibilityManager { } /** + * @hide + */ + @VisibleForTesting + public Handler.Callback getCallback() { + return mCallback; + } + + /** * Returns if the accessibility in the system is enabled. * * @return True if accessibility is enabled, false otherwise. @@ -711,15 +724,15 @@ public final class AccessibilityManager { mIsHighTextContrastEnabled = highTextContrastEnabled; if (wasEnabled != enabled) { - mHandler.sendEmptyMessage(MyHandler.MSG_NOTIFY_ACCESSIBILITY_STATE_CHANGED); + mHandler.sendEmptyMessage(MyCallback.MSG_NOTIFY_ACCESSIBILITY_STATE_CHANGED); } if (wasTouchExplorationEnabled != touchExplorationEnabled) { - mHandler.sendEmptyMessage(MyHandler.MSG_NOTIFY_EXPLORATION_STATE_CHANGED); + mHandler.sendEmptyMessage(MyCallback.MSG_NOTIFY_EXPLORATION_STATE_CHANGED); } if (wasHighTextContrastEnabled != highTextContrastEnabled) { - mHandler.sendEmptyMessage(MyHandler.MSG_NOTIFY_HIGH_TEXT_CONTRAST_STATE_CHANGED); + mHandler.sendEmptyMessage(MyCallback.MSG_NOTIFY_HIGH_TEXT_CONTRAST_STATE_CHANGED); } } @@ -960,19 +973,15 @@ public final class AccessibilityManager { } } - private final class MyHandler extends Handler { + private final class MyCallback implements Handler.Callback { public static final int MSG_NOTIFY_ACCESSIBILITY_STATE_CHANGED = 1; public static final int MSG_NOTIFY_EXPLORATION_STATE_CHANGED = 2; public static final int MSG_NOTIFY_HIGH_TEXT_CONTRAST_STATE_CHANGED = 3; public static final int MSG_SET_STATE = 4; public static final int MSG_NOTIFY_SERVICES_STATE_CHANGED = 5; - public MyHandler(Looper looper) { - super(looper, null, false); - } - @Override - public void handleMessage(Message message) { + public boolean handleMessage(Message message) { switch (message.what) { case MSG_NOTIFY_ACCESSIBILITY_STATE_CHANGED: { handleNotifyAccessibilityStateChanged(); @@ -998,6 +1007,7 @@ public final class AccessibilityManager { } } break; } + return true; } } } diff --git a/core/java/android/view/autofill/AutoFillId.java b/core/java/android/view/autofill/AutoFillId.java deleted file mode 100644 index 081fb0289d76..000000000000 --- a/core/java/android/view/autofill/AutoFillId.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package android.view.autofill; - -import android.os.Parcel; -import android.os.Parcelable; - -/** - * @hide - * @deprecated TODO(b/35956626): remove once clients use getAutoFilltype - */ -@Deprecated -public final class AutoFillId implements Parcelable { - - private final AutofillId mRealId; - - /** @hide */ - public AutoFillId(AutofillId daRealId) { - this.mRealId = daRealId; - } - - @Override - public int hashCode() { - return mRealId.hashCode(); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) return true; - if (obj == null) return false; - if (getClass() != obj.getClass()) return false; - final AutoFillId other = (AutoFillId) obj; - return mRealId.equals(other.mRealId); - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel parcel, int flags) { - parcel.writeParcelable(mRealId, 0); - } - - private AutoFillId(Parcel parcel) { - mRealId = parcel.readParcelable(null); - } - - /** @hide */ - public AutofillId getDaRealId() { - return mRealId; - } - - /** @hide */ - public static AutoFillId forDaRealId(AutofillId id) { - return id == null ? null : new AutoFillId(id); - } - - public static final Parcelable.Creator<AutoFillId> CREATOR = - new Parcelable.Creator<AutoFillId>() { - @Override - public AutoFillId createFromParcel(Parcel source) { - return new AutoFillId(source); - } - - @Override - public AutoFillId[] newArray(int size) { - return new AutoFillId[size]; - } - }; -} diff --git a/core/java/android/view/autofill/AutoFillType.java b/core/java/android/view/autofill/AutoFillType.java deleted file mode 100644 index c508ba4eb892..000000000000 --- a/core/java/android/view/autofill/AutoFillType.java +++ /dev/null @@ -1,188 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.view.autofill; - -import static android.view.autofill.Helper.DEBUG; - -import android.os.Parcel; -import android.os.Parcelable; -import android.view.View; - -/** - * Defines the type of a object that can be used to autofill a {@link View} so the - * {@link android.service.autofill.AutofillService} can use the proper {@link AutofillValue} to - * fill it. - * - * @hide - * @deprecated TODO(b/35956626): remove once clients use getAutoFilltype - */ -@Deprecated -public final class AutoFillType implements Parcelable { - - // Cached instance for types that don't have subtype; it uses the "lazy initialization holder - // class idiom" (Effective Java, Item 71) to avoid memory utilization when autofill is not - // enabled. - private static class DefaultTypesHolder { - static final AutoFillType TEXT = new AutoFillType(TYPE_TEXT); - static final AutoFillType TOGGLE = new AutoFillType(TYPE_TOGGLE); - static final AutoFillType LIST = new AutoFillType(TYPE_LIST); - static final AutoFillType DATE = new AutoFillType(TYPE_DATE); - } - - private static final int TYPE_TEXT = 1; - private static final int TYPE_TOGGLE = 2; - private static final int TYPE_LIST = 3; - private static final int TYPE_DATE = 4; - - private final int mType; - - private AutoFillType(int type) { - mType = type; - } - - /** - * Checks if this is a type for a text field, which is filled by a {@link CharSequence}. - */ - public boolean isText() { - return mType == TYPE_TEXT; - } - - /** - * Checks if this is a a type for a togglable field, which is filled by a {@code boolean}. - */ - public boolean isToggle() { - return mType == TYPE_TOGGLE; - } - - /** - * Checks if this is a type for a selection list field, which is filled by a {@code integer} - * representing the element index inside the list (starting at {@code 0}. - */ - public boolean isList() { - return mType == TYPE_LIST; - } - - /** - * Checks if this is a type for a date and time, which is represented by a long representing - * the number of milliseconds since the standard base time known as "the epoch", namely - * January 1, 1970, 00:00:00 GMT (see {@link java.util.Date#getTime()}. - */ - public boolean isDate() { - return mType == TYPE_DATE; - } - - ///////////////////////////////////// - // Object "contract" methods. // - ///////////////////////////////////// - - @Override - public String toString() { - if (!DEBUG) return super.toString(); - - return "AutoFillType [type=" + mType + "]"; - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + mType; - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) return true; - if (obj == null) return false; - if (getClass() != obj.getClass()) return false; - final AutoFillType other = (AutoFillType) obj; - if (mType != other.mType) return false; - return true; - } - - ///////////////////////////////////// - // Parcelable "contract" methods. // - ///////////////////////////////////// - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel parcel, int flags) { - parcel.writeInt(mType); - } - - private AutoFillType(Parcel parcel) { - mType = parcel.readInt(); - } - - public static final Parcelable.Creator<AutoFillType> CREATOR = - new Parcelable.Creator<AutoFillType>() { - @Override - public AutoFillType createFromParcel(Parcel source) { - return new AutoFillType(source); - } - - @Override - public AutoFillType[] newArray(int size) { - return new AutoFillType[size]; - } - }; - - //////////////////// - // Factory methods // - //////////////////// - - /** - * Creates a text field type, which is filled by a {@link CharSequence}. - * - * <p>See {@link #isText()} for more info. - */ - public static AutoFillType forText() { - return DefaultTypesHolder.TEXT; - } - - /** - * Creates a type that can be toggled which is filled by a {@code boolean}. - * - * <p>See {@link #isToggle()} for more info. - */ - public static AutoFillType forToggle() { - return DefaultTypesHolder.TOGGLE; - } - - /** - * Creates a selection list, which is filled by a {@code integer} representing the element index - * inside the list (starting at {@code 0}. - * - * <p>See {@link #isList()} for more info. - */ - public static AutoFillType forList() { - return DefaultTypesHolder.LIST; - } - - /** - * Creates a type that represents a date. - * - * <p>See {@link #isDate()} for more info. - */ - public static AutoFillType forDate() { - return DefaultTypesHolder.DATE; - } -} diff --git a/core/java/android/view/autofill/AutoFillValue.java b/core/java/android/view/autofill/AutoFillValue.java deleted file mode 100644 index 4774d8f0a71a..000000000000 --- a/core/java/android/view/autofill/AutoFillValue.java +++ /dev/null @@ -1,181 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.view.autofill; - -import static android.view.autofill.Helper.DEBUG; - -import android.annotation.Nullable; -import android.os.Parcel; -import android.os.Parcelable; -import android.view.View; - -/** - * @hide - * @deprecated TODO(b/35956626): remove once clients use AutofillValue - */ -@Deprecated -public final class AutoFillValue implements Parcelable { - private final AutofillValue mRealValue; - - private AutoFillValue(AutofillValue daRealValue) { - this.mRealValue = daRealValue; - } - - /** - * Gets the value to autofill a text field. - * - * <p>See {@link View#AUTOFILL_TYPE_TEXT} for more info. - */ - public CharSequence getTextValue() { - return mRealValue.getTextValue(); - } - - /** - * Gets the value to autofill a toggable field. - * - * <p>See {@link View#AUTOFILL_TYPE_TOGGLE} for more info. - */ - public boolean getToggleValue() { - return mRealValue.getToggleValue(); - } - - /** - * Gets the value to autofill a selection list field. - * - * <p>See {@link View#AUTOFILL_TYPE_LIST} for more info. - */ - public int getListValue() { - return mRealValue.getListValue(); - } - - /** - * Gets the value to autofill a date field. - * - * <p>See {@link View#AUTOFILL_TYPE_DATE} for more info. - */ - public long getDateValue() { - return mRealValue.getDateValue(); - } - - ///////////////////////////////////// - // Object "contract" methods. // - ///////////////////////////////////// - - @Override - public int hashCode() { - return mRealValue.hashCode(); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) return true; - if (obj == null) return false; - if (getClass() != obj.getClass()) return false; - final AutoFillValue other = (AutoFillValue) obj; - return mRealValue.equals(other.mRealValue); - } - - @Override - public String toString() { - if (!DEBUG) return super.toString(); - - return mRealValue.toString(); - } - - ///////////////////////////////////// - // Parcelable "contract" methods. // - ///////////////////////////////////// - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel parcel, int flags) { - parcel.writeParcelable(mRealValue, 0); - } - - private AutoFillValue(Parcel parcel) { - mRealValue = parcel.readParcelable(null); - } - - public static final Parcelable.Creator<AutoFillValue> CREATOR = - new Parcelable.Creator<AutoFillValue>() { - @Override - public AutoFillValue createFromParcel(Parcel source) { - return new AutoFillValue(source); - } - - @Override - public AutoFillValue[] newArray(int size) { - return new AutoFillValue[size]; - } - }; - - //////////////////// - // Factory methods // - //////////////////// - /** - * Creates a new {@link AutoFillValue} to autofill a {@link View} representing a text field. - * - * <p>See {@link View#AUTOFILL_TYPE_TEXT} for more info. - */ - @Nullable - public static AutoFillValue forText(@Nullable CharSequence value) { - return value == null ? null : new AutoFillValue(AutofillValue.forText(value)); - } - - /** - * Creates a new {@link AutoFillValue} to autofill a {@link View} representing a toggable - * field. - * - * <p>See {@link View#AUTOFILL_TYPE_TOGGLE} for more info. - */ - public static AutoFillValue forToggle(boolean value) { - return new AutoFillValue(AutofillValue.forToggle(value)); - } - - /** - * Creates a new {@link AutoFillValue} to autofill a {@link View} representing a selection - * list. - * - * <p>See {@link View#AUTOFILL_TYPE_LIST} for more info. - */ - public static AutoFillValue forList(int value) { - return new AutoFillValue(AutofillValue.forList(value)); - } - - /** - * Creates a new {@link AutoFillValue} to autofill a {@link View} representing a date. - * - * <p>See {@link View#AUTOFILL_TYPE_DATE} for more info. - */ - public static AutoFillValue forDate(long date) { - return new AutoFillValue(AutofillValue.forDate(date)); - } - - /** @hide */ - public static AutoFillValue forDaRealValue(AutofillValue daRealValue) { - return new AutoFillValue(daRealValue); - } - - /** @hide */ - public AutofillValue getDaRealValue() { - return mRealValue; - } -} diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java index 19980fb45b4a..07fad60e5b6a 100644 --- a/core/java/android/view/autofill/AutofillManager.java +++ b/core/java/android/view/autofill/AutofillManager.java @@ -44,6 +44,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.ref.WeakReference; import java.util.List; +import java.util.Objects; /** * App entry point to the AutoFill Framework. @@ -91,6 +92,8 @@ public final class AutofillManager { */ public static final String EXTRA_DATA_EXTRAS = "android.view.autofill.extra.DATA_EXTRAS"; + static final String LAST_AUTOFILLED_DATA_TAG = "android:lastAutoFilledData"; + // Public flags start from the lowest bit /** * Indicates autofill was explicitly requested by the user. @@ -115,6 +118,9 @@ public final class AutofillManager { private boolean mHasSession; private boolean mEnabled; + /** If a view changes to this mapping the autofill operation was successful */ + @Nullable private ParcelableMap mLastAutofilledData; + /** @hide */ public interface AutofillClient { /** @@ -160,7 +166,31 @@ public final class AutofillManager { } /** - * Checkes whether autofill is enabled for the current user. + * Restore state after activity lifecycle + * + * @param savedInstanceState The state to be restored + * + * {@hide} + */ + public void onRestoreInstanceState(Bundle savedInstanceState) { + mLastAutofilledData = savedInstanceState.getParcelable(LAST_AUTOFILLED_DATA_TAG); + } + + /** + * Save state before activity lifecycle + * + * @param outState Place to store the state + * + * {@hide} + */ + public void onSaveInstanceState(Bundle outState) { + if (mLastAutofilledData != null) { + outState.putParcelable(LAST_AUTOFILLED_DATA_TAG, mLastAutofilledData); + } + } + + /** + * Checks whether autofill is enabled for the current user. * * <p>Typically used to determine whether the option to explicitly request autofill should * be offered - see {@link #requestAutofill(View)}. @@ -311,12 +341,43 @@ public final class AutofillManager { * @param view view whose value changed. */ public void notifyValueChanged(View view) { + AutofillId id = null; + boolean valueWasRead = false; + AutofillValue value = null; + + // If the session is gone some fields might still be highlighted, hence we have to remove + // the isAutofilled property even if no sessions are active. + if (mLastAutofilledData == null) { + view.setAutofilled(false); + } else { + id = getAutofillId(view); + if (mLastAutofilledData.containsKey(id)) { + value = view.getAutofillValue(); + valueWasRead = true; + + if (Objects.equals(mLastAutofilledData.get(id), value)) { + view.setAutofilled(true); + } else { + view.setAutofilled(false); + mLastAutofilledData.remove(id); + } + } else { + view.setAutofilled(false); + } + } + if (!mEnabled || !mHasSession) { return; } - final AutofillId id = getAutofillId(view); - final AutofillValue value = view.getAutofillValue(); + if (id == null) { + id = getAutofillId(view); + } + + if (!valueWasRead) { + value = view.getAutofillValue(); + } + updateSession(id, null, value, FLAG_VALUE_CHANGED); } @@ -535,6 +596,23 @@ public final class AutofillManager { } } + /** + * Sets a view as autofilled if the current value is the {code targetValue}. + * + * @param view The view that is to be autofilled + * @param targetValue The value we want to fill into view + */ + private void setAutofilledIfValuesIs(@NonNull View view, @Nullable AutofillValue targetValue) { + AutofillValue currentValue = view.getAutofillValue(); + if (Objects.equals(currentValue, targetValue)) { + if (mLastAutofilledData == null) { + mLastAutofilledData = new ParcelableMap(1); + } + mLastAutofilledData.put(getAutofillId(view), targetValue); + view.setAutofilled(true); + } + } + private void handleAutofill(IBinder windowToken, List<AutofillId> ids, List<AutofillValue> values) { final View root = WindowManagerGlobal.getInstance().getWindowView(windowToken); @@ -568,7 +646,19 @@ public final class AutofillManager { } valuesByParent.put(id.getVirtualChildId(), value); } else { + // Mark the view as to be autofilled with 'value' + if (mLastAutofilledData == null) { + mLastAutofilledData = new ParcelableMap(itemCount - i); + } + mLastAutofilledData.put(id, value); + view.autofill(value); + + // Set as autofilled if the values match now, e.g. when the value was updated + // synchronously. + // If autofill happens async, the view is set to autofilled in notifyValueChanged. + setAutofilledIfValuesIs(view, value); + numApplied++; } } diff --git a/core/java/android/view/autofill/ParcelableMap.java b/core/java/android/view/autofill/ParcelableMap.java new file mode 100644 index 000000000000..f97b1a0d5f44 --- /dev/null +++ b/core/java/android/view/autofill/ParcelableMap.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view.autofill; + +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.HashMap; +import java.util.Map; + +/** + * A parcelable HashMap for {@link AutofillId} and {@link AutofillValue} + * + * {@hide} + */ +class ParcelableMap extends HashMap<AutofillId, AutofillValue> implements Parcelable { + ParcelableMap(int size) { + super(size); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(size()); + + for (Map.Entry<AutofillId, AutofillValue> entry : entrySet()) { + dest.writeParcelable(entry.getKey(), 0); + dest.writeParcelable(entry.getValue(), 0); + } + } + + public static final Parcelable.Creator<ParcelableMap> CREATOR = + new Parcelable.Creator<ParcelableMap>() { + @Override + public ParcelableMap createFromParcel(Parcel source) { + int size = source.readInt(); + + ParcelableMap map = new ParcelableMap(size); + + for (int i = 0; i < size; i++) { + AutofillId key = source.readParcelable(null); + AutofillValue value = source.readParcelable(null); + + map.put(key, value); + } + + return map; + } + + @Override + public ParcelableMap[] newArray(int size) { + return new ParcelableMap[size]; + } + }; +} diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java index 3f72fde73b9d..6213a63e108a 100644 --- a/core/java/android/webkit/WebView.java +++ b/core/java/android/webkit/WebView.java @@ -2507,6 +2507,11 @@ public class WebView extends AbsoluteLayout } @Override + public void onMovedToDisplay(int displayId, Configuration config) { + mProvider.getViewDelegate().onMovedToDisplay(displayId, config); + } + + @Override public void setLayoutParams(ViewGroup.LayoutParams params) { mProvider.getViewDelegate().setLayoutParams(params); } diff --git a/core/java/android/webkit/WebViewProvider.java b/core/java/android/webkit/WebViewProvider.java index f01b349a746d..820b49accb65 100644 --- a/core/java/android/webkit/WebViewProvider.java +++ b/core/java/android/webkit/WebViewProvider.java @@ -378,6 +378,8 @@ public interface WebViewProvider { public void onDetachedFromWindow(); + public default void onMovedToDisplay(int displayId, Configuration config) {} + public void onVisibilityChanged(View changedView, int visibility); public void onWindowFocusChanged(boolean hasWindowFocus); diff --git a/core/java/android/widget/ListPopupWindow.java b/core/java/android/widget/ListPopupWindow.java index ab4cce479005..2e8faeec6f13 100644 --- a/core/java/android/widget/ListPopupWindow.java +++ b/core/java/android/widget/ListPopupWindow.java @@ -76,6 +76,7 @@ public class ListPopupWindow implements ShowableListMenu { private boolean mDropDownVerticalOffsetSet; private boolean mIsAnimatedFromAnchor = true; private boolean mOverlapAnchor; + private boolean mOverlapAnchorSet; private int mDropDownGravity = Gravity.NO_GRAVITY; @@ -681,7 +682,9 @@ public class ListPopupWindow implements ShowableListMenu { mPopup.setOutsideTouchable(!mForceIgnoreOutsideTouch && !mDropDownAlwaysVisible); mPopup.setTouchInterceptor(mTouchInterceptor); mPopup.setEpicenterBounds(mEpicenterBounds); - mPopup.setOverlapAnchor(mOverlapAnchor); + if (mOverlapAnchorSet) { + mPopup.setOverlapAnchor(mOverlapAnchor); + } mPopup.showAsDropDown(getAnchorView(), mDropDownHorizontalOffset, mDropDownVerticalOffset, mDropDownGravity); mDropDownList.setSelection(ListView.INVALID_POSITION); @@ -1259,6 +1262,7 @@ public class ListPopupWindow implements ShowableListMenu { * @hide */ public void setOverlapAnchor(boolean overlap) { + mOverlapAnchorSet = true; mOverlapAnchor = overlap; } diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 1b60ebc6fc27..58da92f6bd19 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -57,6 +57,7 @@ import android.graphics.RectF; import android.graphics.Typeface; import android.graphics.drawable.Drawable; import android.graphics.fonts.FontVariationAxis; +import android.icu.text.DecimalFormatSymbols; import android.os.AsyncTask; import android.os.Bundle; import android.os.LocaleList; @@ -598,6 +599,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private Layout mLayout; private boolean mLocalesChanged = false; + // True if setKeyListener() has been explicitly called + private boolean mListenerChanged = false; + // True if internationalized input should be used for numbers and date and time. + private final boolean mUseInternationalizedInput; + @ViewDebug.ExportedProperty(category = "text") private int mGravity = Gravity.TOP | Gravity.START; private boolean mHorizontallyScrolling; @@ -1356,6 +1362,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener final boolean numberPasswordInputType = variation == (EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD); + mUseInternationalizedInput = + context.getApplicationInfo().targetSdkVersion >= android.os.Build.VERSION_CODES.O; + if (inputMethod != null) { Class<?> c; @@ -1398,15 +1407,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener mEditor.mInputType = inputType = EditorInfo.TYPE_CLASS_PHONE; } else if (numeric != 0) { createEditorIfNeeded(); - mEditor.mKeyListener = DigitsKeyListener.getInstance((numeric & SIGNED) != 0, - (numeric & DECIMAL) != 0); - inputType = EditorInfo.TYPE_CLASS_NUMBER; - if ((numeric & SIGNED) != 0) { - inputType |= EditorInfo.TYPE_NUMBER_FLAG_SIGNED; - } - if ((numeric & DECIMAL) != 0) { - inputType |= EditorInfo.TYPE_NUMBER_FLAG_DECIMAL; - } + mEditor.mKeyListener = DigitsKeyListener.getInstance( + mUseInternationalizedInput ? getTextLocale() : null, + (numeric & SIGNED) != 0, + (numeric & DECIMAL) != 0); + inputType = mEditor.mKeyListener.getInputType(); mEditor.mInputType = inputType; } else if (autotext || autocap != -1) { TextKeyListener.Capitalize cap; @@ -2308,19 +2313,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * @attr ref android.R.styleable#TextView_autoText */ public void setKeyListener(KeyListener input) { + mListenerChanged = true; setKeyListenerOnly(input); fixFocusableAndClickableSettings(); if (input != null) { createEditorIfNeeded(); - try { - mEditor.mInputType = mEditor.mKeyListener.getInputType(); - } catch (IncompatibleClassChangeError e) { - mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT; - } - // Change inputType, without affecting transformation. - // No need to applySingleLine since mSingleLine is unchanged. - setInputTypeSingleLine(mSingleLine); + setInputTypeFromEditor(); } else { if (mEditor != null) mEditor.mInputType = EditorInfo.TYPE_NULL; } @@ -2329,6 +2328,17 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (imm != null) imm.restartInput(this); } + private void setInputTypeFromEditor() { + try { + mEditor.mInputType = mEditor.mKeyListener.getInputType(); + } catch (IncompatibleClassChangeError e) { + mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT; + } + // Change inputType, without affecting transformation. + // No need to applySingleLine since mSingleLine is unchanged. + setInputTypeSingleLine(mSingleLine); + } + private void setKeyListenerOnly(KeyListener input) { if (mEditor == null && input == null) return; // null is the default value @@ -3390,6 +3400,29 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return mTextPaint.getTextLocales(); } + private void changeListenerLocaleTo(@NonNull Locale locale) { + if (mListenerChanged) { + // If a listener has been explicitly set, don't change it. We may break something. + return; + } + if (mEditor != null) { + KeyListener listener = mEditor.mKeyListener; + if (listener instanceof DigitsKeyListener) { + listener = DigitsKeyListener.getInstance(locale, (DigitsKeyListener) listener); + } else if (listener instanceof DateKeyListener) { + listener = DateKeyListener.getInstance(locale); + } else if (listener instanceof TimeKeyListener) { + listener = TimeKeyListener.getInstance(locale); + } else if (listener instanceof DateTimeKeyListener) { + listener = DateTimeKeyListener.getInstance(locale); + } else { + return; + } + setKeyListenerOnly(listener); + setInputTypeFromEditor(); + } + } + /** * Set the default {@link LocaleList} of the text in this TextView to a one-member list * containing just the given value. @@ -3401,6 +3434,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener public void setTextLocale(@NonNull Locale locale) { mLocalesChanged = true; mTextPaint.setTextLocale(locale); + changeListenerLocaleTo(locale); if (mLayout != null) { nullLayouts(); requestLayout(); @@ -3422,6 +3456,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener public void setTextLocales(@NonNull @Size(min = 1) LocaleList locales) { mLocalesChanged = true; mTextPaint.setTextLocales(locales); + changeListenerLocaleTo(locales.get(0)); if (mLayout != null) { nullLayouts(); requestLayout(); @@ -3433,7 +3468,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener protected void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); if (!mLocalesChanged) { - mTextPaint.setTextLocales(LocaleList.getDefault()); + final LocaleList locales = LocaleList.getDefault(); + mTextPaint.setTextLocales(locales); + changeListenerLocaleTo(locales.get(0)); if (mLayout != null) { nullLayouts(); requestLayout(); @@ -5567,26 +5604,35 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener input = TextKeyListener.getInstance(autotext, cap); } else if (cls == EditorInfo.TYPE_CLASS_NUMBER) { input = DigitsKeyListener.getInstance( + mUseInternationalizedInput ? getTextLocale() : null, (type & EditorInfo.TYPE_NUMBER_FLAG_SIGNED) != 0, (type & EditorInfo.TYPE_NUMBER_FLAG_DECIMAL) != 0); + if (mUseInternationalizedInput) { + type = input.getInputType(); // Override type, if necessary for i18n. + } } else if (cls == EditorInfo.TYPE_CLASS_DATETIME) { + final Locale locale = mUseInternationalizedInput ? getTextLocale() : null; switch (type & EditorInfo.TYPE_MASK_VARIATION) { case EditorInfo.TYPE_DATETIME_VARIATION_DATE: - input = DateKeyListener.getInstance(); + input = DateKeyListener.getInstance(locale); break; case EditorInfo.TYPE_DATETIME_VARIATION_TIME: - input = TimeKeyListener.getInstance(); + input = TimeKeyListener.getInstance(locale); break; default: - input = DateTimeKeyListener.getInstance(); + input = DateTimeKeyListener.getInstance(locale); break; } + if (mUseInternationalizedInput) { + type = input.getInputType(); // Override type, if necessary for i18n. + } } else if (cls == EditorInfo.TYPE_CLASS_PHONE) { input = DialerKeyListener.getInstance(); } else { input = TextKeyListener.getInstance(); } setRawInputType(type); + mListenerChanged = false; if (direct) { createEditorIfNeeded(); mEditor.mKeyListener = input; @@ -11013,6 +11059,26 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return TextDirectionHeuristics.LTR; } + if (mEditor != null + && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) + == EditorInfo.TYPE_CLASS_PHONE) { + // Phone numbers must be in the direction of the locale's digits. Most locales have LTR + // digits, but some locales, such as those written in the Adlam or N'Ko scripts, have + // RTL digits. + final DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(getTextLocale()); + final String zero = symbols.getDigitStrings()[0]; + // In case the zero digit is multi-codepoint, just use the first codepoint to determine + // direction. + final int firstCodepoint = zero.codePointAt(0); + final byte digitDirection = Character.getDirectionality(firstCodepoint); + if (digitDirection == Character.DIRECTIONALITY_RIGHT_TO_LEFT + || digitDirection == Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC) { + return TextDirectionHeuristics.RTL; + } else { + return TextDirectionHeuristics.LTR; + } + } + // Always need to resolve layout direction first final boolean defaultIsRtl = (getLayoutDirection() == LAYOUT_DIRECTION_RTL); diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java index b2636578d6a2..d19ffad37c95 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -114,7 +114,7 @@ public class BatteryStatsImpl extends BatteryStats { private static final int MAGIC = 0xBA757475; // 'BATSTATS' // Current on-disk Parcel version - private static final int VERSION = 152 + (USE_OLD_HISTORY ? 1000 : 0); + private static final int VERSION = 153 + (USE_OLD_HISTORY ? 1000 : 0); // Maximum number of items we will record in the history. private static final int MAX_HISTORY_ITEMS = 2000; @@ -2006,107 +2006,92 @@ public class BatteryStatsImpl extends BatteryStats { * State for keeping track of two DurationTimers with different TimeBases, presumably where one * TimeBase is effectively a subset of the other. */ - public static class DualTimer { - // mMainTimer typically tracks the total time. May be pooled (but since it's a durationTimer, - // it also has the unpooled getTotalDurationMsLocked() for STATS_SINCE_CHARGED). - private final DurationTimer mMainTimer; + public static class DualTimer extends DurationTimer { + // This class both is a DurationTimer and also holds a second DurationTimer. + // The main timer (this) typically tracks the total time. It may be pooled (but since it's a + // durationTimer, it also has the unpooled getTotalDurationMsLocked() for + // STATS_SINCE_CHARGED). // mSubTimer typically tracks only part of the total time, such as background time, as // determined by a subTimeBase. It is NOT pooled. private final DurationTimer mSubTimer; /** - * Creates a DualTimer to hold a mMainTimer and a mSubTimer. - * The mMainTimer is based on the given timeBase and timerPool. + * Creates a DualTimer to hold a main timer (this) and a mSubTimer. + * The main timer (this) is based on the given timeBase and timerPool. * The mSubTimer is based on the given subTimeBase. The mSubTimer is not pooled, even if - * the mMainTimer is. + * the main timer is. */ public DualTimer(Clocks clocks, Uid uid, int type, ArrayList<StopwatchTimer> timerPool, TimeBase timeBase, TimeBase subTimeBase, Parcel in) { - mMainTimer = new DurationTimer(clocks, uid, type, timerPool, timeBase, in); + super(clocks, uid, type, timerPool, timeBase, in); mSubTimer = new DurationTimer(clocks, uid, type, null, subTimeBase, in); } /** - * Creates a DualTimer to hold a mMainTimer and a mSubTimer. - * The mMainTimer is based on the given timeBase and timerPool. + * Creates a DualTimer to hold a main timer (this) and a mSubTimer. + * The main timer (this) is based on the given timeBase and timerPool. * The mSubTimer is based on the given subTimeBase. The mSubTimer is not pooled, even if - * the mMainTimer is. + * the main timer is. */ public DualTimer(Clocks clocks, Uid uid, int type, ArrayList<StopwatchTimer> timerPool, TimeBase timeBase, TimeBase subTimeBase) { - mMainTimer = new DurationTimer(clocks, uid, type, timerPool, timeBase); + super(clocks, uid, type, timerPool, timeBase); mSubTimer = new DurationTimer(clocks, uid, type, null, subTimeBase); } - /** Get the main timer. */ - public DurationTimer getMainTimer() { - return mMainTimer; - } - /** Get the secondary timer. */ + @Override public DurationTimer getSubTimer() { return mSubTimer; } + @Override public void startRunningLocked(long elapsedRealtimeMs) { - mMainTimer.startRunningLocked(elapsedRealtimeMs); + super.startRunningLocked(elapsedRealtimeMs); mSubTimer.startRunningLocked(elapsedRealtimeMs); } + @Override public void stopRunningLocked(long elapsedRealtimeMs) { - mMainTimer.stopRunningLocked(elapsedRealtimeMs); + super.stopRunningLocked(elapsedRealtimeMs); mSubTimer.stopRunningLocked(elapsedRealtimeMs); } + @Override public void stopAllRunningLocked(long elapsedRealtimeMs) { - mMainTimer.stopAllRunningLocked(elapsedRealtimeMs); + super.stopAllRunningLocked(elapsedRealtimeMs); mSubTimer.stopAllRunningLocked(elapsedRealtimeMs); } - public void setMark(long elapsedRealtimeMs) { - mMainTimer.setMark(elapsedRealtimeMs); - mSubTimer.setMark(elapsedRealtimeMs); - } - + @Override public boolean reset(boolean detachIfReset) { boolean active = false; - active |= !mMainTimer.reset(detachIfReset); + active |= !super.reset(detachIfReset); active |= !mSubTimer.reset(detachIfReset); return !active; } + @Override public void detach() { - mMainTimer.detach(); + super.detach(); mSubTimer.detach(); } - /** - * Writes a possibly null DualTimer to a Parcel. - * - * @param out the Parcel to which to write. - * @param t a DualTimer, or null. - */ - public static void writeDualTimerToParcel(Parcel out, DualTimer t, long elapsedRealtimeUs) { - if (t != null) { - out.writeInt(1); - t.writeToParcel(out, elapsedRealtimeUs); - } else { - out.writeInt(0); - } - } - + @Override public void writeToParcel(Parcel out, long elapsedRealtimeUs) { - mMainTimer.writeToParcel(out, elapsedRealtimeUs); + super.writeToParcel(out, elapsedRealtimeUs); mSubTimer.writeToParcel(out, elapsedRealtimeUs); } + @Override public void writeSummaryFromParcelLocked(Parcel out, long elapsedRealtimeUs) { - mMainTimer.writeSummaryFromParcelLocked(out, elapsedRealtimeUs); + super.writeSummaryFromParcelLocked(out, elapsedRealtimeUs); mSubTimer.writeSummaryFromParcelLocked(out, elapsedRealtimeUs); } + @Override public void readSummaryFromParcelLocked(Parcel in) { - mMainTimer.readSummaryFromParcelLocked(in); + super.readSummaryFromParcelLocked(in); mSubTimer.readSummaryFromParcelLocked(in); } } @@ -5488,7 +5473,7 @@ public class BatteryStatsImpl extends BatteryStats { /** * The statistics we have collected for this uid's jobs. */ - final OverflowArrayMap<StopwatchTimer> mJobStats; + final OverflowArrayMap<DualTimer> mJobStats; /** * The statistics we have collected for this uid's sensor activations. @@ -5533,10 +5518,10 @@ public class BatteryStatsImpl extends BatteryStats { mBsi.mOnBatteryTimeBase); } }; - mJobStats = mBsi.new OverflowArrayMap<StopwatchTimer>(uid) { - @Override public StopwatchTimer instantiateObject() { - return new StopwatchTimer(mBsi.mClocks, Uid.this, JOB, null, - mBsi.mOnBatteryTimeBase); + mJobStats = mBsi.new OverflowArrayMap<DualTimer>(uid) { + @Override public DualTimer instantiateObject() { + return new DualTimer(mBsi.mClocks, Uid.this, JOB, null, + mBsi.mOnBatteryTimeBase, mOnBatteryBackgroundTimeBase); } }; @@ -5918,7 +5903,7 @@ public class BatteryStatsImpl extends BatteryStats { if (mWifiScanTimer == null) { return 0; } - return mWifiScanTimer.getMainTimer().getTotalTimeLocked(elapsedRealtimeUs, which); + return mWifiScanTimer.getTotalTimeLocked(elapsedRealtimeUs, which); } @Override @@ -5926,12 +5911,12 @@ public class BatteryStatsImpl extends BatteryStats { if (mWifiScanTimer == null) { return 0; } - return mWifiScanTimer.getMainTimer().getCountLocked(which); + return mWifiScanTimer.getCountLocked(which); } @Override public int getWifiScanBackgroundCount(int which) { - if (mWifiScanTimer == null) { + if (mWifiScanTimer == null || mWifiScanTimer.getSubTimer() == null) { return 0; } return mWifiScanTimer.getSubTimer().getCountLocked(which); @@ -5943,12 +5928,12 @@ public class BatteryStatsImpl extends BatteryStats { return 0; } final long elapsedRealtimeMs = (elapsedRealtimeUs + 500) / 1000; - return mWifiScanTimer.getMainTimer().getTotalDurationMsLocked(elapsedRealtimeMs) * 1000; + return mWifiScanTimer.getTotalDurationMsLocked(elapsedRealtimeMs) * 1000; } @Override public long getWifiScanBackgroundTime(final long elapsedRealtimeUs) { - if (mWifiScanTimer == null) { + if (mWifiScanTimer == null || mWifiScanTimer.getSubTimer() == null) { return 0; } final long elapsedRealtimeMs = (elapsedRealtimeUs + 500) / 1000; @@ -6008,10 +5993,7 @@ public class BatteryStatsImpl extends BatteryStats { @Override public Timer getBluetoothScanTimer() { - if (mBluetoothScanTimer == null) { - return null; - } - return mBluetoothScanTimer.getMainTimer(); + return mBluetoothScanTimer; } @Override @@ -6361,9 +6343,9 @@ public class BatteryStatsImpl extends BatteryStats { } } mSyncStats.cleanup(); - final ArrayMap<String, StopwatchTimer> jobStats = mJobStats.getMap(); + final ArrayMap<String, DualTimer> jobStats = mJobStats.getMap(); for (int ij=jobStats.size()-1; ij>=0; ij--) { - StopwatchTimer timer = jobStats.valueAt(ij); + DualTimer timer = jobStats.valueAt(ij); if (timer.reset(false)) { jobStats.removeAt(ij); timer.detach(); @@ -6531,12 +6513,12 @@ public class BatteryStatsImpl extends BatteryStats { Timer.writeTimerToParcel(out, timer, elapsedRealtimeUs); } - final ArrayMap<String, StopwatchTimer> jobStats = mJobStats.getMap(); + final ArrayMap<String, DualTimer> jobStats = mJobStats.getMap(); int NJ = jobStats.size(); out.writeInt(NJ); for (int ij=0; ij<NJ; ij++) { out.writeString(jobStats.keyAt(ij)); - StopwatchTimer timer = jobStats.valueAt(ij); + DualTimer timer = jobStats.valueAt(ij); Timer.writeTimerToParcel(out, timer, elapsedRealtimeUs); } @@ -6756,8 +6738,8 @@ public class BatteryStatsImpl extends BatteryStats { for (int j = 0; j < numJobs; j++) { String jobName = in.readString(); if (in.readInt() != 0) { - mJobStats.add(jobName, new StopwatchTimer(mBsi.mClocks, Uid.this, JOB, null, - timeBase, in)); + mJobStats.add(jobName, new DualTimer(mBsi.mClocks, Uid.this, JOB, null, + mBsi.mOnBatteryTimeBase, mOnBatteryBackgroundTimeBase, in)); } } @@ -7196,15 +7178,12 @@ public class BatteryStatsImpl extends BatteryStats { } void writeToParcelLocked(Parcel out, long elapsedRealtimeUs) { - DualTimer.writeDualTimerToParcel(out, mTimer, elapsedRealtimeUs); + Timer.writeTimerToParcel(out, mTimer, elapsedRealtimeUs); } @Override public Timer getSensorTime() { - if (mTimer == null) { - return null; - } - return mTimer.getMainTimer(); + return mTimer; } @Override @@ -8023,7 +8002,7 @@ public class BatteryStatsImpl extends BatteryStats { } public void readJobSummaryFromParcelLocked(String name, Parcel in) { - StopwatchTimer timer = mJobStats.instantiateObject(); + DualTimer timer = mJobStats.instantiateObject(); timer.readSummaryFromParcelLocked(in); mJobStats.add(name, timer); } @@ -8084,14 +8063,14 @@ public class BatteryStatsImpl extends BatteryStats { } public void noteStartJobLocked(String name, long elapsedRealtimeMs) { - StopwatchTimer t = mJobStats.startObject(name); + DualTimer t = mJobStats.startObject(name); if (t != null) { t.startRunningLocked(elapsedRealtimeMs); } } public void noteStopJobLocked(String name, long elapsedRealtimeMs) { - StopwatchTimer t = mJobStats.stopObject(name); + DualTimer t = mJobStats.stopObject(name); if (t != null) { t.stopRunningLocked(elapsedRealtimeMs); } @@ -9149,7 +9128,7 @@ public class BatteryStatsImpl extends BatteryStats { final Uid uid = mUidStats.valueAt(i); // Sum the total scan power for all apps. - totalScanTimeMs += uid.mWifiScanTimer.getMainTimer().getTimeSinceMarkLocked( + totalScanTimeMs += uid.mWifiScanTimer.getTimeSinceMarkLocked( elapsedRealtimeMs * 1000) / 1000; // Sum the total time holding wifi lock for all apps. @@ -9170,7 +9149,7 @@ public class BatteryStatsImpl extends BatteryStats { for (int i = 0; i < uidStatsSize; i++) { final Uid uid = mUidStats.valueAt(i); - long scanTimeSinceMarkMs = uid.mWifiScanTimer.getMainTimer().getTimeSinceMarkLocked( + long scanTimeSinceMarkMs = uid.mWifiScanTimer.getTimeSinceMarkLocked( elapsedRealtimeMs * 1000) / 1000; if (scanTimeSinceMarkMs > 0) { // Set the new mark so that next time we get new data since this point. @@ -9444,7 +9423,7 @@ public class BatteryStatsImpl extends BatteryStats { continue; } - totalScanTimeMs += u.mBluetoothScanTimer.getMainTimer().getTimeSinceMarkLocked( + totalScanTimeMs += u.mBluetoothScanTimer.getTimeSinceMarkLocked( elapsedRealtimeMs * 1000) / 1000; } @@ -9465,7 +9444,7 @@ public class BatteryStatsImpl extends BatteryStats { continue; } - long scanTimeSinceMarkMs = u.mBluetoothScanTimer.getMainTimer().getTimeSinceMarkLocked( + long scanTimeSinceMarkMs = u.mBluetoothScanTimer.getTimeSinceMarkLocked( elapsedRealtimeMs * 1000) / 1000; if (scanTimeSinceMarkMs > 0) { // Set the new mark so that next time we get new data since this point. @@ -11526,7 +11505,7 @@ public class BatteryStatsImpl extends BatteryStats { syncStats.valueAt(is).writeSummaryFromParcelLocked(out, NOWREAL_SYS); } - final ArrayMap<String, StopwatchTimer> jobStats = u.mJobStats.getMap(); + final ArrayMap<String, DualTimer> jobStats = u.mJobStats.getMap(); int NJ = jobStats.size(); out.writeInt(NJ); for (int ij=0; ij<NJ; ij++) { diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java index b2a2fec879e6..67bce8c59309 100644 --- a/core/java/com/android/internal/os/ZygoteInit.java +++ b/core/java/com/android/internal/os/ZygoteInit.java @@ -548,10 +548,11 @@ public class ZygoteInit { final int dexFlags = 0; final String compilerFilter = "speed"; final String uuid = StorageManager.UUID_PRIVATE_INTERNAL; + final String seInfo = null; try { installd.dexopt(classPathElement, Process.SYSTEM_UID, packageName, instructionSet, dexoptNeeded, outputPath, dexFlags, compilerFilter, - uuid, sharedLibraries); + uuid, sharedLibraries, seInfo); } catch (RemoteException | ServiceSpecificException e) { // Ignore (but log), we need this on the classpath for fallback mode. Log.w(TAG, "Failed compiling classpath element for system server: " diff --git a/core/java/com/android/internal/policy/BackdropFrameRenderer.java b/core/java/com/android/internal/policy/BackdropFrameRenderer.java index a70209c705c0..1abb59b006dd 100644 --- a/core/java/com/android/internal/policy/BackdropFrameRenderer.java +++ b/core/java/com/android/internal/policy/BackdropFrameRenderer.java @@ -74,7 +74,6 @@ public class BackdropFrameRenderer extends Thread implements Choreographer.Frame private final Rect mOldStableInsets = new Rect(); private final Rect mSystemInsets = new Rect(); private final Rect mStableInsets = new Rect(); - private final Rect mTmpRect = new Rect(); public BackdropFrameRenderer(DecorView decorView, ThreadedRenderer renderer, Rect initialBounds, Drawable resizingBackgroundDrawable, Drawable captionBackgroundDrawable, @@ -371,6 +370,12 @@ public class BackdropFrameRenderer extends Thread implements Choreographer.Frame DisplayListCanvas canvas = mSystemBarBackgroundNode.start(width, height); mSystemBarBackgroundNode.setLeftTopRightBottom(left, top, left + width, top + height); final int topInset = DecorView.getColorViewTopInset(mStableInsets.top, mSystemInsets.top); + final int bottomInset = DecorView.getColorViewBottomInset(stableInsets.bottom, + systemInsets.bottom); + final int rightInset = DecorView.getColorViewRightInset(stableInsets.right, + systemInsets.right); + final int leftInset = DecorView.getColorViewLeftInset(stableInsets.left, + systemInsets.left); if (mStatusBarColor != null) { mStatusBarColor.setBounds(0, 0, left + width, topInset); mStatusBarColor.draw(canvas); @@ -380,8 +385,14 @@ public class BackdropFrameRenderer extends Thread implements Choreographer.Frame // don't want the navigation bar background be moving around when resizing in docked mode. // However, we need it for the transitions into/out of docked mode. if (mNavigationBarColor != null && fullscreen) { - DecorView.getNavigationBarRect(width, height, stableInsets, systemInsets, mTmpRect); - mNavigationBarColor.setBounds(mTmpRect); + final int size = DecorView.getNavBarSize(bottomInset, rightInset, leftInset); + if (DecorView.isNavBarToRightEdge(bottomInset, rightInset)) { + mNavigationBarColor.setBounds(width - size, 0, width, height); + } else if (DecorView.isNavBarToLeftEdge(bottomInset, leftInset)) { + mNavigationBarColor.setBounds(0, 0, size, height); + } else { + mNavigationBarColor.setBounds(0, height - size, width, height); + } mNavigationBarColor.draw(canvas); } mSystemBarBackgroundNode.end(canvas); diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java index 653796dc39e7..a8e16c96acfa 100644 --- a/core/java/com/android/internal/policy/DecorView.java +++ b/core/java/com/android/internal/policy/DecorView.java @@ -119,21 +119,6 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind // The height of a window which has not in DIP. private final static int DECOR_SHADOW_UNFOCUSED_HEIGHT_IN_DIP = 5; - public static final ColorViewAttributes STATUS_BAR_COLOR_VIEW_ATTRIBUTES = - new ColorViewAttributes(SYSTEM_UI_FLAG_FULLSCREEN, FLAG_TRANSLUCENT_STATUS, - Gravity.TOP, Gravity.LEFT, Gravity.RIGHT, - Window.STATUS_BAR_BACKGROUND_TRANSITION_NAME, - com.android.internal.R.id.statusBarBackground, - FLAG_FULLSCREEN); - - public static final ColorViewAttributes NAVIGATION_BAR_COLOR_VIEW_ATTRIBUTES = - new ColorViewAttributes( - SYSTEM_UI_FLAG_HIDE_NAVIGATION, FLAG_TRANSLUCENT_NAVIGATION, - Gravity.BOTTOM, Gravity.RIGHT, Gravity.LEFT, - Window.NAVIGATION_BAR_BACKGROUND_TRANSITION_NAME, - com.android.internal.R.id.navigationBarBackground, - 0 /* hideWindowFlag */); - // Cludge to address b/22668382: Set the shadow size to the maximum so that the layer // size calculation takes the shadow size into account. We set the elevation currently // to max until the first layout command has been executed. @@ -177,10 +162,18 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind // View added at runtime to draw under the navigation bar area private View mNavigationGuard; - private final ColorViewState mStatusColorViewState = - new ColorViewState(STATUS_BAR_COLOR_VIEW_ATTRIBUTES); - private final ColorViewState mNavigationColorViewState = - new ColorViewState(NAVIGATION_BAR_COLOR_VIEW_ATTRIBUTES); + private final ColorViewState mStatusColorViewState = new ColorViewState( + SYSTEM_UI_FLAG_FULLSCREEN, FLAG_TRANSLUCENT_STATUS, + Gravity.TOP, Gravity.LEFT, Gravity.RIGHT, + Window.STATUS_BAR_BACKGROUND_TRANSITION_NAME, + com.android.internal.R.id.statusBarBackground, + FLAG_FULLSCREEN); + private final ColorViewState mNavigationColorViewState = new ColorViewState( + SYSTEM_UI_FLAG_HIDE_NAVIGATION, FLAG_TRANSLUCENT_NAVIGATION, + Gravity.BOTTOM, Gravity.RIGHT, Gravity.LEFT, + Window.NAVIGATION_BAR_BACKGROUND_TRANSITION_NAME, + com.android.internal.R.id.navigationBarBackground, + 0 /* hideWindowFlag */); private final Interpolator mShowInterpolator; private final Interpolator mHideInterpolator; @@ -990,50 +983,35 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind return false; } - public static int getColorViewTopInset(int stableTop, int systemTop) { + static int getColorViewTopInset(int stableTop, int systemTop) { return Math.min(stableTop, systemTop); } - public static int getColorViewBottomInset(int stableBottom, int systemBottom) { + static int getColorViewBottomInset(int stableBottom, int systemBottom) { return Math.min(stableBottom, systemBottom); } - public static int getColorViewRightInset(int stableRight, int systemRight) { + static int getColorViewRightInset(int stableRight, int systemRight) { return Math.min(stableRight, systemRight); } - public static int getColorViewLeftInset(int stableLeft, int systemLeft) { + static int getColorViewLeftInset(int stableLeft, int systemLeft) { return Math.min(stableLeft, systemLeft); } - public static boolean isNavBarToRightEdge(int bottomInset, int rightInset) { + static boolean isNavBarToRightEdge(int bottomInset, int rightInset) { return bottomInset == 0 && rightInset > 0; } - public static boolean isNavBarToLeftEdge(int bottomInset, int leftInset) { + static boolean isNavBarToLeftEdge(int bottomInset, int leftInset) { return bottomInset == 0 && leftInset > 0; } - public static int getNavBarSize(int bottomInset, int rightInset, int leftInset) { + static int getNavBarSize(int bottomInset, int rightInset, int leftInset) { return isNavBarToRightEdge(bottomInset, rightInset) ? rightInset : isNavBarToLeftEdge(bottomInset, leftInset) ? leftInset : bottomInset; } - public static void getNavigationBarRect(int canvasWidth, int canvasHeight, Rect stableInsets, - Rect contentInsets, Rect outRect) { - final int bottomInset = getColorViewBottomInset(stableInsets.bottom, contentInsets.bottom); - final int leftInset = getColorViewLeftInset(stableInsets.left, contentInsets.left); - final int rightInset = getColorViewLeftInset(stableInsets.right, contentInsets.right); - final int size = getNavBarSize(bottomInset, rightInset, leftInset); - if (isNavBarToRightEdge(bottomInset, rightInset)) { - outRect.set(canvasWidth - size, 0, canvasWidth, canvasHeight); - } else if (isNavBarToLeftEdge(bottomInset, leftInset)) { - outRect.set(0, 0, size, canvasHeight); - } else { - outRect.set(0, canvasHeight - size, canvasWidth, canvasHeight); - } - } - WindowInsets updateColorViews(WindowInsets insets, boolean animate) { WindowManager.LayoutParams attrs = mWindow.getAttributes(); int sysUiVisibility = attrs.systemUiVisibility | getWindowSystemUiVisibility(); @@ -1153,14 +1131,9 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind } private int calculateStatusBarColor() { - return calculateStatusBarColor(mWindow.getAttributes().flags, - mSemiTransparentStatusBarColor, mWindow.mStatusBarColor); - } - - public static int calculateStatusBarColor(int flags, int semiTransparentStatusBarColor, - int statusBarColor) { - return (flags & FLAG_TRANSLUCENT_STATUS) != 0 ? semiTransparentStatusBarColor - : (flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0 ? statusBarColor + int flags = mWindow.getAttributes().flags; + return (flags & FLAG_TRANSLUCENT_STATUS) != 0 ? mSemiTransparentStatusBarColor + : (flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0 ? mWindow.mStatusBarColor : Color.BLACK; } @@ -1187,9 +1160,13 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind private void updateColorViewInt(final ColorViewState state, int sysUiVis, int color, int size, boolean verticalBar, boolean seascape, int sideMargin, boolean animate, boolean force) { - state.present = state.attributes.isPresent(sysUiVis, mWindow.getAttributes().flags, force); - boolean show = state.attributes.isVisible(state.present, color, - mWindow.getAttributes().flags, force); + state.present = (sysUiVis & state.systemUiHideFlag) == 0 + && (mWindow.getAttributes().flags & state.hideWindowFlag) == 0 + && ((mWindow.getAttributes().flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0 + || force); + boolean show = state.present + && (color & Color.BLACK) != 0 + && ((mWindow.getAttributes().flags & state.translucentFlag) == 0 || force); boolean showView = show && !isResizing() && size > 0; boolean visibilityChanged = false; @@ -1198,15 +1175,15 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind int resolvedHeight = verticalBar ? LayoutParams.MATCH_PARENT : size; int resolvedWidth = verticalBar ? size : LayoutParams.MATCH_PARENT; int resolvedGravity = verticalBar - ? (seascape ? state.attributes.seascapeGravity : state.attributes.horizontalGravity) - : state.attributes.verticalGravity; + ? (seascape ? state.seascapeGravity : state.horizontalGravity) + : state.verticalGravity; if (view == null) { if (showView) { state.view = view = new View(mContext); view.setBackgroundColor(color); - view.setTransitionName(state.attributes.transitionName); - view.setId(state.attributes.id); + view.setTransitionName(state.transitionName); + view.setId(state.id); visibilityChanged = true; view.setVisibility(INVISIBLE); state.targetVisibility = VISIBLE; @@ -2292,15 +2269,6 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind boolean visible; int color; - final ColorViewAttributes attributes; - - ColorViewState(ColorViewAttributes attributes) { - this.attributes = attributes; - } - } - - public static class ColorViewAttributes { - final int id; final int systemUiHideFlag; final int translucentFlag; @@ -2310,9 +2278,9 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind final String transitionName; final int hideWindowFlag; - private ColorViewAttributes(int systemUiHideFlag, int translucentFlag, int verticalGravity, - int horizontalGravity, int seascapeGravity, String transitionName, int id, - int hideWindowFlag) { + ColorViewState(int systemUiHideFlag, + int translucentFlag, int verticalGravity, int horizontalGravity, + int seascapeGravity, String transitionName, int id, int hideWindowFlag) { this.id = id; this.systemUiHideFlag = systemUiHideFlag; this.translucentFlag = translucentFlag; @@ -2322,24 +2290,6 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind this.transitionName = transitionName; this.hideWindowFlag = hideWindowFlag; } - - public boolean isPresent(int sysUiVis, int windowFlags, boolean force) { - return (sysUiVis & systemUiHideFlag) == 0 - && (windowFlags & hideWindowFlag) == 0 - && ((windowFlags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0 - || force); - } - - public boolean isVisible(boolean present, int color, int windowFlags, boolean force) { - return present - && (color & Color.BLACK) != 0 - && ((windowFlags & translucentFlag) == 0 || force); - } - - public boolean isVisible(int sysUiVis, int color, int windowFlags, boolean force) { - final boolean present = isPresent(sysUiVis, windowFlags, force); - return isVisible(present, color, windowFlags, force); - } } /** diff --git a/core/java/com/android/internal/util/ArrayUtils.java b/core/java/com/android/internal/util/ArrayUtils.java index f4dd5a62112c..2c8e4e0414b6 100644 --- a/core/java/com/android/internal/util/ArrayUtils.java +++ b/core/java/com/android/internal/util/ArrayUtils.java @@ -236,6 +236,29 @@ public class ArrayUtils { return false; } + public static boolean contains(@Nullable char[] array, char value) { + if (array == null) return false; + for (char element : array) { + if (element == value) { + return true; + } + } + return false; + } + + /** + * Test if all {@code check} items are contained in {@code array}. + */ + public static <T> boolean containsAll(@Nullable char[] array, char[] check) { + if (check == null) return true; + for (char checkItem : check) { + if (!contains(array, checkItem)) { + return false; + } + } + return true; + } + public static long total(@Nullable long[] array) { long total = 0; if (array != null) { diff --git a/core/jni/Android.mk b/core/jni/Android.mk index da5d04d63457..33fabfc4c2a5 100644 --- a/core/jni/Android.mk +++ b/core/jni/Android.mk @@ -208,6 +208,7 @@ LOCAL_C_INCLUDES += \ $(TOP)/system/core/include \ $(TOP)/system/core/libappfuse/include \ $(TOP)/system/media/camera/include \ + $(TOP)/system/media/private/camera/include \ $(TOP)/system/netd/include \ external/giflib \ external/pdfium/public \ diff --git a/core/jni/android/graphics/Picture.cpp b/core/jni/android/graphics/Picture.cpp index 7b381b41bde5..bfb25113a7e9 100644 --- a/core/jni/android/graphics/Picture.cpp +++ b/core/jni/android/graphics/Picture.cpp @@ -44,7 +44,7 @@ Canvas* Picture::beginRecording(int width, int height) { mWidth = width; mHeight = height; SkCanvas* canvas = mRecorder->beginRecording(SkIntToScalar(width), SkIntToScalar(height)); - return Canvas::create_canvas(canvas); + return Canvas::create_canvas(canvas, Canvas::XformToSRGB::kDefer); } void Picture::endRecording() { diff --git a/core/jni/android/graphics/Shader.cpp b/core/jni/android/graphics/Shader.cpp index 1a353302d7d8..214d97c213e4 100644 --- a/core/jni/android/graphics/Shader.cpp +++ b/core/jni/android/graphics/Shader.cpp @@ -68,11 +68,18 @@ static jlong BitmapShader_constructor(JNIEnv* env, jobject o, jlong matrixPtr, j } sk_sp<SkImage> image = SkMakeImageFromRasterBitmap(bitmap, kNever_SkCopyPixelsMode); - sk_sp<SkShader> shader = image->makeShader( - (SkShader::TileMode)tileModeX, (SkShader::TileMode)tileModeY, matrix); + sk_sp<SkShader> baseShader = image->makeShader( + (SkShader::TileMode)tileModeX, (SkShader::TileMode)tileModeY); - ThrowIAE_IfNull(env, shader.get()); - return reinterpret_cast<jlong>(shader.release()); + SkShader* shader; + if (matrix) { + shader = baseShader->makeWithLocalMatrix(*matrix).release(); + } else { + shader = baseShader.release(); + } + + ThrowIAE_IfNull(env, shader); + return reinterpret_cast<jlong>(shader); } /////////////////////////////////////////////////////////////////////////////////////////////// @@ -95,9 +102,16 @@ static jlong LinearGradient_create1(JNIEnv* env, jobject o, jlong matrixPtr, #error Need to convert float array to SkScalar array before calling the following function. #endif - SkShader* shader = SkGradientShader::MakeLinear(pts, + sk_sp<SkShader> baseShader(SkGradientShader::MakeLinear(pts, reinterpret_cast<const SkColor*>(colorValues), pos, count, - static_cast<SkShader::TileMode>(tileMode), sGradientShaderFlags, matrix).release(); + static_cast<SkShader::TileMode>(tileMode), sGradientShaderFlags, NULL)); + + SkShader* shader; + if (matrix) { + shader = baseShader->makeWithLocalMatrix(*matrix).release(); + } else { + shader = baseShader.release(); + } env->ReleaseIntArrayElements(colorArray, const_cast<jint*>(colorValues), JNI_ABORT); ThrowIAE_IfNull(env, shader); @@ -116,8 +130,15 @@ static jlong LinearGradient_create2(JNIEnv* env, jobject o, jlong matrixPtr, colors[0] = color0; colors[1] = color1; - SkShader* s = SkGradientShader::MakeLinear(pts, colors, NULL, 2, - static_cast<SkShader::TileMode>(tileMode), sGradientShaderFlags, matrix).release(); + sk_sp<SkShader> baseShader(SkGradientShader::MakeLinear(pts, colors, NULL, 2, + static_cast<SkShader::TileMode>(tileMode), sGradientShaderFlags, NULL)); + + SkShader* s; + if (matrix) { + s = baseShader->makeWithLocalMatrix(*matrix).release(); + } else { + s = baseShader.release(); + } ThrowIAE_IfNull(env, s); return reinterpret_cast<jlong>(s); @@ -141,9 +162,17 @@ static jlong RadialGradient_create1(JNIEnv* env, jobject, jlong matrixPtr, jfloa #error Need to convert float array to SkScalar array before calling the following function. #endif - SkShader* shader = SkGradientShader::MakeRadial(center, radius, + sk_sp<SkShader> baseShader = SkGradientShader::MakeRadial(center, radius, reinterpret_cast<const SkColor*>(colorValues), pos, count, - static_cast<SkShader::TileMode>(tileMode), sGradientShaderFlags, matrix).release(); + static_cast<SkShader::TileMode>(tileMode), sGradientShaderFlags, NULL); + + SkShader* shader; + if (matrix) { + shader = baseShader->makeWithLocalMatrix(*matrix).release(); + } else { + shader = baseShader.release(); + } + env->ReleaseIntArrayElements(colorArray, const_cast<jint*>(colorValues), JNI_ABORT); @@ -161,10 +190,17 @@ static jlong RadialGradient_create2(JNIEnv* env, jobject, jlong matrixPtr, jfloa colors[0] = color0; colors[1] = color1; - SkShader* s = SkGradientShader::MakeRadial(center, radius, colors, NULL, 2, - static_cast<SkShader::TileMode>(tileMode), sGradientShaderFlags, matrix).release(); - ThrowIAE_IfNull(env, s); - return reinterpret_cast<jlong>(s); + sk_sp<SkShader> baseShader = SkGradientShader::MakeRadial(center, radius, colors, NULL, 2, + static_cast<SkShader::TileMode>(tileMode), sGradientShaderFlags, NULL); + + SkShader* shader; + if (matrix) { + shader = baseShader->makeWithLocalMatrix(*matrix).release(); + } else { + shader = baseShader.release(); + } + ThrowIAE_IfNull(env, shader); + return reinterpret_cast<jlong>(shader); } /////////////////////////////////////////////////////////////////////////////// @@ -182,8 +218,17 @@ static jlong SweepGradient_create1(JNIEnv* env, jobject, jlong matrixPtr, jfloat #error Need to convert float array to SkScalar array before calling the following function. #endif - SkShader* shader = SkGradientShader::MakeSweep(x, y, reinterpret_cast<const SkColor*>(colors), - pos, count, sGradientShaderFlags, matrix).release(); + sk_sp<SkShader> baseShader = SkGradientShader::MakeSweep(x, y, + reinterpret_cast<const SkColor*>(colors), pos, count, + sGradientShaderFlags, NULL); + + SkShader* shader; + if (matrix) { + shader = baseShader->makeWithLocalMatrix(*matrix).release(); + } else { + shader = baseShader.release(); + } + env->ReleaseIntArrayElements(jcolors, const_cast<jint*>(colors), JNI_ABORT); ThrowIAE_IfNull(env, shader); @@ -196,10 +241,18 @@ static jlong SweepGradient_create2(JNIEnv* env, jobject, jlong matrixPtr, jfloat SkColor colors[2]; colors[0] = color0; colors[1] = color1; - SkShader* s = SkGradientShader::MakeSweep(x, y, colors, NULL, 2, - sGradientShaderFlags, matrix).release(); - ThrowIAE_IfNull(env, s); - return reinterpret_cast<jlong>(s); + + sk_sp<SkShader> baseShader = SkGradientShader::MakeSweep(x, y, colors, + NULL, 2, sGradientShaderFlags, NULL); + + SkShader* shader; + if (matrix) { + shader = baseShader->makeWithLocalMatrix(*matrix).release(); + } else { + shader = baseShader.release(); + } + ThrowIAE_IfNull(env, shader); + return reinterpret_cast<jlong>(shader); } /////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/core/jni/android/graphics/pdf/PdfDocument.cpp b/core/jni/android/graphics/pdf/PdfDocument.cpp index d233f7b6805e..abc3599a48c1 100644 --- a/core/jni/android/graphics/pdf/PdfDocument.cpp +++ b/core/jni/android/graphics/pdf/PdfDocument.cpp @@ -21,6 +21,7 @@ #include "CreateJavaOutputStreamAdaptor.h" +#include "SkColorSpaceXformCanvas.h" #include "SkDocument.h" #include "SkPicture.h" #include "SkPictureRecorder.h" @@ -94,8 +95,10 @@ public: SkCanvas* canvas = document->beginPage(page->mWidth, page->mHeight, &(page->mContentRect)); + std::unique_ptr<SkCanvas> toSRGBCanvas = + SkCreateColorSpaceXformCanvas(canvas, SkColorSpace::MakeSRGB()); - canvas->drawPicture(page->mPicture); + toSRGBCanvas->drawPicture(page->mPicture); document->endPage(); } @@ -128,7 +131,7 @@ static jlong nativeStartPage(JNIEnv* env, jobject thiz, jlong documentPtr, PdfDocument* document = reinterpret_cast<PdfDocument*>(documentPtr); SkCanvas* canvas = document->startPage(pageWidth, pageHeight, contentLeft, contentTop, contentRight, contentBottom); - return reinterpret_cast<jlong>(Canvas::create_canvas(canvas)); + return reinterpret_cast<jlong>(Canvas::create_canvas(canvas, Canvas::XformToSRGB::kDefer)); } static void nativeFinishPage(JNIEnv* env, jobject thiz, jlong documentPtr) { diff --git a/core/jni/android_hardware_SensorManager.cpp b/core/jni/android_hardware_SensorManager.cpp index dae431057209..520302ee02b9 100644 --- a/core/jni/android_hardware_SensorManager.cpp +++ b/core/jni/android_hardware_SensorManager.cpp @@ -282,6 +282,25 @@ static jint nativeConfigDirectChannel(JNIEnv *_env, jclass _this, jlong sensorMa return mgr->configureDirectChannel(channelHandle, sensorHandle, rate); } +static jint nativeSetOperationParameter(JNIEnv *_env, jclass _this, jlong sensorManager, + jint type, jfloatArray floats, jintArray ints) { + SensorManager* mgr = reinterpret_cast<SensorManager*>(sensorManager); + Vector<float> floatVector; + Vector<int32_t> int32Vector; + + if (floats != nullptr) { + floatVector.resize(_env->GetArrayLength(floats)); + _env->GetFloatArrayRegion(floats, 0, _env->GetArrayLength(floats), floatVector.editArray()); + } + + if (ints != nullptr) { + int32Vector.resize(_env->GetArrayLength(ints)); + _env->GetIntArrayRegion(ints, 0, _env->GetArrayLength(ints), int32Vector.editArray()); + } + + return mgr->setOperationParameter(type, floatVector, int32Vector); +} + //---------------------------------------------------------------------------- class Receiver : public LooperCallback { @@ -499,6 +518,10 @@ static const JNINativeMethod gSystemSensorManagerMethods[] = { {"nativeConfigDirectChannel", "(JIII)I", (void*)nativeConfigDirectChannel }, + + {"nativeSetOperationParameter", + "(JI[F[I)I", + (void*)nativeSetOperationParameter }, }; static const JNINativeMethod gBaseEventQueueMethods[] = { diff --git a/core/jni/android_hardware_UsbDeviceConnection.cpp b/core/jni/android_hardware_UsbDeviceConnection.cpp index 6814506ad797..ba08bce7d17b 100644 --- a/core/jni/android_hardware_UsbDeviceConnection.cpp +++ b/core/jni/android_hardware_UsbDeviceConnection.cpp @@ -220,7 +220,7 @@ android_hardware_UsbDeviceConnection_bulk_request(JNIEnv *env, jobject thiz, } static jobject -android_hardware_UsbDeviceConnection_request_wait(JNIEnv *env, jobject thiz, jint timeoutMillis) +android_hardware_UsbDeviceConnection_request_wait(JNIEnv *env, jobject thiz, jlong timeoutMillis) { struct usb_device* device = get_device_from_object(env, thiz); if (!device) { @@ -243,8 +243,17 @@ android_hardware_UsbDeviceConnection_request_wait(JNIEnv *env, jobject thiz, jin - currentTime).count()); int error = errno; + if (request != NULL) { + break; + } + currentTime = steady_clock::now(); - if (request != NULL || error != EAGAIN || currentTime >= endTime) { + if (currentTime >= endTime) { + jniThrowException(env, "java/util/concurrent/TimeoutException", ""); + break; + } + + if (error != EAGAIN) { break; } }; @@ -300,7 +309,7 @@ static const JNINativeMethod method_table[] = { (void *)android_hardware_UsbDeviceConnection_control_request}, {"native_bulk_request", "(I[BIII)I", (void *)android_hardware_UsbDeviceConnection_bulk_request}, - {"native_request_wait", "(I)Landroid/hardware/usb/UsbRequest;", + {"native_request_wait", "(J)Landroid/hardware/usb/UsbRequest;", (void *)android_hardware_UsbDeviceConnection_request_wait}, { "native_get_serial", "()Ljava/lang/String;", (void*)android_hardware_UsbDeviceConnection_get_serial }, diff --git a/core/jni/android_hardware_camera2_CameraMetadata.cpp b/core/jni/android_hardware_camera2_CameraMetadata.cpp index 78a5735279ea..c11ce0fe338e 100644 --- a/core/jni/android_hardware_camera2_CameraMetadata.cpp +++ b/core/jni/android_hardware_camera2_CameraMetadata.cpp @@ -36,6 +36,7 @@ #include <android/hardware/ICameraService.h> #include <binder/IServiceManager.h> #include <camera/CameraMetadata.h> +#include <camera_metadata_hidden.h> #include <camera/VendorTagDescriptor.h> #include <nativehelper/ScopedUtfChars.h> #include <nativehelper/ScopedPrimitiveArray.h> @@ -162,8 +163,10 @@ struct Helpers { extern "C" { static jobject CameraMetadata_getAllVendorKeys(JNIEnv* env, jobject thiz, jclass keyType); -static jint CameraMetadata_getTagFromKey(JNIEnv *env, jobject thiz, jstring keyName); -static jint CameraMetadata_getTypeFromTag(JNIEnv *env, jobject thiz, jint tag); +static jint CameraMetadata_getTagFromKey(JNIEnv *env, jobject thiz, jstring keyName, jlong vendorId); +static jint CameraMetadata_getTagFromKeyLocal(JNIEnv *env, jobject thiz, jstring keyName); +static jint CameraMetadata_getTypeFromTag(JNIEnv *env, jobject thiz, jint tag, jlong vendorId); +static jint CameraMetadata_getTypeFromTagLocal(JNIEnv *env, jobject thiz, jint tag); static jint CameraMetadata_setupGlobalVendorTagDescriptor(JNIEnv *env, jobject thiz); // Less safe access to native pointer. Does NOT throw any Java exceptions if NULL. @@ -286,7 +289,9 @@ static jbyteArray CameraMetadata_readValues(JNIEnv *env, jobject thiz, jint tag) CameraMetadata* metadata = CameraMetadata_getPointerThrow(env, thiz); if (metadata == NULL) return NULL; - int tagType = get_camera_metadata_tag_type(tag); + const camera_metadata_t *metaBuffer = metadata->getAndLock(); + int tagType = get_local_camera_metadata_tag_type(tag, metaBuffer); + metadata->unlock(metaBuffer); if (tagType == -1) { jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException", "Tag (%d) did not have a type", tag); @@ -323,7 +328,9 @@ static void CameraMetadata_writeValues(JNIEnv *env, jobject thiz, jint tag, jbyt CameraMetadata* metadata = CameraMetadata_getPointerThrow(env, thiz); if (metadata == NULL) return; - int tagType = get_camera_metadata_tag_type(tag); + const camera_metadata_t *metaBuffer = metadata->getAndLock(); + int tagType = get_local_camera_metadata_tag_type(tag, metaBuffer); + metadata->unlock(metaBuffer); if (tagType == -1) { jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException", "Tag (%d) did not have a type", tag); @@ -528,14 +535,11 @@ static void CameraMetadata_writeToParcel(JNIEnv *env, jobject thiz, jobject parc static const JNINativeMethod gCameraMetadataMethods[] = { // static methods - { "nativeGetAllVendorKeys", - "(Ljava/lang/Class;)Ljava/util/ArrayList;", - (void *)CameraMetadata_getAllVendorKeys}, { "nativeGetTagFromKey", - "(Ljava/lang/String;)I", + "(Ljava/lang/String;J)I", (void *)CameraMetadata_getTagFromKey }, { "nativeGetTypeFromTag", - "(I)I", + "(IJ)I", (void *)CameraMetadata_getTypeFromTag }, { "nativeSetupGlobalVendorTagDescriptor", "()I", @@ -559,6 +563,12 @@ static const JNINativeMethod gCameraMetadataMethods[] = { { "nativeSwap", "(L" CAMERA_METADATA_CLASS_NAME ";)V", (void *)CameraMetadata_swap }, + { "nativeGetTagFromKeyLocal", + "(Ljava/lang/String;)I", + (void *)CameraMetadata_getTagFromKeyLocal }, + { "nativeGetTypeFromTagLocal", + "(I)I", + (void *)CameraMetadata_getTypeFromTagLocal }, { "nativeReadValues", "(I)[B", (void *)CameraMetadata_readValues }, @@ -568,6 +578,9 @@ static const JNINativeMethod gCameraMetadataMethods[] = { { "nativeDump", "()V", (void *)CameraMetadata_dump }, + { "nativeGetAllVendorKeys", + "(Ljava/lang/Class;)Ljava/util/ArrayList;", + (void *)CameraMetadata_getAllVendorKeys}, // Parcelable interface { "nativeReadFromParcel", "(Landroid/os/Parcel;)V", @@ -590,11 +603,11 @@ int register_android_hardware_camera2_CameraMetadata(JNIEnv *env) gMetadataOffsets.mResultKey = MakeGlobalRefOrDie(env, resultKeyClazz); gMetadataOffsets.mCharacteristicsConstr = GetMethodIDOrDie(env, gMetadataOffsets.mCharacteristicsKey, "<init>", - "(Ljava/lang/String;Ljava/lang/Class;)V"); + "(Ljava/lang/String;Ljava/lang/Class;J)V"); gMetadataOffsets.mRequestConstr = GetMethodIDOrDie(env, - gMetadataOffsets.mRequestKey, "<init>", "(Ljava/lang/String;Ljava/lang/Class;)V"); + gMetadataOffsets.mRequestKey, "<init>", "(Ljava/lang/String;Ljava/lang/Class;J)V"); gMetadataOffsets.mResultConstr = GetMethodIDOrDie(env, - gMetadataOffsets.mResultKey, "<init>", "(Ljava/lang/String;Ljava/lang/Class;)V"); + gMetadataOffsets.mResultKey, "<init>", "(Ljava/lang/String;Ljava/lang/Class;J)V"); // Store global references for primitive array types used by Keys jclass byteClazz = FindClassOrDie(env, "[B"); @@ -630,13 +643,76 @@ int register_android_hardware_camera2_CameraMetadata(JNIEnv *env) extern "C" { -static jobject CameraMetadata_getAllVendorKeys(JNIEnv* env, jobject thiz, jclass keyType) { +static jint CameraMetadata_getTypeFromTagLocal(JNIEnv *env, jobject thiz, jint tag) { + CameraMetadata* metadata = CameraMetadata_getPointerNoThrow(env, thiz); + metadata_vendor_id_t vendorId = CAMERA_METADATA_INVALID_VENDOR_ID; + if (metadata) { + const camera_metadata_t *metaBuffer = metadata->getAndLock(); + vendorId = get_camera_metadata_vendor_id(metaBuffer); + metadata->unlock(metaBuffer); + } + + int tagType = get_local_camera_metadata_tag_type_vendor_id(tag, vendorId); + if (tagType == -1) { + jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException", + "Tag (%d) did not have a type", tag); + return -1; + } + + return tagType; +} + +static jint CameraMetadata_getTagFromKeyLocal(JNIEnv *env, jobject thiz, jstring keyName) { + ScopedUtfChars keyScoped(env, keyName); + const char *key = keyScoped.c_str(); + if (key == NULL) { + // exception thrown by ScopedUtfChars + return 0; + } + ALOGV("%s (key = '%s')", __FUNCTION__, key); + + uint32_t tag = 0; + sp<VendorTagDescriptor> vTags; + CameraMetadata* metadata = CameraMetadata_getPointerNoThrow(env, thiz); + if (metadata) { + sp<VendorTagDescriptorCache> cache = VendorTagDescriptorCache::getGlobalVendorTagCache(); + if (cache.get()) { + const camera_metadata_t *metaBuffer = metadata->getAndLock(); + metadata_vendor_id_t vendorId = get_camera_metadata_vendor_id(metaBuffer); + metadata->unlock(metaBuffer); + cache->getVendorTagDescriptor(vendorId, &vTags); + } + } + status_t res = CameraMetadata::getTagFromName(key, vTags.get(), &tag); + if (res != OK) { + jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException", + "Could not find tag for key '%s')", key); + } + return tag; +} + +static jobject CameraMetadata_getAllVendorKeys(JNIEnv* env, jobject thiz, jclass keyType) { + metadata_vendor_id_t vendorId = CAMERA_METADATA_INVALID_VENDOR_ID; // Get all vendor tags sp<VendorTagDescriptor> vTags = VendorTagDescriptor::getGlobalVendorTagDescriptor(); if (vTags.get() == nullptr) { - // No vendor tags. - return NULL; + sp<VendorTagDescriptorCache> cache = VendorTagDescriptorCache::getGlobalVendorTagCache(); + if (cache.get() == nullptr) { + // No vendor tags. + return nullptr; + } + + CameraMetadata* metadata = CameraMetadata_getPointerThrow(env, thiz); + if (metadata == NULL) return NULL; + + const camera_metadata_t *metaBuffer = metadata->getAndLock(); + vendorId = get_camera_metadata_vendor_id(metaBuffer); + cache->getVendorTagDescriptor(vendorId, &vTags); + metadata->unlock(metaBuffer); + if (vTags.get() == nullptr) { + return nullptr; + } } int count = vTags->getTagCount(); @@ -714,7 +790,7 @@ static jobject CameraMetadata_getAllVendorKeys(JNIEnv* env, jobject thiz, jclass return NULL; } - jobject key = env->NewObject(keyClazz, keyConstr, name, valueClazz); + jobject key = env->NewObject(keyClazz, keyConstr, name, valueClazz, vendorId); if (env->ExceptionCheck()) { return NULL; } @@ -731,8 +807,8 @@ static jobject CameraMetadata_getAllVendorKeys(JNIEnv* env, jobject thiz, jclass return arrayList; } -static jint CameraMetadata_getTagFromKey(JNIEnv *env, jobject thiz, jstring keyName) { - +static jint CameraMetadata_getTagFromKey(JNIEnv *env, jobject thiz, jstring keyName, + jlong vendorId) { ScopedUtfChars keyScoped(env, keyName); const char *key = keyScoped.c_str(); if (key == NULL) { @@ -744,6 +820,13 @@ static jint CameraMetadata_getTagFromKey(JNIEnv *env, jobject thiz, jstring keyN uint32_t tag = 0; sp<VendorTagDescriptor> vTags = VendorTagDescriptor::getGlobalVendorTagDescriptor(); + if (vTags.get() == nullptr) { + sp<VendorTagDescriptorCache> cache = VendorTagDescriptorCache::getGlobalVendorTagCache(); + if (cache.get() != nullptr) { + cache->getVendorTagDescriptor(vendorId, &vTags); + } + } + status_t res = CameraMetadata::getTagFromName(key, vTags.get(), &tag); if (res != OK) { jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException", @@ -752,8 +835,8 @@ static jint CameraMetadata_getTagFromKey(JNIEnv *env, jobject thiz, jstring keyN return tag; } -static jint CameraMetadata_getTypeFromTag(JNIEnv *env, jobject thiz, jint tag) { - int tagType = get_camera_metadata_tag_type(tag); +static jint CameraMetadata_getTypeFromTag(JNIEnv *env, jobject thiz, jint tag, jlong vendorId) { + int tagType = get_local_camera_metadata_tag_type_vendor_id(tag, vendorId); if (tagType == -1) { jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException", "Tag (%d) did not have a type", tag); @@ -787,8 +870,24 @@ static jint CameraMetadata_setupGlobalVendorTagDescriptor(JNIEnv *env, jobject t __FUNCTION__, res.toString8().string()); return res.serviceSpecificErrorCode(); } + if (0 < desc->getTagCount()) { + err = VendorTagDescriptor::setAsGlobalVendorTagDescriptor(desc); + } else { + sp<VendorTagDescriptorCache> cache = new VendorTagDescriptorCache(); + binder::Status res = cameraService->getCameraVendorTagCache(/*out*/cache.get()); + if (res.serviceSpecificErrorCode() == hardware::ICameraService::ERROR_DISCONNECTED) { + // No camera module available, not an error on devices with no cameras + VendorTagDescriptorCache::clearGlobalVendorTagCache(); + return OK; + } else if (!res.isOk()) { + VendorTagDescriptorCache::clearGlobalVendorTagCache(); + ALOGE("%s: Failed to setup vendor tag cache: %s", + __FUNCTION__, res.toString8().string()); + return res.serviceSpecificErrorCode(); + } - err = VendorTagDescriptor::setAsGlobalVendorTagDescriptor(desc); + err = VendorTagDescriptorCache::setAsGlobalVendorTagCache(cache); + } if (err != OK) { return hardware::ICameraService::ERROR_INVALID_OPERATION; diff --git a/core/jni/android_os_HwBinder.cpp b/core/jni/android_os_HwBinder.cpp index 15b2f357b33f..dcb23007a70b 100644 --- a/core/jni/android_os_HwBinder.cpp +++ b/core/jni/android_os_HwBinder.cpp @@ -278,7 +278,8 @@ static jobject JHwBinder_native_getService( jstring ifaceNameObj, jstring serviceNameObj) { - using ::android::vintf::operator<<; + using ::android::hidl::base::V1_0::IBase; + using ::android::hidl::manager::V1_0::IServiceManager; if (ifaceNameObj == NULL) { jniThrowException(env, "java/lang/NullPointerException", NULL); @@ -320,13 +321,20 @@ static jobject JHwBinder_native_getService( << "/" << serviceName; - ::android::vintf::Transport transport = - ::android::hardware::getTransport(ifaceName, serviceName); - if ( transport != ::android::vintf::Transport::EMPTY - && transport != ::android::vintf::Transport::HWBINDER) { + Return<IServiceManager::Transport> transportRet = + manager->getTransport(ifaceNameHStr, serviceNameHStr); + + if (!transportRet.isOk()) { + signalExceptionForError(env, UNKNOWN_ERROR, true /* canThrowRemoteException */); + return NULL; + } + + IServiceManager::Transport transport = transportRet; + + if ( transport != IServiceManager::Transport::EMPTY + && transport != IServiceManager::Transport::HWBINDER) { LOG(ERROR) << "service " << ifaceName << " declares transport method " - << transport << " but framework expects " - << ::android::vintf::Transport::HWBINDER; + << toString(transport) << " but framework expects hwbinder."; signalExceptionForError(env, UNKNOWN_ERROR, true /* canThrowRemoteException */); return NULL; } diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp index a03d3c503e1c..e8c57716a9b0 100644 --- a/core/jni/android_util_Process.cpp +++ b/core/jni/android_util_Process.cpp @@ -176,6 +176,22 @@ void android_os_Process_setThreadGroup(JNIEnv* env, jobject clazz, int tid, jint } } +void android_os_Process_setThreadGroupAndCpuset(JNIEnv* env, jobject clazz, int tid, jint grp) +{ + ALOGV("%s tid=%d grp=%" PRId32, __func__, tid, grp); + SchedPolicy sp = (SchedPolicy) grp; + int res = set_sched_policy(tid, sp); + + if (res != NO_ERROR) { + signalExceptionForGroupError(env, -res, tid); + } + + res = set_cpuset_policy(tid, sp); + if (res != NO_ERROR) { + signalExceptionForGroupError(env, -res, tid); + } +} + void android_os_Process_setProcessGroup(JNIEnv* env, jobject clazz, int pid, jint grp) { ALOGV("%s pid=%d grp=%" PRId32, __func__, pid, grp); @@ -1207,6 +1223,7 @@ static const JNINativeMethod methods[] = { {"getThreadPriority", "(I)I", (void*)android_os_Process_getThreadPriority}, {"getThreadScheduler", "(I)I", (void*)android_os_Process_getThreadScheduler}, {"setThreadGroup", "(II)V", (void*)android_os_Process_setThreadGroup}, + {"setThreadGroupAndCpuset", "(II)V", (void*)android_os_Process_setThreadGroupAndCpuset}, {"setProcessGroup", "(II)V", (void*)android_os_Process_setProcessGroup}, {"getProcessGroup", "(I)I", (void*)android_os_Process_getProcessGroup}, {"getExclusiveCores", "()[I", (void*)android_os_Process_getExclusiveCores}, diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp index e2443bba430a..dc365b41d119 100644 --- a/core/jni/android_view_SurfaceControl.cpp +++ b/core/jni/android_view_SurfaceControl.cpp @@ -167,7 +167,7 @@ static jobject nativeScreenshotToBuffer(JNIEnv* env, jclass clazz, buffer->getHeight(), buffer->getPixelFormat(), buffer->getUsage(), - (void*)buffer.get()); + (jlong)buffer.get()); } static jobject nativeScreenshotBitmap(JNIEnv* env, jclass clazz, diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index a73f54354062..a27c5ac9e5a2 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -914,7 +914,7 @@ android:permissionGroup="android.permission-group.PHONE" android:label="@string/permlab_answerPhoneCalls" android:description="@string/permdesc_answerPhoneCalls" - android:protectionLevel="dangerous" /> + android:protectionLevel="dangerous|runtime" /> <!-- ====================================================================== --> @@ -1300,6 +1300,13 @@ <permission android:name="android.permission.REQUEST_NETWORK_SCORES" android:protectionLevel="signature|setup" /> + <!-- Allows network stack services (Connectivity and Wifi) to coordinate + <p>Not for use by third-party or privileged applications. + @hide This should only be used by Connectivity and Wifi Services. + --> + <permission android:name="android.permission.NETWORK_STACK" + android:protectionLevel="signature" /> + <!-- ======================================= --> <!-- Permissions for short range, peripheral networks --> <!-- ======================================= --> @@ -3365,6 +3372,7 @@ </intent-filter> </activity> <activity android:name="com.android.internal.app.AccessibilityButtonChooserActivity" + android:exported="false" android:theme="@style/Theme.DeviceDefault.Resolver" android:finishOnCloseSystemDialogs="true" android:excludeFromRecents="true" @@ -3374,7 +3382,7 @@ android:process=":ui" android:visibleToInstantApps="true"> <intent-filter> - <action android:name="android.intent.action.CHOOSE_ACCESSIBILITY_BUTTON" /> + <action android:name="com.android.internal.intent.action.CHOOSE_ACCESSIBILITY_BUTTON" /> <category android:name="android.intent.category.DEFAULT" /> </intent-filter> </activity> @@ -3652,7 +3660,6 @@ <service android:name="com.android.internal.app.LRResolverRankerService" android:permission="android.permission.BIND_RESOLVER_RANKER_SERVICE" - android:exported="false" android:priority="-1" > <intent-filter> <action android:name="android.service.resolver.ResolverRankerService" /> diff --git a/core/res/res/drawable/autofilled_highlight.xml b/core/res/res/drawable/autofilled_highlight.xml new file mode 100644 index 000000000000..c7aacb92af60 --- /dev/null +++ b/core/res/res/drawable/autofilled_highlight.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + --> + +<inset xmlns:android="http://schemas.android.com/apk/res/android" + android:insetLeft="4dp" + android:insetRight="4dp" + android:insetBottom="4dp" + android:insetTop="4dp"> + <shape> + <solid android:color="@color/autofilled_highlight" /> + </shape> +</inset> diff --git a/core/res/res/values-watch/config.xml b/core/res/res/values-watch/config.xml index ac7b236ba981..98dc4cfeaa89 100644 --- a/core/res/res/values-watch/config.xml +++ b/core/res/res/values-watch/config.xml @@ -66,4 +66,8 @@ <!-- The small screens of watch devices makes multi-window support undesireable. --> <bool name="config_supportsMultiWindow">false</bool> <bool name="config_supportsSplitScreenMultiWindow">false</bool> + + <!-- Disable Multi-Display because of small screen space and lack of external display connection + options. --> + <bool name="config_supportsMultiDisplay">false</bool> </resources> diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 0717c9dfe9e0..69c6fa4a3a62 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -7739,13 +7739,6 @@ <attr name="settingsActivity" /> </declare-styleable> - <!-- TODO(b/35956626): temporary until clients change to AutofillService --> - <declare-styleable name="AutoFillService"> - <!-- Fully qualified class name of an activity that allows the user to modify - the settings for this service. --> - <attr name="settingsActivity" /> - </declare-styleable> - <!-- =============================== --> <!-- Contacts meta-data attributes --> <!-- =============================== --> @@ -8566,11 +8559,6 @@ <!-- @hide From Theme.colorBackground, used for the TaskDescription background color. --> <attr name="colorBackground" /> - <!-- @hide From Theme.statusBarColor, used for the TaskDescription status bar color. --> - <attr name="statusBarColor"/> - <!-- @hide From Theme.navigationBarColor, used for the TaskDescription navigation bar - color. --> - <attr name="navigationBarColor"/> </declare-styleable> <declare-styleable name="Shortcut"> diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml index ed5a42b707f7..3e4b66d780ad 100644 --- a/core/res/res/values/attrs_manifest.xml +++ b/core/res/res/values/attrs_manifest.xml @@ -245,6 +245,10 @@ <!-- Additional flag from base permission type: this permission can be granted to ephemeral apps --> <flag name="ephemeral" value="0x1000" /> + <!-- Additional flag from base permission type: this permission can only be granted to apps + that target runtime permissions ({@link android.os.Build.VERSION_CODES#M} and above) + --> + <flag name="runtime" value="0x2000" /> </attr> <!-- Flags indicating more context for a permission group. --> diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml index f9fd57cf5df9..937fc6fba247 100644 --- a/core/res/res/values/colors.xml +++ b/core/res/res/values/colors.xml @@ -150,6 +150,7 @@ <color name="keyguard_avatar_frame_pressed_color">#ff35b5e5</color> <color name="accessibility_focus_highlight">#bf39b500</color> + <color name="autofilled_highlight">#4dffeb3b</color> <color name="system_notification_accent_color">#ff607D8B</color> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 221e30874367..3dd7ad415baf 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -2651,6 +2651,9 @@ <!-- True if the device supports split screen as a form of multi-window. --> <bool name="config_supportsSplitScreenMultiWindow">true</bool> + <!-- True if the device supports running activities on secondary displays. --> + <bool name="config_supportsMultiDisplay">true</bool> + <!-- True if the device has no home screen. That is a launcher activity where the user can launch other applications from. --> <bool name="config_noHomeScreen">false</bool> diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index 624eb59eef99..1d1fd5e470a2 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -2825,6 +2825,10 @@ <public name="autofill" /> </public-group> + <public-group type="drawable" first-id="0x010800b4"> + <public name="autofilled_highlight" /> + </public-group> + <!-- =============================================================== DO NOT ADD UN-GROUPED ITEMS HERE diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 11286f3a9473..ae28797c32aa 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -20,8 +20,10 @@ <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <!-- Suffix added to a number to signify size in bytes. --> <string name="byteShort">B</string> - <!-- Suffix added to a number to signify size in kilobytes (1000 bytes). --> - <string name="kilobyteShort">KB</string> + <!-- Suffix added to a number to signify size in kilobytes (1000 bytes). + If you retain the Latin script for the localization, please use the lowercase + 'k', as it signifies 1000 bytes as opposed to 1024 bytes. --> + <string name="kilobyteShort">kB</string> <!-- Suffix added to a number to signify size in megabytes. --> <string name="megabyteShort">MB</string> <!-- Suffix added to a number to signify size in gigabytes. --> @@ -4583,6 +4585,9 @@ <!-- Accessibility title for the autofill dialog used to select a list of options to autofill an activity. [CHAR LIMIT=NONE] --> <string name="autofill_picker_accessibility_title">Autofill options</string> + <!-- Toast message shown when user manually request autofill but service could not figure out the data that would autofill the screen contents. [CHAR LIMIT=NONE] --> + <string name="autofill_error_cannot_autofill">Contents can\u2019t be autofilled</string> + <!-- Title for the autofill save dialog shown when the the contents of the activity can be saved by an autofill service, but the service does not know what the activity represents [CHAR LIMIT=NONE] --> <string name="autofill_save_title">Save to <xliff:g id="label" example="MyPass">%1$s</xliff:g>?</string> @@ -4600,6 +4605,10 @@ <string name="autofill_save_type_address">address</string> <!-- Label for the type of data being saved for autofill when it represents a credit card [CHAR LIMIT=NONE] --> <string name="autofill_save_type_credit_card">credit card</string> + <!-- Label for the type of data being saved for autofill when it represents an username [CHAR LIMIT=NONE] --> + <string name="autofill_save_type_username">username</string> + <!-- Label for the type of data being saved for autofill when it represents an email address [CHAR LIMIT=NONE] --> + <string name="autofill_save_type_email_address">email address</string> <!-- Primary ETWS (Earthquake and Tsunami Warning System) default message for earthquake --> <string name="etws_primary_default_message_earthquake">Stay calm and seek shelter nearby.</string> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index c12116a22b16..379c376bf2ff 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -313,6 +313,7 @@ <java-symbol type="bool" name="config_freeformWindowManagement" /> <java-symbol type="bool" name="config_supportsMultiWindow" /> <java-symbol type="bool" name="config_supportsSplitScreenMultiWindow" /> + <java-symbol type="bool" name="config_supportsMultiDisplay" /> <java-symbol type="bool" name="config_noHomeScreen" /> <java-symbol type="bool" name="config_guestUserEphemeral" /> <java-symbol type="bool" name="config_localDisplaysMirrorContent" /> @@ -1333,6 +1334,7 @@ <java-symbol type="drawable" name="ic_sim_card_multi_48px_clr" /> <java-symbol type="drawable" name="stat_notify_mmcc_indication_icn" /> + <java-symbol type="drawable" name="autofilled_highlight"/> <java-symbol type="drawable" name="ic_account_circle" /> <java-symbol type="color" name="user_icon_1" /> @@ -2874,6 +2876,7 @@ <java-symbol type="id" name="autofill_save_no" /> <java-symbol type="id" name="autofill_save_yes" /> <java-symbol type="id" name="autofill_save_close" /> + <java-symbol type="string" name="autofill_error_cannot_autofill" /> <java-symbol type="string" name="autofill" /> <java-symbol type="string" name="autofill_picker_accessibility_title " /> <java-symbol type="string" name="autofill_save_title" /> @@ -2883,6 +2886,8 @@ <java-symbol type="string" name="autofill_save_type_password" /> <java-symbol type="string" name="autofill_save_type_address" /> <java-symbol type="string" name="autofill_save_type_credit_card" /> + <java-symbol type="string" name="autofill_save_type_username" /> + <java-symbol type="string" name="autofill_save_type_email_address" /> <!-- Accessibility fingerprint gestures --> <java-symbol type="string" name="capability_title_canCaptureFingerprintGestures" /> diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsBackgroundStatsTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsBackgroundStatsTest.java index 6b52b98e4758..828333528efd 100644 --- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsBackgroundStatsTest.java +++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsBackgroundStatsTest.java @@ -21,6 +21,7 @@ import android.app.ActivityManager; import android.os.BatteryStats; import android.os.WorkSource; import android.support.test.filters.SmallTest; +import android.util.ArrayMap; import junit.framework.TestCase; @@ -187,4 +188,65 @@ public class BatteryStatsBackgroundStatsTest extends TestCase { assertEquals((305 - 202) * 1000, actualTime); assertEquals((305 - 254) * 1000, bgTime); } + + @SmallTest + public void testJob() throws Exception { + final MockClocks clocks = new MockClocks(); + MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks); + final String jobName = "job_name"; + long curr = 0; // realtime in us + + // On battery + curr = 1000 * (clocks.realtime = clocks.uptime = 100); + bi.updateTimeBasesLocked(true, false, curr, curr); // on battery + // App in foreground + bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND); + + // Start timer + curr = 1000 * (clocks.realtime = clocks.uptime = 151); + bi.noteJobStartLocked(jobName, UID); + + // Stop timer + curr = 1000 * (clocks.realtime = clocks.uptime = 161); + bi.noteJobFinishLocked(jobName, UID); + + // Start timer + curr = 1000 * (clocks.realtime = clocks.uptime = 202); + bi.noteJobStartLocked(jobName, UID); + + // Move to background + curr = 1000 * (clocks.realtime = clocks.uptime = 254); + bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND); + + // Off battery + curr = 1000 * (clocks.realtime = clocks.uptime = 305); + bi.updateTimeBasesLocked(false, false, curr, curr); // off battery + + // Stop timer + curr = 1000 * (clocks.realtime = clocks.uptime = 409); + bi.noteJobFinishLocked(jobName, UID); + + // Test + curr = 1000 * (clocks.realtime = clocks.uptime = 657); + final ArrayMap<String, ? extends BatteryStats.Timer> jobs = + bi.getUidStats().get(UID).getJobStats(); + assertEquals(1, jobs.size()); + BatteryStats.Timer timer = jobs.valueAt(0); + BatteryStats.Timer bgTimer = timer.getSubTimer(); + long time = timer.getTotalTimeLocked(curr, STATS_SINCE_CHARGED); + int count = timer.getCountLocked(STATS_SINCE_CHARGED); + int bgCount = bgTimer.getCountLocked(STATS_SINCE_CHARGED); + long bgTime = bgTimer.getTotalTimeLocked(curr, STATS_SINCE_CHARGED); + assertEquals((161 - 151 + 305 - 202) * 1000, time); + assertEquals(2, count); + assertEquals(1, bgCount); + assertEquals((305 - 254) * 1000, bgTime); + + // Test that a second job is separate. + curr = 1000 * (clocks.realtime = clocks.uptime = 3000); + final String jobName2 = "second_job"; + bi.noteJobStartLocked(jobName2, UID); + assertEquals(2, bi.getUidStats().get(UID).getJobStats().size()); + bi.noteJobFinishLocked(jobName2, UID); + } } diff --git a/graphics/java/android/graphics/ColorSpace.java b/graphics/java/android/graphics/ColorSpace.java index e03dcf34a40c..e61d46736364 100644 --- a/graphics/java/android/graphics/ColorSpace.java +++ b/graphics/java/android/graphics/ColorSpace.java @@ -3530,7 +3530,7 @@ public abstract class ColorSpace { * * @see RenderIntent */ - public RenderIntent getIntent() { + public RenderIntent getRenderIntent() { return mIntent; } diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp index daf14af87288..13d7e09b6e79 100644 --- a/libs/hwui/SkiaCanvas.cpp +++ b/libs/hwui/SkiaCanvas.cpp @@ -23,6 +23,7 @@ #include "hwui/MinikinUtils.h" #include "pipeline/skia/AnimatedDrawables.h" +#include <SkColorSpaceXformCanvas.h> #include <SkDrawable.h> #include <SkDeque.h> #include <SkDrawFilter.h> @@ -44,18 +45,22 @@ Canvas* Canvas::create_canvas(const SkBitmap& bitmap) { return new SkiaCanvas(bitmap); } -Canvas* Canvas::create_canvas(SkCanvas* skiaCanvas) { - return new SkiaCanvas(skiaCanvas); +Canvas* Canvas::create_canvas(SkCanvas* skiaCanvas, XformToSRGB xformToSRGB) { + return new SkiaCanvas(skiaCanvas, xformToSRGB); } SkiaCanvas::SkiaCanvas() {} -SkiaCanvas::SkiaCanvas(SkCanvas* canvas) - : mCanvas(canvas) {} +SkiaCanvas::SkiaCanvas(SkCanvas* canvas, XformToSRGB xformToSRGB) + : mCanvas(canvas) +{ + LOG_ALWAYS_FATAL_IF(XformToSRGB::kImmediate == xformToSRGB); +} SkiaCanvas::SkiaCanvas(const SkBitmap& bitmap) { mCanvasOwned = std::unique_ptr<SkCanvas>(new SkCanvas(bitmap)); - mCanvas = mCanvasOwned.get(); + mCanvasWrapper = SkCreateColorSpaceXformCanvas(mCanvasOwned.get(), SkColorSpace::MakeSRGB()); + mCanvas = mCanvasWrapper.get(); } SkiaCanvas::~SkiaCanvas() {} @@ -92,19 +97,22 @@ private: }; void SkiaCanvas::setBitmap(const SkBitmap& bitmap) { - SkCanvas* newCanvas = new SkCanvas(bitmap); + std::unique_ptr<SkCanvas> newCanvas = std::unique_ptr<SkCanvas>(new SkCanvas(bitmap)); + std::unique_ptr<SkCanvas> newCanvasWrapper = + SkCreateColorSpaceXformCanvas(newCanvas.get(), SkColorSpace::MakeSRGB()); if (!bitmap.isNull()) { // Copy the canvas matrix & clip state. - newCanvas->setMatrix(mCanvas->getTotalMatrix()); + newCanvasWrapper->setMatrix(mCanvas->getTotalMatrix()); - ClipCopier copier(newCanvas); + ClipCopier copier(newCanvasWrapper.get()); mCanvas->replayClips(&copier); } // deletes the previously owned canvas (if any) - mCanvasOwned = std::unique_ptr<SkCanvas>(newCanvas); - mCanvas = newCanvas; + mCanvasOwned = std::move(newCanvas); + mCanvasWrapper = std::move(newCanvasWrapper); + mCanvas = mCanvasWrapper.get(); // clean up the old save stack mSaveStack.reset(nullptr); diff --git a/libs/hwui/SkiaCanvas.h b/libs/hwui/SkiaCanvas.h index 34c3717557ff..13f979cf7d71 100644 --- a/libs/hwui/SkiaCanvas.h +++ b/libs/hwui/SkiaCanvas.h @@ -37,8 +37,12 @@ public: * @param canvas SkCanvas to handle calls made to this SkiaCanvas. Must * not be NULL. This constructor does not take ownership, so the caller * must guarantee that it remains valid while the SkiaCanvas is valid. + * @param xformToSRGB Indicates if bitmaps should be xformed to the sRGB + * color space before drawing. This makes sense for software rendering. + * For the picture case, it may make more sense to leave bitmaps as is, + * and handle the xform when replaying the picture. */ - explicit SkiaCanvas(SkCanvas* canvas); + explicit SkiaCanvas(SkCanvas* canvas, XformToSRGB xformToSRGB); virtual ~SkiaCanvas(); @@ -181,6 +185,7 @@ private: class Clip; + std::unique_ptr<SkCanvas> mCanvasWrapper; // might own a wrapper on the canvas std::unique_ptr<SkCanvas> mCanvasOwned; // might own a canvas we allocated SkCanvas* mCanvas; // we do NOT own this canvas, it must survive us // unless it is the same as mCanvasOwned.get() diff --git a/libs/hwui/Texture.cpp b/libs/hwui/Texture.cpp index 8b71086e1625..959059fede0c 100644 --- a/libs/hwui/Texture.cpp +++ b/libs/hwui/Texture.cpp @@ -227,10 +227,16 @@ void Texture::colorTypeToGlFormatAndType(const Caches& caches, SkColorType color *outType = GL_UNSIGNED_BYTE; break; case kRGBA_F16_SkColorType: - // This format is always linear - *outFormat = GL_RGBA; - *outInternalFormat = GL_RGBA16F; - *outType = GL_HALF_FLOAT; + if (caches.extensions().getMajorGlVersion() >= 3) { + // This format is always linear + *outFormat = GL_RGBA; + *outInternalFormat = GL_RGBA16F; + *outType = GL_HALF_FLOAT; + } else { + *outFormat = GL_RGBA; + *outInternalFormat = caches.rgbaInternalFormat(true); + *outType = GL_UNSIGNED_BYTE; + } break; default: LOG_ALWAYS_FATAL("Unsupported bitmap colorType: %d", colorType); @@ -244,8 +250,17 @@ SkBitmap Texture::uploadToN32(const SkBitmap& bitmap, bool hasLinearBlending, rgbaBitmap.allocPixels(SkImageInfo::MakeN32(bitmap.width(), bitmap.height(), bitmap.info().alphaType(), hasLinearBlending ? sRGB : nullptr)); rgbaBitmap.eraseColor(0); - SkCanvas canvas(rgbaBitmap); - canvas.drawBitmap(bitmap, 0.0f, 0.0f, nullptr); + + if (bitmap.colorType() == kRGBA_F16_SkColorType) { + // Drawing RGBA_F16 onto ARGB_8888 is not supported + bitmap.readPixels(rgbaBitmap.info() + .makeColorSpace(SkColorSpace::MakeSRGB()), + rgbaBitmap.getPixels(), rgbaBitmap.rowBytes(), 0, 0); + } else { + SkCanvas canvas(rgbaBitmap); + canvas.drawBitmap(bitmap, 0.0f, 0.0f, nullptr); + } + return rgbaBitmap; } @@ -254,7 +269,9 @@ bool Texture::hasUnsupportedColorType(const SkImageInfo& info, bool hasLinearBle || info.colorType() == kIndex_8_SkColorType || (info.colorType() == kRGB_565_SkColorType && hasLinearBlending - && info.colorSpace()->isSRGB()); + && info.colorSpace()->isSRGB()) + || (info.colorType() == kRGBA_F16_SkColorType + && Caches::getInstance().extensions().getMajorGlVersion() < 3); } void Texture::upload(Bitmap& bitmap) { @@ -287,10 +304,16 @@ void Texture::upload(Bitmap& bitmap) { colorTypeToGlFormatAndType(mCaches, bitmap.colorType(), needSRGB && hasLinearBlending, &internalFormat, &format, &type); + // Some devices don't support GL_RGBA16F, so we need to compare the color type + // and internal GL format to decide what to do with 16 bit bitmaps + bool rgba16fNeedsConversion = bitmap.colorType() == kRGBA_F16_SkColorType + && internalFormat != GL_RGBA16F; + mConnector.reset(); // RGBA16F is always extended sRGB, alpha masks don't have color profiles - if (internalFormat != GL_RGBA16F && internalFormat != GL_ALPHA) { + // If an RGBA16F bitmap needs conversion, we know the target will be sRGB + if (internalFormat != GL_RGBA16F && internalFormat != GL_ALPHA && !rgba16fNeedsConversion) { SkColorSpace* colorSpace = bitmap.info().colorSpace(); // If the bitmap is sRGB we don't need conversion if (colorSpace != nullptr && !colorSpace->isSRGB()) { diff --git a/libs/hwui/hwui/Bitmap.cpp b/libs/hwui/hwui/Bitmap.cpp index eed5b242c1d2..729c7b8a1ecb 100644 --- a/libs/hwui/hwui/Bitmap.cpp +++ b/libs/hwui/hwui/Bitmap.cpp @@ -228,7 +228,7 @@ sk_sp<Bitmap> Bitmap::allocateHardwareBitmap(uirenderer::renderthread::RenderThr bool hasLinearBlending = caches.extensions().hasLinearBlending(); GLint format, type, internalFormat; uirenderer::Texture::colorTypeToGlFormatAndType(caches, skBitmap.colorType(), - needSRGB, &internalFormat, &format, &type); + needSRGB && hasLinearBlending, &internalFormat, &format, &type); PixelFormat pixelFormat = internalFormatToPixelFormat(internalFormat); sp<GraphicBuffer> buffer = new GraphicBuffer(info.width(), info.height(), pixelFormat, diff --git a/libs/hwui/hwui/Canvas.h b/libs/hwui/hwui/Canvas.h index 969d87716ce6..ed328328dbd6 100644 --- a/libs/hwui/hwui/Canvas.h +++ b/libs/hwui/hwui/Canvas.h @@ -93,6 +93,15 @@ public: static WARN_UNUSED_RESULT Canvas* create_recording_canvas(int width, int height, uirenderer::RenderNode* renderNode = nullptr); + enum class XformToSRGB { + // Transform any Bitmaps to the sRGB color space before drawing. + kImmediate, + + // Draw the Bitmap as is. This likely means that we are recording and that the + // transform can be handled at playback time. + kDefer, + }; + /** * Create a new Canvas object which delegates to an SkCanvas. * @@ -100,10 +109,12 @@ public: * delegated to this object. This function will call ref() on the * SkCanvas, and the returned Canvas will unref() it upon * destruction. + * @param xformToSRGB Indicates if bitmaps should be xformed to the sRGB + * color space before drawing. * @return new non-null Canvas Object. The type of DisplayList produced by this canvas is * determined based on Properties::getRenderPipelineType(). */ - static Canvas* create_canvas(SkCanvas* skiaCanvas); + static Canvas* create_canvas(SkCanvas* skiaCanvas, XformToSRGB xformToSRGB); /** * Provides a Skia SkCanvas interface that acts as a proxy to this Canvas. diff --git a/libs/hwui/tests/unit/SkiaCanvasTests.cpp b/libs/hwui/tests/unit/SkiaCanvasTests.cpp index 7fb75dce8a19..44476af742f1 100644 --- a/libs/hwui/tests/unit/SkiaCanvasTests.cpp +++ b/libs/hwui/tests/unit/SkiaCanvasTests.cpp @@ -44,7 +44,8 @@ OPENGL_PIPELINE_TEST(SkiaCanvasProxy, drawGlyphsViaPicture) { // record the same text draw into a SkPicture and replay it into a Recording canvas SkPictureRecorder recorder; SkCanvas* skCanvas = recorder.beginRecording(200, 200, NULL, 0); - std::unique_ptr<Canvas> pictCanvas(Canvas::create_canvas(skCanvas)); + std::unique_ptr<Canvas> pictCanvas(Canvas::create_canvas(skCanvas, + Canvas::XformToSRGB::kDefer)); TestUtils::drawUtf8ToCanvas(pictCanvas.get(), text, paint, 25, 25); sk_sp<SkPicture> picture = recorder.finishRecordingAsPicture(); @@ -63,7 +64,7 @@ OPENGL_PIPELINE_TEST(SkiaCanvasProxy, drawGlyphsViaPicture) { TEST(SkiaCanvas, drawShadowLayer) { auto surface = SkSurface::MakeRasterN32Premul(10, 10); - SkiaCanvas canvas(surface->getCanvas()); + SkiaCanvas canvas(surface->getCanvas(), Canvas::XformToSRGB::kDefer); // clear to white canvas.drawColor(SK_ColorWHITE, SkBlendMode::kSrc); @@ -78,3 +79,52 @@ TEST(SkiaCanvas, drawShadowLayer) { ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorWHITE); ASSERT_NE(TestUtils::getColor(surface, 5, 5), SK_ColorWHITE); } + +TEST(SkiaCanvas, colorSpaceXform) { + sk_sp<SkColorSpace> adobe = SkColorSpace::MakeRGB(SkColorSpace::kSRGB_RenderTargetGamma, + SkColorSpace::kAdobeRGB_Gamut); + + SkImageInfo adobeInfo = SkImageInfo::Make(1, 1, kN32_SkColorType, kOpaque_SkAlphaType, adobe); + sk_sp<Bitmap> adobeBitmap = Bitmap::allocateHeapBitmap(adobeInfo); + SkBitmap adobeSkBitmap; + adobeBitmap->getSkBitmap(&adobeSkBitmap); + adobeSkBitmap.lockPixels(); + *adobeSkBitmap.getAddr32(0, 0) = 0xFF0000F0; // Opaque, almost fully-red + + SkImageInfo info = adobeInfo.makeColorSpace(nullptr); + sk_sp<Bitmap> bitmap = Bitmap::allocateHeapBitmap(info); + SkBitmap skBitmap; + bitmap->getSkBitmap(&skBitmap); + + // Create a software canvas. + SkiaCanvas canvas(skBitmap); + canvas.drawBitmap(*adobeBitmap, 0, 0, nullptr); + // The result should be fully red, since we convert to sRGB at draw time. + skBitmap.lockPixels(); + ASSERT_EQ(0xFF0000FF, *skBitmap.getAddr32(0, 0)); + + // Now try in kDefer mode. This is a little strange given that, in practice, all software + // canvases are kImmediate. + SkCanvas skCanvas(skBitmap); + SkiaCanvas deferCanvas(&skCanvas, Canvas::XformToSRGB::kDefer); + deferCanvas.drawBitmap(*adobeBitmap, 0, 0, nullptr); + // The result should be as initialized, since we deferred the conversion to sRGB. + skBitmap.lockPixels(); + ASSERT_EQ(0xFF0000F0, *skBitmap.getAddr32(0, 0)); + + // Test picture recording. We will kDefer the xform at recording time, but handle it when + // we playback to the software canvas. + SkPictureRecorder recorder; + SkCanvas* skPicCanvas = recorder.beginRecording(1, 1, NULL, 0); + SkiaCanvas picCanvas(skPicCanvas, Canvas::XformToSRGB::kDefer); + picCanvas.drawBitmap(*adobeBitmap, 0, 0, nullptr); + sk_sp<SkPicture> picture = recorder.finishRecordingAsPicture(); + + // Playback to a deferred canvas. The result should be as initialized. + deferCanvas.asSkCanvas()->drawPicture(picture); + ASSERT_EQ(0xFF0000F0, *skBitmap.getAddr32(0, 0)); + + // Playback to an immediate canvas. The result should be fully red. + canvas.asSkCanvas()->drawPicture(picture); + ASSERT_EQ(0xFF0000FF, *skBitmap.getAddr32(0, 0)); +} diff --git a/media/java/android/media/MediaDrm.java b/media/java/android/media/MediaDrm.java index 2723826cc92e..b8d1d12b350e 100644 --- a/media/java/android/media/MediaDrm.java +++ b/media/java/android/media/MediaDrm.java @@ -753,9 +753,12 @@ public final class MediaDrm { * @param init container-specific data, its meaning is interpreted based on the * mime type provided in the mimeType parameter. It could contain, for example, * the content ID, key ID or other data obtained from the content metadata that is - * required in generating the key request. init may be null when keyType is - * KEY_TYPE_RELEASE. - * @param mimeType identifies the mime type of the content + * required in generating the key request. May be null when keyType is + * KEY_TYPE_RELEASE or if the request is a renewal, i.e. not the first key + * request for the session. + * @param mimeType identifies the mime type of the content. May be null if the + * keyType is KEY_TYPE_RELEASE or if the request is a renewal, i.e. not the + * first key request for the session. * @param keyType specifes the type of the request. The request may be to acquire * keys for streaming or offline content, or to release previously acquired * keys, which are identified by a keySetId. @@ -779,13 +782,17 @@ public final class MediaDrm { * response is for an offline key request, a keySetId is returned that can be * used to later restore the keys to a new session with the method * {@link #restoreKeys}. - * When the response is for a streaming or release request, null is returned. + * When the response is for a streaming or release request, an empty byte array + * is returned. * * @param scope may be a sessionId or keySetId depending on the type of the * response. Scope should be set to the sessionId when the response is for either * streaming or offline key requests. Scope should be set to the keySetId when * the response is for a release request. * @param response the byte array response from the server + * @return If the response is for an offline request, the keySetId for the offline + * keys will be returned. If the response is for a streaming or release request + * an empty byte array will be returned. * * @throws NotProvisionedException if the response indicates that * reprovisioning is required @@ -1014,13 +1021,13 @@ public final class MediaDrm { * Set a DRM engine plugin String property value. */ public native void setPropertyString( - @StringProperty String propertyName, @NonNull String value); + String propertyName, @NonNull String value); /** * Set a DRM engine plugin byte array property value. */ public native void setPropertyByteArray( - @ArrayProperty String propertyName, @NonNull byte[] value); + String propertyName, @NonNull byte[] value); private static final native void setCipherAlgorithmNative( @NonNull MediaDrm drm, @NonNull byte[] sessionId, @NonNull String algorithm); diff --git a/media/java/android/media/tv/TvContract.java b/media/java/android/media/tv/TvContract.java index 0b27d183021d..e82dd8255d21 100644 --- a/media/java/android/media/tv/TvContract.java +++ b/media/java/android/media/tv/TvContract.java @@ -129,6 +129,23 @@ public final class TvContract { public static final String ACTION_PREVIEW_PROGRAM_ADDED_TO_WATCH_NEXT = "android.media.tv.action.PREVIEW_PROGRAM_ADDED_TO_WATCH_NEXT"; + /** + * Broadcast Action: sent to the target TV input after it is first installed to notify the input + * to initialize its channels and programs to the system content provider. + * + * <p>Note that this intent is sent only on devices with + * {@link android.content.pm.PackageManager#FEATURE_LEANBACK} enabled. Besides that, in order + * to receive this intent, the target TV input must: + * <ul> + * <li>Declare a broadcast receiver for this intent in its + * <code>AndroidManifest.xml</code>.</li> + * <li>Declare appropriate permissions to write channel and program data in its + * <code>AndroidManifest.xml</code>.</li> + * </ul> + */ + public static final String ACTION_INITIALIZE_PROGRAMS = + "android.media.tv.action.INITIALIZE_PROGRAMS"; + /** The key for a bundle parameter containing a channel ID as a long integer */ public static final String EXTRA_CHANNEL_ID = "android.media.tv.extra.CHANNEL_ID"; diff --git a/media/jni/Android.mk b/media/jni/Android.mk index 23bf3d6711f6..431d5d83a4bb 100644 --- a/media/jni/Android.mk +++ b/media/jni/Android.mk @@ -34,6 +34,7 @@ LOCAL_SHARED_LIBRARIES := \ libutils \ libbinder \ libmedia \ + libmediametrics \ libmediadrm \ libmidi \ libskia \ diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraMetadataTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraMetadataTest.java index cd0e587744ed..54442b37d7d9 100644 --- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraMetadataTest.java +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraMetadataTest.java @@ -154,18 +154,18 @@ public class CameraMetadataTest extends junit.framework.TestCase { @SmallTest public void testGetTypeFromTag() { assertEquals(TYPE_BYTE, - CameraMetadataNative.getNativeType(ANDROID_COLOR_CORRECTION_MODE)); + CameraMetadataNative.getNativeType(ANDROID_COLOR_CORRECTION_MODE, Long.MAX_VALUE)); assertEquals(TYPE_RATIONAL, - CameraMetadataNative.getNativeType(ANDROID_COLOR_CORRECTION_TRANSFORM)); + CameraMetadataNative.getNativeType(ANDROID_COLOR_CORRECTION_TRANSFORM, Long.MAX_VALUE)); assertEquals(TYPE_FLOAT, - CameraMetadataNative.getNativeType(ANDROID_COLOR_CORRECTION_GAINS)); + CameraMetadataNative.getNativeType(ANDROID_COLOR_CORRECTION_GAINS, Long.MAX_VALUE)); assertEquals(TYPE_BYTE, - CameraMetadataNative.getNativeType(ANDROID_CONTROL_AE_ANTIBANDING_MODE)); + CameraMetadataNative.getNativeType(ANDROID_CONTROL_AE_ANTIBANDING_MODE, Long.MAX_VALUE)); assertEquals(TYPE_INT32, - CameraMetadataNative.getNativeType(ANDROID_CONTROL_AE_EXPOSURE_COMPENSATION)); + CameraMetadataNative.getNativeType(ANDROID_CONTROL_AE_EXPOSURE_COMPENSATION, Long.MAX_VALUE)); try { - CameraMetadataNative.getNativeType(0xDEADF00D); + CameraMetadataNative.getNativeType(0xDEADF00D, Long.MAX_VALUE); fail("No type should exist for invalid tag 0xDEADF00D"); } catch(IllegalArgumentException e) { } diff --git a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/PrintServicePlugin.java b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/PrintServicePlugin.java index d604ef8a49ea..d723d2fd19f7 100644 --- a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/PrintServicePlugin.java +++ b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/PrintServicePlugin.java @@ -16,10 +16,13 @@ package com.android.printservice.recommendation; -import android.annotation.IntRange; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.StringRes; +import java.net.InetAddress; +import java.util.List; + /** * Interface to be implemented by each print service plugin. * <p/> @@ -35,9 +38,9 @@ public interface PrintServicePlugin { /** * Announce that something changed and the UI for this plugin should be updated. * - * @param numDiscoveredPrinters The number of printers discovered. + * @param discoveredPrinters The printers discovered. */ - void onChanged(@IntRange(from = 0) int numDiscoveredPrinters); + void onChanged(@Nullable List<InetAddress> discoveredPrinters); } /** diff --git a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/RecommendationServiceImpl.java b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/RecommendationServiceImpl.java index d0483962eae3..e18ee90d39d7 100644 --- a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/RecommendationServiceImpl.java +++ b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/RecommendationServiceImpl.java @@ -17,20 +17,24 @@ package com.android.printservice.recommendation; import android.content.res.Configuration; +import android.printservice.PrintService; import android.printservice.recommendation.RecommendationInfo; import android.printservice.recommendation.RecommendationService; -import android.printservice.PrintService; import android.util.Log; + import com.android.printservice.recommendation.plugin.hp.HPRecommendationPlugin; import com.android.printservice.recommendation.plugin.mdnsFilter.MDNSFilterPlugin; import com.android.printservice.recommendation.plugin.mdnsFilter.VendorConfig; import com.android.printservice.recommendation.plugin.mopria.MopriaRecommendationPlugin; import com.android.printservice.recommendation.plugin.samsung.SamsungRecommendationPlugin; import com.android.printservice.recommendation.plugin.xerox.XeroxPrintServiceRecommendationPlugin; + import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; +import java.net.InetAddress; import java.util.ArrayList; +import java.util.List; /** * Service that recommends {@link PrintService print services} that might be a good idea to install. @@ -129,12 +133,11 @@ public class RecommendationServiceImpl extends RecommendationService RemotePrintServicePlugin plugin = mPlugins.get(i); try { - int numPrinters = plugin.getNumPrinters(); + List<InetAddress> printers = plugin.getPrinters(); - if (numPrinters > 0) { + if (!printers.isEmpty()) { recommendations.add(new RecommendationInfo(plugin.packageName, - getString(plugin.name), numPrinters, - plugin.recommendsMultiVendorService)); + getString(plugin.name), printers, plugin.recommendsMultiVendorService)); } } catch (Exception e) { Log.e(LOG_TAG, "Could not read state of plugin for " + plugin.packageName, e); diff --git a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/RemotePrintServicePlugin.java b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/RemotePrintServicePlugin.java index dbd164946dfb..fd929a7c8233 100644 --- a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/RemotePrintServicePlugin.java +++ b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/RemotePrintServicePlugin.java @@ -16,11 +16,16 @@ package com.android.printservice.recommendation; -import android.annotation.IntRange; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.StringRes; + import com.android.internal.util.Preconditions; +import java.net.InetAddress; +import java.util.Collections; +import java.util.List; + /** * Wrapper for a {@link PrintServicePlugin}, isolating issues with the plugin as good as possible * from the {@link RecommendationServiceImpl service}. @@ -41,13 +46,13 @@ class RemotePrintServicePlugin implements PrintServicePlugin.PrinterDiscoveryCal /** Wrapped plugin */ private final @NonNull PrintServicePlugin mPlugin; - /** The number of printers discovered by the plugin */ - private @IntRange(from = 0) int mNumPrinters; + /** The printers discovered by the plugin */ + private @NonNull List<InetAddress> mPrinters; /** If the plugin is started by not yet stopped */ private boolean isRunning; - /** Listener for changes to {@link #mNumPrinters}. */ + /** Listener for changes to {@link #mPrinters}. */ private @NonNull OnChangedListener mListener; /** @@ -65,6 +70,8 @@ class RemotePrintServicePlugin implements PrintServicePlugin.PrinterDiscoveryCal throws PluginException { mListener = listener; mPlugin = plugin; + mPrinters = Collections.emptyList(); + this.recommendsMultiVendorService = recommendsMultiVendorService; // We handle any throwable to isolate our self from bugs in the plugin code. @@ -116,26 +123,28 @@ class RemotePrintServicePlugin implements PrintServicePlugin.PrinterDiscoveryCal * * @return The number of printers reported by the stub. */ - public @IntRange(from = 0) int getNumPrinters() { - return mNumPrinters; + public @NonNull List<InetAddress> getPrinters() { + return mPrinters; } @Override - public void onChanged(@IntRange(from = 0) int numDiscoveredPrinters) { + public void onChanged(@Nullable List<InetAddress> discoveredPrinters) { synchronized (mLock) { Preconditions.checkState(isRunning); - mNumPrinters = Preconditions.checkArgumentNonnegative(numDiscoveredPrinters, - "numDiscoveredPrinters"); - - if (mNumPrinters > 0) { - mListener.onChanged(); + if (discoveredPrinters == null) { + mPrinters = Collections.emptyList(); + } else { + mPrinters = Preconditions.checkCollectionElementsNotNull(discoveredPrinters, + "discoveredPrinters"); } + + mListener.onChanged(); } } /** - * Listener to listen for changes to {@link #getNumPrinters} + * Listener to listen for changes to {@link #getPrinters} */ public interface OnChangedListener { void onChanged(); diff --git a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/hp/ServiceListener.java b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/hp/ServiceListener.java index e34247a66fb6..600af1ff6da4 100644 --- a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/hp/ServiceListener.java +++ b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/hp/ServiceListener.java @@ -21,17 +21,17 @@ import android.content.res.TypedArray; import android.net.nsd.NsdManager; import android.net.nsd.NsdServiceInfo; import android.text.TextUtils; -import android.util.Pair; +import com.android.printservice.recommendation.R; +import com.android.printservice.recommendation.util.DiscoveryListenerMultiplexer; + +import java.net.InetAddress; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; -import com.android.printservice.recommendation.R; -import com.android.printservice.recommendation.util.DiscoveryListenerMultiplexer; - public class ServiceListener implements ServiceResolveQueue.ResolveCallback { private final NsdManager mNSDManager; @@ -176,11 +176,18 @@ public class ServiceListener implements ServiceResolveQueue.ResolveCallback { mListeners.clear(); } - public Pair<Integer, Integer> getCount() { - int count = 0; - for (PrinterHashMap map : mVendorHashMap.values()) { - count += map.size(); + /** + * @return The {@link InetAddress addresses} of the discovered printers + */ + public ArrayList<InetAddress> getPrinters() { + ArrayList<InetAddress> printerAddressess = new ArrayList<>(); + + for (PrinterHashMap oneVendorPrinters : mVendorHashMap.values()) { + for (NsdServiceInfo printer : oneVendorPrinters.values()) { + printerAddressess.add(printer.getHost()); + } } - return Pair.create(mVendorHashMap.size(), count); + + return printerAddressess; } } diff --git a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/hp/ServiceRecommendationPlugin.java b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/hp/ServiceRecommendationPlugin.java index 7ea530dda9d8..4e3bf933a524 100644 --- a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/hp/ServiceRecommendationPlugin.java +++ b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/hp/ServiceRecommendationPlugin.java @@ -16,13 +16,17 @@ package com.android.printservice.recommendation.plugin.hp; +import android.annotation.NonNull; import android.content.Context; import android.net.nsd.NsdManager; import android.net.nsd.NsdServiceInfo; -import android.annotation.NonNull; import android.text.TextUtils; + import com.android.printservice.recommendation.PrintServicePlugin; +import java.net.InetAddress; +import java.util.ArrayList; + public abstract class ServiceRecommendationPlugin implements PrintServicePlugin, ServiceListener.Observer { protected static final String PDL_ATTRIBUTE = "pdl"; @@ -71,7 +75,7 @@ public abstract class ServiceRecommendationPlugin implements PrintServicePlugin, @Override public void dataSetChanged() { synchronized (mLock) { - if (mCallback != null) mCallback.onChanged(getCount()); + if (mCallback != null) mCallback.onChanged(getPrinters()); } } @@ -80,7 +84,7 @@ public abstract class ServiceRecommendationPlugin implements PrintServicePlugin, return TextUtils.equals(vendor, mVendorInfo.mVendorID); } - public int getCount() { - return mListener.getCount().second; + public ArrayList<InetAddress> getPrinters() { + return mListener.getPrinters(); } } diff --git a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/mopria/MopriaRecommendationPlugin.java b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/mopria/MopriaRecommendationPlugin.java index 18c9da51865a..a9e1aed0d624 100644 --- a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/mopria/MopriaRecommendationPlugin.java +++ b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/mopria/MopriaRecommendationPlugin.java @@ -20,12 +20,14 @@ package com.android.printservice.recommendation.plugin.mopria; import android.content.Context; import android.net.nsd.NsdServiceInfo; import android.text.TextUtils; -import android.util.Pair; +import com.android.printservice.recommendation.R; import com.android.printservice.recommendation.plugin.hp.MDnsUtils; import com.android.printservice.recommendation.plugin.hp.ServiceRecommendationPlugin; import com.android.printservice.recommendation.plugin.hp.VendorInfo; -import com.android.printservice.recommendation.R; + +import java.net.InetAddress; +import java.util.ArrayList; public class MopriaRecommendationPlugin extends ServiceRecommendationPlugin { @@ -47,8 +49,7 @@ public class MopriaRecommendationPlugin extends ServiceRecommendationPlugin { } @Override - public int getCount() { - Pair<Integer, Integer> count = mListener.getCount(); - return ((count.first > 1) ? count.second : 0); + public ArrayList<InetAddress> getPrinters() { + return mListener.getPrinters(); } } diff --git a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/xerox/ServiceResolver.java b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/xerox/ServiceResolver.java index f64eed47f785..4d0efd8be23d 100755 --- a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/xerox/ServiceResolver.java +++ b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/xerox/ServiceResolver.java @@ -19,9 +19,11 @@ import android.content.Context; import android.net.nsd.NsdManager; import android.net.nsd.NsdServiceInfo; import android.text.TextUtils; + import com.android.printservice.recommendation.util.DiscoveryListenerMultiplexer; import com.android.printservice.recommendation.util.NsdResolveQueue; +import java.net.InetAddress; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; @@ -192,8 +194,13 @@ class ServiceResolver { } } - public int getCount() { - return mPrinterHashMap.size(); + public ArrayList<InetAddress> getPrinters() { + ArrayList<InetAddress> printerAddresses = new ArrayList<>(); + for (NsdServiceInfo printer : mPrinterHashMap.values()) { + printerAddresses.add(printer.getHost()); + } + + return printerAddresses; } } diff --git a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/xerox/XeroxPrintServiceRecommendationPlugin.java b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/xerox/XeroxPrintServiceRecommendationPlugin.java index 3fb9ca239716..e0942b7e91a4 100755 --- a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/xerox/XeroxPrintServiceRecommendationPlugin.java +++ b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/xerox/XeroxPrintServiceRecommendationPlugin.java @@ -15,11 +15,11 @@ */ package com.android.printservice.recommendation.plugin.xerox; +import android.annotation.NonNull; import android.content.Context; import android.net.nsd.NsdManager; -import android.annotation.NonNull; -import com.android.printservice.recommendation.PrintServicePlugin; +import com.android.printservice.recommendation.PrintServicePlugin; import com.android.printservice.recommendation.R; public class XeroxPrintServiceRecommendationPlugin implements PrintServicePlugin, ServiceResolver.Observer { @@ -69,11 +69,9 @@ public class XeroxPrintServiceRecommendationPlugin implements PrintServicePlugin @Override public void dataSetChanged() { synchronized (mLock) { - if (mDiscoveryCallback != null) mDiscoveryCallback.onChanged(getCount()); + if (mDiscoveryCallback != null) { + mDiscoveryCallback.onChanged(mServiceResolver.getPrinters()); + } } } - - public int getCount() { - return mServiceResolver.getCount(); - } } diff --git a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/util/MDNSFilteredDiscovery.java b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/util/MDNSFilteredDiscovery.java index c5dbc8c32e91..87ab2d3fcf22 100644 --- a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/util/MDNSFilteredDiscovery.java +++ b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/util/MDNSFilteredDiscovery.java @@ -15,17 +15,19 @@ */ package com.android.printservice.recommendation.util; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.content.Context; import android.net.nsd.NsdManager; import android.net.nsd.NsdServiceInfo; -import android.annotation.NonNull; -import android.annotation.Nullable; import android.util.Log; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.Preconditions; import com.android.printservice.recommendation.PrintServicePlugin; +import java.net.InetAddress; +import java.util.ArrayList; import java.util.HashSet; import java.util.Set; @@ -54,7 +56,7 @@ public class MDNSFilteredDiscovery implements NsdManager.DiscoveryListener { /** Printer identifiers of the mPrinters found. */ @GuardedBy("mLock") - private final @NonNull HashSet<String> mPrinters; + private final @NonNull HashSet<InetAddress> mPrinters; /** Service types discovered by this plugin */ private final @NonNull HashSet<String> mServiceTypes; @@ -111,7 +113,7 @@ public class MDNSFilteredDiscovery implements NsdManager.DiscoveryListener { */ public void start(@NonNull PrintServicePlugin.PrinterDiscoveryCallback callback) { mCallback = callback; - mCallback.onChanged(mPrinters.size()); + mCallback.onChanged(new ArrayList<>(mPrinters)); for (String serviceType : mServiceTypes) { DiscoveryListenerMultiplexer.addListener(getNDSManager(), serviceType, this); @@ -122,7 +124,7 @@ public class MDNSFilteredDiscovery implements NsdManager.DiscoveryListener { * Stop the discovery. This can only return once the plugin is completely finished and cleaned up. */ public void stop() { - mCallback.onChanged(0); + mCallback.onChanged(null); mCallback = null; for (int i = 0; i < mServiceTypes.size(); ++i) { @@ -130,14 +132,6 @@ public class MDNSFilteredDiscovery implements NsdManager.DiscoveryListener { } } - /** - * - * @return The number of discovered printers - */ - public int getCount() { - return mPrinters.size(); - } - @Override public void onStartDiscoveryFailed(String serviceType, int errorCode) { Log.w(LOG_TAG, "Failed to start network discovery for type " + serviceType + ": " @@ -174,9 +168,9 @@ public class MDNSFilteredDiscovery implements NsdManager.DiscoveryListener { public void onServiceResolved(NsdServiceInfo serviceInfo) { if (mPrinterFilter.matchesCriteria(serviceInfo)) { if (mCallback != null) { - boolean added = mPrinters.add(serviceInfo.getHost().getHostAddress()); + boolean added = mPrinters.add(serviceInfo.getHost()); if (added) { - mCallback.onChanged(mPrinters.size()); + mCallback.onChanged(new ArrayList<>(mPrinters)); } } } @@ -198,11 +192,10 @@ public class MDNSFilteredDiscovery implements NsdManager.DiscoveryListener { public void onServiceResolved(NsdServiceInfo serviceInfo) { if (mPrinterFilter.matchesCriteria(serviceInfo)) { if (mCallback != null) { - boolean removed = mPrinters - .remove(serviceInfo.getHost().getHostAddress()); + boolean removed = mPrinters.remove(serviceInfo.getHost()); if (removed) { - mCallback.onChanged(mPrinters.size()); + mCallback.onChanged(new ArrayList<>(mPrinters)); } } } diff --git a/packages/PrintSpooler/res/values/strings.xml b/packages/PrintSpooler/res/values/strings.xml index 2e6ed69b4892..63850aea59e6 100644 --- a/packages/PrintSpooler/res/values/strings.xml +++ b/packages/PrintSpooler/res/values/strings.xml @@ -149,6 +149,12 @@ <!-- Description of printer info icon. [CHAR LIMIT=50] --> <string name="printer_info_desc">More information about this printer</string> + <!-- Label for the notification channel that contains print jobs without problems. [CHAR LIMIT=40] --> + <string name="notification_channel_progress">Running print jobs</string> + + <!-- Label for the notification channel that contains print jobs with problems. [CHAR LIMIT=40] --> + <string name="notification_channel_failure">Failed print jobs</string> + <!-- Notification that we could not create a file name for the printed PDF. [CHAR LIMIT=50] --> <string name="could_not_create_file">Could not create file</string> diff --git a/packages/PrintSpooler/src/com/android/printspooler/model/NotificationController.java b/packages/PrintSpooler/src/com/android/printspooler/model/NotificationController.java index cd1d540d922b..9d737e0cd9d5 100644 --- a/packages/PrintSpooler/src/com/android/printspooler/model/NotificationController.java +++ b/packages/PrintSpooler/src/com/android/printspooler/model/NotificationController.java @@ -20,13 +20,12 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.app.Notification; import android.app.Notification.Action; -import android.app.Notification.InboxStyle; +import android.app.NotificationChannel; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; -import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Icon; import android.net.Uri; import android.os.AsyncTask; @@ -57,14 +56,14 @@ final class NotificationController { public static final String LOG_TAG = "NotificationController"; + private static final String NOTIFICATION_CHANNEL_PROGRESS = "PRINT_PROGRESS"; + private static final String NOTIFICATION_CHANNEL_FAILURES = "PRINT_FAILURES"; + private static final String INTENT_ACTION_CANCEL_PRINTJOB = "INTENT_ACTION_CANCEL_PRINTJOB"; private static final String INTENT_ACTION_RESTART_PRINTJOB = "INTENT_ACTION_RESTART_PRINTJOB"; private static final String EXTRA_PRINT_JOB_ID = "EXTRA_PRINT_JOB_ID"; - private static final String PRINT_JOB_NOTIFICATION_GROUP_KEY = "PRINT_JOB_NOTIFICATIONS"; - private static final String PRINT_JOB_NOTIFICATION_SUMMARY = "PRINT_JOB_NOTIFICATIONS_SUMMARY"; - private final Context mContext; private final NotificationManager mNotificationManager; @@ -78,6 +77,15 @@ final class NotificationController { mNotificationManager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE); mNotifications = new ArraySet<>(0); + + mNotificationManager.createNotificationChannel( + new NotificationChannel(NOTIFICATION_CHANNEL_PROGRESS, + context.getString(R.string.notification_channel_progress), + NotificationManager.IMPORTANCE_LOW)); + mNotificationManager.createNotificationChannel( + new NotificationChannel(NOTIFICATION_CHANNEL_FAILURES, + context.getString(R.string.notification_channel_failure), + NotificationManager.IMPORTANCE_DEFAULT)); } public void onUpdateNotifications(List<PrintJobInfo> printJobs) { @@ -104,13 +112,6 @@ final class NotificationController { final int numPrintJobs = printJobs.size(); - // Create summary notification - if (numPrintJobs > 1) { - createStackedNotification(printJobs); - } else { - mNotificationManager.cancel(PRINT_JOB_NOTIFICATION_SUMMARY, 0); - } - // Create per print job notification for (int i = 0; i < numPrintJobs; i++) { PrintJobInfo printJob = printJobs.get(i); @@ -178,16 +179,16 @@ final class NotificationController { */ private void createNotification(@NonNull PrintJobInfo printJob, @Nullable Action firstAction, @Nullable Action secondAction) { - Notification.Builder builder = new Notification.Builder(mContext) + Notification.Builder builder = new Notification.Builder(mContext, computeChannel(printJob)) .setContentIntent(createContentIntent(printJob.getId())) .setSmallIcon(computeNotificationIcon(printJob)) .setContentTitle(computeNotificationTitle(printJob)) .setWhen(System.currentTimeMillis()) .setOngoing(true) .setShowWhen(true) + .setOnlyAlertOnce(true) .setColor(mContext.getColor( - com.android.internal.R.color.system_notification_accent_color)) - .setGroup(PRINT_JOB_NOTIFICATION_GROUP_KEY); + com.android.internal.R.color.system_notification_accent_color)); if (firstAction != null) { builder.addAction(firstAction); @@ -238,43 +239,6 @@ final class NotificationController { createNotification(printJob, null, null); } - private void createStackedNotification(List<PrintJobInfo> printJobs) { - Notification.Builder builder = new Notification.Builder(mContext) - .setContentIntent(createContentIntent(null)) - .setWhen(System.currentTimeMillis()) - .setOngoing(true) - .setShowWhen(true) - .setGroup(PRINT_JOB_NOTIFICATION_GROUP_KEY) - .setGroupSummary(true); - - final int printJobCount = printJobs.size(); - - InboxStyle inboxStyle = new InboxStyle(); - - int icon = com.android.internal.R.drawable.ic_print; - for (int i = printJobCount - 1; i>= 0; i--) { - PrintJobInfo printJob = printJobs.get(i); - - inboxStyle.addLine(computeNotificationTitle(printJob)); - - // if any print job is in an error state show an error icon for the summary - if (printJob.getState() == PrintJobInfo.STATE_FAILED - || printJob.getState() == PrintJobInfo.STATE_BLOCKED) { - icon = com.android.internal.R.drawable.ic_print_error; - } - } - - builder.setSmallIcon(icon); - builder.setLargeIcon( - ((BitmapDrawable) mContext.getResources().getDrawable(icon, null)).getBitmap()); - builder.setNumber(printJobCount); - builder.setStyle(inboxStyle); - builder.setColor(mContext.getColor( - com.android.internal.R.color.system_notification_accent_color)); - - mNotificationManager.notify(PRINT_JOB_NOTIFICATION_SUMMARY, 0, builder.build()); - } - private String computeNotificationTitle(PrintJobInfo printJob) { switch (printJob.getState()) { case PrintJobInfo.STATE_FAILED: { @@ -359,6 +323,22 @@ final class NotificationController { } } + private static String computeChannel(PrintJobInfo printJob) { + if (printJob.isCancelling()) { + return NOTIFICATION_CHANNEL_PROGRESS; + } + + switch (printJob.getState()) { + case PrintJobInfo.STATE_FAILED: + case PrintJobInfo.STATE_BLOCKED: { + return NOTIFICATION_CHANNEL_FAILURES; + } + default: { + return NOTIFICATION_CHANNEL_PROGRESS; + } + } + } + public static final class NotificationBroadcastReceiver extends BroadcastReceiver { @SuppressWarnings("hiding") private static final String LOG_TAG = "NotificationBroadcastReceiver"; diff --git a/packages/SettingsLib/Android.mk b/packages/SettingsLib/Android.mk index 67ef40a23e4e..1ad4fea5e0a5 100644 --- a/packages/SettingsLib/Android.mk +++ b/packages/SettingsLib/Android.mk @@ -6,6 +6,7 @@ LOCAL_USE_AAPT2 := true LOCAL_MODULE := SettingsLib LOCAL_SHARED_ANDROID_LIBRARIES := \ + android-support-annotations \ android-support-v4 \ android-support-v7-recyclerview \ android-support-v7-preference \ diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml index ad985c7e5f34..ff310cc233a7 100644 --- a/packages/SettingsLib/res/values/strings.xml +++ b/packages/SettingsLib/res/values/strings.xml @@ -781,8 +781,10 @@ <!-- Summary shown for color space correction preference when its value is overridden by another preference [CHAR LIMIT=35] --> <string name="daltonizer_type_overridden">Overridden by <xliff:g id="title" example="Simulate color space">%1$s</xliff:g></string> - <!-- [CHAR_LIMIT=40] Label for estimated remaining duration of battery charging/discharging --> - <string name="power_remaining_duration_only">Approx. <xliff:g id="time">%1$s</xliff:g> left</string> + <!-- [CHAR_LIMIT=40] Label for estimated remaining duration of battery discharging --> + <string name="power_remaining_duration_only">About <xliff:g id="time">%1$s</xliff:g> left</string> + <!-- [CHAR_LIMIT=40] Label for estimated remaining duration of battery charging --> + <string name="power_remaining_charging_duration_only"><xliff:g id="time">%1$s</xliff:g> left until fully charged</string> <!-- [CHAR_LIMIT=40] Short label for estimated remaining duration of battery charging/discharging --> <string name="power_remaining_duration_only_short"><xliff:g id="time">%1$s</xliff:g> left</string> @@ -804,41 +806,13 @@ <!-- [CHAR_LIMIT=40] Short label for battery level chart when charging with duration --> <string name="power_charging_duration_short"><xliff:g id="level">%1$s</xliff:g> - <xliff:g id="time">%2$s</xliff:g></string> - <!-- [CHAR_LIMIT=40] Label for battery level chart when charging with duration --> - <string name="power_charging_duration_ac"><xliff:g id="level">%1$s</xliff:g> - - <xliff:g id="time">%2$s</xliff:g> until fully charged on AC</string> - <!-- [CHAR_LIMIT=40] Short label for battery level chart when charging with duration --> - <string name="power_charging_duration_ac_short"><xliff:g id="level">%1$s</xliff:g> - - <xliff:g id="time">%2$s</xliff:g></string> - <!-- [CHAR_LIMIT=40] Label for battery level chart when charging with duration --> - <string name="power_charging_duration_usb"><xliff:g id="level">%1$s</xliff:g> - - <xliff:g id="time">%2$s</xliff:g> until fully charged over USB</string> - <!-- [CHAR_LIMIT=40] Short label for battery level chart when charging with duration --> - <string name="power_charging_duration_usb_short"><xliff:g id="level">%1$s</xliff:g> - - <xliff:g id="time">%2$s</xliff:g></string> - <!-- [CHAR_LIMIT=40] Label for battery level chart when charging with duration --> - <string name="power_charging_duration_wireless"><xliff:g id="level">%1$s</xliff:g> - - <xliff:g id="time">%2$s</xliff:g> until fully charged from wireless</string> - <!-- [CHAR_LIMIT=40] Label for battery level chart when charging with duration --> - <string name="power_charging_duration_wireless_short"><xliff:g id="level">%1$s</xliff:g> - - <xliff:g id="time">%2$s</xliff:g></string> <!-- Battery Info screen. Value for a status item. Used for diagnostic info screens, precise translation isn't needed --> <string name="battery_info_status_unknown">Unknown</string> <!-- [CHAR_LIMIT=20] Battery use screen. Battery status shown in chart label when charging from an unknown source. --> <string name="battery_info_status_charging">Charging</string> - <!-- [CHAR_LIMIT=20] Battery use screen. Battery status shown in chart label when charging on AC. --> - <string name="battery_info_status_charging_ac">Charging on AC</string> - <!-- [CHAR_LIMIT=20] Battery short status label when charing on AC --> - <string name="battery_info_status_charging_ac_short">Charging</string> - <!-- [CHAR_LIMIT=20] Battery use screen. Battery status shown in chart label when charging over USB. --> - <string name="battery_info_status_charging_usb">Charging over USB</string> - <!-- [CHAR_LIMIT=20] Battery short status label when charging over USB. --> - <string name="battery_info_status_charging_usb_short">Charging</string> - <!-- [CHAR_LIMIT=20] Battery use screen. Battery status shown in chart label when charging over a wireless connection. --> - <string name="battery_info_status_charging_wireless">Charging wirelessly</string> - <!-- [CHAR_LIMIT=20] Battery short status label when charging wirelessly. --> - <string name="battery_info_status_charging_wireless_short">Charging</string> + <!-- [CHAR_LIMIT=20] Battery use screen with lower case. Battery status shown in chart label when charging from an unknown source. --> + <string name="battery_info_status_charging_lower">charging</string> <!-- Battery Info screen. Value for a status item. Used for diagnostic info screens, precise translation isn't needed --> <string name="battery_info_status_discharging">Not charging</string> <!-- Battery Info screen. Value for a status item. Used for diagnostic info screens, precise translation isn't needed --> diff --git a/packages/SettingsLib/src/com/android/settingslib/BatteryInfo.java b/packages/SettingsLib/src/com/android/settingslib/BatteryInfo.java index 22f885611992..5b2541c8e7d1 100644 --- a/packages/SettingsLib/src/com/android/settingslib/BatteryInfo.java +++ b/packages/SettingsLib/src/com/android/settingslib/BatteryInfo.java @@ -97,7 +97,7 @@ public class BatteryInfo { } public static void getBatteryInfo(final Context context, final Callback callback) { - BatteryInfo.getBatteryInfo(context, callback, false); + BatteryInfo.getBatteryInfo(context, callback, false /* shortString */); } public static void getBatteryInfo(final Context context, final Callback callback, @@ -115,8 +115,8 @@ public class BatteryInfo { final long elapsedRealtimeUs = SystemClock.elapsedRealtime() * 1000; Intent batteryBroadcast = context.registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED)); - BatteryInfo batteryInfo = BatteryInfo.getBatteryInfo(context, - batteryBroadcast, batteryStats, elapsedRealtimeUs, shortString); + BatteryInfo batteryInfo = BatteryInfo.getBatteryInfo(context, batteryBroadcast, + batteryStats, elapsedRealtimeUs, shortString); callback.onBatteryInfoLoaded(batteryInfo); } }.execute(); @@ -125,7 +125,7 @@ public class BatteryInfo { public static BatteryInfo getBatteryInfo(Context context, Intent batteryBroadcast, BatteryStats stats, long elapsedRealtimeUs) { return BatteryInfo.getBatteryInfo(context, batteryBroadcast, stats, elapsedRealtimeUs, - false); + false /* shortString */); } public static BatteryInfo getBatteryInfo(Context context, Intent batteryBroadcast, @@ -136,7 +136,7 @@ public class BatteryInfo { info.batteryPercentString = Utils.formatPercentage(info.mBatteryLevel); info.mCharging = batteryBroadcast.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0) != 0; final Resources resources = context.getResources(); - info.statusLabel = Utils.getBatteryStatus(resources, batteryBroadcast, shortString); + info.statusLabel = Utils.getBatteryStatus(resources, batteryBroadcast); if (!info.mCharging) { final long drainTime = stats.computeBatteryTimeRemaining(elapsedRealtimeUs); if (drainTime > 0) { @@ -164,29 +164,18 @@ public class BatteryInfo { info.remainingTimeUs = chargeTime; String timeString = Formatter.formatShortElapsedTime(context, chargeTime / 1000); - int plugType = batteryBroadcast.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0); - int resId; - if (plugType == BatteryManager.BATTERY_PLUGGED_AC) { - resId = shortString ? R.string.power_charging_duration_ac_short - : R.string.power_charging_duration_ac; - } else if (plugType == BatteryManager.BATTERY_PLUGGED_USB) { - resId = shortString ? R.string.power_charging_duration_usb_short - : R.string.power_charging_duration_usb; - } else if (plugType == BatteryManager.BATTERY_PLUGGED_WIRELESS) { - resId = shortString ? R.string.power_charging_duration_wireless_short - : R.string.power_charging_duration_wireless; - } else { - resId = shortString ? R.string.power_charging_duration_short - : R.string.power_charging_duration; - } - info.remainingLabel = resources.getString(R.string.power_remaining_duration_only, + int resId = shortString ? R.string.power_charging_duration_short + : R.string.power_charging_duration; + info.remainingLabel = resources.getString( + R.string.power_remaining_charging_duration_only, timeString); + info.mChargeLabelString = resources.getString(resId, info.batteryPercentString, timeString); - info.mChargeLabelString = resources.getString( - resId, info.batteryPercentString, timeString); } else { + final String chargeStatusLabel = resources.getString( + R.string.battery_info_status_charging_lower); info.remainingLabel = null; info.mChargeLabelString = resources.getString( - R.string.power_charging, info.batteryPercentString, info.statusLabel); + R.string.power_charging, info.batteryPercentString, chargeStatusLabel); } } return info; diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java index 7e7b391ffd1a..78ad34acf8f0 100644 --- a/packages/SettingsLib/src/com/android/settingslib/Utils.java +++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java @@ -140,30 +140,11 @@ public class Utils { } public static String getBatteryStatus(Resources res, Intent batteryChangedIntent) { - return Utils.getBatteryStatus(res, batteryChangedIntent, false); - } - - public static String getBatteryStatus(Resources res, Intent batteryChangedIntent, - boolean shortString) { - int plugType = batteryChangedIntent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0); int status = batteryChangedIntent.getIntExtra(BatteryManager.EXTRA_STATUS, BatteryManager.BATTERY_STATUS_UNKNOWN); String statusString; if (status == BatteryManager.BATTERY_STATUS_CHARGING) { - int resId; - if (plugType == BatteryManager.BATTERY_PLUGGED_AC) { - resId = shortString ? R.string.battery_info_status_charging_ac_short - : R.string.battery_info_status_charging_ac; - } else if (plugType == BatteryManager.BATTERY_PLUGGED_USB) { - resId = shortString ? R.string.battery_info_status_charging_usb_short - : R.string.battery_info_status_charging_usb; - } else if (plugType == BatteryManager.BATTERY_PLUGGED_WIRELESS) { - resId = shortString ? R.string.battery_info_status_charging_wireless_short - : R.string.battery_info_status_charging_wireless; - } else { - resId = R.string.battery_info_status_charging; - } - statusString = res.getString(resId); + statusString = res.getString(R.string.battery_info_status_charging); } else if (status == BatteryManager.BATTERY_STATUS_DISCHARGING) { statusString = res.getString(R.string.battery_info_status_discharging); } else if (status == BatteryManager.BATTERY_STATUS_NOT_CHARGING) { diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java index 77f2e19abd97..a1c8de566578 100755 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java @@ -122,9 +122,6 @@ final class A2dpSinkProfile implements LocalBluetoothProfile { return true; } } - for (BluetoothDevice src : srcs) { - mService.disconnect(src); - } } return mService.connect(device); } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HfpClientProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HfpClientProfile.java index 9b699bcfd38c..169aac9eb60f 100755 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HfpClientProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HfpClientProfile.java @@ -132,11 +132,6 @@ final class HfpClientProfile implements LocalBluetoothProfile { return true; } } - // Handsfree HF only supports one source connection and hence it is OK to disconnect - // the only connected device here. - for (BluetoothDevice src : srcs) { - mService.disconnect(src); - } } return mService.connect(device); } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapClientProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapClientProfile.java index a7621fcf02fb..6efa4687d913 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapClientProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapClientProfile.java @@ -115,10 +115,10 @@ public final class MapClientProfile implements LocalBluetoothProfile { public boolean connect(BluetoothDevice device) { if (mService == null) return false; List<BluetoothDevice> connectedDevices = getConnectedDevices(); - if (connectedDevices != null) { - for (BluetoothDevice connectedDevice : connectedDevices) { - mService.disconnect(connectedDevice); - } + if (connectedDevices != null && connectedDevices.contains(device)) { + // Connect to same device, Ignore it + Log.d(TAG,"Ignoring Connect"); + return true; } return mService.connect(device); } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java index 72a3b3a00e22..bd37abeb3b0c 100755 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java @@ -144,9 +144,6 @@ final class PbapClientProfile implements LocalBluetoothProfile { return true; } } - for (BluetoothDevice src : srcs) { - mService.disconnect(src); - } } Log.d(TAG,"PBAPClientProfile attempting to connect to " + device.getAddress()); diff --git a/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java b/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java index 4bfca9b61d6b..474de9074062 100644 --- a/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java +++ b/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java @@ -211,7 +211,16 @@ public class ZoneGetter { if (preferLongName) { displayName = getZoneLongName(timeZoneNames, tz, now); } else { - displayName = timeZoneNames.getExemplarLocationName(tz.getID()); + // Canonicalize the zone ID for ICU. It will only return valid strings for zone IDs + // that match ICUs zone IDs (which are similar but not guaranteed the same as those + // in timezones.xml). timezones.xml and related files uses the IANA IDs. ICU IDs are + // stable and IANA IDs have changed over time so they have drifted. + // See http://bugs.icu-project.org/trac/ticket/13070 / http://b/36469833. + String canonicalZoneId = android.icu.util.TimeZone.getCanonicalID(tz.getID()); + if (canonicalZoneId == null) { + canonicalZoneId = tz.getID(); + } + displayName = timeZoneNames.getExemplarLocationName(canonicalZoneId); if (displayName == null || displayName.isEmpty()) { // getZoneExemplarLocation can return null. Fall back to the long name. displayName = getZoneLongName(timeZoneNames, tz, now); @@ -325,4 +334,4 @@ public class ZoneGetter { } } } -}
\ No newline at end of file +} diff --git a/packages/SettingsLib/src/com/android/settingslib/deviceinfo/PrivateStorageInfo.java b/packages/SettingsLib/src/com/android/settingslib/deviceinfo/PrivateStorageInfo.java index 40abb6c8c2c4..88f133ce57c2 100644 --- a/packages/SettingsLib/src/com/android/settingslib/deviceinfo/PrivateStorageInfo.java +++ b/packages/SettingsLib/src/com/android/settingslib/deviceinfo/PrivateStorageInfo.java @@ -41,8 +41,8 @@ public class PrivateStorageInfo { long privateTotalBytes = 0; for (VolumeInfo info : sm.getVolumes()) { if (info.getType() == VolumeInfo.TYPE_PRIVATE && info.isMountedReadable()) { - privateTotalBytes += stats.getTotalBytes(info.getFsUuid()); - privateFreeBytes += stats.getFreeBytes(info.getFsUuid()); + privateTotalBytes += sm.getTotalBytes(stats, info); + privateFreeBytes += sm.getFreeBytes(stats, info); } } return new PrivateStorageInfo(privateFreeBytes, privateTotalBytes); diff --git a/packages/SettingsLib/src/com/android/settingslib/deviceinfo/StorageManagerVolumeProvider.java b/packages/SettingsLib/src/com/android/settingslib/deviceinfo/StorageManagerVolumeProvider.java index 320494c68faf..11060e6c1a05 100644 --- a/packages/SettingsLib/src/com/android/settingslib/deviceinfo/StorageManagerVolumeProvider.java +++ b/packages/SettingsLib/src/com/android/settingslib/deviceinfo/StorageManagerVolumeProvider.java @@ -16,6 +16,7 @@ package com.android.settingslib.deviceinfo; +import android.app.usage.StorageStatsManager; import android.os.storage.StorageManager; import android.os.storage.VolumeInfo; @@ -46,4 +47,14 @@ public class StorageManagerVolumeProvider implements StorageVolumeProvider { public VolumeInfo findEmulatedForPrivate(VolumeInfo privateVolume) { return mStorageManager.findEmulatedForPrivate(privateVolume); } + + @Override + public long getTotalBytes(StorageStatsManager stats, VolumeInfo volume) { + return stats.getTotalBytes(volume.getFsUuid()); + } + + @Override + public long getFreeBytes(StorageStatsManager stats, VolumeInfo volume) { + return stats.getFreeBytes(volume.getFsUuid()); + } } diff --git a/packages/SettingsLib/src/com/android/settingslib/deviceinfo/StorageVolumeProvider.java b/packages/SettingsLib/src/com/android/settingslib/deviceinfo/StorageVolumeProvider.java index 646c42f05a8e..e5d85d147bee 100644 --- a/packages/SettingsLib/src/com/android/settingslib/deviceinfo/StorageVolumeProvider.java +++ b/packages/SettingsLib/src/com/android/settingslib/deviceinfo/StorageVolumeProvider.java @@ -16,6 +16,7 @@ package com.android.settingslib.deviceinfo; +import android.app.usage.StorageStatsManager; import android.os.storage.VolumeInfo; import java.util.List; @@ -39,4 +40,18 @@ public interface StorageVolumeProvider { * Returns the emulated volume for a given private volume. */ VolumeInfo findEmulatedForPrivate(VolumeInfo privateVolume); + + /** + * Returns the total bytes for a given storage volume. + * + * @pre The volume is a private volume and is readable. + */ + long getTotalBytes(StorageStatsManager stats, VolumeInfo volume); + + /** + * Returns the free bytes for a given storage volume. + * + * @pre The volume is a private volume and is readable. + */ + long getFreeBytes(StorageStatsManager stats, VolumeInfo volume); } diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java b/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java index f31c09b104fd..6bcf256a8033 100644 --- a/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java +++ b/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java @@ -135,10 +135,18 @@ public class TileUtils { * Name of the meta-data item that should be set in the AndroidManifest.xml * to specify the title that should be displayed for the preference. */ + @Deprecated public static final String META_DATA_PREFERENCE_TITLE = "com.android.settings.title"; /** * Name of the meta-data item that should be set in the AndroidManifest.xml + * to specify the title that should be displayed for the preference. + */ + public static final String META_DATA_PREFERENCE_TITLE_RES_ID = + "com.android.settings.title.resid"; + + /** + * Name of the meta-data item that should be set in the AndroidManifest.xml * to specify the summary text that should be displayed for the preference. */ public static final String META_DATA_PREFERENCE_SUMMARY = "com.android.settings.summary"; @@ -364,7 +372,16 @@ public class TileUtils { if (metaData.containsKey(META_DATA_PREFERENCE_ICON)) { icon = metaData.getInt(META_DATA_PREFERENCE_ICON); } - if (metaData.containsKey(META_DATA_PREFERENCE_TITLE)) { + int resId = 0; + if (metaData.containsKey(META_DATA_PREFERENCE_TITLE_RES_ID)) { + resId = metaData.getInt(META_DATA_PREFERENCE_TITLE_RES_ID); + if (resId != 0) { + title = res.getString(resId); + } + } + // Fallback to legacy title extraction if we couldn't get the title through + // res id. + if ((resId == 0) && metaData.containsKey(META_DATA_PREFERENCE_TITLE)) { if (metaData.get(META_DATA_PREFERENCE_TITLE) instanceof Integer) { title = res.getString(metaData.getInt(META_DATA_PREFERENCE_TITLE)); } else { diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java index 1ea4183fc326..901848af43a0 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java @@ -149,6 +149,7 @@ public class AccessPoint implements Comparable<AccessPoint> { private int mRankingScore = Integer.MIN_VALUE; private int mBadge = NetworkBadging.BADGING_NONE; + private boolean mIsScoredNetworkMetered = false; // used to co-relate internal vs returned accesspoint. int mId; @@ -248,6 +249,7 @@ public class AccessPoint implements Comparable<AccessPoint> { this.mScanResultCache.putAll(that.mScanResultCache); this.mId = that.mId; this.mBadge = that.mBadge; + this.mIsScoredNetworkMetered = that.mIsScoredNetworkMetered; this.mRankingScore = that.mRankingScore; } @@ -336,16 +338,32 @@ public class AccessPoint implements Comparable<AccessPoint> { builder.append(",level=").append(getLevel()); builder.append(",rankingScore=").append(mRankingScore); builder.append(",badge=").append(mBadge); + builder.append(",metered=").append(isMetered()); return builder.append(')').toString(); } /** + * Updates the AccessPoint rankingScore, metering, and badge, returning true if the data has + * changed. + * + * @param scoreCache The score cache to use to retrieve scores. + * @param scoringUiEnabled Whether to show scoring and badging UI. + */ + boolean update(WifiNetworkScoreCache scoreCache, boolean scoringUiEnabled) { + boolean scoreChanged = false; + if (scoringUiEnabled) { + scoreChanged = updateScores(scoreCache); + } + return updateMetered(scoreCache) || scoreChanged; + } + + /** * Updates the AccessPoint rankingScore and badge, returning true if the data has changed. * * @param scoreCache The score cache to use to retrieve scores. */ - boolean updateScores(WifiNetworkScoreCache scoreCache) { + private boolean updateScores(WifiNetworkScoreCache scoreCache) { int oldBadge = mBadge; int oldRankingScore = mRankingScore; mBadge = NetworkBadging.BADGING_NONE; @@ -366,6 +384,23 @@ public class AccessPoint implements Comparable<AccessPoint> { return (oldBadge != mBadge || oldRankingScore != mRankingScore); } + /** + * Updates the AccessPoint's metering based on {@link ScoredNetwork#meteredHint}, returning + * true if the metering changed. + */ + private boolean updateMetered(WifiNetworkScoreCache scoreCache) { + boolean oldMetering = mIsScoredNetworkMetered; + mIsScoredNetworkMetered = false; + for (ScanResult result : mScanResultCache.values()) { + ScoredNetwork score = scoreCache.getScoredNetwork(result); + if (score == null) { + continue; + } + mIsScoredNetworkMetered |= score.meteredHint; + } + return oldMetering == mIsScoredNetworkMetered; + } + private void evictOldScanResults() { long nowMs = SystemClock.elapsedRealtime(); for (Iterator<ScanResult> iter = mScanResultCache.values().iterator(); iter.hasNext(); ) { @@ -474,6 +509,17 @@ public class AccessPoint implements Comparable<AccessPoint> { mSeen = seen; } + /** + * Returns if the network is marked metered. Metering can be marked through its config in + * {@link WifiConfiguration}, after connection in {@link WifiInfo}, or from a score config in + * {@link ScoredNetwork}. + */ + public boolean isMetered() { + return mIsScoredNetworkMetered + || (mConfig != null && mConfig.meteredHint) + || (mInfo != null && mInfo.getMeteredHint()); + } + public NetworkInfo getNetworkInfo() { return mNetworkInfo; } diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java index 8f8167ee8187..e82bf8114fe9 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java @@ -183,7 +183,7 @@ public class AccessPointPreference extends Preference { } if (mAccessPoint.getSecurity() != AccessPoint.SECURITY_NONE) { mFrictionSld.setState(STATE_SECURED); - } else if (mAccessPoint.getConfig() != null && mAccessPoint.getConfig().meteredHint) { + } else if (mAccessPoint.isMetered()) { mFrictionSld.setState(STATE_METERED); } Drawable drawable = mFrictionSld.getCurrent(); diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java index 50f294c00eaa..fc8c42cd663f 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java @@ -543,11 +543,9 @@ public class WifiTracker { } } - if (mNetworkScoringUiEnabled) { - requestScoresForNetworkKeys(scoresToRequest); - for (AccessPoint ap : accessPoints) { - ap.updateScores(mScoreCache); - } + requestScoresForNetworkKeys(scoresToRequest); + for (AccessPoint ap : accessPoints) { + ap.update(mScoreCache, mNetworkScoringUiEnabled); } // Pre-sort accessPoints to speed preference insertion @@ -648,7 +646,7 @@ public class WifiTracker { if (ap.update(connectionConfig, mLastInfo, mLastNetworkInfo)) { reorder = true; } - if (mNetworkScoringUiEnabled && ap.updateScores(mScoreCache)) { + if (ap.update(mScoreCache, mNetworkScoringUiEnabled)) { reorder = true; } } @@ -659,15 +657,11 @@ public class WifiTracker { } /** - * Update all the internal access points rankingScores and badge. + * Update all the internal access points rankingScores, badge and metering. * * <p>Will trigger a resort and notify listeners of changes if applicable. */ private void updateNetworkScores() { - if (!mNetworkScoringUiEnabled) { - return; - } - // Lock required to prevent accidental copying of AccessPoint states while the modification // is in progress. see #copyAndNotifyListeners long before = System.currentTimeMillis(); @@ -679,7 +673,7 @@ public class WifiTracker { boolean reorder = false; for (int i = 0; i < mInternalAccessPoints.size(); i++) { - if (mInternalAccessPoints.get(i).updateScores(mScoreCache)) { + if (mInternalAccessPoints.get(i).update(mScoreCache, mNetworkScoringUiEnabled)) { reorder = true; } } diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTrackerFactory.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTrackerFactory.java index d3bdeb770c01..a2becf717e89 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTrackerFactory.java +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTrackerFactory.java @@ -17,6 +17,7 @@ package com.android.settingslib.wifi; import android.content.Context; import android.os.Looper; +import android.support.annotation.Keep; /** * Factory method used to inject WifiTracker instances. @@ -26,14 +27,7 @@ public class WifiTrackerFactory { private static WifiTracker sTestingWifiTracker; - public static void enableTestingMode() { - sTestingMode = true; - } - - public static void disableTestingMode() { - sTestingMode = false; - } - + @Keep // Keep proguard from stripping this method since it is only used in tests public static void setTestingWifiTracker(WifiTracker tracker) { sTestingWifiTracker = tracker; } diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java index 762d9f876cbe..3e01b34765c0 100644 --- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java +++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java @@ -17,15 +17,19 @@ package com.android.settingslib.wifi; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; - import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.when; import android.content.Context; import android.net.ConnectivityManager; import android.net.NetworkInfo; +import android.net.NetworkKey; +import android.net.ScoredNetwork; import android.net.wifi.ScanResult; import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiInfo; +import android.net.wifi.WifiNetworkScoreCache; import android.net.wifi.WifiSsid; import android.net.wifi.hotspot2.PasspointConfiguration; import android.net.wifi.hotspot2.pps.HomeSp; @@ -36,10 +40,11 @@ import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; import android.text.SpannableString; import android.text.style.TtsSpan; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; import java.util.ArrayList; import java.util.Collections; @@ -50,9 +55,11 @@ public class AccessPointTest { private static final String TEST_SSID = "test_ssid"; private Context mContext; + @Mock private WifiNetworkScoreCache mWifiNetworkScoreCache; @Before public void setUp() { + MockitoAnnotations.initMocks(this); mContext = InstrumentationRegistry.getTargetContext(); } @@ -74,6 +81,7 @@ public class AccessPointTest { @Test public void testCopyAccessPoint_dataShouldMatch() { WifiConfiguration configuration = createWifiConfiguration(); + configuration.meteredHint = true; NetworkInfo networkInfo = new NetworkInfo(ConnectivityManager.TYPE_WIFI, 2, "WIFI", "WIFI_SUBTYPE"); @@ -88,6 +96,7 @@ public class AccessPointTest { assertThat(originalAccessPoint.getBssid()).isEqualTo(copy.getBssid()); assertThat(originalAccessPoint.getConfig()).isEqualTo(copy.getConfig()); assertThat(originalAccessPoint.getSecurity()).isEqualTo(copy.getSecurity()); + assertThat(originalAccessPoint.isMetered()).isEqualTo(copy.isMetered()); assertThat(originalAccessPoint.compareTo(copy) == 0).isTrue(); } @@ -230,6 +239,55 @@ public class AccessPointTest { assertTrue(ap.isPasspointConfig()); } + @Test + public void testIsMetered_returnTrueWhenWifiConfigurationIsMetered() { + WifiConfiguration configuration = createWifiConfiguration(); + configuration.meteredHint = true; + + NetworkInfo networkInfo = + new NetworkInfo(ConnectivityManager.TYPE_WIFI, 2, "WIFI", "WIFI_SUBTYPE"); + AccessPoint accessPoint = new AccessPoint(mContext, configuration); + WifiInfo wifiInfo = new WifiInfo(); + wifiInfo.setSSID(WifiSsid.createFromAsciiEncoded(configuration.SSID)); + wifiInfo.setBSSID(configuration.BSSID); + wifiInfo.setNetworkId(configuration.networkId); + accessPoint.update(configuration, wifiInfo, networkInfo); + + assertTrue(accessPoint.isMetered()); + }; + + @Test + public void testIsMetered_returnTrueWhenWifiInfoIsMetered() { + WifiConfiguration configuration = createWifiConfiguration(); + + NetworkInfo networkInfo = + new NetworkInfo(ConnectivityManager.TYPE_WIFI, 2, "WIFI", "WIFI_SUBTYPE"); + AccessPoint accessPoint = new AccessPoint(mContext, configuration); + WifiInfo wifiInfo = new WifiInfo(); + wifiInfo.setSSID(WifiSsid.createFromAsciiEncoded(configuration.SSID)); + wifiInfo.setBSSID(configuration.BSSID); + wifiInfo.setNetworkId(configuration.networkId); + wifiInfo.setMeteredHint(true); + accessPoint.update(configuration, wifiInfo, networkInfo); + + assertTrue(accessPoint.isMetered()); + }; + + @Test + public void testIsMetered_returnTrueWhenScoredNetworkIsMetered() { + AccessPoint ap = createAccessPointWithScanResultCache(); + + when(mWifiNetworkScoreCache.getScoredNetwork(any(ScanResult.class))) + .thenReturn( + new ScoredNetwork( + null /* NetworkKey */, + null /* rssiCurve */, + true /* metered */)); + ap.update(mWifiNetworkScoreCache, false /* scoringUiEnabled */); + + assertTrue(ap.isMetered()); + }; + private AccessPoint createAccessPointWithScanResultCache() { Bundle bundle = new Bundle(); ArrayList<ScanResult> scanResults = new ArrayList<>(); diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java index 02deb44d0cd4..b71915f30e1c 100644 --- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java +++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java @@ -18,6 +18,7 @@ package com.android.settingslib.wifi; 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 static org.mockito.Mockito.any; @@ -302,7 +303,7 @@ public class WifiTrackerTest { new ScoredNetwork( NETWORK_KEY_2, mockCurve2, - false /* meteredHint */, + true /* meteredHint */, attr2); WifiNetworkScoreCache scoreCache = mScoreCacheCaptor.getValue(); @@ -515,6 +516,23 @@ public class WifiTrackerTest { } @Test + public void scoreCacheUpdateMeteredShouldUpdateAccessPointMetering() + throws InterruptedException { + WifiTracker tracker = createTrackerWithImmediateBroadcastsAndInjectInitialScanResults(); + updateScoresAndWaitForAccessPointsChangedCallback(); + + List<AccessPoint> aps = tracker.getAccessPoints(); + + for (AccessPoint ap : aps) { + if (ap.getSsidStr().equals(SSID_1)) { + assertFalse(ap.isMetered()); + } else if (ap.getSsidStr().equals(SSID_2)) { + assertTrue(ap.isMetered()); + } + } + } + + @Test public void noBadgesShouldBeInsertedIntoAccessPointWhenScoringUiDisabled() throws InterruptedException { Settings.Global.putInt( diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/BatteryInfoTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/BatteryInfoTest.java index 136495819b75..962c4e7c3a27 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/BatteryInfoTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/BatteryInfoTest.java @@ -21,6 +21,7 @@ import android.content.Intent; import android.os.BatteryManager; import android.os.BatteryStats; import android.os.SystemClock; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -31,14 +32,23 @@ import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyLong; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.when; @RunWith(RobolectricTestRunner.class) @Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) public class BatteryInfoTest { private static final String STATUS_FULL = "Full"; - private Intent mBatteryBroadcast; - @Mock + private static final String STATUS_CHARGING_NO_TIME = "Charging"; + private static final String STATUS_CHARGING_TIME = "Charging - 2h left"; + private static final long REMAINING_TIME_NULL = -1; + private static final long REMAINING_TIME = 2; + private Intent mDisChargingBatteryBroadcast; + private Intent mChargingBatteryBroadcast; + @Mock(answer = Answers.RETURNS_DEEP_STUBS) private BatteryStats mBatteryStats; @Mock(answer = Answers.RETURNS_DEEP_STUBS) private Context mContext; @@ -47,21 +57,53 @@ public class BatteryInfoTest { public void setUp() { MockitoAnnotations.initMocks(this); - mBatteryBroadcast = new Intent(); - mBatteryBroadcast.putExtra(BatteryManager.EXTRA_PLUGGED, 0); - mBatteryBroadcast.putExtra(BatteryManager.EXTRA_LEVEL, 0); - mBatteryBroadcast.putExtra(BatteryManager.EXTRA_SCALE, 100); - mBatteryBroadcast.putExtra(BatteryManager.EXTRA_STATUS, BatteryManager.BATTERY_STATUS_FULL); + mDisChargingBatteryBroadcast = new Intent(); + mDisChargingBatteryBroadcast.putExtra(BatteryManager.EXTRA_PLUGGED, 0); + mDisChargingBatteryBroadcast.putExtra(BatteryManager.EXTRA_LEVEL, 0); + mDisChargingBatteryBroadcast.putExtra(BatteryManager.EXTRA_SCALE, 100); + mDisChargingBatteryBroadcast.putExtra(BatteryManager.EXTRA_STATUS, + BatteryManager.BATTERY_STATUS_FULL); + + mChargingBatteryBroadcast = new Intent(); + mChargingBatteryBroadcast.putExtra(BatteryManager.EXTRA_PLUGGED, + BatteryManager.BATTERY_PLUGGED_AC); + mChargingBatteryBroadcast.putExtra(BatteryManager.EXTRA_LEVEL, 50); + mChargingBatteryBroadcast.putExtra(BatteryManager.EXTRA_SCALE, 100); + mChargingBatteryBroadcast.putExtra(BatteryManager.EXTRA_STATUS, + BatteryManager.BATTERY_STATUS_UNKNOWN); when(mContext.getResources().getString(R.string.battery_info_status_full)) .thenReturn(STATUS_FULL); + when(mContext.getResources().getString(eq(R.string.power_charging), any(), + any())).thenReturn(STATUS_CHARGING_NO_TIME); + when(mContext.getResources().getString(eq(R.string.power_charging_duration), any(), + any())).thenReturn(STATUS_CHARGING_TIME); } @Test - public void testGetBatteryInfo_HasStatusLabel() { - BatteryInfo info = BatteryInfo.getBatteryInfo(mContext, mBatteryBroadcast, mBatteryStats, - SystemClock.elapsedRealtime() * 1000, true); + public void testGetBatteryInfo_hasStatusLabel() { + doReturn(REMAINING_TIME_NULL).when(mBatteryStats).computeBatteryTimeRemaining(anyLong()); + BatteryInfo info = BatteryInfo.getBatteryInfo(mContext, mDisChargingBatteryBroadcast, + mBatteryStats, SystemClock.elapsedRealtime() * 1000, true); assertThat(info.statusLabel).isEqualTo(STATUS_FULL); } + + @Test + public void testGetBatteryInfo_doNotShowChargingMethod_hasRemainingTime() { + doReturn(REMAINING_TIME).when(mBatteryStats).computeChargeTimeRemaining(anyLong()); + BatteryInfo info = BatteryInfo.getBatteryInfo(mContext, mChargingBatteryBroadcast, + mBatteryStats, SystemClock.elapsedRealtime() * 1000, false); + + assertThat(info.mChargeLabelString).isEqualTo(STATUS_CHARGING_TIME); + } + + @Test + public void testGetBatteryInfo_doNotShowChargingMethod_noRemainingTime() { + doReturn(REMAINING_TIME_NULL).when(mBatteryStats).computeChargeTimeRemaining(anyLong()); + BatteryInfo info = BatteryInfo.getBatteryInfo(mContext, mChargingBatteryBroadcast, + mBatteryStats, SystemClock.elapsedRealtime() * 1000, false); + + assertThat(info.mChargeLabelString).isEqualTo(STATUS_CHARGING_NO_TIME); + } } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/RestrictedPreferenceHelperTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/RestrictedPreferenceHelperTest.java index 43e238ba02db..e5ad6ab84a48 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/RestrictedPreferenceHelperTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/RestrictedPreferenceHelperTest.java @@ -52,7 +52,7 @@ public class RestrictedPreferenceHelperTest { @Before public void setUp() { MockitoAnnotations.initMocks(this); - mViewHolder = new PreferenceViewHolder(mock(View.class)); + mViewHolder = PreferenceViewHolder.createInstanceForTests(mock(View.class)); mHelper = new RestrictedPreferenceHelper(mContext, mPreference, null); } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/TwoTargetPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/TwoTargetPreferenceTest.java index 59eca257443c..505d7a6c5a06 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/TwoTargetPreferenceTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/TwoTargetPreferenceTest.java @@ -52,7 +52,7 @@ public class TwoTargetPreferenceTest { MockitoAnnotations.initMocks(this); mContext = RuntimeEnvironment.application; mPreference = spy(new TwoTargetPreference(mContext)); - mViewHolder = new PreferenceViewHolder(mock(View.class)); + mViewHolder = PreferenceViewHolder.createInstanceForTests(mock(View.class)); when(mViewHolder.findViewById(R.id.two_target_divider)) .thenReturn(mDivider); when(mViewHolder.findViewById(android.R.id.widget_frame)) diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java index 2d3c4a786f5c..5310b7a604b0 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java @@ -231,6 +231,51 @@ public class TileUtilsTest { } @Test + public void getTilesForIntent_shouldReadMetadataTitleAsString() throws RemoteException { + Intent intent = new Intent(); + Map<Pair<String, String>, Tile> addedCache = new ArrayMap<>(); + List<Tile> outTiles = new ArrayList<>(); + List<ResolveInfo> info = new ArrayList<>(); + ResolveInfo resolveInfo = newInfo(true, null /* category */, null, URI_GET_ICON, + URI_GET_SUMMARY, "my title", 0); + info.add(resolveInfo); + + when(mPackageManager.queryIntentActivitiesAsUser(eq(intent), anyInt(), anyInt())) + .thenReturn(info); + + TileUtils.getTilesForIntent(mContext, UserHandle.CURRENT, intent, addedCache, + null /* defaultCategory */, outTiles, false /* usePriority */, + false /* checkCategory */); + + assertThat(outTiles.size()).isEqualTo(1); + assertThat(outTiles.get(0).title).isEqualTo("my title"); + } + + @Test + public void getTilesForIntent_shouldReadMetadataTitleFromResource() throws RemoteException { + Intent intent = new Intent(); + Map<Pair<String, String>, Tile> addedCache = new ArrayMap<>(); + List<Tile> outTiles = new ArrayList<>(); + List<ResolveInfo> info = new ArrayList<>(); + ResolveInfo resolveInfo = newInfo(true, null /* category */, null, URI_GET_ICON, + URI_GET_SUMMARY, null, 123); + info.add(resolveInfo); + + when(mPackageManager.queryIntentActivitiesAsUser(eq(intent), anyInt(), anyInt())) + .thenReturn(info); + + when(mResources.getString(eq(123))) + .thenReturn("my localized title"); + + TileUtils.getTilesForIntent(mContext, UserHandle.CURRENT, intent, addedCache, + null /* defaultCategory */, outTiles, false /* usePriority */, + false /* checkCategory */); + + assertThat(outTiles.size()).isEqualTo(1); + assertThat(outTiles.get(0).title).isEqualTo("my localized title"); + } + + @Test public void getTilesForIntent_shouldNotProcessInvalidUriContentSystemApp() throws RemoteException { Intent intent = new Intent(); @@ -298,7 +343,13 @@ public class TileUtilsTest { } private static ResolveInfo newInfo(boolean systemApp, String category, String keyHint, - String iconUri, String summaryUri) { + String iconUri, String summaryUri) { + return newInfo(systemApp, category, keyHint, iconUri, summaryUri, null, 0); + } + + private static ResolveInfo newInfo(boolean systemApp, String category, String keyHint, + String iconUri, String summaryUri, String title, int titleResId) { + ResolveInfo info = new ResolveInfo(); info.system = systemApp; info.activityInfo = new ActivityInfo(); @@ -317,6 +368,13 @@ public class TileUtilsTest { if (summaryUri != null) { info.activityInfo.metaData.putString("com.android.settings.summary_uri", summaryUri); } + if (title != null) { + info.activityInfo.metaData.putString(TileUtils.META_DATA_PREFERENCE_TITLE, title); + } + if (titleResId != 0) { + info.activityInfo.metaData.putInt( + TileUtils.META_DATA_PREFERENCE_TITLE_RES_ID, titleResId); + } info.activityInfo.applicationInfo = new ApplicationInfo(); if (systemApp) { info.activityInfo.applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM; diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index 1f559e4eb88a..169a034f447c 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java @@ -994,6 +994,15 @@ public class SettingsProvider extends ContentProvider { return false; } + private PackageInfo getCallingPackageInfo(int userId) { + try { + return mPackageManager.getPackageInfo(getCallingPackage(), + PackageManager.GET_SIGNATURES, userId); + } catch (RemoteException e) { + throw new IllegalStateException("Package " + getCallingPackage() + " doesn't exist"); + } + } + private Cursor getAllSecureSettings(int userId, String[] projection) { if (DEBUG) { Slog.v(LOG_TAG, "getAllSecureSettings(" + userId + ")"); @@ -1002,6 +1011,13 @@ public class SettingsProvider extends ContentProvider { // Resolve the userId on whose behalf the call is made. final int callingUserId = resolveCallingUserIdEnforcingPermissionsLocked(userId); + // The relevant "calling package" userId will be the owning userId for some + // profiles, and we can't do the lookup inside our [lock held] loop, so work out + // up front who the effective "new SSAID" user ID for that settings name will be. + final int ssaidUserId = resolveOwningUserIdForSecureSettingLocked(callingUserId, + Settings.Secure.ANDROID_ID); + final PackageInfo ssaidCallingPkg = getCallingPackageInfo(ssaidUserId); + synchronized (mLock) { List<String> names = getSettingsNamesLocked(SETTINGS_TYPE_SECURE, callingUserId); @@ -1026,7 +1042,7 @@ public class SettingsProvider extends ContentProvider { // SETTINGS_FILE_SSAID, unless accessed by a system process. final Setting setting; if (isNewSsaidSetting(name)) { - setting = getSsaidSettingLocked(owningUserId); + setting = getSsaidSettingLocked(ssaidCallingPkg, owningUserId); } else { setting = mSettingsRegistry.getSettingLocked(SETTINGS_TYPE_SECURE, owningUserId, name); @@ -1060,14 +1076,17 @@ public class SettingsProvider extends ContentProvider { return settings != null ? settings.getNullSetting() : null; } - // Get the value. - synchronized (mLock) { - // As of Android O, the SSAID is read from an app-specific entry in table - // SETTINGS_FILE_SSAID, unless accessed by a system process. - if (isNewSsaidSetting(name)) { - return getSsaidSettingLocked(owningUserId); + // As of Android O, the SSAID is read from an app-specific entry in table + // SETTINGS_FILE_SSAID, unless accessed by a system process. + if (isNewSsaidSetting(name)) { + PackageInfo callingPkg = getCallingPackageInfo(owningUserId); + synchronized (mLock) { + return getSsaidSettingLocked(callingPkg, owningUserId); } + } + // Not the SSAID; do a straight lookup + synchronized (mLock) { return mSettingsRegistry.getSettingLocked(SETTINGS_TYPE_SECURE, owningUserId, name); } @@ -1078,7 +1097,7 @@ public class SettingsProvider extends ContentProvider { && UserHandle.getAppId(Binder.getCallingUid()) >= Process.FIRST_APPLICATION_UID; } - private Setting getSsaidSettingLocked(int owningUserId) { + private Setting getSsaidSettingLocked(PackageInfo callingPkg, int owningUserId) { // Get uid of caller (key) used to store ssaid value String name = Integer.toString( UserHandle.getUid(owningUserId, UserHandle.getAppId(Binder.getCallingUid()))); @@ -1093,7 +1112,7 @@ public class SettingsProvider extends ContentProvider { // Lazy initialize ssaid if not yet present in ssaid table. if (ssaid == null || ssaid.isNull() || ssaid.getValue() == null) { - return mSettingsRegistry.generateSsaidLocked(getCallingPackage(), owningUserId); + return mSettingsRegistry.generateSsaidLocked(callingPkg, owningUserId); } return ssaid; @@ -2070,15 +2089,7 @@ public class SettingsProvider extends ContentProvider { return ByteBuffer.allocate(4).putInt(data.length).array(); } - public Setting generateSsaidLocked(String packageName, int userId) { - final PackageInfo packageInfo; - try { - packageInfo = mPackageManager.getPackageInfo(packageName, - PackageManager.GET_SIGNATURES, userId); - } catch (RemoteException e) { - throw new IllegalStateException("Package info doesn't exist"); - } - + public Setting generateSsaidLocked(PackageInfo callingPkg, int userId) { // Read the user's key from the ssaid table. Setting userKeySetting = getSettingLocked(SETTINGS_TYPE_SSAID, userId, SSAID_USER_KEY); if (userKeySetting == null || userKeySetting.isNull() @@ -2113,11 +2124,12 @@ public class SettingsProvider extends ContentProvider { } // Mac the package name and each of the signatures. - byte[] packageNameBytes = packageInfo.packageName.getBytes(StandardCharsets.UTF_8); + final String packageName = callingPkg.packageName; + byte[] packageNameBytes = packageName.getBytes(StandardCharsets.UTF_8); m.update(getLengthPrefix(packageNameBytes), 0, 4); m.update(packageNameBytes); - for (int i = 0; i < packageInfo.signatures.length; i++) { - byte[] sig = packageInfo.signatures[i].toByteArray(); + for (int i = 0; i < callingPkg.signatures.length; i++) { + byte[] sig = callingPkg.signatures[i].toByteArray(); m.update(getLengthPrefix(sig), 0, 4); m.update(sig); } @@ -2127,7 +2139,7 @@ public class SettingsProvider extends ContentProvider { .toLowerCase(Locale.US); // Save the ssaid in the ssaid table. - final String uid = Integer.toString(packageInfo.applicationInfo.uid); + final String uid = Integer.toString(callingPkg.applicationInfo.uid); final SettingsState ssaidSettings = getSettingsLocked(SETTINGS_TYPE_SSAID, userId); final boolean success = ssaidSettings.insertSettingLocked(uid, ssaid, null, true, packageName); diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index 1147f16f5522..55f32d7d8b43 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -22,6 +22,9 @@ android:sharedUserId="android.uid.systemui" coreApp="true"> + <protected-broadcast android:name="com.android.systemui.action.PLUGIN_CHANGED" /> + + <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> @@ -183,6 +186,9 @@ <!-- to control accessibility volume --> <uses-permission android:name="android.permission.CHANGE_ACCESSIBILITY_VOLUME" /> + <!-- to access ResolverRankerServices --> + <uses-permission android:name="android.permission.BIND_RESOLVER_RANKER_SERVICE" /> + <application android:name=".SystemUIApplication" android:persistent="true" diff --git a/packages/SystemUI/res/layout/notification_info.xml b/packages/SystemUI/res/layout/notification_info.xml index 195eb9bafdab..ff22ffb319fb 100644 --- a/packages/SystemUI/res/layout/notification_info.xml +++ b/packages/SystemUI/res/layout/notification_info.xml @@ -97,6 +97,15 @@ android:layout_height="wrap_content" android:text="@string/notification_channel_disabled" style="@style/TextAppearance.NotificationInfo.Secondary" /> + <!-- Optional link to app. Only appears if the channel is not disabled --> + <TextView + android:id="@+id/app_settings" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:visibility="gone" + android:ellipsize="end" + android:maxLines="1" + style="@style/TextAppearance.NotificationInfo.Secondary.Link"/> </LinearLayout> <!-- Ban Channel Switch --> <Switch diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 2263f23558e3..7bd9526cb274 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -1472,6 +1472,9 @@ <!-- Notification: Control panel: Label for button that launches notification settings. Used when this app has only defined a single channel for notifications. --> <string name="notification_more_settings">More settings</string> + <!-- Notification: Control panel: Label for a link that launches notification settings in the + app that sent the notification. --> + <string name="notification_app_settings">Customize: <xliff:g id="sub_category" example="Work chats">%1$s</xliff:g></string> <!-- Notification: Control panel: Label for button that dismisses control panel. [CHAR LIMIT=NONE] --> <string name="notification_done">Done</string> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index d6abda6bd4c9..c9479b8b41f7 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -370,6 +370,10 @@ <item name="android:textColor">?android:attr/colorError</item> </style> + <style name="TextAppearance.NotificationInfo.Secondary.Link"> + <item name="android:textColor">?android:attr/colorAccent</item> + </style> + <style name="TextAppearance.NotificationInfo.Button"> <item name="android:fontFamily">sans-serif-medium</item> <item name="android:textSize">14sp</item> diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java index fbd9f0c54725..4e7cf723f662 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java @@ -87,7 +87,9 @@ public class PipMenuActivity extends Activity { private boolean mMenuVisible; private boolean mAllowMenuTimeout = true; + private final List<RemoteAction> mActions = new ArrayList<>(); + private View mViewRoot; private Drawable mBackgroundDrawable; private View mMenuContainer; @@ -266,7 +268,6 @@ public class PipMenuActivity extends Activity { } notifyMenuVisibility(true); updateExpandButtonFromBounds(stackBounds, movementBounds); - setDecorViewVisibility(true); mMenuContainerAnimator = ObjectAnimator.ofFloat(mMenuContainer, View.ALPHA, mMenuContainer.getAlpha(), 1f); mMenuContainerAnimator.setInterpolator(Interpolators.ALPHA_IN); @@ -311,11 +312,15 @@ public class PipMenuActivity extends Activity { if (animationFinishedRunnable != null) { animationFinishedRunnable.run(); } - setDecorViewVisibility(false); + + finish(); } }); mMenuContainerAnimator.addUpdateListener(mMenuBgUpdateListener); mMenuContainerAnimator.start(); + } else { + // If the menu is not visible, just finish now + finish(); } } @@ -431,7 +436,6 @@ public class PipMenuActivity extends Activity { alpha = (int) (fraction * DISMISS_BACKGROUND_ALPHA * 255); } mBackgroundDrawable.setAlpha(alpha); - setDecorViewVisibility(alpha > 0); } private void notifyRegisterInputConsumer() { @@ -508,16 +512,4 @@ public class PipMenuActivity extends Activity { v.removeCallbacks(mFinishRunnable); v.postDelayed(mFinishRunnable, delay); } - - /** - * Sets the visibility of the root view of the window to disable drawing and touches for the - * activity. This differs from {@link Activity#setVisible(boolean)} in that it does not set - * the internal mVisibleFromClient state. - */ - private void setDecorViewVisibility(boolean visible) { - final View decorView = getWindow().getDecorView(); - if (decorView != null) { - decorView.setVisibility(visible ? View.VISIBLE : View.INVISIBLE); - } - } } diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java index bcaa39528837..875fb141bb7b 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java @@ -50,6 +50,7 @@ import java.util.List; public class PipMenuActivityController { private static final String TAG = "PipMenuActController"; + private static final boolean DEBUG = false; public static final String EXTRA_CONTROLLER_MESSENGER = "messenger"; public static final String EXTRA_ACTIONS = "actions"; @@ -195,6 +196,10 @@ public class PipMenuActivityController { * Updates the appearance of the menu and scrim on top of the PiP while dismissing. */ public void setDismissFraction(float fraction) { + if (DEBUG) { + Log.d(TAG, "setDismissFraction() hasActivity=" + (mToActivityMessenger != null) + + " fraction=" + fraction); + } if (mToActivityMessenger != null) { mTmpDismissFractionData.clear(); mTmpDismissFractionData.putFloat(EXTRA_DISMISS_FRACTION, fraction); @@ -216,6 +221,9 @@ public class PipMenuActivityController { * Shows the menu activity. */ public void showMenu(Rect stackBounds, Rect movementBounds, boolean allowMenuTimeout) { + if (DEBUG) { + Log.d(TAG, "showMenu() hasActivity=" + (mToActivityMessenger != null)); + } if (mToActivityMessenger != null) { Bundle data = new Bundle(); data.putParcelable(EXTRA_STACK_BOUNDS, stackBounds); @@ -238,6 +246,9 @@ public class PipMenuActivityController { * Pokes the menu, indicating that the user is interacting with it. */ public void pokeMenu() { + if (DEBUG) { + Log.d(TAG, "pokeMenu() hasActivity=" + (mToActivityMessenger != null)); + } if (mToActivityMessenger != null) { Message m = Message.obtain(); m.what = PipMenuActivity.MESSAGE_POKE_MENU; @@ -253,6 +264,9 @@ public class PipMenuActivityController { * Hides the menu activity. */ public void hideMenu() { + if (DEBUG) { + Log.d(TAG, "hideMenu() hasActivity=" + (mToActivityMessenger != null)); + } if (mToActivityMessenger != null) { Message m = Message.obtain(); m.what = PipMenuActivity.MESSAGE_HIDE_MENU; @@ -365,6 +379,10 @@ public class PipMenuActivityController { * Handles changes in menu visibility. */ private void onMenuVisibilityChanged(boolean visible, boolean resize) { + if (DEBUG) { + Log.d(TAG, "onMenuVisibilityChanged() mMenuVisible=" + mMenuVisible + + " menuVisible=" + visible + " resize=" + resize); + } if (visible) { mInputConsumerController.unregisterInputConsumer(); } else { @@ -389,6 +407,7 @@ public class PipMenuActivityController { final String innerPrefix = prefix + " "; pw.println(prefix + TAG); pw.println(innerPrefix + "mMenuVisible=" + mMenuVisible); + pw.println(innerPrefix + "mToActivityMessenger=" + mToActivityMessenger); pw.println(innerPrefix + "mListeners=" + mListeners.size()); } } diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java index a14a71247da3..fb8574da8e81 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java @@ -23,6 +23,7 @@ import static com.android.systemui.Interpolators.FAST_OUT_SLOW_IN; import static com.android.systemui.Interpolators.LINEAR_OUT_SLOW_IN; import android.animation.Animator; +import android.animation.Animator.AnimatorListener; import android.animation.AnimatorListenerAdapter; import android.animation.RectEvaluator; import android.animation.ValueAnimator; @@ -253,7 +254,7 @@ public class PipMotionHelper { * Flings the PiP to the closest snap target. */ Rect flingToSnapTarget(float velocity, float velocityX, float velocityY, Rect movementBounds, - AnimatorUpdateListener listener) { + AnimatorUpdateListener updateListener, AnimatorListener listener) { cancelAnimations(); Rect toBounds = mSnapAlgorithm.findClosestSnapBounds(movementBounds, mBounds, velocityX, velocityY); @@ -263,8 +264,11 @@ public class PipMotionHelper { mFlingAnimationUtils.apply(mBoundsAnimator, 0, distanceBetweenRectOffsets(mBounds, toBounds), velocity); - if (listener != null) { - mBoundsAnimator.addUpdateListener(listener); + if (updateListener != null) { + mBoundsAnimator.addUpdateListener(updateListener); + } + if (listener != null){ + mBoundsAnimator.addListener(listener); } mBoundsAnimator.start(); } @@ -274,14 +278,18 @@ public class PipMotionHelper { /** * Animates the PiP to the closest snap target. */ - Rect animateToClosestSnapTarget(Rect movementBounds, AnimatorUpdateListener listener) { + Rect animateToClosestSnapTarget(Rect movementBounds, AnimatorUpdateListener updateListener, + AnimatorListener listener) { cancelAnimations(); Rect toBounds = mSnapAlgorithm.findClosestSnapBounds(movementBounds, mBounds); if (!mBounds.equals(toBounds)) { mBoundsAnimator = createAnimationToBounds(mBounds, toBounds, SNAP_STACK_DURATION, FAST_OUT_SLOW_IN, mUpdateBoundsListener); - if (listener != null) { - mBoundsAnimator.addUpdateListener(listener); + if (updateListener != null) { + mBoundsAnimator.addUpdateListener(updateListener); + } + if (listener != null){ + mBoundsAnimator.addListener(listener); } mBoundsAnimator.start(); } diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java index d68836c717e4..161bdac77f89 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java @@ -16,6 +16,8 @@ package com.android.systemui.pip.phone; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; import android.animation.ValueAnimator.AnimatorUpdateListener; import android.app.IActivityManager; @@ -391,7 +393,10 @@ public class PipTouchHandler implements TunerService.Tunable { final float distance = bounds.bottom - target; fraction = Math.min(distance / bounds.height(), 1f); } - mMenuController.setDismissFraction(fraction); + if (Float.compare(fraction, 0f) != 0 || mMenuController.isMenuVisible()) { + // Update if the fraction > 0, or if fraction == 0 and the menu was already visible + mMenuController.setDismissFraction(fraction); + } } } @@ -611,22 +616,34 @@ public class PipTouchHandler implements TunerService.Tunable { setMinimizedStateInternal(false); } - // If the menu is still visible, and we aren't minimized, then just poke the menu - // so that it will timeout after the user stops touching it + AnimatorListenerAdapter postAnimationCallback = null; if (mMenuController.isMenuVisible()) { + // If the menu is still visible, and we aren't minimized, then just poke the + // menu so that it will timeout after the user stops touching it mMenuController.showMenu(mMotionHelper.getBounds(), mMovementBounds, true /* allowMenuTimeout */); + } else { + // If the menu is not visible, then we can still be showing the activity for the + // dismiss overlay, so just finish it after the animation completes + postAnimationCallback = new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mMenuController.hideMenu(); + } + }; } if (isFling) { mMotionHelper.flingToSnapTarget(velocity, vel.x, vel.y, mMovementBounds, - mUpdateScrimListener); + mUpdateScrimListener, postAnimationCallback); } else { - mMotionHelper.animateToClosestSnapTarget(mMovementBounds, mUpdateScrimListener); + mMotionHelper.animateToClosestSnapTarget(mMovementBounds, mUpdateScrimListener, + postAnimationCallback); } } else if (mIsMinimized) { // This was a tap, so no longer minimized - mMotionHelper.animateToClosestSnapTarget(mMovementBounds, null /* listener */); + mMotionHelper.animateToClosestSnapTarget(mMovementBounds, null /* updateListener */, + null /* animatorListener */); setMinimizedStateInternal(false); } else if (!mIsMenuVisible) { mMenuController.showMenu(mMotionHelper.getBounds(), mMovementBounds, diff --git a/packages/SystemUI/src/com/android/systemui/plugins/PluginInstanceManager.java b/packages/SystemUI/src/com/android/systemui/plugins/PluginInstanceManager.java index 07ac52d718aa..a8daed531357 100644 --- a/packages/SystemUI/src/com/android/systemui/plugins/PluginInstanceManager.java +++ b/packages/SystemUI/src/com/android/systemui/plugins/PluginInstanceManager.java @@ -48,7 +48,7 @@ public class PluginInstanceManager<T extends Plugin> { private static final boolean DEBUG = false; private static final String TAG = "PluginInstanceManager"; - private static final String PLUGIN_PERMISSION = "com.android.systemui.permission.PLUGIN"; + public static final String PLUGIN_PERMISSION = "com.android.systemui.permission.PLUGIN"; private final Context mContext; private final PluginListener<T> mListener; diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java index 3a43d304b1ca..1c71da007fd0 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java +++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java @@ -355,10 +355,9 @@ public class SystemServicesProxy { rti.firstActiveTime = rti.lastActiveTime = i; if (i % 2 == 0) { rti.taskDescription = new ActivityManager.TaskDescription(description, - Bitmap.createBitmap(mDummyIcon), null, - 0xFF000000 | (0xFFFFFF & new Random().nextInt()), - 0xFF000000 | (0xFFFFFF & new Random().nextInt()), - 0, 0); + Bitmap.createBitmap(mDummyIcon), null, + 0xFF000000 | (0xFFFFFF & new Random().nextInt()), + 0xFF000000 | (0xFFFFFF & new Random().nextInt())); } else { rti.taskDescription = new ActivityManager.TaskDescription(); } diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java index dc666e90fbdb..311f8ffe258f 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java @@ -524,7 +524,7 @@ public class TaskViewHeader extends FrameLayout * changes. */ public void onTaskDataLoaded() { - if (mTask.icon != null) { + if (mTask != null && mTask.icon != null) { mIconView.setImageDrawable(mTask.icon); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java index 8298f35bbcca..b4b1cd3409f6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java @@ -16,45 +16,34 @@ package com.android.systemui.statusbar; -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; +import static android.app.NotificationManager.IMPORTANCE_NONE; + import android.app.INotificationManager; +import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationChannelGroup; -import android.app.NotificationManager; import android.content.Context; +import android.content.Intent; +import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; -import android.content.pm.ParceledListSlice; -import android.content.res.ColorStateList; -import android.content.res.TypedArray; -import android.graphics.Canvas; +import android.content.pm.ResolveInfo; import android.graphics.drawable.Drawable; -import android.os.Handler; import android.os.RemoteException; -import android.os.ServiceManager; -import android.service.notification.NotificationListenerService; +import android.service.notification.StatusBarNotification; +import android.text.TextUtils; import android.util.AttributeSet; -import android.util.Log; import android.view.View; -import android.view.ViewAnimationUtils; -import android.view.ViewGroup; -import android.view.View.OnClickListener; -import android.widget.CompoundButton; import android.widget.ImageView; import android.widget.LinearLayout; -import android.widget.SeekBar; import android.widget.Switch; import android.widget.TextView; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.settingslib.Utils; -import com.android.systemui.Interpolators; import com.android.systemui.R; -import com.android.systemui.statusbar.NotificationGuts.OnSettingsClickListener; -import com.android.systemui.statusbar.stack.StackStateAnimator; import java.lang.IllegalArgumentException; import java.util.List; @@ -71,12 +60,16 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G private int mAppUid; private List<NotificationChannel> mNotificationChannels; private NotificationChannel mSingleNotificationChannel; + private StatusBarNotification mSbn; private int mStartingUserImportance; private TextView mNumChannelsView; private View mChannelDisabledView; + private TextView mSettingsLinkView; private Switch mChannelEnabledSwitch; private CheckSaveListener mCheckSaveListener; + private OnAppSettingsClickListener mAppSettingsClickListener; + private PackageManager mPm; private NotificationGuts mGutsContainer; @@ -95,11 +88,18 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G void onClick(View v, NotificationChannel channel, int appUid); } + public interface OnAppSettingsClickListener { + void onClick(View v, Intent intent); + } + public void bindNotification(final PackageManager pm, final INotificationManager iNotificationManager, final String pkg, final List<NotificationChannel> notificationChannels, + int startingUserImportance, + final StatusBarNotification sbn, OnSettingsClickListener onSettingsClick, + OnAppSettingsClickListener onAppSettingsClick, OnClickListener onDoneClick, CheckSaveListener checkSaveListener, final Set<String> nonBlockablePkgs) @@ -108,16 +108,21 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G mPkg = pkg; mNotificationChannels = notificationChannels; mCheckSaveListener = checkSaveListener; + mSbn = sbn; + mPm = pm; + mAppSettingsClickListener = onAppSettingsClick; boolean isSingleDefaultChannel = false; + mStartingUserImportance = startingUserImportance; if (mNotificationChannels.isEmpty()) { throw new IllegalArgumentException("bindNotification requires at least one channel"); - } else if (mNotificationChannels.size() == 1) { - mSingleNotificationChannel = mNotificationChannels.get(0); - mStartingUserImportance = mSingleNotificationChannel.getImportance(); - isSingleDefaultChannel = mSingleNotificationChannel.getId() - .equals(NotificationChannel.DEFAULT_CHANNEL_ID); - } else { - mSingleNotificationChannel = null; + } else { + if (mNotificationChannels.size() == 1) { + mSingleNotificationChannel = mNotificationChannels.get(0); + isSingleDefaultChannel = mSingleNotificationChannel.getId() + .equals(NotificationChannel.DEFAULT_CHANNEL_ID); + } else { + mSingleNotificationChannel = null; + } } String appName = mPkg; @@ -248,6 +253,9 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G final TextView doneButton = (TextView) findViewById(R.id.done); doneButton.setText(R.string.notification_done); doneButton.setOnClickListener(onDoneClick); + + // Optional settings link + updateAppSettingsLink(); } private boolean hasImportanceChanged() { @@ -276,7 +284,7 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G private int getSelectedImportance() { if (!mChannelEnabledSwitch.isChecked()) { - return NotificationManager.IMPORTANCE_NONE; + return IMPORTANCE_NONE; } else { return mStartingUserImportance; } @@ -286,7 +294,7 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G // Enabled Switch mChannelEnabledSwitch = (Switch) findViewById(R.id.channel_enabled_switch); mChannelEnabledSwitch.setChecked( - mStartingUserImportance != NotificationManager.IMPORTANCE_NONE); + mStartingUserImportance != IMPORTANCE_NONE); final boolean visible = !nonBlockable && mSingleNotificationChannel != null; mChannelEnabledSwitch.setVisibility(visible ? View.VISIBLE : View.INVISIBLE); @@ -296,12 +304,13 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G mGutsContainer.resetFalsingCheck(); } updateSecondaryText(); + updateAppSettingsLink(); }); } private void updateSecondaryText() { final boolean disabled = mSingleNotificationChannel != null && - getSelectedImportance() == NotificationManager.IMPORTANCE_NONE; + getSelectedImportance() == IMPORTANCE_NONE; if (disabled) { mChannelDisabledView.setVisibility(View.VISIBLE); mNumChannelsView.setVisibility(View.GONE); @@ -311,6 +320,45 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G } } + private void updateAppSettingsLink() { + mSettingsLinkView = findViewById(R.id.app_settings); + Intent settingsIntent = getAppSettingsIntent(mPm, mPkg, mSingleNotificationChannel, + mSbn.getId(), mSbn.getTag()); + if (settingsIntent != null && getSelectedImportance() != IMPORTANCE_NONE + && !TextUtils.isEmpty(mSbn.getNotification().getSettingsText())) { + mSettingsLinkView.setVisibility(View.VISIBLE); + mSettingsLinkView.setText(mContext.getString(R.string.notification_app_settings, + mSbn.getNotification().getSettingsText())); + mSettingsLinkView.setOnClickListener((View view) -> { + mAppSettingsClickListener.onClick(view, settingsIntent); + }); + } else { + mSettingsLinkView.setVisibility(View.GONE); + } + } + + private Intent getAppSettingsIntent(PackageManager pm, String packageName, + NotificationChannel channel, int id, String tag) { + Intent intent = new Intent(Intent.ACTION_MAIN) + .addCategory(Notification.INTENT_CATEGORY_NOTIFICATION_PREFERENCES) + .setPackage(packageName); + final List<ResolveInfo> resolveInfos = pm.queryIntentActivities( + intent, + PackageManager.MATCH_DEFAULT_ONLY + ); + if (resolveInfos == null || resolveInfos.size() == 0 || resolveInfos.get(0) == null) { + return null; + } + final ActivityInfo activityInfo = resolveInfos.get(0).activityInfo; + intent.setClassName(activityInfo.packageName, activityInfo.name); + if (channel != null) { + intent.putExtra(Notification.EXTRA_CHANNEL_ID, channel.getId()); + } + intent.putExtra(Notification.EXTRA_NOTIFICATION_ID, id); + intent.putExtra(Notification.EXTRA_NOTIFICATION_TAG, tag); + return intent; + } + @Override public void setGutsParent(NotificationGuts guts) { mGutsContainer = guts; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java index 6354255d2711..51345c20abe1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java @@ -24,7 +24,6 @@ import android.view.View; import android.view.ViewGroup; import android.view.accessibility.AccessibilityNodeInfo; -import com.android.internal.widget.CachingIconView; import com.android.systemui.Interpolators; import com.android.systemui.R; import com.android.systemui.ViewInvertHelper; @@ -128,12 +127,8 @@ public class NotificationShelf extends ActivatableNotificationView implements super.setDark(dark, fade, delay); if (mDark == dark) return; mDark = dark; - if (fade) { - mViewInvertHelper.fade(dark, delay); - } else { - mViewInvertHelper.update(dark); - } - mShelfIcons.setAmbient(dark); + mShelfIcons.setDark(dark, fade, delay); + updateInteractiveness(); } @Override @@ -440,7 +435,8 @@ public class NotificationShelf extends ActivatableNotificationView implements iconState.scaleY = 1.0f; iconState.hidden = false; } - if (row.isAboveShelf() || (!row.isInShelf() && isLastChild && row.areGutsExposed())) { + if (row.isAboveShelf() || (!row.isInShelf() && (isLastChild && row.areGutsExposed() + || row.getTranslationZ() > mAmbientState.getBaseZHeight()))) { iconState.hidden = true; } int shelfColor = icon.getStaticDrawableColor(); @@ -577,7 +573,8 @@ public class NotificationShelf extends ActivatableNotificationView implements } private void updateInteractiveness() { - mInteractive = mStatusBarState == StatusBarState.KEYGUARD && mHasItemsInStableShelf; + mInteractive = mStatusBarState == StatusBarState.KEYGUARD && mHasItemsInStableShelf + && !mDark; setClickable(mInteractive); setFocusable(mInteractive); setImportantForAccessibility(mInteractive ? View.IMPORTANT_FOR_ACCESSIBILITY_YES diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java index b9c8a784e779..92bfae9e3f5c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java @@ -27,6 +27,7 @@ import android.content.res.ColorStateList; import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Canvas; +import android.graphics.Color; import android.graphics.Paint; import android.graphics.Rect; import android.graphics.drawable.Drawable; @@ -46,6 +47,7 @@ import android.view.animation.Interpolator; import com.android.internal.statusbar.StatusBarIcon; import com.android.systemui.Interpolators; import com.android.systemui.R; +import com.android.systemui.statusbar.notification.NotificationIconDozeHelper; import com.android.systemui.statusbar.notification.NotificationUtils; import java.text.NumberFormat; @@ -99,7 +101,6 @@ public class StatusBarIconView extends AnimatedImageView { private int mDensity; private float mIconScale = 1.0f; private final Paint mDotPaint = new Paint(); - private boolean mDotVisible; private float mDotRadius; private int mStaticDotRadius; private int mVisibleState = STATE_ICON; @@ -110,6 +111,8 @@ public class StatusBarIconView extends AnimatedImageView { private OnVisibilityChangedListener mOnVisibilityChangedListener; private int mDrawableColor; private int mIconColor; + private int mDecorColor; + private float mDarkAmount; private ValueAnimator mColorAnimator; private int mCurrentSetColor = NO_COLOR; private int mAnimationStartColor = NO_COLOR; @@ -119,6 +122,7 @@ public class StatusBarIconView extends AnimatedImageView { animation.getAnimatedFraction()); setColorInternal(newColor); }; + private final NotificationIconDozeHelper mDozer; public StatusBarIconView(Context context, String slot, Notification notification) { this(context, slot, notification, false); @@ -127,6 +131,7 @@ public class StatusBarIconView extends AnimatedImageView { public StatusBarIconView(Context context, String slot, Notification notification, boolean blocked) { super(context); + mDozer = new NotificationIconDozeHelper(context); mBlocked = blocked; mSlot = slot; mNumberPain = new Paint(); @@ -190,6 +195,7 @@ public class StatusBarIconView extends AnimatedImageView { public StatusBarIconView(Context context, AttributeSet attrs) { super(context, attrs); + mDozer = new NotificationIconDozeHelper(context); mBlocked = false; mAlwaysScaleIcon = true; updateIconScale(); @@ -466,7 +472,19 @@ public class StatusBarIconView extends AnimatedImageView { * to the drawable. */ public void setDecorColor(int iconTint) { - mDotPaint.setColor(iconTint); + mDecorColor = iconTint; + updateDecorColor(); + } + + private void updateDecorColor() { + int color = NotificationUtils.interpolateColors(mDecorColor, Color.WHITE, mDarkAmount); + if (mDotPaint.getColor() != color) { + mDotPaint.setColor(color); + + if (mDotAppearAmount != 0) { + invalidate(); + } + } } /** @@ -477,6 +495,7 @@ public class StatusBarIconView extends AnimatedImageView { mDrawableColor = color; setColorInternal(color); mIconColor = color; + mDozer.setColor(color); } private void setColorInternal(int color) { @@ -649,6 +668,14 @@ public class StatusBarIconView extends AnimatedImageView { mOnVisibilityChangedListener = listener; } + public void setDark(boolean dark, boolean fade, long delay) { + mDozer.setImageDark(this, dark, fade, delay, mIconColor == NO_COLOR); + mDozer.setIntensityDark(f -> { + mDarkAmount = f; + updateDecorColor(); + }, dark, fade, delay); + } + public interface OnVisibilityChangedListener { void onVisibilityChanged(int newVisibility); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java index 3efa29f87450..bca4b43afc0c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java @@ -18,7 +18,7 @@ package com.android.systemui.statusbar.notification; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; -import android.animation.ValueAnimator; +import android.content.Context; import android.graphics.ColorMatrixColorFilter; import android.graphics.Paint; import android.view.View; @@ -38,8 +38,8 @@ public class NotificationCustomViewWrapper extends NotificationViewWrapper { private boolean mIsLegacy; private int mLegacyColor; - protected NotificationCustomViewWrapper(View view, ExpandableNotificationRow row) { - super(view, row); + protected NotificationCustomViewWrapper(Context ctx, View view, ExpandableNotificationRow row) { + super(ctx, view, row); mInvertHelper = new ViewInvertHelper(view, NotificationPanelView.DOZE_ANIMATION_DURATION); mLegacyColor = row.getContext().getColor(R.color.notification_legacy_background_color); } @@ -67,13 +67,11 @@ public class NotificationCustomViewWrapper extends NotificationViewWrapper { } protected void fadeGrayscale(final boolean dark, long delay) { - startIntensityAnimation(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - updateGrayscaleMatrix((float) animation.getAnimatedValue()); - mGreyPaint.setColorFilter(new ColorMatrixColorFilter(mGrayscaleColorMatrix)); - mView.setLayerPaint(mGreyPaint); - } + getDozer().startIntensityAnimation(animation -> { + getDozer().updateGrayscaleMatrix((float) animation.getAnimatedValue()); + mGreyPaint.setColorFilter( + new ColorMatrixColorFilter(getDozer().getGrayscaleColorMatrix())); + mView.setLayerPaint(mGreyPaint); }, dark, delay, new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { @@ -86,9 +84,9 @@ public class NotificationCustomViewWrapper extends NotificationViewWrapper { protected void updateGrayscale(boolean dark) { if (dark) { - updateGrayscaleMatrix(1f); + getDozer().updateGrayscaleMatrix(1f); mGreyPaint.setColorFilter( - new ColorMatrixColorFilter(mGrayscaleColorMatrix)); + new ColorMatrixColorFilter(getDozer().getGrayscaleColorMatrix())); mView.setLayerPaint(mGreyPaint); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationDozeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationDozeHelper.java new file mode 100644 index 000000000000..d592c5f5b7f3 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationDozeHelper.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ValueAnimator; +import android.graphics.ColorMatrix; +import android.graphics.ColorMatrixColorFilter; +import android.widget.ImageView; + +import com.android.systemui.Interpolators; +import com.android.systemui.statusbar.phone.NotificationPanelView; + +import java.util.function.Consumer; + +public class NotificationDozeHelper { + private final ColorMatrix mGrayscaleColorMatrix = new ColorMatrix(); + + public void fadeGrayscale(final ImageView target, final boolean dark, long delay) { + startIntensityAnimation(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + updateGrayscaleMatrix((float) animation.getAnimatedValue()); + target.setColorFilter(new ColorMatrixColorFilter(mGrayscaleColorMatrix)); + } + }, dark, delay, new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + if (!dark) { + target.setColorFilter(null); + } + } + }); + } + + public void updateGrayscale(ImageView target, boolean dark) { + if (dark) { + updateGrayscaleMatrix(1f); + target.setColorFilter(new ColorMatrixColorFilter(mGrayscaleColorMatrix)); + } else { + target.setColorFilter(null); + } + } + + public void startIntensityAnimation(ValueAnimator.AnimatorUpdateListener updateListener, + boolean dark, long delay, Animator.AnimatorListener listener) { + float startIntensity = dark ? 0f : 1f; + float endIntensity = dark ? 1f : 0f; + ValueAnimator animator = ValueAnimator.ofFloat(startIntensity, endIntensity); + animator.addUpdateListener(updateListener); + animator.setDuration(NotificationPanelView.DOZE_ANIMATION_DURATION); + animator.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN); + animator.setStartDelay(delay); + if (listener != null) { + animator.addListener(listener); + } + animator.start(); + } + + public void setIntensityDark(Consumer<Float> listener, boolean dark, + boolean animate, long delay) { + if (animate) { + startIntensityAnimation(a -> listener.accept((Float) a.getAnimatedValue()), dark, delay, + null /* listener */); + } else { + listener.accept(dark ? 1f : 0f); + } + } + + public void updateGrayscaleMatrix(float intensity) { + mGrayscaleColorMatrix.setSaturation(1 - intensity); + } + + public ColorMatrix getGrayscaleColorMatrix() { + return mGrayscaleColorMatrix; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationHeaderViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationHeaderViewWrapper.java index 38e4ec1a4d7f..1ffc94480dab 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationHeaderViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationHeaderViewWrapper.java @@ -16,17 +16,10 @@ package com.android.systemui.statusbar.notification; -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.ValueAnimator; import android.app.Notification; import android.content.Context; -import android.graphics.Color; import android.graphics.ColorFilter; -import android.graphics.ColorMatrixColorFilter; -import android.graphics.PorterDuff; import android.graphics.PorterDuffColorFilter; -import android.graphics.drawable.Drawable; import android.util.ArraySet; import android.view.NotificationHeaderView; import android.view.View; @@ -37,7 +30,6 @@ import android.widget.ImageView; import android.widget.TextView; import com.android.systemui.Interpolators; -import com.android.systemui.R; import com.android.systemui.ViewInvertHelper; import com.android.systemui.statusbar.ExpandableNotificationRow; import com.android.systemui.statusbar.TransformableView; @@ -55,10 +47,6 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper { private static final Interpolator LOW_PRIORITY_HEADER_CLOSE = new PathInterpolator(0.4f, 0f, 0.7f, 1f); - private final PorterDuffColorFilter mIconColorFilter = new PorterDuffColorFilter( - 0, PorterDuff.Mode.SRC_ATOP); - private final int mIconDarkAlpha; - private final int mIconDarkColor = 0xffffffff; protected final ViewInvertHelper mInvertHelper; protected final ViewTransformationHelper mTransformationHelper; @@ -74,8 +62,7 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper { private boolean mTransformLowPriorityTitle; protected NotificationHeaderViewWrapper(Context ctx, View view, ExpandableNotificationRow row) { - super(view, row); - mIconDarkAlpha = ctx.getResources().getInteger(R.integer.doze_small_icon_alpha); + super(ctx, view, row); mInvertHelper = new ViewInvertHelper(ctx, NotificationPanelView.DOZE_ANIMATION_DURATION); mTransformationHelper = new ViewTransformationHelper(); @@ -108,6 +95,16 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper { updateInvertHelper(); } + @Override + protected NotificationDozeHelper createDozer(Context ctx) { + return new NotificationIconDozeHelper(ctx); + } + + @Override + protected NotificationIconDozeHelper getDozer() { + return (NotificationIconDozeHelper) super.getDozer(); + } + protected void resolveHeaderViews() { mIcon = (ImageView) mView.findViewById(com.android.internal.R.id.icon); mHeaderText = (TextView) mView.findViewById(com.android.internal.R.id.header_text); @@ -116,6 +113,7 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper { mColor = resolveColor(mExpandButton); mNotificationHeader = (NotificationHeaderView) mView.findViewById( com.android.internal.R.id.notification_header); + getDozer().setColor(mColor); } private int resolveColor(ImageView icon) { @@ -223,90 +221,8 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper { // It also may lead to bugs where the icon isn't correctly greyed out. boolean hadColorFilter = mNotificationHeader.getOriginalIconColor() != NotificationHeaderView.NO_COLOR; - if (fade) { - if (hadColorFilter) { - fadeIconColorFilter(mIcon, dark, delay); - fadeIconAlpha(mIcon, dark, delay); - } else { - fadeGrayscale(mIcon, dark, delay); - } - } else { - if (hadColorFilter) { - updateIconColorFilter(mIcon, dark); - updateIconAlpha(mIcon, dark); - } else { - updateGrayscale(mIcon, dark); - } - } - } - } - - private void fadeIconColorFilter(final ImageView target, boolean dark, long delay) { - startIntensityAnimation(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - updateIconColorFilter(target, (Float) animation.getAnimatedValue()); - } - }, dark, delay, null /* listener */); - } - - private void fadeIconAlpha(final ImageView target, boolean dark, long delay) { - startIntensityAnimation(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - float t = (float) animation.getAnimatedValue(); - target.setImageAlpha((int) (255 * (1f - t) + mIconDarkAlpha * t)); - } - }, dark, delay, null /* listener */); - } - - protected void fadeGrayscale(final ImageView target, final boolean dark, long delay) { - startIntensityAnimation(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - updateGrayscaleMatrix((float) animation.getAnimatedValue()); - target.setColorFilter(new ColorMatrixColorFilter(mGrayscaleColorMatrix)); - } - }, dark, delay, new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - if (!dark) { - target.setColorFilter(null); - } - } - }); - } - - private void updateIconColorFilter(ImageView target, boolean dark) { - updateIconColorFilter(target, dark ? 1f : 0f); - } - private void updateIconColorFilter(ImageView target, float intensity) { - int color = interpolateColor(mColor, mIconDarkColor, intensity); - mIconColorFilter.setColor(color); - Drawable iconDrawable = target.getDrawable(); - - // Also, the notification might have been modified during the animation, so background - // might be null here. - if (iconDrawable != null) { - Drawable d = iconDrawable.mutate(); - // DrawableContainer ignores the color filter if it's already set, so clear it first to - // get it set and invalidated properly. - d.setColorFilter(null); - d.setColorFilter(mIconColorFilter); - } - } - - private void updateIconAlpha(ImageView target, boolean dark) { - target.setImageAlpha(dark ? mIconDarkAlpha : 255); - } - - protected void updateGrayscale(ImageView target, boolean dark) { - if (dark) { - updateGrayscaleMatrix(1f); - target.setColorFilter(new ColorMatrixColorFilter(mGrayscaleColorMatrix)); - } else { - target.setColorFilter(null); + getDozer().setImageDark(mIcon, dark, fade, delay, !hadColorFilter); } } @@ -316,22 +232,6 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper { mNotificationHeader.setOnClickListener(expandable ? onClickListener : null); } - private static int interpolateColor(int source, int target, float t) { - int aSource = Color.alpha(source); - int rSource = Color.red(source); - int gSource = Color.green(source); - int bSource = Color.blue(source); - int aTarget = Color.alpha(target); - int rTarget = Color.red(target); - int gTarget = Color.green(target); - int bTarget = Color.blue(target); - return Color.argb( - (int) (aSource * (1f - t) + aTarget * t), - (int) (rSource * (1f - t) + rTarget * t), - (int) (gSource * (1f - t) + gTarget * t), - (int) (bSource * (1f - t) + bTarget * t)); - } - @Override public NotificationHeaderView getNotificationHeader() { return mNotificationHeader; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationIconDozeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationIconDozeHelper.java new file mode 100644 index 000000000000..9f79ef2491d9 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationIconDozeHelper.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification; + +import android.content.Context; +import android.graphics.Color; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; +import android.graphics.drawable.Drawable; +import android.widget.ImageView; + +import com.android.systemui.R; + +public class NotificationIconDozeHelper extends NotificationDozeHelper { + + private final int mImageDarkAlpha; + private final int mImageDarkColor = 0xffffffff; + private final PorterDuffColorFilter mImageColorFilter = new PorterDuffColorFilter( + 0, PorterDuff.Mode.SRC_ATOP); + + private int mColor = Color.BLACK; + + public NotificationIconDozeHelper(Context ctx) { + mImageDarkAlpha = ctx.getResources().getInteger(R.integer.doze_small_icon_alpha); + } + + public void setColor(int color) { + mColor = color; + } + + public void setImageDark(ImageView target, boolean dark, boolean fade, long delay, + boolean useGrayscale) { + if (fade) { + if (!useGrayscale) { + fadeImageColorFilter(target, dark, delay); + fadeImageAlpha(target, dark, delay); + } else { + fadeGrayscale(target, dark, delay); + } + } else { + if (!useGrayscale) { + updateImageColorFilter(target, dark); + updateImageAlpha(target, dark); + } else { + updateGrayscale(target, dark); + } + } + } + + private void fadeImageColorFilter(final ImageView target, boolean dark, long delay) { + startIntensityAnimation(animation -> { + updateImageColorFilter(target, (Float) animation.getAnimatedValue()); + }, dark, delay, null /* listener */); + } + + private void fadeImageAlpha(final ImageView target, boolean dark, long delay) { + startIntensityAnimation(animation -> { + float t = (float) animation.getAnimatedValue(); + target.setImageAlpha((int) (255 * (1f - t) + mImageDarkAlpha * t)); + }, dark, delay, null /* listener */); + } + + private void updateImageColorFilter(ImageView target, boolean dark) { + updateImageColorFilter(target, dark ? 1f : 0f); + } + + private void updateImageColorFilter(ImageView target, float intensity) { + int color = NotificationUtils.interpolateColors(mColor, mImageDarkColor, intensity); + mImageColorFilter.setColor(color); + Drawable imageDrawable = target.getDrawable(); + + // Also, the notification might have been modified during the animation, so background + // might be null here. + if (imageDrawable != null) { + Drawable d = imageDrawable.mutate(); + // DrawableContainer ignores the color filter if it's already set, so clear it first to + // get it set and invalidated properly. + d.setColorFilter(null); + d.setColorFilter(mImageColorFilter); + } + } + + private void updateImageAlpha(ImageView target, boolean dark) { + target.setImageAlpha(dark ? mImageDarkAlpha : 255); + } + +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java index 846d03ac74dd..f0b6b2e89e9f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java @@ -16,7 +16,6 @@ package com.android.systemui.statusbar.notification; -import android.animation.ValueAnimator; import android.content.Context; import android.graphics.Color; import android.service.notification.StatusBarNotification; @@ -46,7 +45,8 @@ public class NotificationTemplateViewWrapper extends NotificationHeaderViewWrapp private int mContentHeight; private int mMinHeightHint; - protected NotificationTemplateViewWrapper(Context ctx, View view, ExpandableNotificationRow row) { + protected NotificationTemplateViewWrapper(Context ctx, View view, + ExpandableNotificationRow row) { super(ctx, view, row); mTransformationHelper.setCustomTransformation( new ViewTransformationHelper.CustomTransformation() { @@ -154,16 +154,20 @@ public class NotificationTemplateViewWrapper extends NotificationHeaderViewWrapp // This also clears the existing types super.updateTransformedTypes(); if (mTitle != null) { - mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_TITLE, mTitle); + mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_TITLE, + mTitle); } if (mText != null) { - mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_TEXT, mText); + mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_TEXT, + mText); } if (mPicture != null) { - mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_IMAGE, mPicture); + mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_IMAGE, + mPicture); } if (mProgressBar != null) { - mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_PROGRESS, mProgressBar); + mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_PROGRESS, + mProgressBar); } } @@ -173,7 +177,7 @@ public class NotificationTemplateViewWrapper extends NotificationHeaderViewWrapp return; } super.setDark(dark, fade, delay); - setPictureGrayscale(dark, fade, delay); + setPictureDark(dark, fade, delay); setProgressBarDark(dark, fade, delay); } @@ -188,12 +192,9 @@ public class NotificationTemplateViewWrapper extends NotificationHeaderViewWrapp } private void fadeProgressDark(final ProgressBar target, final boolean dark, long delay) { - startIntensityAnimation(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - float t = (float) animation.getAnimatedValue(); - updateProgressDark(target, t); - } + getDozer().startIntensityAnimation(animation -> { + float t = (float) animation.getAnimatedValue(); + updateProgressDark(target, t); }, dark, delay, null /* listener */); } @@ -207,13 +208,9 @@ public class NotificationTemplateViewWrapper extends NotificationHeaderViewWrapp updateProgressDark(target, dark ? 1f : 0f); } - protected void setPictureGrayscale(boolean grayscale, boolean fade, long delay) { + private void setPictureDark(boolean dark, boolean fade, long delay) { if (mPicture != null) { - if (fade) { - fadeGrayscale(mPicture, grayscale, delay); - } else { - updateGrayscale(mPicture, grayscale); - } + getDozer().setImageDark(mPicture, dark, fade, delay, true /* useGrayscale */); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java index c85e8d853b0d..f4db9a1977f3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java @@ -16,24 +16,17 @@ package com.android.systemui.statusbar.notification; -import android.animation.Animator; -import android.animation.ValueAnimator; import android.content.Context; import android.graphics.Color; -import android.graphics.ColorMatrix; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; -import android.service.notification.StatusBarNotification; import android.support.v4.graphics.ColorUtils; import android.view.NotificationHeaderView; import android.view.View; -import com.android.systemui.Interpolators; -import com.android.systemui.R; import com.android.systemui.statusbar.CrossFadeHelper; import com.android.systemui.statusbar.ExpandableNotificationRow; import com.android.systemui.statusbar.TransformableView; -import com.android.systemui.statusbar.phone.NotificationPanelView; /** * Wraps the actual notification content view; used to implement behaviors which are different for @@ -41,14 +34,14 @@ import com.android.systemui.statusbar.phone.NotificationPanelView; */ public abstract class NotificationViewWrapper implements TransformableView { - protected final ColorMatrix mGrayscaleColorMatrix = new ColorMatrix(); protected final View mView; protected final ExpandableNotificationRow mRow; + private final NotificationDozeHelper mDozer; + protected boolean mDark; private int mBackgroundColor = 0; protected boolean mShouldInvertDark; protected boolean mDarkInitialized = false; - private boolean mForcedInvisible; public static NotificationViewWrapper wrap(Context ctx, View v, ExpandableNotificationRow row) { if (v.getId() == com.android.internal.R.id.status_bar_latest_event_content) { @@ -65,13 +58,22 @@ public abstract class NotificationViewWrapper implements TransformableView { } else if (v instanceof NotificationHeaderView) { return new NotificationHeaderViewWrapper(ctx, v, row); } else { - return new NotificationCustomViewWrapper(v, row); + return new NotificationCustomViewWrapper(ctx, v, row); } } - protected NotificationViewWrapper(View view, ExpandableNotificationRow row) { + protected NotificationViewWrapper(Context ctx, View view, ExpandableNotificationRow row) { mView = view; mRow = row; + mDozer = createDozer(ctx); + } + + protected NotificationDozeHelper createDozer(Context ctx) { + return new NotificationDozeHelper(); + } + + protected NotificationDozeHelper getDozer() { + return mDozer; } /** @@ -112,26 +114,6 @@ public abstract class NotificationViewWrapper implements TransformableView { || ColorUtils.calculateLuminance(backgroundColor) > 0.5; } - - protected void startIntensityAnimation(ValueAnimator.AnimatorUpdateListener updateListener, - boolean dark, long delay, Animator.AnimatorListener listener) { - float startIntensity = dark ? 0f : 1f; - float endIntensity = dark ? 1f : 0f; - ValueAnimator animator = ValueAnimator.ofFloat(startIntensity, endIntensity); - animator.addUpdateListener(updateListener); - animator.setDuration(NotificationPanelView.DOZE_ANIMATION_DURATION); - animator.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN); - animator.setStartDelay(delay); - if (listener != null) { - animator.addListener(listener); - } - animator.start(); - } - - protected void updateGrayscaleMatrix(float intensity) { - mGrayscaleColorMatrix.setSaturation(1 - intensity); - } - /** * Update the appearance of the expand button. * diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java index 3706dc8242b9..dee15d8163a6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java @@ -95,7 +95,7 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { private int mActualLayoutWidth = NO_VALUE; private float mActualPaddingEnd = NO_VALUE; private float mActualPaddingStart = NO_VALUE; - private boolean mCentered; + private boolean mDark; private boolean mChangingViewPositions; private int mAddAnimationStartIndex = -1; private int mCannedAnimationStartIndex = -1; @@ -183,6 +183,9 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { mAddAnimationStartIndex = Math.min(mAddAnimationStartIndex, childIndex); } } + if (mDark && child instanceof StatusBarIconView) { + ((StatusBarIconView) child).setDark(mDark, false, 0); + } } @Override @@ -312,7 +315,8 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { numDots++; } } - if (mCentered && translationX < getLayoutEnd()) { + boolean center = mDark; + if (center && translationX < getLayoutEnd()) { float delta = (getLayoutEnd() - translationX) / 2; for (int i = 0; i < childCount; i++) { View view = getChildAt(i); @@ -390,9 +394,15 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { mChangingViewPositions = changingViewPositions; } - public void setAmbient(boolean ambient) { - mCentered = ambient; + public void setDark(boolean dark, boolean fade, long delay) { + mDark = dark; mDisallowNextAnimation = true; + for (int i = 0; i < getChildCount(); i++) { + View view = getChildAt(i); + if (view instanceof StatusBarIconView) { + ((StatusBarIconView) view).setDark(dark, fade, delay); + } + } } public IconState getIconState(StatusBarIconView icon) { 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 472af653af6e..9304de52a6be 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -48,8 +48,10 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.IntentSender; +import android.content.pm.ActivityInfo; import android.content.pm.IPackageManager; import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; import android.content.res.Configuration; import android.content.res.Resources; import android.database.ContentObserver; @@ -5701,7 +5703,7 @@ public class StatusBar extends SystemUI implements DemoMode, .findViewById(com.android.internal.R.id.media_actions) != null; } - // The (i) button in the guts that links to the system notification settings for that app + // The button in the guts that links to the system notification settings for that app private void startAppNotificationSettingsActivity(String packageName, final int appUid, final NotificationChannel channel) { final Intent intent = new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS); @@ -5781,10 +5783,17 @@ public class StatusBar extends SystemUI implements DemoMode, startAppNotificationSettingsActivity(pkg, appUid, channel); }; } + final NotificationInfo.OnAppSettingsClickListener onAppSettingsClick = (View v, + Intent intent) -> { + mMetricsLogger.action(MetricsEvent.ACTION_APP_NOTE_SETTINGS); + guts.resetFalsingCheck(); + startNotificationGutsIntent(intent, sbn.getUid()); + }; final View.OnClickListener onDoneClick = (View v) -> { saveAndCloseNotificationMenu(info, row, guts, v); }; - final NotificationInfo.CheckSaveListener checkSaveListener = (Runnable saveImportance) -> { + final NotificationInfo.CheckSaveListener checkSaveListener = + (Runnable saveImportance) -> { // If the user has security enabled, show challenge if the setting is changed. if (isLockscreenPublicMode(userHandle.getIdentifier()) && (mState == StatusBarState.KEYGUARD @@ -5817,7 +5826,9 @@ public class StatusBar extends SystemUI implements DemoMode, } try { info.bindNotification(pmUser, iNotificationManager, pkg, new ArrayList(channels), - onSettingsClick, onDoneClick, checkSaveListener, mNonBlockablePkgs); + row.getEntry().channel.getImportance(), sbn, onSettingsClick, + onAppSettingsClick, onDoneClick, checkSaveListener, + mNonBlockablePkgs); } catch (RemoteException e) { Log.e(TAG, e.toString()); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java index b5d22b1da288..d77796144282 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java @@ -24,6 +24,7 @@ import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; import android.hardware.input.InputManager; import android.media.AudioManager; +import android.metrics.LogMaker; import android.os.AsyncTask; import android.os.Bundle; import android.os.SystemClock; @@ -41,6 +42,9 @@ import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; import android.widget.ImageView; +import com.android.internal.logging.MetricsLogger; +import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.plugins.statusbar.phone.NavBarButtonProvider.ButtonInterface; @@ -60,6 +64,7 @@ public class KeyButtonView extends ImageView implements ButtonInterface { private boolean mLongClicked; private OnClickListener mOnClickListener; private final KeyButtonRipple mRipple; + private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class); private final Runnable mCheckLongPress = new Runnable() { public void run() { @@ -252,6 +257,11 @@ public class KeyButtonView extends ImageView implements ButtonInterface { } void sendEvent(int action, int flags, long when) { + mMetricsLogger.write(new LogMaker(MetricsEvent.ACTION_NAV_BUTTON_EVENT) + .setType(MetricsEvent.TYPE_ACTION) + .setSubtype(mCode) + .addTaggedData(MetricsEvent.FIELD_NAV_ACTION, action) + .addTaggedData(MetricsEvent.FIELD_FLAGS, flags)); final int repeatCount = (flags & KeyEvent.FLAG_LONG_PRESS) != 0 ? 1 : 0; final KeyEvent ev = new KeyEvent(mDownTime, when, action, mCode, repeatCount, 0, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java index c8659fb62051..5b594be507d2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java @@ -190,7 +190,9 @@ public class ViewState { view.setScaleY(scaleY); } - boolean becomesInvisible = this.alpha == 0.0f || (this.hidden && !isAnimating(view)); + int oldVisibility = view.getVisibility(); + boolean becomesInvisible = this.alpha == 0.0f + || (this.hidden && (!isAnimating(view) || oldVisibility != View.VISIBLE)); boolean animatingAlpha = isAnimating(view, TAG_ANIMATOR_ALPHA); if (animatingAlpha) { updateAlphaAnimation(view); @@ -212,7 +214,6 @@ public class ViewState { } // apply visibility - int oldVisibility = view.getVisibility(); int newVisibility = becomesInvisible ? View.INVISIBLE : View.VISIBLE; if (newVisibility != oldVisibility) { if (!(view instanceof ExpandableView) || !((ExpandableView) view).willBeGone()) { diff --git a/packages/SystemUI/src/com/android/systemui/tuner/PluginFragment.java b/packages/SystemUI/src/com/android/systemui/tuner/PluginFragment.java index 266f05372813..f91e45d05e79 100644 --- a/packages/SystemUI/src/com/android/systemui/tuner/PluginFragment.java +++ b/packages/SystemUI/src/com/android/systemui/tuner/PluginFragment.java @@ -19,6 +19,7 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.net.Uri; @@ -26,15 +27,18 @@ import android.os.Bundle; import android.provider.Settings; import android.support.v14.preference.PreferenceFragment; import android.support.v14.preference.SwitchPreference; -import android.support.v7.preference.PreferenceCategory; import android.support.v7.preference.PreferenceScreen; import android.support.v7.preference.PreferenceViewHolder; +import android.util.ArrayMap; +import android.util.ArraySet; import android.view.View; import com.android.systemui.R; +import com.android.systemui.plugins.PluginInstanceManager; import com.android.systemui.plugins.PluginManager; import com.android.systemui.plugins.PluginPrefs; +import java.util.ArrayList; import java.util.List; import java.util.Set; @@ -51,7 +55,6 @@ public class PluginFragment extends PreferenceFragment { IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED); filter.addAction(Intent.ACTION_PACKAGE_CHANGED); filter.addAction(Intent.ACTION_PACKAGE_REMOVED); - filter.addAction(PluginManager.PLUGIN_CHANGED); filter.addDataScheme("package"); getContext().registerReceiver(mReceiver, filter); filter = new IntentFilter(Intent.ACTION_USER_UNLOCKED); @@ -74,24 +77,59 @@ public class PluginFragment extends PreferenceFragment { screen.setOrderingAsAdded(false); Context prefContext = getPreferenceManager().getContext(); mPluginPrefs = new PluginPrefs(getContext()); + PackageManager pm = getContext().getPackageManager(); + Set<String> pluginActions = mPluginPrefs.getPluginList(); + ArrayMap<String, ArraySet<String>> plugins = new ArrayMap<>(); for (String action : pluginActions) { - String name = action.replace("com.android.systemui.action.PLUGIN_", ""); - PreferenceCategory category = new PreferenceCategory(prefContext); - category.setTitle(name); - - List<ResolveInfo> result = getContext().getPackageManager().queryIntentServices( + String name = toName(action); + List<ResolveInfo> result = pm.queryIntentServices( new Intent(action), PackageManager.MATCH_DISABLED_COMPONENTS); - if (result.size() > 0) { - screen.addPreference(category); - } for (ResolveInfo info : result) { - category.addPreference(new PluginPreference(prefContext, info)); + String packageName = info.serviceInfo.packageName; + if (!plugins.containsKey(packageName)) { + plugins.put(packageName, new ArraySet<>()); + } + plugins.get(packageName).add(name); } } + + List<PackageInfo> apps = pm.getPackagesHoldingPermissions(new String[]{ + PluginInstanceManager.PLUGIN_PERMISSION}, + PackageManager.MATCH_DISABLED_COMPONENTS | PackageManager.GET_SERVICES); + apps.forEach(app -> { + if (!plugins.containsKey(app.packageName)) return; + SwitchPreference pref = new PluginPreference(prefContext, app); + pref.setSummary("Plugins: " + toString(plugins.get(app.packageName))); + screen.addPreference(pref); + }); setPreferenceScreen(screen); } + private String toString(ArraySet<String> plugins) { + StringBuilder b = new StringBuilder(); + for (String string : plugins) { + if (b.length() != 0) { + b.append(", "); + } + b.append(string); + } + return b.toString(); + } + + private String toName(String action) { + String str = action.replace("com.android.systemui.action.PLUGIN_", ""); + StringBuilder b = new StringBuilder(); + for (String s : str.split("_")) { + if (b.length() != 0) { + b.append(' '); + } + b.append(s.substring(0, 1)); + b.append(s.substring(1).toLowerCase()); + } + return b.toString(); + } + private final BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { @@ -100,31 +138,44 @@ public class PluginFragment extends PreferenceFragment { }; private static class PluginPreference extends SwitchPreference { - private final ComponentName mComponent; private final boolean mHasSettings; + private final PackageInfo mInfo; + private final PackageManager mPm; - public PluginPreference(Context prefContext, ResolveInfo info) { + public PluginPreference(Context prefContext, PackageInfo info) { super(prefContext); - mComponent = new ComponentName(info.serviceInfo.packageName, info.serviceInfo.name); - PackageManager pm = prefContext.getPackageManager(); - mHasSettings = pm.resolveActivity(new Intent(ACTION_PLUGIN_SETTINGS) - .setPackage(mComponent.getPackageName()), 0) != null; - setTitle(info.serviceInfo.loadLabel(pm)); - setChecked(pm.getComponentEnabledSetting(mComponent) - != PackageManager.COMPONENT_ENABLED_STATE_DISABLED); + mPm = prefContext.getPackageManager(); + mHasSettings = mPm.resolveActivity(new Intent(ACTION_PLUGIN_SETTINGS) + .setPackage(info.packageName), 0) != null; + mInfo = info; + setTitle(info.applicationInfo.loadLabel(mPm)); + setChecked(isPluginEnabled()); setWidgetLayoutResource(R.layout.tuner_widget_settings_switch); } + private boolean isPluginEnabled() { + for (int i = 0; i < mInfo.services.length; i++) { + ComponentName componentName = new ComponentName(mInfo.packageName, + mInfo.services[i].name); + if (mPm.getComponentEnabledSetting(componentName) + == PackageManager.COMPONENT_ENABLED_STATE_DISABLED) { + return false; + } + } + return true; + } + @Override protected boolean persistBoolean(boolean value) { - PackageManager pm = getContext().getPackageManager(); final int desiredState = value ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED : PackageManager.COMPONENT_ENABLED_STATE_DISABLED; - if (pm.getComponentEnabledSetting(mComponent) == desiredState) return true; - pm.setComponentEnabledSetting(mComponent, - desiredState, - PackageManager.DONT_KILL_APP); - final String pkg = mComponent.getPackageName(); + for (int i = 0; i < mInfo.services.length; i++) { + ComponentName componentName = new ComponentName(mInfo.packageName, + mInfo.services[i].name); + mPm.setComponentEnabledSetting(componentName, desiredState, + PackageManager.DONT_KILL_APP); + } + final String pkg = mInfo.packageName; final Intent intent = new Intent(PluginManager.PLUGIN_CHANGED, pkg != null ? Uri.fromParts("package", pkg, null) : null); getContext().sendBroadcast(intent); @@ -141,7 +192,7 @@ public class PluginFragment extends PreferenceFragment { holder.findViewById(R.id.settings).setOnClickListener(v -> { ResolveInfo result = v.getContext().getPackageManager().resolveActivity( new Intent(ACTION_PLUGIN_SETTINGS).setPackage( - mComponent.getPackageName()), 0); + mInfo.packageName), 0); if (result != null) { v.getContext().startActivity(new Intent().setComponent( new ComponentName(result.activityInfo.packageName, @@ -150,7 +201,7 @@ public class PluginFragment extends PreferenceFragment { }); holder.itemView.setOnLongClickListener(v -> { Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); - intent.setData(Uri.fromParts("package", mComponent.getPackageName(), null)); + intent.setData(Uri.fromParts("package", mInfo.packageName, null)); getContext().startActivity(intent); return true; }); diff --git a/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginInstanceManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginInstanceManagerTest.java index 4f0815d0f283..04441abee92e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginInstanceManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginInstanceManagerTest.java @@ -273,6 +273,11 @@ public class PluginInstanceManagerTest extends SysuiTestCase { @Override public void unregisterReceiver(BroadcastReceiver receiver) { } + + @Override + public void sendBroadcast(Intent intent) { + // Do nothing. + } } // This target class doesn't matter, it just needs to have a Requires to hit the flow where diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInfoTest.java index 0621f4ab8fc6..2bb7f3b2b36d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInfoTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInfoTest.java @@ -18,9 +18,9 @@ package com.android.systemui.statusbar; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertNull; import static junit.framework.Assert.assertTrue; -import static org.mockito.Matchers.anyObject; import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyBoolean; import static org.mockito.Mockito.anyInt; @@ -33,13 +33,18 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.INotificationManager; +import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationChannelGroup; import android.app.NotificationManager; +import android.content.Intent; +import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; import android.graphics.drawable.Drawable; +import android.os.UserHandle; import android.service.notification.StatusBarNotification; import android.test.suitebuilder.annotation.SmallTest; import android.testing.AndroidTestingRunner; @@ -56,6 +61,8 @@ import com.android.systemui.SysuiTestCase; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; + +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -74,6 +81,7 @@ public class NotificationInfoTest extends SysuiTestCase { private final PackageManager mMockPackageManager = mock(PackageManager.class); private NotificationChannel mNotificationChannel; private NotificationChannel mDefaultNotificationChannel; + private StatusBarNotification mSbn; @Before public void setUp() throws Exception { @@ -101,6 +109,8 @@ public class NotificationInfoTest extends SysuiTestCase { mDefaultNotificationChannel = new NotificationChannel( NotificationChannel.DEFAULT_CHANNEL_ID, TEST_CHANNEL_NAME, NotificationManager.IMPORTANCE_LOW); + mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, 0, null, 0, 0, + new Notification(), UserHandle.CURRENT, null, 0); } private CharSequence getStringById(int resId) { @@ -135,7 +145,9 @@ public class NotificationInfoTest extends SysuiTestCase { public void testBindNotification_SetsTextApplicationName() throws Exception { when(mMockPackageManager.getApplicationLabel(any())).thenReturn("App Name"); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, null, null, null); + TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), + mNotificationChannel.getImportance(), mSbn, null, null, null, + null, null); final TextView textView = (TextView) mNotificationInfo.findViewById(R.id.pkgname); assertTrue(textView.getText().toString().contains("App Name")); } @@ -146,7 +158,9 @@ public class NotificationInfoTest extends SysuiTestCase { when(mMockPackageManager.getApplicationIcon(any(ApplicationInfo.class))) .thenReturn(iconDrawable); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, null, null, null); + TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), + mNotificationChannel.getImportance(), mSbn, null, null, null, + null, null); final ImageView iconView = (ImageView) mNotificationInfo.findViewById(R.id.pkgicon); assertEquals(iconDrawable, iconView.getDrawable()); } @@ -154,7 +168,9 @@ public class NotificationInfoTest extends SysuiTestCase { @Test public void testBindNotification_GroupNameHiddenIfNoGroup() throws Exception { mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, null, null, null); + TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), + mNotificationChannel.getImportance(), mSbn, null, null, null, + null, null); final TextView groupNameView = (TextView) mNotificationInfo.findViewById(R.id.group_name); assertEquals(View.GONE, groupNameView.getVisibility()); final TextView groupDividerView = @@ -171,7 +187,9 @@ public class NotificationInfoTest extends SysuiTestCase { eq("test_group_id"), eq(TEST_PACKAGE_NAME), anyInt())) .thenReturn(notificationChannelGroup); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, null, null, null); + TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), + mNotificationChannel.getImportance(), mSbn, null, null, null, + null, null); final TextView groupNameView = (TextView) mNotificationInfo.findViewById(R.id.group_name); assertEquals(View.VISIBLE, groupNameView.getVisibility()); assertEquals("Test Group Name", groupNameView.getText()); @@ -183,7 +201,9 @@ public class NotificationInfoTest extends SysuiTestCase { @Test public void testBindNotification_SetsTextChannelName() throws Exception { mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, null, null, null); + TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), + mNotificationChannel.getImportance(), mSbn, null, null, null, + null, null); final TextView textView = (TextView) mNotificationInfo.findViewById(R.id.channel_name); assertEquals(TEST_CHANNEL_NAME, textView.getText()); } @@ -193,10 +213,11 @@ public class NotificationInfoTest extends SysuiTestCase { final CountDownLatch latch = new CountDownLatch(1); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), + mNotificationChannel.getImportance(), mSbn, (View v, NotificationChannel c, int appUid) -> { - assertEquals(mNotificationChannel, c); - latch.countDown(); - }, null, null, null); + assertEquals(mNotificationChannel, c); + latch.countDown(); + }, null, null, null, null); final TextView settingsButton = (TextView) mNotificationInfo.findViewById(R.id.more_settings); @@ -209,7 +230,7 @@ public class NotificationInfoTest extends SysuiTestCase { public void testBindNotification_SettingsButtonInvisibleWhenNoClickListener() throws Exception { mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), - null, null, null, null); + mNotificationChannel.getImportance(), mSbn, null, null, null, null, null); final TextView settingsButton = (TextView) mNotificationInfo.findViewById(R.id.more_settings); assertTrue(settingsButton.getVisibility() != View.VISIBLE); @@ -219,10 +240,11 @@ public class NotificationInfoTest extends SysuiTestCase { public void testBindNotification_SettingsButtonReappersAfterSecondBind() throws Exception { mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), - null, null, null, null); + mNotificationChannel.getImportance(), mSbn, null, null, null, null, null); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), - (View v, NotificationChannel c, int appUid) -> {}, null, null, null); + mNotificationChannel.getImportance(), mSbn, + (View v, NotificationChannel c, int appUid) -> {}, null, null, null, null); final TextView settingsButton = (TextView) mNotificationInfo.findViewById(R.id.more_settings); assertEquals(View.VISIBLE, settingsButton.getVisibility()); @@ -234,10 +256,11 @@ public class NotificationInfoTest extends SysuiTestCase { mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel, mDefaultNotificationChannel), + mNotificationChannel.getImportance(), mSbn, (View v, NotificationChannel c, int appUid) -> { - assertEquals(null, c); - latch.countDown(); - }, null, null, null); + assertEquals(null, c); + latch.countDown(); + }, null, null, null, null); final TextView settingsButton = (TextView) mNotificationInfo.findViewById(R.id.more_settings); @@ -250,7 +273,9 @@ public class NotificationInfoTest extends SysuiTestCase { public void testBindNotification_SettingsTextWithOneChannel() throws Exception { mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), - (View v, NotificationChannel c, int appUid) -> {}, null, null, null); + mNotificationChannel.getImportance(), mSbn, + (View v, NotificationChannel c, int appUid) -> { + }, null, null, null, null); final TextView settingsButton = (TextView) mNotificationInfo.findViewById(R.id.more_settings); assertEquals(getStringById(R.string.notification_more_settings), settingsButton.getText()); @@ -262,7 +287,9 @@ public class NotificationInfoTest extends SysuiTestCase { eq(TEST_PACKAGE_NAME), anyInt(), anyBoolean())).thenReturn(2); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), - (View v, NotificationChannel c, int appUid) -> {}, null, null, null); + mNotificationChannel.getImportance(), mSbn, + (View v, NotificationChannel c, int appUid) -> { + }, null, null, null, null); final TextView settingsButton = (TextView) mNotificationInfo.findViewById(R.id.more_settings); assertEquals(getStringById(R.string.notification_all_categories), settingsButton.getText()); @@ -272,8 +299,11 @@ public class NotificationInfoTest extends SysuiTestCase { public void testBindNotification_SetsOnClickListenerForDone() throws Exception { final CountDownLatch latch = new CountDownLatch(1); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, - (View v) -> { latch.countDown(); }, + TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), + mNotificationChannel.getImportance(), mSbn, null, + null, (View v) -> { + latch.countDown(); + }, null, null); final TextView doneButton = (TextView) mNotificationInfo.findViewById(R.id.done); @@ -286,7 +316,8 @@ public class NotificationInfoTest extends SysuiTestCase { public void testBindNotification_NumChannelsTextUniqueWhenDefaultChannel() throws Exception { mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, Arrays.asList(mDefaultNotificationChannel), - null, null, null, null); + mNotificationChannel.getImportance(), mSbn, null, null, + null, null, null); final TextView numChannelsView = (TextView) mNotificationInfo.findViewById(R.id.num_channels_desc); assertEquals(View.VISIBLE, numChannelsView.getVisibility()); @@ -298,7 +329,9 @@ public class NotificationInfoTest extends SysuiTestCase { public void testBindNotification_NumChannelsTextDisplaysWhenNotDefaultChannel() throws Exception { mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, null, null, null); + TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), + mNotificationChannel.getImportance(), mSbn, null, null, null, + null, null); final TextView numChannelsView = (TextView) mNotificationInfo.findViewById(R.id.num_channels_desc); assertEquals(numChannelsView.getVisibility(), View.VISIBLE); @@ -311,7 +344,9 @@ public class NotificationInfoTest extends SysuiTestCase { when(mMockINotificationManager.getNumNotificationChannelsForPackage( eq(TEST_PACKAGE_NAME), anyInt(), anyBoolean())).thenReturn(2); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, null, null, null); + TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), + mNotificationChannel.getImportance(), mSbn, null, null, null, + null, null); final TextView numChannelsView = (TextView) mNotificationInfo.findViewById(R.id.num_channels_desc); assertEquals(getNumChannelsDescString(2), numChannelsView.getText()); @@ -323,7 +358,7 @@ public class NotificationInfoTest extends SysuiTestCase { throws Exception { mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel, mDefaultNotificationChannel), - null, null, null, null); + mNotificationChannel.getImportance(), mSbn, null, null, null, null, null); final TextView numChannelsView = (TextView) mNotificationInfo.findViewById(R.id.num_channels_desc); assertEquals(getChannelsListDescString(mNotificationChannel, mDefaultNotificationChannel), @@ -339,7 +374,7 @@ public class NotificationInfoTest extends SysuiTestCase { mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel, mDefaultNotificationChannel, thirdChannel), - null, null, null, null); + mNotificationChannel.getImportance(), mSbn, null, null, null, null, null); final TextView numChannelsView = (TextView) mNotificationInfo.findViewById(R.id.num_channels_desc); assertEquals( @@ -359,8 +394,8 @@ public class NotificationInfoTest extends SysuiTestCase { mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel, mDefaultNotificationChannel, thirdChannel, - fourthChannel), - null, null, null, null); + fourthChannel), mNotificationChannel.getImportance(), mSbn, null, null, + null, null, null); final TextView numChannelsView = (TextView) mNotificationInfo.findViewById(R.id.num_channels_desc); assertEquals( @@ -375,7 +410,7 @@ public class NotificationInfoTest extends SysuiTestCase { throws Exception { mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel, mDefaultNotificationChannel), - null, null, null, null); + mNotificationChannel.getImportance(), mSbn, null, null, null, null, null); final TextView channelNameView = (TextView) mNotificationInfo.findViewById(R.id.channel_name); assertEquals(getNumChannelsString(2), channelNameView.getText()); @@ -386,7 +421,7 @@ public class NotificationInfoTest extends SysuiTestCase { public void testEnabledSwitchInvisibleIfBundleFromDifferentChannels() throws Exception { mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel, mDefaultNotificationChannel), - null, null, null, null); + mNotificationChannel.getImportance(), mSbn, null, null, null, null, null); Switch enabledSwitch = (Switch) mNotificationInfo.findViewById(R.id.channel_enabled_switch); assertEquals(View.INVISIBLE, enabledSwitch.getVisibility()); } @@ -394,7 +429,8 @@ public class NotificationInfoTest extends SysuiTestCase { @Test public void testbindNotification_ChannelDisabledTextGoneWhenNotDisabled() throws Exception { mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, null, null, null); + TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), + mNotificationChannel.getImportance(), mSbn, null, null, null, null, null); final TextView channelDisabledView = (TextView) mNotificationInfo.findViewById(R.id.channel_disabled); assertEquals(channelDisabledView.getVisibility(), View.GONE); @@ -404,7 +440,9 @@ public class NotificationInfoTest extends SysuiTestCase { public void testbindNotification_ChannelDisabledTextVisibleWhenDisabled() throws Exception { mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_NONE); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, null, null, null); + TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), + mNotificationChannel.getImportance(), mSbn, null, null, null, + null, null); final TextView channelDisabledView = (TextView) mNotificationInfo.findViewById(R.id.channel_disabled); assertEquals(channelDisabledView.getVisibility(), View.VISIBLE); @@ -421,7 +459,8 @@ public class NotificationInfoTest extends SysuiTestCase { mDefaultNotificationChannel.setImportance(NotificationManager.IMPORTANCE_NONE); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, Arrays.asList(mDefaultNotificationChannel), - null, null, null, null); + mDefaultNotificationChannel.getImportance(), mSbn, null, null, + null, null, null); final TextView channelDisabledView = (TextView) mNotificationInfo.findViewById(R.id.channel_disabled); assertEquals(View.VISIBLE, channelDisabledView.getVisibility()); @@ -430,7 +469,9 @@ public class NotificationInfoTest extends SysuiTestCase { @Test public void testBindNotification_DoesNotUpdateNotificationChannel() throws Exception { mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, null, null, null); + TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), + mNotificationChannel.getImportance(), mSbn, null, null, null, + null, null); verify(mMockINotificationManager, never()).updateNotificationChannelForPackage( anyString(), anyInt(), any()); } @@ -439,7 +480,9 @@ public class NotificationInfoTest extends SysuiTestCase { public void testDoesNotUpdateNotificationChannelAfterImportanceChanged() throws Exception { mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, null, null, null); + TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), + mNotificationChannel.getImportance(), mSbn, null, null, null, + null, null); Switch enabledSwitch = (Switch) mNotificationInfo.findViewById(R.id.channel_enabled_switch); enabledSwitch.setChecked(false); @@ -451,7 +494,9 @@ public class NotificationInfoTest extends SysuiTestCase { public void testHandleCloseControls_DoesNotUpdateNotificationChannelIfUnchanged() throws Exception { mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, null, null, null); + TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), + mNotificationChannel.getImportance(), mSbn, null, null, null, + null, null); mNotificationInfo.handleCloseControls(true); verify(mMockINotificationManager, never()).updateNotificationChannelForPackage( @@ -463,7 +508,9 @@ public class NotificationInfoTest extends SysuiTestCase { throws Exception { mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_UNSPECIFIED); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, null, null, null); + TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), + mNotificationChannel.getImportance(), mSbn, null, null, null, + null, null); mNotificationInfo.handleCloseControls(true); verify(mMockINotificationManager, never()).updateNotificationChannelForPackage( @@ -474,7 +521,9 @@ public class NotificationInfoTest extends SysuiTestCase { public void testEnabledSwitchOnByDefault() throws Exception { mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, null, null, null); + TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), + mNotificationChannel.getImportance(), mSbn, null, null, null, + null, null); Switch enabledSwitch = (Switch) mNotificationInfo.findViewById(R.id.channel_enabled_switch); assertTrue(enabledSwitch.isChecked()); @@ -484,7 +533,9 @@ public class NotificationInfoTest extends SysuiTestCase { public void testEnabledButtonOffWhenAlreadyBanned() throws Exception { mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_NONE); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, null, null, null); + TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), + mNotificationChannel.getImportance(), mSbn, null, null, null, + null, null); Switch enabledSwitch = (Switch) mNotificationInfo.findViewById(R.id.channel_enabled_switch); assertFalse(enabledSwitch.isChecked()); @@ -494,7 +545,9 @@ public class NotificationInfoTest extends SysuiTestCase { public void testEnabledSwitchVisibleByDefault() throws Exception { mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, null, null, null); + TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), + mNotificationChannel.getImportance(), mSbn, null, null, null, + null, null); Switch enabledSwitch = (Switch) mNotificationInfo.findViewById(R.id.channel_enabled_switch); assertEquals(View.VISIBLE, enabledSwitch.getVisibility()); @@ -504,8 +557,9 @@ public class NotificationInfoTest extends SysuiTestCase { public void testEnabledSwitchInvisibleIfNonBlockable() throws Exception { mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, null, null, - Collections.singleton(TEST_PACKAGE_NAME)); + TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), + mNotificationChannel.getImportance(), mSbn, null, null, null, + null, Collections.singleton(TEST_PACKAGE_NAME)); Switch enabledSwitch = (Switch) mNotificationInfo.findViewById(R.id.channel_enabled_switch); assertEquals(View.INVISIBLE, enabledSwitch.getVisibility()); @@ -515,8 +569,9 @@ public class NotificationInfoTest extends SysuiTestCase { public void testNonBlockableAppDoesNotBecomeBlocked() throws Exception { mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, null, null, - Collections.singleton(TEST_PACKAGE_NAME)); + TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), + mNotificationChannel.getImportance(), mSbn, null, null, null, + null, Collections.singleton(TEST_PACKAGE_NAME)); mNotificationInfo.handleCloseControls(true); verify(mMockINotificationManager, never()).updateNotificationChannelForPackage( anyString(), anyInt(), any()); @@ -526,8 +581,9 @@ public class NotificationInfoTest extends SysuiTestCase { public void testEnabledSwitchChangedCallsUpdateNotificationChannel() throws Exception { mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, null, null, - Collections.singleton(TEST_PACKAGE_NAME)); + TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), + mNotificationChannel.getImportance(), mSbn, null, null, null, + null, Collections.singleton(TEST_PACKAGE_NAME)); Switch enabledSwitch = (Switch) mNotificationInfo.findViewById(R.id.channel_enabled_switch); enabledSwitch.setChecked(false); @@ -540,8 +596,9 @@ public class NotificationInfoTest extends SysuiTestCase { public void testCloseControlsDoesNotUpdateIfSaveIsFalse() throws Exception { mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, null, null, - Collections.singleton(TEST_PACKAGE_NAME)); + TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), + mNotificationChannel.getImportance(), mSbn, null, null, null, + null, Collections.singleton(TEST_PACKAGE_NAME)); Switch enabledSwitch = (Switch) mNotificationInfo.findViewById(R.id.channel_enabled_switch); enabledSwitch.setChecked(false); @@ -554,8 +611,10 @@ public class NotificationInfoTest extends SysuiTestCase { public void testCloseControlsDoesNotUpdateIfCheckSaveListenerIsNoOp() throws Exception { mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, null, - (Runnable saveImportance) -> {}, + TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), + mNotificationChannel.getImportance(), mSbn, null, null, null, + (Runnable saveImportance) -> { + }, Collections.singleton(TEST_PACKAGE_NAME)); Switch enabledSwitch = (Switch) mNotificationInfo.findViewById(R.id.channel_enabled_switch); @@ -569,8 +628,11 @@ public class NotificationInfoTest extends SysuiTestCase { public void testCloseControlsUpdatesWhenCheckSaveListenerUsesCallback() throws Exception { mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, null, - (Runnable saveImportance) -> { saveImportance.run(); }, + TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), + mNotificationChannel.getImportance(), mSbn, null, null, null, + (Runnable saveImportance) -> { + saveImportance.run(); + }, Collections.singleton(TEST_PACKAGE_NAME)); Switch enabledSwitch = (Switch) mNotificationInfo.findViewById(R.id.channel_enabled_switch); @@ -579,4 +641,137 @@ public class NotificationInfoTest extends SysuiTestCase { verify(mMockINotificationManager, times(1)).updateNotificationChannelForPackage( eq(TEST_PACKAGE_NAME), anyInt(), eq(mNotificationChannel)); } + + @Test + public void testDisplaySettingsLink() throws Exception { + final CountDownLatch latch = new CountDownLatch(1); + final String settingsText = "work chats"; + final ResolveInfo ri = new ResolveInfo(); + ri.activityInfo = new ActivityInfo(); + ri.activityInfo.packageName = TEST_PACKAGE_NAME; + ri.activityInfo.name = "something"; + List<ResolveInfo> ris = new ArrayList<>(); + ris.add(ri); + when(mMockPackageManager.queryIntentActivities(any(), anyInt())).thenReturn(ris); + mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW); + Notification n = new Notification.Builder(mContext, mNotificationChannel.getId()) + .setSettingsText(settingsText).build(); + StatusBarNotification sbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, + 0, null, 0, 0, n, UserHandle.CURRENT, null, 0); + + mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, + TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), + mNotificationChannel.getImportance(), sbn, null, + (View v, Intent intent) -> { + latch.countDown(); + }, null, null, null); + final TextView settingsLink = mNotificationInfo.findViewById(R.id.app_settings); + assertEquals(View.VISIBLE, settingsLink.getVisibility()); + assertTrue(settingsLink.getText().toString().length() > settingsText.length()); + assertTrue(settingsLink.getText().toString().contains(settingsText)); + settingsLink.performClick(); + assertEquals(0, latch.getCount()); + } + + @Test + public void testDisplaySettingsLink_multipleChannels() throws Exception { + final CountDownLatch latch = new CountDownLatch(1); + final String settingsText = "work chats"; + final ResolveInfo ri = new ResolveInfo(); + ri.activityInfo = new ActivityInfo(); + ri.activityInfo.packageName = TEST_PACKAGE_NAME; + ri.activityInfo.name = "something"; + List<ResolveInfo> ris = new ArrayList<>(); + ris.add(ri); + when(mMockPackageManager.queryIntentActivities(any(), anyInt())).thenReturn(ris); + mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW); + Notification n = new Notification.Builder(mContext, mNotificationChannel.getId()) + .setSettingsText(settingsText).build(); + StatusBarNotification sbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, + 0, null, 0, 0, n, UserHandle.CURRENT, null, 0); + + mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, + TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel, mDefaultNotificationChannel), + mNotificationChannel.getImportance(), sbn, null, (View v, Intent intent) -> { + latch.countDown(); + }, null, null, null); + final TextView settingsLink = mNotificationInfo.findViewById(R.id.app_settings); + assertEquals(View.VISIBLE, settingsLink.getVisibility()); + settingsLink.performClick(); + assertEquals(0, latch.getCount()); + } + + @Test + public void testNoSettingsLink_noHandlingActivity() throws Exception { + final String settingsText = "work chats"; + when(mMockPackageManager.queryIntentActivities(any(), anyInt())).thenReturn(null); + mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW); + Notification n = new Notification.Builder(mContext, mNotificationChannel.getId()) + .setSettingsText(settingsText).build(); + StatusBarNotification sbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, + 0, null, 0, 0, n, UserHandle.CURRENT, null, 0); + + mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, + TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), + mNotificationChannel.getImportance(), sbn, null, null, null, + null, null); + final TextView settingsLink = mNotificationInfo.findViewById(R.id.app_settings); + assertEquals(View.GONE, settingsLink.getVisibility()); + } + + @Test + public void testNoSettingsLink_noLinkText() throws Exception { + final ResolveInfo ri = new ResolveInfo(); + ri.activityInfo = new ActivityInfo(); + ri.activityInfo.packageName = TEST_PACKAGE_NAME; + ri.activityInfo.name = "something"; + List<ResolveInfo> ris = new ArrayList<>(); + ris.add(ri); + when(mMockPackageManager.queryIntentActivities(any(), anyInt())).thenReturn(ris); + mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW); + Notification n = new Notification.Builder(mContext, mNotificationChannel.getId()).build(); + StatusBarNotification sbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, + 0, null, 0, 0, n, UserHandle.CURRENT, null, 0); + + mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, + TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), + mNotificationChannel.getImportance(), sbn, null, null, null, + null, null); + final TextView settingsLink = mNotificationInfo.findViewById(R.id.app_settings); + assertEquals(View.GONE, settingsLink.getVisibility()); + } + + @Test + public void testNoSettingsLink_afterBlockingChannel() throws Exception { + final String settingsText = "work chats"; + final ResolveInfo ri = new ResolveInfo(); + ri.activityInfo = new ActivityInfo(); + ri.activityInfo.packageName = TEST_PACKAGE_NAME; + ri.activityInfo.name = "something"; + List<ResolveInfo> ris = new ArrayList<>(); + ris.add(ri); + when(mMockPackageManager.queryIntentActivities(any(), anyInt())).thenReturn(ris); + mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW); + Notification n = new Notification.Builder(mContext, mNotificationChannel.getId()) + .setSettingsText(settingsText).build(); + StatusBarNotification sbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, + 0, null, 0, 0, n, UserHandle.CURRENT, null, 0); + + mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, + TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), + mNotificationChannel.getImportance(), sbn, null, null, null, + null, null); + final TextView settingsLink = mNotificationInfo.findViewById(R.id.app_settings); + assertEquals(View.VISIBLE, settingsLink.getVisibility()); + + // Block channel + Switch enabledSwitch = (Switch) mNotificationInfo.findViewById(R.id.channel_enabled_switch); + enabledSwitch.setChecked(false); + + assertEquals(View.GONE, settingsLink.getVisibility()); + + //unblock + enabledSwitch.setChecked(true); + assertEquals(View.VISIBLE, settingsLink.getVisibility()); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationViewWrapperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationViewWrapperTest.java new file mode 100644 index 000000000000..0dad52f2942e --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationViewWrapperTest.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification; + +import android.content.Context; +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; +import android.view.View; + +import com.android.systemui.statusbar.ExpandableNotificationRow; + +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +public class NotificationViewWrapperTest { + + private Context mContext; + + @Before + public void setUp() throws Exception { + mContext = InstrumentationRegistry.getTargetContext(); + } + + @Ignore("Broken") + @Test + public void constructor_doesntUseViewContext() throws Exception { + new TestableNotificationViewWrapper(mContext, null /* view */, null /* row */); + } + + static class TestableNotificationViewWrapper extends NotificationViewWrapper { + protected TestableNotificationViewWrapper(Context ctx, View view, + ExpandableNotificationRow row) { + super(ctx, view, row); + } + } +}
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyButtonViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyButtonViewTest.java new file mode 100644 index 000000000000..21fddf2106d4 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyButtonViewTest.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package com.android.systemui.statusbar.policy; + +import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_NAV_BUTTON_EVENT; +import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_FLAGS; +import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_NAV_ACTION; + +import static org.mockito.ArgumentMatchers.argThat; + +import android.metrics.LogMaker; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; +import android.testing.TestableLooper.RunWithLooper; +import android.view.KeyEvent; + +import com.android.internal.logging.MetricsLogger; +import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import com.android.systemui.SysuiTestCase; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentMatcher; +import org.mockito.Mockito; + +import java.util.Objects; + +@RunWith(AndroidTestingRunner.class) +@RunWithLooper +public class KeyButtonViewTest extends SysuiTestCase { + + private KeyButtonView mKeyButtonView; + private MetricsLogger mMetricsLogger; + + @Before + public void setup() throws Exception { + mMetricsLogger = mDependency.injectMockDependency(MetricsLogger.class); + TestableLooper.get(this).runWithLooper(() -> + mKeyButtonView = new KeyButtonView(mContext, null)); + } + + @Test + public void testMetrics() { + int action = 42; + int flags = 0x141; + int code = KeyEvent.KEYCODE_ENTER; + mKeyButtonView.setCode(code); + mKeyButtonView.sendEvent(action, flags); + + Mockito.verify(mMetricsLogger).write(argThat(new ArgumentMatcher<LogMaker>() { + public String mReason; + + @Override + public boolean matches(LogMaker argument) { + return checkField("category", argument.getCategory(), ACTION_NAV_BUTTON_EVENT) + && checkField("type", argument.getType(), MetricsEvent.TYPE_ACTION) + && checkField("subtype", argument.getSubtype(), code) + && checkField("FIELD_FLAGS", argument.getTaggedData(FIELD_FLAGS), flags) + && checkField("FIELD_NAV_ACTION", argument.getTaggedData(FIELD_NAV_ACTION), + action); + } + + private boolean checkField(String field, Object val, Object val2) { + if (!Objects.equals(val, val2)) { + mReason = "Expected " + field + " " + val2 + " but was " + val; + return false; + } + return true; + } + + @Override + public String toString() { + return mReason; + } + })); + } + +}
\ No newline at end of file diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto index 78bc01dc5389..b59e00d1956c 100644 --- a/proto/src/metrics_constants.proto +++ b/proto/src/metrics_constants.proto @@ -3892,6 +3892,15 @@ message MetricsEvent { // ACTION: QS -> Click date ACTION_QS_DATE = 930; + // ACTION: Event on nav button + ACTION_NAV_BUTTON_EVENT = 931; + + // FIELD: Flags for a nav button event + FIELD_FLAGS = 932; + + // FIELD: Action for a nav button event + FIELD_NAV_ACTION = 933; + // ---- End O Constants, all O constants go above this line ---- // Add new aosp constants above this line. diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index 1968d2e925aa..cb6d4df355b1 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -183,9 +183,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { private final SimpleStringSplitter mStringColonSplitter = new SimpleStringSplitter(COMPONENT_NAME_SEPARATOR); - private final List<AccessibilityServiceInfo> mEnabledServicesForFeedbackTempList = - new ArrayList<>(); - private final Rect mTempRect = new Rect(); private final Rect mTempRect1 = new Rect(); @@ -568,7 +565,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { @Override public List<AccessibilityServiceInfo> getEnabledAccessibilityServiceList(int feedbackType, int userId) { - List<AccessibilityServiceInfo> result = null; synchronized (mLock) { // We treat calls from a profile as if made by its parent as profiles // share the accessibility state of the parent. The call below @@ -577,24 +573,24 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { .resolveCallingUserIdEnforcingPermissionsLocked(userId); // The automation service can suppress other services. - UserState userState = getUserStateLocked(resolvedUserId); + final UserState userState = getUserStateLocked(resolvedUserId); if (userState.isUiAutomationSuppressingOtherServices()) { return Collections.emptyList(); } - result = mEnabledServicesForFeedbackTempList; - result.clear(); - List<Service> services = userState.mBoundServices; - for (int serviceCount = services.size(), i = 0; i < serviceCount; ++i) { - Service service = services.get(i); + final List<Service> services = userState.mBoundServices; + final int serviceCount = services.size(); + final List<AccessibilityServiceInfo> result = new ArrayList<>(serviceCount); + for (int i = 0; i < serviceCount; ++i) { + final Service service = services.get(i); // Don't report the UIAutomation (fake service) if (!sFakeAccessibilityServiceComponentName.equals(service.mComponentName) && (service.mFeedbackType & feedbackType) != 0) { result.add(service.mAccessibilityServiceInfo); } } + return result; } - return result; } @Override @@ -876,10 +872,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { */ @Override public void notifyAccessibilityButtonClicked() { - if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.STATUS_BAR) + if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.STATUS_BAR_SERVICE) != PackageManager.PERMISSION_GRANTED) { throw new SecurityException("Caller does not hold permission " - + android.Manifest.permission.STATUS_BAR); + + android.Manifest.permission.STATUS_BAR_SERVICE); } synchronized (mLock) { notifyAccessibilityButtonClickedLocked(); @@ -895,10 +891,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { */ @Override public void notifyAccessibilityButtonAvailabilityChanged(boolean available) { - if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.STATUS_BAR) + if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.STATUS_BAR_SERVICE) != PackageManager.PERMISSION_GRANTED) { throw new SecurityException("Caller does not hold permission " - + android.Manifest.permission.STATUS_BAR); + + android.Manifest.permission.STATUS_BAR_SERVICE); } synchronized (mLock) { notifyAccessibilityButtonAvailabilityChangedLocked(available); diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java index 502b5fc1ea6f..2bcc260b1c70 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java @@ -283,13 +283,6 @@ public final class AutofillManagerService extends SystemService { } } - // Called by Shell command. - public void setSaveTimeout(int timeout) { - Slog.i(TAG, "setSaveTimeout(" + timeout + ")"); - mContext.enforceCallingPermission(MANAGE_AUTO_FILL, TAG); - mUi.setSaveTimeout(timeout); - } - /** * Removes a cached service for a given user. */ diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java index 15ec98fd67b2..c8a5780fedf1 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java @@ -148,7 +148,7 @@ final class AutofillManagerServiceImpl { // TODO(b/33197203): since service is fetching the data (to use for save later), // we should optimize what's sent (for example, remove layout containers, // color / font info, etc...) - session.mStructure = structure; + session.setStructureLocked(structure); } @@ -194,13 +194,17 @@ final class AutofillManagerServiceImpl { } } + private String getComponentNameFromSettings() { + return Settings.Secure.getStringForUser( + mContext.getContentResolver(), Settings.Secure.AUTOFILL_SERVICE, mUserId); + } + void updateLocked(boolean disabled) { final boolean wasEnabled = isEnabled(); mDisabled = disabled; ComponentName serviceComponent = null; ServiceInfo serviceInfo = null; - final String componentName = Settings.Secure.getStringForUser( - mContext.getContentResolver(), Settings.Secure.AUTOFILL_SERVICE, mUserId); + final String componentName = getComponentNameFromSettings(); if (!TextUtils.isEmpty(componentName)) { try { serviceComponent = ComponentName.unflattenFromString(componentName); @@ -413,8 +417,7 @@ final class AutofillManagerServiceImpl { void disableSelf() { final long identity = Binder.clearCallingIdentity(); try { - final String autoFillService = Settings.Secure.getStringForUser( - mContext.getContentResolver(), Settings.Secure.AUTOFILL_SERVICE, mUserId); + final String autoFillService = getComponentNameFromSettings(); if (mInfo.getServiceInfo().getComponentName().equals( ComponentName.unflattenFromString(autoFillService))) { Settings.Secure.putStringForUser(mContext.getContentResolver(), @@ -432,12 +435,14 @@ final class AutofillManagerServiceImpl { void dumpLocked(String prefix, PrintWriter pw) { final String prefix2 = prefix + " "; - pw.print(prefix); pw.print("User :"); pw.println(mUserId); - pw.print(prefix); pw.print("Component:"); pw.println(mInfo != null + pw.print(prefix); pw.print("User: "); pw.println(mUserId); + pw.print(prefix); pw.print("Component: "); pw.println(mInfo != null ? mInfo.getServiceInfo().getComponentName() : null); + pw.print(prefix); pw.print("Component from settings: "); + pw.println(getComponentNameFromSettings()); pw.print(prefix); pw.print("Default component: "); pw.println(mContext.getString(R.string.config_defaultAutofillService)); - pw.print(prefix); pw.print("Disabled:"); pw.println(mDisabled); + pw.print(prefix); pw.print("Disabled: "); pw.println(mDisabled); if (VERBOSE && mInfo != null) { // ServiceInfo dump is too noisy and redundant (it can be obtained through other dumps) diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceShellCommand.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceShellCommand.java index 80560f18038e..6debc2fe26d7 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceShellCommand.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceShellCommand.java @@ -47,8 +47,6 @@ public final class AutofillManagerServiceShellCommand extends ShellCommand { switch (cmd) { case "save": return requestSave(); - case "set": - return requestSet(); case "list": return requestList(pw); case "destroy": @@ -76,9 +74,6 @@ public final class AutofillManagerServiceShellCommand extends ShellCommand { pw.println(" save [--user USER_ID]"); pw.println(" Request provider to save contents of the top activity."); pw.println(""); - pw.println(" set save_timeout MS"); - pw.println(" Sets how long (in ms) the save snack bar is shown."); - pw.println(""); pw.println(" reset"); pw.println(" Reset all pending sessions and cached service connections."); pw.println(""); @@ -91,18 +86,6 @@ public final class AutofillManagerServiceShellCommand extends ShellCommand { return 0; } - private int requestSet() { - final String type = getNextArgRequired(); - switch (type) { - case "save_timeout": - mService.setSaveTimeout(Integer.parseInt(getNextArgRequired())); - break; - default: - throw new IllegalArgumentException("Invalid 'set' type: " + type); - } - return 0; - } - private int requestDestroy(PrintWriter pw) { if (!isNextArgSessions(pw)) { return -1; diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index 801769cc42e1..9092bdb4e999 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -29,6 +29,7 @@ import static com.android.server.autofill.Helper.VERBOSE; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.assist.AssistStructure; +import android.app.assist.AssistStructure.AutofillOverlay; import android.app.assist.AssistStructure.ViewNode; import android.app.assist.AssistStructure.WindowNode; import android.content.ComponentName; @@ -56,6 +57,7 @@ import android.view.autofill.AutofillValue; import android.view.autofill.IAutoFillManagerClient; import android.view.autofill.IAutofillWindowPresenter; +import com.android.internal.R; import com.android.internal.annotations.GuardedBy; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; @@ -100,7 +102,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState @NonNull private final String mPackageName; @GuardedBy("mLock") - private final Map<AutofillId, ViewState> mViewStates = new ArrayMap<>(); + private final ArrayMap<AutofillId, ViewState> mViewStates = new ArrayMap<>(); /** * Id of the View currently being displayed. @@ -132,7 +134,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState * Assist structure sent by the app; it will be updated (sanitized, change values for save) * before sent to {@link AutofillService}. */ - @GuardedBy("mLock") AssistStructure mStructure; + @GuardedBy("mLock") + private AssistStructure mStructure; /** * Whether the client has an {@link android.view.autofill.AutofillManager.AutofillCallback}. @@ -140,6 +143,13 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState private boolean mHasCallback; /** + * Extras sent by service on {@code onFillRequest()} calls; the first non-null extra is saved + * and used on subsequent {@code onFillRequest()} and {@code onSaveRequest()} calls. + */ + @GuardedBy("mLock") + private Bundle mExtras; + + /** * Flags used to start the session. */ int mFlags; @@ -181,6 +191,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState public void onFillRequestSuccess(@Nullable FillResponse response, @NonNull String servicePackageName) { if (response == null) { + if ((mFlags & FLAG_MANUAL_REQUEST) != 0) { + getUiForShowing().showError(R.string.autofill_error_cannot_autofill); + } // Nothing to be done, but need to notify client. notifyUnavailableToClient(); removeSelf(); @@ -302,12 +315,22 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState @Override public void requestShowFillUi(AutofillId id, int width, int height, IAutofillWindowPresenter presenter) { - try { - final ViewState currentView = mViewStates.get(mCurrentViewId); - mClient.requestShowFillUi(mWindowToken, id, width, height, - currentView.getVirtualBounds(), presenter); - } catch (RemoteException e) { - Slog.e(TAG, "Error requesting to show fill UI", e); + synchronized (mLock) { + if (id.equals(mCurrentViewId)) { + try { + final ViewState view = mViewStates.get(id); + mClient.requestShowFillUi(mWindowToken, id, width, height, + view.getVirtualBounds(), + presenter); + } catch (RemoteException e) { + Slog.e(TAG, "Error requesting to show fill UI", e); + } + } else { + if (DEBUG) { + Slog.d(TAG, "Do not show full UI on " + id + " as it is not the current view (" + + mCurrentViewId + ") anymore"); + } + } } } @@ -352,6 +375,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mHasCallback = hasIt; } + public void setStructureLocked(AssistStructure structure) { + mStructure = structure; + } + /** * Shows the save UI, when session can be saved. * @@ -474,9 +501,6 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState Slog.d(TAG, "callSaveLocked(): mViewStates=" + mViewStates); } - // TODO(b/33197203 , b/35707731): decide how to handle bundle in multiple partitions - final Bundle extras = mResponses != null ? mResponses.get(0).getExtras() : null; - for (Entry<AutofillId, ViewState> entry : mViewStates.entrySet()) { final AutofillValue value = entry.getValue().getCurrentValue(); if (value == null) { @@ -506,7 +530,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mStructure.dump(); } - mRemoteFillService.onSaveRequest(mStructure, extras); + mRemoteFillService.onSaveRequest(mStructure, mExtras); } void updateLocked(AutofillId id, Rect virtualBounds, AutofillValue value, int flags) { @@ -517,10 +541,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState if (DEBUG) { Slog.d(TAG, "Creating viewState for " + id + " on " + getFlagAsString(flags)); } - viewState = new ViewState(this, id, this, ViewState.STATE_INITIAL); + viewState = new ViewState(this, id, value, this, ViewState.STATE_INITIAL); mViewStates.put(id, viewState); } else if ((flags & FLAG_VIEW_ENTERED) != 0) { - viewState = startPartitionLocked(id); + viewState = startPartitionLocked(id, value); } else { if (VERBOSE) Slog.v(TAG, "Ignored " + getFlagAsString(flags) + " for " + id); return; @@ -584,25 +608,45 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState Slog.w(TAG, "updateLocked(): unknown flags " + flags + ": " + getFlagAsString(flags)); } - private ViewState startPartitionLocked(AutofillId id) { + private ViewState startPartitionLocked(AutofillId id, AutofillValue value) { + // TODO(b/33197203 , b/35707731): temporary workaround until partitioning supports auth + if (mResponseWaitingAuth != null) { + final ViewState viewState = + new ViewState(this, id, value, this, ViewState.STATE_WAITING_RESPONSE_AUTH); + mViewStates.put(id, viewState); + return viewState; + } if (DEBUG) { Slog.d(TAG, "Starting partition for view id " + id); } - final ViewState viewState = - new ViewState(this, id, this,ViewState.STATE_STARTED_PARTITION); - mViewStates.put(id, viewState); + final ViewState newViewState = + new ViewState(this, id, value, this,ViewState.STATE_STARTED_PARTITION); + mViewStates.put(id, newViewState); - /* - * TODO(b/33197203 , b/35707731): when start a new partition, it should - * - * - add autofilled fields as sanitized - * - set focus on ViewStructure that triggered it - * - pass the first onFillRequest() bundle - * - optional: perhaps add a new flag onFilLRequest() to indicate it's a new partition? - */ - mRemoteFillService.onFillRequest(mStructure, null, 0); + // Must update value of nodes so: + // - proper node is focused + // - autofillValue is sent back to service when it was previously autofilled + for (int i = 0; i < mViewStates.size(); i++) { + final ViewState viewState = mViewStates.valueAt(i); - return viewState; + final ViewNode node = findViewNodeByIdLocked(viewState.id); + if (node == null) { + Slog.w(TAG, "startPartitionLocked(): no node for " + viewState.id); + continue; + } + + final AutofillValue initialValue = viewState.getInitialValue(); + final AutofillValue filledValue = viewState.getAutofilledValue(); + final AutofillOverlay overlay = new AutofillOverlay(); + if (filledValue != null && !filledValue.equals(initialValue)) { + overlay.value = filledValue; + } + overlay.focused = id.equals(viewState.id); + node.setAutofillOverlay(overlay); + } + mRemoteFillService.onFillRequest(mStructure, mExtras, 0); + + return newViewState; } @Override @@ -650,6 +694,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mResponses = new ArrayList<>(4); } mResponses.add(response); + if (response != null) { + mExtras = response.getExtras(); + } setViewStatesLocked(response, ViewState.STATE_FILLABLE); @@ -695,7 +742,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState if (viewState != null) { viewState.setState(state); } else { - viewState = new ViewState(this, id, this, state); + viewState = new ViewState(this, id, null, this, state); if (DEBUG) { // TODO(b/33197203): change to VERBOSE once stable Slog.d(TAG, "Adding autofillable view with id " + id + " and state " + state); } @@ -791,6 +838,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } } pw.print(prefix); pw.print("mHasCallback: "); pw.println(mHasCallback); + pw.print(prefix); pw.print("mExtras: "); pw.println(Helper.bundleToString(mExtras)); mRemoteFillService.dump(prefix, pw); } diff --git a/services/autofill/java/com/android/server/autofill/ViewState.java b/services/autofill/java/com/android/server/autofill/ViewState.java index 549f231367c6..ea5f1137568c 100644 --- a/services/autofill/java/com/android/server/autofill/ViewState.java +++ b/services/autofill/java/com/android/server/autofill/ViewState.java @@ -61,21 +61,25 @@ final class ViewState { public static final int STATE_STARTED_PARTITION = 0x20; /** User select a dataset in this view, but service must authenticate first. */ public static final int STATE_WAITING_DATASET_AUTH = 0x40; + // TODO(b/33197203 , b/35707731): temporary workaround until partitioning supports auth + public static final int STATE_WAITING_RESPONSE_AUTH = 0x80; public final AutofillId id; private final Listener mListener; private final Session mSession; private FillResponse mResponse; + private AutofillValue mInitialValue; private AutofillValue mCurrentValue; private AutofillValue mAutofilledValue; private Rect mVirtualBounds; private int mState; - ViewState(Session session, AutofillId id, Listener listener, int state) { + ViewState(Session session, AutofillId id, AutofillValue value, Listener listener, int state) { mSession = session; this.id = id; + mInitialValue = value; mListener = listener; mState = state; } @@ -110,6 +114,11 @@ final class ViewState { } @Nullable + AutofillValue getInitialValue() { + return mInitialValue; + } + + @Nullable FillResponse getResponse() { return mResponse; } @@ -184,14 +193,16 @@ final class ViewState { @Override public String toString() { - return "ViewState: [id=" + id + ", currentValue=" + mCurrentValue - + ", bounds=" + mVirtualBounds + ", state=" + getStateAsString() +"]"; + return "ViewState: [id=" + id + ", initialValue=" + mInitialValue + + ", currentValue=" + mCurrentValue + ", autofilledValue=" + mAutofilledValue + + ", bounds=" + mVirtualBounds + ", state=" + getStateAsString() + "]"; } void dump(String prefix, PrintWriter pw) { pw.print(prefix); pw.print("id:" ); pw.println(this.id); pw.print(prefix); pw.print("state:" ); pw.println(getStateAsString()); pw.print(prefix); pw.print("has response:" ); pw.println(mResponse != null); + pw.print(prefix); pw.print("initialValue:" ); pw.println(mInitialValue); pw.print(prefix); pw.print("currentValue:" ); pw.println(mCurrentValue); pw.print(prefix); pw.print("autofilledValue:" ); pw.println(mAutofilledValue); pw.print(prefix); pw.print("virtualBounds:" ); pw.println(mVirtualBounds); diff --git a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java index 832ff9a4435c..64dee584165e 100644 --- a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java +++ b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java @@ -89,6 +89,13 @@ public final class AutoFillUI { /** * Displays an error message to the user. */ + public void showError(int resId) { + showError(mContext.getString(resId)); + } + + /** + * Displays an error message to the user. + */ public void showError(@Nullable CharSequence message) { mHandler.post(() -> { if (!hasCallback()) { @@ -255,7 +262,7 @@ public final class AutoFillUI { } mMetricsLogger.write(log); } - }, mSaveTimeoutMs); + }); }); } diff --git a/services/autofill/java/com/android/server/autofill/ui/SaveUi.java b/services/autofill/java/com/android/server/autofill/ui/SaveUi.java index 509351bf0858..d35cc95e6d26 100644 --- a/services/autofill/java/com/android/server/autofill/ui/SaveUi.java +++ b/services/autofill/java/com/android/server/autofill/ui/SaveUi.java @@ -98,7 +98,7 @@ final class SaveUi { private boolean mDestroyed; SaveUi(@NonNull Context context, @NonNull CharSequence providerLabel, @NonNull SaveInfo info, - @NonNull OnSaveListener listener, int lifeTimeMs) { + @NonNull OnSaveListener listener) { mListener = new OneTimeListener(listener); final LayoutInflater inflater = LayoutInflater.from(context); @@ -117,6 +117,12 @@ final class SaveUi { case SaveInfo.SAVE_DATA_TYPE_CREDIT_CARD: type = context.getString(R.string.autofill_save_type_credit_card); break; + case SaveInfo.SAVE_DATA_TYPE_USERNAME: + type = context.getString(R.string.autofill_save_type_username); + break; + case SaveInfo.SAVE_DATA_TYPE_EMAIL_ADDRESS: + type = context.getString(R.string.autofill_save_type_email_address); + break; default: type = null; } @@ -163,13 +169,6 @@ final class SaveUi { window.getAttributes().width = WindowManager.LayoutParams.MATCH_PARENT; mDialog.show(); - - mHandler.postDelayed(() -> { - if (!mListener.mDone) { - mListener.onCancel(null); - Slog.d(TAG, "Save snackbar timed out after " + lifeTimeMs + "ms"); - } - }, lifeTimeMs); } void destroy() { diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java index 413746fab911..d647c635cbb2 100644 --- a/services/backup/java/com/android/server/backup/BackupManagerService.java +++ b/services/backup/java/com/android/server/backup/BackupManagerService.java @@ -3109,13 +3109,6 @@ public class BackupManagerService { if (!mCancelAll && mStatus == BackupTransport.TRANSPORT_OK && mPendingFullBackups != null && !mPendingFullBackups.isEmpty()) { Slog.d(TAG, "Starting full backups for: " + mPendingFullBackups); - CountDownLatch latch = new CountDownLatch(1); - String[] fullBackups = - mPendingFullBackups.toArray(new String[mPendingFullBackups.size()]); - PerformFullTransportBackupTask task = - new PerformFullTransportBackupTask(/*fullBackupRestoreObserver*/ null, - fullBackups, /*updateSchedule*/ false, /*runningJob*/ null, latch, - mObserver, mMonitor, mUserInitiated); // Acquiring wakelock for PerformFullTransportBackupTask before its start. mWakelock.acquire(); (new Thread(mFullBackupTask, "full-transport-requested")).start(); diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 25ac008fa586..50c0a124fb8a 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -1265,13 +1265,16 @@ public class ConnectivityService extends IConnectivityManager.Stub @Override public LinkProperties getLinkProperties(Network network) { enforceAccessPermission(); - NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network); - if (nai != null) { - synchronized (nai) { - return new LinkProperties(nai.linkProperties); - } + return getLinkProperties(getNetworkAgentInfoForNetwork(network)); + } + + private LinkProperties getLinkProperties(NetworkAgentInfo nai) { + if (nai == null) { + return null; + } + synchronized (nai) { + return new LinkProperties(nai.linkProperties); } - return null; } private NetworkCapabilities getNetworkCapabilitiesInternal(NetworkAgentInfo nai) { @@ -3027,7 +3030,8 @@ public class ConnectivityService extends IConnectivityManager.Stub enforceAccessPermission(); enforceInternetPermission(); - NetworkAgentInfo nai; + // TODO: execute this logic on ConnectivityService handler. + final NetworkAgentInfo nai; if (network == null) { nai = getDefaultNetwork(); } else { @@ -3038,21 +3042,24 @@ public class ConnectivityService extends IConnectivityManager.Stub return; } // Revalidate if the app report does not match our current validated state. - if (hasConnectivity == nai.lastValidated) return; + if (hasConnectivity == nai.lastValidated) { + return; + } final int uid = Binder.getCallingUid(); if (DBG) { log("reportNetworkConnectivity(" + nai.network.netId + ", " + hasConnectivity + ") by " + uid); } - synchronized (nai) { - // Validating a network that has not yet connected could result in a call to - // rematchNetworkAndRequests() which is not meant to work on such networks. - if (!nai.everConnected) return; - - if (isNetworkWithLinkPropertiesBlocked(nai.linkProperties, uid, false)) return; - - nai.networkMonitor.sendMessage(NetworkMonitor.CMD_FORCE_REEVALUATION, uid); + // Validating a network that has not yet connected could result in a call to + // rematchNetworkAndRequests() which is not meant to work on such networks. + if (!nai.everConnected) { + return; + } + LinkProperties lp = getLinkProperties(nai); + if (isNetworkWithLinkPropertiesBlocked(lp, uid, false)) { + return; } + nai.networkMonitor.sendMessage(NetworkMonitor.CMD_FORCE_REEVALUATION, uid); } private ProxyInfo getDefaultProxy() { diff --git a/services/core/java/com/android/server/IpSecService.java b/services/core/java/com/android/server/IpSecService.java new file mode 100644 index 000000000000..a7ce95ba0c88 --- /dev/null +++ b/services/core/java/com/android/server/IpSecService.java @@ -0,0 +1,494 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server; + +import static android.Manifest.permission.DUMP; +import static android.net.IpSecManager.INVALID_RESOURCE_ID; +import static android.net.IpSecManager.KEY_RESOURCE_ID; +import static android.net.IpSecManager.KEY_SPI; +import static android.net.IpSecManager.KEY_STATUS; + +import android.content.Context; +import android.net.IIpSecService; +import android.net.INetd; +import android.net.IpSecAlgorithm; +import android.net.IpSecConfig; +import android.net.IpSecManager; +import android.net.IpSecTransform; +import android.net.util.NetdService; +import android.os.Binder; +import android.os.Bundle; +import android.os.IBinder; +import android.os.ParcelFileDescriptor; +import android.os.RemoteException; +import android.os.ServiceSpecificException; +import android.util.Log; +import android.util.Slog; +import android.util.SparseArray; +import com.android.internal.annotations.GuardedBy; +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.concurrent.atomic.AtomicInteger; + +/** @hide */ +public class IpSecService extends IIpSecService.Stub { + private static final String TAG = "IpSecService"; + private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG); + private static final String NETD_SERVICE_NAME = "netd"; + private static final int[] DIRECTIONS = + new int[] {IpSecTransform.DIRECTION_OUT, IpSecTransform.DIRECTION_IN}; + + /** Binder context for this service */ + private final Context mContext; + + private Object mLock = new Object(); + + private static final int NETD_FETCH_TIMEOUT = 5000; //ms + + private AtomicInteger mNextResourceId = new AtomicInteger(0x00FADED0); + + private abstract class ManagedResource implements IBinder.DeathRecipient { + final int pid; + final int uid; + private IBinder mBinder; + + ManagedResource(IBinder binder) { + super(); + mBinder = binder; + pid = Binder.getCallingPid(); + uid = Binder.getCallingUid(); + + try { + mBinder.linkToDeath(this, 0); + } catch (RemoteException e) { + binderDied(); + } + } + + /** + * When this record is no longer needed for managing system resources this function should + * unlink all references held by the record to allow efficient garbage collection. + */ + public final void release() { + //Release all the underlying system resources first + releaseResources(); + + if (mBinder != null) { + mBinder.unlinkToDeath(this, 0); + } + mBinder = null; + + //remove this record so that it can be cleaned up + nullifyRecord(); + } + + /** + * If the Binder object dies, this function is called to free the system resources that are + * being managed by this record and to subsequently release this record for garbage + * collection + */ + public final void binderDied() { + release(); + } + + /** + * Implement this method to release all object references contained in the subclass to allow + * efficient garbage collection of the record. This should remove any references to the + * record from all other locations that hold a reference as the record is no longer valid. + */ + protected abstract void nullifyRecord(); + + /** + * Implement this method to release all system resources that are being protected by this + * record. Once the resources are released, the record should be invalidated and no longer + * used by calling releaseRecord() + */ + protected abstract void releaseResources(); + }; + + private final class TransformRecord extends ManagedResource { + private IpSecConfig mConfig; + private int mResourceId; + + TransformRecord(IpSecConfig config, int resourceId, IBinder binder) { + super(binder); + mConfig = config; + mResourceId = resourceId; + } + + public IpSecConfig getConfig() { + return mConfig; + } + + @Override + protected void releaseResources() { + for (int direction : DIRECTIONS) { + try { + getNetdInstance() + .ipSecDeleteSecurityAssociation( + mResourceId, + direction, + (mConfig.getLocalAddress() != null) + ? mConfig.getLocalAddress().getHostAddress() + : "", + (mConfig.getRemoteAddress() != null) + ? mConfig.getRemoteAddress().getHostAddress() + : "", + mConfig.getSpi(direction)); + } catch (ServiceSpecificException e) { + // FIXME: get the error code and throw is at an IOException from Errno Exception + } catch (RemoteException e) { + Log.e(TAG, "Failed to delete SA with ID: " + mResourceId); + } + } + } + + @Override + protected void nullifyRecord() { + mConfig = null; + mResourceId = INVALID_RESOURCE_ID; + } + } + + private final class SpiRecord extends ManagedResource { + private final int mDirection; + private final String mLocalAddress; + private final String mRemoteAddress; + private final IBinder mBinder; + private int mSpi; + private int mResourceId; + + SpiRecord( + int resourceId, + int direction, + String localAddress, + String remoteAddress, + int spi, + IBinder binder) { + super(binder); + mResourceId = resourceId; + mDirection = direction; + mLocalAddress = localAddress; + mRemoteAddress = remoteAddress; + mSpi = spi; + mBinder = binder; + } + + protected void releaseResources() { + try { + getNetdInstance() + .ipSecDeleteSecurityAssociation( + mResourceId, mDirection, mLocalAddress, mRemoteAddress, mSpi); + } catch (ServiceSpecificException e) { + // FIXME: get the error code and throw is at an IOException from Errno Exception + } catch (RemoteException e) { + Log.e(TAG, "Failed to delete SPI reservation with ID: " + mResourceId); + } + } + + protected void nullifyRecord() { + mSpi = IpSecManager.INVALID_SECURITY_PARAMETER_INDEX; + mResourceId = INVALID_RESOURCE_ID; + } + } + + @GuardedBy("mSpiRecords") + private final SparseArray<SpiRecord> mSpiRecords = new SparseArray<>(); + + @GuardedBy("mTransformRecords") + private final SparseArray<TransformRecord> mTransformRecords = new SparseArray<>(); + + /** + * Constructs a new IpSecService instance + * + * @param context Binder context for this service + */ + private IpSecService(Context context) { + mContext = context; + } + + static IpSecService create(Context context) throws InterruptedException { + final IpSecService service = new IpSecService(context); + service.connectNativeNetdService(); + return service; + } + + public void systemReady() { + if (isNetdAlive()) { + Slog.d(TAG, "IpSecService is ready"); + } else { + Slog.wtf(TAG, "IpSecService not ready: failed to connect to NetD Native Service!"); + } + } + + private void connectNativeNetdService() { + // Avoid blocking the system server to do this + Thread t = + new Thread( + new Runnable() { + @Override + public void run() { + synchronized (mLock) { + NetdService.get(NETD_FETCH_TIMEOUT); + } + } + }); + t.run(); + } + + INetd getNetdInstance() throws RemoteException { + final INetd netd = NetdService.getInstance(); + if (netd == null) { + throw new RemoteException("Failed to Get Netd Instance"); + } + return netd; + } + + boolean isNetdAlive() { + synchronized (mLock) { + try { + final INetd netd = getNetdInstance(); + if (netd == null) { + return false; + } + return netd.isAlive(); + } catch (RemoteException re) { + return false; + } + } + } + + @Override + /** Get a new SPI and maintain the reservation in the system server */ + public Bundle reserveSecurityParameterIndex( + int direction, String remoteAddress, int requestedSpi, IBinder binder) + throws RemoteException { + int resourceId = mNextResourceId.getAndIncrement(); + + int spi = IpSecManager.INVALID_SECURITY_PARAMETER_INDEX; + String localAddress = ""; + Bundle retBundle = new Bundle(3); + try { + spi = + getNetdInstance() + .ipSecAllocateSpi( + resourceId, + direction, + localAddress, + remoteAddress, + requestedSpi); + Log.d(TAG, "Allocated SPI " + spi); + retBundle.putInt(KEY_STATUS, IpSecManager.Status.OK); + retBundle.putInt(KEY_RESOURCE_ID, resourceId); + retBundle.putInt(KEY_SPI, spi); + synchronized (mSpiRecords) { + mSpiRecords.put( + resourceId, + new SpiRecord( + resourceId, direction, localAddress, remoteAddress, spi, binder)); + } + } catch (ServiceSpecificException e) { + // TODO: Add appropriate checks when other ServiceSpecificException types are supported + retBundle.putInt(KEY_STATUS, IpSecManager.Status.SPI_UNAVAILABLE); + retBundle.putInt(KEY_RESOURCE_ID, resourceId); + retBundle.putInt(KEY_SPI, spi); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + return retBundle; + } + + /** Release a previously allocated SPI that has been registered with the system server */ + @Override + public void releaseSecurityParameterIndex(int resourceId) throws RemoteException {} + + /** + * Open a socket via the system server and bind it to the specified port (random if port=0). + * This will return a PFD to the user that represent a bound UDP socket. The system server will + * cache the socket and a record of its owner so that it can and must be freed when no longer + * needed. + */ + @Override + public Bundle openUdpEncapsulationSocket(int port, IBinder binder) throws RemoteException { + return null; + } + + /** close a socket that has been been allocated by and registered with the system server */ + @Override + public void closeUdpEncapsulationSocket(ParcelFileDescriptor socket) {} + + /** + * Create a transport mode transform, which represent two security associations (one in each + * direction) in the kernel. The transform will be cached by the system server and must be freed + * when no longer needed. It is possible to free one, deleting the SA from underneath sockets + * that are using it, which will result in all of those sockets becoming unable to send or + * receive data. + */ + @Override + public Bundle createTransportModeTransform(IpSecConfig c, IBinder binder) + throws RemoteException { + // TODO: Basic input validation here since it's coming over the Binder + int resourceId = mNextResourceId.getAndIncrement(); + for (int direction : DIRECTIONS) { + IpSecAlgorithm auth = c.getAuthentication(direction); + IpSecAlgorithm crypt = c.getEncryption(direction); + try { + int result = + getNetdInstance() + .ipSecAddSecurityAssociation( + resourceId, + c.getMode(), + direction, + (c.getLocalAddress() != null) + ? c.getLocalAddress().getHostAddress() + : "", + (c.getRemoteAddress() != null) + ? c.getRemoteAddress().getHostAddress() + : "", + (c.getNetwork() != null) + ? c.getNetwork().getNetworkHandle() + : 0, + c.getSpi(direction), + (auth != null) ? auth.getName() : "", + (auth != null) ? auth.getKey() : null, + (auth != null) ? auth.getTruncationLengthBits() : 0, + (crypt != null) ? crypt.getName() : "", + (crypt != null) ? crypt.getKey() : null, + (crypt != null) ? crypt.getTruncationLengthBits() : 0, + c.getEncapType(), + c.getEncapLocalPort(), + c.getEncapRemotePort()); + if (result != c.getSpi(direction)) { + // TODO: cleanup the first SA if creation of second SA fails + Bundle retBundle = new Bundle(2); + retBundle.putInt(KEY_STATUS, IpSecManager.Status.SPI_UNAVAILABLE); + retBundle.putInt(KEY_RESOURCE_ID, INVALID_RESOURCE_ID); + return retBundle; + } + } catch (ServiceSpecificException e) { + // FIXME: get the error code and throw is at an IOException from Errno Exception + } + } + synchronized (mTransformRecords) { + mTransformRecords.put(resourceId, new TransformRecord(c, resourceId, binder)); + } + + Bundle retBundle = new Bundle(2); + retBundle.putInt(KEY_STATUS, IpSecManager.Status.OK); + retBundle.putInt(KEY_RESOURCE_ID, resourceId); + return retBundle; + } + + /** + * Delete a transport mode transform that was previously allocated by + registered with the + * system server. If this is called on an inactive (or non-existent) transform, it will not + * return an error. It's safe to de-allocate transforms that may have already been deleted for + * other reasons. + */ + @Override + public void deleteTransportModeTransform(int resourceId) throws RemoteException { + synchronized (mTransformRecords) { + TransformRecord record; + // We want to non-destructively get so that we can check credentials before removing + // this from the records. + record = mTransformRecords.get(resourceId); + + if (record == null) { + throw new IllegalArgumentException( + "Transform " + resourceId + " is not available to be deleted"); + } + + if (record.pid != Binder.getCallingPid() || record.uid != Binder.getCallingUid()) { + throw new SecurityException("Only the owner of an IpSec Transform may delete it!"); + } + + // TODO: if releaseResources() throws RemoteException, we can try again to clean up on + // binder death. Need to make sure that path is actually functional. + record.releaseResources(); + mTransformRecords.remove(resourceId); + record.nullifyRecord(); + } + } + + /** + * Apply an active transport mode transform to a socket, which will apply the IPsec security + * association as a correspondent policy to the provided socket + */ + @Override + public void applyTransportModeTransform(ParcelFileDescriptor socket, int resourceId) + throws RemoteException { + + synchronized (mTransformRecords) { + TransformRecord info; + // FIXME: this code should be factored out into a security check + getter + info = mTransformRecords.get(resourceId); + + if (info == null) { + throw new IllegalArgumentException("Transform " + resourceId + " is not active"); + } + + // TODO: make this a function. + if (info.pid != getCallingPid() || info.uid != getCallingUid()) { + throw new SecurityException("Only the owner of an IpSec Transform may apply it!"); + } + + IpSecConfig c = info.getConfig(); + try { + for (int direction : DIRECTIONS) { + getNetdInstance() + .ipSecApplyTransportModeTransform( + socket.getFileDescriptor(), + resourceId, + direction, + (c.getLocalAddress() != null) + ? c.getLocalAddress().getHostAddress() + : "", + (c.getRemoteAddress() != null) + ? c.getRemoteAddress().getHostAddress() + : "", + c.getSpi(direction)); + } + } catch (ServiceSpecificException e) { + // FIXME: get the error code and throw is at an IOException from Errno Exception + } + } + } + /** + * Remove a transport mode transform from a socket, applying the default (empty) policy. This + * will ensure that NO IPsec policy is applied to the socket (would be the equivalent of + * applying a policy that performs no IPsec). Today the resourceId parameter is passed but not + * used: reserved for future improved input validation. + */ + @Override + public void removeTransportModeTransform(ParcelFileDescriptor socket, int resourceId) + throws RemoteException { + try { + getNetdInstance().ipSecRemoveTransportModeTransform(socket.getFileDescriptor()); + } catch (ServiceSpecificException e) { + // FIXME: get the error code and throw is at an IOException from Errno Exception + } + } + + @Override + protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + mContext.enforceCallingOrSelfPermission(DUMP, TAG); + + pw.println("IpSecService Log:"); + pw.println("NetdNativeService Connection: " + (isNetdAlive() ? "alive" : "dead")); + pw.println(); + } +} diff --git a/services/core/java/com/android/server/NetworkScorerAppManager.java b/services/core/java/com/android/server/NetworkScorerAppManager.java index e127eb9c44af..8404025e6726 100644 --- a/services/core/java/com/android/server/NetworkScorerAppManager.java +++ b/services/core/java/com/android/server/NetworkScorerAppManager.java @@ -42,6 +42,7 @@ import java.util.List; * * @hide */ +@VisibleForTesting public class NetworkScorerAppManager { private static final String TAG = "NetworkScorerAppManager"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); @@ -63,7 +64,8 @@ public class NetworkScorerAppManager { * Returns the list of available scorer apps. The list will be empty if there are * no valid scorers. */ - List<NetworkScorerAppData> getAllValidScorers() { + @VisibleForTesting + public List<NetworkScorerAppData> getAllValidScorers() { if (VERBOSE) Log.v(TAG, "getAllValidScorers()"); final PackageManager pm = mContext.getPackageManager(); final Intent serviceIntent = new Intent(NetworkScoreManager.ACTION_RECOMMEND_NETWORKS); @@ -168,7 +170,8 @@ public class NetworkScorerAppManager { * it was disabled or uninstalled). */ @Nullable - NetworkScorerAppData getActiveScorer() { + @VisibleForTesting + public NetworkScorerAppData getActiveScorer() { final int enabledSetting = getNetworkRecommendationsEnabledSetting(); if (enabledSetting == NetworkScoreManager.RECOMMENDATIONS_ENABLED_FORCED_OFF) { return null; @@ -211,7 +214,8 @@ public class NetworkScorerAppManager { * @return true if the scorer was changed, or false if the package is not a valid scorer or * a valid network recommendation provider exists. */ - boolean setActiveScorer(String packageName) { + @VisibleForTesting + public boolean setActiveScorer(String packageName) { final String oldPackageName = getNetworkRecommendationsPackage(); if (TextUtils.equals(oldPackageName, packageName)) { @@ -246,7 +250,8 @@ public class NetworkScorerAppManager { * is no longer valid then {@link Settings.Global#NETWORK_RECOMMENDATIONS_ENABLED} will be set * to <code>0</code> (disabled). */ - void updateState() { + @VisibleForTesting + public void updateState() { final int enabledSetting = getNetworkRecommendationsEnabledSetting(); if (enabledSetting == NetworkScoreManager.RECOMMENDATIONS_ENABLED_FORCED_OFF) { // Don't change anything if it's forced off. @@ -284,7 +289,8 @@ public class NetworkScorerAppManager { /** * Migrates the NETWORK_SCORER_APP Setting to the USE_OPEN_WIFI_PACKAGE Setting. */ - void migrateNetworkScorerAppSettingIfNeeded() { + @VisibleForTesting + public void migrateNetworkScorerAppSettingIfNeeded() { final String scorerAppPkgNameSetting = mSettingsFacade.getString(mContext, Settings.Global.NETWORK_SCORER_APP); if (TextUtils.isEmpty(scorerAppPkgNameSetting)) { diff --git a/services/core/java/com/android/server/SensorNotificationService.java b/services/core/java/com/android/server/SensorNotificationService.java index 06104646cd87..7f5befabc576 100644 --- a/services/core/java/com/android/server/SensorNotificationService.java +++ b/services/core/java/com/android/server/SensorNotificationService.java @@ -20,25 +20,46 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.hardware.GeomagneticField; import android.hardware.Sensor; +import android.hardware.SensorAdditionalInfo; import android.hardware.SensorEvent; import android.hardware.SensorEventListener; import android.hardware.SensorManager; +import android.location.Location; +import android.location.LocationListener; +import android.location.LocationManager; +import android.os.Bundle; import android.os.SystemClock; import android.os.SystemProperties; import android.os.UserHandle; import android.provider.Settings; import android.util.Slog; -public class SensorNotificationService extends SystemService implements SensorEventListener { - //TODO: set DBG to false or remove Slog before release - private static final boolean DBG = true; +public class SensorNotificationService extends SystemService + implements SensorEventListener, LocationListener { + private static final boolean DBG = false; private static final String TAG = "SensorNotificationService"; - private Context mContext; + private static final long MINUTE_IN_MS = 60 * 1000; + private static final long KM_IN_M = 1000; + + private static final long LOCATION_MIN_TIME = 30 * MINUTE_IN_MS; + private static final long LOCATION_MIN_DISTANCE = 100 * KM_IN_M; + + private static final String PROPERTY_USE_MOCKED_LOCATION = + "sensor.notification.use_mocked"; // max key length is 32 + + private static final long MILLIS_2010_1_1 = 1262358000000l; + + private Context mContext; private SensorManager mSensorManager; + private LocationManager mLocationManager; private Sensor mMetaSensor; + // for rate limiting + private long mLocalGeomagneticFieldUpdateTime = -LOCATION_MIN_TIME; + public SensorNotificationService(Context context) { super(context); mContext = context; @@ -50,7 +71,6 @@ public class SensorNotificationService extends SystemService implements SensorEv public void onBootPhase(int phase) { if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) { - // start mSensorManager = (SensorManager) mContext.getSystemService(Context.SENSOR_SERVICE); mMetaSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_DYNAMIC_SENSOR_META); if (mMetaSensor == null) { @@ -60,13 +80,28 @@ public class SensorNotificationService extends SystemService implements SensorEv SensorManager.SENSOR_DELAY_FASTEST); } } + + if (phase == PHASE_BOOT_COMPLETED) { + // LocationManagerService is initialized after PHASE_THIRD_PARTY_APPS_CAN_START + mLocationManager = + (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE); + if (mLocationManager == null) { + if (DBG) Slog.d(TAG, "Cannot obtain location service."); + } else { + mLocationManager.requestLocationUpdates( + LocationManager.PASSIVE_PROVIDER, + LOCATION_MIN_TIME, + LOCATION_MIN_DISTANCE, + this); + } + } } private void broadcastDynamicSensorChanged() { Intent i = new Intent(Intent.ACTION_DYNAMIC_SENSOR_CHANGED); i.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); // avoid waking up manifest receivers mContext.sendBroadcastAsUser(i, UserHandle.ALL); - if (DBG) Slog.d(TAG, "DYNS sent dynamic sensor broadcast"); + if (DBG) Slog.d(TAG, "dynamic sensor broadcast sent"); } @Override @@ -77,8 +112,62 @@ public class SensorNotificationService extends SystemService implements SensorEv } @Override - public void onAccuracyChanged(Sensor sensor, int accuracy) { + public void onLocationChanged(Location location) { + if (DBG) Slog.d(TAG, String.format( + "Location is (%f, %f), h %f, acc %f, mocked %b", + location.getLatitude(), location.getLongitude(), + location.getAltitude(), location.getAccuracy(), + location.isFromMockProvider())); + + // lat long == 0 usually means invalid location + if (location.getLatitude() == 0 && location.getLongitude() == 0) { + return; + } + + // update too often, ignore + if (SystemClock.elapsedRealtime() - mLocalGeomagneticFieldUpdateTime < 10 * MINUTE_IN_MS) { + return; + } + + long time = System.currentTimeMillis(); + // Mocked location should not be used. Except in test, only use mocked location + // Wrong system clock also gives bad values so ignore as well. + if (useMockedLocation() == location.isFromMockProvider() || time < MILLIS_2010_1_1) { + return; + } + + GeomagneticField field = new GeomagneticField( + (float) location.getLatitude(), (float) location.getLongitude(), + (float) location.getAltitude(), time); + if (DBG) Slog.d(TAG, String.format( + "Nominal mag field, norm %fuT, decline %f deg, incline %f deg", + field.getFieldStrength() / 1000, field.getDeclination(), field.getInclination())); + + try { + SensorAdditionalInfo info = SensorAdditionalInfo.createLocalGeomagneticField( + field.getFieldStrength() / 1000, // convert from nT to uT + (float)(field.getDeclination() * Math.PI / 180), // from degree to rad + (float)(field.getInclination() * Math.PI / 180)); // from degree to rad + if (info != null) { + mSensorManager.setOperationParameter(info); + mLocalGeomagneticFieldUpdateTime = SystemClock.elapsedRealtime(); + } + } catch (IllegalArgumentException e) { + Slog.e(TAG, "Invalid local geomagnetic field, ignore."); + } + } + + @Override + public void onAccuracyChanged(Sensor sensor, int accuracy) {} + @Override + public void onStatusChanged(String provider, int status, Bundle extras) {} + @Override + public void onProviderEnabled(String provider) {} + @Override + public void onProviderDisabled(String provider) {} + private boolean useMockedLocation() { + return "false".equals(System.getProperty(PROPERTY_USE_MOCKED_LOCATION, "false")); } } diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java index 7c1a6093beda..dd4d9065f126 100644 --- a/services/core/java/com/android/server/TelephonyRegistry.java +++ b/services/core/java/com/android/server/TelephonyRegistry.java @@ -32,37 +32,39 @@ import android.os.IBinder; import android.os.Message; import android.os.RemoteException; import android.os.UserHandle; -import android.telephony.CellLocation; -import android.telephony.Rlog; -import android.telephony.TelephonyManager; -import android.telephony.SubscriptionManager; -import android.telephony.PhoneStateListener; -import android.telephony.ServiceState; -import android.telephony.SignalStrength; import android.telephony.CellInfo; -import android.telephony.VoLteServiceState; +import android.telephony.CellLocation; import android.telephony.DisconnectCause; +import android.telephony.PhoneStateListener; import android.telephony.PreciseCallState; import android.telephony.PreciseDataConnectionState; import android.telephony.PreciseDisconnectCause; +import android.telephony.Rlog; +import android.telephony.ServiceState; +import android.telephony.SignalStrength; +import android.telephony.SubscriptionManager; +import android.telephony.TelephonyManager; +import android.telephony.VoLteServiceState; import android.text.TextUtils; -import android.text.format.Time; - -import java.util.ArrayList; -import java.util.List; -import java.io.FileDescriptor; -import java.io.PrintWriter; +import android.util.LocalLog; import com.android.internal.app.IBatteryStats; import com.android.internal.telephony.IOnSubscriptionsChangedListener; -import com.android.internal.telephony.ITelephonyRegistry; import com.android.internal.telephony.IPhoneStateListener; +import com.android.internal.telephony.ITelephonyRegistry; import com.android.internal.telephony.PhoneConstantConversions; import com.android.internal.telephony.PhoneConstants; import com.android.internal.telephony.TelephonyIntents; import com.android.internal.util.DumpUtils; +import com.android.internal.util.IndentingPrintWriter; import com.android.server.am.BatteryStatsService; +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + /** * Since phone process can be restarted, this class provides a centralized place * that applications can register and be called back from. @@ -159,8 +161,6 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { private String[] mDataConnectionReason; - private String[] mDataConnectionApn; - private ArrayList<String>[] mConnectedApns; private LinkProperties[] mDataConnectionLinkProperties; @@ -191,6 +191,8 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { private boolean mCarrierNetworkChangeState = false; + private final LocalLog mLocalLog = new LocalLog(100); + private PreciseDataConnectionState mPreciseDataConnectionState = new PreciseDataConnectionState(); @@ -310,7 +312,6 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { mMessageWaiting = new boolean[numPhones]; mDataConnectionPossible = new boolean[numPhones]; mDataConnectionReason = new String[numPhones]; - mDataConnectionApn = new String[numPhones]; mCallForwarding = new boolean[numPhones]; mCellLocation = new Bundle[numPhones]; mDataConnectionLinkProperties = new LinkProperties[numPhones]; @@ -329,7 +330,6 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { mCallForwarding[i] = false; mDataConnectionPossible[i] = false; mDataConnectionReason[i] = ""; - mDataConnectionApn[i] = ""; mCellLocation[i] = new Bundle(); mCellInfo.add(i, null); mConnectedApns[i] = new ArrayList<String>(); @@ -536,7 +536,6 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { if (DBG) { log("listen: Register r=" + r + " r.subId=" + r.subId + " phoneId=" + phoneId); } - if (VDBG) toStringLogSSC("listen"); if (notifyNow && validatePhoneId(phoneId)) { if ((events & PhoneStateListener.LISTEN_SERVICE_STATE) != 0) { try { @@ -780,14 +779,14 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { } synchronized (mRecords) { + String str = "notifyServiceStateForSubscriber: subId=" + subId + " phoneId=" + phoneId + + " state=" + state; if (VDBG) { - log("notifyServiceStateForSubscriber: subId=" + subId + " phoneId=" + phoneId - + " state=" + state); + log(str); } + mLocalLog.log(str); if (validatePhoneId(phoneId)) { mServiceState[phoneId] = state; - logServiceStateChanged("notifyServiceStateForSubscriber", subId, phoneId, state); - if (VDBG) toStringLogSSC("notifyServiceStateForSubscriber"); for (Record r : mRecords) { if (VDBG) { @@ -885,7 +884,6 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { if (VDBG) { log("notifySignalStrengthForPhoneId: subId=" + subId +" phoneId=" + phoneId + " signalStrength=" + signalStrength); - toStringLogSSC("notifySignalStrengthForPhoneId"); } synchronized (mRecords) { @@ -1137,18 +1135,20 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { modified = true; } if (modified) { - if (DBG) { - log("onDataConnectionStateChanged(" + mDataConnectionState[phoneId] - + ", " + mDataConnectionNetworkType[phoneId] + ")"); - } + String str = "onDataConnectionStateChanged(" + mDataConnectionState[phoneId] + + ", " + mDataConnectionNetworkType[phoneId] + ")"; + log(str); + mLocalLog.log(str); for (Record r : mRecords) { if (r.matchPhoneStateListenerEvent( PhoneStateListener.LISTEN_DATA_CONNECTION_STATE) && idMatch(r.subId, subId, phoneId)) { try { - log("Notify data connection state changed on sub: " + - subId); - r.callback.onDataConnectionStateChanged(mDataConnectionState[phoneId], + if (DBG) { + log("Notify data connection state changed on sub: " + subId); + } + r.callback.onDataConnectionStateChanged( + mDataConnectionState[phoneId], mDataConnectionNetworkType[phoneId]); } catch (RemoteException ex) { mRemoveList.add(r.binder); @@ -1163,7 +1163,8 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { if (r.matchPhoneStateListenerEvent( PhoneStateListener.LISTEN_PRECISE_DATA_CONNECTION_STATE)) { try { - r.callback.onPreciseDataConnectionStateChanged(mPreciseDataConnectionState); + r.callback.onPreciseDataConnectionStateChanged( + mPreciseDataConnectionState); } catch (RemoteException ex) { mRemoveList.add(r.binder); } @@ -1391,36 +1392,58 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { } @Override - public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + public void dump(FileDescriptor fd, PrintWriter writer, String[] args) { + final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " "); + if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return; + synchronized (mRecords) { final int recordCount = mRecords.size(); pw.println("last known state:"); + pw.increaseIndent(); for (int i = 0; i < TelephonyManager.getDefault().getPhoneCount(); i++) { - pw.println(" Phone Id=" + i); - pw.println(" mCallState=" + mCallState[i]); - pw.println(" mCallIncomingNumber=" + mCallIncomingNumber[i]); - pw.println(" mServiceState=" + mServiceState[i]); - pw.println(" mVoiceActivationState= " + mVoiceActivationState[i]); - pw.println(" mDataActivationState= " + mDataActivationState[i]); - pw.println(" mSignalStrength=" + mSignalStrength[i]); - pw.println(" mMessageWaiting=" + mMessageWaiting[i]); - pw.println(" mCallForwarding=" + mCallForwarding[i]); - pw.println(" mDataActivity=" + mDataActivity[i]); - pw.println(" mDataConnectionState=" + mDataConnectionState[i]); - pw.println(" mDataConnectionPossible=" + mDataConnectionPossible[i]); - pw.println(" mDataConnectionReason=" + mDataConnectionReason[i]); - pw.println(" mDataConnectionApn=" + mDataConnectionApn[i]); - pw.println(" mDataConnectionLinkProperties=" + mDataConnectionLinkProperties[i]); - pw.println(" mDataConnectionNetworkCapabilities=" + + pw.println("Phone Id=" + i); + pw.increaseIndent(); + pw.println("mCallState=" + mCallState[i]); + pw.println("mCallIncomingNumber=" + mCallIncomingNumber[i]); + pw.println("mServiceState=" + mServiceState[i]); + pw.println("mVoiceActivationState= " + mVoiceActivationState[i]); + pw.println("mDataActivationState= " + mDataActivationState[i]); + pw.println("mSignalStrength=" + mSignalStrength[i]); + pw.println("mMessageWaiting=" + mMessageWaiting[i]); + pw.println("mCallForwarding=" + mCallForwarding[i]); + pw.println("mDataActivity=" + mDataActivity[i]); + pw.println("mDataConnectionState=" + mDataConnectionState[i]); + pw.println("mDataConnectionPossible=" + mDataConnectionPossible[i]); + pw.println("mDataConnectionReason=" + mDataConnectionReason[i]); + pw.println("mDataConnectionLinkProperties=" + mDataConnectionLinkProperties[i]); + pw.println("mDataConnectionNetworkCapabilities=" + mDataConnectionNetworkCapabilities[i]); - pw.println(" mCellLocation=" + mCellLocation[i]); - pw.println(" mCellInfo=" + mCellInfo.get(i)); + pw.println("mCellLocation=" + mCellLocation[i]); + pw.println("mCellInfo=" + mCellInfo.get(i)); + pw.decreaseIndent(); } + pw.println("mConnectedApns=" + Arrays.toString(mConnectedApns)); + pw.println("mPreciseDataConnectionState=" + mPreciseDataConnectionState); + pw.println("mPreciseCallState=" + mPreciseCallState); + pw.println("mCarrierNetworkChangeState=" + mCarrierNetworkChangeState); + pw.println("mRingingCallState=" + mRingingCallState); + pw.println("mForegroundCallState=" + mForegroundCallState); + pw.println("mBackgroundCallState=" + mBackgroundCallState); + pw.println("mVoLteServiceState=" + mVoLteServiceState); + + pw.decreaseIndent(); + + pw.println("local logs:"); + pw.increaseIndent(); + mLocalLog.dump(fd, pw, args); + pw.decreaseIndent(); pw.println("registrations: count=" + recordCount); + pw.increaseIndent(); for (Record r : mRecords) { - pw.println(" " + r); + pw.println(r); } + pw.decreaseIndent(); } } @@ -1705,63 +1728,6 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { Rlog.d(TAG, s); } - private static class LogSSC { - private Time mTime; - private String mS; - private int mSubId; - private int mPhoneId; - private ServiceState mState; - - public void set(Time t, String s, int subId, int phoneId, ServiceState state) { - mTime = t; mS = s; mSubId = subId; mPhoneId = phoneId; mState = state; - } - - @Override - public String toString() { - return mS + " Time " + mTime.toString() + " mSubId " + mSubId + " mPhoneId " - + mPhoneId + " mState " + mState; - } - } - - private LogSSC logSSC [] = new LogSSC[10]; - private int next = 0; - - private void logServiceStateChanged(String s, int subId, int phoneId, ServiceState state) { - if (logSSC == null || logSSC.length == 0) { - return; - } - if (logSSC[next] == null) { - logSSC[next] = new LogSSC(); - } - Time t = new Time(); - t.setToNow(); - logSSC[next].set(t, s, subId, phoneId, state); - if (++next >= logSSC.length) { - next = 0; - } - } - - private void toStringLogSSC(String prompt) { - if (logSSC == null || logSSC.length == 0 || (next == 0 && logSSC[next] == null)) { - log(prompt + ": logSSC is empty"); - } else { - // There is at least one element - log(prompt + ": logSSC.length=" + logSSC.length + " next=" + next); - int i = next; - if (logSSC[i] == null) { - // logSSC is not full so back to the beginning - i = 0; - } - do { - log(logSSC[i].toString()); - if (++i >= logSSC.length) { - i = 0; - } - } while (i != next); - log(prompt + ": ----------------"); - } - } - boolean idMatch(int rSubId, int subId, int phoneId) { if(subId < 0) { diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java index e560d325e6dd..738365d5b36f 100644 --- a/services/core/java/com/android/server/accounts/AccountManagerService.java +++ b/services/core/java/com/android/server/accounts/AccountManagerService.java @@ -427,14 +427,13 @@ public class AccountManagerService public boolean addAccountExplicitlyWithVisibility(Account account, String password, Bundle extras, Map packageToVisibility) { Bundle.setDefusable(extras, true); - - final int callingUid = Binder.getCallingUid(); + int callingUid = Binder.getCallingUid(); + int userId = UserHandle.getCallingUserId(); if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "addAccountExplicitly: " + account + ", caller's uid " + callingUid + ", pid " + Binder.getCallingPid()); } Preconditions.checkNotNull(account, "account cannot be null"); - int userId = UserHandle.getCallingUserId(); if (!isAccountManagedByCaller(account.type, callingUid, userId)) { String msg = String.format("uid %s cannot explicitly add accounts of type: %s", callingUid, account.type); @@ -461,9 +460,9 @@ public class AccountManagerService public Map<Account, Integer> getAccountsAndVisibilityForPackage(String packageName, String accountType) { int callingUid = Binder.getCallingUid(); + int userId = UserHandle.getCallingUserId(); boolean isSystemUid = UserHandle.isSameApp(callingUid, Process.SYSTEM_UID); - List<String> managedTypes = - getTypesForCaller(callingUid, UserHandle.getUserId(callingUid), isSystemUid); + List<String> managedTypes = getTypesForCaller(callingUid, userId, isSystemUid); if ((accountType != null && !managedTypes.contains(accountType)) || (accountType == null && !isSystemUid)) { @@ -478,8 +477,9 @@ public class AccountManagerService long identityToken = clearCallingIdentity(); try { + UserAccounts accounts = getUserAccounts(userId); return getAccountsAndVisibilityForPackage(packageName, managedTypes, callingUid, - getUserAccounts(UserHandle.getUserId(callingUid))); + accounts); } finally { restoreCallingIdentity(identityToken); } @@ -490,12 +490,8 @@ public class AccountManagerService */ private Map<Account, Integer> getAccountsAndVisibilityForPackage(String packageName, List<String> accountTypes, Integer callingUid, UserAccounts accounts) { - int uid = 0; - try { - uid = mPackageManager.getPackageUidAsUser(packageName, - UserHandle.getUserId(callingUid)); - } catch (NameNotFoundException e) { - Log.d(TAG, "Package not found " + e.getMessage()); + if (!packageExistsForUser(packageName, accounts.userId)) { + Log.d(TAG, "Package not found " + packageName); return new LinkedHashMap<>(); } @@ -520,19 +516,26 @@ public class AccountManagerService public Map<String, Integer> getPackagesAndVisibilityForAccount(Account account) { Preconditions.checkNotNull(account, "account cannot be null"); int callingUid = Binder.getCallingUid(); - int userId = UserHandle.getUserId(callingUid); - UserAccounts accounts = getUserAccounts(userId); + int userId = UserHandle.getCallingUserId(); if (!isAccountManagedByCaller(account.type, callingUid, userId) && !isSystemUid(callingUid)) { String msg = String.format("uid %s cannot get secrets for account %s", callingUid, account); throw new SecurityException(msg); } - synchronized (accounts.dbLock) { - synchronized (accounts.cacheLock) { - return getPackagesAndVisibilityForAccountLocked(account, accounts); + + long identityToken = clearCallingIdentity(); + try { + UserAccounts accounts = getUserAccounts(userId); + synchronized (accounts.dbLock) { + synchronized (accounts.cacheLock) { + return getPackagesAndVisibilityForAccountLocked(account, accounts); + } } + } finally { + restoreCallingIdentity(identityToken); } + } /** @@ -560,8 +563,8 @@ public class AccountManagerService Preconditions.checkNotNull(account, "account cannot be null"); Preconditions.checkNotNull(packageName, "packageName cannot be null"); int callingUid = Binder.getCallingUid(); - UserAccounts accounts = getUserAccounts(UserHandle.getUserId(callingUid)); - if (!isAccountManagedByCaller(account.type, callingUid, accounts.userId) + int userId = UserHandle.getCallingUserId(); + if (!isAccountManagedByCaller(account.type, callingUid, userId) && !isSystemUid(callingUid)) { String msg = String.format( "uid %s cannot get secrets for accounts of type: %s", @@ -569,7 +572,13 @@ public class AccountManagerService account.type); throw new SecurityException(msg); } - return resolveAccountVisibility(account, packageName, accounts); + long identityToken = clearCallingIdentity(); + try { + UserAccounts accounts = getUserAccounts(userId); + return resolveAccountVisibility(account, packageName, accounts); + } finally { + restoreCallingIdentity(identityToken); + } } /** @@ -708,8 +717,8 @@ public class AccountManagerService Preconditions.checkNotNull(account, "account cannot be null"); Preconditions.checkNotNull(packageName, "packageName cannot be null"); int callingUid = Binder.getCallingUid(); - UserAccounts accounts = getUserAccounts(UserHandle.getUserId(callingUid)); - if (!isAccountManagedByCaller(account.type, callingUid, accounts.userId) + int userId = UserHandle.getCallingUserId(); + if (!isAccountManagedByCaller(account.type, callingUid, userId) && !isSystemUid(callingUid)) { String msg = String.format( "uid %s cannot get secrets for accounts of type: %s", @@ -717,8 +726,14 @@ public class AccountManagerService account.type); throw new SecurityException(msg); } - return setAccountVisibility(account, packageName, newVisibility, true /* notify */, + long identityToken = clearCallingIdentity(); + try { + UserAccounts accounts = getUserAccounts(userId); + return setAccountVisibility(account, packageName, newVisibility, true /* notify */, accounts); + } finally { + restoreCallingIdentity(identityToken); + } } /** @@ -805,8 +820,15 @@ public class AccountManagerService public void registerAccountListener(String[] accountTypes, String opPackageName) { int callingUid = Binder.getCallingUid(); mAppOpsManager.checkPackage(callingUid, opPackageName); - registerAccountListener(accountTypes, opPackageName, - getUserAccounts(UserHandle.getUserId(callingUid))); + + int userId = UserHandle.getCallingUserId(); + long identityToken = clearCallingIdentity(); + try { + UserAccounts accounts = getUserAccounts(userId); + registerAccountListener(accountTypes, opPackageName, accounts); + } finally { + restoreCallingIdentity(identityToken); + } } private void registerAccountListener(String[] accountTypes, String opPackageName, @@ -832,7 +854,18 @@ public class AccountManagerService public void unregisterAccountListener(String[] accountTypes, String opPackageName) { int callingUid = Binder.getCallingUid(); mAppOpsManager.checkPackage(callingUid, opPackageName); - UserAccounts accounts = getUserAccounts(UserHandle.getUserId(callingUid)); + int userId = UserHandle.getCallingUserId(); + long identityToken = clearCallingIdentity(); + try { + UserAccounts accounts = getUserAccounts(userId); + unregisterAccountListener(accountTypes, opPackageName, accounts); + } finally { + restoreCallingIdentity(identityToken); + } + } + + private void unregisterAccountListener(String[] accountTypes, String opPackageName, + UserAccounts accounts) { synchronized (accounts.mReceiversForType) { if (accountTypes == null) { // null for any type @@ -903,7 +936,7 @@ public class AccountManagerService long identityToken = clearCallingIdentity(); try { mPackageManager.getPackageUidAsUser(packageName, userId); - return true; // package exist + return true; } finally { restoreCallingIdentity(identityToken); } diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index 4cbfb275fd32..7dd75df5252a 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -31,8 +31,10 @@ import java.util.Set; import android.app.ActivityThread; import android.app.AppOpsManager; +import android.app.ServiceStartArgs; import android.content.IIntentSender; import android.content.IntentSender; +import android.content.pm.ParceledListSlice; import android.os.Build; import android.os.Bundle; import android.os.DeadObjectException; @@ -369,7 +371,8 @@ public final class ActiveServices { } // This app knows it is in the new model where this operation is not // allowed, so tell it what has happened. - return new ComponentName("?", "app is in background"); + UidRecord uidRec = mAm.mActiveUids.get(r.appInfo.uid); + return new ComponentName("?", "app is in background uid " + uidRec); } } finally { Binder.restoreCallingIdentity(token); @@ -1956,78 +1959,86 @@ public final class ActiveServices { return; } + ArrayList<ServiceStartArgs> args = new ArrayList<>(); + while (r.pendingStarts.size() > 0) { - Exception caughtException = null; - ServiceRecord.StartItem si = null; - try { - si = r.pendingStarts.remove(0); - if (DEBUG_SERVICE) { - Slog.v(TAG_SERVICE, "Sending arguments to: " - + r + " " + r.intent + " args=" + si.intent); - } - if (si.intent == null && N > 1) { - // If somehow we got a dummy null intent in the middle, - // then skip it. DO NOT skip a null intent when it is - // the only one in the list -- this is to support the - // onStartCommand(null) case. - continue; - } - si.deliveredTime = SystemClock.uptimeMillis(); - r.deliveredStarts.add(si); - si.deliveryCount++; - if (si.neededGrants != null) { - mAm.grantUriPermissionUncheckedFromIntentLocked(si.neededGrants, - si.getUriPermissionsLocked()); - } - mAm.grantEphemeralAccessLocked(r.userId, si.intent, - r.appInfo.uid, UserHandle.getAppId(si.callingId)); - bumpServiceExecutingLocked(r, execInFg, "start"); - if (!oomAdjusted) { - oomAdjusted = true; - mAm.updateOomAdjLocked(r.app); - } - if (r.fgRequired && !r.fgWaiting) { - if (!r.isForeground) { - if (DEBUG_BACKGROUND_CHECK) { - Slog.i(TAG, "Launched service must call startForeground() within timeout: " + r); - } - scheduleServiceForegroundTransitionTimeoutLocked(r); - } else { - if (DEBUG_BACKGROUND_CHECK) { - Slog.i(TAG, "Service already foreground; no new timeout: " + r); - } - r.fgRequired = false; + ServiceRecord.StartItem si = r.pendingStarts.remove(0); + if (DEBUG_SERVICE) { + Slog.v(TAG_SERVICE, "Sending arguments to: " + + r + " " + r.intent + " args=" + si.intent); + } + if (si.intent == null && N > 1) { + // If somehow we got a dummy null intent in the middle, + // then skip it. DO NOT skip a null intent when it is + // the only one in the list -- this is to support the + // onStartCommand(null) case. + continue; + } + si.deliveredTime = SystemClock.uptimeMillis(); + r.deliveredStarts.add(si); + si.deliveryCount++; + if (si.neededGrants != null) { + mAm.grantUriPermissionUncheckedFromIntentLocked(si.neededGrants, + si.getUriPermissionsLocked()); + } + mAm.grantEphemeralAccessLocked(r.userId, si.intent, + r.appInfo.uid, UserHandle.getAppId(si.callingId)); + bumpServiceExecutingLocked(r, execInFg, "start"); + if (!oomAdjusted) { + oomAdjusted = true; + mAm.updateOomAdjLocked(r.app); + } + if (r.fgRequired && !r.fgWaiting) { + if (!r.isForeground) { + if (DEBUG_BACKGROUND_CHECK) { + Slog.i(TAG, "Launched service must call startForeground() within timeout: " + r); } + scheduleServiceForegroundTransitionTimeoutLocked(r); + } else { + if (DEBUG_BACKGROUND_CHECK) { + Slog.i(TAG, "Service already foreground; no new timeout: " + r); + } + r.fgRequired = false; } - int flags = 0; - if (si.deliveryCount > 1) { - flags |= Service.START_FLAG_RETRY; - } - if (si.doneExecutingCount > 0) { - flags |= Service.START_FLAG_REDELIVERY; - } - r.app.thread.scheduleServiceArgs(r, si.taskRemoved, si.id, flags, si.intent); - } catch (TransactionTooLargeException e) { - if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Transaction too large: intent=" - + si.intent); - caughtException = e; - } catch (RemoteException e) { - // Remote process gone... we'll let the normal cleanup take care of this. - if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Crashed while sending args: " + r); - caughtException = e; - } catch (Exception e) { - Slog.w(TAG, "Unexpected exception", e); - caughtException = e; } + int flags = 0; + if (si.deliveryCount > 1) { + flags |= Service.START_FLAG_RETRY; + } + if (si.doneExecutingCount > 0) { + flags |= Service.START_FLAG_REDELIVERY; + } + args.add(new ServiceStartArgs(si.taskRemoved, si.id, flags, si.intent)); + } - if (caughtException != null) { - // Keep nesting count correct - final boolean inDestroying = mDestroyingServices.contains(r); + ParceledListSlice<ServiceStartArgs> slice = new ParceledListSlice<>(args); + slice.setInlineCountLimit(4); + Exception caughtException = null; + try { + r.app.thread.scheduleServiceArgs(r, slice); + } catch (TransactionTooLargeException e) { + if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Transaction too large for " + args.size() + + " args, first: " + args.get(0).args); + Slog.w(TAG, "Failed delivering service starts", e); + caughtException = e; + } catch (RemoteException e) { + // Remote process gone... we'll let the normal cleanup take care of this. + if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Crashed while sending args: " + r); + Slog.w(TAG, "Failed delivering service starts", e); + caughtException = e; + } catch (Exception e) { + Slog.w(TAG, "Unexpected exception", e); + caughtException = e; + } + + if (caughtException != null) { + // Keep nesting count correct + final boolean inDestroying = mDestroyingServices.contains(r); + for (int i = 0; i < args.size(); i++) { serviceDoneExecutingLocked(r, inDestroying, inDestroying); - if (caughtException instanceof TransactionTooLargeException) { - throw (TransactionTooLargeException)caughtException; - } - break; + } + if (caughtException instanceof TransactionTooLargeException) { + throw (TransactionTooLargeException)caughtException; } } } diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 19fc2b876c3e..be5ff803ed56 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -333,7 +333,6 @@ import android.provider.Settings; import android.service.voice.IVoiceInteractionSession; import android.service.voice.VoiceInteractionManagerInternal; import android.service.voice.VoiceInteractionSession; -import android.service.vr.IPersistentVrStateCallbacks; import android.telecom.TelecomManager; import android.text.TextUtils; import android.text.format.DateUtils; @@ -664,70 +663,8 @@ public class ActivityManagerService extends IActivityManager.Stub // default action automatically. Important for devices without direct input // devices. private boolean mShowDialogs = true; - // VR state flags. - static final int NON_VR_MODE = 0; - static final int VR_MODE = 1; - static final int PERSISTENT_VR_MODE = 2; - private int mVrState = NON_VR_MODE; - private int mTopAppVrThreadTid = 0; - private int mPersistentVrThreadTid = 0; - final IPersistentVrStateCallbacks mPersistentVrModeListener = - new IPersistentVrStateCallbacks.Stub() { - @Override - public void onPersistentVrStateChanged(boolean enabled) { - synchronized(ActivityManagerService.this) { - // There are 4 possible cases here: - // - // Cases for enabled == true - // Invariant: mVrState != PERSISTENT_VR_MODE; - // This is guaranteed as only this function sets mVrState to PERSISTENT_VR_MODE - // Invariant: mPersistentVrThreadTid == 0 - // This is guaranteed by VrManagerService, which only emits callbacks when the - // mode changes, and in setPersistentVrThread, which only sets - // mPersistentVrThreadTid when mVrState = PERSISTENT_VR_MODE - // Case 1: mTopAppVrThreadTid > 0 (someone called setVrThread successfully and is - // the top-app) - // We reset the app which currently has SCHED_FIFO (mPersistentVrThreadTid) to - // SCHED_OTHER - // Case 2: mTopAppVrThreadTid == 0 - // Do nothing - // - // Cases for enabled == false - // Invariant: mVrState == PERSISTENT_VR_MODE; - // This is guaranteed by VrManagerService, which only emits callbacks when the - // mode changes, and the only other assignment of mVrState outside of this - // function checks if mVrState != PERSISTENT_VR_MODE - // Invariant: mTopAppVrThreadTid == 0 - // This is guaranteed in that mTopAppVrThreadTid is only set to a tid when - // mVrState is VR_MODE, and is explicitly set to 0 when setPersistentVrThread is - // called - // mPersistentVrThreadTid > 0 (someone called setPersistentVrThread successfully) - // 3. Reset mPersistentVrThreadTidto SCHED_OTHER - // mPersistentVrThreadTid == 0 - // 4. Do nothing - if (enabled) { - mVrState = PERSISTENT_VR_MODE; - } else { - // Leaving persistent mode implies leaving VR mode. - mVrState = NON_VR_MODE; - } - if (mVrState == PERSISTENT_VR_MODE) { - if (mTopAppVrThreadTid > 0) { - // Ensure that when entering persistent VR mode the last top-app loses - // SCHED_FIFO. - setThreadScheduler(mTopAppVrThreadTid, SCHED_OTHER, 0); - mTopAppVrThreadTid = 0; - } - } else if (mPersistentVrThreadTid > 0) { - // Ensure that when leaving persistent VR mode we reschedule the high priority - // persistent thread. - setThreadScheduler(mPersistentVrThreadTid, SCHED_OTHER, 0); - mPersistentVrThreadTid = 0; - } - } - } - }; + private final VrController mVrController; // VR Compatibility Display Id. int mVrCompatibilityDisplayId = INVALID_DISPLAY; @@ -1553,6 +1490,7 @@ public class ActivityManagerService extends IActivityManager.Stub boolean mSupportsSplitScreenMultiWindow; boolean mSupportsFreeformWindowManagement; boolean mSupportsPictureInPicture; + boolean mSupportsMultiDisplay; boolean mSupportsLeanbackOnly; IActivityController mController = null; boolean mControllerIsAMonkey = false; @@ -2447,53 +2385,19 @@ public class ActivityManagerService extends IActivityManager.Stub idleUids(); } break; case VR_MODE_CHANGE_MSG: { - VrManagerInternal vrService = LocalServices.getService(VrManagerInternal.class); - if (vrService == null) { - break; - } - final ActivityRecord r = (ActivityRecord) msg.obj; - boolean vrMode; - boolean inVrMode; - ComponentName requestedPackage; - ComponentName callingPackage; - int userId; - synchronized (ActivityManagerService.this) { - vrMode = r.requestedVrComponent != null; - inVrMode = mVrState != NON_VR_MODE; - requestedPackage = r.requestedVrComponent; - userId = r.userId; - callingPackage = r.info.getComponentName(); - if (vrMode != inVrMode) { - // Don't change state if we're in persistent VR mode, but do update thread - // priorities if necessary. - if (mVrState != PERSISTENT_VR_MODE) { - mVrState = vrMode ? VR_MODE : NON_VR_MODE; - } - mShowDialogs = shouldShowDialogs(getGlobalConfiguration(), vrMode); - if (r.app != null) { - ProcessRecord proc = r.app; - if (proc.vrThreadTid > 0) { - if (proc.curSchedGroup == ProcessList.SCHED_GROUP_TOP_APP) { - try { - if (mVrState == VR_MODE) { - setThreadScheduler(proc.vrThreadTid, - SCHED_FIFO | SCHED_RESET_ON_FORK, 1); - mTopAppVrThreadTid = proc.vrThreadTid; - } else { - setThreadScheduler(proc.vrThreadTid, - SCHED_OTHER, 0); - mTopAppVrThreadTid = 0; - } - } catch (IllegalArgumentException e) { - Slog.w(TAG, "Failed to set scheduling policy, thread does" - + " not exist:\n" + e); - } - } + if (mVrController.onVrModeChanged((ActivityRecord) msg.obj)) { + synchronized (ActivityManagerService.this) { + if (mVrController.shouldDisableNonVrUiLocked()) { + // If we are in a VR mode where Picture-in-Picture mode is unsupported, + // then remove the pinned stack. + final PinnedActivityStack pinnedStack = mStackSupervisor.getStack( + PINNED_STACK_ID); + if (pinnedStack != null) { + mStackSupervisor.removeStackLocked(PINNED_STACK_ID); } } } } - vrService.setVrMode(vrMode, requestedPackage, userId, callingPackage); } break; case NOTIFY_VR_SLEEPING_MSG: { notifyVrManagerOfSleepState(msg.arg1 != 0); @@ -2770,6 +2674,7 @@ public class ActivityManagerService extends IActivityManager.Stub mTaskChangeNotificationController = null; mUiHandler = injector.getUiHandler(null); mUserController = null; + mVrController = null; } // Note: This method is invoked on the main thread but may need to attach various @@ -2856,6 +2761,8 @@ public class ActivityManagerService extends IActivityManager.Stub mUserController = new UserController(this); + mVrController = new VrController(this); + GL_ES_VERSION = SystemProperties.getInt("ro.opengles.version", ConfigurationInfo.GL_ES_VERSION_UNDEFINED); @@ -5026,7 +4933,7 @@ public class ActivityManagerService extends IActivityManager.Stub try { r.forceNewConfig = true; r.ensureActivityConfigurationLocked(0 /* globalChanges */, - false /* preserveWindow */); + true /* preserveWindow */); } finally { Binder.restoreCallingIdentity(origId); } @@ -12143,6 +12050,24 @@ public class ActivityManagerService extends IActivityManager.Stub return false; } + @Override + public void backgroundWhitelistUid(final int uid) { + if (Binder.getCallingUid() != Process.SYSTEM_UID) { + throw new SecurityException("Only the OS may call backgroundWhitelistUid()"); + } + + if (DEBUG_BACKGROUND_CHECK) { + Slog.i(TAG, "Adding uid " + uid + " to bg uid whitelist"); + } + synchronized (this) { + final int N = mBackgroundUidWhitelist.length; + int[] newList = new int[N+1]; + System.arraycopy(mBackgroundUidWhitelist, 0, newList, 0, N); + newList[N] = uid; + mBackgroundUidWhitelist = newList; + } + } + final ProcessRecord addAppLocked(ApplicationInfo info, String customProcess, boolean isolated, String abiOverride) { ProcessRecord app; @@ -13245,23 +13170,12 @@ public class ActivityManagerService extends IActivityManager.Stub @Override public void setVrThread(int tid) { - if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_VR_MODE)) { - throw new UnsupportedOperationException("VR mode not supported on this device!"); - } - + enforceSystemHasVrFeature(); synchronized (this) { - if (tid > 0 && mVrState == PERSISTENT_VR_MODE) { - Slog.e(TAG, "VR thread cannot be set in persistent VR mode!"); - return; - } - ProcessRecord proc; synchronized (mPidsSelfLocked) { final int pid = Binder.getCallingPid(); - proc = mPidsSelfLocked.get(pid); - if (proc != null && mVrState == VR_MODE && tid >= 0) { - proc.vrThreadTid = updateVrThreadLocked(proc, proc.vrThreadTid, pid, tid); - mTopAppVrThreadTid = proc.vrThreadTid; - } + final ProcessRecord proc = mPidsSelfLocked.get(pid); + mVrController.setVrThreadLocked(tid, pid, proc); } } } @@ -13269,72 +13183,71 @@ public class ActivityManagerService extends IActivityManager.Stub @Override public void setPersistentVrThread(int tid) { if (checkCallingPermission(permission.RESTRICTED_VR_ACCESS) != PERMISSION_GRANTED) { - String msg = "Permission Denial: setPersistentVrThread() from pid=" + final String msg = "Permission Denial: setPersistentVrThread() from pid=" + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid() + " requires " + permission.RESTRICTED_VR_ACCESS; Slog.w(TAG, msg); throw new SecurityException(msg); } - if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_VR_MODE)) { - throw new UnsupportedOperationException("VR mode not supported on this device!"); - } - + enforceSystemHasVrFeature(); synchronized (this) { - // Disable any existing VR thread. - if (mTopAppVrThreadTid > 0) { - setThreadScheduler(mTopAppVrThreadTid, SCHED_OTHER, 0); - mTopAppVrThreadTid = 0; - } - - if (tid > 0 && mVrState != PERSISTENT_VR_MODE) { - Slog.e(TAG, "Persistent VR thread may only be set in persistent VR mode!"); - return; - } - ProcessRecord proc; synchronized (mPidsSelfLocked) { final int pid = Binder.getCallingPid(); - mPersistentVrThreadTid = - updateVrThreadLocked(null, mPersistentVrThreadTid, pid, tid); + final ProcessRecord proc = mPidsSelfLocked.get(pid); + mVrController.setPersistentVrThreadLocked(tid, pid, proc); } } } /** - * Used by setVrThread and setPersistentVrThread to update a thread's priority. When proc is - * non-null it must be in SCHED_GROUP_TOP_APP. When it is null, the tid is unconditionally - * rescheduled. + * Schedule the given thread a normal scheduling priority. + * + * @param newTid the tid of the thread to adjust the scheduling of. + * @param suppressLogs {@code true} if any error logging should be disabled. + * + * @return {@code true} if this succeeded. */ - private int updateVrThreadLocked(ProcessRecord proc, int lastTid, int pid, int tid) { - // ensure the tid belongs to the process - if (!isThreadInProcess(pid, tid)) { - throw new IllegalArgumentException("VR thread does not belong to process"); - } - - // reset existing VR thread to CFS if this thread still exists and belongs to - // the calling process - if (lastTid != 0 && isThreadInProcess(pid, lastTid)) { - try { - setThreadScheduler(lastTid, SCHED_OTHER, 0); - } catch (IllegalArgumentException e) { - // Ignore this. Only occurs in race condition where previous VR thread - // was destroyed during this method call. + static boolean scheduleAsRegularPriority(int tid, boolean suppressLogs) { + try { + Process.setThreadScheduler(tid, Process.SCHED_OTHER, 0); + return true; + } catch (IllegalArgumentException e) { + if (!suppressLogs) { + Slog.w(TAG, "Failed to set scheduling policy, thread does not exist:\n" + e); } } + return false; + } - // promote to FIFO now if the tid is non-zero + /** + * Schedule the given thread an FIFO scheduling priority. + * + * @param newTid the tid of the thread to adjust the scheduling of. + * @param suppressLogs {@code true} if any error logging should be disabled. + * + * @return {@code true} if this succeeded. + */ + static boolean scheduleAsFifoPriority(int tid, boolean suppressLogs) { try { - if ((proc == null || proc.curSchedGroup == ProcessList.SCHED_GROUP_TOP_APP) - && tid > 0) { - setThreadScheduler(tid, - SCHED_FIFO | SCHED_RESET_ON_FORK, 1); - } - return tid; + Process.setThreadScheduler(tid, Process.SCHED_FIFO | Process.SCHED_RESET_ON_FORK, 1); + return true; } catch (IllegalArgumentException e) { - Slog.e(TAG, "Failed to set scheduling policy, thread does" - + " not exist:\n" + e); + if (!suppressLogs) { + Slog.w(TAG, "Failed to set scheduling policy, thread does not exist:\n" + e); + } + } + return false; + } + + /** + * Check that we have the features required for VR-related API calls, and throw an exception if + * not. + */ + private void enforceSystemHasVrFeature() { + if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_VR_MODE)) { + throw new UnsupportedOperationException("VR mode not supported on this device!"); } - return lastTid; } @Override @@ -13429,6 +13342,13 @@ public class ActivityManagerService extends IActivityManager.Stub } } + /** + * @return whether the system should disable UI modes incompatible with VR mode. + */ + boolean shouldDisableNonVrUiLocked() { + return mVrController.shouldDisableNonVrUiLocked(); + } + @Override public boolean isTopOfTask(IBinder token) { synchronized (this) { @@ -13908,6 +13828,8 @@ public class ActivityManagerService extends IActivityManager.Stub com.android.internal.R.fraction.thumbnail_fullscreen_scale, 1, 1); } mWaitForNetworkTimeoutMs = waitForNetworkTimeoutMs; + mSupportsMultiDisplay = res.getBoolean( + com.android.internal.R.bool.config_supportsMultiDisplay); } } @@ -13926,10 +13848,7 @@ public class ActivityManagerService extends IActivityManager.Stub mLocalDeviceIdleController = LocalServices.getService(DeviceIdleController.LocalService.class); mAssistUtils = new AssistUtils(mContext); - VrManagerInternal vrManagerInternal = LocalServices.getService(VrManagerInternal.class); - if (vrManagerInternal != null) { - vrManagerInternal.addPersistentVrModeStateListener(mPersistentVrModeListener); - } + mVrController.onSystemReady(); // Make sure we have the current profile info, since it is needed for security checks. mUserController.onSystemReady(); mRecentTasks.onSystemReadyLocked(); @@ -15611,6 +15530,7 @@ public class ActivityManagerService extends IActivityManager.Stub pw.println(" mVoiceWakeLock" + mVoiceWakeLock); } } + pw.println(" mVrController=" + mVrController); if (mDebugApp != null || mOrigDebugApp != null || mDebugTransient || mOrigWaitForDebugger) { if (dumpPackage == null || dumpPackage.equals(mDebugApp) @@ -20037,7 +19957,7 @@ public class ActivityManagerService extends IActivityManager.Stub mUserController.getCurrentUserIdLocked()); // TODO: If our config changes, should we auto dismiss any currently showing dialogs? - mShowDialogs = shouldShowDialogs(mTempConfig, mVrState != NON_VR_MODE); + mShowDialogs = shouldShowDialogs(mTempConfig); AttributeCache ac = AttributeCache.instance(); if (ac != null) { @@ -20267,15 +20187,16 @@ public class ActivityManagerService extends IActivityManager.Stub * A thought: SystemUI might also want to get told about this, the Power * dialog / global actions also might want different behaviors. */ - private static boolean shouldShowDialogs(Configuration config, boolean inVrMode) { + private static boolean shouldShowDialogs(Configuration config) { final boolean inputMethodExists = !(config.keyboard == Configuration.KEYBOARD_NOKEYS && config.touchscreen == Configuration.TOUCHSCREEN_NOTOUCH && config.navigation == Configuration.NAVIGATION_NONAV); int modeType = config.uiMode & Configuration.UI_MODE_TYPE_MASK; final boolean uiModeSupportsDialogs = (modeType != Configuration.UI_MODE_TYPE_CAR && !(modeType == Configuration.UI_MODE_TYPE_WATCH && "user".equals(Build.TYPE)) - && modeType != Configuration.UI_MODE_TYPE_TELEVISION); - return inputMethodExists && uiModeSupportsDialogs && !inVrMode; + && modeType != Configuration.UI_MODE_TYPE_TELEVISION + && modeType != Configuration.UI_MODE_TYPE_VR_HEADSET); + return inputMethodExists && uiModeSupportsDialogs; } @Override @@ -21578,32 +21499,14 @@ public class ActivityManagerService extends IActivityManager.Stub if (app.curSchedGroup == ProcessList.SCHED_GROUP_TOP_APP) { // do nothing if we already switched to RT if (oldSchedGroup != ProcessList.SCHED_GROUP_TOP_APP) { - // Switch VR thread for app to SCHED_FIFO - if (mVrState == VR_MODE && app.vrThreadTid != 0) { - try { - setThreadScheduler(app.vrThreadTid, - SCHED_FIFO | SCHED_RESET_ON_FORK, 1); - mTopAppVrThreadTid = app.vrThreadTid; - } catch (IllegalArgumentException e) { - // thread died, ignore - } - } + mVrController.onTopProcChangedLocked(app); if (mUseFifoUiScheduling) { // Switch UI pipeline for app to SCHED_FIFO - app.savedPriority = getThreadPriority(app.pid); - try { - setThreadScheduler(app.pid, - SCHED_FIFO | SCHED_RESET_ON_FORK, 1); - } catch (IllegalArgumentException e) { - // thread died, ignore - } + app.savedPriority = Process.getThreadPriority(app.pid); + scheduleAsFifoPriority(app.pid, /* suppressLogs */true); if (app.renderThreadTid != 0) { - try { - setThreadScheduler(app.renderThreadTid, - SCHED_FIFO | SCHED_RESET_ON_FORK, 1); - } catch (IllegalArgumentException e) { - // thread died, ignore - } + scheduleAsFifoPriority(app.renderThreadTid, + /* suppressLogs */true); if (DEBUG_OOM_ADJ) { Slog.d("UI_FIFO", "Set RenderThread (TID " + app.renderThreadTid + ") to FIFO"); @@ -21627,12 +21530,7 @@ public class ActivityManagerService extends IActivityManager.Stub } } else if (oldSchedGroup == ProcessList.SCHED_GROUP_TOP_APP && app.curSchedGroup != ProcessList.SCHED_GROUP_TOP_APP) { - // Reset VR thread to SCHED_OTHER - // Safe to do even if we're not in VR mode - if (app.vrThreadTid != 0) { - setThreadScheduler(app.vrThreadTid, SCHED_OTHER, 0); - mTopAppVrThreadTid = 0; - } + mVrController.onTopProcChangedLocked(app); if (mUseFifoUiScheduling) { // Reset UI pipeline to SCHED_OTHER setThreadScheduler(app.pid, SCHED_OTHER, 0); diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java index 748aa6ff256a..601141827f3c 100644 --- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java +++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java @@ -247,6 +247,8 @@ final class ActivityManagerShellCommand extends ShellCommand { return runSupportsMultiwindow(pw); case "supports-split-screen-multi-window": return runSupportsSplitScreenMultiwindow(pw); + case "supports-multi-display": + return runSupportsMultiDisplay(pw); case "update-appinfo": return runUpdateApplicationInfo(pw); case "no-home-screen": @@ -2398,6 +2400,15 @@ final class ActivityManagerShellCommand extends ShellCommand { return 0; } + int runSupportsMultiDisplay(PrintWriter pw) throws RemoteException { + final Resources res = getResources(pw); + if (res == null) { + return -1; + } + pw.println(res.getBoolean(com.android.internal.R.bool.config_supportsMultiDisplay)); + return 0; + } + int runUpdateApplicationInfo(PrintWriter pw) throws RemoteException { int userid = UserHandle.parseUserArg(getNextArgRequired()); ArrayList<String> packages = new ArrayList<>(); @@ -2627,6 +2638,8 @@ final class ActivityManagerShellCommand extends ShellCommand { pw.println(" Returns true if the device supports multiwindow."); pw.println(" supports-split-screen-multi-window"); pw.println(" Returns true if the device supports split screen multiwindow."); + pw.println(" supports-multi-display"); + pw.println(" Returns true if the device supports multi-display."); pw.println(" suppress-resize-config-changes <true|false>"); pw.println(" Suppresses configuration changes due to user resizing an activity/task."); pw.println(" set-inactive [--user <USER_ID>] <PACKAGE> true|false"); diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java index 3a2941467cb6..2b953adfde18 100644 --- a/services/core/java/com/android/server/am/ActivityRecord.java +++ b/services/core/java/com/android/server/am/ActivityRecord.java @@ -505,7 +505,8 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo else TimeUtils.formatDuration(startTime, now, pw); pw.println(); } - final boolean waitingVisible = mStackSupervisor.mWaitingVisibleActivities.contains(this); + final boolean waitingVisible = + mStackSupervisor.mActivitiesWaitingForVisibleActivity.contains(this); if (lastVisibleTime != 0 || waitingVisible || nowVisible) { pw.print(prefix); pw.print("waitingVisible="); pw.print(waitingVisible); pw.print(" nowVisible="); pw.print(nowVisible); @@ -1164,6 +1165,11 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo return false; } + // Check to see if we are in VR mode, and disallow PiP if so + if (service.shouldDisableNonVrUiLocked()) { + return false; + } + boolean isCurrentAppLocked = mStackSupervisor.getLockTaskModeState() != LOCK_TASK_MODE_NONE; boolean isKeyguardLocked = service.isKeyguardLocked(); boolean hasPinnedStack = mStackSupervisor.getStack(PINNED_STACK_ID) != null; @@ -1907,13 +1913,14 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo // If this activity was already idle, then we now need to make sure we perform // the full stop of any activities that are waiting to do so. This is because // we won't do that while they are still waiting for this one to become visible. - final int size = mStackSupervisor.mWaitingVisibleActivities.size(); + final int size = mStackSupervisor.mActivitiesWaitingForVisibleActivity.size(); if (size > 0) { for (int i = 0; i < size; i++) { - ActivityRecord r = mStackSupervisor.mWaitingVisibleActivities.get(i); + final ActivityRecord r = + mStackSupervisor.mActivitiesWaitingForVisibleActivity.get(i); if (DEBUG_SWITCH) Log.v(TAG_SWITCH, "Was waiting for visible: " + r); } - mStackSupervisor.mWaitingVisibleActivities.clear(); + mStackSupervisor.mActivitiesWaitingForVisibleActivity.clear(); mStackSupervisor.scheduleIdleLocked(); } } @@ -1954,7 +1961,7 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo // First find the real culprit... if this activity is waiting for // another activity to start or has stopped, then the key dispatching // timeout should not be caused by this. - if (mStackSupervisor.mWaitingVisibleActivities.contains(this) || stopped) { + if (mStackSupervisor.mActivitiesWaitingForVisibleActivity.contains(this) || stopped) { final ActivityStack stack = mStackSupervisor.getFocusedStack(); // Try to use the one which is closest to top. ActivityRecord r = stack.mResumedActivity; diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java index f13b11e65a88..ceddcacdd9a9 100644 --- a/services/core/java/com/android/server/am/ActivityStack.java +++ b/services/core/java/com/android/server/am/ActivityStack.java @@ -116,7 +116,6 @@ import android.util.Slog; import android.util.SparseArray; import android.view.Display; -import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.IVoiceInteractor; import com.android.internal.os.BatteryStatsImpl; import com.android.server.Watchdog; @@ -1349,7 +1348,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai } else if (prev.app != null) { if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Enqueue pending stop if needed: " + prev + " wasStopping=" + wasStopping + " visible=" + prev.visible); - if (mStackSupervisor.mWaitingVisibleActivities.remove(prev)) { + if (mStackSupervisor.mActivitiesWaitingForVisibleActivity.remove(prev)) { if (DEBUG_SWITCH || DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Complete pause, no longer waiting: " + prev); } @@ -2262,7 +2261,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai mStackSupervisor.mStoppingActivities.remove(next); mStackSupervisor.mGoingToSleepActivities.remove(next); next.sleeping = false; - mStackSupervisor.mWaitingVisibleActivities.remove(next); + mStackSupervisor.mActivitiesWaitingForVisibleActivity.remove(next); if (DEBUG_SWITCH) Slog.v(TAG_SWITCH, "Resuming " + next); @@ -2324,9 +2323,9 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai } if (prev != null && prev != next) { - if (!mStackSupervisor.mWaitingVisibleActivities.contains(prev) + if (!mStackSupervisor.mActivitiesWaitingForVisibleActivity.contains(prev) && next != null && !next.nowVisible) { - mStackSupervisor.mWaitingVisibleActivities.add(prev); + mStackSupervisor.mActivitiesWaitingForVisibleActivity.add(prev); if (DEBUG_SWITCH) Slog.v(TAG_SWITCH, "Resuming top, waiting visible to hide: " + prev); } else { @@ -2342,13 +2341,13 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai prev.setVisibility(false); if (DEBUG_SWITCH) Slog.v(TAG_SWITCH, "Not waiting for visible to hide: " + prev + ", waitingVisible=" - + mStackSupervisor.mWaitingVisibleActivities.contains(prev) + + mStackSupervisor.mActivitiesWaitingForVisibleActivity.contains(prev) + ", nowVisible=" + next.nowVisible); } else { if (DEBUG_SWITCH) Slog.v(TAG_SWITCH, "Previous already visible but still waiting to hide: " + prev + ", waitingVisible=" - + mStackSupervisor.mWaitingVisibleActivities.contains(prev) + + mStackSupervisor.mActivitiesWaitingForVisibleActivity.contains(prev) + ", nowVisible=" + next.nowVisible); } } @@ -3617,8 +3616,8 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai mWindowManager.prepareAppTransition(transit, false); r.setVisibility(false); mWindowManager.executeAppTransition(); - if (!mStackSupervisor.mWaitingVisibleActivities.contains(r)) { - mStackSupervisor.mWaitingVisibleActivities.add(r); + if (!mStackSupervisor.mActivitiesWaitingForVisibleActivity.contains(r)) { + mStackSupervisor.mActivitiesWaitingForVisibleActivity.add(r); } } @@ -3650,7 +3649,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai // make sure the record is cleaned out of other places. mStackSupervisor.mStoppingActivities.remove(r); mStackSupervisor.mGoingToSleepActivities.remove(r); - mStackSupervisor.mWaitingVisibleActivities.remove(r); + mStackSupervisor.mActivitiesWaitingForVisibleActivity.remove(r); if (mResumedActivity == r) { mResumedActivity = null; } @@ -3870,11 +3869,9 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai r.app = null; } - // Make sure this record is no longer in the pending finishes list. - // This could happen, for example, if we are trimming activities - // down to the max limit while they are still waiting to finish. - mStackSupervisor.mFinishingActivities.remove(r); - mStackSupervisor.mWaitingVisibleActivities.remove(r); + // Inform supervisor the activity has been removed. + mStackSupervisor.cleanupActivity(r); + // Remove any pending results. if (r.finishing && r.pendingResults != null) { @@ -4253,8 +4250,8 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai "mStoppingActivities"); removeHistoryRecordsForAppLocked(mStackSupervisor.mGoingToSleepActivities, app, "mGoingToSleepActivities"); - removeHistoryRecordsForAppLocked(mStackSupervisor.mWaitingVisibleActivities, app, - "mWaitingVisibleActivities"); + removeHistoryRecordsForAppLocked(mStackSupervisor.mActivitiesWaitingForVisibleActivity, app, + "mActivitiesWaitingForVisibleActivity"); removeHistoryRecordsForAppLocked(mStackSupervisor.mFinishingActivities, app, "mFinishingActivities"); diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java index 3e3fee54bdd0..baa7cf467a0c 100644 --- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java @@ -153,6 +153,7 @@ import android.provider.MediaStore; import android.provider.Settings; import android.provider.Settings.SettingNotFoundException; import android.service.voice.IVoiceInteractionSession; +import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; import android.util.EventLog; @@ -164,7 +165,6 @@ import android.view.Display; import android.view.InputEvent; import android.view.Surface; -import com.android.internal.annotations.VisibleForTesting; import com.android.internal.content.ReferrerIntent; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; @@ -174,7 +174,6 @@ import com.android.internal.util.ArrayUtils; import com.android.internal.widget.LockPatternUtils; import com.android.server.LocalServices; import com.android.server.am.ActivityStack.ActivityState; -import com.android.server.wm.StackWindowController; import com.android.server.wm.WindowManagerService; import java.io.FileDescriptor; @@ -346,10 +345,12 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D /** List of activities that are waiting for a new activity to become visible before completing * whatever operation they are supposed to do. */ - final ArrayList<ActivityRecord> mWaitingVisibleActivities = new ArrayList<>(); + // TODO: Remove mActivitiesWaitingForVisibleActivity list and just remove activity from + // mStoppingActivities when something else comes up. + final ArrayList<ActivityRecord> mActivitiesWaitingForVisibleActivity = new ArrayList<>(); - /** List of processes waiting to find out about the next visible activity. */ - final ArrayList<WaitResult> mWaitingActivityVisible = new ArrayList<>(); + /** List of processes waiting to find out when a specific activity becomes visible. */ + private final ArrayList<WaitInfo> mWaitingForActivityVisible = new ArrayList<>(); /** List of processes waiting to find out about the next launched activity. */ final ArrayList<WaitResult> mWaitingActivityLaunched = new ArrayList<>(); @@ -1003,7 +1004,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D final ActivityStack stack = stacks.get(stackNdx); final ActivityRecord r = stack.mResumedActivity; if (r != null) { - if (!r.nowVisible || mWaitingVisibleActivities.contains(r)) { + if (!r.nowVisible || mActivitiesWaitingForVisibleActivity.contains(r)) { return false; } foundResumed = true; @@ -1083,22 +1084,41 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D } } + void waitActivityVisible(ComponentName name, WaitResult result) { + final WaitInfo waitInfo = new WaitInfo(name, result); + mWaitingForActivityVisible.add(waitInfo); + } + + void cleanupActivity(ActivityRecord r) { + // Make sure this record is no longer in the pending finishes list. + // This could happen, for example, if we are trimming activities + // down to the max limit while they are still waiting to finish. + mFinishingActivities.remove(r); + mActivitiesWaitingForVisibleActivity.remove(r); + + for (int i = mWaitingForActivityVisible.size() - 1; i >= 0; --i) { + if (mWaitingForActivityVisible.get(i).matches(r)) { + mWaitingForActivityVisible.remove(i); + } + } + } + void reportActivityVisibleLocked(ActivityRecord r) { sendWaitingVisibleReportLocked(r); } void sendWaitingVisibleReportLocked(ActivityRecord r) { boolean changed = false; - for (int i = mWaitingActivityVisible.size()-1; i >= 0; i--) { - WaitResult w = mWaitingActivityVisible.get(i); - if (w.who == null) { + for (int i = mWaitingForActivityVisible.size() - 1; i >= 0; --i) { + final WaitInfo w = mWaitingForActivityVisible.get(i); + if (w.matches(r)) { + final WaitResult result = w.getResult(); changed = true; - w.timeout = false; - if (r != null) { - w.who = new ComponentName(r.info.packageName, r.info.name); - } - w.totalTime = SystemClock.uptimeMillis() - w.thisTime; - w.thisTime = w.totalTime; + result.timeout = false; + result.who = w.getComponent(); + result.totalTime = SystemClock.uptimeMillis() - result.thisTime; + result.thisTime = result.totalTime; + mWaitingForActivityVisible.remove(w); } } if (changed) { @@ -2782,6 +2802,15 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D + " reparent task=" + task + " to stackId=" + stackId); } + // Ensure that we're not moving a task to a dynamic stack if device doesn't support + // multi-display. + // TODO(multi-display): Support non-dynamic stacks on secondary displays. + // TODO: Check ActivityView after fixing b/35349678. + if (StackId.isDynamicStack(stackId) && !mService.mSupportsMultiDisplay) { + throw new IllegalArgumentException("Device doesn't support multi-display, can not" + + " reparent task=" + task + " to stackId=" + stackId); + } + // Ensure that we aren't trying to move into a freeform stack without freeform // support if (stackId == FREEFORM_WORKSPACE_STACK_ID && !mService.mSupportsFreeformWindowManagement) { @@ -3412,13 +3441,11 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D final boolean nowVisible = allResumedActivitiesVisible(); for (int activityNdx = mStoppingActivities.size() - 1; activityNdx >= 0; --activityNdx) { ActivityRecord s = mStoppingActivities.get(activityNdx); - // TODO: Remove mWaitingVisibleActivities list and just remove activity from - // mStoppingActivities when something else comes up. - boolean waitingVisible = mWaitingVisibleActivities.contains(s); + boolean waitingVisible = mActivitiesWaitingForVisibleActivity.contains(s); if (DEBUG_STATES) Slog.v(TAG, "Stopping " + s + ": nowVisible=" + nowVisible + " waitingVisible=" + waitingVisible + " finishing=" + s.finishing); if (waitingVisible && nowVisible) { - mWaitingVisibleActivities.remove(s); + mActivitiesWaitingForVisibleActivity.remove(s); waitingVisible = false; if (s.finishing) { // If this activity is finishing, it is sitting on top of @@ -3511,6 +3538,13 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D pw.print(":"); pw.println(Arrays.toString(packages.valueAt(i))); } } + if (!mWaitingForActivityVisible.isEmpty()) { + pw.print(prefix); pw.println("mWaitingForActivityVisible="); + for (int i = 0; i < mWaitingForActivityVisible.size(); ++i) { + pw.print(prefix); pw.print(prefix); mWaitingForActivityVisible.get(i).dump(pw, prefix); + } + } + pw.println(" mLockTaskModeTasks" + mLockTaskModeTasks); mKeyguardController.dump(pw, prefix); } @@ -3633,9 +3667,9 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D false, dumpPackage, true, " Activities waiting to finish:", null); printed |= dumpHistoryList(fd, pw, mStoppingActivities, " ", "Stop", false, !dumpAll, false, dumpPackage, true, " Activities waiting to stop:", null); - printed |= dumpHistoryList(fd, pw, mWaitingVisibleActivities, " ", "Wait", false, !dumpAll, - false, dumpPackage, true, " Activities waiting for another to become visible:", - null); + printed |= dumpHistoryList(fd, pw, mActivitiesWaitingForVisibleActivity, " ", "Wait", + false, !dumpAll, false, dumpPackage, true, + " Activities waiting for another to become visible:", null); printed |= dumpHistoryList(fd, pw, mGoingToSleepActivities, " ", "Sleep", false, !dumpAll, false, dumpPackage, true, " Activities waiting to sleep:", null); printed |= dumpHistoryList(fd, pw, mGoingToSleepActivities, " ", "Sleep", false, !dumpAll, @@ -4986,4 +5020,38 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D } return topActivityTokens; } + + /** + * Internal container to store a match qualifier alongside a WaitResult. + */ + static class WaitInfo { + private final ComponentName mTargetComponent; + private final WaitResult mResult; + + public WaitInfo(ComponentName targetComponent, WaitResult result) { + this.mTargetComponent = targetComponent; + this.mResult = result; + } + + public boolean matches(ActivityRecord record) { + return mTargetComponent == null || + (TextUtils.equals(mTargetComponent.getPackageName(), record.info.packageName) + && TextUtils.equals(mTargetComponent.getClassName(), record.info.name)); + } + + public WaitResult getResult() { + return mResult; + } + + public ComponentName getComponent() { + return mTargetComponent; + } + + public void dump(PrintWriter pw, String prefix) { + pw.println(prefix + "WaitInfo:"); + pw.println(prefix + " mTargetComponent=" + mTargetComponent); + pw.println(prefix + " mResult="); + mResult.dump(pw, prefix); + } + } } diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java index b4085697f2da..dcd293a340df 100644 --- a/services/core/java/com/android/server/am/ActivityStarter.java +++ b/services/core/java/com/android/server/am/ActivityStarter.java @@ -118,14 +118,11 @@ import android.os.UserManager; import android.service.voice.IVoiceInteractionSession; import android.util.EventLog; import android.util.Slog; -import android.view.Display; import com.android.internal.app.HeavyWeightSwitcherActivity; import com.android.internal.app.IVoiceInteractor; -import com.android.server.LocalServices; import com.android.server.am.ActivityStackSupervisor.PendingActivityLaunch; import com.android.server.pm.InstantAppResolver; -import com.android.server.vr.VrManagerInternal; import com.android.server.wm.WindowManagerService; import java.util.ArrayList; @@ -152,7 +149,6 @@ class ActivityStarter { // Share state variable among methods when starting an activity. private ActivityRecord mStartActivity; - private ActivityRecord mReusedActivity; private Intent mIntent; private int mCallingUid; private ActivityOptions mOptions; @@ -520,7 +516,7 @@ class ActivityStarter { doPendingActivityLaunchesLocked(false); return startActivity(r, sourceRecord, voiceSession, voiceInteractor, startFlags, true, - options, inTask); + options, inTask, outActivity); } /** @@ -821,15 +817,19 @@ class ActivityStarter { } } if (res == START_TASK_TO_FRONT) { - ActivityRecord r = stack.topRunningActivityLocked(); + final ActivityRecord r = outRecord[0]; + + // ActivityRecord may represent a different activity, but it should not be in + // the resumed state. if (r.nowVisible && r.state == RESUMED) { outResult.timeout = false; - outResult.who = new ComponentName(r.info.packageName, r.info.name); + outResult.who = r.realActivity; outResult.totalTime = 0; outResult.thisTime = 0; } else { outResult.thisTime = SystemClock.uptimeMillis(); - mSupervisor.mWaitingActivityVisible.add(outResult); + mSupervisor.waitActivityVisible(r.realActivity, outResult); + // Note: the timeout variable is not currently not ever set. do { try { mService.wait(); @@ -840,9 +840,7 @@ class ActivityStarter { } } - final ActivityRecord launchedActivity = mReusedActivity != null - ? mReusedActivity : outRecord[0]; - mSupervisor.mActivityMetricsLogger.notifyActivityLaunched(res, launchedActivity); + mSupervisor.mActivityMetricsLogger.notifyActivityLaunched(res, outRecord[0]); return res; } } @@ -954,12 +952,13 @@ class ActivityStarter { private int startActivity(final ActivityRecord r, ActivityRecord sourceRecord, IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor, - int startFlags, boolean doResume, ActivityOptions options, TaskRecord inTask) { + int startFlags, boolean doResume, ActivityOptions options, TaskRecord inTask, + ActivityRecord[] outActivity) { int result = START_CANCELED; try { mService.mWindowManager.deferSurfaceLayout(); result = startActivityUnchecked(r, sourceRecord, voiceSession, voiceInteractor, - startFlags, doResume, options, inTask); + startFlags, doResume, options, inTask, outActivity); } finally { // If we are not able to proceed, disassociate the activity from the task. Leaving an // activity in an incomplete state can lead to issues, such as performing operations @@ -979,7 +978,8 @@ class ActivityStarter { // Note: This method should only be called from {@link startActivity}. private int startActivityUnchecked(final ActivityRecord r, ActivityRecord sourceRecord, IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor, - int startFlags, boolean doResume, ActivityOptions options, TaskRecord inTask) { + int startFlags, boolean doResume, ActivityOptions options, TaskRecord inTask, + ActivityRecord[] outActivity) { setInitialState(r, options, inTask, doResume, startFlags, sourceRecord, voiceSession, voiceInteractor); @@ -990,16 +990,16 @@ class ActivityStarter { mIntent.setFlags(mLaunchFlags); - mReusedActivity = getReusableIntentActivity(); + ActivityRecord reusedActivity = getReusableIntentActivity(); final int preferredLaunchStackId = (mOptions != null) ? mOptions.getLaunchStackId() : INVALID_STACK_ID; - if (mReusedActivity != null) { + if (reusedActivity != null) { // When the flags NEW_TASK and CLEAR_TASK are set, then the task gets reused but // still needs to be a lock task mode violation since the task gets cleared out and // the device would otherwise leave the locked task. - if (mSupervisor.isLockTaskModeViolation(mReusedActivity.getTask(), + if (mSupervisor.isLockTaskModeViolation(reusedActivity.getTask(), (mLaunchFlags & (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK)) == (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK))) { mSupervisor.showLockTaskToast(); @@ -1008,12 +1008,12 @@ class ActivityStarter { } if (mStartActivity.getTask() == null) { - mStartActivity.setTask(mReusedActivity.getTask()); + mStartActivity.setTask(reusedActivity.getTask()); } - if (mReusedActivity.getTask().intent == null) { + if (reusedActivity.getTask().intent == null) { // This task was started because of movement of the activity based on affinity... // Now that we are actually launching it, we can assign the base intent. - mReusedActivity.getTask().setIntent(mStartActivity); + reusedActivity.getTask().setIntent(mStartActivity); } // This code path leads to delivering a new intent, we want to make sure we schedule it @@ -1022,7 +1022,7 @@ class ActivityStarter { if ((mLaunchFlags & FLAG_ACTIVITY_CLEAR_TOP) != 0 || isDocumentLaunchesIntoExisting(mLaunchFlags) || mLaunchSingleInstance || mLaunchSingleTask) { - final TaskRecord task = mReusedActivity.getTask(); + final TaskRecord task = reusedActivity.getTask(); // In this situation we want to remove all activities from the task up to the one // being started. In most cases this means we are resetting the task to its initial @@ -1030,12 +1030,12 @@ class ActivityStarter { final ActivityRecord top = task.performClearTaskForReuseLocked(mStartActivity, mLaunchFlags); - // The above code can remove {@code mReusedActivity} from the task, leading to the + // The above code can remove {@code reusedActivity} from the task, leading to the // the {@code ActivityRecord} removing its reference to the {@code TaskRecord}. The // task reference is needed in the call below to // {@link setTargetStackAndMoveToFrontIfNeeded}. - if (mReusedActivity.getTask() == null) { - mReusedActivity.setTask(task); + if (reusedActivity.getTask() == null) { + reusedActivity.setTask(task); } if (top != null) { @@ -1052,7 +1052,10 @@ class ActivityStarter { sendPowerHintForLaunchStartIfNeeded(false /* forceSend */); - mReusedActivity = setTargetStackAndMoveToFrontIfNeeded(mReusedActivity); + reusedActivity = setTargetStackAndMoveToFrontIfNeeded(reusedActivity); + if (outActivity.length > 0) { + outActivity[0] = reusedActivity; + } if ((mStartFlags & START_FLAG_ONLY_IF_NEEDED) != 0) { // We don't need to start a new activity, and the client said not to do anything @@ -1061,7 +1064,7 @@ class ActivityStarter { resumeTargetStackIfNeeded(); return START_RETURN_INTENT_TO_CALLER; } - setTaskFromIntentActivity(mReusedActivity); + setTaskFromIntentActivity(reusedActivity); if (!mAddingToTask && mReuseTask == null) { // We didn't do anything... but it was needed (a.k.a., client don't use that @@ -1963,7 +1966,7 @@ class ActivityStarter { final boolean resume = doResume && mPendingActivityLaunches.isEmpty(); try { startActivity(pal.r, pal.sourceRecord, null, null, pal.startFlags, resume, null, - null); + null, null /*outRecords*/); } catch (Exception e) { Slog.e(TAG, "Exception during pending activity launch pal=" + pal, e); pal.sendErrorResult(e.getMessage()); @@ -2178,7 +2181,8 @@ class ActivityStarter { case ASSISTANT_STACK_ID: return r.isAssistantActivity(); default: - if (StackId.isDynamicStack(stackId)) { + // TODO: Check ActivityView after fixing b/35349678. + if (StackId.isDynamicStack(stackId) && mService.mSupportsMultiDisplay) { return true; } Slog.e(TAG, "isValidLaunchStackId: Unexpected stackId=" + stackId); diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java index e5b2ecad2842..983c97510687 100644 --- a/services/core/java/com/android/server/am/BatteryStatsService.java +++ b/services/core/java/com/android/server/am/BatteryStatsService.java @@ -1021,32 +1021,24 @@ public final class BatteryStatsService extends IBatteryStats.Stub mHandler.post(new Runnable() { @Override public void run() { - Slog.d(TAG, "begin setBatteryStateLocked"); - try { - synchronized (mStats) { - final boolean onBattery = plugType == BatteryStatsImpl.BATTERY_PLUGGED_NONE; - if (mStats.isOnBattery() == onBattery) { - // The battery state has not changed, so we don't need to sync external - // stats immediately. - mStats.setBatteryStateLocked(status, health, plugType, level, temp, - volt, - chargeUAh, chargeFullUAh); - return; - } + synchronized (mStats) { + final boolean onBattery = plugType == BatteryStatsImpl.BATTERY_PLUGGED_NONE; + if (mStats.isOnBattery() == onBattery) { + // The battery state has not changed, so we don't need to sync external + // stats immediately. + mStats.setBatteryStateLocked(status, health, plugType, level, temp, volt, + chargeUAh, chargeFullUAh); + return; } - } finally { - Slog.d(TAG, "end setBatteryStateLocked"); } // Sync external stats first as the battery has changed states. If we don't sync // immediately here, we may not collect the relevant data later. updateExternalStatsSync("battery-state", BatteryStatsImpl.ExternalStatsSync.UPDATE_ALL); - Slog.d(TAG, "begin setBatteryStateLocked"); synchronized (mStats) { mStats.setBatteryStateLocked(status, health, plugType, level, temp, volt, chargeUAh, chargeFullUAh); } - Slog.d(TAG, "end setBatteryStateLocked"); } }); } diff --git a/services/core/java/com/android/server/am/CoreSettingsObserver.java b/services/core/java/com/android/server/am/CoreSettingsObserver.java index 73a17c613c4d..160c753d9540 100644 --- a/services/core/java/com/android/server/am/CoreSettingsObserver.java +++ b/services/core/java/com/android/server/am/CoreSettingsObserver.java @@ -21,6 +21,9 @@ import android.database.ContentObserver; import android.net.Uri; import android.os.Bundle; import android.provider.Settings; + +import com.android.internal.annotations.VisibleForTesting; + import java.util.HashMap; import java.util.Map; @@ -34,11 +37,14 @@ final class CoreSettingsObserver extends ContentObserver { private static final String LOG_TAG = CoreSettingsObserver.class.getSimpleName(); // mapping form property name to its type - private static final Map<String, Class<?>> sSecureSettingToTypeMap = new HashMap< + @VisibleForTesting + static final Map<String, Class<?>> sSecureSettingToTypeMap = new HashMap< String, Class<?>>(); - private static final Map<String, Class<?>> sSystemSettingToTypeMap = new HashMap< + @VisibleForTesting + static final Map<String, Class<?>> sSystemSettingToTypeMap = new HashMap< String, Class<?>>(); - private static final Map<String, Class<?>> sGlobalSettingToTypeMap = new HashMap< + @VisibleForTesting + static final Map<String, Class<?>> sGlobalSettingToTypeMap = new HashMap< String, Class<?>>(); static { sSecureSettingToTypeMap.put(Settings.Secure.LONG_PRESS_TIMEOUT, int.class); @@ -101,51 +107,31 @@ final class CoreSettingsObserver extends ContentObserver { } } - private void populateSettings(Bundle snapshot, Map<String, Class<?>> map) { + @VisibleForTesting + void populateSettings(Bundle snapshot, Map<String, Class<?>> map) { Context context = mActivityManagerService.mContext; for (Map.Entry<String, Class<?>> entry : map.entrySet()) { String setting = entry.getKey(); + final String value; + if (map == sSecureSettingToTypeMap) { + value = Settings.Secure.getString(context.getContentResolver(), setting); + } else if (map == sSystemSettingToTypeMap) { + value = Settings.System.getString(context.getContentResolver(), setting); + } else { + value = Settings.Global.getString(context.getContentResolver(), setting); + } + if (value == null) { + continue; + } Class<?> type = entry.getValue(); if (type == String.class) { - final String value; - if (map == sSecureSettingToTypeMap) { - value = Settings.Secure.getString(context.getContentResolver(), setting); - } else if (map == sSystemSettingToTypeMap) { - value = Settings.System.getString(context.getContentResolver(), setting); - } else { - value = Settings.Global.getString(context.getContentResolver(), setting); - } snapshot.putString(setting, value); } else if (type == int.class) { - final int value; - if (map == sSecureSettingToTypeMap) { - value = Settings.Secure.getInt(context.getContentResolver(), setting, 0); - } else if (map == sSystemSettingToTypeMap) { - value = Settings.System.getInt(context.getContentResolver(), setting, 0); - } else { - value = Settings.Global.getInt(context.getContentResolver(), setting, 0); - } - snapshot.putInt(setting, value); + snapshot.putInt(setting, Integer.parseInt(value)); } else if (type == float.class) { - final float value; - if (map == sSecureSettingToTypeMap) { - value = Settings.Secure.getFloat(context.getContentResolver(), setting, 0); - } else if (map == sSystemSettingToTypeMap) { - value = Settings.System.getFloat(context.getContentResolver(), setting, 0); - } else { - value = Settings.Global.getFloat(context.getContentResolver(), setting, 0); - } - snapshot.putFloat(setting, value); + snapshot.putFloat(setting, Float.parseFloat(value)); } else if (type == long.class) { - final long value; - if (map == sSecureSettingToTypeMap) { - value = Settings.Secure.getLong(context.getContentResolver(), setting, 0); - } else if (map == sSystemSettingToTypeMap) { - value = Settings.System.getLong(context.getContentResolver(), setting, 0); - } else { - value = Settings.Global.getLong(context.getContentResolver(), setting, 0); - } - snapshot.putLong(setting, value); + snapshot.putLong(setting, Long.parseLong(value)); } } } diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java index c7f20b9ff904..0c2c2043fb15 100644 --- a/services/core/java/com/android/server/am/TaskRecord.java +++ b/services/core/java/com/android/server/am/TaskRecord.java @@ -1613,9 +1613,6 @@ final class TaskRecord extends ConfigurationContainer implements TaskWindowConta String iconFilename = null; int colorPrimary = 0; int colorBackground = 0; - int statusBarColor = 0; - int navigationBarColor = 0; - boolean topActivity = true; for (--activityNdx; activityNdx >= 0; --activityNdx) { final ActivityRecord r = mActivities.get(activityNdx); if (r.taskDescription != null) { @@ -1628,16 +1625,13 @@ final class TaskRecord extends ConfigurationContainer implements TaskWindowConta if (colorPrimary == 0) { colorPrimary = r.taskDescription.getPrimaryColor(); } - if (topActivity) { + if (colorBackground == 0) { colorBackground = r.taskDescription.getBackgroundColor(); - statusBarColor = r.taskDescription.getStatusBarColor(); - navigationBarColor = r.taskDescription.getNavigationBarColor(); } } - topActivity = false; } lastTaskDescription = new TaskDescription(label, null, iconFilename, colorPrimary, - colorBackground, statusBarColor, navigationBarColor); + colorBackground); if (mWindowContainerController != null) { mWindowContainerController.setTaskDescription(lastTaskDescription); } diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java index 728476aebf3e..3b5e5bcce7ae 100644 --- a/services/core/java/com/android/server/am/UserController.java +++ b/services/core/java/com/android/server/am/UserController.java @@ -670,7 +670,7 @@ final class UserController { } mInjector.systemServiceManagerCleanupUser(userId); synchronized (mLock) { - mInjector.stackSupervisorRemoveUserLocked(userId); + mInjector.getActivityStackSupervisor().removeUserLocked(userId); } // Remove the user if it is ephemeral. if (getUserInfo(userId).isEphemeral()) { @@ -823,8 +823,10 @@ final class UserController { return true; } - mInjector.stackSupervisorSetLockTaskModeLocked(null, - ActivityManager.LOCK_TASK_MODE_NONE, "startUser", false); + if (foreground) { + mInjector.getActivityStackSupervisor().setLockTaskModeLocked( + null, ActivityManager.LOCK_TASK_MODE_NONE, "startUser", false); + } final UserInfo userInfo = getUserInfo(userId); if (userInfo == null) { @@ -1202,11 +1204,12 @@ final class UserController { } void moveUserToForegroundLocked(UserState uss, int oldUserId, int newUserId) { - boolean homeInFront = mInjector.stackSupervisorSwitchUserLocked(newUserId, uss); + boolean homeInFront = + mInjector.getActivityStackSupervisor().switchUserLocked(newUserId, uss); if (homeInFront) { mInjector.startHomeActivityLocked(newUserId, "moveUserToForeground"); } else { - mInjector.stackSupervisorResumeFocusedStackTopActivityLocked(); + mInjector.getActivityStackSupervisor().resumeFocusedStackTopActivityLocked(); } EventLogTags.writeAmSwitchUser(newUserId); sendUserSwitchBroadcastsLocked(oldUserId, newUserId); @@ -1680,10 +1683,6 @@ final class UserController { mService.mSystemServiceManager.cleanupUser(userId); } - void stackSupervisorRemoveUserLocked(int userId) { - mService.mStackSupervisor.removeUserLocked(userId); - } - protected UserManagerService getUserManager() { if (mUserManager == null) { IBinder b = ServiceManager.getService(Context.USER_SERVICE); @@ -1743,24 +1742,10 @@ final class UserController { return mService.checkComponentPermission(permission, pid, uid, owningUid, exported); } - boolean stackSupervisorSwitchUserLocked(int userId, UserState uss) { - return mService.mStackSupervisor.switchUserLocked(userId, uss); - } - void startHomeActivityLocked(int userId, String reason) { mService.startHomeActivityLocked(userId, reason); } - void stackSupervisorResumeFocusedStackTopActivityLocked() { - mService.mStackSupervisor.resumeFocusedStackTopActivityLocked(); - } - - void stackSupervisorSetLockTaskModeLocked(TaskRecord task, int lockTaskModeState, - String reason, boolean andResume) { - mService.mStackSupervisor.setLockTaskModeLocked(task, lockTaskModeState, reason, - andResume); - } - void updateUserConfigurationLocked() { mService.updateUserConfigurationLocked(); } @@ -1778,5 +1763,9 @@ final class UserController { true /* above system */); d.show(); } + + ActivityStackSupervisor getActivityStackSupervisor() { + return mService.mStackSupervisor; + } } } diff --git a/services/core/java/com/android/server/am/VrController.java b/services/core/java/com/android/server/am/VrController.java new file mode 100644 index 000000000000..048bef7b19f5 --- /dev/null +++ b/services/core/java/com/android/server/am/VrController.java @@ -0,0 +1,418 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.am; + +import android.content.ComponentName; +import android.os.Process; +import android.service.vr.IPersistentVrStateCallbacks; +import android.util.Slog; +import com.android.server.LocalServices; +import com.android.server.vr.VrManagerInternal; + +/** + * Helper class for {@link ActivityManagerService} responsible for VrMode-related ActivityManager + * functionality. + * + * <p>Specifically, this class is responsible for: + * <ul> + * <li>Adjusting the scheduling of VR render threads while in VR mode. + * <li>Handling ActivityManager calls to set a VR or a 'persistent' VR thread. + * <li>Tracking the state of ActivityManagerService's view of VR-related behavior flags. + * </ul> + * + * <p>This is NOT the class that manages the system VR mode lifecycle. The class responsible for + * handling everything related to VR mode state changes (e.g. the lifecycles of the associated + * VrListenerService, VrStateCallbacks, VR HAL etc.) is VrManagerService. + * + * <p>This class is exclusively for use by ActivityManagerService. Do not add callbacks or other + * functionality to this for things that belong in VrManagerService. + */ +final class VrController { + private static final String TAG = "VrController"; + + // VR state flags. + private static final int FLAG_NON_VR_MODE = 0; + private static final int FLAG_VR_MODE = 1; + private static final int FLAG_PERSISTENT_VR_MODE = 2; + + // Invariants maintained for mVrState + // + // Always true: + // - Only a single VR-related thread will have elevated scheduling priorities at a time + // across all threads in all processes (and for all possible running modes). + // + // Always true while FLAG_PERSISTENT_VR_MODE is set: + // - An application has set a flag to run in persistent VR mode the next time VR mode is + // entered. The device may or may not be in VR mode. + // - mVrState will contain FLAG_PERSISTENT_VR_MODE + // - An application may set a persistent VR thread that gains elevated scheduling + // priorities via a call to setPersistentVrThread. + // - Calls to set a regular (non-persistent) VR thread via setVrThread will fail, and + // thread that had previously elevated its scheduling priority in this way is returned + // to its normal scheduling priority. + // + // Always true while FLAG_VR_MODE is set: + // - The current top application is running in VR mode. + // - mVrState will contain FLAG_VR_MODE + // + // While FLAG_VR_MODE is set without FLAG_PERSISTENT_VR_MODE: + // - The current top application may set one of its threads to run at an elevated + // scheduling priority via a call to setVrThread. + // + // While FLAG_VR_MODE is set with FLAG_PERSISTENT_VR_MODE: + // - The current top application may NOT set one of its threads to run at an elevated + // scheduling priority via a call to setVrThread (instead, the persistent VR thread will + // be kept if an application has set one). + // + // While mVrState == FLAG_NON_VR_MODE: + // - Calls to setVrThread will fail. + // - Calls to setPersistentVrThread will fail. + // - No threads will have elevated scheduling priority for VR. + // + private int mVrState = FLAG_NON_VR_MODE; + + // The single VR render thread on the device that is given elevated scheduling priority. + private int mVrRenderThreadTid = 0; + + private final Object mGlobalAmLock; + + private final IPersistentVrStateCallbacks mPersistentVrModeListener = + new IPersistentVrStateCallbacks.Stub() { + @Override + public void onPersistentVrStateChanged(boolean enabled) { + synchronized(mGlobalAmLock) { + // Note: This is the only place where mVrState should have its + // FLAG_PERSISTENT_VR_MODE setting changed. + if (enabled) { + setVrRenderThreadLocked(0, ProcessList.SCHED_GROUP_TOP_APP, true); + mVrState |= FLAG_PERSISTENT_VR_MODE; + } else { + setPersistentVrRenderThreadLocked(0, true); + mVrState &= ~FLAG_PERSISTENT_VR_MODE; + } + } + } + }; + + /** + * Create new VrController instance. + * + * @param globalAmLock the global ActivityManagerService lock. + */ + public VrController(final Object globalAmLock) { + mGlobalAmLock = globalAmLock; + } + + /** + * Called when ActivityManagerService receives its systemReady call during boot. + */ + public void onSystemReady() { + VrManagerInternal vrManagerInternal = LocalServices.getService(VrManagerInternal.class); + if (vrManagerInternal != null) { + vrManagerInternal.addPersistentVrModeStateListener(mPersistentVrModeListener); + } + } + + /** + * Called when ActivityManagerService's TOP_APP process has changed. + * + * <p>Note: This must be called with the global ActivityManagerService lock held. + * + * @param proc is the ProcessRecord of the process that entered or left the TOP_APP scheduling + * group. + */ + public void onTopProcChangedLocked(ProcessRecord proc) { + if (proc.curSchedGroup == ProcessList.SCHED_GROUP_TOP_APP) { + setVrRenderThreadLocked(proc.vrThreadTid, proc.curSchedGroup, true); + } else { + if (proc.vrThreadTid == mVrRenderThreadTid) { + clearVrRenderThreadLocked(true); + } + } + } + + /** + * Called when ActivityManagerService is switching VR mode for the TOP_APP process. + * + * @param record the ActivityRecord of the activity changing the system VR mode. + * @return {@code true} if the VR state changed. + */ + public boolean onVrModeChanged(ActivityRecord record) { + // This message means that the top focused activity enabled VR mode (or an activity + // that previously set this has become focused). + VrManagerInternal vrService = LocalServices.getService(VrManagerInternal.class); + if (vrService == null) { + // VR mode isn't supported on this device. + return false; + } + boolean vrMode; + ComponentName requestedPackage; + ComponentName callingPackage; + int userId; + boolean changed = false; + synchronized (mGlobalAmLock) { + vrMode = record.requestedVrComponent != null; + requestedPackage = record.requestedVrComponent; + userId = record.userId; + callingPackage = record.info.getComponentName(); + + // Tell the VrController that a VR mode change is requested. + changed = changeVrModeLocked(vrMode, record.app); + } + + // Tell VrManager that a VR mode changed is requested, VrManager will handle + // notifying all non-AM dependencies if needed. + vrService.setVrMode(vrMode, requestedPackage, userId, callingPackage); + return changed; + } + + /** + * Called to set an application's VR thread. + * + * <p>This will fail if the system is not in VR mode, the system has the persistent VR flag set, + * or the scheduling group of the thread is not for the current top app. If this succeeds, any + * previous VR thread will be returned to a normal sheduling priority; if this fails, the + * scheduling for the previous thread will be unaffected. + * + * <p>Note: This must be called with the global ActivityManagerService lock and the + * mPidsSelfLocked object locks held. + * + * @param tid the tid of the thread to set, or 0 to unset the current thread. + * @param pid the pid of the process owning the thread to set. + * @param proc the ProcessRecord of the process owning the thread to set. + */ + public void setVrThreadLocked(int tid, int pid, ProcessRecord proc) { + if (hasPersistentVrFlagSet()) { + Slog.w(TAG, "VR thread cannot be set in persistent VR mode!"); + return; + } + if (proc == null) { + Slog.w(TAG, "Persistent VR thread not set, calling process doesn't exist!"); + return; + } + if (tid != 0) { + enforceThreadInProcess(tid, pid); + } + if (!inVrMode()) { + Slog.w(TAG, "VR thread cannot be set when not in VR mode!"); + } else { + setVrRenderThreadLocked(tid, proc.curSchedGroup, false); + } + proc.vrThreadTid = (tid > 0) ? tid : 0; + } + + /** + * Called to set an application's persistent VR thread. + * + * <p>This will fail if the system does not have the persistent VR flag set. If this succeeds, + * any previous VR thread will be returned to a normal sheduling priority; if this fails, + * the scheduling for the previous thread will be unaffected. + * + * <p>Note: This must be called with the global ActivityManagerService lock and the + * mPidsSelfLocked object locks held. + * + * @param tid the tid of the thread to set, or 0 to unset the current thread. + * @param pid the pid of the process owning the thread to set. + * @param proc the ProcessRecord of the process owning the thread to set. + */ + public void setPersistentVrThreadLocked(int tid, int pid, ProcessRecord proc) { + if (!hasPersistentVrFlagSet()) { + Slog.w(TAG, "Persistent VR thread may only be set in persistent VR mode!"); + return; + } + if (proc == null) { + Slog.w(TAG, "Persistent VR thread not set, calling process doesn't exist!"); + return; + } + if (tid != 0) { + enforceThreadInProcess(tid, pid); + } + setPersistentVrRenderThreadLocked(tid, false); + } + + /** + * Return {@code true} when UI features incompatible with VR mode should be disabled. + * + * <p>Note: This must be called with the global ActivityManagerService lock held. + */ + public boolean shouldDisableNonVrUiLocked() { + return mVrState != FLAG_NON_VR_MODE; + } + + /** + * Called when to update this VrController instance's state when the system VR mode is being + * changed. + * + * <p>Note: This must be called with the global ActivityManagerService lock held. + * + * @param vrMode {@code true} if the system VR mode is being enabled. + * @param proc the ProcessRecord of the process enabling the system VR mode. + * + * @return {@code true} if our state changed. + */ + private boolean changeVrModeLocked(boolean vrMode, ProcessRecord proc) { + final int oldVrState = mVrState; + + // This is the only place where mVrState should have its FLAG_VR_MODE setting + // changed. + if (vrMode) { + mVrState |= FLAG_VR_MODE; + } else { + mVrState &= ~FLAG_VR_MODE; + } + + boolean changed = (oldVrState != mVrState); + + if (changed) { + if (proc != null) { + if (proc.vrThreadTid > 0) { + setVrRenderThreadLocked(proc.vrThreadTid, proc.curSchedGroup, false); + } + } else { + clearVrRenderThreadLocked(false); + } + } + return changed; + } + + /** + * Set the given thread as the new VR thread, and give it special scheduling priority. + * + * <p>If the current thread is this thread, do nothing. If the current thread is different from + * the given thread, the current thread will be returned to a normal scheduling priority. + * + * @param newTid the tid of the thread to set, or 0 to unset the current thread. + * @param suppressLogs {@code true} if any error logging should be disabled. + * + * @return the tid of the thread configured to run at the scheduling priority for VR + * mode after this call completes (this may be the previous thread). + */ + private int updateVrRenderThreadLocked(int newTid, boolean suppressLogs) { + if (mVrRenderThreadTid == newTid) { + return mVrRenderThreadTid; + } + + if (mVrRenderThreadTid > 0) { + ActivityManagerService.scheduleAsRegularPriority(mVrRenderThreadTid, suppressLogs); + mVrRenderThreadTid = 0; + } + + if (newTid > 0) { + mVrRenderThreadTid = newTid; + ActivityManagerService.scheduleAsFifoPriority(mVrRenderThreadTid, suppressLogs); + } + return mVrRenderThreadTid; + } + + /** + * Set special scheduling for the given application persistent VR thread, if allowed. + * + * <p>This will fail if the system does not have the persistent VR flag set. If this succeeds, + * any previous VR thread will be returned to a normal sheduling priority; if this fails, + * the scheduling for the previous thread will be unaffected. + * + * @param newTid the tid of the thread to set, or 0 to unset the current thread. + * @param suppressLogs {@code true} if any error logging should be disabled. + * + * @return the tid of the thread configured to run at the scheduling priority for VR + * mode after this call completes (this may be the previous thread). + */ + private int setPersistentVrRenderThreadLocked(int newTid, boolean suppressLogs) { + if (!hasPersistentVrFlagSet()) { + if (!suppressLogs) { + Slog.w(TAG, "Failed to set persistent VR thread, " + + "system not in persistent VR mode."); + } + return mVrRenderThreadTid; + } + return updateVrRenderThreadLocked(newTid, suppressLogs); + } + + /** + * Set special scheduling for the given application VR thread, if allowed. + * + * <p>This will fail if the system is not in VR mode, the system has the persistent VR flag set, + * or the scheduling group of the thread is not for the current top app. If this succeeds, any + * previous VR thread will be returned to a normal sheduling priority; if this fails, the + * scheduling for the previous thread will be unaffected. + * + * @param newTid the tid of the thread to set, or 0 to unset the current thread. + * @param schedGroup the current scheduling group of the thread to set. + * @param suppressLogs {@code true} if any error logging should be disabled. + * + * @return the tid of the thread configured to run at the scheduling priority for VR + * mode after this call completes (this may be the previous thread). + */ + private int setVrRenderThreadLocked(int newTid, int schedGroup, boolean suppressLogs) { + boolean inVr = inVrMode(); + boolean inPersistentVr = hasPersistentVrFlagSet(); + if (!inVr || inPersistentVr || schedGroup != ProcessList.SCHED_GROUP_TOP_APP) { + if (!suppressLogs) { + String reason = "caller is not the current top application."; + if (!inVr) { + reason = "system not in VR mode."; + } else if (inPersistentVr) { + reason = "system in persistent VR mode."; + } + Slog.w(TAG, "Failed to set VR thread, " + reason); + } + return mVrRenderThreadTid; + } + return updateVrRenderThreadLocked(newTid, suppressLogs); + } + + /** + * Unset any special scheduling used for the current VR render thread, and return it to normal + * scheduling priority. + * + * @param suppressLogs {@code true} if any error logging should be disabled. + */ + private void clearVrRenderThreadLocked(boolean suppressLogs) { + updateVrRenderThreadLocked(0, suppressLogs); + } + + /** + * Check that the given tid is running in the process for the given pid, and throw an exception + * if not. + */ + private void enforceThreadInProcess(int tid, int pid) { + if (!Process.isThreadInProcess(pid, tid)) { + throw new IllegalArgumentException("VR thread does not belong to process"); + } + } + + /** + * True when the system is in VR mode. + */ + private boolean inVrMode() { + return (mVrState & FLAG_VR_MODE) != 0; + } + + /** + * True when the persistent VR mode flag has been set. + * + * Note: Currently this does not necessarily mean that the system is in VR mode. + */ + private boolean hasPersistentVrFlagSet() { + return (mVrState & FLAG_PERSISTENT_VR_MODE) != 0; + } + + @Override + public String toString() { + return String.format("[VrState=0x%x,VrRenderThreadTid=%d]", mVrState, mVrRenderThreadTid); + } +} diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index ddd918fdcc5d..d83676b58552 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -253,6 +253,17 @@ public final class DisplayManagerService extends SystemService { PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); mGlobalDisplayBrightness = pm.getDefaultScreenBrightnessSetting(); + + } + + public void setupSchedulerPolicies() { + /* + * android.display is critical to user experience and we should + * make sure it is not in the default foregroup groups, add it to + * top-app to make sure it uses all the cores and scheduling + * settings for top-app when it runs. + */ + Process.setThreadGroupAndCpuset(DisplayThread.get().getThreadId(), Process.THREAD_GROUP_TOP_APP); } @Override diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java index 02e106e92aeb..c8028fe6d1de 100644 --- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java +++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java @@ -248,8 +248,8 @@ import java.util.concurrent.TimeUnit; */ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { static final String TAG = "NetworkPolicy"; - private static final boolean LOGD = true; // UNDO - private static final boolean LOGV = true; // UNDO + private static final boolean LOGD = false; + private static final boolean LOGV = false; private static final int VERSION_INIT = 1; private static final int VERSION_ADDED_SNOOZE = 2; diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 44c715ba706e..64ee1e976378 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -1294,19 +1294,14 @@ public class NotificationManagerService extends SystemService { sendRegisteredOnlyBroadcast(NotificationManager.ACTION_EFFECTS_SUPPRESSOR_CHANGED); } - private void updateNotificationChannelInt(String pkg, int uid, NotificationChannel channel, - boolean fromAssistant) { + private void updateNotificationChannelInt(String pkg, int uid, NotificationChannel channel) { if (channel.getImportance() == NotificationManager.IMPORTANCE_NONE) { // cancel cancelAllNotificationsInt(MY_UID, MY_PID, pkg, channel.getId(), 0, 0, true, UserHandle.getUserId(Binder.getCallingUid()), REASON_CHANNEL_BANNED, null); } - if (fromAssistant) { - mRankingHelper.updateNotificationChannelFromAssistant(pkg, uid, channel); - } else { - mRankingHelper.updateNotificationChannel(pkg, uid, channel); - } + mRankingHelper.updateNotificationChannel(pkg, uid, channel); synchronized (mNotificationLock) { final int N = mNotificationList.size(); @@ -1709,7 +1704,7 @@ public class NotificationManagerService extends SystemService { NotificationChannel channel) { enforceSystemOrSystemUI("Caller not system or systemui"); Preconditions.checkNotNull(channel); - updateNotificationChannelInt(pkg, uid, channel, false); + updateNotificationChannelInt(pkg, uid, channel); } @Override @@ -2646,47 +2641,6 @@ public class NotificationManagerService extends SystemService { Binder.restoreCallingIdentity(identity); } } - - @Override - public void createNotificationChannelFromAssistant(INotificationListener token, String pkg, - NotificationChannel channel) throws RemoteException { - ManagedServiceInfo info = mNotificationAssistants.checkServiceTokenLocked(token); - int uid = mPackageManager.getPackageUid(pkg, 0, info.userid); - mRankingHelper.createNotificationChannel(pkg, uid, channel, false /* fromTargetApp */); - savePolicyFile(); - } - - @Override - public void deleteNotificationChannelFromAssistant(INotificationListener token, String pkg, - String channelId) throws RemoteException { - ManagedServiceInfo info = mNotificationAssistants.checkServiceTokenLocked(token); - if (NotificationChannel.DEFAULT_CHANNEL_ID.equals(channelId)) { - throw new IllegalArgumentException("Cannot delete default channel"); - } - - int uid = mPackageManager.getPackageUid(pkg, 0, info.userid); - cancelAllNotificationsInt(MY_UID, MY_PID, pkg, channelId, 0, 0, true, - info.userid, REASON_CHANNEL_BANNED, null); - mRankingHelper.deleteNotificationChannel(pkg, uid, channelId); - savePolicyFile(); - } - - @Override - public void updateNotificationChannelFromAssistant(INotificationListener token, String pkg, - NotificationChannel channel) throws RemoteException { - ManagedServiceInfo info = mNotificationAssistants.checkServiceTokenLocked(token); - Preconditions.checkNotNull(channel); - int uid = mPackageManager.getPackageUid(pkg, 0, info.userid); - updateNotificationChannelInt(pkg, uid, channel, true); - } - - @Override - public ParceledListSlice<NotificationChannel> getNotificationChannelsFromAssistant( - INotificationListener token, String pkg) throws RemoteException { - ManagedServiceInfo info = mNotificationAssistants.checkServiceTokenLocked(token); - int uid = mPackageManager.getPackageUid(pkg, 0, info.userid); - return mRankingHelper.getNotificationChannels(pkg, uid, false /* includeDeleted */); - } }; private void applyAdjustment(NotificationRecord n, Adjustment adjustment) { @@ -2695,17 +2649,10 @@ public class NotificationManagerService extends SystemService { } if (adjustment.getSignals() != null) { Bundle.setDefusable(adjustment.getSignals(), true); - final String overrideChannelId = - adjustment.getSignals().getString(Adjustment.KEY_CHANNEL_ID, null); final ArrayList<String> people = adjustment.getSignals().getStringArrayList(Adjustment.KEY_PEOPLE); final ArrayList<SnoozeCriterion> snoozeCriterionList = adjustment.getSignals().getParcelableArrayList(Adjustment.KEY_SNOOZE_CRITERIA); - if (!TextUtils.isEmpty(overrideChannelId)) { - n.updateNotificationChannel(mRankingHelper.getNotificationChannel( - n.sbn.getPackageName(), n.sbn.getUid(), overrideChannelId, - false /* includeDeleted */)); - } n.setPeopleOverride(people); n.setSnoozeCriteria(snoozeCriterionList); } diff --git a/services/core/java/com/android/server/notification/RankingConfig.java b/services/core/java/com/android/server/notification/RankingConfig.java index e13df192acb6..4d19b52c327a 100644 --- a/services/core/java/com/android/server/notification/RankingConfig.java +++ b/services/core/java/com/android/server/notification/RankingConfig.java @@ -37,7 +37,6 @@ public interface RankingConfig { void createNotificationChannel(String pkg, int uid, NotificationChannel channel, boolean fromTargetApp); void updateNotificationChannel(String pkg, int uid, NotificationChannel channel); - void updateNotificationChannelFromAssistant(String pkg, int uid, NotificationChannel channel); NotificationChannel getNotificationChannel(String pkg, int uid, String channelId, boolean includeDeleted); void deleteNotificationChannel(String pkg, int uid, String channelId); void permanentlyDeleteNotificationChannel(String pkg, int uid, String channelId); diff --git a/services/core/java/com/android/server/notification/RankingHelper.java b/services/core/java/com/android/server/notification/RankingHelper.java index 65aaee0f8ce8..b63b05f1f59b 100644 --- a/services/core/java/com/android/server/notification/RankingHelper.java +++ b/services/core/java/com/android/server/notification/RankingHelper.java @@ -609,55 +609,6 @@ public class RankingHelper implements RankingConfig { } @Override - public void updateNotificationChannelFromAssistant(String pkg, int uid, - NotificationChannel updatedChannel) { - Record r = getOrCreateRecord(pkg, uid); - if (r == null) { - throw new IllegalArgumentException("Invalid package"); - } - NotificationChannel channel = r.channels.get(updatedChannel.getId()); - if (channel == null || channel.isDeleted()) { - throw new IllegalArgumentException("Channel does not exist"); - } - - if ((channel.getUserLockedFields() & NotificationChannel.USER_LOCKED_IMPORTANCE) == 0) { - channel.setImportance(updatedChannel.getImportance()); - } - if ((channel.getUserLockedFields() & NotificationChannel.USER_LOCKED_LIGHTS) == 0) { - channel.enableLights(updatedChannel.shouldShowLights()); - channel.setLightColor(updatedChannel.getLightColor()); - } - if ((channel.getUserLockedFields() & NotificationChannel.USER_LOCKED_PRIORITY) == 0) { - channel.setBypassDnd(updatedChannel.canBypassDnd()); - } - if ((channel.getUserLockedFields() & NotificationChannel.USER_LOCKED_SOUND) == 0) { - channel.setSound(updatedChannel.getSound(), updatedChannel.getAudioAttributes()); - } - if ((channel.getUserLockedFields() & NotificationChannel.USER_LOCKED_VIBRATION) == 0) { - channel.enableVibration(updatedChannel.shouldVibrate()); - channel.setVibrationPattern(updatedChannel.getVibrationPattern()); - } - if ((channel.getUserLockedFields() & NotificationChannel.USER_LOCKED_VISIBILITY) == 0) { - if (updatedChannel.getLockscreenVisibility() == Notification.VISIBILITY_PUBLIC) { - channel.setLockscreenVisibility(Ranking.VISIBILITY_NO_OVERRIDE); - } else { - channel.setLockscreenVisibility(updatedChannel.getLockscreenVisibility()); - } - } - if ((channel.getUserLockedFields() & NotificationChannel.USER_LOCKED_SHOW_BADGE) == 0) { - channel.setShowBadge(updatedChannel.canShowBadge()); - } - if (updatedChannel.isDeleted()) { - channel.setDeleted(true); - } - // Assistant cannot change the group - - MetricsLogger.action(getChannelLog(channel, pkg)); - r.channels.put(channel.getId(), channel); - updateConfig(); - } - - @Override public NotificationChannel getNotificationChannel(String pkg, int uid, String channelId, boolean includeDeleted) { Preconditions.checkNotNull(pkg); diff --git a/services/core/java/com/android/server/pm/BasePermission.java b/services/core/java/com/android/server/pm/BasePermission.java index 07c9dec7b353..21000388e746 100644 --- a/services/core/java/com/android/server/pm/BasePermission.java +++ b/services/core/java/com/android/server/pm/BasePermission.java @@ -98,4 +98,8 @@ final class BasePermission { public boolean isInstant() { return (protectionLevel & PermissionInfo.PROTECTION_FLAG_EPHEMERAL) != 0; } + + public boolean isRuntimeOnly() { + return (protectionLevel & PermissionInfo.PROTECTION_FLAG_RUNTIME_ONLY) != 0; + } } diff --git a/services/core/java/com/android/server/pm/EphemeralResolverConnection.java b/services/core/java/com/android/server/pm/EphemeralResolverConnection.java index f6e96b2bf876..c4cc4fbf3cf0 100644 --- a/services/core/java/com/android/server/pm/EphemeralResolverConnection.java +++ b/services/core/java/com/android/server/pm/EphemeralResolverConnection.java @@ -64,9 +64,10 @@ final class EphemeralResolverConnection implements DeathRecipient { private volatile boolean mBindRequested; private IInstantAppResolver mRemoteInstance; - public EphemeralResolverConnection(Context context, ComponentName componentName) { + public EphemeralResolverConnection( + Context context, ComponentName componentName, String action) { mContext = context; - mIntent = new Intent(Intent.ACTION_RESOLVE_INSTANT_APP_PACKAGE).setComponent(componentName); + mIntent = new Intent(action).setComponent(componentName); } public final List<InstantAppResolveInfo> getInstantAppResolveInfoList(int hashPrefix[], diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java index 9f7c4a2793db..1e2b743d5e46 100644 --- a/services/core/java/com/android/server/pm/Installer.java +++ b/services/core/java/com/android/server/pm/Installer.java @@ -279,13 +279,14 @@ public class Installer extends SystemService { public void dexopt(String apkPath, int uid, @Nullable String pkgName, String instructionSet, int dexoptNeeded, @Nullable String outputPath, int dexFlags, - String compilerFilter, @Nullable String volumeUuid, @Nullable String sharedLibraries) + String compilerFilter, @Nullable String volumeUuid, @Nullable String sharedLibraries, + @Nullable String seInfo) throws InstallerException { assertValidInstructionSet(instructionSet); if (!checkBeforeRemote()) return; try { mInstalld.dexopt(apkPath, uid, pkgName, instructionSet, dexoptNeeded, outputPath, - dexFlags, compilerFilter, volumeUuid, sharedLibraries); + dexFlags, compilerFilter, volumeUuid, sharedLibraries, seInfo); } catch (Exception e) { throw InstallerException.from(e); } diff --git a/services/core/java/com/android/server/pm/InstantAppRegistry.java b/services/core/java/com/android/server/pm/InstantAppRegistry.java index 0ae5f314d473..89a303d21413 100644 --- a/services/core/java/com/android/server/pm/InstantAppRegistry.java +++ b/services/core/java/com/android/server/pm/InstantAppRegistry.java @@ -272,6 +272,7 @@ class InstantAppRegistry { } else { // Deleting an app prunes all instant state such as cookie deleteDir(getInstantApplicationDir(pkg.packageName, userId)); + mCookiePersistence.cancelPendingPersistLPw(pkg, userId); removeAppLPw(userId, ps.appId); } } diff --git a/services/core/java/com/android/server/pm/InstantAppResolver.java b/services/core/java/com/android/server/pm/InstantAppResolver.java index 59f8a2d0da20..6f593b08e258 100644 --- a/services/core/java/com/android/server/pm/InstantAppResolver.java +++ b/services/core/java/com/android/server/pm/InstantAppResolver.java @@ -86,22 +86,18 @@ public abstract class InstantAppResolver { final List<InstantAppResolveInfo> instantAppResolveInfoList = connection.getInstantAppResolveInfoList(shaPrefix, token); - final AuxiliaryResolveInfo resolveInfo; if (instantAppResolveInfoList == null || instantAppResolveInfoList.size() == 0) { // No hash prefix match; there are no instant apps for this domain. if (DEBUG_EPHEMERAL) { Log.d(TAG, "No results returned"); } - resolveInfo = null; - } else { - resolveInfo = InstantAppResolver.filterInstantAppIntent(instantAppResolveInfoList, - intent, requestObj.resolvedType, requestObj.userId, - intent.getPackage(), digest, token); + return null; } - + final AuxiliaryResolveInfo resolveInfo = InstantAppResolver.filterInstantAppIntent( + instantAppResolveInfoList, intent, requestObj.resolvedType, requestObj.userId, + intent.getPackage(), digest, token); logMetrics(ACTION_INSTANT_APP_RESOLUTION_PHASE_ONE, startTime, token, - resolveInfo != null ? RESOLUTION_SUCCESS : RESOLUTION_FAILURE); - + RESOLUTION_SUCCESS); return resolveInfo; } diff --git a/services/core/java/com/android/server/pm/OtaDexoptService.java b/services/core/java/com/android/server/pm/OtaDexoptService.java index 6245ffc64009..498181b5179e 100644 --- a/services/core/java/com/android/server/pm/OtaDexoptService.java +++ b/services/core/java/com/android/server/pm/OtaDexoptService.java @@ -278,7 +278,7 @@ public class OtaDexoptService extends IOtaDexopt.Stub { public void dexopt(String apkPath, int uid, @Nullable String pkgName, String instructionSet, int dexoptNeeded, @Nullable String outputPath, int dexFlags, String compilerFilter, @Nullable String volumeUuid, - @Nullable String sharedLibraries) throws InstallerException { + @Nullable String sharedLibraries, @Nullable String seInfo) throws InstallerException { commands.add(buildCommand("dexopt", apkPath, uid, @@ -289,7 +289,8 @@ public class OtaDexoptService extends IOtaDexopt.Stub { dexFlags, compilerFilter, volumeUuid, - sharedLibraries)); + sharedLibraries, + seInfo)); } }; diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java index d9ea7284616d..9039647cbffd 100644 --- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java +++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java @@ -22,6 +22,7 @@ import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageParser; import android.os.Environment; +import android.os.FileUtils; import android.os.PowerManager; import android.os.UserHandle; import android.os.WorkSource; @@ -151,6 +152,7 @@ public class PackageDexOptimizer { // TODO(calin,jeffhao): shared library paths should be adjusted to include previous code // paths (b/34169257). final String sharedLibrariesPath = getSharedLibrariesPath(sharedLibraries); + // Get the dexopt flags after getRealCompilerFilter to make sure we get the correct flags. final int dexoptFlags = getDexFlags(pkg, compilerFilter); int result = DEX_OPT_SKIPPED; @@ -202,7 +204,7 @@ public class PackageDexOptimizer { long startTime = System.currentTimeMillis(); mInstaller.dexopt(path, uid, pkg.packageName, isa, dexoptNeeded, oatDir, dexoptFlags, - compilerFilter, pkg.volumeUuid, sharedLibrariesPath); + compilerFilter, pkg.volumeUuid, sharedLibrariesPath, pkg.applicationInfo.seInfo); if (packageStats != null) { long endTime = System.currentTimeMillis(); @@ -254,17 +256,20 @@ public class PackageDexOptimizer { @GuardedBy("mInstallLock") private int dexOptSecondaryDexPathLI(ApplicationInfo info, String path, Set<String> isas, String compilerFilter, boolean isUsedByOtherApps) { + compilerFilter = getRealCompilerFilter(info, compilerFilter, isUsedByOtherApps); + // Get the dexopt flags after getRealCompilerFilter to make sure we get the correct flags. int dexoptFlags = getDexFlags(info, compilerFilter) | DEXOPT_SECONDARY_DEX; // Check the app storage and add the appropriate flags. - if (info.dataDir.equals(info.deviceProtectedDataDir)) { + if (info.deviceProtectedDataDir != null && + FileUtils.contains(info.deviceProtectedDataDir, path)) { dexoptFlags |= DEXOPT_STORAGE_DE; - } else if (info.dataDir.equals(info.credentialProtectedDataDir)) { + } else if (info.credentialProtectedDataDir != null && + FileUtils.contains(info.credentialProtectedDataDir, path)) { dexoptFlags |= DEXOPT_STORAGE_CE; } else { Slog.e(TAG, "Could not infer CE/DE storage for package " + info.packageName); return DEX_OPT_FAILED; } - compilerFilter = getRealCompilerFilter(info, compilerFilter, isUsedByOtherApps); Log.d(TAG, "Running dexopt on: " + path + " pkg=" + info.packageName + " isa=" + isas + " dexoptFlags=" + printDexoptFlags(dexoptFlags) @@ -278,7 +283,7 @@ public class PackageDexOptimizer { // TODO(calin): maybe add a separate call. mInstaller.dexopt(path, info.uid, info.packageName, isa, /*dexoptNeeded*/ 0, /*oatDir*/ null, dexoptFlags, - compilerFilter, info.volumeUuid, SKIP_SHARED_LIBRARY_CHECK); + compilerFilter, info.volumeUuid, SKIP_SHARED_LIBRARY_CHECK, info.seInfoUser); } return DEX_OPT_PERFORMED; diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 3303081314a5..dd3959001f44 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -608,6 +608,10 @@ public class PackageManagerService extends IPackageManager.Stub { final boolean mIsPreNUpgrade; final boolean mIsPreNMR1Upgrade; + // Have we told the Activity Manager to whitelist the default container service by uid yet? + @GuardedBy("mPackages") + boolean mDefaultContainerWhitelisted = false; + @GuardedBy("mPackages") private boolean mDexOptDialogShown; @@ -845,8 +849,7 @@ public class PackageManagerService extends IPackageManager.Stub { /** Component used to show resolver settings for Instant Apps */ final ComponentName mInstantAppResolverSettingsComponent; - /** Component used to install ephemeral applications */ - ComponentName mInstantAppInstallerComponent; + /** Activity used to install instant applications */ ActivityInfo mInstantAppInstallerActivity; final ResolveInfo mInstantAppInstallerInfo = new ResolveInfo(); @@ -1912,7 +1915,11 @@ public class PackageManagerService extends IPackageManager.Stub { // survive long enough to benefit of background optimizations. for (int userId : firstUsers) { PackageInfo info = getPackageInfo(packageName, /*flags*/ 0, userId); - mDexManager.notifyPackageInstalled(info, userId); + // There's a race currently where some install events may interleave with an uninstall. + // This can lead to package info being null (b/36642664). + if (info != null) { + mDexManager.notifyPackageInstalled(info, userId); + } } } @@ -2052,6 +2059,7 @@ public class PackageManagerService extends IPackageManager.Stub { } if (bp != null && (bp.isRuntime() || bp.isDevelopment()) && (!instantApp || bp.isInstant()) + && (supportsRuntimePermissions || !bp.isRuntimeOnly()) && (grantedPermissions == null || ArrayUtils.contains(grantedPermissions, permission))) { final int flags = permissionsState.getPermissionFlags(permission, userId); @@ -2809,20 +2817,22 @@ public class PackageManagerService extends IPackageManager.Stub { } mInstallerService = new PackageInstallerService(context, this); - final ComponentName ephemeralResolverComponent = getEphemeralResolverLPr(); - if (ephemeralResolverComponent != null) { + final Pair<ComponentName, String> instantAppResolverComponent = + getInstantAppResolverLPr(); + if (instantAppResolverComponent != null) { if (DEBUG_EPHEMERAL) { - Slog.d(TAG, "Set ephemeral resolver: " + ephemeralResolverComponent); + Slog.d(TAG, "Set ephemeral resolver: " + instantAppResolverComponent); } - mInstantAppResolverConnection = - new EphemeralResolverConnection(mContext, ephemeralResolverComponent); + mInstantAppResolverConnection = new EphemeralResolverConnection( + mContext, instantAppResolverComponent.first, + instantAppResolverComponent.second); mInstantAppResolverSettingsComponent = - getEphemeralResolverSettingsLPr(ephemeralResolverComponent); + getInstantAppResolverSettingsLPr(instantAppResolverComponent.first); } else { mInstantAppResolverConnection = null; mInstantAppResolverSettingsComponent = null; } - updateInstantAppInstallerLocked(); + updateInstantAppInstallerLocked(null); // Read and update the usage of dex files. // Do this at the end of PM init so that all the packages have their @@ -2862,22 +2872,15 @@ public class PackageManagerService extends IPackageManager.Stub { Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); } - private void updateInstantAppInstallerLocked() { - final ComponentName oldInstantAppInstallerComponent = mInstantAppInstallerComponent; - final ActivityInfo newInstantAppInstaller = getEphemeralInstallerLPr(); - ComponentName newInstantAppInstallerComponent = newInstantAppInstaller == null - ? null : newInstantAppInstaller.getComponentName(); - - if (newInstantAppInstallerComponent != null - && !newInstantAppInstallerComponent.equals(oldInstantAppInstallerComponent)) { - if (DEBUG_EPHEMERAL) { - Slog.d(TAG, "Set ephemeral installer: " + newInstantAppInstallerComponent); - } - setUpInstantAppInstallerActivityLP(newInstantAppInstaller); - } else if (DEBUG_EPHEMERAL && newInstantAppInstallerComponent == null) { - Slog.d(TAG, "Unset ephemeral installer; none available"); + private void updateInstantAppInstallerLocked(String modifiedPackage) { + // we're only interested in updating the installer appliction when 1) it's not + // already set or 2) the modified package is the installer + if (mInstantAppInstallerActivity != null + && !mInstantAppInstallerActivity.getComponentName().getPackageName() + .equals(modifiedPackage)) { + return; } - mInstantAppInstallerComponent = newInstantAppInstallerComponent; + setUpInstantAppInstallerActivityLP(getInstantAppInstallerLPr()); } private static File preparePackageParserCache(boolean isUpgrade) { @@ -3041,7 +3044,7 @@ public class PackageManagerService extends IPackageManager.Stub { } } - private @Nullable ComponentName getEphemeralResolverLPr() { + private @Nullable Pair<ComponentName, String> getInstantAppResolverLPr() { final String[] packageArray = mContext.getResources().getStringArray(R.array.config_ephemeralResolverPackage); if (packageArray.length == 0 && !Build.IS_DEBUGGABLE) { @@ -3056,10 +3059,20 @@ public class PackageManagerService extends IPackageManager.Stub { MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE | (!Build.IS_DEBUGGABLE ? MATCH_SYSTEM_ONLY : 0); - final Intent resolverIntent = new Intent(Intent.ACTION_RESOLVE_INSTANT_APP_PACKAGE); - final List<ResolveInfo> resolvers = queryIntentServicesInternal(resolverIntent, null, + String actionName = Intent.ACTION_RESOLVE_INSTANT_APP_PACKAGE; + final Intent resolverIntent = new Intent(actionName); + List<ResolveInfo> resolvers = queryIntentServicesInternal(resolverIntent, null, resolveFlags, UserHandle.USER_SYSTEM, callingUid, false /*includeInstantApps*/); - + // temporarily look for the old action + if (resolvers.size() == 0) { + if (DEBUG_EPHEMERAL) { + Slog.d(TAG, "Ephemeral resolver not found with new action; try old one"); + } + actionName = Intent.ACTION_RESOLVE_EPHEMERAL_PACKAGE; + resolverIntent.setAction(actionName); + resolvers = queryIntentServicesInternal(resolverIntent, null, + resolveFlags, UserHandle.USER_SYSTEM, callingUid, false /*includeInstantApps*/); + } final int N = resolvers.size(); if (N == 0) { if (DEBUG_EPHEMERAL) { @@ -3089,7 +3102,7 @@ public class PackageManagerService extends IPackageManager.Stub { Slog.v(TAG, "Ephemeral resolver found;" + " pkg: " + packageName + ", info:" + info); } - return new ComponentName(packageName, info.serviceInfo.name); + return new Pair<>(new ComponentName(packageName, info.serviceInfo.name), actionName); } if (DEBUG_EPHEMERAL) { Slog.v(TAG, "Ephemeral resolver NOT found"); @@ -3097,7 +3110,7 @@ public class PackageManagerService extends IPackageManager.Stub { return null; } - private @Nullable ActivityInfo getEphemeralInstallerLPr() { + private @Nullable ActivityInfo getInstantAppInstallerLPr() { final Intent intent = new Intent(Intent.ACTION_INSTALL_INSTANT_APP_PACKAGE); intent.addCategory(Intent.CATEGORY_DEFAULT); intent.setDataAndType(Uri.fromFile(new File("foo.apk")), PACKAGE_MIME_TYPE); @@ -3106,8 +3119,17 @@ public class PackageManagerService extends IPackageManager.Stub { MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE | (!Build.IS_DEBUGGABLE ? MATCH_SYSTEM_ONLY : 0); - final List<ResolveInfo> matches = queryIntentActivitiesInternal(intent, PACKAGE_MIME_TYPE, + List<ResolveInfo> matches = queryIntentActivitiesInternal(intent, PACKAGE_MIME_TYPE, resolveFlags, UserHandle.USER_SYSTEM); + // temporarily look for the old action + if (matches.isEmpty()) { + if (DEBUG_EPHEMERAL) { + Slog.d(TAG, "Ephemeral installer not found with new action; try old one"); + } + intent.setAction(Intent.ACTION_INSTALL_EPHEMERAL_PACKAGE); + matches = queryIntentActivitiesInternal(intent, PACKAGE_MIME_TYPE, + resolveFlags, UserHandle.USER_SYSTEM); + } Iterator<ResolveInfo> iter = matches.iterator(); while (iter.hasNext()) { final ResolveInfo rInfo = iter.next(); @@ -3130,14 +3152,23 @@ public class PackageManagerService extends IPackageManager.Stub { } } - private @Nullable ComponentName getEphemeralResolverSettingsLPr( + private @Nullable ComponentName getInstantAppResolverSettingsLPr( @NonNull ComponentName resolver) { final Intent intent = new Intent(Intent.ACTION_INSTANT_APP_RESOLVER_SETTINGS) .addCategory(Intent.CATEGORY_DEFAULT) .setPackage(resolver.getPackageName()); final int resolveFlags = MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE; - final List<ResolveInfo> matches = queryIntentActivitiesInternal(intent, null, resolveFlags, + List<ResolveInfo> matches = queryIntentActivitiesInternal(intent, null, resolveFlags, UserHandle.USER_SYSTEM); + // temporarily look for the old action + if (matches.isEmpty()) { + if (DEBUG_EPHEMERAL) { + Slog.d(TAG, "Ephemeral resolver settings not found with new action; try old one"); + } + intent.setAction(Intent.ACTION_EPHEMERAL_RESOLVER_SETTINGS); + matches = queryIntentActivitiesInternal(intent, null, resolveFlags, + UserHandle.USER_SYSTEM); + } if (matches.isEmpty()) { return null; } @@ -5702,7 +5733,7 @@ public class PackageManagerService extends IPackageManager.Stub { if (mInstantAppResolverConnection == null) { return false; } - if (mInstantAppInstallerComponent == null) { + if (mInstantAppInstallerActivity == null) { return false; } if (intent.getComponent() != null) { @@ -5723,20 +5754,23 @@ public class PackageManagerService extends IPackageManager.Stub { synchronized (mPackages) { final int count = (resolvedActivities == null ? 0 : resolvedActivities.size()); for (int n = 0; n < count; n++) { - ResolveInfo info = resolvedActivities.get(n); - String packageName = info.activityInfo.packageName; - PackageSetting ps = mSettings.mPackages.get(packageName); + final ResolveInfo info = resolvedActivities.get(n); + final String packageName = info.activityInfo.packageName; + final PackageSetting ps = mSettings.mPackages.get(packageName); if (ps != null) { - // Try to get the status from User settings first - long packedStatus = getDomainVerificationStatusLPr(ps, userId); - int status = (int) (packedStatus >> 32); - if (status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS + // only check domain verification status if the app is not a browser + if (!info.handleAllWebDataURI) { + // Try to get the status from User settings first + final long packedStatus = getDomainVerificationStatusLPr(ps, userId); + final int status = (int) (packedStatus >> 32); + if (status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS || status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK) { - if (DEBUG_EPHEMERAL) { - Slog.v(TAG, "DENY ephemeral apps;" - + " pkg: " + packageName + ", status: " + status); + if (DEBUG_EPHEMERAL) { + Slog.v(TAG, "DENY instant app;" + + " pkg: " + packageName + ", status: " + status); + } + return false; } - return false; } if (ps.getInstantApp(userId)) { if (DEBUG_EPHEMERAL) { @@ -8543,6 +8577,7 @@ public class PackageManagerService extends IPackageManager.Stub { @Override public boolean performDexOptSecondary(String packageName, String compilerFilter, boolean force) { + mDexManager.reconcileSecondaryDexFiles(packageName); return mDexManager.dexoptSecondaryDex(packageName, compilerFilter, force); } @@ -11378,6 +11413,8 @@ public class PackageManagerService extends IPackageManager.Stub { for (int i=0; i<N; i++) { final String name = pkg.requestedPermissions.get(i); final BasePermission bp = mSettings.mPermissions.get(name); + final boolean appSupportsRuntimePermissions = pkg.applicationInfo.targetSdkVersion + >= Build.VERSION_CODES.M; if (DEBUG_INSTALL) { Log.i(TAG, "Package " + pkg.packageName + " checking " + name + ": " + bp); @@ -11399,6 +11436,12 @@ public class PackageManagerService extends IPackageManager.Stub { continue; } + if (bp.isRuntimeOnly() && !appSupportsRuntimePermissions) { + Log.i(TAG, "Denying runtime-only permission " + bp.name + " for package " + + pkg.packageName); + continue; + } + final String perm = bp.name; boolean allowedSig = false; int grant = GRANT_DENIED; @@ -11414,8 +11457,6 @@ public class PackageManagerService extends IPackageManager.Stub { } final int level = bp.protectionLevel & PermissionInfo.PROTECTION_MASK_BASE; - final boolean appSupportsRuntimePermissions = pkg.applicationInfo.targetSdkVersion - >= Build.VERSION_CODES.M; switch (level) { case PermissionInfo.PROTECTION_NORMAL: { // For all apps normal permissions are install time ones. @@ -13024,7 +13065,18 @@ public class PackageManagerService extends IPackageManager.Stub { intent.setComponent(DEFAULT_CONTAINER_COMPONENT); IActivityManager am = ActivityManager.getService(); if (am != null) { + int dcsUid = -1; + synchronized (mPackages) { + if (!mDefaultContainerWhitelisted) { + mDefaultContainerWhitelisted = true; + PackageSetting ps = mSettings.mPackages.get(DEFAULT_CONTAINER_PACKAGE); + dcsUid = UserHandle.getUid(UserHandle.USER_SYSTEM, ps.appId); + } + } try { + if (dcsUid > 0) { + am.backgroundWhitelistUid(dcsUid); + } am.startService(null, intent, null, -1, null, false, mContext.getOpPackageName(), UserHandle.USER_SYSTEM); } catch (RemoteException e) { @@ -17037,7 +17089,7 @@ public class PackageManagerService extends IPackageManager.Stub { if (res.returnCode == PackageManager.INSTALL_SUCCEEDED) { updateSequenceNumberLP(pkgName, res.newUsers); - updateInstantAppInstallerLocked(); + updateInstantAppInstallerLocked(pkgName); } } } @@ -17613,7 +17665,7 @@ public class PackageManagerService extends IPackageManager.Stub { mInstantAppRegistry.onPackageUninstalledLPw(pkg, info.removedUsers); } updateSequenceNumberLP(packageName, info.removedUsers); - updateInstantAppInstallerLocked(); + updateInstantAppInstallerLocked(packageName); } } } @@ -19976,7 +20028,7 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName()); updateSequenceNumberLP(packageName, new int[] { userId }); final long callingId = Binder.clearCallingIdentity(); try { - updateInstantAppInstallerLocked(); + updateInstantAppInstallerLocked(packageName); } finally { Binder.restoreCallingIdentity(callingId); } @@ -23168,7 +23220,8 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName()); @Override public boolean isInstantAppInstallerComponent(ComponentName component) { synchronized (mPackages) { - return component != null && component.equals(mInstantAppInstallerComponent); + return mInstantAppInstallerActivity != null + && mInstantAppInstallerActivity.getComponentName().equals(component); } } diff --git a/services/core/java/com/android/server/pm/dex/DexManager.java b/services/core/java/com/android/server/pm/dex/DexManager.java index c693a475ed59..3d7cedce522a 100644 --- a/services/core/java/com/android/server/pm/dex/DexManager.java +++ b/services/core/java/com/android/server/pm/dex/DexManager.java @@ -19,7 +19,7 @@ package com.android.server.pm.dex; import android.content.pm.ApplicationInfo; import android.content.pm.IPackageManager; import android.content.pm.PackageInfo; -import android.content.pm.PackageParser; +import android.os.FileUtils; import android.os.RemoteException; import android.os.storage.StorageManager; import android.os.UserHandle; @@ -93,7 +93,7 @@ public class DexManager { * Note that this method is invoked when apps load dex files and it should * return as fast as possible. * - * @param loadingPackage the package performing the load + * @param loadingAppInfo the package performing the load * @param dexPaths the list of dex files being loaded * @param loaderIsa the ISA of the app loading the dex files * @param loaderUserId the user id which runs the code loading the dex files @@ -191,8 +191,7 @@ public class DexManager { throw new IllegalArgumentException( "notifyPackageInstalled called with USER_ALL"); } - cachePackageCodeLocation(pi.packageName, pi.applicationInfo.sourceDir, - pi.applicationInfo.splitSourceDirs, pi.applicationInfo.dataDir, userId); + cachePackageInfo(pi, userId); } /** @@ -231,13 +230,32 @@ public class DexManager { } } - public void cachePackageCodeLocation(String packageName, String baseCodePath, - String[] splitCodePaths, String dataDir, int userId) { + /** + * Caches the code location from the given package info. + */ + private void cachePackageInfo(PackageInfo pi, int userId) { + ApplicationInfo ai = pi.applicationInfo; + String[] dataDirs = new String[] {ai.dataDir, ai.deviceProtectedDataDir, + ai.credentialProtectedDataDir}; + cachePackageCodeLocation(pi.packageName, ai.sourceDir, ai.splitSourceDirs, + dataDirs, userId); + } + + private void cachePackageCodeLocation(String packageName, String baseCodePath, + String[] splitCodePaths, String[] dataDirs, int userId) { PackageCodeLocations pcl = putIfAbsent(mPackageCodeLocationsCache, packageName, new PackageCodeLocations(packageName, baseCodePath, splitCodePaths)); pcl.updateCodeLocation(baseCodePath, splitCodePaths); - if (dataDir != null) { - pcl.mergeAppDataDirs(dataDir, userId); + if (dataDirs != null) { + for (String dataDir : dataDirs) { + // The set of data dirs includes deviceProtectedDataDir and + // credentialProtectedDataDir which might be null for shared + // libraries. Currently we don't track these but be lenient + // and check in case we ever decide to store their usage data. + if (dataDir != null) { + pcl.mergeAppDataDirs(dataDir, userId); + } + } } } @@ -250,8 +268,7 @@ public class DexManager { int userId = entry.getKey(); for (PackageInfo pi : packageInfoList) { // Cache the code locations. - cachePackageCodeLocation(pi.packageName, pi.applicationInfo.sourceDir, - pi.applicationInfo.splitSourceDirs, pi.applicationInfo.dataDir, userId); + cachePackageInfo(pi, userId); // Cache a map from package name to the set of user ids who installed the package. // We will use it to sync the data and remove obsolete entries from @@ -329,6 +346,7 @@ public class DexManager { mPackageDexUsage.removeUserPackage(packageName, dexUseInfo.getOwnerUserId()); continue; } + int result = pdo.dexOptSecondaryDexPath(pkg.applicationInfo, dexPath, dexUseInfo.getLoaderIsas(), compilerFilter, dexUseInfo.isUsedByOtherApps()); success = success && (result != PackageDexOptimizer.DEX_OPT_FAILED); @@ -350,7 +368,7 @@ public class DexManager { // Nothing to reconcile. return; } - Set<String> dexFilesToRemove = new HashSet<>(); + boolean updated = false; for (Map.Entry<String, DexUseInfo> entry : useInfo.getDexUseInfoMap().entrySet()) { String dexPath = entry.getKey(); @@ -378,14 +396,16 @@ public class DexManager { } ApplicationInfo info = pkg.applicationInfo; int flags = 0; - if (info.dataDir.equals(info.deviceProtectedDataDir)) { + if (info.deviceProtectedDataDir != null && + FileUtils.contains(info.deviceProtectedDataDir, dexPath)) { flags |= StorageManager.FLAG_STORAGE_DE; - } else if (info.dataDir.equals(info.credentialProtectedDataDir)) { + } else if (info.credentialProtectedDataDir!= null && + FileUtils.contains(info.credentialProtectedDataDir, dexPath)) { flags |= StorageManager.FLAG_STORAGE_CE; } else { - Slog.e(TAG, "Could not infer CE/DE storage for package " + info.packageName); - updated = mPackageDexUsage.removeUserPackage( - packageName, dexUseInfo.getOwnerUserId()) || updated; + Slog.e(TAG, "Could not infer CE/DE storage for path " + dexPath); + updated = mPackageDexUsage.removeDexFile( + packageName, dexPath, dexUseInfo.getOwnerUserId()) || updated; continue; } diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 95fb5af37b60..6633efdc4141 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -5255,8 +5255,11 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } + // Don't allow snapshots to influence SystemUI visibility flags. + // TODO: Revisit this once SystemUI flags for snapshots are handled correctly boolean appWindow = attrs.type >= FIRST_APPLICATION_WINDOW - && attrs.type < FIRST_SYSTEM_WINDOW; + && attrs.type < FIRST_SYSTEM_WINDOW + && (attrs.privateFlags & PRIVATE_FLAG_TASK_SNAPSHOT) == 0; final int stackId = win.getStackId(); if (mTopFullscreenOpaqueWindowState == null && visible) { if ((fl & FLAG_FORCE_NOT_FULLSCREEN) != 0) { diff --git a/services/core/java/com/android/server/vr/CompatibilityDisplay.java b/services/core/java/com/android/server/vr/CompatibilityDisplay.java index ae1d50ff1f71..ee615fd6fde4 100644 --- a/services/core/java/com/android/server/vr/CompatibilityDisplay.java +++ b/services/core/java/com/android/server/vr/CompatibilityDisplay.java @@ -8,7 +8,7 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.graphics.ImageFormat; +import android.graphics.PixelFormat; import android.hardware.display.DisplayManager; import android.hardware.display.VirtualDisplay; import android.media.ImageReader; @@ -279,7 +279,7 @@ class CompatibilityDisplay { */ private void startImageReader() { if (mImageReader == null) { - mImageReader = ImageReader.newInstance(WIDTH, HEIGHT, ImageFormat.RAW_PRIVATE, + mImageReader = ImageReader.newInstance(WIDTH, HEIGHT, PixelFormat.RGBA_8888, 2 /* maxImages */); } synchronized (mVdLock) { diff --git a/services/core/java/com/android/server/wm/AppWindowContainerController.java b/services/core/java/com/android/server/wm/AppWindowContainerController.java index 2bc3c5f9abba..4b4be40880ee 100644 --- a/services/core/java/com/android/server/wm/AppWindowContainerController.java +++ b/services/core/java/com/android/server/wm/AppWindowContainerController.java @@ -566,7 +566,7 @@ public class AppWindowContainerController return false; } - mContainer.startingData = new SnapshotStartingData(mService, snapshot); + mContainer.startingData = new SnapshotStartingData(mService, snapshot.getSnapshot()); scheduleAddStartingWindow(); return true; } diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java index 1decf4e19e9f..a8664a5727ab 100644 --- a/services/core/java/com/android/server/wm/AppWindowToken.java +++ b/services/core/java/com/android/server/wm/AppWindowToken.java @@ -505,6 +505,13 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree getController().removeStartingWindow(); } + // If this window was animating, then we need to ensure that the app transition notifies + // that animations have completed in WMS.handleAnimatingStoppedAndTransitionLocked(), so + // add to that list now + if (mAppAnimator.animating) { + mService.mNoAnimationNotifyOnTransitionFinished.add(token); + } + final TaskStack stack = getTask().mStack; if (delayed && !isEmpty()) { // set the token aside because it has an active animation to be finished diff --git a/services/core/java/com/android/server/wm/BoundsAnimationController.java b/services/core/java/com/android/server/wm/BoundsAnimationController.java index 9f0ed2100462..7b8057cafbb4 100644 --- a/services/core/java/com/android/server/wm/BoundsAnimationController.java +++ b/services/core/java/com/android/server/wm/BoundsAnimationController.java @@ -61,17 +61,21 @@ public class BoundsAnimationController { extends WindowManagerInternal.AppTransitionListener implements Runnable { public void onAppTransitionCancelledLocked() { + if (DEBUG) Slog.d(TAG, "onAppTransitionCancelledLocked:" + + " mFinishAnimationAfterTransition=" + mFinishAnimationAfterTransition); animationFinished(); } public void onAppTransitionFinishedLocked(IBinder token) { + if (DEBUG) Slog.d(TAG, "onAppTransitionFinishedLocked:" + + " mFinishAnimationAfterTransition=" + mFinishAnimationAfterTransition); animationFinished(); } private void animationFinished() { if (mFinishAnimationAfterTransition) { mHandler.removeCallbacks(this); - // This might end up calling into activity manager which will be bad since we have the - // window manager lock held at this point. Post a message to take care of the processing - // so we don't deadlock. + // This might end up calling into activity manager which will be bad since we have + // the window manager lock held at this point. Post a message to take care of the + // processing so we don't deadlock. mHandler.post(this); } } @@ -195,6 +199,7 @@ public class BoundsAnimationController { if (!mTarget.setPinnedStackSize(mTmpRect, mTmpTaskBounds)) { // Whoops, the target doesn't feel like animating anymore. Let's immediately finish // any further animation. + if (DEBUG) Slog.d(TAG, "animateUpdate: cancelled"); animation.cancel(); } } @@ -203,7 +208,9 @@ public class BoundsAnimationController { public void onAnimationEnd(Animator animation) { if (DEBUG) Slog.d(TAG, "onAnimationEnd: mTarget=" + mTarget + " mMoveToFullScreen=" + mMoveToFullScreen - + " mSkipAnimationEnd=" + mSkipAnimationEnd); + + " mSkipAnimationEnd=" + mSkipAnimationEnd + + " mFinishAnimationAfterTransition=" + mFinishAnimationAfterTransition + + " mAppTransitionIsRunning=" + mAppTransition.isRunning()); // There could be another animation running. For example in the // move to fullscreen case, recents will also be closing while the diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 4ebf1fcb7206..21be74242aab 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -665,7 +665,8 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo } } } - if (!winAnimator.isAnimationStarting() && !winAnimator.isWaitingForOpening()) { + if ((!winAnimator.isAnimationStarting() && !winAnimator.isWaitingForOpening()) || + winAnimator.isDummyAnimation()) { // Updates the shown frame before we set up the surface. This is needed // because the resizing could change the top-left position (in addition to // size) of the window. setSurfaceBoundariesLocked uses mShownPosition to @@ -2920,11 +2921,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo if (stack != null) { stack.getBounds(frame); } - - // We want to screenshot with the exact bounds of the surface of the app. Thus, - // intersect it with the frame. - frame.intersect(w.mFrame); - }else if (!mutableIncludeFullDisplay.value && !w.mIsWallpaper) { + } else if (!mutableIncludeFullDisplay.value && !w.mIsWallpaper) { final Rect wf = w.mFrame; final Rect cr = w.mContentInsets; int left = wf.left + cr.left; diff --git a/services/core/java/com/android/server/wm/SnapshotStartingData.java b/services/core/java/com/android/server/wm/SnapshotStartingData.java index 35f35db5ada3..e73d4d2559fb 100644 --- a/services/core/java/com/android/server/wm/SnapshotStartingData.java +++ b/services/core/java/com/android/server/wm/SnapshotStartingData.java @@ -16,7 +16,6 @@ package com.android.server.wm; -import android.app.ActivityManager.TaskSnapshot; import android.graphics.GraphicBuffer; import android.view.WindowManagerPolicy.StartingSurface; @@ -26,9 +25,9 @@ import android.view.WindowManagerPolicy.StartingSurface; class SnapshotStartingData extends StartingData { private final WindowManagerService mService; - private final TaskSnapshot mSnapshot; + private final GraphicBuffer mSnapshot; - SnapshotStartingData(WindowManagerService service, TaskSnapshot snapshot) { + SnapshotStartingData(WindowManagerService service, GraphicBuffer snapshot) { super(service); mService = service; mSnapshot = snapshot; diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index b816d8199aa6..3ffb093ba386 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -17,14 +17,15 @@ package com.android.server.wm; import static android.app.ActivityManager.RESIZE_MODE_SYSTEM_SCREEN_ROTATION; -import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID; import static android.app.ActivityManager.StackId.PINNED_STACK_ID; +import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID; import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_LANDSCAPE_ONLY; -import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_PORTRAIT_ONLY; import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_PRESERVE_ORIENTATION; +import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_PORTRAIT_ONLY; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET; import static com.android.server.EventLogTags.WM_TASK_REMOVED; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STACK; +import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TASK_MOVEMENT; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; @@ -39,6 +40,7 @@ import android.view.DisplayInfo; import android.view.Surface; import com.android.internal.annotations.VisibleForTesting; +import com.android.server.EventLogTags; import java.io.PrintWriter; diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java index 48b01f40fc65..b8d0b8c096fe 100644 --- a/services/core/java/com/android/server/wm/TaskSnapshotController.java +++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java @@ -28,7 +28,6 @@ import android.app.ActivityManager.StackId; import android.app.ActivityManager.TaskSnapshot; import android.graphics.Canvas; import android.graphics.GraphicBuffer; -import android.graphics.Rect; import android.os.Environment; import android.util.ArraySet; import android.view.WindowManagerPolicy.StartingSurface; @@ -153,7 +152,7 @@ class TaskSnapshotController { * MANAGER LOCK WHEN CALLING THIS METHOD! */ StartingSurface createStartingSurface(AppWindowToken token, - TaskSnapshot snapshot) { + GraphicBuffer snapshot) { return TaskSnapshotSurface.create(mService, token, snapshot); } @@ -167,17 +166,8 @@ class TaskSnapshotController { if (buffer == null) { return null; } - final WindowState mainWindow = top.findMainWindow(); return new TaskSnapshot(buffer, top.getConfiguration().orientation, - minRect(mainWindow.mContentInsets, mainWindow.mStableInsets), false /* reduced */, - 1f /* scale */); - } - - private Rect minRect(Rect rect1, Rect rect2) { - return new Rect(Math.min(rect1.left, rect2.left), - Math.min(rect1.top, rect2.top), - Math.min(rect1.right, rect2.right), - Math.min(rect1.bottom, rect2.bottom)); + top.findMainWindow().mStableInsets, false /* reduced */, 1f /* scale */); } /** diff --git a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java index 1591e480af4f..04403e2712c1 100644 --- a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java +++ b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java @@ -16,35 +16,20 @@ package com.android.server.wm; -import static android.graphics.Color.WHITE; -import static android.graphics.Color.alpha; -import static android.view.SurfaceControl.HIDDEN; -import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; -import static android.view.WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; -import static android.view.WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES; -import static android.view.WindowManager.LayoutParams.FLAG_LOCAL_FOCUS_MODE; +import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; +import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR; +import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN; import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; -import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; -import static android.view.WindowManager.LayoutParams.FLAG_SCALED; -import static android.view.WindowManager.LayoutParams.FLAG_SECURE; -import static android.view.WindowManager.LayoutParams.FLAG_SLIPPERY; -import static android.view.WindowManager.LayoutParams.FLAG_SPLIT_TOUCH; -import static android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH; -import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DRAW_STATUS_BAR_BACKGROUND; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TASK_SNAPSHOT; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; -import static com.android.internal.policy.DecorView.NAVIGATION_BAR_COLOR_VIEW_ATTRIBUTES; -import static com.android.internal.policy.DecorView.STATUS_BAR_COLOR_VIEW_ATTRIBUTES; -import static com.android.internal.policy.DecorView.getColorViewLeftInset; -import static com.android.internal.policy.DecorView.getColorViewTopInset; -import static com.android.internal.policy.DecorView.getNavigationBarRect; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import android.app.ActivityManager.TaskDescription; -import android.app.ActivityManager.TaskSnapshot; +import android.graphics.Bitmap; import android.graphics.Canvas; +import android.graphics.Color; import android.graphics.GraphicBuffer; import android.graphics.Paint; import android.graphics.Rect; @@ -52,22 +37,17 @@ import android.os.Handler; import android.os.Looper; import android.os.Message; import android.os.RemoteException; -import android.os.SystemClock; import android.util.MergedConfiguration; import android.util.Slog; import android.view.IWindowSession; import android.view.Surface; -import android.view.SurfaceControl; -import android.view.SurfaceSession; import android.view.View; import android.view.ViewGroup.LayoutParams; import android.view.WindowManager; import android.view.WindowManagerGlobal; import android.view.WindowManagerPolicy.StartingSurface; -import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.policy.DecorView; import com.android.internal.view.BaseIWindow; /** @@ -77,57 +57,19 @@ import com.android.internal.view.BaseIWindow; */ class TaskSnapshotSurface implements StartingSurface { - private static final long SIZE_MISMATCH_MINIMUM_TIME_MS = 450; - - /** - * When creating the starting window, we use the exact same layout flags such that we end up - * with a window with the exact same dimensions etc. However, these flags are not used in layout - * and might cause other side effects so we exclude them. - */ - private static final int FLAG_INHERIT_EXCLUDES = FLAG_NOT_FOCUSABLE - | FLAG_NOT_TOUCHABLE - | FLAG_NOT_TOUCH_MODAL - | FLAG_ALT_FOCUSABLE_IM - | FLAG_NOT_FOCUSABLE - | FLAG_HARDWARE_ACCELERATED - | FLAG_IGNORE_CHEEK_PRESSES - | FLAG_LOCAL_FOCUS_MODE - | FLAG_SLIPPERY - | FLAG_WATCH_OUTSIDE_TOUCH - | FLAG_SPLIT_TOUCH - | FLAG_SCALED - | FLAG_SECURE; - private static final String TAG = TAG_WITH_CLASS_NAME ? "SnapshotStartingWindow" : TAG_WM; private static final int MSG_REPORT_DRAW = 0; private static final String TITLE_FORMAT = "SnapshotStartingWindow for taskId=%s"; private final Window mWindow; private final Surface mSurface; - private SurfaceControl mChildSurfaceControl; private final IWindowSession mSession; private final WindowManagerService mService; - private final Rect mTaskBounds; - private final Rect mStableInsets = new Rect(); - private final Rect mContentInsets = new Rect(); - private final Rect mFrame = new Rect(); - private final TaskSnapshot mSnapshot; - private final CharSequence mTitle; private boolean mHasDrawn; private boolean mReportNextDraw; - private long mShownTime; - private final Handler mHandler; - private final boolean mSizeMismatch; - private final Paint mBackgroundPaint = new Paint(); - private final Paint mStatusBarPaint = new Paint(); - private final Paint mNavigationBarPaint = new Paint(); - private final int mStatusBarColor; - private final int mNavigationBarColor; - private final int mSysUiVis; - private final int mWindowFlags; - private final int mWindowPrivateFlags; + private Paint mFillBackgroundPaint = new Paint(); static TaskSnapshotSurface create(WindowManagerService service, AppWindowToken token, - TaskSnapshot snapshot) { + GraphicBuffer snapshot) { final WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(); final Window window = new Window(); @@ -136,51 +78,32 @@ class TaskSnapshotSurface implements StartingSurface { final Surface surface = new Surface(); final Rect tmpRect = new Rect(); final Rect tmpFrame = new Rect(); - final Rect taskBounds; - final Rect tmpContentInsets = new Rect(); - final Rect tmpStableInsets = new Rect(); final MergedConfiguration tmpMergedConfiguration = new MergedConfiguration(); - int backgroundColor = WHITE; - int statusBarColor = 0; - int navigationBarColor = 0; - final int sysUiVis; - final int windowFlags; - final int windowPrivateFlags; + int fillBackgroundColor = Color.WHITE; synchronized (service.mWindowMap) { - final WindowState mainWindow = token.findMainWindow(); - if (mainWindow == null) { - Slog.w(TAG, "TaskSnapshotSurface.create: Failed to find main window for token=" - + token); - return null; - } - sysUiVis = mainWindow.getSystemUiVisibility(); - windowFlags = mainWindow.getAttrs().flags; - windowPrivateFlags = mainWindow.getAttrs().privateFlags; - layoutParams.type = TYPE_APPLICATION_STARTING; - layoutParams.format = snapshot.getSnapshot().getFormat(); - layoutParams.flags = (windowFlags & ~FLAG_INHERIT_EXCLUDES) + layoutParams.format = snapshot.getFormat(); + layoutParams.flags = FLAG_LAYOUT_INSET_DECOR + | FLAG_LAYOUT_IN_SCREEN | FLAG_NOT_FOCUSABLE - | FLAG_NOT_TOUCHABLE; + | FLAG_NOT_TOUCHABLE + | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; layoutParams.privateFlags = PRIVATE_FLAG_TASK_SNAPSHOT; layoutParams.token = token.token; layoutParams.width = LayoutParams.MATCH_PARENT; layoutParams.height = LayoutParams.MATCH_PARENT; - layoutParams.systemUiVisibility = sysUiVis; + + // TODO: Inherit behavior whether to draw behind status bar/nav bar. + layoutParams.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN + | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION; final Task task = token.getTask(); if (task != null) { - layoutParams.setTitle(String.format(TITLE_FORMAT, task.mTaskId)); + layoutParams.setTitle(String.format(TITLE_FORMAT,task.mTaskId)); final TaskDescription taskDescription = task.getTaskDescription(); if (taskDescription != null) { - backgroundColor = taskDescription.getBackgroundColor(); - statusBarColor = taskDescription.getStatusBarColor(); - navigationBarColor = taskDescription.getNavigationBarColor(); + fillBackgroundColor = taskDescription.getBackgroundColor(); } - taskBounds = new Rect(); - task.getBounds(taskBounds); - } else { - taskBounds = null; } } try { @@ -195,57 +118,31 @@ class TaskSnapshotSurface implements StartingSurface { // Local call. } final TaskSnapshotSurface snapshotSurface = new TaskSnapshotSurface(service, window, - surface, snapshot, layoutParams.getTitle(), backgroundColor, statusBarColor, - navigationBarColor, sysUiVis, windowFlags, windowPrivateFlags, taskBounds); + surface, fillBackgroundColor); window.setOuter(snapshotSurface); try { session.relayout(window, window.mSeq, layoutParams, -1, -1, View.VISIBLE, 0, tmpFrame, - tmpRect, tmpContentInsets, tmpRect, tmpStableInsets, tmpRect, tmpRect, - tmpMergedConfiguration, surface); + tmpRect, tmpRect, tmpRect, tmpRect, tmpRect, tmpRect, tmpMergedConfiguration, + surface); } catch (RemoteException e) { // Local call. } - snapshotSurface.setFrames(tmpFrame, tmpContentInsets, tmpStableInsets); - snapshotSurface.drawSnapshot(); + snapshotSurface.drawSnapshot(snapshot); return snapshotSurface; } @VisibleForTesting TaskSnapshotSurface(WindowManagerService service, Window window, Surface surface, - TaskSnapshot snapshot, CharSequence title, int backgroundColor, int statusBarColor, - int navigationBarColor, int sysUiVis, int windowFlags, int windowPrivateFlags, - Rect taskBounds) { + int fillBackgroundColor) { mService = service; - mHandler = new Handler(mService.mH.getLooper()); mSession = WindowManagerGlobal.getWindowSession(); mWindow = window; mSurface = surface; - mSnapshot = snapshot; - mTitle = title; - mBackgroundPaint.setColor(backgroundColor != 0 ? backgroundColor : WHITE); - mTaskBounds = taskBounds; - mSysUiVis = sysUiVis; - mWindowFlags = windowFlags; - mWindowPrivateFlags = windowPrivateFlags; - mSizeMismatch = (mFrame.width() != snapshot.getSnapshot().getWidth() - || mFrame.height() != snapshot.getSnapshot().getHeight()); - mStatusBarColor = DecorView.calculateStatusBarColor(windowFlags, - service.mContext.getColor(R.color.system_bar_background_semi_transparent), - statusBarColor); - mNavigationBarColor = navigationBarColor; - mStatusBarPaint.setColor(mStatusBarColor); - mNavigationBarPaint.setColor(navigationBarColor); + mFillBackgroundPaint.setColor(fillBackgroundColor); } @Override public void remove() { - synchronized (mService.mWindowMap) { - final long now = SystemClock.uptimeMillis(); - if (mSizeMismatch && now - mShownTime < SIZE_MISMATCH_MINIMUM_TIME_MS) { - mHandler.postAtTime(this::remove, mShownTime + SIZE_MISMATCH_MINIMUM_TIME_MS); - return; - } - } try { mSession.remove(mWindow); } catch (RemoteException e) { @@ -253,149 +150,31 @@ class TaskSnapshotSurface implements StartingSurface { } } - @VisibleForTesting - void setFrames(Rect frame, Rect contentInsets, Rect stableInsets) { - mFrame.set(frame); - mContentInsets.set(contentInsets); - mStableInsets.set(stableInsets); - } - - private void drawSnapshot() { - final GraphicBuffer buffer = mSnapshot.getSnapshot(); - if (mSizeMismatch) { - // The dimensions of the buffer and the window don't match, so attaching the buffer - // will fail. Better create a child window with the exact dimensions and fill the parent - // window with the background color! - drawSizeMismatchSnapshot(buffer); - } else { - drawSizeMatchSnapshot(buffer); - } + private void drawSnapshot(GraphicBuffer snapshot) { + mSurface.attachAndQueueBuffer(snapshot); final boolean reportNextDraw; synchronized (mService.mWindowMap) { - mShownTime = SystemClock.uptimeMillis(); mHasDrawn = true; reportNextDraw = mReportNextDraw; } if (reportNextDraw) { reportDrawn(); } - } - - private void drawSizeMatchSnapshot(GraphicBuffer buffer) { - mSurface.attachAndQueueBuffer(buffer); - mSurface.release(); - } - - private void drawSizeMismatchSnapshot(GraphicBuffer buffer) { - final SurfaceSession session = new SurfaceSession(mSurface); - - // Keep a reference to it such that it doesn't get destroyed when finalized. - mChildSurfaceControl = new SurfaceControl(session, - mTitle + " - task-snapshot-surface", - buffer.getWidth(), buffer.getHeight(), buffer.getFormat(), HIDDEN); - Surface surface = new Surface(); - surface.copyFrom(mChildSurfaceControl); - - // Clip off ugly navigation bar. - final Rect crop = calculateSnapshotCrop(); - final Rect frame = calculateSnapshotFrame(crop); - SurfaceControl.openTransaction(); - try { - // We can just show the surface here as it will still be hidden as the parent is - // still hidden. - mChildSurfaceControl.show(); - mChildSurfaceControl.setWindowCrop(crop); - mChildSurfaceControl.setPosition(frame.left, frame.top); - } finally { - SurfaceControl.closeTransaction(); - } - surface.attachAndQueueBuffer(buffer); - surface.release(); - - final Canvas c = mSurface.lockCanvas(null); - drawBackgroundAndBars(c, frame); - mSurface.unlockCanvasAndPost(c); mSurface.release(); } @VisibleForTesting - Rect calculateSnapshotCrop() { - final Rect rect = new Rect(); - rect.set(0, 0, mSnapshot.getSnapshot().getWidth(), mSnapshot.getSnapshot().getHeight()); - final Rect insets = mSnapshot.getContentInsets(); - - // Let's remove all system decorations except the status bar, but only if the task is at the - // very top of the screen. - rect.inset(insets.left, mTaskBounds.top != 0 ? insets.top : 0, insets.right, insets.bottom); - return rect; - } - - @VisibleForTesting - Rect calculateSnapshotFrame(Rect crop) { - final Rect frame = new Rect(crop); - - // By default, offset it to to top/left corner - frame.offsetTo(-crop.left, -crop.top); - - // However, we also need to make space for the navigation bar on the left side. - final int colorViewLeftInset = getColorViewLeftInset(mStableInsets.left, - mContentInsets.left); - frame.offset(colorViewLeftInset, 0); - return frame; - } - - @VisibleForTesting - void drawBackgroundAndBars(Canvas c, Rect frame) { - final int statusBarHeight = getStatusBarColorViewHeight(); - final boolean fillHorizontally = c.getWidth() > frame.right; - final boolean fillVertically = c.getHeight() > frame.bottom; + void fillEmptyBackground(Canvas c, Bitmap b) { + final boolean fillHorizontally = c.getWidth() > b.getWidth(); + final boolean fillVertically = c.getHeight() > b.getHeight(); if (fillHorizontally) { - c.drawRect(frame.right, alpha(mStatusBarColor) == 0xFF ? statusBarHeight : 0, - c.getWidth(), fillVertically - ? frame.bottom - : c.getHeight(), - mBackgroundPaint); + c.drawRect(b.getWidth(), 0, c.getWidth(), fillVertically + ? b.getHeight() + : c.getHeight(), + mFillBackgroundPaint); } if (fillVertically) { - c.drawRect(0, frame.bottom, c.getWidth(), c.getHeight(), mBackgroundPaint); - } - drawStatusBarBackground(c, frame, statusBarHeight); - drawNavigationBarBackground(c); - } - - private int getStatusBarColorViewHeight() { - final boolean forceStatusBarBackground = - (mWindowPrivateFlags & PRIVATE_FLAG_FORCE_DRAW_STATUS_BAR_BACKGROUND) != 0; - if (STATUS_BAR_COLOR_VIEW_ATTRIBUTES.isVisible( - mSysUiVis, mStatusBarColor, mWindowFlags, forceStatusBarBackground)) { - return getColorViewTopInset(mStableInsets.top, mContentInsets.top); - } else { - return 0; - } - } - - private boolean isNavigationBarColorViewVisible() { - return NAVIGATION_BAR_COLOR_VIEW_ATTRIBUTES.isVisible( - mSysUiVis, mNavigationBarColor, mWindowFlags, false /* force */); - } - - @VisibleForTesting - void drawStatusBarBackground(Canvas c, Rect frame, int statusBarHeight) { - if (statusBarHeight > 0 && c.getWidth() > frame.right) { - final int rightInset = DecorView.getColorViewRightInset(mStableInsets.right, - mContentInsets.right); - c.drawRect(frame.right, 0, c.getWidth() - rightInset, statusBarHeight, mStatusBarPaint); - } - } - - @VisibleForTesting - void drawNavigationBarBackground(Canvas c) { - final Rect navigationBarRect = new Rect(); - getNavigationBarRect(c.getWidth(), c.getHeight(), mStableInsets, mContentInsets, - navigationBarRect); - final boolean visible = isNavigationBarColorViewVisible(); - if (visible && !navigationBarRect.isEmpty()) { - c.drawRect(navigationBarRect, mNavigationBarPaint); + c.drawRect(0, b.getHeight(), c.getWidth(), c.getHeight(), mFillBackgroundPaint); } } @@ -432,10 +211,10 @@ class TaskSnapshotSurface implements StartingSurface { } }; - @VisibleForTesting - static class Window extends BaseIWindow { + private static class Window extends BaseIWindow { private TaskSnapshotSurface mOuter; + public void setOuter(TaskSnapshotSurface outer) { mOuter = outer; } diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java index 3cb96a1145fc..ee2d5de710c1 100644 --- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java +++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java @@ -441,6 +441,8 @@ class WindowSurfacePlacer { if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "Now closing app " + wtoken); appAnimator.clearThumbnail(); appAnimator.setNullAnimation(); + // TODO: Do we need to add to mNoAnimationNotifyOnTransitionFinished like above if not + // animating? wtoken.setVisibility(animLp, false, transit, false, voiceInteraction); wtoken.updateReportedVisibilityLocked(); // Force the allDrawn flag, because we want to start diff --git a/services/core/jni/com_android_server_location_ContextHubService.cpp b/services/core/jni/com_android_server_location_ContextHubService.cpp index c976fe55a922..d834e2527e08 100644 --- a/services/core/jni/com_android_server_location_ContextHubService.cpp +++ b/services/core/jni/com_android_server_location_ContextHubService.cpp @@ -929,15 +929,15 @@ jobject constructJContextHubInfo(JNIEnv *env, const ContextHub &hub) { db.jniInfo.contextHubInfoCtor); env->CallVoidMethod(jHub, db.jniInfo.contextHubInfoSetId, hub.hubId); - jstrBuf = env->NewStringUTF(hub.name); + jstrBuf = env->NewStringUTF(hub.name.c_str()); env->CallVoidMethod(jHub, db.jniInfo.contextHubInfoSetName, jstrBuf); env->DeleteLocalRef(jstrBuf); - jstrBuf = env->NewStringUTF(hub.vendor); + jstrBuf = env->NewStringUTF(hub.vendor.c_str()); env->CallVoidMethod(jHub, db.jniInfo.contextHubInfoSetVendor, jstrBuf); env->DeleteLocalRef(jstrBuf); - jstrBuf = env->NewStringUTF(hub.toolchain); + jstrBuf = env->NewStringUTF(hub.toolchain.c_str()); env->CallVoidMethod(jHub, db.jniInfo.contextHubInfoSetToolchain, jstrBuf); env->DeleteLocalRef(jstrBuf); diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index a5eac46281f3..f74512a00122 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -614,6 +614,10 @@ public final class SystemServer { mActivityManagerService.setSystemProcess(); traceEnd(); + // DisplayManagerService needs to setup android.display scheduling related policies + // since setSystemProcess() would have overridden policies due to setProcessGroup + mDisplayManagerService.setupSchedulerPolicies(); + // Manages Overlay packages traceBeginAndSlog("StartOverlayManagerService"); mSystemServiceManager.startService(new OverlayManagerService(mSystemContext, installer)); @@ -668,6 +672,7 @@ public final class SystemServer { VibratorService vibrator = null; IStorageManager storageManager = null; NetworkManagementService networkManagement = null; + IpSecService ipSecService = null; NetworkStatsService networkStats = null; NetworkPolicyManagerService networkPolicy = null; ConnectivityService connectivity = null; @@ -1015,6 +1020,15 @@ public final class SystemServer { reportWtf("starting NetworkManagement Service", e); } traceEnd(); + + traceBeginAndSlog("StartIpSecService"); + try { + ipSecService = IpSecService.create(context); + ServiceManager.addService(Context.IPSEC_SERVICE, ipSecService); + } catch (Throwable e) { + reportWtf("starting IpSec Service", e); + } + traceEnd(); } if (!disableNonCoreServices && !disableTextServices) { @@ -1628,6 +1642,7 @@ public final class SystemServer { final TelephonyRegistry telephonyRegistryF = telephonyRegistry; final MediaRouterService mediaRouterF = mediaRouter; final MmsServiceBroker mmsServiceF = mmsService; + final IpSecService ipSecServiceF = ipSecService; // We now tell the activity manager it is okay to run third party // code. It will call back into us once it has gotten to the state @@ -1691,6 +1706,13 @@ public final class SystemServer { .networkScoreAndNetworkManagementServiceReady(); } traceEnd(); + traceBeginAndSlog("MakeIpSecServiceReady"); + try { + if (ipSecServiceF != null) ipSecServiceF.systemReady(); + } catch (Throwable e) { + reportWtf("making IpSec Service ready", e); + } + traceEnd(); traceBeginAndSlog("MakeNetworkStatsServiceReady"); try { if (networkStatsF != null) networkStatsF.systemReady(); diff --git a/services/net/java/android/net/ip/IpManager.java b/services/net/java/android/net/ip/IpManager.java index 590bce186430..61a929404131 100644 --- a/services/net/java/android/net/ip/IpManager.java +++ b/services/net/java/android/net/ip/IpManager.java @@ -23,7 +23,6 @@ import android.content.Context; import android.net.apf.ApfCapabilities; import android.net.apf.ApfFilter; import android.net.DhcpResults; -import android.net.INetd; import android.net.InterfaceConfiguration; import android.net.LinkAddress; import android.net.LinkProperties; @@ -35,12 +34,10 @@ import android.net.dhcp.DhcpClient; import android.net.metrics.IpConnectivityLog; import android.net.metrics.IpManagerEvent; import android.net.util.MultinetworkPolicyTracker; -import android.net.util.NetdService; import android.os.INetworkManagementService; import android.os.Message; import android.os.RemoteException; import android.os.ServiceManager; -import android.os.ServiceSpecificException; import android.os.SystemClock; import android.text.TextUtils; import android.util.LocalLog; @@ -1030,16 +1027,14 @@ public class IpManager extends StateMachine { private boolean startIPv6() { // Set privacy extensions. - final String PREFER_TEMPADDRS = "2"; try { - NetdService.run((INetd netd) -> { - netd.setProcSysNet( - INetd.IPV6, INetd.CONF, mInterfaceName, "use_tempaddr", - PREFER_TEMPADDRS); - }); + mNwService.setInterfaceIpv6PrivacyExtensions(mInterfaceName, true); mNwService.enableIpv6(mInterfaceName); - } catch (IllegalStateException|RemoteException|ServiceSpecificException e) { - logError("Unable to change interface settings: %s", e); + } catch (RemoteException re) { + logError("Unable to change interface settings: %s", re); + return false; + } catch (IllegalStateException ie) { + logError("Unable to change interface settings: %s", ie); return false; } diff --git a/services/print/java/com/android/server/print/UserState.java b/services/print/java/com/android/server/print/UserState.java index 7474a64c32cb..75df892b30b1 100644 --- a/services/print/java/com/android/server/print/UserState.java +++ b/services/print/java/com/android/server/print/UserState.java @@ -45,17 +45,17 @@ import android.os.RemoteException; import android.os.UserHandle; import android.print.IPrintDocumentAdapter; import android.print.IPrintJobStateChangeListener; -import android.printservice.recommendation.IRecommendationsChangeListener; import android.print.IPrintServicesChangeListener; import android.print.IPrinterDiscoveryObserver; import android.print.PrintAttributes; import android.print.PrintJobId; import android.print.PrintJobInfo; import android.print.PrintManager; -import android.printservice.recommendation.RecommendationInfo; import android.print.PrinterId; import android.print.PrinterInfo; import android.printservice.PrintServiceInfo; +import android.printservice.recommendation.IRecommendationsChangeListener; +import android.printservice.recommendation.RecommendationInfo; import android.provider.DocumentsContract; import android.provider.Settings; import android.text.TextUtils; @@ -72,8 +72,9 @@ import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.os.BackgroundThread; import com.android.internal.os.SomeArgs; import com.android.server.print.RemotePrintService.PrintServiceCallbacks; +import com.android.server.print.RemotePrintServiceRecommendationService + .RemotePrintServiceRecommendationServiceCallbacks; import com.android.server.print.RemotePrintSpooler.PrintSpoolerCallbacks; -import com.android.server.print.RemotePrintServiceRecommendationService.RemotePrintServiceRecommendationServiceCallbacks; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -882,7 +883,7 @@ final class UserState implements PrintSpoolerCallbacks, PrintServiceCallbacks, + android.Manifest.permission.BIND_PRINT_SERVICE); continue; } - tempPrintServices.add(PrintServiceInfo.create(installedService, mContext)); + tempPrintServices.add(PrintServiceInfo.create(mContext, installedService)); } mInstalledServices.clear(); diff --git a/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java b/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java index 40af2f89c93a..ad593be199b7 100644 --- a/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java +++ b/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java @@ -497,149 +497,6 @@ public class RankingHelperTest { new NotificationChannel("bananas", "bananas", IMPORTANCE_LOW), true); } - @Test - public void testUpdate_userLockedImportance() throws Exception { - // all fields locked by user - final NotificationChannel channel = - new NotificationChannel("id2", "name2", IMPORTANCE_LOW); - channel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE); - - mHelper.createNotificationChannel(PKG, UID, channel, false); - - // same id, try to update - final NotificationChannel channel2 = - new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_HIGH); - - mHelper.updateNotificationChannelFromAssistant(PKG, UID, channel2); - - // no fields should be changed - assertEquals(channel, mHelper.getNotificationChannel(PKG, UID, channel.getId(), false)); - } - - @Test - public void testUpdate_userLockedVisibility() throws Exception { - // all fields locked by user - final NotificationChannel channel = - new NotificationChannel("id2", "name2", IMPORTANCE_LOW); - channel.setLockscreenVisibility(Notification.VISIBILITY_SECRET); - channel.lockFields(NotificationChannel.USER_LOCKED_VISIBILITY); - - mHelper.createNotificationChannel(PKG, UID, channel, false); - - // same id, try to update - final NotificationChannel channel2 = - new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_HIGH); - channel2.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC); - - mHelper.updateNotificationChannelFromAssistant(PKG, UID, channel2); - - // no fields should be changed - assertEquals(channel, mHelper.getNotificationChannel(PKG, UID, channel.getId(), false)); - } - - @Test - public void testUpdate_userLockedVibration() throws Exception { - // all fields locked by user - final NotificationChannel channel = - new NotificationChannel("id2", "name2", IMPORTANCE_LOW); - channel.enableLights(false); - channel.lockFields(NotificationChannel.USER_LOCKED_VIBRATION); - - mHelper.createNotificationChannel(PKG, UID, channel, false); - - // same id, try to update - final NotificationChannel channel2 = - new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_HIGH); - channel2.enableVibration(true); - channel2.setVibrationPattern(new long[]{100}); - - mHelper.updateNotificationChannelFromAssistant(PKG, UID, channel2); - - // no fields should be changed - assertEquals(channel, mHelper.getNotificationChannel(PKG, UID, channel.getId(), false)); - } - - @Test - public void testUpdate_userLockedLights() throws Exception { - // all fields locked by user - final NotificationChannel channel = - new NotificationChannel("id2", "name2", IMPORTANCE_LOW); - channel.enableLights(false); - channel.lockFields(NotificationChannel.USER_LOCKED_LIGHTS); - - mHelper.createNotificationChannel(PKG, UID, channel, false); - - // same id, try to update - final NotificationChannel channel2 = - new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_HIGH); - channel2.enableLights(true); - - mHelper.updateNotificationChannelFromAssistant(PKG, UID, channel2); - - // no fields should be changed - assertEquals(channel, mHelper.getNotificationChannel(PKG, UID, channel.getId(), false)); - } - - @Test - public void testUpdate_userLockedPriority() throws Exception { - // all fields locked by user - final NotificationChannel channel = - new NotificationChannel("id2", "name2", IMPORTANCE_LOW); - channel.setBypassDnd(true); - channel.lockFields(NotificationChannel.USER_LOCKED_PRIORITY); - - mHelper.createNotificationChannel(PKG, UID, channel, false); - - // same id, try to update all fields - final NotificationChannel channel2 = - new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_HIGH); - channel2.setBypassDnd(false); - - mHelper.updateNotificationChannelFromAssistant(PKG, UID, channel2); - - // no fields should be changed - assertEquals(channel, mHelper.getNotificationChannel(PKG, UID, channel.getId(), false)); - } - - @Test - public void testUpdate_userLockedRingtone() throws Exception { - // all fields locked by user - final NotificationChannel channel = - new NotificationChannel("id2", "name2", IMPORTANCE_LOW); - channel.setSound(new Uri.Builder().scheme("test").build(), mAudioAttributes); - channel.lockFields(NotificationChannel.USER_LOCKED_SOUND); - - mHelper.createNotificationChannel(PKG, UID, channel, false); - - // same id, try to update all fields - final NotificationChannel channel2 = - new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_HIGH); - channel2.setSound(new Uri.Builder().scheme("test2").build(), mAudioAttributes); - - mHelper.updateNotificationChannelFromAssistant(PKG, UID, channel2); - - // no fields should be changed - assertEquals(channel, mHelper.getNotificationChannel(PKG, UID, channel.getId(), false)); - } - - @Test - public void testUpdate_userLockedBadge() throws Exception { - final NotificationChannel channel = - new NotificationChannel("id2", "name2", IMPORTANCE_LOW); - channel.setShowBadge(true); - channel.lockFields(NotificationChannel.USER_LOCKED_SHOW_BADGE); - - mHelper.createNotificationChannel(PKG, UID, channel, false); - - final NotificationChannel channel2 = - new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_HIGH); - channel2.setShowBadge(false); - - mHelper.updateNotificationChannelFromAssistant(PKG, UID, channel2); - - // no fields should be changed - assertEquals(channel, mHelper.getNotificationChannel(PKG, UID, channel.getId(), false)); - } @Test public void testUpdate() throws Exception { @@ -816,30 +673,6 @@ public class RankingHelperTest { } @Test - public void testUpdateDeletedChannels() throws Exception { - NotificationChannel channel = - new NotificationChannel("id2", "name2", IMPORTANCE_LOW); - mHelper.createNotificationChannel(PKG, UID, channel, true); - - mHelper.deleteNotificationChannel(PKG, UID, channel.getId()); - - channel.setSound(new Uri.Builder().scheme("test").build(), mAudioAttributes); - try { - mHelper.updateNotificationChannel(PKG, UID, channel); - fail("Updated deleted channel"); - } catch (IllegalArgumentException e) { - // :) - } - - try { - mHelper.updateNotificationChannelFromAssistant(PKG, UID, channel); - fail("Updated deleted channel"); - } catch (IllegalArgumentException e) { - // :) - } - } - - @Test public void testCreateDeletedChannel() throws Exception { long[] vibration = new long[]{100, 67, 145, 156}; NotificationChannel channel = diff --git a/services/tests/servicestests/Android.mk b/services/tests/servicestests/Android.mk index bb4507d8386a..d47a67c56317 100644 --- a/services/tests/servicestests/Android.mk +++ b/services/tests/servicestests/Android.mk @@ -27,6 +27,10 @@ LOCAL_STATIC_JAVA_LIBRARIES := \ ShortcutManagerTestUtils \ truth-prebuilt +LOCAL_AIDL_INCLUDES := $(LOCAL_PATH)/aidl + +LOCAL_SRC_FILES += aidl/com/android/servicestests/aidl/INetworkStateObserver.aidl + LOCAL_JAVA_LIBRARIES := android.test.runner LOCAL_PACKAGE_NAME := FrameworksServicesTests diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml index 6c7f146ed680..cc682c4ee985 100644 --- a/services/tests/servicestests/AndroidManifest.xml +++ b/services/tests/servicestests/AndroidManifest.xml @@ -48,24 +48,12 @@ <uses-permission android:name="android.permission.MANAGE_ACTIVITY_STACKS" /> <uses-permission android:name="android.permission.INSTALL_PACKAGES" /> <uses-permission android:name="android.permission.CHANGE_CONFIGURATION" /> + <uses-permission android:name="android.permission.CHANGE_COMPONENT_ENABLED_STATE" /> + <uses-permission android:name="android.permission.DELETE_PACKAGES" /> <application> <uses-library android:name="android.test.runner" /> - <service android:name="com.android.server.AccessibilityManagerServiceTest$MyFirstMockAccessibilityService" - android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"> - <intent-filter> - <action android:name="android.accessibilityservice.AccessibilityService"/> - </intent-filter> - </service> - - <service android:name="com.android.server.AccessibilityManagerServiceTest$MySecondMockAccessibilityService" - android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"> - <intent-filter> - <action android:name="android.accessibilityservice.AccessibilityService"/> - </intent-filter> - </service> - <service android:name="com.android.server.accounts.TestAccountType1AuthenticatorService" android:exported="false"> <intent-filter> diff --git a/services/tests/servicestests/aidl/Android.mk b/services/tests/servicestests/aidl/Android.mk new file mode 100644 index 000000000000..0c9b83962833 --- /dev/null +++ b/services/tests/servicestests/aidl/Android.mk @@ -0,0 +1,23 @@ +# Copyright (C) 2017 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) +LOCAL_MODULE_TAGS := tests +LOCAL_SDK_VERSION := current +LOCAL_SRC_FILES := \ + com/android/servicestests/aidl/INetworkStateObserver.aidl +LOCAL_MODULE := servicestests-aidl +include $(BUILD_STATIC_JAVA_LIBRARY)
\ No newline at end of file diff --git a/core/java/android/service/autofill/AutoFillService.java b/services/tests/servicestests/aidl/com/android/servicestests/aidl/INetworkStateObserver.aidl index c26f679cbda8..ca9fc4c439d2 100644 --- a/core/java/android/service/autofill/AutoFillService.java +++ b/services/tests/servicestests/aidl/com/android/servicestests/aidl/INetworkStateObserver.aidl @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016 The Android Open Source Project + * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,12 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package android.service.autofill; -/** - * @hide - * @deprecated TODO(b/35956626): remove once clients use AutofillService - */ -@Deprecated -public abstract class AutoFillService extends AutofillService { -} +package com.android.servicestests.aidl; + +oneway interface INetworkStateObserver { + /** + * {@param resultData} will be in the format + * NetinfoState|NetinfoDetailedState|RealConnectionCheck|RealConnectionCheckDetails|Netinfo. + * For detailed info, see + * servicestests/test-apps/ConnTestApp/.../ConnTestActivity#checkNetworkStatus + */ + void onNetworkStateChecked(String resultData); +}
\ No newline at end of file diff --git a/services/tests/servicestests/res/raw/conntestapp b/services/tests/servicestests/res/raw/conntestapp Binary files differnew file mode 100644 index 000000000000..6093303658b5 --- /dev/null +++ b/services/tests/servicestests/res/raw/conntestapp diff --git a/services/tests/servicestests/src/com/android/server/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/AccessibilityManagerServiceTest.java deleted file mode 100644 index 340c62405814..000000000000 --- a/services/tests/servicestests/src/com/android/server/AccessibilityManagerServiceTest.java +++ /dev/null @@ -1,762 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server; - -import android.accessibilityservice.AccessibilityService; -import android.accessibilityservice.AccessibilityServiceInfo; -import android.content.ComponentName; -import android.content.Context; -import android.content.pm.ServiceInfo; -import android.os.IBinder; -import android.os.Message; -import android.os.ServiceManager; -import android.os.SystemClock; -import android.os.UserHandle; -import android.provider.Settings; -import android.test.AndroidTestCase; -import android.test.suitebuilder.annotation.LargeTest; -import android.view.accessibility.AccessibilityEvent; -import android.view.accessibility.AccessibilityManager; -import android.view.accessibility.IAccessibilityManager; -import android.view.accessibility.IAccessibilityManagerClient; - -import com.android.internal.util.IntPair; - -/** - * This test exercises the - * {@link com.android.server.accessibility.AccessibilityManagerService} by mocking the - * {@link android.view.accessibility.AccessibilityManager} which talks to to the - * service. The service itself is interacting with the platform. Note: Testing - * the service in full isolation would require significant amount of work for - * mocking all system interactions. It would also require a lot of mocking code. - */ -public class AccessibilityManagerServiceTest extends AndroidTestCase { - - /** - * Timeout required for pending Binder calls or event processing to - * complete. - */ - private static final long TIMEOUT_BINDER_CALL = 100; - - /** - * Timeout in which we are waiting for the system to start the mock - * accessibility services. - */ - private static final long TIMEOUT_START_MOCK_ACCESSIBILITY_SERVICES = 1000; - - /** - * Timeout used for testing that a service is notified only upon a - * notification timeout. - */ - private static final long TIMEOUT_TEST_NOTIFICATION_TIMEOUT = 300; - - /** - * The interface used to talk to the tested service. - */ - private IAccessibilityManager mManagerService; - - @Override - protected void setUp() throws Exception { - // Reset the state. - ensureOnlyMockServicesEnabled(getContext(), false, false); - } - - @Override - public void setContext(Context context) { - super.setContext(context); - if (MyFirstMockAccessibilityService.sComponentName == null) { - MyFirstMockAccessibilityService.sComponentName = new ComponentName( - context.getPackageName(), MyFirstMockAccessibilityService.class.getName()) - .flattenToShortString(); - } - if (MySecondMockAccessibilityService.sComponentName == null) { - MySecondMockAccessibilityService.sComponentName = new ComponentName( - context.getPackageName(), MySecondMockAccessibilityService.class.getName()) - .flattenToShortString(); - } - } - - /** - * Creates a new instance. - */ - public AccessibilityManagerServiceTest() { - IBinder iBinder = ServiceManager.getService(Context.ACCESSIBILITY_SERVICE); - mManagerService = IAccessibilityManager.Stub.asInterface(iBinder); - } - - @LargeTest - public void testAddClient_AccessibilityDisabledThenEnabled() throws Exception { - // at least some service must be enabled, otherwise accessibility will always be disabled. - ensureOnlyMockServicesEnabled(mContext, true, false); - - // make sure accessibility is disabled - ensureAccessibilityEnabled(mContext, false); - - // create a client mock instance - MyMockAccessibilityManagerClient mockClient = new MyMockAccessibilityManagerClient(); - - // invoke the method under test - final int stateFlagsDisabled = - IntPair.first(mManagerService.addClient(mockClient, UserHandle.USER_CURRENT)); - boolean enabledAccessibilityDisabled = - (stateFlagsDisabled & AccessibilityManager.STATE_FLAG_ACCESSIBILITY_ENABLED) != 0; - - // check expected result - assertFalse("The client must be disabled since accessibility is disabled.", - enabledAccessibilityDisabled); - - // enable accessibility - ensureAccessibilityEnabled(mContext, true); - - // invoke the method under test - final int stateFlagsEnabled = - IntPair.first(mManagerService.addClient(mockClient, UserHandle.USER_CURRENT)); - boolean enabledAccessibilityEnabled = - (stateFlagsEnabled & AccessibilityManager.STATE_FLAG_ACCESSIBILITY_ENABLED) != 0; - - // check expected result - assertTrue("The client must be enabled since accessibility is enabled.", - enabledAccessibilityEnabled); - } - - @LargeTest - public void testAddClient_AccessibilityEnabledThenDisabled() throws Exception { - // at least some service must be enabled, otherwise accessibility will always be disabled. - ensureOnlyMockServicesEnabled(mContext, true, false); - - // enable accessibility before registering the client - ensureAccessibilityEnabled(mContext, true); - - // create a client mock instance - MyMockAccessibilityManagerClient mockClient = new MyMockAccessibilityManagerClient(); - - // invoke the method under test - final int stateFlagsEnabled = - IntPair.first(mManagerService.addClient(mockClient, UserHandle.USER_CURRENT)); - boolean enabledAccessibilityEnabled = - (stateFlagsEnabled & AccessibilityManager.STATE_FLAG_ACCESSIBILITY_ENABLED) != 0; - - // check expected result - assertTrue("The client must be enabled since accessibility is enabled.", - enabledAccessibilityEnabled); - - // disable accessibility - ensureAccessibilityEnabled(mContext, false); - - // invoke the method under test - final int stateFlagsDisabled = - IntPair.first(mManagerService.addClient(mockClient, UserHandle.USER_CURRENT)); - boolean enabledAccessibilityDisabled = - (stateFlagsDisabled & AccessibilityManager.STATE_FLAG_ACCESSIBILITY_ENABLED) != 0; - - // check expected result - assertFalse("The client must be disabled since accessibility is disabled.", - enabledAccessibilityDisabled); - } - - @LargeTest - public void testGetAccessibilityServicesList() throws Exception { - boolean firstMockServiceInstalled = false; - boolean secondMockServiceInstalled = false; - - String packageName = getContext().getPackageName(); - String firstMockServiceClassName = MyFirstMockAccessibilityService.class.getName(); - String secondMockServiceClassName = MySecondMockAccessibilityService.class.getName(); - - // look for the two mock services - for (AccessibilityServiceInfo info : mManagerService.getInstalledAccessibilityServiceList( - UserHandle.USER_CURRENT)) { - ServiceInfo serviceInfo = info.getResolveInfo().serviceInfo; - if (packageName.equals(serviceInfo.packageName)) { - if (firstMockServiceClassName.equals(serviceInfo.name)) { - firstMockServiceInstalled = true; - } else if (secondMockServiceClassName.equals(serviceInfo.name)) { - secondMockServiceInstalled = true; - } - } - } - - // check expected result - assertTrue("First mock service must be installed", firstMockServiceInstalled); - assertTrue("Second mock service must be installed", secondMockServiceInstalled); - } - - @LargeTest - public void testSendAccessibilityEvent_OneService_MatchingPackageAndEventType() - throws Exception { - // enable the mock accessibility service - ensureOnlyMockServicesEnabled(mContext, true, false); - - // set the accessibility setting value - ensureAccessibilityEnabled(mContext, true); - - // configure the mock service - MockAccessibilityService service = MyFirstMockAccessibilityService.sInstance; - service.setServiceInfo(MockAccessibilityService.createDefaultInfo()); - - // wait for the binder call to #setService to complete - Thread.sleep(TIMEOUT_BINDER_CALL); - - // create and populate an event to be sent - AccessibilityEvent sentEvent = AccessibilityEvent.obtain(); - fullyPopulateDefaultAccessibilityEvent(sentEvent); - - // set expectations - service.expectEvent(sentEvent); - service.replay(); - - // send the event - mManagerService.sendAccessibilityEvent(sentEvent, UserHandle.USER_CURRENT); - - // verify if all expected methods have been called - assertMockServiceVerifiedWithinTimeout(service); - } - - @LargeTest - public void testSendAccessibilityEvent_OneService_NotMatchingPackage() throws Exception { - // enable the mock accessibility service - ensureOnlyMockServicesEnabled(mContext, true, false); - - // set the accessibility setting value - ensureAccessibilityEnabled(mContext, true); - - // configure the mock service - MockAccessibilityService service = MyFirstMockAccessibilityService.sInstance; - service.setServiceInfo(MockAccessibilityService.createDefaultInfo()); - - // wait for the binder call to #setService to complete - Thread.sleep(TIMEOUT_BINDER_CALL); - - // create and populate an event to be sent - AccessibilityEvent sentEvent = AccessibilityEvent.obtain(); - fullyPopulateDefaultAccessibilityEvent(sentEvent); - sentEvent.setPackageName("no.service.registered.for.this.package"); - - // set expectations - service.replay(); - - // send the event - mManagerService.sendAccessibilityEvent(sentEvent, UserHandle.USER_CURRENT); - - // verify if all expected methods have been called - assertMockServiceVerifiedWithinTimeout(service); - } - - @LargeTest - public void testSendAccessibilityEvent_OneService_NotMatchingEventType() throws Exception { - // enable the mock accessibility service - ensureOnlyMockServicesEnabled(mContext, true, false); - - // set the accessibility setting value - ensureAccessibilityEnabled(mContext, true); - - // configure the mock service - MockAccessibilityService service = MyFirstMockAccessibilityService.sInstance; - service.setServiceInfo(MockAccessibilityService.createDefaultInfo()); - - // wait for the binder call to #setService to complete - Thread.sleep(TIMEOUT_BINDER_CALL); - - // create and populate an event to be sent - AccessibilityEvent sentEvent = AccessibilityEvent.obtain(); - fullyPopulateDefaultAccessibilityEvent(sentEvent); - sentEvent.setEventType(AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED); - - // set expectations - service.replay(); - - // send the event - mManagerService.sendAccessibilityEvent(sentEvent, UserHandle.USER_CURRENT); - - // verify if all expected methods have been called - assertMockServiceVerifiedWithinTimeout(service); - } - - @LargeTest - public void testSendAccessibilityEvent_OneService_NotificationAfterTimeout() throws Exception { - // enable the mock accessibility service - ensureOnlyMockServicesEnabled(mContext, true, false); - - // set the accessibility setting value - ensureAccessibilityEnabled(mContext, true); - - // configure the mock service - MockAccessibilityService service = MyFirstMockAccessibilityService.sInstance; - AccessibilityServiceInfo info = MockAccessibilityService.createDefaultInfo(); - info.notificationTimeout = TIMEOUT_TEST_NOTIFICATION_TIMEOUT; - service.setServiceInfo(info); - - // wait for the binder call to #setService to complete - Thread.sleep(TIMEOUT_BINDER_CALL); - - // create and populate the first event to be sent - AccessibilityEvent firstEvent = AccessibilityEvent.obtain(); - fullyPopulateDefaultAccessibilityEvent(firstEvent); - - // create and populate the second event to be sent - AccessibilityEvent secondEvent = AccessibilityEvent.obtain(); - fullyPopulateDefaultAccessibilityEvent(secondEvent); - - // set expectations - service.expectEvent(secondEvent); - service.replay(); - - // send the events - mManagerService.sendAccessibilityEvent(firstEvent, UserHandle.USER_CURRENT); - mManagerService.sendAccessibilityEvent(secondEvent, UserHandle.USER_CURRENT); - - // wait for #sendAccessibilityEvent to reach the backing service - Thread.sleep(TIMEOUT_BINDER_CALL); - - try { - service.verify(); - fail("No events must be dispatched before the expiration of the notification timeout."); - } catch (IllegalStateException ise) { - /* expected */ - } - - // wait for the configured notification timeout to expire - Thread.sleep(TIMEOUT_TEST_NOTIFICATION_TIMEOUT); - - // verify if all expected methods have been called - assertMockServiceVerifiedWithinTimeout(service); - } - - @LargeTest - public void testSendAccessibilityEvent_TwoServices_MatchingPackageAndEventType_DiffFeedback() - throws Exception { - // enable the mock accessibility services - ensureOnlyMockServicesEnabled(mContext, true, true); - - // set the accessibility setting value - ensureAccessibilityEnabled(mContext, true); - - // configure the first mock service - MockAccessibilityService firstService = MyFirstMockAccessibilityService.sInstance; - AccessibilityServiceInfo firstInfo = MockAccessibilityService.createDefaultInfo(); - firstInfo.feedbackType = AccessibilityServiceInfo.FEEDBACK_AUDIBLE; - firstService.setServiceInfo(firstInfo); - - // configure the second mock service - MockAccessibilityService secondService = MySecondMockAccessibilityService.sInstance; - AccessibilityServiceInfo secondInfo = MockAccessibilityService.createDefaultInfo(); - secondInfo.feedbackType = AccessibilityServiceInfo.FEEDBACK_HAPTIC; - secondService.setServiceInfo(secondInfo); - - // wait for the binder calls to #setService to complete - Thread.sleep(TIMEOUT_BINDER_CALL); - - // create and populate an event to be sent - AccessibilityEvent sentEvent = AccessibilityEvent.obtain(); - fullyPopulateDefaultAccessibilityEvent(sentEvent); - - // set expectations for the first mock service - firstService.expectEvent(sentEvent); - firstService.replay(); - - // set expectations for the second mock service - secondService.expectEvent(sentEvent); - secondService.replay(); - - // send the event - mManagerService.sendAccessibilityEvent(sentEvent, UserHandle.USER_CURRENT); - - // verify if all expected methods have been called - assertMockServiceVerifiedWithinTimeout(firstService); - assertMockServiceVerifiedWithinTimeout(secondService); - } - - @LargeTest - public void testSendAccessibilityEvent_TwoServices_MatchingPackageAndEventType() - throws Exception { - // enable the mock accessibility services - ensureOnlyMockServicesEnabled(mContext, true, true); - - // set the accessibility setting value - ensureAccessibilityEnabled(mContext, true); - - // configure the first mock service - MockAccessibilityService firstService = MyFirstMockAccessibilityService.sInstance; - firstService.setServiceInfo(MockAccessibilityService.createDefaultInfo()); - - // configure the second mock service - MockAccessibilityService secondService = MySecondMockAccessibilityService.sInstance; - secondService.setServiceInfo(MockAccessibilityService.createDefaultInfo()); - - // wait for the binder calls to #setService to complete - Thread.sleep(TIMEOUT_BINDER_CALL); - - // create and populate an event to be sent - AccessibilityEvent sentEvent = AccessibilityEvent.obtain(); - fullyPopulateDefaultAccessibilityEvent(sentEvent); - - // set expectations for the first mock service - firstService.expectEvent(sentEvent); - firstService.replay(); - - // set expectations for the second mock service - secondService.replay(); - - // send the event - mManagerService.sendAccessibilityEvent(sentEvent, UserHandle.USER_CURRENT); - - // verify if all expected methods have been called - assertMockServiceVerifiedWithinTimeout(firstService); - assertMockServiceVerifiedWithinTimeout(secondService); - } - - @LargeTest - public void testSendAccessibilityEvent_TwoServices_MatchingPackageAndEventType_OneDefault() - throws Exception { - // enable the mock accessibility services - ensureOnlyMockServicesEnabled(mContext, true, true); - - // set the accessibility setting value - ensureAccessibilityEnabled(mContext, true); - - // configure the first mock service - MockAccessibilityService firstService = MyFirstMockAccessibilityService.sInstance; - AccessibilityServiceInfo firstInfo = MyFirstMockAccessibilityService.createDefaultInfo(); - firstInfo.flags = AccessibilityServiceInfo.DEFAULT; - firstService.setServiceInfo(firstInfo); - - // configure the second mock service - MockAccessibilityService secondService = MySecondMockAccessibilityService.sInstance; - secondService.setServiceInfo(MySecondMockAccessibilityService.createDefaultInfo()); - - // wait for the binder calls to #setService to complete - Thread.sleep(TIMEOUT_BINDER_CALL); - - // create and populate an event to be sent - AccessibilityEvent sentEvent = AccessibilityEvent.obtain(); - fullyPopulateDefaultAccessibilityEvent(sentEvent); - - // set expectations for the first mock service - firstService.replay(); - - // set expectations for the second mock service - secondService.expectEvent(sentEvent); - secondService.replay(); - - // send the event - mManagerService.sendAccessibilityEvent(sentEvent, UserHandle.USER_CURRENT); - - // verify if all expected methods have been called - assertMockServiceVerifiedWithinTimeout(firstService); - assertMockServiceVerifiedWithinTimeout(secondService); - } - - @LargeTest - public void testSendAccessibilityEvent_TwoServices_MatchingPackageAndEventType_TwoDefault() - throws Exception { - // enable the mock accessibility services - ensureOnlyMockServicesEnabled(mContext, true, true); - - // set the accessibility setting value - ensureAccessibilityEnabled(mContext, true); - - // configure the first mock service - MockAccessibilityService firstService = MyFirstMockAccessibilityService.sInstance; - AccessibilityServiceInfo firstInfo = MyFirstMockAccessibilityService.createDefaultInfo(); - firstInfo.flags = AccessibilityServiceInfo.DEFAULT; - firstService.setServiceInfo(firstInfo); - - // configure the second mock service - MockAccessibilityService secondService = MySecondMockAccessibilityService.sInstance; - AccessibilityServiceInfo secondInfo = MyFirstMockAccessibilityService.createDefaultInfo(); - secondInfo.flags = AccessibilityServiceInfo.DEFAULT; - secondService.setServiceInfo(firstInfo); - - // wait for the binder calls to #setService to complete - Thread.sleep(TIMEOUT_BINDER_CALL); - - // create and populate an event to be sent - AccessibilityEvent sentEvent = AccessibilityEvent.obtain(); - fullyPopulateDefaultAccessibilityEvent(sentEvent); - - // set expectations for the first mock service - firstService.expectEvent(sentEvent); - firstService.replay(); - - // set expectations for the second mock service - secondService.replay(); - - // send the event - mManagerService.sendAccessibilityEvent(sentEvent, UserHandle.USER_CURRENT); - - // verify if all expected methods have been called - assertMockServiceVerifiedWithinTimeout(firstService); - assertMockServiceVerifiedWithinTimeout(secondService); - } - - @LargeTest - public void testInterrupt() throws Exception { - // enable the mock accessibility services - ensureOnlyMockServicesEnabled(mContext, true, true); - - // set the accessibility setting value - ensureAccessibilityEnabled(mContext, true); - - // configure the first mock service - MockAccessibilityService firstService = MyFirstMockAccessibilityService.sInstance; - firstService.setServiceInfo(MockAccessibilityService.createDefaultInfo()); - - // configure the second mock service - MockAccessibilityService secondService = MySecondMockAccessibilityService.sInstance; - secondService.setServiceInfo(MockAccessibilityService.createDefaultInfo()); - - // wait for the binder calls to #setService to complete - Thread.sleep(TIMEOUT_BINDER_CALL); - - // set expectations for the first mock service - firstService.expectInterrupt(); - firstService.replay(); - - // set expectations for the second mock service - secondService.expectInterrupt(); - secondService.replay(); - - // call the method under test - mManagerService.interrupt(UserHandle.USER_CURRENT); - - // verify if all expected methods have been called - assertMockServiceVerifiedWithinTimeout(firstService); - assertMockServiceVerifiedWithinTimeout(secondService); - } - - /** - * Fully populates the {@link AccessibilityEvent} to marshal. - * - * @param sentEvent The event to populate. - */ - private void fullyPopulateDefaultAccessibilityEvent(AccessibilityEvent sentEvent) { - sentEvent.setAddedCount(1); - sentEvent.setBeforeText("BeforeText"); - sentEvent.setChecked(true); - sentEvent.setClassName("foo.bar.baz.Class"); - sentEvent.setContentDescription("ContentDescription"); - sentEvent.setCurrentItemIndex(1); - sentEvent.setEnabled(true); - sentEvent.setEventType(AccessibilityEvent.TYPE_ANNOUNCEMENT); - sentEvent.setEventTime(1000); - sentEvent.setFromIndex(1); - sentEvent.setFullScreen(true); - sentEvent.setItemCount(1); - sentEvent.setPackageName("foo.bar.baz"); - sentEvent.setParcelableData(Message.obtain(null, 1, null)); - sentEvent.setPassword(true); - sentEvent.setRemovedCount(1); - } - - /** - * This class is a mock {@link IAccessibilityManagerClient}. - */ - public class MyMockAccessibilityManagerClient extends IAccessibilityManagerClient.Stub { - int mState; - - public void setState(int state) { - mState = state; - } - - public void notifyServicesStateChanged() {} - - public void setRelevantEventTypes(int eventTypes) {} - - public void setTouchExplorationEnabled(boolean enabled) {} - } - - /** - * Ensures accessibility is in a given state by writing the state to the - * settings and waiting until the accessibility manager service pick it up. - * - * @param context A context handle to access the settings. - * @param enabled The accessibility state to write to the settings. - * @throws Exception If any error occurs. - */ - private void ensureAccessibilityEnabled(Context context, boolean enabled) throws Exception { - boolean isEnabled = Settings.Secure.getInt(context.getContentResolver(), - Settings.Secure.ACCESSIBILITY_ENABLED, 0) == 1; - - if (isEnabled == enabled) { - return; - } - - Settings.Secure.putInt(context.getContentResolver(), Settings.Secure.ACCESSIBILITY_ENABLED, - enabled ? 1 : 0); - - // wait the accessibility manager service to pick the change up - Thread.sleep(TIMEOUT_BINDER_CALL); - } - - /** - * Ensures the only {@link MockAccessibilityService}s with given component - * names are enabled by writing to the system settings and waiting until the - * accessibility manager service picks that up or the - * {@link #TIMEOUT_START_MOCK_ACCESSIBILITY_SERVICES} is exceeded. - * - * @param context A context handle to access the settings. - * @param firstMockServiceEnabled If the first mock accessibility service is enabled. - * @param secondMockServiceEnabled If the second mock accessibility service is enabled. - * @throws IllegalStateException If some of the requested for enabling mock services - * is not properly started. - * @throws Exception Exception If any error occurs. - */ - private void ensureOnlyMockServicesEnabled(Context context, boolean firstMockServiceEnabled, - boolean secondMockServiceEnabled) throws Exception { - String enabledServices = Settings.Secure.getString(context.getContentResolver(), - Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES); - - StringBuilder servicesToEnable = new StringBuilder(); - if (firstMockServiceEnabled) { - servicesToEnable.append(MyFirstMockAccessibilityService.sComponentName).append(":"); - } - if (secondMockServiceEnabled) { - servicesToEnable.append(MySecondMockAccessibilityService.sComponentName).append(":"); - } - - Settings.Secure.putString(context.getContentResolver(), - Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, servicesToEnable.toString()); - - // Optimization. If things will not change, we don't have to do anything. - if (servicesToEnable.equals(enabledServices)) { - return; - } - - // we have enabled the services of interest and need to wait until they - // are instantiated and started (if needed) and the system binds to them - boolean firstMockServiceOK = false; - boolean secondMockServiceOK = false; - long start = SystemClock.uptimeMillis(); - long pollingInterval = TIMEOUT_START_MOCK_ACCESSIBILITY_SERVICES / 6; - - while (SystemClock.uptimeMillis() - start < TIMEOUT_START_MOCK_ACCESSIBILITY_SERVICES) { - firstMockServiceOK = !firstMockServiceEnabled - || (MyFirstMockAccessibilityService.sInstance != null - && MyFirstMockAccessibilityService.sInstance.isSystemBoundAsClient()); - - secondMockServiceOK = !secondMockServiceEnabled - || (MySecondMockAccessibilityService.sInstance != null - && MySecondMockAccessibilityService.sInstance.isSystemBoundAsClient()); - - if (firstMockServiceOK && secondMockServiceOK) { - return; - } - - Thread.sleep(pollingInterval); - } - - StringBuilder message = new StringBuilder(); - message.append("Mock accessibility services not started or system not bound as a client: "); - if (!firstMockServiceOK) { - message.append(MyFirstMockAccessibilityService.sComponentName); - message.append(" "); - } - if (!secondMockServiceOK) { - message.append(MySecondMockAccessibilityService.sComponentName); - } - throw new IllegalStateException(message.toString()); - } - - /** - * Asserts the the mock accessibility service has been successfully verified - * (which is it has received the expected method calls with expected - * arguments) within the {@link #TIMEOUT_BINDER_CALL}. The verified state is - * checked by polling upon small intervals. - * - * @param service The service to verify. - * @throws Exception If the verification has failed with exception after the - * {@link #TIMEOUT_BINDER_CALL}. - */ - private void assertMockServiceVerifiedWithinTimeout(MockAccessibilityService service) - throws Exception { - Exception lastVerifyException = null; - long beginTime = SystemClock.uptimeMillis(); - long pollTimeout = TIMEOUT_BINDER_CALL / 5; - - // poll until the timeout has elapsed - while (SystemClock.uptimeMillis() - beginTime < TIMEOUT_BINDER_CALL) { - // sleep first since immediate call will always fail - try { - Thread.sleep(pollTimeout); - } catch (InterruptedException ie) { - /* ignore */ - } - // poll for verification and if this fails save the exception and - // keep polling - try { - service.verify(); - // reset so it does not accept more events - service.reset(); - return; - } catch (Exception e) { - lastVerifyException = e; - } - } - - // reset, we have already failed - service.reset(); - - // always not null - throw lastVerifyException; - } - - /** - * This class is the first mock {@link AccessibilityService}. - */ - public static class MyFirstMockAccessibilityService extends MockAccessibilityService { - - /** - * The service {@link ComponentName} flattened as a string. - */ - static String sComponentName; - - /** - * Handle to the service instance. - */ - static MyFirstMockAccessibilityService sInstance; - - /** - * Creates a new instance. - */ - public MyFirstMockAccessibilityService() { - sInstance = this; - } - } - - /** - * This class is the first mock {@link AccessibilityService}. - */ - public static class MySecondMockAccessibilityService extends MockAccessibilityService { - - /** - * The service {@link ComponentName} flattened as a string. - */ - static String sComponentName; - - /** - * Handle to the service instance. - */ - static MySecondMockAccessibilityService sInstance; - - /** - * Creates a new instance. - */ - public MySecondMockAccessibilityService() { - sInstance = this; - } - } -} diff --git a/services/tests/servicestests/src/com/android/server/MockAccessibilityService.java b/services/tests/servicestests/src/com/android/server/MockAccessibilityService.java deleted file mode 100644 index e1c5cee752e1..000000000000 --- a/services/tests/servicestests/src/com/android/server/MockAccessibilityService.java +++ /dev/null @@ -1,256 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server; - -import android.accessibilityservice.AccessibilityService; -import android.accessibilityservice.AccessibilityServiceInfo; -import android.content.Intent; -import android.os.Message; -import android.view.accessibility.AccessibilityEvent; - -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; -import java.util.Queue; - -import junit.framework.TestCase; - -/** - * This is the base class for mock {@link AccessibilityService}s. - */ -public abstract class MockAccessibilityService extends AccessibilityService { - - /** - * The event this service expects to receive. - */ - private final Queue<AccessibilityEvent> mExpectedEvents = new LinkedList<AccessibilityEvent>(); - - /** - * Interruption call this service expects to receive. - */ - private boolean mExpectedInterrupt; - - /** - * Flag if the mock is currently replaying. - */ - private boolean mReplaying; - - /** - * Flag if the system is bound as a client to this service. - */ - private boolean mIsSystemBoundAsClient; - - /** - * Creates an {@link AccessibilityServiceInfo} populated with default - * values. - * - * @return The default info. - */ - public static AccessibilityServiceInfo createDefaultInfo() { - AccessibilityServiceInfo defaultInfo = new AccessibilityServiceInfo(); - defaultInfo.eventTypes = AccessibilityEvent.TYPE_ANNOUNCEMENT; - defaultInfo.feedbackType = AccessibilityServiceInfo.FEEDBACK_AUDIBLE; - defaultInfo.flags = 0; - defaultInfo.notificationTimeout = 0; - defaultInfo.packageNames = new String[] { - "foo.bar.baz" - }; - - return defaultInfo; - } - - /** - * Starts replaying the mock. - */ - public void replay() { - mReplaying = true; - } - - /** - * Verifies if all expected service methods have been called. - */ - public void verify() { - if (!mReplaying) { - throw new IllegalStateException("Did you forget to call replay()"); - } - - if (mExpectedInterrupt) { - throw new IllegalStateException("Expected call to #interrupt() not received"); - } - if (!mExpectedEvents.isEmpty()) { - throw new IllegalStateException("Expected a call to onAccessibilityEvent() for " - + "events \"" + mExpectedEvents + "\" not received"); - } - } - - /** - * Resets this instance so it can be reused. - */ - public void reset() { - mExpectedEvents.clear(); - mExpectedInterrupt = false; - mReplaying = false; - } - - /** - * Sets an expected call to - * {@link #onAccessibilityEvent(AccessibilityEvent)} with given event as - * argument. - * - * @param expectedEvent The expected event argument. - */ - public void expectEvent(AccessibilityEvent expectedEvent) { - mExpectedEvents.add(expectedEvent); - } - - /** - * Sets an expected call of {@link #onInterrupt()}. - */ - public void expectInterrupt() { - mExpectedInterrupt = true; - } - - @Override - public void onAccessibilityEvent(AccessibilityEvent receivedEvent) { - if (!mReplaying) { - return; - } - - if (mExpectedEvents.isEmpty()) { - throw new IllegalStateException("Unexpected event: " + receivedEvent); - } - - AccessibilityEvent expectedEvent = mExpectedEvents.poll(); - assertEqualsAccessiblityEvent(expectedEvent, receivedEvent); - } - - @Override - public void onInterrupt() { - if (!mReplaying) { - return; - } - - if (!mExpectedInterrupt) { - throw new IllegalStateException("Unexpected call to onInterrupt()"); - } - - mExpectedInterrupt = false; - } - - @Override - protected void onServiceConnected() { - mIsSystemBoundAsClient = true; - } - - @Override - public boolean onUnbind(Intent intent) { - mIsSystemBoundAsClient = false; - return false; - } - - /** - * Returns if the system is bound as client to this service. - * - * @return True if the system is bound, false otherwise. - */ - public boolean isSystemBoundAsClient() { - return mIsSystemBoundAsClient; - } - - /** - * Compares all properties of the <code>expectedEvent</code> and the - * <code>receviedEvent</code> to verify that the received event is the one - * that is expected. - */ - private void assertEqualsAccessiblityEvent(AccessibilityEvent expectedEvent, - AccessibilityEvent receivedEvent) { - TestCase.assertEquals("addedCount has incorrect value", expectedEvent.getAddedCount(), - receivedEvent.getAddedCount()); - TestCase.assertEquals("beforeText has incorrect value", expectedEvent.getBeforeText(), - receivedEvent.getBeforeText()); - TestCase.assertEquals("checked has incorrect value", expectedEvent.isChecked(), - receivedEvent.isChecked()); - TestCase.assertEquals("className has incorrect value", expectedEvent.getClassName(), - receivedEvent.getClassName()); - TestCase.assertEquals("contentDescription has incorrect value", expectedEvent - .getContentDescription(), receivedEvent.getContentDescription()); - TestCase.assertEquals("currentItemIndex has incorrect value", expectedEvent - .getCurrentItemIndex(), receivedEvent.getCurrentItemIndex()); - TestCase.assertEquals("enabled has incorrect value", expectedEvent.isEnabled(), - receivedEvent.isEnabled()); - TestCase.assertEquals("eventType has incorrect value", expectedEvent.getEventType(), - receivedEvent.getEventType()); - TestCase.assertEquals("fromIndex has incorrect value", expectedEvent.getFromIndex(), - receivedEvent.getFromIndex()); - TestCase.assertEquals("fullScreen has incorrect value", expectedEvent.isFullScreen(), - receivedEvent.isFullScreen()); - TestCase.assertEquals("itemCount has incorrect value", expectedEvent.getItemCount(), - receivedEvent.getItemCount()); - assertEqualsNotificationAsParcelableData(expectedEvent, receivedEvent); - TestCase.assertEquals("password has incorrect value", expectedEvent.isPassword(), - receivedEvent.isPassword()); - TestCase.assertEquals("removedCount has incorrect value", expectedEvent.getRemovedCount(), - receivedEvent.getRemovedCount()); - assertEqualsText(expectedEvent, receivedEvent); - } - - /** - * Compares the {@link android.os.Parcelable} data of the - * <code>expectedEvent</code> and <code>receivedEvent</code> to verify that - * the received event is the one that is expected. - */ - private void assertEqualsNotificationAsParcelableData(AccessibilityEvent expectedEvent, - AccessibilityEvent receivedEvent) { - String message = "parcelableData has incorrect value"; - Message expectedMessage = (Message) expectedEvent.getParcelableData(); - Message receivedMessage = (Message) receivedEvent.getParcelableData(); - - if (expectedMessage == null) { - if (receivedMessage == null) { - return; - } - } - - TestCase.assertNotNull(message, receivedMessage); - - // we do a very simple sanity check since we do not test Message - TestCase.assertEquals(message, expectedMessage.what, receivedMessage.what); - } - - /** - * Compares the text of the <code>expectedEvent</code> and - * <code>receivedEvent</code> by comparing the string representation of the - * corresponding {@link CharSequence}s. - */ - private void assertEqualsText(AccessibilityEvent expectedEvent, - AccessibilityEvent receivedEvent) { - String message = "text has incorrect value"; - List<CharSequence> expectedText = expectedEvent.getText(); - List<CharSequence> receivedText = receivedEvent.getText(); - - TestCase.assertEquals(message, expectedText.size(), receivedText.size()); - - Iterator<CharSequence> expectedTextIterator = expectedText.iterator(); - Iterator<CharSequence> receivedTextIterator = receivedText.iterator(); - - for (int i = 0; i < expectedText.size(); i++) { - // compare the string representation - TestCase.assertEquals(message, expectedTextIterator.next().toString(), - receivedTextIterator.next().toString()); - } - } -} diff --git a/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java index e0ac393290ac..353199ab0b0b 100644 --- a/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java @@ -645,20 +645,6 @@ public class NetworkScoreServiceTest { } @Test - public void testDump_noDumpPermission() { - doThrow(new SecurityException()).when(mContext).enforceCallingOrSelfPermission( - eq(permission.DUMP), anyString()); - - try { - mNetworkScoreService.dump( - new FileDescriptor(), new PrintWriter(new StringWriter()), new String[0]); - fail("SecurityException expected"); - } catch (SecurityException e) { - // expected - } - } - - @Test public void testDump_doesNotCrash() { when(mNetworkScorerAppManager.getActiveScorer()).thenReturn(NEW_SCORER); StringWriter stringWriter = new StringWriter(); diff --git a/services/tests/servicestests/src/com/android/server/AccessibilityManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerTest.java index 92617716e28d..5d09e31a3dc7 100644 --- a/services/tests/servicestests/src/com/android/server/AccessibilityManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerTest.java @@ -14,18 +14,24 @@ * limitations under the License. */ -package com.android.server; +package com.android.server.accessibility; +import static junit.framework.TestCase.assertFalse; +import static junit.framework.TestCase.assertSame; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyInt; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.accessibilityservice.AccessibilityServiceInfo; +import android.app.Instrumentation; +import android.os.Looper; import android.os.UserHandle; -import android.test.AndroidTestCase; -import android.test.suitebuilder.annotation.LargeTest; -import android.test.suitebuilder.annotation.MediumTest; +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.IAccessibilityManager; @@ -33,6 +39,10 @@ import android.view.accessibility.IAccessibilityManagerClient; import com.android.internal.util.IntPair; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @@ -40,44 +50,48 @@ import java.util.ArrayList; import java.util.List; /** - * Tests for the AccessibilityManager which mocking the backing service. + * Tests for the AccessibilityManager by mocking the backing service. */ -public class AccessibilityManagerTest extends AndroidTestCase { - - /** - * Timeout required for pending Binder calls or event processing to - * complete. - */ - public static final long TIMEOUT_BINDER_CALL = 50; - - @Mock - private IAccessibilityManager mMockService; +@RunWith(AndroidJUnit4.class) +public class AccessibilityManagerTest { + private static final boolean WITH_A11Y_ENABLED = true; + private static final boolean WITH_A11Y_DISABLED = false; + + @Mock private IAccessibilityManager mMockService; + private MessageCapturingHandler mHandler; + private Instrumentation mInstrumentation; + + @BeforeClass + public static void oneTimeInitialization() { + if (Looper.myLooper() == null) { + Looper.prepare(); + } + } - @Override + @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); + mHandler = new MessageCapturingHandler(null); + mInstrumentation = InstrumentationRegistry.getInstrumentation(); } private AccessibilityManager createManager(boolean enabled) throws Exception { - if (enabled) { - when(mMockService.addClient(any(IAccessibilityManagerClient.class), anyInt())) - .thenReturn( - IntPair.of(AccessibilityManager.STATE_FLAG_ACCESSIBILITY_ENABLED, - AccessibilityEvent.TYPES_ALL_MASK)); - } else { - when(mMockService.addClient(any(IAccessibilityManagerClient.class), anyInt())) - .thenReturn(IntPair.of(0, AccessibilityEvent.TYPES_ALL_MASK)); - } + long serviceReturnValue = IntPair.of( + (enabled) ? AccessibilityManager.STATE_FLAG_ACCESSIBILITY_ENABLED : 0, + AccessibilityEvent.TYPES_ALL_MASK); + when(mMockService.addClient(any(IAccessibilityManagerClient.class), anyInt())) + .thenReturn(serviceReturnValue); AccessibilityManager manager = - new AccessibilityManager(mContext, mMockService, UserHandle.USER_CURRENT); + new AccessibilityManager(mHandler, mMockService, UserHandle.USER_CURRENT); verify(mMockService).addClient(any(IAccessibilityManagerClient.class), anyInt()); - + mHandler.setCallback(manager.getCallback()); + mHandler.sendAllMessages(); return manager; } - @MediumTest + @Test public void testGetAccessibilityServiceList() throws Exception { // create a list of installed accessibility services the mock service returns List<AccessibilityServiceInfo> expectedServices = new ArrayList<>(); @@ -99,59 +113,50 @@ public class AccessibilityManagerTest extends AndroidTestCase { assertEquals("All expected services must be returned", expectedServices, receivedServices); } - @MediumTest + @Test public void testInterrupt() throws Exception { - AccessibilityManager manager = createManager(true); + AccessibilityManager manager = createManager(WITH_A11Y_ENABLED); manager.interrupt(); verify(mMockService).interrupt(UserHandle.USER_CURRENT); } - @LargeTest + @Test public void testIsEnabled() throws Exception { - // invoke the method under test - AccessibilityManager manager = createManager(true); - boolean isEnabledServiceEnabled = manager.isEnabled(); + // Create manager with a11y enabled + AccessibilityManager manager = createManager(WITH_A11Y_ENABLED); + assertTrue("Must be enabled since the mock service is enabled", manager.isEnabled()); - // check expected result - assertTrue("Must be enabled since the mock service is enabled", isEnabledServiceEnabled); - - // disable accessibility + // Disable accessibility manager.getClient().setState(0); - - // wait for the asynchronous IBinder call to complete - Thread.sleep(TIMEOUT_BINDER_CALL); - - // invoke the method under test - boolean isEnabledServcieDisabled = manager.isEnabled(); - - // check expected result - assertFalse("Must be disabled since the mock service is disabled", - isEnabledServcieDisabled); + mHandler.sendAllMessages(); + assertFalse("Must be disabled since the mock service is disabled", manager.isEnabled()); } - @MediumTest + @Test public void testSendAccessibilityEvent_AccessibilityEnabled() throws Exception { - AccessibilityEvent sentEvent = AccessibilityEvent.obtain(); + AccessibilityEvent sentEvent = AccessibilityEvent.obtain( + AccessibilityEvent.TYPE_ANNOUNCEMENT); - AccessibilityManager manager = createManager(true); + AccessibilityManager manager = createManager(WITH_A11Y_ENABLED); manager.sendAccessibilityEvent(sentEvent); assertSame("The event should be recycled.", sentEvent, AccessibilityEvent.obtain()); } - @MediumTest + @Test public void testSendAccessibilityEvent_AccessibilityDisabled() throws Exception { AccessibilityEvent sentEvent = AccessibilityEvent.obtain(); - AccessibilityManager manager = createManager(false /* disabled */); - - try { - manager.sendAccessibilityEvent(sentEvent); - fail("No accessibility events are sent if accessibility is disabled"); - } catch (IllegalStateException ise) { - // check expected result - assertEquals("Accessibility off. Did you forget to check that?", ise.getMessage()); - } + AccessibilityManager manager = createManager(WITH_A11Y_DISABLED); + mInstrumentation.runOnMainSync(() -> { + try { + manager.sendAccessibilityEvent(sentEvent); + fail("No accessibility events are sent if accessibility is disabled"); + } catch (IllegalStateException ise) { + // check expected result + assertEquals("Accessibility off. Did you forget to check that?", ise.getMessage()); + } + }); } } diff --git a/services/tests/servicestests/src/com/android/server/accessibility/MagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationControllerTest.java index d44c1ca0c0ef..5887215257e3 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/MagnificationControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationControllerTest.java @@ -20,11 +20,12 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertFalse; -import static org.mockito.Matchers.anyInt; +import static org.mockito.ArgumentMatchers.anyFloat; import static org.mockito.Matchers.anyObject; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.reset; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; @@ -48,6 +49,7 @@ import android.view.WindowManagerInternal; import android.view.WindowManagerInternal.MagnificationCallbacks; import com.android.internal.R; + import org.hamcrest.CoreMatchers; import org.hamcrest.Description; import org.hamcrest.TypeSafeMatcher; @@ -471,9 +473,10 @@ public class MagnificationControllerTest { public void testResetIfNeeded_doesWhatItSays() { mMagnificationController.register(); zoomIn2xToMiddle(); + reset(mMockAms); assertTrue(mMagnificationController.resetIfNeeded(false)); verify(mMockAms).notifyMagnificationChanged( - eq(INITIAL_MAGNIFICATION_REGION), eq(1.0f), anyInt(), anyInt()); + eq(INITIAL_MAGNIFICATION_REGION), eq(1.0f), anyFloat(), anyFloat()); assertFalse(mMagnificationController.isMagnifying()); assertFalse(mMagnificationController.resetIfNeeded(false)); } diff --git a/services/tests/servicestests/src/com/android/server/accessibility/MessageCapturingHandler.java b/services/tests/servicestests/src/com/android/server/accessibility/MessageCapturingHandler.java index 003f7abf6b00..0dba35f2a164 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/MessageCapturingHandler.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/MessageCapturingHandler.java @@ -42,6 +42,10 @@ public class MessageCapturingHandler extends Handler { return super.sendMessageAtTime(message, uptimeMillis); } + public void setCallback(Handler.Callback callback) { + mCallback = callback; + } + public void sendOneMessage() { Message message = timedMessages.remove(0).first; removeMessages(message.what, message.obj); diff --git a/services/tests/servicestests/src/com/android/server/accounts/AccountsDbTest.java b/services/tests/servicestests/src/com/android/server/accounts/AccountsDbTest.java index aa374073e4a5..5d0c23f81737 100644 --- a/services/tests/servicestests/src/com/android/server/accounts/AccountsDbTest.java +++ b/services/tests/servicestests/src/com/android/server/accounts/AccountsDbTest.java @@ -149,7 +149,7 @@ public class AccountsDbTest { // 2nd account Account account2 = new Account("name", "example2.com"); long accId2 = mAccountsDb.insertCeAccount(account2, "password"); - mAccountsDb.insertDeAccount(account2, accId); + mAccountsDb.insertDeAccount(account2, accId2); mAccountsDb.insertAuthToken(accId2, "type", "token"); mAccountsDb.deleteAuthTokensByAccountId(accId2); diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java index 092c60be254e..6701b716aa93 100644 --- a/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java @@ -137,7 +137,7 @@ public class ActivityManagerServiceTest { mHandler = new TestHandler(mHandlerThread.getLooper()); mInjector = new TestInjector(); mAms = new ActivityManagerService(mInjector); - mAms.mWaitForNetworkTimeoutMs = 100; + mAms.mWaitForNetworkTimeoutMs = 2000; when(mContext.getPackageManager()).thenReturn(mPackageManager); } diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityStackSupervisorTests.java b/services/tests/servicestests/src/com/android/server/am/ActivityStackSupervisorTests.java index b59c2bc93d73..8423affecd30 100644 --- a/services/tests/servicestests/src/com/android/server/am/ActivityStackSupervisorTests.java +++ b/services/tests/servicestests/src/com/android/server/am/ActivityStackSupervisorTests.java @@ -18,7 +18,6 @@ package com.android.server.am; import static org.junit.Assert.assertNull; -import android.os.Debug; import android.platform.test.annotations.Presubmit; import android.support.test.filters.MediumTest; import android.support.test.runner.AndroidJUnit4; @@ -45,7 +44,6 @@ public class ActivityStackSupervisorTests extends ActivityTestsBase { */ @Test public void testRestoringInvalidTask() throws Exception { - Debug.waitForDebugger(); final ActivityManagerService service = createActivityManagerService(); TaskRecord task = service.mStackSupervisor.anyTaskForIdLocked(0 /*taskId*/, MATCH_TASK_IN_STACKS_OR_RECENT_TASKS_AND_RESTORE, 0 /*stackId*/); diff --git a/services/tests/servicestests/src/com/android/server/am/CoreSettingsObserverTest.java b/services/tests/servicestests/src/com/android/server/am/CoreSettingsObserverTest.java new file mode 100644 index 000000000000..19defe158122 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/am/CoreSettingsObserverTest.java @@ -0,0 +1,153 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.am; + +import static com.android.server.am.ActivityManagerService.Injector; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.os.Bundle; +import android.os.Handler; +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 com.android.internal.util.test.FakeSettingsProvider; +import com.android.server.AppOpsService; + +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.io.File; + +/** + * Test class for {@link CoreSettingsObserver}. + * + * To run the tests, use + * + * runtest -c com.android.server.am.CoreSettingsObserverTest frameworks-services + * + * or the following steps: + * + * Build: m FrameworksServicesTests + * Install: adb install -r \ + * ${ANDROID_PRODUCT_OUT}/data/app/FrameworksServicesTests/FrameworksServicesTests.apk + * Run: adb shell am instrument -e class com.android.server.am.CoreSettingsObserverTest -w \ + * com.android.frameworks.servicestests/android.support.test.runner.AndroidJUnitRunner + */ +@SmallTest +@RunWith(AndroidJUnit4.class) +public class CoreSettingsObserverTest { + private static final String TEST_SETTING_SECURE_INT = "secureInt"; + private static final String TEST_SETTING_GLOBAL_FLOAT = "globalFloat"; + private static final String TEST_SETTING_SYSTEM_STRING = "systemString"; + + private static final int TEST_INT = 111; + private static final float TEST_FLOAT = 3.14f; + private static final String TEST_STRING = "testString"; + + private ActivityManagerService mAms; + @Mock private Context mContext; + + private MockContentResolver mContentResolver; + private CoreSettingsObserver mCoreSettingsObserver; + + @BeforeClass + public static void setupOnce() { + CoreSettingsObserver.sSecureSettingToTypeMap.put(TEST_SETTING_SECURE_INT, int.class); + CoreSettingsObserver.sGlobalSettingToTypeMap.put(TEST_SETTING_GLOBAL_FLOAT, float.class); + CoreSettingsObserver.sSystemSettingToTypeMap.put(TEST_SETTING_SYSTEM_STRING, String.class); + } + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + final Context originalContext = InstrumentationRegistry.getContext(); + when(mContext.getApplicationInfo()).thenReturn(originalContext.getApplicationInfo()); + mContentResolver = new MockContentResolver(mContext); + mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider()); + when(mContext.getContentResolver()).thenReturn(mContentResolver); + + mAms = new ActivityManagerService(new TestInjector()); + mCoreSettingsObserver = new CoreSettingsObserver(mAms); + } + + @Test + public void testPopulateSettings() { + Settings.Secure.putInt(mContentResolver, TEST_SETTING_SECURE_INT, TEST_INT); + Settings.Global.putFloat(mContentResolver, TEST_SETTING_GLOBAL_FLOAT, TEST_FLOAT); + Settings.System.putString(mContentResolver, TEST_SETTING_SYSTEM_STRING, TEST_STRING); + + final Bundle settingsBundle = getPopulatedBundle(); + + assertEquals("Unexpected value of " + TEST_SETTING_SECURE_INT, + TEST_INT, settingsBundle.getInt(TEST_SETTING_SECURE_INT)); + assertEquals("Unexpected value of " + TEST_SETTING_GLOBAL_FLOAT, + TEST_FLOAT, settingsBundle.getFloat(TEST_SETTING_GLOBAL_FLOAT), 0); + assertEquals("Unexpected value of " + TEST_SETTING_SYSTEM_STRING, + TEST_STRING, settingsBundle.getString(TEST_SETTING_SYSTEM_STRING)); + } + + @Test + public void testPopulateSettings_settingNotSet() { + final Bundle settingsBundle = getPopulatedBundle(); + + assertFalse("Bundle should not contain " + TEST_SETTING_SECURE_INT, + settingsBundle.containsKey(TEST_SETTING_SECURE_INT)); + assertFalse("Bundle should not contain " + TEST_SETTING_GLOBAL_FLOAT, + settingsBundle.containsKey(TEST_SETTING_GLOBAL_FLOAT)); + assertFalse("Bundle should not contain " + TEST_SETTING_SYSTEM_STRING, + settingsBundle.containsKey(TEST_SETTING_SYSTEM_STRING)); + } + + private Bundle getPopulatedBundle() { + final Bundle settingsBundle = new Bundle(); + mCoreSettingsObserver.populateSettings(settingsBundle, + CoreSettingsObserver.sGlobalSettingToTypeMap); + mCoreSettingsObserver.populateSettings(settingsBundle, + CoreSettingsObserver.sSecureSettingToTypeMap); + mCoreSettingsObserver.populateSettings(settingsBundle, + CoreSettingsObserver.sSystemSettingToTypeMap); + return settingsBundle; + } + + private class TestInjector extends Injector { + @Override + public Context getContext() { + return mContext; + } + + public AppOpsService getAppOpsService(File file, Handler handler) { + return null; + } + + @Override + public Handler getUiHandler(ActivityManagerService service) { + return null; + } + } +} diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java index 0f1b81e49f9e..7a4746a7f54b 100644 --- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java @@ -16,6 +16,7 @@ package com.android.server.am; +import android.app.ActivityManager; import android.app.IUserSwitchObserver; import android.content.Context; import android.content.IIntentReceiver; @@ -49,16 +50,20 @@ import java.util.Set; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static com.android.server.am.ActivityManagerService.CONTINUE_USER_SWITCH_MSG; +import static com.android.server.am.ActivityManagerService.REPORT_LOCKED_BOOT_COMPLETE_MSG; import static com.android.server.am.ActivityManagerService.REPORT_USER_SWITCH_COMPLETE_MSG; import static com.android.server.am.ActivityManagerService.REPORT_USER_SWITCH_MSG; import static com.android.server.am.ActivityManagerService.SYSTEM_USER_CURRENT_MSG; import static com.android.server.am.ActivityManagerService.SYSTEM_USER_START_MSG; import static com.android.server.am.ActivityManagerService.USER_SWITCH_TIMEOUT_MSG; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyBoolean; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; @@ -71,9 +76,29 @@ public class UserControllerTest extends AndroidTestCase { private UserController mUserController; private TestInjector mInjector; + private static final List<String> START_FOREGROUND_USER_ACTIONS = + Arrays.asList( + Intent.ACTION_USER_STARTED, + Intent.ACTION_USER_SWITCHED, + Intent.ACTION_USER_STARTING); + + private static final List<String> START_BACKGROUND_USER_ACTIONS = + Arrays.asList( + Intent.ACTION_USER_STARTED, + Intent.ACTION_LOCKED_BOOT_COMPLETED, + Intent.ACTION_USER_STARTING); + + private static final Set<Integer> START_FOREGROUND_USER_MESSAGE_CODES = + new HashSet<>(Arrays.asList(REPORT_USER_SWITCH_MSG, USER_SWITCH_TIMEOUT_MSG, + SYSTEM_USER_START_MSG, SYSTEM_USER_CURRENT_MSG)); + + private static final Set<Integer> START_BACKGROUND_USER_MESSAGE_CODES = + new HashSet<>(Arrays.asList(SYSTEM_USER_START_MSG, REPORT_LOCKED_BOOT_COMPLETE_MSG)); + @Override public void setUp() throws Exception { super.setUp(); + System.setProperty("dexmaker.share_classloader", "true"); mInjector = new TestInjector(getContext()); mUserController = new UserController(mInjector); setUpUser(TEST_USER_ID, 0); @@ -83,39 +108,62 @@ public class UserControllerTest extends AndroidTestCase { protected void tearDown() throws Exception { super.tearDown(); mInjector.handlerThread.quit(); - } @SmallTest - public void testStartUser() throws RemoteException { - mUserController.startUser(TEST_USER_ID, true); + public void testStartUser_foreground() throws RemoteException { + mUserController.startUser(TEST_USER_ID, true /* foreground */); Mockito.verify(mInjector.getWindowManager()).startFreezingScreen(anyInt(), anyInt()); Mockito.verify(mInjector.getWindowManager(), never()).stopFreezingScreen(); Mockito.verify(mInjector.getWindowManager(), times(1)).setSwitchingUser(anyBoolean()); Mockito.verify(mInjector.getWindowManager()).setSwitchingUser(true); - startUserAssertions(); + Mockito.verify(mInjector.getActivityStackSupervisor()).setLockTaskModeLocked( + nullable(TaskRecord.class), + eq(ActivityManager.LOCK_TASK_MODE_NONE), + anyString(), + anyBoolean()); + startForegroundUserAssertions(); + } + + @SmallTest + public void testStartUser_background() throws RemoteException { + mUserController.startUser(TEST_USER_ID, false /* foreground */); + Mockito.verify( + mInjector.getWindowManager(), never()).startFreezingScreen(anyInt(), anyInt()); + Mockito.verify(mInjector.getWindowManager(), never()).setSwitchingUser(anyBoolean()); + Mockito.verify(mInjector.getActivityStackSupervisor(), never()).setLockTaskModeLocked( + nullable(TaskRecord.class), + eq(ActivityManager.LOCK_TASK_MODE_NONE), + anyString(), + anyBoolean()); + startBackgroundUserAssertions(); } @SmallTest public void testStartUserUIDisabled() throws RemoteException { mUserController.mUserSwitchUiEnabled = false; - mUserController.startUser(TEST_USER_ID, true); + mUserController.startUser(TEST_USER_ID, true /* foreground */); Mockito.verify(mInjector.getWindowManager(), never()) .startFreezingScreen(anyInt(), anyInt()); Mockito.verify(mInjector.getWindowManager(), never()).stopFreezingScreen(); Mockito.verify(mInjector.getWindowManager(), never()).setSwitchingUser(anyBoolean()); - startUserAssertions(); + startForegroundUserAssertions(); } - private void startUserAssertions() throws RemoteException { - List<String> expectedActions = Arrays.asList(Intent.ACTION_USER_STARTED, - Intent.ACTION_USER_SWITCHED, Intent.ACTION_USER_STARTING); + private void startUserAssertions( + List<String> expectedActions, Set<Integer> expectedMessageCodes) + throws RemoteException { assertEquals(expectedActions, getActions(mInjector.sentIntents)); - Set<Integer> expectedCodes = new HashSet<>( - Arrays.asList(REPORT_USER_SWITCH_MSG, USER_SWITCH_TIMEOUT_MSG, - SYSTEM_USER_START_MSG, SYSTEM_USER_CURRENT_MSG)); Set<Integer> actualCodes = mInjector.handler.getMessageCodes(); - assertEquals("Unexpected message sent", expectedCodes, actualCodes); + assertEquals("Unexpected message sent", expectedMessageCodes, actualCodes); + } + + private void startBackgroundUserAssertions() throws RemoteException { + startUserAssertions(START_BACKGROUND_USER_ACTIONS, START_BACKGROUND_USER_MESSAGE_CODES); + } + + private void startForegroundUserAssertions() throws RemoteException { + startUserAssertions(START_FOREGROUND_USER_ACTIONS, START_FOREGROUND_USER_MESSAGE_CODES); Message reportMsg = mInjector.handler.getMessageForCode(REPORT_USER_SWITCH_MSG); assertNotNull(reportMsg); UserState userState = (UserState) reportMsg.obj; @@ -275,6 +323,7 @@ public class UserControllerTest extends AndroidTestCase { UserManagerService userManagerMock; UserManagerInternal userManagerInternalMock; WindowManagerService windowManagerMock; + ActivityStackSupervisor activityStackSupervisor; private Context mCtx; List<Intent> sentIntents = new ArrayList<>(); @@ -287,6 +336,7 @@ public class UserControllerTest extends AndroidTestCase { userManagerMock = mock(UserManagerService.class); userManagerInternalMock = mock(UserManagerInternal.class); windowManagerMock = mock(WindowManagerService.class); + activityStackSupervisor = mock(ActivityStackSupervisor.class); } @Override @@ -321,12 +371,6 @@ public class UserControllerTest extends AndroidTestCase { } @Override - void stackSupervisorSetLockTaskModeLocked(TaskRecord task, int lockTaskModeState, - String reason, boolean andResume) { - Log.i(TAG, "stackSupervisorSetLockTaskModeLocked"); - } - - @Override WindowManagerService getWindowManager() { return windowManagerMock; } @@ -347,16 +391,15 @@ public class UserControllerTest extends AndroidTestCase { } @Override - boolean stackSupervisorSwitchUserLocked(int userId, UserState uss) { - Log.i(TAG, "stackSupervisorSwitchUserLocked " + userId); - return true; + void startHomeActivityLocked(int userId, String reason) { + Log.i(TAG, "startHomeActivityLocked " + userId); } @Override - void startHomeActivityLocked(int userId, String reason) { - Log.i(TAG, "startHomeActivityLocked " + userId); + ActivityStackSupervisor getActivityStackSupervisor() { + return activityStackSupervisor; } - } + } private static class TestHandler extends Handler { private final List<Message> mMessages = new ArrayList<>(); diff --git a/services/tests/servicestests/src/com/android/server/net/ConnOnActivityStartTest.java b/services/tests/servicestests/src/com/android/server/net/ConnOnActivityStartTest.java new file mode 100644 index 000000000000..f9719711cad9 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/net/ConnOnActivityStartTest.java @@ -0,0 +1,443 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.net; + +import static android.util.DebugUtils.valueToString; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import com.android.frameworks.servicestests.R; +import com.android.servicestests.aidl.INetworkStateObserver; + +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.IntentSender; +import android.content.pm.IPackageDeleteObserver; +import android.content.pm.PackageInstaller; +import android.content.pm.PackageManager; +import android.net.NetworkInfo; +import android.net.Uri; +import android.os.Bundle; +import android.os.RemoteException; +import android.os.SystemClock; +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.LargeTest; +import android.support.test.runner.AndroidJUnit4; +import android.support.test.uiautomator.UiDevice; +import android.util.Log; + +import libcore.io.IoUtils; + +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +/** + * Tests for verifying network availability on activity start. + * + * To run the tests, use + * + * runtest -c com.android.server.net.ConnOnActivityStartTest frameworks-services + * + * or the following steps: + * + * Build: m FrameworksServicesTests + * Install: adb install -r \ + * ${ANDROID_PRODUCT_OUT}/data/app/FrameworksServicesTests/FrameworksServicesTests.apk + * Run: adb shell am instrument -e class com.android.server.net.ConnOnActivityStartTest -w \ + * com.android.frameworks.servicestests/android.support.test.runner.AndroidJUnitRunner + */ +@LargeTest +@RunWith(AndroidJUnit4.class) +public class ConnOnActivityStartTest { + private static final String TAG = ConnOnActivityStartTest.class.getSimpleName(); + + private static final String ACTION_INSTALL_COMPLETE = "com.android.server.net.INSTALL_COMPLETE"; + + private static final String TEST_APP_URI = + "android.resource://com.android.frameworks.servicestests/raw/conntestapp"; + private static final String TEST_PKG = "com.android.servicestests.apps.conntestapp"; + private static final String TEST_ACTIVITY_CLASS = TEST_PKG + ".ConnTestActivity"; + + private static final String ACTION_FINISH_ACTIVITY = TEST_PKG + ".FINISH"; + + private static final String EXTRA_NETWORK_STATE_OBSERVER = TEST_PKG + ".observer"; + + private static final int WAIT_FOR_INSTALL_TIMEOUT_MS = 2000; // 2 sec + + private static final int NETWORK_CHECK_TIMEOUT_MS = 6000; // 6 sec + + private static final int SCREEN_ON_DELAY_MS = 500; // 0.5 sec + + private static final String NETWORK_STATUS_SEPARATOR = "\\|"; + + private static final int REPEAT_TEST_COUNT = 5; + + private static Context mContext; + private static UiDevice mUiDevice; + private static int mTestPkgUid; + + @BeforeClass + public static void setUpOnce() throws Exception { + mContext = InstrumentationRegistry.getContext(); + mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); + + installAppAndAssertInstalled(); + mContext.getPackageManager().setApplicationEnabledSetting(TEST_PKG, + PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 0); + mTestPkgUid = mContext.getPackageManager().getPackageUid(TEST_PKG, 0); + } + + @AfterClass + public static void tearDownOnce() { + mContext.getPackageManager().deletePackage(TEST_PKG, + new IPackageDeleteObserver.Stub() { + @Override + public void packageDeleted(String packageName, int returnCode) + throws RemoteException { + Log.e(TAG, packageName + " deleted, returnCode: " + returnCode); + } + }, 0); + } + + @Test + public void testStartActivity_batterySaver() throws Exception { + setBatterySaverMode(true); + try { + testConnOnActivityStart("testStartActivity_batterySaver"); + } finally { + setBatterySaverMode(false); + } + } + + @Test + public void testStartActivity_dataSaver() throws Exception { + setDataSaverMode(true); + try { + testConnOnActivityStart("testStartActivity_dataSaver"); + } finally { + setDataSaverMode(false); + } + } + + @Test + public void testStartActivity_dozeMode() throws Exception { + setDozeMode(true); + try { + testConnOnActivityStart("testStartActivity_dozeMode"); + } finally { + setDozeMode(false); + } + } + + @Test + public void testStartActivity_appStandby() throws Exception { + try{ + turnBatteryOff(); + setAppIdle(true); + SystemClock.sleep(30000); + turnScreenOn(); + startActivityAndCheckNetworkAccess(); + } finally { + turnBatteryOn(); + setAppIdle(false); + } + } + + @Test + public void testStartActivity_backgroundRestrict() throws Exception { + updateRestrictBackgroundBlacklist(true); + try { + testConnOnActivityStart("testStartActivity_backgroundRestrict"); + } finally { + updateRestrictBackgroundBlacklist(false); + } + } + + private void testConnOnActivityStart(String testName) throws Exception { + for (int i = 1; i <= REPEAT_TEST_COUNT; ++i) { + try { + Log.d(TAG, testName + " Start #" + i); + turnScreenOn(); + SystemClock.sleep(SCREEN_ON_DELAY_MS); + startActivityAndCheckNetworkAccess(); + Log.d(TAG, testName + " end #" + i); + } finally { + finishActivity(); + } + } + } + + // TODO: Some of these methods are also used in CTS, so instead of duplicating code, + // create a static library which can be used by both servicestests and cts. + private void setBatterySaverMode(boolean enabled) throws Exception { + if (enabled) { + turnBatteryOff(); + executeCommand("settings put global low_power 1"); + } else { + executeCommand("settings put global low_power 0"); + turnBatteryOn(); + } + final String result = executeCommand("settings get global low_power"); + assertEquals(enabled ? "1" : "0", result); + } + + private void setDataSaverMode(boolean enabled) throws Exception { + executeCommand("cmd netpolicy set restrict-background " + enabled); + final String output = executeCommand("cmd netpolicy get restrict-background"); + final String expectedSuffix = enabled ? "enabled" : "disabled"; + assertTrue("output '" + output + "' should end with '" + expectedSuffix + "'", + output.endsWith(expectedSuffix)); + } + + private void setDozeMode(boolean enabled) throws Exception { + if (enabled) { + turnBatteryOff(); + turnScreenOff(); + executeCommand("dumpsys deviceidle force-idle deep"); + } else { + turnScreenOn(); + turnBatteryOn(); + executeCommand("dumpsys deviceidle unforce"); + } + assertDelayedCommandResult("dumpsys deviceidle get deep", enabled ? "IDLE" : "ACTIVE", + 5 /* maxTries */, 500 /* napTimeMs */); + } + + private void setAppIdle(boolean enabled) throws Exception { + executeCommand("am set-inactive " + TEST_PKG + " " + enabled); + assertDelayedCommandResult("am get-inactive " + TEST_PKG, "Idle=" + enabled, + 10 /* maxTries */, 2000 /* napTimeMs */); + } + + private void updateRestrictBackgroundBlacklist(boolean add) throws Exception { + if (add) { + executeCommand("cmd netpolicy add restrict-background-blacklist " + mTestPkgUid); + } else { + executeCommand("cmd netpolicy remove restrict-background-blacklist " + mTestPkgUid); + } + assertRestrictBackground("restrict-background-blacklist", mTestPkgUid, add); + } + + private void assertRestrictBackground(String list, int uid, boolean expected) throws Exception { + final int maxTries = 5; + boolean actual = false; + final String expectedUid = Integer.toString(uid); + String uids = ""; + for (int i = 1; i <= maxTries; i++) { + final String output = executeCommand("cmd netpolicy list " + list); + uids = output.split(":")[1]; + for (String candidate : uids.split(" ")) { + actual = candidate.trim().equals(expectedUid); + if (expected == actual) { + return; + } + } + Log.v(TAG, list + " check for uid " + uid + " doesn't match yet (expected " + + expected + ", got " + actual + "); sleeping 1s before polling again"); + SystemClock.sleep(1000); + } + fail(list + " check for uid " + uid + " failed: expected " + expected + ", got " + actual + + ". Full list: " + uids); + } + + private void turnBatteryOff() throws Exception { + executeCommand("cmd battery unplug"); + } + + private void turnBatteryOn() throws Exception { + executeCommand("cmd battery reset"); + } + + private void turnScreenOff() throws Exception { + executeCommand("input keyevent KEYCODE_SLEEP"); + } + + private void turnScreenOn() throws Exception { + executeCommand("input keyevent KEYCODE_WAKEUP"); + executeCommand("wm dismiss-keyguard"); + } + + private String executeCommand(String cmd) throws IOException { + final String result = mUiDevice.executeShellCommand(cmd).trim(); + Log.d(TAG, String.format("Result for '%s': %s", cmd, result)); + return result; + } + + private void assertDelayedCommandResult(String cmd, String expectedResult, + int maxTries, int napTimeMs) throws IOException { + String result = ""; + for (int i = 1; i <= maxTries; ++i) { + result = executeCommand(cmd); + if (expectedResult.equals(result)) { + return; + } + Log.v(TAG, "Command '" + cmd + "' returned '" + result + " instead of '" + + expectedResult + "' on attempt #" + i + + "; sleeping " + napTimeMs + "ms before trying again"); + SystemClock.sleep(napTimeMs); + } + fail("Command '" + cmd + "' did not return '" + expectedResult + "' after " + + maxTries + " attempts. Last result: '" + result + "'"); + } + + private void startActivityAndCheckNetworkAccess() throws Exception { + final CountDownLatch latch = new CountDownLatch(1); + final Intent launchIntent = new Intent().setComponent( + new ComponentName(TEST_PKG, TEST_ACTIVITY_CLASS)); + final Bundle extras = new Bundle(); + final String[] errors = new String[] {null}; + extras.putBinder(EXTRA_NETWORK_STATE_OBSERVER, new INetworkStateObserver.Stub() { + @Override + public void onNetworkStateChecked(String resultData) { + errors[0] = checkForAvailability(resultData); + latch.countDown(); + } + }); + launchIntent.putExtras(extras); + mContext.startActivity(launchIntent); + if (latch.await(NETWORK_CHECK_TIMEOUT_MS, TimeUnit.MILLISECONDS)) { + if (!errors[0].isEmpty()) { + fail("Network not available for test app " + mTestPkgUid); + } + } else { + fail("Timed out waiting for network availability status from test app " + mTestPkgUid); + } + } + + private void finishActivity() { + final Intent finishIntent = new Intent(ACTION_FINISH_ACTIVITY) + .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); + mContext.sendBroadcast(finishIntent); + } + + private String checkForAvailability(String resultData) { + if (resultData == null) { + assertNotNull("Network status from app2 is null, Uid: " + mTestPkgUid, resultData); + } + // Network status format is described on MyBroadcastReceiver.checkNetworkStatus() + final String[] parts = resultData.split(NETWORK_STATUS_SEPARATOR); + assertEquals("Wrong network status: " + resultData + ", Uid: " + mTestPkgUid, + 5, parts.length); // Sanity check + final NetworkInfo.State state = parts[0].equals("null") + ? null : NetworkInfo.State.valueOf(parts[0]); + final NetworkInfo.DetailedState detailedState = parts[1].equals("null") + ? null : NetworkInfo.DetailedState.valueOf(parts[1]); + final boolean connected = Boolean.valueOf(parts[2]); + final String connectionCheckDetails = parts[3]; + final String networkInfo = parts[4]; + + final StringBuilder errors = new StringBuilder(); + final NetworkInfo.State expectedState = NetworkInfo.State.CONNECTED; + final NetworkInfo.DetailedState expectedDetailedState = NetworkInfo.DetailedState.CONNECTED; + + if (true != connected) { + errors.append(String.format("External site connection failed: expected %s, got %s\n", + true, connected)); + } + if (expectedState != state || expectedDetailedState != detailedState) { + errors.append(String.format("Connection state mismatch: expected %s/%s, got %s/%s\n", + expectedState, expectedDetailedState, state, detailedState)); + } + + if (errors.length() > 0) { + errors.append("\tnetworkInfo: " + networkInfo + "\n"); + errors.append("\tconnectionCheckDetails: " + connectionCheckDetails + "\n"); + } + return errors.toString(); + } + + private static void installAppAndAssertInstalled() throws Exception { + final CountDownLatch latch = new CountDownLatch(1); + final int[] result = {PackageInstaller.STATUS_SUCCESS}; + final BroadcastReceiver installStatusReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + final String pkgName = intent.getStringExtra(PackageInstaller.EXTRA_PACKAGE_NAME); + if (!TEST_PKG.equals(pkgName)) { + return; + } + result[0] = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, + PackageInstaller.STATUS_FAILURE); + latch.countDown(); + } + }; + mContext.registerReceiver(installStatusReceiver, new IntentFilter(ACTION_INSTALL_COMPLETE)); + try { + installApp(); + if (latch.await(WAIT_FOR_INSTALL_TIMEOUT_MS, TimeUnit.MILLISECONDS)) { + if (result[0] != PackageInstaller.STATUS_SUCCESS) { + fail("Couldn't install test app, result: " + + valueToString(PackageInstaller.class, "STATUS_", result[0])); + } + } else { + fail("Timed out waiting for the test app to install"); + } + } finally { + mContext.unregisterReceiver(installStatusReceiver); + } + } + + private static void installApp() throws Exception { + final Uri packageUri = Uri.parse(TEST_APP_URI); + final InputStream in = mContext.getContentResolver().openInputStream(packageUri); + + final PackageInstaller packageInstaller + = mContext.getPackageManager().getPackageInstaller(); + final PackageInstaller.SessionParams params = new PackageInstaller.SessionParams( + PackageInstaller.SessionParams.MODE_FULL_INSTALL); + params.setAppPackageName(TEST_PKG); + + final int sessionId = packageInstaller.createSession(params); + final PackageInstaller.Session session = packageInstaller.openSession(sessionId); + + OutputStream out = null; + try { + out = session.openWrite(TAG, 0, -1); + final byte[] buffer = new byte[65536]; + int c; + while ((c = in.read(buffer)) != -1) { + out.write(buffer, 0, c); + } + session.fsync(out); + } finally { + IoUtils.closeQuietly(in); + IoUtils.closeQuietly(out); + } + session.commit(createIntentSender(mContext, sessionId)); + } + + private static IntentSender createIntentSender(Context context, int sessionId) { + PendingIntent pendingIntent = PendingIntent.getBroadcast( + context, sessionId, new Intent(ACTION_INSTALL_COMPLETE), 0); + return pendingIntent.getIntentSender(); + } +}
\ No newline at end of file diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java index 72fb78e89ea2..afc0f67fe993 100644 --- a/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java +++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java @@ -360,6 +360,19 @@ public class DexManagerTests { assertNull(mDexManager.getPackageUseInfo(frameworkDex)); } + @Test + public void testNotifySecondaryFromProtected() { + // Foo loads its own secondary files. + List<String> fooSecondaries = mFooUser0.getSecondaryDexPathsFromProtectedDirs(); + notifyDexLoad(mFooUser0, fooSecondaries, mUser0); + + PackageUseInfo pui = getPackageUseInfo(mFooUser0); + assertNotNull(pui); + assertFalse(pui.isUsedByOtherApps()); + assertEquals(fooSecondaries.size(), pui.getDexUseInfoMap().size()); + assertSecondaryUse(mFooUser0, pui, fooSecondaries, /*isUsedByOtherApps*/false, mUser0); + } + private void assertSecondaryUse(TestData testData, PackageUseInfo pui, List<String> secondaries, boolean isUsedByOtherApps, int ownerUserId) { for (String dex : secondaries) { @@ -394,6 +407,8 @@ public class DexManagerTests { ai.setBaseCodePath(codeDir + "/base.dex"); ai.setSplitCodePaths(new String[] {codeDir + "/split-1.dex", codeDir + "/split-2.dex"}); ai.dataDir = "/data/user/" + userId + "/" + packageName; + ai.deviceProtectedDataDir = "/data/user_de/" + userId + "/" + packageName; + ai.credentialProtectedDataDir = "/data/user_ce/" + userId + "/" + packageName; ai.packageName = packageName; return ai; } @@ -426,6 +441,13 @@ public class DexManagerTests { return paths; } + List<String> getSecondaryDexPathsFromProtectedDirs() { + List<String> paths = new ArrayList<>(); + paths.add(mPackageInfo.applicationInfo.dataDir + "/secondary6.dex"); + paths.add(mPackageInfo.applicationInfo.dataDir + "/secondary7.dex"); + return paths; + } + List<String> getBaseAndSplitDexPaths() { List<String> paths = new ArrayList<>(); paths.add(mPackageInfo.applicationInfo.sourceDir); diff --git a/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java b/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java index 717ddf26eb2f..aab75ee1699b 100644 --- a/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java +++ b/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java @@ -16,9 +16,6 @@ package com.android.server.wm; -import static android.content.res.Configuration.ORIENTATION_PORTRAIT; -import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; -import static org.junit.Assert.assertEquals; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.eq; @@ -27,19 +24,15 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import android.app.ActivityManager.TaskSnapshot; +import android.graphics.Bitmap; +import android.graphics.Bitmap.Config; import android.graphics.Canvas; import android.graphics.Color; -import android.graphics.GraphicBuffer; -import android.graphics.PixelFormat; -import android.graphics.Rect; import android.platform.test.annotations.Presubmit; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; -import android.view.Surface; - -import com.android.server.wm.TaskSnapshotSurface.Window; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -55,174 +48,59 @@ public class TaskSnapshotSurfaceTest extends WindowTestsBase { private TaskSnapshotSurface mSurface; - private void setupSurface(int width, int height, Rect contentInsets, int sysuiVis, - int windowFlags, Rect taskBounds) { - final GraphicBuffer buffer = GraphicBuffer.create(width, height, PixelFormat.RGBA_8888, - GraphicBuffer.USAGE_SW_READ_NEVER | GraphicBuffer.USAGE_SW_WRITE_NEVER); - final TaskSnapshot snapshot = new TaskSnapshot(buffer, - ORIENTATION_PORTRAIT, contentInsets, false, 1.0f); - mSurface = new TaskSnapshotSurface(sWm, new Window(), new Surface(), snapshot, "Test", - Color.WHITE, Color.RED, Color.BLUE, sysuiVis, windowFlags, 0, taskBounds); - } - - private void setupSurface(int width, int height) { - setupSurface(width, height, new Rect(), 0, FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS, - new Rect(0, 0, width, height)); + @Before + public void setUp() { + mSurface = new TaskSnapshotSurface(null, null, null, Color.WHITE); } @Test public void fillEmptyBackground_fillHorizontally() throws Exception { - setupSurface(200, 100); final Canvas mockCanvas = mock(Canvas.class); when(mockCanvas.getWidth()).thenReturn(200); when(mockCanvas.getHeight()).thenReturn(100); - mSurface.drawBackgroundAndBars(mockCanvas, new Rect(0, 0, 100, 200)); + final Bitmap b = Bitmap.createBitmap(100, 200, Config.ARGB_8888); + mSurface.fillEmptyBackground(mockCanvas, b); verify(mockCanvas).drawRect(eq(100.0f), eq(0.0f), eq(200.0f), eq(100.0f), any()); } @Test public void fillEmptyBackground_fillVertically() throws Exception { - setupSurface(100, 200); final Canvas mockCanvas = mock(Canvas.class); when(mockCanvas.getWidth()).thenReturn(100); when(mockCanvas.getHeight()).thenReturn(200); - mSurface.drawBackgroundAndBars(mockCanvas, new Rect(0, 0, 200, 100)); + final Bitmap b = Bitmap.createBitmap(200, 100, Config.ARGB_8888); + mSurface.fillEmptyBackground(mockCanvas, b); verify(mockCanvas).drawRect(eq(0.0f), eq(100.0f), eq(100.0f), eq(200.0f), any()); } @Test public void fillEmptyBackground_fillBoth() throws Exception { - setupSurface(200, 200); final Canvas mockCanvas = mock(Canvas.class); when(mockCanvas.getWidth()).thenReturn(200); when(mockCanvas.getHeight()).thenReturn(200); - mSurface.drawBackgroundAndBars(mockCanvas, new Rect(0, 0, 100, 100)); + final Bitmap b = Bitmap.createBitmap(100, 100, Config.ARGB_8888); + mSurface.fillEmptyBackground(mockCanvas, b); verify(mockCanvas).drawRect(eq(100.0f), eq(0.0f), eq(200.0f), eq(100.0f), any()); verify(mockCanvas).drawRect(eq(0.0f), eq(100.0f), eq(200.0f), eq(200.0f), any()); } @Test public void fillEmptyBackground_dontFill_sameSize() throws Exception { - setupSurface(100, 100); final Canvas mockCanvas = mock(Canvas.class); when(mockCanvas.getWidth()).thenReturn(100); when(mockCanvas.getHeight()).thenReturn(100); - mSurface.drawBackgroundAndBars(mockCanvas, new Rect(0, 0, 100, 100)); + final Bitmap b = Bitmap.createBitmap(100, 100, Config.ARGB_8888); + mSurface.fillEmptyBackground(mockCanvas, b); verify(mockCanvas, never()).drawRect(anyInt(), anyInt(), anyInt(), anyInt(), any()); } @Test public void fillEmptyBackground_dontFill_bitmapLarger() throws Exception { - setupSurface(100, 100); - final Canvas mockCanvas = mock(Canvas.class); - when(mockCanvas.getWidth()).thenReturn(100); - when(mockCanvas.getHeight()).thenReturn(100); - mSurface.drawBackgroundAndBars(mockCanvas, new Rect(0, 0, 200, 200)); - verify(mockCanvas, never()).drawRect(anyInt(), anyInt(), anyInt(), anyInt(), any()); - } - - @Test - public void testCalculateSnapshotCrop() { - setupSurface(100, 100, new Rect(0, 10, 0, 10), 0, 0, new Rect(0, 0, 100, 100)); - assertEquals(new Rect(0, 0, 100, 90), mSurface.calculateSnapshotCrop()); - } - - @Test - public void testCalculateSnapshotCrop_taskNotOnTop() { - setupSurface(100, 100, new Rect(0, 10, 0, 10), 0, 0, new Rect(0, 50, 100, 100)); - assertEquals(new Rect(0, 10, 100, 90), mSurface.calculateSnapshotCrop()); - } - - @Test - public void testCalculateSnapshotCrop_navBarLeft() { - setupSurface(100, 100, new Rect(10, 10, 0, 0), 0, 0, new Rect(0, 0, 100, 100)); - assertEquals(new Rect(10, 0, 100, 100), mSurface.calculateSnapshotCrop()); - } - - @Test - public void testCalculateSnapshotCrop_navBarRight() { - setupSurface(100, 100, new Rect(0, 10, 10, 0), 0, 0, new Rect(0, 0, 100, 100)); - assertEquals(new Rect(0, 0, 90, 100), mSurface.calculateSnapshotCrop()); - } - - @Test - public void testCalculateSnapshotFrame() { - setupSurface(100, 100); - final Rect insets = new Rect(0, 10, 0, 10); - mSurface.setFrames(new Rect(0, 0, 100, 100), insets, insets); - assertEquals(new Rect(0, -10, 100, 70), - mSurface.calculateSnapshotFrame(new Rect(0, 10, 100, 90))); - } - - @Test - public void testCalculateSnapshotFrame_navBarLeft() { - setupSurface(100, 100); - final Rect insets = new Rect(10, 10, 0, 0); - mSurface.setFrames(new Rect(0, 0, 100, 100), insets, insets); - assertEquals(new Rect(0, -10, 90, 80), - mSurface.calculateSnapshotFrame(new Rect(10, 10, 100, 100))); - } - - @Test - public void testDrawStatusBarBackground() { - setupSurface(100, 100); - final Rect insets = new Rect(0, 10, 10, 0); - mSurface.setFrames(new Rect(0, 0, 100, 100), insets, insets); final Canvas mockCanvas = mock(Canvas.class); when(mockCanvas.getWidth()).thenReturn(100); when(mockCanvas.getHeight()).thenReturn(100); - mSurface.drawStatusBarBackground(mockCanvas, new Rect(0, 0, 50, 100), 10); - verify(mockCanvas).drawRect(eq(50.0f), eq(0.0f), eq(90.0f), eq(10.0f), any()); - } - - @Test - public void testDrawStatusBarBackground_nope() { - setupSurface(100, 100); - final Rect insets = new Rect(0, 10, 10, 0); - mSurface.setFrames(new Rect(0, 0, 100, 100), insets, insets); - final Canvas mockCanvas = mock(Canvas.class); - when(mockCanvas.getWidth()).thenReturn(100); - when(mockCanvas.getHeight()).thenReturn(100); - mSurface.drawStatusBarBackground(mockCanvas, new Rect(0, 0, 100, 100), 10); + final Bitmap b = Bitmap.createBitmap(200, 200, Config.ARGB_8888); + mSurface.fillEmptyBackground(mockCanvas, b); verify(mockCanvas, never()).drawRect(anyInt(), anyInt(), anyInt(), anyInt(), any()); } - - @Test - public void testDrawNavigationBarBackground() { - final Rect insets = new Rect(0, 10, 0, 10); - setupSurface(100, 100, insets, 0, FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS, - new Rect(0, 0, 100, 100)); - mSurface.setFrames(new Rect(0, 0, 100, 100), insets, insets); - final Canvas mockCanvas = mock(Canvas.class); - when(mockCanvas.getWidth()).thenReturn(100); - when(mockCanvas.getHeight()).thenReturn(100); - mSurface.drawNavigationBarBackground(mockCanvas); - verify(mockCanvas).drawRect(eq(new Rect(0, 90, 100, 100)), any()); - } - - @Test - public void testDrawNavigationBarBackground_left() { - final Rect insets = new Rect(10, 10, 0, 0); - setupSurface(100, 100, insets, 0, FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS, - new Rect(0, 0, 100, 100)); - mSurface.setFrames(new Rect(0, 0, 100, 100), insets, insets); - final Canvas mockCanvas = mock(Canvas.class); - when(mockCanvas.getWidth()).thenReturn(100); - when(mockCanvas.getHeight()).thenReturn(100); - mSurface.drawNavigationBarBackground(mockCanvas); - verify(mockCanvas).drawRect(eq(new Rect(0, 0, 10, 100)), any()); - } - - @Test - public void testDrawNavigationBarBackground_right() { - final Rect insets = new Rect(0, 10, 10, 0); - setupSurface(100, 100, insets, 0, FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS, - new Rect(0, 0, 100, 100)); - mSurface.setFrames(new Rect(0, 0, 100, 100), insets, insets); - final Canvas mockCanvas = mock(Canvas.class); - when(mockCanvas.getWidth()).thenReturn(100); - when(mockCanvas.getHeight()).thenReturn(100); - mSurface.drawNavigationBarBackground(mockCanvas); - verify(mockCanvas).drawRect(eq(new Rect(90, 0, 100, 100)), any()); - } } diff --git a/services/tests/servicestests/test-apps/ConnTestApp/Android.mk b/services/tests/servicestests/test-apps/ConnTestApp/Android.mk new file mode 100644 index 000000000000..02afe83efb99 --- /dev/null +++ b/services/tests/servicestests/test-apps/ConnTestApp/Android.mk @@ -0,0 +1,30 @@ +# Copyright (C) 2017 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +LOCAL_PATH:= $(call my-dir) + +include $(CLEAR_VARS) + +LOCAL_MODULE_TAGS := tests +LOCAL_SDK_VERSION := current + +LOCAL_STATIC_JAVA_LIBRARIES := servicestests-aidl +LOCAL_SRC_FILES := $(call all-subdir-java-files) + +LOCAL_PACKAGE_NAME := ConnTestApp +LOCAL_CERTIFICATE := platform +LOCAL_DEX_PREOPT := false +LOCAL_PROGUARD_ENABLED := disabled + +include $(BUILD_PACKAGE)
\ No newline at end of file diff --git a/services/tests/servicestests/test-apps/ConnTestApp/AndroidManifest.xml b/services/tests/servicestests/test-apps/ConnTestApp/AndroidManifest.xml new file mode 100644 index 000000000000..0da3562c9afa --- /dev/null +++ b/services/tests/servicestests/test-apps/ConnTestApp/AndroidManifest.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2017 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.servicestests.apps.conntestapp"> + + <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> + <uses-permission android:name="android.permission.INTERNET" /> + + <application> + <activity android:name=".ConnTestActivity" + android:exported="true" /> + </application> + +</manifest>
\ No newline at end of file diff --git a/services/tests/servicestests/test-apps/ConnTestApp/src/com/android/servicestests/apps/conntestapp/ConnTestActivity.java b/services/tests/servicestests/test-apps/ConnTestApp/src/com/android/servicestests/apps/conntestapp/ConnTestActivity.java new file mode 100644 index 000000000000..11ebfca67ad4 --- /dev/null +++ b/services/tests/servicestests/test-apps/ConnTestApp/src/com/android/servicestests/apps/conntestapp/ConnTestActivity.java @@ -0,0 +1,170 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.servicestests.apps.conntestapp; + +import android.app.Activity; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.PackageManager; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; +import android.os.AsyncTask; +import android.os.Bundle; +import android.os.RemoteException; +import android.util.Log; + +import com.android.servicestests.aidl.INetworkStateObserver; + +import java.net.HttpURLConnection; +import java.net.URL; + +public class ConnTestActivity extends Activity { + private static final String TAG = ConnTestActivity.class.getSimpleName(); + + private static final String TEST_PKG = ConnTestActivity.class.getPackage().getName(); + private static final String ACTION_FINISH_ACTIVITY = TEST_PKG + ".FINISH"; + private static final String EXTRA_NETWORK_STATE_OBSERVER = TEST_PKG + ".observer"; + + private static final int NETWORK_TIMEOUT_MS = 5 * 1000; + + private static final String NETWORK_STATUS_TEMPLATE = "%s|%s|%s|%s|%s"; + + private BroadcastReceiver finishCommandReceiver = null; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + notifyNetworkStateObserver(); + + finishCommandReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + ConnTestActivity.this.finish(); + } + }; + registerReceiver(finishCommandReceiver, new IntentFilter(ACTION_FINISH_ACTIVITY)); + } + + @Override + public void onStop() { + if (finishCommandReceiver != null) { + unregisterReceiver(finishCommandReceiver); + } + super.onStop(); + } + + private void notifyNetworkStateObserver() { + if (getIntent() == null) { + return; + } + + final Bundle extras = getIntent().getExtras(); + if (extras == null) { + return; + } + final INetworkStateObserver observer = INetworkStateObserver.Stub.asInterface( + extras.getBinder(EXTRA_NETWORK_STATE_OBSERVER)); + if (observer != null) { + AsyncTask.execute(() -> { + try { + observer.onNetworkStateChecked(checkNetworkStatus(ConnTestActivity.this)); + } catch (RemoteException e) { + Log.e(TAG, "Error occured while notifying the observer: " + e); + } + }); + } + } + + /** + * Checks whether the network is available and return a string which can then be send as a + * result data for the ordered broadcast. + * + * <p> + * The string has the following format: + * + * <p><pre><code> + * NetinfoState|NetinfoDetailedState|RealConnectionCheck|RealConnectionCheckDetails|Netinfo + * </code></pre> + * + * <p>Where: + * + * <ul> + * <li>{@code NetinfoState}: enum value of {@link NetworkInfo.State}. + * <li>{@code NetinfoDetailedState}: enum value of {@link NetworkInfo.DetailedState}. + * <li>{@code RealConnectionCheck}: boolean value of a real connection check (i.e., an attempt + * to access an external website. + * <li>{@code RealConnectionCheckDetails}: if HTTP output core or exception string of the real + * connection attempt + * <li>{@code Netinfo}: string representation of the {@link NetworkInfo}. + * </ul> + * + * For example, if the connection was established fine, the result would be something like: + * <p><pre><code> + * CONNECTED|CONNECTED|true|200|[type: WIFI[], state: CONNECTED/CONNECTED, reason: ...] + * </code></pre> + */ + private String checkNetworkStatus(Context context) { + final ConnectivityManager cm = + (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + final String address = "http://example.com"; + final NetworkInfo networkInfo = cm.getActiveNetworkInfo(); + Log.d(TAG, "Running checkNetworkStatus() on thread " + + Thread.currentThread().getName() + " for UID " + getUid(context) + + "\n\tactiveNetworkInfo: " + networkInfo + "\n\tURL: " + address); + boolean checkStatus = false; + String checkDetails = "N/A"; + try { + final URL url = new URL(address); + final HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + conn.setReadTimeout(NETWORK_TIMEOUT_MS); + conn.setConnectTimeout(NETWORK_TIMEOUT_MS / 2); + conn.setRequestMethod("GET"); + conn.setDoInput(true); + conn.connect(); + final int response = conn.getResponseCode(); + checkStatus = true; + checkDetails = "HTTP response for " + address + ": " + response; + } catch (Exception e) { + checkStatus = false; + checkDetails = "Exception getting " + address + ": " + e; + } + Log.d(TAG, checkDetails); + final String state, detailedState; + if (networkInfo != null) { + state = networkInfo.getState().name(); + detailedState = networkInfo.getDetailedState().name(); + } else { + state = detailedState = "null"; + } + final String status = String.format(NETWORK_STATUS_TEMPLATE, state, detailedState, + Boolean.valueOf(checkStatus), checkDetails, networkInfo); + Log.d(TAG, "Offering " + status); + return status; + } + + private int getUid(Context context) { + final String packageName = context.getPackageName(); + try { + return context.getPackageManager().getPackageUid(packageName, 0); + } catch (PackageManager.NameNotFoundException e) { + throw new IllegalStateException("Could not get UID for " + packageName, e); + } + } +}
\ No newline at end of file diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 8357a2b535a4..1fd1929dbe01 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -3106,30 +3106,6 @@ public class TelephonyManager { } /** - * Send the special dialer code. The IPC caller must be the current default dialer. - * <p> - * Requires Permission: - * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} - * - * @param inputCode The special dialer code to send which follows the format of *#*#<code>#*#* - * @return true if sent sucessfully, false otherwise - * @deprecated use {@link #sendDialerSpecialCode(String)} ()} instead. - */ - public boolean sendDialerCode(String inputCode) { - try { - final ITelephony telephony = getITelephony(); - if (telephony == null) { - Log.e(TAG, "Telephony service unavailable"); - return false; - } - return telephony.sendDialerCode(mContext.getOpPackageName(), inputCode); - } catch (RemoteException | NullPointerException ex) { - // This could happen before phone restarts due to crashing - return false; - } - } - - /** * Send the special dialer code. The IPC caller must be the current default dialer or has * carrier privileges. * @see #hasCarrierPrivileges diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl index cd15c444d8bd..db7e417cbcb5 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -530,9 +530,6 @@ interface ITelephony { in String number, in int port, in String text, in PendingIntent sentIntent); // Send the special dialer code. The IPC caller must be the current default dialer. - boolean sendDialerCode(String callingPackageName, String inputCode); - - // Send the special dialer code. The IPC caller must be the current default dialer. void sendDialerSpecialCode(String callingPackageName, String inputCode); /** diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp index 8461905d8034..90f713b67985 100644 --- a/tools/aapt2/ResourceParser.cpp +++ b/tools/aapt2/ResourceParser.cpp @@ -155,7 +155,10 @@ bool ResourceParser::FlattenXmlSubtree( xml::XmlPullParser* parser, std::string* out_raw_string, StyleString* out_style_string, std::vector<UntranslatableSection>* out_untranslatable_sections) { // Keeps track of formatting tags (<b>, <i>) and the range of characters for which they apply. - std::vector<Span> span_stack; + // The stack elements refer to the indices in out_style_string->spans. + // By first adding to the out_style_string->spans vector, and then using the stack to refer + // to this vector, the original order of tags is preserved in cases such as <b><i>hello</b></i>. + std::vector<size_t> span_stack; // Clear the output variables. out_raw_string->clear(); @@ -192,7 +195,9 @@ bool ResourceParser::FlattenXmlSubtree( return false; } - span_stack.push_back(Span{std::move(span_name), static_cast<uint32_t>(builder.Utf16Len())}); + out_style_string->spans.push_back( + Span{std::move(span_name), static_cast<uint32_t>(builder.Utf16Len())}); + span_stack.push_back(out_style_string->spans.size() - 1); } else if (parser->element_namespace() == sXliffNamespaceUri) { if (parser->element_name() == "g") { if (untranslatable_start_depth) { @@ -233,9 +238,8 @@ bool ResourceParser::FlattenXmlSubtree( if (parser->element_namespace().empty()) { // This is an HTML tag which we encode as a span. Update the span // stack and pop the top entry. - Span& top_span = span_stack.back(); + Span& top_span = out_style_string->spans[span_stack.back()]; top_span.last_char = builder.Utf16Len() - 1; - out_style_string->spans.push_back(std::move(top_span)); span_stack.pop_back(); } else if (untranslatable_start_depth == make_value(depth)) { // This is the end of an untranslatable section. Use UTF8 indices/lengths. diff --git a/tools/aapt2/ResourceParser_test.cpp b/tools/aapt2/ResourceParser_test.cpp index eefa320a4418..8062c2e6afea 100644 --- a/tools/aapt2/ResourceParser_test.cpp +++ b/tools/aapt2/ResourceParser_test.cpp @@ -101,20 +101,24 @@ TEST_F(ResourceParserTest, ParseStyledString) { // Use a surrogate pair unicode point so that we can verify that the span // indices use UTF-16 length and not UTF-8 length. std::string input = - "<string name=\"foo\">This is my aunt\u2019s <b>string</b></string>"; + "<string name=\"foo\">This is my aunt\u2019s <b>fickle <small>string</small></b></string>"; ASSERT_TRUE(TestParse(input)); StyledString* str = test::GetValue<StyledString>(&table_, "string/foo"); ASSERT_NE(nullptr, str); - const std::string expected_str = "This is my aunt\u2019s string"; + const std::string expected_str = "This is my aunt\u2019s fickle string"; EXPECT_EQ(expected_str, *str->value->str); - EXPECT_EQ(1u, str->value->spans.size()); + EXPECT_EQ(2u, str->value->spans.size()); EXPECT_TRUE(str->untranslatable_sections.empty()); EXPECT_EQ(std::string("b"), *str->value->spans[0].name); EXPECT_EQ(17u, str->value->spans[0].first_char); - EXPECT_EQ(23u, str->value->spans[0].last_char); + EXPECT_EQ(30u, str->value->spans[0].last_char); + + EXPECT_EQ(std::string("small"), *str->value->spans[1].name); + EXPECT_EQ(24u, str->value->spans[1].first_char); + EXPECT_EQ(30u, str->value->spans[1].last_char); } TEST_F(ResourceParserTest, ParseStringWithWhitespace) { diff --git a/tools/aapt2/compile/PseudolocaleGenerator.cpp b/tools/aapt2/compile/PseudolocaleGenerator.cpp index fad9edd04e4c..a031ea4c31ec 100644 --- a/tools/aapt2/compile/PseudolocaleGenerator.cpp +++ b/tools/aapt2/compile/PseudolocaleGenerator.cpp @@ -22,136 +22,194 @@ #include "ResourceValues.h" #include "ValueVisitor.h" #include "compile/Pseudolocalizer.h" +#include "util/Util.h" using android::StringPiece; +using android::StringPiece16; namespace aapt { -std::unique_ptr<StyledString> PseudolocalizeStyledString( - StyledString* string, Pseudolocalizer::Method method, StringPool* pool) { - Pseudolocalizer localizer(method); +// The struct that represents both Span objects and UntranslatableSections. +struct UnifiedSpan { + // Only present for Span objects. If not present, this was an UntranslatableSection. + Maybe<std::string> tag; - const StringPiece original_text = *string->value->str; + // The UTF-16 index into the string where this span starts. + uint32_t first_char; - StyleString localized; + // The UTF-16 index into the string where this span ends, inclusive. + uint32_t last_char; +}; - // Copy the spans. We will update their offsets when we localize. - localized.spans.reserve(string->value->spans.size()); - for (const StringPool::Span& span : string->value->spans) { - localized.spans.push_back( - Span{*span.name, span.first_char, span.last_char}); +inline static bool operator<(const UnifiedSpan& left, const UnifiedSpan& right) { + if (left.first_char < right.first_char) { + return true; + } else if (left.first_char > right.first_char) { + return false; + } else if (left.last_char < right.last_char) { + return true; } + return false; +} - // The ranges are all represented with a single value. This is the start of - // one range and end of another. - struct Range { - size_t start; - - // If set to true, toggles the state of translatability. - bool toggle_translatability; - - // Once the new string is localized, these are the pointers to the spans to adjust. - // Since this struct represents the start of one range and end of another, - // we have the two pointers respectively. - uint32_t* update_start; - uint32_t* update_end; - }; - - auto cmp = [](const Range& r, size_t index) -> bool { - return r.start < index; - }; - - // Construct the ranges. The ranges are represented like so: [0, 2, 5, 7] - // The ranges are the spaces in between. In this example, with a total string - // length of 9, the vector represents: (0,1], (2,4], (5,6], (7,9] - // - std::vector<Range> ranges; - ranges.push_back(Range{0, false, nullptr, nullptr}); - ranges.push_back(Range{original_text.size() - 1, false, nullptr, nullptr}); - for (size_t i = 0; i < string->value->spans.size(); i++) { - const StringPool::Span& span = string->value->spans[i]; - - // Insert or update the Range marker for the start of this span. - auto iter = - std::lower_bound(ranges.begin(), ranges.end(), span.first_char, cmp); - if (iter != ranges.end() && iter->start == span.first_char) { - iter->update_start = &localized.spans[i].first_char; - } else { - ranges.insert(iter, Range{span.first_char, false, &localized.spans[i].first_char, nullptr}); - } +inline static UnifiedSpan SpanToUnifiedSpan(const StringPool::Span& span) { + return UnifiedSpan{*span.name, span.first_char, span.last_char}; +} + +inline static UnifiedSpan UntranslatableSectionToUnifiedSpan(const UntranslatableSection& section) { + return UnifiedSpan{ + {}, static_cast<uint32_t>(section.start), static_cast<uint32_t>(section.end) - 1}; +} - // Insert or update the Range marker for the end of this span. - iter = std::lower_bound(ranges.begin(), ranges.end(), span.last_char, cmp); - if (iter != ranges.end() && iter->start == span.last_char) { - iter->update_end = &localized.spans[i].last_char; +// Merges the Span and UntranslatableSections of this StyledString into a single vector of +// UnifiedSpans. This will first check that the Spans are sorted in ascending order. +static std::vector<UnifiedSpan> MergeSpans(const StyledString& string) { + // Ensure the Spans are sorted and converted. + std::vector<UnifiedSpan> sorted_spans; + sorted_spans.reserve(string.value->spans.size()); + std::transform(string.value->spans.begin(), string.value->spans.end(), + std::back_inserter(sorted_spans), SpanToUnifiedSpan); + + // Stable sort to ensure tag sequences like "<b><i>" are preserved. + std::stable_sort(sorted_spans.begin(), sorted_spans.end()); + + // Ensure the UntranslatableSections are sorted and converted. + std::vector<UnifiedSpan> sorted_untranslatable_sections; + sorted_untranslatable_sections.reserve(string.untranslatable_sections.size()); + std::transform(string.untranslatable_sections.begin(), string.untranslatable_sections.end(), + std::back_inserter(sorted_untranslatable_sections), + UntranslatableSectionToUnifiedSpan); + std::sort(sorted_untranslatable_sections.begin(), sorted_untranslatable_sections.end()); + + std::vector<UnifiedSpan> merged_spans; + merged_spans.reserve(sorted_spans.size() + sorted_untranslatable_sections.size()); + auto span_iter = sorted_spans.begin(); + auto untranslatable_iter = sorted_untranslatable_sections.begin(); + while (span_iter != sorted_spans.end() && + untranslatable_iter != sorted_untranslatable_sections.end()) { + if (*span_iter < *untranslatable_iter) { + merged_spans.push_back(std::move(*span_iter)); + ++span_iter; } else { - ranges.insert(iter, Range{span.last_char, false, nullptr, &localized.spans[i].last_char}); + merged_spans.push_back(std::move(*untranslatable_iter)); + ++untranslatable_iter; } } - // Parts of the string may be untranslatable. Merge those ranges - // in as well, so that we have continuous sections of text to - // feed into the pseudolocalizer. - // We do this by marking the beginning of a range as either toggling - // the translatability state or not. - for (const UntranslatableSection& section : string->untranslatable_sections) { - auto iter = std::lower_bound(ranges.begin(), ranges.end(), section.start, cmp); - if (iter != ranges.end() && iter->start == section.start) { - // An existing span starts (or ends) here. We just need to mark that - // the translatability should toggle here. If translatability was - // already being toggled, then that means we have two adjacent ranges of untranslatable - // text, so remove the toggle and only toggle at the end of this range, - // effectively merging these ranges. - iter->toggle_translatability = !iter->toggle_translatability; - } else { - // Insert a new range that specifies to toggle the translatability. - iter = ranges.insert(iter, Range{section.start, true, nullptr, nullptr}); - } + while (span_iter != sorted_spans.end()) { + merged_spans.push_back(std::move(*span_iter)); + ++span_iter; + } - // Update/create an end to the untranslatable section. - iter = std::lower_bound(iter, ranges.end(), section.end, cmp); - if (iter != ranges.end() && iter->start == section.end) { - iter->toggle_translatability = true; - } else { - iter = ranges.insert(iter, Range{section.end, true, nullptr, nullptr}); - } + while (untranslatable_iter != sorted_untranslatable_sections.end()) { + merged_spans.push_back(std::move(*untranslatable_iter)); + ++untranslatable_iter; } + return merged_spans; +} - localized.str += localizer.Start(); +std::unique_ptr<StyledString> PseudolocalizeStyledString(StyledString* string, + Pseudolocalizer::Method method, + StringPool* pool) { + Pseudolocalizer localizer(method); - // Iterate over the ranges and localize each section. - // The text starts as translatable, and each time a range has toggle_translatability - // set to true, we toggle whether to translate or not. - // This assumes no untranslatable ranges overlap. - bool translatable = true; - for (size_t i = 0; i < ranges.size(); i++) { - const size_t start = ranges[i].start; - size_t len = original_text.size() - start; - if (i + 1 < ranges.size()) { - len = ranges[i + 1].start - start; - } + // Collect the spans and untranslatable sections into one set of spans, sorted by first_char. + // This will effectively subdivide the string into multiple sections that can be individually + // pseudolocalized, while keeping the span indices synchronized. + std::vector<UnifiedSpan> merged_spans = MergeSpans(*string); - if (ranges[i].update_start) { - *ranges[i].update_start = localized.str.size(); - } + // All Span indices are UTF-16 based, according to the resources.arsc format expected by the + // runtime. So we will do all our processing in UTF-16, then convert back. + const std::u16string text16 = util::Utf8ToUtf16(*string->value->str); - if (ranges[i].update_end) { - *ranges[i].update_end = localized.str.size(); - } + // Convenient wrapper around the text that allows us to work with StringPieces. + const StringPiece16 text(text16); + + // The new string. + std::string new_string = localizer.Start(); + + // The stack that keeps track of what nested Span we're in. + std::vector<size_t> span_stack; + + // The current position in the original text. + uint32_t cursor = 0u; + + // The current position in the new text. + uint32_t new_cursor = utf8_to_utf16_length(reinterpret_cast<const uint8_t*>(new_string.data()), + new_string.size(), false); - if (ranges[i].toggle_translatability) { - translatable = !translatable; + // We assume no nesting of untranslatable sections, since XLIFF doesn't allow it. + bool translatable = true; + size_t span_idx = 0u; + while (span_idx < merged_spans.size() || !span_stack.empty()) { + UnifiedSpan* span = span_idx >= merged_spans.size() ? nullptr : &merged_spans[span_idx]; + UnifiedSpan* parent_span = span_stack.empty() ? nullptr : &merged_spans[span_stack.back()]; + + if (span != nullptr) { + if (parent_span == nullptr || parent_span->last_char > span->first_char) { + // There is no parent, or this span is the child of the parent. + // Pseudolocalize all the text until this span. + const StringPiece16 substr = text.substr(cursor, span->first_char - cursor); + cursor += substr.size(); + + // Pseudolocalize the substring. + std::string new_substr = util::Utf16ToUtf8(substr); + if (translatable) { + new_substr = localizer.Text(new_substr); + } + new_cursor += utf8_to_utf16_length(reinterpret_cast<const uint8_t*>(new_substr.data()), + new_substr.size(), false); + new_string += new_substr; + + // Rewrite the first_char. + span->first_char = new_cursor; + if (!span->tag) { + // An untranslatable section has begun! + translatable = false; + } + span_stack.push_back(span_idx); + ++span_idx; + continue; + } } - if (translatable) { - localized.str += localizer.Text(original_text.substr(start, len)); - } else { - localized.str += original_text.substr(start, len); + if (parent_span != nullptr) { + // There is a parent, and either this span is not a child of it, or there are no more spans. + // Pop this off the stack. + const StringPiece16 substr = text.substr(cursor, parent_span->last_char - cursor + 1); + cursor += substr.size(); + + // Pseudolocalize the substring. + std::string new_substr = util::Utf16ToUtf8(substr); + if (translatable) { + new_substr = localizer.Text(new_substr); + } + new_cursor += utf8_to_utf16_length(reinterpret_cast<const uint8_t*>(new_substr.data()), + new_substr.size(), false); + new_string += new_substr; + + parent_span->last_char = new_cursor - 1; + if (parent_span->tag) { + // An end to an untranslatable section. + translatable = true; + } + span_stack.pop_back(); } } - localized.str += localizer.End(); + // Finish the pseudolocalization at the end of the string. + new_string += localizer.Text(util::Utf16ToUtf8(text.substr(cursor, text.size() - cursor))); + new_string += localizer.End(); + + StyleString localized; + localized.str = std::move(new_string); + // Convert the UnifiedSpans into regular Spans, skipping the UntranslatableSections. + for (UnifiedSpan& span : merged_spans) { + if (span.tag) { + localized.spans.push_back(Span{std::move(span.tag.value()), span.first_char, span.last_char}); + } + } return util::make_unique<StyledString>(pool->MakeRef(localized)); } @@ -175,8 +233,7 @@ class Visitor : public RawValueVisitor { if (sub_visitor.value) { localized->values[i] = std::move(sub_visitor.item); } else { - localized->values[i] = - std::unique_ptr<Item>(plural->values[i]->Clone(pool_)); + localized->values[i] = std::unique_ptr<Item>(plural->values[i]->Clone(pool_)); } } } @@ -210,8 +267,7 @@ class Visitor : public RawValueVisitor { } result += localizer_.End(); - std::unique_ptr<String> localized = - util::make_unique<String>(pool_->MakeRef(result)); + std::unique_ptr<String> localized = util::make_unique<String>(pool_->MakeRef(result)); localized->SetSource(string->GetSource()); localized->SetWeak(true); item = std::move(localized); @@ -282,14 +338,10 @@ void PseudolocalizeIfNeeded(const Pseudolocalizer::Method method, } } -/** - * A value is pseudolocalizable if it does not define a locale (or is the - * default locale) - * and is translatable. - */ +// A value is pseudolocalizable if it does not define a locale (or is the default locale) and is +// translatable. static bool IsPseudolocalizable(ResourceConfigValue* config_value) { - const int diff = - config_value->config.diff(ConfigDescription::DefaultConfig()); + const int diff = config_value->config.diff(ConfigDescription::DefaultConfig()); if (diff & ConfigDescription::CONFIG_LOCALE) { return false; } @@ -298,19 +350,16 @@ static bool IsPseudolocalizable(ResourceConfigValue* config_value) { } // namespace -bool PseudolocaleGenerator::Consume(IAaptContext* context, - ResourceTable* table) { +bool PseudolocaleGenerator::Consume(IAaptContext* context, ResourceTable* table) { for (auto& package : table->packages) { for (auto& type : package->types) { for (auto& entry : type->entries) { - std::vector<ResourceConfigValue*> values = - entry->FindValuesIf(IsPseudolocalizable); - + std::vector<ResourceConfigValue*> values = entry->FindValuesIf(IsPseudolocalizable); for (ResourceConfigValue* value : values) { - PseudolocalizeIfNeeded(Pseudolocalizer::Method::kAccent, value, - &table->string_pool, entry.get()); - PseudolocalizeIfNeeded(Pseudolocalizer::Method::kBidi, value, - &table->string_pool, entry.get()); + PseudolocalizeIfNeeded(Pseudolocalizer::Method::kAccent, value, &table->string_pool, + entry.get()); + PseudolocalizeIfNeeded(Pseudolocalizer::Method::kBidi, value, &table->string_pool, + entry.get()); } } } diff --git a/tools/aapt2/compile/PseudolocaleGenerator_test.cpp b/tools/aapt2/compile/PseudolocaleGenerator_test.cpp index 4db37db55eb7..b08e1dab35a9 100644 --- a/tools/aapt2/compile/PseudolocaleGenerator_test.cpp +++ b/tools/aapt2/compile/PseudolocaleGenerator_test.cpp @@ -25,7 +25,7 @@ TEST(PseudolocaleGeneratorTest, PseudolocalizeStyledString) { StringPool pool; StyleString original_style; original_style.str = "Hello world!"; - original_style.spans = {Span{"b", 2, 3}, Span{"b", 6, 7}, Span{"i", 1, 10}}; + original_style.spans = {Span{"i", 1, 10}, Span{"b", 2, 3}, Span{"b", 6, 7}}; std::unique_ptr<StyledString> new_string = PseudolocalizeStyledString( util::make_unique<StyledString>(pool.MakeRef(original_style)).get(), @@ -34,22 +34,19 @@ TEST(PseudolocaleGeneratorTest, PseudolocalizeStyledString) { EXPECT_EQ(original_style.str, *new_string->value->str); ASSERT_EQ(original_style.spans.size(), new_string->value->spans.size()); - EXPECT_EQ(std::string("He").size(), new_string->value->spans[0].first_char); - EXPECT_EQ(std::string("Hel").size(), new_string->value->spans[0].last_char); - EXPECT_EQ(std::string("b"), *new_string->value->spans[0].name); + EXPECT_EQ(std::string("i"), *new_string->value->spans[0].name); + EXPECT_EQ(std::u16string(u"H").size(), new_string->value->spans[0].first_char); + EXPECT_EQ(std::u16string(u"Hello worl").size(), new_string->value->spans[0].last_char); - EXPECT_EQ(std::string("Hello ").size(), - new_string->value->spans[1].first_char); - EXPECT_EQ(std::string("Hello w").size(), - new_string->value->spans[1].last_char); EXPECT_EQ(std::string("b"), *new_string->value->spans[1].name); + EXPECT_EQ(std::u16string(u"He").size(), new_string->value->spans[1].first_char); + EXPECT_EQ(std::u16string(u"Hel").size(), new_string->value->spans[1].last_char); - EXPECT_EQ(std::string("H").size(), new_string->value->spans[2].first_char); - EXPECT_EQ(std::string("Hello worl").size(), - new_string->value->spans[2].last_char); - EXPECT_EQ(std::string("i"), *new_string->value->spans[2].name); + EXPECT_EQ(std::string("b"), *new_string->value->spans[2].name); + EXPECT_EQ(std::u16string(u"Hello ").size(), new_string->value->spans[2].first_char); + EXPECT_EQ(std::u16string(u"Hello w").size(), new_string->value->spans[2].last_char); - original_style.spans.push_back(Span{"em", 0, 11u}); + original_style.spans.insert(original_style.spans.begin(), Span{"em", 0, 11u}); new_string = PseudolocalizeStyledString( util::make_unique<StyledString>(pool.MakeRef(original_style)).get(), @@ -58,23 +55,128 @@ TEST(PseudolocaleGeneratorTest, PseudolocalizeStyledString) { EXPECT_EQ(std::string("[Ĥéļļö ŵöŕļð¡ one two]"), *new_string->value->str); ASSERT_EQ(original_style.spans.size(), new_string->value->spans.size()); - EXPECT_EQ(std::string("[Ĥé").size(), new_string->value->spans[0].first_char); - EXPECT_EQ(std::string("[Ĥéļ").size(), new_string->value->spans[0].last_char); + EXPECT_EQ(std::u16string(u"[").size(), new_string->value->spans[0].first_char); + EXPECT_EQ(std::u16string(u"[Ĥéļļö ŵöŕļð").size(), new_string->value->spans[0].last_char); + + EXPECT_EQ(std::u16string(u"[Ĥ").size(), new_string->value->spans[1].first_char); + EXPECT_EQ(std::u16string(u"[Ĥéļļö ŵöŕļ").size(), new_string->value->spans[1].last_char); + + EXPECT_EQ(std::u16string(u"[Ĥé").size(), new_string->value->spans[2].first_char); + EXPECT_EQ(std::u16string(u"[Ĥéļ").size(), new_string->value->spans[2].last_char); + + EXPECT_EQ(std::u16string(u"[Ĥéļļö ").size(), new_string->value->spans[3].first_char); + EXPECT_EQ(std::u16string(u"[Ĥéļļö ŵ").size(), new_string->value->spans[3].last_char); +} + +TEST(PseudolocaleGeneratorTest, PseudolocalizeAdjacentNestedTags) { + StringPool pool; + StyleString original_style; + original_style.str = "bold"; + original_style.spans = {Span{"b", 0, 3}, Span{"i", 0, 3}}; + + std::unique_ptr<StyledString> new_string = PseudolocalizeStyledString( + util::make_unique<StyledString>(pool.MakeRef(original_style)).get(), + Pseudolocalizer::Method::kAccent, &pool); + ASSERT_NE(nullptr, new_string); + ASSERT_EQ(2u, new_string->value->spans.size()); + EXPECT_EQ(std::string("[ɓöļð one]"), *new_string->value->str); + + EXPECT_EQ(std::string("b"), *new_string->value->spans[0].name); + EXPECT_EQ(std::u16string(u"[").size(), new_string->value->spans[0].first_char); + EXPECT_EQ(std::u16string(u"[ɓöļ").size(), new_string->value->spans[0].last_char); + + EXPECT_EQ(std::string("i"), *new_string->value->spans[1].name); + EXPECT_EQ(std::u16string(u"[").size(), new_string->value->spans[1].first_char); + EXPECT_EQ(std::u16string(u"[ɓöļ").size(), new_string->value->spans[1].last_char); +} + +TEST(PseudolocaleGeneratorTest, PseudolocalizeAdjacentTagsUnsorted) { + StringPool pool; + StyleString original_style; + original_style.str = "bold"; + original_style.spans = {Span{"i", 2, 3}, Span{"b", 0, 1}}; + + std::unique_ptr<StyledString> new_string = PseudolocalizeStyledString( + util::make_unique<StyledString>(pool.MakeRef(original_style)).get(), + Pseudolocalizer::Method::kAccent, &pool); + ASSERT_NE(nullptr, new_string); + ASSERT_EQ(2u, new_string->value->spans.size()); + EXPECT_EQ(std::string("[ɓöļð one]"), *new_string->value->str); + + EXPECT_EQ(std::string("b"), *new_string->value->spans[0].name); + EXPECT_EQ(std::u16string(u"[").size(), new_string->value->spans[0].first_char); + EXPECT_EQ(std::u16string(u"[ɓ").size(), new_string->value->spans[0].last_char); + + EXPECT_EQ(std::string("i"), *new_string->value->spans[1].name); + EXPECT_EQ(std::u16string(u"[ɓö").size(), new_string->value->spans[1].first_char); + EXPECT_EQ(std::u16string(u"[ɓöļ").size(), new_string->value->spans[1].last_char); +} + +TEST(PseudolocaleGeneratorTest, PseudolocalizeNestedAndAdjacentTags) { + StringPool pool; + StyleString original_style; + original_style.str = "This sentence is not what you think it is at all."; + original_style.spans = {Span{"b", 16u, 19u}, Span{"em", 29u, 47u}, Span{"i", 38u, 40u}, + Span{"b", 44u, 47u}}; + + std::unique_ptr<StyledString> new_string = PseudolocalizeStyledString( + util::make_unique<StyledString>(pool.MakeRef(original_style)).get(), + Pseudolocalizer::Method::kAccent, &pool); + ASSERT_NE(nullptr, new_string); + ASSERT_EQ(4u, new_string->value->spans.size()); + EXPECT_EQ(std::string( + "[Ţĥîš šéñţéñçé îš ñöţ ŵĥåţ ýöû ţĥîñķ îţ îš åţ åļļ. one two three four five six]"), + *new_string->value->str); + + EXPECT_EQ(std::string("b"), *new_string->value->spans[0].name); + EXPECT_EQ(std::u16string(u"[Ţĥîš šéñţéñçé îš").size(), new_string->value->spans[0].first_char); + EXPECT_EQ(std::u16string(u"[Ţĥîš šéñţéñçé îš ñö").size(), new_string->value->spans[0].last_char); - EXPECT_EQ(std::string("[Ĥéļļö ").size(), + EXPECT_EQ(std::string("em"), *new_string->value->spans[1].name); + EXPECT_EQ(std::u16string(u"[Ţĥîš šéñţéñçé îš ñöţ ŵĥåţ ýöû").size(), new_string->value->spans[1].first_char); - EXPECT_EQ(std::string("[Ĥéļļö ŵ").size(), + EXPECT_EQ(std::u16string(u"[Ţĥîš šéñţéñçé îš ñöţ ŵĥåţ ýöû ţĥîñķ îţ îš åţ åļ").size(), new_string->value->spans[1].last_char); - EXPECT_EQ(std::string("[Ĥ").size(), new_string->value->spans[2].first_char); - EXPECT_EQ(std::string("[Ĥéļļö ŵöŕļ").size(), + EXPECT_EQ(std::string("i"), *new_string->value->spans[2].name); + EXPECT_EQ(std::u16string(u"[Ţĥîš šéñţéñçé îš ñöţ ŵĥåţ ýöû ţĥîñķ îţ").size(), + new_string->value->spans[2].first_char); + EXPECT_EQ(std::u16string(u"[Ţĥîš šéñţéñçé îš ñöţ ŵĥåţ ýöû ţĥîñķ îţ î").size(), new_string->value->spans[2].last_char); - EXPECT_EQ(std::string("[").size(), new_string->value->spans[3].first_char); - EXPECT_EQ(std::string("[Ĥéļļö ŵöŕļð").size(), + EXPECT_EQ(std::string("b"), *new_string->value->spans[3].name); + EXPECT_EQ(std::u16string(u"[Ţĥîš šéñţéñçé îš ñöţ ŵĥåţ ýöû ţĥîñķ îţ îš åţ").size(), + new_string->value->spans[3].first_char); + EXPECT_EQ(std::u16string(u"[Ţĥîš šéñţéñçé îš ñöţ ŵĥåţ ýöû ţĥîñķ îţ îš åţ åļ").size(), new_string->value->spans[3].last_char); } +TEST(PseudolocaleGeneratorTest, PseudolocalizePartsOfString) { + StringPool pool; + StyleString original_style; + original_style.str = "This should NOT be pseudolocalized."; + original_style.spans = {Span{"em", 4u, 14u}, Span{"i", 18u, 33u}}; + std::unique_ptr<StyledString> original_string = + util::make_unique<StyledString>(pool.MakeRef(original_style)); + original_string->untranslatable_sections = {UntranslatableSection{11u, 15u}}; + + std::unique_ptr<StyledString> new_string = + PseudolocalizeStyledString(original_string.get(), Pseudolocalizer::Method::kAccent, &pool); + ASSERT_NE(nullptr, new_string); + ASSERT_EQ(2u, new_string->value->spans.size()); + EXPECT_EQ(std::string("[Ţĥîš šĥöûļð NOT ɓé þšéûðöļöçåļîžéð. one two three four]"), + *new_string->value->str); + + EXPECT_EQ(std::string("em"), *new_string->value->spans[0].name); + EXPECT_EQ(std::u16string(u"[Ţĥîš").size(), new_string->value->spans[0].first_char); + EXPECT_EQ(std::u16string(u"[Ţĥîš šĥöûļð NO").size(), new_string->value->spans[0].last_char); + + EXPECT_EQ(std::string("i"), *new_string->value->spans[1].name); + EXPECT_EQ(std::u16string(u"[Ţĥîš šĥöûļð NOT ɓé").size(), new_string->value->spans[1].first_char); + EXPECT_EQ(std::u16string(u"[Ţĥîš šĥöûļð NOT ɓé þšéûðöļöçåļîžé").size(), + new_string->value->spans[1].last_char); +} + TEST(PseudolocaleGeneratorTest, PseudolocalizeOnlyDefaultConfigs) { std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() @@ -138,7 +240,7 @@ TEST(PseudolocaleGeneratorTest, RespectUntranslateableSections) { { StyleString original_style; original_style.str = "Hello world!"; - original_style.spans = {Span{"b", 2, 3}, Span{"b", 6, 7}, Span{"i", 1, 10}}; + original_style.spans = {Span{"i", 1, 10}, Span{"b", 2, 3}, Span{"b", 6, 7}}; auto styled_string = util::make_unique<StyledString>(table->string_pool.MakeRef(original_style)); diff --git a/tools/fonts/fontchain_lint.py b/tools/fonts/fontchain_lint.py index bb11dabee8e6..baee21c315d6 100755 --- a/tools/fonts/fontchain_lint.py +++ b/tools/fonts/fontchain_lint.py @@ -413,6 +413,11 @@ def parse_ucd(ucd_path): global _emoji_sequences, _emoji_zwj_sequences _emoji_properties = parse_unicode_datafile( path.join(ucd_path, 'emoji-data.txt'), reverse=True) + emoji_properties_additions = parse_unicode_datafile( + path.join(ucd_path, 'additions', 'emoji-data.txt'), reverse=True) + for prop in emoji_properties_additions.keys(): + _emoji_properties[prop].update(emoji_properties_additions[prop]) + _chars_by_age = parse_unicode_datafile( path.join(ucd_path, 'DerivedAge.txt'), reverse=True) sequences = parse_standardized_variants( @@ -420,6 +425,7 @@ def parse_ucd(ucd_path): _text_variation_sequences, _emoji_variation_sequences = sequences _emoji_sequences = parse_unicode_datafile( path.join(ucd_path, 'emoji-sequences.txt')) + _emoji_zwj_sequences = parse_unicode_datafile( path.join(ucd_path, 'emoji-zwj-sequences.txt')) _emoji_zwj_sequences.update(parse_unicode_datafile( @@ -450,22 +456,6 @@ EQUIVALENT_FLAGS = { COMBINING_KEYCAP = 0x20E3 -# Characters that Android defaults to emoji style, different from the recommendations in UTR #51 -ANDROID_DEFAULT_EMOJI = frozenset({ - 0x2600, # BLACK SUN WITH RAYS - 0x2601, # CLOUD - 0x260E, # BLACK TELEPHONE - 0x261D, # WHITE UP POINTING INDEX - 0x263A, # WHITE SMILING FACE - 0x2660, # BLACK SPADE SUIT - 0x2663, # BLACK CLUB SUIT - 0x2665, # BLACK HEART SUIT - 0x2666, # BLACK DIAMOND SUIT - 0x270C, # VICTORY HAND - 0x2744, # SNOWFLAKE - 0x2764, # HEAVY BLACK HEART -}) - LEGACY_ANDROID_EMOJI = { 0xFE4E5: flag_sequence('JP'), 0xFE4E6: flag_sequence('US'), @@ -554,7 +544,6 @@ def compute_expected_emoji(): set(LEGACY_ANDROID_EMOJI.keys())) default_emoji = ( _emoji_properties['Emoji_Presentation'] | - ANDROID_DEFAULT_EMOJI | all_sequences | set(LEGACY_ANDROID_EMOJI.keys())) |