Merge "Fix issue #37306208: CTS: ServiceTest#testForegroundService..." into oc-dev
diff --git a/api/current.txt b/api/current.txt
index d3184d6..e4169b2 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -1710,6 +1710,7 @@
field public static final int ic_notification_clear_all = 17301594; // 0x108005a
field public static final int ic_notification_overlay = 17301595; // 0x108005b
field public static final int ic_partial_secure = 17301596; // 0x108005c
+ field public static final int ic_picture_in_picture = 17301685; // 0x10800b5
field public static final int ic_popup_disk_full = 17301597; // 0x108005d
field public static final int ic_popup_reminder = 17301598; // 0x108005e
field public static final int ic_popup_sync = 17301599; // 0x108005f
@@ -3848,7 +3849,7 @@
method public deprecated java.util.List<android.app.ActivityManager.RecentTaskInfo> getRecentTasks(int, int) throws java.lang.SecurityException;
method public java.util.List<android.app.ActivityManager.RunningAppProcessInfo> getRunningAppProcesses();
method public android.app.PendingIntent getRunningServiceControlPanel(android.content.ComponentName) throws java.lang.SecurityException;
- method public java.util.List<android.app.ActivityManager.RunningServiceInfo> getRunningServices(int) throws java.lang.SecurityException;
+ method public deprecated java.util.List<android.app.ActivityManager.RunningServiceInfo> getRunningServices(int) throws java.lang.SecurityException;
method public deprecated java.util.List<android.app.ActivityManager.RunningTaskInfo> getRunningTasks(int) throws java.lang.SecurityException;
method public deprecated boolean isInLockTaskMode();
method public boolean isLowRamDevice();
@@ -4577,6 +4578,7 @@
method public final android.app.FragmentManager getFragmentManager();
method public final java.lang.Object getHost();
method public final int getId();
+ method public final android.view.LayoutInflater getLayoutInflater();
method public android.app.LoaderManager getLoaderManager();
method public final android.app.Fragment getParentFragment();
method public android.transition.Transition getReenterTransition();
@@ -5604,7 +5606,6 @@
method public boolean removeAutomaticZenRule(java.lang.String);
method public final void setInterruptionFilter(int);
method public void setNotificationPolicy(android.app.NotificationManager.Policy);
- method public deprecated android.content.ComponentName startServiceInForeground(android.content.Intent, int, android.app.Notification);
method public boolean updateAutomaticZenRule(java.lang.String, android.app.AutomaticZenRule);
field public static final java.lang.String ACTION_INTERRUPTION_FILTER_CHANGED = "android.app.action.INTERRUPTION_FILTER_CHANGED";
field public static final java.lang.String ACTION_NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED = "android.app.action.NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED";
@@ -6625,7 +6626,7 @@
public static class AssistStructure.ViewNode {
method public float getAlpha();
- method public java.lang.String[] getAutoFillHints();
+ method public java.lang.String[] getAutofillHints();
method public android.view.autofill.AutofillId getAutofillId();
method public java.lang.String[] getAutofillOptions();
method public int getAutofillType();
@@ -7064,6 +7065,7 @@
method public static void deleteAllHosts();
method public void deleteAppWidgetId(int);
method public void deleteHost();
+ method public int[] getAppWidgetIds();
method protected android.appwidget.AppWidgetHostView onCreateView(android.content.Context, int, android.appwidget.AppWidgetProviderInfo);
method protected void onProviderChanged(int, android.appwidget.AppWidgetProviderInfo);
method protected void onProvidersChanged();
@@ -10941,12 +10943,13 @@
method public android.content.pm.VersionedPackage getDeclaringPackage();
method public java.util.List<android.content.pm.VersionedPackage> getDependentPackages();
method public java.lang.String getName();
- method public int getVersion();
- method public boolean isBuiltin();
- method public boolean isDynamic();
- method public boolean isStatic();
+ method public int getType();
+ method public long getVersion();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.content.pm.SharedLibraryInfo> CREATOR;
+ field public static final int TYPE_BUILTIN = 0; // 0x0
+ field public static final int TYPE_DYNAMIC = 1; // 0x1
+ field public static final int TYPE_STATIC = 2; // 0x2
field public static final int VERSION_UNDEFINED = -1; // 0xffffffff
}
@@ -12600,7 +12603,6 @@
public class BitmapShader extends android.graphics.Shader {
ctor public BitmapShader(android.graphics.Bitmap, android.graphics.Shader.TileMode, android.graphics.Shader.TileMode);
- method public void set(android.graphics.Bitmap, android.graphics.Shader.TileMode, android.graphics.Shader.TileMode);
}
public class BlurMaskFilter extends android.graphics.MaskFilter {
@@ -12853,8 +12855,6 @@
ctor public ColorMatrixColorFilter(android.graphics.ColorMatrix);
ctor public ColorMatrixColorFilter(float[]);
method public void getColorMatrix(android.graphics.ColorMatrix);
- method public void setColorMatrix(android.graphics.ColorMatrix);
- method public void setColorMatrixArray(float[]);
}
public abstract class ColorSpace {
@@ -12995,8 +12995,6 @@
public class ComposeShader extends android.graphics.Shader {
ctor public ComposeShader(android.graphics.Shader, android.graphics.Shader, android.graphics.Xfermode);
ctor public ComposeShader(android.graphics.Shader, android.graphics.Shader, android.graphics.PorterDuff.Mode);
- method public void set(android.graphics.Shader, android.graphics.Shader, android.graphics.Xfermode);
- method public void set(android.graphics.Shader, android.graphics.Shader, android.graphics.PorterDuff.Mode);
}
public class CornerPathEffect extends android.graphics.PathEffect {
@@ -13069,15 +13067,11 @@
ctor public LightingColorFilter(int, int);
method public int getColorAdd();
method public int getColorMultiply();
- method public void setColorAdd(int);
- method public void setColorMultiply(int);
}
public class LinearGradient extends android.graphics.Shader {
ctor public LinearGradient(float, float, float, float, int[], float[], android.graphics.Shader.TileMode);
ctor public LinearGradient(float, float, float, float, int, int, android.graphics.Shader.TileMode);
- method public void set(float, float, float, float, int[], float[], android.graphics.Shader.TileMode);
- method public void set(float, float, float, float, int, int, android.graphics.Shader.TileMode);
}
public class MaskFilter {
@@ -13587,10 +13581,6 @@
public class PorterDuffColorFilter extends android.graphics.ColorFilter {
ctor public PorterDuffColorFilter(int, android.graphics.PorterDuff.Mode);
- method public int getColor();
- method public android.graphics.PorterDuff.Mode getMode();
- method public void setColor(int);
- method public void setMode(android.graphics.PorterDuff.Mode);
}
public class PorterDuffXfermode extends android.graphics.Xfermode {
@@ -13600,8 +13590,6 @@
public class RadialGradient extends android.graphics.Shader {
ctor public RadialGradient(float, float, float, int[], float[], android.graphics.Shader.TileMode);
ctor public RadialGradient(float, float, float, int, int, android.graphics.Shader.TileMode);
- method public void set(float, float, float, int[], float[], android.graphics.Shader.TileMode);
- method public void set(float, float, float, int, int, android.graphics.Shader.TileMode);
}
public final class Rect implements android.os.Parcelable {
@@ -13786,8 +13774,6 @@
public class SweepGradient extends android.graphics.Shader {
ctor public SweepGradient(float, float, int[], float[]);
ctor public SweepGradient(float, float, int, int);
- method public void set(float, float, int[], float[]);
- method public void set(float, float, int, int);
}
public class Typeface {
@@ -20801,13 +20787,10 @@
method public boolean hasSpeedAccuracy();
method public boolean hasVerticalAccuracy();
method public boolean isFromMockProvider();
- method public void removeAccuracy();
- method public void removeAltitude();
- method public void removeBearing();
- method public void removeBearingAccuracy();
- method public void removeSpeed();
- method public void removeSpeedAccuracy();
- method public void removeVerticalAccuracy();
+ method public deprecated void removeAccuracy();
+ method public deprecated void removeAltitude();
+ method public deprecated void removeBearing();
+ method public deprecated void removeSpeed();
method public void reset();
method public void set(android.location.Location);
method public void setAccuracy(float);
@@ -22775,7 +22758,7 @@
method public android.media.MediaPlayer.DrmInfo getDrmInfo();
method public java.lang.String getDrmPropertyString(java.lang.String) throws android.media.MediaPlayer.NoDrmSchemeException;
method public int getDuration();
- method public android.media.MediaDrm.KeyRequest getKeyRequest(byte[], java.lang.String, int, java.util.Map<java.lang.String, java.lang.String>) throws android.media.MediaPlayer.NoDrmSchemeException;
+ method public android.media.MediaDrm.KeyRequest getKeyRequest(byte[], byte[], java.lang.String, int, java.util.Map<java.lang.String, java.lang.String>) throws android.media.MediaPlayer.NoDrmSchemeException;
method public android.os.PersistableBundle getMetrics();
method public android.media.PlaybackParams getPlaybackParams();
method public int getSelectedTrack(int) throws java.lang.IllegalStateException;
@@ -22789,7 +22772,7 @@
method public void pause() throws java.lang.IllegalStateException;
method public void prepare() throws java.io.IOException, java.lang.IllegalStateException;
method public void prepareAsync() throws java.lang.IllegalStateException;
- method public void prepareDrm(java.util.UUID) throws android.media.MediaPlayer.ProvisioningErrorException, android.media.ResourceBusyException, android.media.UnsupportedSchemeException;
+ method public void prepareDrm(java.util.UUID) throws android.media.MediaPlayer.ProvisioningNetworkErrorException, android.media.MediaPlayer.ProvisioningServerErrorException, android.media.ResourceBusyException, android.media.UnsupportedSchemeException;
method public byte[] provideKeyResponse(byte[], byte[]) throws android.media.DeniedByServerException, android.media.MediaPlayer.NoDrmSchemeException;
method public void release();
method public void releaseDrm() throws android.media.MediaPlayer.NoDrmSchemeException;
@@ -22816,7 +22799,7 @@
method public void setNextMediaPlayer(android.media.MediaPlayer);
method public void setOnBufferingUpdateListener(android.media.MediaPlayer.OnBufferingUpdateListener);
method public void setOnCompletionListener(android.media.MediaPlayer.OnCompletionListener);
- method public void setOnDrmConfigListener(android.media.MediaPlayer.OnDrmConfigListener);
+ method public void setOnDrmConfigHelper(android.media.MediaPlayer.OnDrmConfigHelper);
method public void setOnDrmInfoListener(android.media.MediaPlayer.OnDrmInfoListener);
method public void setOnDrmInfoListener(android.media.MediaPlayer.OnDrmInfoListener, android.os.Handler);
method public void setOnDrmPreparedListener(android.media.MediaPlayer.OnDrmPreparedListener);
@@ -22857,6 +22840,10 @@
field public static final int MEDIA_INFO_VIDEO_RENDERING_START = 3; // 0x3
field public static final int MEDIA_INFO_VIDEO_TRACK_LAGGING = 700; // 0x2bc
field public static final java.lang.String MEDIA_MIMETYPE_TEXT_SUBRIP = "application/x-subrip";
+ field public static final int PREPARE_DRM_STATUS_PREPARATION_ERROR = 3; // 0x3
+ field public static final int PREPARE_DRM_STATUS_PROVISIONING_NETWORK_ERROR = 1; // 0x1
+ field public static final int PREPARE_DRM_STATUS_PROVISIONING_SERVER_ERROR = 2; // 0x2
+ field public static final int PREPARE_DRM_STATUS_SUCCESS = 0; // 0x0
field public static final int SEEK_CLOSEST = 3; // 0x3
field public static final int SEEK_CLOSEST_SYNC = 2; // 0x2
field public static final int SEEK_NEXT_SYNC = 1; // 0x1
@@ -22866,7 +22853,6 @@
}
public static final class MediaPlayer.DrmInfo {
- method public java.lang.String[] getMimes();
method public java.util.Map<java.util.UUID, byte[]> getPssh();
method public java.util.UUID[] getSupportedSchemes();
}
@@ -22898,7 +22884,7 @@
method public abstract void onCompletion(android.media.MediaPlayer);
}
- public static abstract interface MediaPlayer.OnDrmConfigListener {
+ public static abstract interface MediaPlayer.OnDrmConfigHelper {
method public abstract void onDrmConfig(android.media.MediaPlayer);
}
@@ -22907,7 +22893,7 @@
}
public static abstract interface MediaPlayer.OnDrmPreparedListener {
- method public abstract void onDrmPrepared(android.media.MediaPlayer, boolean);
+ method public abstract void onDrmPrepared(android.media.MediaPlayer, int);
}
public static abstract interface MediaPlayer.OnErrorListener {
@@ -22938,8 +22924,12 @@
method public abstract void onVideoSizeChanged(android.media.MediaPlayer, int, int);
}
- public static final class MediaPlayer.ProvisioningErrorException extends android.media.MediaDrmException {
- ctor public MediaPlayer.ProvisioningErrorException(java.lang.String);
+ public static final class MediaPlayer.ProvisioningNetworkErrorException extends android.media.MediaDrmException {
+ ctor public MediaPlayer.ProvisioningNetworkErrorException(java.lang.String);
+ }
+
+ public static final class MediaPlayer.ProvisioningServerErrorException extends android.media.MediaDrmException {
+ ctor public MediaPlayer.ProvisioningServerErrorException(java.lang.String);
}
public static class MediaPlayer.TrackInfo implements android.os.Parcelable {
@@ -25528,14 +25518,10 @@
}
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 applyTransportModeTransform(java.io.FileDescriptor, android.net.IpSecTransform) throws java.io.IOException;
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 removeTransportModeTransform(java.io.FileDescriptor, android.net.IpSecTransform);
+ method public void removeTransportModeTransform(java.io.FileDescriptor, android.net.IpSecTransform) throws java.io.IOException;
method public android.net.IpSecManager.SecurityParameterIndex reserveSecurityParameterIndex(int, java.net.InetAddress) throws android.net.IpSecManager.ResourceUnavailableException;
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
@@ -25554,7 +25540,7 @@
}
public static final class IpSecManager.UdpEncapsulationSocket implements java.lang.AutoCloseable {
- method public void close();
+ method public void close() throws java.io.IOException;
method public int getPort();
method public java.io.FileDescriptor getSocket();
}
@@ -26752,8 +26738,8 @@
}
public class DiscoverySession {
- method public java.lang.String createNetworkSpecifierOpen(android.net.wifi.aware.PeerHandle);
- method public java.lang.String createNetworkSpecifierPassphrase(android.net.wifi.aware.PeerHandle, java.lang.String);
+ method public android.net.NetworkSpecifier createNetworkSpecifierOpen(android.net.wifi.aware.PeerHandle);
+ method public android.net.NetworkSpecifier createNetworkSpecifierPassphrase(android.net.wifi.aware.PeerHandle, java.lang.String);
method public void destroy();
method public void sendMessage(android.net.wifi.aware.PeerHandle, int, byte[]);
}
@@ -26839,8 +26825,8 @@
}
public class WifiAwareSession {
- method public java.lang.String createNetworkSpecifierOpen(int, byte[]);
- method public java.lang.String createNetworkSpecifierPassphrase(int, byte[], java.lang.String);
+ method public android.net.NetworkSpecifier createNetworkSpecifierOpen(int, byte[]);
+ method public android.net.NetworkSpecifier createNetworkSpecifierPassphrase(int, byte[], java.lang.String);
method public void destroy();
method public void publish(android.net.wifi.aware.PublishConfig, android.net.wifi.aware.DiscoverySessionCallback, android.os.Handler);
method public void subscribe(android.net.wifi.aware.SubscribeConfig, android.net.wifi.aware.DiscoverySessionCallback, android.os.Handler);
@@ -37048,6 +37034,7 @@
public abstract class AutofillService extends android.app.Service {
ctor public AutofillService();
method public final deprecated void disableSelf();
+ method public final android.service.autofill.FillEventHistory getFillEventHistory();
method public final android.os.IBinder onBind(android.content.Intent);
method public void onConnected();
method public void onDisconnected();
@@ -37070,6 +37057,7 @@
ctor public Dataset.Builder();
method public android.service.autofill.Dataset build();
method public android.service.autofill.Dataset.Builder setAuthentication(android.content.IntentSender);
+ method public android.service.autofill.Dataset.Builder setId(java.lang.String);
method public android.service.autofill.Dataset.Builder setValue(android.view.autofill.AutofillId, android.view.autofill.AutofillValue);
method public android.service.autofill.Dataset.Builder setValue(android.view.autofill.AutofillId, android.view.autofill.AutofillValue, android.widget.RemoteViews);
}
@@ -37087,6 +37075,23 @@
field public static final android.os.Parcelable.Creator<android.service.autofill.FillContext> CREATOR;
}
+ public final class FillEventHistory implements android.os.Parcelable {
+ method public int describeContents();
+ method public android.os.Bundle getClientState();
+ method public java.util.List<android.service.autofill.FillEventHistory.Event> getEvents();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.service.autofill.FillEventHistory> CREATOR;
+ }
+
+ public static final class FillEventHistory.Event {
+ method public java.lang.String getDatasetId();
+ method public int getType();
+ field public static final int TYPE_AUTHENTICATION_SELECTED = 2; // 0x2
+ field public static final int TYPE_DATASET_AUTHENTICATION_SELECTED = 1; // 0x1
+ field public static final int TYPE_DATASET_SELECTED = 0; // 0x0
+ field public static final int TYPE_SAVE_SHOWN = 3; // 0x3
+ }
+
public final class FillRequest implements android.os.Parcelable {
method public int describeContents();
method public android.os.Bundle getClientState();
@@ -37111,6 +37116,7 @@
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 setClientState(android.os.Bundle);
method public deprecated android.service.autofill.FillResponse.Builder setExtras(android.os.Bundle);
+ method public android.service.autofill.FillResponse.Builder setIgnoredIds(android.view.autofill.AutofillId...);
method public android.service.autofill.FillResponse.Builder setSaveInfo(android.service.autofill.SaveInfo);
}
@@ -37123,6 +37129,7 @@
method public int describeContents();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.service.autofill.SaveInfo> CREATOR;
+ field public static final int FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE = 1; // 0x1
field public static final int SAVE_DATA_TYPE_ADDRESS = 2; // 0x2
field public static final int SAVE_DATA_TYPE_CREDIT_CARD = 4; // 0x4
field public static final int SAVE_DATA_TYPE_EMAIL_ADDRESS = 16; // 0x10
@@ -37135,9 +37142,9 @@
ctor public SaveInfo.Builder(int, android.view.autofill.AutofillId[]);
method public android.service.autofill.SaveInfo build();
method public android.service.autofill.SaveInfo.Builder setDescription(java.lang.CharSequence);
+ method public android.service.autofill.SaveInfo.Builder setFlags(int);
method public android.service.autofill.SaveInfo.Builder setNegativeAction(java.lang.CharSequence, android.content.IntentSender);
method public android.service.autofill.SaveInfo.Builder setOptionalIds(android.view.autofill.AutofillId[]);
- method public android.service.autofill.SaveInfo.Builder setSaveOnAllViewsInvisible(boolean);
}
public final class SaveRequest implements android.os.Parcelable {
@@ -40906,7 +40913,6 @@
method public void startIntentSender(android.content.IntentSender, android.content.Intent, int, int, int) throws android.content.IntentSender.SendIntentException;
method public void startIntentSender(android.content.IntentSender, android.content.Intent, int, int, int, android.os.Bundle) throws android.content.IntentSender.SendIntentException;
method public android.content.ComponentName startService(android.content.Intent);
- method public android.content.ComponentName startServiceInForeground(android.content.Intent, int, android.app.Notification);
method public boolean stopService(android.content.Intent);
method public void unbindService(android.content.ServiceConnection);
method public void unregisterReceiver(android.content.BroadcastReceiver);
@@ -45940,7 +45946,9 @@
field public static final int IMPORTANT_FOR_ACCESSIBILITY_YES = 1; // 0x1
field public static final int IMPORTANT_FOR_AUTOFILL_AUTO = 0; // 0x0
field public static final int IMPORTANT_FOR_AUTOFILL_NO = 2; // 0x2
+ field public static final int IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS = 8; // 0x8
field public static final int IMPORTANT_FOR_AUTOFILL_YES = 1; // 0x1
+ field public static final int IMPORTANT_FOR_AUTOFILL_YES_EXCLUDE_DESCENDANTS = 4; // 0x4
field public static final int INVISIBLE = 4; // 0x4
field public static final int KEEP_SCREEN_ON = 67108864; // 0x4000000
field public static final int LAYER_TYPE_HARDWARE = 2; // 0x2
@@ -55390,6 +55398,7 @@
method public static java.lang.invoke.MethodHandle dropArguments(java.lang.invoke.MethodHandle, int, java.util.List<java.lang.Class<?>>);
method public static java.lang.invoke.MethodHandle dropArguments(java.lang.invoke.MethodHandle, int, java.lang.Class<?>...);
method public static java.lang.invoke.MethodHandle exactInvoker(java.lang.invoke.MethodType);
+ method public static java.lang.invoke.MethodHandle explicitCastArguments(java.lang.invoke.MethodHandle, java.lang.invoke.MethodType);
method public static java.lang.invoke.MethodHandle filterArguments(java.lang.invoke.MethodHandle, int, java.lang.invoke.MethodHandle...);
method public static java.lang.invoke.MethodHandle filterReturnValue(java.lang.invoke.MethodHandle, java.lang.invoke.MethodHandle);
method public static java.lang.invoke.MethodHandle foldArguments(java.lang.invoke.MethodHandle, java.lang.invoke.MethodHandle);
diff --git a/api/removed.txt b/api/removed.txt
index 0f5b81a..b6c2a98 100644
--- a/api/removed.txt
+++ b/api/removed.txt
@@ -63,6 +63,12 @@
field public static final int REQUESTED_PERMISSION_REQUIRED = 1; // 0x1
}
+ public final class SharedLibraryInfo implements android.os.Parcelable {
+ method public boolean isBuiltin();
+ method public boolean isDynamic();
+ method public boolean isStatic();
+ }
+
}
package android.database {
@@ -126,6 +132,16 @@
}
+package android.location {
+
+ public class Location implements android.os.Parcelable {
+ method public deprecated void removeBearingAccuracy();
+ method public deprecated void removeSpeedAccuracy();
+ method public deprecated void removeVerticalAccuracy();
+ }
+
+}
+
package android.media {
public final class AudioFormat implements android.os.Parcelable {
diff --git a/api/system-current.txt b/api/system-current.txt
index b216832..5dd3b88 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -1834,6 +1834,7 @@
field public static final int ic_notification_clear_all = 17301594; // 0x108005a
field public static final int ic_notification_overlay = 17301595; // 0x108005b
field public static final int ic_partial_secure = 17301596; // 0x108005c
+ field public static final int ic_picture_in_picture = 17301685; // 0x10800b5
field public static final int ic_popup_disk_full = 17301597; // 0x108005d
field public static final int ic_popup_reminder = 17301598; // 0x108005e
field public static final int ic_popup_sync = 17301599; // 0x108005f
@@ -3989,7 +3990,7 @@
method public deprecated java.util.List<android.app.ActivityManager.RecentTaskInfo> getRecentTasks(int, int) throws java.lang.SecurityException;
method public java.util.List<android.app.ActivityManager.RunningAppProcessInfo> getRunningAppProcesses();
method public android.app.PendingIntent getRunningServiceControlPanel(android.content.ComponentName) throws java.lang.SecurityException;
- method public java.util.List<android.app.ActivityManager.RunningServiceInfo> getRunningServices(int) throws java.lang.SecurityException;
+ method public deprecated java.util.List<android.app.ActivityManager.RunningServiceInfo> getRunningServices(int) throws java.lang.SecurityException;
method public deprecated java.util.List<android.app.ActivityManager.RunningTaskInfo> getRunningTasks(int) throws java.lang.SecurityException;
method public int getUidImportance(int);
method public deprecated boolean isInLockTaskMode();
@@ -4744,6 +4745,7 @@
method public final android.app.FragmentManager getFragmentManager();
method public final java.lang.Object getHost();
method public final int getId();
+ method public final android.view.LayoutInflater getLayoutInflater();
method public android.app.LoaderManager getLoaderManager();
method public final android.app.Fragment getParentFragment();
method public android.transition.Transition getReenterTransition();
@@ -5806,7 +5808,6 @@
method public boolean removeAutomaticZenRule(java.lang.String);
method public final void setInterruptionFilter(int);
method public void setNotificationPolicy(android.app.NotificationManager.Policy);
- method public deprecated android.content.ComponentName startServiceInForeground(android.content.Intent, int, android.app.Notification);
method public boolean updateAutomaticZenRule(java.lang.String, android.app.AutomaticZenRule);
field public static final java.lang.String ACTION_INTERRUPTION_FILTER_CHANGED = "android.app.action.INTERRUPTION_FILTER_CHANGED";
field public static final java.lang.String ACTION_NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED = "android.app.action.NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED";
@@ -6870,7 +6871,7 @@
public static class AssistStructure.ViewNode {
method public float getAlpha();
- method public java.lang.String[] getAutoFillHints();
+ method public java.lang.String[] getAutofillHints();
method public android.view.autofill.AutofillId getAutofillId();
method public java.lang.String[] getAutofillOptions();
method public int getAutofillType();
@@ -7530,6 +7531,7 @@
method public static void deleteAllHosts();
method public void deleteAppWidgetId(int);
method public void deleteHost();
+ method public int[] getAppWidgetIds();
method protected android.appwidget.AppWidgetHostView onCreateView(android.content.Context, int, android.appwidget.AppWidgetProviderInfo);
method protected void onProviderChanged(int, android.appwidget.AppWidgetProviderInfo);
method protected void onProvidersChanged();
@@ -11701,12 +11703,13 @@
method public android.content.pm.VersionedPackage getDeclaringPackage();
method public java.util.List<android.content.pm.VersionedPackage> getDependentPackages();
method public java.lang.String getName();
- method public int getVersion();
- method public boolean isBuiltin();
- method public boolean isDynamic();
- method public boolean isStatic();
+ method public int getType();
+ method public long getVersion();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.content.pm.SharedLibraryInfo> CREATOR;
+ field public static final int TYPE_BUILTIN = 0; // 0x0
+ field public static final int TYPE_DYNAMIC = 1; // 0x1
+ field public static final int TYPE_STATIC = 2; // 0x2
field public static final int VERSION_UNDEFINED = -1; // 0xffffffff
}
@@ -13374,7 +13377,6 @@
public class BitmapShader extends android.graphics.Shader {
ctor public BitmapShader(android.graphics.Bitmap, android.graphics.Shader.TileMode, android.graphics.Shader.TileMode);
- method public void set(android.graphics.Bitmap, android.graphics.Shader.TileMode, android.graphics.Shader.TileMode);
}
public class BlurMaskFilter extends android.graphics.MaskFilter {
@@ -13627,8 +13629,6 @@
ctor public ColorMatrixColorFilter(android.graphics.ColorMatrix);
ctor public ColorMatrixColorFilter(float[]);
method public void getColorMatrix(android.graphics.ColorMatrix);
- method public void setColorMatrix(android.graphics.ColorMatrix);
- method public void setColorMatrixArray(float[]);
}
public abstract class ColorSpace {
@@ -13769,8 +13769,6 @@
public class ComposeShader extends android.graphics.Shader {
ctor public ComposeShader(android.graphics.Shader, android.graphics.Shader, android.graphics.Xfermode);
ctor public ComposeShader(android.graphics.Shader, android.graphics.Shader, android.graphics.PorterDuff.Mode);
- method public void set(android.graphics.Shader, android.graphics.Shader, android.graphics.Xfermode);
- method public void set(android.graphics.Shader, android.graphics.Shader, android.graphics.PorterDuff.Mode);
}
public class CornerPathEffect extends android.graphics.PathEffect {
@@ -13843,15 +13841,11 @@
ctor public LightingColorFilter(int, int);
method public int getColorAdd();
method public int getColorMultiply();
- method public void setColorAdd(int);
- method public void setColorMultiply(int);
}
public class LinearGradient extends android.graphics.Shader {
ctor public LinearGradient(float, float, float, float, int[], float[], android.graphics.Shader.TileMode);
ctor public LinearGradient(float, float, float, float, int, int, android.graphics.Shader.TileMode);
- method public void set(float, float, float, float, int[], float[], android.graphics.Shader.TileMode);
- method public void set(float, float, float, float, int, int, android.graphics.Shader.TileMode);
}
public class MaskFilter {
@@ -14361,10 +14355,6 @@
public class PorterDuffColorFilter extends android.graphics.ColorFilter {
ctor public PorterDuffColorFilter(int, android.graphics.PorterDuff.Mode);
- method public int getColor();
- method public android.graphics.PorterDuff.Mode getMode();
- method public void setColor(int);
- method public void setMode(android.graphics.PorterDuff.Mode);
}
public class PorterDuffXfermode extends android.graphics.Xfermode {
@@ -14374,8 +14364,6 @@
public class RadialGradient extends android.graphics.Shader {
ctor public RadialGradient(float, float, float, int[], float[], android.graphics.Shader.TileMode);
ctor public RadialGradient(float, float, float, int, int, android.graphics.Shader.TileMode);
- method public void set(float, float, float, int[], float[], android.graphics.Shader.TileMode);
- method public void set(float, float, float, int, int, android.graphics.Shader.TileMode);
}
public final class Rect implements android.os.Parcelable {
@@ -14560,8 +14548,6 @@
public class SweepGradient extends android.graphics.Shader {
ctor public SweepGradient(float, float, int[], float[]);
ctor public SweepGradient(float, float, int, int);
- method public void set(float, float, int[], float[]);
- method public void set(float, float, int, int);
}
public class Typeface {
@@ -22535,13 +22521,10 @@
method public boolean isComplete();
method public boolean isFromMockProvider();
method public void makeComplete();
- method public void removeAccuracy();
- method public void removeAltitude();
- method public void removeBearing();
- method public void removeBearingAccuracy();
- method public void removeSpeed();
- method public void removeSpeedAccuracy();
- method public void removeVerticalAccuracy();
+ method public deprecated void removeAccuracy();
+ method public deprecated void removeAltitude();
+ method public deprecated void removeBearing();
+ method public deprecated void removeSpeed();
method public void reset();
method public void set(android.location.Location);
method public void setAccuracy(float);
@@ -24611,7 +24594,7 @@
method public android.media.MediaPlayer.DrmInfo getDrmInfo();
method public java.lang.String getDrmPropertyString(java.lang.String) throws android.media.MediaPlayer.NoDrmSchemeException;
method public int getDuration();
- method public android.media.MediaDrm.KeyRequest getKeyRequest(byte[], java.lang.String, int, java.util.Map<java.lang.String, java.lang.String>) throws android.media.MediaPlayer.NoDrmSchemeException;
+ method public android.media.MediaDrm.KeyRequest getKeyRequest(byte[], byte[], java.lang.String, int, java.util.Map<java.lang.String, java.lang.String>) throws android.media.MediaPlayer.NoDrmSchemeException;
method public android.os.PersistableBundle getMetrics();
method public android.media.PlaybackParams getPlaybackParams();
method public int getSelectedTrack(int) throws java.lang.IllegalStateException;
@@ -24625,7 +24608,7 @@
method public void pause() throws java.lang.IllegalStateException;
method public void prepare() throws java.io.IOException, java.lang.IllegalStateException;
method public void prepareAsync() throws java.lang.IllegalStateException;
- method public void prepareDrm(java.util.UUID) throws android.media.MediaPlayer.ProvisioningErrorException, android.media.ResourceBusyException, android.media.UnsupportedSchemeException;
+ method public void prepareDrm(java.util.UUID) throws android.media.MediaPlayer.ProvisioningNetworkErrorException, android.media.MediaPlayer.ProvisioningServerErrorException, android.media.ResourceBusyException, android.media.UnsupportedSchemeException;
method public byte[] provideKeyResponse(byte[], byte[]) throws android.media.DeniedByServerException, android.media.MediaPlayer.NoDrmSchemeException;
method public void release();
method public void releaseDrm() throws android.media.MediaPlayer.NoDrmSchemeException;
@@ -24652,7 +24635,7 @@
method public void setNextMediaPlayer(android.media.MediaPlayer);
method public void setOnBufferingUpdateListener(android.media.MediaPlayer.OnBufferingUpdateListener);
method public void setOnCompletionListener(android.media.MediaPlayer.OnCompletionListener);
- method public void setOnDrmConfigListener(android.media.MediaPlayer.OnDrmConfigListener);
+ method public void setOnDrmConfigHelper(android.media.MediaPlayer.OnDrmConfigHelper);
method public void setOnDrmInfoListener(android.media.MediaPlayer.OnDrmInfoListener);
method public void setOnDrmInfoListener(android.media.MediaPlayer.OnDrmInfoListener, android.os.Handler);
method public void setOnDrmPreparedListener(android.media.MediaPlayer.OnDrmPreparedListener);
@@ -24693,6 +24676,10 @@
field public static final int MEDIA_INFO_VIDEO_RENDERING_START = 3; // 0x3
field public static final int MEDIA_INFO_VIDEO_TRACK_LAGGING = 700; // 0x2bc
field public static final java.lang.String MEDIA_MIMETYPE_TEXT_SUBRIP = "application/x-subrip";
+ field public static final int PREPARE_DRM_STATUS_PREPARATION_ERROR = 3; // 0x3
+ field public static final int PREPARE_DRM_STATUS_PROVISIONING_NETWORK_ERROR = 1; // 0x1
+ field public static final int PREPARE_DRM_STATUS_PROVISIONING_SERVER_ERROR = 2; // 0x2
+ field public static final int PREPARE_DRM_STATUS_SUCCESS = 0; // 0x0
field public static final int SEEK_CLOSEST = 3; // 0x3
field public static final int SEEK_CLOSEST_SYNC = 2; // 0x2
field public static final int SEEK_NEXT_SYNC = 1; // 0x1
@@ -24702,7 +24689,6 @@
}
public static final class MediaPlayer.DrmInfo {
- method public java.lang.String[] getMimes();
method public java.util.Map<java.util.UUID, byte[]> getPssh();
method public java.util.UUID[] getSupportedSchemes();
}
@@ -24734,7 +24720,7 @@
method public abstract void onCompletion(android.media.MediaPlayer);
}
- public static abstract interface MediaPlayer.OnDrmConfigListener {
+ public static abstract interface MediaPlayer.OnDrmConfigHelper {
method public abstract void onDrmConfig(android.media.MediaPlayer);
}
@@ -24743,7 +24729,7 @@
}
public static abstract interface MediaPlayer.OnDrmPreparedListener {
- method public abstract void onDrmPrepared(android.media.MediaPlayer, boolean);
+ method public abstract void onDrmPrepared(android.media.MediaPlayer, int);
}
public static abstract interface MediaPlayer.OnErrorListener {
@@ -24774,8 +24760,12 @@
method public abstract void onVideoSizeChanged(android.media.MediaPlayer, int, int);
}
- public static final class MediaPlayer.ProvisioningErrorException extends android.media.MediaDrmException {
- ctor public MediaPlayer.ProvisioningErrorException(java.lang.String);
+ public static final class MediaPlayer.ProvisioningNetworkErrorException extends android.media.MediaDrmException {
+ ctor public MediaPlayer.ProvisioningNetworkErrorException(java.lang.String);
+ }
+
+ public static final class MediaPlayer.ProvisioningServerErrorException extends android.media.MediaDrmException {
+ ctor public MediaPlayer.ProvisioningServerErrorException(java.lang.String);
}
public static class MediaPlayer.TrackInfo implements android.os.Parcelable {
@@ -27717,14 +27707,10 @@
}
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 applyTransportModeTransform(java.io.FileDescriptor, android.net.IpSecTransform) throws java.io.IOException;
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 removeTransportModeTransform(java.io.FileDescriptor, android.net.IpSecTransform);
+ method public void removeTransportModeTransform(java.io.FileDescriptor, android.net.IpSecTransform) throws java.io.IOException;
method public android.net.IpSecManager.SecurityParameterIndex reserveSecurityParameterIndex(int, java.net.InetAddress) throws android.net.IpSecManager.ResourceUnavailableException;
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
@@ -27743,7 +27729,7 @@
}
public static final class IpSecManager.UdpEncapsulationSocket implements java.lang.AutoCloseable {
- method public void close();
+ method public void close() throws java.io.IOException;
method public int getPort();
method public java.io.FileDescriptor getSocket();
}
@@ -29495,9 +29481,9 @@
}
public class DiscoverySession {
- method public java.lang.String createNetworkSpecifierOpen(android.net.wifi.aware.PeerHandle);
- method public java.lang.String createNetworkSpecifierPassphrase(android.net.wifi.aware.PeerHandle, java.lang.String);
- method public java.lang.String createNetworkSpecifierPmk(android.net.wifi.aware.PeerHandle, byte[]);
+ method public android.net.NetworkSpecifier createNetworkSpecifierOpen(android.net.wifi.aware.PeerHandle);
+ method public android.net.NetworkSpecifier createNetworkSpecifierPassphrase(android.net.wifi.aware.PeerHandle, java.lang.String);
+ method public android.net.NetworkSpecifier createNetworkSpecifierPmk(android.net.wifi.aware.PeerHandle, byte[]);
method public void destroy();
method public void sendMessage(android.net.wifi.aware.PeerHandle, int, byte[]);
}
@@ -29583,9 +29569,9 @@
}
public class WifiAwareSession {
- method public java.lang.String createNetworkSpecifierOpen(int, byte[]);
- method public java.lang.String createNetworkSpecifierPassphrase(int, byte[], java.lang.String);
- method public java.lang.String createNetworkSpecifierPmk(int, byte[], byte[]);
+ method public android.net.NetworkSpecifier createNetworkSpecifierOpen(int, byte[]);
+ method public android.net.NetworkSpecifier createNetworkSpecifierPassphrase(int, byte[], java.lang.String);
+ method public android.net.NetworkSpecifier createNetworkSpecifierPmk(int, byte[], byte[]);
method public void destroy();
method public void publish(android.net.wifi.aware.PublishConfig, android.net.wifi.aware.DiscoverySessionCallback, android.os.Handler);
method public void subscribe(android.net.wifi.aware.SubscribeConfig, android.net.wifi.aware.DiscoverySessionCallback, android.os.Handler);
@@ -34377,6 +34363,7 @@
method public static void rebootWipeUserData(android.content.Context) throws java.io.IOException;
method public static void scheduleUpdateOnBoot(android.content.Context, java.io.File) throws java.io.IOException;
method public static void verifyPackage(java.io.File, android.os.RecoverySystem.ProgressListener, java.io.File) throws java.security.GeneralSecurityException, java.io.IOException;
+ method public static boolean verifyPackageCompatibility(java.io.File) throws java.io.IOException;
}
public static abstract interface RecoverySystem.ProgressListener {
@@ -40160,6 +40147,7 @@
public abstract class AutofillService extends android.app.Service {
ctor public AutofillService();
method public final deprecated void disableSelf();
+ method public final android.service.autofill.FillEventHistory getFillEventHistory();
method public final android.os.IBinder onBind(android.content.Intent);
method public void onConnected();
method public void onDisconnected();
@@ -40182,6 +40170,7 @@
ctor public Dataset.Builder();
method public android.service.autofill.Dataset build();
method public android.service.autofill.Dataset.Builder setAuthentication(android.content.IntentSender);
+ method public android.service.autofill.Dataset.Builder setId(java.lang.String);
method public android.service.autofill.Dataset.Builder setValue(android.view.autofill.AutofillId, android.view.autofill.AutofillValue);
method public android.service.autofill.Dataset.Builder setValue(android.view.autofill.AutofillId, android.view.autofill.AutofillValue, android.widget.RemoteViews);
}
@@ -40199,6 +40188,23 @@
field public static final android.os.Parcelable.Creator<android.service.autofill.FillContext> CREATOR;
}
+ public final class FillEventHistory implements android.os.Parcelable {
+ method public int describeContents();
+ method public android.os.Bundle getClientState();
+ method public java.util.List<android.service.autofill.FillEventHistory.Event> getEvents();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.service.autofill.FillEventHistory> CREATOR;
+ }
+
+ public static final class FillEventHistory.Event {
+ method public java.lang.String getDatasetId();
+ method public int getType();
+ field public static final int TYPE_AUTHENTICATION_SELECTED = 2; // 0x2
+ field public static final int TYPE_DATASET_AUTHENTICATION_SELECTED = 1; // 0x1
+ field public static final int TYPE_DATASET_SELECTED = 0; // 0x0
+ field public static final int TYPE_SAVE_SHOWN = 3; // 0x3
+ }
+
public final class FillRequest implements android.os.Parcelable {
method public int describeContents();
method public android.os.Bundle getClientState();
@@ -40223,6 +40229,7 @@
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 setClientState(android.os.Bundle);
method public deprecated android.service.autofill.FillResponse.Builder setExtras(android.os.Bundle);
+ method public android.service.autofill.FillResponse.Builder setIgnoredIds(android.view.autofill.AutofillId...);
method public android.service.autofill.FillResponse.Builder setSaveInfo(android.service.autofill.SaveInfo);
}
@@ -40235,6 +40242,7 @@
method public int describeContents();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.service.autofill.SaveInfo> CREATOR;
+ field public static final int FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE = 1; // 0x1
field public static final int SAVE_DATA_TYPE_ADDRESS = 2; // 0x2
field public static final int SAVE_DATA_TYPE_CREDIT_CARD = 4; // 0x4
field public static final int SAVE_DATA_TYPE_EMAIL_ADDRESS = 16; // 0x10
@@ -40247,9 +40255,9 @@
ctor public SaveInfo.Builder(int, android.view.autofill.AutofillId[]);
method public android.service.autofill.SaveInfo build();
method public android.service.autofill.SaveInfo.Builder setDescription(java.lang.CharSequence);
+ method public android.service.autofill.SaveInfo.Builder setFlags(int);
method public android.service.autofill.SaveInfo.Builder setNegativeAction(java.lang.CharSequence, android.content.IntentSender);
method public android.service.autofill.SaveInfo.Builder setOptionalIds(android.view.autofill.AutofillId[]);
- method public android.service.autofill.SaveInfo.Builder setSaveOnAllViewsInvisible(boolean);
}
public final class SaveRequest implements android.os.Parcelable {
@@ -44462,7 +44470,6 @@
method public void startIntentSender(android.content.IntentSender, android.content.Intent, int, int, int) throws android.content.IntentSender.SendIntentException;
method public void startIntentSender(android.content.IntentSender, android.content.Intent, int, int, int, android.os.Bundle) throws android.content.IntentSender.SendIntentException;
method public android.content.ComponentName startService(android.content.Intent);
- method public android.content.ComponentName startServiceInForeground(android.content.Intent, int, android.app.Notification);
method public boolean stopService(android.content.Intent);
method public void unbindService(android.content.ServiceConnection);
method public void unregisterReceiver(android.content.BroadcastReceiver);
@@ -49515,7 +49522,9 @@
field public static final int IMPORTANT_FOR_ACCESSIBILITY_YES = 1; // 0x1
field public static final int IMPORTANT_FOR_AUTOFILL_AUTO = 0; // 0x0
field public static final int IMPORTANT_FOR_AUTOFILL_NO = 2; // 0x2
+ field public static final int IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS = 8; // 0x8
field public static final int IMPORTANT_FOR_AUTOFILL_YES = 1; // 0x1
+ field public static final int IMPORTANT_FOR_AUTOFILL_YES_EXCLUDE_DESCENDANTS = 4; // 0x4
field public static final int INVISIBLE = 4; // 0x4
field public static final int KEEP_SCREEN_ON = 67108864; // 0x4000000
field public static final int LAYER_TYPE_HARDWARE = 2; // 0x2
@@ -59333,6 +59342,7 @@
method public static java.lang.invoke.MethodHandle dropArguments(java.lang.invoke.MethodHandle, int, java.util.List<java.lang.Class<?>>);
method public static java.lang.invoke.MethodHandle dropArguments(java.lang.invoke.MethodHandle, int, java.lang.Class<?>...);
method public static java.lang.invoke.MethodHandle exactInvoker(java.lang.invoke.MethodType);
+ method public static java.lang.invoke.MethodHandle explicitCastArguments(java.lang.invoke.MethodHandle, java.lang.invoke.MethodType);
method public static java.lang.invoke.MethodHandle filterArguments(java.lang.invoke.MethodHandle, int, java.lang.invoke.MethodHandle...);
method public static java.lang.invoke.MethodHandle filterReturnValue(java.lang.invoke.MethodHandle, java.lang.invoke.MethodHandle);
method public static java.lang.invoke.MethodHandle foldArguments(java.lang.invoke.MethodHandle, java.lang.invoke.MethodHandle);
diff --git a/api/system-removed.txt b/api/system-removed.txt
index 823d88f..6d82a49 100644
--- a/api/system-removed.txt
+++ b/api/system-removed.txt
@@ -61,6 +61,12 @@
field public static final int REQUESTED_PERMISSION_REQUIRED = 1; // 0x1
}
+ public final class SharedLibraryInfo implements android.os.Parcelable {
+ method public boolean isBuiltin();
+ method public boolean isDynamic();
+ method public boolean isStatic();
+ }
+
}
package android.database {
@@ -124,6 +130,16 @@
}
+package android.location {
+
+ public class Location implements android.os.Parcelable {
+ method public deprecated void removeBearingAccuracy();
+ method public deprecated void removeSpeedAccuracy();
+ method public deprecated void removeVerticalAccuracy();
+ }
+
+}
+
package android.media {
public final class AudioFormat implements android.os.Parcelable {
diff --git a/api/test-current.txt b/api/test-current.txt
index e3292f9..47a0fd8 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -1710,6 +1710,7 @@
field public static final int ic_notification_clear_all = 17301594; // 0x108005a
field public static final int ic_notification_overlay = 17301595; // 0x108005b
field public static final int ic_partial_secure = 17301596; // 0x108005c
+ field public static final int ic_picture_in_picture = 17301685; // 0x10800b5
field public static final int ic_popup_disk_full = 17301597; // 0x108005d
field public static final int ic_popup_reminder = 17301598; // 0x108005e
field public static final int ic_popup_sync = 17301599; // 0x108005f
@@ -3852,7 +3853,7 @@
method public deprecated java.util.List<android.app.ActivityManager.RecentTaskInfo> getRecentTasks(int, int) throws java.lang.SecurityException;
method public java.util.List<android.app.ActivityManager.RunningAppProcessInfo> getRunningAppProcesses();
method public android.app.PendingIntent getRunningServiceControlPanel(android.content.ComponentName) throws java.lang.SecurityException;
- method public java.util.List<android.app.ActivityManager.RunningServiceInfo> getRunningServices(int) throws java.lang.SecurityException;
+ method public deprecated java.util.List<android.app.ActivityManager.RunningServiceInfo> getRunningServices(int) throws java.lang.SecurityException;
method public deprecated java.util.List<android.app.ActivityManager.RunningTaskInfo> getRunningTasks(int) throws java.lang.SecurityException;
method public int getUidImportance(int);
method public deprecated boolean isInLockTaskMode();
@@ -4590,6 +4591,7 @@
method public final android.app.FragmentManager getFragmentManager();
method public final java.lang.Object getHost();
method public final int getId();
+ method public final android.view.LayoutInflater getLayoutInflater();
method public android.app.LoaderManager getLoaderManager();
method public final android.app.Fragment getParentFragment();
method public android.transition.Transition getReenterTransition();
@@ -5618,7 +5620,6 @@
method public boolean removeAutomaticZenRule(java.lang.String);
method public final void setInterruptionFilter(int);
method public void setNotificationPolicy(android.app.NotificationManager.Policy);
- method public deprecated android.content.ComponentName startServiceInForeground(android.content.Intent, int, android.app.Notification);
method public boolean updateAutomaticZenRule(java.lang.String, android.app.AutomaticZenRule);
field public static final java.lang.String ACTION_INTERRUPTION_FILTER_CHANGED = "android.app.action.INTERRUPTION_FILTER_CHANGED";
field public static final java.lang.String ACTION_NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED = "android.app.action.NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED";
@@ -6655,7 +6656,7 @@
public static class AssistStructure.ViewNode {
method public float getAlpha();
- method public java.lang.String[] getAutoFillHints();
+ method public java.lang.String[] getAutofillHints();
method public android.view.autofill.AutofillId getAutofillId();
method public java.lang.String[] getAutofillOptions();
method public int getAutofillType();
@@ -7095,6 +7096,7 @@
method public static void deleteAllHosts();
method public void deleteAppWidgetId(int);
method public void deleteHost();
+ method public int[] getAppWidgetIds();
method protected android.appwidget.AppWidgetHostView onCreateView(android.content.Context, int, android.appwidget.AppWidgetProviderInfo);
method protected void onProviderChanged(int, android.appwidget.AppWidgetProviderInfo);
method protected void onProvidersChanged();
@@ -10982,12 +10984,13 @@
method public android.content.pm.VersionedPackage getDeclaringPackage();
method public java.util.List<android.content.pm.VersionedPackage> getDependentPackages();
method public java.lang.String getName();
- method public int getVersion();
- method public boolean isBuiltin();
- method public boolean isDynamic();
- method public boolean isStatic();
+ method public int getType();
+ method public long getVersion();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.content.pm.SharedLibraryInfo> CREATOR;
+ field public static final int TYPE_BUILTIN = 0; // 0x0
+ field public static final int TYPE_DYNAMIC = 1; // 0x1
+ field public static final int TYPE_STATIC = 2; // 0x2
field public static final int VERSION_UNDEFINED = -1; // 0xffffffff
}
@@ -12642,7 +12645,6 @@
public class BitmapShader extends android.graphics.Shader {
ctor public BitmapShader(android.graphics.Bitmap, android.graphics.Shader.TileMode, android.graphics.Shader.TileMode);
- method public void set(android.graphics.Bitmap, android.graphics.Shader.TileMode, android.graphics.Shader.TileMode);
}
public class BlurMaskFilter extends android.graphics.MaskFilter {
@@ -12895,8 +12897,6 @@
ctor public ColorMatrixColorFilter(android.graphics.ColorMatrix);
ctor public ColorMatrixColorFilter(float[]);
method public void getColorMatrix(android.graphics.ColorMatrix);
- method public void setColorMatrix(android.graphics.ColorMatrix);
- method public void setColorMatrixArray(float[]);
}
public abstract class ColorSpace {
@@ -13037,8 +13037,6 @@
public class ComposeShader extends android.graphics.Shader {
ctor public ComposeShader(android.graphics.Shader, android.graphics.Shader, android.graphics.Xfermode);
ctor public ComposeShader(android.graphics.Shader, android.graphics.Shader, android.graphics.PorterDuff.Mode);
- method public void set(android.graphics.Shader, android.graphics.Shader, android.graphics.Xfermode);
- method public void set(android.graphics.Shader, android.graphics.Shader, android.graphics.PorterDuff.Mode);
}
public class CornerPathEffect extends android.graphics.PathEffect {
@@ -13111,15 +13109,11 @@
ctor public LightingColorFilter(int, int);
method public int getColorAdd();
method public int getColorMultiply();
- method public void setColorAdd(int);
- method public void setColorMultiply(int);
}
public class LinearGradient extends android.graphics.Shader {
ctor public LinearGradient(float, float, float, float, int[], float[], android.graphics.Shader.TileMode);
ctor public LinearGradient(float, float, float, float, int, int, android.graphics.Shader.TileMode);
- method public void set(float, float, float, float, int[], float[], android.graphics.Shader.TileMode);
- method public void set(float, float, float, float, int, int, android.graphics.Shader.TileMode);
}
public class MaskFilter {
@@ -13629,10 +13623,6 @@
public class PorterDuffColorFilter extends android.graphics.ColorFilter {
ctor public PorterDuffColorFilter(int, android.graphics.PorterDuff.Mode);
- method public int getColor();
- method public android.graphics.PorterDuff.Mode getMode();
- method public void setColor(int);
- method public void setMode(android.graphics.PorterDuff.Mode);
}
public class PorterDuffXfermode extends android.graphics.Xfermode {
@@ -13642,8 +13632,6 @@
public class RadialGradient extends android.graphics.Shader {
ctor public RadialGradient(float, float, float, int[], float[], android.graphics.Shader.TileMode);
ctor public RadialGradient(float, float, float, int, int, android.graphics.Shader.TileMode);
- method public void set(float, float, float, int[], float[], android.graphics.Shader.TileMode);
- method public void set(float, float, float, int, int, android.graphics.Shader.TileMode);
}
public final class Rect implements android.os.Parcelable {
@@ -13828,8 +13816,6 @@
public class SweepGradient extends android.graphics.Shader {
ctor public SweepGradient(float, float, int[], float[]);
ctor public SweepGradient(float, float, int, int);
- method public void set(float, float, int[], float[]);
- method public void set(float, float, int, int);
}
public class Typeface {
@@ -20907,13 +20893,10 @@
method public boolean hasSpeedAccuracy();
method public boolean hasVerticalAccuracy();
method public boolean isFromMockProvider();
- method public void removeAccuracy();
- method public void removeAltitude();
- method public void removeBearing();
- method public void removeBearingAccuracy();
- method public void removeSpeed();
- method public void removeSpeedAccuracy();
- method public void removeVerticalAccuracy();
+ method public deprecated void removeAccuracy();
+ method public deprecated void removeAltitude();
+ method public deprecated void removeBearing();
+ method public deprecated void removeSpeed();
method public void reset();
method public void set(android.location.Location);
method public void setAccuracy(float);
@@ -22882,7 +22865,7 @@
method public android.media.MediaPlayer.DrmInfo getDrmInfo();
method public java.lang.String getDrmPropertyString(java.lang.String) throws android.media.MediaPlayer.NoDrmSchemeException;
method public int getDuration();
- method public android.media.MediaDrm.KeyRequest getKeyRequest(byte[], java.lang.String, int, java.util.Map<java.lang.String, java.lang.String>) throws android.media.MediaPlayer.NoDrmSchemeException;
+ method public android.media.MediaDrm.KeyRequest getKeyRequest(byte[], byte[], java.lang.String, int, java.util.Map<java.lang.String, java.lang.String>) throws android.media.MediaPlayer.NoDrmSchemeException;
method public android.os.PersistableBundle getMetrics();
method public android.media.PlaybackParams getPlaybackParams();
method public int getSelectedTrack(int) throws java.lang.IllegalStateException;
@@ -22896,7 +22879,7 @@
method public void pause() throws java.lang.IllegalStateException;
method public void prepare() throws java.io.IOException, java.lang.IllegalStateException;
method public void prepareAsync() throws java.lang.IllegalStateException;
- method public void prepareDrm(java.util.UUID) throws android.media.MediaPlayer.ProvisioningErrorException, android.media.ResourceBusyException, android.media.UnsupportedSchemeException;
+ method public void prepareDrm(java.util.UUID) throws android.media.MediaPlayer.ProvisioningNetworkErrorException, android.media.MediaPlayer.ProvisioningServerErrorException, android.media.ResourceBusyException, android.media.UnsupportedSchemeException;
method public byte[] provideKeyResponse(byte[], byte[]) throws android.media.DeniedByServerException, android.media.MediaPlayer.NoDrmSchemeException;
method public void release();
method public void releaseDrm() throws android.media.MediaPlayer.NoDrmSchemeException;
@@ -22923,7 +22906,7 @@
method public void setNextMediaPlayer(android.media.MediaPlayer);
method public void setOnBufferingUpdateListener(android.media.MediaPlayer.OnBufferingUpdateListener);
method public void setOnCompletionListener(android.media.MediaPlayer.OnCompletionListener);
- method public void setOnDrmConfigListener(android.media.MediaPlayer.OnDrmConfigListener);
+ method public void setOnDrmConfigHelper(android.media.MediaPlayer.OnDrmConfigHelper);
method public void setOnDrmInfoListener(android.media.MediaPlayer.OnDrmInfoListener);
method public void setOnDrmInfoListener(android.media.MediaPlayer.OnDrmInfoListener, android.os.Handler);
method public void setOnDrmPreparedListener(android.media.MediaPlayer.OnDrmPreparedListener);
@@ -22964,6 +22947,10 @@
field public static final int MEDIA_INFO_VIDEO_RENDERING_START = 3; // 0x3
field public static final int MEDIA_INFO_VIDEO_TRACK_LAGGING = 700; // 0x2bc
field public static final java.lang.String MEDIA_MIMETYPE_TEXT_SUBRIP = "application/x-subrip";
+ field public static final int PREPARE_DRM_STATUS_PREPARATION_ERROR = 3; // 0x3
+ field public static final int PREPARE_DRM_STATUS_PROVISIONING_NETWORK_ERROR = 1; // 0x1
+ field public static final int PREPARE_DRM_STATUS_PROVISIONING_SERVER_ERROR = 2; // 0x2
+ field public static final int PREPARE_DRM_STATUS_SUCCESS = 0; // 0x0
field public static final int SEEK_CLOSEST = 3; // 0x3
field public static final int SEEK_CLOSEST_SYNC = 2; // 0x2
field public static final int SEEK_NEXT_SYNC = 1; // 0x1
@@ -22973,7 +22960,6 @@
}
public static final class MediaPlayer.DrmInfo {
- method public java.lang.String[] getMimes();
method public java.util.Map<java.util.UUID, byte[]> getPssh();
method public java.util.UUID[] getSupportedSchemes();
}
@@ -23005,7 +22991,7 @@
method public abstract void onCompletion(android.media.MediaPlayer);
}
- public static abstract interface MediaPlayer.OnDrmConfigListener {
+ public static abstract interface MediaPlayer.OnDrmConfigHelper {
method public abstract void onDrmConfig(android.media.MediaPlayer);
}
@@ -23014,7 +23000,7 @@
}
public static abstract interface MediaPlayer.OnDrmPreparedListener {
- method public abstract void onDrmPrepared(android.media.MediaPlayer, boolean);
+ method public abstract void onDrmPrepared(android.media.MediaPlayer, int);
}
public static abstract interface MediaPlayer.OnErrorListener {
@@ -23045,8 +23031,12 @@
method public abstract void onVideoSizeChanged(android.media.MediaPlayer, int, int);
}
- public static final class MediaPlayer.ProvisioningErrorException extends android.media.MediaDrmException {
- ctor public MediaPlayer.ProvisioningErrorException(java.lang.String);
+ public static final class MediaPlayer.ProvisioningNetworkErrorException extends android.media.MediaDrmException {
+ ctor public MediaPlayer.ProvisioningNetworkErrorException(java.lang.String);
+ }
+
+ public static final class MediaPlayer.ProvisioningServerErrorException extends android.media.MediaDrmException {
+ ctor public MediaPlayer.ProvisioningServerErrorException(java.lang.String);
}
public static class MediaPlayer.TrackInfo implements android.os.Parcelable {
@@ -25635,14 +25625,10 @@
}
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 applyTransportModeTransform(java.io.FileDescriptor, android.net.IpSecTransform) throws java.io.IOException;
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 removeTransportModeTransform(java.io.FileDescriptor, android.net.IpSecTransform);
+ method public void removeTransportModeTransform(java.io.FileDescriptor, android.net.IpSecTransform) throws java.io.IOException;
method public android.net.IpSecManager.SecurityParameterIndex reserveSecurityParameterIndex(int, java.net.InetAddress) throws android.net.IpSecManager.ResourceUnavailableException;
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
@@ -25661,7 +25647,7 @@
}
public static final class IpSecManager.UdpEncapsulationSocket implements java.lang.AutoCloseable {
- method public void close();
+ method public void close() throws java.io.IOException;
method public int getPort();
method public java.io.FileDescriptor getSocket();
}
@@ -26859,8 +26845,8 @@
}
public class DiscoverySession {
- method public java.lang.String createNetworkSpecifierOpen(android.net.wifi.aware.PeerHandle);
- method public java.lang.String createNetworkSpecifierPassphrase(android.net.wifi.aware.PeerHandle, java.lang.String);
+ method public android.net.NetworkSpecifier createNetworkSpecifierOpen(android.net.wifi.aware.PeerHandle);
+ method public android.net.NetworkSpecifier createNetworkSpecifierPassphrase(android.net.wifi.aware.PeerHandle, java.lang.String);
method public void destroy();
method public void sendMessage(android.net.wifi.aware.PeerHandle, int, byte[]);
}
@@ -26946,8 +26932,8 @@
}
public class WifiAwareSession {
- method public java.lang.String createNetworkSpecifierOpen(int, byte[]);
- method public java.lang.String createNetworkSpecifierPassphrase(int, byte[], java.lang.String);
+ method public android.net.NetworkSpecifier createNetworkSpecifierOpen(int, byte[]);
+ method public android.net.NetworkSpecifier createNetworkSpecifierPassphrase(int, byte[], java.lang.String);
method public void destroy();
method public void publish(android.net.wifi.aware.PublishConfig, android.net.wifi.aware.DiscoverySessionCallback, android.os.Handler);
method public void subscribe(android.net.wifi.aware.SubscribeConfig, android.net.wifi.aware.DiscoverySessionCallback, android.os.Handler);
@@ -37201,6 +37187,7 @@
public abstract class AutofillService extends android.app.Service {
ctor public AutofillService();
method public final deprecated void disableSelf();
+ method public final android.service.autofill.FillEventHistory getFillEventHistory();
method public final android.os.IBinder onBind(android.content.Intent);
method public void onConnected();
method public void onDisconnected();
@@ -37223,6 +37210,7 @@
ctor public Dataset.Builder();
method public android.service.autofill.Dataset build();
method public android.service.autofill.Dataset.Builder setAuthentication(android.content.IntentSender);
+ method public android.service.autofill.Dataset.Builder setId(java.lang.String);
method public android.service.autofill.Dataset.Builder setValue(android.view.autofill.AutofillId, android.view.autofill.AutofillValue);
method public android.service.autofill.Dataset.Builder setValue(android.view.autofill.AutofillId, android.view.autofill.AutofillValue, android.widget.RemoteViews);
}
@@ -37240,6 +37228,23 @@
field public static final android.os.Parcelable.Creator<android.service.autofill.FillContext> CREATOR;
}
+ public final class FillEventHistory implements android.os.Parcelable {
+ method public int describeContents();
+ method public android.os.Bundle getClientState();
+ method public java.util.List<android.service.autofill.FillEventHistory.Event> getEvents();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.service.autofill.FillEventHistory> CREATOR;
+ }
+
+ public static final class FillEventHistory.Event {
+ method public java.lang.String getDatasetId();
+ method public int getType();
+ field public static final int TYPE_AUTHENTICATION_SELECTED = 2; // 0x2
+ field public static final int TYPE_DATASET_AUTHENTICATION_SELECTED = 1; // 0x1
+ field public static final int TYPE_DATASET_SELECTED = 0; // 0x0
+ field public static final int TYPE_SAVE_SHOWN = 3; // 0x3
+ }
+
public final class FillRequest implements android.os.Parcelable {
method public int describeContents();
method public android.os.Bundle getClientState();
@@ -37264,6 +37269,7 @@
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 setClientState(android.os.Bundle);
method public deprecated android.service.autofill.FillResponse.Builder setExtras(android.os.Bundle);
+ method public android.service.autofill.FillResponse.Builder setIgnoredIds(android.view.autofill.AutofillId...);
method public android.service.autofill.FillResponse.Builder setSaveInfo(android.service.autofill.SaveInfo);
}
@@ -37276,6 +37282,7 @@
method public int describeContents();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.service.autofill.SaveInfo> CREATOR;
+ field public static final int FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE = 1; // 0x1
field public static final int SAVE_DATA_TYPE_ADDRESS = 2; // 0x2
field public static final int SAVE_DATA_TYPE_CREDIT_CARD = 4; // 0x4
field public static final int SAVE_DATA_TYPE_EMAIL_ADDRESS = 16; // 0x10
@@ -37288,9 +37295,9 @@
ctor public SaveInfo.Builder(int, android.view.autofill.AutofillId[]);
method public android.service.autofill.SaveInfo build();
method public android.service.autofill.SaveInfo.Builder setDescription(java.lang.CharSequence);
+ method public android.service.autofill.SaveInfo.Builder setFlags(int);
method public android.service.autofill.SaveInfo.Builder setNegativeAction(java.lang.CharSequence, android.content.IntentSender);
method public android.service.autofill.SaveInfo.Builder setOptionalIds(android.view.autofill.AutofillId[]);
- method public android.service.autofill.SaveInfo.Builder setSaveOnAllViewsInvisible(boolean);
}
public final class SaveRequest implements android.os.Parcelable {
@@ -41101,7 +41108,6 @@
method public void startIntentSender(android.content.IntentSender, android.content.Intent, int, int, int) throws android.content.IntentSender.SendIntentException;
method public void startIntentSender(android.content.IntentSender, android.content.Intent, int, int, int, android.os.Bundle) throws android.content.IntentSender.SendIntentException;
method public android.content.ComponentName startService(android.content.Intent);
- method public android.content.ComponentName startServiceInForeground(android.content.Intent, int, android.app.Notification);
method public boolean stopService(android.content.Intent);
method public void unbindService(android.content.ServiceConnection);
method public void unregisterReceiver(android.content.BroadcastReceiver);
@@ -46310,7 +46316,9 @@
field public static final int IMPORTANT_FOR_ACCESSIBILITY_YES = 1; // 0x1
field public static final int IMPORTANT_FOR_AUTOFILL_AUTO = 0; // 0x0
field public static final int IMPORTANT_FOR_AUTOFILL_NO = 2; // 0x2
+ field public static final int IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS = 8; // 0x8
field public static final int IMPORTANT_FOR_AUTOFILL_YES = 1; // 0x1
+ field public static final int IMPORTANT_FOR_AUTOFILL_YES_EXCLUDE_DESCENDANTS = 4; // 0x4
field public static final int INVISIBLE = 4; // 0x4
field public static final int KEEP_SCREEN_ON = 67108864; // 0x4000000
field public static final int LAYER_TYPE_HARDWARE = 2; // 0x2
@@ -55783,6 +55791,7 @@
method public static java.lang.invoke.MethodHandle dropArguments(java.lang.invoke.MethodHandle, int, java.util.List<java.lang.Class<?>>);
method public static java.lang.invoke.MethodHandle dropArguments(java.lang.invoke.MethodHandle, int, java.lang.Class<?>...);
method public static java.lang.invoke.MethodHandle exactInvoker(java.lang.invoke.MethodType);
+ method public static java.lang.invoke.MethodHandle explicitCastArguments(java.lang.invoke.MethodHandle, java.lang.invoke.MethodType);
method public static java.lang.invoke.MethodHandle filterArguments(java.lang.invoke.MethodHandle, int, java.lang.invoke.MethodHandle...);
method public static java.lang.invoke.MethodHandle filterReturnValue(java.lang.invoke.MethodHandle, java.lang.invoke.MethodHandle);
method public static java.lang.invoke.MethodHandle foldArguments(java.lang.invoke.MethodHandle, java.lang.invoke.MethodHandle);
diff --git a/api/test-removed.txt b/api/test-removed.txt
index 0f5b81a..b6c2a98 100644
--- a/api/test-removed.txt
+++ b/api/test-removed.txt
@@ -63,6 +63,12 @@
field public static final int REQUESTED_PERMISSION_REQUIRED = 1; // 0x1
}
+ public final class SharedLibraryInfo implements android.os.Parcelable {
+ method public boolean isBuiltin();
+ method public boolean isDynamic();
+ method public boolean isStatic();
+ }
+
}
package android.database {
@@ -126,6 +132,16 @@
}
+package android.location {
+
+ public class Location implements android.os.Parcelable {
+ method public deprecated void removeBearingAccuracy();
+ method public deprecated void removeSpeedAccuracy();
+ method public deprecated void removeVerticalAccuracy();
+ }
+
+}
+
package android.media {
public final class AudioFormat implements android.os.Parcelable {
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 169dcb0..c4b7ed7 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -2070,6 +2070,7 @@
if (args == null) {
throw new IllegalArgumentException("Expected non-null picture-in-picture args");
}
+ updatePictureInPictureArgsForContentInsets(args);
return ActivityManagerNative.getDefault().enterPictureInPictureMode(mToken, args);
} catch (RemoteException e) {
return false;
@@ -2087,11 +2088,27 @@
if (args == null) {
throw new IllegalArgumentException("Expected non-null picture-in-picture args");
}
+ updatePictureInPictureArgsForContentInsets(args);
ActivityManagerNative.getDefault().setPictureInPictureArgs(mToken, args);
} catch (RemoteException e) {
}
}
+ /**
+ * Updates the provided {@param args} with the last known content insets for this activity, to
+ * be used with the source hint rect for the transition into PiP.
+ */
+ private void updatePictureInPictureArgsForContentInsets(PictureInPictureArgs args) {
+ if (args != null && args.hasSourceBoundsHint() && getWindow() != null &&
+ getWindow().peekDecorView() != null &&
+ getWindow().peekDecorView().getViewRootImpl() != null) {
+ args.setSourceRectHintInsets(
+ getWindow().peekDecorView().getViewRootImpl().getLastContentInsets());
+ } else {
+ args.setSourceRectHintInsets(null);
+ }
+ }
+
void dispatchMovedToDisplay(int displayId, Configuration config) {
updateDisplay(displayId);
onMovedToDisplay(displayId, config);
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 66167a3..a4dcf63 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -2564,6 +2564,10 @@
* <p><b>Note: this method is only intended for debugging or implementing
* service management type user interfaces.</b></p>
*
+ * @deprecated As of {@link android.os.Build.VERSION_CODES#O}, this method
+ * is no longer available to third party applications. For backwards compatibility,
+ * it will still return the caller's own services.
+ *
* @param maxNum The maximum number of entries to return in the list. The
* actual number returned may be smaller, depending on how many services
* are running.
@@ -2571,6 +2575,7 @@
* @return Returns a list of RunningServiceInfo records describing each of
* the running tasks.
*/
+ @Deprecated
public List<RunningServiceInfo> getRunningServices(int maxNum)
throws SecurityException {
try {
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index 02d1884..63e8cc6 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -1211,9 +1211,6 @@
* methods that take an options Bundle.
*/
public Bundle toBundle() {
- if (mAnimationType == ANIM_DEFAULT) {
- return null;
- }
Bundle b = new Bundle();
if (mPackageName != null) {
b.putString(KEY_PACKAGE_NAME, mPackageName);
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index e07b7e4..2ce02df 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -79,6 +79,8 @@
import android.os.storage.VolumeInfo;
import android.provider.Settings;
import android.util.ArrayMap;
+import android.util.IconDrawableFactory;
+import android.util.LauncherIcons;
import android.util.Log;
import android.view.Display;
@@ -1245,16 +1247,9 @@
if (!isManagedProfile(user.getIdentifier())) {
return icon;
}
- Drawable badgeShadow = getDrawable("system",
- com.android.internal.R.drawable.ic_corp_icon_badge_shadow, null);
- Drawable badgeColor = getDrawable("system",
- com.android.internal.R.drawable.ic_corp_icon_badge_color, null);
- badgeColor.setTint(getUserBadgeColor(user));
- Drawable badgeForeground = getDrawable("system",
- com.android.internal.R.drawable.ic_corp_icon_badge_case, null);
-
- Drawable badge = new LayerDrawable(
- new Drawable[] {badgeShadow, badgeColor, badgeForeground });
+ Drawable badge = new LauncherIcons(mContext).getBadgeDrawable(
+ com.android.internal.R.drawable.ic_corp_icon_badge_case,
+ getUserBadgeColor(user));
return getBadgedDrawable(icon, badge, null, true);
}
@@ -1268,14 +1263,6 @@
return getBadgedDrawable(drawable, badgeDrawable, badgeLocation, true);
}
- // Should have enough colors to cope with UserManagerService.getMaxManagedProfiles()
- @VisibleForTesting
- public static final int[] CORP_BADGE_COLORS = new int[] {
- com.android.internal.R.color.profile_badge_1,
- com.android.internal.R.color.profile_badge_2,
- com.android.internal.R.color.profile_badge_3
- };
-
@VisibleForTesting
public static final int[] CORP_BADGE_LABEL_RES_ID = new int[] {
com.android.internal.R.string.managed_profile_label_badge,
@@ -1284,12 +1271,7 @@
};
private int getUserBadgeColor(UserHandle user) {
- int badge = getUserManager().getManagedProfileBadge(user.getIdentifier());
- if (badge < 0) {
- badge = 0;
- }
- int resourceId = CORP_BADGE_COLORS[badge % CORP_BADGE_COLORS.length];
- return Resources.getSystem().getColor(resourceId, null);
+ return IconDrawableFactory.getUserBadgeColor(getUserManager(), user.getIdentifier());
}
@Override
@@ -2648,4 +2630,13 @@
throw e.rethrowAsRuntimeException();
}
}
+
+ @Override
+ public String getInstantAppAndroidId(String packageName, UserHandle user) {
+ try {
+ return mPM.getInstantAppAndroidId(packageName, user.getIdentifier());
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
}
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 6cc8a14..80de64b 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -1447,21 +1447,13 @@
@Override
public ComponentName startService(Intent service) {
warnIfCallingFromSystemProcess();
- return startServiceCommon(service, -1, null, false, mUser);
+ return startServiceCommon(service, false, mUser);
}
@Override
public ComponentName startForegroundService(Intent service) {
warnIfCallingFromSystemProcess();
- return startServiceCommon(service, -1, null, true, mUser);
- }
-
- // STOPSHIP: remove when NotificationManager.startServiceInForeground() is retired
- @Override
- public ComponentName startServiceInForeground(Intent service,
- int id, Notification notification) {
- warnIfCallingFromSystemProcess();
- return startServiceCommon(service, id, notification, false, mUser);
+ return startServiceCommon(service, true, mUser);
}
@Override
@@ -1472,29 +1464,22 @@
@Override
public ComponentName startServiceAsUser(Intent service, UserHandle user) {
- return startServiceCommon(service, -1, null, false, user);
+ return startServiceCommon(service, false, user);
}
@Override
public ComponentName startForegroundServiceAsUser(Intent service, UserHandle user) {
- return startServiceCommon(service, -1, null, true, user);
+ return startServiceCommon(service, true, user);
}
- // STOPSHIP: remove when NotificationManager.startServiceInForeground() is retired
- @Override
- public ComponentName startServiceInForegroundAsUser(Intent service,
- int id, Notification notification, UserHandle user) {
- return startServiceCommon(service, id, notification, false, user);
- }
-
- private ComponentName startServiceCommon(Intent service, int id, Notification notification,
- boolean requireForeground, UserHandle user) {
+ private ComponentName startServiceCommon(Intent service, boolean requireForeground,
+ UserHandle user) {
try {
validateServiceIntent(service);
service.prepareToLeaveProcess(this);
ComponentName cn = ActivityManager.getService().startService(
mMainThread.getApplicationThread(), service, service.resolveTypeIfNeeded(
- getContentResolver()), id, notification, requireForeground,
+ getContentResolver()), requireForeground,
getOpPackageName(), user.getIdentifier());
if (cn != null) {
if (cn.getPackageName().equals("!")) {
diff --git a/core/java/android/app/Fragment.java b/core/java/android/app/Fragment.java
index c7bcc54..c5b93cd 100644
--- a/core/java/android/app/Fragment.java
+++ b/core/java/android/app/Fragment.java
@@ -509,6 +509,10 @@
// True if mHidden has been changed and the animation should be scheduled.
boolean mHiddenChanged;
+ // The cached value from onGetLayoutInflater(Bundle) that will be returned from
+ // getLayoutInflater()
+ LayoutInflater mLayoutInflater;
+
/**
* State information that has been retrieved from a fragment instance
* through {@link FragmentManager#saveFragmentInstanceState(Fragment)
@@ -1389,6 +1393,38 @@
}
/**
+ * Returns the cached LayoutInflater used to inflate Views of this Fragment. If
+ * {@link #onGetLayoutInflater(Bundle)} has not been called {@link #onGetLayoutInflater(Bundle)}
+ * will be called with a {@code null} argument and that value will be cached.
+ * <p>
+ * The cached LayoutInflater will be replaced immediately prior to
+ * {@link #onCreateView(LayoutInflater, ViewGroup, Bundle)} and cleared immediately after
+ * {@link #onDetach()}.
+ *
+ * @return The LayoutInflater used to inflate Views of this Fragment.
+ */
+ public final LayoutInflater getLayoutInflater() {
+ if (mLayoutInflater == null) {
+ return performGetLayoutInflater(null);
+ }
+ return mLayoutInflater;
+ }
+
+ /**
+ * Calls {@link #onGetLayoutInflater(Bundle)} and caches the result for use by
+ * {@link #getLayoutInflater()}.
+ *
+ * @param savedInstanceState If the fragment is being re-created from
+ * a previous saved state, this is the state.
+ * @return The LayoutInflater used to inflate Views of this Fragment.
+ */
+ LayoutInflater performGetLayoutInflater(Bundle savedInstanceState) {
+ LayoutInflater layoutInflater = onGetLayoutInflater(savedInstanceState);
+ mLayoutInflater = layoutInflater;
+ return mLayoutInflater;
+ }
+
+ /**
* @deprecated Use {@link #onInflate(Context, AttributeSet, Bundle)} instead.
*/
@Deprecated
@@ -2835,6 +2871,7 @@
void performDetach() {
mCalled = false;
onDetach();
+ mLayoutInflater = null;
if (!mCalled) {
throw new SuperNotCalledException("Fragment " + this
+ " did not call through to super.onDetach()");
diff --git a/core/java/android/app/FragmentManager.java b/core/java/android/app/FragmentManager.java
index 91578a2..c1161a2 100644
--- a/core/java/android/app/FragmentManager.java
+++ b/core/java/android/app/FragmentManager.java
@@ -1259,7 +1259,7 @@
}
}
f.mContainer = container;
- f.mView = f.performCreateView(f.onGetLayoutInflater(
+ f.mView = f.performCreateView(f.performGetLayoutInflater(
f.mSavedFragmentState), container, f.mSavedFragmentState);
if (f.mView != null) {
f.mView.setSaveFromParentEnabled(false);
@@ -1431,7 +1431,7 @@
void ensureInflatedFragmentView(Fragment f) {
if (f.mFromLayout && !f.mPerformedCreateView) {
- f.mView = f.performCreateView(f.onGetLayoutInflater(
+ f.mView = f.performCreateView(f.performGetLayoutInflater(
f.mSavedFragmentState), null, f.mSavedFragmentState);
if (f.mView != null) {
f.mView.setSaveFromParentEnabled(false);
@@ -1462,18 +1462,22 @@
if (fragment.isHideReplaced()) {
fragment.setHideReplaced(false);
} else {
+ final ViewGroup container = fragment.mContainer;
+ final View animatingView = fragment.mView;
+ container.startViewTransition(animatingView);
// Delay the actual hide operation until the animation finishes, otherwise
// the fragment will just immediately disappear
anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
+ container.endViewTransition(animatingView);
animation.removeListener(this);
- if (fragment.mView != null) {
- fragment.mView.setVisibility(View.GONE);
- }
+ animatingView.setVisibility(View.GONE);
}
});
}
+ } else {
+ fragment.mView.setVisibility(View.VISIBLE);
}
setHWLayerAnimListenerIfAlpha(fragment.mView, anim);
anim.start();
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 4210d2e..d270244 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -129,8 +129,7 @@
void finishSubActivity(in IBinder token, in String resultWho, int requestCode);
PendingIntent getRunningServiceControlPanel(in ComponentName service);
ComponentName startService(in IApplicationThread caller, in Intent service,
- in String resolvedType, int id, in Notification notification,
- boolean requireForeground, in String callingPackage, int userId);
+ in String resolvedType, boolean requireForeground, in String callingPackage, int userId);
int stopService(in IApplicationThread caller, in Intent service,
in String resolvedType, int userId);
int bindService(in IApplicationThread caller, in IBinder token, in Intent service,
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index 72c5978..242d4a5 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -1155,40 +1155,4 @@
}
}
- /**
- * Start a service directly into the "foreground service" state. Unlike
- * {@link android.content.Context#startService(Intent)}, this method
- * can be used from within background operations like broadcast receivers
- * or scheduled jobs.
- *
- * @param service Description of the service to be started. The Intent must be either
- * fully explicit (supplying a component name) or specify a specific package
- * name it is targeted to.
- * @param id The identifier for this notification as per
- * {@link #notify(int, Notification) NotificationManager.notify(int, Notification)};
- * must not be 0.
- * @param notification The Notification to be displayed.
- * @return If the service is being started or is already running, the
- * {@link ComponentName} of the actual service that was started is
- * returned; else if the service does not exist null is returned.
- *
- * @deprecated STOPSHIP transition away from this for O
- */
- @Nullable
- @Deprecated
- public ComponentName startServiceInForeground(Intent service,
- int id, Notification notification) {
- return mContext.startServiceInForeground(service, id, notification);
- }
-
- /**
- * @hide like {@link #startServiceInForeground(Intent, int, Notification)}
- * but for a specific user.
- */
- @Nullable
- public ComponentName startServiceInForegroundAsUser(Intent service,
- int id, Notification notification, UserHandle user) {
- return mContext.startServiceInForegroundAsUser(service, id, notification, user);
- }
-
}
diff --git a/core/java/android/app/PictureInPictureArgs.java b/core/java/android/app/PictureInPictureArgs.java
index 0ce5eeb..2fa6360 100644
--- a/core/java/android/app/PictureInPictureArgs.java
+++ b/core/java/android/app/PictureInPictureArgs.java
@@ -49,6 +49,13 @@
@Nullable
private Rect mSourceRectHint;
+ /**
+ * The content insets that are used with the source hint rect for the transition into PiP where
+ * the insets are removed at the beginning of the transition.
+ */
+ @Nullable
+ private Rect mSourceRectHintInsets;
+
PictureInPictureArgs(Parcel in) {
if (in.readInt() != 0) {
mAspectRatio = in.readFloat();
@@ -60,6 +67,9 @@
if (in.readInt() != 0) {
mSourceRectHint = Rect.CREATOR.createFromParcel(in);
}
+ if (in.readInt() != 0) {
+ mSourceRectHintInsets = Rect.CREATOR.createFromParcel(in);
+ }
}
/**
@@ -94,6 +104,9 @@
if (otherArgs.hasSourceBoundsHint()) {
mSourceRectHint = new Rect(otherArgs.getSourceRectHint());
}
+ if (otherArgs.hasSourceBoundsHintInsets()) {
+ mSourceRectHintInsets = new Rect(otherArgs.getSourceRectHintInsets());
+ }
}
/**
@@ -167,7 +180,19 @@
}
/**
- * @return the launch bounds
+ * Sets the insets to be used with the source rect hint bounds.
+ * @hide
+ */
+ public void setSourceRectHintInsets(Rect insets) {
+ if (insets == null) {
+ mSourceRectHintInsets = null;
+ } else {
+ mSourceRectHintInsets = new Rect(insets);
+ }
+ }
+
+ /**
+ * @return the source rect hint
* @hide
*/
public Rect getSourceRectHint() {
@@ -175,6 +200,14 @@
}
/**
+ * @return the source rect hint insets.
+ * @hide
+ */
+ public Rect getSourceRectHintInsets() {
+ return mSourceRectHintInsets;
+ }
+
+ /**
* @return whether there are launch bounds set
* @hide
*/
@@ -182,12 +215,23 @@
return mSourceRectHint != null && !mSourceRectHint.isEmpty();
}
+ /**
+ * @return whether there are source rect hint insets set
+ * @hide
+ */
+ public boolean hasSourceBoundsHintInsets() {
+ return mSourceRectHintInsets != null;
+ }
+
@Override
public PictureInPictureArgs clone() {
PictureInPictureArgs args = new PictureInPictureArgs(mAspectRatio, mUserActions);
if (mSourceRectHint != null) {
args.setSourceRectHint(mSourceRectHint);
}
+ if (mSourceRectHintInsets != null) {
+ args.setSourceRectHintInsets(mSourceRectHintInsets);
+ }
return args;
}
@@ -216,6 +260,12 @@
} else {
out.writeInt(0);
}
+ if (mSourceRectHintInsets != null) {
+ out.writeInt(1);
+ mSourceRectHintInsets.writeToParcel(out, 0);
+ } else {
+ out.writeInt(0);
+ }
}
public static final Creator<PictureInPictureArgs> CREATOR =
diff --git a/core/java/android/app/assist/AssistStructure.java b/core/java/android/app/assist/AssistStructure.java
index 545aef5..9f911f5 100644
--- a/core/java/android/app/assist/AssistStructure.java
+++ b/core/java/android/app/assist/AssistStructure.java
@@ -964,6 +964,16 @@
*
* @return The hints for this view
*/
+ @Nullable public String[] getAutofillHints() {
+ return mAutofillHints;
+ }
+
+ /**
+ * @hide
+ * @deprecated use getAutofillHints() instead.
+ */
+ // TODO(b/33197203): remove once clients don't use it anymore...
+ @Deprecated
@Nullable public String[] getAutoFillHints() {
return mAutofillHints;
}
diff --git a/core/java/android/appwidget/AppWidgetHost.java b/core/java/android/appwidget/AppWidgetHost.java
index 6ba52b7..c1ff580 100644
--- a/core/java/android/appwidget/AppWidgetHost.java
+++ b/core/java/android/appwidget/AppWidgetHost.java
@@ -276,8 +276,6 @@
/**
* Gets a list of all the appWidgetIds that are bound to the current host
- *
- * @hide
*/
public int[] getAppWidgetIds() {
try {
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 18120c7..46a7042 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -57,6 +57,7 @@
import android.os.StatFs;
import android.os.UserHandle;
import android.os.UserManager;
+import android.os.storage.StorageManager;
import android.provider.MediaStore;
import android.util.AttributeSet;
import android.view.Display;
@@ -1146,16 +1147,26 @@
/**
* Returns the absolute path to the application specific cache directory on
- * the filesystem. These files will be ones that get deleted first when the
- * device runs low on storage. There is no guarantee when these files will
- * be deleted.
+ * the filesystem.
* <p>
- * <strong>Note: you should not <em>rely</em> on the system deleting these
- * files for you; you should always have a reasonable maximum, such as 1 MB,
- * for the amount of space you consume with cache files, and prune those
- * files when exceeding that space.</strong> If your app requires a larger
- * cache (larger than 1 MB), you should use {@link #getExternalCacheDir()}
- * instead.
+ * The system will automatically delete files in this directory as disk
+ * space is needed elsewhere on the device. The system will always delete
+ * older files first, as reported by {@link File#lastModified()}. If
+ * desired, you can exert more control over how files are deleted using
+ * {@link StorageManager#setCacheBehaviorGroup(File, boolean)} and
+ * {@link StorageManager#setCacheBehaviorTombstone(File, boolean)}.
+ * <p>
+ * Apps are strongly encouraged to keep their usage of cache space below the
+ * quota returned by
+ * {@link StorageManager#getCacheQuotaBytes(java.util.UUID)}. If your app
+ * goes above this quota, your cached files will be some of the first to be
+ * deleted when additional disk space is needed. Conversely, if your app
+ * stays under this quota, your cached files will be some of the last to be
+ * deleted when additional disk space is needed.
+ * <p>
+ * Note that your cache quota will change over time depending on how
+ * frequently the user interacts with your app, and depending on how much
+ * system-wide disk space is used.
* <p>
* The returned path may change over time if the calling app is moved to an
* adopted storage device, so only relative paths should be persisted.
@@ -1173,9 +1184,11 @@
/**
* Returns the absolute path to the application specific cache directory on
- * the filesystem designed for storing cached code. The system will delete
- * any files stored in this location both when your specific application is
- * upgraded, and when the entire platform is upgraded.
+ * the filesystem designed for storing cached code.
+ * <p>
+ * The system will delete any files stored in this location both when your
+ * specific application is upgraded, and when the entire platform is
+ * upgraded.
* <p>
* This location is optimal for storing compiled or optimized code generated
* by your application at runtime.
@@ -2647,18 +2660,6 @@
public abstract ComponentName startForegroundServiceAsUser(Intent service, UserHandle user);
/**
- * Start a service directly into the "foreground service" state. Unlike {@link #startService},
- * this method can be used from within background operations like broadcast receivers
- * or scheduled jobs. The API entry point for this is in NotificationManager in order to
- * preserve appropriate public package layering.
- * @hide
- * @deprecated STOPSHIP remove in favor of two-step startForegroundService() + startForeground()
- */
- @Nullable
- public abstract ComponentName startServiceInForeground(Intent service,
- int id, Notification notification);
-
- /**
* Request that a given application service be stopped. If the service is
* not running, nothing happens. Otherwise it is stopped. Note that calls
* to startService() are not counted -- this stops the service no matter
@@ -2696,16 +2697,6 @@
public abstract ComponentName startServiceAsUser(Intent service, UserHandle user);
/**
- * @hide like {@link #startServiceInForeground(Intent, int, Notification)}
- * but for a specific user.
- * @deprecated STOPSHIP remove when trial API is turned off
- */
- @Deprecated
- @Nullable
- public abstract ComponentName startServiceInForegroundAsUser(Intent service,
- int id, Notification notification, UserHandle user);
-
- /**
* @hide like {@link #stopService(Intent)} but for a specific user.
*/
public abstract boolean stopServiceAsUser(Intent service, UserHandle user);
@@ -3744,6 +3735,7 @@
public static final String PRINT_SERVICE = "print";
/**
+ * Use with {@link #getSystemService} to retrieve a
* {@link android.companion.CompanionDeviceManager} for managing companion devices
*
* @see #getSystemService
diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java
index 53b021c..b59fc3dd 100644
--- a/core/java/android/content/ContextWrapper.java
+++ b/core/java/android/content/ContextWrapper.java
@@ -649,13 +649,6 @@
return mBase.startForegroundService(service);
}
- /** @hide STOPSHIP remove when trial API is turned down */
- @Override
- public ComponentName startServiceInForeground(Intent service,
- int id, Notification notification) {
- return mBase.startServiceInForeground(service, id, notification);
- }
-
@Override
public boolean stopService(Intent name) {
return mBase.stopService(name);
@@ -673,13 +666,6 @@
return mBase.startForegroundServiceAsUser(service, user);
}
- /** @hide STOPSHIP removed when trial API is turned down */
- @Override
- public ComponentName startServiceInForegroundAsUser(Intent service,
- int id, Notification notification, UserHandle user) {
- return mBase.startServiceInForegroundAsUser(service, id, notification, user);
- }
-
/** @hide */
@Override
public boolean stopServiceAsUser(Intent name, UserHandle user) {
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 5ca4fa3..11eab61 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -8616,6 +8616,32 @@
* a single statement.
* @see #setFlags(int)
* @see #removeFlags(int)
+ *
+ * @see #FLAG_GRANT_READ_URI_PERMISSION
+ * @see #FLAG_GRANT_WRITE_URI_PERMISSION
+ * @see #FLAG_GRANT_PERSISTABLE_URI_PERMISSION
+ * @see #FLAG_GRANT_PREFIX_URI_PERMISSION
+ * @see #FLAG_DEBUG_LOG_RESOLUTION
+ * @see #FLAG_FROM_BACKGROUND
+ * @see #FLAG_ACTIVITY_BROUGHT_TO_FRONT
+ * @see #FLAG_ACTIVITY_CLEAR_TASK
+ * @see #FLAG_ACTIVITY_CLEAR_TOP
+ * @see #FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET
+ * @see #FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
+ * @see #FLAG_ACTIVITY_FORWARD_RESULT
+ * @see #FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY
+ * @see #FLAG_ACTIVITY_MULTIPLE_TASK
+ * @see #FLAG_ACTIVITY_NEW_DOCUMENT
+ * @see #FLAG_ACTIVITY_NEW_TASK
+ * @see #FLAG_ACTIVITY_NO_ANIMATION
+ * @see #FLAG_ACTIVITY_NO_HISTORY
+ * @see #FLAG_ACTIVITY_NO_USER_ACTION
+ * @see #FLAG_ACTIVITY_PREVIOUS_IS_TOP
+ * @see #FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
+ * @see #FLAG_ACTIVITY_REORDER_TO_FRONT
+ * @see #FLAG_ACTIVITY_SINGLE_TOP
+ * @see #FLAG_ACTIVITY_TASK_ON_HOME
+ * @see #FLAG_RECEIVER_REGISTERED_ONLY
*/
public Intent addFlags(int flags) {
mFlags |= flags;
@@ -8628,6 +8654,32 @@
* @param flags The flags to remove.
* @see #setFlags(int)
* @see #addFlags(int)
+ *
+ * @see #FLAG_GRANT_READ_URI_PERMISSION
+ * @see #FLAG_GRANT_WRITE_URI_PERMISSION
+ * @see #FLAG_GRANT_PERSISTABLE_URI_PERMISSION
+ * @see #FLAG_GRANT_PREFIX_URI_PERMISSION
+ * @see #FLAG_DEBUG_LOG_RESOLUTION
+ * @see #FLAG_FROM_BACKGROUND
+ * @see #FLAG_ACTIVITY_BROUGHT_TO_FRONT
+ * @see #FLAG_ACTIVITY_CLEAR_TASK
+ * @see #FLAG_ACTIVITY_CLEAR_TOP
+ * @see #FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET
+ * @see #FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
+ * @see #FLAG_ACTIVITY_FORWARD_RESULT
+ * @see #FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY
+ * @see #FLAG_ACTIVITY_MULTIPLE_TASK
+ * @see #FLAG_ACTIVITY_NEW_DOCUMENT
+ * @see #FLAG_ACTIVITY_NEW_TASK
+ * @see #FLAG_ACTIVITY_NO_ANIMATION
+ * @see #FLAG_ACTIVITY_NO_HISTORY
+ * @see #FLAG_ACTIVITY_NO_USER_ACTION
+ * @see #FLAG_ACTIVITY_PREVIOUS_IS_TOP
+ * @see #FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
+ * @see #FLAG_ACTIVITY_REORDER_TO_FRONT
+ * @see #FLAG_ACTIVITY_SINGLE_TOP
+ * @see #FLAG_ACTIVITY_TASK_ON_HOME
+ * @see #FLAG_RECEIVER_REGISTERED_ONLY
*/
public void removeFlags(int flags) {
mFlags &= ~flags;
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 8b2809a..b0f8aa7 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -1203,7 +1203,12 @@
dest.writeInt(requiresSmallestWidthDp);
dest.writeInt(compatibleWidthLimitDp);
dest.writeInt(largestWidthLimitDp);
- dest.writeUuid(storageUuid);
+ if (storageUuid != null) {
+ dest.writeInt(1);
+ dest.writeUuid(storageUuid);
+ } else {
+ dest.writeInt(0);
+ }
dest.writeString(scanSourceDir);
dest.writeString(scanPublicSourceDir);
dest.writeString(sourceDir);
@@ -1265,8 +1270,10 @@
requiresSmallestWidthDp = source.readInt();
compatibleWidthLimitDp = source.readInt();
largestWidthLimitDp = source.readInt();
- storageUuid = source.readUuid();
- volumeUuid = StorageManager.convert(storageUuid);
+ if (source.readInt() != 0) {
+ storageUuid = source.readUuid();
+ volumeUuid = StorageManager.convert(storageUuid);
+ }
scanSourceDir = source.readString();
scanPublicSourceDir = source.readString();
sourceDir = source.readString();
diff --git a/core/java/android/content/pm/ChangedPackages.java b/core/java/android/content/pm/ChangedPackages.java
index 94b8a5d..78c057d 100644
--- a/core/java/android/content/pm/ChangedPackages.java
+++ b/core/java/android/content/pm/ChangedPackages.java
@@ -17,6 +17,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.TestApi;
import android.content.Intent;
import android.os.Parcel;
import android.os.Parcelable;
@@ -26,6 +27,7 @@
/**
* Packages that have been changed since the last time they
* were requested.
+ * @see PackageManager#getChangedPackages(int)
*/
public final class ChangedPackages implements Parcelable {
/** The last known sequence number for these changes */
@@ -33,6 +35,7 @@
/** The names of the packages that have changed */
private final List<String> mPackageNames;
+ @TestApi
public ChangedPackages(int sequenceNumber, @NonNull List<String> packageNames) {
this.mSequenceNumber = sequenceNumber;
this.mPackageNames = packageNames;
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index bbc942a..bc7a612 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -634,4 +634,6 @@
ComponentName getInstantAppResolverSettingsComponent();
ComponentName getInstantAppInstallerComponent();
+
+ String getInstantAppAndroidId(String packageName, int userId);
}
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 09906be..7cc2334 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -54,6 +54,7 @@
import android.os.UserManager;
import android.os.storage.StorageManager;
import android.os.storage.VolumeInfo;
+import android.provider.Settings;
import android.util.AndroidException;
import android.util.Log;
@@ -3983,6 +3984,8 @@
* <p>If no packages have been changed, returns <code>null</code>.
* <p>The sequence number starts at <code>0</code> and is
* reset every boot.
+ * @param sequenceNumber The first sequence number for which to retrieve package changes.
+ * @see Settings.Global#BOOT_COUNT
*/
public abstract @Nullable ChangedPackages getChangedPackages(
@IntRange(from=0) int sequenceNumber);
@@ -5937,8 +5940,20 @@
* <p>
* This hint can only be set by the app which installed this package, as
* determined by {@link #getInstallerPackageName(String)}.
+ *
+ * @param packageName the package to change the category hint for.
+ * @param categoryHint the category hint to set; one of
+ * {@link ApplicationInfo#CATEGORY_AUDIO},
+ * {@link ApplicationInfo#CATEGORY_GAME},
+ * {@link ApplicationInfo#CATEGORY_IMAGE},
+ * {@link ApplicationInfo#CATEGORY_MAPS},
+ * {@link ApplicationInfo#CATEGORY_NEWS},
+ * {@link ApplicationInfo#CATEGORY_PRODUCTIVITY},
+ * {@link ApplicationInfo#CATEGORY_SOCIAL},
+ * {@link ApplicationInfo#CATEGORY_UNDEFINED}, or
+ * {@link ApplicationInfo#CATEGORY_VIDEO}.
*/
- public abstract void setApplicationCategoryHint(String packageName,
+ public abstract void setApplicationCategoryHint(@NonNull String packageName,
@ApplicationInfo.Category int categoryHint);
/** {@hide} */
@@ -6256,18 +6271,18 @@
/**
* Checks whether the calling package is allowed to request package installs through package
- * installer. Apps are encouraged to call this api before launching the package installer via
+ * installer. Apps are encouraged to call this API before launching the package installer via
* intent {@link android.content.Intent#ACTION_INSTALL_PACKAGE}. Starting from Android O, the
* user can explicitly choose what external sources they trust to install apps on the device.
- * If this api returns false, the install request will be blocked by the package installer and
+ * If this API returns false, the install request will be blocked by the package installer and
* a dialog will be shown to the user with an option to launch settings to change their
* preference. An application must target Android O or higher and declare permission
- * {@link android.Manifest.permission#REQUEST_INSTALL_PACKAGES} in order to use this api.
+ * {@link android.Manifest.permission#REQUEST_INSTALL_PACKAGES} in order to use this API.
*
* @return true if the calling package is trusted by the user to request install packages on
* the device, false otherwise.
- * @see {@link android.content.Intent#ACTION_INSTALL_PACKAGE}
- * @see {@link android.provider.Settings#ACTION_MANAGE_UNKNOWN_APP_SOURCES}
+ * @see android.content.Intent#ACTION_INSTALL_PACKAGE
+ * @see android.provider.Settings#ACTION_MANAGE_UNKNOWN_APP_SOURCES
*/
public abstract boolean canRequestPackageInstalls();
@@ -6290,4 +6305,12 @@
*/
@SystemApi
public abstract ComponentName getInstantAppInstallerComponent();
+
+ /**
+ * Return the Android Id for a given Instant App.
+ *
+ * @see {@link android.provider.Settings.Secure#ANDROID_ID}
+ * @hide
+ */
+ public abstract String getInstantAppAndroidId(String packageName, @NonNull UserHandle user);
}
diff --git a/core/java/android/content/pm/SharedLibraryInfo.java b/core/java/android/content/pm/SharedLibraryInfo.java
index d79deb2..0ad4874 100644
--- a/core/java/android/content/pm/SharedLibraryInfo.java
+++ b/core/java/android/content/pm/SharedLibraryInfo.java
@@ -16,11 +16,14 @@
package android.content.pm;
+import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.os.Parcel;
import android.os.Parcelable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.Collections;
import java.util.List;
@@ -31,28 +34,37 @@
* static - updatable non backwards-compatible emulating static linking.
*/
public final class SharedLibraryInfo implements Parcelable {
+
+ /** @hide */
+ @IntDef(
+ flag = true,
+ value = {
+ TYPE_BUILTIN,
+ TYPE_DYNAMIC,
+ TYPE_STATIC,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface Type{}
+
/**
* Shared library type: this library is a part of the OS
* and cannot be updated or uninstalled.
- * @hide
*/
- public static final int TYPE_BUILTIN = 0x1<<0;
+ public static final int TYPE_BUILTIN = 0;
/**
* Shared library type: this library is backwards-compatible, can
* be updated, and updates can be uninstalled. Clients link against
* the latest version of the library.
- * @hide
*/
- public static final int TYPE_DYNAMIC = 0x1<<1;
+ public static final int TYPE_DYNAMIC = 1;
/**
* Shared library type: this library is <strong>not</strong> backwards
* -compatible, can be updated and updates can be uninstalled. Clients
* link against a specific version of the library.
- * @hide
*/
- public static final int TYPE_STATIC = 0x1<<2;
+ public static final int TYPE_STATIC = 2;
/**
* Constant for referring to an undefined version.
@@ -60,8 +72,10 @@
public static final int VERSION_UNDEFINED = -1;
private final String mName;
+
+ // TODO: Make long when we change the paltform to use longs
private final int mVersion;
- private final int mType;
+ private final @Type int mType;
private final VersionedPackage mDeclaringPackage;
private final List<VersionedPackage> mDependentPackages;
@@ -90,13 +104,18 @@
parcel.readParcelable(null), parcel.readArrayList(null));
}
- /** @hide */
- public int getType() {
+ /**
+ * Gets the type of this library.
+ *
+ * @return The library type.
+ */
+ public @Type int getType() {
return mType;
}
/**
- * Gets the library name.
+ * Gets the library name an app defines in its manifest
+ * to depend on the library.
*
* @return The name.
*/
@@ -105,40 +124,36 @@
}
/**
- * Gets the version of the library. For {@link #isStatic()} static} libraries
- * this is the declared version and for {@link #isDynamic()} dynamic} and
- * {@link #isBuiltin()} builtin} it is {@link #VERSION_UNDEFINED} as these
+ * Gets the version of the library. For {@link #TYPE_STATIC static} libraries
+ * this is the declared version and for {@link #TYPE_DYNAMIC dynamic} and
+ * {@link #TYPE_BUILTIN builtin} it is {@link #VERSION_UNDEFINED} as these
* are not versioned.
*
* @return The version.
*/
- public @IntRange(from = -1) int getVersion() {
+ public @IntRange(from = -1) long getVersion() {
return mVersion;
}
/**
- * @return whether this library is builtin which means that it
- * is a part of the OS and cannot be updated or uninstalled.
+ * @hide
+ * @removed
*/
public boolean isBuiltin() {
return mType == TYPE_BUILTIN;
}
/**
- * @return whether this library is dynamic which means that it
- * is backwards-compatible, can be updated, and updates can be
- * uninstalled. Clients link against the latest version of the
- * library.
+ * @hide
+ * @removed
*/
public boolean isDynamic() {
return mType == TYPE_DYNAMIC;
}
/**
- * @return whether this library is dynamic which means that it
- * is <strong>not</strong> backwards-compatible, can be updated
- * and updates can be uninstalled. Clients link against a specific
- * version of the library.
+ * @hide
+ * @removed
*/
public boolean isStatic() {
return mType == TYPE_STATIC;
diff --git a/core/java/android/content/res/TypedArray.java b/core/java/android/content/res/TypedArray.java
index f48afb5..88bb1a4 100644
--- a/core/java/android/content/res/TypedArray.java
+++ b/core/java/android/content/res/TypedArray.java
@@ -56,12 +56,13 @@
// Reset the assets, which may have changed due to configuration changes
// or further resource loading.
attrs.mAssets = res.getAssets();
+ attrs.mMetrics = res.getDisplayMetrics();
attrs.resize(len);
return attrs;
}
private final Resources mResources;
- private final DisplayMetrics mMetrics;
+ private DisplayMetrics mMetrics;
private AssetManager mAssets;
private boolean mRecycled;
diff --git a/core/java/android/metrics/LogMaker.java b/core/java/android/metrics/LogMaker.java
index 0448221..3c6baa7 100644
--- a/core/java/android/metrics/LogMaker.java
+++ b/core/java/android/metrics/LogMaker.java
@@ -54,7 +54,11 @@
/* Deserialize from the eventlog */
public LogMaker(Object[] items) {
- deserialize(items);
+ if (items != null) {
+ deserialize(items);
+ } else {
+ setCategory(MetricsEvent.VIEW_UNKNOWN);
+ }
}
/** @param category to replace the existing setting. */
@@ -94,6 +98,16 @@
}
/**
+ * Set event latency.
+ *
+ * @hide // TODO Expose in the future? Too late for O.
+ */
+ public LogMaker setLatency(long milliseconds) {
+ entries.put(MetricsEvent.NOTIFICATION_SINCE_CREATE_MILLIS, milliseconds);
+ return this;
+ }
+
+ /**
* This will be set by the system when the log is persisted.
* Client-supplied values will be ignored.
*
@@ -363,13 +377,13 @@
*/
public void deserialize(Object[] items) {
int i = 0;
- while (i < items.length) {
+ while (items != null && i < items.length) {
Object key = items[i++];
Object value = i < items.length ? items[i++] : null;
if (key instanceof Integer) {
entries.put((Integer) key, value);
} else {
- Log.i(TAG, "Invalid key " + key.toString());
+ Log.i(TAG, "Invalid key " + (key == null ? "null" : key.toString()));
}
}
}
diff --git a/core/java/android/net/IpSecManager.java b/core/java/android/net/IpSecManager.java
index f8702e2..375b7ee 100644
--- a/core/java/android/net/IpSecManager.java
+++ b/core/java/android/net/IpSecManager.java
@@ -245,6 +245,7 @@
*
* @param socket a stream socket
* @param transform an {@link IpSecTransform}, which must be an active Transport Mode transform.
+ * @hide
*/
public void applyTransportModeTransform(Socket socket, IpSecTransform transform)
throws IOException {
@@ -262,6 +263,7 @@
*
* @param socket a datagram socket
* @param transform an {@link IpSecTransform}, which must be an active Transport Mode transform.
+ * @hide
*/
public void applyTransportModeTransform(DatagramSocket socket, IpSecTransform transform)
throws IOException {
@@ -284,7 +286,7 @@
* address associated with that transform will throw an IOException. In addition, if the
* IpSecTransform is later deactivated, the socket will throw an IOException on any calls to
* send() or receive() until the transform is removed from the socket by calling {@link
- * #removeTransportModeTransform(Socket, IpSecTransform)};
+ * #removeTransportModeTransform(FileDescriptor, IpSecTransform)};
*
* @param socket a socket file descriptor
* @param transform an {@link IpSecTransform}, which must be an active Transport Mode transform.
@@ -316,8 +318,10 @@
*
* @param socket a socket that previously had a transform applied to it.
* @param transform the IPsec Transform that was previously applied to the given socket
+ * @hide
*/
- public void removeTransportModeTransform(Socket socket, IpSecTransform transform) {
+ public void removeTransportModeTransform(Socket socket, IpSecTransform transform)
+ throws IOException {
removeTransportModeTransform(ParcelFileDescriptor.fromSocket(socket), transform);
}
@@ -330,8 +334,10 @@
*
* @param socket a socket that previously had a transform applied to it.
* @param transform the IPsec Transform that was previously applied to the given socket
+ * @hide
*/
- public void removeTransportModeTransform(DatagramSocket socket, IpSecTransform transform) {
+ public void removeTransportModeTransform(DatagramSocket socket, IpSecTransform transform)
+ throws IOException {
removeTransportModeTransform(ParcelFileDescriptor.fromDatagramSocket(socket), transform);
}
@@ -345,7 +351,8 @@
* @param socket a socket file descriptor that previously had a transform applied to it.
* @param transform the IPsec Transform that was previously applied to the given socket
*/
- public void removeTransportModeTransform(FileDescriptor socket, IpSecTransform transform) {
+ public void removeTransportModeTransform(FileDescriptor socket, IpSecTransform transform)
+ throws IOException {
removeTransportModeTransform(new ParcelFileDescriptor(socket), transform);
}
@@ -419,7 +426,7 @@
*
* @param fd a file descriptor previously returned as a UDP Encapsulation socket.
*/
- public void close() {
+ public void close() throws IOException {
// TODO: Go close the socket
mCloseGuard.close();
}
diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java
index afca0b0..bf7207c 100644
--- a/core/java/android/net/NetworkCapabilities.java
+++ b/core/java/android/net/NetworkCapabilities.java
@@ -19,6 +19,7 @@
import android.os.Parcel;
import android.os.Parcelable;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.BitUtils;
import java.util.Objects;
@@ -230,7 +231,8 @@
* Capabilities that suggest that a network is restricted.
* {@see #maybeMarkCapabilitiesRestricted}.
*/
- private static final long RESTRICTED_CAPABILITIES =
+ @VisibleForTesting
+ /* package */ static final long RESTRICTED_CAPABILITIES =
(1 << NET_CAPABILITY_CBS) |
(1 << NET_CAPABILITY_DUN) |
(1 << NET_CAPABILITY_EIMS) |
@@ -241,6 +243,17 @@
(1 << NET_CAPABILITY_XCAP);
/**
+ * Capabilities that suggest that a network is unrestricted.
+ * {@see #maybeMarkCapabilitiesRestricted}.
+ */
+ @VisibleForTesting
+ /* package */ static final long UNRESTRICTED_CAPABILITIES =
+ (1 << NET_CAPABILITY_INTERNET) |
+ (1 << NET_CAPABILITY_MMS) |
+ (1 << NET_CAPABILITY_SUPL) |
+ (1 << NET_CAPABILITY_WIFI_P2P);
+
+ /**
* Adds the given capability to this {@code NetworkCapability} instance.
* Multiple capabilities may be applied sequentially. Note that when searching
* for a network to satisfy a request, all capabilities requested must be satisfied.
@@ -353,12 +366,16 @@
* @hide
*/
public void maybeMarkCapabilitiesRestricted() {
- // If all the capabilities are typically provided by restricted networks, conclude that this
- // network is restricted.
- if ((mNetworkCapabilities & ~(DEFAULT_CAPABILITIES | RESTRICTED_CAPABILITIES)) == 0 &&
- // Must have at least some restricted capabilities, otherwise a request for an
- // internet-less network will get marked restricted.
- (mNetworkCapabilities & RESTRICTED_CAPABILITIES) != 0) {
+ // Verify there aren't any unrestricted capabilities. If there are we say
+ // the whole thing is unrestricted.
+ final boolean hasUnrestrictedCapabilities =
+ ((mNetworkCapabilities & UNRESTRICTED_CAPABILITIES) != 0);
+
+ // Must have at least some restricted capabilities.
+ final boolean hasRestrictedCapabilities =
+ ((mNetworkCapabilities & RESTRICTED_CAPABILITIES) != 0);
+
+ if (hasRestrictedCapabilities && !hasUnrestrictedCapabilities) {
removeCapability(NET_CAPABILITY_NOT_RESTRICTED);
}
}
diff --git a/core/java/android/os/RecoverySystem.java b/core/java/android/os/RecoverySystem.java
index 5f66abd..447f280 100644
--- a/core/java/android/os/RecoverySystem.java
+++ b/core/java/android/os/RecoverySystem.java
@@ -16,6 +16,8 @@
package android.os;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
import android.annotation.SystemApi;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -24,9 +26,12 @@
import android.text.TextUtils;
import android.util.Log;
+import libcore.io.Streams;
+
import java.io.ByteArrayInputStream;
import java.io.BufferedReader;
import java.io.File;
+import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
@@ -39,6 +44,7 @@
import java.security.SignatureException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
+import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Iterator;
@@ -46,6 +52,7 @@
import java.util.Locale;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
+import java.util.zip.ZipInputStream;
import com.android.internal.logging.MetricsLogger;
@@ -317,6 +324,70 @@
} finally {
raf.close();
}
+
+ // Additionally verify the package compatibility.
+ if (!readAndVerifyPackageCompatibilityEntry(packageFile)) {
+ throw new SignatureException("package compatibility verification failed");
+ }
+ }
+
+ /**
+ * Verifies the compatibility entry from an {@link InputStream}.
+ *
+ * @return the verification result.
+ */
+ private static boolean verifyPackageCompatibility(InputStream inputStream) throws IOException {
+ ArrayList<String> list = new ArrayList<>();
+ ZipInputStream zis = new ZipInputStream(inputStream);
+ ZipEntry entry;
+ while ((entry = zis.getNextEntry()) != null) {
+ long entrySize = entry.getSize();
+ if (entrySize > Integer.MAX_VALUE || entrySize < 0) {
+ throw new IOException(
+ "invalid entry size (" + entrySize + ") in the compatibility file");
+ }
+ byte[] bytes = new byte[(int) entrySize];
+ Streams.readFully(zis, bytes);
+ list.add(new String(bytes, UTF_8));
+ }
+ if (list.isEmpty()) {
+ throw new IOException("no entries found in the compatibility file");
+ }
+ return (VintfObject.verify(list.toArray(new String[list.size()])) == 0);
+ }
+
+ /**
+ * Reads and verifies the compatibility entry in an OTA zip package. The compatibility entry is
+ * a zip file (inside the OTA package zip).
+ *
+ * @return {@code true} if the entry doesn't exist or verification passes.
+ */
+ private static boolean readAndVerifyPackageCompatibilityEntry(File packageFile)
+ throws IOException {
+ try (ZipFile zip = new ZipFile(packageFile)) {
+ ZipEntry entry = zip.getEntry("compatibility.zip");
+ if (entry == null) {
+ return true;
+ }
+ InputStream inputStream = zip.getInputStream(entry);
+ return verifyPackageCompatibility(inputStream);
+ }
+ }
+
+ /**
+ * Verifies the package compatibility info against the current system.
+ *
+ * @param compatibilityFile the {@link File} that contains the package compatibility info.
+ * @throws IOException if there were any errors reading the compatibility file.
+ * @return the compatibility verification result.
+ *
+ * {@hide}
+ */
+ @SystemApi
+ public static boolean verifyPackageCompatibility(File compatibilityFile) throws IOException {
+ try (InputStream inputStream = new FileInputStream(compatibilityFile)) {
+ return verifyPackageCompatibility(inputStream);
+ }
}
/**
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index 3942531..1c15004 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -1518,7 +1518,8 @@
* last to be deleted when additional disk space is needed.
* <p>
* This quota will change over time depending on how frequently the user
- * interacts with your app, and depending on how much disk space is used.
+ * interacts with your app, and depending on how much system-wide disk space
+ * is used.
* <p class="note">
* Note: if your app uses the {@code android:sharedUserId} manifest feature,
* then cached data for all packages in your shared UID is tracked together
diff --git a/core/java/android/preference/Preference.java b/core/java/android/preference/Preference.java
index d3adce7..7496cb2 100644
--- a/core/java/android/preference/Preference.java
+++ b/core/java/android/preference/Preference.java
@@ -135,6 +135,7 @@
private boolean mDependencyMet = true;
private boolean mParentDependencyMet = true;
private boolean mRecycleEnabled = true;
+ private boolean mHasSingleLineTitleAttr;
private boolean mSingleLineTitle = true;
private boolean mIconSpaceReserved;
@@ -303,6 +304,7 @@
case com.android.internal.R.styleable.Preference_singleLineTitle:
mSingleLineTitle = a.getBoolean(attr, mSingleLineTitle);
+ mHasSingleLineTitleAttr = true;
break;
case com.android.internal.R.styleable.Preference_iconSpaceReserved:
@@ -609,7 +611,9 @@
if (!TextUtils.isEmpty(title)) {
titleView.setText(title);
titleView.setVisibility(View.VISIBLE);
- titleView.setSingleLine(mSingleLineTitle);
+ if (mHasSingleLineTitleAttr) {
+ titleView.setSingleLine(mSingleLineTitle);
+ }
} else {
titleView.setVisibility(View.GONE);
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index c6ea958..22de27f 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -5082,6 +5082,10 @@
* (available on certain devices running Android 4.2 or higher), each user appears as a
* completely separate device, so the {@code ANDROID_ID} value is unique to each
* user.</p>
+ *
+ * <p class="note"><strong>Note:</strong> If the caller is an Instant App the id is scoped
+ * to the Instant App, it is generated when the Instant App is first installed and reset if
+ * the user clears the Instant App.
*/
public static final String ANDROID_ID = "android_id";
@@ -7077,6 +7081,7 @@
CLONE_TO_MANAGED_PROFILE.add(ACCESSIBILITY_ENABLED);
CLONE_TO_MANAGED_PROFILE.add(ALLOW_MOCK_LOCATION);
CLONE_TO_MANAGED_PROFILE.add(ALLOWED_GEOLOCATION_ORIGINS);
+ CLONE_TO_MANAGED_PROFILE.add(AUTOFILL_SERVICE);
CLONE_TO_MANAGED_PROFILE.add(DEFAULT_INPUT_METHOD);
CLONE_TO_MANAGED_PROFILE.add(ENABLED_ACCESSIBILITY_SERVICES);
CLONE_TO_MANAGED_PROFILE.add(ENABLED_INPUT_METHODS);
diff --git a/core/java/android/service/autofill/AutofillService.java b/core/java/android/service/autofill/AutofillService.java
index 5e49b8f..813c54f 100644
--- a/core/java/android/service/autofill/AutofillService.java
+++ b/core/java/android/service/autofill/AutofillService.java
@@ -247,7 +247,7 @@
* @param callback object used to notify the result of the request.
*/
public void onSaveRequest(@NonNull SaveRequest request, @NonNull SaveCallback callback) {
- List<FillContext> contexts = request.getFillContexts();
+ final List<FillContext> contexts = request.getFillContexts();
onSaveRequest(contexts.get(contexts.size() - 1).getStructure(),
request.getClientState(), callback);
}
@@ -285,4 +285,22 @@
// TODO(b/33197203): Remove when GCore has migrated off this API
getSystemService(AutofillManager.class).disableOwnedAutofillServices();
}
+
+ /**
+ * Returns the {@link FillEventHistory.Event events} since the last {@link FillResponse} was
+ * returned.
+ *
+ * <p>The history is not persisted over reboots.
+ *
+ * @return The history or {@code null} if there are not events.
+ */
+ @Nullable public final FillEventHistory getFillEventHistory() {
+ AutofillManager afm = getSystemService(AutofillManager.class);
+
+ if (afm == null) {
+ return null;
+ } else {
+ return afm.getFillEventHistory();
+ }
+ }
}
diff --git a/core/java/android/service/autofill/Dataset.java b/core/java/android/service/autofill/Dataset.java
index e77bd0d..e04fae7 100644
--- a/core/java/android/service/autofill/Dataset.java
+++ b/core/java/android/service/autofill/Dataset.java
@@ -24,6 +24,7 @@
import android.os.Parcel;
import android.os.Parcelable;
import android.view.autofill.AutofillId;
+import android.view.autofill.AutofillManager;
import android.view.autofill.AutofillValue;
import android.widget.RemoteViews;
import com.android.internal.util.Preconditions;
@@ -50,6 +51,7 @@
private final ArrayList<RemoteViews> mFieldPresentations;
private final RemoteViews mPresentation;
private final IntentSender mAuthentication;
+ @Nullable String mId;
private Dataset(Builder builder) {
mFieldIds = builder.mFieldIds;
@@ -57,6 +59,7 @@
mFieldPresentations = builder.mFieldPresentations;
mPresentation = builder.mPresentation;
mAuthentication = builder.mAuthentication;
+ mId = builder.mId;
}
/** @hide */
@@ -89,7 +92,7 @@
public String toString() {
if (!DEBUG) return super.toString();
- return new StringBuilder("Dataset [")
+ return new StringBuilder("Dataset " + mId + " [")
.append("fieldIds=").append(mFieldIds)
.append(", fieldValues=").append(mFieldValues)
.append(", fieldPresentations=")
@@ -100,6 +103,17 @@
}
/**
+ * Gets the id of this dataset.
+ *
+ * @return The id of this dataset or {@code null} if not set
+ *
+ * @hide
+ */
+ public String getId() {
+ return mId;
+ }
+
+ /**
* A builder for {@link Dataset} objects. You must to provide at least
* one value for a field or set an authentication intent.
*/
@@ -110,6 +124,7 @@
private RemoteViews mPresentation;
private IntentSender mAuthentication;
private boolean mDestroyed;
+ @Nullable private String mId;
/**
* Creates a new builder.
@@ -173,6 +188,25 @@
}
/**
+ * Sets the id for the dataset.
+ *
+ * <p>The id of the last selected dataset can be read from
+ * {@link AutofillService#getFillEventHistory()}. If the id is not set it will not be clear
+ * if a dataset was selected as {@link AutofillService#getFillEventHistory()} uses
+ * {@code null} to indicate that no dataset was selected.
+ *
+ * @param id id for this dataset or {@code null} to unset.
+
+ * @return This builder.
+ */
+ public @NonNull Builder setId(@Nullable String id) {
+ throwIfDestroyed();
+
+ mId = id;
+ return this;
+ }
+
+ /**
* Sets the value of a field.
*
* @param id id returned by {@link
@@ -269,6 +303,7 @@
parcel.writeTypedArrayList(mFieldValues, flags);
parcel.writeParcelableList(mFieldPresentations, flags);
parcel.writeParcelable(mAuthentication, flags);
+ parcel.writeString(mId);
}
public static final Creator<Dataset> CREATOR = new Creator<Dataset>() {
@@ -295,6 +330,7 @@
builder.setValueAndPresentation(id, value, fieldPresentation);
}
builder.setAuthentication(parcel.readParcelable(null));
+ builder.setId(parcel.readString());
return builder.build();
}
diff --git a/core/java/android/service/autofill/FillCallback.java b/core/java/android/service/autofill/FillCallback.java
index a009be8..7774bdd 100644
--- a/core/java/android/service/autofill/FillCallback.java
+++ b/core/java/android/service/autofill/FillCallback.java
@@ -18,7 +18,6 @@
import android.annotation.Nullable;
import android.app.Activity;
-import android.os.Bundle;
import android.os.RemoteException;
/**
@@ -38,8 +37,8 @@
/**
* Notifies the Android System that an
- * {@link AutofillService#onFillRequest(android.app.assist.AssistStructure, Bundle,
- * int, android.os.CancellationSignal, FillCallback)} was successfully fulfilled by the service.
+ * {@link AutofillService#onFillRequest(FillRequest, android.os.CancellationSignal,
+ * FillCallback)} was successfully fulfilled by the service.
*
* @param response autofill information for that activity, or {@code null} when the activity
* cannot be autofilled (for example, if it only contains read-only fields). See
@@ -57,9 +56,8 @@
/**
* Notifies the Android System that an
- * {@link AutofillService#onFillRequest(android.app.assist.AssistStructure,
- * Bundle, int, android.os.CancellationSignal, FillCallback)}
- * could not be fulfilled by the service.
+ * {@link AutofillService#onFillRequest(FillRequest, android.os.CancellationSignal,
+ * FillCallback)} could not be fulfilled by the service.
*
* @param message error message to be displayed to the user.
*/
diff --git a/core/java/android/service/autofill/FillEventHistory.aidl b/core/java/android/service/autofill/FillEventHistory.aidl
new file mode 100644
index 0000000..3c48524
--- /dev/null
+++ b/core/java/android/service/autofill/FillEventHistory.aidl
@@ -0,0 +1,19 @@
+/**
+ * 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.service.autofill;
+
+parcelable FillEventHistory;
diff --git a/core/java/android/service/autofill/FillEventHistory.java b/core/java/android/service/autofill/FillEventHistory.java
new file mode 100644
index 0000000..3d72fcc
--- /dev/null
+++ b/core/java/android/service/autofill/FillEventHistory.java
@@ -0,0 +1,208 @@
+/*
+ * 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.service.autofill;
+
+import android.annotation.IntDef;
+import android.annotation.Nullable;
+import android.content.IntentSender;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.view.autofill.AutofillId;
+import android.widget.RemoteViews;
+
+import com.android.internal.util.Preconditions;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Describes what happened after the latest call to {@link FillCallback#onSuccess(FillResponse)}.
+ */
+public final class FillEventHistory implements Parcelable {
+ /**
+ * Not in parcel. The UID of the {@link AutofillService} that created the {@link FillResponse}.
+ */
+ private final int mServiceUid;
+
+ @Nullable private final Bundle mClientState;
+ @Nullable List<Event> mEvents;
+
+ /**
+ * Gets the UID of the {@link AutofillService} that created the {@link FillResponse}.
+ *
+ * @return The UID of the {@link AutofillService}
+ *
+ * @hide
+ */
+ public int getServiceUid() {
+ return mServiceUid;
+ }
+
+ /**
+ * Returns the client state of the {@link FillResponse}.
+ *
+ * @return The client state set by the last {@link FillResponse}
+ */
+ @Nullable public Bundle getClientState() {
+ return mClientState;
+ }
+
+ /**
+ * Returns the events occurred after the latest call to
+ * {@link FillCallback#onSuccess(FillResponse)}.
+ *
+ * @return The list of events or {@code null} if non occurred.
+ */
+ @Nullable public List<Event> getEvents() {
+ return mEvents;
+ }
+
+ /**
+ * @hide
+ */
+ public void addEvent(Event event) {
+ if (mEvents == null) {
+ mEvents = new ArrayList<>(1);
+ }
+ mEvents.add(event);
+ }
+
+ /**
+ * @hide
+ */
+ public FillEventHistory(int serviceUid, @Nullable Bundle clientState) {
+ mClientState = clientState;
+ mServiceUid = serviceUid;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeBundle(mClientState);
+
+ if (mEvents == null) {
+ dest.writeInt(0);
+ } else {
+ dest.writeInt(mEvents.size());
+
+ int numEvents = mEvents.size();
+ for (int i = 0; i < numEvents; i++) {
+ Event event = mEvents.get(i);
+ dest.writeInt(event.getType());
+ dest.writeString(event.getDatasetId());
+ }
+ }
+ }
+
+ /**
+ * Description of an event that occured after the latest call to
+ * {@link FillCallback#onSuccess(FillResponse)}.
+ */
+ public static final class Event {
+ /**
+ * A dataset was selected. The dataset selected can be read from {@link #getDatasetId()}.
+ */
+ public static final int TYPE_DATASET_SELECTED = 0;
+
+ /**
+ * A {@link Dataset.Builder#setAuthentication(IntentSender) dataset authentication} was
+ * selected. The dataset authenticated can be read from {@link #getDatasetId()}.
+ */
+ public static final int TYPE_DATASET_AUTHENTICATION_SELECTED = 1;
+
+ /**
+ * A {@link FillResponse.Builder#setAuthentication(AutofillId[], IntentSender, RemoteViews)
+ * fill response authentication} was selected.
+ */
+ public static final int TYPE_AUTHENTICATION_SELECTED = 2;
+
+ /** A save UI was shown. */
+ public static final int TYPE_SAVE_SHOWN = 3;
+
+ /** @hide */
+ @IntDef(
+ value = {TYPE_DATASET_SELECTED,
+ TYPE_DATASET_AUTHENTICATION_SELECTED,
+ TYPE_AUTHENTICATION_SELECTED,
+ TYPE_SAVE_SHOWN})
+ @Retention(RetentionPolicy.SOURCE)
+ @interface EventIds{}
+
+ @EventIds private final int mEventType;
+ @Nullable private final String mDatasetId;
+
+ /**
+ * Returns the type of the event.
+ *
+ * @return The type of the event
+ */
+ public int getType() {
+ return mEventType;
+ }
+
+ /**
+ * Returns the id of dataset the id was on.
+ *
+ * @return The id of dataset, or {@code null} the event is not associated with a dataset.
+ */
+ @Nullable public String getDatasetId() {
+ return mDatasetId;
+ }
+
+ /**
+ * Creates a new event.
+ *
+ * @param eventType The type of the event
+ * @param datasetId The dataset the event was on, or {@code null} if the event was on the
+ * whole response.
+ *
+ * @hide
+ */
+ public Event(int eventType, String datasetId) {
+ mEventType = Preconditions.checkArgumentInRange(eventType, 0, TYPE_SAVE_SHOWN,
+ "eventType");
+ mDatasetId = datasetId;
+ }
+ }
+
+ public static final Parcelable.Creator<FillEventHistory> CREATOR =
+ new Parcelable.Creator<FillEventHistory>() {
+ @Override
+ public FillEventHistory createFromParcel(Parcel parcel) {
+ FillEventHistory selection = new FillEventHistory(0, parcel.readBundle());
+
+ int numEvents = parcel.readInt();
+ for (int i = 0; i < numEvents; i++) {
+ selection.addEvent(new Event(parcel.readInt(), parcel.readString()));
+ }
+
+ return selection;
+ }
+
+ @Override
+ public FillEventHistory[] newArray(int size) {
+ return new FillEventHistory[size];
+ }
+ };
+}
diff --git a/core/java/android/service/autofill/FillResponse.java b/core/java/android/service/autofill/FillResponse.java
index 8c8060a..0025365 100644
--- a/core/java/android/service/autofill/FillResponse.java
+++ b/core/java/android/service/autofill/FillResponse.java
@@ -32,8 +32,7 @@
/**
* Response for a {@link
- * AutofillService#onFillRequest(android.app.assist.AssistStructure,
- * Bundle, int, android.os.CancellationSignal, FillCallback)}.
+ * AutofillService#onFillRequest(FillRequest, android.os.CancellationSignal, FillCallback)}.
*
* <p>The response typically contains one or more {@link Dataset}s, each representing a set of
* fields that can be autofilled together, and the Android system displays a dataset picker UI
@@ -258,6 +257,19 @@
}
/**
+ * Specifies views that should not trigger new
+ * {@link AutofillService#onFillRequest(FillRequest, android.os.CancellationSignal,
+ * FillCallback)} requests.
+ *
+ * <p>This is typically used when the service cannot autofill the view; for example, an
+ * {@code EditText} representing a captcha.
+ */
+ public Builder setIgnoredIds(AutofillId...ids) {
+ // TODO: implement
+ return this;
+ }
+
+ /**
* Adds a new {@link Dataset} to this response.
*
* @return This builder.
@@ -289,6 +301,9 @@
return this;
}
+ /**
+ * @deprecated Use {@link #setClientState(Bundle)} instead.
+ */
@Deprecated
public Builder setExtras(@Nullable Bundle extras) {
throwIfDestroyed();
@@ -299,11 +314,9 @@
/**
* Sets a {@link Bundle state} that will be passed to subsequent APIs that
* manipulate this response. For example, they are passed to subsequent
- * calls to {@link AutofillService#onFillRequest(
- * android.app.assist.AssistStructure, Bundle, int,
- * android.os.CancellationSignal, FillCallback)} and {@link AutofillService#onSaveRequest(
- * android.app.assist.AssistStructure, Bundle, SaveCallback)}. You can use
- * this to store intermediate state that is persistent across multiple
+ * calls to {@link AutofillService#onFillRequest(FillRequest, android.os.CancellationSignal,
+ * FillCallback)} and {@link AutofillService#onSaveRequest(SaveRequest, SaveCallback)}.
+ * You can use this to store intermediate state that is persistent across multiple
* fill requests and the subsequent save request.
*
* <p>If this method is called on multiple {@link FillResponse} objects for the same
diff --git a/core/java/android/service/autofill/SaveCallback.java b/core/java/android/service/autofill/SaveCallback.java
index 2c4ba6c..3a70138 100644
--- a/core/java/android/service/autofill/SaveCallback.java
+++ b/core/java/android/service/autofill/SaveCallback.java
@@ -17,7 +17,6 @@
package android.service.autofill;
import android.app.Activity;
-import android.os.Bundle;
import android.os.RemoteException;
/**
@@ -35,8 +34,8 @@
/**
* Notifies the Android System that an
- * {@link AutofillService#onSaveRequest (android.app.assist.AssistStructure, Bundle,
- * SaveCallback)} was successfully fulfilled by the service.
+ * {@link AutofillService#onSaveRequest(SaveRequest, SaveCallback)} was successfully fulfilled
+ * by the service.
*
* @throws RuntimeException if an error occurred while calling the Android System.
*/
@@ -52,8 +51,8 @@
/**
* Notifies the Android System that an
- * {@link AutofillService#onSaveRequest(android.app.assist.AssistStructure, Bundle,
- * SaveCallback)} could not be fulfilled by the service.
+ * {@link AutofillService#onSaveRequest(SaveRequest, SaveCallback)} could not be fulfilled
+ * by the service.
*
* @param message error message to be displayed to the user.
*
diff --git a/core/java/android/service/autofill/SaveInfo.java b/core/java/android/service/autofill/SaveInfo.java
index 7f960df..f796444 100644
--- a/core/java/android/service/autofill/SaveInfo.java
+++ b/core/java/android/service/autofill/SaveInfo.java
@@ -40,7 +40,7 @@
/**
* Information used to indicate that an {@link AutofillService} is interested on saving the
* user-inputed data for future use, through a
- * {@link AutofillService#onSaveRequest(android.app.assist.AssistStructure, Bundle, SaveCallback)}
+ * {@link AutofillService#onSaveRequest(SaveRequest, SaveCallback)}
* call.
*
* <p>A {@link SaveInfo} is always associated with a {@link FillResponse}, and it contains at least
@@ -94,7 +94,7 @@
* </pre>
*
* The
- * {@link AutofillService#onSaveRequest(android.app.assist.AssistStructure, Bundle, SaveCallback)}
+ * {@link AutofillService#onSaveRequest(SaveRequest, SaveCallback)}
* is triggered after a call to {@link AutofillManager#commit()}, but only when all conditions
* below are met:
*
@@ -153,13 +153,27 @@
@Retention(RetentionPolicy.SOURCE)
@interface SaveDataType{}
+ /**
+ * Usually {@link AutofillService#onSaveRequest(AssistStructure, Bundle, SaveCallback)}
+ * is called once the activity finishes. If this flag is set it is called once all saved views
+ * become invisible.
+ */
+ public static final int FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE = 0x1;
+
+ /** @hide */
+ @IntDef(
+ flag = true,
+ value = {FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE})
+ @Retention(RetentionPolicy.SOURCE)
+ @interface SaveInfoFlags{}
+
private final @SaveDataType int mType;
private final CharSequence mNegativeActionTitle;
private final IntentSender mNegativeActionListener;
private final AutofillId[] mRequiredIds;
private final AutofillId[] mOptionalIds;
private final CharSequence mDescription;
- private final boolean mSaveOnAllViewsInvisible;
+ private final int mFlags;
private SaveInfo(Builder builder) {
mType = builder.mType;
@@ -168,7 +182,7 @@
mRequiredIds = builder.mRequiredIds;
mOptionalIds = builder.mOptionalIds;
mDescription = builder.mDescription;
- mSaveOnAllViewsInvisible = builder.mSaveOnAllViewsInvisible;
+ mFlags = builder.mFlags;
}
/** @hide */
@@ -197,8 +211,8 @@
}
/** @hide */
- public boolean saveOnAllViewsInvisible() {
- return mSaveOnAllViewsInvisible;
+ public @SaveInfoFlags int getFlags() {
+ return mFlags;
}
/** @hide */
@@ -219,7 +233,7 @@
private AutofillId[] mOptionalIds;
private CharSequence mDescription;
private boolean mDestroyed;
- private boolean mSaveOnAllViewsInvisible;
+ private int mFlags;
/**
* Creates a new builder.
@@ -268,17 +282,15 @@
}
/**
- * Usually {@link AutofillService#onSaveRequest(AssistStructure, Bundle, SaveCallback)}
- * is called once the activity finishes. If this property is set it is called once all
- * autofillable or saved views become invisible.
+ * Set flags changing the save behavior.
*
- * @param saveOnAllViewsInvisible Set to {@code true} if the data should be saved once
- * all the views become invisible.
+ * @param flags {@link #FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE} or 0.
* @return This builder.
*/
- public @NonNull Builder setSaveOnAllViewsInvisible(boolean saveOnAllViewsInvisible) {
+ public @NonNull Builder setFlags(@SaveInfoFlags int flags) {
throwIfDestroyed();
- mSaveOnAllViewsInvisible = saveOnAllViewsInvisible;
+
+ mFlags = Preconditions.checkFlagsArgument(flags, FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE);
return this;
}
@@ -378,7 +390,7 @@
.append(", requiredIds=").append(Arrays.toString(mRequiredIds))
.append(", optionalIds=").append(Arrays.toString(mOptionalIds))
.append(", description=").append(mDescription)
- .append(", saveOnNoVisibleTrackedViews=").append(mSaveOnAllViewsInvisible)
+ .append(", mFlags=").append(mFlags)
.append("]").toString();
}
@@ -399,7 +411,7 @@
parcel.writeParcelable(mNegativeActionListener, flags);
parcel.writeParcelableArray(mOptionalIds, flags);
parcel.writeCharSequence(mDescription);
- parcel.writeBoolean(mSaveOnAllViewsInvisible);
+ parcel.writeInt(mFlags);
}
public static final Parcelable.Creator<SaveInfo> CREATOR = new Parcelable.Creator<SaveInfo>() {
@@ -413,7 +425,7 @@
builder.setNegativeAction(parcel.readCharSequence(), parcel.readParcelable(null));
builder.setOptionalIds(parcel.readParcelableArray(null, AutofillId.class));
builder.setDescription(parcel.readCharSequence());
- builder.setSaveOnAllViewsInvisible(parcel.readBoolean());
+ builder.setFlags(parcel.readInt());
return builder.build();
}
diff --git a/core/java/android/util/IconDrawableFactory.java b/core/java/android/util/IconDrawableFactory.java
new file mode 100644
index 0000000..b07942f
--- /dev/null
+++ b/core/java/android/util/IconDrawableFactory.java
@@ -0,0 +1,112 @@
+/*
+ * 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.util;
+
+import android.annotation.UserIdInt;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageItemInfo;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.graphics.Color;
+import android.graphics.drawable.Drawable;
+import android.os.UserHandle;
+import android.os.UserManager;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * Utility class to load app drawables with appropriate badging.
+ *
+ * @hide
+ */
+public class IconDrawableFactory {
+
+ protected final Context mContext;
+ protected final PackageManager mPm;
+ protected final UserManager mUm;
+ protected final LauncherIcons mLauncherIcons;
+ protected final boolean mEmbedShadow;
+
+ private IconDrawableFactory(Context context, boolean embedShadow) {
+ mContext = context;
+ mPm = context.getPackageManager();
+ mUm = context.getSystemService(UserManager.class);
+ mLauncherIcons = new LauncherIcons(context);
+ mEmbedShadow = embedShadow;
+ }
+
+ protected boolean needsBadging(ApplicationInfo appInfo, @UserIdInt int userId) {
+ return appInfo.isInstantApp() || mUm.isManagedProfile(userId);
+ }
+
+ public Drawable getBadgedIcon(ApplicationInfo appInfo) {
+ return getBadgedIcon(appInfo, UserHandle.getUserId(appInfo.uid));
+ }
+
+ public Drawable getBadgedIcon(ApplicationInfo appInfo, @UserIdInt int userId) {
+ return getBadgedIcon(appInfo, appInfo, userId);
+ }
+
+ public Drawable getBadgedIcon(PackageItemInfo itemInfo, ApplicationInfo appInfo,
+ @UserIdInt int userId) {
+ Drawable icon = mPm.loadUnbadgedItemIcon(itemInfo, appInfo);
+ if (!mEmbedShadow && !needsBadging(appInfo, userId)) {
+ return icon;
+ }
+
+ // Before badging, add shadow to adaptive icon if needed.
+ icon = mLauncherIcons.wrapIconDrawableWithShadow(icon);
+ if (appInfo.isInstantApp()) {
+ int badgeColor = Resources.getSystem().getColor(
+ com.android.internal.R.color.instant_app_badge, null);
+ icon = mLauncherIcons.getBadgedDrawable(icon,
+ com.android.internal.R.drawable.ic_instant_icon_badge_bolt,
+ badgeColor);
+ }
+ if (mUm.isManagedProfile(userId)) {
+ icon = mLauncherIcons.getBadgedDrawable(icon,
+ com.android.internal.R.drawable.ic_corp_icon_badge_case,
+ getUserBadgeColor(mUm, userId));
+ }
+ return icon;
+ }
+
+ // Should have enough colors to cope with UserManagerService.getMaxManagedProfiles()
+ @VisibleForTesting
+ public static final int[] CORP_BADGE_COLORS = new int[] {
+ com.android.internal.R.color.profile_badge_1,
+ com.android.internal.R.color.profile_badge_2,
+ com.android.internal.R.color.profile_badge_3
+ };
+
+ public static int getUserBadgeColor(UserManager um, @UserIdInt int userId) {
+ int badge = um.getManagedProfileBadge(userId);
+ if (badge < 0) {
+ badge = 0;
+ }
+ int resourceId = CORP_BADGE_COLORS[badge % CORP_BADGE_COLORS.length];
+ return Resources.getSystem().getColor(resourceId, null);
+ }
+
+ public static IconDrawableFactory newInstance(Context context) {
+ return new IconDrawableFactory(context, true);
+ }
+
+ public static IconDrawableFactory newInstance(Context context, boolean embedShadow) {
+ return new IconDrawableFactory(context, embedShadow);
+ }
+}
diff --git a/core/java/android/util/LauncherIcons.java b/core/java/android/util/LauncherIcons.java
index 89b9646..402bef9 100644
--- a/core/java/android/util/LauncherIcons.java
+++ b/core/java/android/util/LauncherIcons.java
@@ -21,10 +21,11 @@
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
-import android.graphics.PaintFlagsDrawFilter;
+import android.graphics.Rect;
import android.graphics.drawable.AdaptiveIconDrawable;
-import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
+import android.graphics.drawable.DrawableWrapper;
+import android.graphics.drawable.LayerDrawable;
/**
* Utility class to handle icon treatments (e.g., shadow generation) for the Launcher icons.
@@ -32,78 +33,152 @@
*/
public final class LauncherIcons {
- private final Paint mPaint = new Paint();
- private final Canvas mCanvas = new Canvas();
+ // Percent of actual icon size
+ private static final float ICON_SIZE_BLUR_FACTOR = 0.5f/48;
+ // Percent of actual icon size
+ private static final float ICON_SIZE_KEY_SHADOW_DELTA_FACTOR = 1f/48;
private static final int KEY_SHADOW_ALPHA = 61;
private static final int AMBIENT_SHADOW_ALPHA = 30;
- private static final float BLUR_FACTOR = 0.5f / 48;
- private int mShadowInset;
- private Bitmap mShadowBitmap;
- private int mIconSize;
- private Resources mRes;
+
+ private final SparseArray<Bitmap> mShadowCache = new SparseArray<>();
+ private final int mIconSize;
+ private final Resources mRes;
public LauncherIcons(Context context) {
mRes = context.getResources();
- DisplayMetrics metrics = mRes.getDisplayMetrics();
- mShadowInset = (int)(2 * metrics.density);
- mCanvas.setDrawFilter(new PaintFlagsDrawFilter(Paint.DITHER_FLAG,
- Paint.FILTER_BITMAP_FLAG));
- mIconSize = (int) mRes.getDimensionPixelSize(android.R.dimen.app_icon_size);
- }
-
- /**
- * Draw the drawable into a bitmap.
- */
- public Bitmap createIconBitmap(Drawable icon) {
- final Bitmap bitmap = Bitmap.createBitmap(mIconSize, mIconSize, Bitmap.Config.ARGB_8888);
- mPaint.setAlpha(255);
- mCanvas.setBitmap(bitmap);
- int iconInset = 0;
- if (mShadowBitmap != null) {
- mCanvas.drawBitmap(mShadowBitmap, 0, 0, mPaint);
- iconInset = mShadowInset;
- }
-
- icon.setBounds(iconInset, iconInset, mIconSize - iconInset,
- mIconSize - iconInset);
- icon.draw(mCanvas);
- mCanvas.setBitmap(null);
- return bitmap;
+ mIconSize = mRes.getDimensionPixelSize(android.R.dimen.app_icon_size);
}
public Drawable wrapIconDrawableWithShadow(Drawable drawable) {
if (!(drawable instanceof AdaptiveIconDrawable)) {
return drawable;
}
- AdaptiveIconDrawable d =
- (AdaptiveIconDrawable) drawable.getConstantState().newDrawable().mutate();
- getShadowBitmap(d);
- Bitmap iconbitmap = createIconBitmap(d);
- return new BitmapDrawable(mRes, iconbitmap);
+ Bitmap shadow = getShadowBitmap((AdaptiveIconDrawable) drawable);
+ return new ShadowDrawable(shadow, drawable);
}
private Bitmap getShadowBitmap(AdaptiveIconDrawable d) {
- if (mShadowBitmap != null) {
- return mShadowBitmap;
+ int shadowSize = Math.max(mIconSize, d.getIntrinsicHeight());
+ synchronized (mShadowCache) {
+ Bitmap shadow = mShadowCache.get(shadowSize);
+ if (shadow != null) {
+ return shadow;
+ }
}
- mShadowBitmap = Bitmap.createBitmap(mIconSize, mIconSize, Bitmap.Config.ALPHA_8);
- mCanvas.setBitmap(mShadowBitmap);
+ d.setBounds(0, 0, shadowSize, shadowSize);
- // Draw key shadow
- mPaint.setColor(Color.TRANSPARENT);
- float blur = BLUR_FACTOR * mIconSize;
- mPaint.setShadowLayer(blur, 0, mShadowInset, KEY_SHADOW_ALPHA << 24);
- d.setBounds(mShadowInset, mShadowInset, mIconSize - mShadowInset, mIconSize - mShadowInset);
- mCanvas.drawPath(d.getIconMask(), mPaint);
+ float blur = ICON_SIZE_BLUR_FACTOR * shadowSize;
+ float keyShadowDistance = ICON_SIZE_KEY_SHADOW_DELTA_FACTOR * shadowSize;
+
+ int bitmapSize = (int) (shadowSize + 2 * blur + keyShadowDistance);
+ Bitmap shadow = Bitmap.createBitmap(bitmapSize, bitmapSize, Bitmap.Config.ARGB_8888);
+
+ Canvas canvas = new Canvas(shadow);
+ canvas.translate(blur + keyShadowDistance / 2, blur);
+
+ Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ paint.setColor(Color.TRANSPARENT);
// Draw ambient shadow
- mPaint.setShadowLayer(blur, 0, 0, AMBIENT_SHADOW_ALPHA << 24);
- d.setBounds(mShadowInset, 2 * mShadowInset, mIconSize - mShadowInset, mIconSize);
- mCanvas.drawPath(d.getIconMask(), mPaint);
- mPaint.clearShadowLayer();
+ paint.setShadowLayer(blur, 0, 0, AMBIENT_SHADOW_ALPHA << 24);
+ canvas.drawPath(d.getIconMask(), paint);
- return mShadowBitmap;
+ // Draw key shadow
+ canvas.translate(0, keyShadowDistance);
+ paint.setShadowLayer(blur, 0, 0, KEY_SHADOW_ALPHA << 24);
+ canvas.drawPath(d.getIconMask(), paint);
+
+ canvas.setBitmap(null);
+ synchronized (mShadowCache) {
+ mShadowCache.put(shadowSize, shadow);
+ }
+ return shadow;
+ }
+
+ public Drawable getBadgeDrawable(int foregroundRes, int backgroundColor) {
+ return getBadgedDrawable(null, foregroundRes, backgroundColor);
+ }
+
+ public Drawable getBadgedDrawable(Drawable base, int foregroundRes, int backgroundColor) {
+ Resources sysRes = Resources.getSystem();
+
+ Drawable badgeShadow = sysRes.getDrawable(
+ com.android.internal.R.drawable.ic_corp_icon_badge_shadow);
+
+ Drawable badgeColor = sysRes.getDrawable(
+ com.android.internal.R.drawable.ic_corp_icon_badge_color)
+ .getConstantState().newDrawable().mutate();
+ badgeColor.setTint(backgroundColor);
+
+ Drawable badgeForeground = sysRes.getDrawable(foregroundRes);
+
+ Drawable[] drawables = base == null
+ ? new Drawable[] {badgeShadow, badgeColor, badgeForeground }
+ : new Drawable[] {base, badgeShadow, badgeColor, badgeForeground };
+ return new LayerDrawable(drawables);
+ }
+
+ /**
+ * A drawable which draws a shadow bitmap behind a drawable
+ */
+ private static class ShadowDrawable extends DrawableWrapper {
+
+ final MyConstantState mState;
+
+ public ShadowDrawable(Bitmap shadow, Drawable dr) {
+ super(dr);
+ mState = new MyConstantState(shadow, dr.getConstantState());
+ }
+
+ ShadowDrawable(MyConstantState state) {
+ super(state.mChildState.newDrawable());
+ mState = state;
+ }
+
+ @Override
+ public ConstantState getConstantState() {
+ return mState;
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ Rect bounds = getBounds();
+ canvas.drawBitmap(mState.mShadow, null, bounds, mState.mPaint);
+ canvas.save();
+ // Ratio of child drawable size to shadow bitmap size
+ float factor = 1 / (1 + 2 * ICON_SIZE_BLUR_FACTOR + ICON_SIZE_KEY_SHADOW_DELTA_FACTOR);
+
+ canvas.translate(
+ bounds.width() * factor *
+ (ICON_SIZE_BLUR_FACTOR + ICON_SIZE_KEY_SHADOW_DELTA_FACTOR / 2),
+ bounds.height() * factor * ICON_SIZE_BLUR_FACTOR);
+ canvas.scale(factor, factor);
+ super.draw(canvas);
+ canvas.restore();
+ }
+
+ private static class MyConstantState extends ConstantState {
+
+ final Paint mPaint = new Paint(Paint.FILTER_BITMAP_FLAG);
+ final Bitmap mShadow;
+ final ConstantState mChildState;
+
+ MyConstantState(Bitmap shadow, ConstantState childState) {
+ mShadow = shadow;
+ mChildState = childState;
+ }
+
+ @Override
+ public Drawable newDrawable() {
+ return new ShadowDrawable(this);
+ }
+
+ @Override
+ public int getChangingConfigurations() {
+ return mChildState.getChangingConfigurations();
+ }
+ }
}
}
diff --git a/core/java/android/view/FocusFinder.java b/core/java/android/view/FocusFinder.java
index f47c355..1ccf16a 100644
--- a/core/java/android/view/FocusFinder.java
+++ b/core/java/android/view/FocusFinder.java
@@ -127,20 +127,23 @@
if (focused == null || focused == root) {
return root;
}
- ViewParent effective = focused.getParent();
+ ViewGroup effective = null;
+ ViewParent nextParent = focused.getParent();
do {
- if (effective == root) {
- return root;
+ if (nextParent == root) {
+ return effective != null ? effective : root;
}
- ViewGroup vg = (ViewGroup) effective;
+ ViewGroup vg = (ViewGroup) nextParent;
if (vg.getTouchscreenBlocksFocus()
&& focused.getContext().getPackageManager().hasSystemFeature(
PackageManager.FEATURE_TOUCHSCREEN)
&& vg.isKeyboardNavigationCluster()) {
- return vg;
+ // Don't stop and return here because the cluster could be nested and we only
+ // care about the top-most one.
+ effective = vg;
}
- effective = effective.getParent();
- } while (effective != null);
+ nextParent = nextParent.getParent();
+ } while (nextParent instanceof ViewGroup);
return root;
}
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 1a71679..9d40895 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -295,7 +295,15 @@
@Override
protected void onDetachedFromWindow() {
- getViewRootImpl().removeWindowStoppedCallback(this);
+ ViewRootImpl viewRoot = getViewRootImpl();
+ // It's possible to create a SurfaceView using the default constructor and never
+ // attach it to a view hierarchy, this is a common use case when dealing with
+ // OpenGL. A developer will probably create a new GLSurfaceView, and let it manage
+ // the lifecycle. Instead of attaching it to a view, he/she can just pass
+ // the SurfaceHolder forward, most live wallpapers do it.
+ if (viewRoot != null) {
+ viewRoot.removeWindowStoppedCallback(this);
+ }
mAttachedToWindow = false;
if (mGlobalListenersAdded) {
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 7d2d77e..4f0aa32 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -1135,27 +1135,39 @@
@IntDef({
IMPORTANT_FOR_AUTOFILL_AUTO,
IMPORTANT_FOR_AUTOFILL_YES,
- IMPORTANT_FOR_AUTOFILL_NO
+ IMPORTANT_FOR_AUTOFILL_NO,
+ IMPORTANT_FOR_AUTOFILL_YES_EXCLUDE_DESCENDANTS,
+ IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS
})
@Retention(RetentionPolicy.SOURCE)
public @interface AutofillImportance {}
/**
- * Automatically determine whether a view is important for auto-fill.
+ * Automatically determine whether a view is important for autofill.
*/
public static final int IMPORTANT_FOR_AUTOFILL_AUTO = 0x0;
/**
- * The view is important for important for auto-fill.
+ * The view is important for autofill, and its children (if any) will be traversed.
*/
public static final int IMPORTANT_FOR_AUTOFILL_YES = 0x1;
/**
- * The view is not important for auto-fill.
+ * The view is not important for autofill, and its children (if any) will be traversed.
*/
public static final int IMPORTANT_FOR_AUTOFILL_NO = 0x2;
/**
+ * The view is important for autofill, but its children (if any) will not be traversed.
+ */
+ public static final int IMPORTANT_FOR_AUTOFILL_YES_EXCLUDE_DESCENDANTS = 0x4;
+
+ /**
+ * The view is not important for autofill, and its children (if any) will not be traversed.
+ */
+ public static final int IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS = 0x8;
+
+ /**
* This view is enabled. Interpretation varies by subclass.
* Use with ENABLED_MASK when calling setFlags.
* {@hide}
@@ -7568,7 +7580,7 @@
* should use {@link #setImportantForAutofill(int)} instead.
*
* <p><strong>Note:</strong> returning {@code false} does not guarantee the view will be
- * excluded from the structure; for example, if the user explicitly requested auto-fill, the
+ * excluded from the structure; for example, if the user explicitly requested autofill, the
* View might be always included.
*
* <p>This decision applies just for the view, not its children - if the view children are not
@@ -9720,7 +9732,7 @@
@ViewDebug.IntToString(from = NOT_FOCUSABLE, to = "NOT_FOCUSABLE"),
@ViewDebug.IntToString(from = FOCUSABLE, to = "FOCUSABLE"),
@ViewDebug.IntToString(from = FOCUSABLE_AUTO, to = "FOCUSABLE_AUTO")
- })
+ }, category = "focus")
@Focusable
public int getFocusable() {
return (mViewFlags & FOCUSABLE_AUTO) > 0 ? FOCUSABLE_AUTO : mViewFlags & FOCUSABLE;
@@ -9734,7 +9746,7 @@
* @return Whether the view is focusable in touch mode.
* @attr ref android.R.styleable#View_focusableInTouchMode
*/
- @ViewDebug.ExportedProperty
+ @ViewDebug.ExportedProperty(category = "focus")
public final boolean isFocusableInTouchMode() {
return FOCUSABLE_IN_TOUCH_MODE == (mViewFlags & FOCUSABLE_IN_TOUCH_MODE);
}
@@ -9762,12 +9774,31 @@
* @return True if this view is a root of a cluster, or false otherwise.
* @attr ref android.R.styleable#View_keyboardNavigationCluster
*/
- @ViewDebug.ExportedProperty(category = "keyboardNavigationCluster")
+ @ViewDebug.ExportedProperty(category = "focus")
public final boolean isKeyboardNavigationCluster() {
return (mPrivateFlags3 & PFLAG3_CLUSTER) != 0;
}
/**
+ * Searches up the view hierarchy to find the top-most cluster. All deeper/nested clusters
+ * will be ignored.
+ *
+ * @return the keyboard navigation cluster that this view is in (can be this view)
+ * or {@code null} if not in one
+ */
+ View findKeyboardNavigationCluster() {
+ if (mParent instanceof View) {
+ View cluster = ((View) mParent).findKeyboardNavigationCluster();
+ if (cluster != null) {
+ return cluster;
+ } else if (isKeyboardNavigationCluster()) {
+ return this;
+ }
+ }
+ return null;
+ }
+
+ /**
* Set whether this view is a root of a keyboard navigation cluster.
*
* @param isCluster If true, this view is a root of a cluster.
@@ -9789,9 +9820,20 @@
*
* @hide
*/
- public void setFocusedInCluster() {
- if (mParent instanceof ViewGroup) {
- ((ViewGroup) mParent).setFocusInCluster(this);
+ public final void setFocusedInCluster() {
+ View top = findKeyboardNavigationCluster();
+ if (top == this) {
+ return;
+ }
+ ViewParent parent = mParent;
+ View child = this;
+ while (parent instanceof ViewGroup) {
+ ((ViewGroup) parent).setFocusedInCluster(child);
+ if (parent == top) {
+ return;
+ }
+ child = (View) parent;
+ parent = parent.getParent();
}
}
@@ -9807,7 +9849,7 @@
* @return {@code true} if this view is the default-focus view, {@code false} otherwise
* @attr ref android.R.styleable#View_focusedByDefault
*/
- @ViewDebug.ExportedProperty(category = "focusedByDefault")
+ @ViewDebug.ExportedProperty(category = "focus")
public final boolean isFocusedByDefault() {
return (mPrivateFlags3 & PFLAG3_FOCUSED_BY_DEFAULT) != 0;
}
@@ -9920,7 +9962,7 @@
* @return True if this View should use a default focus highlight.
* @attr ref android.R.styleable#View_defaultFocusHighlightEnabled
*/
- @ViewDebug.ExportedProperty(category = "defaultFocusHighlightEnabled")
+ @ViewDebug.ExportedProperty(category = "focus")
public final boolean getDefaultFocusHighlightEnabled() {
return mDefaultFocusHighlightEnabled;
}
@@ -25743,6 +25785,7 @@
// focus
stream.addProperty("focus:hasFocus", hasFocus());
stream.addProperty("focus:isFocused", isFocused());
+ stream.addProperty("focus:focusable", getFocusable());
stream.addProperty("focus:isFocusable", isFocusable());
stream.addProperty("focus:isFocusableInTouchMode", isFocusableInTouchMode());
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index f9eb25d..1977ef5 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -807,33 +807,27 @@
return mDefaultFocus != null || super.hasDefaultFocus();
}
- void setFocusInCluster(View child) {
- // Stop at the root of the cluster
- if (child.isKeyboardNavigationCluster()) {
- return;
- }
-
+ void setFocusedInCluster(View child) {
mFocusedInCluster = child;
-
- if (mParent instanceof ViewGroup) {
- ((ViewGroup) mParent).setFocusInCluster(this);
- }
}
- void clearFocusInCluster(View child) {
+ /**
+ * Removes {@code child} (and associated focusedInCluster chain) from the cluster containing
+ * it.
+ * <br>
+ * This is intended to be run on {@code child}'s immediate parent. This is necessary because
+ * the chain is sometimes cleared after {@code child} has been detached.
+ */
+ void clearFocusedInCluster(View child) {
if (mFocusedInCluster != child) {
return;
}
-
- if (child.isKeyboardNavigationCluster()) {
- return;
- }
-
- mFocusedInCluster = null;
-
- if (mParent instanceof ViewGroup) {
- ((ViewGroup) mParent).clearFocusInCluster(this);
- }
+ View top = findKeyboardNavigationCluster();
+ ViewParent parent = this;
+ do {
+ ((ViewGroup) parent).mFocusedInCluster = null;
+ parent = parent.getParent();
+ } while (parent != top && parent instanceof ViewGroup);
}
@Override
@@ -1281,7 +1275,7 @@
public void setTouchscreenBlocksFocus(boolean touchscreenBlocksFocus) {
if (touchscreenBlocksFocus) {
mGroupFlags |= FLAG_TOUCHSCREEN_BLOCKS_FOCUS;
- if (hasFocus()) {
+ if (hasFocus() && !isKeyboardNavigationCluster()) {
final View focusedChild = getDeepestFocusedChild();
if (!focusedChild.isFocusableInTouchMode()) {
final View newFocus = focusSearch(FOCUS_FORWARD);
@@ -1306,6 +1300,7 @@
/**
* Check whether this ViewGroup should ignore focus requests for itself and its children.
*/
+ @ViewDebug.ExportedProperty(category = "focus")
public boolean getTouchscreenBlocksFocus() {
return (mGroupFlags & FLAG_TOUCHSCREEN_BLOCKS_FOCUS) != 0;
}
@@ -1316,7 +1311,8 @@
// cluster, focus is free to move around within it.
return getTouchscreenBlocksFocus() &&
mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN)
- && (!hasFocus() || !isKeyboardNavigationCluster());
+ && !(isKeyboardNavigationCluster()
+ && (hasFocus() || (findKeyboardNavigationCluster() != this)));
}
@Override
@@ -3217,8 +3213,7 @@
}
private boolean restoreFocusInClusterInternal(@FocusRealDirection int direction) {
- if (mFocusedInCluster != null && !mFocusedInCluster.isKeyboardNavigationCluster()
- && getDescendantFocusability() != FOCUS_BLOCK_DESCENDANTS
+ if (mFocusedInCluster != null && getDescendantFocusability() != FOCUS_BLOCK_DESCENDANTS
&& (mFocusedInCluster.mViewFlags & VISIBILITY_MASK) == VISIBLE
&& mFocusedInCluster.restoreFocusInCluster(direction)) {
return true;
@@ -5182,7 +5177,7 @@
clearChildFocus = true;
}
if (view == mFocusedInCluster) {
- clearFocusInCluster(view);
+ clearFocusedInCluster(view);
}
view.clearAccessibilityFocus();
@@ -5302,7 +5297,7 @@
clearDefaultFocus = view;
}
if (view == mFocusedInCluster) {
- clearFocusInCluster(view);
+ clearFocusedInCluster(view);
}
view.clearAccessibilityFocus();
@@ -5458,7 +5453,7 @@
clearDefaultFocus(child);
}
if (child == mFocusedInCluster) {
- clearFocusInCluster(child);
+ clearFocusedInCluster(child);
}
child.clearAccessibilityFocus();
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index a7ececf..9ecced6 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -1561,6 +1561,16 @@
host.dispatchApplyWindowInsets(getWindowInsets(true /* forceConstruct */));
}
+ /**
+ * @return the last content insets for use in adjusting the source hint rect for the
+ * picture-in-picture transition.
+ *
+ * @hide
+ */
+ public Rect getLastContentInsets() {
+ return mAttachInfo.mContentInsets;
+ }
+
private static boolean shouldUseDisplaySize(final WindowManager.LayoutParams lp) {
return lp.type == TYPE_STATUS_BAR_PANEL
|| lp.type == TYPE_INPUT_METHOD
@@ -4665,7 +4675,8 @@
if (focused == null && mView.restoreDefaultFocus()) {
return true;
}
- View cluster = focused.keyboardNavigationClusterSearch(null, direction);
+ View cluster = focused == null ? keyboardNavigationClusterSearch(null, direction)
+ : focused.keyboardNavigationClusterSearch(null, direction);
// Since requestFocus only takes "real" focus directions (and therefore also
// restoreFocusInCluster), convert forward/backward focus into FOCUS_DOWN.
diff --git a/core/java/android/view/ViewStructure.java b/core/java/android/view/ViewStructure.java
index b157709..92b0d98 100644
--- a/core/java/android/view/ViewStructure.java
+++ b/core/java/android/view/ViewStructure.java
@@ -326,8 +326,8 @@
/**
* Sets whether the data on this node is sensitive; if it is, then its content (text, autofill
* value, etc..) is striped before calls to {@link
- * android.service.autofill.AutofillService#onFillRequest(android.app.assist.AssistStructure,
- * Bundle, int, android.os.CancellationSignal, android.service.autofill.FillCallback)}.
+ * android.service.autofill.AutofillService#onFillRequest(android.service.autofill.FillRequest,
+ * android.os.CancellationSignal, android.service.autofill.FillCallback)}.
*
* <p>By default, all nodes are assumed to be sensitive, and only nodes that does not have PII
* (Personally Identifiable Information - sensitive data such as email addresses, credit card
@@ -336,8 +336,8 @@
*
* <p>Notice that the content of even sensitive nodes are sent to the service (through the
* {@link
- * android.service.autofill.AutofillService#onSaveRequest(android.app.assist.AssistStructure,
- * Bundle, android.service.autofill.SaveCallback)} call) when the user consented to save
+ * android.service.autofill.AutofillService#onSaveRequest(android.service.autofill.SaveRequest,
+ * android.service.autofill.SaveCallback)} call) when the user consented to save
* thedata, so it is important to set the content of sensitive nodes as well, but mark them as
* sensitive.
*
diff --git a/core/java/android/view/WindowManagerInternal.java b/core/java/android/view/WindowManagerInternal.java
index 6dbc09c..bf0e10f 100644
--- a/core/java/android/view/WindowManagerInternal.java
+++ b/core/java/android/view/WindowManagerInternal.java
@@ -303,13 +303,16 @@
* hidden, no matter how WindowManagerService will react / has reacted
* to corresponding API calls. Note that this state is not guaranteed
* to be synchronized with state in WindowManagerService.
+ * @param dismissImeOnBackKeyPressed {@code true} if the software keyboard is shown and the back
+ * key is expected to dismiss the software keyboard.
* @param targetWindowToken token to identify the target window that the IME is associated with.
* {@code null} when application, system, or the IME itself decided to
* change its window visibility before being associated with any target
* window.
*/
public abstract void updateInputMethodWindowStatus(@NonNull IBinder imeToken,
- boolean imeWindowVisible, @Nullable IBinder targetWindowToken);
+ boolean imeWindowVisible, boolean dismissImeOnBackKeyPressed,
+ @Nullable IBinder targetWindowToken);
/**
* Returns true when the hardware keyboard is available.
diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java
index bb6e0ee..030c78b 100644
--- a/core/java/android/view/WindowManagerPolicy.java
+++ b/core/java/android/view/WindowManagerPolicy.java
@@ -1537,6 +1537,18 @@
public void setLastInputMethodWindowLw(WindowState ime, WindowState target);
/**
+ * An internal callback (from InputMethodManagerService) to notify a state change regarding
+ * whether the back key should dismiss the software keyboard (IME) or not.
+ *
+ * @param newValue {@code true} if the software keyboard is shown and the back key is expected
+ * to dismiss the software keyboard.
+ * @hide
+ */
+ default void setDismissImeOnBackKeyPressed(boolean newValue) {
+ // Default implementation does nothing.
+ }
+
+ /**
* Show the recents task list app.
* @hide
*/
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index f9f400d..8ed0762 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -31,6 +31,8 @@
import android.os.IBinder;
import android.os.Parcelable;
import android.os.RemoteException;
+import android.service.autofill.AutofillService;
+import android.service.autofill.FillEventHistory;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
@@ -105,6 +107,7 @@
*
* @deprecated Use {@link android.service.autofill.FillRequest#FLAG_MANUAL_REQUEST}
*/
+ // TODO(b/33197203): remove
@Deprecated
public static final int FLAG_MANUAL_REQUEST = 0x1;
@@ -334,6 +337,20 @@
}
/**
+ * Should always be called from {@link AutofillService#getFillEventHistory()}.
+ *
+ * @hide
+ */
+ @Nullable public FillEventHistory getFillEventHistory() {
+ try {
+ return mService.getFillEventHistory();
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ return null;
+ }
+ }
+
+ /**
* Explicitly requests a new autofill context.
*
* <p>Normally, the autofill context is automatically started when autofillable views are
diff --git a/core/java/android/view/autofill/IAutoFillManager.aidl b/core/java/android/view/autofill/IAutoFillManager.aidl
index 68b3ccabc..df777c4 100644
--- a/core/java/android/view/autofill/IAutoFillManager.aidl
+++ b/core/java/android/view/autofill/IAutoFillManager.aidl
@@ -19,6 +19,7 @@
import android.graphics.Rect;
import android.os.Bundle;
import android.os.IBinder;
+import android.service.autofill.FillEventHistory;
import android.view.autofill.AutofillId;
import android.view.autofill.AutofillValue;
import android.view.autofill.IAutoFillManagerClient;
@@ -33,6 +34,7 @@
int startSession(IBinder activityToken, IBinder windowToken, in IBinder appCallback,
in AutofillId autoFillId, in Rect bounds, in AutofillValue value, int userId,
boolean hasCallback, int flags, String packageName);
+ FillEventHistory getFillEventHistory();
boolean restoreSession(int sessionId, in IBinder activityToken, in IBinder appCallback);
void setWindow(int sessionId, in IBinder windowToken);
void updateSession(int sessionId, in AutofillId id, in Rect bounds,
diff --git a/core/java/android/view/textclassifier/TextClassifierImpl.java b/core/java/android/view/textclassifier/TextClassifierImpl.java
index f634a1b..022c157 100644
--- a/core/java/android/view/textclassifier/TextClassifierImpl.java
+++ b/core/java/android/view/textclassifier/TextClassifierImpl.java
@@ -53,7 +53,6 @@
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
-import java.util.StringJoiner;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -89,7 +88,7 @@
@Override
public TextSelection suggestSelection(
@NonNull CharSequence text, int selectionStartIndex, int selectionEndIndex,
- LocaleList defaultLocales) {
+ @Nullable LocaleList defaultLocales) {
validateInput(text, selectionStartIndex, selectionEndIndex);
try {
if (text.length() > 0) {
@@ -128,7 +127,8 @@
@Override
public TextClassificationResult getTextClassificationResult(
- @NonNull CharSequence text, int startIndex, int endIndex, LocaleList defaultLocales) {
+ @NonNull CharSequence text, int startIndex, int endIndex,
+ @Nullable LocaleList defaultLocales) {
validateInput(text, startIndex, endIndex);
try {
if (text.length() > 0) {
@@ -156,7 +156,8 @@
}
@Override
- public LinksInfo getLinks(CharSequence text, int linkMask, LocaleList defaultLocales) {
+ public LinksInfo getLinks(
+ @NonNull CharSequence text, int linkMask, @Nullable LocaleList defaultLocales) {
Preconditions.checkArgument(text != null);
try {
return LinksInfoFactory.create(
@@ -199,12 +200,11 @@
@GuardedBy("mSmartSelectionLock") // Do not call outside this lock.
@Nullable
private Locale findBestSupportedLocaleLocked(LocaleList localeList) {
- final List<Locale.LanguageRange> languageRangeList = Locale.LanguageRange.parse(
- new StringJoiner(",")
- // Specified localeList takes priority over the system default
- .add(localeList.toLanguageTags())
- .add(LocaleList.getDefault().toLanguageTags())
- .toString());
+ // Specified localeList takes priority over the system default, so it is listed first.
+ final String languages = localeList.isEmpty()
+ ? LocaleList.getDefault().toLanguageTags()
+ : localeList.toLanguageTags() + "," + LocaleList.getDefault().toLanguageTags();
+ final List<Locale.LanguageRange> languageRangeList = Locale.LanguageRange.parse(languages);
return Locale.lookup(languageRangeList, loadModelFilePathsLocked().keySet());
}
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 4fb7b19..0d3baa8 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -146,7 +146,7 @@
private static final String UNDO_OWNER_TAG = "Editor";
// Ordering constants used to place the Action Mode or context menu items in their menu.
- private static final int MENU_ITEM_ORDER_ASSIST = 1;
+ private static final int MENU_ITEM_ORDER_ASSIST = 0;
private static final int MENU_ITEM_ORDER_UNDO = 2;
private static final int MENU_ITEM_ORDER_REDO = 3;
private static final int MENU_ITEM_ORDER_CUT = 4;
@@ -156,8 +156,8 @@
private static final int MENU_ITEM_ORDER_PASTE_AS_PLAIN_TEXT = 8;
private static final int MENU_ITEM_ORDER_SELECT_ALL = 9;
private static final int MENU_ITEM_ORDER_REPLACE = 10;
- private static final int MENU_ITEM_ORDER_PROCESS_TEXT_INTENT_ACTIONS_START = 11;
- private static final int MENU_ITEM_ORDER_AUTOFILL = 12;
+ private static final int MENU_ITEM_ORDER_AUTOFILL = 11;
+ private static final int MENU_ITEM_ORDER_PROCESS_TEXT_INTENT_ACTIONS_START = 100;
// Each Editor manages its own undo stack.
private final UndoManager mUndoManager = new UndoManager();
@@ -6322,9 +6322,10 @@
* Adds "PROCESS_TEXT" menu items to the specified menu.
*/
public void onInitializeMenu(Menu menu) {
- int i = 0;
+ final int size = mSupportedActivities.size();
loadSupportedActivities();
- for (ResolveInfo resolveInfo : mSupportedActivities) {
+ for (int i = 0; i < size; i++) {
+ final ResolveInfo resolveInfo = mSupportedActivities.get(i);
menu.add(Menu.NONE, Menu.NONE,
Editor.MENU_ITEM_ORDER_PROCESS_TEXT_INTENT_ACTIONS_START + i++,
getLabel(resolveInfo))
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 9245134..036b391 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -252,9 +252,7 @@
if (mEnterAnimationId != 0) {
opts = ActivityOptions.makeCustomAnimation(context, mEnterAnimationId, 0);
} else {
- opts = ActivityOptions.makeScaleUpAnimation(view,
- 0, 0,
- view.getMeasuredWidth(), view.getMeasuredHeight());
+ opts = ActivityOptions.makeBasic();
}
if (launchStackId != StackId.INVALID_STACK_ID) {
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index f9f10af..f1a3ff5 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -8196,9 +8196,11 @@
mTempTextPaint.set(getPaint());
mTempTextPaint.setTextSize(suggestedSizeInPx);
+ final int availableWidth = mHorizontallyScrolling
+ ? VERY_WIDE
+ : getMeasuredWidth() - getTotalPaddingLeft() - getTotalPaddingRight();
final StaticLayout.Builder layoutBuilder = StaticLayout.Builder.obtain(
- text, 0, text.length(), mTempTextPaint,
- getMeasuredWidth() - getTotalPaddingLeft() - getTotalPaddingRight());
+ text, 0, text.length(), mTempTextPaint, availableWidth);
layoutBuilder.setAlignment(getLayoutAlignment())
.setLineSpacing(getLineSpacingExtra(), getLineSpacingMultiplier())
diff --git a/core/java/com/android/internal/app/AccessibilityButtonChooserActivity.java b/core/java/com/android/internal/app/AccessibilityButtonChooserActivity.java
index 032c775..b9ed963 100644
--- a/core/java/com/android/internal/app/AccessibilityButtonChooserActivity.java
+++ b/core/java/com/android/internal/app/AccessibilityButtonChooserActivity.java
@@ -34,6 +34,7 @@
import android.widget.TextView;
import com.android.internal.R;
+import com.android.internal.widget.ResolverDrawerLayout;
import java.util.ArrayList;
import java.util.Collections;
@@ -56,6 +57,11 @@
super.onCreate(savedInstanceState);
setContentView(R.layout.accessibility_button_chooser);
+ final ResolverDrawerLayout rdl = findViewById(R.id.contentPanel);
+ if (rdl != null) {
+ rdl.setOnDismissedListener(this::finish);
+ }
+
String component = Settings.Secure.getString(getContentResolver(),
Settings.Secure.ACCESSIBILITY_BUTTON_TARGET_COMPONENT);
if (TextUtils.isEmpty(component)) {
diff --git a/core/java/com/android/internal/graphics/ColorUtils.java b/core/java/com/android/internal/graphics/ColorUtils.java
new file mode 100644
index 0000000..6c1efa4
--- /dev/null
+++ b/core/java/com/android/internal/graphics/ColorUtils.java
@@ -0,0 +1,618 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.internal.graphics;
+
+import android.annotation.ColorInt;
+import android.annotation.FloatRange;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.graphics.Color;
+
+/**
+ * Copied from: frameworks/support/core-utils/java/android/support/v4/graphics/ColorUtils.java
+ *
+ * A set of color-related utility methods, building upon those available in {@code Color}.
+ */
+public final class ColorUtils {
+
+ private static final double XYZ_WHITE_REFERENCE_X = 95.047;
+ private static final double XYZ_WHITE_REFERENCE_Y = 100;
+ private static final double XYZ_WHITE_REFERENCE_Z = 108.883;
+ private static final double XYZ_EPSILON = 0.008856;
+ private static final double XYZ_KAPPA = 903.3;
+
+ private static final int MIN_ALPHA_SEARCH_MAX_ITERATIONS = 10;
+ private static final int MIN_ALPHA_SEARCH_PRECISION = 1;
+
+ private static final ThreadLocal<double[]> TEMP_ARRAY = new ThreadLocal<>();
+
+ private ColorUtils() {}
+
+ /**
+ * Composite two potentially translucent colors over each other and returns the result.
+ */
+ public static int compositeColors(@ColorInt int foreground, @ColorInt int background) {
+ int bgAlpha = Color.alpha(background);
+ int fgAlpha = Color.alpha(foreground);
+ int a = compositeAlpha(fgAlpha, bgAlpha);
+
+ int r = compositeComponent(Color.red(foreground), fgAlpha,
+ Color.red(background), bgAlpha, a);
+ int g = compositeComponent(Color.green(foreground), fgAlpha,
+ Color.green(background), bgAlpha, a);
+ int b = compositeComponent(Color.blue(foreground), fgAlpha,
+ Color.blue(background), bgAlpha, a);
+
+ return Color.argb(a, r, g, b);
+ }
+
+ private static int compositeAlpha(int foregroundAlpha, int backgroundAlpha) {
+ return 0xFF - (((0xFF - backgroundAlpha) * (0xFF - foregroundAlpha)) / 0xFF);
+ }
+
+ private static int compositeComponent(int fgC, int fgA, int bgC, int bgA, int a) {
+ if (a == 0) return 0;
+ return ((0xFF * fgC * fgA) + (bgC * bgA * (0xFF - fgA))) / (a * 0xFF);
+ }
+
+ /**
+ * Returns the luminance of a color as a float between {@code 0.0} and {@code 1.0}.
+ * <p>Defined as the Y component in the XYZ representation of {@code color}.</p>
+ */
+ @FloatRange(from = 0.0, to = 1.0)
+ public static double calculateLuminance(@ColorInt int color) {
+ final double[] result = getTempDouble3Array();
+ colorToXYZ(color, result);
+ // Luminance is the Y component
+ return result[1] / 100;
+ }
+
+ /**
+ * Returns the contrast ratio between {@code foreground} and {@code background}.
+ * {@code background} must be opaque.
+ * <p>
+ * Formula defined
+ * <a href="http://www.w3.org/TR/2008/REC-WCAG20-20081211/#contrast-ratiodef">here</a>.
+ */
+ public static double calculateContrast(@ColorInt int foreground, @ColorInt int background) {
+ if (Color.alpha(background) != 255) {
+ throw new IllegalArgumentException("background can not be translucent: #"
+ + Integer.toHexString(background));
+ }
+ if (Color.alpha(foreground) < 255) {
+ // If the foreground is translucent, composite the foreground over the background
+ foreground = compositeColors(foreground, background);
+ }
+
+ final double luminance1 = calculateLuminance(foreground) + 0.05;
+ final double luminance2 = calculateLuminance(background) + 0.05;
+
+ // Now return the lighter luminance divided by the darker luminance
+ return Math.max(luminance1, luminance2) / Math.min(luminance1, luminance2);
+ }
+
+ /**
+ * Calculates the minimum alpha value which can be applied to {@code foreground} so that would
+ * have a contrast value of at least {@code minContrastRatio} when compared to
+ * {@code background}.
+ *
+ * @param foreground the foreground color
+ * @param background the opaque background color
+ * @param minContrastRatio the minimum contrast ratio
+ * @return the alpha value in the range 0-255, or -1 if no value could be calculated
+ */
+ public static int calculateMinimumAlpha(@ColorInt int foreground, @ColorInt int background,
+ float minContrastRatio) {
+ if (Color.alpha(background) != 255) {
+ throw new IllegalArgumentException("background can not be translucent: #"
+ + Integer.toHexString(background));
+ }
+
+ // First lets check that a fully opaque foreground has sufficient contrast
+ int testForeground = setAlphaComponent(foreground, 255);
+ double testRatio = calculateContrast(testForeground, background);
+ if (testRatio < minContrastRatio) {
+ // Fully opaque foreground does not have sufficient contrast, return error
+ return -1;
+ }
+
+ // Binary search to find a value with the minimum value which provides sufficient contrast
+ int numIterations = 0;
+ int minAlpha = 0;
+ int maxAlpha = 255;
+
+ while (numIterations <= MIN_ALPHA_SEARCH_MAX_ITERATIONS &&
+ (maxAlpha - minAlpha) > MIN_ALPHA_SEARCH_PRECISION) {
+ final int testAlpha = (minAlpha + maxAlpha) / 2;
+
+ testForeground = setAlphaComponent(foreground, testAlpha);
+ testRatio = calculateContrast(testForeground, background);
+
+ if (testRatio < minContrastRatio) {
+ minAlpha = testAlpha;
+ } else {
+ maxAlpha = testAlpha;
+ }
+
+ numIterations++;
+ }
+
+ // Conservatively return the max of the range of possible alphas, which is known to pass.
+ return maxAlpha;
+ }
+
+ /**
+ * Convert RGB components to HSL (hue-saturation-lightness).
+ * <ul>
+ * <li>outHsl[0] is Hue [0 .. 360)</li>
+ * <li>outHsl[1] is Saturation [0...1]</li>
+ * <li>outHsl[2] is Lightness [0...1]</li>
+ * </ul>
+ *
+ * @param r red component value [0..255]
+ * @param g green component value [0..255]
+ * @param b blue component value [0..255]
+ * @param outHsl 3-element array which holds the resulting HSL components
+ */
+ public static void RGBToHSL(@IntRange(from = 0x0, to = 0xFF) int r,
+ @IntRange(from = 0x0, to = 0xFF) int g, @IntRange(from = 0x0, to = 0xFF) int b,
+ @NonNull float[] outHsl) {
+ final float rf = r / 255f;
+ final float gf = g / 255f;
+ final float bf = b / 255f;
+
+ final float max = Math.max(rf, Math.max(gf, bf));
+ final float min = Math.min(rf, Math.min(gf, bf));
+ final float deltaMaxMin = max - min;
+
+ float h, s;
+ float l = (max + min) / 2f;
+
+ if (max == min) {
+ // Monochromatic
+ h = s = 0f;
+ } else {
+ if (max == rf) {
+ h = ((gf - bf) / deltaMaxMin) % 6f;
+ } else if (max == gf) {
+ h = ((bf - rf) / deltaMaxMin) + 2f;
+ } else {
+ h = ((rf - gf) / deltaMaxMin) + 4f;
+ }
+
+ s = deltaMaxMin / (1f - Math.abs(2f * l - 1f));
+ }
+
+ h = (h * 60f) % 360f;
+ if (h < 0) {
+ h += 360f;
+ }
+
+ outHsl[0] = constrain(h, 0f, 360f);
+ outHsl[1] = constrain(s, 0f, 1f);
+ outHsl[2] = constrain(l, 0f, 1f);
+ }
+
+ /**
+ * Convert the ARGB color to its HSL (hue-saturation-lightness) components.
+ * <ul>
+ * <li>outHsl[0] is Hue [0 .. 360)</li>
+ * <li>outHsl[1] is Saturation [0...1]</li>
+ * <li>outHsl[2] is Lightness [0...1]</li>
+ * </ul>
+ *
+ * @param color the ARGB color to convert. The alpha component is ignored
+ * @param outHsl 3-element array which holds the resulting HSL components
+ */
+ public static void colorToHSL(@ColorInt int color, @NonNull float[] outHsl) {
+ RGBToHSL(Color.red(color), Color.green(color), Color.blue(color), outHsl);
+ }
+
+ /**
+ * Convert HSL (hue-saturation-lightness) components to a RGB color.
+ * <ul>
+ * <li>hsl[0] is Hue [0 .. 360)</li>
+ * <li>hsl[1] is Saturation [0...1]</li>
+ * <li>hsl[2] is Lightness [0...1]</li>
+ * </ul>
+ * If hsv values are out of range, they are pinned.
+ *
+ * @param hsl 3-element array which holds the input HSL components
+ * @return the resulting RGB color
+ */
+ @ColorInt
+ public static int HSLToColor(@NonNull float[] hsl) {
+ final float h = hsl[0];
+ final float s = hsl[1];
+ final float l = hsl[2];
+
+ final float c = (1f - Math.abs(2 * l - 1f)) * s;
+ final float m = l - 0.5f * c;
+ final float x = c * (1f - Math.abs((h / 60f % 2f) - 1f));
+
+ final int hueSegment = (int) h / 60;
+
+ int r = 0, g = 0, b = 0;
+
+ switch (hueSegment) {
+ case 0:
+ r = Math.round(255 * (c + m));
+ g = Math.round(255 * (x + m));
+ b = Math.round(255 * m);
+ break;
+ case 1:
+ r = Math.round(255 * (x + m));
+ g = Math.round(255 * (c + m));
+ b = Math.round(255 * m);
+ break;
+ case 2:
+ r = Math.round(255 * m);
+ g = Math.round(255 * (c + m));
+ b = Math.round(255 * (x + m));
+ break;
+ case 3:
+ r = Math.round(255 * m);
+ g = Math.round(255 * (x + m));
+ b = Math.round(255 * (c + m));
+ break;
+ case 4:
+ r = Math.round(255 * (x + m));
+ g = Math.round(255 * m);
+ b = Math.round(255 * (c + m));
+ break;
+ case 5:
+ case 6:
+ r = Math.round(255 * (c + m));
+ g = Math.round(255 * m);
+ b = Math.round(255 * (x + m));
+ break;
+ }
+
+ r = constrain(r, 0, 255);
+ g = constrain(g, 0, 255);
+ b = constrain(b, 0, 255);
+
+ return Color.rgb(r, g, b);
+ }
+
+ /**
+ * Set the alpha component of {@code color} to be {@code alpha}.
+ */
+ @ColorInt
+ public static int setAlphaComponent(@ColorInt int color,
+ @IntRange(from = 0x0, to = 0xFF) int alpha) {
+ if (alpha < 0 || alpha > 255) {
+ throw new IllegalArgumentException("alpha must be between 0 and 255.");
+ }
+ return (color & 0x00ffffff) | (alpha << 24);
+ }
+
+ /**
+ * Convert the ARGB color to its CIE Lab representative components.
+ *
+ * @param color the ARGB color to convert. The alpha component is ignored
+ * @param outLab 3-element array which holds the resulting LAB components
+ */
+ public static void colorToLAB(@ColorInt int color, @NonNull double[] outLab) {
+ RGBToLAB(Color.red(color), Color.green(color), Color.blue(color), outLab);
+ }
+
+ /**
+ * Convert RGB components to its CIE Lab representative components.
+ *
+ * <ul>
+ * <li>outLab[0] is L [0 ...1)</li>
+ * <li>outLab[1] is a [-128...127)</li>
+ * <li>outLab[2] is b [-128...127)</li>
+ * </ul>
+ *
+ * @param r red component value [0..255]
+ * @param g green component value [0..255]
+ * @param b blue component value [0..255]
+ * @param outLab 3-element array which holds the resulting LAB components
+ */
+ public static void RGBToLAB(@IntRange(from = 0x0, to = 0xFF) int r,
+ @IntRange(from = 0x0, to = 0xFF) int g, @IntRange(from = 0x0, to = 0xFF) int b,
+ @NonNull double[] outLab) {
+ // First we convert RGB to XYZ
+ RGBToXYZ(r, g, b, outLab);
+ // outLab now contains XYZ
+ XYZToLAB(outLab[0], outLab[1], outLab[2], outLab);
+ // outLab now contains LAB representation
+ }
+
+ /**
+ * Convert the ARGB color to its CIE XYZ representative components.
+ *
+ * <p>The resulting XYZ representation will use the D65 illuminant and the CIE
+ * 2° Standard Observer (1931).</p>
+ *
+ * <ul>
+ * <li>outXyz[0] is X [0 ...95.047)</li>
+ * <li>outXyz[1] is Y [0...100)</li>
+ * <li>outXyz[2] is Z [0...108.883)</li>
+ * </ul>
+ *
+ * @param color the ARGB color to convert. The alpha component is ignored
+ * @param outXyz 3-element array which holds the resulting LAB components
+ */
+ public static void colorToXYZ(@ColorInt int color, @NonNull double[] outXyz) {
+ RGBToXYZ(Color.red(color), Color.green(color), Color.blue(color), outXyz);
+ }
+
+ /**
+ * Convert RGB components to its CIE XYZ representative components.
+ *
+ * <p>The resulting XYZ representation will use the D65 illuminant and the CIE
+ * 2° Standard Observer (1931).</p>
+ *
+ * <ul>
+ * <li>outXyz[0] is X [0 ...95.047)</li>
+ * <li>outXyz[1] is Y [0...100)</li>
+ * <li>outXyz[2] is Z [0...108.883)</li>
+ * </ul>
+ *
+ * @param r red component value [0..255]
+ * @param g green component value [0..255]
+ * @param b blue component value [0..255]
+ * @param outXyz 3-element array which holds the resulting XYZ components
+ */
+ public static void RGBToXYZ(@IntRange(from = 0x0, to = 0xFF) int r,
+ @IntRange(from = 0x0, to = 0xFF) int g, @IntRange(from = 0x0, to = 0xFF) int b,
+ @NonNull double[] outXyz) {
+ if (outXyz.length != 3) {
+ throw new IllegalArgumentException("outXyz must have a length of 3.");
+ }
+
+ double sr = r / 255.0;
+ sr = sr < 0.04045 ? sr / 12.92 : Math.pow((sr + 0.055) / 1.055, 2.4);
+ double sg = g / 255.0;
+ sg = sg < 0.04045 ? sg / 12.92 : Math.pow((sg + 0.055) / 1.055, 2.4);
+ double sb = b / 255.0;
+ sb = sb < 0.04045 ? sb / 12.92 : Math.pow((sb + 0.055) / 1.055, 2.4);
+
+ outXyz[0] = 100 * (sr * 0.4124 + sg * 0.3576 + sb * 0.1805);
+ outXyz[1] = 100 * (sr * 0.2126 + sg * 0.7152 + sb * 0.0722);
+ outXyz[2] = 100 * (sr * 0.0193 + sg * 0.1192 + sb * 0.9505);
+ }
+
+ /**
+ * Converts a color from CIE XYZ to CIE Lab representation.
+ *
+ * <p>This method expects the XYZ representation to use the D65 illuminant and the CIE
+ * 2° Standard Observer (1931).</p>
+ *
+ * <ul>
+ * <li>outLab[0] is L [0 ...1)</li>
+ * <li>outLab[1] is a [-128...127)</li>
+ * <li>outLab[2] is b [-128...127)</li>
+ * </ul>
+ *
+ * @param x X component value [0...95.047)
+ * @param y Y component value [0...100)
+ * @param z Z component value [0...108.883)
+ * @param outLab 3-element array which holds the resulting Lab components
+ */
+ public static void XYZToLAB(@FloatRange(from = 0f, to = XYZ_WHITE_REFERENCE_X) double x,
+ @FloatRange(from = 0f, to = XYZ_WHITE_REFERENCE_Y) double y,
+ @FloatRange(from = 0f, to = XYZ_WHITE_REFERENCE_Z) double z,
+ @NonNull double[] outLab) {
+ if (outLab.length != 3) {
+ throw new IllegalArgumentException("outLab must have a length of 3.");
+ }
+ x = pivotXyzComponent(x / XYZ_WHITE_REFERENCE_X);
+ y = pivotXyzComponent(y / XYZ_WHITE_REFERENCE_Y);
+ z = pivotXyzComponent(z / XYZ_WHITE_REFERENCE_Z);
+ outLab[0] = Math.max(0, 116 * y - 16);
+ outLab[1] = 500 * (x - y);
+ outLab[2] = 200 * (y - z);
+ }
+
+ /**
+ * Converts a color from CIE Lab to CIE XYZ representation.
+ *
+ * <p>The resulting XYZ representation will use the D65 illuminant and the CIE
+ * 2° Standard Observer (1931).</p>
+ *
+ * <ul>
+ * <li>outXyz[0] is X [0 ...95.047)</li>
+ * <li>outXyz[1] is Y [0...100)</li>
+ * <li>outXyz[2] is Z [0...108.883)</li>
+ * </ul>
+ *
+ * @param l L component value [0...100)
+ * @param a A component value [-128...127)
+ * @param b B component value [-128...127)
+ * @param outXyz 3-element array which holds the resulting XYZ components
+ */
+ public static void LABToXYZ(@FloatRange(from = 0f, to = 100) final double l,
+ @FloatRange(from = -128, to = 127) final double a,
+ @FloatRange(from = -128, to = 127) final double b,
+ @NonNull double[] outXyz) {
+ final double fy = (l + 16) / 116;
+ final double fx = a / 500 + fy;
+ final double fz = fy - b / 200;
+
+ double tmp = Math.pow(fx, 3);
+ final double xr = tmp > XYZ_EPSILON ? tmp : (116 * fx - 16) / XYZ_KAPPA;
+ final double yr = l > XYZ_KAPPA * XYZ_EPSILON ? Math.pow(fy, 3) : l / XYZ_KAPPA;
+
+ tmp = Math.pow(fz, 3);
+ final double zr = tmp > XYZ_EPSILON ? tmp : (116 * fz - 16) / XYZ_KAPPA;
+
+ outXyz[0] = xr * XYZ_WHITE_REFERENCE_X;
+ outXyz[1] = yr * XYZ_WHITE_REFERENCE_Y;
+ outXyz[2] = zr * XYZ_WHITE_REFERENCE_Z;
+ }
+
+ /**
+ * Converts a color from CIE XYZ to its RGB representation.
+ *
+ * <p>This method expects the XYZ representation to use the D65 illuminant and the CIE
+ * 2° Standard Observer (1931).</p>
+ *
+ * @param x X component value [0...95.047)
+ * @param y Y component value [0...100)
+ * @param z Z component value [0...108.883)
+ * @return int containing the RGB representation
+ */
+ @ColorInt
+ public static int XYZToColor(@FloatRange(from = 0f, to = XYZ_WHITE_REFERENCE_X) double x,
+ @FloatRange(from = 0f, to = XYZ_WHITE_REFERENCE_Y) double y,
+ @FloatRange(from = 0f, to = XYZ_WHITE_REFERENCE_Z) double z) {
+ double r = (x * 3.2406 + y * -1.5372 + z * -0.4986) / 100;
+ double g = (x * -0.9689 + y * 1.8758 + z * 0.0415) / 100;
+ double b = (x * 0.0557 + y * -0.2040 + z * 1.0570) / 100;
+
+ r = r > 0.0031308 ? 1.055 * Math.pow(r, 1 / 2.4) - 0.055 : 12.92 * r;
+ g = g > 0.0031308 ? 1.055 * Math.pow(g, 1 / 2.4) - 0.055 : 12.92 * g;
+ b = b > 0.0031308 ? 1.055 * Math.pow(b, 1 / 2.4) - 0.055 : 12.92 * b;
+
+ return Color.rgb(
+ constrain((int) Math.round(r * 255), 0, 255),
+ constrain((int) Math.round(g * 255), 0, 255),
+ constrain((int) Math.round(b * 255), 0, 255));
+ }
+
+ /**
+ * Converts a color from CIE Lab to its RGB representation.
+ *
+ * @param l L component value [0...100]
+ * @param a A component value [-128...127]
+ * @param b B component value [-128...127]
+ * @return int containing the RGB representation
+ */
+ @ColorInt
+ public static int LABToColor(@FloatRange(from = 0f, to = 100) final double l,
+ @FloatRange(from = -128, to = 127) final double a,
+ @FloatRange(from = -128, to = 127) final double b) {
+ final double[] result = getTempDouble3Array();
+ LABToXYZ(l, a, b, result);
+ return XYZToColor(result[0], result[1], result[2]);
+ }
+
+ /**
+ * Returns the euclidean distance between two LAB colors.
+ */
+ public static double distanceEuclidean(@NonNull double[] labX, @NonNull double[] labY) {
+ return Math.sqrt(Math.pow(labX[0] - labY[0], 2)
+ + Math.pow(labX[1] - labY[1], 2)
+ + Math.pow(labX[2] - labY[2], 2));
+ }
+
+ private static float constrain(float amount, float low, float high) {
+ return amount < low ? low : (amount > high ? high : amount);
+ }
+
+ private static int constrain(int amount, int low, int high) {
+ return amount < low ? low : (amount > high ? high : amount);
+ }
+
+ private static double pivotXyzComponent(double component) {
+ return component > XYZ_EPSILON
+ ? Math.pow(component, 1 / 3.0)
+ : (XYZ_KAPPA * component + 16) / 116;
+ }
+
+ /**
+ * Blend between two ARGB colors using the given ratio.
+ *
+ * <p>A blend ratio of 0.0 will result in {@code color1}, 0.5 will give an even blend,
+ * 1.0 will result in {@code color2}.</p>
+ *
+ * @param color1 the first ARGB color
+ * @param color2 the second ARGB color
+ * @param ratio the blend ratio of {@code color1} to {@code color2}
+ */
+ @ColorInt
+ public static int blendARGB(@ColorInt int color1, @ColorInt int color2,
+ @FloatRange(from = 0.0, to = 1.0) float ratio) {
+ final float inverseRatio = 1 - ratio;
+ float a = Color.alpha(color1) * inverseRatio + Color.alpha(color2) * ratio;
+ float r = Color.red(color1) * inverseRatio + Color.red(color2) * ratio;
+ float g = Color.green(color1) * inverseRatio + Color.green(color2) * ratio;
+ float b = Color.blue(color1) * inverseRatio + Color.blue(color2) * ratio;
+ return Color.argb((int) a, (int) r, (int) g, (int) b);
+ }
+
+ /**
+ * Blend between {@code hsl1} and {@code hsl2} using the given ratio. This will interpolate
+ * the hue using the shortest angle.
+ *
+ * <p>A blend ratio of 0.0 will result in {@code hsl1}, 0.5 will give an even blend,
+ * 1.0 will result in {@code hsl2}.</p>
+ *
+ * @param hsl1 3-element array which holds the first HSL color
+ * @param hsl2 3-element array which holds the second HSL color
+ * @param ratio the blend ratio of {@code hsl1} to {@code hsl2}
+ * @param outResult 3-element array which holds the resulting HSL components
+ */
+ public static void blendHSL(@NonNull float[] hsl1, @NonNull float[] hsl2,
+ @FloatRange(from = 0.0, to = 1.0) float ratio, @NonNull float[] outResult) {
+ if (outResult.length != 3) {
+ throw new IllegalArgumentException("result must have a length of 3.");
+ }
+ final float inverseRatio = 1 - ratio;
+ // Since hue is circular we will need to interpolate carefully
+ outResult[0] = circularInterpolate(hsl1[0], hsl2[0], ratio);
+ outResult[1] = hsl1[1] * inverseRatio + hsl2[1] * ratio;
+ outResult[2] = hsl1[2] * inverseRatio + hsl2[2] * ratio;
+ }
+
+ /**
+ * Blend between two CIE-LAB colors using the given ratio.
+ *
+ * <p>A blend ratio of 0.0 will result in {@code lab1}, 0.5 will give an even blend,
+ * 1.0 will result in {@code lab2}.</p>
+ *
+ * @param lab1 3-element array which holds the first LAB color
+ * @param lab2 3-element array which holds the second LAB color
+ * @param ratio the blend ratio of {@code lab1} to {@code lab2}
+ * @param outResult 3-element array which holds the resulting LAB components
+ */
+ public static void blendLAB(@NonNull double[] lab1, @NonNull double[] lab2,
+ @FloatRange(from = 0.0, to = 1.0) double ratio, @NonNull double[] outResult) {
+ if (outResult.length != 3) {
+ throw new IllegalArgumentException("outResult must have a length of 3.");
+ }
+ final double inverseRatio = 1 - ratio;
+ outResult[0] = lab1[0] * inverseRatio + lab2[0] * ratio;
+ outResult[1] = lab1[1] * inverseRatio + lab2[1] * ratio;
+ outResult[2] = lab1[2] * inverseRatio + lab2[2] * ratio;
+ }
+
+ static float circularInterpolate(float a, float b, float f) {
+ if (Math.abs(b - a) > 180) {
+ if (b > a) {
+ a += 360;
+ } else {
+ b += 360;
+ }
+ }
+ return (a + ((b - a) * f)) % 360;
+ }
+
+ private static double[] getTempDouble3Array() {
+ double[] result = TEMP_ARRAY.get();
+ if (result == null) {
+ result = new double[3];
+ TEMP_ARRAY.set(result);
+ }
+ return result;
+ }
+
+}
\ No newline at end of file
diff --git a/core/java/com/android/internal/graphics/palette/ColorCutQuantizer.java b/core/java/com/android/internal/graphics/palette/ColorCutQuantizer.java
new file mode 100644
index 0000000..2d0ad66
--- /dev/null
+++ b/core/java/com/android/internal/graphics/palette/ColorCutQuantizer.java
@@ -0,0 +1,535 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.internal.graphics.palette;
+
+/*
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import android.graphics.Color;
+import android.util.TimingLogger;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.List;
+import java.util.PriorityQueue;
+
+import com.android.internal.graphics.ColorUtils;
+import com.android.internal.graphics.palette.Palette.Swatch;
+
+/**
+ * Copied from: frameworks/support/v7/palette/src/main/java/android/support/v7/
+ * graphics/ColorCutQuantizer.java
+ *
+ * An color quantizer based on the Median-cut algorithm, but optimized for picking out distinct
+ * colors rather than representation colors.
+ *
+ * The color space is represented as a 3-dimensional cube with each dimension being an RGB
+ * component. The cube is then repeatedly divided until we have reduced the color space to the
+ * requested number of colors. An average color is then generated from each cube.
+ *
+ * What makes this different to median-cut is that median-cut divided cubes so that all of the cubes
+ * have roughly the same population, where this quantizer divides boxes based on their color volume.
+ * This means that the color space is divided into distinct colors, rather than representative
+ * colors.
+ */
+final class ColorCutQuantizer {
+
+ private static final String LOG_TAG = "ColorCutQuantizer";
+ private static final boolean LOG_TIMINGS = false;
+
+ static final int COMPONENT_RED = -3;
+ static final int COMPONENT_GREEN = -2;
+ static final int COMPONENT_BLUE = -1;
+
+ private static final int QUANTIZE_WORD_WIDTH = 5;
+ private static final int QUANTIZE_WORD_MASK = (1 << QUANTIZE_WORD_WIDTH) - 1;
+
+ final int[] mColors;
+ final int[] mHistogram;
+ final List<Swatch> mQuantizedColors;
+ final TimingLogger mTimingLogger;
+ final Palette.Filter[] mFilters;
+
+ private final float[] mTempHsl = new float[3];
+
+ /**
+ * Constructor.
+ *
+ * @param pixels histogram representing an image's pixel data
+ * @param maxColors The maximum number of colors that should be in the result palette.
+ * @param filters Set of filters to use in the quantization stage
+ */
+ ColorCutQuantizer(final int[] pixels, final int maxColors, final Palette.Filter[] filters) {
+ mTimingLogger = LOG_TIMINGS ? new TimingLogger(LOG_TAG, "Creation") : null;
+ mFilters = filters;
+
+ final int[] hist = mHistogram = new int[1 << (QUANTIZE_WORD_WIDTH * 3)];
+ for (int i = 0; i < pixels.length; i++) {
+ final int quantizedColor = quantizeFromRgb888(pixels[i]);
+ // Now update the pixel value to the quantized value
+ pixels[i] = quantizedColor;
+ // And update the histogram
+ hist[quantizedColor]++;
+ }
+
+ if (LOG_TIMINGS) {
+ mTimingLogger.addSplit("Histogram created");
+ }
+
+ // Now let's count the number of distinct colors
+ int distinctColorCount = 0;
+ for (int color = 0; color < hist.length; color++) {
+ if (hist[color] > 0 && shouldIgnoreColor(color)) {
+ // If we should ignore the color, set the population to 0
+ hist[color] = 0;
+ }
+ if (hist[color] > 0) {
+ // If the color has population, increase the distinct color count
+ distinctColorCount++;
+ }
+ }
+
+ if (LOG_TIMINGS) {
+ mTimingLogger.addSplit("Filtered colors and distinct colors counted");
+ }
+
+ // Now lets go through create an array consisting of only distinct colors
+ final int[] colors = mColors = new int[distinctColorCount];
+ int distinctColorIndex = 0;
+ for (int color = 0; color < hist.length; color++) {
+ if (hist[color] > 0) {
+ colors[distinctColorIndex++] = color;
+ }
+ }
+
+ if (LOG_TIMINGS) {
+ mTimingLogger.addSplit("Distinct colors copied into array");
+ }
+
+ if (distinctColorCount <= maxColors) {
+ // The image has fewer colors than the maximum requested, so just return the colors
+ mQuantizedColors = new ArrayList<>();
+ for (int color : colors) {
+ mQuantizedColors.add(new Swatch(approximateToRgb888(color), hist[color]));
+ }
+
+ if (LOG_TIMINGS) {
+ mTimingLogger.addSplit("Too few colors present. Copied to Swatches");
+ mTimingLogger.dumpToLog();
+ }
+ } else {
+ // We need use quantization to reduce the number of colors
+ mQuantizedColors = quantizePixels(maxColors);
+
+ if (LOG_TIMINGS) {
+ mTimingLogger.addSplit("Quantized colors computed");
+ mTimingLogger.dumpToLog();
+ }
+ }
+ }
+
+ /**
+ * @return the list of quantized colors
+ */
+ List<Swatch> getQuantizedColors() {
+ return mQuantizedColors;
+ }
+
+ private List<Swatch> quantizePixels(int maxColors) {
+ // Create the priority queue which is sorted by volume descending. This means we always
+ // split the largest box in the queue
+ final PriorityQueue<Vbox> pq = new PriorityQueue<>(maxColors, VBOX_COMPARATOR_VOLUME);
+
+ // To start, offer a box which contains all of the colors
+ pq.offer(new Vbox(0, mColors.length - 1));
+
+ // Now go through the boxes, splitting them until we have reached maxColors or there are no
+ // more boxes to split
+ splitBoxes(pq, maxColors);
+
+ // Finally, return the average colors of the color boxes
+ return generateAverageColors(pq);
+ }
+
+ /**
+ * Iterate through the {@link java.util.Queue}, popping
+ * {@link ColorCutQuantizer.Vbox} objects from the queue
+ * and splitting them. Once split, the new box and the remaining box are offered back to the
+ * queue.
+ *
+ * @param queue {@link java.util.PriorityQueue} to poll for boxes
+ * @param maxSize Maximum amount of boxes to split
+ */
+ private void splitBoxes(final PriorityQueue<Vbox> queue, final int maxSize) {
+ while (queue.size() < maxSize) {
+ final Vbox vbox = queue.poll();
+
+ if (vbox != null && vbox.canSplit()) {
+ // First split the box, and offer the result
+ queue.offer(vbox.splitBox());
+
+ if (LOG_TIMINGS) {
+ mTimingLogger.addSplit("Box split");
+ }
+ // Then offer the box back
+ queue.offer(vbox);
+ } else {
+ if (LOG_TIMINGS) {
+ mTimingLogger.addSplit("All boxes split");
+ }
+ // If we get here then there are no more boxes to split, so return
+ return;
+ }
+ }
+ }
+
+ private List<Swatch> generateAverageColors(Collection<Vbox> vboxes) {
+ ArrayList<Swatch> colors = new ArrayList<>(vboxes.size());
+ for (Vbox vbox : vboxes) {
+ Swatch swatch = vbox.getAverageColor();
+ if (!shouldIgnoreColor(swatch)) {
+ // As we're averaging a color box, we can still get colors which we do not want, so
+ // we check again here
+ colors.add(swatch);
+ }
+ }
+ return colors;
+ }
+
+ /**
+ * Represents a tightly fitting box around a color space.
+ */
+ private class Vbox {
+ // lower and upper index are inclusive
+ private int mLowerIndex;
+ private int mUpperIndex;
+ // Population of colors within this box
+ private int mPopulation;
+
+ private int mMinRed, mMaxRed;
+ private int mMinGreen, mMaxGreen;
+ private int mMinBlue, mMaxBlue;
+
+ Vbox(int lowerIndex, int upperIndex) {
+ mLowerIndex = lowerIndex;
+ mUpperIndex = upperIndex;
+ fitBox();
+ }
+
+ final int getVolume() {
+ return (mMaxRed - mMinRed + 1) * (mMaxGreen - mMinGreen + 1) *
+ (mMaxBlue - mMinBlue + 1);
+ }
+
+ final boolean canSplit() {
+ return getColorCount() > 1;
+ }
+
+ final int getColorCount() {
+ return 1 + mUpperIndex - mLowerIndex;
+ }
+
+ /**
+ * Recomputes the boundaries of this box to tightly fit the colors within the box.
+ */
+ final void fitBox() {
+ final int[] colors = mColors;
+ final int[] hist = mHistogram;
+
+ // Reset the min and max to opposite values
+ int minRed, minGreen, minBlue;
+ minRed = minGreen = minBlue = Integer.MAX_VALUE;
+ int maxRed, maxGreen, maxBlue;
+ maxRed = maxGreen = maxBlue = Integer.MIN_VALUE;
+ int count = 0;
+
+ for (int i = mLowerIndex; i <= mUpperIndex; i++) {
+ final int color = colors[i];
+ count += hist[color];
+
+ final int r = quantizedRed(color);
+ final int g = quantizedGreen(color);
+ final int b = quantizedBlue(color);
+ if (r > maxRed) {
+ maxRed = r;
+ }
+ if (r < minRed) {
+ minRed = r;
+ }
+ if (g > maxGreen) {
+ maxGreen = g;
+ }
+ if (g < minGreen) {
+ minGreen = g;
+ }
+ if (b > maxBlue) {
+ maxBlue = b;
+ }
+ if (b < minBlue) {
+ minBlue = b;
+ }
+ }
+
+ mMinRed = minRed;
+ mMaxRed = maxRed;
+ mMinGreen = minGreen;
+ mMaxGreen = maxGreen;
+ mMinBlue = minBlue;
+ mMaxBlue = maxBlue;
+ mPopulation = count;
+ }
+
+ /**
+ * Split this color box at the mid-point along its longest dimension
+ *
+ * @return the new ColorBox
+ */
+ final Vbox splitBox() {
+ if (!canSplit()) {
+ throw new IllegalStateException("Can not split a box with only 1 color");
+ }
+
+ // find median along the longest dimension
+ final int splitPoint = findSplitPoint();
+
+ Vbox newBox = new Vbox(splitPoint + 1, mUpperIndex);
+
+ // Now change this box's upperIndex and recompute the color boundaries
+ mUpperIndex = splitPoint;
+ fitBox();
+
+ return newBox;
+ }
+
+ /**
+ * @return the dimension which this box is largest in
+ */
+ final int getLongestColorDimension() {
+ final int redLength = mMaxRed - mMinRed;
+ final int greenLength = mMaxGreen - mMinGreen;
+ final int blueLength = mMaxBlue - mMinBlue;
+
+ if (redLength >= greenLength && redLength >= blueLength) {
+ return COMPONENT_RED;
+ } else if (greenLength >= redLength && greenLength >= blueLength) {
+ return COMPONENT_GREEN;
+ } else {
+ return COMPONENT_BLUE;
+ }
+ }
+
+ /**
+ * Finds the point within this box's lowerIndex and upperIndex index of where to split.
+ *
+ * This is calculated by finding the longest color dimension, and then sorting the
+ * sub-array based on that dimension value in each color. The colors are then iterated over
+ * until a color is found with at least the midpoint of the whole box's dimension midpoint.
+ *
+ * @return the index of the colors array to split from
+ */
+ final int findSplitPoint() {
+ final int longestDimension = getLongestColorDimension();
+ final int[] colors = mColors;
+ final int[] hist = mHistogram;
+
+ // We need to sort the colors in this box based on the longest color dimension.
+ // As we can't use a Comparator to define the sort logic, we modify each color so that
+ // its most significant is the desired dimension
+ modifySignificantOctet(colors, longestDimension, mLowerIndex, mUpperIndex);
+
+ // Now sort... Arrays.sort uses a exclusive toIndex so we need to add 1
+ Arrays.sort(colors, mLowerIndex, mUpperIndex + 1);
+
+ // Now revert all of the colors so that they are packed as RGB again
+ modifySignificantOctet(colors, longestDimension, mLowerIndex, mUpperIndex);
+
+ final int midPoint = mPopulation / 2;
+ for (int i = mLowerIndex, count = 0; i <= mUpperIndex; i++) {
+ count += hist[colors[i]];
+ if (count >= midPoint) {
+ return i;
+ }
+ }
+
+ return mLowerIndex;
+ }
+
+ /**
+ * @return the average color of this box.
+ */
+ final Swatch getAverageColor() {
+ final int[] colors = mColors;
+ final int[] hist = mHistogram;
+ int redSum = 0;
+ int greenSum = 0;
+ int blueSum = 0;
+ int totalPopulation = 0;
+
+ for (int i = mLowerIndex; i <= mUpperIndex; i++) {
+ final int color = colors[i];
+ final int colorPopulation = hist[color];
+
+ totalPopulation += colorPopulation;
+ redSum += colorPopulation * quantizedRed(color);
+ greenSum += colorPopulation * quantizedGreen(color);
+ blueSum += colorPopulation * quantizedBlue(color);
+ }
+
+ final int redMean = Math.round(redSum / (float) totalPopulation);
+ final int greenMean = Math.round(greenSum / (float) totalPopulation);
+ final int blueMean = Math.round(blueSum / (float) totalPopulation);
+
+ return new Swatch(approximateToRgb888(redMean, greenMean, blueMean), totalPopulation);
+ }
+ }
+
+ /**
+ * Modify the significant octet in a packed color int. Allows sorting based on the value of a
+ * single color component. This relies on all components being the same word size.
+ *
+ * @see Vbox#findSplitPoint()
+ */
+ static void modifySignificantOctet(final int[] a, final int dimension,
+ final int lower, final int upper) {
+ switch (dimension) {
+ case COMPONENT_RED:
+ // Already in RGB, no need to do anything
+ break;
+ case COMPONENT_GREEN:
+ // We need to do a RGB to GRB swap, or vice-versa
+ for (int i = lower; i <= upper; i++) {
+ final int color = a[i];
+ a[i] = quantizedGreen(color) << (QUANTIZE_WORD_WIDTH + QUANTIZE_WORD_WIDTH)
+ | quantizedRed(color) << QUANTIZE_WORD_WIDTH
+ | quantizedBlue(color);
+ }
+ break;
+ case COMPONENT_BLUE:
+ // We need to do a RGB to BGR swap, or vice-versa
+ for (int i = lower; i <= upper; i++) {
+ final int color = a[i];
+ a[i] = quantizedBlue(color) << (QUANTIZE_WORD_WIDTH + QUANTIZE_WORD_WIDTH)
+ | quantizedGreen(color) << QUANTIZE_WORD_WIDTH
+ | quantizedRed(color);
+ }
+ break;
+ }
+ }
+
+ private boolean shouldIgnoreColor(int color565) {
+ final int rgb = approximateToRgb888(color565);
+ ColorUtils.colorToHSL(rgb, mTempHsl);
+ return shouldIgnoreColor(rgb, mTempHsl);
+ }
+
+ private boolean shouldIgnoreColor(Swatch color) {
+ return shouldIgnoreColor(color.getRgb(), color.getHsl());
+ }
+
+ private boolean shouldIgnoreColor(int rgb, float[] hsl) {
+ if (mFilters != null && mFilters.length > 0) {
+ for (int i = 0, count = mFilters.length; i < count; i++) {
+ if (!mFilters[i].isAllowed(rgb, hsl)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Comparator which sorts {@link Vbox} instances based on their volume, in descending order
+ */
+ private static final Comparator<Vbox> VBOX_COMPARATOR_VOLUME = new Comparator<Vbox>() {
+ @Override
+ public int compare(Vbox lhs, Vbox rhs) {
+ return rhs.getVolume() - lhs.getVolume();
+ }
+ };
+
+ /**
+ * Quantized a RGB888 value to have a word width of {@value #QUANTIZE_WORD_WIDTH}.
+ */
+ private static int quantizeFromRgb888(int color) {
+ int r = modifyWordWidth(Color.red(color), 8, QUANTIZE_WORD_WIDTH);
+ int g = modifyWordWidth(Color.green(color), 8, QUANTIZE_WORD_WIDTH);
+ int b = modifyWordWidth(Color.blue(color), 8, QUANTIZE_WORD_WIDTH);
+ return r << (QUANTIZE_WORD_WIDTH + QUANTIZE_WORD_WIDTH) | g << QUANTIZE_WORD_WIDTH | b;
+ }
+
+ /**
+ * Quantized RGB888 values to have a word width of {@value #QUANTIZE_WORD_WIDTH}.
+ */
+ static int approximateToRgb888(int r, int g, int b) {
+ return Color.rgb(modifyWordWidth(r, QUANTIZE_WORD_WIDTH, 8),
+ modifyWordWidth(g, QUANTIZE_WORD_WIDTH, 8),
+ modifyWordWidth(b, QUANTIZE_WORD_WIDTH, 8));
+ }
+
+ private static int approximateToRgb888(int color) {
+ return approximateToRgb888(quantizedRed(color), quantizedGreen(color), quantizedBlue(color));
+ }
+
+ /**
+ * @return red component of the quantized color
+ */
+ static int quantizedRed(int color) {
+ return (color >> (QUANTIZE_WORD_WIDTH + QUANTIZE_WORD_WIDTH)) & QUANTIZE_WORD_MASK;
+ }
+
+ /**
+ * @return green component of a quantized color
+ */
+ static int quantizedGreen(int color) {
+ return (color >> QUANTIZE_WORD_WIDTH) & QUANTIZE_WORD_MASK;
+ }
+
+ /**
+ * @return blue component of a quantized color
+ */
+ static int quantizedBlue(int color) {
+ return color & QUANTIZE_WORD_MASK;
+ }
+
+ private static int modifyWordWidth(int value, int currentWidth, int targetWidth) {
+ final int newValue;
+ if (targetWidth > currentWidth) {
+ // If we're approximating up in word width, we'll shift up
+ newValue = value << (targetWidth - currentWidth);
+ } else {
+ // Else, we will just shift and keep the MSB
+ newValue = value >> (currentWidth - targetWidth);
+ }
+ return newValue & ((1 << targetWidth) - 1);
+ }
+
+}
\ No newline at end of file
diff --git a/core/java/com/android/internal/graphics/palette/Palette.java b/core/java/com/android/internal/graphics/palette/Palette.java
new file mode 100644
index 0000000..9f1504a
--- /dev/null
+++ b/core/java/com/android/internal/graphics/palette/Palette.java
@@ -0,0 +1,990 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.internal.graphics.palette;
+
+import android.annotation.ColorInt;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.graphics.Rect;
+import android.os.AsyncTask;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.SparseBooleanArray;
+import android.util.TimingLogger;
+
+import com.android.internal.graphics.ColorUtils;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+
+/**
+ * Copied from: /frameworks/support/v7/palette/src/main/java/android/support/v7/
+ * graphics/Palette.java
+ *
+ * A helper class to extract prominent colors from an image.
+ * <p>
+ * A number of colors with different profiles are extracted from the image:
+ * <ul>
+ * <li>Vibrant</li>
+ * <li>Vibrant Dark</li>
+ * <li>Vibrant Light</li>
+ * <li>Muted</li>
+ * <li>Muted Dark</li>
+ * <li>Muted Light</li>
+ * </ul>
+ * These can be retrieved from the appropriate getter method.
+ *
+ * <p>
+ * Instances are created with a {@link Palette.Builder} which supports several options to tweak the
+ * generated Palette. See that class' documentation for more information.
+ * <p>
+ * Generation should always be completed on a background thread, ideally the one in
+ * which you load your image on. {@link Palette.Builder} supports both synchronous and asynchronous
+ * generation:
+ *
+ * <pre>
+ * // Synchronous
+ * Palette p = Palette.from(bitmap).generate();
+ *
+ * // Asynchronous
+ * Palette.from(bitmap).generate(new PaletteAsyncListener() {
+ * public void onGenerated(Palette p) {
+ * // Use generated instance
+ * }
+ * });
+ * </pre>
+ */
+public final class Palette {
+
+ /**
+ * Listener to be used with {@link #generateAsync(Bitmap, Palette.PaletteAsyncListener)} or
+ * {@link #generateAsync(Bitmap, int, Palette.PaletteAsyncListener)}
+ */
+ public interface PaletteAsyncListener {
+
+ /**
+ * Called when the {@link Palette} has been generated.
+ */
+ void onGenerated(Palette palette);
+ }
+
+ static final int DEFAULT_RESIZE_BITMAP_AREA = 112 * 112;
+ static final int DEFAULT_CALCULATE_NUMBER_COLORS = 16;
+
+ static final float MIN_CONTRAST_TITLE_TEXT = 3.0f;
+ static final float MIN_CONTRAST_BODY_TEXT = 4.5f;
+
+ static final String LOG_TAG = "Palette";
+ static final boolean LOG_TIMINGS = false;
+
+ /**
+ * Start generating a {@link Palette} with the returned {@link Palette.Builder} instance.
+ */
+ public static Palette.Builder from(Bitmap bitmap) {
+ return new Palette.Builder(bitmap);
+ }
+
+ /**
+ * Generate a {@link Palette} from the pre-generated list of {@link Palette.Swatch} swatches.
+ * This is useful for testing, or if you want to resurrect a {@link Palette} instance from a
+ * list of swatches. Will return null if the {@code swatches} is null.
+ */
+ public static Palette from(List<Palette.Swatch> swatches) {
+ return new Palette.Builder(swatches).generate();
+ }
+
+ /**
+ * @deprecated Use {@link Palette.Builder} to generate the Palette.
+ */
+ @Deprecated
+ public static Palette generate(Bitmap bitmap) {
+ return from(bitmap).generate();
+ }
+
+ /**
+ * @deprecated Use {@link Palette.Builder} to generate the Palette.
+ */
+ @Deprecated
+ public static Palette generate(Bitmap bitmap, int numColors) {
+ return from(bitmap).maximumColorCount(numColors).generate();
+ }
+
+ /**
+ * @deprecated Use {@link Palette.Builder} to generate the Palette.
+ */
+ @Deprecated
+ public static AsyncTask<Bitmap, Void, Palette> generateAsync(
+ Bitmap bitmap, Palette.PaletteAsyncListener listener) {
+ return from(bitmap).generate(listener);
+ }
+
+ /**
+ * @deprecated Use {@link Palette.Builder} to generate the Palette.
+ */
+ @Deprecated
+ public static AsyncTask<Bitmap, Void, Palette> generateAsync(
+ final Bitmap bitmap, final int numColors, final Palette.PaletteAsyncListener listener) {
+ return from(bitmap).maximumColorCount(numColors).generate(listener);
+ }
+
+ private final List<Palette.Swatch> mSwatches;
+ private final List<Target> mTargets;
+
+ private final Map<Target, Palette.Swatch> mSelectedSwatches;
+ private final SparseBooleanArray mUsedColors;
+
+ private final Palette.Swatch mDominantSwatch;
+
+ Palette(List<Palette.Swatch> swatches, List<Target> targets) {
+ mSwatches = swatches;
+ mTargets = targets;
+
+ mUsedColors = new SparseBooleanArray();
+ mSelectedSwatches = new ArrayMap<>();
+
+ mDominantSwatch = findDominantSwatch();
+ }
+
+ /**
+ * Returns all of the swatches which make up the palette.
+ */
+ @NonNull
+ public List<Palette.Swatch> getSwatches() {
+ return Collections.unmodifiableList(mSwatches);
+ }
+
+ /**
+ * Returns the targets used to generate this palette.
+ */
+ @NonNull
+ public List<Target> getTargets() {
+ return Collections.unmodifiableList(mTargets);
+ }
+
+ /**
+ * Returns the most vibrant swatch in the palette. Might be null.
+ *
+ * @see Target#VIBRANT
+ */
+ @Nullable
+ public Palette.Swatch getVibrantSwatch() {
+ return getSwatchForTarget(Target.VIBRANT);
+ }
+
+ /**
+ * Returns a light and vibrant swatch from the palette. Might be null.
+ *
+ * @see Target#LIGHT_VIBRANT
+ */
+ @Nullable
+ public Palette.Swatch getLightVibrantSwatch() {
+ return getSwatchForTarget(Target.LIGHT_VIBRANT);
+ }
+
+ /**
+ * Returns a dark and vibrant swatch from the palette. Might be null.
+ *
+ * @see Target#DARK_VIBRANT
+ */
+ @Nullable
+ public Palette.Swatch getDarkVibrantSwatch() {
+ return getSwatchForTarget(Target.DARK_VIBRANT);
+ }
+
+ /**
+ * Returns a muted swatch from the palette. Might be null.
+ *
+ * @see Target#MUTED
+ */
+ @Nullable
+ public Palette.Swatch getMutedSwatch() {
+ return getSwatchForTarget(Target.MUTED);
+ }
+
+ /**
+ * Returns a muted and light swatch from the palette. Might be null.
+ *
+ * @see Target#LIGHT_MUTED
+ */
+ @Nullable
+ public Palette.Swatch getLightMutedSwatch() {
+ return getSwatchForTarget(Target.LIGHT_MUTED);
+ }
+
+ /**
+ * Returns a muted and dark swatch from the palette. Might be null.
+ *
+ * @see Target#DARK_MUTED
+ */
+ @Nullable
+ public Palette.Swatch getDarkMutedSwatch() {
+ return getSwatchForTarget(Target.DARK_MUTED);
+ }
+
+ /**
+ * Returns the most vibrant color in the palette as an RGB packed int.
+ *
+ * @param defaultColor value to return if the swatch isn't available
+ * @see #getVibrantSwatch()
+ */
+ @ColorInt
+ public int getVibrantColor(@ColorInt final int defaultColor) {
+ return getColorForTarget(Target.VIBRANT, defaultColor);
+ }
+
+ /**
+ * Returns a light and vibrant color from the palette as an RGB packed int.
+ *
+ * @param defaultColor value to return if the swatch isn't available
+ * @see #getLightVibrantSwatch()
+ */
+ @ColorInt
+ public int getLightVibrantColor(@ColorInt final int defaultColor) {
+ return getColorForTarget(Target.LIGHT_VIBRANT, defaultColor);
+ }
+
+ /**
+ * Returns a dark and vibrant color from the palette as an RGB packed int.
+ *
+ * @param defaultColor value to return if the swatch isn't available
+ * @see #getDarkVibrantSwatch()
+ */
+ @ColorInt
+ public int getDarkVibrantColor(@ColorInt final int defaultColor) {
+ return getColorForTarget(Target.DARK_VIBRANT, defaultColor);
+ }
+
+ /**
+ * Returns a muted color from the palette as an RGB packed int.
+ *
+ * @param defaultColor value to return if the swatch isn't available
+ * @see #getMutedSwatch()
+ */
+ @ColorInt
+ public int getMutedColor(@ColorInt final int defaultColor) {
+ return getColorForTarget(Target.MUTED, defaultColor);
+ }
+
+ /**
+ * Returns a muted and light color from the palette as an RGB packed int.
+ *
+ * @param defaultColor value to return if the swatch isn't available
+ * @see #getLightMutedSwatch()
+ */
+ @ColorInt
+ public int getLightMutedColor(@ColorInt final int defaultColor) {
+ return getColorForTarget(Target.LIGHT_MUTED, defaultColor);
+ }
+
+ /**
+ * Returns a muted and dark color from the palette as an RGB packed int.
+ *
+ * @param defaultColor value to return if the swatch isn't available
+ * @see #getDarkMutedSwatch()
+ */
+ @ColorInt
+ public int getDarkMutedColor(@ColorInt final int defaultColor) {
+ return getColorForTarget(Target.DARK_MUTED, defaultColor);
+ }
+
+ /**
+ * Returns the selected swatch for the given target from the palette, or {@code null} if one
+ * could not be found.
+ */
+ @Nullable
+ public Palette.Swatch getSwatchForTarget(@NonNull final Target target) {
+ return mSelectedSwatches.get(target);
+ }
+
+ /**
+ * Returns the selected color for the given target from the palette as an RGB packed int.
+ *
+ * @param defaultColor value to return if the swatch isn't available
+ */
+ @ColorInt
+ public int getColorForTarget(@NonNull final Target target, @ColorInt final int defaultColor) {
+ Palette.Swatch swatch = getSwatchForTarget(target);
+ return swatch != null ? swatch.getRgb() : defaultColor;
+ }
+
+ /**
+ * Returns the dominant swatch from the palette.
+ *
+ * <p>The dominant swatch is defined as the swatch with the greatest population (frequency)
+ * within the palette.</p>
+ */
+ @Nullable
+ public Palette.Swatch getDominantSwatch() {
+ return mDominantSwatch;
+ }
+
+ /**
+ * Returns the color of the dominant swatch from the palette, as an RGB packed int.
+ *
+ * @param defaultColor value to return if the swatch isn't available
+ * @see #getDominantSwatch()
+ */
+ @ColorInt
+ public int getDominantColor(@ColorInt int defaultColor) {
+ return mDominantSwatch != null ? mDominantSwatch.getRgb() : defaultColor;
+ }
+
+ void generate() {
+ // We need to make sure that the scored targets are generated first. This is so that
+ // inherited targets have something to inherit from
+ for (int i = 0, count = mTargets.size(); i < count; i++) {
+ final Target target = mTargets.get(i);
+ target.normalizeWeights();
+ mSelectedSwatches.put(target, generateScoredTarget(target));
+ }
+ // We now clear out the used colors
+ mUsedColors.clear();
+ }
+
+ private Palette.Swatch generateScoredTarget(final Target target) {
+ final Palette.Swatch maxScoreSwatch = getMaxScoredSwatchForTarget(target);
+ if (maxScoreSwatch != null && target.isExclusive()) {
+ // If we have a swatch, and the target is exclusive, add the color to the used list
+ mUsedColors.append(maxScoreSwatch.getRgb(), true);
+ }
+ return maxScoreSwatch;
+ }
+
+ private Palette.Swatch getMaxScoredSwatchForTarget(final Target target) {
+ float maxScore = 0;
+ Palette.Swatch maxScoreSwatch = null;
+ for (int i = 0, count = mSwatches.size(); i < count; i++) {
+ final Palette.Swatch swatch = mSwatches.get(i);
+ if (shouldBeScoredForTarget(swatch, target)) {
+ final float score = generateScore(swatch, target);
+ if (maxScoreSwatch == null || score > maxScore) {
+ maxScoreSwatch = swatch;
+ maxScore = score;
+ }
+ }
+ }
+ return maxScoreSwatch;
+ }
+
+ private boolean shouldBeScoredForTarget(final Palette.Swatch swatch, final Target target) {
+ // Check whether the HSL values are within the correct ranges, and this color hasn't
+ // been used yet.
+ final float hsl[] = swatch.getHsl();
+ return hsl[1] >= target.getMinimumSaturation() && hsl[1] <= target.getMaximumSaturation()
+ && hsl[2] >= target.getMinimumLightness() && hsl[2] <= target.getMaximumLightness()
+ && !mUsedColors.get(swatch.getRgb());
+ }
+
+ private float generateScore(Palette.Swatch swatch, Target target) {
+ final float[] hsl = swatch.getHsl();
+
+ float saturationScore = 0;
+ float luminanceScore = 0;
+ float populationScore = 0;
+
+ final int maxPopulation = mDominantSwatch != null ? mDominantSwatch.getPopulation() : 1;
+
+ if (target.getSaturationWeight() > 0) {
+ saturationScore = target.getSaturationWeight()
+ * (1f - Math.abs(hsl[1] - target.getTargetSaturation()));
+ }
+ if (target.getLightnessWeight() > 0) {
+ luminanceScore = target.getLightnessWeight()
+ * (1f - Math.abs(hsl[2] - target.getTargetLightness()));
+ }
+ if (target.getPopulationWeight() > 0) {
+ populationScore = target.getPopulationWeight()
+ * (swatch.getPopulation() / (float) maxPopulation);
+ }
+
+ return saturationScore + luminanceScore + populationScore;
+ }
+
+ private Palette.Swatch findDominantSwatch() {
+ int maxPop = Integer.MIN_VALUE;
+ Palette.Swatch maxSwatch = null;
+ for (int i = 0, count = mSwatches.size(); i < count; i++) {
+ Palette.Swatch swatch = mSwatches.get(i);
+ if (swatch.getPopulation() > maxPop) {
+ maxSwatch = swatch;
+ maxPop = swatch.getPopulation();
+ }
+ }
+ return maxSwatch;
+ }
+
+ private static float[] copyHslValues(Palette.Swatch color) {
+ final float[] newHsl = new float[3];
+ System.arraycopy(color.getHsl(), 0, newHsl, 0, 3);
+ return newHsl;
+ }
+
+ /**
+ * Represents a color swatch generated from an image's palette. The RGB color can be retrieved
+ * by calling {@link #getRgb()}.
+ */
+ public static final class Swatch {
+ private final int mRed, mGreen, mBlue;
+ private final int mRgb;
+ private final int mPopulation;
+
+ private boolean mGeneratedTextColors;
+ private int mTitleTextColor;
+ private int mBodyTextColor;
+
+ private float[] mHsl;
+
+ public Swatch(@ColorInt int color, int population) {
+ mRed = Color.red(color);
+ mGreen = Color.green(color);
+ mBlue = Color.blue(color);
+ mRgb = color;
+ mPopulation = population;
+ }
+
+ Swatch(int red, int green, int blue, int population) {
+ mRed = red;
+ mGreen = green;
+ mBlue = blue;
+ mRgb = Color.rgb(red, green, blue);
+ mPopulation = population;
+ }
+
+ Swatch(float[] hsl, int population) {
+ this(ColorUtils.HSLToColor(hsl), population);
+ mHsl = hsl;
+ }
+
+ /**
+ * @return this swatch's RGB color value
+ */
+ @ColorInt
+ public int getRgb() {
+ return mRgb;
+ }
+
+ /**
+ * Return this swatch's HSL values.
+ * hsv[0] is Hue [0 .. 360)
+ * hsv[1] is Saturation [0...1]
+ * hsv[2] is Lightness [0...1]
+ */
+ public float[] getHsl() {
+ if (mHsl == null) {
+ mHsl = new float[3];
+ }
+ ColorUtils.RGBToHSL(mRed, mGreen, mBlue, mHsl);
+ return mHsl;
+ }
+
+ /**
+ * @return the number of pixels represented by this swatch
+ */
+ public int getPopulation() {
+ return mPopulation;
+ }
+
+ /**
+ * Returns an appropriate color to use for any 'title' text which is displayed over this
+ * {@link Palette.Swatch}'s color. This color is guaranteed to have sufficient contrast.
+ */
+ @ColorInt
+ public int getTitleTextColor() {
+ ensureTextColorsGenerated();
+ return mTitleTextColor;
+ }
+
+ /**
+ * Returns an appropriate color to use for any 'body' text which is displayed over this
+ * {@link Palette.Swatch}'s color. This color is guaranteed to have sufficient contrast.
+ */
+ @ColorInt
+ public int getBodyTextColor() {
+ ensureTextColorsGenerated();
+ return mBodyTextColor;
+ }
+
+ private void ensureTextColorsGenerated() {
+ if (!mGeneratedTextColors) {
+ // First check white, as most colors will be dark
+ final int lightBodyAlpha = ColorUtils.calculateMinimumAlpha(
+ Color.WHITE, mRgb, MIN_CONTRAST_BODY_TEXT);
+ final int lightTitleAlpha = ColorUtils.calculateMinimumAlpha(
+ Color.WHITE, mRgb, MIN_CONTRAST_TITLE_TEXT);
+
+ if (lightBodyAlpha != -1 && lightTitleAlpha != -1) {
+ // If we found valid light values, use them and return
+ mBodyTextColor = ColorUtils.setAlphaComponent(Color.WHITE, lightBodyAlpha);
+ mTitleTextColor = ColorUtils.setAlphaComponent(Color.WHITE, lightTitleAlpha);
+ mGeneratedTextColors = true;
+ return;
+ }
+
+ final int darkBodyAlpha = ColorUtils.calculateMinimumAlpha(
+ Color.BLACK, mRgb, MIN_CONTRAST_BODY_TEXT);
+ final int darkTitleAlpha = ColorUtils.calculateMinimumAlpha(
+ Color.BLACK, mRgb, MIN_CONTRAST_TITLE_TEXT);
+
+ if (darkBodyAlpha != -1 && darkTitleAlpha != -1) {
+ // If we found valid dark values, use them and return
+ mBodyTextColor = ColorUtils.setAlphaComponent(Color.BLACK, darkBodyAlpha);
+ mTitleTextColor = ColorUtils.setAlphaComponent(Color.BLACK, darkTitleAlpha);
+ mGeneratedTextColors = true;
+ return;
+ }
+
+ // If we reach here then we can not find title and body values which use the same
+ // lightness, we need to use mismatched values
+ mBodyTextColor = lightBodyAlpha != -1
+ ? ColorUtils.setAlphaComponent(Color.WHITE, lightBodyAlpha)
+ : ColorUtils.setAlphaComponent(Color.BLACK, darkBodyAlpha);
+ mTitleTextColor = lightTitleAlpha != -1
+ ? ColorUtils.setAlphaComponent(Color.WHITE, lightTitleAlpha)
+ : ColorUtils.setAlphaComponent(Color.BLACK, darkTitleAlpha);
+ mGeneratedTextColors = true;
+ }
+ }
+
+ @Override
+ public String toString() {
+ return new StringBuilder(getClass().getSimpleName())
+ .append(" [RGB: #").append(Integer.toHexString(getRgb())).append(']')
+ .append(" [HSL: ").append(Arrays.toString(getHsl())).append(']')
+ .append(" [Population: ").append(mPopulation).append(']')
+ .append(" [Title Text: #").append(Integer.toHexString(getTitleTextColor()))
+ .append(']')
+ .append(" [Body Text: #").append(Integer.toHexString(getBodyTextColor()))
+ .append(']').toString();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ Palette.Swatch
+ swatch = (Palette.Swatch) o;
+ return mPopulation == swatch.mPopulation && mRgb == swatch.mRgb;
+ }
+
+ @Override
+ public int hashCode() {
+ return 31 * mRgb + mPopulation;
+ }
+ }
+
+ /**
+ * Builder class for generating {@link Palette} instances.
+ */
+ public static final class Builder {
+ private final List<Palette.Swatch> mSwatches;
+ private final Bitmap mBitmap;
+
+ private final List<Target> mTargets = new ArrayList<>();
+
+ private int mMaxColors = DEFAULT_CALCULATE_NUMBER_COLORS;
+ private int mResizeArea = DEFAULT_RESIZE_BITMAP_AREA;
+ private int mResizeMaxDimension = -1;
+
+ private final List<Palette.Filter> mFilters = new ArrayList<>();
+ private Rect mRegion;
+
+ /**
+ * Construct a new {@link Palette.Builder} using a source {@link Bitmap}
+ */
+ public Builder(Bitmap bitmap) {
+ if (bitmap == null || bitmap.isRecycled()) {
+ throw new IllegalArgumentException("Bitmap is not valid");
+ }
+ mFilters.add(DEFAULT_FILTER);
+ mBitmap = bitmap;
+ mSwatches = null;
+
+ // Add the default targets
+ mTargets.add(Target.LIGHT_VIBRANT);
+ mTargets.add(Target.VIBRANT);
+ mTargets.add(Target.DARK_VIBRANT);
+ mTargets.add(Target.LIGHT_MUTED);
+ mTargets.add(Target.MUTED);
+ mTargets.add(Target.DARK_MUTED);
+ }
+
+ /**
+ * Construct a new {@link Palette.Builder} using a list of {@link Palette.Swatch} instances.
+ * Typically only used for testing.
+ */
+ public Builder(List<Palette.Swatch> swatches) {
+ if (swatches == null || swatches.isEmpty()) {
+ throw new IllegalArgumentException("List of Swatches is not valid");
+ }
+ mFilters.add(DEFAULT_FILTER);
+ mSwatches = swatches;
+ mBitmap = null;
+ }
+
+ /**
+ * Set the maximum number of colors to use in the quantization step when using a
+ * {@link android.graphics.Bitmap} as the source.
+ * <p>
+ * Good values for depend on the source image type. For landscapes, good values are in
+ * the range 10-16. For images which are largely made up of people's faces then this
+ * value should be increased to ~24.
+ */
+ @NonNull
+ public Palette.Builder maximumColorCount(int colors) {
+ mMaxColors = colors;
+ return this;
+ }
+
+ /**
+ * Set the resize value when using a {@link android.graphics.Bitmap} as the source.
+ * If the bitmap's largest dimension is greater than the value specified, then the bitmap
+ * will be resized so that its largest dimension matches {@code maxDimension}. If the
+ * bitmap is smaller or equal, the original is used as-is.
+ *
+ * @deprecated Using {@link #resizeBitmapArea(int)} is preferred since it can handle
+ * abnormal aspect ratios more gracefully.
+ *
+ * @param maxDimension the number of pixels that the max dimension should be scaled down to,
+ * or any value <= 0 to disable resizing.
+ */
+ @NonNull
+ @Deprecated
+ public Palette.Builder resizeBitmapSize(final int maxDimension) {
+ mResizeMaxDimension = maxDimension;
+ mResizeArea = -1;
+ return this;
+ }
+
+ /**
+ * Set the resize value when using a {@link android.graphics.Bitmap} as the source.
+ * If the bitmap's area is greater than the value specified, then the bitmap
+ * will be resized so that its area matches {@code area}. If the
+ * bitmap is smaller or equal, the original is used as-is.
+ * <p>
+ * This value has a large effect on the processing time. The larger the resized image is,
+ * the greater time it will take to generate the palette. The smaller the image is, the
+ * more detail is lost in the resulting image and thus less precision for color selection.
+ *
+ * @param area the number of pixels that the intermediary scaled down Bitmap should cover,
+ * or any value <= 0 to disable resizing.
+ */
+ @NonNull
+ public Palette.Builder resizeBitmapArea(final int area) {
+ mResizeArea = area;
+ mResizeMaxDimension = -1;
+ return this;
+ }
+
+ /**
+ * Clear all added filters. This includes any default filters added automatically by
+ * {@link Palette}.
+ */
+ @NonNull
+ public Palette.Builder clearFilters() {
+ mFilters.clear();
+ return this;
+ }
+
+ /**
+ * Add a filter to be able to have fine grained control over which colors are
+ * allowed in the resulting palette.
+ *
+ * @param filter filter to add.
+ */
+ @NonNull
+ public Palette.Builder addFilter(
+ Palette.Filter filter) {
+ if (filter != null) {
+ mFilters.add(filter);
+ }
+ return this;
+ }
+
+ /**
+ * Set a region of the bitmap to be used exclusively when calculating the palette.
+ * <p>This only works when the original input is a {@link Bitmap}.</p>
+ *
+ * @param left The left side of the rectangle used for the region.
+ * @param top The top of the rectangle used for the region.
+ * @param right The right side of the rectangle used for the region.
+ * @param bottom The bottom of the rectangle used for the region.
+ */
+ @NonNull
+ public Palette.Builder setRegion(int left, int top, int right, int bottom) {
+ if (mBitmap != null) {
+ if (mRegion == null) mRegion = new Rect();
+ // Set the Rect to be initially the whole Bitmap
+ mRegion.set(0, 0, mBitmap.getWidth(), mBitmap.getHeight());
+ // Now just get the intersection with the region
+ if (!mRegion.intersect(left, top, right, bottom)) {
+ throw new IllegalArgumentException("The given region must intersect with "
+ + "the Bitmap's dimensions.");
+ }
+ }
+ return this;
+ }
+
+ /**
+ * Clear any previously region set via {@link #setRegion(int, int, int, int)}.
+ */
+ @NonNull
+ public Palette.Builder clearRegion() {
+ mRegion = null;
+ return this;
+ }
+
+ /**
+ * Add a target profile to be generated in the palette.
+ *
+ * <p>You can retrieve the result via {@link Palette#getSwatchForTarget(Target)}.</p>
+ */
+ @NonNull
+ public Palette.Builder addTarget(@NonNull final Target target) {
+ if (!mTargets.contains(target)) {
+ mTargets.add(target);
+ }
+ return this;
+ }
+
+ /**
+ * Clear all added targets. This includes any default targets added automatically by
+ * {@link Palette}.
+ */
+ @NonNull
+ public Palette.Builder clearTargets() {
+ if (mTargets != null) {
+ mTargets.clear();
+ }
+ return this;
+ }
+
+ /**
+ * Generate and return the {@link Palette} synchronously.
+ */
+ @NonNull
+ public Palette generate() {
+ final TimingLogger logger = LOG_TIMINGS
+ ? new TimingLogger(LOG_TAG, "Generation")
+ : null;
+
+ List<Palette.Swatch> swatches;
+
+ if (mBitmap != null) {
+ // We have a Bitmap so we need to use quantization to reduce the number of colors
+
+ // First we'll scale down the bitmap if needed
+ final Bitmap bitmap = scaleBitmapDown(mBitmap);
+
+ if (logger != null) {
+ logger.addSplit("Processed Bitmap");
+ }
+
+ final Rect region = mRegion;
+ if (bitmap != mBitmap && region != null) {
+ // If we have a scaled bitmap and a selected region, we need to scale down the
+ // region to match the new scale
+ final double scale = bitmap.getWidth() / (double) mBitmap.getWidth();
+ region.left = (int) Math.floor(region.left * scale);
+ region.top = (int) Math.floor(region.top * scale);
+ region.right = Math.min((int) Math.ceil(region.right * scale),
+ bitmap.getWidth());
+ region.bottom = Math.min((int) Math.ceil(region.bottom * scale),
+ bitmap.getHeight());
+ }
+
+ // Now generate a quantizer from the Bitmap
+ final ColorCutQuantizer quantizer = new ColorCutQuantizer(
+ getPixelsFromBitmap(bitmap),
+ mMaxColors,
+ mFilters.isEmpty() ? null : mFilters.toArray(new Palette.Filter[mFilters.size()]));
+
+ // If created a new bitmap, recycle it
+ if (bitmap != mBitmap) {
+ bitmap.recycle();
+ }
+
+ swatches = quantizer.getQuantizedColors();
+
+ if (logger != null) {
+ logger.addSplit("Color quantization completed");
+ }
+ } else {
+ // Else we're using the provided swatches
+ swatches = mSwatches;
+ }
+
+ // Now create a Palette instance
+ final Palette p = new Palette(swatches, mTargets);
+ // And make it generate itself
+ p.generate();
+
+ if (logger != null) {
+ logger.addSplit("Created Palette");
+ logger.dumpToLog();
+ }
+
+ return p;
+ }
+
+ /**
+ * Generate the {@link Palette} asynchronously. The provided listener's
+ * {@link Palette.PaletteAsyncListener#onGenerated} method will be called with the palette when
+ * generated.
+ */
+ @NonNull
+ public AsyncTask<Bitmap, Void, Palette> generate(final Palette.PaletteAsyncListener listener) {
+ if (listener == null) {
+ throw new IllegalArgumentException("listener can not be null");
+ }
+
+ return new AsyncTask<Bitmap, Void, Palette>() {
+ @Override
+ protected Palette doInBackground(Bitmap... params) {
+ try {
+ return generate();
+ } catch (Exception e) {
+ Log.e(LOG_TAG, "Exception thrown during async generate", e);
+ return null;
+ }
+ }
+
+ @Override
+ protected void onPostExecute(Palette colorExtractor) {
+ listener.onGenerated(colorExtractor);
+ }
+ }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, mBitmap);
+ }
+
+ private int[] getPixelsFromBitmap(Bitmap bitmap) {
+ final int bitmapWidth = bitmap.getWidth();
+ final int bitmapHeight = bitmap.getHeight();
+ final int[] pixels = new int[bitmapWidth * bitmapHeight];
+ bitmap.getPixels(pixels, 0, bitmapWidth, 0, 0, bitmapWidth, bitmapHeight);
+
+ if (mRegion == null) {
+ // If we don't have a region, return all of the pixels
+ return pixels;
+ } else {
+ // If we do have a region, lets create a subset array containing only the region's
+ // pixels
+ final int regionWidth = mRegion.width();
+ final int regionHeight = mRegion.height();
+ // pixels contains all of the pixels, so we need to iterate through each row and
+ // copy the regions pixels into a new smaller array
+ final int[] subsetPixels = new int[regionWidth * regionHeight];
+ for (int row = 0; row < regionHeight; row++) {
+ System.arraycopy(pixels, ((row + mRegion.top) * bitmapWidth) + mRegion.left,
+ subsetPixels, row * regionWidth, regionWidth);
+ }
+ return subsetPixels;
+ }
+ }
+
+ /**
+ * Scale the bitmap down as needed.
+ */
+ private Bitmap scaleBitmapDown(final Bitmap bitmap) {
+ double scaleRatio = -1;
+
+ if (mResizeArea > 0) {
+ final int bitmapArea = bitmap.getWidth() * bitmap.getHeight();
+ if (bitmapArea > mResizeArea) {
+ scaleRatio = Math.sqrt(mResizeArea / (double) bitmapArea);
+ }
+ } else if (mResizeMaxDimension > 0) {
+ final int maxDimension = Math.max(bitmap.getWidth(), bitmap.getHeight());
+ if (maxDimension > mResizeMaxDimension) {
+ scaleRatio = mResizeMaxDimension / (double) maxDimension;
+ }
+ }
+
+ if (scaleRatio <= 0) {
+ // Scaling has been disabled or not needed so just return the Bitmap
+ return bitmap;
+ }
+
+ return Bitmap.createScaledBitmap(bitmap,
+ (int) Math.ceil(bitmap.getWidth() * scaleRatio),
+ (int) Math.ceil(bitmap.getHeight() * scaleRatio),
+ false);
+ }
+ }
+
+ /**
+ * A Filter provides a mechanism for exercising fine-grained control over which colors
+ * are valid within a resulting {@link Palette}.
+ */
+ public interface Filter {
+ /**
+ * Hook to allow clients to be able filter colors from resulting palette.
+ *
+ * @param rgb the color in RGB888.
+ * @param hsl HSL representation of the color.
+ *
+ * @return true if the color is allowed, false if not.
+ *
+ * @see Palette.Builder#addFilter(Palette.Filter)
+ */
+ boolean isAllowed(int rgb, float[] hsl);
+ }
+
+ /**
+ * The default filter.
+ */
+ static final Palette.Filter
+ DEFAULT_FILTER = new Palette.Filter() {
+ private static final float BLACK_MAX_LIGHTNESS = 0.05f;
+ private static final float WHITE_MIN_LIGHTNESS = 0.95f;
+
+ @Override
+ public boolean isAllowed(int rgb, float[] hsl) {
+ return !isWhite(hsl) && !isBlack(hsl) && !isNearRedILine(hsl);
+ }
+
+ /**
+ * @return true if the color represents a color which is close to black.
+ */
+ private boolean isBlack(float[] hslColor) {
+ return hslColor[2] <= BLACK_MAX_LIGHTNESS;
+ }
+
+ /**
+ * @return true if the color represents a color which is close to white.
+ */
+ private boolean isWhite(float[] hslColor) {
+ return hslColor[2] >= WHITE_MIN_LIGHTNESS;
+ }
+
+ /**
+ * @return true if the color lies close to the red side of the I line.
+ */
+ private boolean isNearRedILine(float[] hslColor) {
+ return hslColor[0] >= 10f && hslColor[0] <= 37f && hslColor[1] <= 0.82f;
+ }
+ };
+}
diff --git a/core/java/com/android/internal/graphics/palette/Target.java b/core/java/com/android/internal/graphics/palette/Target.java
new file mode 100644
index 0000000..0540d80
--- /dev/null
+++ b/core/java/com/android/internal/graphics/palette/Target.java
@@ -0,0 +1,435 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.internal.graphics.palette;
+
+/*
+ * Copyright 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import android.annotation.FloatRange;
+
+/**
+ * Copied from: frameworks/support/v7/palette/src/main/java/android/support/v7/graphics/Target.java
+ *
+ * A class which allows custom selection of colors in a {@link Palette}'s generation. Instances
+ * can be created via the {@link android.support.v7.graphics.Target.Builder} class.
+ *
+ * <p>To use the target, use the {@link Palette.Builder#addTarget(Target)} API when building a
+ * Palette.</p>
+ */
+public final class Target {
+
+ private static final float TARGET_DARK_LUMA = 0.26f;
+ private static final float MAX_DARK_LUMA = 0.45f;
+
+ private static final float MIN_LIGHT_LUMA = 0.55f;
+ private static final float TARGET_LIGHT_LUMA = 0.74f;
+
+ private static final float MIN_NORMAL_LUMA = 0.3f;
+ private static final float TARGET_NORMAL_LUMA = 0.5f;
+ private static final float MAX_NORMAL_LUMA = 0.7f;
+
+ private static final float TARGET_MUTED_SATURATION = 0.3f;
+ private static final float MAX_MUTED_SATURATION = 0.4f;
+
+ private static final float TARGET_VIBRANT_SATURATION = 1f;
+ private static final float MIN_VIBRANT_SATURATION = 0.35f;
+
+ private static final float WEIGHT_SATURATION = 0.24f;
+ private static final float WEIGHT_LUMA = 0.52f;
+ private static final float WEIGHT_POPULATION = 0.24f;
+
+ static final int INDEX_MIN = 0;
+ static final int INDEX_TARGET = 1;
+ static final int INDEX_MAX = 2;
+
+ static final int INDEX_WEIGHT_SAT = 0;
+ static final int INDEX_WEIGHT_LUMA = 1;
+ static final int INDEX_WEIGHT_POP = 2;
+
+ /**
+ * A target which has the characteristics of a vibrant color which is light in luminance.
+ */
+ public static final Target LIGHT_VIBRANT;
+
+ /**
+ * A target which has the characteristics of a vibrant color which is neither light or dark.
+ */
+ public static final Target VIBRANT;
+
+ /**
+ * A target which has the characteristics of a vibrant color which is dark in luminance.
+ */
+ public static final Target DARK_VIBRANT;
+
+ /**
+ * A target which has the characteristics of a muted color which is light in luminance.
+ */
+ public static final Target LIGHT_MUTED;
+
+ /**
+ * A target which has the characteristics of a muted color which is neither light or dark.
+ */
+ public static final Target MUTED;
+
+ /**
+ * A target which has the characteristics of a muted color which is dark in luminance.
+ */
+ public static final Target DARK_MUTED;
+
+ static {
+ LIGHT_VIBRANT = new Target();
+ setDefaultLightLightnessValues(LIGHT_VIBRANT);
+ setDefaultVibrantSaturationValues(LIGHT_VIBRANT);
+
+ VIBRANT = new Target();
+ setDefaultNormalLightnessValues(VIBRANT);
+ setDefaultVibrantSaturationValues(VIBRANT);
+
+ DARK_VIBRANT = new Target();
+ setDefaultDarkLightnessValues(DARK_VIBRANT);
+ setDefaultVibrantSaturationValues(DARK_VIBRANT);
+
+ LIGHT_MUTED = new Target();
+ setDefaultLightLightnessValues(LIGHT_MUTED);
+ setDefaultMutedSaturationValues(LIGHT_MUTED);
+
+ MUTED = new Target();
+ setDefaultNormalLightnessValues(MUTED);
+ setDefaultMutedSaturationValues(MUTED);
+
+ DARK_MUTED = new Target();
+ setDefaultDarkLightnessValues(DARK_MUTED);
+ setDefaultMutedSaturationValues(DARK_MUTED);
+ }
+
+ final float[] mSaturationTargets = new float[3];
+ final float[] mLightnessTargets = new float[3];
+ final float[] mWeights = new float[3];
+ boolean mIsExclusive = true; // default to true
+
+ Target() {
+ setTargetDefaultValues(mSaturationTargets);
+ setTargetDefaultValues(mLightnessTargets);
+ setDefaultWeights();
+ }
+
+ Target(Target from) {
+ System.arraycopy(from.mSaturationTargets, 0, mSaturationTargets, 0,
+ mSaturationTargets.length);
+ System.arraycopy(from.mLightnessTargets, 0, mLightnessTargets, 0,
+ mLightnessTargets.length);
+ System.arraycopy(from.mWeights, 0, mWeights, 0, mWeights.length);
+ }
+
+ /**
+ * The minimum saturation value for this target.
+ */
+ @FloatRange(from = 0, to = 1)
+ public float getMinimumSaturation() {
+ return mSaturationTargets[INDEX_MIN];
+ }
+
+ /**
+ * The target saturation value for this target.
+ */
+ @FloatRange(from = 0, to = 1)
+ public float getTargetSaturation() {
+ return mSaturationTargets[INDEX_TARGET];
+ }
+
+ /**
+ * The maximum saturation value for this target.
+ */
+ @FloatRange(from = 0, to = 1)
+ public float getMaximumSaturation() {
+ return mSaturationTargets[INDEX_MAX];
+ }
+
+ /**
+ * The minimum lightness value for this target.
+ */
+ @FloatRange(from = 0, to = 1)
+ public float getMinimumLightness() {
+ return mLightnessTargets[INDEX_MIN];
+ }
+
+ /**
+ * The target lightness value for this target.
+ */
+ @FloatRange(from = 0, to = 1)
+ public float getTargetLightness() {
+ return mLightnessTargets[INDEX_TARGET];
+ }
+
+ /**
+ * The maximum lightness value for this target.
+ */
+ @FloatRange(from = 0, to = 1)
+ public float getMaximumLightness() {
+ return mLightnessTargets[INDEX_MAX];
+ }
+
+ /**
+ * Returns the weight of importance that this target places on a color's saturation within
+ * the image.
+ *
+ * <p>The larger the weight, relative to the other weights, the more important that a color
+ * being close to the target value has on selection.</p>
+ *
+ * @see #getTargetSaturation()
+ */
+ public float getSaturationWeight() {
+ return mWeights[INDEX_WEIGHT_SAT];
+ }
+
+ /**
+ * Returns the weight of importance that this target places on a color's lightness within
+ * the image.
+ *
+ * <p>The larger the weight, relative to the other weights, the more important that a color
+ * being close to the target value has on selection.</p>
+ *
+ * @see #getTargetLightness()
+ */
+ public float getLightnessWeight() {
+ return mWeights[INDEX_WEIGHT_LUMA];
+ }
+
+ /**
+ * Returns the weight of importance that this target places on a color's population within
+ * the image.
+ *
+ * <p>The larger the weight, relative to the other weights, the more important that a
+ * color's population being close to the most populous has on selection.</p>
+ */
+ public float getPopulationWeight() {
+ return mWeights[INDEX_WEIGHT_POP];
+ }
+
+ /**
+ * Returns whether any color selected for this target is exclusive for this target only.
+ *
+ * <p>If false, then the color can be selected for other targets.</p>
+ */
+ public boolean isExclusive() {
+ return mIsExclusive;
+ }
+
+ private static void setTargetDefaultValues(final float[] values) {
+ values[INDEX_MIN] = 0f;
+ values[INDEX_TARGET] = 0.5f;
+ values[INDEX_MAX] = 1f;
+ }
+
+ private void setDefaultWeights() {
+ mWeights[INDEX_WEIGHT_SAT] = WEIGHT_SATURATION;
+ mWeights[INDEX_WEIGHT_LUMA] = WEIGHT_LUMA;
+ mWeights[INDEX_WEIGHT_POP] = WEIGHT_POPULATION;
+ }
+
+ void normalizeWeights() {
+ float sum = 0;
+ for (int i = 0, z = mWeights.length; i < z; i++) {
+ float weight = mWeights[i];
+ if (weight > 0) {
+ sum += weight;
+ }
+ }
+ if (sum != 0) {
+ for (int i = 0, z = mWeights.length; i < z; i++) {
+ if (mWeights[i] > 0) {
+ mWeights[i] /= sum;
+ }
+ }
+ }
+ }
+
+ private static void setDefaultDarkLightnessValues(Target target) {
+ target.mLightnessTargets[INDEX_TARGET] = TARGET_DARK_LUMA;
+ target.mLightnessTargets[INDEX_MAX] = MAX_DARK_LUMA;
+ }
+
+ private static void setDefaultNormalLightnessValues(Target target) {
+ target.mLightnessTargets[INDEX_MIN] = MIN_NORMAL_LUMA;
+ target.mLightnessTargets[INDEX_TARGET] = TARGET_NORMAL_LUMA;
+ target.mLightnessTargets[INDEX_MAX] = MAX_NORMAL_LUMA;
+ }
+
+ private static void setDefaultLightLightnessValues(Target target) {
+ target.mLightnessTargets[INDEX_MIN] = MIN_LIGHT_LUMA;
+ target.mLightnessTargets[INDEX_TARGET] = TARGET_LIGHT_LUMA;
+ }
+
+ private static void setDefaultVibrantSaturationValues(Target target) {
+ target.mSaturationTargets[INDEX_MIN] = MIN_VIBRANT_SATURATION;
+ target.mSaturationTargets[INDEX_TARGET] = TARGET_VIBRANT_SATURATION;
+ }
+
+ private static void setDefaultMutedSaturationValues(Target target) {
+ target.mSaturationTargets[INDEX_TARGET] = TARGET_MUTED_SATURATION;
+ target.mSaturationTargets[INDEX_MAX] = MAX_MUTED_SATURATION;
+ }
+
+ /**
+ * Builder class for generating custom {@link Target} instances.
+ */
+ public final static class Builder {
+ private final Target mTarget;
+
+ /**
+ * Create a new {@link Target} builder from scratch.
+ */
+ public Builder() {
+ mTarget = new Target();
+ }
+
+ /**
+ * Create a new builder based on an existing {@link Target}.
+ */
+ public Builder(Target target) {
+ mTarget = new Target(target);
+ }
+
+ /**
+ * Set the minimum saturation value for this target.
+ */
+ public Target.Builder setMinimumSaturation(@FloatRange(from = 0, to = 1) float value) {
+ mTarget.mSaturationTargets[INDEX_MIN] = value;
+ return this;
+ }
+
+ /**
+ * Set the target/ideal saturation value for this target.
+ */
+ public Target.Builder setTargetSaturation(@FloatRange(from = 0, to = 1) float value) {
+ mTarget.mSaturationTargets[INDEX_TARGET] = value;
+ return this;
+ }
+
+ /**
+ * Set the maximum saturation value for this target.
+ */
+ public Target.Builder setMaximumSaturation(@FloatRange(from = 0, to = 1) float value) {
+ mTarget.mSaturationTargets[INDEX_MAX] = value;
+ return this;
+ }
+
+ /**
+ * Set the minimum lightness value for this target.
+ */
+ public Target.Builder setMinimumLightness(@FloatRange(from = 0, to = 1) float value) {
+ mTarget.mLightnessTargets[INDEX_MIN] = value;
+ return this;
+ }
+
+ /**
+ * Set the target/ideal lightness value for this target.
+ */
+ public Target.Builder setTargetLightness(@FloatRange(from = 0, to = 1) float value) {
+ mTarget.mLightnessTargets[INDEX_TARGET] = value;
+ return this;
+ }
+
+ /**
+ * Set the maximum lightness value for this target.
+ */
+ public Target.Builder setMaximumLightness(@FloatRange(from = 0, to = 1) float value) {
+ mTarget.mLightnessTargets[INDEX_MAX] = value;
+ return this;
+ }
+
+ /**
+ * Set the weight of importance that this target will place on saturation values.
+ *
+ * <p>The larger the weight, relative to the other weights, the more important that a color
+ * being close to the target value has on selection.</p>
+ *
+ * <p>A weight of 0 means that it has no weight, and thus has no
+ * bearing on the selection.</p>
+ *
+ * @see #setTargetSaturation(float)
+ */
+ public Target.Builder setSaturationWeight(@FloatRange(from = 0) float weight) {
+ mTarget.mWeights[INDEX_WEIGHT_SAT] = weight;
+ return this;
+ }
+
+ /**
+ * Set the weight of importance that this target will place on lightness values.
+ *
+ * <p>The larger the weight, relative to the other weights, the more important that a color
+ * being close to the target value has on selection.</p>
+ *
+ * <p>A weight of 0 means that it has no weight, and thus has no
+ * bearing on the selection.</p>
+ *
+ * @see #setTargetLightness(float)
+ */
+ public Target.Builder setLightnessWeight(@FloatRange(from = 0) float weight) {
+ mTarget.mWeights[INDEX_WEIGHT_LUMA] = weight;
+ return this;
+ }
+
+ /**
+ * Set the weight of importance that this target will place on a color's population within
+ * the image.
+ *
+ * <p>The larger the weight, relative to the other weights, the more important that a
+ * color's population being close to the most populous has on selection.</p>
+ *
+ * <p>A weight of 0 means that it has no weight, and thus has no
+ * bearing on the selection.</p>
+ */
+ public Target.Builder setPopulationWeight(@FloatRange(from = 0) float weight) {
+ mTarget.mWeights[INDEX_WEIGHT_POP] = weight;
+ return this;
+ }
+
+ /**
+ * Set whether any color selected for this target is exclusive to this target only.
+ * Defaults to true.
+ *
+ * @param exclusive true if any the color is exclusive to this target, or false is the
+ * color can be selected for other targets.
+ */
+ public Target.Builder setExclusive(boolean exclusive) {
+ mTarget.mIsExclusive = exclusive;
+ return this;
+ }
+
+ /**
+ * Builds and returns the resulting {@link Target}.
+ */
+ public Target build() {
+ return mTarget;
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/core/java/com/android/internal/os/WebViewZygoteInit.java b/core/java/com/android/internal/os/WebViewZygoteInit.java
index cc3f58c..e28079f 100644
--- a/core/java/com/android/internal/os/WebViewZygoteInit.java
+++ b/core/java/com/android/internal/os/WebViewZygoteInit.java
@@ -70,6 +70,7 @@
@Override
protected boolean handlePreloadPackage(String packagePath, String libsPath,
String cacheKey) {
+ Log.i(TAG, "Beginning package preload");
// Ask ApplicationLoaders to create and cache a classloader for the WebView APK so that
// our children will reuse the same classloader instead of creating their own.
// This enables us to preload Java and native code in the webview zygote process and
@@ -97,6 +98,7 @@
IllegalAccessException | InvocationTargetException e) {
Log.e(TAG, "Exception while preloading package", e);
}
+ Log.i(TAG, "Package preload done");
return false;
}
}
diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java
index baf6db9..80b6b08 100644
--- a/core/java/com/android/internal/policy/DecorView.java
+++ b/core/java/com/android/internal/policy/DecorView.java
@@ -82,6 +82,7 @@
import static android.app.ActivityManager.StackId;
import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
+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.INVALID_STACK_ID;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
@@ -2182,19 +2183,22 @@
final boolean wasAdjustedForStack = mElevationAdjustedForStack;
// Do not use a shadow when we are in resizing mode (mBackdropFrameRenderer not null)
// since the shadow is bound to the content size and not the target size.
- if (StackId.hasWindowShadow(mStackId) && !isResizing()) {
+ if ((mStackId == FREEFORM_WORKSPACE_STACK_ID) && !isResizing()) {
elevation = hasWindowFocus() ?
DECOR_SHADOW_FOCUSED_HEIGHT_IN_DIP : DECOR_SHADOW_UNFOCUSED_HEIGHT_IN_DIP;
// Add a maximum shadow height value to the top level view.
// Note that pinned stack doesn't have focus
// so maximum shadow height adjustment isn't needed.
// TODO(skuhne): Remove this if clause once b/22668382 got fixed.
- if (!mAllowUpdateElevation && mStackId != PINNED_STACK_ID) {
+ if (!mAllowUpdateElevation) {
elevation = DECOR_SHADOW_FOCUSED_HEIGHT_IN_DIP;
}
// Convert the DP elevation into physical pixels.
elevation = dipToPx(elevation);
mElevationAdjustedForStack = true;
+ } else if (mStackId == PINNED_STACK_ID) {
+ elevation = dipToPx(DECOR_SHADOW_UNFOCUSED_HEIGHT_IN_DIP);
+ mElevationAdjustedForStack = true;
} else {
mElevationAdjustedForStack = false;
}
diff --git a/core/java/com/android/internal/widget/FloatingToolbar.java b/core/java/com/android/internal/widget/FloatingToolbar.java
index 818cc2c..8c71cf7 100644
--- a/core/java/com/android/internal/widget/FloatingToolbar.java
+++ b/core/java/com/android/internal/widget/FloatingToolbar.java
@@ -233,6 +233,7 @@
private void doShow() {
List<MenuItem> menuItems = getVisibleAndEnabledMenuItems(mMenu);
+ tidy(menuItems);
if (!isCurrentlyShowing(menuItems) || mWidthChanged) {
mPopup.dismiss();
mPopup.layoutMenuItems(menuItems, mMenuItemClickListener, mSuggestedWidth);
@@ -274,6 +275,36 @@
return menuItems;
}
+ /**
+ * Update the list of menu items to conform to certain requirements.
+ */
+ private void tidy(List<MenuItem> menuItems) {
+ int assistItemIndex = -1;
+ Drawable assistItemDrawable = null;
+
+ final int size = menuItems.size();
+ for (int i = 0; i < size; i++) {
+ final MenuItem menuItem = menuItems.get(i);
+
+ if (menuItem.getItemId() == android.R.id.textAssist) {
+ assistItemIndex = i;
+ assistItemDrawable = menuItem.getIcon();
+ }
+
+ // Remove icons for all menu items with text.
+ if (!TextUtils.isEmpty(menuItem.getTitle())) {
+ menuItem.setIcon(null);
+ }
+ }
+ if (assistItemIndex > -1) {
+ final MenuItem assistMenuItem = menuItems.remove(assistItemIndex);
+ // Ensure the assist menu item preserves its icon.
+ assistMenuItem.setIcon(assistItemDrawable);
+ // Ensure the assist menu item is always the first item.
+ menuItems.add(0, assistMenuItem);
+ }
+ }
+
private List<Object> getShowingMenuItemsReferences(List<MenuItem> menuItems) {
List<Object> references = new ArrayList<Object>();
for (MenuItem menuItem : menuItems) {
diff --git a/core/java/com/android/server/BootReceiver.java b/core/java/com/android/server/BootReceiver.java
index 4a9a2c5..c1f443f 100644
--- a/core/java/com/android/server/BootReceiver.java
+++ b/core/java/com/android/server/BootReceiver.java
@@ -35,6 +35,7 @@
import android.util.Slog;
import android.util.Xml;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FastXmlSerializer;
@@ -89,6 +90,11 @@
// example: fs_stat,/dev/block/platform/soc/by-name/userdata,0x5
private static final String FS_STAT_PATTERN = "fs_stat,[^,]*/([^/,]+),(0x[0-9a-fA-F]+)";
+ private static final int FS_STAT_FS_FIXED = 0x400; // should match with fs_mgr.cpp:FsStatFlags
+ private static final String FSCK_PASS_PATTERN = "Pass ([1-9]E?):";
+ private static final String FSCK_TREE_OPTIMIZATION_PATTERN =
+ "Inode [0-9]+ extent tree.*could be shorter";
+ private static final String FSCK_FS_MODIFIED = "FILE SYSTEM WAS MODIFIED";
// ro.boottime.init.mount_all. + postfix for mount_all duration
private static final String[] MOUNT_DURATION_PROPS_POSTFIX =
new String[] { "early", "default", "late" };
@@ -334,17 +340,22 @@
String log = FileUtils.readTextFile(file, maxSize, "[[TRUNCATED]]\n");
Pattern pattern = Pattern.compile(FS_STAT_PATTERN);
- for (String line : log.split("\n")) { // should check all lines
- if (line.contains("FILE SYSTEM WAS MODIFIED")) {
+ String lines[] = log.split("\n");
+ int lineNumber = 0;
+ int lastFsStatLineNumber = 0;
+ for (String line : lines) { // should check all lines
+ if (line.contains(FSCK_FS_MODIFIED)) {
uploadNeeded = true;
} else if (line.contains("fs_stat")){
Matcher matcher = pattern.matcher(line);
if (matcher.find()) {
- handleFsckFsStat(matcher);
+ handleFsckFsStat(matcher, lines, lastFsStatLineNumber, lineNumber);
+ lastFsStatLineNumber = lineNumber;
} else {
Slog.w(TAG, "cannot parse fs_stat:" + line);
}
}
+ lineNumber++;
}
if (uploadEnabled && uploadNeeded ) {
@@ -403,7 +414,88 @@
}
}
- private static void handleFsckFsStat(Matcher match) {
+ /**
+ * Fix fs_stat from e2fsck.
+ * For now, only handle the case of quota warning caused by tree optimization. Clear fs fix
+ * flag (=0x400) caused by that.
+ *
+ * @param partition partition name
+ * @param statOrg original stat reported from e2fsck log
+ * @param lines e2fsck logs broken down into lines
+ * @param startLineNumber start line to parse
+ * @param endLineNumber end line. exclusive.
+ * @return updated fs_stat. For tree optimization, will clear bit 0x400.
+ */
+ @VisibleForTesting
+ public static int fixFsckFsStat(String partition, int statOrg, String[] lines,
+ int startLineNumber, int endLineNumber) {
+ int stat = statOrg;
+ if ((stat & FS_STAT_FS_FIXED) != 0) {
+ // fs was fixed. should check if quota warning was caused by tree optimization.
+ // This is not a real fix but optimization, so should not be counted as a fs fix.
+ Pattern passPattern = Pattern.compile(FSCK_PASS_PATTERN);
+ Pattern treeOptPattern = Pattern.compile(FSCK_TREE_OPTIMIZATION_PATTERN);
+ String currentPass = "";
+ boolean foundTreeOptimization = false;
+ boolean foundQuotaFix = false;
+ boolean foundOtherFix = false;
+ String otherFixLine = null;
+ for (int i = startLineNumber; i < endLineNumber; i++) {
+ String line = lines[i];
+ if (line.contains(FSCK_FS_MODIFIED)) { // no need to parse above this
+ break;
+ } else if (line.startsWith("Pass ")) {
+ Matcher matcher = passPattern.matcher(line);
+ if (matcher.find()) {
+ currentPass = matcher.group(1);
+ }
+ } else if (line.startsWith("Inode ")) {
+ Matcher matcher = treeOptPattern.matcher(line);
+ if (matcher.find() && currentPass.equals("1")) {
+ foundTreeOptimization = true;
+ Slog.i(TAG, "fs_stat, partition:" + partition + " found tree optimization:"
+ + line);
+ } else {
+ foundOtherFix = true;
+ otherFixLine = line;
+ break;
+ }
+ } else if (line.startsWith("[QUOTA WARNING]") && currentPass.equals("5")) {
+ Slog.i(TAG, "fs_stat, partition:" + partition + " found quota warning:"
+ + line);
+ foundQuotaFix = true;
+ if (!foundTreeOptimization) { // only quota warning, this is real fix.
+ otherFixLine = line;
+ break;
+ }
+ } else if (line.startsWith("Update quota info") && currentPass.equals("5")) {
+ // follows "[QUOTA WARNING]", ignore
+ } else {
+ line = line.trim();
+ // ignore empty msg or any msg before Pass 1
+ if (!line.isEmpty() && !currentPass.isEmpty()) {
+ foundOtherFix = true;
+ otherFixLine = line;
+ break;
+ }
+ }
+ }
+ if (!foundOtherFix && foundTreeOptimization && foundQuotaFix) {
+ // not a real fix, so clear it.
+ Slog.i(TAG, "fs_stat, partition:" + partition +
+ " quota fix due to tree optimization");
+ stat &= ~FS_STAT_FS_FIXED;
+ } else {
+ if (otherFixLine != null) {
+ Slog.i(TAG, "fs_stat, partition:" + partition + " fix:" + otherFixLine);
+ }
+ }
+ }
+ return stat;
+ }
+
+ private static void handleFsckFsStat(Matcher match, String[] lines, int startLineNumber,
+ int endLineNumber) {
String partition = match.group(1);
int stat;
try {
@@ -412,9 +504,9 @@
Slog.w(TAG, "cannot parse fs_stat: partition:" + partition + " stat:" + match.group(2));
return;
}
-
+ stat = fixFsckFsStat(partition, stat, lines, startLineNumber, endLineNumber);
MetricsLogger.histogram(null, "boot_fs_stat_" + partition, stat);
- Slog.i(TAG, "fs_stat, partition:" + partition + " stat:" + match.group(2));
+ Slog.i(TAG, "fs_stat, partition:" + partition + " stat:0x" + Integer.toHexString(stat));
}
private static HashMap<String, Long> readTimestamps() {
diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp
index 1aed501..de67c50 100644
--- a/core/jni/android_util_Binder.cpp
+++ b/core/jni/android_util_Binder.cpp
@@ -193,10 +193,10 @@
if (env->IsInstanceOf(excep, gErrorOffsets.mClass)) {
/*
* It's an Error: Reraise the exception and ask the runtime to abort.
- * This will dump the pending exception as well as all thread traces
- * to the log.
*/
env->Throw(excep);
+ ALOGE("java.lang.Error thrown during binder transaction (stack trace follows) : ");
+ env->ExceptionDescribe();
env->FatalError("java.lang.Error thrown during binder transaction.");
}
diff --git a/core/proto/android/os/incident.proto b/core/proto/android/os/incident.proto
index a2f07d9..2042bf62 100644
--- a/core/proto/android/os/incident.proto
+++ b/core/proto/android/os/incident.proto
@@ -21,10 +21,14 @@
import "frameworks/base/libs/incident/proto/android/privacy.proto";
import "frameworks/base/core/proto/android/service/appwidget.proto";
+import "frameworks/base/core/proto/android/service/battery.proto";
import "frameworks/base/core/proto/android/service/graphicsstats.proto";
import "frameworks/base/core/proto/android/service/fingerprint.proto";
+import "frameworks/base/core/proto/android/service/diskstats.proto";
import "frameworks/base/core/proto/android/service/netstats.proto";
import "frameworks/base/core/proto/android/service/notification.proto";
+import "frameworks/base/core/proto/android/service/package.proto";
+import "frameworks/base/core/proto/android/service/power.proto";
import "frameworks/base/core/proto/android/providers/settings.proto";
package android.os;
@@ -57,6 +61,10 @@
android.service.NetworkStatsServiceDumpProto netstats = 3001;
android.providers.settings.SettingsServiceDumpProto settings = 3002;
android.service.appwidget.AppWidgetServiceDumpProto appwidget = 3003;
+ android.service.battery.BatteryServiceDumpProto battery = 3006;
+ android.service.diskstats.DiskStatsServiceDumpProto diskstats = 3007;
android.service.notification.NotificationServiceDumpProto notification = 3004;
+ android.service.pm.PackageServiceDumpProto package = 3008;
+ android.service.power.PowerServiceDumpProto power = 3009;
android.service.GraphicsStatsServiceDumpProto graphicsstats = 3005;
}
diff --git a/core/res/res/drawable/ic_corp_badge.xml b/core/res/res/drawable/ic_corp_badge.xml
deleted file mode 100644
index 8917431..0000000
--- a/core/res/res/drawable/ic_corp_badge.xml
+++ /dev/null
@@ -1,33 +0,0 @@
-<!--
-Copyright (C) 2014 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="20.0dp"
- android:height="20.0dp"
- android:viewportWidth="20.0"
- android:viewportHeight="20.0">
- <path
- android:pathData="M10.0,10.0m-10.0,0.0a10.0,10.0 0.0,1.0 1.0,20.0 0.0a10.0,10.0 0.0,1.0 1.0,-20.0 0.0"
- android:fillColor="#FF5722"/>
- <path
- android:pathData="M15.2,6.2L4.8,6.2c-0.5,0.0 -0.9,0.4 -0.9,1.0L3.9,10.0c0.0,0.5 0.4,1.0 0.9,1.0l3.8,0.0l0.0,-1.0l2.9,0.0l0.0,1.0l3.8,0.0c0.5,0.0 1.0,-0.4 1.0,-1.0L16.3,7.1C16.2,6.6 15.8,6.2 15.2,6.2z"
- android:fillColor="#FFFFFF"/>
- <path
- android:pathData="M8.6,12.9l0.0,-1.0L4.3,11.9l0.0,2.4c0.0,0.5 0.4,0.9 0.9,0.9l9.5,0.0c0.5,0.0 0.9,-0.4 0.9,-0.9l0.0,-2.4l-4.3,0.0l0.0,1.0L8.6,12.9z"
- android:fillColor="#FFFFFF"/>
- <path
- android:pathData="M7.1,5.2l0.0,1.0 1.0,0.0 0.0,-1.0 3.799999,0.0 0.0,1.0 1.0,0.0 0.0,-1.0 -1.0,-0.9 -3.799999,0.0z"
- android:fillColor="#FFFFFF"/>
-</vector>
diff --git a/core/res/res/drawable/ic_instant_icon_badge_bolt.xml b/core/res/res/drawable/ic_instant_icon_badge_bolt.xml
new file mode 100644
index 0000000..08512b7
--- /dev/null
+++ b/core/res/res/drawable/ic_instant_icon_badge_bolt.xml
@@ -0,0 +1,29 @@
+<!--
+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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="64.0dp"
+ android:height="64.0dp"
+ android:viewportWidth="64.0"
+ android:viewportHeight="64.0">
+
+ <!--
+ The path is similar to ic_corp_icon_badge_case, such that it positions on top of
+ ic_corp_icon_badge_shadow.
+ -->
+ <path
+ android:pathData="M43.9 50.9h4v8l6-12h-4v-8l-6 12z"
+ android:fillColor="#757575"/>
+</vector>
diff --git a/core/res/res/drawable/ic_picture_in_picture.xml b/core/res/res/drawable/ic_picture_in_picture.xml
new file mode 100644
index 0000000..e2dda33
--- /dev/null
+++ b/core/res/res/drawable/ic_picture_in_picture.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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+
+ <path
+ android:fillColor="#FFFFFF"
+ android:pathData="M19 11h-8v6h8v-6zm4 8V4.98C23 3.88 22.1 3 21 3H3c-1.1 0-2 .88-2 1.98V19c0 1.1 .9
+2 2 2h18c1.1 0 2-.9 2-2zm-2 .02H3V4.97h18v14.05z" />
+ <path
+ android:pathData="M0 0h24v24H0V0z" />
+</vector>
\ No newline at end of file
diff --git a/core/res/res/layout/accessibility_button_chooser.xml b/core/res/res/layout/accessibility_button_chooser.xml
index 0ef785f..480defb 100644
--- a/core/res/res/layout/accessibility_button_chooser.xml
+++ b/core/res/res/layout/accessibility_button_chooser.xml
@@ -19,7 +19,7 @@
<com.android.internal.widget.ResolverDrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
- android:layout_height="match_parent"
+ android:layout_height="wrap_content"
android:maxWidth="@dimen/resolver_max_width"
android:maxCollapsedHeight="256dp"
android:maxCollapsedHeightSmall="56dp"
@@ -27,11 +27,14 @@
<LinearLayout
android:layout_width="match_parent"
- android:layout_height="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_alwaysShow="true"
android:orientation="vertical"
android:background="?attr/colorBackground"
android:paddingTop="8dp"
- android:paddingBottom="8dp">
+ android:paddingBottom="8dp"
+ android:paddingStart="?attr/dialogPreferredPadding"
+ android:paddingEnd="?attr/dialogPreferredPadding">
<TextView
android:layout_width="match_parent"
@@ -41,8 +44,6 @@
android:text="@string/accessibility_button_prompt_text"
android:gravity="start|center_vertical"
android:layout_alignParentStart="true"
- android:paddingStart="?attr/dialogPreferredPadding"
- android:paddingEnd="?attr/dialogPreferredPadding"
android:paddingTop="8dp"
android:paddingBottom="8dp"/>
@@ -55,20 +56,15 @@
android:verticalSpacing="10dp"
android:horizontalSpacing="10dp"
android:stretchMode="columnWidth"
- android:paddingStart="?attr/dialogPreferredPadding"
- android:paddingEnd="?attr/dialogPreferredPadding"
android:gravity="center"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/accessibility_button_prompt"
- android:layout_alwaysShow="true"
android:textAppearance="?attr/textAppearanceMedium"
android:text="@string/accessibility_button_instructional_text"
android:gravity="start|center_vertical"
- android:paddingStart="?attr/dialogPreferredPadding"
- android:paddingEnd="?attr/dialogPreferredPadding"
android:paddingTop="8dp"
android:paddingBottom="8dp"
android:visibility="gone"/>
diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml
index 937fc6f..536906c 100644
--- a/core/res/res/values/colors.xml
+++ b/core/res/res/values/colors.xml
@@ -171,6 +171,9 @@
<color name="profile_badge_2">#ff000000</color><!-- Black -->
<color name="profile_badge_3">#ff22f033</color><!-- Green -->
+ <!-- Default instant app badge color -->
+ <color name="instant_app_badge">#FFFFFFFF</color><!-- White -->
+
<!-- Multi-sim sim colors -->
<color name="Teal_700">#ff00796b</color>
<color name="Teal_800">#ff00695c</color>
diff --git a/core/res/res/values/colors_material.xml b/core/res/res/values/colors_material.xml
index e0cc5b5..8c37d4b 100644
--- a/core/res/res/values/colors_material.xml
+++ b/core/res/res/values/colors_material.xml
@@ -74,10 +74,10 @@
<item name="disabled_alpha_material_light" format="float" type="dimen">0.26</item>
<item name="disabled_alpha_material_dark" format="float" type="dimen">0.30</item>
- <item name="primary_content_alpha_material_light" format="float" type="dimen">1</item>
- <item name="primary_content_alpha_material_dark" format="float" type="dimen">0.87</item>
- <item name="secondary_content_alpha_material_light" format="float" type="dimen">.7</item>
- <item name="secondary_content_alpha_material_dark" format="float" type="dimen">0.54</item>
+ <item name="primary_content_alpha_material_dark" format="float" type="dimen">1</item>
+ <item name="primary_content_alpha_material_light" format="float" type="dimen">0.87</item>
+ <item name="secondary_content_alpha_material_dark" format="float" type="dimen">.7</item>
+ <item name="secondary_content_alpha_material_light" format="float" type="dimen">0.54</item>
<item name="highlight_alpha_material_light" format="float" type="dimen">0.12</item>
<item name="highlight_alpha_material_dark" format="float" type="dimen">0.20</item>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 45dccb6..e85f560 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -852,6 +852,8 @@
1 - Go to sleep (doze)
2 - Really go to sleep (don't doze)
3 - Really go to sleep and go home (don't doze)
+ 4 - Go to home
+ 5 - Dismiss IME if shown. Otherwise go to home
-->
<integer name="config_shortPressOnPowerBehavior">1</integer>
@@ -2794,7 +2796,7 @@
<string name="config_defaultCellBroadcastReceiverPkg" translatable="false">com.android.cellbroadcastreceiver</string>
<!-- Specifies the path that is used by AdaptiveIconDrawable class to crop launcher icons. -->
- <string name="config_icon_mask" translatable="false">"M50,0L92,0 A8,8,0,0 1 100,8 L100,92 A8,8,0,0 1 92,100 L8,100 A8,8,0,0 1 0,92 L 0,8 A8,8,0,0 1 8,0z"</string>
+ <string name="config_icon_mask" translatable="false">"M50,0L92,0C96.42,0 100,4.58 100 8L100,92C100, 96.42 96.42 100 92 100L8 100C4.58, 100 0 96.42 0 92L0 8 C 0 4.42 4.42 0 8 0L50 0Z"</string>
<!-- The component name, flattened to a string, for the default accessibility service to be
enabled by the accessibility shortcut. This service must be trusted, as it can be activated
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 9e98efd..89c912fd 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2834,6 +2834,7 @@
<public-group type="drawable" first-id="0x010800b4">
<public name="autofilled_highlight" />
+ <public name="ic_picture_in_picture" />
</public-group>
<public-group type="string" first-id="0x01040019">
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index f2da845..907294d 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1322,6 +1322,7 @@
<java-symbol type="drawable" name="ic_corp_user_badge" />
<java-symbol type="drawable" name="ic_corp_badge_no_background" />
<java-symbol type="drawable" name="ic_corp_statusbar_icon" />
+ <java-symbol type="drawable" name="ic_instant_icon_badge_bolt" />
<java-symbol type="drawable" name="emulator_circular_window_overlay" />
<java-symbol type="drawable" name="sim_light_blue" />
@@ -1353,6 +1354,7 @@
<java-symbol type="color" name="profile_badge_1" />
<java-symbol type="color" name="profile_badge_2" />
<java-symbol type="color" name="profile_badge_3" />
+ <java-symbol type="color" name="instant_app_badge" />
<java-symbol type="layout" name="action_bar_home" />
<java-symbol type="layout" name="action_bar_title_item" />
diff --git a/core/res/res/values/themes_device_defaults.xml b/core/res/res/values/themes_device_defaults.xml
index e0b4ec5..d80ff1d 100644
--- a/core/res/res/values/themes_device_defaults.xml
+++ b/core/res/res/values/themes_device_defaults.xml
@@ -794,6 +794,8 @@
<!-- Theme used for the intent picker activity. -->
<style name="Theme.DeviceDefault.Resolver" parent="Theme.Material.Light">
+ <item name="windowEnterTransition">@empty</item>
+ <item name="windowExitTransition">@empty</item>
<item name="windowIsTranslucent">true</item>
<item name="windowNoTitle">true</item>
<item name="windowBackground">@color/transparent</item>
diff --git a/core/tests/coretests/src/android/metrics/LogMakerTest.java b/core/tests/coretests/src/android/metrics/LogMakerTest.java
index bab9f63..63c1f87 100644
--- a/core/tests/coretests/src/android/metrics/LogMakerTest.java
+++ b/core/tests/coretests/src/android/metrics/LogMakerTest.java
@@ -263,4 +263,32 @@
assertFalse(a.isSubsetOf(b));
assertFalse(b.isSubsetOf(a));
}
+
+ public void testConstructFromNull() {
+ new LogMaker(null);
+ // no promises, just don't throw
+ }
+
+ public void testConstructFromNullKey() {
+ Object[] items = new Object[2];
+ items[0] = null;
+ items[1] = "foo";
+ new LogMaker(items);
+ // no promises, just don't throw
+ }
+
+ public void testConstructFromNullField() {
+ Object[] items = new Object[2];
+ items[0] = 10;
+ items[1] = null;
+ new LogMaker(items);
+ // no promises, just don't throw
+ }
+
+ public void testConstructFromTruncatedArray() {
+ Object[] items = new Object[1];
+ items[0] = 10;
+ new LogMaker(items);
+ // no promises, just don't throw
+ }
}
diff --git a/core/tests/coretests/src/android/view/textclassifier/TextClassificationManagerTest.java b/core/tests/coretests/src/android/view/textclassifier/TextClassificationManagerTest.java
new file mode 100644
index 0000000..39b999d
--- /dev/null
+++ b/core/tests/coretests/src/android/view/textclassifier/TextClassificationManagerTest.java
@@ -0,0 +1,237 @@
+/*
+ * 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.textclassifier;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.mock;
+
+import android.os.LocaleList;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.hamcrest.BaseMatcher;
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+import java.util.Locale;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class TextClassificationManagerTest {
+
+ private static final LocaleList LOCALES = LocaleList.forLanguageTags("en");
+
+ private TextClassificationManager mTcm;
+ private TextClassifier mClassifier;
+
+ @Before
+ public void setup() {
+ mTcm = InstrumentationRegistry.getTargetContext()
+ .getSystemService(TextClassificationManager.class);
+ mTcm.setTextClassifier(null);
+ mClassifier = mTcm.getTextClassifier();
+ }
+
+ @Test
+ public void testSmartSelection() {
+ if (isTextClassifierDisabled()) return;
+
+ String text = "Contact me at droid@android.com";
+ String selected = "droid";
+ String suggested = "droid@android.com";
+ int startIndex = text.indexOf(selected);
+ int endIndex = startIndex + selected.length();
+ int smartStartIndex = text.indexOf(suggested);
+ int smartEndIndex = smartStartIndex + suggested.length();
+
+ assertThat(mClassifier.suggestSelection(text, startIndex, endIndex, LOCALES),
+ isTextSelection(smartStartIndex, smartEndIndex, TextClassifier.TYPE_EMAIL));
+ }
+
+ @Test
+ public void testSmartSelection_nullLocaleList() {
+ if (isTextClassifierDisabled()) return;
+
+ String text = "Contact me at droid@android.com";
+ String selected = "droid";
+ String suggested = "droid@android.com";
+ int startIndex = text.indexOf(selected);
+ int endIndex = startIndex + selected.length();
+ int smartStartIndex = text.indexOf(suggested);
+ int smartEndIndex = smartStartIndex + suggested.length();
+ LocaleList nullLocales = null;
+
+ assertThat(mClassifier.suggestSelection(text, startIndex, endIndex, nullLocales),
+ isTextSelection(smartStartIndex, smartEndIndex, TextClassifier.TYPE_EMAIL));
+ }
+
+ @Test
+ public void testSmartSelection_url() {
+ if (isTextClassifierDisabled()) return;
+
+ String text = "Visit http://www.android.com for more information";
+ String selected = "http";
+ String suggested = "http://www.android.com";
+ int startIndex = text.indexOf(selected);
+ int endIndex = startIndex + selected.length();
+ int smartStartIndex = text.indexOf(suggested);
+ int smartEndIndex = smartStartIndex + suggested.length();
+
+ assertThat(mClassifier.suggestSelection(text, startIndex, endIndex, LOCALES),
+ isTextSelection(smartStartIndex, smartEndIndex, TextClassifier.TYPE_URL));
+ }
+
+ @Test
+ public void testTextClassificationResult() {
+ if (isTextClassifierDisabled()) return;
+
+ String text = "Contact me at droid@android.com";
+ String classifiedText = "droid@android.com";
+ int startIndex = text.indexOf(classifiedText);
+ int endIndex = startIndex + classifiedText.length();
+ assertThat(mClassifier.getTextClassificationResult(text, startIndex, endIndex, LOCALES),
+ isTextClassificationResult(classifiedText, TextClassifier.TYPE_EMAIL));
+ }
+
+ @Test
+ public void testTextClassificationResult_url() {
+ if (isTextClassifierDisabled()) return;
+
+ String text = "Visit http://www.android.com for more information";
+ String classifiedText = "http://www.android.com";
+ int startIndex = text.indexOf(classifiedText);
+ int endIndex = startIndex + classifiedText.length();
+ assertThat(mClassifier.getTextClassificationResult(text, startIndex, endIndex, LOCALES),
+ isTextClassificationResult(classifiedText, TextClassifier.TYPE_URL));
+ }
+
+ @Test
+ public void testTextClassificationResult_nullLocaleList() {
+ if (isTextClassifierDisabled()) return;
+
+ String text = "Contact me at droid@android.com";
+ String classifiedText = "droid@android.com";
+ int startIndex = text.indexOf(classifiedText);
+ int endIndex = startIndex + classifiedText.length();
+ LocaleList nullLocales = null;
+ assertThat(mClassifier.getTextClassificationResult(text, startIndex, endIndex, nullLocales),
+ isTextClassificationResult(classifiedText, TextClassifier.TYPE_EMAIL));
+ }
+
+ @Test
+ public void testLanguageDetection() {
+ if (isTextClassifierDisabled()) return;
+
+ String text = "This is a piece of English text";
+ assertThat(mTcm.detectLanguages(text), isDetectedLanguage("en"));
+
+ text = "Das ist ein deutscher Text";
+ assertThat(mTcm.detectLanguages(text), isDetectedLanguage("de"));
+
+ text = "これは日本語のテキストです";
+ assertThat(mTcm.detectLanguages(text), isDetectedLanguage("ja"));
+ }
+
+ @Test
+ public void testSetTextClassifier() {
+ TextClassifier classifier = mock(TextClassifier.class);
+ mTcm.setTextClassifier(classifier);
+ assertEquals(classifier, mTcm.getTextClassifier());
+ }
+
+ private boolean isTextClassifierDisabled() {
+ return mClassifier == TextClassifier.NO_OP;
+ }
+
+ private static Matcher<TextSelection> isTextSelection(
+ final int startIndex, final int endIndex, final String type) {
+ return new BaseMatcher<TextSelection>() {
+ @Override
+ public boolean matches(Object o) {
+ if (o instanceof TextSelection) {
+ TextSelection selection = (TextSelection) o;
+ return startIndex == selection.getSelectionStartIndex()
+ && endIndex == selection.getSelectionEndIndex()
+ && selection.getEntityCount() > 0
+ && type.equals(selection.getEntity(0));
+ }
+ return false;
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendValue(
+ String.format("%d, %d, %s", startIndex, endIndex, type));
+ }
+ };
+ }
+
+ private static Matcher<TextClassificationResult> isTextClassificationResult(
+ final String text, final String type) {
+ return new BaseMatcher<TextClassificationResult>() {
+ @Override
+ public boolean matches(Object o) {
+ if (o instanceof TextClassificationResult) {
+ TextClassificationResult result = (TextClassificationResult) o;
+ return text.equals(result.getText())
+ && result.getEntityCount() > 0
+ && type.equals(result.getEntity(0));
+ // TODO: Include other properties.
+ }
+ return false;
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("text=").appendValue(text)
+ .appendText(", type=").appendValue(type);
+ }
+ };
+ }
+
+ private static Matcher<List<TextLanguage>> isDetectedLanguage(final String language) {
+ return new BaseMatcher<List<TextLanguage>>() {
+ @Override
+ public boolean matches(Object o) {
+ if (o instanceof List) {
+ List languages = (List) o;
+ if (!languages.isEmpty()) {
+ Object o1 = languages.get(0);
+ if (o1 instanceof TextLanguage) {
+ TextLanguage lang = (TextLanguage) o1;
+ return lang.getLanguageCount() > 0
+ && new Locale(language).getLanguage()
+ .equals(lang.getLanguage(0).getLanguage());
+ }
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendValue(String.format("%s", language));
+ }
+ };
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/TextViewActivityTest.java b/core/tests/coretests/src/android/widget/TextViewActivityTest.java
index 3029134..2203b6a 100644
--- a/core/tests/coretests/src/android/widget/TextViewActivityTest.java
+++ b/core/tests/coretests/src/android/widget/TextViewActivityTest.java
@@ -28,6 +28,7 @@
import static android.widget.espresso.TextViewActions.longPressOnTextAtIndex;
import static android.widget.espresso.TextViewAssertions.hasInsertionPointerAtIndex;
import static android.widget.espresso.TextViewAssertions.hasSelection;
+import static android.widget.espresso.FloatingToolbarEspressoUtils.assertFloatingToolbarItemIndex;
import static android.widget.espresso.FloatingToolbarEspressoUtils.assertFloatingToolbarIsDisplayed;
import static android.widget.espresso.FloatingToolbarEspressoUtils.assertFloatingToolbarIsNotDisplayed;
import static android.widget.espresso.FloatingToolbarEspressoUtils.assertFloatingToolbarContainsItem;
@@ -46,6 +47,11 @@
import static org.hamcrest.Matchers.anyOf;
import static org.hamcrest.Matchers.is;
+import android.view.ActionMode;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.textclassifier.TextClassificationManager;
+import android.view.textclassifier.TextClassifier;
import android.widget.espresso.CustomViewActions.RelativeCoordinatesProvider;
import android.support.test.espresso.action.EspressoKey;
@@ -71,7 +77,8 @@
@Override
public void setUp() throws Exception {
super.setUp();
- getActivity();
+ getActivity().getSystemService(TextClassificationManager.class)
+ .setTextClassifier(TextClassifier.NO_OP);
}
public void testTypedTextIsOnScreen() throws Exception {
@@ -676,4 +683,38 @@
// hasTransientState should return false when selection is created by API.
assertFalse(textView.hasTransientState());
}
+
+ public void testAssistItemIsAtIndexZero() throws Exception {
+ getActivity().getSystemService(TextClassificationManager.class).setTextClassifier(null);
+ final TextView textView = (TextView) getActivity().findViewById(R.id.textview);
+ textView.post(() -> textView.setCustomSelectionActionModeCallback(
+ new ActionMode.Callback() {
+ @Override
+ public boolean onCreateActionMode(ActionMode actionMode, Menu menu) {
+ // Create another item at order position 0 to confirm that it will never be
+ // placed before the textAssist item.
+ menu.add(Menu.NONE, 0 /* id */, 0 /* order */, "Test");
+ return true;
+ }
+
+ @Override
+ public boolean onPrepareActionMode(ActionMode actionMode, Menu menu) {
+ return true;
+ }
+
+ @Override
+ public boolean onActionItemClicked(ActionMode actionMode, MenuItem menuItem) {
+ return false;
+ }
+
+ @Override
+ public void onDestroyActionMode(ActionMode actionMode) {}
+ }));
+ final String text = "droid@android.com";
+
+ onView(withId(R.id.textview)).perform(replaceText(text));
+ onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('@')));
+ sleepForFloatingToolbarPopup();
+ assertFloatingToolbarItemIndex(android.R.id.textAssist, 0);
+ }
}
diff --git a/core/tests/coretests/src/android/widget/espresso/FloatingToolbarEspressoUtils.java b/core/tests/coretests/src/android/widget/espresso/FloatingToolbarEspressoUtils.java
index 838f4db..5206c9b 100644
--- a/core/tests/coretests/src/android/widget/espresso/FloatingToolbarEspressoUtils.java
+++ b/core/tests/coretests/src/android/widget/espresso/FloatingToolbarEspressoUtils.java
@@ -29,7 +29,13 @@
import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.is;
+import android.view.MenuItem;
+import android.view.ViewGroup;
+import java.util.ArrayList;
+import java.util.List;
+import org.hamcrest.Description;
import org.hamcrest.Matcher;
+import org.hamcrest.TypeSafeMatcher;
import android.support.test.espresso.NoMatchingRootException;
import android.support.test.espresso.NoMatchingViewException;
@@ -123,6 +129,39 @@
}
/**
+ * Asserts that the floating toolbar contains a specified item at a specified index.
+ *
+ * @param menuItemId id of the menu item
+ * @param index expected index of the menu item in the floating toolbar
+ * @throws AssertionError if the assertion fails
+ */
+ public static void assertFloatingToolbarItemIndex(final int menuItemId, final int index) {
+ onFloatingToolBar().check(matches(new TypeSafeMatcher<View>() {
+ private List<Integer> menuItemIds = new ArrayList<>();
+
+ @Override
+ public boolean matchesSafely(View view) {
+ collectMenuItemIds(view);
+ return menuItemIds.size() > index && menuItemIds.get(index) == menuItemId;
+ }
+
+ @Override
+ public void describeTo(Description description) {}
+
+ private void collectMenuItemIds(View view) {
+ if (view.getTag() instanceof MenuItem) {
+ menuItemIds.add(((MenuItem) view.getTag()).getItemId());
+ } else if (view instanceof ViewGroup) {
+ ViewGroup viewGroup = (ViewGroup) view;
+ for (int i = 0; i < viewGroup.getChildCount(); i++) {
+ collectMenuItemIds(viewGroup.getChildAt(i));
+ }
+ }
+ }
+ }));
+ }
+
+ /**
* Asserts that the floating toolbar doesn't contain the specified item.
*
* @param itemLabel label of the item.
diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java
index 7a8e487..abdab39 100644
--- a/graphics/java/android/graphics/Bitmap.java
+++ b/graphics/java/android/graphics/Bitmap.java
@@ -916,17 +916,20 @@
* @param hasAlpha If the bitmap is ARGB_8888 or RGBA_16F this flag can be used to
* mark the bitmap as opaque. Doing so will clear the bitmap in black
* instead of transparent.
- * @param colorSpace The color space of the bitmap. If null or if the config is not
- * {@link Config#ARGB_8888}, {@link ColorSpace.Named#SRGB sRGB} is assumed.
+ * @param colorSpace The color space of the bitmap. If the config is {@link Config#RGBA_F16},
+ * {@link ColorSpace.Named#EXTENDED_SRGB scRGB} is assumed, and if the
+ * config is not {@link Config#ARGB_8888}, {@link ColorSpace.Named#SRGB sRGB}
+ * is assumed.
*
* @throws IllegalArgumentException if the width or height are <= 0, if
* Config is Config.HARDWARE (because hardware bitmaps are always
* immutable), if the specified color space is not {@link ColorSpace.Model#RGB RGB},
- * or if the specified color space's transfer function is not an
- * {@link ColorSpace.Rgb.TransferParameters ICC parametric curve}
+ * if the specified color space's transfer function is not an
+ * {@link ColorSpace.Rgb.TransferParameters ICC parametric curve}, or if
+ * the color space is null
*/
public static Bitmap createBitmap(int width, int height, @NonNull Config config,
- boolean hasAlpha, @Nullable ColorSpace colorSpace) {
+ boolean hasAlpha, @NonNull ColorSpace colorSpace) {
return createBitmap(null, width, height, config, hasAlpha, colorSpace);
}
@@ -950,7 +953,8 @@
*/
public static Bitmap createBitmap(@Nullable DisplayMetrics display, int width, int height,
@NonNull Config config, boolean hasAlpha) {
- return createBitmap(display, width, height, config, hasAlpha, null);
+ return createBitmap(display, width, height, config, hasAlpha,
+ ColorSpace.get(ColorSpace.Named.SRGB));
}
/**
@@ -967,26 +971,34 @@
* @param hasAlpha If the bitmap is ARGB_8888 or RGBA_16F this flag can be used to
* mark the bitmap as opaque. Doing so will clear the bitmap in black
* instead of transparent.
- * @param colorSpace The color space of the bitmap. If null or if the config is not
- * {@link Config#ARGB_8888}, {@link ColorSpace.Named#SRGB sRGB} is assumed.
+ * @param colorSpace The color space of the bitmap. If the config is {@link Config#RGBA_F16},
+ * {@link ColorSpace.Named#EXTENDED_SRGB scRGB} is assumed, and if the
+ * config is not {@link Config#ARGB_8888}, {@link ColorSpace.Named#SRGB sRGB}
+ * is assumed.
*
* @throws IllegalArgumentException if the width or height are <= 0, if
* Config is Config.HARDWARE (because hardware bitmaps are always
* immutable), if the specified color space is not {@link ColorSpace.Model#RGB RGB},
- * or if the specified color space's transfer function is not an
- * {@link ColorSpace.Rgb.TransferParameters ICC parametric curve}
+ * if the specified color space's transfer function is not an
+ * {@link ColorSpace.Rgb.TransferParameters ICC parametric curve}, or if
+ * the color space is null
*/
public static Bitmap createBitmap(@Nullable DisplayMetrics display, int width, int height,
- @NonNull Config config, boolean hasAlpha, @Nullable ColorSpace colorSpace) {
+ @NonNull Config config, boolean hasAlpha, @NonNull ColorSpace colorSpace) {
if (width <= 0 || height <= 0) {
throw new IllegalArgumentException("width and height must be > 0");
}
if (config == Config.HARDWARE) {
throw new IllegalArgumentException("can't create mutable bitmap with Config.HARDWARE");
}
+ if (colorSpace == null) {
+ throw new IllegalArgumentException("can't create bitmap without a color space");
+ }
Bitmap bm;
- if (colorSpace == null || config != Config.ARGB_8888) {
+ // nullptr color spaces have a particular meaning in native and are interpreted as sRGB
+ // (we also avoid the unnecessary extra work of the else branch)
+ if (config != Config.ARGB_8888 || colorSpace == ColorSpace.get(ColorSpace.Named.SRGB)) {
bm = nativeCreate(null, 0, width, width, height, config.nativeInt, true, null, null);
} else {
if (!(colorSpace instanceof ColorSpace.Rgb)) {
diff --git a/graphics/java/android/graphics/BitmapShader.java b/graphics/java/android/graphics/BitmapShader.java
index 7e6756e..5577f53 100644
--- a/graphics/java/android/graphics/BitmapShader.java
+++ b/graphics/java/android/graphics/BitmapShader.java
@@ -41,35 +41,16 @@
* @param tileY The tiling mode for y to draw the bitmap in.
*/
public BitmapShader(@NonNull Bitmap bitmap, @NonNull TileMode tileX, @NonNull TileMode tileY) {
- set(bitmap, tileX, tileY);
+ this(bitmap, tileX.nativeInt, tileY.nativeInt);
}
private BitmapShader(Bitmap bitmap, int tileX, int tileY) {
- setInternal(bitmap, tileX, tileY);
- }
-
- /**
- * Reinitialize the BitmapShader's Bitmap and tile modes.
- *
- * @param bitmap The bitmap to use inside the shader
- * @param tileX The tiling mode for x to draw the bitmap in.
- * @param tileY The tiling mode for y to draw the bitmap in.
- */
- public void set(@NonNull Bitmap bitmap, @NonNull TileMode tileX, @NonNull TileMode tileY) {
- if (tileX == null || tileY == null) {
- throw new IllegalArgumentException();
- }
- setInternal(bitmap, tileX.nativeInt, tileY.nativeInt);
- }
-
- private void setInternal(Bitmap bitmap, int tileX, int tileY) {
if (bitmap == null) {
throw new IllegalArgumentException("Bitmap must be non-null");
}
if (bitmap == mBitmap && tileX == mTileX && tileY == mTileY) {
return;
}
- discardNativeInstance();
mBitmap = bitmap;
mTileX = tileX;
mTileY = tileY;
diff --git a/graphics/java/android/graphics/ColorMatrixColorFilter.java b/graphics/java/android/graphics/ColorMatrixColorFilter.java
index 61f6cc5..9201a2e 100644
--- a/graphics/java/android/graphics/ColorMatrixColorFilter.java
+++ b/graphics/java/android/graphics/ColorMatrixColorFilter.java
@@ -73,6 +73,8 @@
* @see #getColorMatrix(ColorMatrix)
* @see #setColorMatrixArray(float[])
* @see ColorMatrix#reset()
+ *
+ * @hide
*/
public void setColorMatrix(@Nullable ColorMatrix matrix) {
discardNativeInstance();
@@ -99,6 +101,8 @@
*
* @throws ArrayIndexOutOfBoundsException if the specified array's
* length is < 20
+ *
+ * @hide
*/
public void setColorMatrixArray(@Nullable float[] array) {
// called '...Array' so that passing null isn't ambiguous
diff --git a/graphics/java/android/graphics/ComposeShader.java b/graphics/java/android/graphics/ComposeShader.java
index 8438bf2..0b1141a 100644
--- a/graphics/java/android/graphics/ComposeShader.java
+++ b/graphics/java/android/graphics/ComposeShader.java
@@ -59,43 +59,10 @@
}
private ComposeShader(Shader shaderA, Shader shaderB, int nativeMode) {
- setInternal(shaderA, shaderB, nativeMode);
- }
-
- /**
- * Reinitialize the ComposeShader's component Shaders and blend mode.
- *
- * @param shaderA The colors from this shader are seen as the "dst" by the mode
- * @param shaderB The colors from this shader are seen as the "src" by the mode
- * @param mode The PorterDuff mode that combines the colors from the two shaders.
- */
- public void set(@NonNull Shader shaderA, @NonNull Shader shaderB, @NonNull Xfermode mode) {
- setInternal(shaderA, shaderB, mode.porterDuffMode);
- }
-
- /**
- * Reinitialize the ComposeShader's component Shaders and blend mode.
- *
- * @param shaderA The colors from this shader are seen as the "dst" by the mode
- * @param shaderB The colors from this shader are seen as the "src" by the mode
- * @param mode The PorterDuff mode that combines the colors from the two shaders.
- */
- public void set(@NonNull Shader shaderA, @NonNull Shader shaderB,
- @NonNull PorterDuff.Mode mode) {
- setInternal(shaderA, shaderB, mode.nativeInt);
- }
-
- private void setInternal(Shader shaderA, Shader shaderB, int nativeMode) {
if (shaderA == null || shaderB == null) {
throw new IllegalArgumentException("Shader parameters must not be null");
}
- if (shaderA == mShaderA && shaderB == mShaderB && mPorterDuffMode == nativeMode) {
- // no work to do...
- return;
- }
-
- discardNativeInstance();
mShaderA = shaderA;
mShaderB = shaderB;
mPorterDuffMode = nativeMode;
diff --git a/graphics/java/android/graphics/LightingColorFilter.java b/graphics/java/android/graphics/LightingColorFilter.java
index b0c145b..1578ffb 100644
--- a/graphics/java/android/graphics/LightingColorFilter.java
+++ b/graphics/java/android/graphics/LightingColorFilter.java
@@ -57,8 +57,6 @@
/**
* Returns the RGB color used to multiply the source color when the
* color filter is applied.
- *
- * @see #setColorMultiply(int)
*/
@ColorInt
public int getColorMultiply() {
@@ -71,6 +69,8 @@
* The alpha channel of this color is ignored.
*
* @see #getColorMultiply()
+ *
+ * @hide
*/
public void setColorMultiply(@ColorInt int mul) {
if (mMul != mul) {
@@ -82,8 +82,6 @@
/**
* Returns the RGB color that will be added to the source color
* when the color filter is applied.
- *
- * @see #setColorAdd(int)
*/
@ColorInt
public int getColorAdd() {
@@ -96,6 +94,8 @@
* The alpha channel of this color is ignored.
*
* @see #getColorAdd()
+ *
+ * @hide
*/
public void setColorAdd(@ColorInt int add) {
if (mAdd != add) {
diff --git a/graphics/java/android/graphics/LinearGradient.java b/graphics/java/android/graphics/LinearGradient.java
index 0e4cd0a..7139efe 100644
--- a/graphics/java/android/graphics/LinearGradient.java
+++ b/graphics/java/android/graphics/LinearGradient.java
@@ -57,7 +57,20 @@
*/
public LinearGradient(float x0, float y0, float x1, float y1, @NonNull @ColorInt int colors[],
@Nullable float positions[], @NonNull TileMode tile) {
- set(x0, y0, x1, y1, colors, positions, tile);
+ if (colors.length < 2) {
+ throw new IllegalArgumentException("needs >= 2 number of colors");
+ }
+ if (positions != null && colors.length != positions.length) {
+ throw new IllegalArgumentException("color and position arrays must be of equal length");
+ }
+ mType = TYPE_COLORS_AND_POSITIONS;
+ mX0 = x0;
+ mY0 = y0;
+ mX1 = x1;
+ mY1 = y1;
+ mColors = colors.clone();
+ mPositions = positions != null ? positions.clone() : null;
+ mTileMode = tile;
}
/**
@@ -74,56 +87,6 @@
public LinearGradient(float x0, float y0, float x1, float y1,
@ColorInt int color0, @ColorInt int color1,
@NonNull TileMode tile) {
- set(x0, y0, x1, y1, color0, color1, tile);
- }
-
- /**
- * Reinitialize the shader.
- *
- * @param x0 The x-coordinate for the start of the gradient line
- * @param y0 The y-coordinate for the start of the gradient line
- * @param x1 The x-coordinate for the end of the gradient line
- * @param y1 The y-coordinate for the end of the gradient line
- * @param colors The colors to be distributed along the gradient line
- * @param positions May be null. The relative positions [0..1] of
- * each corresponding color in the colors array. If this is null,
- * the the colors are distributed evenly along the gradient line.
- * @param tile The Shader tiling mode
- */
- public void set(float x0, float y0, float x1, float y1, @NonNull @ColorInt int colors[],
- @Nullable float positions[], @NonNull TileMode tile) {
- if (colors.length < 2) {
- throw new IllegalArgumentException("needs >= 2 number of colors");
- }
- if (positions != null && colors.length != positions.length) {
- throw new IllegalArgumentException("color and position arrays must be of equal length");
- }
- discardNativeInstance();
- mType = TYPE_COLORS_AND_POSITIONS;
- mX0 = x0;
- mY0 = y0;
- mX1 = x1;
- mY1 = y1;
- mColors = colors.clone();
- mPositions = positions != null ? positions.clone() : null;
- mTileMode = tile;
- }
-
- /**
- * Reinitialize the shader.
- *
- * @param x0 The x-coordinate for the start of the gradient line
- * @param y0 The y-coordinate for the start of the gradient line
- * @param x1 The x-coordinate for the end of the gradient line
- * @param y1 The y-coordinate for the end of the gradient line
- * @param color0 The color at the start of the gradient line.
- * @param color1 The color at the end of the gradient line.
- * @param tile The Shader tiling mode
- */
- public void set(float x0, float y0, float x1, float y1,
- @ColorInt int color0, @ColorInt int color1,
- @NonNull TileMode tile) {
- discardNativeInstance();
mType = TYPE_COLOR_START_AND_COLOR_END;
mX0 = x0;
mY0 = y0;
diff --git a/graphics/java/android/graphics/PorterDuffColorFilter.java b/graphics/java/android/graphics/PorterDuffColorFilter.java
index ccc6ead..01d5825 100644
--- a/graphics/java/android/graphics/PorterDuffColorFilter.java
+++ b/graphics/java/android/graphics/PorterDuffColorFilter.java
@@ -49,6 +49,8 @@
*
* @see Color
* @see #setColor(int)
+ *
+ * @hide
*/
@ColorInt
public int getColor() {
@@ -64,6 +66,8 @@
* @see Color
* @see #getColor()
* @see #getMode()
+ *
+ * @hide
*/
public void setColor(@ColorInt int color) {
if (mColor != color) {
@@ -78,6 +82,8 @@
*
* @see PorterDuff
* @see #setMode(android.graphics.PorterDuff.Mode)
+ *
+ * @hide
*/
public PorterDuff.Mode getMode() {
return mMode;
@@ -90,6 +96,8 @@
* @see PorterDuff
* @see #getMode()
* @see #getColor()
+ *
+ * @hide
*/
public void setMode(@NonNull PorterDuff.Mode mode) {
if (mode == null) {
diff --git a/graphics/java/android/graphics/RadialGradient.java b/graphics/java/android/graphics/RadialGradient.java
index ae8f7da..f4b1191 100644
--- a/graphics/java/android/graphics/RadialGradient.java
+++ b/graphics/java/android/graphics/RadialGradient.java
@@ -57,7 +57,22 @@
public RadialGradient(float centerX, float centerY, float radius,
@NonNull @ColorInt int colors[], @Nullable float stops[],
@NonNull TileMode tileMode) {
- set(centerX, centerY, radius, colors, stops, tileMode);
+ if (radius <= 0) {
+ throw new IllegalArgumentException("radius must be > 0");
+ }
+ if (colors.length < 2) {
+ throw new IllegalArgumentException("needs >= 2 number of colors");
+ }
+ if (stops != null && colors.length != stops.length) {
+ throw new IllegalArgumentException("color and position arrays must be of equal length");
+ }
+ mType = TYPE_COLORS_AND_POSITIONS;
+ mX = centerX;
+ mY = centerY;
+ mRadius = radius;
+ mColors = colors.clone();
+ mPositions = stops != null ? stops.clone() : null;
+ mTileMode = tileMode;
}
/**
@@ -72,59 +87,9 @@
*/
public RadialGradient(float centerX, float centerY, float radius,
@ColorInt int centerColor, @ColorInt int edgeColor, @NonNull TileMode tileMode) {
- set(centerX, centerY, radius, centerColor, edgeColor, tileMode);
- }
-
- /**
- * Reinitialize the shader.
- *
- * @param centerX The x-coordinate of the center of the radius
- * @param centerY The y-coordinate of the center of the radius
- * @param radius Must be positive. The radius of the circle for this gradient.
- * @param colors The colors to be distributed between the center and edge of the circle
- * @param stops May be <code>null</code>. Valid values are between <code>0.0f</code> and
- * <code>1.0f</code>. The relative position of each corresponding color in
- * the colors array. If <code>null</code>, colors are distributed evenly
- * between the center and edge of the circle.
- * @param tileMode The Shader tiling mode
- */
- public void set(float centerX, float centerY, float radius,
- @NonNull @ColorInt int colors[], @Nullable float stops[], @NonNull TileMode tileMode) {
if (radius <= 0) {
throw new IllegalArgumentException("radius must be > 0");
}
- if (colors.length < 2) {
- throw new IllegalArgumentException("needs >= 2 number of colors");
- }
- if (stops != null && colors.length != stops.length) {
- throw new IllegalArgumentException("color and position arrays must be of equal length");
- }
- discardNativeInstance();
- mType = TYPE_COLORS_AND_POSITIONS;
- mX = centerX;
- mY = centerY;
- mRadius = radius;
- mColors = colors.clone();
- mPositions = stops != null ? stops.clone() : null;
- mTileMode = tileMode;
- }
-
- /**
- * Reinitialize the shader.
- *
- * @param centerX The x-coordinate of the center of the radius
- * @param centerY The y-coordinate of the center of the radius
- * @param radius Must be positive. The radius of the circle for this gradient
- * @param centerColor The color at the center of the circle.
- * @param edgeColor The color at the edge of the circle.
- * @param tileMode The Shader tiling mode
- */
- public void set(float centerX, float centerY, float radius,
- @ColorInt int centerColor, @ColorInt int edgeColor, @NonNull TileMode tileMode) {
- if (radius <= 0) {
- throw new IllegalArgumentException("radius must be > 0");
- }
- discardNativeInstance();
mType = TYPE_COLOR_CENTER_AND_COLOR_EDGE;
mX = centerX;
mY = centerY;
diff --git a/graphics/java/android/graphics/SweepGradient.java b/graphics/java/android/graphics/SweepGradient.java
index 0a1aef6..b6b80b4 100644
--- a/graphics/java/android/graphics/SweepGradient.java
+++ b/graphics/java/android/graphics/SweepGradient.java
@@ -54,7 +54,18 @@
*/
public SweepGradient(float cx, float cy,
@NonNull @ColorInt int colors[], @Nullable float positions[]) {
- set(cx, cy, colors, positions);
+ if (colors.length < 2) {
+ throw new IllegalArgumentException("needs >= 2 number of colors");
+ }
+ if (positions != null && colors.length != positions.length) {
+ throw new IllegalArgumentException(
+ "color and position arrays must be of equal length");
+ }
+ mType = TYPE_COLORS_AND_POSITIONS;
+ mCx = cx;
+ mCy = cy;
+ mColors = colors.clone();
+ mPositions = positions != null ? positions.clone() : null;
}
/**
@@ -66,50 +77,6 @@
* @param color1 The color to use at the end of the sweep
*/
public SweepGradient(float cx, float cy, @ColorInt int color0, @ColorInt int color1) {
- set(cx, cy, color0, color1);
- }
-
- /**
- * Reinitialize the shader.
- *
- * @param cx The x-coordinate of the center
- * @param cy The y-coordinate of the center
- * @param colors The colors to be distributed between around the center.
- * There must be at least 2 colors in the array.
- * @param positions May be NULL. The relative position of
- * each corresponding color in the colors array, beginning
- * with 0 and ending with 1.0. If the values are not
- * monotonic, the drawing may produce unexpected results.
- * If positions is NULL, then the colors are automatically
- * spaced evenly.
- */
- public void set(float cx, float cy,
- @NonNull @ColorInt int colors[], @Nullable float positions[]) {
- if (colors.length < 2) {
- throw new IllegalArgumentException("needs >= 2 number of colors");
- }
- if (positions != null && colors.length != positions.length) {
- throw new IllegalArgumentException(
- "color and position arrays must be of equal length");
- }
- discardNativeInstance();
- mType = TYPE_COLORS_AND_POSITIONS;
- mCx = cx;
- mCy = cy;
- mColors = colors.clone();
- mPositions = positions != null ? positions.clone() : null;
- }
-
- /**
- * Reinitialize the shader.
- *
- * @param cx The x-coordinate of the center
- * @param cy The y-coordinate of the center
- * @param color0 The color to use at the start of the sweep
- * @param color1 The color to use at the end of the sweep
- */
- public void set(float cx, float cy, @ColorInt int color0, @ColorInt int color1) {
- discardNativeInstance();
mType = TYPE_COLOR_START_AND_COLOR_END;
mCx = cx;
mCy = cy;
diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java
index 97d3e5e..18dc0dc 100644
--- a/graphics/java/android/graphics/Typeface.java
+++ b/graphics/java/android/graphics/Typeface.java
@@ -563,6 +563,9 @@
/**
* Constructs a builder with a file descriptor.
*
+ * Caller is responsible for closing the passed file descriptor after {@link #build} is
+ * called.
+ *
* @param fd The file descriptor. The passed fd must be mmap-able.
*/
public Builder(@NonNull FileDescriptor fd) {
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index d95acff..3e5e3bf 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -390,14 +390,8 @@
}
bool SkiaCanvas::clipPath(const SkPath* path, SkClipOp op) {
- SkRRect roundRect;
- if (path->isRRect(&roundRect)) {
- this->recordClip(roundRect, op);
- mCanvas->clipRRect(roundRect, op);
- } else {
- this->recordClip(*path, op);
- mCanvas->clipPath(*path, op);
- }
+ this->recordClip(*path, op);
+ mCanvas->clipPath(*path, op);
return !mCanvas->isClipEmpty();
}
diff --git a/libs/hwui/tests/unit/SkiaBehaviorTests.cpp b/libs/hwui/tests/unit/SkiaBehaviorTests.cpp
index 7ae58a6..dafa074 100644
--- a/libs/hwui/tests/unit/SkiaBehaviorTests.cpp
+++ b/libs/hwui/tests/unit/SkiaBehaviorTests.cpp
@@ -20,6 +20,7 @@
#include <SkColorMatrixFilter.h>
#include <SkColorSpace.h>
#include <SkImagePriv.h>
+#include <SkPathOps.h>
#include <SkShader.h>
using namespace android;
@@ -90,6 +91,16 @@
ASSERT_EQ(expected, paint.getBlendMode());
}
+TEST(SkiaBehavior, pathIntersection) {
+ SkPath p0, p1, result;
+ p0.addRect(SkRect::MakeXYWH(-5.0f, 0.0f, 1080.0f, 242.0f));
+ p1.addRect(SkRect::MakeXYWH(0.0f, 0.0f, 1080.0f, 242.0f));
+ Op(p0, p1, kIntersect_SkPathOp, &result);
+ SkRect resultRect;
+ ASSERT_TRUE(result.isRect(&resultRect));
+ ASSERT_EQ(SkRect::MakeXYWH(0.0f, 0.0f, 1075.0f, 242.0f), resultRect);
+}
+
TEST(SkiaBehavior, srgbColorSpaceIsSingleton) {
sk_sp<SkColorSpace> sRGB1 = SkColorSpace::MakeSRGB();
sk_sp<SkColorSpace> sRGB2 = SkColorSpace::MakeSRGB();
diff --git a/location/java/android/location/Location.java b/location/java/android/location/Location.java
index b222a6d..68f46ad 100644
--- a/location/java/android/location/Location.java
+++ b/location/java/android/location/Location.java
@@ -646,7 +646,10 @@
*
* <p>Following this call {@link #hasAltitude} will return false,
* and {@link #getAltitude} will return 0.0.
+ *
+ * @deprecated use a new Location object for location updates.
*/
+ @Deprecated
public void removeAltitude() {
mAltitude = 0.0f;
mFieldsMask &= ~HAS_ALTITUDE_MASK;
@@ -683,7 +686,10 @@
*
* <p>Following this call {@link #hasSpeed} will return false,
* and {@link #getSpeed} will return 0.0.
+ *
+ * @deprecated use a new Location object for location updates.
*/
+ @Deprecated
public void removeSpeed() {
mSpeed = 0.0f;
mFieldsMask &= ~HAS_SPEED_MASK;
@@ -733,7 +739,10 @@
*
* <p>Following this call {@link #hasBearing} will return false,
* and {@link #getBearing} will return 0.0.
+ *
+ * @deprecated use a new Location object for location updates.
*/
+ @Deprecated
public void removeBearing() {
mBearing = 0.0f;
mFieldsMask &= ~HAS_BEARING_MASK;
@@ -790,7 +799,10 @@
*
* <p>Following this call {@link #hasAccuracy} will return false, and
* {@link #getAccuracy} will return 0.0.
+ *
+ * @deprecated use a new Location object for location updates.
*/
+ @Deprecated
public void removeAccuracy() {
mHorizontalAccuracyMeters = 0.0f;
mFieldsMask &= ~HAS_HORIZONTAL_ACCURACY_MASK;
@@ -839,7 +851,11 @@
*
* <p>Following this call {@link #hasVerticalAccuracy} will return false, and
* {@link #getVerticalAccuracyMeters} will return 0.0.
+ *
+ * @deprecated use a new Location object for location updates.
+ * @removed
*/
+ @Deprecated
public void removeVerticalAccuracy() {
mVerticalAccuracyMeters = 0.0f;
mFieldsMask &= ~HAS_VERTICAL_ACCURACY_MASK;
@@ -883,7 +899,11 @@
*
* <p>Following this call {@link #hasSpeedAccuracy} will return false, and
* {@link #getSpeedAccuracyMetersPerSecond} will return 0.0.
+ *
+ * @deprecated use a new Location object for location updates.
+ * @removed
*/
+ @Deprecated
public void removeSpeedAccuracy() {
mSpeedAccuracyMetersPerSecond = 0.0f;
mFieldsMask &= ~HAS_SPEED_ACCURACY_MASK;
@@ -927,7 +947,11 @@
*
* <p>Following this call {@link #hasBearingAccuracy} will return false, and
* {@link #getBearingAccuracyDegrees} will return 0.0.
+ *
+ * @deprecated use a new Location object for location updates.
+ * @removed
*/
+ @Deprecated
public void removeBearingAccuracy() {
mBearingAccuracyDegrees = 0.0f;
mFieldsMask &= ~HAS_BEARING_ACCURACY_MASK;
diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java
index 9386246..d5efc97 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -1007,13 +1007,14 @@
* @param context the Context to use when resolving the Uri
* @param uri the Content URI of the data you want to play
* @param headers the headers to be sent together with the request for the data
- * Note that the cross domain redirection is allowed by default, but that can be
- * changed with key/value pairs through the headers parameter with
- * "android-allow-cross-domain-redirect" as the key and "0" or "1" as the value
- * to disallow or allow cross domain redirection.
* The headers must not include cookies. Instead, use the cookies param.
* @param cookies the cookies to be sent together with the request
* @throws IllegalStateException if it is called in an invalid state
+ *
+ * <p><strong>Note</strong> that the cross domain redirection is allowed by default,
+ * but that can be changed with key/value pairs through the headers parameter with
+ * "android-allow-cross-domain-redirect" as the key and "0" or "1" as the value to
+ * disallow or allow cross domain redirection.
*/
public void setDataSource(@NonNull Context context, @NonNull Uri uri,
@Nullable Map<String, String> headers, @Nullable List<HttpCookie> cookies)
@@ -1056,11 +1057,12 @@
* @param context the Context to use when resolving the Uri
* @param uri the Content URI of the data you want to play
* @param headers the headers to be sent together with the request for the data
- * Note that the cross domain redirection is allowed by default, but that can be
- * changed with key/value pairs through the headers parameter with
- * "android-allow-cross-domain-redirect" as the key and "0" or "1" as the value
- * to disallow or allow cross domain redirection.
* @throws IllegalStateException if it is called in an invalid state
+ *
+ * <p><strong>Note</strong> that the cross domain redirection is allowed by default,
+ * but that can be changed with key/value pairs through the headers parameter with
+ * "android-allow-cross-domain-redirect" as the key and "0" or "1" as the value to
+ * disallow or allow cross domain redirection.
*/
public void setDataSource(@NonNull Context context, @NonNull Uri uri,
@Nullable Map<String, String> headers)
@@ -1981,7 +1983,7 @@
mOnSubtitleDataListener = null;
// Modular DRM clean up
- mOnDrmConfigListener = null;
+ mOnDrmConfigHelper = null;
mOnDrmInfoHandlerDelegate = null;
mOnDrmPreparedHandlerDelegate = null;
resetDrmState();
@@ -3905,11 +3907,11 @@
* 'securityLevel', which has to be set after DRM scheme creation but
* before the DRM session is opened.
*
- * The only allowed DRM calls in this listener are getDrmPropertyString
- * and setDrmPropertyString.
+ * The only allowed DRM calls in this listener are {@code getDrmPropertyString}
+ * and {@code setDrmPropertyString}.
*
*/
- public interface OnDrmConfigListener
+ public interface OnDrmConfigHelper
{
/**
* Called to give the app the opportunity to configure DRM before the session is created
@@ -3922,19 +3924,19 @@
/**
* Register a callback to be invoked for configuration of the DRM object before
* the session is created.
- * The callback will be invoked synchronously half-way into the execution
+ * The callback will be invoked synchronously during the execution
* of {@link #prepareDrm(UUID uuid)}.
*
* @param listener the callback that will be run
*/
- public void setOnDrmConfigListener(OnDrmConfigListener listener)
+ public void setOnDrmConfigHelper(OnDrmConfigHelper listener)
{
synchronized (mDrmLock) {
- mOnDrmConfigListener = listener;
+ mOnDrmConfigHelper = listener;
} // synchronized
}
- private OnDrmConfigListener mOnDrmConfigListener;
+ private OnDrmConfigHelper mOnDrmConfigHelper;
/**
* Interface definition of a callback to be invoked when the
@@ -3946,7 +3948,7 @@
* Called to indicate DRM info is available
*
* @param mp the {@code MediaPlayer} associated with this callback
- * @param drmInfo DRM info of the source including PSSH, mimes, and subset
+ * @param drmInfo DRM info of the source including PSSH, and subset
* of crypto schemes supported by this device
*/
public void onDrmInfo(MediaPlayer mp, DrmInfo drmInfo);
@@ -3982,6 +3984,41 @@
private OnDrmInfoHandlerDelegate mOnDrmInfoHandlerDelegate;
+
+ /**
+ * The status codes for {@link OnDrmPreparedListener#onDrmPrepared} listener.
+ * <p>
+ *
+ * DRM preparation has succeeded.
+ */
+ public static final int PREPARE_DRM_STATUS_SUCCESS = 0;
+
+ /**
+ * The device required DRM provisioning but couldn't reach the provisioning server.
+ */
+ public static final int PREPARE_DRM_STATUS_PROVISIONING_NETWORK_ERROR = 1;
+
+ /**
+ * The device required DRM provisioning but the provisioning server denied the request.
+ */
+ public static final int PREPARE_DRM_STATUS_PROVISIONING_SERVER_ERROR = 2;
+
+ /**
+ * The DRM preparation has failed .
+ */
+ public static final int PREPARE_DRM_STATUS_PREPARATION_ERROR = 3;
+
+
+ /** @hide */
+ @IntDef({
+ PREPARE_DRM_STATUS_SUCCESS,
+ PREPARE_DRM_STATUS_PROVISIONING_NETWORK_ERROR,
+ PREPARE_DRM_STATUS_PROVISIONING_SERVER_ERROR,
+ PREPARE_DRM_STATUS_PREPARATION_ERROR,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface PrepareDrmStatusCode {}
+
/**
* Interface definition of a callback to notify the app when the
* DRM is ready for key request/response
@@ -3992,9 +4029,13 @@
* Called to notify the app that prepareDrm is finished and ready for key request/response
*
* @param mp the {@code MediaPlayer} associated with this callback
- * @param success the result of DRM preparation
+ * @param status the result of DRM preparation which can be
+ * {@link #PREPARE_DRM_STATUS_SUCCESS},
+ * {@link #PREPARE_DRM_STATUS_PROVISIONING_NETWORK_ERROR},
+ * {@link #PREPARE_DRM_STATUS_PROVISIONING_SERVER_ERROR}, or
+ * {@link #PREPARE_DRM_STATUS_PREPARATION_ERROR}.
*/
- public void onDrmPrepared(MediaPlayer mp, boolean success);
+ public void onDrmPrepared(MediaPlayer mp, @PrepareDrmStatusCode int status);
}
/**
@@ -4038,30 +4079,28 @@
mOnDrmInfoListener = listener;
// find the looper for our new event handler
- Looper looper = null;
if (handler != null) {
- looper = handler.getLooper();
- }
-
- // construct the event handler with this looper
- if (looper != null) {
- // implement the event handler delegate
- mHandler = new Handler(looper) {
- public void handleMessage(Message msg) {
- DrmInfo drmInfo = (DrmInfo)msg.obj;
- mOnDrmInfoListener.onDrmInfo(mMediaPlayer, drmInfo);
- }
- };
+ mHandler = handler;
+ } else {
+ // handler == null
+ // Will let OnDrmInfoListener be called in mEventHandler similar to other
+ // legacy notifications. This is because MEDIA_DRM_INFO's notification has to be
+ // sent before MEDIA_PREPARED's (i.e., in the same order they are issued by
+ // mediaserver). As a result, the callback has to be called directly by
+ // EventHandler.handleMessage similar to onPrepared.
}
}
void notifyClient(DrmInfo drmInfo) {
- if ( mHandler != null ) {
- Message msg = new Message(); // no message type needed
- msg.obj = drmInfo;
- mHandler.sendMessage(msg);
+ if (mHandler != null) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mOnDrmInfoListener.onDrmInfo(mMediaPlayer, drmInfo);
+ }
+ });
}
- else { // no handler: direct call
+ else { // no handler: direct call by mEventHandler
mOnDrmInfoListener.onDrmInfo(mMediaPlayer, drmInfo);
}
}
@@ -4078,31 +4117,26 @@
mOnDrmPreparedListener = listener;
// find the looper for our new event handler
- Looper looper = null;
if (handler != null) {
- looper = handler.getLooper();
- }
-
- // construct the event handler with this looper
- if (looper != null) {
- // implement the event handler delegate
- mHandler = new Handler(looper) {
- public void handleMessage(Message msg) {
- boolean success = (msg.arg1 == 0) ? false : true;
- mOnDrmPreparedListener.onDrmPrepared(mMediaPlayer, success);
- }
- };
+ mHandler = handler;
+ } else if (mEventHandler != null) {
+ // Otherwise, use mEventHandler
+ mHandler = mEventHandler;
+ } else {
+ Log.e(TAG, "OnDrmPreparedHandlerDelegate: Unexpected null mEventHandler");
}
}
- void notifyClient(boolean success) {
- if ( mHandler != null ) {
- Message msg = new Message(); // no message type needed
- msg.arg1 = success ? 1 : 0;
- mHandler.sendMessage(msg);
- }
- else { // no handler: direct call
- mOnDrmPreparedListener.onDrmPrepared(mMediaPlayer, success);
+ void notifyClient(int status) {
+ if (mHandler != null) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mOnDrmPreparedListener.onDrmPrepared(mMediaPlayer, status);
+ }
+ });
+ } else {
+ Log.e(TAG, "OnDrmPreparedHandlerDelegate:notifyClient: Unexpected null mHandler");
}
}
}
@@ -4137,7 +4171,7 @@
/**
* Prepares the DRM for the current source
* <p>
- * If {@code OnDrmConfigListener} is registered, it will be called half-way into
+ * If {@code OnDrmConfigHelper} is registered, it will be called during
* preparation to allow configuration of the DRM properties before opening the
* DRM session. Note that the callback is called synchronously in the thread that called
* {@code prepareDrm}. It should be used only for a series of {@code getDrmPropertyString}
@@ -4148,9 +4182,9 @@
* complete depending on the network connectivity.
* If {@code OnDrmPreparedListener} is registered, prepareDrm() runs in non-blocking
* mode by launching the provisioning in the background and returning. The listener
- * will be called when provisioning and preperation has finished. If a
+ * will be called when provisioning and preparation has finished. If a
* {@code OnDrmPreparedListener} is not registered, prepareDrm() waits till provisioning
- * and preperation has finished, i.e., runs in blocking mode.
+ * and preparation has finished, i.e., runs in blocking mode.
* <p>
* If {@code OnDrmPreparedListener} is registered, it is called to indicate the DRM
* session being ready. The application should not make any assumption about its call
@@ -4158,18 +4192,23 @@
* execute the listener (unless the listener is registered with a handler thread).
* <p>
*
- * @param uuid The UUID of the crypto scheme.
+ * @param uuid The UUID of the crypto scheme. If not known beforehand, it can be retrieved
+ * from the source through {@code getDrmInfo} or registering a {@code onDrmInfoListener}.
*
- * @throws IllegalStateException if called before prepare(), or there exists a Drm already
- * @throws UnsupportedSchemeException if the crypto scheme is not supported
- * @throws ResourceBusyException if required DRM resources are in use
- * @throws ProvisioningErrorException if provisioning is required but an attempt failed
+ * @throws IllegalStateException if called before prepare(), or the DRM was
+ * prepared already
+ * @throws UnsupportedSchemeException if the crypto scheme is not supported
+ * @throws ResourceBusyException if required DRM resources are in use
+ * @throws ProvisioningNetworkErrorException if provisioning is required but failed due to a
+ * network error
+ * @throws ProvisioningServerErrorException if provisioning is required but failed due to
+ * the request denied by the provisioning server
*/
public void prepareDrm(@NonNull UUID uuid)
- throws UnsupportedSchemeException,
- ResourceBusyException, ProvisioningErrorException
+ throws UnsupportedSchemeException, ResourceBusyException,
+ ProvisioningNetworkErrorException, ProvisioningServerErrorException
{
- Log.v(TAG, "prepareDrm: uuid: " + uuid + " mOnDrmConfigListener: " + mOnDrmConfigListener);
+ Log.v(TAG, "prepareDrm: uuid: " + uuid + " mOnDrmConfigHelper: " + mOnDrmConfigHelper);
boolean allDoneWithoutProvisioning = false;
// get a snapshot as we'll use them outside the lock
@@ -4177,7 +4216,7 @@
synchronized (mDrmLock) {
- // only allowing if tied to a protected source; might releax for releasing offline keys
+ // only allowing if tied to a protected source; might relax for releasing offline keys
if (mDrmInfo == null) {
final String msg = "prepareDrm(): Wrong usage: The player must be prepared and " +
"DRM info be retrieved before this call.";
@@ -4226,8 +4265,8 @@
// call the callback outside the lock
- if (mOnDrmConfigListener != null) {
- mOnDrmConfigListener.onDrmConfig(this);
+ if (mOnDrmConfigHelper != null) {
+ mOnDrmConfigHelper.onDrmConfig(this);
}
synchronized (mDrmLock) {
@@ -4251,15 +4290,33 @@
Log.w(TAG, "prepareDrm: NotProvisionedException");
// handle provisioning internally; it'll reset mPrepareDrmInProgress
- boolean result = HandleProvisioninig(uuid);
+ int result = HandleProvisioninig(uuid);
// if blocking mode, we're already done;
// if non-blocking mode, we attempted to launch background provisioning
- if (result == false) {
- final String msg = "prepareDrm: Provisioning was required but failed.";
- Log.e(TAG, msg);
+ if (result != PREPARE_DRM_STATUS_SUCCESS) {
earlyExit = true;
- throw new ProvisioningErrorException(msg);
+ String msg;
+
+ switch (result) {
+ case PREPARE_DRM_STATUS_PROVISIONING_NETWORK_ERROR:
+ msg = "prepareDrm: Provisioning was required but failed " +
+ "due to a network error.";
+ Log.e(TAG, msg);
+ throw new ProvisioningNetworkErrorException(msg);
+
+ case PREPARE_DRM_STATUS_PROVISIONING_SERVER_ERROR:
+ msg = "prepareDrm: Provisioning was required but the request " +
+ "was denied by the server.";
+ Log.e(TAG, msg);
+ throw new ProvisioningServerErrorException(msg);
+
+ case PREPARE_DRM_STATUS_PREPARATION_ERROR:
+ default: // default for safeguard
+ msg = "prepareDrm: Post-provisioning preparation failed.";
+ Log.e(TAG, msg);
+ throw new IllegalStateException(msg);
+ }
}
// nothing else to do;
// if blocking or non-blocking, HandleProvisioninig does the re-attempt & cleanup
@@ -4281,7 +4338,7 @@
// if finished successfully without provisioning, call the callback outside the lock
if (allDoneWithoutProvisioning) {
if (onDrmPreparedHandlerDelegate != null)
- onDrmPreparedHandlerDelegate.notifyClient(true /*success*/);
+ onDrmPreparedHandlerDelegate.notifyClient(PREPARE_DRM_STATUS_SUCCESS);
}
}
@@ -4291,6 +4348,10 @@
/**
* Releases the DRM session
+ * <p>
+ * The player has to have an active DRM session and be in stopped, or prepared
+ * state before this call is made.
+ * A {@code reset()} call will release the DRM session implicitly.
*
* @throws NoDrmSchemeException if there is no active DRM session to release
*/
@@ -4307,7 +4368,7 @@
try {
// we don't have the player's state in this layer. The below call raises
- // exception if we're in a non-stopped/idle state.
+ // exception if we're in a non-stopped/prepared state.
// for cleaning native/mediaserver crypto object
_releaseDrm();
@@ -4316,9 +4377,11 @@
cleanDrmObj();
mActiveDrmScheme = false;
- } catch (Exception e) {
+ } catch (IllegalStateException e) {
Log.w(TAG, "releaseDrm: Exception ", e);
- throw e;
+ throw new IllegalStateException("releaseDrm: The player is not in a valid state.");
+ } catch (Exception e) {
+ Log.e(TAG, "releaseDrm: Exception ", e);
}
} // synchronized
}
@@ -4337,21 +4400,23 @@
* it should deliver to the response to the DRM engine plugin using the method
* {@link #provideKeyResponse}.
*
- * @param scope may be a container-specific initialization data or a keySetId,
- * depending on the specified keyType.
- * When the keyType is KEY_TYPE_STREAMING or KEY_TYPE_OFFLINE, scope should be set to
- * the container-specific initialization 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.
- * When the keyType is KEY_TYPE_RELEASE, scope should be set to the keySetId of
- * the keys being released.
+ * @param keySetId is the key-set identifier of the offline keys being released when keyType is
+ * {@link MediaDrm#KEY_TYPE_RELEASE}. It should be set to null for other key requests, when
+ * keyType is {@link MediaDrm#KEY_TYPE_STREAMING} or {@link MediaDrm#KEY_TYPE_OFFLINE}.
+ *
+ * @param initData is the container-specific initialization data when the keyType is
+ * {@link MediaDrm#KEY_TYPE_STREAMING} or {@link MediaDrm#KEY_TYPE_OFFLINE}. 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.
+ * When the keyType is {@link MediaDrm#KEY_TYPE_RELEASE}, it should be set to null.
*
* @param mimeType identifies the mime type of the content
*
- * @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.
+ * @param keyType specifies the type of the request. The request may be to acquire
+ * keys for streaming, {@link MediaDrm#KEY_TYPE_STREAMING}, or for offline content
+ * {@link MediaDrm#KEY_TYPE_OFFLINE}, or to release previously acquired
+ * keys ({@link MediaDrm#KEY_TYPE_RELEASE}), which are identified by a keySetId.
*
* @param optionalParameters are included in the key request message to
* allow a client application to provide additional message parameters to the server.
@@ -4360,12 +4425,13 @@
* @throws NoDrmSchemeException if there is no active DRM session
*/
@NonNull
- public MediaDrm.KeyRequest getKeyRequest(@NonNull byte[] scope, @Nullable String mimeType,
- @MediaDrm.KeyType int keyType, @Nullable Map<String, String> optionalParameters)
+ public MediaDrm.KeyRequest getKeyRequest(@Nullable byte[] keySetId, @Nullable byte[] initData,
+ @Nullable String mimeType, @MediaDrm.KeyType int keyType,
+ @Nullable Map<String, String> optionalParameters)
throws NoDrmSchemeException
{
Log.v(TAG, "getKeyRequest: " +
- " scope: " + scope + " mimeType: " + mimeType +
+ " keySetId: " + keySetId + " initData:" + initData + " mimeType: " + mimeType +
" keyType: " + keyType + " optionalParameters: " + optionalParameters);
synchronized (mDrmLock) {
@@ -4375,20 +4441,16 @@
}
try {
- byte[] scopeOut = (keyType != MediaDrm.KEY_TYPE_RELEASE) ?
- mDrmSessionId : // sessionId for KEY_TYPE_STREAMING/OFFLINE
- scope; // keySetId for KEY_TYPE_RELEASE
-
- byte[] initData = (keyType != MediaDrm.KEY_TYPE_RELEASE) ?
- scope : // initData for KEY_TYPE_STREAMING/OFFLINE
- null; // not used for KEY_TYPE_RELEASE
+ byte[] scope = (keyType != MediaDrm.KEY_TYPE_RELEASE) ?
+ mDrmSessionId : // sessionId for KEY_TYPE_STREAMING/OFFLINE
+ keySetId; // keySetId for KEY_TYPE_RELEASE
HashMap<String, String> hmapOptionalParameters =
(optionalParameters != null) ?
new HashMap<String, String>(optionalParameters) :
null;
- MediaDrm.KeyRequest request = mDrmObj.getKeyRequest(scopeOut, initData, mimeType,
+ MediaDrm.KeyRequest request = mDrmObj.getKeyRequest(scope, initData, mimeType,
keyType, hmapOptionalParameters);
Log.v(TAG, "getKeyRequest: --> request: " + request);
@@ -4499,8 +4561,8 @@
* @param propertyName the property name
*
* Standard fields names are:
- * {link #PROPERTY_VENDOR}, {link #PROPERTY_VERSION},
- * {link #PROPERTY_DESCRIPTION}, {link #PROPERTY_ALGORITHMS}
+ * {@link MediaDrm#PROPERTY_VENDOR}, {@link MediaDrm#PROPERTY_VERSION},
+ * {@link MediaDrm#PROPERTY_DESCRIPTION}, {@link MediaDrm#PROPERTY_ALGORITHMS}
*/
@NonNull
public String getDrmPropertyString(@NonNull @MediaDrm.StringProperty String propertyName)
@@ -4537,8 +4599,8 @@
* @param value the property value
*
* Standard fields names are:
- * {link #PROPERTY_VENDOR}, {link #PROPERTY_VERSION},
- * {link #PROPERTY_DESCRIPTION}, {link #PROPERTY_ALGORITHMS}
+ * {@link MediaDrm#PROPERTY_VENDOR}, {@link MediaDrm#PROPERTY_VERSION},
+ * {@link MediaDrm#PROPERTY_DESCRIPTION}, {@link MediaDrm#PROPERTY_ALGORITHMS}
*/
public void setDrmPropertyString(@NonNull @MediaDrm.StringProperty String propertyName,
@NonNull String value)
@@ -4565,8 +4627,6 @@
public static final class DrmInfo {
private Map<UUID, byte[]> mapPssh;
private UUID[] supportedSchemes;
- // TODO: Won't need this in final release. Only keeping it for the existing test app.
- private String[] mimes;
public Map<UUID, byte[]> getPssh() {
return mapPssh;
@@ -4574,15 +4634,10 @@
public UUID[] getSupportedSchemes() {
return supportedSchemes;
}
- // TODO: Won't need this in final release. Only keeping it for the existing test app.
- public String[] getMimes() {
- return mimes;
- }
- private DrmInfo(Map<UUID, byte[]> Pssh, UUID[] SupportedSchemes, String[] Mimes) {
+ private DrmInfo(Map<UUID, byte[]> Pssh, UUID[] SupportedSchemes) {
mapPssh = Pssh;
supportedSchemes = SupportedSchemes;
- mimes = Mimes;
}
private DrmInfo(Parcel parcel) {
@@ -4608,18 +4663,12 @@
supportedSchemes[i]);
}
- // TODO: Won't need this in final release. Only keeping it for the test app.
- mimes = parcel.readStringArray();
- int mimeCount = mimes.length;
- Log.v(TAG, "DrmInfo() mime: " + Arrays.toString(mimes));
-
Log.v(TAG, "DrmInfo() Parcel psshsize: " + psshsize +
- " supportedDRMsCount: " + supportedDRMsCount +
- " mimeCount: " + mimeCount);
+ " supportedDRMsCount: " + supportedDRMsCount);
}
private DrmInfo makeCopy() {
- return new DrmInfo(this.mapPssh, this.supportedSchemes, this.mimes);
+ return new DrmInfo(this.mapPssh, this.supportedSchemes);
}
private String arrToHex(byte[] bytes) {
@@ -4714,11 +4763,22 @@
/**
* Thrown when the device requires DRM provisioning but the provisioning attempt has
- * failed (for example: network timeout, provisioning server error).
+ * failed due to a network error (Internet reachability, timeout, etc.).
* Extends MediaDrm.MediaDrmException
*/
- public static final class ProvisioningErrorException extends MediaDrmException {
- public ProvisioningErrorException(String detailMessage) {
+ public static final class ProvisioningNetworkErrorException extends MediaDrmException {
+ public ProvisioningNetworkErrorException(String detailMessage) {
+ super(detailMessage);
+ }
+ }
+
+ /**
+ * Thrown when the device requires DRM provisioning but the provisioning attempt has
+ * failed due to the provisioning server denying the request.
+ * Extends MediaDrm.MediaDrmException
+ */
+ public static final class ProvisioningServerErrorException extends MediaDrmException {
+ public ProvisioningServerErrorException(String detailMessage) {
super(detailMessage);
}
}
@@ -4770,14 +4830,13 @@
private UUID uuid;
private String urlStr;
- private byte[] response;
private Object drmLock;
private OnDrmPreparedHandlerDelegate onDrmPreparedHandlerDelegate;
private MediaPlayer mediaPlayer;
- private boolean succeeded;
+ private int status;
private boolean finished;
- public boolean succeeded() {
- return succeeded;
+ public int status() {
+ return status;
}
public ProvisioningThread initialize(MediaDrm.ProvisionRequest request,
@@ -4790,12 +4849,15 @@
urlStr = request.getDefaultUrl() + "&signedRequest=" + new String(request.getData());
this.uuid = uuid;
+ status = PREPARE_DRM_STATUS_PREPARATION_ERROR;
+
Log.v(TAG, "HandleProvisioninig: Thread is initialised url: " + urlStr);
return this;
}
public void run() {
+ byte[] response = null;
boolean provisioningSucceeded = false;
try {
URL url = new URL(urlStr);
@@ -4813,11 +4875,13 @@
Log.v(TAG, "HandleProvisioninig: Thread run: response " +
response.length + " " + response);
} catch (Exception e) {
+ status = PREPARE_DRM_STATUS_PROVISIONING_NETWORK_ERROR;
Log.w(TAG, "HandleProvisioninig: Thread run: connect " + e + " url: " + url);
} finally {
connection.disconnect();
}
} catch (Exception e) {
+ status = PREPARE_DRM_STATUS_PROVISIONING_NETWORK_ERROR;
Log.w(TAG, "HandleProvisioninig: Thread run: openConnection " + e);
}
@@ -4828,12 +4892,15 @@
"provideProvisionResponse SUCCEEDED!");
provisioningSucceeded = true;
- } catch (Exception e) {
+ } catch (Exception e) {
+ status = PREPARE_DRM_STATUS_PROVISIONING_SERVER_ERROR;
Log.w(TAG, "HandleProvisioninig: Thread run: " +
"provideProvisionResponse " + e);
}
}
+ boolean succeeded = false;
+
// non-blocking mode needs the lock
if (onDrmPreparedHandlerDelegate != null) {
@@ -4841,6 +4908,9 @@
// continuing with prepareDrm
if (provisioningSucceeded) {
succeeded = mediaPlayer.resumePrepareDrm(uuid);
+ status = (succeeded) ?
+ PREPARE_DRM_STATUS_SUCCESS :
+ PREPARE_DRM_STATUS_PREPARATION_ERROR;
}
mediaPlayer.mDrmProvisioningInProgress = false;
mediaPlayer.mPrepareDrmInProgress = false;
@@ -4850,12 +4920,15 @@
} // synchronized
// calling the callback outside the lock
- onDrmPreparedHandlerDelegate.notifyClient(succeeded);
+ onDrmPreparedHandlerDelegate.notifyClient(status);
} else { // blocking mode already has the lock
// continuing with prepareDrm
if (provisioningSucceeded) {
succeeded = mediaPlayer.resumePrepareDrm(uuid);
+ status = (succeeded) ?
+ PREPARE_DRM_STATUS_SUCCESS :
+ PREPARE_DRM_STATUS_PREPARATION_ERROR;
}
mediaPlayer.mDrmProvisioningInProgress = false;
mediaPlayer.mPrepareDrmInProgress = false;
@@ -4869,19 +4942,19 @@
} // ProvisioningThread
- private boolean HandleProvisioninig(UUID uuid)
+ private int HandleProvisioninig(UUID uuid)
{
// the lock is already held by the caller
if (mDrmProvisioningInProgress) {
Log.e(TAG, "HandleProvisioninig: Unexpected mDrmProvisioningInProgress");
- return false;
+ return PREPARE_DRM_STATUS_PREPARATION_ERROR;
}
MediaDrm.ProvisionRequest provReq = mDrmObj.getProvisionRequest();
if (provReq == null) {
Log.e(TAG, "HandleProvisioninig: getProvisionRequest returned null.");
- return false;
+ return PREPARE_DRM_STATUS_PREPARATION_ERROR;
}
Log.v(TAG, "HandleProvisioninig provReq " +
@@ -4893,11 +4966,11 @@
mDrmProvisioningThread = new ProvisioningThread().initialize(provReq, uuid, this);
mDrmProvisioningThread.start();
- boolean result = false;
+ int result;
- // non-blocking
+ // non-blocking: this is not the final result
if (mOnDrmPreparedHandlerDelegate != null) {
- result = true;
+ result = PREPARE_DRM_STATUS_SUCCESS;
} else {
// if blocking mode, wait till provisioning is done
try {
@@ -4905,7 +4978,7 @@
} catch (Exception e) {
Log.w(TAG, "HandleProvisioninig: Thread.join Exception " + e);
}
- result = mDrmProvisioningThread.succeeded();
+ result = mDrmProvisioningThread.status();
// no longer need the thread
mDrmProvisioningThread = null;
}
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java
index 246bd2b..5beb412 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java
@@ -314,8 +314,8 @@
}
class DevicesAdapter extends ArrayAdapter<DeviceFilterPair> {
- //TODO wifi icon
private Drawable BLUETOOTH_ICON = icon(android.R.drawable.stat_sys_data_bluetooth);
+ private Drawable WIFI_ICON = icon(com.android.internal.R.drawable.ic_wifi_signal_3);
private Drawable icon(int drawableRes) {
Drawable icon = getResources().getDrawable(drawableRes, null);
@@ -345,6 +345,11 @@
device.equals(mSelectedDevice)
? Color.GRAY
: Color.TRANSPARENT);
+ textView.setCompoundDrawablesWithIntrinsicBounds(
+ device.device instanceof android.net.wifi.ScanResult
+ ? WIFI_ICON
+ : BLUETOOTH_ICON,
+ null, null, null);
textView.setOnClickListener((view) -> {
mSelectedDevice = device;
notifyDataSetChanged();
@@ -357,8 +362,6 @@
textView.setTextColor(Color.BLACK);
final int padding = DeviceChooserActivity.getPadding(getResources());
textView.setPadding(padding, padding, padding, padding);
- textView.setCompoundDrawablesWithIntrinsicBounds(
- BLUETOOTH_ICON, null, null, null);
textView.setCompoundDrawablePadding(padding);
return textView;
}
diff --git a/packages/SettingsLib/res/layout/usage_view.xml b/packages/SettingsLib/res/layout/usage_view.xml
index 1d56668..151d1ee 100644
--- a/packages/SettingsLib/res/layout/usage_view.xml
+++ b/packages/SettingsLib/res/layout/usage_view.xml
@@ -76,17 +76,23 @@
android:id="@+id/bottom_label_space"
android:layout_width="@dimen/usage_graph_labels_width"
android:layout_height="wrap_content"/>
- <include android:id="@+id/label_start"
- layout="@layout/usage_side_label" />
-
- <Space
+ <LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
- android:layout_weight="1" />
+ android:layout_weight="1"
+ android:orientation="horizontal"
+ android:layoutDirection="ltr">
+ <include android:id="@+id/label_start"
+ layout="@layout/usage_side_label" />
- <include android:id="@+id/label_end"
- layout="@layout/usage_side_label" />
+ <Space
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1" />
+ <include android:id="@+id/label_end"
+ layout="@layout/usage_side_label" />
+ </LinearLayout>
</LinearLayout>
</LinearLayout>
diff --git a/packages/SettingsLib/src/com/android/settingslib/accessibility/AccessibilityButtonHelper.java b/packages/SettingsLib/src/com/android/settingslib/accessibility/AccessibilityButtonHelper.java
new file mode 100644
index 0000000..972ea34
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/accessibility/AccessibilityButtonHelper.java
@@ -0,0 +1,62 @@
+/*
+ * 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.settingslib.accessibility;
+
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.content.Context;
+import android.content.res.Resources;
+import android.provider.Settings;
+import android.view.accessibility.AccessibilityManager;
+
+import java.util.List;
+
+/**
+ * A helper class to assist determining the state of the accessibility button that can appear
+ * within the software-rendered navigation area.
+ */
+public class AccessibilityButtonHelper {
+ public static boolean isRequestedByMagnification(Context ctx) {
+ return Settings.Secure.getInt(ctx.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED, 0) == 1;
+ }
+
+ public static boolean isRequestedByAccessibilityService(Context ctx) {
+ final AccessibilityManager accessibilityManager = ctx.getSystemService(
+ AccessibilityManager.class);
+ List<AccessibilityServiceInfo> services =
+ accessibilityManager.getEnabledAccessibilityServiceList(
+ AccessibilityServiceInfo.FEEDBACK_ALL_MASK);
+ if (services != null) {
+ for (int i = 0, size = services.size(); i < size; i++) {
+ if ((services.get(i).flags
+ & AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON)
+ != 0) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ public static boolean isRequested(Context ctx) {
+ return isRequestedByMagnification(ctx) || isRequestedByAccessibilityService(ctx);
+ }
+
+ public static boolean isDeviceSupported(Resources res) {
+ return res.getBoolean(com.android.internal.R.bool.config_showNavigationBar);
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
index 10f3b63..c109704 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
@@ -46,6 +46,7 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.text.format.Formatter;
+import android.util.IconDrawableFactory;
import android.util.Log;
import android.util.SparseArray;
@@ -95,6 +96,7 @@
final Context mContext;
final PackageManager mPm;
+ final IconDrawableFactory mDrawableFactory;
final IPackageManager mIpm;
final UserManager mUm;
final StorageStatsManager mStats;
@@ -132,6 +134,7 @@
private ApplicationsState(Application app) {
mContext = app;
mPm = mContext.getPackageManager();
+ mDrawableFactory = IconDrawableFactory.newInstance(mContext);
mIpm = AppGlobals.getPackageManager();
mUm = mContext.getSystemService(UserManager.class);
mStats = mContext.getSystemService(StorageStatsManager.class);
@@ -327,7 +330,7 @@
return;
}
synchronized (entry) {
- entry.ensureIconLocked(mContext, mPm);
+ entry.ensureIconLocked(mContext, mDrawableFactory);
}
}
@@ -943,7 +946,7 @@
AppEntry entry = mAppEntries.get(i);
if (entry.icon == null || !entry.mounted) {
synchronized (entry) {
- if (entry.ensureIconLocked(mContext, mPm)) {
+ if (entry.ensureIconLocked(mContext, mDrawableFactory)) {
if (!mRunning) {
mRunning = true;
Message m = mMainHandler.obtainMessage(
@@ -1266,10 +1269,10 @@
}
}
- boolean ensureIconLocked(Context context, PackageManager pm) {
+ boolean ensureIconLocked(Context context, IconDrawableFactory drawableFactory) {
if (this.icon == null) {
if (this.apkFile.exists()) {
- this.icon = getBadgedIcon(pm);
+ this.icon = drawableFactory.getBadgedIcon(info);
return true;
} else {
this.mounted = false;
@@ -1281,19 +1284,13 @@
// its icon.
if (this.apkFile.exists()) {
this.mounted = true;
- this.icon = getBadgedIcon(pm);
+ this.icon = drawableFactory.getBadgedIcon(info);
return true;
}
}
return false;
}
- private Drawable getBadgedIcon(PackageManager pm) {
- // Do badging ourself so that it comes from the user of the app not the current user.
- return pm.getUserBadgedIcon(pm.loadUnbadgedItemIcon(info, info),
- new UserHandle(UserHandle.getUserId(info.uid)));
- }
-
public String getVersion(Context context) {
try {
return context.getPackageManager().getPackageInfo(info.packageName, 0).versionName;
diff --git a/packages/SettingsLib/src/com/android/settingslib/location/RecentLocationApps.java b/packages/SettingsLib/src/com/android/settingslib/location/RecentLocationApps.java
index 231fc69..3f826cc 100644
--- a/packages/SettingsLib/src/com/android/settingslib/location/RecentLocationApps.java
+++ b/packages/SettingsLib/src/com/android/settingslib/location/RecentLocationApps.java
@@ -27,6 +27,7 @@
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
+import android.util.IconDrawableFactory;
import android.util.Log;
import java.util.ArrayList;
@@ -48,10 +49,12 @@
private final PackageManager mPackageManager;
private final Context mContext;
+ private final IconDrawableFactory mDrawableFactory;
public RecentLocationApps(Context context) {
mContext = context;
mPackageManager = context.getPackageManager();
+ mDrawableFactory = IconDrawableFactory.newInstance(context);
}
/**
@@ -146,8 +149,7 @@
}
final UserHandle userHandle = new UserHandle(userId);
- Drawable appIcon = mPackageManager.getApplicationIcon(appInfo);
- Drawable icon = mPackageManager.getUserBadgedIcon(appIcon, userHandle);
+ Drawable icon = mDrawableFactory.getBadgedIcon(appInfo, userId);
CharSequence appLabel = mPackageManager.getApplicationLabel(appInfo);
CharSequence badgedAppLabel = mPackageManager.getUserBadgedLabel(appLabel, userHandle);
if (appLabel.toString().contentEquals(badgedAppLabel)) {
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 169a034..48429e8 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -1109,6 +1109,33 @@
// Retrieve the ssaid from the table if present.
final Setting ssaid = mSettingsRegistry.getSettingLocked(SETTINGS_TYPE_SSAID, owningUserId,
name);
+ // If the app is an Instant App use its stored SSAID instead of our own.
+ final String instantSsaid;
+ final long token = Binder.clearCallingIdentity();
+ try {
+ instantSsaid = mPackageManager.getInstantAppAndroidId(callingPkg.packageName,
+ owningUserId);
+ } catch (RemoteException e) {
+ Slog.e(LOG_TAG, "Failed to get Instant App Android ID", e);
+ return null;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ if (instantSsaid != null) {
+ // Use the stored value if it is still valid.
+ if (ssaid != null && instantSsaid.equals(ssaid.getValue())) {
+ return ssaid;
+ }
+ // The value has changed, update the stored value.
+ final SettingsState ssaidSettings = mSettingsRegistry.getSettingsLocked(
+ SETTINGS_TYPE_SSAID, owningUserId);
+ final boolean success = ssaidSettings.insertSettingLocked(name, instantSsaid, null,
+ true, callingPkg.packageName);
+ if (!success) {
+ throw new IllegalStateException("Failed to update instant app android id");
+ }
+ return mSettingsRegistry.getSettingLocked(SETTINGS_TYPE_SSAID, owningUserId, name);
+ }
// Lazy initialize ssaid if not yet present in ssaid table.
if (ssaid == null || ssaid.isNull() || ssaid.getValue() == null) {
diff --git a/packages/SystemUI/res/drawable/ic_info_outline.xml b/packages/SystemUI/res/drawable/ic_info_outline.xml
new file mode 100644
index 0000000..a4a3e9a
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_info_outline.xml
@@ -0,0 +1,25 @@
+<!--
+ Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="32dp"
+ android:height="32dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FFFFFFFF"
+ android:pathData="M11,17h2v-6h-2v6zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8zM11,9h2L13,7h-2v2z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_pause_white_24dp.xml b/packages/SystemUI/res/drawable/ic_pause_white.xml
similarity index 100%
rename from packages/SystemUI/res/drawable/ic_pause_white_24dp.xml
rename to packages/SystemUI/res/drawable/ic_pause_white.xml
diff --git a/packages/SystemUI/res/drawable/ic_play_arrow_white_24dp.xml b/packages/SystemUI/res/drawable/ic_play_arrow_white.xml
similarity index 100%
rename from packages/SystemUI/res/drawable/ic_play_arrow_white_24dp.xml
rename to packages/SystemUI/res/drawable/ic_play_arrow_white.xml
diff --git a/packages/SystemUI/res/drawable/ic_skip_next_white.xml b/packages/SystemUI/res/drawable/ic_skip_next_white.xml
new file mode 100644
index 0000000..040c7e6
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_skip_next_white.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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+
+ <path
+ android:fillColor="#FFFFFF"
+ android:pathData="M6 18l8.5-6L6 6v12zM16 6v12h2V6h-2z" />
+ <path
+ android:pathData="M0 0h24v24H0z" />
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_skip_previous_white.xml b/packages/SystemUI/res/drawable/ic_skip_previous_white.xml
new file mode 100644
index 0000000..b9b94b7
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_skip_previous_white.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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+
+ <path
+ android:fillColor="#FFFFFF"
+ android:pathData="M6 6h2v12H6zm3.5 6l8.5 6V6z" />
+ <path
+ android:pathData="M0 0h24v24H0z" />
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/notification_snooze.xml b/packages/SystemUI/res/layout/notification_snooze.xml
index 5bd64de..b70f24b 100644
--- a/packages/SystemUI/res/layout/notification_snooze.xml
+++ b/packages/SystemUI/res/layout/notification_snooze.xml
@@ -19,47 +19,56 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:minHeight="@dimen/snooze_snackbar_min_height"
- android:id="@+id/notification_snooze"
- android:clickable="true"
- android:gravity="center_vertical"
- android:orientation="horizontal"
- android:paddingStart="24dp"
- android:paddingEnd="24dp"
- android:background="@color/snooze_snackbar_bg">
-
- <TextView
- android:id="@+id/snooze_option_default"
- style="@style/TextAppearance.SnoozeSnackBar"
- android:layout_width="wrap_content"
- android:layout_height="match_parent"
- android:gravity="center_vertical"
- android:drawableTint="@android:color/white"
- android:drawableEnd="@drawable/notification_expand_more"/>
-
- <android.widget.Space
- android:layout_width="0dp"
- android:layout_height="0dp"
- android:layout_weight="1"
- />
-
- <TextView
- android:id="@+id/undo"
- style="@style/TextAppearance.SnoozeSnackBar.Button"
- android:layout_width="wrap_content"
- android:layout_height="match_parent"
- android:layout_marginEnd="8dp"
- android:layout_marginStart="8dp"
- android:background="@drawable/btn_borderless_rect"
- android:layout_gravity="end"
- android:text="@string/snooze_undo" />
-
+ android:orientation="vertical"
+ android:background="@color/notification_guts_bg_color"
+ android:theme="@*android:style/Theme.DeviceDefault.Light">
+
+ <RelativeLayout
+ android:layout_width="match_parent"
+ android:id="@+id/notification_snooze"
+ android:layout_height="@dimen/snooze_snackbar_min_height">
+
+ <TextView
+ android:id="@+id/snooze_option_default"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentStart="true"
+ android:layout_centerVertical="true"
+ android:paddingStart="16dp"
+ android:textColor="#DD000000"
+ android:paddingEnd="4dp"/>
+
+ <ImageView
+ android:id="@+id/expand_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_toEndOf="@+id/snooze_option_default"
+ android:layout_centerVertical="true"
+ android:paddingTop="1dp"
+ android:tint="#9E9E9E" />
+
+ <TextView
+ android:id="@+id/undo"
+ style="@style/TextAppearance.NotificationInfo.Button"
+ android:layout_width="wrap_content"
+ android:layout_height="36dp"
+ android:layout_marginEnd="8dp"
+ android:layout_alignParentEnd="true"
+ android:layout_centerVertical="true"
+ android:text="@string/snooze_undo" />
+ </RelativeLayout>
+
+ <View
+ android:id="@+id/divider"
+ android:layout_width="match_parent"
+ android:layout_height="0.5dp"
+ android:background="#9E9E9E" />
+
<LinearLayout
android:id="@+id/snooze_options"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:paddingTop="8dp"
android:paddingBottom="8dp"
- android:orientation="vertical"/>
-
+ android:orientation="vertical" />
+
</com.android.systemui.statusbar.NotificationSnooze>
diff --git a/packages/SystemUI/res/layout/notification_snooze_option.xml b/packages/SystemUI/res/layout/notification_snooze_option.xml
new file mode 100644
index 0000000..aaf45f3
--- /dev/null
+++ b/packages/SystemUI/res/layout/notification_snooze_option.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+-->
+<TextView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="48dp"
+ android:layout_marginStart="@*android:dimen/notification_content_margin_start"
+ android:layout_marginEnd="@*android:dimen/notification_content_margin_end"
+ android:gravity="center_vertical"
+ android:textSize="14sp"
+ android:textColor="#DD000000"/>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/quick_settings_footer.xml b/packages/SystemUI/res/layout/quick_settings_footer.xml
index 8667a5a..bb1b288 100644
--- a/packages/SystemUI/res/layout/quick_settings_footer.xml
+++ b/packages/SystemUI/res/layout/quick_settings_footer.xml
@@ -14,41 +14,31 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clickable="true"
android:paddingBottom="@dimen/qs_tile_padding_top"
- android:paddingTop="@dimen/qs_tile_padding_top" >
+ android:paddingTop="@dimen/qs_tile_padding_top"
+ android:paddingStart="@dimen/qs_footer_padding_start"
+ android:paddingEnd="@dimen/qs_footer_padding_end"
+ android:gravity="center_vertical"
+ android:background="?android:attr/colorPrimaryDark" >
<TextView
android:id="@+id/footer_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_centerHorizontal="true"
- android:textSize="@dimen/qs_tile_text_size" />
+ android:gravity="start"
+ android:layout_weight="1"
+ android:textAppearance="@style/TextAppearance.QS.TileLabel"
+ android:textColor="?android:attr/textColorSecondary"/>
<ImageView
android:id="@+id/footer_icon"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_centerVertical="true"
- android:layout_marginEnd="8dp"
- android:layout_toStartOf="@id/footer_text"
+ android:layout_width="@dimen/qs_footer_icon_size"
+ android:layout_height="@dimen/qs_footer_icon_size"
android:contentDescription="@null"
- android:src="@drawable/ic_qs_vpn"
- android:visibility="invisible" />
+ android:src="@drawable/ic_info_outline" />
- <!-- Only shown if both images are visible -->
- <ImageView
- android:id="@+id/footer_icon2"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_centerVertical="true"
- android:layout_marginEnd="8dp"
- android:layout_toStartOf="@id/footer_icon"
- android:contentDescription="@null"
- android:src="@drawable/ic_qs_network_logging"
- android:visibility="invisible" />
-
-</RelativeLayout>
+</LinearLayout>
diff --git a/packages/SystemUI/res/layout/tv_pip_controls.xml b/packages/SystemUI/res/layout/tv_pip_controls.xml
index c6bcd32..61ac6f6 100644
--- a/packages/SystemUI/res/layout/tv_pip_controls.xml
+++ b/packages/SystemUI/res/layout/tv_pip_controls.xml
@@ -40,7 +40,7 @@
android:layout_width="100dp"
android:layout_height="wrap_content"
android:layout_marginStart="-50dp"
- android:src="@drawable/ic_pause_white_24dp"
+ android:src="@drawable/ic_pause_white"
android:text="@string/pip_pause"
android:visibility="gone" />
</merge>
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index f461bb3..83a0b7b 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -96,10 +96,6 @@
<!-- The "inside" of a notification, reached via longpress -->
<color name="notification_guts_bg_color">#eeeeee</color>
- <!-- Colors of the snooze menu reached via snooze icon behind a notification -->
- <color name="snooze_snackbar_bg">#FF4A4A4A</color>
- <color name="snooze_snackbar_text">#FFA6BAFF</color>
-
<color name="assist_orb_color">#ffffff</color>
<color name="keyguard_user_switcher_background_gradient_color">#77000000</color>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 9c7a6a0..4edadea 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -119,7 +119,7 @@
<dimen name="notification_menu_icon_padding">20dp</dimen>
<!-- The minimum height for the snackbar shown after the snooze option has been chosen. -->
- <dimen name="snooze_snackbar_min_height">48dp</dimen>
+ <dimen name="snooze_snackbar_min_height">56dp</dimen>
<!-- The text size of options in the snooze menu. -->
<dimen name="snooze_option_text_size">14sp</dimen>
@@ -263,6 +263,9 @@
<dimen name="qs_detail_item_icon_size">24dp</dimen>
<dimen name="qs_detail_item_icon_marginStart">0dp</dimen>
<dimen name="qs_detail_item_icon_marginEnd">20dp</dimen>
+ <dimen name="qs_footer_padding_start">16dp</dimen>
+ <dimen name="qs_footer_padding_end">24dp</dimen>
+ <dimen name="qs_footer_icon_size">16dp</dimen>
<!-- Desired qs icon overlay size. -->
<dimen name="qs_detail_icon_overlay_size">24dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 33b1dd4..c9da889 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -376,13 +376,13 @@
<string name="accessibility_no_sim">No SIM.</string>
<!-- Content description of the cell data. [CHAR LIMIT=NONE] -->
- <string name="accessibility_cell_data">Cellular Data</string>
+ <string name="accessibility_cell_data">Mobile Data</string>
<!-- Content description of the cell data being enabled. [CHAR LIMIT=NONE] -->
- <string name="accessibility_cell_data_on">Cellular Data On</string>
+ <string name="accessibility_cell_data_on">Mobile Data On</string>
<!-- Content description of the cell data being disabled. [CHAR LIMIT=NONE] -->
- <string name="accessibility_cell_data_off">Cellular Data Off</string>
+ <string name="accessibility_cell_data_off">Mobile Data Off</string>
<!-- Content description of the bluetooth tethering icon for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
<string name="accessibility_bluetooth_tether">Bluetooth tethering.</string>
@@ -574,11 +574,11 @@
<!-- Title of dialog shown when 4G data usage has exceeded limit and has been disabled. [CHAR LIMIT=48] -->
<string name="data_usage_disabled_dialog_4g_title">4G data is paused</string>
<!-- Title of dialog shown when mobile data usage has exceeded limit and has been disabled. [CHAR LIMIT=48] -->
- <string name="data_usage_disabled_dialog_mobile_title">Cellular data is paused</string>
+ <string name="data_usage_disabled_dialog_mobile_title">Mobile data is paused</string>
<!-- Title of dialog shown when data usage has exceeded limit and has been disabled. [CHAR LIMIT=48] -->
<string name="data_usage_disabled_dialog_title">Data is paused</string>
<!-- Body of dialog shown when data usage has exceeded limit and has been disabled. [CHAR LIMIT=NONE] -->
- <string name="data_usage_disabled_dialog">The data limit you set has been reached. You are no longer using cellular data.\n\nIf you resume, charges may apply for data usage.</string>
+ <string name="data_usage_disabled_dialog">The data limit you set has been reached. You are no longer using mobile data.\n\nIf you resume, charges may apply for data usage.</string>
<!-- Dialog button indicating that data connection should be re-enabled. [CHAR LIMIT=28] -->
<string name="data_usage_disabled_dialog_enable">Resume</string>
@@ -749,7 +749,7 @@
<!-- QuickSettings: Flashlight [CHAR LIMIT=NONE] -->
<string name="quick_settings_flashlight_label">Flashlight</string>
<!-- QuickSettings: Cellular detail panel title [CHAR LIMIT=NONE] -->
- <string name="quick_settings_cellular_detail_title">Cellular data</string>
+ <string name="quick_settings_cellular_detail_title">Mobile data</string>
<!-- QuickSettings: Cellular detail panel, data usage title [CHAR LIMIT=NONE] -->
<string name="quick_settings_cellular_detail_data_usage">Data usage</string>
<!-- QuickSettings: Cellular detail panel, remaining data title [CHAR LIMIT=NONE] -->
@@ -1502,8 +1502,6 @@
<string name="snooze_option_30_min">30 minutes</string>
<!-- Notification: Menu row: Snooze options: 1 hour option. [CHAR LIMIT=50]-->
<string name="snooze_option_1_hour">1 hour</string>
- <!-- Notification: Menu row: Snooze options: cancel snoozing option. [CHAR LIMIT=50] -->
- <string name="snooze_option_dont_snooze">Cancel</string>
<!-- Notification: Menu row: Snooze undo button label. [CHAR LIMIT=50]-->
<string name="snooze_undo">UNDO</string>
@@ -1897,6 +1895,18 @@
<!-- PiP minimize description. [CHAR LIMIT=NONE] -->
<string name="pip_minimize_description" translatable="false">Drag or fling the PIP to the edges of the screen to minimize it.</string>
+ <!-- Button to play the current media on picture-in-picture (PIP) [CHAR LIMIT=30] -->
+ <string name="pip_play">Play</string>
+
+ <!-- Button to pause the current media on picture-in-picture (PIP) [CHAR LIMIT=30] -->
+ <string name="pip_pause">Pause</string>
+
+ <!-- Button to skip to the next media on picture-in-picture (PIP) [CHAR LIMIT=30] -->
+ <string name="pip_skip_to_next">Skip to next</string>
+
+ <!-- Button to skip to the prev media on picture-in-picture (PIP) [CHAR LIMIT=30] -->
+ <string name="pip_skip_to_prev">Skip to previous</string>
+
<!-- Tuner string -->
<string name="change_theme_reboot" translatable="false">Changing the theme requires a restart.</string>
<!-- Tuner string -->
@@ -1904,11 +1914,11 @@
<!-- Tuner string -->
<string name="default_theme" translatable="false">Default</string>
- <!-- Title for notification & dialog that the user's phone last shut down because it got too hot. [CHAR LIMIT=30] -->
+ <!-- Title for notification & dialog that the user's phone last shut down because it got too hot. [CHAR LIMIT=40] -->
<string name="thermal_shutdown_title">Phone turned off due to heat</string>
<!-- Message body for notification that user's phone last shut down because it got too hot. [CHAR LIMIT=100] -->
<string name="thermal_shutdown_message">Your phone is now running normally</string>
- <!-- Text body for dialog alerting user that their phone last shut down bewcause it got too hot. [CHAR LIMIT=300] -->
+ <!-- Text body for dialog alerting user that their phone last shut down because it got too hot. [CHAR LIMIT=450] -->
<string name="thermal_shutdown_dialog_message">Your phone was too hot, so it turned off to cool down. Your phone is now running normally.\n\nYour phone may get too hot if you:\n\t• Use resource-intensive apps (such as gaming, video, or navigation apps)\n\t• Download or upload large files\n\t• Use your phone in high temperatures</string>
<!-- Title for notification (and dialog) that user's phone has reached a certain temperature and may start to slow down in order to cool down. [CHAR LIMIT=30] -->
diff --git a/packages/SystemUI/res/values/strings_tv.xml b/packages/SystemUI/res/values/strings_tv.xml
index 41626fc..e578068 100644
--- a/packages/SystemUI/res/values/strings_tv.xml
+++ b/packages/SystemUI/res/values/strings_tv.xml
@@ -23,8 +23,4 @@
<string name="pip_close">Close PIP</string>
<!-- Button to move picture-in-picture (PIP) screen to the fullscreen in PIP menu [CHAR LIMIT=30] -->
<string name="pip_fullscreen">Full screen</string>
- <!-- Button to play the current media on picture-in-picture (PIP) [CHAR LIMIT=30] -->
- <string name="pip_play">Play</string>
- <!-- Button to pause the current media on picture-in-picture (PIP) [CHAR LIMIT=30] -->
- <string name="pip_pause">Pause</string>
</resources>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index e5f04c6..dbdbd1e 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -386,20 +386,6 @@
<item name="android:paddingEnd">8dp</item>
</style>
- <style name="TextAppearance.SnoozeSnackBar">
- <item name="android:textSize">14sp</item>
- <item name="android:fontFamily">sans-serif</item>
- <item name="android:textColor">@android:color/white</item>
- </style>
-
- <style name="TextAppearance.SnoozeSnackBar.Button">
- <item name="android:textSize">14sp</item>
- <item name="android:textAllCaps">true</item>
- <item name="android:fontFamily">sans-serif-medium</item>
- <item name="android:gravity">center</item>
- <item name="android:textColor">@color/snooze_snackbar_text</item>
- </style>
-
<style name="edit_theme" parent="@*android:style/Theme.DeviceDefault.QuickSettings">
<item name="android:colorBackground">?android:attr/colorSecondary</item>
</style>
diff --git a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
index 5a04108d..8c4159a 100644
--- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
@@ -462,11 +462,20 @@
int duration = SNAP_ANIM_LEN;
anim.setDuration(duration);
anim.addListener(new AnimatorListenerAdapter() {
+ boolean wasCancelled = false;
+
+ @Override
+ public void onAnimationCancel(Animator animator) {
+ wasCancelled = true;
+ }
+
@Override
public void onAnimationEnd(Animator animator) {
mSnappingChild = false;
- updateSwipeProgressFromOffset(animView, canBeDismissed);
- mCallback.onChildSnappedBack(animView, targetLeft);
+ if (!wasCancelled) {
+ updateSwipeProgressFromOffset(animView, canBeDismissed);
+ mCallback.onChildSnappedBack(animView, targetLeft);
+ }
}
});
prepareSnapBackAnimation(animView, anim);
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipDismissViewController.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipDismissViewController.java
index 86bb0de..4b3cdfb 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipDismissViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipDismissViewController.java
@@ -39,7 +39,8 @@
// This delay controls how long to wait before we show the target when the user first moves
// the PIP, to prevent the target from animating if the user just wants to fling the PIP
private static final int SHOW_TARGET_DELAY = 100;
- private static final int SHOW_TARGET_DURATION = 200;
+ private static final int SHOW_TARGET_DURATION = 350;
+ private static final int HIDE_TARGET_DURATION = 225;
private Context mContext;
private WindowManager mWindowManager;
@@ -96,7 +97,7 @@
public void showDismissTarget() {
mDismissView.animate()
.alpha(1f)
- .setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN)
+ .setInterpolator(Interpolators.LINEAR)
.setStartDelay(SHOW_TARGET_DELAY)
.setDuration(SHOW_TARGET_DURATION)
.start();
@@ -109,9 +110,9 @@
if (mDismissView != null) {
mDismissView.animate()
.alpha(0f)
- .setInterpolator(Interpolators.FAST_OUT_LINEAR_IN)
+ .setInterpolator(Interpolators.LINEAR)
.setStartDelay(0)
- .setDuration(SHOW_TARGET_DURATION)
+ .setDuration(HIDE_TARGET_DURATION)
.withEndAction(new Runnable() {
@Override
public void run() {
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMediaController.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMediaController.java
index 3a4caa9..62ec09b 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMediaController.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMediaController.java
@@ -48,6 +48,8 @@
private static final String ACTION_PLAY = "com.android.systemui.pip.phone.PLAY";
private static final String ACTION_PAUSE = "com.android.systemui.pip.phone.PAUSE";
+ private static final String ACTION_NEXT = "com.android.systemui.pip.phone.NEXT";
+ private static final String ACTION_PREV = "com.android.systemui.pip.phone.PREV";
/**
* A listener interface to receive notification on changes to the media actions.
@@ -67,6 +69,8 @@
private RemoteAction mPauseAction;
private RemoteAction mPlayAction;
+ private RemoteAction mNextAction;
+ private RemoteAction mPrevAction;
private BroadcastReceiver mPlayPauseActionReceiver = new BroadcastReceiver() {
@Override
@@ -76,6 +80,10 @@
mMediaController.getTransportControls().play();
} else if (action.equals(ACTION_PAUSE)) {
mMediaController.getTransportControls().pause();
+ } else if (action.equals(ACTION_NEXT)) {
+ mMediaController.getTransportControls().skipToNext();
+ } else if (action.equals(ACTION_PREV)) {
+ mMediaController.getTransportControls().skipToPrevious();
}
}
};
@@ -95,6 +103,8 @@
IntentFilter mediaControlFilter = new IntentFilter();
mediaControlFilter.addAction(ACTION_PLAY);
mediaControlFilter.addAction(ACTION_PAUSE);
+ mediaControlFilter.addAction(ACTION_NEXT);
+ mediaControlFilter.addAction(ACTION_PREV);
mContext.registerReceiver(mPlayPauseActionReceiver, mediaControlFilter);
createMediaActions();
@@ -143,11 +153,21 @@
int state = mMediaController.getPlaybackState().getState();
boolean isPlaying = MediaSession.isActiveState(state);
long actions = mMediaController.getPlaybackState().getActions();
+
+ // Prev action
+ mPrevAction.setEnabled((actions & PlaybackState.ACTION_SKIP_TO_PREVIOUS) != 0);
+ mediaActions.add(mPrevAction);
+
+ // Play/pause action
if (!isPlaying && ((actions & PlaybackState.ACTION_PLAY) != 0)) {
mediaActions.add(mPlayAction);
} else if (isPlaying && ((actions & PlaybackState.ACTION_PAUSE) != 0)) {
mediaActions.add(mPauseAction);
}
+
+ // Next action
+ mNextAction.setEnabled((actions & PlaybackState.ACTION_SKIP_TO_NEXT) != 0);
+ mediaActions.add(mNextAction);
return mediaActions;
}
@@ -157,15 +177,27 @@
private void createMediaActions() {
String pauseDescription = mContext.getString(R.string.pip_pause);
mPauseAction = new RemoteAction(Icon.createWithResource(mContext,
- R.drawable.ic_pause_white_24dp), pauseDescription, pauseDescription,
+ R.drawable.ic_pause_white), pauseDescription, pauseDescription,
PendingIntent.getBroadcast(mContext, 0, new Intent(ACTION_PAUSE),
FLAG_UPDATE_CURRENT));
String playDescription = mContext.getString(R.string.pip_play);
mPlayAction = new RemoteAction(Icon.createWithResource(mContext,
- R.drawable.ic_play_arrow_white_24dp), playDescription, playDescription,
+ R.drawable.ic_play_arrow_white), playDescription, playDescription,
PendingIntent.getBroadcast(mContext, 0, new Intent(ACTION_PLAY),
FLAG_UPDATE_CURRENT));
+
+ String nextDescription = mContext.getString(R.string.pip_skip_to_next);
+ mNextAction = new RemoteAction(Icon.createWithResource(mContext,
+ R.drawable.ic_skip_next_white), nextDescription, nextDescription,
+ PendingIntent.getBroadcast(mContext, 0, new Intent(ACTION_NEXT),
+ FLAG_UPDATE_CURRENT));
+
+ String prevDescription = mContext.getString(R.string.pip_skip_to_prev);
+ mPrevAction = new RemoteAction(Icon.createWithResource(mContext,
+ R.drawable.ic_skip_previous_white), prevDescription, prevDescription,
+ PendingIntent.getBroadcast(mContext, 0, new Intent(ACTION_PREV),
+ FLAG_UPDATE_CURRENT));
}
/**
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 982b808..79ac816 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
@@ -412,16 +412,26 @@
} else {
actionsContainer.setVisibility(View.VISIBLE);
if (mActionsGroup != null) {
- mActionsGroup.removeAllViews();
+ // Hide extra views
+ for (int i = mActions.size(); i < mActionsGroup.getChildCount(); i++) {
+ mActionsGroup.getChildAt(i).setVisibility(View.GONE);
+ }
+ // Add needed views
+ final LayoutInflater inflater = LayoutInflater.from(this);
+ while (mActionsGroup.getChildCount() < mActions.size()) {
+ final ImageView actionView = (ImageView) inflater.inflate(
+ R.layout.pip_menu_action, mActionsGroup, false);
+ mActionsGroup.addView(actionView);
+ }
// Recreate the layout
final boolean isLandscapePip = stackBounds != null &&
(stackBounds.width() > stackBounds.height());
- final LayoutInflater inflater = LayoutInflater.from(this);
for (int i = 0; i < mActions.size(); i++) {
final RemoteAction action = mActions.get(i);
- final ImageView actionView = (ImageView) inflater.inflate(
- R.layout.pip_menu_action, mActionsGroup, false);
+ final ImageView actionView = (ImageView) mActionsGroup.getChildAt(i);
+
+ // TODO: Check if the action drawable has changed before we reload it
action.getIcon().loadDrawableAsync(this, d -> {
d.setTint(Color.WHITE);
actionView.setImageDrawable(d);
@@ -439,12 +449,11 @@
actionView.setAlpha(DISABLED_ACTION_ALPHA);
actionView.setEnabled(false);
}
- if (isLandscapePip && i > 0) {
- LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)
- actionView.getLayoutParams();
- lp.leftMargin = mBetweenActionPaddingLand;
- }
- mActionsGroup.addView(actionView);
+
+ // Update the margin between actions
+ LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)
+ actionView.getLayoutParams();
+ lp.leftMargin = (isLandscapePip && i > 0) ? mBetweenActionPaddingLand : 0;
}
}
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 a60ecf7..3223f9f 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
@@ -63,7 +63,7 @@
private static final int METRIC_VALUE_DISMISSED_BY_TAP = 0;
private static final int METRIC_VALUE_DISMISSED_BY_DRAG = 1;
- private static final int SHOW_DISMISS_AFFORDANCE_DELAY = 200;
+ private static final int SHOW_DISMISS_AFFORDANCE_DELAY = 225;
// Allow dragging the PIP to a location to close it
private static final boolean ENABLE_DISMISS_DRAG_TO_EDGE = true;
@@ -78,6 +78,7 @@
private final PipDismissViewController mDismissViewController;
private final PipSnapAlgorithm mSnapAlgorithm;
private final AccessibilityManager mAccessibilityManager;
+ private boolean mShowPipMenuOnAnimationEnd = false;
// The current movement bounds
private Rect mMovementBounds = new Rect();
@@ -221,13 +222,18 @@
setMinimizedStateInternal(false);
}
mDismissViewController.destroyDismissTarget();
- mMenuController.showMenu(MENU_STATE_CLOSE, mMotionHelper.getBounds(),
- mMovementBounds, true /* allowMenuTimeout */);
+ mShowPipMenuOnAnimationEnd = true;
}
public void onPinnedStackAnimationEnded() {
// Always synchronize the motion helper bounds once PiP animations finish
mMotionHelper.synchronizePinnedStackBounds();
+
+ if (mShowPipMenuOnAnimationEnd) {
+ mMenuController.showMenu(MENU_STATE_CLOSE, mMotionHelper.getBounds(),
+ mMovementBounds, true /* allowMenuTimeout */);
+ mShowPipMenuOnAnimationEnd = false;
+ }
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipControlsView.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipControlsView.java
index 4c81907..acea3b6 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipControlsView.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/tv/PipControlsView.java
@@ -186,10 +186,10 @@
} else {
mPlayPauseButtonView.setVisibility(View.VISIBLE);
if (state == PipManager.PLAYBACK_STATE_PLAYING) {
- mPlayPauseButtonView.setImageResource(R.drawable.ic_pause_white_24dp);
+ mPlayPauseButtonView.setImageResource(R.drawable.ic_pause_white);
mPlayPauseButtonView.setText(R.string.pip_pause);
} else {
- mPlayPauseButtonView.setImageResource(R.drawable.ic_play_arrow_white_24dp);
+ mPlayPauseButtonView.setImageResource(R.drawable.ic_play_arrow_white);
mPlayPauseButtonView.setText(R.string.pip_play);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 38485c7..f5e096eb 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -98,9 +98,6 @@
setupTileLayout();
- mFooter = new QSSecurityFooter(this, context);
- addView(mFooter.getView());
-
mPageIndicator = LayoutInflater.from(context).inflate(
R.layout.qs_page_indicator, this, false);
addView(mPageIndicator);
@@ -110,6 +107,9 @@
addDivider();
+ mFooter = new QSSecurityFooter(this, context);
+ addView(mFooter.getView());
+
updateResources();
mBrightnessController = new BrightnessController(getContext(),
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java
index 5b9d95d..51c6ad8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java
@@ -1,4 +1,3 @@
-
/*
* Copyright (C) 2014 The Android Open Source Project
*
@@ -51,24 +50,21 @@
private final View mRootView;
private final TextView mFooterText;
private final ImageView mFooterIcon;
- private final ImageView mFooterIcon2;
private final Context mContext;
private final Callback mCallback = new Callback();
private final SecurityController mSecurityController;
private final ActivityStarter mActivityStarter;
private final Handler mMainHandler;
+ private final View mDivider;
private AlertDialog mDialog;
private QSTileHost mHost;
protected H mHandler;
private boolean mIsVisible;
- private boolean mIsIconVisible;
- private boolean mIsIcon2Visible;
private CharSequence mFooterTextContent = null;
private int mFooterTextId;
private int mFooterIconId;
- private int mFooterIcon2Id;
public QSSecurityFooter(QSPanel qsPanel, Context context) {
mRootView = LayoutInflater.from(context)
@@ -76,14 +72,13 @@
mRootView.setOnClickListener(this);
mFooterText = (TextView) mRootView.findViewById(R.id.footer_text);
mFooterIcon = (ImageView) mRootView.findViewById(R.id.footer_icon);
- mFooterIcon2 = (ImageView) mRootView.findViewById(R.id.footer_icon2);
- mFooterIconId = R.drawable.ic_qs_vpn;
- mFooterIcon2Id = R.drawable.ic_qs_network_logging;
+ mFooterIconId = R.drawable.ic_info_outline;
mContext = context;
mMainHandler = new Handler(Looper.getMainLooper());
mActivityStarter = Dependency.get(ActivityStarter.class);
mSecurityController = Dependency.get(SecurityController.class);
mHandler = new H(Dependency.get(Dependency.BG_LOOPER));
+ mDivider = qsPanel == null ? null : qsPanel.getDivider();
}
public void setHostEnvironment(QSTileHost host) {
@@ -130,45 +125,102 @@
}
private void handleRefreshState() {
- boolean isVpnEnabled = mSecurityController.isVpnEnabled();
- boolean isNetworkLoggingEnabled = mSecurityController.isNetworkLoggingEnabled();
- mIsIconVisible = isVpnEnabled || isNetworkLoggingEnabled;
- mIsIcon2Visible = isVpnEnabled && isNetworkLoggingEnabled;
- if (mSecurityController.isDeviceManaged()) {
- final CharSequence organizationName =
- mSecurityController.getDeviceOwnerOrganizationName();
- if (organizationName != null) {
- mFooterTextContent = mContext.getResources().getString(
- R.string.do_disclosure_with_name, organizationName);
- } else {
- mFooterTextContent =
- mContext.getResources().getString(R.string.do_disclosure_generic);
- }
- mIsVisible = true;
- int footerIconId = isVpnEnabled
- ? R.drawable.ic_qs_vpn
- : R.drawable.ic_qs_network_logging;
- if (mFooterIconId != footerIconId) {
- mFooterIconId = footerIconId;
- mMainHandler.post(mUpdateIcon);
- }
- } else {
- boolean isBranded = mSecurityController.isVpnBranded();
- mFooterTextContent = mContext.getResources().getText(
- isBranded ? R.string.branded_vpn_footer : R.string.vpn_footer);
- // Update the VPN footer icon, if needed.
- int footerIconId = isVpnEnabled
- ? (isBranded ? R.drawable.ic_qs_branded_vpn : R.drawable.ic_qs_vpn)
- : R.drawable.ic_qs_network_logging;
- if (mFooterIconId != footerIconId) {
- mFooterIconId = footerIconId;
- mMainHandler.post(mUpdateIcon);
- }
- mIsVisible = mIsIconVisible;
+ final boolean isDeviceManaged = mSecurityController.isDeviceManaged();
+ final boolean hasWorkProfile = mSecurityController.hasWorkProfile();
+ final boolean hasCACerts = mSecurityController.hasCACertInCurrentUser();
+ final boolean hasCACertsInWorkProfile = mSecurityController.hasCACertInWorkProfile();
+ final boolean isNetworkLoggingEnabled = mSecurityController.isNetworkLoggingEnabled();
+ final String vpnName = mSecurityController.getPrimaryVpnName();
+ final String vpnNameWorkProfile = mSecurityController.getWorkProfileVpnName();
+ final CharSequence organizationName = mSecurityController.getDeviceOwnerOrganizationName();
+ final CharSequence workProfileName = mSecurityController.getWorkProfileOrganizationName();
+ // Update visibility of footer
+ mIsVisible = isDeviceManaged || hasCACerts || hasCACertsInWorkProfile ||
+ vpnName != null || vpnNameWorkProfile != null;
+ // Update the string
+ mFooterTextContent = getFooterText(isDeviceManaged, hasWorkProfile,
+ hasCACerts, hasCACertsInWorkProfile, isNetworkLoggingEnabled, vpnName,
+ vpnNameWorkProfile, organizationName, workProfileName);
+ // Update the icon
+ int footerIconId = vpnName != null || vpnNameWorkProfile != null
+ ? R.drawable.ic_qs_vpn
+ : R.drawable.ic_info_outline;
+ if (mFooterIconId != footerIconId) {
+ mFooterIconId = footerIconId;
+ mMainHandler.post(mUpdateIcon);
}
mMainHandler.post(mUpdateDisplayState);
}
+ protected CharSequence getFooterText(boolean isDeviceManaged, boolean hasWorkProfile,
+ boolean hasCACerts, boolean hasCACertsInWorkProfile, boolean isNetworkLoggingEnabled,
+ String vpnName, String vpnNameWorkProfile, CharSequence organizationName,
+ CharSequence workProfileName) {
+ if (isDeviceManaged) {
+ if (hasCACerts || hasCACertsInWorkProfile || isNetworkLoggingEnabled) {
+ if (organizationName == null) {
+ return mContext.getString(
+ R.string.quick_settings_disclosure_management_monitoring);
+ }
+ return mContext.getString(
+ R.string.quick_settings_disclosure_named_management_monitoring,
+ organizationName);
+ }
+ if (vpnName != null && vpnNameWorkProfile != null) {
+ if (organizationName == null) {
+ return mContext.getString(R.string.quick_settings_disclosure_management_vpns);
+ }
+ return mContext.getString(R.string.quick_settings_disclosure_named_management_vpns,
+ organizationName);
+ }
+ if (vpnName != null || vpnNameWorkProfile != null) {
+ if (organizationName == null) {
+ return mContext.getString(
+ R.string.quick_settings_disclosure_management_named_vpn,
+ vpnName != null ? vpnName : vpnNameWorkProfile);
+ }
+ return mContext.getString(
+ R.string.quick_settings_disclosure_named_management_named_vpn,
+ organizationName,
+ vpnName != null ? vpnName : vpnNameWorkProfile);
+ }
+ if (organizationName == null) {
+ return mContext.getString(R.string.quick_settings_disclosure_management);
+ }
+ return mContext.getString(R.string.quick_settings_disclosure_named_management,
+ organizationName);
+ } // end if(isDeviceManaged)
+ if (hasCACertsInWorkProfile) {
+ if (workProfileName == null) {
+ return mContext.getString(
+ R.string.quick_settings_disclosure_managed_profile_monitoring);
+ }
+ return mContext.getString(
+ R.string.quick_settings_disclosure_named_managed_profile_monitoring,
+ workProfileName);
+ }
+ if (hasCACerts) {
+ return mContext.getString(R.string.quick_settings_disclosure_monitoring);
+ }
+ if (vpnName != null && vpnNameWorkProfile != null) {
+ return mContext.getString(R.string.quick_settings_disclosure_vpns);
+ }
+ if (vpnNameWorkProfile != null) {
+ return mContext.getString(R.string.quick_settings_disclosure_managed_profile_named_vpn,
+ vpnNameWorkProfile);
+ }
+ if (vpnName != null) {
+ if (hasWorkProfile) {
+ return mContext.getString(
+ R.string.quick_settings_disclosure_personal_profile_named_vpn,
+ vpnName);
+ }
+ return mContext.getString(R.string.quick_settings_disclosure_named_vpn,
+ vpnName);
+ }
+ return null;
+ }
+
@Override
public void onClick(DialogInterface dialog, int which) {
if (which == DialogInterface.BUTTON_NEGATIVE) {
@@ -182,18 +234,15 @@
final String profileOwnerPackage = mSecurityController.getProfileOwnerName();
final boolean isNetworkLoggingEnabled = mSecurityController.isNetworkLoggingEnabled();
final String primaryVpn = mSecurityController.getPrimaryVpnName();
- final String profileVpn = mSecurityController.getProfileVpnName();
+ final String profileVpn = mSecurityController.getWorkProfileVpnName();
final CharSequence deviceOwnerOrganization =
mSecurityController.getDeviceOwnerOrganizationName();
boolean hasProfileOwner = mSecurityController.hasProfileOwner();
- boolean isBranded = deviceOwnerPackage == null && mSecurityController.isVpnBranded();
mDialog = new SystemUIDialog(mContext);
- if (!isBranded) {
- mDialog.setTitle(getTitle(deviceOwnerPackage));
- }
+ mDialog.setTitle(getTitle(deviceOwnerPackage));
CharSequence msg = getMessage(deviceOwnerPackage, profileOwnerPackage, primaryVpn,
- profileVpn, deviceOwnerOrganization, hasProfileOwner, isBranded);
+ profileVpn, deviceOwnerOrganization, hasProfileOwner);
if (deviceOwnerPackage == null) {
mDialog.setMessage(msg);
if (mSecurityController.isVpnEnabled() && !mSecurityController.isVpnRestricted()) {
@@ -235,7 +284,7 @@
}
}
- mDialog.setButton(DialogInterface.BUTTON_POSITIVE, getPositiveButton(isBranded), this);
+ mDialog.setButton(DialogInterface.BUTTON_POSITIVE, getPositiveButton(), this);
mDialog.show();
mDialog.getWindow().setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
}
@@ -244,13 +293,13 @@
return mContext.getString(R.string.status_bar_settings_settings_button);
}
- private String getPositiveButton(boolean isBranded) {
- return mContext.getString(isBranded ? android.R.string.ok : R.string.quick_settings_done);
+ private String getPositiveButton() {
+ return mContext.getString(R.string.quick_settings_done);
}
protected CharSequence getMessage(String deviceOwnerPackage, String profileOwnerPackage,
String primaryVpn, String profileVpn, CharSequence deviceOwnerOrganization,
- boolean hasProfileOwner, boolean isBranded) {
+ boolean hasProfileOwner) {
if (deviceOwnerPackage != null) {
final SpannableStringBuilder message = new SpannableStringBuilder();
if (deviceOwnerOrganization != null) {
@@ -273,13 +322,8 @@
return mContext.getString(R.string.monitoring_description_app_personal_work,
profileOwnerPackage, profileVpn, primaryVpn);
} else {
- if (isBranded) {
- return mContext.getString(R.string.branded_monitoring_description_app_personal,
- primaryVpn);
- } else {
- return mContext.getString(R.string.monitoring_description_app_personal,
- primaryVpn);
- }
+ return mContext.getString(R.string.monitoring_description_app_personal,
+ primaryVpn);
}
} else if (profileVpn != null) {
return mContext.getString(R.string.monitoring_description_app_work,
@@ -305,7 +349,6 @@
@Override
public void run() {
mFooterIcon.setImageResource(mFooterIconId);
- mFooterIcon2.setImageResource(mFooterIcon2Id);
}
};
@@ -316,8 +359,7 @@
mFooterText.setText(mFooterTextContent);
}
mRootView.setVisibility(mIsVisible ? View.VISIBLE : View.GONE);
- mFooterIcon.setVisibility(mIsIconVisible ? View.VISIBLE : View.INVISIBLE);
- mFooterIcon2.setVisibility(mIsIcon2Visible ? View.VISIBLE : View.INVISIBLE);
+ if (mDivider != null) mDivider.setVisibility(mIsVisible ? View.GONE : View.VISIBLE);
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
index 6781c16..976efb2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
@@ -340,12 +340,12 @@
switch (state) {
case Tile.STATE_UNAVAILABLE:
return Utils.getDisabled(context,
- Utils.getColorAttr(context, android.R.attr.textColorTertiary));
+ Utils.getColorAttr(context, android.R.attr.textColorPrimary));
case Tile.STATE_INACTIVE:
return Utils.getDisabled(context,
- Utils.getColorAttr(context, android.R.attr.textColorSecondary));
+ Utils.getColorAttr(context, android.R.attr.colorForeground));
case Tile.STATE_ACTIVE:
- return Utils.getColorAttr(context, attr.textColorSecondary);
+ return Utils.getColorAttr(context, android.R.attr.textColorPrimary);
default:
Log.e("QSTile", "Invalid state " + state);
return 0;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
index d27bb85..9b20a7a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
@@ -259,22 +259,27 @@
ArrayList<Item> items = new ArrayList<Item>();
final Collection<CachedBluetoothDevice> devices = mController.getDevices();
if (devices != null) {
+ int connectedDevices = 0;
for (CachedBluetoothDevice device : devices) {
if (device.getBondState() == BluetoothDevice.BOND_NONE) continue;
final Item item = new Item();
item.icon = R.drawable.ic_qs_bluetooth_on;
item.line1 = device.getName();
+ item.tag = device;
int state = device.getMaxConnectionState();
if (state == BluetoothProfile.STATE_CONNECTED) {
item.icon = R.drawable.ic_qs_bluetooth_connected;
item.line2 = mContext.getString(R.string.quick_settings_connected);
item.canDisconnect = true;
+ items.add(connectedDevices, item);
+ connectedDevices++;
} else if (state == BluetoothProfile.STATE_CONNECTING) {
item.icon = R.drawable.ic_qs_bluetooth_connecting;
item.line2 = mContext.getString(R.string.quick_settings_connecting);
+ items.add(connectedDevices, item);
+ } else {
+ items.add(item);
}
- item.tag = device;
- items.add(item);
}
}
mItems.setItems(items.toArray(new Item[items.size()]));
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 ccebd7c..429ace6 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
@@ -65,7 +65,7 @@
import android.os.UserManager;
import android.provider.Settings;
import android.util.ArraySet;
-import android.util.LauncherIcons;
+import android.util.IconDrawableFactory;
import android.util.Log;
import android.util.MutableBoolean;
import android.view.Display;
@@ -121,6 +121,7 @@
ActivityManager mAm;
IActivityManager mIam;
PackageManager mPm;
+ IconDrawableFactory mDrawableFactory;
IPackageManager mIpm;
AssistUtils mAssistUtils;
WindowManager mWm;
@@ -139,7 +140,6 @@
int mDummyThumbnailHeight;
Paint mBgProtectionPaint;
Canvas mBgProtectionCanvas;
- LauncherIcons mLauncherIcons;
private final Handler mHandler = new H();
@@ -258,6 +258,7 @@
mAm = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
mIam = ActivityManager.getService();
mPm = context.getPackageManager();
+ mDrawableFactory = IconDrawableFactory.newInstance(context);
mIpm = AppGlobals.getPackageManager();
mAssistUtils = new AssistUtils(context);
mWm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
@@ -296,8 +297,6 @@
Collections.addAll(sRecentsBlacklist,
res.getStringArray(R.array.recents_blacklist_array));
-
- mLauncherIcons = new LauncherIcons(context);
}
/**
@@ -808,13 +807,19 @@
* Returns the content description for a given task, badging it if necessary. The content
* description joins the app and activity labels.
*/
- public String getBadgedContentDescription(ActivityInfo info, int userId, Resources res) {
+ public String getBadgedContentDescription(ActivityInfo info, int userId,
+ ActivityManager.TaskDescription td, Resources res) {
// If we are mocking, then return a mock label
if (RecentsDebugFlags.Static.EnableMockTasks) {
return "Recent Task Content Description: " + userId;
}
- String activityLabel = info.loadLabel(mPm).toString();
+ String activityLabel;
+ if (td != null && td.getLabel() != null) {
+ activityLabel = td.getLabel();
+ } else {
+ activityLabel = info.loadLabel(mPm).toString();
+ }
String applicationLabel = info.applicationInfo.loadLabel(mPm).toString();
String badgedApplicationLabel = getBadgedLabel(applicationLabel, userId);
return applicationLabel.equals(activityLabel) ? badgedApplicationLabel
@@ -834,8 +839,7 @@
return new ColorDrawable(0xFF666666);
}
- Drawable icon = mLauncherIcons.wrapIconDrawableWithShadow(info.loadIcon(mPm));
- return getBadgedIcon(icon, userId);
+ return mDrawableFactory.getBadgedIcon(info, info.applicationInfo, userId);
}
/**
@@ -850,8 +854,7 @@
return new ColorDrawable(0xFF666666);
}
- Drawable icon = mLauncherIcons.wrapIconDrawableWithShadow(appInfo.loadIcon(mPm));
- return getBadgedIcon(icon, userId);
+ return mDrawableFactory.getBadgedIcon(appInfo, userId);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
index 5c25bfd..78c71a1 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
@@ -182,7 +182,8 @@
// Load the title, icon, and color
ActivityInfo info = loader.getAndUpdateActivityInfo(taskKey);
String title = loader.getAndUpdateActivityTitle(taskKey, t.taskDescription);
- String titleDescription = loader.getAndUpdateContentDescription(taskKey, res);
+ String titleDescription = loader.getAndUpdateContentDescription(taskKey,
+ t.taskDescription, res);
String dismissDescription = String.format(dismissDescFormat, titleDescription);
String appInfoDescription = String.format(appInfoDescFormat, titleDescription);
Drawable icon = isStackTask
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
index e8ffb91..5e78b61 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
@@ -449,7 +449,8 @@
* Returns the cached task content description if the task key is not expired, updating the
* cache if it is.
*/
- String getAndUpdateContentDescription(Task.TaskKey taskKey, Resources res) {
+ String getAndUpdateContentDescription(Task.TaskKey taskKey, ActivityManager.TaskDescription td,
+ Resources res) {
SystemServicesProxy ssp = Recents.getSystemServices();
// Return the cached content description if it exists
@@ -461,8 +462,15 @@
// All short paths failed, load the label from the activity info and cache it
ActivityInfo activityInfo = getAndUpdateActivityInfo(taskKey);
if (activityInfo != null) {
- label = ssp.getBadgedContentDescription(activityInfo, taskKey.userId, res);
- mContentDescriptionCache.put(taskKey, label);
+ label = ssp.getBadgedContentDescription(activityInfo, taskKey.userId, td, res);
+ if (td == null) {
+ // Only add to the cache if the task description is null, otherwise, it is possible
+ // for the task description to change between calls without the last active time
+ // changing (ie. between preloading and Overview starting) which would lead to stale
+ // content descriptions
+ // TODO: Investigate improving this
+ mContentDescriptionCache.put(taskKey, label);
+ }
return label;
}
// If the content description does not exist, return an empty label for now, but do not
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
index cc91753..dff09bd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
@@ -1494,7 +1494,7 @@
return getActualHeight();
}
if (mGuts != null && mGuts.isExposed()) {
- return mGuts.getHeight();
+ return mGuts.getIntrinsicHeight();
} else if ((isChildInGroup() && !isGroupExpanded())) {
return mPrivateLayout.getMinHeight();
} else if (mShowAmbient) {
@@ -1835,7 +1835,9 @@
@Override
public int getMinHeight() {
- if (!mOnKeyguard && mIsHeadsUp && mHeadsUpManager.isTrackingHeadsUp()) {
+ if (mGuts != null && mGuts.isExposed()) {
+ return mGuts.getIntrinsicHeight();
+ } else if (!mOnKeyguard && mIsHeadsUp && mHeadsUpManager.isTrackingHeadsUp()) {
return getPinnedHeadsUpHeight(false /* atLeastMinHeight */);
} else if (mIsSummaryWithChildren && !isGroupExpanded() && !mShowingPublic) {
return mChildrenContainer.getMinHeight();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java
index 0a99ee6..1375310 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java
@@ -74,7 +74,8 @@
private Handler mHandler;
private Runnable mFalsingCheck;
private boolean mNeedsFalsingProtection;
- private OnGutsClosedListener mListener;
+ private OnGutsClosedListener mClosedListener;
+ private OnHeightChangedListener mHeightListener;
private GutsContent mGutsContent;
@@ -88,6 +89,11 @@
public View getContentView();
/**
+ * @return the actual height of the content.
+ */
+ public int getActualHeight();
+
+ /**
* Called when the guts view have been told to close, typically after an outside
* interaction. Returning {@code true} here will prevent the guts view to close.
*/
@@ -103,6 +109,10 @@
public void onGutsClosed(NotificationGuts guts);
}
+ public interface OnHeightChangedListener {
+ public void onHeightChanged(NotificationGuts guts);
+ }
+
interface OnSettingsClickListener {
void onClick(View v, int appUid);
}
@@ -126,7 +136,6 @@
public NotificationGuts(Context context) {
this(context, null);
-
}
public void setGutsContent(GutsContent content) {
@@ -190,8 +199,8 @@
public void closeControls(int x, int y, boolean save) {
if (getWindowToken() == null) {
- if (mListener != null) {
- mListener.onGutsClosed(this);
+ if (mClosedListener != null) {
+ mClosedListener.onGutsClosed(this);
}
return;
}
@@ -199,8 +208,8 @@
animateClose(x, y);
}
setExposed(false, mNeedsFalsingProtection);
- if (mListener != null) {
- mListener.onGutsClosed(this);
+ if (mClosedListener != null) {
+ mClosedListener.onGutsClosed(this);
}
}
@@ -235,6 +244,10 @@
return mActualHeight;
}
+ public int getIntrinsicHeight() {
+ return mGutsContent != null && mExposed ? mGutsContent.getActualHeight() : getHeight();
+ }
+
public void setClipTopAmount(int clipTopAmount) {
mClipTopAmount = clipTopAmount;
invalidate();
@@ -252,7 +265,17 @@
}
public void setClosedListener(OnGutsClosedListener listener) {
- mListener = listener;
+ mClosedListener = listener;
+ }
+
+ public void setHeightChangedListener(OnHeightChangedListener listener) {
+ mHeightListener = listener;
+ }
+
+ protected void onHeightChanged() {
+ if (mHeightListener != null) {
+ mHeightListener.onHeightChanged(this);
+ }
}
public void setExposed(boolean exposed, boolean needsFalsingProtection) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java
index 8925189..f3c7b84 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java
@@ -403,4 +403,9 @@
}
return false;
}
+
+ @Override
+ public int getActualHeight() {
+ return getHeight();
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMenuRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMenuRow.java
index 802925a..7563fd1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMenuRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMenuRow.java
@@ -317,6 +317,10 @@
}
private void dismiss(View animView, float velocity) {
+ if (mFadeAnimator != null) {
+ mFadeAnimator.cancel();
+ }
+ mHandler.removeCallbacks(mCheckForDrag);
mMenuSnappedTo = false;
mDismissing = true;
mSwipeHelper.dismiss(animView, velocity);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSnooze.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSnooze.java
index 4a3f112..ccc99c5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSnooze.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSnooze.java
@@ -21,35 +21,47 @@
import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
import android.content.Context;
import android.content.res.Resources;
-import android.graphics.Color;
+import android.graphics.Typeface;
import android.service.notification.SnoozeCriterion;
import android.service.notification.StatusBarNotification;
+import android.text.SpannableString;
+import android.text.style.StyleSpan;
import android.util.AttributeSet;
import android.util.Log;
-import android.util.TypedValue;
+import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
+
+import com.android.systemui.Interpolators;
import com.android.systemui.R;
public class NotificationSnooze extends LinearLayout
implements NotificationGuts.GutsContent, View.OnClickListener {
- private static final int MAX_ASSISTANT_SUGGESTIONS = 2;
+ private static final int MAX_ASSISTANT_SUGGESTIONS = 1;
private NotificationGuts mGutsContainer;
private NotificationSwipeActionHelper mSnoozeListener;
private StatusBarNotification mSbn;
private TextView mSelectedOptionText;
private TextView mUndoButton;
- private ViewGroup mSnoozeOptionView;
+ private ImageView mExpandButton;
+ private View mDivider;
+ private ViewGroup mSnoozeOptionContainer;
private List<SnoozeOption> mSnoozeOptions;
- private boolean mSnoozing;
+ private int mCollapsedHeight;
private SnoozeOption mSelectedOption;
+ private boolean mSnoozing;
+ private boolean mExpanded;
+ private AnimatorSet mExpandAnimation;
public NotificationSnooze(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -58,16 +70,21 @@
@Override
protected void onFinishInflate() {
super.onFinishInflate();
+ mCollapsedHeight = getResources().getDimensionPixelSize(R.dimen.snooze_snackbar_min_height);
+ findViewById(R.id.notification_snooze).setOnClickListener(this);
+ mSelectedOptionText = (TextView) findViewById(R.id.snooze_option_default);
+ mUndoButton = (TextView) findViewById(R.id.undo);
+ mUndoButton.setOnClickListener(this);
+ mExpandButton = (ImageView) findViewById(R.id.expand_button);
+ mDivider = findViewById(R.id.divider);
+ mDivider.setAlpha(0f);
+ mSnoozeOptionContainer = (ViewGroup) findViewById(R.id.snooze_options);
+ mSnoozeOptionContainer.setAlpha(0f);
+
// Create the different options based on list
mSnoozeOptions = getDefaultSnoozeOptions();
createOptionViews();
- // Snackbar
- mSelectedOptionText = findViewById(R.id.snooze_option_default);
- mSelectedOptionText.setOnClickListener(this);
- mUndoButton = findViewById(R.id.undo);
- mUndoButton.setOnClickListener(this);
-
// Default to first option in list
setSelected(mSnoozeOptions.get(0));
}
@@ -96,52 +113,68 @@
private SnoozeOption createOption(int descriptionResId, int minutes) {
Resources res = getResources();
- String resultText = String.format(
- res.getString(R.string.snoozed_for_time), res.getString(descriptionResId));
- return new SnoozeOption(null, minutes, res.getString(descriptionResId), resultText);
+ final String description = res.getString(descriptionResId);
+ String resultText = String.format(res.getString(R.string.snoozed_for_time), description);
+ SpannableString string = new SpannableString(resultText);
+ string.setSpan(new StyleSpan(Typeface.BOLD),
+ resultText.length() - description.length(), resultText.length(), 0 /* flags */);
+ return new SnoozeOption(null, minutes, res.getString(descriptionResId), string);
}
private void createOptionViews() {
- mSnoozeOptionView = findViewById(R.id.snooze_options);
- mSnoozeOptionView.removeAllViews();
- mSnoozeOptionView.setVisibility(View.GONE);
- final Resources res = getResources();
- final int textSize = res.getDimensionPixelSize(R.dimen.snooze_option_text_size);
- final int p = res.getDimensionPixelSize(R.dimen.snooze_option_padding);
-
- // Add all the options
+ mSnoozeOptionContainer.removeAllViews();
+ LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(
+ Context.LAYOUT_INFLATER_SERVICE);
for (int i = 0; i < mSnoozeOptions.size(); i++) {
SnoozeOption option = mSnoozeOptions.get(i);
- TextView tv = new TextView(getContext());
- tv.setTextColor(Color.WHITE);
- tv.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
- tv.setPadding(p, p, p, p);
- mSnoozeOptionView.addView(tv);
+ TextView tv = (TextView) inflater.inflate(R.layout.notification_snooze_option,
+ mSnoozeOptionContainer, false);
+ mSnoozeOptionContainer.addView(tv);
tv.setText(option.description);
tv.setTag(option);
tv.setOnClickListener(this);
}
+ }
- // Add the undo option as final item
- TextView tv = new TextView(getContext());
- tv.setTextColor(Color.WHITE);
- tv.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
- tv.setPadding(p, p, p, p);
- mSnoozeOptionView.addView(tv);
- tv.setText(R.string.snooze_option_dont_snooze);
- tv.setOnClickListener(this);
+ private void hideSelectedOption() {
+ final int childCount = mSnoozeOptionContainer.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ final View child = mSnoozeOptionContainer.getChildAt(i);
+ child.setVisibility(child.getTag() == mSelectedOption ? View.GONE : View.VISIBLE);
+ }
}
private void showSnoozeOptions(boolean show) {
- mSelectedOptionText.setVisibility(show ? View.GONE : View.VISIBLE);
- mUndoButton.setVisibility(show ? View.GONE : View.VISIBLE);
- mSnoozeOptionView.setVisibility(show ? View.VISIBLE : View.GONE);
+ mExpanded = show;
+ animateSnoozeOptions(show);
+ int drawableId = show ? com.android.internal.R.drawable.ic_collapse_notification
+ : com.android.internal.R.drawable.ic_expand_notification;
+ mExpandButton.setImageResource(drawableId);
+ if (mGutsContainer != null) {
+ mGutsContainer.onHeightChanged();
+ }
+ }
+
+ private void animateSnoozeOptions(boolean show) {
+ if (mExpandAnimation != null) {
+ mExpandAnimation.cancel();
+ }
+ ObjectAnimator dividerAnim = ObjectAnimator.ofFloat(mDivider, View.ALPHA,
+ mDivider.getAlpha(), show ? 1f : 0f);
+ ObjectAnimator optionAnim = ObjectAnimator.ofFloat(mSnoozeOptionContainer, View.ALPHA,
+ mSnoozeOptionContainer.getAlpha(), show ? 1f : 0f);
+ mExpandAnimation = new AnimatorSet();
+ mExpandAnimation.playTogether(dividerAnim, optionAnim);
+ mExpandAnimation.setDuration(150);
+ mExpandAnimation.setInterpolator(show ? Interpolators.ALPHA_IN : Interpolators.ALPHA_OUT);
+ mExpandAnimation.start();
}
private void setSelected(SnoozeOption option) {
mSelectedOption = option;
mSelectedOptionText.setText(option.confirmation);
showSnoozeOptions(false);
+ hideSelectedOption();
}
@Override
@@ -153,17 +186,28 @@
final SnoozeOption tag = (SnoozeOption) v.getTag();
if (tag != null) {
setSelected(tag);
- } else if (id == R.id.snooze_option_default) {
- // Show more snooze options
- showSnoozeOptions(true);
+ } else if (id == R.id.notification_snooze) {
+ // Toggle snooze options
+ showSnoozeOptions(!mExpanded);
} else {
- undoSnooze();
+ // Undo snooze was selected
+ mSelectedOption = null;
+ int[] parentLoc = new int[2];
+ int[] targetLoc = new int[2];
+ mGutsContainer.getLocationOnScreen(parentLoc);
+ v.getLocationOnScreen(targetLoc);
+ final int centerX = v.getWidth() / 2;
+ final int centerY = v.getHeight() / 2;
+ final int x = targetLoc[0] - parentLoc[0] + centerX;
+ final int y = targetLoc[1] - parentLoc[1] + centerY;
+ showSnoozeOptions(false);
+ mGutsContainer.closeControls(x, y, false /* save */);
}
}
- private void undoSnooze() {
- mSelectedOption = null;
- mGutsContainer.closeControls(-1 /* x */, -1 /* y */, true /* notify */);
+ @Override
+ public int getActualHeight() {
+ return mExpanded ? getHeight() : mCollapsedHeight;
}
@Override
@@ -173,6 +217,8 @@
@Override
public View getContentView() {
+ // Reset the view before use
+ setSelected(mSnoozeOptions.get(0));
return this;
}
@@ -197,11 +243,8 @@
mSnoozing = true;
mSnoozeListener.snooze(mSbn, mSelectedOption);
return true;
- } else {
- // Reset the view once it's closed
- setSelected(mSnoozeOptions.get(0));
- showSnoozeOptions(false);
}
+ // The view should be closed
return false;
}
}
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 79191f3..46d6415 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -5774,6 +5774,9 @@
snoozeGuts.setSnoozeListener(mStackScroller.getSwipeActionHelper());
snoozeGuts.setStatusBarNotification(sbn);
snoozeGuts.setSnoozeOptions(row.getEntry().snoozeCriteria);
+ guts.setHeightChangedListener((NotificationGuts g) -> {
+ mStackScroller.onHeightChanged(row, row.isShown() /* needsAnimation */);
+ });
}
if (gutsView instanceof NotificationInfo) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityController.java
index 3f8e41a..1fb9b69 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityController.java
@@ -23,16 +23,20 @@
/** Whether the device has device owner, even if not on this user. */
boolean isDeviceManaged();
boolean hasProfileOwner();
+ boolean hasWorkProfile();
String getDeviceOwnerName();
String getProfileOwnerName();
CharSequence getDeviceOwnerOrganizationName();
+ CharSequence getWorkProfileOrganizationName();
boolean isNetworkLoggingEnabled();
boolean isVpnEnabled();
boolean isVpnRestricted();
/** Whether the VPN app should use branded VPN iconography. */
boolean isVpnBranded();
String getPrimaryVpnName();
- String getProfileVpnName();
+ String getWorkProfileVpnName();
+ boolean hasCACertInCurrentUser();
+ boolean hasCACertInWorkProfile();
void onUserSwitched(int newUserId);
public interface SecurityControllerCallback {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
index 19ced23..fcb7289 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
@@ -138,6 +138,13 @@
}
@Override
+ public CharSequence getWorkProfileOrganizationName() {
+ final int profileId = getWorkProfileUserId(mCurrentUserId);
+ if (profileId == UserHandle.USER_NULL) return null;
+ return mDevicePolicyManager.getOrganizationNameForUser(profileId);
+ }
+
+ @Override
public String getPrimaryVpnName() {
VpnConfig cfg = mCurrentVpns.get(mVpnUserId);
if (cfg != null) {
@@ -147,16 +154,27 @@
}
}
+ private int getWorkProfileUserId(int userId) {
+ for (final UserInfo userInfo : mUserManager.getProfiles(userId)) {
+ if (userInfo.isManagedProfile()) {
+ return userInfo.id;
+ }
+ }
+ return UserHandle.USER_NULL;
+ }
+
@Override
- public String getProfileVpnName() {
- for (int profileId : mUserManager.getProfileIdsWithDisabled(mVpnUserId)) {
- if (profileId == mVpnUserId) {
- continue;
- }
- VpnConfig cfg = mCurrentVpns.get(profileId);
- if (cfg != null) {
- return getNameForVpnConfig(cfg, UserHandle.of(profileId));
- }
+ public boolean hasWorkProfile() {
+ return getWorkProfileUserId(mCurrentUserId) != UserHandle.USER_NULL;
+ }
+
+ @Override
+ public String getWorkProfileVpnName() {
+ final int profileId = getWorkProfileUserId(mVpnUserId);
+ if (profileId == UserHandle.USER_NULL) return null;
+ VpnConfig cfg = mCurrentVpns.get(profileId);
+ if (cfg != null) {
+ return getNameForVpnConfig(cfg, UserHandle.of(profileId));
}
return null;
}
@@ -199,6 +217,18 @@
}
@Override
+ public boolean hasCACertInCurrentUser() {
+ //TODO: implement
+ return false;
+ }
+
+ @Override
+ public boolean hasCACertInWorkProfile() {
+ //TODO: implement
+ return false;
+ }
+
+ @Override
public void removeCallback(SecurityControllerCallback callback) {
synchronized (mCallbacks) {
if (callback == null) return;
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 697cac9..b8b046b 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -433,7 +433,7 @@
return false;
}
});
- row.icon = (ImageButton) row.view.findViewById(R.id.volume_row_icon);
+ row.icon = row.view.findViewById(R.id.volume_row_icon);
row.icon.setImageResource(iconRes);
if (row.stream != AudioSystem.STREAM_ACCESSIBILITY) {
row.icon.setOnClickListener(new OnClickListener() {
@@ -465,6 +465,8 @@
row.userAttempt = 0; // reset the grace period, slider updates immediately
}
});
+ } else {
+ row.icon.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
index 1ff373c..ff644d8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
@@ -40,6 +40,15 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+/*
+ * Compile and run the whole SystemUI test suite:
+ runtest --path frameworks/base/packages/SystemUI/tests
+ *
+ * Compile and run just this class:
+ runtest --path \
+ frameworks/base/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
+*/
+
@SmallTest
@RunWith(AndroidJUnit4.class)
public class QSSecurityFooterTest extends SysuiTestCase {
@@ -47,11 +56,11 @@
private final String MANAGING_ORGANIZATION = "organization";
private final String DEVICE_OWNER_PACKAGE = "TestDPC";
private final String VPN_PACKAGE = "TestVPN";
+ private final String VPN_PACKAGE_2 = "TestVPN 2";
private ViewGroup mRootView;
private TextView mFooterText;
private TestableImageView mFooterIcon;
- private TestableImageView mFooterIcon2;
private QSSecurityFooter mFooter;
private SecurityController mSecurityController = mock(SecurityController.class);
@@ -69,15 +78,12 @@
mRootView = (ViewGroup) mFooter.getView();
mFooterText = (TextView) mRootView.findViewById(R.id.footer_text);
mFooterIcon = (TestableImageView) mRootView.findViewById(R.id.footer_icon);
- mFooterIcon2 = (TestableImageView) mRootView.findViewById(R.id.footer_icon2);
mFooter.setHostEnvironment(null);
}
@Test
public void testUnmanaged() {
when(mSecurityController.isDeviceManaged()).thenReturn(false);
- when(mSecurityController.isVpnEnabled()).thenReturn(false);
- when(mSecurityController.isVpnBranded()).thenReturn(false);
mFooter.refreshState();
waitForIdleSync(mFooter.mHandler);
@@ -91,8 +97,12 @@
mFooter.refreshState();
waitForIdleSync(mFooter.mHandler);
- assertEquals(mContext.getString(R.string.do_disclosure_generic), mFooterText.getText());
+ assertEquals(mContext.getString(R.string.quick_settings_disclosure_management),
+ mFooterText.getText());
assertEquals(View.VISIBLE, mRootView.getVisibility());
+ assertEquals(View.VISIBLE, mFooterIcon.getVisibility());
+ // -1 == never set.
+ assertEquals(-1, mFooterIcon.getLastImageResource());
}
@Test
@@ -103,37 +113,100 @@
mFooter.refreshState();
waitForIdleSync(mFooter.mHandler);
- assertEquals(mContext.getString(R.string.do_disclosure_with_name, MANAGING_ORGANIZATION),
+ assertEquals(mContext.getString(R.string.quick_settings_disclosure_named_management,
+ MANAGING_ORGANIZATION),
mFooterText.getText());
assertEquals(View.VISIBLE, mRootView.getVisibility());
+ assertEquals(View.VISIBLE, mFooterIcon.getVisibility());
+ // -1 == never set.
+ assertEquals(-1, mFooterIcon.getLastImageResource());
}
@Test
public void testNetworkLoggingEnabled() {
when(mSecurityController.isDeviceManaged()).thenReturn(true);
when(mSecurityController.isNetworkLoggingEnabled()).thenReturn(true);
- when(mSecurityController.isVpnEnabled()).thenReturn(false);
mFooter.refreshState();
waitForIdleSync(mFooter.mHandler);
- assertEquals(View.VISIBLE, mFooterIcon.getVisibility());
- assertEquals(R.drawable.ic_qs_network_logging, mFooterIcon.getLastImageResource());
- assertEquals(View.INVISIBLE, mFooterIcon2.getVisibility());
- }
-
- @Test
- public void testVpnEnabled() {
- when(mSecurityController.isDeviceManaged()).thenReturn(true);
- when(mSecurityController.isNetworkLoggingEnabled()).thenReturn(false);
- when(mSecurityController.isVpnEnabled()).thenReturn(true);
- when(mSecurityController.isVpnBranded()).thenReturn(false);
- mFooter.refreshState();
-
- waitForIdleSync(mFooter.mHandler);
+ assertEquals(mContext.getString(R.string.quick_settings_disclosure_management_monitoring),
+ mFooterText.getText());
assertEquals(View.VISIBLE, mFooterIcon.getVisibility());
// -1 == never set.
assertEquals(-1, mFooterIcon.getLastImageResource());
- assertEquals(View.INVISIBLE, mFooterIcon2.getVisibility());
+
+ // Same situation, but with organization name set
+ when(mSecurityController.getDeviceOwnerOrganizationName())
+ .thenReturn(MANAGING_ORGANIZATION);
+ mFooter.refreshState();
+
+ waitForIdleSync(mFooter.mHandler);
+ assertEquals(mContext.getString(
+ R.string.quick_settings_disclosure_named_management_monitoring,
+ MANAGING_ORGANIZATION),
+ mFooterText.getText());
+ }
+
+ @Test
+ public void testManagedCACertsInstalled() {
+ when(mSecurityController.isDeviceManaged()).thenReturn(true);
+ when(mSecurityController.hasCACertInCurrentUser()).thenReturn(true);
+ mFooter.refreshState();
+
+ waitForIdleSync(mFooter.mHandler);
+ assertEquals(mContext.getString(R.string.quick_settings_disclosure_management_monitoring),
+ mFooterText.getText());
+ }
+
+ @Test
+ public void testManagedOneVpnEnabled() {
+ when(mSecurityController.isDeviceManaged()).thenReturn(true);
+ when(mSecurityController.isVpnEnabled()).thenReturn(true);
+ when(mSecurityController.getPrimaryVpnName()).thenReturn(VPN_PACKAGE);
+ mFooter.refreshState();
+
+ waitForIdleSync(mFooter.mHandler);
+ assertEquals(mContext.getString(R.string.quick_settings_disclosure_management_named_vpn,
+ VPN_PACKAGE),
+ mFooterText.getText());
+ assertEquals(View.VISIBLE, mFooterIcon.getVisibility());
+ assertEquals(R.drawable.ic_qs_vpn, mFooterIcon.getLastImageResource());
+
+ // Same situation, but with organization name set
+ when(mSecurityController.getDeviceOwnerOrganizationName())
+ .thenReturn(MANAGING_ORGANIZATION);
+ mFooter.refreshState();
+
+ waitForIdleSync(mFooter.mHandler);
+ assertEquals(mContext.getString(
+ R.string.quick_settings_disclosure_named_management_named_vpn,
+ MANAGING_ORGANIZATION, VPN_PACKAGE),
+ mFooterText.getText());
+ }
+
+ @Test
+ public void testManagedTwoVpnsEnabled() {
+ when(mSecurityController.isDeviceManaged()).thenReturn(true);
+ when(mSecurityController.isVpnEnabled()).thenReturn(true);
+ when(mSecurityController.getPrimaryVpnName()).thenReturn(VPN_PACKAGE);
+ when(mSecurityController.getWorkProfileVpnName()).thenReturn(VPN_PACKAGE_2);
+ mFooter.refreshState();
+
+ waitForIdleSync(mFooter.mHandler);
+ assertEquals(mContext.getString(R.string.quick_settings_disclosure_management_vpns),
+ mFooterText.getText());
+ assertEquals(View.VISIBLE, mFooterIcon.getVisibility());
+ assertEquals(R.drawable.ic_qs_vpn, mFooterIcon.getLastImageResource());
+
+ // Same situation, but with organization name set
+ when(mSecurityController.getDeviceOwnerOrganizationName())
+ .thenReturn(MANAGING_ORGANIZATION);
+ mFooter.refreshState();
+
+ waitForIdleSync(mFooter.mHandler);
+ assertEquals(mContext.getString(R.string.quick_settings_disclosure_named_management_vpns,
+ MANAGING_ORGANIZATION),
+ mFooterText.getText());
}
@Test
@@ -141,15 +214,101 @@
when(mSecurityController.isDeviceManaged()).thenReturn(true);
when(mSecurityController.isNetworkLoggingEnabled()).thenReturn(true);
when(mSecurityController.isVpnEnabled()).thenReturn(true);
- when(mSecurityController.isVpnBranded()).thenReturn(false);
+ when(mSecurityController.getPrimaryVpnName()).thenReturn("VPN Test App");
mFooter.refreshState();
waitForIdleSync(mFooter.mHandler);
assertEquals(View.VISIBLE, mFooterIcon.getVisibility());
- assertEquals(View.VISIBLE, mFooterIcon2.getVisibility());
+ assertEquals(R.drawable.ic_qs_vpn, mFooterIcon.getLastImageResource());
+ assertEquals(mContext.getString(R.string.quick_settings_disclosure_management_monitoring),
+ mFooterText.getText());
+ }
+
+ @Test
+ public void testWorkProfileCACertsInstalled() {
+ when(mSecurityController.isDeviceManaged()).thenReturn(false);
+ when(mSecurityController.hasCACertInWorkProfile()).thenReturn(true);
+ mFooter.refreshState();
+
+ waitForIdleSync(mFooter.mHandler);
// -1 == never set.
assertEquals(-1, mFooterIcon.getLastImageResource());
- assertEquals(-1, mFooterIcon2.getLastImageResource());
+ assertEquals(mContext.getString(
+ R.string.quick_settings_disclosure_managed_profile_monitoring),
+ mFooterText.getText());
+
+ // Same situation, but with organization name set
+ when(mSecurityController.getWorkProfileOrganizationName())
+ .thenReturn(MANAGING_ORGANIZATION);
+ mFooter.refreshState();
+
+ waitForIdleSync(mFooter.mHandler);
+ assertEquals(mContext.getString(
+ R.string.quick_settings_disclosure_named_managed_profile_monitoring,
+ MANAGING_ORGANIZATION),
+ mFooterText.getText());
+ }
+
+ @Test
+ public void testCACertsInstalled() {
+ when(mSecurityController.isDeviceManaged()).thenReturn(false);
+ when(mSecurityController.hasCACertInCurrentUser()).thenReturn(true);
+ mFooter.refreshState();
+
+ waitForIdleSync(mFooter.mHandler);
+ // -1 == never set.
+ assertEquals(-1, mFooterIcon.getLastImageResource());
+ assertEquals(mContext.getString(R.string.quick_settings_disclosure_monitoring),
+ mFooterText.getText());
+ }
+
+ @Test
+ public void testTwoVpnsEnabled() {
+ when(mSecurityController.isVpnEnabled()).thenReturn(true);
+ when(mSecurityController.getPrimaryVpnName()).thenReturn(VPN_PACKAGE);
+ when(mSecurityController.getWorkProfileVpnName()).thenReturn(VPN_PACKAGE_2);
+ mFooter.refreshState();
+
+ waitForIdleSync(mFooter.mHandler);
+ assertEquals(R.drawable.ic_qs_vpn, mFooterIcon.getLastImageResource());
+ assertEquals(mContext.getString(R.string.quick_settings_disclosure_vpns),
+ mFooterText.getText());
+ }
+
+ @Test
+ public void testWorkProfileVpnEnabled() {
+ when(mSecurityController.isVpnEnabled()).thenReturn(true);
+ when(mSecurityController.getWorkProfileVpnName()).thenReturn(VPN_PACKAGE_2);
+ mFooter.refreshState();
+
+ waitForIdleSync(mFooter.mHandler);
+ assertEquals(R.drawable.ic_qs_vpn, mFooterIcon.getLastImageResource());
+ assertEquals(mContext.getString(
+ R.string.quick_settings_disclosure_managed_profile_named_vpn,
+ VPN_PACKAGE_2),
+ mFooterText.getText());
+ }
+
+ @Test
+ public void testVpnEnabled() {
+ when(mSecurityController.isVpnEnabled()).thenReturn(true);
+ when(mSecurityController.getPrimaryVpnName()).thenReturn(VPN_PACKAGE);
+ mFooter.refreshState();
+
+ waitForIdleSync(mFooter.mHandler);
+ assertEquals(R.drawable.ic_qs_vpn, mFooterIcon.getLastImageResource());
+ assertEquals(mContext.getString(R.string.quick_settings_disclosure_named_vpn,
+ VPN_PACKAGE),
+ mFooterText.getText());
+
+ when(mSecurityController.hasWorkProfile()).thenReturn(true);
+ mFooter.refreshState();
+
+ waitForIdleSync(mFooter.mHandler);
+ assertEquals(mContext.getString(
+ R.string.quick_settings_disclosure_personal_profile_named_vpn,
+ VPN_PACKAGE),
+ mFooterText.getText());
}
@Test
@@ -160,8 +319,7 @@
null /* primaryVpn */,
null /* profileVpn */,
null /* deviceOwnerOrganization */,
- false /* hasProfileOwner */,
- false /* isBranded */));
+ false /* hasProfileOwner */));
}
@Test
@@ -172,8 +330,7 @@
VPN_PACKAGE,
null /* profileVpn */,
null /* deviceOwnerOrganization */,
- false /* hasProfileOwner */,
- false /* isBranded */));
+ false /* hasProfileOwner */));
}
@Test
@@ -184,8 +341,7 @@
null /* primaryVpn */,
null /* profileVpn */,
MANAGING_ORGANIZATION,
- false /* hasProfileOwner */,
- false /* isBranded */));
+ false /* hasProfileOwner */));
}
@Test
@@ -196,8 +352,7 @@
VPN_PACKAGE,
null /* profileVpn */,
MANAGING_ORGANIZATION,
- false /* hasProfileOwner */,
- false /* isBranded */));
+ false /* hasProfileOwner */));
}
private CharSequence getExpectedMessage(boolean hasDeviceOwnerOrganization, boolean hasVPN) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeSecurityController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeSecurityController.java
index 157b8a0..fee5e32 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeSecurityController.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeSecurityController.java
@@ -36,6 +36,11 @@
}
@Override
+ public boolean hasWorkProfile() {
+ return false;
+ }
+
+ @Override
public String getDeviceOwnerName() {
return null;
}
@@ -51,6 +56,11 @@
}
@Override
+ public CharSequence getWorkProfileOrganizationName() {
+ return null;
+ }
+
+ @Override
public boolean isNetworkLoggingEnabled() {
return false;
}
@@ -76,11 +86,21 @@
}
@Override
- public String getProfileVpnName() {
+ public String getWorkProfileVpnName() {
return null;
}
@Override
+ public boolean hasCACertInCurrentUser() {
+ return false;
+ }
+
+ @Override
+ public boolean hasCACertInWorkProfile() {
+ return false;
+ }
+
+ @Override
public void onUserSwitched(int newUserId) {
}
diff --git a/services/Android.mk b/services/Android.mk
index a4c891b..5c863b0 100644
--- a/services/Android.mk
+++ b/services/Android.mk
@@ -27,6 +27,7 @@
appwidget \
autofill \
backup \
+ companion \
coverage\
devicepolicy \
midi \
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
index d6f5256..1b5b2c6 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
@@ -48,6 +48,7 @@
import android.os.UserManager;
import android.os.UserManagerInternal;
import android.provider.Settings;
+import android.service.autofill.FillEventHistory;
import android.util.LocalLog;
import android.util.Log;
import android.util.Slog;
@@ -184,7 +185,7 @@
}
@Override
- public void onStopUser(int userId) {
+ public void onCleanupUser(int userId) {
synchronized (mLock) {
removeCachedServiceLocked(userId);
}
@@ -211,7 +212,7 @@
/**
* Peeks the service instance for a user.
*
- * @return service instance or null if not already present
+ * @return service instance or {@code null} if not already present
*/
@Nullable
AutofillManagerServiceImpl peekServiceForUserLocked(int userId) {
@@ -398,6 +399,21 @@
}
@Override
+ public FillEventHistory getFillEventHistory() throws RemoteException {
+ UserHandle user = getCallingUserHandle();
+ int uid = getCallingUid();
+
+ synchronized (mLock) {
+ AutofillManagerServiceImpl service = peekServiceForUserLocked(user.getIdentifier());
+ if (service != null) {
+ return service.getFillEventHistory(uid);
+ }
+ }
+
+ return null;
+ }
+
+ @Override
public boolean restoreSession(int sessionId, IBinder activityToken, IBinder appCallback)
throws RemoteException {
activityToken = Preconditions.checkNotNull(activityToken, "activityToken");
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
index 57ef6d0..e274e18 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -48,7 +48,9 @@
import android.provider.Settings;
import android.service.autofill.AutofillService;
import android.service.autofill.AutofillServiceInfo;
+import android.service.autofill.FillEventHistory;
import android.service.autofill.FillRequest;
+import android.service.autofill.FillResponse;
import android.service.autofill.IAutoFillService;
import android.text.TextUtils;
import android.util.LocalLog;
@@ -122,6 +124,10 @@
@GuardedBy("mLock")
private final SparseArray<Session> mSessions = new SparseArray<>();
+ /** The last selection */
+ @GuardedBy("mLock")
+ private FillEventHistory mEventHistory;
+
/**
* Receiver of assist data from the app's {@link Activity}.
*/
@@ -500,6 +506,72 @@
return mInfo.getServiceInfo().loadLabel(mContext.getPackageManager());
}
+ /**
+ * Initializes the last fill selection after an autofill service returned a new
+ * {@link FillResponse}.
+ */
+ void setLastResponse(int serviceUid, @NonNull FillResponse response) {
+ synchronized (mLock) {
+ mEventHistory = new FillEventHistory(serviceUid, response.getClientState());
+ }
+ }
+
+ /**
+ * Updates the last fill selection when an authentication was selected.
+ */
+ void setAuthenticationSelected() {
+ synchronized (mLock) {
+ mEventHistory.addEvent(
+ new FillEventHistory.Event(FillEventHistory.Event.TYPE_AUTHENTICATION_SELECTED, null));
+ }
+ }
+
+ /**
+ * Updates the last fill selection when an dataset authentication was selected.
+ */
+ void setDatasetAuthenticationSelected(@Nullable String selectedDataset) {
+ synchronized (mLock) {
+ mEventHistory.addEvent(new FillEventHistory.Event(
+ FillEventHistory.Event.TYPE_DATASET_AUTHENTICATION_SELECTED, selectedDataset));
+ }
+ }
+
+ /**
+ * Updates the last fill selection when an save Ui is shown.
+ */
+ void setSaveShown() {
+ synchronized (mLock) {
+ mEventHistory.addEvent(new FillEventHistory.Event(FillEventHistory.Event.TYPE_SAVE_SHOWN, null));
+ }
+ }
+
+ /**
+ * Updates the last fill response when a dataset was selected.
+ */
+ void setDatasetSelected(@Nullable String selectedDataset) {
+ synchronized (mLock) {
+ mEventHistory.addEvent(
+ new FillEventHistory.Event(FillEventHistory.Event.TYPE_DATASET_SELECTED, selectedDataset));
+ }
+ }
+
+ /**
+ * Gets the fill event history.
+ *
+ * @param callingUid The calling uid
+ *
+ * @return The history or {@code null} if there is none.
+ */
+ FillEventHistory getFillEventHistory(int callingUid) {
+ synchronized (mLock) {
+ if (mEventHistory != null && mEventHistory.getServiceUid() == callingUid) {
+ return mEventHistory;
+ }
+ }
+
+ return null;
+ }
+
void dumpLocked(String prefix, PrintWriter pw) {
final String prefix2 = prefix + " ";
@@ -528,6 +600,20 @@
mSessions.valueAt(i).dumpLocked(prefix2, pw);
}
}
+
+ if (mEventHistory == null || mEventHistory.getEvents().size() == 0) {
+ pw.print(prefix); pw.println("No event on last fill response");
+ } else {
+ pw.print(prefix); pw.println("Events of last fill response:");
+ pw.print(prefix);
+
+ int numEvents = mEventHistory.getEvents().size();
+ for (int i = 0; i < numEvents; i++) {
+ FillEventHistory.Event event = mEventHistory.getEvents().get(i);
+ pw.println(" " + i + ": eventType=" + event.getType() + " datasetId="
+ + event.getDatasetId());
+ }
+ }
}
void destroySessionsLocked() {
diff --git a/services/autofill/java/com/android/server/autofill/RemoteFillService.java b/services/autofill/java/com/android/server/autofill/RemoteFillService.java
index 3badcfc..4d0f380 100644
--- a/services/autofill/java/com/android/server/autofill/RemoteFillService.java
+++ b/services/autofill/java/com/android/server/autofill/RemoteFillService.java
@@ -87,7 +87,7 @@
private PendingRequest mPendingRequest;
public interface FillServiceCallbacks {
- void onFillRequestSuccess(@Nullable FillResponse response,
+ void onFillRequestSuccess(@Nullable FillResponse response, int serviceUid,
@NonNull String servicePackageName, int requestId);
void onFillRequestFailure(@Nullable CharSequence message,
@NonNull String servicePackageName);
@@ -252,11 +252,11 @@
}
private void dispatchOnFillRequestSuccess(PendingRequest pendingRequest,
- FillResponse response, int requestId) {
+ int callingUid, FillResponse response, int requestId) {
mHandler.getHandler().post(() -> {
if (handleResponseCallbackCommon(pendingRequest)) {
- mCallbacks.onFillRequestSuccess(response, mComponentName.getPackageName(),
- requestId);
+ mCallbacks.onFillRequestSuccess(response, callingUid,
+ mComponentName.getPackageName(), requestId);
}
});
}
@@ -424,7 +424,7 @@
RemoteFillService remoteService = mWeakService.get();
if (remoteService != null) {
remoteService.dispatchOnFillRequestSuccess(
- PendingFillRequest.this, response, requestId);
+ PendingFillRequest.this, getCallingUid(), response, requestId);
}
}
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 7c3f324..2b99614 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -229,7 +229,7 @@
// FillServiceCallbacks
@Override
- public void onFillRequestSuccess(@Nullable FillResponse response,
+ public void onFillRequestSuccess(@Nullable FillResponse response, int serviceUid,
@NonNull String servicePackageName, int requestId) {
if (response == null) {
if ((mFlags & FLAG_MANUAL_REQUEST) != 0) {
@@ -241,6 +241,8 @@
return;
}
+ mService.setLastResponse(serviceUid, response);
+
if ((response.getDatasets() == null || response.getDatasets().isEmpty())
&& response.getAuthentication() == null) {
// Response is "empty" from an UI point of view, need to notify client.
@@ -314,6 +316,9 @@
synchronized (mLock) {
fillInIntent = createAuthFillInIntent(mStructure, extras);
}
+
+ mService.setAuthenticationSelected();
+
mHandlerCaller.getHandler().post(() -> startAuthentication(intent, fillInIntent));
}
@@ -442,7 +447,7 @@
*/
public boolean showSaveLocked() {
if (mStructure == null) {
- Slog.wtf(TAG, "showSaveLocked(): no mStructure");
+ Slog.d(TAG, "showSaveLocked(): no mStructure");
return true;
}
if (mResponses == null) {
@@ -542,6 +547,7 @@
}
}
if (atLeastOneChanged) {
+ mService.setSaveShown();
getUiForShowing().showSaveUi(mService.getServiceLabel(), saveInfo, mPackageName);
return false;
}
@@ -764,7 +770,8 @@
boolean saveOnAllViewsInvisible = false;
SaveInfo saveInfo = mResponses.valueAt(getLastResponseIndex()).getSaveInfo();
if (saveInfo != null) {
- saveOnAllViewsInvisible = saveInfo.saveOnAllViewsInvisible();
+ saveOnAllViewsInvisible =
+ (saveInfo.getFlags() & SaveInfo.FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE) != 0;
// We only need to track views if we want to save once they become invisible.
if (saveOnAllViewsInvisible) {
@@ -881,12 +888,15 @@
synchronized (mLock) {
// Autofill it directly...
if (dataset.getAuthentication() == null) {
+ mService.setDatasetSelected(dataset.getId());
+
autoFillApp(dataset);
return;
}
// ...or handle authentication.
// TODO(b/33197203 , b/35707731): make sure it's ignored if there is one already
+ mService.setDatasetAuthenticationSelected(dataset.getId());
mDatasetWaitingAuth = dataset;
setViewStatesLocked(null, dataset, ViewState.STATE_WAITING_DATASET_AUTH);
final Intent fillInIntent = createAuthFillInIntent(mStructure, null);
diff --git a/services/companion/Android.mk b/services/companion/Android.mk
new file mode 100644
index 0000000..be48761
--- /dev/null
+++ b/services/companion/Android.mk
@@ -0,0 +1,12 @@
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := services.companion
+
+LOCAL_SRC_FILES += \
+ $(call all-java-files-under,java)
+
+LOCAL_JAVA_LIBRARIES := services.core
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/services/print/java/com/android/server/print/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
similarity index 99%
rename from services/print/java/com/android/server/print/CompanionDeviceManagerService.java
rename to services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 122a954..41b70a1 100644
--- a/services/print/java/com/android/server/print/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -15,7 +15,7 @@
*/
-package com.android.server.print;
+package com.android.server.companion;
import static com.android.internal.util.CollectionUtils.size;
import static com.android.internal.util.Preconditions.checkArgument;
@@ -81,7 +81,6 @@
import java.util.concurrent.ConcurrentMap;
import java.util.function.Function;
-//TODO move to own package!
//TODO onStop schedule unbind in 5 seconds
//TODO make sure APIs are only callable from currently focused app
//TODO schedule stopScan on activity destroy(except if configuration change)
diff --git a/services/core/java/com/android/server/EventLogTags.logtags b/services/core/java/com/android/server/EventLogTags.logtags
index 2e61550..6502c01 100644
--- a/services/core/java/com/android/server/EventLogTags.logtags
+++ b/services/core/java/com/android/server/EventLogTags.logtags
@@ -25,7 +25,7 @@
# This is logged when the screen on broadcast has completed
2727 power_screen_broadcast_stop (which|1|5),(wakelockCount|1|1)
# This is logged when the screen is turned on or off.
-2728 power_screen_state (offOrOn|1|5),(becauseOfUser|1|5),(totalTouchDownTime|2|3),(touchCycles|1|1)
+2728 power_screen_state (offOrOn|1|5),(becauseOfUser|1|5),(totalTouchDownTime|2|3),(touchCycles|1|1),(latency|1|3)
# This is logged when the partial wake lock (keeping the device awake
# regardless of whether the screen is off) is acquired or released.
2729 power_partial_wake_state (releasedorAcquired|1|5),(tag|3)
diff --git a/services/core/java/com/android/server/InputMethodManagerService.java b/services/core/java/com/android/server/InputMethodManagerService.java
index 39bfeda..8ad3d23 100644
--- a/services/core/java/com/android/server/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/InputMethodManagerService.java
@@ -49,6 +49,7 @@
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
+import android.annotation.BinderThread;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -2146,6 +2147,7 @@
return mKeyguardManager != null && mKeyguardManager.isKeyguardLocked();
}
+ @BinderThread
@SuppressWarnings("deprecation")
@Override
public void setImeWindowStatus(IBinder token, IBinder startInputToken, int vis,
@@ -2161,9 +2163,23 @@
mBackDisposition = backDisposition;
updateSystemUiLocked(token, vis, backDisposition);
}
+
+ final boolean dismissImeOnBackKeyPressed;
+ switch (backDisposition) {
+ case InputMethodService.BACK_DISPOSITION_WILL_DISMISS:
+ dismissImeOnBackKeyPressed = true;
+ break;
+ case InputMethodService.BACK_DISPOSITION_WILL_NOT_DISMISS:
+ dismissImeOnBackKeyPressed = false;
+ break;
+ default:
+ case InputMethodService.BACK_DISPOSITION_DEFAULT:
+ dismissImeOnBackKeyPressed = ((vis & InputMethodService.IME_VISIBLE) != 0);
+ break;
+ }
mWindowManagerInternal.updateInputMethodWindowStatus(token,
(vis & InputMethodService.IME_VISIBLE) != 0,
- info != null ? info.mTargetWindow : null);
+ dismissImeOnBackKeyPressed, info != null ? info.mTargetWindow : null);
}
private void updateSystemUi(IBinder token, int vis, int backDisposition) {
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index a3c691c..b91ecf5 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -312,8 +312,7 @@
}
ComponentName startServiceLocked(IApplicationThread caller, Intent service, String resolvedType,
- int id, Notification notification, int callingPid, int callingUid,
- boolean fgRequired, String callingPackage, final int userId)
+ int callingPid, int callingUid, boolean fgRequired, String callingPackage, final int userId)
throws TransactionTooLargeException {
if (DEBUG_DELAYED_STARTS) Slog.v(TAG_SERVICE, "startService: " + service
+ " type=" + resolvedType + " args=" + service.getExtras());
@@ -464,10 +463,6 @@
}
ComponentName cmp = startServiceInnerLocked(smap, service, r, callerFg, addToStarting);
- // STOPSHIP deprecated; remove when NotificationManager.startServiceInForeground is retired
- if (notification != null) {
- setServiceForegroundInnerLocked(r, id, notification, 0);
- }
return cmp;
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index be30e4b..6af4840 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -7849,7 +7849,14 @@
r.pictureInPictureArgs.copyOnlySet(args);
final float aspectRatio = r.pictureInPictureArgs.getAspectRatio();
final List<RemoteAction> actions = r.pictureInPictureArgs.getActions();
- final Rect sourceBounds = r.pictureInPictureArgs.getSourceRectHint();
+ // Adjust the source bounds by the insets for the transition down
+ final Rect sourceBounds = new Rect(r.pictureInPictureArgs.getSourceRectHint());
+ final Rect insets = r.pictureInPictureArgs.getSourceRectHintInsets();
+ if (insets != null) {
+ sourceBounds.offsetTo(Math.max(0, sourceBounds.left - insets.left),
+ Math.max(0, sourceBounds.top - insets.top));
+ }
+
mStackSupervisor.moveActivityToPinnedStackLocked(r, sourceBounds, aspectRatio,
true /* moveHomeStackToFront */, "enterPictureInPictureMode");
final PinnedActivityStack stack = mStackSupervisor.getStack(PINNED_STACK_ID);
@@ -10682,6 +10689,13 @@
return;
}
+ // When a task is locked, dismiss the pinned stack if it exists
+ final PinnedActivityStack pinnedStack = mStackSupervisor.getStack(
+ PINNED_STACK_ID);
+ if (pinnedStack != null) {
+ mStackSupervisor.removeStackLocked(PINNED_STACK_ID);
+ }
+
// isSystemInitiated is used to distinguish between locked and pinned mode, as pinned mode
// is initiated by system after the pinning request was shown and locked mode is initiated
// by an authorized app directly
@@ -17928,8 +17942,7 @@
@Override
public ComponentName startService(IApplicationThread caller, Intent service,
- String resolvedType, int id, Notification notification, boolean requireForeground,
- String callingPackage, int userId)
+ String resolvedType, boolean requireForeground, String callingPackage, int userId)
throws TransactionTooLargeException {
enforceNotIsolatedCaller("startService");
// Refuse possible leaked file descriptors
@@ -17950,7 +17963,7 @@
ComponentName res;
try {
res = mServices.startServiceLocked(caller, service,
- resolvedType, id, notification, callingPid, callingUid,
+ resolvedType, callingPid, callingUid,
requireForeground, callingPackage, userId);
} finally {
Binder.restoreCallingIdentity(origId);
@@ -17969,7 +17982,7 @@
ComponentName res;
try {
res = mServices.startServiceLocked(null, service,
- resolvedType, 0, null, -1, uid, fgRequired, callingPackage, userId);
+ resolvedType, -1, uid, fgRequired, callingPackage, userId);
} finally {
Binder.restoreCallingIdentity(origId);
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 0fcf3e6..b6bfb00 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -524,7 +524,7 @@
pw.println("Starting service: " + intent);
pw.flush();
ComponentName cn = mInterface.startService(null, intent, intent.getType(),
- -1, null, asForeground, SHELL_PACKAGE_NAME, mUserId);
+ asForeground, SHELL_PACKAGE_NAME, mUserId);
if (cn == null) {
err.println("Error: Not found; no service started.");
return -1;
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index 17c7dde..fe03c36 100644
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -1178,6 +1178,10 @@
* the activity is not currently visible and {@param noThrow} is not set.
*/
boolean checkEnterPictureInPictureState(String caller, boolean noThrow, boolean beforeStopping) {
+ if (!supportsPictureInPicture()) {
+ return false;
+ }
+
// Check app-ops and see if PiP is supported for this package
if (!checkEnterPictureInPictureAppOpsState()) {
return false;
@@ -2126,6 +2130,11 @@
if (mWindowContainerController == null) {
return;
}
+ if (mTaskOverlay) {
+ // We don't show starting window for overlay activities.
+ return;
+ }
+
final CompatibilityInfo compatInfo =
service.compatibilityInfoForPackageLocked(info.applicationInfo);
final boolean shown = mWindowContainerController.addStartingWindow(packageName, theme,
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 825e8ac..85c5c64 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -65,6 +65,8 @@
import static com.android.server.am.ActivityRecord.APPLICATION_ACTIVITY_TYPE;
import static com.android.server.am.ActivityRecord.ASSISTANT_ACTIVITY_TYPE;
import static com.android.server.am.ActivityRecord.HOME_ACTIVITY_TYPE;
+import static com.android.server.am.ActivityStack.ActivityState.STOPPED;
+import static com.android.server.am.ActivityStack.ActivityState.STOPPING;
import static com.android.server.am.ActivityStackSupervisor.FindTaskResult;
import static com.android.server.am.ActivityStackSupervisor.ON_TOP;
import static com.android.server.am.ActivityStackSupervisor.PAUSE_IMMEDIATELY;
@@ -1179,7 +1181,7 @@
final ArrayList<ActivityRecord> activities = mTaskHistory.get(taskNdx).mActivities;
for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
final ActivityRecord r = activities.get(activityNdx);
- if (r.state == ActivityState.STOPPING || r.state == ActivityState.STOPPED
+ if (r.state == STOPPING || r.state == STOPPED
|| r.state == ActivityState.PAUSED || r.state == ActivityState.PAUSING) {
r.setSleeping(true);
}
@@ -1362,7 +1364,7 @@
if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Complete pause: " + prev);
if (prev != null) {
- final boolean wasStopping = prev.state == ActivityState.STOPPING;
+ final boolean wasStopping = prev.state == STOPPING;
prev.state = ActivityState.PAUSED;
if (prev.finishing) {
if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Executing finish of activity: " + prev);
@@ -1383,7 +1385,7 @@
// We are also stopping, the stop request must have gone soon after the pause.
// We can't clobber it, because the stop confirmation will not be handled.
// We don't need to schedule another stop, we only need to let it happen.
- prev.state = ActivityState.STOPPING;
+ prev.state = STOPPING;
} else if ((!prev.visible && !hasVisibleBehindActivity())
|| mService.isSleepingOrShuttingDownLocked()) {
// If we were visible then resumeTopActivities will release resources before
@@ -2002,10 +2004,17 @@
// keeping the screen frozen.
if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Making invisible: " + r + " " + r.state);
try {
+ final boolean canEnterPictureInPicture = r.checkEnterPictureInPictureState(
+ "makeInvisible", true /* noThrow */, true /* beforeStopping */);
+ // We don't want to call setVisible(false) to avoid notifying the client of this
+ // intermittent invisible state if it can enter Pip and isn't stopped or stopping.
+ if (!canEnterPictureInPicture || r.state == STOPPING || r.state == STOPPED) {
+ r.setVisible(false);
+ }
+
switch (r.state) {
case STOPPING:
case STOPPED:
- r.setVisible(false);
if (r.app != null && r.app.thread != null) {
if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY,
"Scheduling invisibility: " + r);
@@ -2024,25 +2033,16 @@
// This case created for transitioning activities from
// translucent to opaque {@link Activity#convertToOpaque}.
if (visibleBehind == r) {
- r.setVisible(false);
releaseBackgroundResources(r);
} else {
// If this activity is in a state where it can currently enter
// picture-in-picture, then don't immediately schedule the idle now in case
// the activity tries to enterPictureInPictureMode() later. Otherwise,
// we will try and stop the activity next time idle is processed.
- final boolean canEnterPictureInPicture = r.checkEnterPictureInPictureState(
- "makeInvisible", true /* noThrow */, true /* beforeStopping */);
if (canEnterPictureInPicture) {
- // We set r.visible=false so that Stop will later
- // call setVisible for us. In this case
- // we don't want to call setVisible(false) to avoid
- // notifying the client of this intermittent invisible
- // state.
+ // We set r.visible=false so that Stop will later call setVisible for us
r.visible = false;
- } else {
- r.setVisible(false);
}
addToStopping(r, true /* scheduleIdle */,
canEnterPictureInPicture /* idleDelayed */);
@@ -2318,9 +2318,20 @@
mStackSupervisor.setLaunchSource(next.info.applicationInfo.uid);
+ final boolean prevCanPip = prev != null && prev.checkEnterPictureInPictureState(
+ "resumeTopActivity", true /* noThrow */, userLeaving /* beforeStopping */);
// If the flag RESUME_WHILE_PAUSING is set, then continue to schedule the previous activity
- // to be paused, while at the same time resuming the new resume activity
+ // to be paused, while at the same time resuming the new resume activity only if the
+ // previous activity can't go into Pip since we want to give Pip activities a chance to
+ // enter Pip before resuming the next activity.
final boolean resumeWhilePausing = (next.info.flags & FLAG_RESUME_WHILE_PAUSING) != 0;
+ // TODO: This would be go to have however, the various call points that pass in
+ // prev need to be corrected first. In some cases the prev is equal to the next e.g. launch
+ // an app from home. And, is come other cases it is null e.g. press home button after
+ // launching an app. The doc on the method says prev. is null expect for the case we are
+ // coming from pause. We need to see if that is a valid thing and also if all the code in
+ // this method using prev. are setup to function like that.
+ //&& !prevCanPip;
boolean pausing = mStackSupervisor.pauseBackStacks(userLeaving, next, false);
if (mResumedActivity != null) {
if (DEBUG_STATES) Slog.d(TAG_STATES,
@@ -3360,11 +3371,11 @@
r.stopped = false;
if (DEBUG_STATES) Slog.v(TAG_STATES,
"Moving to STOPPING: " + r + " (stop requested)");
- r.state = ActivityState.STOPPING;
+ r.state = STOPPING;
if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY,
"Stopping visible=" + r.visible + " for " + r);
if (!r.visible) {
- r.setVisibility(false);
+ r.setVisible(false);
}
EventLogTags.writeAmStopActivity(
r.userId, System.identityHashCode(r), r.shortComponentName);
@@ -3382,7 +3393,7 @@
// Just in case, assume it to be stopped.
r.stopped = true;
if (DEBUG_STATES) Slog.v(TAG_STATES, "Stop failed; moving to STOPPED: " + r);
- r.state = ActivityState.STOPPED;
+ r.state = STOPPED;
if (r.deferRelaunchUntilPaused) {
destroyActivityLocked(r, true, "stop-except");
}
@@ -3687,7 +3698,7 @@
}
if (DEBUG_STATES) Slog.v(TAG_STATES,
"Moving to STOPPING: "+ r + " (finish requested)");
- r.state = ActivityState.STOPPING;
+ r.state = STOPPING;
if (oomAdj) {
mService.updateOomAdjLocked();
}
@@ -3712,8 +3723,8 @@
|| (prevState == ActivityState.PAUSED
&& (mode == FINISH_AFTER_PAUSE || mStackId == PINNED_STACK_ID))
|| finishingActivityInNonFocusedStack
- || prevState == ActivityState.STOPPING
- || prevState == ActivityState.STOPPED
+ || prevState == STOPPING
+ || prevState == STOPPED
|| prevState == ActivityState.INITIALIZING) {
r.makeFinishingLocked();
boolean activityRemoved = destroyActivityLocked(r, true, "finish-imm");
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 43ae4b2..152d5f4 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -2878,6 +2878,10 @@
mWindowManager.deferSurfaceLayout();
+ // This will clear the pinned stack by moving an existing task to the full screen stack,
+ // ensuring only one task is present.
+ moveTasksToFullscreenStackLocked(PINNED_STACK_ID, !ON_TOP);
+
// Need to make sure the pinned stack exist so we can resize it below...
final PinnedActivityStack stack = getStack(PINNED_STACK_ID, CREATE_IF_NEEDED, ON_TOP);
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index f8a4d4b..d42b6a7 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -48,6 +48,7 @@
import android.util.DisplayMetrics;
import android.util.Slog;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.IVoiceInteractor;
import com.android.internal.util.XmlUtils;
@@ -445,10 +446,23 @@
final Rect bounds = updateOverrideConfigurationFromLaunchBounds();
final Configuration overrideConfig = getOverrideConfiguration();
- mWindowContainerController = new TaskWindowContainerController(taskId, this,
+ setWindowContainerController(new TaskWindowContainerController(taskId, this,
getStack().getWindowContainerController(), userId, bounds, overrideConfig,
mResizeMode, mSupportsPictureInPicture, isHomeTask(), onTop, showForAllUsers,
- lastTaskDescription);
+ lastTaskDescription));
+ }
+
+ /**
+ * Should only be invoked from {@link #createWindowContainer(boolean, boolean)}.
+ */
+ @VisibleForTesting
+ protected void setWindowContainerController(TaskWindowContainerController controller) {
+ if (mWindowContainerController != null) {
+ throw new IllegalArgumentException("Window container=" + mWindowContainerController
+ + " already created for task=" + this);
+ }
+
+ mWindowContainerController = controller;
}
void removeWindowContainer() {
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 2687242..aa1b74c 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -520,7 +520,11 @@
private int mPrevVolDirection = AudioManager.ADJUST_SAME;
// mVolumeControlStream is set by VolumePanel to temporarily force the stream type which volume
// is controlled by Vol keys.
- private int mVolumeControlStream = -1;
+ private int mVolumeControlStream = -1;
+ // interpretation of whether the volume stream has been selected by the user by clicking on a
+ // volume slider to change which volume is controlled by the volume keys. Is false
+ // when mVolumeControlStream is -1.
+ private boolean mUserSelectedVolumeControlStream = false;
private final Object mForceControlStreamLock = new Object();
// VolumePanel is currently the only client of forceVolumeControlStream() and runs in system
// server process so in theory it is not necessary to monitor the client death.
@@ -929,11 +933,8 @@
synchronized (VolumeStreamState.class) {
int numStreamTypes = AudioSystem.getNumStreamTypes();
for (int streamType = 0; streamType < numStreamTypes; streamType++) {
- if (streamType != mStreamVolumeAlias[streamType]) {
- mStreamStates[streamType].
- setAllIndexes(mStreamStates[mStreamVolumeAlias[streamType]],
- TAG);
- }
+ mStreamStates[streamType]
+ .setAllIndexes(mStreamStates[mStreamVolumeAlias[streamType]], TAG);
// apply stream volume
if (!mStreamStates[streamType].mIsMuted) {
mStreamStates[streamType].applyAllVolumes();
@@ -1022,20 +1023,21 @@
}
mStreamVolumeAlias[AudioSystem.STREAM_DTMF] = dtmfStreamAlias;
- final int oldStreamA11yAlias = mStreamVolumeAlias[AudioSystem.STREAM_ACCESSIBILITY];
- if (oldStreamA11yAlias != a11yStreamAlias) {
- mStreamVolumeAlias[AudioSystem.STREAM_ACCESSIBILITY] = a11yStreamAlias;
- mStreamStates[AudioSystem.STREAM_ACCESSIBILITY].mVolumeIndexSettingName =
- System.VOLUME_SETTINGS_INT[a11yStreamAlias];
- // restore the value from the settings when the alias changes
- mStreamStates[AudioSystem.STREAM_ACCESSIBILITY].readSettings();
- }
+ mStreamVolumeAlias[AudioSystem.STREAM_ACCESSIBILITY] = a11yStreamAlias;
if (updateVolumes) {
mStreamStates[AudioSystem.STREAM_DTMF].setAllIndexes(mStreamStates[dtmfStreamAlias],
caller);
+
+ mStreamStates[AudioSystem.STREAM_ACCESSIBILITY].mVolumeIndexSettingName =
+ System.VOLUME_SETTINGS_INT[a11yStreamAlias];
mStreamStates[AudioSystem.STREAM_ACCESSIBILITY].setAllIndexes(
mStreamStates[a11yStreamAlias], caller);
+ if (sIndependentA11yVolume) {
+ // restore the a11y values from the settings
+ mStreamStates[AudioSystem.STREAM_ACCESSIBILITY].readSettings();
+ }
+
// apply stream mute states according to new value of mRingerModeAffectedStreams
setRingerModeInt(getRingerModeInternal(), false);
sendMsg(mAudioHandler,
@@ -1226,14 +1228,29 @@
private void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags,
String callingPackage, String caller, int uid) {
if (DEBUG_VOL) Log.d(TAG, "adjustSuggestedStreamVolume() stream=" + suggestedStreamType
- + ", flags=" + flags + ", caller=" + caller);
- int streamType;
- boolean isMute = isMuteAdjust(direction);
- if (mVolumeControlStream != -1) {
+ + ", flags=" + flags + ", caller=" + caller
+ + ", volControlStream=" + mVolumeControlStream
+ + ", userSelect=" + mUserSelectedVolumeControlStream);
+ final int streamType;
+ if (mUserSelectedVolumeControlStream) { // implies mVolumeControlStream != -1
streamType = mVolumeControlStream;
} else {
- streamType = getActiveStreamType(suggestedStreamType);
+ final int maybeActiveStreamType = getActiveStreamType(suggestedStreamType);
+ final boolean activeForReal;
+ if (maybeActiveStreamType == AudioSystem.STREAM_MUSIC) {
+ activeForReal = isAfMusicActiveRecently(0);
+ } else {
+ activeForReal = AudioSystem.isStreamActive(maybeActiveStreamType, 0);
+ }
+ if (activeForReal || mVolumeControlStream == -1) {
+ streamType = maybeActiveStreamType;
+ } else {
+ streamType = mVolumeControlStream;
+ }
}
+
+ final boolean isMute = isMuteAdjust(direction);
+
ensureValidStreamType(streamType);
final int resolvedStream = mStreamVolumeAlias[streamType];
@@ -1709,13 +1726,18 @@
/** @see AudioManager#forceVolumeControlStream(int) */
public void forceVolumeControlStream(int streamType, IBinder cb) {
+ if (DEBUG_VOL) { Log.d(TAG, String.format("forceVolumeControlStream(%d)", streamType)); }
synchronized(mForceControlStreamLock) {
+ if (mVolumeControlStream != -1 && streamType != -1) {
+ mUserSelectedVolumeControlStream = true;
+ }
mVolumeControlStream = streamType;
if (mVolumeControlStream == -1) {
if (mForceControlStreamClient != null) {
mForceControlStreamClient.release();
mForceControlStreamClient = null;
}
+ mUserSelectedVolumeControlStream = false;
} else {
mForceControlStreamClient = new ForceControlStreamClient(cb);
}
@@ -1746,6 +1768,7 @@
} else {
mForceControlStreamClient = null;
mVolumeControlStream = -1;
+ mUserSelectedVolumeControlStream = false;
}
}
}
@@ -4228,7 +4251,17 @@
return mIndexMin;
}
+ /**
+ * Copies all device/index pairs from the given VolumeStreamState after initializing
+ * them with the volume for DEVICE_OUT_DEFAULT. No-op if the source VolumeStreamState
+ * has the same stream type as this instance.
+ * @param srcStream
+ * @param caller
+ */
public void setAllIndexes(VolumeStreamState srcStream, String caller) {
+ if (mStreamType == srcStream.mStreamType) {
+ return;
+ }
synchronized (VolumeStreamState.class) {
int srcStreamType = srcStream.getStreamType();
// apply default device volume from source stream to all devices first in case
diff --git a/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java b/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java
index 1ffa864..601ed01 100644
--- a/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java
+++ b/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java
@@ -78,7 +78,6 @@
public static final int CMD_IPV6_TETHER_UPDATE = BASE_IFACE + 13;
private final State mInitialState;
- private final State mServingState;
private final State mLocalHotspotState;
private final State mTetheredState;
private final State mUnavailableState;
@@ -107,14 +106,12 @@
mLastError = ConnectivityManager.TETHER_ERROR_NO_ERROR;
mInitialState = new InitialState();
- mServingState = new ServingState();
mLocalHotspotState = new LocalHotspotState();
mTetheredState = new TetheredState();
mUnavailableState = new UnavailableState();
addState(mInitialState);
- addState(mServingState);
- addState(mLocalHotspotState, mServingState);
- addState(mTetheredState, mServingState);
+ addState(mLocalHotspotState);
+ addState(mTetheredState);
addState(mUnavailableState);
setInitialState(mInitialState);
@@ -222,12 +219,11 @@
}
}
- class ServingState extends State {
+ class BaseServingState extends State {
@Override
public void enter() {
if (!configureIfaceIp(true)) {
mLastError = ConnectivityManager.TETHER_ERROR_IFACE_CFG_ERROR;
- transitionTo(mInitialState);
return;
}
@@ -236,12 +232,13 @@
} catch (Exception e) {
Log.e(TAG, "Error Tethering: " + e.toString());
mLastError = ConnectivityManager.TETHER_ERROR_TETHER_IFACE_ERROR;
- transitionTo(mInitialState);
return;
}
if (!mIPv6TetherSvc.start()) {
Log.e(TAG, "Failed to start IPv6TetheringInterfaceServices");
+ // TODO: Make this a fatal error once Bluetooth IPv6 is sorted.
+ return;
}
}
@@ -254,9 +251,9 @@
try {
mNMService.untetherInterface(mIfaceName);
- } catch (Exception ee) {
+ } catch (Exception e) {
mLastError = ConnectivityManager.TETHER_ERROR_UNTETHER_IFACE_ERROR;
- Log.e(TAG, "Failed to untether interface: " + ee.toString());
+ Log.e(TAG, "Failed to untether interface: " + e.toString());
}
configureIfaceIp(false);
@@ -293,15 +290,27 @@
}
}
- class LocalHotspotState extends State {
+ // Handling errors in BaseServingState.enter() by transitioning is
+ // problematic because transitioning during a multi-state jump yields
+ // a Log.wtf(). Ultimately, there should be only one ServingState,
+ // and forwarding and NAT rules should be handled by a coordinating
+ // functional element outside of TetherInterfaceStateMachine.
+ class LocalHotspotState extends BaseServingState {
@Override
public void enter() {
+ super.enter();
+ if (mLastError != ConnectivityManager.TETHER_ERROR_NO_ERROR) {
+ transitionTo(mInitialState);
+ }
+
if (DBG) Log.d(TAG, "Local hotspot " + mIfaceName);
sendInterfaceState(IControlsTethering.STATE_LOCAL_HOTSPOT);
}
@Override
public boolean processMessage(Message message) {
+ if (super.processMessage(message)) return true;
+
maybeLogMessage(this, message.what);
switch (message.what) {
case CMD_TETHER_REQUESTED:
@@ -317,9 +326,19 @@
}
}
- class TetheredState extends State {
+ // Handling errors in BaseServingState.enter() by transitioning is
+ // problematic because transitioning during a multi-state jump yields
+ // a Log.wtf(). Ultimately, there should be only one ServingState,
+ // and forwarding and NAT rules should be handled by a coordinating
+ // functional element outside of TetherInterfaceStateMachine.
+ class TetheredState extends BaseServingState {
@Override
public void enter() {
+ super.enter();
+ if (mLastError != ConnectivityManager.TETHER_ERROR_NO_ERROR) {
+ transitionTo(mInitialState);
+ }
+
if (DBG) Log.d(TAG, "Tethered " + mIfaceName);
sendInterfaceState(IControlsTethering.STATE_TETHERED);
}
@@ -327,6 +346,7 @@
@Override
public void exit() {
cleanupUpstream();
+ super.exit();
}
private void cleanupUpstream() {
@@ -361,6 +381,8 @@
@Override
public boolean processMessage(Message message) {
+ if (super.processMessage(message)) return true;
+
maybeLogMessage(this, message.what);
boolean retValue = true;
switch (message.what) {
diff --git a/services/core/java/com/android/server/location/GnssLocationProvider.java b/services/core/java/com/android/server/location/GnssLocationProvider.java
index 3a1ddd7..fdaa599 100644
--- a/services/core/java/com/android/server/location/GnssLocationProvider.java
+++ b/services/core/java/com/android/server/location/GnssLocationProvider.java
@@ -288,6 +288,9 @@
// current setting - 4 hours
private static final long MAX_RETRY_INTERVAL = 4*60*60*1000;
+ // Timeout when holding wakelocks for downloading XTRA data.
+ private static final long DOWNLOAD_XTRA_DATA_TIMEOUT_MS = 60 * 1000;
+
private BackOff mNtpBackOff = new BackOff(RETRY_INTERVAL, MAX_RETRY_INTERVAL);
private BackOff mXtraBackOff = new BackOff(RETRY_INTERVAL, MAX_RETRY_INTERVAL);
@@ -986,7 +989,7 @@
mDownloadXtraDataPending = STATE_DOWNLOADING;
// hold wake lock while task runs
- mWakeLock.acquire();
+ mWakeLock.acquire(DOWNLOAD_XTRA_DATA_TIMEOUT_MS);
Log.i(TAG, "WakeLock acquired by handleDownloadXtraData()");
AsyncTask.THREAD_POOL_EXECUTOR.execute(new Runnable() {
@Override
@@ -1009,7 +1012,11 @@
}
// release wake lock held by task
- mWakeLock.release();
+ if (mWakeLock.isHeld()) {
+ mWakeLock.release();
+ } else {
+ Log.e(TAG, "WakeLock expired before release in handleDownloadXtraData()");
+ }
Log.i(TAG, "WakeLock released by handleDownloadXtraData()");
}
});
diff --git a/services/core/java/com/android/server/media/MediaSessionStack.java b/services/core/java/com/android/server/media/MediaSessionStack.java
index 2f82915..0e69bca 100644
--- a/services/core/java/com/android/server/media/MediaSessionStack.java
+++ b/services/core/java/com/android/server/media/MediaSessionStack.java
@@ -121,9 +121,12 @@
public void removeSession(MediaSessionRecord record) {
mSessions.remove(record);
if (mMediaButtonSession == record) {
- // When the media button session is gone, try to find the alternative media session
- // in the media button session app.
- onMediaSessionChangeInMediaButtonSessionApp();
+ // When the media button session is removed, nullify the media button session and do not
+ // search for the alternative media session within the app. It's because the alternative
+ // media session might be a dummy which isn't able to handle the media key events.
+ mOnMediaButtonSessionChangedListener.onMediaButtonSessionChanged(
+ mMediaButtonSession, null);
+ mMediaButtonSession = null;
}
clearCache(record.getUserId());
}
@@ -157,7 +160,13 @@
// In that case, we pick the media session whose PlaybackState matches
// the audio playback configuration.
if (mMediaButtonSession != null && mMediaButtonSession.getUid() == record.getUid()) {
- onMediaSessionChangeInMediaButtonSessionApp();
+ MediaSessionRecord newMediaButtonSession =
+ findMediaButtonSession(mMediaButtonSession.getUid());
+ if (newMediaButtonSession != mMediaButtonSession) {
+ mOnMediaButtonSessionChangedListener.onMediaButtonSessionChanged(
+ mMediaButtonSession, newMediaButtonSession);
+ mMediaButtonSession = newMediaButtonSession;
+ }
}
}
@@ -200,22 +209,6 @@
}
/**
- * Handle the change in a media session in the media button session app.
- * <p>If the app has multiple media sessions, change in a media sesion in the app may change
- * the media button session.
- * @see #findMediaButtonSession
- */
- private void onMediaSessionChangeInMediaButtonSessionApp() {
- MediaSessionRecord newMediaButtonSession =
- findMediaButtonSession(mMediaButtonSession.getUid());
- if (newMediaButtonSession != mMediaButtonSession) {
- mOnMediaButtonSessionChangedListener.onMediaButtonSessionChanged(mMediaButtonSession,
- newMediaButtonSession);
- mMediaButtonSession = newMediaButtonSession;
- }
- }
-
- /**
* Find the media button session with the given {@param uid}.
* If the app has multiple media sessions, the media session matches the audio playback state
* becomes the media button session.
diff --git a/services/core/java/com/android/server/notification/GlobalSortKeyComparator.java b/services/core/java/com/android/server/notification/GlobalSortKeyComparator.java
index ce976d2..acedafc 100644
--- a/services/core/java/com/android/server/notification/GlobalSortKeyComparator.java
+++ b/services/core/java/com/android/server/notification/GlobalSortKeyComparator.java
@@ -15,19 +15,25 @@
*/
package com.android.server.notification;
+import android.util.Slog;
+
import java.util.Comparator;
/**
* Sorts notifications by their global sort key.
*/
public class GlobalSortKeyComparator implements Comparator<NotificationRecord> {
+ private final static String TAG = "GlobalSortComp";
+
@Override
public int compare(NotificationRecord left, NotificationRecord right) {
if (left.getGlobalSortKey() == null) {
- throw new IllegalStateException("Missing left global sort key: " + left);
+ Slog.wtf(TAG, "Missing left global sort key: " + left);
+ return 1;
}
if (right.getGlobalSortKey() == null) {
- throw new IllegalStateException("Missing right global sort key: " + right);
+ Slog.wtf(TAG, "Missing right global sort key: " + right);
+ return -1;
}
return left.getGlobalSortKey().compareTo(right.getGlobalSortKey());
}
diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java
index 90e9b92..3cb2f35 100644
--- a/services/core/java/com/android/server/notification/ManagedServices.java
+++ b/services/core/java/com/android/server/notification/ManagedServices.java
@@ -356,7 +356,6 @@
private void rebuildRestoredPackages() {
mRestoredPackages.clear();
- mSnoozingForCurrentProfiles.clear();
String secureSettingName = restoredSettingName(mConfig.secureSettingName);
String secondarySettingName = mConfig.secondarySettingName == null
? null : restoredSettingName(mConfig.secondarySettingName);
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index cc3948e..c289204 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -1874,10 +1874,9 @@
int userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
Binder.getCallingUid(), incomingUserId, true, false,
"getAppActiveNotifications", pkg);
- final ArrayMap<String, StatusBarNotification> map
- = new ArrayMap<>(mNotificationList.size() + mEnqueuedNotifications.size());
-
synchronized (mNotificationLock) {
+ final ArrayMap<String, StatusBarNotification> map
+ = new ArrayMap<>(mNotificationList.size() + mEnqueuedNotifications.size());
final int N = mNotificationList.size();
for (int i = 0; i < N; i++) {
StatusBarNotification sbn = sanitizeSbn(pkg, userId,
@@ -1900,11 +1899,10 @@
map.put(sbn.getKey(), sbn); // pending update overwrites existing post here
}
}
+ final ArrayList<StatusBarNotification> list = new ArrayList<>(map.size());
+ list.addAll(map.values());
+ return new ParceledListSlice<StatusBarNotification>(list);
}
-
- final ArrayList<StatusBarNotification> list = new ArrayList<>(map.size());
- list.addAll(map.values());
- return new ParceledListSlice<StatusBarNotification>(list);
}
private StatusBarNotification sanitizeSbn(String pkg, int userId,
@@ -2036,8 +2034,10 @@
long identity = Binder.clearCallingIdentity();
try {
// allow bound services to disable themselves
- final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token);
- info.getOwner().setComponentState(info.component, false);
+ synchronized (mNotificationLock) {
+ final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token);
+ info.getOwner().setComponentState(info.component, false);
+ }
} finally {
Binder.restoreCallingIdentity(identity);
}
@@ -2101,8 +2101,10 @@
String key, String snoozeCriterionId) {
long identity = Binder.clearCallingIdentity();
try {
- final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token);
- snoozeNotificationInt(key, SNOOZE_UNTIL_UNSPECIFIED, snoozeCriterionId, info);
+ synchronized (mNotificationLock) {
+ final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token);
+ snoozeNotificationInt(key, SNOOZE_UNTIL_UNSPECIFIED, snoozeCriterionId, info);
+ }
} finally {
Binder.restoreCallingIdentity(identity);
}
@@ -2118,8 +2120,10 @@
long duration) {
long identity = Binder.clearCallingIdentity();
try {
- final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token);
- snoozeNotificationInt(key, duration, null, info);
+ synchronized (mNotificationLock) {
+ final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token);
+ snoozeNotificationInt(key, duration, null, info);
+ }
} finally {
Binder.restoreCallingIdentity(identity);
}
@@ -2134,9 +2138,11 @@
public void unsnoozeNotificationFromAssistant(INotificationListener token, String key) {
long identity = Binder.clearCallingIdentity();
try {
- final ManagedServiceInfo info =
- mNotificationAssistants.checkServiceTokenLocked(token);
- unsnoozeNotificationInt(key, info);
+ synchronized (mNotificationLock) {
+ final ManagedServiceInfo info =
+ mNotificationAssistants.checkServiceTokenLocked(token);
+ unsnoozeNotificationInt(key, info);
+ }
} finally {
Binder.restoreCallingIdentity(identity);
}
@@ -2734,7 +2740,10 @@
}
private void verifyPrivilegedListener(INotificationListener token, UserHandle user) {
- ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token);
+ ManagedServiceInfo info;
+ synchronized (mNotificationLock) {
+ info = mListeners.checkServiceTokenLocked(token);
+ }
if (!hasCompanionDevice(info)) {
throw new SecurityException(info + " does not have access");
}
@@ -3099,8 +3108,10 @@
@Override
public void run() {
synchronized (mNotificationLock) {
- removeForegroundServiceFlagByListLocked(mEnqueuedNotifications, pkg, notificationId, userId);
- removeForegroundServiceFlagByListLocked(mNotificationList, pkg, notificationId, userId);
+ removeForegroundServiceFlagByListLocked(
+ mEnqueuedNotifications, pkg, notificationId, userId);
+ removeForegroundServiceFlagByListLocked(
+ mNotificationList, pkg, notificationId, userId);
}
}
});
@@ -3192,6 +3203,8 @@
return;
} else if (channelId == null && shouldWarnUseChannels(pkg, notificationUid)) {
// STOPSHIP TODO: remove once default channel is removed for all apps that target O.
+ Log.e(TAG, "Developer Warning for package " + pkg
+ + ", no channel specified for posted notification: " + notification);
doDebugOnlyToast("Developer warning for package \"" + pkg + "\"\n" +
"Posted notification should specify a channel");
}
@@ -3229,8 +3242,12 @@
private void doDebugOnlyToast(CharSequence toastText) {
if (Build.IS_DEBUGGABLE) {
- Toast toast = Toast.makeText(getContext(), toastText, Toast.LENGTH_LONG);
- toast.show();
+ try {
+ Toast toast = Toast.makeText(getContext(), toastText, Toast.LENGTH_LONG);
+ toast.show();
+ } catch (RuntimeException e) {
+ Slog.w(TAG, "Unable to toast with text: " + toastText, e);
+ }
}
}
@@ -3596,7 +3613,7 @@
final boolean aboveThreshold =
record.getImportance() >= NotificationManager.IMPORTANCE_DEFAULT;
final boolean canInterrupt = aboveThreshold && !record.isIntercepted();
- if (DBG || record.isIntercepted())
+ if (DBG)
Slog.v(TAG,
"pkg=" + record.sbn.getPackageName() + " canInterrupt=" + canInterrupt +
" intercept=" + record.isIntercepted()
diff --git a/services/core/java/com/android/server/pm/InstantAppRegistry.java b/services/core/java/com/android/server/pm/InstantAppRegistry.java
index 89a303d..46bc69b 100644
--- a/services/core/java/com/android/server/pm/InstantAppRegistry.java
+++ b/services/core/java/com/android/server/pm/InstantAppRegistry.java
@@ -35,6 +35,7 @@
import android.provider.Settings;
import android.util.ArrayMap;
import android.util.AtomicFile;
+import android.util.ByteStringUtils;
import android.util.PackageUtils;
import android.util.Slog;
import android.util.SparseArray;
@@ -56,8 +57,10 @@
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
+import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.List;
+import java.util.Locale;
import java.util.Set;
import java.util.function.Predicate;
@@ -81,6 +84,7 @@
private static final String INSTANT_APP_COOKIE_FILE_PREFIX = "cookie_";
private static final String INSTANT_APP_COOKIE_FILE_SIFFIX = ".dat";
private static final String INSTANT_APP_METADATA_FILE = "metadata.xml";
+ private static final String INSTANT_APP_ANDROID_ID_FILE = "android_id";
private static final String TAG_PACKAGE = "package";
private static final String TAG_PERMISSIONS = "permissions";
@@ -195,6 +199,36 @@
return null;
}
+ public String getInstantAppAndroidIdLPw(@NonNull String packageName,
+ @UserIdInt int userId) {
+ File idFile = new File(getInstantApplicationDir(packageName, userId),
+ INSTANT_APP_ANDROID_ID_FILE);
+ if (idFile.exists()) {
+ try {
+ return IoUtils.readFileAsString(idFile.getAbsolutePath());
+ } catch (IOException e) {
+ Slog.e(LOG_TAG, "Failed to read instant app android id file: " + idFile, e);
+ }
+ }
+ return generateInstantAppAndroidIdLPw(packageName, userId);
+ }
+
+ private String generateInstantAppAndroidIdLPw(@NonNull String packageName,
+ @UserIdInt int userId) {
+ byte[] randomBytes = new byte[8];
+ new SecureRandom().nextBytes(randomBytes);
+ String id = ByteStringUtils.toHexString(randomBytes).toLowerCase(Locale.US);
+ File idFile = new File(getInstantApplicationDir(packageName, userId),
+ INSTANT_APP_ANDROID_ID_FILE);
+ try (FileOutputStream fos = new FileOutputStream(idFile)) {
+ fos.write(id.getBytes());
+ } catch (IOException e) {
+ Slog.e(LOG_TAG, "Error writing instant app android id file: " + idFile, e);
+ }
+ return id;
+
+ }
+
public @Nullable List<InstantAppInfo> getInstantAppsLPr(@UserIdInt int userId) {
List<InstantAppInfo> installedApps = getInstalledInstantApplicationsLPr(userId);
List<InstantAppInfo> uninstalledApps = getUninstalledInstantApplicationsLPr(userId);
@@ -462,6 +496,7 @@
File instantAppDir = getInstantApplicationDir(packageName, userId);
new File(instantAppDir, INSTANT_APP_METADATA_FILE).delete();
new File(instantAppDir, INSTANT_APP_ICON_FILE).delete();
+ new File(instantAppDir, INSTANT_APP_ANDROID_ID_FILE).delete();
File cookie = peekInstantCookieFile(packageName, userId);
if (cookie != null) {
cookie.delete();
diff --git a/services/core/java/com/android/server/pm/OtaDexoptService.java b/services/core/java/com/android/server/pm/OtaDexoptService.java
index 7b86542..2e4b49a 100644
--- a/services/core/java/com/android/server/pm/OtaDexoptService.java
+++ b/services/core/java/com/android/server/pm/OtaDexoptService.java
@@ -274,23 +274,56 @@
// Intercept and collect dexopt requests
final List<String> commands = new ArrayList<String>();
final Installer collectingInstaller = new Installer(mContext, true) {
+ /**
+ * Encode the dexopt command into a string.
+ *
+ * Note: If you have to change the signature of this function, increase the version
+ * number, and update the counterpart in
+ * frameworks/native/cmds/installd/otapreopt.cpp.
+ */
@Override
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, @Nullable String seInfo) throws InstallerException {
- commands.add(buildCommand("dexopt",
- apkPath,
- uid,
- pkgName,
- instructionSet,
- dexoptNeeded,
- outputPath,
- dexFlags,
- compilerFilter,
- volumeUuid,
- sharedLibraries,
- seInfo));
+ final StringBuilder builder = new StringBuilder();
+
+ // The version. Right now it's 2.
+ builder.append("2 ");
+
+ builder.append("dexopt");
+
+ encodeParameter(builder, apkPath);
+ encodeParameter(builder, uid);
+ encodeParameter(builder, pkgName);
+ encodeParameter(builder, instructionSet);
+ encodeParameter(builder, dexoptNeeded);
+ encodeParameter(builder, outputPath);
+ encodeParameter(builder, dexFlags);
+ encodeParameter(builder, compilerFilter);
+ encodeParameter(builder, volumeUuid);
+ encodeParameter(builder, sharedLibraries);
+ encodeParameter(builder, seInfo);
+
+ commands.add(builder.toString());
+ }
+
+ /**
+ * Encode a parameter as necessary for the commands string.
+ */
+ private void encodeParameter(StringBuilder builder, Object arg) {
+ builder.append(' ');
+
+ if (arg == null) {
+ builder.append('!');
+ }
+
+ String txt = String.valueOf(arg);
+ if (txt.indexOf('\0') != -1 || txt.indexOf(' ') != -1 || "!".equals(txt)) {
+ throw new IllegalArgumentException(
+ "Invalid argument while executing " + arg);
+ }
+ builder.append(txt);
}
};
@@ -430,28 +463,4 @@
super(installer, installLock, context, "*otadexopt*");
}
}
-
- /**
- * Cook up argument list in the format that {@code installd} expects.
- */
- private static String buildCommand(Object... args) {
- final StringBuilder builder = new StringBuilder();
- for (Object arg : args) {
- String escaped;
- if (arg == null) {
- escaped = "";
- } else {
- escaped = String.valueOf(arg);
- }
- if (escaped.indexOf('\0') != -1 || escaped.indexOf(' ') != -1 || "!".equals(escaped)) {
- throw new IllegalArgumentException(
- "Invalid argument while executing " + Arrays.toString(args));
- }
- if (TextUtils.isEmpty(escaped)) {
- escaped = "!";
- }
- builder.append(' ').append(escaped);
- }
- return builder.toString();
- }
}
diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
index 9039647..8413491 100644
--- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java
+++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
@@ -149,13 +149,14 @@
final boolean profileUpdated = checkForProfileUpdates &&
isProfileUpdated(pkg, sharedGid, compilerFilter);
- // TODO(calin,jeffhao): shared library paths should be adjusted to include previous code
- // paths (b/34169257).
- final String sharedLibrariesPath = getSharedLibrariesPath(sharedLibraries);
+ 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;
+ // TODO: Iterate based on dependency hierarchy (currently alphabetically by name)
+ // (b/37480811).
+ String basePathCheck = null;
for (String path : paths) {
for (String dexCodeIsa : dexCodeInstructionSets) {
int newResult = dexOptPath(pkg, path, dexCodeIsa, compilerFilter, profileUpdated,
@@ -167,6 +168,22 @@
if ((result != DEX_OPT_FAILED) && (newResult != DEX_OPT_SKIPPED)) {
result = newResult;
}
+ // Add the relative path of code we just compiled to the shared libraries.
+ int slashIndex = path.lastIndexOf('/') + 1;
+ String relativePath = path.substring(slashIndex);
+ if (sharedLibrariesPath == null) {
+ sharedLibrariesPath = relativePath;
+ } else {
+ sharedLibrariesPath += ":" + relativePath;
+ }
+ // Sanity check that the base paths are all the same.
+ String basePath = path.substring(0, slashIndex);
+ if (basePathCheck == null) {
+ basePathCheck = basePath;
+ } else if (!basePath.equals(basePathCheck)) {
+ Slog.wtf(TAG, "Split paths have different base paths: " + basePath + " and " +
+ basePathCheck);
+ }
}
}
return result;
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 95b4903..3411c4e 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -2805,8 +2805,12 @@
mRequiredInstallerPackage = getRequiredInstallerLPr();
mRequiredUninstallerPackage = getRequiredUninstallerLPr();
mIntentFilterVerifierComponent = getIntentFilterVerifierComponentNameLPr();
- mIntentFilterVerifier = new IntentVerifierProxy(mContext,
- mIntentFilterVerifierComponent);
+ if (mIntentFilterVerifierComponent != null) {
+ mIntentFilterVerifier = new IntentVerifierProxy(mContext,
+ mIntentFilterVerifierComponent);
+ } else {
+ mIntentFilterVerifier = null;
+ }
mServicesSystemSharedLibraryPackageName = getRequiredSharedLibraryLPr(
PackageManager.SYSTEM_SHARED_LIBRARY_SERVICES,
SharedLibraryInfo.VERSION_UNDEFINED);
@@ -3046,9 +3050,9 @@
if (best != null) {
return best.getComponentInfo().getComponentName();
- } else {
- throw new RuntimeException("There must be at least one intent filter verifier");
}
+ Slog.w(TAG, "Intent filter verifier not found");
+ return null;
}
private @Nullable Pair<ComponentName, String> getInstantAppResolverLPr() {
@@ -4218,8 +4222,10 @@
}
SharedLibraryInfo resLibInfo = new SharedLibraryInfo(libInfo.getName(),
- libInfo.getVersion(), libInfo.getType(), libInfo.getDeclaringPackage(),
- getPackagesUsingSharedLibraryLPr(libInfo, flags, userId));
+ // TODO: Remove cast for lib version once internally we support longs.
+ (int) libInfo.getVersion(), libInfo.getType(),
+ libInfo.getDeclaringPackage(), getPackagesUsingSharedLibraryLPr(libInfo,
+ flags, userId));
if (result == null) {
result = new ArrayList<>();
@@ -13202,7 +13208,7 @@
if (dcsUid > 0) {
am.backgroundWhitelistUid(dcsUid);
}
- am.startService(null, intent, null, -1, null, false, mContext.getOpPackageName(),
+ am.startService(null, intent, null, false, mContext.getOpPackageName(),
UserHandle.USER_SYSTEM);
} catch (RemoteException e) {
}
@@ -17593,7 +17599,8 @@
for (int i = 0; i < versionCount; i++) {
SharedLibraryEntry libEntry = versionedLib.valueAt(i);
if (versionsCallerCanSee != null && versionsCallerCanSee.indexOfKey(
- libEntry.info.getVersion()) < 0) {
+ // TODO: Remove cast for lib version once internally we support longs.
+ (int) libEntry.info.getVersion()) < 0) {
continue;
}
// TODO: We will change version code to long, so in the new API it is long
@@ -23651,6 +23658,22 @@
return mInstantAppInstallerActivity == null
? null : mInstantAppInstallerActivity.getComponentName();
}
+
+ @Override
+ public String getInstantAppAndroidId(String packageName, int userId) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.ACCESS_INSTANT_APPS,
+ "getInstantAppAndroidId");
+ enforceCrossUserPermission(Binder.getCallingUid(), userId,
+ true /* requireFullPermission */, false /* checkShell */,
+ "getInstantAppAndroidId");
+ // Make sure the target is an Instant App.
+ if (!isInstantApp(packageName, userId)) {
+ return null;
+ }
+ synchronized (mPackages) {
+ return mInstantAppRegistry.getInstantAppAndroidIdLPw(packageName, userId);
+ }
+ }
}
interface PackageSender {
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 4f29bfa..5c9f749 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -222,7 +222,7 @@
import android.view.animation.Animation;
import android.view.animation.AnimationSet;
import android.view.animation.AnimationUtils;
-import android.widget.ImageView;
+import android.view.inputmethod.InputMethodManagerInternal;
import com.android.internal.R;
import com.android.internal.logging.MetricsLogger;
@@ -279,6 +279,7 @@
static final int SHORT_PRESS_POWER_REALLY_GO_TO_SLEEP = 2;
static final int SHORT_PRESS_POWER_REALLY_GO_TO_SLEEP_AND_GO_HOME = 3;
static final int SHORT_PRESS_POWER_GO_HOME = 4;
+ static final int SHORT_PRESS_POWER_CLOSE_IME_OR_GO_HOME = 5;
static final int LONG_PRESS_POWER_NOTHING = 0;
static final int LONG_PRESS_POWER_GLOBAL_ACTIONS = 1;
@@ -407,6 +408,7 @@
PowerManager mPowerManager;
ActivityManagerInternal mActivityManagerInternal;
InputManagerInternal mInputManagerInternal;
+ InputMethodManagerInternal mInputMethodManagerInternal;
DreamManagerInternal mDreamManagerInternal;
PowerManagerInternal mPowerManagerInternal;
IStatusBarService mStatusBarService;
@@ -513,6 +515,7 @@
volatile boolean mPictureInPictureVisible;
// Written by vr manager thread, only read in this class
volatile boolean mPersistentVrModeEnabled;
+ volatile private boolean mDismissImeOnBackKeyPressed;
// Used to hold the last user key used to wake the device. This helps us prevent up events
// from being passed to the foregrounded app without a corresponding down event
@@ -1396,6 +1399,21 @@
case SHORT_PRESS_POWER_GO_HOME:
launchHomeFromHotKey(true /* awakenFromDreams */, false /*respectKeyguard*/);
break;
+ case SHORT_PRESS_POWER_CLOSE_IME_OR_GO_HOME: {
+ if (mDismissImeOnBackKeyPressed) {
+ if (mInputMethodManagerInternal == null) {
+ mInputMethodManagerInternal =
+ LocalServices.getService(InputMethodManagerInternal.class);
+ }
+ if (mInputMethodManagerInternal != null) {
+ mInputMethodManagerInternal.hideCurrentInputMethod();
+ }
+ } else {
+ launchHomeFromHotKey(true /* awakenFromDreams */,
+ false /*respectKeyguard*/);
+ }
+ break;
+ }
}
}
}
@@ -7955,6 +7973,11 @@
}
@Override
+ public void setDismissImeOnBackKeyPressed(boolean newValue) {
+ mDismissImeOnBackKeyPressed = newValue;
+ }
+
+ @Override
public int getInputMethodWindowVisibleHeightLw() {
return mDockBottom - mCurBottom;
}
@@ -8170,6 +8193,8 @@
pw.print(prefix); pw.print("mLastInputMethodTargetWindow=");
pw.println(mLastInputMethodTargetWindow);
}
+ pw.print(prefix); pw.print("mDismissImeOnBackKeyPressed=");
+ pw.println(mDismissImeOnBackKeyPressed);
if (mStatusBar != null) {
pw.print(prefix); pw.print("mStatusBar=");
pw.print(mStatusBar); pw.print(" isStatusBarKeyguard=");
diff --git a/services/core/java/com/android/server/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java
index 8f11436..f5bb082 100644
--- a/services/core/java/com/android/server/power/Notifier.java
+++ b/services/core/java/com/android/server/power/Notifier.java
@@ -406,11 +406,7 @@
mHandler.post(new Runnable() {
@Override
public void run() {
- LogMaker log = new LogMaker(MetricsEvent.SCREEN);
- log.setType(MetricsEvent.TYPE_OPEN);
- log.setSubtype(0); // not user initiated
- MetricsLogger.action(log);
- EventLog.writeEvent(EventLogTags.POWER_SCREEN_STATE, 1, 0, 0, 0);
+ // Note a SCREEN tron event is logged in PowerManagerService.
mPolicy.startedWakingUp();
}
});
@@ -470,7 +466,7 @@
log.setType(MetricsEvent.TYPE_CLOSE);
log.setSubtype(why);
MetricsLogger.action(log);
- EventLog.writeEvent(EventLogTags.POWER_SCREEN_STATE, 0, why, 0, 0);
+ EventLogTags.writePowerScreenState(0, why, 0, 0, 0);
mPolicy.finishedGoingToSleep(why);
}
});
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index cf597b05..4f239a5 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -32,6 +32,7 @@
import android.hardware.display.DisplayManagerInternal;
import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
import android.hardware.power.V1_0.PowerHint;
+import android.metrics.LogMaker;
import android.net.Uri;
import android.os.BatteryManager;
import android.os.BatteryManagerInternal;
@@ -75,6 +76,8 @@
import com.android.internal.app.IAppOpsService;
import com.android.internal.app.IBatteryStats;
import com.android.internal.hardware.AmbientDisplayConfiguration;
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.os.BackgroundThread;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.DumpUtils;
@@ -188,6 +191,11 @@
// System property indicating that the screen should remain off until an explicit user action
private static final String SYSTEM_PROPERTY_QUIESCENT = "ro.boot.quiescent";
+ private static final String TRACE_SCREEN_ON = "Screen turning on";
+
+ /** If turning screen on takes more than this long, we show a warning on logcat. */
+ private static final int SCREEN_ON_LATENCY_WARNING_MS = 200;
+
/** Constants for {@link #shutdownOrRebootInternal} */
@Retention(RetentionPolicy.SOURCE)
@IntDef({HALT_MODE_SHUTDOWN, HALT_MODE_REBOOT, HALT_MODE_REBOOT_SAFE_MODE})
@@ -1369,6 +1377,8 @@
return false;
}
+ Trace.asyncTraceBegin(Trace.TRACE_TAG_POWER, TRACE_SCREEN_ON, 0);
+
Trace.traceBegin(Trace.TRACE_TAG_POWER, "wakeUp");
try {
switch (mWakefulness) {
@@ -1551,6 +1561,23 @@
}
}
+ private void logScreenOn() {
+ Trace.asyncTraceEnd(Trace.TRACE_TAG_POWER, TRACE_SCREEN_ON, 0);
+
+ final int latencyMs = (int) (SystemClock.uptimeMillis() - mLastWakeTime);
+
+ LogMaker log = new LogMaker(MetricsEvent.SCREEN);
+ log.setType(MetricsEvent.TYPE_OPEN);
+ log.setSubtype(0); // not user initiated
+ log.setLatency(latencyMs); // How long it took.
+ MetricsLogger.action(log);
+ EventLogTags.writePowerScreenState(1, 0, 0, 0, latencyMs);
+
+ if (latencyMs >= SCREEN_ON_LATENCY_WARNING_MS) {
+ Slog.w(TAG, "Screen on took " + latencyMs+ " ms");
+ }
+ }
+
private void finishWakefulnessChangeIfNeededLocked() {
if (mWakefulnessChanging && mDisplayReady) {
if (mWakefulness == WAKEFULNESS_DOZING
@@ -1560,6 +1587,9 @@
if (mWakefulness == WAKEFULNESS_DOZING || mWakefulness == WAKEFULNESS_ASLEEP) {
logSleepTimeoutRecapturedLocked();
}
+ if (mWakefulness == WAKEFULNESS_AWAKE) {
+ logScreenOn();
+ }
mWakefulnessChanging = false;
mNotifier.onWakefulnessChangeFinished();
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 0049585..252b4d4 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -7267,13 +7267,16 @@
@Override
public void updateInputMethodWindowStatus(@NonNull IBinder imeToken,
- boolean imeWindowVisible, @Nullable IBinder targetWindowToken) {
+ boolean imeWindowVisible, boolean dismissImeOnBackKeyPressed,
+ @Nullable IBinder targetWindowToken) {
// TODO (b/34628091): Use this method to address the window animation issue.
if (DEBUG_INPUT_METHOD) {
Slog.w(TAG_WM, "updateInputMethodWindowStatus: imeToken=" + imeToken
+ + " dismissImeOnBackKeyPressed=" + dismissImeOnBackKeyPressed
+ " imeWindowVisible=" + imeWindowVisible
+ " targetWindowToken=" + targetWindowToken);
}
+ mPolicy.setDismissImeOnBackKeyPressed(dismissImeOnBackKeyPressed);
}
@Override
diff --git a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
index 86662b9..74ecd11 100644
--- a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
+++ b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
@@ -68,12 +68,15 @@
using android::OK;
using android::sp;
+using android::wp;
using android::status_t;
using android::String16;
using android::hardware::Return;
using android::hardware::Void;
using android::hardware::hidl_vec;
+using android::hardware::hidl_death_recipient;
+using android::hidl::base::V1_0::IBase;
using android::hardware::gnss::V1_0::IAGnss;
using android::hardware::gnss::V1_0::IAGnssCallback;
@@ -97,7 +100,18 @@
using android::hardware::gnss::V1_0::IGnssXtra;
using android::hardware::gnss::V1_0::IGnssXtraCallback;
+struct GnssDeathRecipient : virtual public hidl_death_recipient
+{
+ // hidl_death_recipient interface
+ virtual void serviceDied(uint64_t cookie, const wp<IBase>& who) override {
+ // TODO(gomo): implement a better death recovery mechanism without
+ // crashing system server process as described in go//treble-gnss-death
+ LOG_ALWAYS_FATAL("Abort due to IGNSS hidl service failure,"
+ " restarting system server");
+ }
+};
+sp<GnssDeathRecipient> gnssHalDeathRecipient = nullptr;
sp<IGnss> gnssHal = nullptr;
sp<IGnssXtra> gnssXtraIface = nullptr;
sp<IAGnssRil> agnssRilIface = nullptr;
@@ -1038,6 +1052,18 @@
// TODO(b/31632518)
gnssHal = IGnss::getService();
if (gnssHal != nullptr) {
+ gnssHalDeathRecipient = new GnssDeathRecipient();
+ hardware::Return<bool> linked = gnssHal->linkToDeath(
+ gnssHalDeathRecipient, /*cookie*/ 0);
+ if (!linked.isOk()) {
+ ALOGE("Transaction error in linking to GnssHAL death: %s",
+ linked.description().c_str());
+ } else if (!linked) {
+ ALOGW("Unable to link to GnssHal death notifications");
+ } else {
+ ALOGD("Link to death notification successful");
+ }
+
auto gnssXtra = gnssHal->getExtensionXtra();
if (!gnssXtra.isOk()) {
ALOGD("Unable to get a handle to Xtra");
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index bfa1b99..e82ba9c 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -742,8 +742,8 @@
boolean forceEphemeralUsers = false; // Can only be set by a device owner.
boolean isNetworkLoggingEnabled = false; // Can only be set by a device owner.
- // one notification after enabling + 3 more after reboots
- static final int DEF_MAXIMUM_NETWORK_LOGGING_NOTIFICATIONS_SHOWN = 4;
+ // one notification after enabling + one more after reboots
+ static final int DEF_MAXIMUM_NETWORK_LOGGING_NOTIFICATIONS_SHOWN = 2;
int numNetworkLoggingNotifications = 0;
long lastNetworkLoggingNotificationTimeMs = 0; // Time in milliseconds since epoch
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 42a9232..978803d 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -159,7 +159,7 @@
private static final String PRINT_MANAGER_SERVICE_CLASS =
"com.android.server.print.PrintManagerService";
private static final String COMPANION_DEVICE_MANAGER_SERVICE_CLASS =
- "com.android.server.print.CompanionDeviceManagerService";
+ "com.android.server.companion.CompanionDeviceManagerService";
private static final String USB_SERVICE_CLASS =
"com.android.server.usb.UsbService$Lifecycle";
private static final String MIDI_SERVICE_CLASS =
diff --git a/services/tests/notification/src/com/android/server/notification/GlobalSortKeyComparatorTest.java b/services/tests/notification/src/com/android/server/notification/GlobalSortKeyComparatorTest.java
new file mode 100644
index 0000000..24cb72e
--- /dev/null
+++ b/services/tests/notification/src/com/android/server/notification/GlobalSortKeyComparatorTest.java
@@ -0,0 +1,153 @@
+/*
+ * 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 com.android.server.notification;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.eq;
+
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.os.UserHandle;
+import android.service.notification.StatusBarNotification;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class GlobalSortKeyComparatorTest {
+
+ private final String PKG = "PKG";
+ private final int UID = 1111111;
+ private static final String TEST_CHANNEL_ID = "test_channel_id";
+
+ @Test
+ public void testComparator() throws Exception {
+ Notification n = new Notification.Builder(
+ InstrumentationRegistry.getContext(), TEST_CHANNEL_ID)
+ .build();
+ NotificationRecord left = new NotificationRecord(InstrumentationRegistry.getContext(),
+ new StatusBarNotification(PKG,
+ PKG, 1, "media", UID, UID, n,
+ new UserHandle(UserHandle.myUserId()),
+ "", 1499), getDefaultChannel());
+ left.setGlobalSortKey("first");
+
+ NotificationRecord right = new NotificationRecord(InstrumentationRegistry.getContext(),
+ new StatusBarNotification(PKG,
+ PKG, 1, "media", UID, UID, n,
+ new UserHandle(UserHandle.myUserId()),
+ "", 1499), getDefaultChannel());
+ right.setGlobalSortKey("second");
+
+ NotificationRecord last = new NotificationRecord(InstrumentationRegistry.getContext(),
+ new StatusBarNotification(PKG,
+ PKG, 1, "media", UID, UID, n,
+ new UserHandle(UserHandle.myUserId()),
+ "", 1499), getDefaultChannel());
+
+
+ final List<NotificationRecord> expected = new ArrayList<>();
+ expected.add(left);
+ expected.add(right);
+ expected.add(last);
+
+ List<NotificationRecord> actual = new ArrayList<>();
+ actual.addAll(expected);
+ Collections.shuffle(actual);
+
+ Collections.sort(actual, new GlobalSortKeyComparator());
+
+ assertEquals(expected, actual);
+ }
+
+ @Test
+ public void testNoCrash_leftNull() throws Exception {
+ Notification n = new Notification.Builder(
+ InstrumentationRegistry.getContext(), TEST_CHANNEL_ID)
+ .build();
+ NotificationRecord left = new NotificationRecord(InstrumentationRegistry.getContext(),
+ new StatusBarNotification(PKG,
+ PKG, 1, "media", UID, UID, n,
+ new UserHandle(UserHandle.myUserId()),
+ "", 1499), getDefaultChannel());
+
+ NotificationRecord right = new NotificationRecord(InstrumentationRegistry.getContext(),
+ new StatusBarNotification(PKG,
+ PKG, 1, "media", UID, UID, n,
+ new UserHandle(UserHandle.myUserId()),
+ "", 1499), getDefaultChannel());
+ right.setGlobalSortKey("not null");
+
+ final List<NotificationRecord> expected = new ArrayList<>();
+ expected.add(right);
+ expected.add(left);
+
+ List<NotificationRecord> actual = new ArrayList<>();
+ actual.addAll(expected);
+ Collections.shuffle(actual);
+
+ Collections.sort(actual, new GlobalSortKeyComparator());
+
+ assertEquals(expected, actual);
+ }
+
+ @Test
+ public void testNoCrash_rightNull() throws Exception {
+ Notification n = new Notification.Builder(
+ InstrumentationRegistry.getContext(), TEST_CHANNEL_ID)
+ .build();
+ NotificationRecord left = new NotificationRecord(InstrumentationRegistry.getContext(),
+ new StatusBarNotification(PKG,
+ PKG, 1, "media", UID, UID, n,
+ new UserHandle(UserHandle.myUserId()),
+ "", 1499), getDefaultChannel());
+ left.setGlobalSortKey("not null");
+
+ NotificationRecord right = new NotificationRecord(InstrumentationRegistry.getContext(),
+ new StatusBarNotification(PKG,
+ PKG, 1, "media", UID, UID, n,
+ new UserHandle(UserHandle.myUserId()),
+ "", 1499), getDefaultChannel());
+
+ final List<NotificationRecord> expected = new ArrayList<>();
+ expected.add(left);
+ expected.add(right);
+
+ List<NotificationRecord> actual = new ArrayList<>();
+ actual.addAll(expected);
+ Collections.shuffle(actual);
+
+ Collections.sort(actual, new GlobalSortKeyComparator());
+
+ assertEquals(expected, actual);
+ }
+
+ private NotificationChannel getDefaultChannel() {
+ return new NotificationChannel(NotificationChannel.DEFAULT_CHANNEL_ID, "name",
+ NotificationManager.IMPORTANCE_LOW);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/BootReceiverFixFsckFsStatTest.java b/services/tests/servicestests/src/com/android/server/BootReceiverFixFsckFsStatTest.java
new file mode 100644
index 0000000..362c47a
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/BootReceiverFixFsckFsStatTest.java
@@ -0,0 +1,111 @@
+/*
+ * 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 junit.framework.Assert.*;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import junit.framework.Assert;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class BootReceiverFixFsckFsStatTest {
+
+ private static final String PARTITION = "userdata";
+
+ @Test
+ public void testTreeOptimization() {
+ final String[] logs = {
+ "e2fsck 1.43.3 (04-Sep-2016)",
+ "Pass 1: Checking inodes, blocks, and sizes",
+ "Inode 877141 extent tree (at level 1) could be shorter. Fix? yes",
+ " ",
+ "Pass 1E: Optimizing extent trees",
+ "Pass 2: Checking directory structure",
+ "Pass 3: Checking directory connectivity",
+ "Pass 4: Checking reference counts",
+ "Pass 5: Checking group summary information",
+ "[QUOTA WARNING] Usage inconsistent for ID 10038:actual (71667712, 1000) != expected (71671808, 1000)",
+ "Update quota info for quota type 0? yes",
+ " ",
+ "[QUOTA WARNING] Usage inconsistent for ID 10038:actual (59555840, 953) != expected (59559936, 953)",
+ "Update quota info for quota type 1? yes",
+ " ",
+ "/dev/block/platform/soc/624000.ufshc/by-name/userdata: ***** FILE SYSTEM WAS MODIFIED *****"
+ };
+ doTestFsckFsStat(logs, 0x405, 5, 0, logs.length);
+
+ final String[] doubleLogs = new String[logs.length * 2];
+ System.arraycopy(logs, 0, doubleLogs, 0, logs.length);
+ System.arraycopy(logs, 0, doubleLogs, logs.length, logs.length);
+ doTestFsckFsStat(doubleLogs, 0x401, 1, 0, logs.length);
+ doTestFsckFsStat(doubleLogs, 0x402, 2, logs.length, logs.length * 2);
+ }
+
+ @Test
+ public void testQuotaOnly() {
+ final String[] logs = {
+ "e2fsck 1.43.3 (04-Sep-2016)",
+ "Pass 1: Checking inodes, blocks, and sizes",
+ "Pass 1E: Optimizing extent trees",
+ "Pass 2: Checking directory structure",
+ "Pass 3: Checking directory connectivity",
+ "Pass 4: Checking reference counts",
+ "Pass 5: Checking group summary information",
+ "[QUOTA WARNING] Usage inconsistent for ID 10038:actual (71667712, 1000) != expected (71671808, 1000)",
+ "Update quota info for quota type 0? yes",
+ " ",
+ "[QUOTA WARNING] Usage inconsistent for ID 10038:actual (59555840, 953) != expected (59559936, 953)",
+ "Update quota info for quota type 1? yes",
+ " ",
+ "/dev/block/platform/soc/624000.ufshc/by-name/userdata: ***** FILE SYSTEM WAS MODIFIED *****"
+ };
+ doTestFsckFsStat(logs, 0x405, 0x405, 0, logs.length);
+ }
+
+ @Test
+ public void testOrphaned() {
+ final String[] logs = {
+ "e2fsck 1.43.3 (04-Sep-2016)",
+ "Pass 1: Checking inodes, blocks, and sizes",
+ "Inodes that were part of a corrupted orphan linked list found. Fix? yes",
+ " ",
+ "Inode 589877 was part of the orphaned inode list. FIXED.",
+ " ",
+ "Inode 589878 was part of the orphaned inode list. FIXED.",
+ " ",
+ "Pass 2: Checking directory structure",
+ "Pass 3: Checking directory connectivity",
+ "Pass 4: Checking reference counts",
+ "Pass 5: Checking group summary information",
+ " ",
+ "/dev/block/platform/soc/624000.ufshc/by-name/userdata: ***** FILE SYSTEM WAS MODIFIED *****"
+ };
+ doTestFsckFsStat(logs, 0x405, 0x405, 0, logs.length);
+ }
+
+ private void doTestFsckFsStat(String[] lines, int statOrg, int statUpdated, int startLineNumber,
+ int endLineNumber) {
+ assertEquals(statUpdated, BootReceiver.fixFsckFsStat(PARTITION, statOrg, lines,
+ startLineNumber, endLineNumber));
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityRecordTests.java b/services/tests/servicestests/src/com/android/server/am/ActivityRecordTests.java
index 54ecab3..f75d49c 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityRecordTests.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityRecordTests.java
@@ -16,7 +16,8 @@
package com.android.server.am;
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
import android.content.ComponentName;
import android.platform.test.annotations.Presubmit;
@@ -36,50 +37,61 @@
@Presubmit
@RunWith(AndroidJUnit4.class)
public class ActivityRecordTests extends ActivityTestsBase {
+ private static final int TEST_STACK_ID = 100;
+
private final ComponentName testActivityComponent =
ComponentName.unflattenFromString("com.foo/.BarActivity");
@Test
public void testStackCleanupOnClearingTask() throws Exception {
final ActivityManagerService service = createActivityManagerService();
- final TestActivityStack testStack = new ActivityStackBuilder(service).build();
- final TaskRecord task = createTask(service, testActivityComponent, testStack);
+ final TaskRecord task = createTask(service, testActivityComponent, TEST_STACK_ID);
final ActivityRecord record = createActivity(service, testActivityComponent, task);
record.setTask(null);
- assertTrue(testStack.onActivityRemovedFromStackInvocationCount() == 1);
+ assertEquals(getActivityRemovedFromStackCount(service, TEST_STACK_ID), 1);
}
@Test
public void testStackCleanupOnActivityRemoval() throws Exception {
final ActivityManagerService service = createActivityManagerService();
- final TestActivityStack testStack = new ActivityStackBuilder(service).build();
- final TaskRecord task = createTask(service, testActivityComponent, testStack);
+ final TaskRecord task = createTask(service, testActivityComponent, TEST_STACK_ID);
final ActivityRecord record = createActivity(service, testActivityComponent, task);
task.removeActivity(record);
- assertTrue(testStack.onActivityRemovedFromStackInvocationCount() == 1);
+ assertEquals(getActivityRemovedFromStackCount(service, TEST_STACK_ID), 1);
}
@Test
public void testStackCleanupOnTaskRemoval() throws Exception {
final ActivityManagerService service = createActivityManagerService();
- final TestActivityStack testStack = new ActivityStackBuilder(service).build();
- final TaskRecord task = createTask(service, testActivityComponent, testStack);
+ final TaskRecord task = createTask(service, testActivityComponent, TEST_STACK_ID);
final ActivityRecord record = createActivity(service, testActivityComponent, task);
- testStack.removeTask(task, null /*reason*/, ActivityStack.REMOVE_TASK_MODE_MOVING);
- assertTrue(testStack.onActivityRemovedFromStackInvocationCount() == 1);
+ service.mStackSupervisor.getStack(TEST_STACK_ID)
+ .removeTask(task, null /*reason*/, ActivityStack.REMOVE_TASK_MODE_MOVING);
+
+ // Stack should be gone on task removal.
+ assertNull(service.mStackSupervisor.getStack(TEST_STACK_ID));
}
@Test
public void testNoCleanupMovingActivityInSameStack() throws Exception {
final ActivityManagerService service = createActivityManagerService();
- final TestActivityStack testStack = new ActivityStackBuilder(service).build();
- final TaskRecord oldTask = createTask(service, testActivityComponent, testStack);
+ final TaskRecord oldTask = createTask(service, testActivityComponent, TEST_STACK_ID);
final ActivityRecord record = createActivity(service, testActivityComponent, oldTask);
- final TaskRecord newTask = createTask(service, testActivityComponent, testStack);
+ final TaskRecord newTask = createTask(service, testActivityComponent, TEST_STACK_ID);
record.reparent(newTask, 0, null /*reason*/);
- assertTrue(testStack.onActivityRemovedFromStackInvocationCount() == 0);
+ assertEquals(getActivityRemovedFromStackCount(service, TEST_STACK_ID), 0);
+ }
+
+ private static int getActivityRemovedFromStackCount(ActivityManagerService service,
+ int stackId) {
+ final ActivityStack stack = service.mStackSupervisor.getStack(stackId);
+ if (stack instanceof ActivityStackReporter) {
+ return ((ActivityStackReporter) stack).onActivityRemovedFromStackInvocationCount();
+ }
+
+ return -1;
}
}
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 8423aff..fc9ab96 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityStackSupervisorTests.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityStackSupervisorTests.java
@@ -16,8 +16,14 @@
package com.android.server.am;
+import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import android.content.ComponentName;
+import android.graphics.Rect;
import android.platform.test.annotations.Presubmit;
import android.support.test.filters.MediumTest;
import android.support.test.runner.AndroidJUnit4;
@@ -25,6 +31,10 @@
import org.junit.runner.RunWith;
import org.junit.Test;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
import static com.android.server.am.ActivityStackSupervisor.MATCH_TASK_IN_STACKS_OR_RECENT_TASKS_AND_RESTORE;
/**
@@ -37,6 +47,9 @@
@Presubmit
@RunWith(AndroidJUnit4.class)
public class ActivityStackSupervisorTests extends ActivityTestsBase {
+ private final ComponentName testActivityComponent =
+ ComponentName.unflattenFromString("com.foo/.BarActivity");
+
/**
* This test ensures that we do not try to restore a task based off an invalid task id. The
* stack supervisor is a test version so there will be no tasks present. We should expect
@@ -49,4 +62,59 @@
MATCH_TASK_IN_STACKS_OR_RECENT_TASKS_AND_RESTORE, 0 /*stackId*/);
assertNull(task);
}
+
+ /**
+ * This test ensures that an existing task in the pinned stack is moved to the fullscreen
+ * activity stack when a new task is added.
+ */
+ @Test
+ public void testReplacingTaskInPinnedStack() throws Exception {
+ final ActivityManagerService service = createActivityManagerService();
+ final TaskRecord firstTask = createTask(service, testActivityComponent,
+ FULLSCREEN_WORKSPACE_STACK_ID);
+ final ActivityRecord firstActivity = createActivity(service, testActivityComponent,
+ firstTask);
+ // Create a new task on the full screen stack
+ final TaskRecord secondTask = createTask(service, testActivityComponent,
+ FULLSCREEN_WORKSPACE_STACK_ID);
+ final ActivityRecord secondActivity = createActivity(service, testActivityComponent,
+ secondTask);
+ service.mStackSupervisor.setFocusStackUnchecked("testReplacingTaskInPinnedStack",
+ service.mStackSupervisor.getStack(FULLSCREEN_WORKSPACE_STACK_ID));
+
+ // Ensure full screen stack has both tasks.
+ ensureStackPlacement(service.mStackSupervisor, FULLSCREEN_WORKSPACE_STACK_ID, firstTask,
+ secondTask);
+
+ // Move first activity to pinned stack.
+ service.mStackSupervisor.moveActivityToPinnedStackLocked(firstActivity,
+ new Rect() /*sourceBounds*/, 0f /*aspectRatio*/, false, "initialMove");
+
+ // Ensure a task has moved over.
+ ensureStackPlacement(service.mStackSupervisor, PINNED_STACK_ID, firstTask);
+ ensureStackPlacement(service.mStackSupervisor, FULLSCREEN_WORKSPACE_STACK_ID, secondTask);
+
+ // Move second activity to pinned stack.
+ service.mStackSupervisor.moveActivityToPinnedStackLocked(secondActivity,
+ new Rect() /*sourceBounds*/, 0f /*aspectRatio*/ /*destBounds*/, false, "secondMove");
+
+ // Ensure stacks have swapped tasks.
+ ensureStackPlacement(service.mStackSupervisor, PINNED_STACK_ID, secondTask);
+ ensureStackPlacement(service.mStackSupervisor, FULLSCREEN_WORKSPACE_STACK_ID, firstTask);
+ }
+
+ private static void ensureStackPlacement(ActivityStackSupervisor supervisor, int stackId,
+ TaskRecord... tasks) {
+ final ActivityStack stack = supervisor.getStack(stackId);
+ final ArrayList<TaskRecord> stackTasks = stack.getAllTasks();
+ assertEquals(stackTasks.size(), tasks != null ? tasks.length : 0);
+
+ if (tasks == null) {
+ return;
+ }
+
+ for (TaskRecord task : tasks) {
+ assertTrue(stackTasks.contains(task));
+ }
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityStackTests.java b/services/tests/servicestests/src/com/android/server/am/ActivityStackTests.java
index 1d80b33..f42abf1 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityStackTests.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityStackTests.java
@@ -37,28 +37,27 @@
@Presubmit
@RunWith(AndroidJUnit4.class)
public class ActivityStackTests extends ActivityTestsBase {
- private final ComponentName testActivityComponent =
+ private static final int TEST_STACK_ID = 100;
+ private static final ComponentName testActivityComponent =
ComponentName.unflattenFromString("com.foo/.BarActivity");
@Test
public void testEmptyTaskCleanupOnRemove() throws Exception {
final ActivityManagerService service = createActivityManagerService();
- final TestActivityStack testStack = new ActivityStackBuilder(service).build();
- final TaskRecord task = createTask(service, testActivityComponent, testStack);
+ final TaskRecord task = createTask(service, testActivityComponent, TEST_STACK_ID);
assertNotNull(task.getWindowContainerController());
- testStack.removeTask(task, "testEmptyTaskCleanupOnRemove",
- ActivityStack.REMOVE_TASK_MODE_DESTROYING);
+ service.mStackSupervisor.getStack(TEST_STACK_ID).removeTask(task,
+ "testEmptyTaskCleanupOnRemove", ActivityStack.REMOVE_TASK_MODE_DESTROYING);
assertNull(task.getWindowContainerController());
}
@Test
public void testOccupiedTaskCleanupOnRemove() throws Exception {
final ActivityManagerService service = createActivityManagerService();
- final TestActivityStack testStack = new ActivityStackBuilder(service).build();
- final TaskRecord task = createTask(service, testActivityComponent, testStack);
+ final TaskRecord task = createTask(service, testActivityComponent, TEST_STACK_ID);
final ActivityRecord activityRecord = createActivity(service, testActivityComponent, task);
assertNotNull(task.getWindowContainerController());
- testStack.removeTask(task, "testOccupiedTaskCleanupOnRemove",
- ActivityStack.REMOVE_TASK_MODE_DESTROYING);
+ service.mStackSupervisor.getStack(TEST_STACK_ID).removeTask(task,
+ "testOccupiedTaskCleanupOnRemove", ActivityStack.REMOVE_TASK_MODE_DESTROYING);
assertNotNull(task.getWindowContainerController());
}
}
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java b/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java
index 3bf0e5f..0827084 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java
@@ -17,6 +17,11 @@
package com.android.server.am;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doAnswer;
+
+import org.mockito.invocation.InvocationOnMock;
import android.app.ActivityManager;
import android.content.ComponentName;
@@ -33,6 +38,7 @@
import com.android.server.wm.AppWindowContainerController;
import com.android.server.wm.StackWindowController;
+import com.android.server.wm.TaskWindowContainerController;
import com.android.server.wm.WindowManagerService;
import com.android.server.wm.WindowTestUtils;
import org.junit.After;
@@ -64,16 +70,15 @@
protected ActivityManagerService createActivityManagerService() {
final ActivityManagerService service = new TestActivityManagerService(mContext);
- service.mWindowManager = WindowTestUtils.getWindowManagerService(mContext);
+ service.mWindowManager = WindowTestUtils.getMockWindowManagerService();
return service;
}
- protected static TestActivityStack createActivityStack(ActivityManagerService service,
+ protected static ActivityStack createActivityStack(ActivityManagerService service,
int stackId, int displayId, boolean onTop) {
if (service.mStackSupervisor instanceof TestActivityStackSupervisor) {
- final TestActivityStack stack = ((TestActivityStackSupervisor) service.mStackSupervisor)
+ return ((TestActivityStackSupervisor) service.mStackSupervisor)
.createTestStack(service, stackId, onTop);
- return stack;
}
return null;
@@ -103,7 +108,7 @@
}
protected static TaskRecord createTask(ActivityManagerService service,
- ComponentName component, ActivityStack stack) {
+ ComponentName component, int stackId) {
final ActivityInfo aInfo = new ActivityInfo();
aInfo.applicationInfo = new ApplicationInfo();
aInfo.applicationInfo.packageName = component.getPackageName();
@@ -113,13 +118,16 @@
final TaskRecord task = new TaskRecord(service, 0, aInfo, intent /*intent*/,
null /*_taskDescription*/, new ActivityManager.TaskThumbnailInfo());
+ final ActivityStack stack = service.mStackSupervisor.getStack(stackId,
+ true /*createStaticStackIfNeeded*/, true /*onTop*/);
stack.addTask(task, true, "creating test task");
task.setStack(stack);
- task.createWindowContainer(true, true);
+ task.setWindowContainerController(mock(TaskWindowContainerController.class));
return task;
}
+
/**
* An {@link ActivityManagerService} subclass which provides a test
* {@link ActivityStackSupervisor}.
@@ -127,6 +135,9 @@
protected static class TestActivityManagerService extends ActivityManagerService {
public TestActivityManagerService(Context context) {
super(context);
+ mSupportsMultiWindow = true;
+ mSupportsMultiDisplay = true;
+ mWindowManager = WindowTestUtils.getWindowManagerService(context);
}
@Override
@@ -142,6 +153,12 @@
protected static class TestActivityStackSupervisor extends ActivityStackSupervisor {
public TestActivityStackSupervisor(ActivityManagerService service, Looper looper) {
super(service, looper);
+ mWindowManager = prepareMockWindowManager();
+ }
+
+ // No home stack is set.
+ @Override
+ void moveHomeStackToFront(String reason) {
}
// Invoked during {@link ActivityStack} creation.
@@ -149,18 +166,45 @@
void updateUIDsPresentOnDisplay() {
}
- public TestActivityStack createTestStack(ActivityManagerService service, int stackId,
- boolean onTop) {
+ // Just return the current front task.
+ @Override
+ ActivityStack getNextFocusableStackLocked(ActivityStack currentFocus) {
+ return mFocusedStack;
+ }
+
+ // Called when moving activity to pinned stack.
+ @Override
+ void ensureActivitiesVisibleLocked(ActivityRecord starting, int configChanges,
+ boolean preserveWindows) {
+ }
+
+ public <T extends ActivityStack> T createTestStack(ActivityManagerService service,
+ int stackId, boolean onTop) {
final ActivityDisplay display = new ActivityDisplay();
final TestActivityContainer container =
new TestActivityContainer(service, stackId, display, onTop);
- return container.getStack();
+ mActivityContainers.put(stackId, container);
+ return (T) container.getStack();
+ }
+
+ @Override
+ protected <T extends ActivityStack> T getStack(int stackId,
+ boolean createStaticStackIfNeeded, boolean createOnTop) {
+ final T stack = super.getStack(stackId, createStaticStackIfNeeded, createOnTop);
+
+ if (stack != null || !createStaticStackIfNeeded) {
+ return stack;
+ }
+
+ return createTestStack(mService, stackId, createOnTop);
}
private class TestActivityContainer extends ActivityContainer {
- private ActivityManagerService mService;
- private TestActivityStack mStack;
+ private final ActivityManagerService mService;
+
private boolean mOnTop;
+ private int mStackId;
+ private ActivityStack mStack;
TestActivityContainer(ActivityManagerService service, int stackId,
ActivityDisplay activityDisplay, boolean onTop) {
@@ -174,12 +218,16 @@
// we cannot set {@link mService} by the time the super constructor calling this
// method is invoked.
mOnTop = onTop;
+ mStackId = stackId;
}
- public TestActivityStack getStack() {
+ public ActivityStack getStack() {
if (mStack == null) {
- mStack = new TestActivityStack(this,
- new RecentTasks(mService, mService.mStackSupervisor), mOnTop);
+ final RecentTasks recents =
+ new RecentTasks(mService, mService.mStackSupervisor);
+ mStack = mStackId == ActivityManager.StackId.PINNED_STACK_ID
+ ? new PinnedActivityStack(this, recents, mOnTop)
+ : new TestActivityStack(this, recents, mOnTop);
}
return mStack;
@@ -187,13 +235,31 @@
}
}
+ private static WindowManagerService prepareMockWindowManager() {
+ final WindowManagerService service = mock(WindowManagerService.class);
+
+ doAnswer((InvocationOnMock invocationOnMock) -> {
+ final Runnable runnable = invocationOnMock.<Runnable>getArgument(0);
+ if (runnable != null) {
+ runnable.run();
+ }
+ return null;
+ }).when(service).inSurfaceTransaction(any());
+
+ return service;
+ }
+
+ protected interface ActivityStackReporter {
+ int onActivityRemovedFromStackInvocationCount();
+ }
+
/**
* Override of {@link ActivityStack} that tracks test metrics, such as the number of times a
* method is called. Note that its functionality depends on the implementations of the
* construction arguments.
*/
protected static class TestActivityStack<T extends StackWindowController>
- extends ActivityStack<T> {
+ extends ActivityStack<T> implements ActivityStackReporter {
private int mOnActivityRemovedFromStackCount = 0;
private T mContainerController;
TestActivityStack(ActivityStackSupervisor.ActivityContainer activityContainer,
@@ -208,6 +274,7 @@
}
// Returns the number of times {@link #onActivityRemovedFromStack} has been called
+ @Override
public int onActivityRemovedFromStackInvocationCount() {
return mOnActivityRemovedFromStackCount;
}
@@ -225,6 +292,7 @@
}
}
+
protected static class ActivityStackBuilder {
private boolean mOnTop = true;
private int mStackId = 0;
@@ -251,7 +319,7 @@
return this;
}
- public TestActivityStack build() {
+ public ActivityStack build() {
return createActivityStack(mService, mStackId, mDisplayId, mOnTop);
}
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceCreateProfileTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceCreateProfileTest.java
index 929a73d..c314de4 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceCreateProfileTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceCreateProfileTest.java
@@ -27,6 +27,7 @@
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.MediumTest;
import android.support.test.runner.AndroidJUnit4;
+import android.util.IconDrawableFactory;
import com.android.server.LocalServices;
@@ -155,9 +156,9 @@
public void testNumberOfBadges() {
assertTrue("Max profiles greater than number of badges",
UserManagerService.MAX_MANAGED_PROFILES
- <= ApplicationPackageManager.CORP_BADGE_COLORS.length);
+ <= IconDrawableFactory.CORP_BADGE_COLORS.length);
assertEquals("Num colors doesn't match number of badge labels",
- ApplicationPackageManager.CORP_BADGE_COLORS.length,
+ IconDrawableFactory.CORP_BADGE_COLORS.length,
ApplicationPackageManager.CORP_BADGE_LABEL_RES_ID.length);
}
diff --git a/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java b/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java
index fbeda0a..9392e8e 100644
--- a/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java
+++ b/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java
@@ -90,6 +90,7 @@
return null;
}).when(am).notifyKeyguardFlagsChanged(any());
}
+
sWm = WindowManagerService.main(context, mock(InputManagerService.class), true, false,
false, new TestWindowManagerPolicy());
}
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowTestUtils.java b/services/tests/servicestests/src/com/android/server/wm/WindowTestUtils.java
index 3a44357..ae3eb52 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowTestUtils.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowTestUtils.java
@@ -48,6 +48,13 @@
}
/**
+ * Retrieves an instance of a mock {@link WindowManagerService}.
+ */
+ public static WindowManagerService getMockWindowManagerService() {
+ return mock(WindowManagerService.class);
+ }
+
+ /**
* Creates a mock instance of {@link StackWindowController}.
*/
public static StackWindowController createMockStackWindowContainerController() {
diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
index 80b73d3..31595a6 100644
--- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java
+++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
@@ -857,13 +857,16 @@
break;
case MSG_UPDATE_HOST_STATE:
SomeArgs args = (SomeArgs) msg.obj;
+ boolean prevHostConnected = mHostConnected;
mHostConnected = (args.argi1 == 1);
mSourcePower = (args.argi2 == 1);
mSinkPower = (args.argi3 == 1);
args.recycle();
updateUsbNotification();
if (mBootCompleted) {
- updateUsbStateBroadcastIfNeeded(false);
+ if (mHostConnected || prevHostConnected) {
+ updateUsbStateBroadcastIfNeeded(false);
+ }
} else {
mPendingBootBroadcast = true;
}
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 397aa00..8ee6454 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -911,7 +911,7 @@
* {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
*/
public String getDeviceSoftwareVersion() {
- return getDeviceSoftwareVersion(getDefaultSim());
+ return getDeviceSoftwareVersion(getSlotIndex());
}
/**
@@ -997,7 +997,7 @@
* {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
*/
public String getImei() {
- return getImei(getDefaultSim());
+ return getImei(getSlotIndex());
}
/**
@@ -1029,7 +1029,7 @@
* {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
*/
public String getMeid() {
- return getMeid(getDefaultSim());
+ return getMeid(getSlotIndex());
}
/**
@@ -1059,7 +1059,7 @@
*/
/** {@hide}*/
public String getNai() {
- return getNai(getDefaultSim());
+ return getNai(getSlotIndex());
}
/**
@@ -1303,7 +1303,7 @@
}
private int getPhoneTypeFromProperty() {
- return getPhoneTypeFromProperty(getDefaultPhone());
+ return getPhoneTypeFromProperty(getPhoneId());
}
/** {@hide} */
@@ -1317,7 +1317,7 @@
}
private int getPhoneTypeFromNetworkType() {
- return getPhoneTypeFromNetworkType(getDefaultPhone());
+ return getPhoneTypeFromNetworkType(getPhoneId());
}
/** {@hide} */
@@ -1500,7 +1500,7 @@
* on a CDMA network).
*/
public String getNetworkOperator() {
- return getNetworkOperatorForPhone(getDefaultPhone());
+ return getNetworkOperatorForPhone(getPhoneId());
}
/**
@@ -1546,7 +1546,7 @@
* @see #createForPhoneAccountHandle(PhoneAccountHandle)
*/
public String getNetworkSpecifier() {
- return String.valueOf(mSubId);
+ return String.valueOf(getSubId());
}
/**
@@ -1565,7 +1565,7 @@
public PersistableBundle getCarrierConfig() {
CarrierConfigManager carrierConfigManager = mContext
.getSystemService(CarrierConfigManager.class);
- return carrierConfigManager.getConfigForSubId(mSubId);
+ return carrierConfigManager.getConfigForSubId(getSubId());
}
/**
@@ -1602,7 +1602,7 @@
* on a CDMA network).
*/
public String getNetworkCountryIso() {
- return getNetworkCountryIsoForPhone(getDefaultPhone());
+ return getNetworkCountryIsoForPhone(getPhoneId());
}
/**
@@ -1748,6 +1748,9 @@
* Returns a constant indicating the radio technology (network type)
* currently in use on the device for data transmission.
*
+ * If this object has been created with {@link #createForSubscriptionId}, applies to the given
+ * subId. Otherwise, applies to {@link SubscriptionManager#getDefaultDataSubscriptionId()}
+ *
* <p>
* Requires Permission:
* {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
@@ -1772,7 +1775,7 @@
* @see #NETWORK_TYPE_HSPAP
*/
public int getDataNetworkType() {
- return getDataNetworkType(getSubId());
+ return getDataNetworkType(getSubId(SubscriptionManager.getDefaultDataSubscriptionId()));
}
/**
@@ -1996,7 +1999,7 @@
* @return true if a ICC card is present
*/
public boolean hasIccCard() {
- return hasIccCard(getDefaultSim());
+ return hasIccCard(getSlotIndex());
}
/**
@@ -2037,7 +2040,7 @@
* @see #SIM_STATE_CARD_RESTRICTED
*/
public int getSimState() {
- int slotIndex = getDefaultSim();
+ int slotIndex = getSlotIndex();
// slotIndex may be invalid due to sim being absent. In that case query all slots to get
// sim state
if (slotIndex < 0) {
@@ -2166,7 +2169,7 @@
* @see #getSimState
*/
public String getSimOperatorName() {
- return getSimOperatorNameForPhone(getDefaultPhone());
+ return getSimOperatorNameForPhone(getPhoneId());
}
/**
@@ -2198,7 +2201,7 @@
* Returns the ISO country code equivalent for the SIM provider's country code.
*/
public String getSimCountryIso() {
- return getSimCountryIsoForPhone(getDefaultPhone());
+ return getSimCountryIsoForPhone(getPhoneId());
}
/**
@@ -2754,7 +2757,7 @@
ITelephony telephony = getITelephony();
if (telephony != null) {
return telephony
- .getVisualVoicemailPackageName(mContext.getOpPackageName(), mSubId);
+ .getVisualVoicemailPackageName(mContext.getOpPackageName(), getSubId());
}
} catch (RemoteException ex) {
} catch (NullPointerException ex) {
@@ -4057,35 +4060,67 @@
* subId is returned. Otherwise, the default subId will be returned.
*/
private int getSubId() {
- if (mSubId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) {
- return getDefaultSubscription();
+ if (SubscriptionManager.isUsableSubIdValue(mSubId)) {
+ return mSubId;
}
- return mSubId;
+ return SubscriptionManager.getDefaultSubscriptionId();
}
/**
- * Returns Default subscription.
+ * Return an appropriate subscription ID for any situation.
+ *
+ * If this object has been created with {@link #createForSubscriptionId}, then the provided
+ * subId is returned. Otherwise, the preferred subId which is based on caller's context is
+ * returned.
+ * {@see SubscriptionManager#getDefaultDataSubscriptionId()}
+ * {@see SubscriptionManager#getDefaultVoiceSubscriptionId()}
+ * {@see SubscriptionManager#getDefaultSmsSubscriptionId()}
*/
- private static int getDefaultSubscription() {
- return SubscriptionManager.getDefaultSubscriptionId();
+ private int getSubId(int preferredSubId) {
+ if (SubscriptionManager.isUsableSubIdValue(mSubId)) {
+ return mSubId;
+ }
+ return preferredSubId;
}
/**
- * Returns Default phone.
+ * Return an appropriate phone ID for any situation.
+ *
+ * If this object has been created with {@link #createForSubscriptionId}, then the phoneId
+ * associated with the provided subId is returned. Otherwise, the default phoneId associated
+ * with the default subId will be returned.
*/
- private static int getDefaultPhone() {
- return SubscriptionManager.getPhoneId(SubscriptionManager.getDefaultSubscriptionId());
+ private int getPhoneId() {
+ return SubscriptionManager.getPhoneId(getSubId());
}
/**
- * @return default SIM's slot index. If SIM is not inserted, return default SIM slot index.
+ * Return an appropriate phone ID for any situation.
+ *
+ * If this object has been created with {@link #createForSubscriptionId}, then the phoneId
+ * associated with the provided subId is returned. Otherwise, return the phoneId associated
+ * with the preferred subId based on caller's context.
+ * {@see SubscriptionManager#getDefaultDataSubscriptionId()}
+ * {@see SubscriptionManager#getDefaultVoiceSubscriptionId()}
+ * {@see SubscriptionManager#getDefaultSmsSubscriptionId()}
+ */
+ private int getPhoneId(int preferredSubId) {
+ return SubscriptionManager.getPhoneId(getSubId(preferredSubId));
+ }
+
+ /**
+ * Return an appropriate slot index for any situation.
+ *
+ * if this object has been created with {@link #createForSubscriptionId}, then the slot index
+ * associated with the provided subId is returned. Otherwise, return the slot index associated
+ * with the default subId.
+ * If SIM is not inserted, return default SIM slot index.
*
* {@hide}
*/
@VisibleForTesting
- public int getDefaultSim() {
- int slotIndex = SubscriptionManager.getSlotIndex(
- SubscriptionManager.getDefaultSubscriptionId());
+ public int getSlotIndex() {
+ int slotIndex = SubscriptionManager.getSlotIndex(getSubId());
if (slotIndex == SubscriptionManager.SIM_NOT_INSERTED) {
slotIndex = SubscriptionManager.DEFAULT_SIM_SLOT_INDEX;
}
@@ -4393,7 +4428,7 @@
* Returns null if the query fails.
*
*
- * <p>Requires that the caller has READ_PRIVILEGED_PHONE_STATE
+ * <p>Requires that the caller has READ_PHONE_STATE
*
* @return an array of forbidden PLMNs or null if not available
*/
@@ -4406,7 +4441,7 @@
* Returns null if the query fails.
*
*
- * <p>Requires that the calling app has READ_PRIVILEGED_PHONE_STATE
+ * <p>Requires that the calling app has READ_PHONE_STATE
*
* @param subId subscription ID used for authentication
* @param appType the icc application type, like {@link #APPTYPE_USIM}
@@ -4902,7 +4937,7 @@
/** @hide */
@SystemApi
public List<String> getCarrierPackageNamesForIntent(Intent intent) {
- return getCarrierPackageNamesForIntentAndPhone(intent, getDefaultPhone());
+ return getCarrierPackageNamesForIntentAndPhone(intent, getPhoneId());
}
/** @hide */
@@ -5164,7 +5199,7 @@
try {
ITelephony telephony = getITelephony();
if (telephony != null) {
- telephony.handleUssdRequest(mSubId, ussdRequest, wrappedCallback);
+ telephony.handleUssdRequest(getSubId(), ussdRequest, wrappedCallback);
}
} catch (RemoteException e) {
Log.e(TAG, "Error calling ITelephony#sendUSSDCode", e);
@@ -5184,7 +5219,8 @@
public boolean isConcurrentVoiceAndDataSupported() {
try {
ITelephony telephony = getITelephony();
- return (telephony == null ? false : telephony.isConcurrentVoiceAndDataAllowed(mSubId));
+ return (telephony == null ? false : telephony.isConcurrentVoiceAndDataAllowed(
+ getSubId()));
} catch (RemoteException e) {
Log.e(TAG, "Error calling ITelephony#isConcurrentVoiceAndDataAllowed", e);
}
@@ -5321,6 +5357,8 @@
/**
* Turns mobile data on or off.
+ * If this object has been created with {@link #createForSubscriptionId}, applies to the given
+ * subId. Otherwise, applies to {@link SubscriptionManager#getDefaultDataSubscriptionId()}
*
* <p>Requires Permission:
* {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} or that the
@@ -5331,7 +5369,7 @@
* @see #hasCarrierPrivileges
*/
public void setDataEnabled(boolean enable) {
- setDataEnabled(getSubId(), enable);
+ setDataEnabled(getSubId(SubscriptionManager.getDefaultDataSubscriptionId()), enable);
}
/** @hide */
@@ -5361,6 +5399,9 @@
/**
* Returns whether mobile data is enabled or not.
*
+ * If this object has been created with {@link #createForSubscriptionId}, applies to the given
+ * subId. Otherwise, applies to {@link SubscriptionManager#getDefaultDataSubscriptionId()}
+ *
* <p>Requires one of the following permissions:
* {@link android.Manifest.permission#ACCESS_NETWORK_STATE ACCESS_NETWORK_STATE},
* {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}, or that the
@@ -5376,7 +5417,7 @@
*/
@SuppressWarnings("deprecation")
public boolean isDataEnabled() {
- return getDataEnabled(getSubId());
+ return getDataEnabled(getSubId(SubscriptionManager.getDefaultDataSubscriptionId()));
}
/**
@@ -5626,7 +5667,7 @@
* @hide
*/
public void setSimOperatorNumeric(String numeric) {
- int phoneId = getDefaultPhone();
+ int phoneId = getPhoneId();
setSimOperatorNumericForPhone(phoneId, numeric);
}
@@ -5646,7 +5687,7 @@
* @hide
*/
public void setSimOperatorName(String name) {
- int phoneId = getDefaultPhone();
+ int phoneId = getPhoneId();
setSimOperatorNameForPhone(phoneId, name);
}
@@ -5666,7 +5707,7 @@
* @hide
*/
public void setSimCountryIso(String iso) {
- int phoneId = getDefaultPhone();
+ int phoneId = getPhoneId();
setSimCountryIsoForPhone(phoneId, iso);
}
@@ -5686,7 +5727,7 @@
* @hide
*/
public void setSimState(String state) {
- int phoneId = getDefaultPhone();
+ int phoneId = getPhoneId();
setSimStateForPhone(phoneId, state);
}
@@ -5711,7 +5752,7 @@
* @hide
**/
public void setSimPowerState(boolean powerUp) {
- setSimPowerStateForSlot(getDefaultSim(), powerUp);
+ setSimPowerStateForSlot(getSlotIndex(), powerUp);
}
/**
@@ -5745,7 +5786,7 @@
* @hide
*/
public void setBasebandVersion(String version) {
- int phoneId = getDefaultPhone();
+ int phoneId = getPhoneId();
setBasebandVersionForPhone(phoneId, version);
}
@@ -5772,7 +5813,7 @@
* @hide
*/
public void setPhoneType(int type) {
- int phoneId = getDefaultPhone();
+ int phoneId = getPhoneId();
setPhoneType(phoneId, type);
}
@@ -5800,7 +5841,7 @@
* @hide
*/
public String getOtaSpNumberSchema(String defaultValue) {
- int phoneId = getDefaultPhone();
+ int phoneId = getPhoneId();
return getOtaSpNumberSchemaForPhone(phoneId, defaultValue);
}
@@ -5831,7 +5872,7 @@
* @hide
*/
public boolean getSmsReceiveCapable(boolean defaultValue) {
- int phoneId = getDefaultPhone();
+ int phoneId = getPhoneId();
return getSmsReceiveCapableForPhone(phoneId, defaultValue);
}
@@ -5862,7 +5903,7 @@
* @hide
*/
public boolean getSmsSendCapable(boolean defaultValue) {
- int phoneId = getDefaultPhone();
+ int phoneId = getPhoneId();
return getSmsSendCapableForPhone(phoneId, defaultValue);
}
@@ -5890,7 +5931,7 @@
* @hide
*/
public void setNetworkOperatorName(String name) {
- int phoneId = getDefaultPhone();
+ int phoneId = getPhoneId();
setNetworkOperatorNameForPhone(phoneId, name);
}
@@ -5912,7 +5953,7 @@
* @hide
*/
public void setNetworkOperatorNumeric(String numeric) {
- int phoneId = getDefaultPhone();
+ int phoneId = getPhoneId();
setNetworkOperatorNumericForPhone(phoneId, numeric);
}
@@ -5932,7 +5973,7 @@
* @hide
*/
public void setNetworkRoaming(boolean isRoaming) {
- int phoneId = getDefaultPhone();
+ int phoneId = getPhoneId();
setNetworkRoamingForPhone(phoneId, isRoaming);
}
@@ -5956,7 +5997,7 @@
* @hide
*/
public void setNetworkCountryIso(String iso) {
- int phoneId = getDefaultPhone();
+ int phoneId = getPhoneId();
setNetworkCountryIsoForPhone(phoneId, iso);
}
@@ -5976,11 +6017,15 @@
/**
* Set the network type currently in use on the device for data transmission.
+ *
+ * If this object has been created with {@link #createForSubscriptionId}, applies to the
+ * phoneId associated with the given subId. Otherwise, applies to the phoneId associated with
+ * {@link SubscriptionManager#getDefaultDataSubscriptionId()}
* @param type the network type currently in use on the device for data transmission
* @hide
*/
public void setDataNetworkType(int type) {
- int phoneId = getDefaultPhone();
+ int phoneId = getPhoneId(SubscriptionManager.getDefaultDataSubscriptionId());
setDataNetworkTypeForPhone(phoneId, type);
}
@@ -6200,7 +6245,7 @@
* @hide
*/
public String getAidForAppType(int appType) {
- return getAidForAppType(getDefaultSubscription(), appType);
+ return getAidForAppType(getSubId(), appType);
}
/**
@@ -6234,7 +6279,7 @@
* @hide
*/
public String getEsn() {
- return getEsn(getDefaultSubscription());
+ return getEsn(getSubId());
}
/**
@@ -6267,7 +6312,7 @@
* @hide
*/
public String getCdmaPrlVersion() {
- return getCdmaPrlVersion(getDefaultSubscription());
+ return getCdmaPrlVersion(getSubId());
}
/**
diff --git a/test-runner/src/android/test/mock/MockContext.java b/test-runner/src/android/test/mock/MockContext.java
index a8eb986..b4e3a47 100644
--- a/test-runner/src/android/test/mock/MockContext.java
+++ b/test-runner/src/android/test/mock/MockContext.java
@@ -530,13 +530,6 @@
throw new UnsupportedOperationException();
}
- /** STOPSHIP remove when trial API is turned down */
- @Override
- public ComponentName startServiceInForeground(Intent service,
- int id, Notification notification) {
- throw new UnsupportedOperationException();
- }
-
@Override
public boolean stopService(Intent service) {
throw new UnsupportedOperationException();
@@ -554,13 +547,6 @@
throw new UnsupportedOperationException();
}
- /** @hide STOPSHIP removed when trial API is turned down */
- @Override
- public ComponentName startServiceInForegroundAsUser(Intent service,
- int id, Notification notification, UserHandle user) {
- throw new UnsupportedOperationException();
- }
-
/** @hide */
@Override
public boolean stopServiceAsUser(Intent service, UserHandle user) {
diff --git a/test-runner/src/android/test/mock/MockPackageManager.java b/test-runner/src/android/test/mock/MockPackageManager.java
index 20392e7..589dd0c 100644
--- a/test-runner/src/android/test/mock/MockPackageManager.java
+++ b/test-runner/src/android/test/mock/MockPackageManager.java
@@ -1134,4 +1134,11 @@
public ComponentName getInstantAppInstallerComponent() {
throw new UnsupportedOperationException();
}
+
+ /**
+ * @hide
+ */
+ public String getInstantAppAndroidId(String packageName, UserHandle user) {
+ throw new UnsupportedOperationException();
+ }
}
diff --git a/tests/net/java/android/net/NetworkCapabilitiesTest.java b/tests/net/java/android/net/NetworkCapabilitiesTest.java
new file mode 100644
index 0000000..e3b06c8
--- /dev/null
+++ b/tests/net/java/android/net/NetworkCapabilitiesTest.java
@@ -0,0 +1,117 @@
+/*
+ * 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.net;
+
+import static android.net.NetworkCapabilities.NET_CAPABILITY_CBS;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_EIMS;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
+import static android.net.NetworkCapabilities.RESTRICTED_CAPABILITIES;
+import static android.net.NetworkCapabilities.UNRESTRICTED_CAPABILITIES;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+
+import android.net.NetworkCapabilities;
+import android.support.test.runner.AndroidJUnit4;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class NetworkCapabilitiesTest {
+ @Test
+ public void testMaybeMarkCapabilitiesRestricted() {
+ // verify EIMS is restricted
+ assertEquals((1 << NET_CAPABILITY_EIMS) & RESTRICTED_CAPABILITIES,
+ (1 << NET_CAPABILITY_EIMS));
+
+ // verify CBS is also restricted
+ assertEquals((1 << NET_CAPABILITY_CBS) & RESTRICTED_CAPABILITIES,
+ (1 << NET_CAPABILITY_CBS));
+
+ // verify default is not restricted
+ assertEquals((1 << NET_CAPABILITY_INTERNET) & RESTRICTED_CAPABILITIES, 0);
+
+ // just to see
+ assertEquals(RESTRICTED_CAPABILITIES & UNRESTRICTED_CAPABILITIES, 0);
+
+ // check that internet does not get restricted
+ NetworkCapabilities netCap = new NetworkCapabilities();
+ netCap.addCapability(NET_CAPABILITY_INTERNET);
+ netCap.maybeMarkCapabilitiesRestricted();
+ assertTrue(netCap.hasCapability(NET_CAPABILITY_NOT_RESTRICTED));
+
+ // metered-ness shouldn't matter
+ netCap = new NetworkCapabilities();
+ netCap.addCapability(NET_CAPABILITY_INTERNET);
+ netCap.addCapability(NET_CAPABILITY_NOT_METERED);
+ netCap.maybeMarkCapabilitiesRestricted();
+ assertTrue(netCap.hasCapability(NET_CAPABILITY_NOT_RESTRICTED));
+ netCap = new NetworkCapabilities();
+ netCap.addCapability(NET_CAPABILITY_INTERNET);
+ netCap.removeCapability(NET_CAPABILITY_NOT_METERED);
+ netCap.maybeMarkCapabilitiesRestricted();
+ assertTrue(netCap.hasCapability(NET_CAPABILITY_NOT_RESTRICTED));
+
+ // add EIMS - bundled with unrestricted means it's unrestricted
+ netCap = new NetworkCapabilities();
+ netCap.addCapability(NET_CAPABILITY_INTERNET);
+ netCap.addCapability(NET_CAPABILITY_EIMS);
+ netCap.addCapability(NET_CAPABILITY_NOT_METERED);
+ netCap.maybeMarkCapabilitiesRestricted();
+ assertTrue(netCap.hasCapability(NET_CAPABILITY_NOT_RESTRICTED));
+ netCap = new NetworkCapabilities();
+ netCap.addCapability(NET_CAPABILITY_INTERNET);
+ netCap.addCapability(NET_CAPABILITY_EIMS);
+ netCap.removeCapability(NET_CAPABILITY_NOT_METERED);
+ netCap.maybeMarkCapabilitiesRestricted();
+ assertTrue(netCap.hasCapability(NET_CAPABILITY_NOT_RESTRICTED));
+
+ // just a restricted cap should be restricted regardless of meteredness
+ netCap = new NetworkCapabilities();
+ netCap.addCapability(NET_CAPABILITY_EIMS);
+ netCap.addCapability(NET_CAPABILITY_NOT_METERED);
+ netCap.maybeMarkCapabilitiesRestricted();
+ assertFalse(netCap.hasCapability(NET_CAPABILITY_NOT_RESTRICTED));
+ netCap = new NetworkCapabilities();
+ netCap.addCapability(NET_CAPABILITY_EIMS);
+ netCap.removeCapability(NET_CAPABILITY_NOT_METERED);
+ netCap.maybeMarkCapabilitiesRestricted();
+ assertFalse(netCap.hasCapability(NET_CAPABILITY_NOT_RESTRICTED));
+
+ // try 2 restricted caps
+ netCap = new NetworkCapabilities();
+ netCap.addCapability(NET_CAPABILITY_CBS);
+ netCap.addCapability(NET_CAPABILITY_EIMS);
+ netCap.addCapability(NET_CAPABILITY_NOT_METERED);
+ netCap.maybeMarkCapabilitiesRestricted();
+ assertFalse(netCap.hasCapability(NET_CAPABILITY_NOT_RESTRICTED));
+ netCap = new NetworkCapabilities();
+ netCap.addCapability(NET_CAPABILITY_CBS);
+ netCap.addCapability(NET_CAPABILITY_EIMS);
+ netCap.removeCapability(NET_CAPABILITY_NOT_METERED);
+ netCap.maybeMarkCapabilitiesRestricted();
+ assertFalse(netCap.hasCapability(NET_CAPABILITY_NOT_RESTRICTED));
+ }
+
+}
diff --git a/tools/aapt/Resource.cpp b/tools/aapt/Resource.cpp
index 2bf5206..bd2b2a36 100644
--- a/tools/aapt/Resource.cpp
+++ b/tools/aapt/Resource.cpp
@@ -782,7 +782,79 @@
}
}
-status_t massageManifest(Bundle* bundle, sp<XMLNode> root)
+static sp<ResourceTable::ConfigList> findEntry(const String16& packageStr, const String16& typeStr,
+ const String16& nameStr, ResourceTable* table) {
+ sp<ResourceTable::Package> pkg = table->getPackage(packageStr);
+ if (pkg != NULL) {
+ sp<ResourceTable::Type> type = pkg->getTypes().valueFor(typeStr);
+ if (type != NULL) {
+ return type->getConfigs().valueFor(nameStr);
+ }
+ }
+ return NULL;
+}
+
+static uint16_t getMaxSdkVersion(const sp<ResourceTable::ConfigList>& configList) {
+ const DefaultKeyedVector<ConfigDescription, sp<ResourceTable::Entry>>& entries =
+ configList->getEntries();
+ uint16_t maxSdkVersion = 0u;
+ for (size_t i = 0; i < entries.size(); i++) {
+ maxSdkVersion = std::max(maxSdkVersion, entries.keyAt(i).sdkVersion);
+ }
+ return maxSdkVersion;
+}
+
+static void massageRoundIconSupport(const String16& iconRef, const String16& roundIconRef,
+ ResourceTable* table) {
+ bool publicOnly = false;
+ const char* err;
+
+ String16 iconPackage, iconType, iconName;
+ if (!ResTable::expandResourceRef(iconRef.string(), iconRef.size(), &iconPackage, &iconType,
+ &iconName, NULL, &table->getAssetsPackage(), &err,
+ &publicOnly)) {
+ // Errors will be raised in later XML compilation.
+ return;
+ }
+
+ sp<ResourceTable::ConfigList> iconEntry = findEntry(iconPackage, iconType, iconName, table);
+ if (iconEntry == NULL || getMaxSdkVersion(iconEntry) < SDK_O) {
+ // The icon is not adaptive, so nothing to massage.
+ return;
+ }
+
+ String16 roundIconPackage, roundIconType, roundIconName;
+ if (!ResTable::expandResourceRef(roundIconRef.string(), roundIconRef.size(), &roundIconPackage,
+ &roundIconType, &roundIconName, NULL, &table->getAssetsPackage(),
+ &err, &publicOnly)) {
+ // Errors will be raised in later XML compilation.
+ return;
+ }
+
+ sp<ResourceTable::ConfigList> roundIconEntry = findEntry(roundIconPackage, roundIconType,
+ roundIconName, table);
+ if (roundIconEntry == NULL || getMaxSdkVersion(roundIconEntry) >= SDK_O) {
+ // The developer explicitly used a v26 compatible drawable as the roundIcon, meaning we should
+ // not generate an alias to the icon drawable.
+ return;
+ }
+
+ String16 aliasValue = String16(String8::format("@%s:%s/%s", String8(iconPackage).string(),
+ String8(iconType).string(),
+ String8(iconName).string()));
+
+ // Add an equivalent v26 entry to the roundIcon for each v26 variant of the regular icon.
+ const DefaultKeyedVector<ConfigDescription, sp<ResourceTable::Entry>>& configList =
+ iconEntry->getEntries();
+ for (size_t i = 0; i < configList.size(); i++) {
+ if (configList.keyAt(i).sdkVersion >= SDK_O) {
+ table->addEntry(SourcePos(), roundIconPackage, roundIconType, roundIconName, aliasValue,
+ NULL, &configList.keyAt(i));
+ }
+ }
+}
+
+status_t massageManifest(Bundle* bundle, ResourceTable* table, sp<XMLNode> root)
{
root = root->searchElement(String16(), String16("manifest"));
if (root == NULL) {
@@ -916,7 +988,7 @@
String8 tag(child->getElementName());
if (tag == "instrumentation") {
XMLNode::attribute_entry* attr = child->editAttribute(
- String16("http://schemas.android.com/apk/res/android"), String16("targetPackage"));
+ String16(RESOURCES_ANDROID_NAMESPACE), String16("targetPackage"));
if (attr != NULL) {
attr->string.setTo(String16(instrumentationPackageNameOverride));
}
@@ -924,6 +996,19 @@
}
}
+ sp<XMLNode> application = root->getChildElement(String16(), String16("application"));
+ if (application != NULL) {
+ XMLNode::attribute_entry* icon_attr = application->editAttribute(
+ String16(RESOURCES_ANDROID_NAMESPACE), String16("icon"));
+ if (icon_attr != NULL) {
+ XMLNode::attribute_entry* round_icon_attr = application->editAttribute(
+ String16(RESOURCES_ANDROID_NAMESPACE), String16("roundIcon"));
+ if (round_icon_attr != NULL) {
+ massageRoundIconSupport(icon_attr->string, round_icon_attr->string, table);
+ }
+ }
+ }
+
// Generate split name if feature is present.
const XMLNode::attribute_entry* attr = root->getAttribute(String16(), String16("featureName"));
if (attr != NULL) {
@@ -1638,7 +1723,7 @@
if (manifestTree == NULL) {
return UNKNOWN_ERROR;
}
- err = massageManifest(bundle, manifestTree);
+ err = massageManifest(bundle, &table, manifestTree);
if (err < NO_ERROR) {
return err;
}
diff --git a/tools/aapt/ResourceTable.cpp b/tools/aapt/ResourceTable.cpp
index 221f3c2..52b93a9 100644
--- a/tools/aapt/ResourceTable.cpp
+++ b/tools/aapt/ResourceTable.cpp
@@ -4832,8 +4832,7 @@
item.resPath = resPath;
item.file = newFile;
item.xmlRoot = root->clone();
- item.needsCompiling = false; // This step occurs after we parse/assign, so we don't need
- // to do it again.
+ item.needsCompiling = true;
mWorkQueue.push(item);
// Now mark the old entry as deleted.
diff --git a/tools/aapt/ResourceTable.h b/tools/aapt/ResourceTable.h
index aff22d4..b340fc5 100644
--- a/tools/aapt/ResourceTable.h
+++ b/tools/aapt/ResourceTable.h
@@ -590,9 +590,10 @@
const String16& comment,
bool appendComment);
+ sp<Package> getPackage(const String16& package);
+
private:
void writePublicDefinitions(const String16& package, FILE* fp, bool pub);
- sp<Package> getPackage(const String16& package);
sp<Type> getType(const String16& package,
const String16& type,
const SourcePos& pos,
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
index 328fc0a..1e77ac1 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
@@ -1865,13 +1865,6 @@
}
@Override
- public ComponentName startServiceInForeground(Intent service,
- int id, Notification notification) {
- // pass
- return null;
- }
-
- @Override
public boolean stopService(Intent arg0) {
// pass
return false;
@@ -1884,13 +1877,6 @@
}
@Override
- public ComponentName startServiceInForegroundAsUser(Intent service,
- int id, Notification notification, UserHandle user) {
- // pass
- return null;
- }
-
- @Override
public boolean stopServiceAsUser(Intent arg0, UserHandle arg1) {
// pass
return false;
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePackageManager.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePackageManager.java
index ad26bc8..93319a3 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePackageManager.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePackageManager.java
@@ -928,4 +928,9 @@
public ComponentName getInstantAppInstallerComponent() {
return null;
}
+
+ @Override
+ public String getInstantAppAndroidId(String packageName, UserHandle user) {
+ return null;
+ }
}
diff --git a/wifi/java/android/net/wifi/aware/DiscoverySession.java b/wifi/java/android/net/wifi/aware/DiscoverySession.java
index 82b3792..bf5c42b 100644
--- a/wifi/java/android/net/wifi/aware/DiscoverySession.java
+++ b/wifi/java/android/net/wifi/aware/DiscoverySession.java
@@ -19,6 +19,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
+import android.net.NetworkSpecifier;
import android.net.wifi.RttManager;
import android.util.Log;
@@ -250,8 +251,8 @@
}
/**
- * Create a {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(String)} for an
- * unencrypted WiFi Aware connection (link) to the specified peer. The
+ * Create a {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(NetworkSpecifier)} for
+ * an unencrypted WiFi Aware connection (link) to the specified peer. The
* {@link android.net.NetworkRequest.Builder#addTransportType(int)} should be set to
* {@link android.net.NetworkCapabilities#TRANSPORT_WIFI_AWARE}.
* <p>
@@ -276,13 +277,13 @@
* request from only that peer. A RESPONDER may specify a {@code null} -
* indicating that it will accept connection requests from any device.
*
- * @return A string to be used to construct
- * {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(String)} to pass to
+ * @return A {@link NetworkSpecifier} to be used to construct
+ * {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(NetworkSpecifier)} to pass to
* {@link android.net.ConnectivityManager#requestNetwork(android.net.NetworkRequest,
* android.net.ConnectivityManager.NetworkCallback)}
* [or other varieties of that API].
*/
- public String createNetworkSpecifierOpen(@Nullable PeerHandle peerHandle) {
+ public NetworkSpecifier createNetworkSpecifierOpen(@Nullable PeerHandle peerHandle) {
if (mTerminated) {
Log.w(TAG, "createNetworkSpecifierOpen: called on terminated session");
return null;
@@ -302,8 +303,8 @@
}
/**
- * Create a {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(String)} for an
- * encrypted WiFi Aware connection (link) to the specified peer. The
+ * Create a {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(NetworkSpecifier)} for
+ * an encrypted WiFi Aware connection (link) to the specified peer. The
* {@link android.net.NetworkRequest.Builder#addTransportType(int)} should be set to
* {@link android.net.NetworkCapabilities#TRANSPORT_WIFI_AWARE}.
* <p>
@@ -329,14 +330,14 @@
* {@link #createNetworkSpecifierOpen(PeerHandle)} API to
* specify an open (unencrypted) link.
*
- * @return A string to be used to construct
- * {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(String)} to pass to
+ * @return A {@link NetworkSpecifier} to be used to construct
+ * {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(NetworkSpecifier)} to pass to
* {@link android.net.ConnectivityManager#requestNetwork(android.net.NetworkRequest,
* android.net.ConnectivityManager.NetworkCallback)}
* [or other varieties of that API].
*/
- public String createNetworkSpecifierPassphrase(@Nullable PeerHandle peerHandle,
- @NonNull String passphrase) {
+ public NetworkSpecifier createNetworkSpecifierPassphrase(
+ @Nullable PeerHandle peerHandle, @NonNull String passphrase) {
if (passphrase == null || passphrase.length() == 0) {
throw new IllegalArgumentException("Passphrase must not be null or empty");
}
@@ -361,8 +362,8 @@
}
/**
- * Create a {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(String)} for an
- * encrypted WiFi Aware connection (link) to the specified peer. The
+ * Create a {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(NetworkSpecifier)} for
+ * an encrypted WiFi Aware connection (link) to the specified peer. The
* {@link android.net.NetworkRequest.Builder#addTransportType(int)} should be set to
* {@link android.net.NetworkCapabilities#TRANSPORT_WIFI_AWARE}.
* <p>
@@ -389,8 +390,8 @@
* Passphrase or {@link #createNetworkSpecifierOpen(PeerHandle)} to specify an
* open (unencrypted) link.
*
- * @return A string to be used to construct
- * {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(String)} to pass to
+ * @return A {@link NetworkSpecifier} to be used to construct
+ * {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(NetworkSpecifier)} to pass to
* {@link android.net.ConnectivityManager#requestNetwork(android.net.NetworkRequest,
* android.net.ConnectivityManager.NetworkCallback)}
* [or other varieties of that API].
@@ -398,7 +399,7 @@
* @hide
*/
@SystemApi
- public String createNetworkSpecifierPmk(@Nullable PeerHandle peerHandle,
+ public NetworkSpecifier createNetworkSpecifierPmk(@Nullable PeerHandle peerHandle,
@NonNull byte[] pmk) {
if (pmk == null || pmk.length == 0) {
throw new IllegalArgumentException("PMK must not be null or empty");
diff --git a/wifi/java/android/net/wifi/aware/WifiAwareManager.java b/wifi/java/android/net/wifi/aware/WifiAwareManager.java
index 4d3957a..3fcbd4b 100644
--- a/wifi/java/android/net/wifi/aware/WifiAwareManager.java
+++ b/wifi/java/android/net/wifi/aware/WifiAwareManager.java
@@ -24,6 +24,7 @@
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkRequest;
+import android.net.NetworkSpecifier;
import android.net.wifi.RttManager;
import android.os.Binder;
import android.os.Bundle;
@@ -31,7 +32,6 @@
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
-import android.util.Base64;
import android.util.Log;
import android.util.SparseArray;
@@ -39,9 +39,6 @@
import libcore.util.HexEncoding;
-import org.json.JSONException;
-import org.json.JSONObject;
-
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.ref.WeakReference;
@@ -129,65 +126,6 @@
private static final boolean VDBG = false; // STOPSHIP if true
/**
- * Keys used to generate a Network Specifier for the Aware network request. The network
- * specifier is formatted as a JSON string.
- */
-
- /**
- * TYPE: in band, specific peer: role, client_id, session_id, peer_id, pmk/passphrase optional
- * @hide
- */
- public static final int NETWORK_SPECIFIER_TYPE_IB = 0;
-
- /**
- * TYPE: in band, any peer: role, client_id, session_id, pmk/passphrase optional
- * [only permitted for RESPONDER]
- * @hide
- */
- public static final int NETWORK_SPECIFIER_TYPE_IB_ANY_PEER = 1;
-
- /**
- * TYPE: out-of-band: role, client_id, peer_mac, pmk/passphrase optional
- * @hide
- */
- public static final int NETWORK_SPECIFIER_TYPE_OOB = 2;
-
- /**
- * TYPE: out-of-band, any peer: role, client_id, pmk/passphrase optional
- * [only permitted for RESPONDER]
- * @hide
- */
- public static final int NETWORK_SPECIFIER_TYPE_OOB_ANY_PEER = 3;
-
-
- /** @hide */
- public static final int NETWORK_SPECIFIER_TYPE_MAX_VALID = NETWORK_SPECIFIER_TYPE_OOB_ANY_PEER;
-
- /** @hide */
- public static final String NETWORK_SPECIFIER_KEY_TYPE = "type";
-
- /** @hide */
- public static final String NETWORK_SPECIFIER_KEY_ROLE = "role";
-
- /** @hide */
- public static final String NETWORK_SPECIFIER_KEY_CLIENT_ID = "client_id";
-
- /** @hide */
- public static final String NETWORK_SPECIFIER_KEY_SESSION_ID = "session_id";
-
- /** @hide */
- public static final String NETWORK_SPECIFIER_KEY_PEER_ID = "peer_id";
-
- /** @hide */
- public static final String NETWORK_SPECIFIER_KEY_PEER_MAC = "peer_mac";
-
- /** @hide */
- public static final String NETWORK_SPECIFIER_KEY_PMK = "pmk";
-
- /** @hide */
- public static final String NETWORK_SPECIFIER_KEY_PASSPHRASE = "passphrase";
-
- /**
* Broadcast intent action to indicate that the state of Wi-Fi Aware availability has changed.
* Use the {@link #isAvailable()} to query the current status.
* This broadcast is <b>not</b> sticky, use the {@link #isAvailable()} API after registering
@@ -483,7 +421,7 @@
}
/** @hide */
- public String createNetworkSpecifier(int clientId, int role, int sessionId,
+ public NetworkSpecifier createNetworkSpecifier(int clientId, int role, int sessionId,
PeerHandle peerHandle, @Nullable byte[] pmk, @Nullable String passphrase) {
if (VDBG) {
Log.v(TAG, "createNetworkSpecifier: role=" + role + ", sessionId=" + sessionId
@@ -492,9 +430,6 @@
+ ", passphrase=" + ((passphrase == null) ? "null" : "non-null"));
}
- int type = (peerHandle == null) ? NETWORK_SPECIFIER_TYPE_IB_ANY_PEER
- : NETWORK_SPECIFIER_TYPE_IB;
-
if (role != WIFI_AWARE_DATA_PATH_ROLE_INITIATOR
&& role != WIFI_AWARE_DATA_PATH_ROLE_RESPONDER) {
throw new IllegalArgumentException(
@@ -509,35 +444,20 @@
}
}
- JSONObject json;
- try {
- json = new JSONObject();
- json.put(NETWORK_SPECIFIER_KEY_TYPE, type);
- json.put(NETWORK_SPECIFIER_KEY_ROLE, role);
- json.put(NETWORK_SPECIFIER_KEY_CLIENT_ID, clientId);
- json.put(NETWORK_SPECIFIER_KEY_SESSION_ID, sessionId);
- if (peerHandle != null) {
- json.put(NETWORK_SPECIFIER_KEY_PEER_ID, peerHandle.peerId);
- }
- if (pmk == null) {
- pmk = new byte[0];
- }
- json.put(NETWORK_SPECIFIER_KEY_PMK,
- Base64.encodeToString(pmk, 0, pmk.length, Base64.DEFAULT));
- if (passphrase == null) {
- passphrase = new String();
- }
- json.put(NETWORK_SPECIFIER_KEY_PASSPHRASE, passphrase);
-
- } catch (JSONException e) {
- return "";
- }
-
- return json.toString();
+ return new WifiAwareNetworkSpecifier(
+ (peerHandle == null) ? WifiAwareNetworkSpecifier.NETWORK_SPECIFIER_TYPE_IB_ANY_PEER
+ : WifiAwareNetworkSpecifier.NETWORK_SPECIFIER_TYPE_IB,
+ role,
+ clientId,
+ sessionId,
+ peerHandle != null ? peerHandle.peerId : 0, // 0 is an invalid peer ID
+ null, // peerMac (not used in this method)
+ pmk,
+ passphrase);
}
/** @hide */
- public String createNetworkSpecifier(int clientId, @DataPathRole int role,
+ public NetworkSpecifier createNetworkSpecifier(int clientId, @DataPathRole int role,
@Nullable byte[] peer, @Nullable byte[] pmk, @Nullable String passphrase) {
if (VDBG) {
Log.v(TAG, "createNetworkSpecifier: role=" + role
@@ -545,9 +465,6 @@
+ ", passphrase=" + ((passphrase == null) ? "null" : "non-null"));
}
- int type = (peer == null) ?
- NETWORK_SPECIFIER_TYPE_OOB_ANY_PEER : NETWORK_SPECIFIER_TYPE_OOB;
-
if (role != WIFI_AWARE_DATA_PATH_ROLE_INITIATOR
&& role != WIFI_AWARE_DATA_PATH_ROLE_RESPONDER) {
throw new IllegalArgumentException(
@@ -564,29 +481,16 @@
throw new IllegalArgumentException("createNetworkSpecifier: Invalid peer MAC address");
}
- JSONObject json;
- try {
- json = new JSONObject();
- json.put(NETWORK_SPECIFIER_KEY_TYPE, type);
- json.put(NETWORK_SPECIFIER_KEY_ROLE, role);
- json.put(NETWORK_SPECIFIER_KEY_CLIENT_ID, clientId);
- if (peer != null) {
- json.put(NETWORK_SPECIFIER_KEY_PEER_MAC, new String(HexEncoding.encode(peer)));
- }
- if (pmk == null) {
- pmk = new byte[0];
- }
- json.put(NETWORK_SPECIFIER_KEY_PMK,
- Base64.encodeToString(pmk, 0, pmk.length, Base64.DEFAULT));
- if (passphrase == null) {
- passphrase = new String();
- }
- json.put(NETWORK_SPECIFIER_KEY_PASSPHRASE, passphrase);
- } catch (JSONException e) {
- return "";
- }
-
- return json.toString();
+ return new WifiAwareNetworkSpecifier(
+ (peer == null) ? WifiAwareNetworkSpecifier.NETWORK_SPECIFIER_TYPE_OOB_ANY_PEER
+ : WifiAwareNetworkSpecifier.NETWORK_SPECIFIER_TYPE_OOB,
+ role,
+ clientId,
+ 0, // 0 is an invalid session ID
+ 0, // 0 is an invalid peer ID
+ peer,
+ pmk,
+ passphrase);
}
private static class WifiAwareEventCallbackProxy extends IWifiAwareEventCallback.Stub {
diff --git a/wifi/java/android/net/wifi/aware/WifiAwareNetworkSpecifier.java b/wifi/java/android/net/wifi/aware/WifiAwareNetworkSpecifier.java
new file mode 100644
index 0000000..5993480
--- /dev/null
+++ b/wifi/java/android/net/wifi/aware/WifiAwareNetworkSpecifier.java
@@ -0,0 +1,234 @@
+/*
+ * 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.net.wifi.aware;
+
+import android.net.NetworkSpecifier;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Arrays;
+import java.util.Objects;
+
+/**
+ * Network specifier object used to request a Wi-Fi Aware network. Apps do not create these objects
+ * directly but obtain them using
+ * {@link WifiAwareSession#createNetworkSpecifierOpen(int, byte[])} or
+ * {@link DiscoverySession#createNetworkSpecifierOpen(PeerHandle)} or their secure (Passphrase)
+ * versions.
+ *
+ * @hide
+ */
+public final class WifiAwareNetworkSpecifier extends NetworkSpecifier implements Parcelable {
+ /**
+ * TYPE: in band, specific peer: role, client_id, session_id, peer_id, pmk/passphrase optional
+ * @hide
+ */
+ public static final int NETWORK_SPECIFIER_TYPE_IB = 0;
+
+ /**
+ * TYPE: in band, any peer: role, client_id, session_id, pmk/passphrase optional
+ * [only permitted for RESPONDER]
+ * @hide
+ */
+ public static final int NETWORK_SPECIFIER_TYPE_IB_ANY_PEER = 1;
+
+ /**
+ * TYPE: out-of-band: role, client_id, peer_mac, pmk/passphrase optional
+ * @hide
+ */
+ public static final int NETWORK_SPECIFIER_TYPE_OOB = 2;
+
+ /**
+ * TYPE: out-of-band, any peer: role, client_id, pmk/passphrase optional
+ * [only permitted for RESPONDER]
+ * @hide
+ */
+ public static final int NETWORK_SPECIFIER_TYPE_OOB_ANY_PEER = 3;
+
+ /** @hide */
+ public static final int NETWORK_SPECIFIER_TYPE_MAX_VALID = NETWORK_SPECIFIER_TYPE_OOB_ANY_PEER;
+
+ /**
+ * One of the NETWORK_SPECIFIER_TYPE_* constants. The type of the network specifier object.
+ * @hide
+ */
+ public final int type;
+
+ /**
+ * The role of the device: WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_INITIATOR or
+ * WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_RESPONDER.
+ * @hide
+ */
+ public final int role;
+
+ /**
+ * The client ID of the device.
+ * @hide
+ */
+ public final int clientId;
+
+ /**
+ * The session ID in which context to request a data-path. Only relevant for IB requests.
+ * @hide
+ */
+ public final int sessionId;
+
+ /**
+ * The peer ID of the device which the data-path should be connected to. Only relevant for
+ * IB requests (i.e. not IB_ANY_PEER or OOB*).
+ * @hide
+ */
+ public final int peerId;
+
+ /**
+ * The peer MAC address of the device which the data-path should be connected to. Only relevant
+ * for OB requests (i.e. not OOB_ANY_PEER or IB*).
+ * @hide
+ */
+ public final byte[] peerMac;
+
+ /**
+ * The PMK of the requested data-path. Can be null. Only one or none of pmk or passphrase should
+ * be specified.
+ * @hide
+ */
+ public final byte[] pmk;
+
+ /**
+ * The Passphrase of the requested data-path. Can be null. Only one or none of the pmk or
+ * passphrase should be specified.
+ * @hide
+ */
+ public final String passphrase;
+
+ /** @hide */
+ public WifiAwareNetworkSpecifier(int type, int role, int clientId, int sessionId, int peerId,
+ byte[] peerMac, byte[] pmk, String passphrase) {
+ this.type = type;
+ this.role = role;
+ this.clientId = clientId;
+ this.sessionId = sessionId;
+ this.peerId = peerId;
+ this.peerMac = peerMac;
+ this.pmk = pmk;
+ this.passphrase = passphrase;
+ }
+
+ public static final Creator<WifiAwareNetworkSpecifier> CREATOR =
+ new Creator<WifiAwareNetworkSpecifier>() {
+ @Override
+ public WifiAwareNetworkSpecifier createFromParcel(Parcel in) {
+ return new WifiAwareNetworkSpecifier(
+ in.readInt(), // type
+ in.readInt(), // role
+ in.readInt(), // clientId
+ in.readInt(), // sessionId
+ in.readInt(), // peerId
+ in.createByteArray(), // peerMac
+ in.createByteArray(), // pmk
+ in.readString()); // passphrase
+ }
+
+ @Override
+ public WifiAwareNetworkSpecifier[] newArray(int size) {
+ return new WifiAwareNetworkSpecifier[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(type);
+ dest.writeInt(role);
+ dest.writeInt(clientId);
+ dest.writeInt(sessionId);
+ dest.writeInt(peerId);
+ dest.writeByteArray(peerMac);
+ dest.writeByteArray(pmk);
+ dest.writeString(passphrase);
+ }
+
+ /** @hide */
+ @Override
+ public boolean satisfiedBy(NetworkSpecifier other) {
+ // MatchAllNetworkSpecifier is taken care in NetworkCapabilities#satisfiedBySpecifier.
+ return equals(other);
+ }
+
+ /** @hide */
+ @Override
+ public int hashCode() {
+ int result = 17;
+
+ result = 31 * result + type;
+ result = 31 * result + role;
+ result = 31 * result + clientId;
+ result = 31 * result + sessionId;
+ result = 31 * result + peerId;
+ result = 31 * result + Arrays.hashCode(peerMac);
+ result = 31 * result + Arrays.hashCode(pmk);
+ result = 31 * result + Objects.hashCode(passphrase);
+
+ return result;
+ }
+
+ /** @hide */
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+
+ if (!(obj instanceof WifiAwareNetworkSpecifier)) {
+ return false;
+ }
+
+ WifiAwareNetworkSpecifier lhs = (WifiAwareNetworkSpecifier) obj;
+
+ return type == lhs.type
+ && role == lhs.role
+ && clientId == lhs.clientId
+ && sessionId == lhs.sessionId
+ && peerId == lhs.peerId
+ && Arrays.equals(peerMac, lhs.peerMac)
+ && Arrays.equals(pmk, lhs.pmk)
+ && Objects.equals(passphrase, lhs.passphrase);
+ }
+
+ /** @hide */
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder("WifiAwareNetworkSpecifier [");
+ sb.append("type=").append(type)
+ .append(", role=").append(role)
+ .append(", clientId=").append(clientId)
+ .append(", sessionId=").append(sessionId)
+ .append(", peerId=").append(peerId)
+ // masking potential PII (although low impact information)
+ .append(", peerMac=").append((peerMac == null) ? "<null>" : "<non-null>")
+ // masking PII
+ .append(", pmk=").append((pmk == null) ? "<null>" : "<non-null>")
+ // masking PII
+ .append(", passphrase=").append((passphrase == null) ? "<null>" : "<non-null>")
+ .append("]");
+ return sb.toString();
+ }
+}
diff --git a/wifi/java/android/net/wifi/aware/WifiAwareSession.java b/wifi/java/android/net/wifi/aware/WifiAwareSession.java
index 895defb..ac3a6bb 100644
--- a/wifi/java/android/net/wifi/aware/WifiAwareSession.java
+++ b/wifi/java/android/net/wifi/aware/WifiAwareSession.java
@@ -19,6 +19,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
+import android.net.NetworkSpecifier;
import android.os.Binder;
import android.os.Handler;
import android.os.Looper;
@@ -184,8 +185,8 @@
}
/**
- * Create a {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(String)} for an
- * unencrypted WiFi Aware connection (link) to the specified peer. The
+ * Create a {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(NetworkSpecifier)} for
+ * an unencrypted WiFi Aware connection (link) to the specified peer. The
* {@link android.net.NetworkRequest.Builder#addTransportType(int)} should be set to
* {@link android.net.NetworkCapabilities#TRANSPORT_WIFI_AWARE}.
* <p>
@@ -205,29 +206,29 @@
* peer. A RESPONDER may specify a {@code null} - indicating that it will accept
* connection requests from any device.
*
- * @return A string to be used to construct
- * {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(String)} to pass to
+ * @return A {@link NetworkSpecifier} to be used to construct
+ * {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(NetworkSpecifier)} to pass to
* {@link android.net.ConnectivityManager#requestNetwork(android.net.NetworkRequest,
* android.net.ConnectivityManager.NetworkCallback)}
* [or other varieties of that API].
*/
- public String createNetworkSpecifierOpen(@WifiAwareManager.DataPathRole int role,
- @Nullable byte[] peer) {
+ public NetworkSpecifier createNetworkSpecifierOpen(
+ @WifiAwareManager.DataPathRole int role, @Nullable byte[] peer) {
WifiAwareManager mgr = mMgr.get();
if (mgr == null) {
Log.e(TAG, "createNetworkSpecifierOpen: called post GC on WifiAwareManager");
- return "";
+ return null;
}
if (mTerminated) {
Log.e(TAG, "createNetworkSpecifierOpen: called after termination");
- return "";
+ return null;
}
return mgr.createNetworkSpecifier(mClientId, role, peer, null, null);
}
/**
- * Create a {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(String)} for an
- * encrypted WiFi Aware connection (link) to the specified peer. The
+ * Create a {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(NetworkSpecifier)} for
+ * an encrypted WiFi Aware connection (link) to the specified peer. The
* {@link android.net.NetworkRequest.Builder#addTransportType(int)} should be set to
* {@link android.net.NetworkCapabilities#TRANSPORT_WIFI_AWARE}.
* <p>
@@ -247,22 +248,23 @@
* the passphrase. Use {@link #createNetworkSpecifierOpen(int, byte[])} to
* specify an open (unencrypted) link.
*
- * @return A string to be used to construct
- * {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(String)} to pass to
+ * @return A {@link NetworkSpecifier} to be used to construct
+ * {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(NetworkSpecifier)} to pass to
* {@link android.net.ConnectivityManager#requestNetwork(android.net.NetworkRequest,
* android.net.ConnectivityManager.NetworkCallback)}
* [or other varieties of that API].
*/
- public String createNetworkSpecifierPassphrase(@WifiAwareManager.DataPathRole int role,
- @Nullable byte[] peer, @NonNull String passphrase) {
+ public NetworkSpecifier createNetworkSpecifierPassphrase(
+ @WifiAwareManager.DataPathRole int role, @Nullable byte[] peer,
+ @NonNull String passphrase) {
WifiAwareManager mgr = mMgr.get();
if (mgr == null) {
Log.e(TAG, "createNetworkSpecifierPassphrase: called post GC on WifiAwareManager");
- return "";
+ return null;
}
if (mTerminated) {
Log.e(TAG, "createNetworkSpecifierPassphrase: called after termination");
- return "";
+ return null;
}
if (passphrase == null || passphrase.length() == 0) {
throw new IllegalArgumentException("Passphrase must not be null or empty");
@@ -271,8 +273,8 @@
}
/**
- * Create a {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(String)} for an
- * encrypted WiFi Aware connection (link) to the specified peer. The
+ * Create a {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(NetworkSpecifier)} for
+ * an encrypted WiFi Aware connection (link) to the specified peer. The
* {@link android.net.NetworkRequest.Builder#addTransportType(int)} should be set to
* {@link android.net.NetworkCapabilities#TRANSPORT_WIFI_AWARE}.
* <p>
@@ -294,8 +296,8 @@
* Passphrase or {@link #createNetworkSpecifierOpen(int, byte[])} to specify an
* open (unencrypted) link.
*
- * @return A string to be used to construct
- * {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(String)} to pass to
+ * @return A {@link NetworkSpecifier} to be used to construct
+ * {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(NetworkSpecifier)} to pass to
* {@link android.net.ConnectivityManager#requestNetwork(android.net.NetworkRequest,
* android.net.ConnectivityManager.NetworkCallback)}
* [or other varieties of that API].
@@ -303,16 +305,16 @@
* @hide
*/
@SystemApi
- public String createNetworkSpecifierPmk(@WifiAwareManager.DataPathRole int role,
- @Nullable byte[] peer, @NonNull byte[] pmk) {
+ public NetworkSpecifier createNetworkSpecifierPmk(
+ @WifiAwareManager.DataPathRole int role, @Nullable byte[] peer, @NonNull byte[] pmk) {
WifiAwareManager mgr = mMgr.get();
if (mgr == null) {
Log.e(TAG, "createNetworkSpecifierPmk: called post GC on WifiAwareManager");
- return "";
+ return null;
}
if (mTerminated) {
Log.e(TAG, "createNetworkSpecifierPmk: called after termination");
- return "";
+ return null;
}
if (pmk == null || pmk.length == 0) {
throw new IllegalArgumentException("PMK must not be null or empty");
diff --git a/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java b/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java
index 830db22..72a6a7a 100644
--- a/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java
@@ -34,11 +34,9 @@
import android.os.Parcel;
import android.os.test.TestLooper;
import android.test.suitebuilder.annotation.SmallTest;
-import android.util.Base64;
import libcore.util.HexEncoding;
-import org.json.JSONObject;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -959,8 +957,6 @@
final ConfigRequest configRequest = new ConfigRequest.Builder().build();
final PublishConfig publishConfig = new PublishConfig.Builder().build();
- String pmkB64 = Base64.encodeToString(pmk, Base64.DEFAULT);
-
ArgumentCaptor<WifiAwareSession> sessionCaptor = ArgumentCaptor.forClass(
WifiAwareSession.class);
ArgumentCaptor<IWifiAwareEventCallback> clientProxyCallback = ArgumentCaptor
@@ -991,51 +987,37 @@
inOrder.verify(mockSessionCallback).onPublishStarted(publishSession.capture());
// (3) request an open (unencrypted) network specifier from the session
- String networkSpecifier = publishSession.getValue().createNetworkSpecifierOpen(peerHandle);
+ WifiAwareNetworkSpecifier ns =
+ (WifiAwareNetworkSpecifier) publishSession.getValue().createNetworkSpecifierOpen(
+ peerHandle);
// validate format
- JSONObject jsonObject = new JSONObject(networkSpecifier);
- collector.checkThat("role", role,
- equalTo(jsonObject.getInt(WifiAwareManager.NETWORK_SPECIFIER_KEY_ROLE)));
- collector.checkThat("client_id", clientId,
- equalTo(jsonObject.getInt(WifiAwareManager.NETWORK_SPECIFIER_KEY_CLIENT_ID)));
- collector.checkThat("session_id", sessionId,
- equalTo(jsonObject.getInt(WifiAwareManager.NETWORK_SPECIFIER_KEY_SESSION_ID)));
- collector.checkThat("peer_id", peerHandle.peerId,
- equalTo(jsonObject.getInt(WifiAwareManager.NETWORK_SPECIFIER_KEY_PEER_ID)));
+ collector.checkThat("role", role, equalTo(ns.role));
+ collector.checkThat("client_id", clientId, equalTo(ns.clientId));
+ collector.checkThat("session_id", sessionId, equalTo(ns.sessionId));
+ collector.checkThat("peer_id", peerHandle.peerId, equalTo(ns.peerId));
// (4) request an encrypted (PMK) network specifier from the session
- networkSpecifier = publishSession.getValue().createNetworkSpecifierPmk(peerHandle, pmk);
+ ns = (WifiAwareNetworkSpecifier) publishSession.getValue().createNetworkSpecifierPmk(
+ peerHandle, pmk);
// validate format
- jsonObject = new JSONObject(networkSpecifier);
- collector.checkThat("role", role,
- equalTo(jsonObject.getInt(WifiAwareManager.NETWORK_SPECIFIER_KEY_ROLE)));
- collector.checkThat("client_id", clientId,
- equalTo(jsonObject.getInt(WifiAwareManager.NETWORK_SPECIFIER_KEY_CLIENT_ID)));
- collector.checkThat("session_id", sessionId,
- equalTo(jsonObject.getInt(WifiAwareManager.NETWORK_SPECIFIER_KEY_SESSION_ID)));
- collector.checkThat("peer_id", peerHandle.peerId,
- equalTo(jsonObject.getInt(WifiAwareManager.NETWORK_SPECIFIER_KEY_PEER_ID)));
- collector.checkThat("pmk", pmkB64 ,
- equalTo(jsonObject.getString(WifiAwareManager.NETWORK_SPECIFIER_KEY_PMK)));
+ collector.checkThat("role", role, equalTo(ns.role));
+ collector.checkThat("client_id", clientId, equalTo(ns.clientId));
+ collector.checkThat("session_id", sessionId, equalTo(ns.sessionId));
+ collector.checkThat("peer_id", peerHandle.peerId, equalTo(ns.peerId));
+ collector.checkThat("pmk", pmk , equalTo(ns.pmk));
// (5) request an encrypted (Passphrase) network specifier from the session
- networkSpecifier = publishSession.getValue().createNetworkSpecifierPassphrase(peerHandle,
- passphrase);
+ ns = (WifiAwareNetworkSpecifier) publishSession.getValue().createNetworkSpecifierPassphrase(
+ peerHandle, passphrase);
// validate format
- jsonObject = new JSONObject(networkSpecifier);
- collector.checkThat("role", role,
- equalTo(jsonObject.getInt(WifiAwareManager.NETWORK_SPECIFIER_KEY_ROLE)));
- collector.checkThat("client_id", clientId,
- equalTo(jsonObject.getInt(WifiAwareManager.NETWORK_SPECIFIER_KEY_CLIENT_ID)));
- collector.checkThat("session_id", sessionId,
- equalTo(jsonObject.getInt(WifiAwareManager.NETWORK_SPECIFIER_KEY_SESSION_ID)));
- collector.checkThat("peer_id", peerHandle.peerId,
- equalTo(jsonObject.getInt(WifiAwareManager.NETWORK_SPECIFIER_KEY_PEER_ID)));
- collector.checkThat("passphrase", passphrase,
- equalTo(jsonObject.getString(WifiAwareManager.NETWORK_SPECIFIER_KEY_PASSPHRASE)));
+ collector.checkThat("role", role, equalTo(ns.role));
+ collector.checkThat("client_id", clientId, equalTo(ns.clientId));
+ collector.checkThat("session_id", sessionId, equalTo(ns.sessionId));
+ collector.checkThat("peer_id", peerHandle.peerId, equalTo(ns.peerId));
+ collector.checkThat("passphrase", passphrase, equalTo(ns.passphrase));
verifyNoMoreInteractions(mockCallback, mockSessionCallback, mockAwareService,
mockPublishSession, mockRttListener);
@@ -1054,8 +1036,6 @@
final byte[] pmk = "Some arbitrary pmk data".getBytes();
final String passphrase = "A really bad password";
- String pmkB64 = Base64.encodeToString(pmk, Base64.DEFAULT);
-
ArgumentCaptor<WifiAwareSession> sessionCaptor = ArgumentCaptor.forClass(
WifiAwareSession.class);
ArgumentCaptor<IWifiAwareEventCallback> clientProxyCallback = ArgumentCaptor
@@ -1074,47 +1054,32 @@
WifiAwareSession session = sessionCaptor.getValue();
// (2) request an open (unencrypted) direct network specifier
- String networkSpecifier = session.createNetworkSpecifierOpen(role, someMac);
+ WifiAwareNetworkSpecifier ns =
+ (WifiAwareNetworkSpecifier) session.createNetworkSpecifierOpen(role, someMac);
// validate format
- JSONObject jsonObject = new JSONObject(networkSpecifier);
- collector.checkThat("role", role,
- equalTo(jsonObject.getInt(WifiAwareManager.NETWORK_SPECIFIER_KEY_ROLE)));
- collector.checkThat("client_id", clientId,
- equalTo(jsonObject.getInt(WifiAwareManager.NETWORK_SPECIFIER_KEY_CLIENT_ID)));
- collector.checkThat("peer_mac", someMac, equalTo(HexEncoding.decode(
- jsonObject.getString(WifiAwareManager.NETWORK_SPECIFIER_KEY_PEER_MAC).toCharArray(),
- false)));
+ collector.checkThat("role", role, equalTo(ns.role));
+ collector.checkThat("client_id", clientId, equalTo(ns.clientId));
+ collector.checkThat("peer_mac", someMac, equalTo(ns.peerMac));
// (3) request an encrypted (PMK) direct network specifier
- networkSpecifier = session.createNetworkSpecifierPmk(role, someMac, pmk);
+ ns = (WifiAwareNetworkSpecifier) session.createNetworkSpecifierPmk(role, someMac, pmk);
// validate format
- jsonObject = new JSONObject(networkSpecifier);
- collector.checkThat("role", role,
- equalTo(jsonObject.getInt(WifiAwareManager.NETWORK_SPECIFIER_KEY_ROLE)));
- collector.checkThat("client_id", clientId,
- equalTo(jsonObject.getInt(WifiAwareManager.NETWORK_SPECIFIER_KEY_CLIENT_ID)));
- collector.checkThat("peer_mac", someMac, equalTo(HexEncoding.decode(
- jsonObject.getString(WifiAwareManager.NETWORK_SPECIFIER_KEY_PEER_MAC).toCharArray(),
- false)));
- collector.checkThat("pmk", pmkB64,
- equalTo(jsonObject.getString(WifiAwareManager.NETWORK_SPECIFIER_KEY_PMK)));
+ collector.checkThat("role", role, equalTo(ns.role));
+ collector.checkThat("client_id", clientId, equalTo(ns.clientId));
+ collector.checkThat("peer_mac", someMac, equalTo(ns.peerMac));
+ collector.checkThat("pmk", pmk, equalTo(ns.pmk));
// (4) request an encrypted (Passphrase) direct network specifier
- networkSpecifier = session.createNetworkSpecifierPassphrase(role, someMac, passphrase);
+ ns = (WifiAwareNetworkSpecifier) session.createNetworkSpecifierPassphrase(role, someMac,
+ passphrase);
// validate format
- jsonObject = new JSONObject(networkSpecifier);
- collector.checkThat("role", role,
- equalTo(jsonObject.getInt(WifiAwareManager.NETWORK_SPECIFIER_KEY_ROLE)));
- collector.checkThat("client_id", clientId,
- equalTo(jsonObject.getInt(WifiAwareManager.NETWORK_SPECIFIER_KEY_CLIENT_ID)));
- collector.checkThat("peer_mac", someMac, equalTo(HexEncoding.decode(
- jsonObject.getString(WifiAwareManager.NETWORK_SPECIFIER_KEY_PEER_MAC).toCharArray(),
- false)));
- collector.checkThat("passphrase", passphrase,
- equalTo(jsonObject.getString(WifiAwareManager.NETWORK_SPECIFIER_KEY_PASSPHRASE)));
+ collector.checkThat("role", role, equalTo(ns.role));
+ collector.checkThat("client_id", clientId, equalTo(ns.clientId));
+ collector.checkThat("peer_mac", someMac, equalTo(ns.peerMac));
+ collector.checkThat("passphrase", passphrase, equalTo(ns.passphrase));
verifyNoMoreInteractions(mockCallback, mockSessionCallback, mockAwareService,
mockPublishSession, mockRttListener);