diff options
275 files changed, 7689 insertions, 3712 deletions
diff --git a/api/current.txt b/api/current.txt index db77d3365737..617aac02c35a 100644 --- a/api/current.txt +++ b/api/current.txt @@ -8512,6 +8512,7 @@ package android.content { field public static final java.lang.String ACTION_MANAGED_PROFILE_ADDED = "android.intent.action.MANAGED_PROFILE_ADDED"; field public static final java.lang.String ACTION_MANAGED_PROFILE_AVAILABILITY_CHANGED = "android.intent.action.MANAGED_PROFILE_AVAILABILITY_CHANGED"; field public static final java.lang.String ACTION_MANAGED_PROFILE_REMOVED = "android.intent.action.MANAGED_PROFILE_REMOVED"; + field public static final java.lang.String ACTION_MANAGED_PROFILE_UNLOCKED = "android.intent.action.MANAGED_PROFILE_UNLOCKED"; field public static final java.lang.String ACTION_MANAGE_NETWORK_USAGE = "android.intent.action.MANAGE_NETWORK_USAGE"; field public static final java.lang.String ACTION_MANAGE_PACKAGE_STORAGE = "android.intent.action.MANAGE_PACKAGE_STORAGE"; field public static final java.lang.String ACTION_MEDIA_BAD_REMOVAL = "android.intent.action.MEDIA_BAD_REMOVAL"; @@ -19221,7 +19222,6 @@ package android.location { method public double getElevationUncertaintyInDeg(); method public byte getLossOfLock(); method public byte getMultipathIndicator(); - method public byte getPrn(); method public double getPseudorangeInMeters(); method public double getPseudorangeRateCarrierInMetersPerSec(); method public double getPseudorangeRateCarrierUncertaintyInMetersPerSec(); @@ -19232,6 +19232,7 @@ package android.location { method public long getReceivedGpsTowUncertaintyInNs(); method public double getSnrInDb(); method public short getState(); + method public short getSvid(); method public short getTimeFromLastBitInMs(); method public double getTimeOffsetInNs(); method public boolean hasAzimuthInDeg(); @@ -19291,7 +19292,6 @@ package android.location { method public void setElevationUncertaintyInDeg(double); method public void setLossOfLock(byte); method public void setMultipathIndicator(byte); - method public void setPrn(byte); method public void setPseudorangeInMeters(double); method public void setPseudorangeRateCarrierInMetersPerSec(double); method public void setPseudorangeRateCarrierUncertaintyInMetersPerSec(double); @@ -19302,6 +19302,7 @@ package android.location { method public void setReceivedGpsTowUncertaintyInNs(long); method public void setSnrInDb(double); method public void setState(short); + method public void setSvid(short); method public void setTimeFromLastBitInMs(short); method public void setTimeOffsetInNs(double); method public void setUsedInFix(boolean); @@ -19356,17 +19357,17 @@ package android.location { method public int describeContents(); method public byte[] getData(); method public short getMessageId(); - method public byte getPrn(); method public short getStatus(); method public short getSubmessageId(); + method public short getSvid(); method public byte getType(); method public void reset(); method public void set(android.location.GnssNavigationMessage); method public void setData(byte[]); method public void setMessageId(short); - method public void setPrn(byte); method public void setStatus(short); method public void setSubmessageId(short); + method public void setSvid(short); method public void setType(byte); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.location.GnssNavigationMessage> CREATOR; @@ -19412,8 +19413,8 @@ package android.location { method public int getConstellationType(int); method public float getElevation(int); method public int getNumSatellites(); - method public int getPrn(int); method public float getSnr(int); + method public int getSvid(int); method public boolean hasAlmanac(int); method public boolean hasEphemeris(int); method public boolean usedInFix(int); @@ -22950,10 +22951,8 @@ package android.media.tv { field public static final int INPUT_STATE_CONNECTED_STANDBY = 1; // 0x1 field public static final int INPUT_STATE_DISCONNECTED = 2; // 0x2 field public static final java.lang.String META_DATA_CONTENT_RATING_SYSTEMS = "android.media.tv.metadata.CONTENT_RATING_SYSTEMS"; - field public static final int RECORDING_ERROR_CONNECTION_FAILED = 1; // 0x1 - field public static final int RECORDING_ERROR_DISCONNECTED = 2; // 0x2 - field public static final int RECORDING_ERROR_INSUFFICIENT_SPACE = 3; // 0x3 - field public static final int RECORDING_ERROR_RESOURCE_BUSY = 4; // 0x4 + field public static final int RECORDING_ERROR_INSUFFICIENT_SPACE = 1; // 0x1 + field public static final int RECORDING_ERROR_RESOURCE_BUSY = 2; // 0x2 field public static final int RECORDING_ERROR_UNKNOWN = 0; // 0x0 field public static final long TIME_SHIFT_INVALID_TIME = -9223372036854775808L; // 0x8000000000000000L field public static final int TIME_SHIFT_STATUS_AVAILABLE = 3; // 0x3 @@ -23053,6 +23052,8 @@ package android.media.tv { public static abstract class TvRecordingClient.RecordingCallback { ctor public TvRecordingClient.RecordingCallback(); + method public void onConnectionFailed(java.lang.String); + method public void onDisconnected(java.lang.String); method public void onError(int); method public void onRecordingStopped(android.net.Uri); method public void onTuned(); @@ -23067,6 +23068,7 @@ package android.media.tv { method public final java.lang.String getId(); method public final java.lang.String getLanguage(); method public final int getType(); + method public final byte getVideoActiveFormatDescription(); method public final float getVideoFrameRate(); method public final int getVideoHeight(); method public final float getVideoPixelAspectRatio(); @@ -23086,6 +23088,7 @@ package android.media.tv { method public final android.media.tv.TvTrackInfo.Builder setDescription(java.lang.CharSequence); method public final android.media.tv.TvTrackInfo.Builder setExtra(android.os.Bundle); method public final android.media.tv.TvTrackInfo.Builder setLanguage(java.lang.String); + method public final android.media.tv.TvTrackInfo.Builder setVideoActiveFormatDescription(byte); method public final android.media.tv.TvTrackInfo.Builder setVideoFrameRate(float); method public final android.media.tv.TvTrackInfo.Builder setVideoHeight(int); method public final android.media.tv.TvTrackInfo.Builder setVideoPixelAspectRatio(float); @@ -29062,6 +29065,7 @@ package android.os { method public static final int getThreadPriority(int) throws java.lang.IllegalArgumentException; method public static final int getUidForName(java.lang.String); method public static final boolean is64Bit(); + method public static boolean isApplicationUid(int); method public static final void killProcess(int); method public static final int myPid(); method public static final int myTid(); @@ -29242,6 +29246,7 @@ package android.os { public final class UserHandle implements android.os.Parcelable { ctor public UserHandle(android.os.Parcel); method public int describeContents(); + method public static android.os.UserHandle getUserHandleForUid(int); method public static android.os.UserHandle readFromParcel(android.os.Parcel); method public void writeToParcel(android.os.Parcel, int); method public static void writeToParcel(android.os.UserHandle, android.os.Parcel); @@ -51474,7 +51479,6 @@ package java.lang.reflect { method public static int getLength(java.lang.Object); method public static long getLong(java.lang.Object, int) throws java.lang.ArrayIndexOutOfBoundsException, java.lang.IllegalArgumentException; method public static short getShort(java.lang.Object, int) throws java.lang.ArrayIndexOutOfBoundsException, java.lang.IllegalArgumentException; - method public static java.lang.Object newArray(java.lang.Class<?>, int) throws java.lang.NegativeArraySizeException; method public static java.lang.Object newInstance(java.lang.Class<?>, int) throws java.lang.NegativeArraySizeException; method public static java.lang.Object newInstance(java.lang.Class<?>, int...) throws java.lang.IllegalArgumentException, java.lang.NegativeArraySizeException; method public static void set(java.lang.Object, int, java.lang.Object) throws java.lang.ArrayIndexOutOfBoundsException, java.lang.IllegalArgumentException; @@ -52150,7 +52154,6 @@ package java.net { public class InetAddress implements java.io.Serializable { method public byte[] getAddress(); - method public byte[] getAddressInternal(); method public static java.net.InetAddress[] getAllByName(java.lang.String) throws java.net.UnknownHostException; method public static java.net.InetAddress getByAddress(java.lang.String, byte[]) throws java.net.UnknownHostException; method public static java.net.InetAddress getByAddress(byte[]) throws java.net.UnknownHostException; @@ -52424,7 +52427,7 @@ package java.net { method protected abstract void connect(java.net.InetAddress, int) throws java.io.IOException; method protected abstract void connect(java.net.SocketAddress, int) throws java.io.IOException; method protected abstract void create(boolean) throws java.io.IOException; - method public java.io.FileDescriptor getFileDescriptor(); + method protected java.io.FileDescriptor getFileDescriptor(); method protected java.net.InetAddress getInetAddress(); method protected abstract java.io.InputStream getInputStream() throws java.io.IOException; method protected int getLocalPort(); @@ -52966,10 +52969,6 @@ package java.nio { package java.nio.channels { - public class AcceptPendingException extends java.lang.IllegalStateException { - ctor public AcceptPendingException(); - } - public class AlreadyBoundException extends java.lang.IllegalStateException { ctor public AlreadyBoundException(); } @@ -52978,68 +52977,10 @@ package java.nio.channels { ctor public AlreadyConnectedException(); } - public abstract interface AsynchronousByteChannel implements java.nio.channels.AsynchronousChannel { - method public abstract void read(java.nio.ByteBuffer, A, java.nio.channels.CompletionHandler<java.lang.Integer, ? super A>); - method public abstract java.util.concurrent.Future<java.lang.Integer> read(java.nio.ByteBuffer); - method public abstract void write(java.nio.ByteBuffer, A, java.nio.channels.CompletionHandler<java.lang.Integer, ? super A>); - method public abstract java.util.concurrent.Future<java.lang.Integer> write(java.nio.ByteBuffer); - } - - public abstract interface AsynchronousChannel implements java.nio.channels.Channel { - method public abstract void close() throws java.io.IOException; - } - - public abstract class AsynchronousChannelGroup { - ctor protected AsynchronousChannelGroup(java.nio.channels.spi.AsynchronousChannelProvider); - method public abstract boolean awaitTermination(long, java.util.concurrent.TimeUnit) throws java.lang.InterruptedException; - method public abstract boolean isShutdown(); - method public abstract boolean isTerminated(); - method public final java.nio.channels.spi.AsynchronousChannelProvider provider(); - method public abstract void shutdown(); - method public abstract void shutdownNow() throws java.io.IOException; - method public static java.nio.channels.AsynchronousChannelGroup withCachedThreadPool(java.util.concurrent.ExecutorService, int) throws java.io.IOException; - method public static java.nio.channels.AsynchronousChannelGroup withFixedThreadPool(int, java.util.concurrent.ThreadFactory) throws java.io.IOException; - method public static java.nio.channels.AsynchronousChannelGroup withThreadPool(java.util.concurrent.ExecutorService) throws java.io.IOException; - } - public class AsynchronousCloseException extends java.nio.channels.ClosedChannelException { ctor public AsynchronousCloseException(); } - public abstract class AsynchronousServerSocketChannel implements java.nio.channels.AsynchronousChannel java.nio.channels.NetworkChannel { - ctor protected AsynchronousServerSocketChannel(java.nio.channels.spi.AsynchronousChannelProvider); - method public abstract void accept(A, java.nio.channels.CompletionHandler<java.nio.channels.AsynchronousSocketChannel, ? super A>); - method public abstract java.util.concurrent.Future<java.nio.channels.AsynchronousSocketChannel> accept(); - method public final java.nio.channels.AsynchronousServerSocketChannel bind(java.net.SocketAddress) throws java.io.IOException; - method public abstract java.nio.channels.AsynchronousServerSocketChannel bind(java.net.SocketAddress, int) throws java.io.IOException; - method public static java.nio.channels.AsynchronousServerSocketChannel open(java.nio.channels.AsynchronousChannelGroup) throws java.io.IOException; - method public static java.nio.channels.AsynchronousServerSocketChannel open() throws java.io.IOException; - method public final java.nio.channels.spi.AsynchronousChannelProvider provider(); - method public abstract java.nio.channels.AsynchronousServerSocketChannel setOption(java.net.SocketOption<T>, T) throws java.io.IOException; - } - - public abstract class AsynchronousSocketChannel implements java.nio.channels.AsynchronousByteChannel java.nio.channels.NetworkChannel { - ctor protected AsynchronousSocketChannel(java.nio.channels.spi.AsynchronousChannelProvider); - method public abstract java.nio.channels.AsynchronousSocketChannel bind(java.net.SocketAddress) throws java.io.IOException; - method public abstract void connect(java.net.SocketAddress, A, java.nio.channels.CompletionHandler<java.lang.Void, ? super A>); - method public abstract java.util.concurrent.Future<java.lang.Void> connect(java.net.SocketAddress); - method public abstract java.net.SocketAddress getRemoteAddress() throws java.io.IOException; - method public static java.nio.channels.AsynchronousSocketChannel open(java.nio.channels.AsynchronousChannelGroup) throws java.io.IOException; - method public static java.nio.channels.AsynchronousSocketChannel open() throws java.io.IOException; - method public final java.nio.channels.spi.AsynchronousChannelProvider provider(); - method public abstract void read(java.nio.ByteBuffer, long, java.util.concurrent.TimeUnit, A, java.nio.channels.CompletionHandler<java.lang.Integer, ? super A>); - method public final void read(java.nio.ByteBuffer, A, java.nio.channels.CompletionHandler<java.lang.Integer, ? super A>); - method public abstract java.util.concurrent.Future<java.lang.Integer> read(java.nio.ByteBuffer); - method public abstract void read(java.nio.ByteBuffer[], int, int, long, java.util.concurrent.TimeUnit, A, java.nio.channels.CompletionHandler<java.lang.Long, ? super A>); - method public abstract java.nio.channels.AsynchronousSocketChannel setOption(java.net.SocketOption<T>, T) throws java.io.IOException; - method public abstract java.nio.channels.AsynchronousSocketChannel shutdownInput() throws java.io.IOException; - method public abstract java.nio.channels.AsynchronousSocketChannel shutdownOutput() throws java.io.IOException; - method public abstract void write(java.nio.ByteBuffer, long, java.util.concurrent.TimeUnit, A, java.nio.channels.CompletionHandler<java.lang.Integer, ? super A>); - method public final void write(java.nio.ByteBuffer, A, java.nio.channels.CompletionHandler<java.lang.Integer, ? super A>); - method public abstract java.util.concurrent.Future<java.lang.Integer> write(java.nio.ByteBuffer); - method public abstract void write(java.nio.ByteBuffer[], int, int, long, java.util.concurrent.TimeUnit, A, java.nio.channels.CompletionHandler<java.lang.Long, ? super A>); - } - public abstract interface ByteChannel implements java.nio.channels.ReadableByteChannel java.nio.channels.WritableByteChannel { } @@ -53056,9 +52997,7 @@ package java.nio.channels { method public static java.nio.channels.ReadableByteChannel newChannel(java.io.InputStream); method public static java.nio.channels.WritableByteChannel newChannel(java.io.OutputStream); method public static java.io.InputStream newInputStream(java.nio.channels.ReadableByteChannel); - method public static java.io.InputStream newInputStream(java.nio.channels.AsynchronousByteChannel); method public static java.io.OutputStream newOutputStream(java.nio.channels.WritableByteChannel); - method public static java.io.OutputStream newOutputStream(java.nio.channels.AsynchronousByteChannel); method public static java.io.Reader newReader(java.nio.channels.ReadableByteChannel, java.nio.charset.CharsetDecoder, int); method public static java.io.Reader newReader(java.nio.channels.ReadableByteChannel, java.lang.String); method public static java.io.Writer newWriter(java.nio.channels.WritableByteChannel, java.nio.charset.CharsetEncoder, int); @@ -53077,16 +53016,11 @@ package java.nio.channels { ctor public ClosedSelectorException(); } - public abstract interface CompletionHandler { - method public abstract void completed(V, A); - method public abstract void failed(java.lang.Throwable, A); - } - public class ConnectionPendingException extends java.lang.IllegalStateException { ctor public ConnectionPendingException(); } - public abstract class DatagramChannel extends java.nio.channels.spi.AbstractSelectableChannel implements java.nio.channels.ByteChannel java.nio.channels.GatheringByteChannel java.nio.channels.MulticastChannel java.nio.channels.ScatteringByteChannel { + public abstract class DatagramChannel extends java.nio.channels.spi.AbstractSelectableChannel implements java.nio.channels.ByteChannel java.nio.channels.GatheringByteChannel java.nio.channels.ScatteringByteChannel { ctor protected DatagramChannel(java.nio.channels.spi.SelectorProvider); method public abstract java.nio.channels.DatagramChannel bind(java.net.SocketAddress) throws java.io.IOException; method public abstract java.nio.channels.DatagramChannel connect(java.net.SocketAddress) throws java.io.IOException; @@ -53165,40 +53099,14 @@ package java.nio.channels { ctor public IllegalBlockingModeException(); } - public class IllegalChannelGroupException extends java.lang.IllegalArgumentException { - ctor public IllegalChannelGroupException(); - } - public class IllegalSelectorException extends java.lang.IllegalArgumentException { ctor public IllegalSelectorException(); } - public class InterruptedByTimeoutException extends java.io.IOException { - ctor public InterruptedByTimeoutException(); - } - public abstract interface InterruptibleChannel implements java.nio.channels.Channel { method public abstract void close() throws java.io.IOException; } - public abstract class MembershipKey { - ctor protected MembershipKey(); - method public abstract java.nio.channels.MembershipKey block(java.net.InetAddress) throws java.io.IOException; - method public abstract java.nio.channels.MulticastChannel channel(); - method public abstract void drop(); - method public abstract java.net.InetAddress group(); - method public abstract boolean isValid(); - method public abstract java.net.NetworkInterface networkInterface(); - method public abstract java.net.InetAddress sourceAddress(); - method public abstract java.nio.channels.MembershipKey unblock(java.net.InetAddress); - } - - public abstract interface MulticastChannel implements java.nio.channels.NetworkChannel { - method public abstract void close() throws java.io.IOException; - method public abstract java.nio.channels.MembershipKey join(java.net.InetAddress, java.net.NetworkInterface) throws java.io.IOException; - method public abstract java.nio.channels.MembershipKey join(java.net.InetAddress, java.net.NetworkInterface, java.net.InetAddress) throws java.io.IOException; - } - public abstract interface NetworkChannel implements java.nio.channels.Channel { method public abstract java.nio.channels.NetworkChannel bind(java.net.SocketAddress) throws java.io.IOException; method public abstract java.net.SocketAddress getLocalAddress() throws java.io.IOException; @@ -53248,10 +53156,6 @@ package java.nio.channels { method public final int validOps(); } - public class ReadPendingException extends java.lang.IllegalStateException { - ctor public ReadPendingException(); - } - public abstract interface ReadableByteChannel implements java.nio.channels.Channel { method public abstract int read(java.nio.ByteBuffer) throws java.io.IOException; } @@ -53329,10 +53233,6 @@ package java.nio.channels { method public final int validOps(); } - public class ShutdownChannelGroupException extends java.lang.IllegalStateException { - ctor public ShutdownChannelGroupException(); - } - public abstract class SocketChannel extends java.nio.channels.spi.AbstractSelectableChannel implements java.nio.channels.ByteChannel java.nio.channels.GatheringByteChannel java.nio.channels.NetworkChannel java.nio.channels.ScatteringByteChannel { ctor protected SocketChannel(java.nio.channels.spi.SelectorProvider); method public abstract java.nio.channels.SocketChannel bind(java.net.SocketAddress) throws java.io.IOException; @@ -53368,10 +53268,6 @@ package java.nio.channels { method public abstract int write(java.nio.ByteBuffer) throws java.io.IOException; } - public class WritePendingException extends java.lang.IllegalStateException { - ctor public WritePendingException(); - } - } package java.nio.channels.spi { @@ -53418,15 +53314,6 @@ package java.nio.channels.spi { method protected abstract java.nio.channels.SelectionKey register(java.nio.channels.spi.AbstractSelectableChannel, int, java.lang.Object); } - public abstract class AsynchronousChannelProvider { - ctor protected AsynchronousChannelProvider(); - method public abstract java.nio.channels.AsynchronousChannelGroup openAsynchronousChannelGroup(int, java.util.concurrent.ThreadFactory) throws java.io.IOException; - method public abstract java.nio.channels.AsynchronousChannelGroup openAsynchronousChannelGroup(java.util.concurrent.ExecutorService, int) throws java.io.IOException; - method public abstract java.nio.channels.AsynchronousServerSocketChannel openAsynchronousServerSocketChannel(java.nio.channels.AsynchronousChannelGroup) throws java.io.IOException; - method public abstract java.nio.channels.AsynchronousSocketChannel openAsynchronousSocketChannel(java.nio.channels.AsynchronousChannelGroup) throws java.io.IOException; - method public static java.nio.channels.spi.AsynchronousChannelProvider provider(); - } - public abstract class SelectorProvider { ctor protected SelectorProvider(); method public java.nio.channels.Channel inheritedChannel() throws java.io.IOException; @@ -56781,13 +56668,11 @@ package java.text { method public static final java.text.DecimalFormatSymbols getInstance(java.util.Locale); method public java.lang.String getInternationalCurrencySymbol(); method public char getMinusSign(); - method public java.lang.String getMinusSignString(); method public char getMonetaryDecimalSeparator(); method public java.lang.String getNaN(); method public char getPatternSeparator(); method public char getPerMill(); method public char getPercent(); - method public java.lang.String getPercentString(); method public char getZeroDigit(); method public void setCurrency(java.util.Currency); method public void setCurrencySymbol(java.lang.String); diff --git a/api/system-current.txt b/api/system-current.txt index 86419ce86261..ec422c026950 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -8818,6 +8818,7 @@ package android.content { field public static final java.lang.String ACTION_MANAGED_PROFILE_ADDED = "android.intent.action.MANAGED_PROFILE_ADDED"; field public static final java.lang.String ACTION_MANAGED_PROFILE_AVAILABILITY_CHANGED = "android.intent.action.MANAGED_PROFILE_AVAILABILITY_CHANGED"; field public static final java.lang.String ACTION_MANAGED_PROFILE_REMOVED = "android.intent.action.MANAGED_PROFILE_REMOVED"; + field public static final java.lang.String ACTION_MANAGED_PROFILE_UNLOCKED = "android.intent.action.MANAGED_PROFILE_UNLOCKED"; field public static final java.lang.String ACTION_MANAGE_NETWORK_USAGE = "android.intent.action.MANAGE_NETWORK_USAGE"; field public static final java.lang.String ACTION_MANAGE_PACKAGE_STORAGE = "android.intent.action.MANAGE_PACKAGE_STORAGE"; field public static final java.lang.String ACTION_MEDIA_BAD_REMOVAL = "android.intent.action.MEDIA_BAD_REMOVAL"; @@ -20401,7 +20402,6 @@ package android.location { method public double getElevationUncertaintyInDeg(); method public byte getLossOfLock(); method public byte getMultipathIndicator(); - method public byte getPrn(); method public double getPseudorangeInMeters(); method public double getPseudorangeRateCarrierInMetersPerSec(); method public double getPseudorangeRateCarrierUncertaintyInMetersPerSec(); @@ -20412,6 +20412,7 @@ package android.location { method public long getReceivedGpsTowUncertaintyInNs(); method public double getSnrInDb(); method public short getState(); + method public short getSvid(); method public short getTimeFromLastBitInMs(); method public double getTimeOffsetInNs(); method public boolean hasAzimuthInDeg(); @@ -20471,7 +20472,6 @@ package android.location { method public void setElevationUncertaintyInDeg(double); method public void setLossOfLock(byte); method public void setMultipathIndicator(byte); - method public void setPrn(byte); method public void setPseudorangeInMeters(double); method public void setPseudorangeRateCarrierInMetersPerSec(double); method public void setPseudorangeRateCarrierUncertaintyInMetersPerSec(double); @@ -20482,6 +20482,7 @@ package android.location { method public void setReceivedGpsTowUncertaintyInNs(long); method public void setSnrInDb(double); method public void setState(short); + method public void setSvid(short); method public void setTimeFromLastBitInMs(short); method public void setTimeOffsetInNs(double); method public void setUsedInFix(boolean); @@ -20536,17 +20537,17 @@ package android.location { method public int describeContents(); method public byte[] getData(); method public short getMessageId(); - method public byte getPrn(); method public short getStatus(); method public short getSubmessageId(); + method public short getSvid(); method public byte getType(); method public void reset(); method public void set(android.location.GnssNavigationMessage); method public void setData(byte[]); method public void setMessageId(short); - method public void setPrn(byte); method public void setStatus(short); method public void setSubmessageId(short); + method public void setSvid(short); method public void setType(byte); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.location.GnssNavigationMessage> CREATOR; @@ -20592,8 +20593,8 @@ package android.location { method public int getConstellationType(int); method public float getElevation(int); method public int getNumSatellites(); - method public int getPrn(int); method public float getSnr(int); + method public int getSvid(int); method public boolean hasAlmanac(int); method public boolean hasEphemeris(int); method public boolean usedInFix(int); @@ -24649,10 +24650,8 @@ package android.media.tv { field public static final int INPUT_STATE_CONNECTED_STANDBY = 1; // 0x1 field public static final int INPUT_STATE_DISCONNECTED = 2; // 0x2 field public static final java.lang.String META_DATA_CONTENT_RATING_SYSTEMS = "android.media.tv.metadata.CONTENT_RATING_SYSTEMS"; - field public static final int RECORDING_ERROR_CONNECTION_FAILED = 1; // 0x1 - field public static final int RECORDING_ERROR_DISCONNECTED = 2; // 0x2 - field public static final int RECORDING_ERROR_INSUFFICIENT_SPACE = 3; // 0x3 - field public static final int RECORDING_ERROR_RESOURCE_BUSY = 4; // 0x4 + field public static final int RECORDING_ERROR_INSUFFICIENT_SPACE = 1; // 0x1 + field public static final int RECORDING_ERROR_RESOURCE_BUSY = 2; // 0x2 field public static final int RECORDING_ERROR_UNKNOWN = 0; // 0x0 field public static final long TIME_SHIFT_INVALID_TIME = -9223372036854775808L; // 0x8000000000000000L field public static final int TIME_SHIFT_STATUS_AVAILABLE = 3; // 0x3 @@ -24811,6 +24810,8 @@ package android.media.tv { public static abstract class TvRecordingClient.RecordingCallback { ctor public TvRecordingClient.RecordingCallback(); + method public void onConnectionFailed(java.lang.String); + method public void onDisconnected(java.lang.String); method public void onError(int); method public void onEvent(java.lang.String, java.lang.String, android.os.Bundle); method public void onRecordingStopped(android.net.Uri); @@ -24852,6 +24853,7 @@ package android.media.tv { method public final java.lang.String getId(); method public final java.lang.String getLanguage(); method public final int getType(); + method public final byte getVideoActiveFormatDescription(); method public final float getVideoFrameRate(); method public final int getVideoHeight(); method public final float getVideoPixelAspectRatio(); @@ -24871,6 +24873,7 @@ package android.media.tv { method public final android.media.tv.TvTrackInfo.Builder setDescription(java.lang.CharSequence); method public final android.media.tv.TvTrackInfo.Builder setExtra(android.os.Bundle); method public final android.media.tv.TvTrackInfo.Builder setLanguage(java.lang.String); + method public final android.media.tv.TvTrackInfo.Builder setVideoActiveFormatDescription(byte); method public final android.media.tv.TvTrackInfo.Builder setVideoFrameRate(float); method public final android.media.tv.TvTrackInfo.Builder setVideoHeight(int); method public final android.media.tv.TvTrackInfo.Builder setVideoPixelAspectRatio(float); @@ -26680,6 +26683,7 @@ package android.net.wifi { method public boolean reconnect(); method public boolean removeNetwork(int); method public boolean saveConfiguration(); + method public boolean setMetered(int, boolean); method public void setTdlsEnabled(java.net.InetAddress, boolean); method public void setTdlsEnabledWithMacAddress(java.lang.String, boolean); method public boolean setWifiApConfiguration(android.net.wifi.WifiConfiguration); @@ -31355,6 +31359,7 @@ package android.os { method public static final int getThreadPriority(int) throws java.lang.IllegalArgumentException; method public static final int getUidForName(java.lang.String); method public static final boolean is64Bit(); + method public static boolean isApplicationUid(int); method public static final void killProcess(int); method public static final int myPid(); method public static final int myTid(); @@ -31581,6 +31586,7 @@ package android.os { ctor public UserHandle(android.os.Parcel); method public int describeContents(); method public int getIdentifier(); + method public static android.os.UserHandle getUserHandleForUid(int); method public deprecated boolean isOwner(); method public boolean isSystem(); method public static int myUserId(); @@ -54562,7 +54568,6 @@ package java.lang.reflect { method public static int getLength(java.lang.Object); method public static long getLong(java.lang.Object, int) throws java.lang.ArrayIndexOutOfBoundsException, java.lang.IllegalArgumentException; method public static short getShort(java.lang.Object, int) throws java.lang.ArrayIndexOutOfBoundsException, java.lang.IllegalArgumentException; - method public static java.lang.Object newArray(java.lang.Class<?>, int) throws java.lang.NegativeArraySizeException; method public static java.lang.Object newInstance(java.lang.Class<?>, int) throws java.lang.NegativeArraySizeException; method public static java.lang.Object newInstance(java.lang.Class<?>, int...) throws java.lang.IllegalArgumentException, java.lang.NegativeArraySizeException; method public static void set(java.lang.Object, int, java.lang.Object) throws java.lang.ArrayIndexOutOfBoundsException, java.lang.IllegalArgumentException; @@ -55238,7 +55243,6 @@ package java.net { public class InetAddress implements java.io.Serializable { method public byte[] getAddress(); - method public byte[] getAddressInternal(); method public static java.net.InetAddress[] getAllByName(java.lang.String) throws java.net.UnknownHostException; method public static java.net.InetAddress getByAddress(java.lang.String, byte[]) throws java.net.UnknownHostException; method public static java.net.InetAddress getByAddress(byte[]) throws java.net.UnknownHostException; @@ -55512,7 +55516,7 @@ package java.net { method protected abstract void connect(java.net.InetAddress, int) throws java.io.IOException; method protected abstract void connect(java.net.SocketAddress, int) throws java.io.IOException; method protected abstract void create(boolean) throws java.io.IOException; - method public java.io.FileDescriptor getFileDescriptor(); + method protected java.io.FileDescriptor getFileDescriptor(); method protected java.net.InetAddress getInetAddress(); method protected abstract java.io.InputStream getInputStream() throws java.io.IOException; method protected int getLocalPort(); @@ -56054,10 +56058,6 @@ package java.nio { package java.nio.channels { - public class AcceptPendingException extends java.lang.IllegalStateException { - ctor public AcceptPendingException(); - } - public class AlreadyBoundException extends java.lang.IllegalStateException { ctor public AlreadyBoundException(); } @@ -56066,68 +56066,10 @@ package java.nio.channels { ctor public AlreadyConnectedException(); } - public abstract interface AsynchronousByteChannel implements java.nio.channels.AsynchronousChannel { - method public abstract void read(java.nio.ByteBuffer, A, java.nio.channels.CompletionHandler<java.lang.Integer, ? super A>); - method public abstract java.util.concurrent.Future<java.lang.Integer> read(java.nio.ByteBuffer); - method public abstract void write(java.nio.ByteBuffer, A, java.nio.channels.CompletionHandler<java.lang.Integer, ? super A>); - method public abstract java.util.concurrent.Future<java.lang.Integer> write(java.nio.ByteBuffer); - } - - public abstract interface AsynchronousChannel implements java.nio.channels.Channel { - method public abstract void close() throws java.io.IOException; - } - - public abstract class AsynchronousChannelGroup { - ctor protected AsynchronousChannelGroup(java.nio.channels.spi.AsynchronousChannelProvider); - method public abstract boolean awaitTermination(long, java.util.concurrent.TimeUnit) throws java.lang.InterruptedException; - method public abstract boolean isShutdown(); - method public abstract boolean isTerminated(); - method public final java.nio.channels.spi.AsynchronousChannelProvider provider(); - method public abstract void shutdown(); - method public abstract void shutdownNow() throws java.io.IOException; - method public static java.nio.channels.AsynchronousChannelGroup withCachedThreadPool(java.util.concurrent.ExecutorService, int) throws java.io.IOException; - method public static java.nio.channels.AsynchronousChannelGroup withFixedThreadPool(int, java.util.concurrent.ThreadFactory) throws java.io.IOException; - method public static java.nio.channels.AsynchronousChannelGroup withThreadPool(java.util.concurrent.ExecutorService) throws java.io.IOException; - } - public class AsynchronousCloseException extends java.nio.channels.ClosedChannelException { ctor public AsynchronousCloseException(); } - public abstract class AsynchronousServerSocketChannel implements java.nio.channels.AsynchronousChannel java.nio.channels.NetworkChannel { - ctor protected AsynchronousServerSocketChannel(java.nio.channels.spi.AsynchronousChannelProvider); - method public abstract void accept(A, java.nio.channels.CompletionHandler<java.nio.channels.AsynchronousSocketChannel, ? super A>); - method public abstract java.util.concurrent.Future<java.nio.channels.AsynchronousSocketChannel> accept(); - method public final java.nio.channels.AsynchronousServerSocketChannel bind(java.net.SocketAddress) throws java.io.IOException; - method public abstract java.nio.channels.AsynchronousServerSocketChannel bind(java.net.SocketAddress, int) throws java.io.IOException; - method public static java.nio.channels.AsynchronousServerSocketChannel open(java.nio.channels.AsynchronousChannelGroup) throws java.io.IOException; - method public static java.nio.channels.AsynchronousServerSocketChannel open() throws java.io.IOException; - method public final java.nio.channels.spi.AsynchronousChannelProvider provider(); - method public abstract java.nio.channels.AsynchronousServerSocketChannel setOption(java.net.SocketOption<T>, T) throws java.io.IOException; - } - - public abstract class AsynchronousSocketChannel implements java.nio.channels.AsynchronousByteChannel java.nio.channels.NetworkChannel { - ctor protected AsynchronousSocketChannel(java.nio.channels.spi.AsynchronousChannelProvider); - method public abstract java.nio.channels.AsynchronousSocketChannel bind(java.net.SocketAddress) throws java.io.IOException; - method public abstract void connect(java.net.SocketAddress, A, java.nio.channels.CompletionHandler<java.lang.Void, ? super A>); - method public abstract java.util.concurrent.Future<java.lang.Void> connect(java.net.SocketAddress); - method public abstract java.net.SocketAddress getRemoteAddress() throws java.io.IOException; - method public static java.nio.channels.AsynchronousSocketChannel open(java.nio.channels.AsynchronousChannelGroup) throws java.io.IOException; - method public static java.nio.channels.AsynchronousSocketChannel open() throws java.io.IOException; - method public final java.nio.channels.spi.AsynchronousChannelProvider provider(); - method public abstract void read(java.nio.ByteBuffer, long, java.util.concurrent.TimeUnit, A, java.nio.channels.CompletionHandler<java.lang.Integer, ? super A>); - method public final void read(java.nio.ByteBuffer, A, java.nio.channels.CompletionHandler<java.lang.Integer, ? super A>); - method public abstract java.util.concurrent.Future<java.lang.Integer> read(java.nio.ByteBuffer); - method public abstract void read(java.nio.ByteBuffer[], int, int, long, java.util.concurrent.TimeUnit, A, java.nio.channels.CompletionHandler<java.lang.Long, ? super A>); - method public abstract java.nio.channels.AsynchronousSocketChannel setOption(java.net.SocketOption<T>, T) throws java.io.IOException; - method public abstract java.nio.channels.AsynchronousSocketChannel shutdownInput() throws java.io.IOException; - method public abstract java.nio.channels.AsynchronousSocketChannel shutdownOutput() throws java.io.IOException; - method public abstract void write(java.nio.ByteBuffer, long, java.util.concurrent.TimeUnit, A, java.nio.channels.CompletionHandler<java.lang.Integer, ? super A>); - method public final void write(java.nio.ByteBuffer, A, java.nio.channels.CompletionHandler<java.lang.Integer, ? super A>); - method public abstract java.util.concurrent.Future<java.lang.Integer> write(java.nio.ByteBuffer); - method public abstract void write(java.nio.ByteBuffer[], int, int, long, java.util.concurrent.TimeUnit, A, java.nio.channels.CompletionHandler<java.lang.Long, ? super A>); - } - public abstract interface ByteChannel implements java.nio.channels.ReadableByteChannel java.nio.channels.WritableByteChannel { } @@ -56144,9 +56086,7 @@ package java.nio.channels { method public static java.nio.channels.ReadableByteChannel newChannel(java.io.InputStream); method public static java.nio.channels.WritableByteChannel newChannel(java.io.OutputStream); method public static java.io.InputStream newInputStream(java.nio.channels.ReadableByteChannel); - method public static java.io.InputStream newInputStream(java.nio.channels.AsynchronousByteChannel); method public static java.io.OutputStream newOutputStream(java.nio.channels.WritableByteChannel); - method public static java.io.OutputStream newOutputStream(java.nio.channels.AsynchronousByteChannel); method public static java.io.Reader newReader(java.nio.channels.ReadableByteChannel, java.nio.charset.CharsetDecoder, int); method public static java.io.Reader newReader(java.nio.channels.ReadableByteChannel, java.lang.String); method public static java.io.Writer newWriter(java.nio.channels.WritableByteChannel, java.nio.charset.CharsetEncoder, int); @@ -56165,16 +56105,11 @@ package java.nio.channels { ctor public ClosedSelectorException(); } - public abstract interface CompletionHandler { - method public abstract void completed(V, A); - method public abstract void failed(java.lang.Throwable, A); - } - public class ConnectionPendingException extends java.lang.IllegalStateException { ctor public ConnectionPendingException(); } - public abstract class DatagramChannel extends java.nio.channels.spi.AbstractSelectableChannel implements java.nio.channels.ByteChannel java.nio.channels.GatheringByteChannel java.nio.channels.MulticastChannel java.nio.channels.ScatteringByteChannel { + public abstract class DatagramChannel extends java.nio.channels.spi.AbstractSelectableChannel implements java.nio.channels.ByteChannel java.nio.channels.GatheringByteChannel java.nio.channels.ScatteringByteChannel { ctor protected DatagramChannel(java.nio.channels.spi.SelectorProvider); method public abstract java.nio.channels.DatagramChannel bind(java.net.SocketAddress) throws java.io.IOException; method public abstract java.nio.channels.DatagramChannel connect(java.net.SocketAddress) throws java.io.IOException; @@ -56253,40 +56188,14 @@ package java.nio.channels { ctor public IllegalBlockingModeException(); } - public class IllegalChannelGroupException extends java.lang.IllegalArgumentException { - ctor public IllegalChannelGroupException(); - } - public class IllegalSelectorException extends java.lang.IllegalArgumentException { ctor public IllegalSelectorException(); } - public class InterruptedByTimeoutException extends java.io.IOException { - ctor public InterruptedByTimeoutException(); - } - public abstract interface InterruptibleChannel implements java.nio.channels.Channel { method public abstract void close() throws java.io.IOException; } - public abstract class MembershipKey { - ctor protected MembershipKey(); - method public abstract java.nio.channels.MembershipKey block(java.net.InetAddress) throws java.io.IOException; - method public abstract java.nio.channels.MulticastChannel channel(); - method public abstract void drop(); - method public abstract java.net.InetAddress group(); - method public abstract boolean isValid(); - method public abstract java.net.NetworkInterface networkInterface(); - method public abstract java.net.InetAddress sourceAddress(); - method public abstract java.nio.channels.MembershipKey unblock(java.net.InetAddress); - } - - public abstract interface MulticastChannel implements java.nio.channels.NetworkChannel { - method public abstract void close() throws java.io.IOException; - method public abstract java.nio.channels.MembershipKey join(java.net.InetAddress, java.net.NetworkInterface) throws java.io.IOException; - method public abstract java.nio.channels.MembershipKey join(java.net.InetAddress, java.net.NetworkInterface, java.net.InetAddress) throws java.io.IOException; - } - public abstract interface NetworkChannel implements java.nio.channels.Channel { method public abstract java.nio.channels.NetworkChannel bind(java.net.SocketAddress) throws java.io.IOException; method public abstract java.net.SocketAddress getLocalAddress() throws java.io.IOException; @@ -56336,10 +56245,6 @@ package java.nio.channels { method public final int validOps(); } - public class ReadPendingException extends java.lang.IllegalStateException { - ctor public ReadPendingException(); - } - public abstract interface ReadableByteChannel implements java.nio.channels.Channel { method public abstract int read(java.nio.ByteBuffer) throws java.io.IOException; } @@ -56417,10 +56322,6 @@ package java.nio.channels { method public final int validOps(); } - public class ShutdownChannelGroupException extends java.lang.IllegalStateException { - ctor public ShutdownChannelGroupException(); - } - public abstract class SocketChannel extends java.nio.channels.spi.AbstractSelectableChannel implements java.nio.channels.ByteChannel java.nio.channels.GatheringByteChannel java.nio.channels.NetworkChannel java.nio.channels.ScatteringByteChannel { ctor protected SocketChannel(java.nio.channels.spi.SelectorProvider); method public abstract java.nio.channels.SocketChannel bind(java.net.SocketAddress) throws java.io.IOException; @@ -56456,10 +56357,6 @@ package java.nio.channels { method public abstract int write(java.nio.ByteBuffer) throws java.io.IOException; } - public class WritePendingException extends java.lang.IllegalStateException { - ctor public WritePendingException(); - } - } package java.nio.channels.spi { @@ -56506,15 +56403,6 @@ package java.nio.channels.spi { method protected abstract java.nio.channels.SelectionKey register(java.nio.channels.spi.AbstractSelectableChannel, int, java.lang.Object); } - public abstract class AsynchronousChannelProvider { - ctor protected AsynchronousChannelProvider(); - method public abstract java.nio.channels.AsynchronousChannelGroup openAsynchronousChannelGroup(int, java.util.concurrent.ThreadFactory) throws java.io.IOException; - method public abstract java.nio.channels.AsynchronousChannelGroup openAsynchronousChannelGroup(java.util.concurrent.ExecutorService, int) throws java.io.IOException; - method public abstract java.nio.channels.AsynchronousServerSocketChannel openAsynchronousServerSocketChannel(java.nio.channels.AsynchronousChannelGroup) throws java.io.IOException; - method public abstract java.nio.channels.AsynchronousSocketChannel openAsynchronousSocketChannel(java.nio.channels.AsynchronousChannelGroup) throws java.io.IOException; - method public static java.nio.channels.spi.AsynchronousChannelProvider provider(); - } - public abstract class SelectorProvider { ctor protected SelectorProvider(); method public java.nio.channels.Channel inheritedChannel() throws java.io.IOException; @@ -59869,13 +59757,11 @@ package java.text { method public static final java.text.DecimalFormatSymbols getInstance(java.util.Locale); method public java.lang.String getInternationalCurrencySymbol(); method public char getMinusSign(); - method public java.lang.String getMinusSignString(); method public char getMonetaryDecimalSeparator(); method public java.lang.String getNaN(); method public char getPatternSeparator(); method public char getPerMill(); method public char getPercent(); - method public java.lang.String getPercentString(); method public char getZeroDigit(); method public void setCurrency(java.util.Currency); method public void setCurrencySymbol(java.lang.String); diff --git a/api/test-current.txt b/api/test-current.txt index 675b9938dc83..d202108f5326 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -8517,6 +8517,7 @@ package android.content { field public static final java.lang.String ACTION_MANAGED_PROFILE_ADDED = "android.intent.action.MANAGED_PROFILE_ADDED"; field public static final java.lang.String ACTION_MANAGED_PROFILE_AVAILABILITY_CHANGED = "android.intent.action.MANAGED_PROFILE_AVAILABILITY_CHANGED"; field public static final java.lang.String ACTION_MANAGED_PROFILE_REMOVED = "android.intent.action.MANAGED_PROFILE_REMOVED"; + field public static final java.lang.String ACTION_MANAGED_PROFILE_UNLOCKED = "android.intent.action.MANAGED_PROFILE_UNLOCKED"; field public static final java.lang.String ACTION_MANAGE_NETWORK_USAGE = "android.intent.action.MANAGE_NETWORK_USAGE"; field public static final java.lang.String ACTION_MANAGE_PACKAGE_STORAGE = "android.intent.action.MANAGE_PACKAGE_STORAGE"; field public static final java.lang.String ACTION_MEDIA_BAD_REMOVAL = "android.intent.action.MEDIA_BAD_REMOVAL"; @@ -19229,7 +19230,6 @@ package android.location { method public double getElevationUncertaintyInDeg(); method public byte getLossOfLock(); method public byte getMultipathIndicator(); - method public byte getPrn(); method public double getPseudorangeInMeters(); method public double getPseudorangeRateCarrierInMetersPerSec(); method public double getPseudorangeRateCarrierUncertaintyInMetersPerSec(); @@ -19240,6 +19240,7 @@ package android.location { method public long getReceivedGpsTowUncertaintyInNs(); method public double getSnrInDb(); method public short getState(); + method public short getSvid(); method public short getTimeFromLastBitInMs(); method public double getTimeOffsetInNs(); method public boolean hasAzimuthInDeg(); @@ -19299,7 +19300,6 @@ package android.location { method public void setElevationUncertaintyInDeg(double); method public void setLossOfLock(byte); method public void setMultipathIndicator(byte); - method public void setPrn(byte); method public void setPseudorangeInMeters(double); method public void setPseudorangeRateCarrierInMetersPerSec(double); method public void setPseudorangeRateCarrierUncertaintyInMetersPerSec(double); @@ -19310,6 +19310,7 @@ package android.location { method public void setReceivedGpsTowUncertaintyInNs(long); method public void setSnrInDb(double); method public void setState(short); + method public void setSvid(short); method public void setTimeFromLastBitInMs(short); method public void setTimeOffsetInNs(double); method public void setUsedInFix(boolean); @@ -19364,17 +19365,17 @@ package android.location { method public int describeContents(); method public byte[] getData(); method public short getMessageId(); - method public byte getPrn(); method public short getStatus(); method public short getSubmessageId(); + method public short getSvid(); method public byte getType(); method public void reset(); method public void set(android.location.GnssNavigationMessage); method public void setData(byte[]); method public void setMessageId(short); - method public void setPrn(byte); method public void setStatus(short); method public void setSubmessageId(short); + method public void setSvid(short); method public void setType(byte); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.location.GnssNavigationMessage> CREATOR; @@ -19420,8 +19421,8 @@ package android.location { method public int getConstellationType(int); method public float getElevation(int); method public int getNumSatellites(); - method public int getPrn(int); method public float getSnr(int); + method public int getSvid(int); method public boolean hasAlmanac(int); method public boolean hasEphemeris(int); method public boolean usedInFix(int); @@ -22959,10 +22960,8 @@ package android.media.tv { field public static final int INPUT_STATE_CONNECTED_STANDBY = 1; // 0x1 field public static final int INPUT_STATE_DISCONNECTED = 2; // 0x2 field public static final java.lang.String META_DATA_CONTENT_RATING_SYSTEMS = "android.media.tv.metadata.CONTENT_RATING_SYSTEMS"; - field public static final int RECORDING_ERROR_CONNECTION_FAILED = 1; // 0x1 - field public static final int RECORDING_ERROR_DISCONNECTED = 2; // 0x2 - field public static final int RECORDING_ERROR_INSUFFICIENT_SPACE = 3; // 0x3 - field public static final int RECORDING_ERROR_RESOURCE_BUSY = 4; // 0x4 + field public static final int RECORDING_ERROR_INSUFFICIENT_SPACE = 1; // 0x1 + field public static final int RECORDING_ERROR_RESOURCE_BUSY = 2; // 0x2 field public static final int RECORDING_ERROR_UNKNOWN = 0; // 0x0 field public static final long TIME_SHIFT_INVALID_TIME = -9223372036854775808L; // 0x8000000000000000L field public static final int TIME_SHIFT_STATUS_AVAILABLE = 3; // 0x3 @@ -23062,6 +23061,8 @@ package android.media.tv { public static abstract class TvRecordingClient.RecordingCallback { ctor public TvRecordingClient.RecordingCallback(); + method public void onConnectionFailed(java.lang.String); + method public void onDisconnected(java.lang.String); method public void onError(int); method public void onRecordingStopped(android.net.Uri); method public void onTuned(); @@ -23076,6 +23077,7 @@ package android.media.tv { method public final java.lang.String getId(); method public final java.lang.String getLanguage(); method public final int getType(); + method public final byte getVideoActiveFormatDescription(); method public final float getVideoFrameRate(); method public final int getVideoHeight(); method public final float getVideoPixelAspectRatio(); @@ -23095,6 +23097,7 @@ package android.media.tv { method public final android.media.tv.TvTrackInfo.Builder setDescription(java.lang.CharSequence); method public final android.media.tv.TvTrackInfo.Builder setExtra(android.os.Bundle); method public final android.media.tv.TvTrackInfo.Builder setLanguage(java.lang.String); + method public final android.media.tv.TvTrackInfo.Builder setVideoActiveFormatDescription(byte); method public final android.media.tv.TvTrackInfo.Builder setVideoFrameRate(float); method public final android.media.tv.TvTrackInfo.Builder setVideoHeight(int); method public final android.media.tv.TvTrackInfo.Builder setVideoPixelAspectRatio(float); @@ -29071,6 +29074,7 @@ package android.os { method public static final int getThreadPriority(int) throws java.lang.IllegalArgumentException; method public static final int getUidForName(java.lang.String); method public static final boolean is64Bit(); + method public static boolean isApplicationUid(int); method public static final void killProcess(int); method public static final int myPid(); method public static final int myTid(); @@ -29252,6 +29256,7 @@ package android.os { ctor public UserHandle(android.os.Parcel); method public int describeContents(); method public static int getAppId(int); + method public static android.os.UserHandle getUserHandleForUid(int); method public static android.os.UserHandle readFromParcel(android.os.Parcel); method public void writeToParcel(android.os.Parcel, int); method public static void writeToParcel(android.os.UserHandle, android.os.Parcel); @@ -51491,7 +51496,6 @@ package java.lang.reflect { method public static int getLength(java.lang.Object); method public static long getLong(java.lang.Object, int) throws java.lang.ArrayIndexOutOfBoundsException, java.lang.IllegalArgumentException; method public static short getShort(java.lang.Object, int) throws java.lang.ArrayIndexOutOfBoundsException, java.lang.IllegalArgumentException; - method public static java.lang.Object newArray(java.lang.Class<?>, int) throws java.lang.NegativeArraySizeException; method public static java.lang.Object newInstance(java.lang.Class<?>, int) throws java.lang.NegativeArraySizeException; method public static java.lang.Object newInstance(java.lang.Class<?>, int...) throws java.lang.IllegalArgumentException, java.lang.NegativeArraySizeException; method public static void set(java.lang.Object, int, java.lang.Object) throws java.lang.ArrayIndexOutOfBoundsException, java.lang.IllegalArgumentException; @@ -52167,7 +52171,6 @@ package java.net { public class InetAddress implements java.io.Serializable { method public byte[] getAddress(); - method public byte[] getAddressInternal(); method public static java.net.InetAddress[] getAllByName(java.lang.String) throws java.net.UnknownHostException; method public static java.net.InetAddress getByAddress(java.lang.String, byte[]) throws java.net.UnknownHostException; method public static java.net.InetAddress getByAddress(byte[]) throws java.net.UnknownHostException; @@ -52441,7 +52444,7 @@ package java.net { method protected abstract void connect(java.net.InetAddress, int) throws java.io.IOException; method protected abstract void connect(java.net.SocketAddress, int) throws java.io.IOException; method protected abstract void create(boolean) throws java.io.IOException; - method public java.io.FileDescriptor getFileDescriptor(); + method protected java.io.FileDescriptor getFileDescriptor(); method protected java.net.InetAddress getInetAddress(); method protected abstract java.io.InputStream getInputStream() throws java.io.IOException; method protected int getLocalPort(); @@ -52983,10 +52986,6 @@ package java.nio { package java.nio.channels { - public class AcceptPendingException extends java.lang.IllegalStateException { - ctor public AcceptPendingException(); - } - public class AlreadyBoundException extends java.lang.IllegalStateException { ctor public AlreadyBoundException(); } @@ -52995,68 +52994,10 @@ package java.nio.channels { ctor public AlreadyConnectedException(); } - public abstract interface AsynchronousByteChannel implements java.nio.channels.AsynchronousChannel { - method public abstract void read(java.nio.ByteBuffer, A, java.nio.channels.CompletionHandler<java.lang.Integer, ? super A>); - method public abstract java.util.concurrent.Future<java.lang.Integer> read(java.nio.ByteBuffer); - method public abstract void write(java.nio.ByteBuffer, A, java.nio.channels.CompletionHandler<java.lang.Integer, ? super A>); - method public abstract java.util.concurrent.Future<java.lang.Integer> write(java.nio.ByteBuffer); - } - - public abstract interface AsynchronousChannel implements java.nio.channels.Channel { - method public abstract void close() throws java.io.IOException; - } - - public abstract class AsynchronousChannelGroup { - ctor protected AsynchronousChannelGroup(java.nio.channels.spi.AsynchronousChannelProvider); - method public abstract boolean awaitTermination(long, java.util.concurrent.TimeUnit) throws java.lang.InterruptedException; - method public abstract boolean isShutdown(); - method public abstract boolean isTerminated(); - method public final java.nio.channels.spi.AsynchronousChannelProvider provider(); - method public abstract void shutdown(); - method public abstract void shutdownNow() throws java.io.IOException; - method public static java.nio.channels.AsynchronousChannelGroup withCachedThreadPool(java.util.concurrent.ExecutorService, int) throws java.io.IOException; - method public static java.nio.channels.AsynchronousChannelGroup withFixedThreadPool(int, java.util.concurrent.ThreadFactory) throws java.io.IOException; - method public static java.nio.channels.AsynchronousChannelGroup withThreadPool(java.util.concurrent.ExecutorService) throws java.io.IOException; - } - public class AsynchronousCloseException extends java.nio.channels.ClosedChannelException { ctor public AsynchronousCloseException(); } - public abstract class AsynchronousServerSocketChannel implements java.nio.channels.AsynchronousChannel java.nio.channels.NetworkChannel { - ctor protected AsynchronousServerSocketChannel(java.nio.channels.spi.AsynchronousChannelProvider); - method public abstract void accept(A, java.nio.channels.CompletionHandler<java.nio.channels.AsynchronousSocketChannel, ? super A>); - method public abstract java.util.concurrent.Future<java.nio.channels.AsynchronousSocketChannel> accept(); - method public final java.nio.channels.AsynchronousServerSocketChannel bind(java.net.SocketAddress) throws java.io.IOException; - method public abstract java.nio.channels.AsynchronousServerSocketChannel bind(java.net.SocketAddress, int) throws java.io.IOException; - method public static java.nio.channels.AsynchronousServerSocketChannel open(java.nio.channels.AsynchronousChannelGroup) throws java.io.IOException; - method public static java.nio.channels.AsynchronousServerSocketChannel open() throws java.io.IOException; - method public final java.nio.channels.spi.AsynchronousChannelProvider provider(); - method public abstract java.nio.channels.AsynchronousServerSocketChannel setOption(java.net.SocketOption<T>, T) throws java.io.IOException; - } - - public abstract class AsynchronousSocketChannel implements java.nio.channels.AsynchronousByteChannel java.nio.channels.NetworkChannel { - ctor protected AsynchronousSocketChannel(java.nio.channels.spi.AsynchronousChannelProvider); - method public abstract java.nio.channels.AsynchronousSocketChannel bind(java.net.SocketAddress) throws java.io.IOException; - method public abstract void connect(java.net.SocketAddress, A, java.nio.channels.CompletionHandler<java.lang.Void, ? super A>); - method public abstract java.util.concurrent.Future<java.lang.Void> connect(java.net.SocketAddress); - method public abstract java.net.SocketAddress getRemoteAddress() throws java.io.IOException; - method public static java.nio.channels.AsynchronousSocketChannel open(java.nio.channels.AsynchronousChannelGroup) throws java.io.IOException; - method public static java.nio.channels.AsynchronousSocketChannel open() throws java.io.IOException; - method public final java.nio.channels.spi.AsynchronousChannelProvider provider(); - method public abstract void read(java.nio.ByteBuffer, long, java.util.concurrent.TimeUnit, A, java.nio.channels.CompletionHandler<java.lang.Integer, ? super A>); - method public final void read(java.nio.ByteBuffer, A, java.nio.channels.CompletionHandler<java.lang.Integer, ? super A>); - method public abstract java.util.concurrent.Future<java.lang.Integer> read(java.nio.ByteBuffer); - method public abstract void read(java.nio.ByteBuffer[], int, int, long, java.util.concurrent.TimeUnit, A, java.nio.channels.CompletionHandler<java.lang.Long, ? super A>); - method public abstract java.nio.channels.AsynchronousSocketChannel setOption(java.net.SocketOption<T>, T) throws java.io.IOException; - method public abstract java.nio.channels.AsynchronousSocketChannel shutdownInput() throws java.io.IOException; - method public abstract java.nio.channels.AsynchronousSocketChannel shutdownOutput() throws java.io.IOException; - method public abstract void write(java.nio.ByteBuffer, long, java.util.concurrent.TimeUnit, A, java.nio.channels.CompletionHandler<java.lang.Integer, ? super A>); - method public final void write(java.nio.ByteBuffer, A, java.nio.channels.CompletionHandler<java.lang.Integer, ? super A>); - method public abstract java.util.concurrent.Future<java.lang.Integer> write(java.nio.ByteBuffer); - method public abstract void write(java.nio.ByteBuffer[], int, int, long, java.util.concurrent.TimeUnit, A, java.nio.channels.CompletionHandler<java.lang.Long, ? super A>); - } - public abstract interface ByteChannel implements java.nio.channels.ReadableByteChannel java.nio.channels.WritableByteChannel { } @@ -53073,9 +53014,7 @@ package java.nio.channels { method public static java.nio.channels.ReadableByteChannel newChannel(java.io.InputStream); method public static java.nio.channels.WritableByteChannel newChannel(java.io.OutputStream); method public static java.io.InputStream newInputStream(java.nio.channels.ReadableByteChannel); - method public static java.io.InputStream newInputStream(java.nio.channels.AsynchronousByteChannel); method public static java.io.OutputStream newOutputStream(java.nio.channels.WritableByteChannel); - method public static java.io.OutputStream newOutputStream(java.nio.channels.AsynchronousByteChannel); method public static java.io.Reader newReader(java.nio.channels.ReadableByteChannel, java.nio.charset.CharsetDecoder, int); method public static java.io.Reader newReader(java.nio.channels.ReadableByteChannel, java.lang.String); method public static java.io.Writer newWriter(java.nio.channels.WritableByteChannel, java.nio.charset.CharsetEncoder, int); @@ -53094,16 +53033,11 @@ package java.nio.channels { ctor public ClosedSelectorException(); } - public abstract interface CompletionHandler { - method public abstract void completed(V, A); - method public abstract void failed(java.lang.Throwable, A); - } - public class ConnectionPendingException extends java.lang.IllegalStateException { ctor public ConnectionPendingException(); } - public abstract class DatagramChannel extends java.nio.channels.spi.AbstractSelectableChannel implements java.nio.channels.ByteChannel java.nio.channels.GatheringByteChannel java.nio.channels.MulticastChannel java.nio.channels.ScatteringByteChannel { + public abstract class DatagramChannel extends java.nio.channels.spi.AbstractSelectableChannel implements java.nio.channels.ByteChannel java.nio.channels.GatheringByteChannel java.nio.channels.ScatteringByteChannel { ctor protected DatagramChannel(java.nio.channels.spi.SelectorProvider); method public abstract java.nio.channels.DatagramChannel bind(java.net.SocketAddress) throws java.io.IOException; method public abstract java.nio.channels.DatagramChannel connect(java.net.SocketAddress) throws java.io.IOException; @@ -53182,40 +53116,14 @@ package java.nio.channels { ctor public IllegalBlockingModeException(); } - public class IllegalChannelGroupException extends java.lang.IllegalArgumentException { - ctor public IllegalChannelGroupException(); - } - public class IllegalSelectorException extends java.lang.IllegalArgumentException { ctor public IllegalSelectorException(); } - public class InterruptedByTimeoutException extends java.io.IOException { - ctor public InterruptedByTimeoutException(); - } - public abstract interface InterruptibleChannel implements java.nio.channels.Channel { method public abstract void close() throws java.io.IOException; } - public abstract class MembershipKey { - ctor protected MembershipKey(); - method public abstract java.nio.channels.MembershipKey block(java.net.InetAddress) throws java.io.IOException; - method public abstract java.nio.channels.MulticastChannel channel(); - method public abstract void drop(); - method public abstract java.net.InetAddress group(); - method public abstract boolean isValid(); - method public abstract java.net.NetworkInterface networkInterface(); - method public abstract java.net.InetAddress sourceAddress(); - method public abstract java.nio.channels.MembershipKey unblock(java.net.InetAddress); - } - - public abstract interface MulticastChannel implements java.nio.channels.NetworkChannel { - method public abstract void close() throws java.io.IOException; - method public abstract java.nio.channels.MembershipKey join(java.net.InetAddress, java.net.NetworkInterface) throws java.io.IOException; - method public abstract java.nio.channels.MembershipKey join(java.net.InetAddress, java.net.NetworkInterface, java.net.InetAddress) throws java.io.IOException; - } - public abstract interface NetworkChannel implements java.nio.channels.Channel { method public abstract java.nio.channels.NetworkChannel bind(java.net.SocketAddress) throws java.io.IOException; method public abstract java.net.SocketAddress getLocalAddress() throws java.io.IOException; @@ -53265,10 +53173,6 @@ package java.nio.channels { method public final int validOps(); } - public class ReadPendingException extends java.lang.IllegalStateException { - ctor public ReadPendingException(); - } - public abstract interface ReadableByteChannel implements java.nio.channels.Channel { method public abstract int read(java.nio.ByteBuffer) throws java.io.IOException; } @@ -53346,10 +53250,6 @@ package java.nio.channels { method public final int validOps(); } - public class ShutdownChannelGroupException extends java.lang.IllegalStateException { - ctor public ShutdownChannelGroupException(); - } - public abstract class SocketChannel extends java.nio.channels.spi.AbstractSelectableChannel implements java.nio.channels.ByteChannel java.nio.channels.GatheringByteChannel java.nio.channels.NetworkChannel java.nio.channels.ScatteringByteChannel { ctor protected SocketChannel(java.nio.channels.spi.SelectorProvider); method public abstract java.nio.channels.SocketChannel bind(java.net.SocketAddress) throws java.io.IOException; @@ -53385,10 +53285,6 @@ package java.nio.channels { method public abstract int write(java.nio.ByteBuffer) throws java.io.IOException; } - public class WritePendingException extends java.lang.IllegalStateException { - ctor public WritePendingException(); - } - } package java.nio.channels.spi { @@ -53435,15 +53331,6 @@ package java.nio.channels.spi { method protected abstract java.nio.channels.SelectionKey register(java.nio.channels.spi.AbstractSelectableChannel, int, java.lang.Object); } - public abstract class AsynchronousChannelProvider { - ctor protected AsynchronousChannelProvider(); - method public abstract java.nio.channels.AsynchronousChannelGroup openAsynchronousChannelGroup(int, java.util.concurrent.ThreadFactory) throws java.io.IOException; - method public abstract java.nio.channels.AsynchronousChannelGroup openAsynchronousChannelGroup(java.util.concurrent.ExecutorService, int) throws java.io.IOException; - method public abstract java.nio.channels.AsynchronousServerSocketChannel openAsynchronousServerSocketChannel(java.nio.channels.AsynchronousChannelGroup) throws java.io.IOException; - method public abstract java.nio.channels.AsynchronousSocketChannel openAsynchronousSocketChannel(java.nio.channels.AsynchronousChannelGroup) throws java.io.IOException; - method public static java.nio.channels.spi.AsynchronousChannelProvider provider(); - } - public abstract class SelectorProvider { ctor protected SelectorProvider(); method public java.nio.channels.Channel inheritedChannel() throws java.io.IOException; @@ -56798,13 +56685,11 @@ package java.text { method public static final java.text.DecimalFormatSymbols getInstance(java.util.Locale); method public java.lang.String getInternationalCurrencySymbol(); method public char getMinusSign(); - method public java.lang.String getMinusSignString(); method public char getMonetaryDecimalSeparator(); method public java.lang.String getNaN(); method public char getPatternSeparator(); method public char getPerMill(); method public char getPercent(); - method public java.lang.String getPercentString(); method public char getZeroDigit(); method public void setCurrency(java.util.Currency); method public void setCurrencySymbol(java.lang.String); diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 622012ecf2e2..ea58e292e545 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -16,6 +16,8 @@ package android.app; +import static java.lang.Character.MIN_VALUE; + import android.annotation.CallSuper; import android.annotation.DrawableRes; import android.annotation.IdRes; @@ -26,20 +28,6 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.StyleRes; -import android.os.PersistableBundle; -import android.transition.Scene; -import android.transition.TransitionManager; -import android.util.ArrayMap; -import android.util.SuperNotCalledException; -import android.view.DragEvent; -import android.view.DropPermissions; -import android.view.Window.WindowControllerCallback; -import android.widget.Toolbar; - -import com.android.internal.app.IVoiceInteractor; -import com.android.internal.app.WindowDecorActionBar; -import com.android.internal.app.ToolbarActionBar; - import android.annotation.SystemApi; import android.app.admin.DevicePolicyManager; import android.app.assist.AssistContent; @@ -61,7 +49,12 @@ import android.content.res.TypedArray; import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; +import android.graphics.drawable.InsetDrawable; +import android.graphics.drawable.LayerDrawable; +import android.graphics.drawable.ShapeDrawable; import android.media.AudioManager; import android.media.session.MediaController; import android.net.Uri; @@ -71,6 +64,7 @@ import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.Parcelable; +import android.os.PersistableBundle; import android.os.RemoteException; import android.os.StrictMode; import android.os.UserHandle; @@ -78,16 +72,22 @@ import android.text.Selection; import android.text.SpannableStringBuilder; import android.text.TextUtils; import android.text.method.TextKeyListener; +import android.transition.Scene; +import android.transition.TransitionManager; +import android.util.ArrayMap; import android.util.AttributeSet; import android.util.EventLog; import android.util.Log; import android.util.PrintWriterPrinter; import android.util.Slog; import android.util.SparseArray; +import android.util.SuperNotCalledException; import android.view.ActionMode; import android.view.ContextMenu; import android.view.ContextMenu.ContextMenuInfo; import android.view.ContextThemeWrapper; +import android.view.DragEvent; +import android.view.DropPermissions; import android.view.KeyEvent; import android.view.KeyboardShortcutGroup; import android.view.KeyboardShortcutInfo; @@ -104,11 +104,17 @@ import android.view.ViewGroup.LayoutParams; import android.view.ViewManager; import android.view.ViewRootImpl; import android.view.Window; +import android.view.Window.WindowControllerCallback; import android.view.WindowManager; import android.view.WindowManagerGlobal; import android.view.accessibility.AccessibilityEvent; import android.widget.AdapterView; +import android.widget.Toolbar; +import com.android.internal.app.IVoiceInteractor; +import com.android.internal.app.ToolbarActionBar; +import com.android.internal.app.WindowDecorActionBar; +import com.android.internal.policy.DecorView; import com.android.internal.policy.PhoneWindow; import java.io.FileDescriptor; @@ -119,8 +125,6 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; -import static java.lang.Character.MIN_VALUE; - /** * An activity is a single, focused thing that the user can do. Almost all * activities interact with the user, so the Activity class takes care of @@ -3974,14 +3978,49 @@ public class Activity extends ContextThemeWrapper // Get the primary color and update the TaskDescription for this activity if (theme != null) { TypedArray a = theme.obtainStyledAttributes(com.android.internal.R.styleable.Theme); + int windowBgResourceId = a.getResourceId( + com.android.internal.R.styleable.Window_windowBackground, 0); + int windowBgFallbackResourceId = a.getResourceId( + com.android.internal.R.styleable.Window_windowBackgroundFallback, 0); int colorPrimary = a.getColor(com.android.internal.R.styleable.Theme_colorPrimary, 0); + int colorBg = tryExtractColorFromDrawable(DecorView.getResizingBackgroundDrawable(this, + windowBgResourceId, windowBgFallbackResourceId)); a.recycle(); if (colorPrimary != 0) { - ActivityManager.TaskDescription v = new ActivityManager.TaskDescription(null, null, - colorPrimary); - setTaskDescription(v); + ActivityManager.TaskDescription td = new ActivityManager.TaskDescription(); + td.setPrimaryColor(colorPrimary); + td.setBackgroundColor(colorBg); + setTaskDescription(td); + } + } + } + + /** + * Attempts to extract the color from a given drawable. + * + * @return the extracted color or 0 if no color could be extracted. + */ + private int tryExtractColorFromDrawable(Drawable drawable) { + if (drawable instanceof ColorDrawable) { + return ((ColorDrawable) drawable).getColor(); + } else if (drawable instanceof InsetDrawable) { + return tryExtractColorFromDrawable(((InsetDrawable) drawable).getDrawable()); + } else if (drawable instanceof ShapeDrawable) { + Paint p = ((ShapeDrawable) drawable).getPaint(); + if (p != null) { + return p.getColor(); + } + } else if (drawable instanceof LayerDrawable) { + LayerDrawable ld = (LayerDrawable) drawable; + int numLayers = ld.getNumberOfLayers(); + for (int i = 0; i < numLayers; i++) { + int color = tryExtractColorFromDrawable(ld.getDrawable(i)); + if (color != 0) { + return color; + } } } + return 0; } /** @@ -5612,8 +5651,8 @@ public class Activity extends ContextThemeWrapper if (taskDescription.getIconFilename() == null && taskDescription.getIcon() != null) { final int size = ActivityManager.getLauncherLargeIconSizeInner(this); final Bitmap icon = Bitmap.createScaledBitmap(taskDescription.getIcon(), size, size, true); - td = new ActivityManager.TaskDescription(taskDescription.getLabel(), icon, - taskDescription.getPrimaryColor()); + td = new ActivityManager.TaskDescription(taskDescription); + td.setIcon(icon); } else { td = taskDescription; } diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index 1eb2fe2c89ec..dd73261917e7 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -632,6 +632,17 @@ public class ActivityManager { public static boolean useWindowFrameForBackdrop(int stackId) { return stackId == FREEFORM_WORKSPACE_STACK_ID || stackId == PINNED_STACK_ID; } + + /** + * Returns true if a window from the specified stack with {@param stackId} are normally + * fullscreen, i. e. they can become the top opaque fullscreen window, meaning that it + * controls system bars, lockscreen occluded/dismissing state, screen rotation animation, + * etc. + */ + public static boolean normallyFullscreenWindows(int stackId) { + return stackId != PINNED_STACK_ID && stackId != FREEFORM_WORKSPACE_STACK_ID + && stackId != DOCKED_STACK_ID; + } } /** @@ -863,8 +874,10 @@ public class ActivityManager { public static final String ATTR_TASKDESCRIPTION_PREFIX = "task_description_"; private static final String ATTR_TASKDESCRIPTIONLABEL = ATTR_TASKDESCRIPTION_PREFIX + "label"; - private static final String ATTR_TASKDESCRIPTIONCOLOR = + private static final String ATTR_TASKDESCRIPTIONCOLOR_PRIMARY = ATTR_TASKDESCRIPTION_PREFIX + "color"; + private static final String ATTR_TASKDESCRIPTIONCOLOR_BACKGROUND = + ATTR_TASKDESCRIPTION_PREFIX + "colorBackground"; private static final String ATTR_TASKDESCRIPTIONICONFILENAME = ATTR_TASKDESCRIPTION_PREFIX + "icon_filename"; @@ -872,28 +885,21 @@ public class ActivityManager { private Bitmap mIcon; private String mIconFilename; private int mColorPrimary; + private int mColorBackground; /** * Creates the TaskDescription to the specified values. * * @param label A label and description of the current state of this task. * @param icon An icon that represents the current state of this task. - * @param colorPrimary A color to override the theme's primary color. This color must be opaque. + * @param colorPrimary A color to override the theme's primary color. This color must be + * opaque. */ public TaskDescription(String label, Bitmap icon, int colorPrimary) { + this(label, icon, null, colorPrimary, 0); if ((colorPrimary != 0) && (Color.alpha(colorPrimary) != 255)) { throw new RuntimeException("A TaskDescription's primary color should be opaque"); } - - mLabel = label; - mIcon = icon; - mColorPrimary = colorPrimary; - } - - /** @hide */ - public TaskDescription(String label, int colorPrimary, String iconFilename) { - this(label, null, colorPrimary); - mIconFilename = iconFilename; } /** @@ -903,7 +909,7 @@ public class ActivityManager { * @param icon An icon that represents the current state of this activity. */ public TaskDescription(String label, Bitmap icon) { - this(label, icon, 0); + this(label, icon, null, 0, 0); } /** @@ -912,14 +918,24 @@ public class ActivityManager { * @param label A label and description of the current state of this activity. */ public TaskDescription(String label) { - this(label, null, 0); + this(label, null, null, 0, 0); } /** * Creates an empty TaskDescription. */ public TaskDescription() { - this(null, null, 0); + this(null, null, null, 0, 0); + } + + /** @hide */ + public TaskDescription(String label, Bitmap icon, String iconFilename, int colorPrimary, + int colorBackground) { + mLabel = label; + mIcon = icon; + mIconFilename = iconFilename; + mColorPrimary = colorPrimary; + mColorBackground = colorBackground; } /** @@ -928,8 +944,9 @@ public class ActivityManager { public TaskDescription(TaskDescription td) { mLabel = td.mLabel; mIcon = td.mIcon; - mColorPrimary = td.mColorPrimary; mIconFilename = td.mIconFilename; + mColorPrimary = td.mColorPrimary; + mColorBackground = td.mColorBackground; } private TaskDescription(Parcel source) { @@ -957,6 +974,18 @@ public class ActivityManager { } /** + * Sets the background color for this task description. + * @hide + */ + public void setBackgroundColor(int backgroundColor) { + // Ensure that the given color is valid + if ((backgroundColor != 0) && (Color.alpha(backgroundColor) != 255)) { + throw new RuntimeException("A TaskDescription's background color should be opaque"); + } + mColorBackground = backgroundColor; + } + + /** * Sets the icon for this task description. * @hide */ @@ -1005,8 +1034,8 @@ public class ActivityManager { public static Bitmap loadTaskDescriptionIcon(String iconFilename, int userId) { if (iconFilename != null) { try { - return ActivityManagerNative.getDefault(). - getTaskDescriptionIcon(iconFilename, userId); + return ActivityManagerNative.getDefault().getTaskDescriptionIcon(iconFilename, + userId); } catch (RemoteException e) { } } @@ -1020,13 +1049,26 @@ public class ActivityManager { return mColorPrimary; } + /** + * @return The background color. + * @hide + */ + public int getBackgroundColor() { + return mColorBackground; + } + /** @hide */ public void saveToXml(XmlSerializer out) throws IOException { if (mLabel != null) { out.attribute(null, ATTR_TASKDESCRIPTIONLABEL, mLabel); } if (mColorPrimary != 0) { - out.attribute(null, ATTR_TASKDESCRIPTIONCOLOR, Integer.toHexString(mColorPrimary)); + out.attribute(null, ATTR_TASKDESCRIPTIONCOLOR_PRIMARY, + Integer.toHexString(mColorPrimary)); + } + if (mColorBackground != 0) { + out.attribute(null, ATTR_TASKDESCRIPTIONCOLOR_BACKGROUND, + Integer.toHexString(mColorBackground)); } if (mIconFilename != null) { out.attribute(null, ATTR_TASKDESCRIPTIONICONFILENAME, mIconFilename); @@ -1037,8 +1079,10 @@ public class ActivityManager { public void restoreFromXml(String attrName, String attrValue) { if (ATTR_TASKDESCRIPTIONLABEL.equals(attrName)) { setLabel(attrValue); - } else if (ATTR_TASKDESCRIPTIONCOLOR.equals(attrName)) { + } else if (ATTR_TASKDESCRIPTIONCOLOR_PRIMARY.equals(attrName)) { setPrimaryColor((int) Long.parseLong(attrValue, 16)); + } else if (ATTR_TASKDESCRIPTIONCOLOR_BACKGROUND.equals(attrName)) { + setBackgroundColor((int) Long.parseLong(attrValue, 16)); } else if (ATTR_TASKDESCRIPTIONICONFILENAME.equals(attrName)) { setIconFilename(attrValue); } @@ -1064,6 +1108,7 @@ public class ActivityManager { mIcon.writeToParcel(dest, 0); } dest.writeInt(mColorPrimary); + dest.writeInt(mColorBackground); if (mIconFilename == null) { dest.writeInt(0); } else { @@ -1076,6 +1121,7 @@ public class ActivityManager { mLabel = source.readInt() > 0 ? source.readString() : null; mIcon = source.readInt() > 0 ? Bitmap.CREATOR.createFromParcel(source) : null; mColorPrimary = source.readInt(); + mColorBackground = source.readInt(); mIconFilename = source.readInt() > 0 ? source.readString() : null; } @@ -1092,7 +1138,8 @@ public class ActivityManager { @Override public String toString() { return "TaskDescription Label: " + mLabel + " Icon: " + mIcon + - " colorPrimary: " + mColorPrimary; + " IconFilename: " + mIconFilename + " colorPrimary: " + mColorPrimary + + " colorBackground: " + mColorBackground; } } diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl index 0d35cf04b4a6..3c8dfcea7282 100644 --- a/core/java/android/app/INotificationManager.aidl +++ b/core/java/android/app/INotificationManager.aidl @@ -56,7 +56,7 @@ interface INotificationManager void setImportance(String pkg, int uid, in Notification.Topic topic, int importance); int getImportance(String pkg, int uid, in Notification.Topic topic); int getTopicImportance(String pkg, String topicId); - boolean doesAppUseTopics(String pkg, int uid); + boolean doesUserUseTopics(String pkg, int uid); boolean hasBannedTopics(String pkg, int uid); // TODO: Remove this when callers have been migrated to the equivalent diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 55c635360433..35b7c39338fc 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -3358,13 +3358,9 @@ public class Notification implements Parcelable return mN.bigContentView; } else if (mStyle != null) { result = mStyle.makeBigContentView(); - } else if (mActions.size() == 0) { - return null; - } - if (result == null) { - result = applyStandardTemplateWithActions(getBigBaseLayoutResource()); - } else { hideLine1Text(result); + } else if (mActions.size() != 0) { + result = applyStandardTemplateWithActions(getBigBaseLayoutResource()); } adaptNotificationHeaderForBigContentView(result); return result; @@ -3384,11 +3380,15 @@ public class Notification implements Parcelable } private void hideLine1Text(RemoteViews result) { - result.setViewVisibility(R.id.text_line_1, View.GONE); + if (result != null) { + result.setViewVisibility(R.id.text_line_1, View.GONE); + } } private void adaptNotificationHeaderForBigContentView(RemoteViews result) { - result.setBoolean(R.id.notification_header, "setExpanded", true); + if (result != null) { + result.setBoolean(R.id.notification_header, "setExpanded", true); + } } /** @@ -4326,6 +4326,15 @@ public class Notification implements Parcelable return makeMediaBigContentView(); } + /** + * @hide + */ + @Override + public RemoteViews makeHeadsUpContentView() { + RemoteViews expanded = makeMediaBigContentView(); + return expanded != null ? expanded : makeMediaContentView(); + } + /** @hide */ @Override public void addExtras(Bundle extras) { @@ -4407,6 +4416,13 @@ public class Notification implements Parcelable private RemoteViews makeMediaBigContentView() { final int actionCount = Math.min(mBuilder.mActions.size(), MAX_MEDIA_BUTTONS); + // Dont add an expanded view if there is no more content to be revealed + int actionsInCompact = mActionsToShowInCompact == null + ? 0 + : Math.min(mActionsToShowInCompact.length, MAX_MEDIA_BUTTONS_IN_COMPACT); + if (mBuilder.mN.mLargeIcon == null && actionCount <= actionsInCompact) { + return null; + } RemoteViews big = mBuilder.applyStandardTemplate( R.layout.notification_template_material_big_media, false); diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 83f9357d2909..f53170a77220 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -1826,9 +1826,23 @@ public class DevicePolicyManager { * this method; if it has not, a security exception will be thrown. */ public int getCurrentFailedPasswordAttempts() { + return getCurrentFailedPasswordAttempts(myUserId()); + } + + /** + * Retrieve the number of times the given user has failed at entering a + * password since that last successful password entry. + * + * <p>The calling device admin must have requested + * {@link DeviceAdminInfo#USES_POLICY_WATCH_LOGIN} to be able to call this method; if it has + * not and it is not the system uid, a security exception will be thrown. + * + * @hide + */ + public int getCurrentFailedPasswordAttempts(int userHandle) { if (mService != null) { try { - return mService.getCurrentFailedPasswordAttempts(myUserId(), mParentInstance); + return mService.getCurrentFailedPasswordAttempts(userHandle, mParentInstance); } catch (RemoteException e) { Log.w(TAG, REMOTE_EXCEPTION_MESSAGE, e); } @@ -3023,13 +3037,39 @@ public class DevicePolicyManager { } /** + * @hide + */ + public void reportFailedFingerprintAttempt(int userHandle) { + if (mService != null) { + try { + mService.reportFailedFingerprintAttempt(userHandle); + } catch (RemoteException e) { + Log.w(TAG, REMOTE_EXCEPTION_MESSAGE, e); + } + } + } + + /** + * @hide + */ + public void reportSuccessfulFingerprintAttempt(int userHandle) { + if (mService != null) { + try { + mService.reportSuccessfulFingerprintAttempt(userHandle); + } catch (RemoteException e) { + Log.w(TAG, REMOTE_EXCEPTION_MESSAGE, e); + } + } + } + + /** * Should be called when keyguard has been dismissed. * @hide */ - public void reportKeyguardDismissed() { + public void reportKeyguardDismissed(int userHandle) { if (mService != null) { try { - mService.reportKeyguardDismissed(); + mService.reportKeyguardDismissed(userHandle); } catch (RemoteException e) { Log.w(TAG, REMOTE_EXCEPTION_MESSAGE, e); } @@ -3040,10 +3080,10 @@ public class DevicePolicyManager { * Should be called when keyguard view has been shown to the user. * @hide */ - public void reportKeyguardSecured() { + public void reportKeyguardSecured(int userHandle) { if (mService != null) { try { - mService.reportKeyguardSecured(); + mService.reportKeyguardSecured(userHandle); } catch (RemoteException e) { Log.w(TAG, REMOTE_EXCEPTION_MESSAGE, e); } @@ -5730,4 +5770,32 @@ public class DevicePolicyManager { return false; } } + + /** + * @hide + * Returns whether the uninstall for {@code packageName} for the current user is in queue + * to be started + * @param packageName the package to check for + * @return whether the uninstall intent for {@code packageName} is pending + */ + public boolean isUninstallInQueue(String packageName) { + try { + return mService.isUninstallInQueue(packageName); + } catch (RemoteException re) { + Log.w(TAG, REMOTE_EXCEPTION_MESSAGE, re); + return false; + } + } + + /** + * @hide + * @param packageName the package containing active DAs to be uninstalled + */ + public void uninstallPackageWithActiveAdmins(String packageName) { + try { + mService.uninstallPackageWithActiveAdmins(packageName); + } catch (RemoteException re) { + Log.w(TAG, REMOTE_EXCEPTION_MESSAGE, re); + } + } } diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index c6a53443b51c..bd6818264448 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -116,9 +116,10 @@ interface IDevicePolicyManager { int numbers, int symbols, int nonletter, int userHandle); void reportFailedPasswordAttempt(int userHandle); void reportSuccessfulPasswordAttempt(int userHandle); - - void reportKeyguardDismissed(); - void reportKeyguardSecured(); + void reportFailedFingerprintAttempt(int userHandle); + void reportSuccessfulFingerprintAttempt(int userHandle); + void reportKeyguardDismissed(int userHandle); + void reportKeyguardSecured(int userHandle); boolean setDeviceOwner(in ComponentName who, String ownerName, int userId); ComponentName getDeviceOwnerComponent(boolean callingUserOnly); @@ -293,4 +294,7 @@ interface IDevicePolicyManager { boolean getDeviceLoggingEnabled(in ComponentName admin); ParceledListSlice retrieveDeviceLogs(in ComponentName admin); ParceledListSlice retrievePreviousDeviceLogs(in ComponentName admin); + + boolean isUninstallInQueue(String packageName); + void uninstallPackageWithActiveAdmins(String packageName); } diff --git a/core/java/android/auditing/SecurityLog.java b/core/java/android/auditing/SecurityLog.java index 8d8d2f59c677..f1703d644bb8 100644 --- a/core/java/android/auditing/SecurityLog.java +++ b/core/java/android/auditing/SecurityLog.java @@ -77,8 +77,10 @@ public class SecurityLog { SecurityLogTags.SECURITY_KEYGUARD_DISMISSED; /** * Indicate that there has been an authentication attempt to dismiss the keyguard. The log entry - * contains the attempt result (integer, 1 for successful, 0 for unsuccessful), accessible via - * {@link SecurityEvent#getData()}} + * contains the following information about the attempt in order, accessible via + * {@link SecurityEvent#getData()}}: attempt result (integer, 1 for successful, 0 for + * unsuccessful), strength of auth method (integer, 1 if strong auth method was used, + * 0 otherwise) */ public static final int TAG_KEYGUARD_DISMISS_AUTH_ATTEMPT = SecurityLogTags.SECURITY_KEYGUARD_DISMISS_AUTH_ATTEMPT; diff --git a/core/java/android/auditing/SecurityLogTags.logtags b/core/java/android/auditing/SecurityLogTags.logtags index cf858940058b..ccc37995972c 100644 --- a/core/java/android/auditing/SecurityLogTags.logtags +++ b/core/java/android/auditing/SecurityLogTags.logtags @@ -8,5 +8,5 @@ option java_package android.auditing 210004 security_adb_sync_send (path|3) 210005 security_app_process_start (process|3),(start_time|2|3),(uid|1),(pid|1),(seinfo|3),(sha256|3) 210006 security_keyguard_dismissed -210007 security_keyguard_dismiss_auth_attempt (success|1) +210007 security_keyguard_dismiss_auth_attempt (success|1),(method_strength|1) 210008 security_keyguard_secured diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 0f6f856ae12a..b476a25515f5 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -3028,6 +3028,17 @@ public class Intent implements Parcelable, Cloneable { "android.intent.action.MANAGED_PROFILE_REMOVED"; /** + * Broadcast sent to the primary user when the credential-encrypted private storage for + * an associated managed profile is unlocked. Carries an extra {@link #EXTRA_USER} that + * specifies the UserHandle of the profile that was unlocked. Only applications (for example + * Launchers) that need to display merged content across both primary and managed profiles + * need to worry about this broadcast. This is only sent to registered receivers, + * not manifest receivers. + */ + public static final String ACTION_MANAGED_PROFILE_UNLOCKED = + "android.intent.action.MANAGED_PROFILE_UNLOCKED"; + + /** * Broadcast sent to the primary user when an associated managed profile's availability has * changed. This includes when the user toggles the profile's quiet mode. Carries an extra * {@link #EXTRA_USER} that specifies the UserHandle of the profile. When quiet mode is changed, diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java index 43a0cc77d492..f58b16a7204f 100644 --- a/core/java/android/content/pm/ActivityInfo.java +++ b/core/java/android/content/pm/ActivityInfo.java @@ -178,6 +178,11 @@ public class ActivityInfo extends ComponentInfo */ public static final int RESIZE_MODE_RESIZEABLE_AND_PIPABLE = 3; /** + * Activity is does not support resizing, but we are forcing it to be resizeable. + * @hide + */ + public static final int RESIZE_MODE_FORCE_RESIZEABLE = 4; + /** * Value indicating if the resizing mode the activity supports. * See {@link android.R.attr#resizeableActivity}. * @hide @@ -786,7 +791,9 @@ public class ActivityInfo extends ComponentInfo /** @hide */ public static boolean isResizeableMode(int mode) { - return mode == RESIZE_MODE_RESIZEABLE || mode == RESIZE_MODE_RESIZEABLE_AND_PIPABLE; + return mode == RESIZE_MODE_RESIZEABLE + || mode == RESIZE_MODE_RESIZEABLE_AND_PIPABLE + || mode == RESIZE_MODE_FORCE_RESIZEABLE; } /** @hide */ @@ -800,6 +807,8 @@ public class ActivityInfo extends ComponentInfo return "RESIZE_MODE_RESIZEABLE"; case RESIZE_MODE_RESIZEABLE_AND_PIPABLE: return "RESIZE_MODE_RESIZEABLE_AND_PIPABLE"; + case RESIZE_MODE_FORCE_RESIZEABLE: + return "RESIZE_MODE_FORCE_RESIZEABLE"; default: return "unknown=" + mode; } diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java index c79dae5dd913..90824820d0eb 100644 --- a/core/java/android/content/pm/ApplicationInfo.java +++ b/core/java/android/content/pm/ApplicationInfo.java @@ -1038,10 +1038,10 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { } deviceEncryptedDataDir = Environment - .getDataUserDeviceEncryptedPackageDirectory(volumeUuid, userId, packageName) + .getDataUserDePackageDirectory(volumeUuid, userId, packageName) .getAbsolutePath(); credentialEncryptedDataDir = Environment - .getDataUserCredentialEncryptedPackageDirectory(volumeUuid, userId, packageName) + .getDataUserCePackageDirectory(volumeUuid, userId, packageName) .getAbsolutePath(); if ((privateFlags & PRIVATE_FLAG_FORCE_DEVICE_ENCRYPTED) != 0 diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index 5dddebd3cae7..5ae8d4cf0ce4 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -80,6 +80,7 @@ import libcore.io.IoUtils; import static android.content.pm.ActivityInfo.FLAG_ALWAYS_FOCUSABLE; import static android.content.pm.ActivityInfo.FLAG_IMMERSIVE; import static android.content.pm.ActivityInfo.RESIZE_MODE_CROP_WINDOWS; +import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZEABLE; import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE; import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE_AND_PIPABLE; import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE; @@ -3448,7 +3449,7 @@ public class PackageParser { a.info.resizeMode = RESIZE_MODE_UNRESIZEABLE; } else if (a.info.screenOrientation == SCREEN_ORIENTATION_UNSPECIFIED && (a.info.flags & FLAG_IMMERSIVE) == 0) { - a.info.resizeMode = RESIZE_MODE_CROP_WINDOWS; + a.info.resizeMode = RESIZE_MODE_FORCE_RESIZEABLE; } if (sa.getBoolean(R.styleable.AndroidManifestActivity_alwaysFocusable, false)) { diff --git a/core/java/android/hardware/camera2/CameraCaptureSession.java b/core/java/android/hardware/camera2/CameraCaptureSession.java index 766868da9193..8724a96ee30a 100644 --- a/core/java/android/hardware/camera2/CameraCaptureSession.java +++ b/core/java/android/hardware/camera2/CameraCaptureSession.java @@ -847,6 +847,9 @@ public abstract class CameraCaptureSession implements AutoCloseable { * to make forward progress from the partial results and avoid waiting for the completed * result.</p> * + * <p>For a particular request, {@link #onCaptureProgressed} may happen before or after + * {@link #onCaptureStarted}.</p> + * * <p>Each request will generate at least {@code 1} partial results, and at most * {@link CameraCharacteristics#REQUEST_PARTIAL_RESULT_COUNT} partial results.</p> * diff --git a/core/java/android/net/NetworkScoreManager.java b/core/java/android/net/NetworkScoreManager.java index 3f36d65e577c..b6fe68af51ac 100644 --- a/core/java/android/net/NetworkScoreManager.java +++ b/core/java/android/net/NetworkScoreManager.java @@ -105,7 +105,8 @@ public class NetworkScoreManager { /** * Broadcast action: the active scorer has been changed. Scorer apps may listen to this to * perform initialization once selected as the active scorer, or clean up unneeded resources - * if another scorer has been selected. Note that it is unnecessary to clear existing scores as + * if another scorer has been selected. This is an explicit broadcast only sent to the + * previous scorer and new scorer. Note that it is unnecessary to clear existing scores as * this is handled by the system. * * <p>The new scorer will be specified in {@link #EXTRA_NEW_SCORER}. diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java index e841dfe2b2a3..59bf2938cfce 100644 --- a/core/java/android/os/Environment.java +++ b/core/java/android/os/Environment.java @@ -176,35 +176,37 @@ public class Environment { return DIR_VENDOR_ROOT; } - /** {@hide} */ - @Deprecated - public static File getSystemSecureDirectory() { - return getDataSystemDirectory(); - } - - /** {@hide} */ - @Deprecated - public static File getSecureDataDirectory() { - return getDataDirectory(); - } - /** - * Return the system directory for a user. This is for use by system services to store - * files relating to the user. This directory will be automatically deleted when the user - * is removed. + * Return the system directory for a user. This is for use by system + * services to store files relating to the user. This directory will be + * automatically deleted when the user is removed. * + * @deprecated This directory is valid and still exists, but callers should + * <em>strongly</em> consider switching to + * {@link #getDataSystemCeDirectory(int)} which is protected + * with user credentials or + * {@link #getDataSystemDeDirectory(int)} which supports fast + * user wipe. * @hide */ + @Deprecated public static File getUserSystemDirectory(int userId) { return new File(new File(getDataSystemDirectory(), "users"), Integer.toString(userId)); } /** - * Returns the config directory for a user. This is for use by system services to store files - * relating to the user which should be readable by any app running as that user. + * Returns the config directory for a user. This is for use by system + * services to store files relating to the user which should be readable by + * any app running as that user. * + * @deprecated This directory is valid and still exists, but callers should + * <em>strongly</em> consider switching to + * {@link #getDataMiscCeDirectory(int)} which is protected with + * user credentials or {@link #getDataMiscDeDirectory(int)} + * which supports fast user wipe. * @hide */ + @Deprecated public static File getUserConfigDirectory(int userId) { return new File(new File(new File( getDataDirectory(), "misc"), "user"), Integer.toString(userId)); @@ -232,77 +234,72 @@ public class Environment { } /** {@hide} */ - public static File getDataSystemCredentialEncryptedDirectory() { - return new File(getDataDirectory(), "system_ce"); + public static File getDataSystemCeDirectory(int userId) { + return buildPath(getDataDirectory(), "system_ce", String.valueOf(userId)); } /** {@hide} */ - public static File getDataSystemCredentialEncryptedDirectory(int userId) { - return new File(getDataSystemCredentialEncryptedDirectory(), String.valueOf(userId)); + public static File getDataSystemDeDirectory(int userId) { + return buildPath(getDataDirectory(), "system_de", String.valueOf(userId)); } /** {@hide} */ - public static File getDataAppDirectory(String volumeUuid) { - return new File(getDataDirectory(volumeUuid), "app"); + public static File getDataMiscDirectory() { + return new File(getDataDirectory(), "misc"); } /** {@hide} */ - public static File getDataAppEphemeralDirectory(String volumeUuid) { - return new File(getDataDirectory(volumeUuid), "app-ephemeral"); + public static File getDataMiscCeDirectory(int userId) { + return buildPath(getDataDirectory(), "misc_ce", String.valueOf(userId)); } /** {@hide} */ - @Deprecated - public static File getDataUserDirectory(String volumeUuid) { - return getDataUserCredentialEncryptedDirectory(volumeUuid); + public static File getDataMiscDeDirectory(int userId) { + return buildPath(getDataDirectory(), "misc_de", String.valueOf(userId)); } /** {@hide} */ - @Deprecated - public static File getDataUserDirectory(String volumeUuid, int userId) { - return getDataUserCredentialEncryptedDirectory(volumeUuid, userId); + public static File getDataAppDirectory(String volumeUuid) { + return new File(getDataDirectory(volumeUuid), "app"); } /** {@hide} */ - @Deprecated - public static File getDataUserPackageDirectory(String volumeUuid, int userId, - String packageName) { - return getDataUserCredentialEncryptedPackageDirectory(volumeUuid, userId, packageName); + public static File getDataAppEphemeralDirectory(String volumeUuid) { + return new File(getDataDirectory(volumeUuid), "app-ephemeral"); } /** {@hide} */ - public static File getDataUserCredentialEncryptedDirectory(String volumeUuid) { + public static File getDataUserCeDirectory(String volumeUuid) { return new File(getDataDirectory(volumeUuid), "user"); } /** {@hide} */ - public static File getDataUserCredentialEncryptedDirectory(String volumeUuid, int userId) { - return new File(getDataUserCredentialEncryptedDirectory(volumeUuid), - String.valueOf(userId)); + public static File getDataUserCeDirectory(String volumeUuid, int userId) { + return new File(getDataUserCeDirectory(volumeUuid), String.valueOf(userId)); } /** {@hide} */ - public static File getDataUserCredentialEncryptedPackageDirectory(String volumeUuid, int userId, + public static File getDataUserCePackageDirectory(String volumeUuid, int userId, String packageName) { // TODO: keep consistent with installd - return new File(getDataUserCredentialEncryptedDirectory(volumeUuid, userId), packageName); + return new File(getDataUserCeDirectory(volumeUuid, userId), packageName); } /** {@hide} */ - public static File getDataUserDeviceEncryptedDirectory(String volumeUuid) { + public static File getDataUserDeDirectory(String volumeUuid) { return new File(getDataDirectory(volumeUuid), "user_de"); } /** {@hide} */ - public static File getDataUserDeviceEncryptedDirectory(String volumeUuid, int userId) { - return new File(getDataUserDeviceEncryptedDirectory(volumeUuid), String.valueOf(userId)); + public static File getDataUserDeDirectory(String volumeUuid, int userId) { + return new File(getDataUserDeDirectory(volumeUuid), String.valueOf(userId)); } /** {@hide} */ - public static File getDataUserDeviceEncryptedPackageDirectory(String volumeUuid, int userId, + public static File getDataUserDePackageDirectory(String volumeUuid, int userId, String packageName) { // TODO: keep consistent with installd - return new File(getDataUserDeviceEncryptedDirectory(volumeUuid, userId), packageName); + return new File(getDataUserDeDirectory(volumeUuid, userId), packageName); } /** diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java index b51d2dfb8694..9984755d316f 100644 --- a/core/java/android/os/Process.java +++ b/core/java/android/os/Process.java @@ -823,6 +823,16 @@ public class Process { } /** + * Returns whether the given uid belongs to an application. + * @param uid A kernel uid. + * @return Whether the uid corresponds to an application sandbox running in + * a specific user. + */ + public static boolean isApplicationUid(int uid) { + return UserHandle.isApp(uid); + } + + /** * Returns whether the current process is in an isolated sandbox. * @hide */ diff --git a/core/java/android/os/UserHandle.java b/core/java/android/os/UserHandle.java index 24666fe71724..b3f44536214b 100644 --- a/core/java/android/os/UserHandle.java +++ b/core/java/android/os/UserHandle.java @@ -130,6 +130,15 @@ public final class UserHandle implements Parcelable { } /** + * Returns the user for a given uid. + * @param uid A uid for an application running in a particular user. + * @return A {@link UserHandle} for that user. + */ + public static UserHandle getUserHandleForUid(int uid) { + return of(getUserId(uid)); + } + + /** * Returns the user id for a given uid. * @hide */ diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index dc0e249d72ec..69d564fc610b 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -1141,6 +1141,8 @@ public class UserManager { UserInfo user = null; try { user = mService.createUser(name, flags); + // TODO: Keep this in sync with + // UserManagerService.LocalService.createUserEvenWhenDisallowed if (user != null && !user.isAdmin()) { mService.setUserRestriction(DISALLOW_SMS, true, user.id); mService.setUserRestriction(DISALLOW_OUTGOING_CALLS, true, user.id); diff --git a/core/java/android/os/UserManagerInternal.java b/core/java/android/os/UserManagerInternal.java index 58a026904373..d2ece8bba3ad 100644 --- a/core/java/android/os/UserManagerInternal.java +++ b/core/java/android/os/UserManagerInternal.java @@ -17,6 +17,7 @@ package android.os; import android.annotation.NonNull; import android.annotation.Nullable; +import android.content.pm.UserInfo; import android.graphics.Bitmap; /** @@ -106,4 +107,12 @@ public abstract class UserManagerInternal { * non-ephemeral users left. */ public abstract void removeAllUsers(); + + /** + * Same as UserManager.createUser(), but bypasses the check for DISALLOW_ADD_USER. + * + * <p>Called by the {@link com.android.server.devicepolicy.DevicePolicyManagerService} when + * createAndManageUser is called by the device owner. + */ + public abstract UserInfo createUserEvenWhenDisallowed(String name, int flags); } diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java index 8468040b1bad..2ca758935feb 100644 --- a/core/java/android/provider/DocumentsContract.java +++ b/core/java/android/provider/DocumentsContract.java @@ -1201,7 +1201,7 @@ public final class DocumentsContract { final ContentProviderClient client = resolver.acquireUnstableContentProviderClient( sourceDocumentUri.getAuthority()); try { - return moveDocument(client, sourceParentDocumentUri, sourceDocumentUri, + return moveDocument(client, sourceDocumentUri, sourceParentDocumentUri, targetParentDocumentUri); } catch (Exception e) { Log.w(TAG, "Failed to move document", e); diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 7830142d4e4b..3169bf4f0e64 100755 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -4660,6 +4660,14 @@ public final class Settings { "lock_screen_allow_private_notifications"; /** + * When set by a user, allows notification remote input atop a securely locked screen + * without having to unlock + * @hide + */ + public static final String LOCK_SCREEN_ALLOW_REMOTE_INPUT = + "lock_screen_allow_remote_input"; + + /** * Set by the system to track if the user needs to see the call to action for * the lockscreen notification policy. * @hide @@ -5139,14 +5147,6 @@ public final class Settings { public static final String TTS_DEFAULT_SYNTH = "tts_default_synth"; /** - * Whether text-to-speech higher speech rate is enabled. - * 0 = disabled. - * 1 = enabled. - * @hide - */ - public static final String TTS_DEFAULT_HIGHER_SPEECH_RATE_ENABLED = - "tts_default_higher_speech_rate_enabled"; - /** * Default text-to-speech language. * * @deprecated this setting is no longer in use, as of the Ice Cream @@ -5907,13 +5907,6 @@ public final class Settings { "camera_double_tap_power_gesture_disabled"; /** - * Name of the package used as WebView provider (if unset the provider is instead determined - * by the system). - * @hide - */ - public static final String WEBVIEW_PROVIDER = "webview_provider"; - - /** * This are the settings to be backed up. * * NOTE: Settings are backed up and restored in the order they appear @@ -5958,7 +5951,6 @@ public final class Settings { ACCESSIBILITY_CAPTIONING_WINDOW_COLOR, TTS_USE_DEFAULTS, TTS_DEFAULT_RATE, - TTS_DEFAULT_HIGHER_SPEECH_RATE_ENABLED, TTS_DEFAULT_PITCH, TTS_DEFAULT_SYNTH, TTS_DEFAULT_LANG, @@ -6940,6 +6932,13 @@ public final class Settings { public static final String WEBVIEW_DATA_REDUCTION_PROXY_KEY = "webview_data_reduction_proxy_key"; + /** + * Name of the package used as WebView provider (if unset the provider is instead determined + * by the system). + * @hide + */ + public static final String WEBVIEW_PROVIDER = "webview_provider"; + /** * Whether Wifi display is enabled/disabled * 0=disabled. 1=enabled. diff --git a/core/java/android/text/BidiFormatter.java b/core/java/android/text/BidiFormatter.java index 675803c7b7bd..707c0fcb75ee 100644 --- a/core/java/android/text/BidiFormatter.java +++ b/core/java/android/text/BidiFormatter.java @@ -16,6 +16,7 @@ package android.text; +import android.annotation.Nullable; import android.view.View; import static android.text.TextDirectionHeuristics.FIRSTSTRONG_LTR; @@ -390,14 +391,17 @@ public final class BidiFormatter { * @return Input string after applying the above processing. {@code null} if {@code str} is * {@code null}. */ - public String unicodeWrap(String str, TextDirectionHeuristic heuristic, boolean isolate) { + public @Nullable String unicodeWrap(@Nullable String str, TextDirectionHeuristic heuristic, + boolean isolate) { + if (str == null) return null; return unicodeWrap((CharSequence) str, heuristic, isolate).toString(); } /** * @hide */ - public CharSequence unicodeWrap(CharSequence str, TextDirectionHeuristic heuristic, boolean isolate) { + public @Nullable CharSequence unicodeWrap(@Nullable CharSequence str, + TextDirectionHeuristic heuristic, boolean isolate) { if (str == null) return null; final boolean isRtl = heuristic.isRtl(str, 0, str.length()); SpannableStringBuilder result = new SpannableStringBuilder(); diff --git a/core/java/android/text/Html.java b/core/java/android/text/Html.java index 82f69efaac45..409994d7cd84 100644 --- a/core/java/android/text/Html.java +++ b/core/java/android/text/Html.java @@ -54,6 +54,9 @@ import android.text.style.UnderlineSpan; import java.io.IOException; import java.io.StringReader; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -356,24 +359,48 @@ public class Html { } } - private static String getTextStyles(Spanned text, int start, int end) { - final StringBuilder style = new StringBuilder(" style=\"margin-top:0; margin-bottom:0;"); + private static String getTextStyles(Spanned text, int start, int end, + boolean forceNoVerticalMargin, boolean includeTextAlign) { + String margin = null; + String textAlign = null; - final AlignmentSpan[] alignmentSpans = text.getSpans(start, end, AlignmentSpan.class); - final int len = alignmentSpans.length; - if (len > 0) { - final Layout.Alignment alignment = alignmentSpans[len - 1].getAlignment(); - if (alignment == Layout.Alignment.ALIGN_NORMAL) { - style.append(" text-align:start;"); - } else if (alignment == Layout.Alignment.ALIGN_CENTER) { - style.append(" text-align:center;"); - } else if (alignment == Layout.Alignment.ALIGN_OPPOSITE) { - style.append(" text-align:end;"); + if (forceNoVerticalMargin) { + margin = "margin-top:0; margin-bottom:0;"; + } + if (includeTextAlign) { + final AlignmentSpan[] alignmentSpans = text.getSpans(start, end, AlignmentSpan.class); + + // Only use the last AlignmentSpan with flag SPAN_PARAGRAPH + for (int i = alignmentSpans.length - 1; i >= 0; i--) { + AlignmentSpan s = alignmentSpans[i]; + if ((text.getSpanFlags(s) & Spanned.SPAN_PARAGRAPH) == Spanned.SPAN_PARAGRAPH) { + final Layout.Alignment alignment = s.getAlignment(); + if (alignment == Layout.Alignment.ALIGN_NORMAL) { + textAlign = "text-align:start;"; + } else if (alignment == Layout.Alignment.ALIGN_CENTER) { + textAlign = "text-align:center;"; + } else if (alignment == Layout.Alignment.ALIGN_OPPOSITE) { + textAlign = "text-align:end;"; + } + break; + } } } - style.append("\""); - return style.toString(); + if (margin == null && textAlign == null) { + return ""; + } + + final StringBuilder style = new StringBuilder(" style=\""); + if (margin != null && textAlign != null) { + style.append(margin).append(" ").append(textAlign); + } else if (margin != null) { + style.append(margin); + } else if (textAlign != null) { + style.append(textAlign); + } + + return style.append("\"").toString(); } private static void withinBlockquote(StringBuilder out, Spanned text, int start, int end, @@ -395,46 +422,55 @@ public class Html { next = end; } - boolean isListItem = false; - ParagraphStyle[] paragraphStyles = text.getSpans(i, next, ParagraphStyle.class); - for (ParagraphStyle paragraphStyle : paragraphStyles) { - final int spanFlags = text.getSpanFlags(paragraphStyle); - if ((spanFlags & Spanned.SPAN_PARAGRAPH) == Spanned.SPAN_PARAGRAPH - && paragraphStyle instanceof BulletSpan) { - isListItem = true; - break; + if (next == i) { + if (isInList) { + // Current paragraph is no longer a list item; close the previously opened list + isInList = false; + out.append("</ul>\n"); + } + out.append("<br>\n"); + } else { + boolean isListItem = false; + ParagraphStyle[] paragraphStyles = text.getSpans(i, next, ParagraphStyle.class); + for (ParagraphStyle paragraphStyle : paragraphStyles) { + final int spanFlags = text.getSpanFlags(paragraphStyle); + if ((spanFlags & Spanned.SPAN_PARAGRAPH) == Spanned.SPAN_PARAGRAPH + && paragraphStyle instanceof BulletSpan) { + isListItem = true; + break; + } } - } - if (isListItem && !isInList) { - // Current paragraph is the first item in a list - isInList = true; - out.append("<ul>\n"); - } + if (isListItem && !isInList) { + // Current paragraph is the first item in a list + isInList = true; + out.append("<ul") + .append(getTextStyles(text, i, next, true, false)) + .append(">\n"); + } - if (isInList && !isListItem) { - // Current paragraph is no longer a list item; close the previously opened list - isInList = false; - out.append("</ul>\n"); - } + if (isInList && !isListItem) { + // Current paragraph is no longer a list item; close the previously opened list + isInList = false; + out.append("</ul>\n"); + } - String tagType = isListItem ? "li" : "p"; - out.append("<").append(tagType).append(getTextDirection(text, start, next)) - .append(getTextStyles(text, start, next)).append(">"); + String tagType = isListItem ? "li" : "p"; + out.append("<").append(tagType) + .append(getTextDirection(text, i, next)) + .append(getTextStyles(text, i, next, !isListItem, true)) + .append(">"); - if (next - i == 0) { - out.append("<br>"); - } else { withinParagraph(out, text, i, next); - } - out.append("</"); - out.append(tagType); - out.append(">\n"); + out.append("</"); + out.append(tagType); + out.append(">\n"); - if (next == end && isInList) { - isInList = false; - out.append("</ul>\n"); + if (next == end && isInList) { + isInList = false; + out.append("</ul>\n"); + } } next++; @@ -654,6 +690,25 @@ class HtmlToSpannedConverter implements ContentHandler { private int mFlags; private static Pattern sTextAlignPattern; + private static Pattern sForegroundColorPattern; + private static Pattern sBackgroundColorPattern; + private static Pattern sTextDecorationPattern; + + /** + * Name-value mapping of HTML/CSS colors which have different values in {@link Color}. + */ + private static final Map<String, Integer> sColorMap; + + static { + sColorMap = new HashMap<>(); + sColorMap.put("darkgray", 0xFFA9A9A9); + sColorMap.put("gray", 0xFF808080); + sColorMap.put("lightgray", 0xFFD3D3D3); + sColorMap.put("darkgrey", 0xFFA9A9A9); + sColorMap.put("grey", 0xFF808080); + sColorMap.put("lightgrey", 0xFFD3D3D3); + sColorMap.put("green", 0xFF008000); + } private static Pattern getTextAlignPattern() { if (sTextAlignPattern == null) { @@ -662,6 +717,30 @@ class HtmlToSpannedConverter implements ContentHandler { return sTextAlignPattern; } + private static Pattern getForegroundColorPattern() { + if (sForegroundColorPattern == null) { + sForegroundColorPattern = Pattern.compile( + "(?:\\s+|\\A)color\\s*:\\s*(\\S*)\\b"); + } + return sForegroundColorPattern; + } + + private static Pattern getBackgroundColorPattern() { + if (sBackgroundColorPattern == null) { + sBackgroundColorPattern = Pattern.compile( + "(?:\\s+|\\A)background(?:-color)?\\s*:\\s*(\\S*)\\b"); + } + return sBackgroundColorPattern; + } + + private static Pattern getTextDecorationPattern() { + if (sTextDecorationPattern == null) { + sTextDecorationPattern = Pattern.compile( + "(?:\\s+|\\A)text-decoration\\s*:\\s*(\\S*)\\b"); + } + return sTextDecorationPattern; + } + public HtmlToSpannedConverter( String source, Html.ImageGetter imageGetter, Html.TagHandler tagHandler, Parser parser, int flags) { mSource = source; @@ -715,8 +794,15 @@ class HtmlToSpannedConverter implements ContentHandler { // so we can safely emit the linebreaks when we handle the close tag. } else if (tag.equalsIgnoreCase("p")) { startBlockElement(mSpannableStringBuilder, attributes, getMarginParagraph()); + startCssStyle(mSpannableStringBuilder, attributes); + } else if (tag.equalsIgnoreCase("ul")) { + startBlockElement(mSpannableStringBuilder, attributes, getMarginList()); + } else if (tag.equalsIgnoreCase("li")) { + startLi(mSpannableStringBuilder, attributes); } else if (tag.equalsIgnoreCase("div")) { startBlockElement(mSpannableStringBuilder, attributes, getMarginDiv()); + } else if (tag.equalsIgnoreCase("span")) { + startCssStyle(mSpannableStringBuilder, attributes); } else if (tag.equalsIgnoreCase("strong")) { start(mSpannableStringBuilder, new Bold()); } else if (tag.equalsIgnoreCase("b")) { @@ -768,9 +854,16 @@ class HtmlToSpannedConverter implements ContentHandler { if (tag.equalsIgnoreCase("br")) { handleBr(mSpannableStringBuilder); } else if (tag.equalsIgnoreCase("p")) { + endCssStyle(mSpannableStringBuilder); endBlockElement(mSpannableStringBuilder); + } else if (tag.equalsIgnoreCase("ul")) { + endBlockElement(mSpannableStringBuilder); + } else if (tag.equalsIgnoreCase("li")) { + endLi(mSpannableStringBuilder); } else if (tag.equalsIgnoreCase("div")) { endBlockElement(mSpannableStringBuilder); + } else if (tag.equalsIgnoreCase("span")) { + endCssStyle(mSpannableStringBuilder); } else if (tag.equalsIgnoreCase("strong")) { end(mSpannableStringBuilder, Bold.class, new StyleSpan(Typeface.BOLD)); } else if (tag.equalsIgnoreCase("b")) { @@ -824,6 +917,14 @@ class HtmlToSpannedConverter implements ContentHandler { return getMargin(Html.FROM_HTML_SEPARATOR_LINE_BREAK_HEADING); } + private int getMarginListItem() { + return getMargin(Html.FROM_HTML_SEPARATOR_LINE_BREAK_LIST_ITEM); + } + + private int getMarginList() { + return getMargin(Html.FROM_HTML_SEPARATOR_LINE_BREAK_LIST); + } + private int getMarginDiv() { return getMargin(Html.FROM_HTML_SEPARATOR_LINE_BREAK_DIV); } @@ -866,7 +967,7 @@ class HtmlToSpannedConverter implements ContentHandler { final int len = text.length(); if (margin > 0) { appendNewlines(text, margin); - text.setSpan(new Newline(margin), len, len, Spannable.SPAN_INCLUSIVE_EXCLUSIVE); + start(text, new Newline(margin)); } String style = attributes.getValue("", "style"); @@ -875,14 +976,11 @@ class HtmlToSpannedConverter implements ContentHandler { if (m.find()) { String alignment = m.group(1); if (alignment.equalsIgnoreCase("start")) { - text.setSpan(new Alignment(Layout.Alignment.ALIGN_NORMAL), - len, len, Spannable.SPAN_INCLUSIVE_EXCLUSIVE); + start(text, new Alignment(Layout.Alignment.ALIGN_NORMAL)); } else if (alignment.equalsIgnoreCase("center")) { - text.setSpan(new Alignment(Layout.Alignment.ALIGN_CENTER), - len, len, Spannable.SPAN_INCLUSIVE_EXCLUSIVE); + start(text, new Alignment(Layout.Alignment.ALIGN_CENTER)); } else if (alignment.equalsIgnoreCase("end")) { - text.setSpan(new Alignment(Layout.Alignment.ALIGN_OPPOSITE), - len, len, Spannable.SPAN_INCLUSIVE_EXCLUSIVE); + start(text, new Alignment(Layout.Alignment.ALIGN_OPPOSITE)); } } } @@ -905,6 +1003,18 @@ class HtmlToSpannedConverter implements ContentHandler { text.append('\n'); } + private void startLi(Editable text, Attributes attributes) { + startBlockElement(text, attributes, getMarginListItem()); + start(text, new Bullet()); + startCssStyle(text, attributes); + } + + private static void endLi(Editable text) { + endCssStyle(text); + endBlockElement(text); + end(text, Bullet.class, new BulletSpan()); + } + private void startBlockquote(Editable text, Attributes attributes) { startBlockElement(text, attributes, getMarginBlockquote()); start(text, new Blockquote()); @@ -959,7 +1069,7 @@ class HtmlToSpannedConverter implements ContentHandler { private static void start(Editable text, Object mark) { int len = text.length(); - text.setSpan(mark, len, len, Spannable.SPAN_MARK_MARK); + text.setSpan(mark, len, len, Spannable.SPAN_INCLUSIVE_EXCLUSIVE); } private static void end(Editable text, Class kind, Object repl) { @@ -970,6 +1080,52 @@ class HtmlToSpannedConverter implements ContentHandler { } } + private void startCssStyle(Editable text, Attributes attributes) { + String style = attributes.getValue("", "style"); + if (style != null) { + Matcher m = getForegroundColorPattern().matcher(style); + if (m.find()) { + int c = getHtmlColor(m.group(1)); + if (c != -1) { + start(text, new Foreground(c | 0xFF000000)); + } + } + + m = getBackgroundColorPattern().matcher(style); + if (m.find()) { + int c = getHtmlColor(m.group(1)); + if (c != -1) { + start(text, new Background(c | 0xFF000000)); + } + } + + m = getTextDecorationPattern().matcher(style); + if (m.find()) { + String textDecoration = m.group(1); + if (textDecoration.equalsIgnoreCase("line-through")) { + start(text, new Strikethrough()); + } + } + } + } + + private static void endCssStyle(Editable text) { + Strikethrough s = getLast(text, Strikethrough.class); + if (s != null) { + setSpanFromMark(text, s, new StrikethroughSpan()); + } + + Background b = getLast(text, Background.class); + if (b != null) { + setSpanFromMark(text, b, new BackgroundColorSpan(b.mBackgroundColor)); + } + + Foreground f = getLast(text, Foreground.class); + if (f != null) { + setSpanFromMark(text, f, new ForegroundColorSpan(f.mForegroundColor)); + } + } + private static void startImg(Editable text, Attributes attributes, Html.ImageGetter img) { String src = attributes.getValue("", "src"); Drawable d = null; @@ -991,58 +1147,41 @@ class HtmlToSpannedConverter implements ContentHandler { Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); } - private static void startFont(Editable text, Attributes attributes) { + private void startFont(Editable text, Attributes attributes) { String color = attributes.getValue("", "color"); String face = attributes.getValue("", "face"); - int len = text.length(); - text.setSpan(new Font(color, face), len, len, Spannable.SPAN_MARK_MARK); + if (!TextUtils.isEmpty(color)) { + int c = getHtmlColor(color); + if (c != -1) { + start(text, new Foreground(c | 0xFF000000)); + } + } + + if (!TextUtils.isEmpty(face)) { + start(text, new Font(face)); + } } private static void endFont(Editable text) { - int len = text.length(); - Font f = getLast(text, Font.class); - int where = text.getSpanStart(f); - text.removeSpan(f); - - if (where != len) { - if (!TextUtils.isEmpty(f.mColor)) { - if (f.mColor.startsWith("@")) { - Resources res = Resources.getSystem(); - String name = f.mColor.substring(1); - int colorRes = res.getIdentifier(name, "color", "android"); - if (colorRes != 0) { - ColorStateList colors = res.getColorStateList(colorRes, null); - text.setSpan(new TextAppearanceSpan(null, 0, 0, colors, null), - where, len, - Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); - } - } else { - int c = Color.getHtmlColor(f.mColor); - if (c != -1) { - text.setSpan(new ForegroundColorSpan(c | 0xFF000000), - where, len, - Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); - } - } - } + Font font = getLast(text, Font.class); + if (font != null) { + setSpanFromMark(text, font, new TypefaceSpan(font.mFace)); + } - if (f.mFace != null) { - text.setSpan(new TypefaceSpan(f.mFace), where, len, - Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); - } + Foreground foreground = getLast(text, Foreground.class); + if (foreground != null) { + setSpanFromMark(text, foreground, + new ForegroundColorSpan(foreground.mForegroundColor)); } } private static void startA(Editable text, Attributes attributes) { String href = attributes.getValue("", "href"); - - int len = text.length(); - text.setSpan(new Href(href), len, len, Spannable.SPAN_MARK_MARK); + start(text, new Href(href)); } private static void endA(Editable text) { - int len = text.length(); Href h = getLast(text, Href.class); if (h != null) { if (h.mHref != null) { @@ -1051,6 +1190,17 @@ class HtmlToSpannedConverter implements ContentHandler { } } + private int getHtmlColor(String color) { + if ((mFlags & Html.FROM_HTML_OPTION_USE_CSS_COLORS) + == Html.FROM_HTML_OPTION_USE_CSS_COLORS) { + Integer i = sColorMap.get(color.toLowerCase(Locale.US)); + if (i != null) { + return i; + } + } + return Color.getHtmlColor(color); + } + public void setDocumentLocator(Locator locator) { } @@ -1132,13 +1282,12 @@ class HtmlToSpannedConverter implements ContentHandler { private static class Blockquote { } private static class Super { } private static class Sub { } + private static class Bullet { } private static class Font { - public String mColor; public String mFace; - public Font(String color, String face) { - mColor = color; + public Font(String face) { mFace = face; } } @@ -1151,6 +1300,22 @@ class HtmlToSpannedConverter implements ContentHandler { } } + private static class Foreground { + private int mForegroundColor; + + public Foreground(int foregroundColor) { + mForegroundColor = foregroundColor; + } + } + + private static class Background { + private int mBackgroundColor; + + public Background(int backgroundColor) { + mBackgroundColor = backgroundColor; + } + } + private static class Heading { private int mLevel; diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl index 1703ed1810e2..6a2cc802e1f1 100644 --- a/core/java/android/view/IWindowSession.aidl +++ b/core/java/android/view/IWindowSession.aidl @@ -260,4 +260,6 @@ interface IWindowSession { * Returns true if the move started successfully; false otherwise. */ boolean startMovingTask(IWindow window, float startX, float startY); + + void updatePointerIcon(IWindow window); } diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java index a2960515da08..152dd66ec7be 100644 --- a/core/java/android/view/SurfaceView.java +++ b/core/java/android/view/SurfaceView.java @@ -224,7 +224,7 @@ public class SurfaceView extends View { mParent.requestTransparentRegion(this); mSession = getWindowSession(); mLayout.token = getWindowToken(); - mLayout.setTitle("SurfaceView"); + mLayout.setTitle("SurfaceView - " + getViewRootImpl().getTitle()); mViewVisibility = getVisibility() == VISIBLE; if (!mGlobalListenersAdded) { diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 2612ab2f8eaf..5ae426c24733 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -6874,6 +6874,15 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @param info The info whose drawing order should be populated */ private void populateAccessibilityNodeInfoDrawingOrderInParent(AccessibilityNodeInfo info) { + /* + * If the view's bounds haven't been set yet, layout has not completed. In that situation, + * drawing order may not be well-defined, and some Views with custom drawing order may + * not be initialized sufficiently to respond properly getChildDrawingOrder. + */ + if ((mPrivateFlags & PFLAG_HAS_BOUNDS) == 0) { + info.setDrawingOrder(0); + return; + } int drawingOrderInParent = 1; // Iterate up the hierarchy if parents are not important for a11y View viewAtDrawingLevel = this; @@ -21572,6 +21581,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ public void setPointerIcon(PointerIcon pointerIcon) { mPointerIcon = pointerIcon; + if (mAttachInfo == null) { + return; + } + try { + mAttachInfo.mSession.updatePointerIcon(mAttachInfo.mWindow); + } catch (RemoteException e) { + } } /** diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java index 947906bf4403..609c471dfa18 100644 --- a/core/java/android/view/WindowManagerPolicy.java +++ b/core/java/android/view/WindowManagerPolicy.java @@ -18,6 +18,7 @@ package android.view; import android.annotation.IntDef; import android.annotation.SystemApi; +import android.app.ActivityManager.StackId; import android.content.Context; import android.content.pm.ActivityInfo; import android.content.res.CompatibilityInfo; @@ -388,6 +389,12 @@ public interface WindowManagerPolicy { * Check whether the window is currently dimming. */ public boolean isDimming(); + + /** + * @return the stack id this windows belongs to, or {@link StackId#INVALID_STACK_ID} if + * not attached to any stack. + */ + int getStackId(); } /** @@ -465,6 +472,11 @@ public interface WindowManagerPolicy { * @return The content insets of the docked divider window. */ int getDockedDividerInsetsLw(); + + /** + * Retrieves the {@param outBounds} from the stack with id {@param stackId}. + */ + void getStackBounds(int stackId, Rect outBounds); } public interface PointerEventListener { diff --git a/core/java/android/webkit/WebViewProviderInfo.java b/core/java/android/webkit/WebViewProviderInfo.java index 3f50fe2e376c..94e8b70ce38c 100644 --- a/core/java/android/webkit/WebViewProviderInfo.java +++ b/core/java/android/webkit/WebViewProviderInfo.java @@ -97,22 +97,12 @@ public class WebViewProviderInfo implements Parcelable { */ public boolean isEnabled() { try { - PackageManager pm = AppGlobals.getInitialApplication().getPackageManager(); - int enabled_state = pm.getApplicationEnabledSetting(packageName); - switch (enabled_state) { - case PackageManager.COMPONENT_ENABLED_STATE_ENABLED: - return true; - case PackageManager.COMPONENT_ENABLED_STATE_DEFAULT: - ApplicationInfo applicationInfo = getPackageInfo().applicationInfo; - return applicationInfo.enabled; - default: - return false; - } + // Explicitly fetch up-to-date package info here since the enabled-state of the package + // might have changed since we last fetched its package info. + updatePackageInfo(); + return getPackageInfo().applicationInfo.enabled; } catch (WebViewPackageNotFoundException e) { return false; - } catch (IllegalArgumentException e) { - // Thrown by PackageManager.getApplicationEnabledSetting if the package does not exist - return false; } } @@ -124,14 +114,18 @@ public class WebViewProviderInfo implements Parcelable { return availableByDefault; } + private void updatePackageInfo() { + try { + PackageManager pm = AppGlobals.getInitialApplication().getPackageManager(); + packageInfo = pm.getPackageInfo(packageName, PACKAGE_FLAGS); + } catch (PackageManager.NameNotFoundException e) { + throw new WebViewPackageNotFoundException(e); + } + } + public PackageInfo getPackageInfo() { if (packageInfo == null) { - try { - PackageManager pm = AppGlobals.getInitialApplication().getPackageManager(); - packageInfo = pm.getPackageInfo(packageName, PACKAGE_FLAGS); - } catch (PackageManager.NameNotFoundException e) { - throw new WebViewPackageNotFoundException(e); - } + updatePackageInfo(); } return packageInfo; } diff --git a/core/java/android/widget/SearchView.java b/core/java/android/widget/SearchView.java index 45fc6c3d4c7a..3796df78ff7f 100644 --- a/core/java/android/widget/SearchView.java +++ b/core/java/android/widget/SearchView.java @@ -36,6 +36,8 @@ import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; import android.speech.RecognizerIntent; import android.text.Editable; import android.text.InputType; @@ -1332,6 +1334,48 @@ public class SearchView extends LinearLayout implements CollapsibleActionView { setIconified(false); } + static class SavedState extends BaseSavedState { + boolean isIconified; + + SavedState(Parcelable superState) { + super(superState); + } + + public SavedState(Parcel source) { + super(source); + isIconified = (Boolean) source.readValue(null); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + dest.writeValue(isIconified); + } + + @Override + public String toString() { + return "SearchView.SavedState{" + + Integer.toHexString(System.identityHashCode(this)) + + " isIconified=" + isIconified + "}"; + } + } + + @Override + protected Parcelable onSaveInstanceState() { + Parcelable superState = super.onSaveInstanceState(); + SavedState ss = new SavedState(superState); + ss.isIconified = isIconified(); + return ss; + } + + @Override + protected void onRestoreInstanceState(Parcelable state) { + SavedState ss = (SavedState) state; + super.onRestoreInstanceState(ss.getSuperState()); + updateViewsVisibility(ss.isIconified); + requestLayout(); + } + @Override public CharSequence getAccessibilityClassName() { return SearchView.class.getName(); diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java index b0fb93b89052..8c3c2b5c8022 100644 --- a/core/java/com/android/internal/app/ChooserActivity.java +++ b/core/java/com/android/internal/app/ChooserActivity.java @@ -45,6 +45,7 @@ import android.os.RemoteException; import android.os.ResultReceiver; import android.os.UserHandle; import android.os.UserManager; +import android.os.storage.StorageManager; import android.provider.DocumentsContract; import android.service.chooser.ChooserTarget; import android.service.chooser.ChooserTargetService; @@ -128,6 +129,7 @@ public class ChooserActivity extends ResolverActivity { if (mServiceConnections.isEmpty()) { mChooserHandler.removeMessages(CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT); sendVoiceChoicesIfNeeded(); + mChooserListAdapter.setShowServiceTargets(true); } break; @@ -137,6 +139,7 @@ public class ChooserActivity extends ResolverActivity { } unbindRemainingServices(); sendVoiceChoicesIfNeeded(); + mChooserListAdapter.setShowServiceTargets(true); break; default: @@ -232,7 +235,7 @@ public class ChooserActivity extends ResolverActivity { // the case where we don't have access to credential encrypted storage we just won't // have our pinned target info. final File prefsFile = new File(new File( - Environment.getDataUserCredentialEncryptedPackageDirectory(null, + Environment.getDataUserCePackageDirectory(StorageManager.UUID_PRIVATE_INTERNAL, context.getUserId(), context.getPackageName()), "shared_prefs"), PINNED_SHARED_PREFS_NAME + ".xml"); @@ -765,6 +768,7 @@ public class ChooserActivity extends ResolverActivity { private final List<ChooserTargetInfo> mServiceTargets = new ArrayList<>(); private final List<TargetInfo> mCallerTargets = new ArrayList<>(); + private boolean mShowServiceTargets; private float mLateFee = 1.f; @@ -865,6 +869,9 @@ public class ChooserActivity extends ResolverActivity { } public int getServiceTargetCount() { + if (!mShowServiceTargets) { + return 0; + } return Math.min(mServiceTargets.size(), MAX_SERVICE_TARGETS); } @@ -954,6 +961,14 @@ public class ChooserActivity extends ResolverActivity { notifyDataSetChanged(); } + /** + * Set to true to reveal all service targets at once. + */ + public void setShowServiceTargets(boolean show) { + mShowServiceTargets = show; + notifyDataSetChanged(); + } + private void insertServiceTarget(ChooserTargetInfo chooserTargetInfo) { final float newScore = chooserTargetInfo.getModifiedScore(); for (int i = 0, N = mServiceTargets.size(); i < N; i++) { diff --git a/core/java/com/android/internal/app/LocaleStore.java b/core/java/com/android/internal/app/LocaleStore.java index 210adce4d0af..465c4d833aa1 100644 --- a/core/java/com/android/internal/app/LocaleStore.java +++ b/core/java/com/android/internal/app/LocaleStore.java @@ -104,6 +104,9 @@ public class LocaleStore { } private boolean isSuggestionOfType(int suggestionMask) { + if (!mIsTranslated) { // Never suggest an untranslated locale + return false; + } return (mSuggestionFlags & suggestionMask) == suggestionMask; } @@ -207,6 +210,27 @@ public class LocaleStore { } } + /* + * Show all the languages supported for a country in the suggested list. + * This is also handy for devices without SIM (tablets). + */ + private static void addSuggestedLocalesForRegion(Locale locale) { + if (locale == null) { + return; + } + final String country = locale.getCountry(); + if (country.isEmpty()) { + return; + } + + for (LocaleInfo li : sLocaleCache.values()) { + if (country.equals(li.getLocale().getCountry())) { + // We don't need to differentiate between manual and SIM suggestions + li.mSuggestionFlags |= LocaleInfo.SUGGESTION_TYPE_SIM; + } + } + } + public static void fillCache(Context context) { if (sFullyInitialized) { return; @@ -256,6 +280,8 @@ public class LocaleStore { li.setTranslated(localizedLocales.contains(li.getLangScriptKey())); } + addSuggestedLocalesForRegion(Locale.getDefault()); + sFullyInitialized = true; } diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java index 88af920df2d4..af3f7ec73bc3 100644 --- a/core/java/com/android/internal/policy/DecorView.java +++ b/core/java/com/android/internal/policy/DecorView.java @@ -86,6 +86,7 @@ import static android.view.Window.DECOR_CAPTION_SHADE_DARK; import static android.view.Window.DECOR_CAPTION_SHADE_LIGHT; import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; import static android.view.WindowManager.LayoutParams.FLAG_FULLSCREEN; +import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR; import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN; import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION; import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS; @@ -1002,13 +1003,25 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind && (sysUiVisibility & SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) == 0 && (sysUiVisibility & SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0; + // If we didn't request fullscreen layout, but we still got it because of the + // mForceWindowDrawsStatusBarBackground flag, also consume top inset. + boolean consumingStatusBar = (sysUiVisibility & SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) == 0 + && (sysUiVisibility & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) == 0 + && (attrs.flags & FLAG_LAYOUT_IN_SCREEN) == 0 + && (attrs.flags & FLAG_LAYOUT_INSET_DECOR) == 0 + && mForceWindowDrawsStatusBarBackground + && mLastTopInset != 0; + + int consumedTop = consumingStatusBar ? mLastTopInset : 0; int consumedRight = consumingNavBar ? mLastRightInset : 0; int consumedBottom = consumingNavBar ? mLastBottomInset : 0; if (mContentRoot != null && mContentRoot.getLayoutParams() instanceof MarginLayoutParams) { MarginLayoutParams lp = (MarginLayoutParams) mContentRoot.getLayoutParams(); - if (lp.rightMargin != consumedRight || lp.bottomMargin != consumedBottom) { + if (lp.topMargin != consumedTop || lp.rightMargin != consumedRight + || lp.bottomMargin != consumedBottom) { + lp.topMargin = consumedTop; lp.rightMargin = consumedRight; lp.bottomMargin = consumedBottom; mContentRoot.setLayoutParams(lp); @@ -1022,7 +1035,7 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind if (insets != null) { insets = insets.replaceSystemWindowInsets( insets.getSystemWindowInsetLeft(), - insets.getSystemWindowInsetTop(), + insets.getSystemWindowInsetTop() - consumedTop, insets.getSystemWindowInsetRight() - consumedRight, insets.getSystemWindowInsetBottom() - consumedBottom); } @@ -1718,8 +1731,13 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind private void loadBackgroundDrawablesIfNeeded() { if (mResizingBackgroundDrawable == null) { - mResizingBackgroundDrawable = getResizingBackgroundDrawable( + mResizingBackgroundDrawable = getResizingBackgroundDrawable(getContext(), mWindow.mBackgroundResource, mWindow.mBackgroundFallbackResource); + if (mResizingBackgroundDrawable == null) { + // We shouldn't really get here as the background fallback should be always + // available since it is defaulted by the system. + Log.w(mLogTag, "Failed to find background drawable for PhoneWindow=" + mWindow); + } } if (mCaptionBackgroundDrawable == null) { mCaptionBackgroundDrawable = getContext().getDrawable( @@ -1817,9 +1835,8 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind * Returns the color used to fill areas the app has not rendered content to yet when the * user is resizing the window of an activity in multi-window mode. */ - private Drawable getResizingBackgroundDrawable(int backgroundRes, int backgroundFallbackRes) { - final Context context = getContext(); - + public static Drawable getResizingBackgroundDrawable(Context context, int backgroundRes, + int backgroundFallbackRes) { if (backgroundRes != 0) { final Drawable drawable = context.getDrawable(backgroundRes); if (drawable != null) { @@ -1833,10 +1850,6 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind return fallbackDrawable; } } - - // We shouldn't really get here as the background fallback should be always available since - // it is defaulted by the system. - Log.w(mLogTag, "Failed to find background drawable for PhoneWindow=" + mWindow); return null; } diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl index 6e374e28103f..08d4fba8b1b8 100644 --- a/core/java/com/android/internal/statusbar/IStatusBar.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl @@ -17,6 +17,7 @@ package com.android.internal.statusbar; import android.content.ComponentName; +import android.graphics.Rect; import android.os.Bundle; import android.service.notification.StatusBarNotification; @@ -31,7 +32,23 @@ oneway interface IStatusBar void animateExpandNotificationsPanel(); void animateExpandSettingsPanel(String subPanel); void animateCollapsePanels(); - void setSystemUiVisibility(int vis, int mask); + + /** + * Notifies the status bar of a System UI visibility flag change. + * + * @param vis the visibility flags except SYSTEM_UI_FLAG_LIGHT_STATUS_BAR which will be reported + * separately in fullscreenStackVis and dockedStackVis + * @param fullscreenStackVis the flags which only apply in the region of the fullscreen stack, + * which is currently only SYSTEM_UI_FLAG_LIGHT_STATUS_BAR + * @param dockedStackVis the flags that only apply in the region of the docked stack, which is + * currently only SYSTEM_UI_FLAG_LIGHT_STATUS_BAR + * @param mask which flags to change + * @param fullscreenBounds the current bounds of the fullscreen stack, in screen coordinates + * @param dockedBounds the current bounds of the docked stack, in screen coordinates + */ + void setSystemUiVisibility(int vis, int fullscreenStackVis, int dockedStackVis, int mask, + in Rect fullscreenBounds, in Rect dockedBounds); + void topAppWindowChanged(boolean menuVisible); void setImeWindowStatus(in IBinder token, int vis, int backDisposition, boolean showImeSwitcher); diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl index bec18ec48ec2..8acf5d3070d3 100644 --- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl @@ -17,6 +17,7 @@ package com.android.internal.statusbar; import android.content.ComponentName; +import android.graphics.Rect; import android.os.Bundle; import android.service.notification.StatusBarNotification; @@ -37,7 +38,6 @@ interface IStatusBarService void setIcon(String slot, String iconPackage, int iconId, int iconLevel, String contentDescription); void setIconVisibility(String slot, boolean visible); void removeIcon(String slot); - void topAppWindowChanged(boolean menuVisible); void setImeWindowStatus(in IBinder token, int vis, int backDisposition, boolean showImeSwitcher); void expandSettingsPanel(String subPanel); @@ -47,7 +47,8 @@ interface IStatusBarService // You need the STATUS_BAR_SERVICE permission void registerStatusBar(IStatusBar callbacks, out List<String> iconSlots, out List<StatusBarIcon> iconList, - out int[] switches, out List<IBinder> binders); + out int[] switches, out List<IBinder> binders, out Rect fullscreenStackBounds, + out Rect dockedStackBounds); void onPanelRevealed(boolean clearNotificationEffects, int numItems); void onPanelHidden(); // Mark current notifications as "seen" and stop ringing, vibrating, blinking. diff --git a/core/java/com/android/internal/util/LineBreakBufferedWriter.java b/core/java/com/android/internal/util/LineBreakBufferedWriter.java index f831e7a84d2f..552a93f6666a 100644 --- a/core/java/com/android/internal/util/LineBreakBufferedWriter.java +++ b/core/java/com/android/internal/util/LineBreakBufferedWriter.java @@ -96,7 +96,7 @@ public class LineBreakBufferedWriter extends PrintWriter { @Override public void write(int c) { - if (bufferIndex < bufferSize) { + if (bufferIndex < buffer.length) { buffer[bufferIndex] = (char)c; bufferIndex++; if ((char)c == '\n') { diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java index e239852673e8..cbc735fc065c 100644 --- a/core/java/com/android/internal/widget/LockPatternUtils.java +++ b/core/java/com/android/internal/widget/LockPatternUtils.java @@ -283,6 +283,15 @@ public class LockPatternUtils { getTrustManager().reportUnlockAttempt(true /* authenticated */, userId); } + public int getCurrentFailedPasswordAttempts(int userId) { + return getDevicePolicyManager().getCurrentFailedPasswordAttempts(userId); + } + + public int getMaximumFailedPasswordsForWipe(int userId) { + return getDevicePolicyManager().getMaximumFailedPasswordsForWipe( + null /* componentName */, userId); + } + /** * Check to see if a pattern matches the saved pattern. * If pattern matches, return an opaque attestation that the challenge diff --git a/core/java/com/android/server/backup/AccountSyncSettingsBackupHelper.java b/core/java/com/android/server/backup/AccountSyncSettingsBackupHelper.java index 0449340ea21a..1b4049212255 100644 --- a/core/java/com/android/server/backup/AccountSyncSettingsBackupHelper.java +++ b/core/java/com/android/server/backup/AccountSyncSettingsBackupHelper.java @@ -16,10 +16,6 @@ package com.android.server.backup; -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - import android.accounts.Account; import android.accounts.AccountManager; import android.app.backup.BackupDataInputStream; @@ -28,14 +24,21 @@ import android.app.backup.BackupHelper; import android.content.ContentResolver; import android.content.Context; import android.content.SyncAdapterType; +import android.os.Environment; import android.os.ParcelFileDescriptor; import android.util.Log; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + import java.io.BufferedOutputStream; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.EOFException; +import java.io.File; import java.io.FileInputStream; +import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.security.MessageDigest; @@ -73,6 +76,8 @@ public class AccountSyncSettingsBackupHelper implements BackupHelper { private static final String KEY_AUTHORITY_NAME = "name"; private static final String KEY_AUTHORITY_SYNC_STATE = "syncState"; private static final String KEY_AUTHORITY_SYNC_ENABLED = "syncEnabled"; + private static final String STASH_FILE = Environment.getDataDirectory() + + "/backup/unadded_account_syncsettings.json"; private Context mContext; private AccountManager mAccountManager; @@ -256,41 +261,99 @@ public class AccountSyncSettingsBackupHelper implements BackupHelper { } try { - HashSet<Account> currentAccounts = getAccountsHashSet(); - for (int i = 0; i < accountJSONArray.length(); i++) { - JSONObject accountJSON = (JSONObject) accountJSONArray.get(i); - String accountName = accountJSON.getString(KEY_ACCOUNT_NAME); - String accountType = accountJSON.getString(KEY_ACCOUNT_TYPE); - - Account account = new Account(accountName, accountType); - - // Check if the account already exists. Accounts that don't exist on the device - // yet won't be restored. - if (currentAccounts.contains(account)) { - restoreExistingAccountSyncSettingsFromJSON(accountJSON); - } else { - // TODO: - // Stash the data to a file that the SyncManager can read from to restore - // settings at a later date. - } - } + restoreFromJsonArray(accountJSONArray); } finally { // Set the master sync preference to the value from the backup set. ContentResolver.setMasterSyncAutomatically(masterSyncEnabled); } - Log.i(TAG, "Restore successful."); } catch (IOException | JSONException e) { Log.e(TAG, "Couldn't restore account sync settings\n" + e); } } + private void restoreFromJsonArray(JSONArray accountJSONArray) + throws JSONException { + HashSet<Account> currentAccounts = getAccounts(); + JSONArray unaddedAccountsJSONArray = new JSONArray(); + for (int i = 0; i < accountJSONArray.length(); i++) { + JSONObject accountJSON = (JSONObject) accountJSONArray.get(i); + String accountName = accountJSON.getString(KEY_ACCOUNT_NAME); + String accountType = accountJSON.getString(KEY_ACCOUNT_TYPE); + + Account account = null; + try { + account = new Account(accountName, accountType); + } catch (IllegalArgumentException iae) { + continue; + } + + // Check if the account already exists. Accounts that don't exist on the device + // yet won't be restored. + if (currentAccounts.contains(account)) { + if (DEBUG) Log.i(TAG, "Restoring Sync Settings for" + accountName); + restoreExistingAccountSyncSettingsFromJSON(accountJSON); + } else { + unaddedAccountsJSONArray.put(accountJSON); + } + } + + if (unaddedAccountsJSONArray.length() > 0) { + try (FileOutputStream fOutput = new FileOutputStream(STASH_FILE)) { + String jsonString = unaddedAccountsJSONArray.toString(); + DataOutputStream out = new DataOutputStream(fOutput); + out.writeUTF(jsonString); + } catch (IOException ioe) { + // Error in writing to stash file + Log.e(TAG, "unable to write the sync settings to the stash file", ioe); + } + } else { + File stashFile = new File(STASH_FILE); + if (stashFile.exists()) stashFile.delete(); + } + } + + /** + * Restore SyncSettings for all existing accounts from a stashed backup-set + */ + private void accountAddedInternal() { + String jsonString; + + try (FileInputStream fIn = new FileInputStream(new File(STASH_FILE))) { + DataInputStream in = new DataInputStream(fIn); + jsonString = in.readUTF(); + } catch (FileNotFoundException fnfe) { + // This is expected to happen when there is no accounts info stashed + if (DEBUG) Log.d(TAG, "unable to find the stash file", fnfe); + return; + } catch (IOException ioe) { + if (DEBUG) Log.d(TAG, "could not read sync settings from stash file", ioe); + return; + } + + try { + JSONArray unaddedAccountsJSONArray = new JSONArray(jsonString); + restoreFromJsonArray(unaddedAccountsJSONArray); + } catch (JSONException jse) { + // Malformed jsonString + Log.e(TAG, "there was an error with the stashed sync settings", jse); + } + } + + /** + * Restore SyncSettings for all existing accounts from a stashed backup-set + */ + public static void accountAdded(Context context) { + AccountSyncSettingsBackupHelper helper = new AccountSyncSettingsBackupHelper(context); + helper.accountAddedInternal(); + } + /** * Helper method - fetch accounts and return them as a HashSet. * * @return Accounts in a HashSet. */ - private HashSet<Account> getAccountsHashSet() { + private HashSet<Account> getAccounts() { Account[] accounts = mAccountManager.getAccounts(); HashSet<Account> accountHashSet = new HashSet<Account>(); for (Account account : accounts) { @@ -359,4 +422,4 @@ public class AccountSyncSettingsBackupHelper implements BackupHelper { public void writeNewStateDescription(ParcelFileDescriptor newState) { } -} +}
\ No newline at end of file diff --git a/core/jni/android/graphics/BitmapFactory.cpp b/core/jni/android/graphics/BitmapFactory.cpp index 92f781268732..8b248b07a07d 100644 --- a/core/jni/android/graphics/BitmapFactory.cpp +++ b/core/jni/android/graphics/BitmapFactory.cpp @@ -505,12 +505,6 @@ static jobject doDecode(JNIEnv* env, SkStreamRewindable* stream, jobject padding bitmapCreateFlags, ninePatchChunk, ninePatchInsets, -1); } -// Need to buffer enough input to be able to rewind as much as might be read by a decoder -// trying to determine the stream's format. Currently the most is 64, read by -// SkWebpCodec. -// FIXME: Get this number from SkCodec -#define BYTES_TO_BUFFER 64 - static jobject nativeDecodeStream(JNIEnv* env, jobject clazz, jobject is, jbyteArray storage, jobject padding, jobject options) { @@ -519,7 +513,7 @@ static jobject nativeDecodeStream(JNIEnv* env, jobject clazz, jobject is, jbyteA if (stream.get()) { std::unique_ptr<SkStreamRewindable> bufferedStream( - SkFrontBufferedStream::Create(stream.release(), BYTES_TO_BUFFER)); + SkFrontBufferedStream::Create(stream.release(), SkCodec::MinBufferedBytesNeeded())); SkASSERT(bufferedStream.get() != NULL); bitmap = doDecode(env, bufferedStream.release(), padding, options); } @@ -565,7 +559,7 @@ static jobject nativeDecodeFileDescriptor(JNIEnv* env, jobject clazz, jobject fi // ensures that SkImageDecoder::Factory never rewinds beyond the // current position of the file descriptor. std::unique_ptr<SkStreamRewindable> stream(SkFrontBufferedStream::Create(fileStream.release(), - BYTES_TO_BUFFER)); + SkCodec::MinBufferedBytesNeeded())); return doDecode(env, stream.release(), padding, bitmapFactoryOptions); } diff --git a/core/jni/android/graphics/FontFamily.cpp b/core/jni/android/graphics/FontFamily.cpp index 7c8dbe8ecf23..2e974a3ecaa3 100644 --- a/core/jni/android/graphics/FontFamily.cpp +++ b/core/jni/android/graphics/FontFamily.cpp @@ -20,11 +20,13 @@ #include <core_jni_helpers.h> #include "SkData.h" +#include "SkFontMgr.h" #include "SkRefCnt.h" #include "SkTypeface.h" #include "GraphicsJNI.h" #include <ScopedPrimitiveArray.h> #include <ScopedUtfChars.h> +#include <android_runtime/AndroidRuntime.h> #include <android_runtime/android_util_AssetManager.h> #include <androidfw/AssetManager.h> #include "Utils.h" @@ -33,6 +35,8 @@ #include <minikin/FontFamily.h> #include "MinikinSkia.h" +#include <memory> + namespace android { static jlong FontFamily_create(JNIEnv* env, jobject clazz, jstring lang, jint variant) { @@ -69,13 +73,89 @@ static jboolean FontFamily_addFont(JNIEnv* env, jobject clazz, jlong familyPtr, return addSkTypeface(fontFamily, face); } +static struct { + jmethodID mGet; + jmethodID mSize; +} gListClassInfo; + +static struct { + jfieldID mTag; + jfieldID mStyleValue; +} gAxisClassInfo; + +static void release_global_ref(const void* /*data*/, void* context) { + JNIEnv* env = AndroidRuntime::getJNIEnv(); + bool needToAttach = (env == NULL); + if (needToAttach) { + JavaVMAttachArgs args; + args.version = JNI_VERSION_1_4; + args.name = "release_font_data"; + args.group = NULL; + jint result = AndroidRuntime::getJavaVM()->AttachCurrentThread(&env, &args); + if (result != JNI_OK) { + ALOGE("failed to attach to thread to release global ref."); + return; + } + } + + jobject obj = reinterpret_cast<jobject>(context); + env->DeleteGlobalRef(obj); + + if (needToAttach) { + AndroidRuntime::getJavaVM()->DetachCurrentThread(); + } +} + static jboolean FontFamily_addFontWeightStyle(JNIEnv* env, jobject clazz, jlong familyPtr, - jstring path, jint ttcIndex, jint weight, jboolean isItalic) { - NPE_CHECK_RETURN_ZERO(env, path); - ScopedUtfChars str(env, path); - SkTypeface* face = SkTypeface::CreateFromFile(str.c_str(), ttcIndex); + jobject font, jint ttcIndex, jobject listOfAxis, jint weight, jboolean isItalic) { + NPE_CHECK_RETURN_ZERO(env, font); + + // Declare axis native type. + std::unique_ptr<SkFontMgr::FontParameters::Axis[]> skiaAxes; + int skiaAxesLength = 0; + if (listOfAxis) { + jint listSize = env->CallIntMethod(listOfAxis, gListClassInfo.mSize); + + skiaAxes.reset(new SkFontMgr::FontParameters::Axis[listSize]); + skiaAxesLength = listSize; + for (jint i = 0; i < listSize; ++i) { + jobject axisObject = env->CallObjectMethod(listOfAxis, gListClassInfo.mGet, i); + if (!axisObject) { + skiaAxes[i].fTag = 0; + skiaAxes[i].fStyleValue = 0; + continue; + } + + jint tag = env->GetIntField(axisObject, gAxisClassInfo.mTag); + jfloat stylevalue = env->GetFloatField(axisObject, gAxisClassInfo.mStyleValue); + skiaAxes[i].fTag = tag; + skiaAxes[i].fStyleValue = SkFloatToScalar(stylevalue); + } + } + + void* fontPtr = env->GetDirectBufferAddress(font); + if (fontPtr == NULL) { + ALOGE("addFont failed to create font, buffer invalid"); + return false; + } + jlong fontSize = env->GetDirectBufferCapacity(font); + if (fontSize < 0) { + ALOGE("addFont failed to create font, buffer size invalid"); + return false; + } + jobject fontRef = MakeGlobalRefOrDie(env, font); + SkAutoTUnref<SkData> data(SkData::NewWithProc(fontPtr, fontSize, + release_global_ref, reinterpret_cast<void*>(fontRef))); + std::unique_ptr<SkStreamAsset> fontData(new SkMemoryStream(data)); + + SkFontMgr::FontParameters params; + params.setCollectionIndex(ttcIndex); + params.setAxes(skiaAxes.get(), skiaAxesLength); + + SkAutoTUnref<SkFontMgr> fm(SkFontMgr::RefDefault()); + SkTypeface* face = fm->createFromStream(fontData.release(), params); if (face == NULL) { - ALOGE("addFont failed to create font %s", str.c_str()); + ALOGE("addFont failed to create font, invalid request"); return false; } FontFamily* fontFamily = reinterpret_cast<FontFamily*>(familyPtr); @@ -129,15 +209,26 @@ static const JNINativeMethod gFontFamilyMethods[] = { { "nCreateFamily", "(Ljava/lang/String;I)J", (void*)FontFamily_create }, { "nUnrefFamily", "(J)V", (void*)FontFamily_unref }, { "nAddFont", "(JLjava/lang/String;I)Z", (void*)FontFamily_addFont }, - { "nAddFontWeightStyle", "(JLjava/lang/String;IIZ)Z", (void*)FontFamily_addFontWeightStyle }, + { "nAddFontWeightStyle", "(JLjava/nio/ByteBuffer;ILjava/util/List;IZ)Z", + (void*)FontFamily_addFontWeightStyle }, { "nAddFontFromAsset", "(JLandroid/content/res/AssetManager;Ljava/lang/String;)Z", - (void*)FontFamily_addFontFromAsset }, + (void*)FontFamily_addFontFromAsset }, }; int register_android_graphics_FontFamily(JNIEnv* env) { - return RegisterMethodsOrDie(env, "android/graphics/FontFamily", gFontFamilyMethods, - NELEM(gFontFamilyMethods)); + int err = RegisterMethodsOrDie(env, "android/graphics/FontFamily", gFontFamilyMethods, + NELEM(gFontFamilyMethods)); + + jclass listClass = FindClassOrDie(env, "java/util/List"); + gListClassInfo.mGet = GetMethodIDOrDie(env, listClass, "get", "(I)Ljava/lang/Object;"); + gListClassInfo.mSize = GetMethodIDOrDie(env, listClass, "size", "()I"); + + jclass axisClass = FindClassOrDie(env, "android/graphics/FontListParser$Axis"); + gAxisClassInfo.mTag = GetFieldIDOrDie(env, axisClass, "tag", "I"); + gAxisClassInfo.mStyleValue = GetFieldIDOrDie(env, axisClass, "styleValue", "F"); + + return err; } } diff --git a/core/jni/android_media_AudioFormat.h b/core/jni/android_media_AudioFormat.h index bb13c35ced1f..651330446382 100644 --- a/core/jni/android_media_AudioFormat.h +++ b/core/jni/android_media_AudioFormat.h @@ -31,6 +31,7 @@ #define ENCODING_AAC_LC 10 #define ENCODING_AAC_HE_V1 11 #define ENCODING_AAC_HE_V2 12 +#define ENCODING_IEC61937 13 #define ENCODING_INVALID 0 #define ENCODING_DEFAULT 1 @@ -64,6 +65,8 @@ static inline audio_format_t audioFormatToNative(int audioFormat) return AUDIO_FORMAT_AAC_HE_V1; case ENCODING_AAC_HE_V2: return AUDIO_FORMAT_AAC_HE_V2; + case ENCODING_IEC61937: + return AUDIO_FORMAT_IEC61937; case ENCODING_DEFAULT: return AUDIO_FORMAT_DEFAULT; default: @@ -103,6 +106,8 @@ static inline int audioFormatFromNative(audio_format_t nativeFormat) return ENCODING_AAC_HE_V1; case AUDIO_FORMAT_AAC_HE_V2: return ENCODING_AAC_HE_V2; + case AUDIO_FORMAT_IEC61937: + return ENCODING_IEC61937; case AUDIO_FORMAT_DEFAULT: return ENCODING_DEFAULT; default: diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp index 8e8f6c371204..80f8a64122e3 100644 --- a/core/jni/android_media_AudioSystem.cpp +++ b/core/jni/android_media_AudioSystem.cpp @@ -128,11 +128,12 @@ static struct { // other fields unused by JNI } gAudioMixingRuleFields; -static jclass gAttributeMatchCriterionClass; +static jclass gAudioMixMatchCriterionClass; static struct { jfieldID mAttr; + jfieldID mIntProp; jfieldID mRule; -} gAttributeMatchCriterionFields; +} gAudioMixMatchCriterionFields; static jclass gAudioAttributesClass; static struct { @@ -1563,22 +1564,32 @@ static jint convertAudioMixToNative(JNIEnv *env, } for (jint i = 0; i < numCriteria; i++) { - AttributeMatchCriterion nCriterion; + AudioMixMatchCriterion nCriterion; jobject jCriterion = env->GetObjectArrayElement(jCriteria, i); - nCriterion.mRule = env->GetIntField(jCriterion, gAttributeMatchCriterionFields.mRule); + nCriterion.mRule = env->GetIntField(jCriterion, gAudioMixMatchCriterionFields.mRule); - jobject jAttributes = env->GetObjectField(jCriterion, gAttributeMatchCriterionFields.mAttr); - if (nCriterion.mRule == RULE_MATCH_ATTRIBUTE_USAGE || - nCriterion.mRule == RULE_EXCLUDE_ATTRIBUTE_USAGE) { - nCriterion.mAttr.mUsage = (audio_usage_t)env->GetIntField(jAttributes, - gAudioAttributesFields.mUsage); - } else { - nCriterion.mAttr.mSource = (audio_source_t)env->GetIntField(jAttributes, - gAudioAttributesFields.mSource); + const uint32_t match_rule = nCriterion.mRule & ~RULE_EXCLUSION_MASK; + switch (match_rule) { + case RULE_MATCH_UID: + nCriterion.mValue.mUid = env->GetIntField(jCriterion, + gAudioMixMatchCriterionFields.mIntProp); + break; + case RULE_MATCH_ATTRIBUTE_USAGE: + case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET: { + jobject jAttributes = env->GetObjectField(jCriterion, gAudioMixMatchCriterionFields.mAttr); + if (match_rule == RULE_MATCH_ATTRIBUTE_USAGE) { + nCriterion.mValue.mUsage = (audio_usage_t)env->GetIntField(jAttributes, + gAudioAttributesFields.mUsage); + } else { + nCriterion.mValue.mSource = (audio_source_t)env->GetIntField(jAttributes, + gAudioAttributesFields.mSource); + } + env->DeleteLocalRef(jAttributes); + } + break; } - env->DeleteLocalRef(jAttributes); nAudioMix->mCriteria.add(nCriterion); env->DeleteLocalRef(jCriterion); @@ -1833,12 +1844,14 @@ int register_android_media_AudioSystem(JNIEnv *env) gAudioMixingRuleFields.mCriteria = GetFieldIDOrDie(env, audioMixingRuleClass, "mCriteria", "Ljava/util/ArrayList;"); - jclass attributeMatchCriterionClass = - FindClassOrDie(env, "android/media/audiopolicy/AudioMixingRule$AttributeMatchCriterion"); - gAttributeMatchCriterionClass = MakeGlobalRefOrDie(env, attributeMatchCriterionClass); - gAttributeMatchCriterionFields.mAttr = GetFieldIDOrDie(env, attributeMatchCriterionClass, "mAttr", + jclass audioMixMatchCriterionClass = + FindClassOrDie(env, "android/media/audiopolicy/AudioMixingRule$AudioMixMatchCriterion"); + gAudioMixMatchCriterionClass = MakeGlobalRefOrDie(env,audioMixMatchCriterionClass); + gAudioMixMatchCriterionFields.mAttr = GetFieldIDOrDie(env, audioMixMatchCriterionClass, "mAttr", "Landroid/media/AudioAttributes;"); - gAttributeMatchCriterionFields.mRule = GetFieldIDOrDie(env, attributeMatchCriterionClass, "mRule", + gAudioMixMatchCriterionFields.mIntProp = GetFieldIDOrDie(env, audioMixMatchCriterionClass, "mIntProp", + "I"); + gAudioMixMatchCriterionFields.mRule = GetFieldIDOrDie(env, audioMixMatchCriterionClass, "mRule", "I"); jclass audioAttributesClass = FindClassOrDie(env, "android/media/AudioAttributes"); diff --git a/core/jni/android_media_AudioTrack.cpp b/core/jni/android_media_AudioTrack.cpp index 61f185e10f4c..1ab95042a186 100644 --- a/core/jni/android_media_AudioTrack.cpp +++ b/core/jni/android_media_AudioTrack.cpp @@ -246,7 +246,7 @@ android_media_AudioTrack_setup(JNIEnv *env, jobject thiz, jobject weak_this, // compute the frame count size_t frameCount; - if (audio_is_linear_pcm(format)) { + if (audio_has_proportional_frames(format)) { const size_t bytesPerSample = audio_bytes_per_sample(format); frameCount = buffSizeInBytes / (channelCount * bytesPerSample); } else { @@ -1008,7 +1008,7 @@ static jint android_media_AudioTrack_get_min_buff_size(JNIEnv *env, jobject thi return -1; } const audio_format_t format = audioFormatToNative(audioFormat); - if (audio_is_linear_pcm(format)) { + if (audio_has_proportional_frames(format)) { const size_t bytesPerSample = audio_bytes_per_sample(format); return frameCount * channelCount * bytesPerSample; } else { diff --git a/core/jni/android_view_ThreadedRenderer.cpp b/core/jni/android_view_ThreadedRenderer.cpp index dd0e45636ee7..ac7700786ea0 100644 --- a/core/jni/android_view_ThreadedRenderer.cpp +++ b/core/jni/android_view_ThreadedRenderer.cpp @@ -322,7 +322,11 @@ private: void NotifyHandler::handleMessage(const Message& message) { JNIEnv* env = getenv(mVm); - jobject target = env->NewLocalRef(mObserver->getObserverReference()); + ObserverProxy* observer = mObserver.get(); + LOG_ALWAYS_FATAL_IF(observer == nullptr, "received message with no observer configured"); + LOG_ALWAYS_FATAL_IF(mBuffer == nullptr, "received message with no data to report"); + + jobject target = env->NewLocalRef(observer->getObserverReference()); if (target != nullptr) { jlongArray javaBuffer = get_metrics_buffer(env, target); diff --git a/core/res/res/layout/floating_popup_container.xml b/core/res/res/layout/floating_popup_container.xml index dd161e38486e..ca0373773577 100644 --- a/core/res/res/layout/floating_popup_container.xml +++ b/core/res/res/layout/floating_popup_container.xml @@ -19,8 +19,8 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:padding="0dp" - android:layout_margin="20dp" - android:elevation="2dp" + android:layout_margin="@android:dimen/text_edit_floating_toolbar_margin" + android:elevation="@android:dimen/text_edit_floating_toolbar_elevation" android:focusable="true" android:focusableInTouchMode="true" android:background="?attr/floatingToolbarPopupBackgroundDrawable"/> diff --git a/core/res/res/layout/text_edit_suggestion_container.xml b/core/res/res/layout/text_edit_suggestion_container.xml index 17e93d0a9eae..b2589da8ea5b 100644 --- a/core/res/res/layout/text_edit_suggestion_container.xml +++ b/core/res/res/layout/text_edit_suggestion_container.xml @@ -22,8 +22,8 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="vertical" - android:elevation="2dp" - android:layout_margin="20dp" + android:elevation="@android:dimen/text_edit_floating_toolbar_elevation" + android:layout_margin="@android:dimen/text_edit_floating_toolbar_margin" android:background="@drawable/text_edit_suggestions_window" android:dropDownSelector="@drawable/list_selector_background" android:divider="@null"> diff --git a/core/res/res/layout/text_edit_suggestion_container_material.xml b/core/res/res/layout/text_edit_suggestion_container_material.xml index 78268036c827..20a80489239d 100644 --- a/core/res/res/layout/text_edit_suggestion_container_material.xml +++ b/core/res/res/layout/text_edit_suggestion_container_material.xml @@ -24,8 +24,8 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="?android:attr/floatingToolbarPopupBackgroundDrawable" - android:elevation="2dp" - android:layout_margin="20dp" + android:elevation="@android:dimen/text_edit_floating_toolbar_elevation" + android:layout_margin="@android:dimen/text_edit_floating_toolbar_margin" android:orientation="vertical" android:divider="?android:attr/listDivider" android:showDividers="middle"> diff --git a/core/res/res/values-watch/themes_device_defaults.xml b/core/res/res/values-watch/themes_device_defaults.xml index 63df5beff76d..61753b1f0211 100644 --- a/core/res/res/values-watch/themes_device_defaults.xml +++ b/core/res/res/values-watch/themes_device_defaults.xml @@ -25,6 +25,7 @@ <style name="Theme.DeviceDefault.Light.Dialog" parent="Theme.Micro.Dialog" /> <style name="Theme.DeviceDefault.Light.DialogWhenLarge" parent="Theme.Micro.Dialog" /> <style name="Theme.DeviceDefault.Light.Dialog.Alert" parent="Theme.Micro.Dialog.Alert" /> + <style name="Theme.DeviceDefault.Settings" parent="Theme.Micro" /> <style name="Theme.DeviceDefault.Wallpaper" parent="Theme.Micro" /> </resources> diff --git a/core/res/res/values/dimens_material.xml b/core/res/res/values/dimens_material.xml index 96a81d138457..2fe4f6652a87 100644 --- a/core/res/res/values/dimens_material.xml +++ b/core/res/res/values/dimens_material.xml @@ -82,6 +82,9 @@ <dimen name="text_size_medium_material">18sp</dimen> <dimen name="text_size_small_material">14sp</dimen> + <dimen name="text_edit_floating_toolbar_elevation">2dp</dimen> + <dimen name="text_edit_floating_toolbar_margin">20dp</dimen> + <dimen name="floating_window_z">16dp</dimen> <dimen name="floating_window_margin_left">16dp</dimen> <dimen name="floating_window_margin_top">8dp</dimen> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 4e10d39e0005..daa8202fa3e5 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -4204,11 +4204,16 @@ <string name="new_sms_notification_content">Open SMS app to view</string> <!-- Notification title shown when user profile is credential encrypted and requires the user to unlock before some features are usable [CHAR LIMIT=30] --> - <string name="user_encrypted_title">Some functions might not be available</string> + <string name="user_encrypted_title">Some functionality may be limited</string> <!-- Notification message shown when user profile is credential encrypted and requires the user to unlock before some features are usable [CHAR LIMIT=30] --> - <string name="user_encrypted_message">Touch to continue</string> + <string name="user_encrypted_message">Tap to unlock</string> <!-- Notification detail shown when user profile is credential encrypted and requires the user to unlock before some features are usable [CHAR LIMIT=30] --> - <string name="user_encrypted_detail">User profile locked</string> + <string name="user_encrypted_detail">User data locked</string> + + <!-- Notification detail shown when work profile is credential encrypted and requires the user to unlock before some features are usable [CHAR LIMIT=30] --> + <string name="profile_encrypted_detail">Work profile locked</string> + <!-- Notification message shown when work profile is credential encrypted and requires the user to unlock before some features are usable [CHAR LIMIT=30] --> + <string name="profile_encrypted_message">Tap to unlock work profile</string> <!-- Title of notification shown after a MTP device is connected to Android. --> <string name="usb_mtp_launch_notification_title">Connected to <xliff:g id="product_name">%1$s</xliff:g></string> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index f75f023b8c31..8df6c2e923ac 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -2522,6 +2522,8 @@ <java-symbol type="string" name="user_encrypted_title" /> <java-symbol type="string" name="user_encrypted_message" /> <java-symbol type="string" name="user_encrypted_detail" /> + <java-symbol type="string" name="profile_encrypted_detail" /> + <java-symbol type="string" name="profile_encrypted_message" /> <java-symbol type="drawable" name="ic_user_secure" /> <java-symbol type="string" name="usb_mtp_launch_notification_title" /> diff --git a/core/tests/coretests/src/com/android/internal/util/LineBreakBufferedWriterTest.java b/core/tests/coretests/src/com/android/internal/util/LineBreakBufferedWriterTest.java index 49ae10401983..4845c4ef28f8 100644 --- a/core/tests/coretests/src/com/android/internal/util/LineBreakBufferedWriterTest.java +++ b/core/tests/coretests/src/com/android/internal/util/LineBreakBufferedWriterTest.java @@ -180,6 +180,22 @@ public class LineBreakBufferedWriterTest extends TestCase { assertOutput("aaaaaaaaaabbbbbc\nd", "ddddddddd"); } + public void testMoreThenInitialCapacitySimpleWrites() { + // This check is different from testMoreThanBufferSizeChar. The initial capacity is lower + // than the maximum buffer size here. + final LineBreakBufferedWriter lw = new LineBreakBufferedWriter(mWriter, 1024, 3); + + for(int i = 0; i < 10; i++) { + lw.print('$'); + } + for(int i = 0; i < 10; i++) { + lw.print('%'); + } + lw.flush(); + + assertOutput("$$$$$$$$$$%%%%%%%%%%"); + } + private void assertOutput(String... golden) { List<String> goldList = createTestGolden(golden); assertEquals(goldList, mWriter.getStrings()); diff --git a/graphics/java/android/graphics/FontFamily.java b/graphics/java/android/graphics/FontFamily.java index 6309ed36eb51..f741e3cb5f8d 100644 --- a/graphics/java/android/graphics/FontFamily.java +++ b/graphics/java/android/graphics/FontFamily.java @@ -18,6 +18,9 @@ package android.graphics; import android.content.res.AssetManager; +import java.nio.ByteBuffer; +import java.util.List; + /** * A family of typefaces with different styles. * @@ -62,8 +65,9 @@ public class FontFamily { return nAddFont(mNativePtr, path, ttcIndex); } - public boolean addFontWeightStyle(String path, int ttcIndex, int weight, boolean style) { - return nAddFontWeightStyle(mNativePtr, path, ttcIndex, weight, style); + public boolean addFontWeightStyle(ByteBuffer font, int ttcIndex, List<FontListParser.Axis> axes, + int weight, boolean style) { + return nAddFontWeightStyle(mNativePtr, font, ttcIndex, axes, weight, style); } public boolean addFontFromAsset(AssetManager mgr, String path) { @@ -73,8 +77,9 @@ public class FontFamily { private static native long nCreateFamily(String lang, int variant); private static native void nUnrefFamily(long nativePtr); private static native boolean nAddFont(long nativeFamily, String path, int ttcIndex); - private static native boolean nAddFontWeightStyle(long nativeFamily, String path, - int ttcIndex, int weight, boolean isItalic); + private static native boolean nAddFontWeightStyle(long nativeFamily, ByteBuffer font, + int ttcIndex, List<FontListParser.Axis> listOfAxis, + int weight, boolean isItalic); private static native boolean nAddFontFromAsset(long nativeFamily, AssetManager mgr, String path); } diff --git a/graphics/java/android/graphics/FontListParser.java b/graphics/java/android/graphics/FontListParser.java index 774f6b8922e0..8f7c6a62a4a3 100644 --- a/graphics/java/android/graphics/FontListParser.java +++ b/graphics/java/android/graphics/FontListParser.java @@ -25,6 +25,7 @@ import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.List; +import java.util.regex.Pattern; /** * Parser for font config files. @@ -42,15 +43,26 @@ public class FontListParser { public List<Alias> aliases; } + public static class Axis { + Axis(int tag, float styleValue) { + this.tag = tag; + this.styleValue = styleValue; + } + public final int tag; + public final float styleValue; + } + public static class Font { - Font(String fontName, int ttcIndex, int weight, boolean isItalic) { + Font(String fontName, int ttcIndex, List<Axis> axes, int weight, boolean isItalic) { this.fontName = fontName; this.ttcIndex = ttcIndex; + this.axes = axes; this.weight = weight; this.isItalic = isItalic; } public String fontName; public int ttcIndex; + public final List<Axis> axes; public int weight; public boolean isItalic; } @@ -93,9 +105,10 @@ public class FontListParser { parser.require(XmlPullParser.START_TAG, null, "familyset"); while (parser.next() != XmlPullParser.END_TAG) { if (parser.getEventType() != XmlPullParser.START_TAG) continue; - if (parser.getName().equals("family")) { + String tag = parser.getName(); + if (tag.equals("family")) { config.families.add(readFamily(parser)); - } else if (parser.getName().equals("alias")) { + } else if (tag.equals("alias")) { config.aliases.add(readAlias(parser)); } else { skip(parser); @@ -114,14 +127,7 @@ public class FontListParser { if (parser.getEventType() != XmlPullParser.START_TAG) continue; String tag = parser.getName(); if (tag.equals("font")) { - String ttcIndexStr = parser.getAttributeValue(null, "index"); - int ttcIndex = ttcIndexStr == null ? 0 : Integer.parseInt(ttcIndexStr); - String weightStr = parser.getAttributeValue(null, "weight"); - int weight = weightStr == null ? 400 : Integer.parseInt(weightStr); - boolean isItalic = "italic".equals(parser.getAttributeValue(null, "style")); - String filename = parser.nextText(); - String fullFilename = "/system/fonts/" + filename; - fonts.add(new Font(fullFilename, ttcIndex, weight, isItalic)); + fonts.add(readFont(parser)); } else { skip(parser); } @@ -129,6 +135,70 @@ public class FontListParser { return new Family(name, fonts, lang, variant); } + /** Matches leading and trailing XML whitespace. */ + private static final Pattern FILENAME_WHITESPACE_PATTERN = + Pattern.compile("^[ \\n\\r\\t]+|[ \\n\\r\\t]+$"); + + private static Font readFont(XmlPullParser parser) + throws XmlPullParserException, IOException { + String indexStr = parser.getAttributeValue(null, "index"); + int index = indexStr == null ? 0 : Integer.parseInt(indexStr); + List<Axis> axes = new ArrayList<Axis>(); + String weightStr = parser.getAttributeValue(null, "weight"); + int weight = weightStr == null ? 400 : Integer.parseInt(weightStr); + boolean isItalic = "italic".equals(parser.getAttributeValue(null, "style")); + StringBuilder filename = new StringBuilder(); + while (parser.next() != XmlPullParser.END_TAG) { + if (parser.getEventType() == XmlPullParser.TEXT) { + filename.append(parser.getText()); + } + if (parser.getEventType() != XmlPullParser.START_TAG) continue; + String tag = parser.getName(); + if (tag.equals("axis")) { + axes.add(readAxis(parser)); + } else { + skip(parser); + } + } + String fullFilename = "/system/fonts/" + + FILENAME_WHITESPACE_PATTERN.matcher(filename).replaceAll(""); + return new Font(fullFilename, index, axes, weight, isItalic); + } + + /** The 'tag' attribute value is read as four character values between 0 and 255 inclusive. */ + private static final Pattern TAG_PATTERN = Pattern.compile("[\\x00-\\xFF]{4}"); + + /** The 'styleValue' attribute has an optional leading '-', followed by '<digits>', + * '<digits>.<digits>', or '.<digits>' where '<digits>' is one or more of [0-9]. + */ + private static final Pattern STYLE_VALUE_PATTERN = + Pattern.compile("-?(([0-9]+(\\.[0-9]+)?)|(\\.[0-9]+))"); + + private static Axis readAxis(XmlPullParser parser) + throws XmlPullParserException, IOException { + int tag = 0; + String tagStr = parser.getAttributeValue(null, "tag"); + if (tagStr != null && TAG_PATTERN.matcher(tagStr).matches()) { + tag = tagStr.charAt(0) << 24 + + tagStr.charAt(1) << 16 + + tagStr.charAt(2) << 8 + + tagStr.charAt(3); + } else { + throw new XmlPullParserException("Invalid tag attribute value.", parser, null); + } + + float styleValue = 0; + String styleValueStr = parser.getAttributeValue(null, "stylevalue"); + if (styleValueStr != null && STYLE_VALUE_PATTERN.matcher(styleValueStr).matches()) { + styleValue = Float.parseFloat(styleValueStr); + } else { + throw new XmlPullParserException("Invalid styleValue attribute value.", parser, null); + } + + skip(parser); // axis tag is empty, ignore any contents and consume end tag + return new Axis(tag, styleValue); + } + private static Alias readAlias(XmlPullParser parser) throws XmlPullParserException, IOException { Alias alias = new Alias(); diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java index 1294323ce69b..f15aff7de39c 100644 --- a/graphics/java/android/graphics/Typeface.java +++ b/graphics/java/android/graphics/Typeface.java @@ -17,7 +17,6 @@ package android.graphics; import android.content.res.AssetManager; -import android.graphics.FontListParser.Family; import android.util.Log; import android.util.LongSparseArray; import android.util.SparseArray; @@ -28,6 +27,8 @@ import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -259,10 +260,26 @@ public class Typeface { mStyle = nativeGetStyle(ni); } - private static FontFamily makeFamilyFromParsed(FontListParser.Family family) { + private static FontFamily makeFamilyFromParsed(FontListParser.Family family, + Map<String, ByteBuffer> bufferForPath) { FontFamily fontFamily = new FontFamily(family.lang, family.variant); for (FontListParser.Font font : family.fonts) { - fontFamily.addFontWeightStyle(font.fontName, font.ttcIndex, font.weight, font.isItalic); + ByteBuffer fontBuffer = bufferForPath.get(font.fontName); + if (fontBuffer == null) { + try (FileInputStream file = new FileInputStream(font.fontName)) { + FileChannel fileChannel = file.getChannel(); + long fontSize = fileChannel.size(); + fontBuffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, fontSize); + bufferForPath.put(font.fontName, fontBuffer); + } catch (IOException e) { + Log.e(TAG, "Error mapping font file " + font.fontName); + continue; + } + } + if (!fontFamily.addFontWeightStyle(fontBuffer, font.ttcIndex, font.axes, + font.weight, font.isItalic)) { + Log.e(TAG, "Error creating font " + font.fontName + "#" + font.ttcIndex); + } } return fontFamily; } @@ -280,13 +297,15 @@ public class Typeface { FileInputStream fontsIn = new FileInputStream(configFilename); FontListParser.Config fontConfig = FontListParser.parse(fontsIn); + Map<String, ByteBuffer> bufferForPath = new HashMap<String, ByteBuffer>(); + List<FontFamily> familyList = new ArrayList<FontFamily>(); // Note that the default typeface is always present in the fallback list; // this is an enhancement from pre-Minikin behavior. for (int i = 0; i < fontConfig.families.size(); i++) { - Family f = fontConfig.families.get(i); + FontListParser.Family f = fontConfig.families.get(i); if (i == 0 || f.name == null) { - familyList.add(makeFamilyFromParsed(f)); + familyList.add(makeFamilyFromParsed(f, bufferForPath)); } } sFallbackFonts = familyList.toArray(new FontFamily[familyList.size()]); @@ -295,14 +314,14 @@ public class Typeface { Map<String, Typeface> systemFonts = new HashMap<String, Typeface>(); for (int i = 0; i < fontConfig.families.size(); i++) { Typeface typeface; - Family f = fontConfig.families.get(i); + FontListParser.Family f = fontConfig.families.get(i); if (f.name != null) { if (i == 0) { // The first entry is the default typeface; no sense in // duplicating the corresponding FontFamily. typeface = sDefaultTypeface; } else { - FontFamily fontFamily = makeFamilyFromParsed(f); + FontFamily fontFamily = makeFamilyFromParsed(f, bufferForPath); FontFamily[] families = { fontFamily }; typeface = Typeface.createFromFamiliesWithDefault(families); } @@ -324,11 +343,11 @@ public class Typeface { Log.w(TAG, "Didn't create default family (most likely, non-Minikin build)", e); // TODO: normal in non-Minikin case, remove or make error when Minikin-only } catch (FileNotFoundException e) { - Log.e(TAG, "Error opening " + configFilename); + Log.e(TAG, "Error opening " + configFilename, e); } catch (IOException e) { - Log.e(TAG, "Error reading " + configFilename); + Log.e(TAG, "Error reading " + configFilename, e); } catch (XmlPullParserException e) { - Log.e(TAG, "XML parse exception for " + configFilename); + Log.e(TAG, "XML parse exception for " + configFilename, e); } } diff --git a/graphics/java/android/graphics/drawable/VectorDrawable.java b/graphics/java/android/graphics/drawable/VectorDrawable.java index bdbf3c04b000..9e0f1b460839 100644 --- a/graphics/java/android/graphics/drawable/VectorDrawable.java +++ b/graphics/java/android/graphics/drawable/VectorDrawable.java @@ -1482,8 +1482,9 @@ public class VectorDrawable extends Drawable { if (mThemeAttrs != null) { return true; } - boolean fillCanApplyTheme = canGradientApplyTheme(mFillColors); - boolean strokeCanApplyTheme = canGradientApplyTheme(mStrokeColors); + + boolean fillCanApplyTheme = canComplexColorApplyTheme(mFillColors); + boolean strokeCanApplyTheme = canComplexColorApplyTheme(mStrokeColors); if (fillCanApplyTheme || strokeCanApplyTheme) { return true; } @@ -1493,30 +1494,42 @@ public class VectorDrawable extends Drawable { @Override public void applyTheme(Theme t) { + // Resolve the theme attributes directly referred by the VectorDrawable. if (mThemeAttrs != null) { final TypedArray a = t.resolveAttributes(mThemeAttrs, R.styleable.VectorDrawablePath); updateStateFromTypedArray(a); a.recycle(); } - boolean fillCanApplyTheme = canGradientApplyTheme(mFillColors); - boolean strokeCanApplyTheme = canGradientApplyTheme(mStrokeColors); + // Resolve the theme attributes in-directly referred by the VectorDrawable, for example, + // fillColor can refer to a color state list which itself needs to apply theme. + // And this is the reason we still want to keep partial update for the path's properties. + boolean fillCanApplyTheme = canComplexColorApplyTheme(mFillColors); + boolean strokeCanApplyTheme = canComplexColorApplyTheme(mStrokeColors); + if (fillCanApplyTheme) { mFillColors = mFillColors.obtainForTheme(t); - nUpdateFullPathFillGradient(mNativePtr, - ((GradientColor)mFillColors).getShader().getNativeInstance()); + if (mFillColors instanceof GradientColor) { + nUpdateFullPathFillGradient(mNativePtr, + ((GradientColor) mFillColors).getShader().getNativeInstance()); + } else if (mFillColors instanceof ColorStateList) { + nSetFillColor(mNativePtr, mFillColors.getDefaultColor()); + } } if (strokeCanApplyTheme) { mStrokeColors = mStrokeColors.obtainForTheme(t); - nUpdateFullPathStrokeGradient(mNativePtr, - ((GradientColor)mStrokeColors).getShader().getNativeInstance()); + if (mStrokeColors instanceof GradientColor) { + nUpdateFullPathStrokeGradient(mNativePtr, + ((GradientColor) mStrokeColors).getShader().getNativeInstance()); + } else if (mStrokeColors instanceof ColorStateList) { + nSetStrokeColor(mNativePtr, mStrokeColors.getDefaultColor()); + } } } - private boolean canGradientApplyTheme(ComplexColor complexColor) { - return complexColor != null && complexColor.canApplyTheme() - && complexColor instanceof GradientColor; + private boolean canComplexColorApplyTheme(ComplexColor complexColor) { + return complexColor != null && complexColor.canApplyTheme(); } /* Setters and Getters, used by animator from AnimatedVectorDrawable. */ diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp index 1d9fe35bac35..3277c36c8a33 100644 --- a/libs/androidfw/ResourceTypes.cpp +++ b/libs/androidfw/ResourceTypes.cpp @@ -2236,7 +2236,7 @@ bool ResTable_config::isLocaleBetterThan(const ResTable_config& o, // See if any of the regions is better than the other const int region_comparison = localeDataCompareRegions( country, o.country, - language, localeScript, requested->country); + language, requested->localeScript, requested->country); if (region_comparison != 0) { return (region_comparison > 0); } @@ -2526,17 +2526,34 @@ bool ResTable_config::match(const ResTable_config& settings) const { // For backward compatibility and supporting private-use locales, we // fall back to old behavior if we couldn't determine the script for - // either of the desired locale or the provided locale. - if (localeScript[0] == '\0' || localeScript[1] == '\0') { + // either of the desired locale or the provided locale. But if we could determine + // the scripts, they should be the same for the locales to match. + bool countriesMustMatch = false; + char computed_script[4]; + const char* script; + if (settings.localeScript[0] == '\0') { // could not determine the request's script + countriesMustMatch = true; + } else { + if (localeScript[0] == '\0') { // script was not provided, so we try to compute it + localeDataComputeScript(computed_script, language, country); + if (computed_script[0] == '\0') { // we could not compute the script + countriesMustMatch = true; + } else { + script = computed_script; + } + } else { // script was provided, so just use it + script = localeScript; + } + } + + if (countriesMustMatch) { if (country[0] != '\0' && (country[0] != settings.country[0] || country[1] != settings.country[1])) { return false; } } else { - // But if we could determine the scripts, they should be the same - // for the locales to match. - if (memcmp(localeScript, settings.localeScript, sizeof(localeScript)) != 0) { + if (memcmp(script, settings.localeScript, sizeof(settings.localeScript)) != 0) { return false; } } diff --git a/libs/androidfw/tests/ConfigLocale_test.cpp b/libs/androidfw/tests/ConfigLocale_test.cpp index 7b386404fe8c..4b8d65cf86b4 100644 --- a/libs/androidfw/tests/ConfigLocale_test.cpp +++ b/libs/androidfw/tests/ConfigLocale_test.cpp @@ -371,6 +371,19 @@ TEST(ConfigLocaleTest, match) { EXPECT_TRUE(supported.match(requested)); } +TEST(ConfigLocaleTest, match_emptyScript) { + ResTable_config supported, requested; + + fillIn("fr", "FR", NULL, NULL, &supported); + fillIn("fr", "CA", NULL, NULL, &requested); + + // emulate packages built with older AAPT + memset(supported.localeScript, '\0', 4); + supported.localeScriptWasProvided = false; + + EXPECT_TRUE(supported.match(requested)); +} + TEST(ConfigLocaleTest, isLocaleBetterThan_basics) { ResTable_config config1, config2, request; diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk index 1f242a36e80e..6a565033093c 100644 --- a/libs/hwui/Android.mk +++ b/libs/hwui/Android.mk @@ -144,20 +144,8 @@ hwui_c_includes += \ external/skia/include/private \ external/skia/src/core -hwui_shared_libraries := \ - liblog \ - libcutils \ - libutils \ - libEGL \ - libGLESv2 \ - libskia \ - libui \ - libgui \ - libprotobuf-cpp-lite \ - ifneq (false,$(ANDROID_ENABLE_RENDERSCRIPT)) hwui_cflags += -DANDROID_ENABLE_RENDERSCRIPT - hwui_shared_libraries += libRS libRScpp hwui_c_includes += \ $(call intermediates-dir-for,STATIC_LIBRARIES,libRS,TARGET,) \ frameworks/rs/cpp \ @@ -180,12 +168,15 @@ include $(CLEAR_VARS) LOCAL_MODULE_CLASS := STATIC_LIBRARIES LOCAL_MODULE := libhwui_static -LOCAL_SHARED_LIBRARIES := $(hwui_shared_libraries) LOCAL_CFLAGS := $(hwui_cflags) LOCAL_SRC_FILES := $(hwui_src_files) LOCAL_C_INCLUDES := $(hwui_c_includes) $(call hwui_proto_include) -LOCAL_EXPORT_C_INCLUDE_DIRS := $(hwui_c_includes) $(call hwui_proto_include) +LOCAL_EXPORT_C_INCLUDE_DIRS := \ + $(LOCAL_PATH) \ + $(hwui_c_includes) \ + $(call hwui_proto_include) +include $(LOCAL_PATH)/hwui_static_deps.mk include $(BUILD_STATIC_LIBRARY) # ------------------------ @@ -196,7 +187,6 @@ include $(CLEAR_VARS) LOCAL_MODULE_CLASS := STATIC_LIBRARIES LOCAL_MODULE := libhwui_static_null_gpu -LOCAL_SHARED_LIBRARIES := $(hwui_shared_libraries) LOCAL_CFLAGS := \ $(hwui_cflags) \ -DHWUI_NULL_GPU @@ -205,8 +195,12 @@ LOCAL_SRC_FILES := \ debug/nullegl.cpp \ debug/nullgles.cpp LOCAL_C_INCLUDES := $(hwui_c_includes) $(call hwui_proto_include) -LOCAL_EXPORT_C_INCLUDE_DIRS := $(hwui_c_includes) $(call hwui_proto_include) +LOCAL_EXPORT_C_INCLUDE_DIRS := \ + $(LOCAL_PATH) \ + $(hwui_c_includes) \ + $(call hwui_proto_include) +include $(LOCAL_PATH)/hwui_static_deps.mk include $(BUILD_STATIC_LIBRARY) # ------------------------ @@ -218,8 +212,9 @@ include $(CLEAR_VARS) LOCAL_MODULE_CLASS := SHARED_LIBRARIES LOCAL_MODULE := libhwui LOCAL_WHOLE_STATIC_LIBRARIES := libhwui_static -LOCAL_SHARED_LIBRARIES := $(hwui_shared_libraries) +LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH) +include $(LOCAL_PATH)/hwui_static_deps.mk include $(BUILD_SHARED_LIBRARY) # ------------------------ @@ -230,7 +225,6 @@ include $(CLEAR_VARS) LOCAL_MODULE := hwui_unit_tests LOCAL_MODULE_TAGS := tests -LOCAL_SHARED_LIBRARIES := $(hwui_shared_libraries) LOCAL_STATIC_LIBRARIES := libhwui_static_null_gpu LOCAL_CFLAGS := \ $(hwui_cflags) \ @@ -263,6 +257,7 @@ ifeq (true, $(HWUI_NEW_OPS)) tests/unit/RecordingCanvasTests.cpp endif +include $(LOCAL_PATH)/hwui_static_deps.mk include $(BUILD_NATIVE_TEST) # ------------------------ @@ -278,7 +273,6 @@ LOCAL_MODULE_CLASS := EXECUTABLES LOCAL_MULTILIB := both LOCAL_MODULE_STEM_32 := hwuitest LOCAL_MODULE_STEM_64 := hwuitest64 -LOCAL_SHARED_LIBRARIES := $(hwui_shared_libraries) LOCAL_CFLAGS := $(hwui_cflags) # set to libhwui_static_null_gpu to skip actual GL commands @@ -289,6 +283,7 @@ LOCAL_SRC_FILES += \ tests/macrobench/TestSceneRunner.cpp \ tests/macrobench/main.cpp +include $(LOCAL_PATH)/hwui_static_deps.mk include $(BUILD_EXECUTABLE) # ------------------------ @@ -303,7 +298,6 @@ LOCAL_MODULE_CLASS := EXECUTABLES LOCAL_MULTILIB := both LOCAL_MODULE_STEM_32 := hwuimicro LOCAL_MODULE_STEM_64 := hwuimicro64 -LOCAL_SHARED_LIBRARIES := $(hwui_shared_libraries) LOCAL_CFLAGS := \ $(hwui_cflags) \ -DHWUI_NULL_GPU @@ -325,6 +319,5 @@ ifeq (true, $(HWUI_NEW_OPS)) tests/microbench/FrameBuilderBench.cpp endif -LOCAL_CLANG := true # workaround gcc bug - +include $(LOCAL_PATH)/hwui_static_deps.mk include $(BUILD_EXECUTABLE) diff --git a/libs/hwui/BakedOpState.h b/libs/hwui/BakedOpState.h index 3db28c982469..5a5845af81b9 100644 --- a/libs/hwui/BakedOpState.h +++ b/libs/hwui/BakedOpState.h @@ -100,7 +100,7 @@ public: static BakedOpState* tryConstruct(LinearAllocator& allocator, Snapshot& snapshot, const RecordedOp& recordedOp) { if (CC_UNLIKELY(snapshot.getRenderTargetClip().isEmpty())) return nullptr; - BakedOpState* bakedState = new (allocator) BakedOpState( + BakedOpState* bakedState = allocator.create_trivial<BakedOpState>( allocator, snapshot, recordedOp, false); if (bakedState->computedState.clippedBounds.isEmpty()) { // bounds are empty, so op is rejected @@ -124,7 +124,7 @@ public: ? (recordedOp.paint && recordedOp.paint->getStyle() != SkPaint::kFill_Style) : true; - BakedOpState* bakedState = new (allocator) BakedOpState( + BakedOpState* bakedState = allocator.create_trivial<BakedOpState>( allocator, snapshot, recordedOp, expandForStroke); if (bakedState->computedState.clippedBounds.isEmpty()) { // bounds are empty, so op is rejected @@ -140,16 +140,12 @@ public: if (CC_UNLIKELY(snapshot.getRenderTargetClip().isEmpty())) return nullptr; // clip isn't empty, so construct the op - return new (allocator) BakedOpState(allocator, snapshot, shadowOpPtr); + return allocator.create_trivial<BakedOpState>(allocator, snapshot, shadowOpPtr); } static BakedOpState* directConstruct(LinearAllocator& allocator, const ClipRect* clip, const Rect& dstRect, const RecordedOp& recordedOp) { - return new (allocator) BakedOpState(clip, dstRect, recordedOp); - } - - static void* operator new(size_t size, LinearAllocator& allocator) { - return allocator.alloc(size); + return allocator.create_trivial<BakedOpState>(clip, dstRect, recordedOp); } // computed state: @@ -162,6 +158,8 @@ public: const RecordedOp* op; private: + friend class LinearAllocator; + BakedOpState(LinearAllocator& allocator, Snapshot& snapshot, const RecordedOp& recordedOp, bool expandForStroke) : computedState(allocator, snapshot, recordedOp, expandForStroke) diff --git a/libs/hwui/DamageAccumulator.cpp b/libs/hwui/DamageAccumulator.cpp index c2e14a29f29e..6d5833b3be86 100644 --- a/libs/hwui/DamageAccumulator.cpp +++ b/libs/hwui/DamageAccumulator.cpp @@ -45,7 +45,7 @@ struct DirtyStack { }; DamageAccumulator::DamageAccumulator() { - mHead = (DirtyStack*) mAllocator.alloc(sizeof(DirtyStack)); + mHead = mAllocator.create_trivial<DirtyStack>(); memset(mHead, 0, sizeof(DirtyStack)); // Create a root that we will not pop off mHead->prev = mHead; @@ -78,7 +78,7 @@ void DamageAccumulator::computeCurrentTransform(Matrix4* outMatrix) const { void DamageAccumulator::pushCommon() { if (!mHead->next) { - DirtyStack* nextFrame = (DirtyStack*) mAllocator.alloc(sizeof(DirtyStack)); + DirtyStack* nextFrame = mAllocator.create_trivial<DirtyStack>(); nextFrame->next = nullptr; nextFrame->prev = mHead; mHead->next = nextFrame; diff --git a/libs/hwui/DeferredDisplayList.h b/libs/hwui/DeferredDisplayList.h index 2d5979f2f1a7..98ccf11b1c2a 100644 --- a/libs/hwui/DeferredDisplayList.h +++ b/libs/hwui/DeferredDisplayList.h @@ -49,11 +49,6 @@ typedef const void* mergeid_t; class DeferredDisplayState { public: - static void* operator new(size_t size) = delete; - static void* operator new(size_t size, LinearAllocator& allocator) { - return allocator.alloc(size); - } - // global op bounds, mapped by mMatrix to be in screen space coordinates, clipped Rect mBounds; @@ -124,7 +119,7 @@ private: DeferredDisplayList(const DeferredDisplayList& other); // disallow copy DeferredDisplayState* createState() { - return new (mAllocator) DeferredDisplayState(); + return mAllocator.create_trivial<DeferredDisplayState>(); } void tryRecycleState(DeferredDisplayState* state) { diff --git a/libs/hwui/DisplayListCanvas.h b/libs/hwui/DisplayListCanvas.h index e5711e35a88b..a703e227fc8d 100644 --- a/libs/hwui/DisplayListCanvas.h +++ b/libs/hwui/DisplayListCanvas.h @@ -251,7 +251,7 @@ private: inline const T* refBuffer(const T* srcBuffer, int32_t count) { if (!srcBuffer) return nullptr; - T* dstBuffer = (T*) mDisplayList->allocator.alloc(count * sizeof(T)); + T* dstBuffer = (T*) mDisplayList->allocator.alloc<T>(count * sizeof(T)); memcpy(dstBuffer, srcBuffer, count * sizeof(T)); return dstBuffer; } @@ -320,8 +320,7 @@ private: // correctly, such as creating the bitmap from scratch, drawing with it, changing its // contents, and drawing again. The only fix would be to always copy it the first time, // which doesn't seem worth the extra cycles for this unlikely case. - SkBitmap* localBitmap = new (alloc()) SkBitmap(bitmap); - alloc().autoDestroy(localBitmap); + SkBitmap* localBitmap = alloc().create<SkBitmap>(bitmap); mDisplayList->bitmapResources.push_back(localBitmap); return localBitmap; } diff --git a/libs/hwui/DisplayListOp.h b/libs/hwui/DisplayListOp.h index 20501ba3c1d9..98315d0a416a 100644 --- a/libs/hwui/DisplayListOp.h +++ b/libs/hwui/DisplayListOp.h @@ -64,7 +64,9 @@ public: static void operator delete(void* ptr) { LOG_ALWAYS_FATAL("delete not supported"); } static void* operator new(size_t size) = delete; /** PURPOSELY OMITTED **/ static void* operator new(size_t size, LinearAllocator& allocator) { - return allocator.alloc(size); + // FIXME: Quick hack to keep old pipeline working, delete this when + // we no longer need to support HWUI_NEWOPS := false + return allocator.alloc<char>(size); } enum OpLogFlag { diff --git a/libs/hwui/FrameBuilder.cpp b/libs/hwui/FrameBuilder.cpp index 185accec1ef6..4f51036b336e 100644 --- a/libs/hwui/FrameBuilder.cpp +++ b/libs/hwui/FrameBuilder.cpp @@ -209,7 +209,7 @@ void FrameBuilder::deferNodePropsAndOps(RenderNode& node) { // not rejected, so defer render as either Layer, or direct (possibly wrapped in saveLayer) if (node.getLayer()) { // HW layer - LayerOp* drawLayerOp = new (mAllocator) LayerOp(node); + LayerOp* drawLayerOp = mAllocator.create_trivial<LayerOp>(node); BakedOpState* bakedOpState = tryBakeOpState(*drawLayerOp); if (bakedOpState) { // Node's layer already deferred, schedule it to render into parent layer @@ -220,13 +220,13 @@ void FrameBuilder::deferNodePropsAndOps(RenderNode& node) { // (temp layers are clipped to viewport, since they don't persist offscreen content) SkPaint saveLayerPaint; saveLayerPaint.setAlpha(properties.getAlpha()); - deferBeginLayerOp(*new (mAllocator) BeginLayerOp( + deferBeginLayerOp(*mAllocator.create_trivial<BeginLayerOp>( saveLayerBounds, Matrix4::identity(), nullptr, // no record-time clip - need only respect defer-time one &saveLayerPaint)); deferNodeOps(node); - deferEndLayerOp(*new (mAllocator) EndLayerOp()); + deferEndLayerOp(*mAllocator.create_trivial<EndLayerOp>()); } else { deferNodeOps(node); } @@ -549,7 +549,7 @@ void FrameBuilder::deferBitmapRectOp(const BitmapRectOp& op) { void FrameBuilder::deferVectorDrawableOp(const VectorDrawableOp& op) { const SkBitmap& bitmap = op.vectorDrawable->getBitmapUpdateIfDirty(); SkPaint* paint = op.vectorDrawable->getPaint(); - const BitmapRectOp* resolvedOp = new (mAllocator) BitmapRectOp(op.unmappedBounds, + const BitmapRectOp* resolvedOp = mAllocator.create_trivial<BitmapRectOp>(op.unmappedBounds, op.localMatrix, op.localClip, paint, @@ -565,7 +565,7 @@ void FrameBuilder::deferCirclePropsOp(const CirclePropsOp& op) { float y = *(op.y); float radius = *(op.radius); Rect unmappedBounds(x - radius, y - radius, x + radius, y + radius); - const OvalOp* resolvedOp = new (mAllocator) OvalOp( + const OvalOp* resolvedOp = mAllocator.create_trivial<OvalOp>( unmappedBounds, op.localMatrix, op.localClip, @@ -626,7 +626,7 @@ void FrameBuilder::deferRoundRectOp(const RoundRectOp& op) { void FrameBuilder::deferRoundRectPropsOp(const RoundRectPropsOp& op) { // allocate a temporary round rect op (with mAllocator, so it persists until render), so the // renderer doesn't have to handle the RoundRectPropsOp type, and so state baking is simple. - const RoundRectOp* resolvedOp = new (mAllocator) RoundRectOp( + const RoundRectOp* resolvedOp = mAllocator.create_trivial<RoundRectOp>( Rect(*(op.left), *(op.top), *(op.right), *(op.bottom)), op.localMatrix, op.localClip, @@ -754,7 +754,7 @@ void FrameBuilder::deferEndLayerOp(const EndLayerOp& /* ignored */) { // record the draw operation into the previous layer's list of draw commands // uses state from the associated beginLayerOp, since it has all the state needed for drawing - LayerOp* drawLayerOp = new (mAllocator) LayerOp( + LayerOp* drawLayerOp = mAllocator.create_trivial<LayerOp>( beginLayerOp.unmappedBounds, beginLayerOp.localMatrix, beginLayerOp.localClip, @@ -788,7 +788,7 @@ void FrameBuilder::deferBeginUnclippedLayerOp(const BeginUnclippedLayerOp& op) { /** * First, defer an operation to copy out the content from the rendertarget into a layer. */ - auto copyToOp = new (mAllocator) CopyToLayerOp(op, layerHandle); + auto copyToOp = mAllocator.create_trivial<CopyToLayerOp>(op, layerHandle); BakedOpState* bakedState = BakedOpState::directConstruct(mAllocator, &(currentLayer().viewportClip), dstRect, *copyToOp); currentLayer().deferUnmergeableOp(mAllocator, bakedState, OpBatchType::CopyToLayer); @@ -803,7 +803,7 @@ void FrameBuilder::deferBeginUnclippedLayerOp(const BeginUnclippedLayerOp& op) { * And stash an operation to copy that layer back under the rendertarget until * a balanced EndUnclippedLayerOp is seen */ - auto copyFromOp = new (mAllocator) CopyFromLayerOp(op, layerHandle); + auto copyFromOp = mAllocator.create_trivial<CopyFromLayerOp>(op, layerHandle); bakedState = BakedOpState::directConstruct(mAllocator, &(currentLayer().viewportClip), dstRect, *copyFromOp); currentLayer().activeUnclippedSaveLayers.push_back(bakedState); diff --git a/libs/hwui/GradientCache.cpp b/libs/hwui/GradientCache.cpp index 11293d61211b..c8f5e9435594 100644 --- a/libs/hwui/GradientCache.cpp +++ b/libs/hwui/GradientCache.cpp @@ -165,6 +165,10 @@ Texture* GradientCache::addLinearGradient(GradientCacheEntry& gradient, generateTexture(colors, positions, info.width, 2, texture); mSize += size; + LOG_ALWAYS_FATAL_IF((int)size != texture->objectSize(), + "size != texture->objectSize(), size %" PRIu32 ", objectSize %d" + " width = %" PRIu32 " bytesPerPixel() = %zu", + size, texture->objectSize(), info.width, bytesPerPixel()); mCache.put(gradient, texture); return texture; diff --git a/libs/hwui/LayerBuilder.cpp b/libs/hwui/LayerBuilder.cpp index 7170d4fbeea7..1ba3bf26c0d4 100644 --- a/libs/hwui/LayerBuilder.cpp +++ b/libs/hwui/LayerBuilder.cpp @@ -64,10 +64,6 @@ protected: class OpBatch : public BatchBase { public: - static void* operator new(size_t size, LinearAllocator& allocator) { - return allocator.alloc(size); - } - OpBatch(batchid_t batchId, BakedOpState* op) : BatchBase(batchId, op, false) { } @@ -80,10 +76,6 @@ public: class MergingOpBatch : public BatchBase { public: - static void* operator new(size_t size, LinearAllocator& allocator) { - return allocator.alloc(size); - } - MergingOpBatch(batchid_t batchId, BakedOpState* op) : BatchBase(batchId, op, true) , mClipSideFlags(op->computedState.clipSideFlags) { @@ -247,7 +239,7 @@ void LayerBuilder::flushLayerClears(LinearAllocator& allocator) { // put the verts in the frame allocator, since // 1) SimpleRectsOps needs verts, not rects // 2) even if mClearRects stored verts, std::vectors will move their contents - Vertex* const verts = (Vertex*) allocator.alloc(vertCount * sizeof(Vertex)); + Vertex* const verts = (Vertex*) allocator.alloc<Vertex>(vertCount * sizeof(Vertex)); Vertex* currentVert = verts; Rect bounds = mClearRects[0]; @@ -264,7 +256,7 @@ void LayerBuilder::flushLayerClears(LinearAllocator& allocator) { // Flush all of these clears with a single draw SkPaint* paint = allocator.create<SkPaint>(); paint->setXfermodeMode(SkXfermode::kClear_Mode); - SimpleRectsOp* op = new (allocator) SimpleRectsOp(bounds, + SimpleRectsOp* op = allocator.create_trivial<SimpleRectsOp>(bounds, Matrix4::identity(), nullptr, paint, verts, vertCount); BakedOpState* bakedState = BakedOpState::directConstruct(allocator, @@ -292,7 +284,7 @@ void LayerBuilder::deferUnmergeableOp(LinearAllocator& allocator, targetBatch->batchOp(op); } else { // new non-merging batch - targetBatch = new (allocator) OpBatch(batchId, op); + targetBatch = allocator.create<OpBatch>(batchId, op); mBatchLookup[batchId] = targetBatch; mBatches.insert(mBatches.begin() + insertBatchIndex, targetBatch); } @@ -323,7 +315,7 @@ void LayerBuilder::deferMergeableOp(LinearAllocator& allocator, targetBatch->mergeOp(op); } else { // new merging batch - targetBatch = new (allocator) MergingOpBatch(batchId, op); + targetBatch = allocator.create<MergingOpBatch>(batchId, op); mMergingBatchLookup[batchId].insert(std::make_pair(mergeId, targetBatch)); mBatches.insert(mBatches.begin() + insertBatchIndex, targetBatch); diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp index 16929b8ac8ee..269e590892d3 100644 --- a/libs/hwui/RecordingCanvas.cpp +++ b/libs/hwui/RecordingCanvas.cpp @@ -83,9 +83,9 @@ void RecordingCanvas::onViewportInitialized() { void RecordingCanvas::onSnapshotRestored(const Snapshot& removed, const Snapshot& restored) { if (removed.flags & Snapshot::kFlagIsFboLayer) { - addOp(new (alloc()) EndLayerOp()); + addOp(alloc().create_trivial<EndLayerOp>()); } else if (removed.flags & Snapshot::kFlagIsLayer) { - addOp(new (alloc()) EndUnclippedLayerOp()); + addOp(alloc().create_trivial<EndUnclippedLayerOp>()); } } @@ -167,7 +167,7 @@ int RecordingCanvas::saveLayer(float left, float top, float right, float bottom, snapshot.resetClip(clip.left, clip.top, clip.right, clip.bottom); snapshot.roundRectClipState = nullptr; - addOp(new (alloc()) BeginLayerOp( + addOp(alloc().create_trivial<BeginLayerOp>( unmappedBounds, *previous.transform, // transform to *draw* with previousClip, // clip to *draw* with @@ -175,7 +175,7 @@ int RecordingCanvas::saveLayer(float left, float top, float right, float bottom, } else { snapshot.flags |= Snapshot::kFlagIsLayer; - addOp(new (alloc()) BeginUnclippedLayerOp( + addOp(alloc().create_trivial<BeginUnclippedLayerOp>( unmappedBounds, *mState.currentSnapshot()->transform, getRecordedClip(), @@ -241,7 +241,7 @@ void RecordingCanvas::drawColor(int color, SkXfermode::Mode mode) { } void RecordingCanvas::drawPaint(const SkPaint& paint) { - addOp(new (alloc()) RectOp( + addOp(alloc().create_trivial<RectOp>( mState.getRenderTargetClipBounds(), // OK, since we've not passed transform Matrix4::identity(), getRecordedClip(), @@ -261,7 +261,7 @@ void RecordingCanvas::drawPoints(const float* points, int floatCount, const SkPa if (floatCount < 2) return; floatCount &= ~0x1; // round down to nearest two - addOp(new (alloc()) PointsOp( + addOp(alloc().create_trivial<PointsOp>( calcBoundsOfPoints(points, floatCount), *mState.currentSnapshot()->transform, getRecordedClip(), @@ -272,7 +272,7 @@ void RecordingCanvas::drawLines(const float* points, int floatCount, const SkPai if (floatCount < 4) return; floatCount &= ~0x3; // round down to nearest four - addOp(new (alloc()) LinesOp( + addOp(alloc().create_trivial<LinesOp>( calcBoundsOfPoints(points, floatCount), *mState.currentSnapshot()->transform, getRecordedClip(), @@ -280,7 +280,7 @@ void RecordingCanvas::drawLines(const float* points, int floatCount, const SkPai } void RecordingCanvas::drawRect(float left, float top, float right, float bottom, const SkPaint& paint) { - addOp(new (alloc()) RectOp( + addOp(alloc().create_trivial<RectOp>( Rect(left, top, right, bottom), *(mState.currentSnapshot()->transform), getRecordedClip(), @@ -290,7 +290,7 @@ void RecordingCanvas::drawRect(float left, float top, float right, float bottom, void RecordingCanvas::drawSimpleRects(const float* rects, int vertexCount, const SkPaint* paint) { if (rects == nullptr) return; - Vertex* rectData = (Vertex*) mDisplayList->allocator.alloc(vertexCount * sizeof(Vertex)); + Vertex* rectData = (Vertex*) mDisplayList->allocator.alloc<Vertex>(vertexCount * sizeof(Vertex)); Vertex* vertex = rectData; float left = FLT_MAX; @@ -313,7 +313,7 @@ void RecordingCanvas::drawSimpleRects(const float* rects, int vertexCount, const right = std::max(right, r); bottom = std::max(bottom, b); } - addOp(new (alloc()) SimpleRectsOp( + addOp(alloc().create_trivial<SimpleRectsOp>( Rect(left, top, right, bottom), *(mState.currentSnapshot()->transform), getRecordedClip(), @@ -347,7 +347,7 @@ void RecordingCanvas::drawRegion(const SkRegion& region, const SkPaint& paint) { } void RecordingCanvas::drawRoundRect(float left, float top, float right, float bottom, float rx, float ry, const SkPaint& paint) { - addOp(new (alloc()) RoundRectOp( + addOp(alloc().create_trivial<RoundRectOp>( Rect(left, top, right, bottom), *(mState.currentSnapshot()->transform), getRecordedClip(), @@ -367,7 +367,7 @@ void RecordingCanvas::drawRoundRect( mDisplayList->ref(ry); mDisplayList->ref(paint); refBitmapsInShader(paint->value.getShader()); - addOp(new (alloc()) RoundRectPropsOp( + addOp(alloc().create_trivial<RoundRectPropsOp>( *(mState.currentSnapshot()->transform), getRecordedClip(), &paint->value, @@ -389,7 +389,7 @@ void RecordingCanvas::drawCircle( mDisplayList->ref(radius); mDisplayList->ref(paint); refBitmapsInShader(paint->value.getShader()); - addOp(new (alloc()) CirclePropsOp( + addOp(alloc().create_trivial<CirclePropsOp>( *(mState.currentSnapshot()->transform), getRecordedClip(), &paint->value, @@ -397,7 +397,7 @@ void RecordingCanvas::drawCircle( } void RecordingCanvas::drawOval(float left, float top, float right, float bottom, const SkPaint& paint) { - addOp(new (alloc()) OvalOp( + addOp(alloc().create_trivial<OvalOp>( Rect(left, top, right, bottom), *(mState.currentSnapshot()->transform), getRecordedClip(), @@ -406,7 +406,7 @@ void RecordingCanvas::drawOval(float left, float top, float right, float bottom, void RecordingCanvas::drawArc(float left, float top, float right, float bottom, float startAngle, float sweepAngle, bool useCenter, const SkPaint& paint) { - addOp(new (alloc()) ArcOp( + addOp(alloc().create_trivial<ArcOp>( Rect(left, top, right, bottom), *(mState.currentSnapshot()->transform), getRecordedClip(), @@ -415,7 +415,7 @@ void RecordingCanvas::drawArc(float left, float top, float right, float bottom, } void RecordingCanvas::drawPath(const SkPath& path, const SkPaint& paint) { - addOp(new (alloc()) PathOp( + addOp(alloc().create_trivial<PathOp>( Rect(path.getBounds()), *(mState.currentSnapshot()->transform), getRecordedClip(), @@ -424,7 +424,7 @@ void RecordingCanvas::drawPath(const SkPath& path, const SkPaint& paint) { void RecordingCanvas::drawVectorDrawable(VectorDrawableRoot* tree) { mDisplayList->ref(tree); - addOp(new (alloc()) VectorDrawableOp( + addOp(alloc().create_trivial<VectorDrawableOp>( tree, Rect(tree->getBounds()), *(mState.currentSnapshot()->transform), @@ -475,7 +475,7 @@ void RecordingCanvas::drawBitmap(const SkBitmap& bitmap, float srcLeft, float sr drawBitmap(&bitmap, paint); restore(); } else { - addOp(new (alloc()) BitmapRectOp( + addOp(alloc().create_trivial<BitmapRectOp>( Rect(dstLeft, dstTop, dstRight, dstBottom), *(mState.currentSnapshot()->transform), getRecordedClip(), @@ -487,7 +487,7 @@ void RecordingCanvas::drawBitmap(const SkBitmap& bitmap, float srcLeft, float sr void RecordingCanvas::drawBitmapMesh(const SkBitmap& bitmap, int meshWidth, int meshHeight, const float* vertices, const int* colors, const SkPaint* paint) { int vertexCount = (meshWidth + 1) * (meshHeight + 1); - addOp(new (alloc()) BitmapMeshOp( + addOp(alloc().create_trivial<BitmapMeshOp>( calcBoundsOfPoints(vertices, vertexCount * 2), *(mState.currentSnapshot()->transform), getRecordedClip(), @@ -499,7 +499,7 @@ void RecordingCanvas::drawBitmapMesh(const SkBitmap& bitmap, int meshWidth, int void RecordingCanvas::drawNinePatch(const SkBitmap& bitmap, const android::Res_png_9patch& patch, float dstLeft, float dstTop, float dstRight, float dstBottom, const SkPaint* paint) { - addOp(new (alloc()) PatchOp( + addOp(alloc().create_trivial<PatchOp>( Rect(dstLeft, dstTop, dstRight, dstBottom), *(mState.currentSnapshot()->transform), getRecordedClip(), @@ -515,7 +515,7 @@ void RecordingCanvas::drawText(const uint16_t* glyphs, const float* positions, i positions = refBuffer<float>(positions, glyphCount * 2); // TODO: either must account for text shadow in bounds, or record separate ops for text shadows - addOp(new (alloc()) TextOp( + addOp(alloc().create_trivial<TextOp>( Rect(boundsLeft, boundsTop, boundsRight, boundsBottom), *(mState.currentSnapshot()->transform), getRecordedClip(), @@ -527,7 +527,7 @@ void RecordingCanvas::drawTextOnPath(const uint16_t* glyphs, int glyphCount, con float hOffset, float vOffset, const SkPaint& paint) { if (!glyphs || glyphCount <= 0 || PaintUtils::paintWillNotDrawText(paint)) return; glyphs = refBuffer<glyph_t>(glyphs, glyphCount); - addOp(new (alloc()) TextOnPathOp( + addOp(alloc().create_trivial<TextOnPathOp>( mState.getLocalClipBounds(), // TODO: explicitly define bounds *(mState.currentSnapshot()->transform), getRecordedClip(), @@ -535,7 +535,7 @@ void RecordingCanvas::drawTextOnPath(const uint16_t* glyphs, int glyphCount, con } void RecordingCanvas::drawBitmap(const SkBitmap* bitmap, const SkPaint* paint) { - addOp(new (alloc()) BitmapOp( + addOp(alloc().create_trivial<BitmapOp>( Rect(bitmap->width(), bitmap->height()), *(mState.currentSnapshot()->transform), getRecordedClip(), @@ -544,7 +544,7 @@ void RecordingCanvas::drawBitmap(const SkBitmap* bitmap, const SkPaint* paint) { void RecordingCanvas::drawRenderNode(RenderNode* renderNode) { auto&& stagingProps = renderNode->stagingProperties(); - RenderNodeOp* op = new (alloc()) RenderNodeOp( + RenderNodeOp* op = alloc().create_trivial<RenderNodeOp>( Rect(stagingProps.getWidth(), stagingProps.getHeight()), *(mState.currentSnapshot()->transform), getRecordedClip(), @@ -570,7 +570,7 @@ void RecordingCanvas::drawLayer(DeferredLayerUpdater* layerHandle) { Matrix4 totalTransform(*(mState.currentSnapshot()->transform)); totalTransform.multiply(layer->getTransform()); - addOp(new (alloc()) TextureLayerOp( + addOp(alloc().create_trivial<TextureLayerOp>( Rect(layer->getWidth(), layer->getHeight()), totalTransform, getRecordedClip(), @@ -579,7 +579,7 @@ void RecordingCanvas::drawLayer(DeferredLayerUpdater* layerHandle) { void RecordingCanvas::callDrawGLFunction(Functor* functor) { mDisplayList->functors.push_back(functor); - addOp(new (alloc()) FunctorOp( + addOp(alloc().create_trivial<FunctorOp>( mState.getLocalClipBounds(), // TODO: explicitly define bounds *(mState.currentSnapshot()->transform), getRecordedClip(), diff --git a/libs/hwui/RecordingCanvas.h b/libs/hwui/RecordingCanvas.h index cc14e6111cec..719872d35169 100644 --- a/libs/hwui/RecordingCanvas.h +++ b/libs/hwui/RecordingCanvas.h @@ -219,7 +219,7 @@ private: inline const T* refBuffer(const T* srcBuffer, int32_t count) { if (!srcBuffer) return nullptr; - T* dstBuffer = (T*) mDisplayList->allocator.alloc(count * sizeof(T)); + T* dstBuffer = (T*) mDisplayList->allocator.alloc<T>(count * sizeof(T)); memcpy(dstBuffer, srcBuffer, count * sizeof(T)); return dstBuffer; } @@ -290,8 +290,7 @@ private: // correctly, such as creating the bitmap from scratch, drawing with it, changing its // contents, and drawing again. The only fix would be to always copy it the first time, // which doesn't seem worth the extra cycles for this unlikely case. - SkBitmap* localBitmap = new (alloc()) SkBitmap(bitmap); - alloc().autoDestroy(localBitmap); + SkBitmap* localBitmap = alloc().create<SkBitmap>(bitmap); mDisplayList->bitmapResources.push_back(localBitmap); return localBitmap; } diff --git a/libs/hwui/Snapshot.h b/libs/hwui/Snapshot.h index dbaa905b0728..0ac2f1411140 100644 --- a/libs/hwui/Snapshot.h +++ b/libs/hwui/Snapshot.h @@ -46,7 +46,7 @@ class RoundRectClipState { public: /** static void* operator new(size_t size); PURPOSELY OMITTED, allocator only **/ static void* operator new(size_t size, LinearAllocator& allocator) { - return allocator.alloc(size); + return allocator.alloc<RoundRectClipState>(size); } bool areaRequiresRoundRectClip(const Rect& rect) const { @@ -67,7 +67,7 @@ class ProjectionPathMask { public: /** static void* operator new(size_t size); PURPOSELY OMITTED, allocator only **/ static void* operator new(size_t size, LinearAllocator& allocator) { - return allocator.alloc(size); + return allocator.alloc<ProjectionPathMask>(size); } const SkPath* projectionMask; diff --git a/libs/hwui/Texture.cpp b/libs/hwui/Texture.cpp index c09b6dd89e4e..4f49a3518be0 100644 --- a/libs/hwui/Texture.cpp +++ b/libs/hwui/Texture.cpp @@ -28,13 +28,19 @@ namespace uirenderer { static int bytesPerPixel(GLint glFormat) { switch (glFormat) { + // The wrapped-texture case, usually means a SurfaceTexture + case 0: + return 0; case GL_ALPHA: return 1; case GL_RGB: return 3; case GL_RGBA: - default: return 4; + case GL_RGBA16F: + return 16; + default: + LOG_ALWAYS_FATAL("UNKNOWN FORMAT %d", glFormat); } } diff --git a/libs/hwui/hwui_static_deps.mk b/libs/hwui/hwui_static_deps.mk new file mode 100644 index 000000000000..7d4ef0f1f31e --- /dev/null +++ b/libs/hwui/hwui_static_deps.mk @@ -0,0 +1,28 @@ +############################################################################### +# +# +# This file contains the shared and static dependencies needed by any target +# that attempts to statically link HWUI (i.e. libhwui_static build target). This +# file should be included by any target that lists libhwui_static as a +# dependency. +# +# This is a workaround for the fact that the build system does not add these +# transitive dependencies when it attempts to link libhwui_static into another +# library. +# +############################################################################### + +LOCAL_SHARED_LIBRARIES += \ + liblog \ + libcutils \ + libutils \ + libEGL \ + libGLESv2 \ + libskia \ + libui \ + libgui \ + libprotobuf-cpp-lite + +ifneq (false,$(ANDROID_ENABLE_RENDERSCRIPT)) + LOCAL_SHARED_LIBRARIES += libRS libRScpp +endif
\ No newline at end of file diff --git a/libs/hwui/tests/unit/LinearAllocatorTests.cpp b/libs/hwui/tests/unit/LinearAllocatorTests.cpp index 5c442901045e..402a09c55e8f 100644 --- a/libs/hwui/tests/unit/LinearAllocatorTests.cpp +++ b/libs/hwui/tests/unit/LinearAllocatorTests.cpp @@ -30,7 +30,7 @@ struct SimplePair { TEST(LinearAllocator, create) { LinearAllocator la; EXPECT_EQ(0u, la.usedSize()); - la.alloc(64); + la.alloc<char>(64); // There's some internal tracking as well as padding // so the usedSize isn't strictly defined EXPECT_LE(64u, la.usedSize()); @@ -50,13 +50,12 @@ TEST(LinearAllocator, dtor) { la.create<TestUtils::SignalingDtor>()->setSignal(destroyed + i); la.create<SimplePair>(); } - la.alloc(100); + la.alloc<char>(100); for (int i = 0; i < 5; i++) { - auto sd = new (la) TestUtils::SignalingDtor(destroyed + 5 + i); - la.autoDestroy(sd); - new (la) SimplePair(); + la.create<TestUtils::SignalingDtor>(destroyed + 5 + i); + la.create_trivial<SimplePair>(); } - la.alloc(100); + la.alloc<char>(100); for (int i = 0; i < 10; i++) { EXPECT_EQ(0, destroyed[i]); } @@ -70,7 +69,7 @@ TEST(LinearAllocator, rewind) { int destroyed = 0; { LinearAllocator la; - auto addr = la.alloc(100); + auto addr = la.alloc<char>(100); EXPECT_LE(100u, la.usedSize()); la.rewindIfLastAlloc(addr, 100); EXPECT_GT(16u, la.usedSize()); diff --git a/libs/hwui/utils/LinearAllocator.cpp b/libs/hwui/utils/LinearAllocator.cpp index e6a4c03156b4..5bba420a258f 100644 --- a/libs/hwui/utils/LinearAllocator.cpp +++ b/libs/hwui/utils/LinearAllocator.cpp @@ -81,10 +81,6 @@ static void _addAllocation(int count) { #define min(x,y) (((x) < (y)) ? (x) : (y)) -void* operator new(std::size_t size, android::uirenderer::LinearAllocator& la) { - return la.alloc(size); -} - namespace android { namespace uirenderer { @@ -171,7 +167,7 @@ void LinearAllocator::ensureNext(size_t size) { mNext = start(mCurrentPage); } -void* LinearAllocator::alloc(size_t size) { +void* LinearAllocator::allocImpl(size_t size) { size = ALIGN(size); if (size > mMaxAllocSize && !fitsInCurrentPage(size)) { ALOGV("Exceeded max size %zu > %zu", size, mMaxAllocSize); @@ -196,7 +192,7 @@ void LinearAllocator::addToDestructionList(Destructor dtor, void* addr) { "DestructorNode must have standard layout"); static_assert(std::is_trivially_destructible<DestructorNode>::value, "DestructorNode must be trivially destructable"); - auto node = new (*this) DestructorNode(); + auto node = new (allocImpl(sizeof(DestructorNode))) DestructorNode(); node->dtor = dtor; node->addr = addr; node->next = mDtorList; diff --git a/libs/hwui/utils/LinearAllocator.h b/libs/hwui/utils/LinearAllocator.h index dcbc0dda951a..0a0e1858cd91 100644 --- a/libs/hwui/utils/LinearAllocator.h +++ b/libs/hwui/utils/LinearAllocator.h @@ -52,30 +52,36 @@ public: * The lifetime of the returned buffers is tied to that of the LinearAllocator. If calling * delete() on an object stored in a buffer is needed, it should be overridden to use * rewindIfLastAlloc() + * + * Note that unlike create, for alloc the type is purely for compile-time error + * checking and does not affect size. */ - void* alloc(size_t size); + template<class T> + void* alloc(size_t size) { + static_assert(std::is_trivially_destructible<T>::value, + "Error, type is non-trivial! did you mean to use create()?"); + return allocImpl(size); + } /** * Allocates an instance of the template type with the given construction parameters * and adds it to the automatic destruction list. */ template<class T, typename... Params> - T* create(Params... params) { - T* ret = new (*this) T(params...); - autoDestroy(ret); + T* create(Params&&... params) { + T* ret = new (allocImpl(sizeof(T))) T(std::forward<Params>(params)...); + if (!std::is_trivially_destructible<T>::value) { + auto dtor = [](void* ret) { ((T*)ret)->~T(); }; + addToDestructionList(dtor, ret); + } return ret; } - /** - * Adds the pointer to the tracking list to have its destructor called - * when the LinearAllocator is destroyed. - */ - template<class T> - void autoDestroy(T* addr) { - if (!std::is_trivially_destructible<T>::value) { - auto dtor = [](void* addr) { ((T*)addr)->~T(); }; - addToDestructionList(dtor, addr); - } + template<class T, typename... Params> + T* create_trivial(Params&&... params) { + static_assert(std::is_trivially_destructible<T>::value, + "Error, called create_trivial on a non-trivial type"); + return new (allocImpl(sizeof(T))) T(std::forward<Params>(params)...); } /** @@ -114,6 +120,8 @@ private: DestructorNode* next = nullptr; }; + void* allocImpl(size_t size); + void addToDestructionList(Destructor, void* addr); void runDestructorFor(void* addr); Page* newPage(size_t pageSize); @@ -159,7 +167,7 @@ public: : linearAllocator(other.linearAllocator) {} T* allocate(size_t num, const void* = 0) { - return (T*)(linearAllocator.alloc(num * sizeof(T))); + return (T*)(linearAllocator.alloc<void*>(num * sizeof(T))); } void deallocate(pointer p, size_t num) { @@ -187,6 +195,4 @@ public: }; // namespace uirenderer }; // namespace android -void* operator new(std::size_t size, android::uirenderer::LinearAllocator& la); - #endif // ANDROID_LINEARALLOCATOR_H diff --git a/location/java/android/location/GnssMeasurement.java b/location/java/android/location/GnssMeasurement.java index d8f507c9b70e..a490685a3a8b 100644 --- a/location/java/android/location/GnssMeasurement.java +++ b/location/java/android/location/GnssMeasurement.java @@ -28,7 +28,7 @@ import java.lang.annotation.RetentionPolicy; */ public final class GnssMeasurement implements Parcelable { private int mFlags; - private byte mPrn; + private short mSvid; private double mTimeOffsetInNs; private short mState; private long mReceivedGpsTowInNs; @@ -198,7 +198,7 @@ public final class GnssMeasurement implements Parcelable { */ public void set(GnssMeasurement measurement) { mFlags = measurement.mFlags; - mPrn = measurement.mPrn; + mSvid = measurement.mSvid; mTimeOffsetInNs = measurement.mTimeOffsetInNs; mState = measurement.mState; mReceivedGpsTowInNs = measurement.mReceivedGpsTowInNs; @@ -248,15 +248,15 @@ public final class GnssMeasurement implements Parcelable { * Gets the Pseudo-random number (PRN). * Range: [1, 32] */ - public byte getPrn() { - return mPrn; + public short getSvid() { + return mSvid; } /** * Sets the Pseud-random number (PRN). */ - public void setPrn(byte value) { - mPrn = value; + public void setSvid(short value) { + mSvid = value; } /** @@ -1210,7 +1210,7 @@ public final class GnssMeasurement implements Parcelable { GnssMeasurement gnssMeasurement = new GnssMeasurement(); gnssMeasurement.mFlags = parcel.readInt(); - gnssMeasurement.mPrn = parcel.readByte(); + gnssMeasurement.mSvid = (short) parcel.readInt(); gnssMeasurement.mTimeOffsetInNs = parcel.readDouble(); gnssMeasurement.mState = (short) parcel.readInt(); gnssMeasurement.mReceivedGpsTowInNs = parcel.readLong(); @@ -1253,9 +1253,10 @@ public final class GnssMeasurement implements Parcelable { } }; + @Override public void writeToParcel(Parcel parcel, int flags) { parcel.writeInt(mFlags); - parcel.writeByte(mPrn); + parcel.writeInt(mSvid); parcel.writeDouble(mTimeOffsetInNs); parcel.writeInt(mState); parcel.writeLong(mReceivedGpsTowInNs); @@ -1301,7 +1302,7 @@ public final class GnssMeasurement implements Parcelable { final String formatWithUncertainty = " %-29s = %-25s %-40s = %s\n"; StringBuilder builder = new StringBuilder("GnssMeasurement:\n"); - builder.append(String.format(format, "Prn", mPrn)); + builder.append(String.format(format, "Svid", mSvid)); builder.append(String.format(format, "TimeOffsetInNs", mTimeOffsetInNs)); @@ -1422,7 +1423,7 @@ public final class GnssMeasurement implements Parcelable { private void initialize() { mFlags = HAS_NO_FLAGS; - setPrn(Byte.MIN_VALUE); + setSvid((short) 0); setTimeOffsetInNs(Long.MIN_VALUE); setState(STATE_UNKNOWN); setReceivedGpsTowInNs(Long.MIN_VALUE); diff --git a/location/java/android/location/GnssNavigationMessage.java b/location/java/android/location/GnssNavigationMessage.java index 0e011d5c98a8..86328eb5dcd7 100644 --- a/location/java/android/location/GnssNavigationMessage.java +++ b/location/java/android/location/GnssNavigationMessage.java @@ -26,7 +26,7 @@ import java.lang.annotation.RetentionPolicy; import java.security.InvalidParameterException; /** - * A class containing a GPS satellite Navigation Message. + * A class containing a GNSS satellite Navigation Message. */ public final class GnssNavigationMessage implements Parcelable { @@ -84,7 +84,7 @@ public final class GnssNavigationMessage implements Parcelable { // End enumerations in sync with gps.h private byte mType; - private byte mPrn; + private short mSvid; private short mMessageId; private short mSubmessageId; private byte[] mData; @@ -99,7 +99,7 @@ public final class GnssNavigationMessage implements Parcelable { */ public void set(GnssNavigationMessage navigationMessage) { mType = navigationMessage.mType; - mPrn = navigationMessage.mPrn; + mSvid = navigationMessage.mSvid; mMessageId = navigationMessage.mMessageId; mSubmessageId = navigationMessage.mSubmessageId; mData = navigationMessage.mData; @@ -153,15 +153,15 @@ public final class GnssNavigationMessage implements Parcelable { * Gets the Pseudo-random number. * Range: [1, 32]. */ - public byte getPrn() { - return mPrn; + public short getSvid() { + return mSvid; } /** * Sets the Pseud-random number. */ - public void setPrn(byte value) { - mPrn = value; + public void setSvid(short value) { + mSvid = value; } /** @@ -256,7 +256,7 @@ public final class GnssNavigationMessage implements Parcelable { GnssNavigationMessage navigationMessage = new GnssNavigationMessage(); navigationMessage.setType(parcel.readByte()); - navigationMessage.setPrn(parcel.readByte()); + navigationMessage.setSvid((short) parcel.readInt()); navigationMessage.setMessageId((short) parcel.readInt()); navigationMessage.setSubmessageId((short) parcel.readInt()); @@ -281,9 +281,10 @@ public final class GnssNavigationMessage implements Parcelable { } }; + @Override public void writeToParcel(Parcel parcel, int flags) { parcel.writeByte(mType); - parcel.writeByte(mPrn); + parcel.writeInt(mSvid); parcel.writeInt(mMessageId); parcel.writeInt(mSubmessageId); parcel.writeInt(mData.length); @@ -302,7 +303,7 @@ public final class GnssNavigationMessage implements Parcelable { StringBuilder builder = new StringBuilder("GnssNavigationMessage:\n"); builder.append(String.format(format, "Type", getTypeString())); - builder.append(String.format(format, "Prn", mPrn)); + builder.append(String.format(format, "Svid", mSvid)); builder.append(String.format(format, "Status", getStatusString())); builder.append(String.format(format, "MessageId", mMessageId)); builder.append(String.format(format, "SubmessageId", mSubmessageId)); @@ -321,7 +322,7 @@ public final class GnssNavigationMessage implements Parcelable { private void initialize() { mType = MESSAGE_TYPE_UNKNOWN; - mPrn = 0; + mSvid = 0; mMessageId = -1; mSubmessageId = -1; mData = EMPTY_ARRAY; diff --git a/location/java/android/location/GnssStatus.java b/location/java/android/location/GnssStatus.java index 77e8a5bbaca0..906e944f06ab 100644 --- a/location/java/android/location/GnssStatus.java +++ b/location/java/android/location/GnssStatus.java @@ -47,24 +47,26 @@ public final class GnssStatus { public static final int GNSS_SV_FLAGS_USED_IN_FIX = (1 << 2); /** @hide */ - public static final int PRN_SHIFT_WIDTH = 3; + public static final int SVID_SHIFT_WIDTH = 7; + /** @hide */ + public static final int CONSTELLATION_TYPE_SHIFT_WIDTH = 3; + /** @hide */ + public static final int CONSTELLATION_TYPE_MASK = 0xf; /* These package private values are modified by the LocationManager class */ - /* package */ int[] mPrnWithFlags; + /* package */ int[] mSvidWithFlags; /* package */ float[] mSnrs; /* package */ float[] mElevations; /* package */ float[] mAzimuths; - /* package */ int[] mConstellationTypes; /* package */ int mSvCount; - GnssStatus(int svCount, int[] prnWithFlags, float[] snrs, float[] elevations, float[] azimuths, - int[] constellationTypes) { + GnssStatus(int svCount, int[] svidWithFlags, float[] snrs, float[] elevations, + float[] azimuths) { mSvCount = svCount; - mPrnWithFlags = prnWithFlags; + mSvidWithFlags = svidWithFlags; mSnrs = snrs; mElevations = elevations; mAzimuths = azimuths; - mConstellationTypes = constellationTypes; } /** @@ -79,15 +81,16 @@ public final class GnssStatus { * @param satIndex the index of the satellite in the list. */ public int getConstellationType(int satIndex) { - return mConstellationTypes[satIndex]; + return (mSvidWithFlags[satIndex] >> CONSTELLATION_TYPE_SHIFT_WIDTH) + & CONSTELLATION_TYPE_MASK; } /** * Retrieves the pseudo-random number of the satellite at the specified position. * @param satIndex the index of the satellite in the list. */ - public int getPrn(int satIndex) { - return mPrnWithFlags[satIndex] >> PRN_SHIFT_WIDTH; + public int getSvid(int satIndex) { + return mSvidWithFlags[satIndex] >> SVID_SHIFT_WIDTH; } /** @@ -119,7 +122,7 @@ public final class GnssStatus { * @param satIndex the index of the satellite in the list. */ public boolean hasEphemeris(int satIndex) { - return (mPrnWithFlags[satIndex] & GNSS_SV_FLAGS_HAS_EPHEMERIS_DATA) != 0; + return (mSvidWithFlags[satIndex] & GNSS_SV_FLAGS_HAS_EPHEMERIS_DATA) != 0; } /** @@ -127,7 +130,7 @@ public final class GnssStatus { * @param satIndex the index of the satellite in the list. */ public boolean hasAlmanac(int satIndex) { - return (mPrnWithFlags[satIndex] & GNSS_SV_FLAGS_HAS_ALMANAC_DATA) != 0; + return (mSvidWithFlags[satIndex] & GNSS_SV_FLAGS_HAS_ALMANAC_DATA) != 0; } /** @@ -135,6 +138,6 @@ public final class GnssStatus { * @param satIndex the index of the satellite in the list. */ public boolean usedInFix(int satIndex) { - return (mPrnWithFlags[satIndex] & GNSS_SV_FLAGS_USED_IN_FIX) != 0; + return (mSvidWithFlags[satIndex] & GNSS_SV_FLAGS_USED_IN_FIX) != 0; } } diff --git a/location/java/android/location/GpsStatus.java b/location/java/android/location/GpsStatus.java index 8d2f781e497f..e41e20c172f7 100644 --- a/location/java/android/location/GpsStatus.java +++ b/location/java/android/location/GpsStatus.java @@ -138,15 +138,19 @@ public final class GpsStatus { // For API-compat a public ctor() is not available GpsStatus() {} - private void setStatus(int svCount, int[] prnWithFlags, float[] snrs, float[] elevations, - float[] azimuths, int[] constellationTypes) { + private void setStatus(int svCount, int[] svidWithFlags, float[] snrs, float[] elevations, + float[] azimuths) { clearSatellites(); for (int i = 0; i < svCount; i++) { + final int constellationType = + (svidWithFlags[i] >> GnssStatus.CONSTELLATION_TYPE_SHIFT_WIDTH) + & GnssStatus.CONSTELLATION_TYPE_MASK; // Skip all non-GPS satellites. - if (constellationTypes[i] != GnssStatus.CONSTELLATION_GPS) { + if (constellationType != GnssStatus.CONSTELLATION_GPS) { + // TODO: translate the defacto pre-N use of prn's >32 to new struct continue; } - int prn = prnWithFlags[i] >> GnssStatus.PRN_SHIFT_WIDTH; + int prn = svidWithFlags[i] >> GnssStatus.SVID_SHIFT_WIDTH; if (prn > 0 && prn <= NUM_SATELLITES) { GpsSatellite satellite = mSatellites.get(prn); if (satellite == null) { @@ -159,11 +163,11 @@ public final class GpsStatus { satellite.mElevation = elevations[i]; satellite.mAzimuth = azimuths[i]; satellite.mHasEphemeris = - (prnWithFlags[i] & GnssStatus.GNSS_SV_FLAGS_HAS_EPHEMERIS_DATA) != 0; + (svidWithFlags[i] & GnssStatus.GNSS_SV_FLAGS_HAS_EPHEMERIS_DATA) != 0; satellite.mHasAlmanac = - (prnWithFlags[i] & GnssStatus.GNSS_SV_FLAGS_HAS_ALMANAC_DATA) != 0; + (svidWithFlags[i] & GnssStatus.GNSS_SV_FLAGS_HAS_ALMANAC_DATA) != 0; satellite.mUsedInFix = - (prnWithFlags[i] & GnssStatus.GNSS_SV_FLAGS_USED_IN_FIX) != 0; + (svidWithFlags[i] & GnssStatus.GNSS_SV_FLAGS_USED_IN_FIX) != 0; } } } @@ -176,8 +180,8 @@ public final class GpsStatus { */ void setStatus(GnssStatus status, int timeToFirstFix) { mTimeToFirstFix = timeToFirstFix; - setStatus(status.mSvCount, status.mPrnWithFlags, status.mSnrs, status.mElevations, - status.mAzimuths, status.mConstellationTypes); + setStatus(status.mSvCount, status.mSvidWithFlags, status.mSnrs, status.mElevations, + status.mAzimuths); } void setTimeToFirstFix(int ttff) { diff --git a/location/java/android/location/IGnssStatusListener.aidl b/location/java/android/location/IGnssStatusListener.aidl index d1c6a85a9fdd..8c7d06eee5e6 100644 --- a/location/java/android/location/IGnssStatusListener.aidl +++ b/location/java/android/location/IGnssStatusListener.aidl @@ -26,7 +26,7 @@ oneway interface IGnssStatusListener void onGnssStarted(); void onGnssStopped(); void onFirstFix(int ttff); - void onSvStatusChanged(int svCount, in int[] prnWithFlags, in float[] snrs, - in float[] elevations, in float[] azimuths, in int[] constellationTypes); + void onSvStatusChanged(int svCount, in int[] svidWithFlags, in float[] snrs, + in float[] elevations, in float[] azimuths); void onNmeaReceived(long timestamp, String nmea); } diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java index 30cf101397db..23f0710a0a9d 100644 --- a/location/java/android/location/LocationManager.java +++ b/location/java/android/location/LocationManager.java @@ -1562,10 +1562,9 @@ public class LocationManager { @Override public void onSvStatusChanged(int svCount, int[] prnWithFlags, - float[] snrs, float[] elevations, float[] azimuths, int[] constellationTypes) { + float[] snrs, float[] elevations, float[] azimuths) { if (mGnssCallback != null) { - mGnssStatus = new GnssStatus(svCount, prnWithFlags, snrs, elevations, azimuths, - constellationTypes); + mGnssStatus = new GnssStatus(svCount, prnWithFlags, snrs, elevations, azimuths); Message msg = Message.obtain(); msg.what = GpsStatus.GPS_EVENT_SATELLITE_STATUS; diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java index bdf6d9f19558..6fc2f87ee640 100644 --- a/media/java/android/media/AudioTrack.java +++ b/media/java/android/media/AudioTrack.java @@ -885,10 +885,10 @@ public class AudioTrack implements AudioRouting // postcondition: // mNativeBufferSizeInBytes is valid (multiple of frame size, positive) private void audioBuffSizeCheck(int audioBufferSize) { - // NB: this section is only valid with PCM data. + // NB: this section is only valid with PCM or IEC61937 data. // To update when supporting compressed formats int frameSizeInBytes; - if (AudioFormat.isEncodingLinearPcm(mAudioFormat)) { + if (AudioFormat.isEncodingLinearFrames(mAudioFormat)) { frameSizeInBytes = mChannelCount * AudioFormat.getBytesPerSample(mAudioFormat); } else { frameSizeInBytes = 1; @@ -1052,32 +1052,27 @@ public class AudioTrack implements AudioRouting } } -// TODO Change getBufferCapacityInFrames() reference below to -// {@link #getBufferCapacityInFrames()} after @hide is removed. -// TODO Change setBufferSizeInFrames(int) reference below to -// {@link #setBufferSizeInFrames(int)} after @hide is removed. + /** - * Returns the effective size of the <code>AudioTrack</code> buffer + * Returns the effective size of the <code>AudioTrack</code> buffer * that the application writes to. - * <p> This will be less than or equal to the result of - * getBufferCapacityInFrames(). - * It will be equal if setBufferSizeInFrames(int) has never been called. - * <p> If the track is subsequently routed to a different output sink, the buffer - * size and capacity may enlarge to accommodate. - * <p> If the <code>AudioTrack</code> encoding indicates compressed data, - * e.g. {@link AudioFormat#ENCODING_AC3}, then the frame count returned is - * the size of the native <code>AudioTrack</code> buffer in bytes. - * <p> See also {@link AudioManager#getProperty(String)} for key - * {@link AudioManager#PROPERTY_OUTPUT_FRAMES_PER_BUFFER}. - * @return current size in frames of the <code>AudioTrack</code> buffer. - * @throws IllegalStateException + * <p> This will be less than or equal to the result of + * {@link #getBufferCapacityInFrames()}. + * It will be equal if {@link #setBufferSizeInFrames(int)} has never been called. + * <p> If the track is subsequently routed to a different output sink, the buffer + * size and capacity may enlarge to accommodate. + * <p> If the <code>AudioTrack</code> encoding indicates compressed data, + * e.g. {@link AudioFormat#ENCODING_AC3}, then the frame count returned is + * the size of the native <code>AudioTrack</code> buffer in bytes. + * <p> See also {@link AudioManager#getProperty(String)} for key + * {@link AudioManager#PROPERTY_OUTPUT_FRAMES_PER_BUFFER}. + * @return current size in frames of the <code>AudioTrack</code> buffer. + * @throws IllegalStateException */ public int getBufferSizeInFrames() { return native_get_buffer_size_frames(); } -// TODO Change getBufferCapacityInFrames() reference below to -// {@link #getBufferCapacityInFrames()} after @hide is removed. /** * Limits the effective size of the <code>AudioTrack</code> buffer * that the application writes to. @@ -1087,9 +1082,9 @@ public class AudioTrack implements AudioRouting * <p>Changing this limit modifies the latency associated with * the buffer for this track. A smaller size will give lower latency * but there may be more glitches due to buffer underruns. - * <p>The actual size used may not be equal to this requested size. + * <p>The actual size used may not be equal to this requested size. * It will be limited to a valid range with a maximum of - * getBufferCapacityInFrames(). + * {@link #getBufferCapacityInFrames()}. * It may also be adjusted slightly for internal reasons. * If bufferSizeInFrames is less than zero then {@link #ERROR_BAD_VALUE} * will be returned. @@ -1097,9 +1092,9 @@ public class AudioTrack implements AudioRouting * It is not supported for compressed audio tracks. * * @param bufferSizeInFrames requested buffer size - * @return error code or success, see {@link #SUCCESS}, {@link #ERROR_BAD_VALUE}, - * {@link #ERROR_INVALID_OPERATION} - * @throws IllegalStateException + * @return the actual buffer size in frames or an error code, + * {@link #ERROR_BAD_VALUE}, {@link #ERROR_INVALID_OPERATION} + * @throws IllegalStateException */ public int setBufferSizeInFrames(int bufferSizeInFrames) { if (mDataLoadMode == MODE_STATIC || mState == STATE_UNINITIALIZED) { diff --git a/media/java/android/media/MediaMuxer.java b/media/java/android/media/MediaMuxer.java index 4b6b4fad4de7..8618ec98c6c3 100644 --- a/media/java/android/media/MediaMuxer.java +++ b/media/java/android/media/MediaMuxer.java @@ -32,8 +32,8 @@ import java.nio.ByteBuffer; import java.util.Map; /** - * MediaMuxer facilitates muxing elementary streams. Currently only supports an - * mp4 file as the output and at most one audio and/or one video elementary + * MediaMuxer facilitates muxing elementary streams. Currently supports mp4 or + * webm file as the output and at most one audio and/or one video elementary * stream. * <p> * It is generally used like this: diff --git a/media/java/android/media/audiopolicy/AudioMixingRule.java b/media/java/android/media/audiopolicy/AudioMixingRule.java index 54543ec43f89..e197141e0214 100644 --- a/media/java/android/media/audiopolicy/AudioMixingRule.java +++ b/media/java/android/media/audiopolicy/AudioMixingRule.java @@ -42,7 +42,7 @@ import java.util.Objects; @SystemApi public class AudioMixingRule { - private AudioMixingRule(int mixType, ArrayList<AttributeMatchCriterion> criteria) { + private AudioMixingRule(int mixType, ArrayList<AudioMixMatchCriterion> criteria) { mCriteria = criteria; mTargetMixType = mixType; } @@ -91,21 +91,21 @@ public class AudioMixingRule { public static final int RULE_EXCLUDE_UID = RULE_EXCLUSION_MASK | RULE_MATCH_UID; - static final class AttributeMatchCriterion { + static final class AudioMixMatchCriterion { final AudioAttributes mAttr; - final Integer mIntProp; + final int mIntProp; final int mRule; /** input parameters must be valid */ - AttributeMatchCriterion(AudioAttributes attributes, int rule) { + AudioMixMatchCriterion(AudioAttributes attributes, int rule) { mAttr = attributes; - mIntProp = null; + mIntProp = Integer.MIN_VALUE; mRule = rule; } /** input parameters must be valid */ - AttributeMatchCriterion(Integer intProp, int rule) { + AudioMixMatchCriterion(Integer intProp, int rule) { mAttr = null; - mIntProp = intProp; + mIntProp = intProp.intValue(); mRule = rule; } @@ -125,10 +125,10 @@ public class AudioMixingRule { dest.writeInt(mAttr.getCapturePreset()); break; case RULE_MATCH_UID: - dest.writeInt(mIntProp.intValue()); + dest.writeInt(mIntProp); break; default: - Log.e("AttributeMatchCriterion", "Unknown match rule" + match_rule + Log.e("AudioMixMatchCriterion", "Unknown match rule" + match_rule + " when writing to Parcel"); dest.writeInt(-1); } @@ -137,8 +137,8 @@ public class AudioMixingRule { private final int mTargetMixType; int getTargetMixType() { return mTargetMixType; } - private final ArrayList<AttributeMatchCriterion> mCriteria; - ArrayList<AttributeMatchCriterion> getCriteria() { return mCriteria; } + private final ArrayList<AudioMixMatchCriterion> mCriteria; + ArrayList<AudioMixMatchCriterion> getCriteria() { return mCriteria; } @Override public int hashCode() { @@ -205,7 +205,7 @@ public class AudioMixingRule { */ @SystemApi public static class Builder { - private ArrayList<AttributeMatchCriterion> mCriteria; + private ArrayList<AudioMixMatchCriterion> mCriteria; private int mTargetMixType = AudioMix.MIX_TYPE_INVALID; /** @@ -213,7 +213,7 @@ public class AudioMixingRule { */ @SystemApi public Builder() { - mCriteria = new ArrayList<AttributeMatchCriterion>(); + mCriteria = new ArrayList<AudioMixMatchCriterion>(); } /** @@ -378,10 +378,10 @@ public class AudioMixingRule { throw new IllegalArgumentException("Incompatible rule for mix"); } synchronized (mCriteria) { - Iterator<AttributeMatchCriterion> crIterator = mCriteria.iterator(); + Iterator<AudioMixMatchCriterion> crIterator = mCriteria.iterator(); final int match_rule = rule & ~RULE_EXCLUSION_MASK; while (crIterator.hasNext()) { - final AttributeMatchCriterion criterion = crIterator.next(); + final AudioMixMatchCriterion criterion = crIterator.next(); switch (match_rule) { case RULE_MATCH_ATTRIBUTE_USAGE: // "usage"-based rule @@ -413,7 +413,7 @@ public class AudioMixingRule { break; case RULE_MATCH_UID: // "usage"-based rule - if (criterion.mIntProp.intValue() == intProp.intValue()) { + if (criterion.mIntProp == intProp.intValue()) { if (criterion.mRule == rule) { // rule already exists, we're done return this; @@ -431,10 +431,10 @@ public class AudioMixingRule { switch (match_rule) { case RULE_MATCH_ATTRIBUTE_USAGE: case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET: - mCriteria.add(new AttributeMatchCriterion(attrToMatch, rule)); + mCriteria.add(new AudioMixMatchCriterion(attrToMatch, rule)); break; case RULE_MATCH_UID: - mCriteria.add(new AttributeMatchCriterion(intProp, rule)); + mCriteria.add(new AudioMixMatchCriterion(intProp, rule)); break; default: throw new IllegalStateException("Unreachable code in addRuleInternal()"); diff --git a/media/java/android/media/audiopolicy/AudioPolicyConfig.java b/media/java/android/media/audiopolicy/AudioPolicyConfig.java index 5ad61265f439..5d2bac02a5e0 100644 --- a/media/java/android/media/audiopolicy/AudioPolicyConfig.java +++ b/media/java/android/media/audiopolicy/AudioPolicyConfig.java @@ -17,7 +17,7 @@ package android.media.audiopolicy; import android.media.AudioFormat; -import android.media.audiopolicy.AudioMixingRule.AttributeMatchCriterion; +import android.media.audiopolicy.AudioMixingRule.AudioMixMatchCriterion; import android.os.Parcel; import android.os.Parcelable; import android.util.Log; @@ -86,9 +86,9 @@ public class AudioPolicyConfig implements Parcelable { dest.writeInt(mix.getFormat().getEncoding()); dest.writeInt(mix.getFormat().getChannelMask()); // write mix rules - final ArrayList<AttributeMatchCriterion> criteria = mix.getRule().getCriteria(); + final ArrayList<AudioMixMatchCriterion> criteria = mix.getRule().getCriteria(); dest.writeInt(criteria.size()); - for (AttributeMatchCriterion criterion : criteria) { + for (AudioMixMatchCriterion criterion : criteria) { criterion.writeToParcel(dest); } } @@ -150,8 +150,8 @@ public class AudioPolicyConfig implements Parcelable { textDump += " channels=0x"; textDump += Integer.toHexString(mix.getFormat().getChannelMask()).toUpperCase() +"\n"; // write mix rules - final ArrayList<AttributeMatchCriterion> criteria = mix.getRule().getCriteria(); - for (AttributeMatchCriterion criterion : criteria) { + final ArrayList<AudioMixMatchCriterion> criteria = mix.getRule().getCriteria(); + for (AudioMixMatchCriterion criterion : criteria) { switch(criterion.mRule) { case AudioMixingRule.RULE_EXCLUDE_ATTRIBUTE_USAGE: textDump += " exclude usage "; @@ -171,11 +171,11 @@ public class AudioPolicyConfig implements Parcelable { break; case AudioMixingRule.RULE_MATCH_UID: textDump += " match UID "; - textDump += criterion.mIntProp.toString(); + textDump += criterion.mIntProp; break; case AudioMixingRule.RULE_EXCLUDE_UID: textDump += " exclude UID "; - textDump += criterion.mIntProp.toString(); + textDump += criterion.mIntProp; break; default: textDump += "invalid rule!"; diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java index 1320e382a7ea..00083e650314 100644 --- a/media/java/android/media/tv/TvInputManager.java +++ b/media/java/android/media/tv/TvInputManager.java @@ -157,35 +157,22 @@ public final class TvInputManager { public static final int RECORDING_ERROR_UNKNOWN = 0; /** - * Error for {@link TvRecordingClient.RecordingCallback#onError(int)}: The recording client has - * failed to establish a connection to a recording session. - */ - public static final int RECORDING_ERROR_CONNECTION_FAILED = 1; - - /** - * Error for {@link TvRecordingClient.RecordingCallback#onError(int)}: The recording client has - * been disconnected from the current recording session. - */ - public static final int RECORDING_ERROR_DISCONNECTED = 2; - - /** * Error for {@link TvInputService.RecordingSession#notifyError(int)} and * {@link TvRecordingClient.RecordingCallback#onError(int)}: Recording cannot proceed due to * insufficient storage space. */ - public static final int RECORDING_ERROR_INSUFFICIENT_SPACE = 3; + public static final int RECORDING_ERROR_INSUFFICIENT_SPACE = 1; /** * Error for {@link TvInputService.RecordingSession#notifyError(int)} and * {@link TvRecordingClient.RecordingCallback#onError(int)}: Recording cannot proceed because * a required recording resource was not able to be allocated. */ - public static final int RECORDING_ERROR_RESOURCE_BUSY = 4; + public static final int RECORDING_ERROR_RESOURCE_BUSY = 2; /** @hide */ @Retention(RetentionPolicy.SOURCE) - @IntDef({RECORDING_ERROR_UNKNOWN, RECORDING_ERROR_CONNECTION_FAILED, - RECORDING_ERROR_DISCONNECTED, RECORDING_ERROR_INSUFFICIENT_SPACE, + @IntDef({RECORDING_ERROR_UNKNOWN, RECORDING_ERROR_INSUFFICIENT_SPACE, RECORDING_ERROR_RESOURCE_BUSY}) public @interface RecordingError {} diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java index 334c84b1f35e..4ebd0fccc5c8 100644 --- a/media/java/android/media/tv/TvInputService.java +++ b/media/java/android/media/tv/TvInputService.java @@ -1584,6 +1584,10 @@ public abstract class TvInputService extends Service { * new data entry in the {@link TvContract.RecordedPrograms} table that describes the newly * recorded program. * + * <p>The recording session must call this method in response to {@link #onStopRecording()}. + * The session may call it even before receiving a call to {@link #onStopRecording()} if a + * partially recorded program is available when there is an error. + * * @param recordedProgramUri The URI of the newly recorded program. */ public void notifyRecordingStopped(final Uri recordedProgramUri) { @@ -1604,8 +1608,14 @@ public abstract class TvInputService extends Service { } /** - * Informs the application that there is an error. It may be called at any time after this - * recording session is created until {@link #onRelease()} is called. + * Informs the application that there is an error and this recording session is no longer + * able to start or continue recording. It may be called at any time after the recording + * session is created until {@link #onRelease()} is called. + * + * <p>The application may release the current session upon receiving the error code through + * {@link TvRecordingClient.RecordingCallback#onError(int)}. The session may call + * {@link #notifyRecordingStopped(Uri)} if a partially recorded but still playable program + * is available, before calling this method. * * @param error The error code. Should be one of the followings. * <ul> diff --git a/media/java/android/media/tv/TvRecordingClient.java b/media/java/android/media/tv/TvRecordingClient.java index 72606f59f53b..1c920f57de2a 100644 --- a/media/java/android/media/tv/TvRecordingClient.java +++ b/media/java/android/media/tv/TvRecordingClient.java @@ -68,7 +68,7 @@ public class TvRecordingClient { /** * Tunes to a given channel for TV program recording. The first tune request will create a new - * recording session for the corresponding TV input and establish the connection between the + * recording session for the corresponding TV input and establish a connection between the * application and the session. If recording has already started in the current recording * session, this method throws an exception. * @@ -88,7 +88,7 @@ public class TvRecordingClient { /** * Tunes to a given channel for TV program recording. The first tune request will create a new - * recording session for the corresponding TV input and establish the connection between the + * recording session for the corresponding TV input and establish a connection between the * application and the session. If recording has already started in the current recording * session, this method throws an exception. * @@ -226,6 +226,23 @@ public class TvRecordingClient { */ public abstract static class RecordingCallback { /** + * This is called when an error occurred while establishing a connection to the recording + * session for the corresponding TV input. + * + * @param inputId The ID of the TV input bound to the current TvRecordingClient. + */ + public void onConnectionFailed(String inputId) { + } + + /** + * This is called when the connection to the current recording session is lost. + * + * @param inputId The ID of the TV input bound to the current TvRecordingClient. + */ + public void onDisconnected(String inputId) { + } + + /** * This is called when the recording session has been tuned to the given channel and is * ready to start recording. */ @@ -249,8 +266,6 @@ public class TvRecordingClient { * @param error The error code. Should be one of the followings. * <ul> * <li>{@link TvInputManager#RECORDING_ERROR_UNKNOWN} - * <li>{@link TvInputManager#RECORDING_ERROR_CONNECTION_FAILED} - * <li>{@link TvInputManager#RECORDING_ERROR_DISCONNECTED} * <li>{@link TvInputManager#RECORDING_ERROR_INSUFFICIENT_SPACE} * <li>{@link TvInputManager#RECORDING_ERROR_RESOURCE_BUSY} * </ul> @@ -305,7 +320,9 @@ public class TvRecordingClient { mSession.tune(mChannelUri, mConnectionParams); } else { mSessionCallback = null; - mCallback.onError(TvInputManager.RECORDING_ERROR_CONNECTION_FAILED); + if (mCallback != null) { + mCallback.onConnectionFailed(mInputId); + } } } @@ -331,11 +348,13 @@ public class TvRecordingClient { Log.w(TAG, "onSessionReleased - session not created"); return; } - mSessionCallback = null; - mSession = null; mIsTuned = false; mIsRecordingStarted = false; - mCallback.onError(TvInputManager.RECORDING_ERROR_DISCONNECTED); + mSessionCallback = null; + mSession = null; + if (mCallback != null) { + mCallback.onDisconnected(mInputId); + } } @Override diff --git a/media/java/android/media/tv/TvTrackInfo.java b/media/java/android/media/tv/TvTrackInfo.java index ed432c463b8a..6a44b1e6e4e9 100644 --- a/media/java/android/media/tv/TvTrackInfo.java +++ b/media/java/android/media/tv/TvTrackInfo.java @@ -52,11 +52,14 @@ public final class TvTrackInfo implements Parcelable { private final int mVideoHeight; private final float mVideoFrameRate; private final float mVideoPixelAspectRatio; + private final byte mVideoActiveFormatDescription; + private final Bundle mExtra; private TvTrackInfo(int type, String id, String language, CharSequence description, int audioChannelCount, int audioSampleRate, int videoWidth, int videoHeight, - float videoFrameRate, float videoPixelAspectRatio, Bundle extra) { + float videoFrameRate, float videoPixelAspectRatio, byte videoActiveFormatDescription, + Bundle extra) { mType = type; mId = id; mLanguage = language; @@ -67,6 +70,7 @@ public final class TvTrackInfo implements Parcelable { mVideoHeight = videoHeight; mVideoFrameRate = videoFrameRate; mVideoPixelAspectRatio = videoPixelAspectRatio; + mVideoActiveFormatDescription = videoActiveFormatDescription; mExtra = extra; } @@ -81,6 +85,7 @@ public final class TvTrackInfo implements Parcelable { mVideoHeight = in.readInt(); mVideoFrameRate = in.readFloat(); mVideoPixelAspectRatio = in.readFloat(); + mVideoActiveFormatDescription = in.readByte(); mExtra = in.readBundle(); } @@ -179,6 +184,20 @@ public final class TvTrackInfo implements Parcelable { } /** + * Returns the Active Format Description (AFD) code of the video. + * Valid only for {@link #TYPE_VIDEO} tracks. + * + * <p>The complete list of values are defined in ETSI TS 101 154 V1.7.1 Annex B, ATSC A/53 Part + * 4 and SMPTE 2016-1-2007. + */ + public final byte getVideoActiveFormatDescription() { + if (mType != TYPE_VIDEO) { + throw new IllegalStateException("Not a video track"); + } + return mVideoActiveFormatDescription; + } + + /** * Returns the extra information about the current track. */ public final Bundle getExtra() { @@ -208,6 +227,7 @@ public final class TvTrackInfo implements Parcelable { dest.writeInt(mVideoHeight); dest.writeFloat(mVideoFrameRate); dest.writeFloat(mVideoPixelAspectRatio); + dest.writeByte(mVideoActiveFormatDescription); dest.writeBundle(mExtra); } @@ -238,6 +258,7 @@ public final class TvTrackInfo implements Parcelable { private int mVideoHeight; private float mVideoFrameRate; private float mVideoPixelAspectRatio = 1.0f; + private byte mVideoActiveFormatDescription; private Bundle mExtra; /** @@ -368,6 +389,25 @@ public final class TvTrackInfo implements Parcelable { } /** + * Sets the Active Format Description (AFD) code of the video. + * Valid only for {@link #TYPE_VIDEO} tracks. + * + * <p>This is needed for applications to be able to scale the video properly based on the + * information about where in the coded picture the active video is. + * The complete list of values are defined in ETSI TS 101 154 V1.7.1 Annex B, ATSC A/53 Part + * 4 and SMPTE 2016-1-2007. + * + * @param videoActiveFormatDescription The AFD code of the video. + */ + public final Builder setVideoActiveFormatDescription(byte videoActiveFormatDescription) { + if (mType != TYPE_VIDEO) { + throw new IllegalStateException("Not a video track"); + } + mVideoActiveFormatDescription = videoActiveFormatDescription; + return this; + } + + /** * Sets the extra information about the current track. * * @param extra The extra information. @@ -385,7 +425,7 @@ public final class TvTrackInfo implements Parcelable { public TvTrackInfo build() { return new TvTrackInfo(mType, mId, mLanguage, mDescription, mAudioChannelCount, mAudioSampleRate, mVideoWidth, mVideoHeight, mVideoFrameRate, - mVideoPixelAspectRatio, mExtra); + mVideoPixelAspectRatio, mVideoActiveFormatDescription, mExtra); } } } diff --git a/media/jni/android_media_MediaCrypto.cpp b/media/jni/android_media_MediaCrypto.cpp index e414f4838583..35da84ce82e5 100644 --- a/media/jni/android_media_MediaCrypto.cpp +++ b/media/jni/android_media_MediaCrypto.cpp @@ -25,7 +25,9 @@ #include "JNIHelp.h" #include <binder/IServiceManager.h> +#include <cutils/properties.h> #include <media/ICrypto.h> +#include <media/IMediaDrmService.h> #include <media/IMediaPlayerService.h> #include <media/stagefright/foundation/ADebug.h> @@ -61,19 +63,30 @@ JCrypto::~JCrypto() { // static sp<ICrypto> JCrypto::MakeCrypto() { sp<IServiceManager> sm = defaultServiceManager(); - - sp<IBinder> binder = - sm->getService(String16("media.player")); - - sp<IMediaPlayerService> service = - interface_cast<IMediaPlayerService>(binder); - - if (service == NULL) { - return NULL; + sp<ICrypto> crypto; + + char value[PROPERTY_VALUE_MAX]; + if (property_get("media.mediadrmservice.enable", value, NULL) + && (!strcmp("1", value) || !strcasecmp("true", value))) { + sp<IBinder> binder = + sm->getService(String16("media.drm")); + sp<IMediaDrmService> service = + interface_cast<IMediaDrmService>(binder); + if (service == NULL) { + return NULL; + } + crypto = service->makeCrypto(); + } else { + sp<IBinder> binder = + sm->getService(String16("media.player")); + sp<IMediaPlayerService> service = + interface_cast<IMediaPlayerService>(binder); + if (service == NULL) { + return NULL; + } + crypto = service->makeCrypto(); } - sp<ICrypto> crypto = service->makeCrypto(); - if (crypto == NULL || (crypto->initCheck() != OK && crypto->initCheck() != NO_INIT)) { return NULL; } diff --git a/media/jni/android_media_MediaDrm.cpp b/media/jni/android_media_MediaDrm.cpp index b8849c6e1879..73ddedf2bec1 100644 --- a/media/jni/android_media_MediaDrm.cpp +++ b/media/jni/android_media_MediaDrm.cpp @@ -28,7 +28,9 @@ #include <binder/IServiceManager.h> #include <binder/Parcel.h> +#include <cutils/properties.h> #include <media/IDrm.h> +#include <media/IMediaDrmService.h> #include <media/IMediaPlayerService.h> #include <media/stagefright/foundation/ADebug.h> #include <media/stagefright/MediaErrors.h> @@ -352,19 +354,30 @@ JDrm::~JDrm() { // static sp<IDrm> JDrm::MakeDrm() { sp<IServiceManager> sm = defaultServiceManager(); - - sp<IBinder> binder = - sm->getService(String16("media.player")); - - sp<IMediaPlayerService> service = - interface_cast<IMediaPlayerService>(binder); - - if (service == NULL) { - return NULL; + sp<IDrm> drm; + + char value[PROPERTY_VALUE_MAX]; + if (property_get("media.mediadrmservice.enable", value, NULL) + && (!strcmp("1", value) || !strcasecmp("true", value))) { + sp<IBinder> binder = + sm->getService(String16("media.drm")); + sp<IMediaDrmService> service = + interface_cast<IMediaDrmService>(binder); + if (service == NULL) { + return NULL; + } + drm = service->makeDrm(); + } else { + sp<IBinder> binder = + sm->getService(String16("media.player")); + sp<IMediaPlayerService> service = + interface_cast<IMediaPlayerService>(binder); + if (service == NULL) { + return NULL; + } + drm = service->makeDrm(); } - sp<IDrm> drm = service->makeDrm(); - if (drm == NULL || (drm->initCheck() != OK && drm->initCheck() != NO_INIT)) { return NULL; } diff --git a/packages/DocumentsUI/res/layout/fixed_layout.xml b/packages/DocumentsUI/res/layout/fixed_layout.xml index 8414febfb7ca..84a928dc3d9b 100644 --- a/packages/DocumentsUI/res/layout/fixed_layout.xml +++ b/packages/DocumentsUI/res/layout/fixed_layout.xml @@ -16,11 +16,14 @@ <!-- CoordinatorLayout is necessary for various components (e.g. Snackbars, and floating action buttons) to operate correctly. --> +<!-- focusableInTouchMode is set in order to force key events to go to the activity's global key + callback, which is necessary for proper event routing. See BaseActivity.onKeyDown. --> <android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" - android:id="@+id/coordinator_layout"> + android:id="@+id/coordinator_layout" + android:focusableInTouchMode="true"> <LinearLayout android:layout_width="match_parent" diff --git a/packages/DocumentsUI/res/layout/fragment_directory.xml b/packages/DocumentsUI/res/layout/fragment_directory.xml index d0364ff44294..0fb74e5eca34 100644 --- a/packages/DocumentsUI/res/layout/fragment_directory.xml +++ b/packages/DocumentsUI/res/layout/fragment_directory.xml @@ -83,7 +83,7 @@ android:layout_height="match_parent"> <android.support.v7.widget.RecyclerView - android:id="@+id/list" + android:id="@+id/dir_list" android:scrollbars="vertical" android:layout_width="match_parent" android:layout_height="match_parent" diff --git a/packages/DocumentsUI/res/layout/fragment_roots.xml b/packages/DocumentsUI/res/layout/fragment_roots.xml index f3de3b43be50..b33b8d09b992 100644 --- a/packages/DocumentsUI/res/layout/fragment_roots.xml +++ b/packages/DocumentsUI/res/layout/fragment_roots.xml @@ -14,8 +14,8 @@ limitations under the License. --> -<ListView xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@android:id/list" +<com.android.documentsui.RootsList xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/roots_list" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingTop="8dp" diff --git a/packages/DocumentsUI/res/layout/single_pane_layout.xml b/packages/DocumentsUI/res/layout/single_pane_layout.xml index f53d69808929..235d22d0737b 100644 --- a/packages/DocumentsUI/res/layout/single_pane_layout.xml +++ b/packages/DocumentsUI/res/layout/single_pane_layout.xml @@ -16,11 +16,14 @@ <!-- CoordinatorLayout is necessary for various components (e.g. Snackbars, and floating action buttons) to operate correctly. --> +<!-- focusableInTouchMode is set in order to force key events to go to the activity's global key + callback, which is necessary for proper event routing. See BaseActivity.onKeyDown. --> <android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" - android:id="@+id/coordinator_layout"> + android:id="@+id/coordinator_layout" + android:focusableInTouchMode="true"> <LinearLayout android:layout_width="match_parent" diff --git a/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java b/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java index 475387bb224b..4a55906e56c1 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java +++ b/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java @@ -42,6 +42,7 @@ import android.support.annotation.CallSuper; import android.support.annotation.LayoutRes; import android.support.annotation.Nullable; import android.util.Log; +import android.view.KeyEvent; import android.view.Menu; import android.view.MenuItem; import android.widget.Spinner; @@ -83,6 +84,8 @@ public abstract class BaseActivity extends Activity // We use the time gap to figure out whether to close app or reopen the drawer. private long mDrawerLastFiddled; + private boolean mNavDrawerHasFocus; + public abstract void onDocumentPicked(DocumentInfo doc, @Nullable SiblingProvider siblings); public abstract void onDocumentsPicked(List<DocumentInfo> docs); @@ -580,6 +583,54 @@ public abstract class BaseActivity extends Activity } } + /** + * Declare a global key handler to route key events when there isn't a specific focus view. This + * covers the scenario where a user opens DocumentsUI and just starts typing. + * + * @param keyCode + * @param event + * @return + */ + @CallSuper + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + if (Events.isNavigationKeyCode(keyCode)) { + // Forward all unclaimed navigation keystrokes to the DirectoryFragment. This causes any + // stray navigation keystrokes focus the content pane, which is probably what the user + // is trying to do. + DirectoryFragment df = DirectoryFragment.get(getFragmentManager()); + if (df != null) { + df.requestFocus(); + return true; + } + } else if (keyCode == KeyEvent.KEYCODE_TAB) { + toggleNavDrawerFocus(); + return true; + } + return super.onKeyDown(keyCode, event); + } + + /** + * Toggles focus between the navigation drawer and the directory listing. If the drawer isn't + * locked, open/close it as appropriate. + */ + void toggleNavDrawerFocus() { + if (mNavDrawerHasFocus) { + mDrawer.setOpen(false); + DirectoryFragment df = DirectoryFragment.get(getFragmentManager()); + if (df != null) { + df.requestFocus(); + } + } else { + mDrawer.setOpen(true); + RootsFragment rf = RootsFragment.get(getFragmentManager()); + if (rf != null) { + rf.requestFocus(); + } + } + mNavDrawerHasFocus = !mNavDrawerHasFocus; + } + DocumentInfo getRootDocumentBlocking(RootInfo root) { try { final Uri uri = DocumentsContract.buildDocumentUri( diff --git a/packages/DocumentsUI/src/com/android/documentsui/NavigationView.java b/packages/DocumentsUI/src/com/android/documentsui/NavigationView.java index ff1940aea94d..4cba1354d853 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/NavigationView.java +++ b/packages/DocumentsUI/src/com/android/documentsui/NavigationView.java @@ -132,7 +132,7 @@ class NavigationView { showBreadcrumb(true); mToolbar.setTitle(null); mIgnoreNextNavigation = true; - mBreadcrumb.setSelection(mBreadcrumbAdapter.getCount() - 1); + mBreadcrumb.setSelection(mBreadcrumbAdapter.getCount() - 1, false); } if (DEBUG) Log.d(TAG, "Final toolbar title is: " + mToolbar.getTitle()); diff --git a/packages/DocumentsUI/src/com/android/documentsui/RecentsCreateFragment.java b/packages/DocumentsUI/src/com/android/documentsui/RecentsCreateFragment.java index 7dac0c10e077..0e2762291b09 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/RecentsCreateFragment.java +++ b/packages/DocumentsUI/src/com/android/documentsui/RecentsCreateFragment.java @@ -83,7 +83,7 @@ public class RecentsCreateFragment extends Fragment { final View view = inflater.inflate(R.layout.fragment_directory, container, false); - mRecView = (RecyclerView) view.findViewById(R.id.list); + mRecView = (RecyclerView) view.findViewById(R.id.dir_list); mRecView.setLayoutManager(new LinearLayoutManager(getContext())); mRecView.addOnItemTouchListener(mItemListener); diff --git a/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java b/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java index 26bda31261ef..53f82972738c 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java +++ b/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java @@ -89,7 +89,7 @@ public class RootsFragment extends Fragment { final Context context = inflater.getContext(); final View view = inflater.inflate(R.layout.fragment_roots, container, false); - mList = (ListView) view.findViewById(android.R.id.list); + mList = (ListView) view.findViewById(R.id.roots_list); mList.setOnItemClickListener(mItemListener); mList.setChoiceMode(ListView.CHOICE_MODE_SINGLE); return view; @@ -167,6 +167,13 @@ public class RootsFragment extends Fragment { } } + /** + * Attempts to shift focus back to the navigation drawer. + */ + public void requestFocus() { + mList.requestFocus(); + } + private void showAppDetails(ResolveInfo ri) { final Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); intent.setData(Uri.fromParts("package", ri.activityInfo.packageName, null)); diff --git a/packages/DocumentsUI/src/com/android/documentsui/RootsList.java b/packages/DocumentsUI/src/com/android/documentsui/RootsList.java new file mode 100644 index 000000000000..bf03ffd1e6d6 --- /dev/null +++ b/packages/DocumentsUI/src/com/android/documentsui/RootsList.java @@ -0,0 +1,63 @@ +/* + * 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.documentsui; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.KeyEvent; +import android.widget.ListView; + +/** + * The list in the navigation drawer. This class exists for the purpose of overriding the key + * handler on ListView. Ignoring keystrokes (e.g. the tab key) cannot be properly done using + * View.OnKeyListener. + */ +public class RootsList extends ListView { + + // Multiple constructors are needed to handle all the different ways this View could be + // constructed by the framework. Don't remove them! + public RootsList(Context context) { + super(context); + } + + public RootsList(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + public RootsList(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + public RootsList(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + switch (keyCode) { + // Ignore tab key events - this causes them to bubble up to the global key handler where + // they are appropriately handled. See BaseActivity.onKeyDown. + case KeyEvent.KEYCODE_TAB: + return false; + // Prevent left/right arrow keystrokes from shifting focus away from the roots list. + case KeyEvent.KEYCODE_DPAD_LEFT: + case KeyEvent.KEYCODE_DPAD_RIGHT: + return true; + default: + return super.onKeyDown(keyCode, event); + } + } +} diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java index 174984cec8ed..f8735b2f99a1 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java +++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java @@ -101,7 +101,6 @@ import com.android.documentsui.model.RootInfo; import com.android.documentsui.services.FileOperationService; import com.android.documentsui.services.FileOperationService.OpType; import com.android.documentsui.services.FileOperations; - import com.google.common.collect.Lists; import java.lang.annotation.Retention; @@ -183,7 +182,7 @@ public class DirectoryFragment extends Fragment implements DocumentsAdapter.Envi mEmptyView = view.findViewById(android.R.id.empty); - mRecView = (RecyclerView) view.findViewById(R.id.list); + mRecView = (RecyclerView) view.findViewById(R.id.dir_list); mRecView.setRecyclerListener( new RecyclerListener() { @Override @@ -194,6 +193,11 @@ public class DirectoryFragment extends Fragment implements DocumentsAdapter.Envi mRecView.setItemAnimator(new DirectoryItemAnimator(getActivity())); + // Make the RecyclerView unfocusable. This is needed in order for the focus search code in + // FocusManager to work correctly. Setting android:focusable=false in the layout xml doesn't + // work, for some reason. + mRecView.setFocusable(false); + // TODO: Add a divider between views (which might use RecyclerView.ItemDecoration). if (DEBUG_ENABLE_DND) { setupDragAndDropOnDirectoryView(mRecView); @@ -263,7 +267,8 @@ public class DirectoryFragment extends Fragment implements DocumentsAdapter.Envi mSelectionManager.addCallback(selectionListener); - mFocusManager = new FocusManager(mRecView, mSelectionManager); + // Make sure this is done after the RecyclerView is set up. + mFocusManager = new FocusManager(mRecView); mModel = new Model(); mModel.addUpdateListener(mAdapter); @@ -834,6 +839,7 @@ public class DirectoryFragment extends Fragment implements DocumentsAdapter.Envi @Override public void initDocumentHolder(DocumentHolder holder) { holder.addEventListener(mItemEventListener); + holder.itemView.setOnFocusChangeListener(mFocusManager); } @Override @@ -1054,6 +1060,13 @@ public class DirectoryFragment extends Fragment implements DocumentsAdapter.Envi } } + /** + * Attempts to restore focus on the directory listing. + */ + public void requestFocus() { + mFocusManager.restoreLastFocus(); + } + private void setupDragAndDropOnDirectoryView(View view) { // Listen for drops on non-directory items and empty space. view.setOnDragListener(mOnDragListener); @@ -1253,16 +1266,37 @@ public class DirectoryFragment extends Fragment implements DocumentsAdapter.Envi } if (mFocusManager.handleKey(doc, keyCode, event)) { + // Handle range selection adjustments. Extending the selection will adjust the + // bounds of the in-progress range selection. Each time an unshifted navigation + // event is received, the range selection is restarted. + if (shouldExtendSelection(event)) { + if (!mSelectionManager.isRangeSelectionActive()) { + // Start a range selection if one isn't active + mSelectionManager.startRangeSelection(doc.getAdapterPosition()); + } + mSelectionManager.snapRangeSelection(mFocusManager.getFocusPosition()); + } else { + mSelectionManager.endRangeSelection(); + } return true; } // Handle enter key events if (keyCode == KeyEvent.KEYCODE_ENTER) { - return onActivate(doc); + if (event.isShiftPressed()) { + return onSelect(doc); + } else { + return onActivate(doc); + } } return false; } + + private boolean shouldExtendSelection(KeyEvent event) { + return Events.isNavigationKeyCode(event.getKeyCode()) && + event.isShiftPressed(); + } } private final class ModelUpdateListener implements Model.UpdateListener { diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/FocusManager.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/FocusManager.java index 86b9146ba48c..93ec8426e74f 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/FocusManager.java +++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/FocusManager.java @@ -16,7 +16,7 @@ package com.android.documentsui.dirlist; -import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.GridLayoutManager; import android.support.v7.widget.RecyclerView; import android.util.Log; import android.view.KeyEvent; @@ -27,19 +27,19 @@ import com.android.documentsui.Events; /** * A class that handles navigation and focus within the DirectoryFragment. */ -class FocusManager { +class FocusManager implements View.OnFocusChangeListener { private static final String TAG = "FocusManager"; private RecyclerView mView; private RecyclerView.Adapter<?> mAdapter; - private LinearLayoutManager mLayout; - private MultiSelectManager mSelectionManager; + private GridLayoutManager mLayout; - public FocusManager(RecyclerView view, MultiSelectManager selectionManager) { + private int mLastFocusPosition = RecyclerView.NO_POSITION; + + public FocusManager(RecyclerView view) { mView = view; mAdapter = view.getAdapter(); - mLayout = (LinearLayoutManager) view.getLayoutManager(); - mSelectionManager = selectionManager; + mLayout = (GridLayoutManager) view.getLayoutManager(); } /** @@ -52,24 +52,58 @@ class FocusManager { * @return Whether the event was handled. */ public boolean handleKey(DocumentHolder doc, int keyCode, KeyEvent event) { - boolean handled = false; + boolean extendSelection = false; + // Translate space/shift-space into PgDn/PgUp + if (keyCode == KeyEvent.KEYCODE_SPACE) { + if (event.isShiftPressed()) { + keyCode = KeyEvent.KEYCODE_PAGE_UP; + } else { + keyCode = KeyEvent.KEYCODE_PAGE_DOWN; + } + } else { + extendSelection = event.isShiftPressed(); + } + if (Events.isNavigationKeyCode(keyCode)) { // Find the target item and focus it. int endPos = findTargetPosition(doc.itemView, keyCode, event); if (endPos != RecyclerView.NO_POSITION) { focusItem(endPos); - - // Handle any necessary adjustments to selection. - boolean extendSelection = event.isShiftPressed(); - if (extendSelection) { - int startPos = doc.getAdapterPosition(); - mSelectionManager.selectRange(startPos, endPos); - } - handled = true; } + // Swallow all navigation keystrokes. Otherwise they go to the app's global + // key-handler, which will route them back to the DF and cause focus to be reset. + return true; + } + return false; + } + + @Override + public void onFocusChange(View v, boolean hasFocus) { + // Remember focus events on items. + if (hasFocus && v.getParent() == mView) { + mLastFocusPosition = mView.getChildAdapterPosition(v); + } + } + + /** + * Requests focus on the item that last had focus. Scrolls to that item if necessary. + */ + public void restoreLastFocus() { + if (mLastFocusPosition != RecyclerView.NO_POSITION) { + // The system takes care of situations when a view is no longer on screen, etc, + focusItem(mLastFocusPosition); + } else { + // Focus the first visible item + focusItem(mLayout.findFirstVisibleItemPosition()); } - return handled; + } + + /** + * @return The adapter position of the last focused item. + */ + public int getFocusPosition() { + return mLastFocusPosition; } /** @@ -100,12 +134,27 @@ class FocusManager { case KeyEvent.KEYCODE_DPAD_DOWN: searchDir = View.FOCUS_DOWN; break; - case KeyEvent.KEYCODE_DPAD_LEFT: - searchDir = View.FOCUS_LEFT; - break; - case KeyEvent.KEYCODE_DPAD_RIGHT: - searchDir = View.FOCUS_RIGHT; - break; + } + + if (inGridMode()) { + int currentPosition = mView.getChildAdapterPosition(view); + // Left and right arrow keys only work in grid mode. + switch (keyCode) { + case KeyEvent.KEYCODE_DPAD_LEFT: + if (currentPosition > 0) { + // Stop backward focus search at the first item, otherwise focus will wrap + // around to the last visible item. + searchDir = View.FOCUS_BACKWARD; + } + break; + case KeyEvent.KEYCODE_DPAD_RIGHT: + if (currentPosition < mAdapter.getItemCount() - 1) { + // Stop forward focus search at the last item, otherwise focus will wrap + // around to the first visible item. + searchDir = View.FOCUS_FORWARD; + } + break; + } } if (searchDir != -1) { @@ -204,4 +253,11 @@ class FocusManager { }); } } + + /** + * @return Whether the layout manager is currently in a grid-configuration. + */ + private boolean inGridMode() { + return mLayout.getSpanCount() > 1; + } } diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/MultiSelectManager.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/MultiSelectManager.java index d60825baa7cf..c8b6f8528272 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/MultiSelectManager.java +++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/MultiSelectManager.java @@ -370,39 +370,41 @@ public final class MultiSelectManager { } /** - * Handle a range selection event. - * <li> If the MSM is currently in single-select mode, only the last item in the range will - * actually be selected. - * <li>If a range selection is not already active, one will be started, and the given range of - * items will be selected. The given startPos becomes the anchor for the range selection. - * <li>If a range selection is already active, the anchor is not changed. The range is extended - * from its current anchor to endPos. + * Starts a range selection. If a range selection is already active, this will start a new range + * selection (which will reset the range anchor). * - * @param startPos - * @param endPos + * @param pos The anchor position for the selection range. */ - public void selectRange(int startPos, int endPos) { - // In single-select mode, just select the last item in the range. - if (mSingleSelect) { - attemptSelect(mAdapter.getModelId(endPos)); - return; - } + void startRangeSelection(int pos) { + attemptSelect(mAdapter.getModelId(pos)); + setSelectionRangeBegin(pos); + } - // In regular (i.e. multi-select) mode - if (!isRangeSelectionActive()) { - // If a range selection isn't active, start one up - attemptSelect(mAdapter.getModelId(startPos)); - setSelectionRangeBegin(startPos); - } - // Extend the range selection - mRanger.snapSelection(endPos); + /** + * Sets the end point for the current range selection, started by a call to + * {@link #startRangeSelection(int)}. This function should only be called when a range selection + * is active (see {@link #isRangeSelectionActive()}. Items in the range [anchor, end] will be + * selected. + * + * @param pos The new end position for the selection range. + */ + void snapRangeSelection(int pos) { + checkNotNull(mRanger); + mRanger.snapSelection(pos); notifySelectionChanged(); } /** + * Stops an in-progress range selection. + */ + void endRangeSelection() { + mRanger = null; + } + + /** * @return Whether or not there is a current range selection active. */ - private boolean isRangeSelectionActive() { + boolean isRangeSelectionActive() { return mRanger != null; } diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java index 77f16d9ca9e6..609dc0c67e50 100644 --- a/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java +++ b/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java @@ -21,6 +21,7 @@ import static com.android.documentsui.StubProvider.ROOT_1_ID; import android.os.RemoteException; import android.test.suitebuilder.annotation.LargeTest; +import android.view.KeyEvent; @LargeTest public class FilesActivityUiTest extends ActivityTest<FilesActivity> { @@ -115,4 +116,37 @@ public class FilesActivityUiTest extends ActivityTest<FilesActivity> { bot.waitForDeleteSnackbarGone(); assertFalse(bot.hasDocuments("poodles.text")); } + + // Tests that pressing tab switches focus between the roots and directory listings. + public void testKeyboard_tab() throws Exception { + bot.pressKey(KeyEvent.KEYCODE_TAB); + bot.assertHasFocus("com.android.documentsui:id/roots_list"); + bot.pressKey(KeyEvent.KEYCODE_TAB); + bot.assertHasFocus("com.android.documentsui:id/dir_list"); + } + + // Tests that arrow keys do not switch focus away from the dir list. + public void testKeyboard_arrowsDirList() throws Exception { + for (int i = 0; i < 10; i++) { + bot.pressKey(KeyEvent.KEYCODE_DPAD_LEFT); + bot.assertHasFocus("com.android.documentsui:id/dir_list"); + } + for (int i = 0; i < 10; i++) { + bot.pressKey(KeyEvent.KEYCODE_DPAD_RIGHT); + bot.assertHasFocus("com.android.documentsui:id/dir_list"); + } + } + + // Tests that arrow keys do not switch focus away from the roots list. + public void testKeyboard_arrowsRootsList() throws Exception { + bot.pressKey(KeyEvent.KEYCODE_TAB); + for (int i = 0; i < 10; i++) { + bot.pressKey(KeyEvent.KEYCODE_DPAD_RIGHT); + bot.assertHasFocus("com.android.documentsui:id/roots_list"); + } + for (int i = 0; i < 10; i++) { + bot.pressKey(KeyEvent.KEYCODE_DPAD_LEFT); + bot.assertHasFocus("com.android.documentsui:id/roots_list"); + } + } } diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/RenameDocumentUiTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/RenameDocumentUiTest.java index 1069a660862d..770bc2c2fd4b 100644 --- a/packages/DocumentsUI/tests/src/com/android/documentsui/RenameDocumentUiTest.java +++ b/packages/DocumentsUI/tests/src/com/android/documentsui/RenameDocumentUiTest.java @@ -81,7 +81,6 @@ public class RenameDocumentUiTest extends ActivityTest<FilesActivity> { bot.openOverflowMenu(); bot.openDialog(R.string.menu_rename); bot.setDialogText(newName); - bot.dismissKeyboardIfPresent(); device.waitForIdle(TIMEOUT); bot.findRenameDialogOkButton().click(); @@ -110,7 +109,6 @@ public class RenameDocumentUiTest extends ActivityTest<FilesActivity> { bot.openOverflowMenu(); bot.openDialog(R.string.menu_rename); bot.setDialogText(newName); - bot.dismissKeyboardIfPresent(); device.waitForIdle(TIMEOUT); bot.findRenameDialogCancelButton().click(); diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/UiBot.java b/packages/DocumentsUI/tests/src/com/android/documentsui/UiBot.java index 4534c40c6e0e..d2f84034593c 100644 --- a/packages/DocumentsUI/tests/src/com/android/documentsui/UiBot.java +++ b/packages/DocumentsUI/tests/src/com/android/documentsui/UiBot.java @@ -71,7 +71,7 @@ class UiBot { UiObject findRoot(String label) throws UiObjectNotFoundException { final UiSelector rootsList = new UiSelector().resourceId( "com.android.documentsui:id/container_roots").childSelector( - new UiSelector().resourceId("android:id/list")); + new UiSelector().resourceId("com.android.documentsui:id/roots_list")); // We might need to expand drawer if not visible if (!new UiObject(rootsList).waitForExists(mTimeout)) { @@ -195,6 +195,15 @@ class UiBot { assertNotNull(getSnackbar(mContext.getString(id))); } + /** + * Asserts that the specified view or one of its descendents has focus. + */ + void assertHasFocus(String resourceName) { + UiObject2 candidate = mDevice.findObject(By.res(resourceName)); + assertNotNull("Expected " + resourceName + " to have focus, but it didn't.", + candidate.findObject(By.focused(true))); + } + void openDocument(String label) throws UiObjectNotFoundException { int toolType = Configurator.getInstance().getToolType(); Configurator.getInstance().setToolType(MotionEvent.TOOL_TYPE_FINGER); @@ -309,7 +318,7 @@ class UiBot { UiObject findDocument(String label) throws UiObjectNotFoundException { final UiSelector docList = new UiSelector().resourceId( "com.android.documentsui:id/container_directory").childSelector( - new UiSelector().resourceId("com.android.documentsui:id/list")); + new UiSelector().resourceId("com.android.documentsui:id/dir_list")); // Wait for the first list item to appear new UiObject(docList.childSelector(new UiSelector())).waitForExists(mTimeout); @@ -330,7 +339,7 @@ class UiBot { UiObject findDocumentsList() { return findObject( "com.android.documentsui:id/container_directory", - "com.android.documentsui:id/list"); + "com.android.documentsui:id/dir_list"); } UiObject findSearchView() { @@ -416,4 +425,8 @@ class UiBot { mDevice.wait(Until.hasObject(By.pkg(TARGET_PKG).depth(0)), mTimeout); mDevice.waitForIdle(); } + + void pressKey(int keyCode) { + mDevice.pressKeyCode(keyCode); + } } diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManagerTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManagerTest.java index d95fb490d81e..9447d9c18a83 100644 --- a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManagerTest.java +++ b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManagerTest.java @@ -189,6 +189,54 @@ public class MultiSelectManagerTest extends AndroidTestCase { assertSelection(items.get(20)); } + public void testRangeSelection() { + mManager.startRangeSelection(15); + mManager.snapRangeSelection(19); + assertRangeSelection(15, 19); + } + + public void testRangeSelection_snapExpand() { + mManager.startRangeSelection(15); + mManager.snapRangeSelection(19); + mManager.snapRangeSelection(27); + assertRangeSelection(15, 27); + } + + public void testRangeSelection_snapContract() { + mManager.startRangeSelection(15); + mManager.snapRangeSelection(27); + mManager.snapRangeSelection(19); + assertRangeSelection(15, 19); + } + + public void testRangeSelection_snapInvert() { + mManager.startRangeSelection(15); + mManager.snapRangeSelection(27); + mManager.snapRangeSelection(3); + assertRangeSelection(3, 15); + } + + public void testRangeSelection_multiple() { + mManager.startRangeSelection(15); + mManager.snapRangeSelection(27); + mManager.endRangeSelection(); + mManager.startRangeSelection(42); + mManager.snapRangeSelection(57); + assertSelectionSize(29); + assertRangeSelected(15, 27); + assertRangeSelected(42, 57); + + } + + public void testRangeSelection_singleSelect() { + mManager = new MultiSelectManager(mEnv, mAdapter, MultiSelectManager.MODE_SINGLE, null); + mManager.addCallback(mCallback); + mManager.startRangeSelection(11); + mManager.snapRangeSelection(19); + assertSelectionSize(1); + assertSelection(items.get(19)); + } + public void testProvisionalSelection() { Selection s = mManager.getSelection(); assertSelection(); diff --git a/packages/MtpDocumentsProvider/res/values/strings.xml b/packages/MtpDocumentsProvider/res/values/strings.xml index 43a420cf6d97..f3a3fcf0563c 100644 --- a/packages/MtpDocumentsProvider/res/values/strings.xml +++ b/packages/MtpDocumentsProvider/res/values/strings.xml @@ -25,4 +25,8 @@ <string name="accessing_notification_title">Accessing files from <xliff:g id="device_model" example="Nexus 9">%1$s</xliff:g></string> <!-- Description of notification showing Files app is accessing files in a MTP device. [CHAR LIMIT=60]--> <string name="accessing_notification_description">Don\'t disconnect the device</string> + <!-- Error message shown in Files app when the connected MTP device is busy. [CHAR LIMIT=150]--> + <string name="error_busy_device">The other device is busy. You can\'t transfer files until it\'s available.</string> + <!-- Error message shown in Files app when the connected MTP device may be locked. [CHAR LIMIT=150]--> + <string name="error_locked_device">No files found. The other device may be locked. If so, unlock it and try again.</string> </resources> diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java index c456be93d5a3..5a1132721e77 100644 --- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java +++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java @@ -37,6 +37,7 @@ import android.provider.DocumentsContract.Document; import android.provider.DocumentsContract.Root; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.Preconditions; import java.io.FileNotFoundException; import java.util.Objects; @@ -244,15 +245,16 @@ class MtpDatabase { } /** - * Returns identifier of single storage if given document points device and it has only one - * storage. Otherwise null. + * Returns document IDs of storages under the given device document. * - * @param documentId Document ID that may point a device. - * @return Identifier for single storage or null. + * @param documentId Document ID that points a device. + * @return Storage document IDs. * @throws FileNotFoundException The given document ID is not registered in database. */ - @Nullable Identifier getSingleStorageIdentifier(String documentId) + String[] getStorageDocumentIds(String documentId) throws FileNotFoundException { + Preconditions.checkArgument(createIdentifier(documentId).mDocumentType == + DOCUMENT_TYPE_DEVICE); // Check if the parent document is device that has single storage. try (final Cursor cursor = mDatabase.query( TABLE_DOCUMENTS, @@ -267,12 +269,11 @@ class MtpDatabase { null, null, null)) { - if (cursor.getCount() == 1) { - cursor.moveToNext(); - return createIdentifier(cursor.getString(0)); - } else { - return null; + final String[] ids = new String[cursor.getCount()]; + for (int i = 0; cursor.moveToNext(); i++) { + ids[i] = cursor.getString(0); } + return ids; } } @@ -342,6 +343,26 @@ class MtpDatabase { } } + String getDeviceDocumentId(int deviceId) throws FileNotFoundException { + try (final Cursor cursor = mDatabase.query( + TABLE_DOCUMENTS, + strings(Document.COLUMN_DOCUMENT_ID), + COLUMN_DEVICE_ID + " = ? AND " + COLUMN_DOCUMENT_TYPE + " = ? AND " + + COLUMN_ROW_STATE + " != ?", + strings(deviceId, DOCUMENT_TYPE_DEVICE, ROW_STATE_DISCONNECTED), + null, + null, + null, + "1")) { + if (cursor.getCount() > 0) { + cursor.moveToNext(); + return cursor.getString(0); + } else { + throw new FileNotFoundException("The device ID not found: " + deviceId); + } + } + } + /** * Adds new document under the parent. * The method does not affect invalidated and pending documents because we know the document is @@ -557,13 +578,9 @@ class MtpDatabase { @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { - if (oldVersion == 1) { - db.execSQL("DROP TABLE " + TABLE_DOCUMENTS); - db.execSQL("DROP TABLE " + TABLE_ROOT_EXTRA); - onCreate(db); - return; - } - throw new UnsupportedOperationException(); + db.execSQL("DROP TABLE " + TABLE_DOCUMENTS); + db.execSQL("DROP TABLE " + TABLE_ROOT_EXTRA); + onCreate(db); } } @@ -680,7 +697,12 @@ class MtpDatabase { if (formatCodeMimeType != null) { return formatCodeMimeType; } - return MediaFile.getMimeTypeForFile(info.getName()); + final String mediaFileMimeType = MediaFile.getMimeTypeForFile(info.getName()); + if (mediaFileMimeType != null) { + return mediaFileMimeType; + } + // We don't know the file type. + return "application/octet-stream"; } static String[] strings(Object... args) { diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseConstants.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseConstants.java index cb076af118b4..ab356ceba1c2 100644 --- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseConstants.java +++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseConstants.java @@ -30,7 +30,7 @@ import java.util.Map; * Class containing MtpDatabase constants. */ class MtpDatabaseConstants { - static final int DATABASE_VERSION = 2; + static final int DATABASE_VERSION = 3; static final String DATABASE_NAME = "database"; static final int FLAG_DATABASE_IN_MEMORY = 1; @@ -125,7 +125,7 @@ class MtpDatabaseConstants { COLUMN_PARENT_DOCUMENT_ID + " INTEGER," + COLUMN_ROW_STATE + " INTEGER NOT NULL," + COLUMN_DOCUMENT_TYPE + " INTEGER NOT NULL," + - Document.COLUMN_MIME_TYPE + " TEXT," + + Document.COLUMN_MIME_TYPE + " TEXT NOT NULL," + Document.COLUMN_DISPLAY_NAME + " TEXT NOT NULL," + Document.COLUMN_SUMMARY + " TEXT," + Document.COLUMN_LAST_MODIFIED + " INTEGER," + diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java index 033845401b4b..a51250943404 100644 --- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java +++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java @@ -20,10 +20,12 @@ import android.content.ContentResolver; import android.content.res.AssetFileDescriptor; import android.content.res.Resources; import android.database.Cursor; +import android.database.MatrixCursor; import android.graphics.Point; import android.media.MediaFile; import android.mtp.MtpConstants; import android.mtp.MtpObjectInfo; +import android.os.Bundle; import android.os.CancellationSignal; import android.os.ParcelFileDescriptor; import android.os.storage.StorageManager; @@ -35,6 +37,7 @@ import android.util.Log; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import com.android.mtp.exceptions.BusyDeviceException; import java.io.FileNotFoundException; import java.io.IOException; @@ -163,17 +166,25 @@ public class MtpDocumentsProvider extends DocumentsProvider { try { openDevice(parentIdentifier.mDeviceId); if (parentIdentifier.mDocumentType == MtpDatabaseConstants.DOCUMENT_TYPE_DEVICE) { - final Identifier singleStorageIdentifier = - mDatabase.getSingleStorageIdentifier(parentDocumentId); - if (singleStorageIdentifier == null) { + final String[] storageDocIds = mDatabase.getStorageDocumentIds(parentDocumentId); + if (storageDocIds.length == 0) { + // Remote device does not provide storages. Maybe it is locked. + return createErrorCursor(projection, R.string.error_locked_device); + } else if (storageDocIds.length > 1) { // Returns storage list from database. return mDatabase.queryChildDocuments(projection, parentDocumentId); } - parentIdentifier = singleStorageIdentifier; + + // Exact one storage is found. Skip storage and returns object in the single + // storage. + parentIdentifier = mDatabase.createIdentifier(storageDocIds[0]); } + // Returns object list from document loader. return getDocumentLoader(parentIdentifier).queryChildDocuments( projection, parentIdentifier); + } catch (BusyDeviceException exception) { + return createErrorCursor(projection, R.string.error_busy_device); } catch (IOException exception) { Log.e(MtpDocumentsProvider.TAG, "queryChildDocuments", exception); throw new FileNotFoundException(exception.getMessage()); @@ -350,6 +361,16 @@ public class MtpDocumentsProvider extends DocumentsProvider { } /** + * Obtains document ID for the given device ID. + * @param deviceId + * @return document ID + * @throws FileNotFoundException device ID has not been build. + */ + public String getDeviceDocumentId(int deviceId) throws FileNotFoundException { + return mDatabase.getDeviceDocumentId(deviceId); + } + + /** * Resumes root scanner to handle the update of device list. */ void resumeRootScanner() { @@ -442,6 +463,21 @@ public class MtpDocumentsProvider extends DocumentsProvider { } } + /** + * Creates empty cursor with specific error message. + * + * @param projection Column names. + * @param stringResId String resource ID of error message. + * @return Empty cursor with error message. + */ + private Cursor createErrorCursor(String[] projection, int stringResId) { + final Bundle bundle = new Bundle(); + bundle.putString(DocumentsContract.EXTRA_ERROR, mResources.getString(stringResId)); + final Cursor cursor = new MatrixCursor(projection); + cursor.setExtras(bundle); + return cursor; + } + private static class DeviceToolkit { public final PipeManager mPipeManager; public final DocumentLoader mDocumentLoader; diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpManager.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpManager.java index 5519efd5a323..0527790470fb 100644 --- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpManager.java +++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpManager.java @@ -33,6 +33,7 @@ import android.util.Log; import android.util.SparseArray; import com.android.internal.annotations.VisibleForTesting; +import com.android.mtp.exceptions.BusyDeviceException; import java.io.FileNotFoundException; import java.io.IOException; @@ -101,7 +102,8 @@ class MtpManager { } if (!device.open(connection)) { - throw new IOException("Failed to open a MTP device."); + // We cannot open connection when another application use the device. + throw new BusyDeviceException(); } // Handle devices that fail to obtain storages just after opening a MTP session. @@ -134,7 +136,7 @@ class MtpManager { try { roots = getRoots(device.getDeviceId()); } catch (IOException exp) { - Log.e(MtpDocumentsProvider.TAG, exp.getMessage()); + Log.e(MtpDocumentsProvider.TAG, "Failed to open device", exp); // If we failed to fetch roots for the device, we still returns device model // with an empty set of roots so that the device is shown DocumentsUI as long as // the device is physically connected. diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/ReceiverActivity.java b/packages/MtpDocumentsProvider/src/com/android/mtp/ReceiverActivity.java index c7206a7929e0..84745b29e428 100644 --- a/packages/MtpDocumentsProvider/src/com/android/mtp/ReceiverActivity.java +++ b/packages/MtpDocumentsProvider/src/com/android/mtp/ReceiverActivity.java @@ -17,10 +17,15 @@ package com.android.mtp; import android.app.Activity; -import android.content.ComponentName; import android.content.Intent; +import android.hardware.usb.UsbDevice; import android.hardware.usb.UsbManager; +import android.net.Uri; import android.os.Bundle; +import android.provider.DocumentsContract; +import android.util.Log; + +import java.io.IOException; /** * Invisible activity to receive intents. @@ -33,14 +38,21 @@ public class ReceiverActivity extends Activity { protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(getIntent().getAction())) { - // TODO: To obtain data URI for the attached device, we need to wait until RootScanner - // found the device and add it to database. Set correct root URI, and use ACTION_BROWSE - // to launch Documents UI. - final Intent intent = new Intent(Intent.ACTION_MAIN); - intent.addCategory(Intent.CATEGORY_LAUNCHER); - intent.setComponent(new ComponentName( - "com.android.documentsui", "com.android.documentsui.LauncherActivity")); - this.startActivity(intent); + final UsbDevice device = getIntent().getParcelableExtra(UsbManager.EXTRA_DEVICE); + try { + final MtpDocumentsProvider provider = MtpDocumentsProvider.getInstance(); + provider.openDevice(device.getDeviceId()); + final String deviceRootId = provider.getDeviceDocumentId(device.getDeviceId()); + final Uri uri = DocumentsContract.buildRootUri( + MtpDocumentsProvider.AUTHORITY, deviceRootId); + + final Intent intent = new Intent(DocumentsContract.ACTION_BROWSE); + intent.setData(uri); + intent.addCategory(Intent.CATEGORY_DEFAULT); + this.startActivity(intent); + } catch (IOException exception) { + Log.e(MtpDocumentsProvider.TAG, "Failed to open device", exception); + } } finish(); } diff --git a/tools/aapt2/util/Comparators.h b/packages/MtpDocumentsProvider/src/com/android/mtp/exceptions/BusyDeviceException.java index 0ee0bf35457d..55f55b0edbc3 100644 --- a/tools/aapt2/util/Comparators.h +++ b/packages/MtpDocumentsProvider/src/com/android/mtp/exceptions/BusyDeviceException.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 The Android Open Source Project + * 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. @@ -14,24 +14,12 @@ * limitations under the License. */ -#ifndef AAPT_UTIL_COMPARATORS_H -#define AAPT_UTIL_COMPARATORS_H +package com.android.mtp.exceptions; -#include "ConfigDescription.h" -#include "ResourceTable.h" +import java.io.IOException; -namespace aapt { -namespace cmp { - -inline bool lessThanConfig(const ResourceConfigValue& a, const ConfigDescription& b) { - return a.config < b; -} - -inline bool lessThanType(const std::unique_ptr<ResourceTableType>& a, ResourceType b) { - return a->type < b; +/** + * Exception thrown when the device is busy and the requested operation cannon be completed. + */ +public class BusyDeviceException extends IOException { } - -} // namespace cmp -} // namespace aapt - -#endif /* AAPT_UTIL_COMPARATORS_H */ diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java index 5b0f55703a2b..01fcc55d58f0 100644 --- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java +++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java @@ -29,6 +29,8 @@ import android.provider.DocumentsContract; import android.test.AndroidTestCase; import android.test.suitebuilder.annotation.MediumTest; +import com.android.mtp.exceptions.BusyDeviceException; + import java.io.FileNotFoundException; import java.io.IOException; import java.util.Arrays; @@ -526,6 +528,52 @@ public class MtpDocumentsProviderTest extends AndroidTestCase { } } + public void testBusyDevice() throws Exception { + mMtpManager = new TestMtpManager(getContext()) { + @Override + void openDevice(int deviceId) throws IOException { + throw new BusyDeviceException(); + } + }; + setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); + mMtpManager.addValidDevice(new MtpDeviceRecord( + 0, "Device A", false /* unopened */, new MtpRoot[0], null, null)); + + mProvider.resumeRootScanner(); + mResolver.waitForNotification(ROOTS_URI, 1); + + try (final Cursor cursor = mProvider.queryRoots(null)) { + assertEquals(1, cursor.getCount()); + } + + try (final Cursor cursor = mProvider.queryChildDocuments("1", null, null)) { + assertEquals(0, cursor.getCount()); + assertEquals( + "error_busy_device", + cursor.getExtras().getString(DocumentsContract.EXTRA_ERROR)); + } + } + + public void testLockedDevice() throws Exception { + setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); + mMtpManager.addValidDevice(new MtpDeviceRecord( + 0, "Device A", false /* unopened */, new MtpRoot[0], null, null)); + + mProvider.resumeRootScanner(); + mResolver.waitForNotification(ROOTS_URI, 1); + + try (final Cursor cursor = mProvider.queryRoots(null)) { + assertEquals(1, cursor.getCount()); + } + + try (final Cursor cursor = mProvider.queryChildDocuments("1", null, null)) { + assertEquals(0, cursor.getCount()); + assertEquals( + "error_locked_device", + cursor.getExtras().getString(DocumentsContract.EXTRA_ERROR)); + } + } + private void setupProvider(int flag) { mDatabase = new MtpDatabase(getContext(), flag); mProvider = new MtpDocumentsProvider(); diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestResources.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestResources.java index b23038b6c3f6..8676b5a0d315 100644 --- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestResources.java +++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestResources.java @@ -24,6 +24,10 @@ class TestResources extends MockResources { switch (id) { case R.string.root_name: return "%1$s %2$s"; + case R.string.error_busy_device: + return "error_busy_device"; + case R.string.error_locked_device: + return "error_locked_device"; } throw new NotFoundException(); } diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerAdapter.java b/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerAdapter.java index 73171c70d856..1d6197a9333f 100644 --- a/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerAdapter.java +++ b/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerAdapter.java @@ -65,7 +65,7 @@ public class SettingsDrawerAdapter extends BaseAdapter { } public Tile getTile(int position) { - return mItems.get(position).tile; + return mItems.get(position) != null ? mItems.get(position).tile : null; } @Override diff --git a/packages/Shell/src/com/android/shell/BugreportProgressService.java b/packages/Shell/src/com/android/shell/BugreportProgressService.java index 8c555a655bef..bad7e202e1d1 100644 --- a/packages/Shell/src/com/android/shell/BugreportProgressService.java +++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java @@ -88,8 +88,8 @@ import android.widget.Toast; * <p> * The workflow is: * <ol> - * <li>When {@code dumpstate} starts, it sends a {@code BUGREPORT_STARTED} with its pid and the - * estimated total effort. + * <li>When {@code dumpstate} starts, it sends a {@code BUGREPORT_STARTED} with a sequential id, + * its pid, and the estimated total effort. * <li>{@link BugreportReceiver} receives the intent and delegates it to this service. * <li>Upon start, this service: * <ol> @@ -132,6 +132,7 @@ public class BugreportProgressService extends Service { static final String EXTRA_BUGREPORT = "android.intent.extra.BUGREPORT"; static final String EXTRA_SCREENSHOT = "android.intent.extra.SCREENSHOT"; + static final String EXTRA_ID = "android.intent.extra.ID"; static final String EXTRA_PID = "android.intent.extra.PID"; static final String EXTRA_MAX = "android.intent.extra.MAX"; static final String EXTRA_NAME = "android.intent.extra.NAME"; @@ -177,7 +178,7 @@ public class BugreportProgressService extends Service { */ private static final String SCREENSHOT_DIR = "bugreports"; - /** Managed dumpstate processes (keyed by pid) */ + /** Managed dumpstate processes (keyed by id) */ private final SparseArray<BugreportInfo> mProcesses = new SparseArray<>(); private Context mContext; @@ -222,7 +223,7 @@ public class BugreportProgressService extends Service { } // If service is killed it cannot be recreated because it would not know which - // dumpstate PIDs it would have to watch. + // dumpstate IDs it would have to watch. return START_NOT_STICKY; } @@ -299,38 +300,41 @@ public class BugreportProgressService extends Service { } final String action = intent.getAction(); final int pid = intent.getIntExtra(EXTRA_PID, 0); + // TODO: temporarily using pid as id until test cases and dumpstate are changed. + final int id = intent.getIntExtra(EXTRA_ID, pid); final int max = intent.getIntExtra(EXTRA_MAX, -1); final String name = intent.getStringExtra(EXTRA_NAME); - if (DEBUG) Log.v(TAG, "action: " + action + ", name: " + name + ", pid: " + pid - + ", max: "+ max); + if (DEBUG) + Log.v(TAG, "action: " + action + ", name: " + name + ", id: " + id + ", pid: " + + pid + ", max: " + max); switch (action) { case INTENT_BUGREPORT_STARTED: - if (!startProgress(name, pid, max)) { + if (!startProgress(name, id, pid, max)) { stopSelfWhenDone(); return; } poll(); break; case INTENT_BUGREPORT_FINISHED: - if (pid == 0) { + if (id == 0) { // Shouldn't happen, unless BUGREPORT_FINISHED is received from a legacy, // out-of-sync dumpstate process. - Log.w(TAG, "Missing " + EXTRA_PID + " on intent " + intent); + Log.w(TAG, "Missing " + EXTRA_ID + " on intent " + intent); } - onBugreportFinished(pid, intent); + onBugreportFinished(id, intent); break; case INTENT_BUGREPORT_INFO_LAUNCH: - launchBugreportInfoDialog(pid); + launchBugreportInfoDialog(id); break; case INTENT_BUGREPORT_SCREENSHOT: - takeScreenshot(pid, true); + takeScreenshot(id, true); break; case INTENT_BUGREPORT_SHARE: - shareBugreport(pid, (BugreportInfo) intent.getParcelableExtra(EXTRA_INFO)); + shareBugreport(id, (BugreportInfo) intent.getParcelableExtra(EXTRA_INFO)); break; case INTENT_BUGREPORT_CANCEL: - cancel(pid); + cancel(id); break; default: Log.w(TAG, "Unsupported intent: " + action); @@ -367,10 +371,10 @@ public class BugreportProgressService extends Service { } } - private BugreportInfo getInfo(int pid) { - final BugreportInfo info = mProcesses.get(pid); + private BugreportInfo getInfo(int id) { + final BugreportInfo info = mProcesses.get(id); if (info == null) { - Log.w(TAG, "Not monitoring process with PID " + pid); + Log.w(TAG, "Not monitoring process with ID " + id); } return info; } @@ -381,10 +385,14 @@ public class BugreportProgressService extends Service { * * @return whether it succeeded or not. */ - private boolean startProgress(String name, int pid, int max) { + private boolean startProgress(String name, int id, int pid, int max) { if (name == null) { Log.w(TAG, "Missing " + EXTRA_NAME + " on start intent"); } + if (id == -1) { + Log.e(TAG, "Missing " + EXTRA_ID + " on start intent"); + return false; + } if (pid == -1) { Log.e(TAG, "Missing " + EXTRA_PID + " on start intent"); return false; @@ -394,14 +402,14 @@ public class BugreportProgressService extends Service { return false; } - final BugreportInfo info = new BugreportInfo(mContext, pid, name, max); - if (mProcesses.indexOfKey(pid) >= 0) { - Log.w(TAG, "PID " + pid + " already watched"); + final BugreportInfo info = new BugreportInfo(mContext, id, pid, name, max); + if (mProcesses.indexOfKey(id) >= 0) { + Log.w(TAG, "ID " + id + " already watched"); } else { - mProcesses.put(info.pid, info); + mProcesses.put(info.id, info); } // Take initial screenshot. - takeScreenshot(pid, false); + takeScreenshot(id, false); updateProgress(info); return true; } @@ -423,22 +431,22 @@ public class BugreportProgressService extends Service { com.android.internal.R.string.cancel), newCancelIntent(mContext, info)).build(); final Intent infoIntent = new Intent(mContext, BugreportProgressService.class); infoIntent.setAction(INTENT_BUGREPORT_INFO_LAUNCH); - infoIntent.putExtra(EXTRA_PID, info.pid); + infoIntent.putExtra(EXTRA_ID, info.id); final Action infoAction = new Action.Builder(null, mContext.getString(R.string.bugreport_info_action), - PendingIntent.getService(mContext, info.pid, infoIntent, + PendingIntent.getService(mContext, info.id, infoIntent, PendingIntent.FLAG_UPDATE_CURRENT)).build(); final Intent screenshotIntent = new Intent(mContext, BugreportProgressService.class); screenshotIntent.setAction(INTENT_BUGREPORT_SCREENSHOT); - screenshotIntent.putExtra(EXTRA_PID, info.pid); + screenshotIntent.putExtra(EXTRA_ID, info.id); PendingIntent screenshotPendingIntent = mTakingScreenshot ? null : PendingIntent - .getService(mContext, info.pid, screenshotIntent, + .getService(mContext, info.id, screenshotIntent, PendingIntent.FLAG_UPDATE_CURRENT); final Action screenshotAction = new Action.Builder(null, mContext.getString(R.string.bugreport_screenshot_action), screenshotPendingIntent).build(); - final String title = mContext.getString(R.string.bugreport_in_progress_title, info.pid); + final String title = mContext.getString(R.string.bugreport_in_progress_title, info.id); final String name = info.name != null ? info.name : mContext.getString(R.string.bugreport_unnamed); @@ -464,8 +472,8 @@ public class BugreportProgressService extends Service { + info + ")"); return; } - Log.v(TAG, "Sending 'Progress' notification for pid " + info.pid + ": " + percentText); - NotificationManager.from(mContext).notify(TAG, info.pid, notification); + Log.v(TAG, "Sending 'Progress' notification for id " + info.id + ": " + percentText); + NotificationManager.from(mContext).notify(TAG, info.id, notification); } /** @@ -474,38 +482,38 @@ public class BugreportProgressService extends Service { private static PendingIntent newCancelIntent(Context context, BugreportInfo info) { final Intent intent = new Intent(INTENT_BUGREPORT_CANCEL); intent.setClass(context, BugreportProgressService.class); - intent.putExtra(EXTRA_PID, info.pid); - return PendingIntent.getService(context, info.pid, intent, + intent.putExtra(EXTRA_ID, info.id); + return PendingIntent.getService(context, info.id, intent, PendingIntent.FLAG_UPDATE_CURRENT); } /** * Finalizes the progress on a given bugreport and cancel its notification. */ - private void stopProgress(int pid) { - if (mProcesses.indexOfKey(pid) < 0) { - Log.w(TAG, "PID not watched: " + pid); + private void stopProgress(int id) { + if (mProcesses.indexOfKey(id) < 0) { + Log.w(TAG, "ID not watched: " + id); } else { - Log.d(TAG, "Removing PID " + pid); - mProcesses.remove(pid); + Log.d(TAG, "Removing ID " + id); + mProcesses.remove(id); } stopSelfWhenDone(); - Log.v(TAG, "stopProgress(" + pid + "): cancel notification"); - NotificationManager.from(mContext).cancel(TAG, pid); + Log.v(TAG, "stopProgress(" + id + "): cancel notification"); + NotificationManager.from(mContext).cancel(TAG, id); } /** * Cancels a bugreport upon user's request. */ - private void cancel(int pid) { - Log.v(TAG, "cancel: pid=" + pid); - final BugreportInfo info = getInfo(pid); + private void cancel(int id) { + Log.v(TAG, "cancel: ID=" + id); + final BugreportInfo info = getInfo(id); if (info != null && !info.finished) { - Log.i(TAG, "Cancelling bugreport service (pid=" + pid + ") on user's request"); + Log.i(TAG, "Cancelling bugreport service (ID=" + id + ") on user's request"); setSystemProperty(CTL_STOP, BUGREPORT_SERVICE); deleteScreenshots(info); } - stopProgress(pid); + stopProgress(id); } /** @@ -522,14 +530,15 @@ public class BugreportProgressService extends Service { for (int i = 0; i < total; i++) { final BugreportInfo info = mProcesses.valueAt(i); if (info == null) { - Log.wtf(TAG, "pollProgress(): null info at index " + i + "(pid = " + Log.wtf(TAG, "pollProgress(): null info at index " + i + "(ID = " + mProcesses.keyAt(i) + ")"); continue; } final int pid = info.pid; + final int id = info.id; if (info.finished) { - if (DEBUG) Log.v(TAG, "Skipping finished process " + pid); + if (DEBUG) Log.v(TAG, "Skipping finished process " + pid + "(id: " + id + ")"); continue; } activeProcesses++; @@ -544,13 +553,13 @@ public class BugreportProgressService extends Service { if (progressChanged || maxChanged) { if (progressChanged) { - if (DEBUG) Log.v(TAG, "Updating progress for PID " + pid + " from " - + info.progress + " to " + progress); + if (DEBUG) Log.v(TAG, "Updating progress for PID " + pid + "(id: " + id + + ") from " + info.progress + " to " + progress); info.progress = progress; } if (maxChanged) { - Log.i(TAG, "Updating max progress for PID " + pid + " from " + info.max - + " to " + max); + Log.i(TAG, "Updating max progress for PID " + pid + "(id: " + id + + ") from " + info.max + " to " + max); info.max = max; } info.lastUpdate = System.currentTimeMillis(); @@ -558,9 +567,9 @@ public class BugreportProgressService extends Service { } else { long inactiveTime = System.currentTimeMillis() - info.lastUpdate; if (inactiveTime >= INACTIVITY_TIMEOUT) { - Log.w(TAG, "No progress update for process " + pid + " since " + Log.w(TAG, "No progress update for PID " + pid + " since " + info.getFormattedLastUpdate()); - stopProgress(info.pid); + stopProgress(info.id); } } } @@ -572,19 +581,16 @@ public class BugreportProgressService extends Service { * Fetches a {@link BugreportInfo} for a given process and launches a dialog where the user can * change its values. */ - private void launchBugreportInfoDialog(int pid) { + private void launchBugreportInfoDialog(int id) { // Copy values so it doesn't lock mProcesses while UI is being updated final String name, title, description; - final BugreportInfo info = getInfo(pid); + final BugreportInfo info = getInfo(id); if (info == null) { return; } - name = info.name; - title = info.title; - description = info.description; collapseNotificationBar(); - mInfoDialog.initialize(mContext, pid, name, title, description); + mInfoDialog.initialize(mContext, info); } /** @@ -597,7 +603,7 @@ public class BugreportProgressService extends Service { * Typical usage is delaying when taken from the notification action, and taking it right away * upon receiving a {@link #INTENT_BUGREPORT_STARTED}. */ - private void takeScreenshot(int pid, boolean delayed) { + private void takeScreenshot(int id, boolean delayed) { setTakingScreenshot(true); if (delayed) { collapseNotificationBar(); @@ -608,28 +614,28 @@ public class BugreportProgressService extends Service { // Show a toast just once, otherwise it might be captured in the screenshot. Toast.makeText(mContext, msg, Toast.LENGTH_SHORT).show(); - takeScreenshot(pid, SCREENSHOT_DELAY_SECONDS); + takeScreenshot(id, SCREENSHOT_DELAY_SECONDS); } else { - takeScreenshot(pid, 0); + takeScreenshot(id, 0); } } /** * Takes a screenshot after {@code delay} seconds. */ - private void takeScreenshot(int pid, int delay) { + private void takeScreenshot(int id, int delay) { if (delay > 0) { - Log.d(TAG, "Taking screenshot for " + pid + " in " + delay + " seconds"); + Log.d(TAG, "Taking screenshot for " + id + " in " + delay + " seconds"); final Message msg = mMainHandler.obtainMessage(); msg.what = MSG_DELAYED_SCREENSHOT; - msg.arg1 = pid; + msg.arg1 = id; msg.arg2 = delay - 1; mMainHandler.sendMessageDelayed(msg, DateUtils.SECOND_IN_MILLIS); return; } // It's time to take the screenshot: let the proper thread handle it - final BugreportInfo info = getInfo(pid); + final BugreportInfo info = getInfo(id); if (info == null) { return; } @@ -638,7 +644,7 @@ public class BugreportProgressService extends Service { final Message requestMsg = new Message(); requestMsg.what = MSG_SCREENSHOT_REQUEST; - requestMsg.arg1 = pid; + requestMsg.arg1 = id; requestMsg.obj = screenshotPath; mScreenshotHandler.sendMessage(requestMsg); } @@ -715,30 +721,30 @@ public class BugreportProgressService extends Service { */ private void stopSelfWhenDone() { if (mProcesses.size() > 0) { - if (DEBUG) Log.v(TAG, "Staying alive, waiting for pids " + mProcesses); + if (DEBUG) Log.d(TAG, "Staying alive, waiting for IDs " + mProcesses); return; } - Log.v(TAG, "No more pids to handle, shutting down"); + Log.v(TAG, "No more processes to handle, shutting down"); stopSelf(); } /** * Handles the BUGREPORT_FINISHED intent sent by {@code dumpstate}. */ - private void onBugreportFinished(int pid, Intent intent) { + private void onBugreportFinished(int id, Intent intent) { final File bugreportFile = getFileExtra(intent, EXTRA_BUGREPORT); if (bugreportFile == null) { // Should never happen, dumpstate always set the file. Log.wtf(TAG, "Missing " + EXTRA_BUGREPORT + " on intent " + intent); return; } - mInfoDialog.onBugreportFinished(pid); - BugreportInfo info = getInfo(pid); + mInfoDialog.onBugreportFinished(id); + BugreportInfo info = getInfo(id); if (info == null) { // Happens when BUGREPORT_FINISHED was received without a BUGREPORT_STARTED first. - Log.v(TAG, "Creating info for untracked pid " + pid); - info = new BugreportInfo(mContext, pid); - mProcesses.put(pid, info); + Log.v(TAG, "Creating info for untracked ID " + id); + info = new BugreportInfo(mContext, id); + mProcesses.put(id, info); } info.renameScreenshots(mScreenshotsDir); info.bugreportFile = bugreportFile; @@ -765,7 +771,7 @@ public class BugreportProgressService extends Service { if (!info.bugreportFile.exists() || !info.bugreportFile.canRead()) { Log.e(TAG, "Could not read bugreport file " + info.bugreportFile); Toast.makeText(context, R.string.bugreport_unreadable_text, Toast.LENGTH_LONG).show(); - stopProgress(info.pid); + stopProgress(info.id); return; } @@ -837,12 +843,12 @@ public class BugreportProgressService extends Service { * Shares the bugreport upon user's request by issuing a {@link Intent#ACTION_SEND_MULTIPLE} * intent, but issuing a warning dialog the first time. */ - private void shareBugreport(int pid, BugreportInfo sharedInfo) { - BugreportInfo info = getInfo(pid); + private void shareBugreport(int id, BugreportInfo sharedInfo) { + BugreportInfo info = getInfo(id); if (info == null) { // Service was terminated but notification persisted info = sharedInfo; - Log.d(TAG, "shareBugreport(): no info for PID " + pid + " on managed processes (" + Log.d(TAG, "shareBugreport(): no info for ID " + id + " on managed processes (" + mProcesses + "), using info from intent instead (" + info + ")"); } @@ -863,7 +869,7 @@ public class BugreportProgressService extends Service { mContext.startActivity(notifIntent); // ... and stop watching this process. - stopProgress(pid); + stopProgress(id); } /** @@ -877,16 +883,16 @@ public class BugreportProgressService extends Service { final Intent shareIntent = new Intent(INTENT_BUGREPORT_SHARE); shareIntent.setClass(context, BugreportProgressService.class); shareIntent.setAction(INTENT_BUGREPORT_SHARE); - shareIntent.putExtra(EXTRA_PID, info.pid); + shareIntent.putExtra(EXTRA_ID, info.id); shareIntent.putExtra(EXTRA_INFO, info); - final String title = context.getString(R.string.bugreport_finished_title, info.pid); + final String title = context.getString(R.string.bugreport_finished_title, info.id); final Notification.Builder builder = new Notification.Builder(context) .setSmallIcon(com.android.internal.R.drawable.stat_sys_adb) .setContentTitle(title) .setTicker(title) .setContentText(context.getString(R.string.bugreport_finished_text)) - .setContentIntent(PendingIntent.getService(context, info.pid, shareIntent, + .setContentIntent(PendingIntent.getService(context, info.id, shareIntent, PendingIntent.FLAG_UPDATE_CURRENT)) .setDeleteIntent(newCancelIntent(context, info)) .setLocalOnly(true) @@ -897,8 +903,8 @@ public class BugreportProgressService extends Service { builder.setContentInfo(info.name); } - Log.v(TAG, "Sending 'Share' notification for pid " + info.pid + ": " + title); - NotificationManager.from(context).notify(TAG, info.pid, builder.build()); + Log.v(TAG, "Sending 'Share' notification for ID " + info.id + ": " + title); + NotificationManager.from(context).notify(TAG, info.id, builder.build()); } /** @@ -906,7 +912,7 @@ public class BugreportProgressService extends Service { * finishes - at this point there is nothing to be done other than waiting, hence it has no * pending action. */ - private static void sendBugreportBeingUpdatedNotification(Context context, int pid) { + private static void sendBugreportBeingUpdatedNotification(Context context, int id) { final String title = context.getString(R.string.bugreport_updating_title); final Notification.Builder builder = new Notification.Builder(context) .setSmallIcon(com.android.internal.R.drawable.stat_sys_adb) @@ -916,8 +922,8 @@ public class BugreportProgressService extends Service { .setLocalOnly(true) .setColor(context.getColor( com.android.internal.R.color.system_notification_accent_color)); - Log.v(TAG, "Sending 'Updating zip' notification for pid " + pid + ": " + title); - NotificationManager.from(context).notify(TAG, pid, builder.build()); + Log.v(TAG, "Sending 'Updating zip' notification for ID " + id + ": " + title); + NotificationManager.from(context).notify(TAG, id, builder.build()); } /** @@ -985,7 +991,7 @@ public class BugreportProgressService extends Service { // It's not possible to add a new entry into an existing file, so we need to create a new // zip, copy all entries, then rename it. - sendBugreportBeingUpdatedNotification(context, info.pid); // ...and that takes time + sendBugreportBeingUpdatedNotification(context, info.id); // ...and that takes time final File dir = info.bugreportFile.getParentFile(); final File tmpZip = new File(dir, "tmp-" + info.bugreportFile.getName()); Log.d(TAG, "Writing temporary zip file (" + tmpZip + ") with title and/or description"); @@ -1113,8 +1119,8 @@ public class BugreportProgressService extends Service { /** * Updates the user-provided details of a bugreport. */ - private void updateBugreportInfo(int pid, String name, String title, String description) { - final BugreportInfo info = getInfo(pid); + private void updateBugreportInfo(int id, String name, String title, String description) { + final BugreportInfo info = getInfo(id); if (info == null) { return; } @@ -1179,6 +1185,7 @@ public class BugreportProgressService extends Service { private EditText mInfoDescription; private AlertDialog mDialog; private Button mOkButton; + private int mId; private int mPid; /** @@ -1207,8 +1214,7 @@ public class BugreportProgressService extends Service { /** * Sets its internal state and displays the dialog. */ - private void initialize(Context context, int pid, String name, String title, - String description) { + private void initialize(Context context, BugreportInfo info) { // First initializes singleton. if (mDialog == null) { @SuppressLint("InflateParams") @@ -1232,7 +1238,7 @@ public class BugreportProgressService extends Service { mDialog = new AlertDialog.Builder(context) .setView(view) - .setTitle(context.getString(R.string.bugreport_info_dialog_title, pid)) + .setTitle(context.getString(R.string.bugreport_info_dialog_title, info.id)) .setCancelable(false) .setPositiveButton(context.getString(com.android.internal.R.string.ok), null) @@ -1258,16 +1264,17 @@ public class BugreportProgressService extends Service { } // Then set fields. - mSavedName = mTempName = name; - mPid = pid; - if (!TextUtils.isEmpty(name)) { - mInfoName.setText(name); + mSavedName = mTempName = info.name; + mId = info.id; + mPid = info.pid; + if (!TextUtils.isEmpty(info.name)) { + mInfoName.setText(info.name); } - if (!TextUtils.isEmpty(title)) { - mInfoTitle.setText(title); + if (!TextUtils.isEmpty(info.title)) { + mInfoTitle.setText(info.title); } - if (!TextUtils.isEmpty(description)) { - mInfoDescription.setText(description); + if (!TextUtils.isEmpty(info.description)) { + mInfoDescription.setText(info.description); } // And finally display it. @@ -1290,7 +1297,7 @@ public class BugreportProgressService extends Service { final String title = mInfoTitle.getText().toString(); final String description = mInfoDescription.getText().toString(); - updateBugreportInfo(mPid, name, title, description); + updateBugreportInfo(mId, name, title, description); mDialog.dismiss(); } }); @@ -1328,7 +1335,7 @@ public class BugreportProgressService extends Service { // Must update system property for the cases where dumpstate finishes // while the user is still entering other fields (like title or // description) - setBugreportNameProperty(mPid, name); + setBugreportNameProperty(mId, name); } /** @@ -1337,7 +1344,7 @@ public class BugreportProgressService extends Service { * <p>Once the bugreport is finished dumpstate has already generated the final files, so * changing the name would have no effect. */ - private void onBugreportFinished(int pid) { + private void onBugreportFinished(int id) { if (mInfoName != null) { mInfoName.setEnabled(false); mInfoName.setText(mSavedName); @@ -1353,6 +1360,11 @@ public class BugreportProgressService extends Service { private final Context context; /** + * Sequential, user-friendly id used to identify the bugreport. + */ + final int id; + + /** * {@code pid} of the {@code dumpstate} process generating the bugreport. */ final int pid; @@ -1426,8 +1438,9 @@ public class BugreportProgressService extends Service { /** * Constructor for tracked bugreports - typically called upon receiving BUGREPORT_STARTED. */ - BugreportInfo(Context context, int pid, String name, int max) { + BugreportInfo(Context context, int id, int pid, String name, int max) { this.context = context; + this.id = id; this.pid = pid; this.name = name; this.max = max; @@ -1437,8 +1450,8 @@ public class BugreportProgressService extends Service { * Constructor for untracked bugreports - typically called upon receiving BUGREPORT_FINISHED * without a previous call to BUGREPORT_STARTED. */ - BugreportInfo(Context context, int pid) { - this(context, pid, null, 0); + BugreportInfo(Context context, int id) { + this(context, id, id, null, 0); this.finished = true; } @@ -1494,7 +1507,7 @@ public class BugreportProgressService extends Service { @Override public String toString() { final float percent = ((float) progress * 100 / max); - return "pid: " + pid + ", name: " + name + ", finished: " + finished + return "id: " + id + ", pid: " + pid + ", name: " + name + ", finished: " + finished + "\n\ttitle: " + title + "\n\tdescription: " + description + "\n\tfile: " + bugreportFile + "\n\tscreenshots: " + screenshotFiles + "\n\tprogress: " + progress + "/" + max + "(" + percent + ")" @@ -1506,6 +1519,7 @@ public class BugreportProgressService extends Service { // Parcelable contract protected BugreportInfo(Parcel in) { context = null; + id = in.readInt(); pid = in.readInt(); name = in.readString(); title = in.readString(); @@ -1527,6 +1541,7 @@ public class BugreportProgressService extends Service { @Override public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(id); dest.writeInt(pid); dest.writeString(name); dest.writeString(title); diff --git a/packages/SystemUI/res/layout/qs_paged_tile_layout.xml b/packages/SystemUI/res/layout/qs_paged_tile_layout.xml index 127bddd71c43..c23c745931c6 100644 --- a/packages/SystemUI/res/layout/qs_paged_tile_layout.xml +++ b/packages/SystemUI/res/layout/qs_paged_tile_layout.xml @@ -20,11 +20,31 @@ android:layout_width="match_parent" android:layout_height="wrap_content"> - <com.android.systemui.qs.PageIndicator - android:id="@+id/page_indicator" - android:layout_width="match_parent" + <FrameLayout + android:id="@+id/page_decor" + android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_gravity="center_horizontal|bottom" - android:gravity="center" /> + android:layout_gravity="bottom"> + + <com.android.systemui.qs.PageIndicator + android:id="@+id/page_indicator" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:gravity="center" /> + + <TextView + android:id="@android:id/edit" + style="@style/QSBorderlessButton" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="end" + android:minWidth="88dp" + android:textAppearance="@style/TextAppearance.QS.DetailButton" + android:textColor="#4DFFFFFF" + android:focusable="true" + android:text="@string/qs_edit" /> + + </FrameLayout> </com.android.systemui.qs.PagedTileLayout> diff --git a/packages/SystemUI/res/layout/qs_panel.xml b/packages/SystemUI/res/layout/qs_panel.xml index bb37b83af336..45236a075e72 100644 --- a/packages/SystemUI/res/layout/qs_panel.xml +++ b/packages/SystemUI/res/layout/qs_panel.xml @@ -18,13 +18,16 @@ android:id="@+id/quick_settings_container" android:layout_width="match_parent" android:layout_height="wrap_content" - android:background="@drawable/qs_background_primary" - android:paddingBottom="8dp" - android:elevation="2dp"> + android:background="@drawable/qs_background_primary"> <com.android.systemui.qs.QSPanel android:id="@+id/quick_settings_panel" android:background="#0000" + android:layout_marginTop="@dimen/status_bar_header_height" android:layout_width="match_parent" - android:layout_height="wrap_content" /> + android:layout_height="wrap_content" + android:paddingBottom="8dp" /> + + <include layout="@layout/quick_status_bar_expanded_header" /> + </com.android.systemui.qs.QSContainer> diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml index 89abe2dc2c4c..289b1d9db32c 100644 --- a/packages/SystemUI/res/layout/status_bar_expanded.xml +++ b/packages/SystemUI/res/layout/status_bar_expanded.xml @@ -39,32 +39,11 @@ android:clipToPadding="false" android:clipChildren="false"> - <com.android.systemui.statusbar.phone.ObservableScrollView - android:id="@+id/scroll_view" + <include + layout="@layout/qs_panel" android:layout_width="@dimen/notification_panel_width" - android:layout_height="match_parent" - android:layout_gravity="@integer/notification_panel_layout_gravity" - android:scrollbars="none" - android:overScrollMode="never" - android:fillViewport="true"> - <LinearLayout - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="vertical"> - <include - layout="@layout/qs_panel" - android:layout_marginTop="@dimen/status_bar_header_height_expanded" - android:layout_width="match_parent" - android:layout_height="wrap_content" /> - - <!-- A view to reserve space for the collapsed stack --> - <!-- Layout height: notification_min_height + bottom_stack_peek_amount --> - <View - android:id="@+id/reserve_notification_space" - android:layout_height="@dimen/min_stack_height" - android:layout_width="match_parent" /> - </LinearLayout> - </com.android.systemui.statusbar.phone.ObservableScrollView> + android:layout_height="wrap_content" + android:layout_gravity="@integer/notification_panel_layout_gravity" /> <com.android.systemui.statusbar.stack.NotificationStackScrollLayout android:id="@+id/notification_stack_scroller" @@ -90,12 +69,6 @@ layout="@layout/keyguard_bottom_area" android:visibility="gone" /> - <ViewStub - android:id="@+id/status_bar_header" - android:layout_width="@dimen/notification_panel_width" - android:layout_height="@dimen/status_bar_header_height" - android:layout_gravity="@integer/notification_panel_layout_gravity" /> - <com.android.systemui.statusbar.AlphaOptimizedView android:id="@+id/qs_navbar_scrim" android:layout_height="96dp" diff --git a/packages/SystemUI/res/values-sw600dp/dimens.xml b/packages/SystemUI/res/values-sw600dp/dimens.xml index c0652d8a8e8b..122413d2677a 100644 --- a/packages/SystemUI/res/values-sw600dp/dimens.xml +++ b/packages/SystemUI/res/values-sw600dp/dimens.xml @@ -95,4 +95,10 @@ <dimen name="navigation_key_padding">25dp</dimen> <dimen name="qs_expand_margin">0dp</dimen> + + <!-- The top padding for the task stack. --> + <dimen name="recents_stack_top_padding">40dp</dimen> + + <!-- The side padding for the task stack. --> + <dimen name="recents_stack_left_right_padding">64dp</dimen> </resources> diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml index a9b8df2934c7..4cd920a86733 100644 --- a/packages/SystemUI/res/values/colors.xml +++ b/packages/SystemUI/res/values/colors.xml @@ -52,8 +52,12 @@ <!-- Tint color for the content on the notification overflow card. --> <color name="keyguard_overflow_content_color">#ff686868</color> + <!-- The disabled recents task bar background color. --> + <color name="recents_task_bar_disabled_background_color">#ff676767</color> <!-- The default recents task bar background color. --> <color name="recents_task_bar_default_background_color">#ffe6e6e6</color> + <!-- The default recents task view background color. --> + <color name="recents_task_view_default_background_color">#fff3f3f3</color> <!-- The recents task bar light text color to be drawn on top of dark backgrounds. --> <color name="recents_task_bar_light_text_color">#ffeeeeee</color> <!-- The recents task bar dark text color to be drawn on top of light backgrounds. --> diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index a6ba8b59303d..72421a3070ae 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -165,9 +165,6 @@ <!-- The animation duration for entering and exiting the history. --> <integer name="recents_history_transition_duration">250</integer> - <!-- The minimum alpha for the dim applied to cards that go deeper into the stack. --> - <integer name="recents_max_task_stack_view_dim">96</integer> - <!-- The delay to enforce between each alt-tab key press. --> <integer name="recents_alt_tab_key_delay">200</integer> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 6702cefeb3e8..f3b91995cbd2 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -249,15 +249,15 @@ <!-- The height of the search bar space. --> <dimen name="recents_search_bar_space_height">64dp</dimen> - <!-- The side padding for the task stack as a percentage of the width. --> - <item name="recents_stack_width_padding_percentage" format="float" type="dimen">0.03333</item> - <!-- The overscroll percentage allowed on the stack. --> <item name="recents_stack_overscroll_percentage" format="float" type="dimen">0.0875</item> - <!-- The top offset for the task stack. --> + <!-- The top padding for the task stack. --> <dimen name="recents_stack_top_padding">16dp</dimen> + <!-- The side padding for the task stack. --> + <dimen name="recents_stack_left_right_padding">16dp</dimen> + <!-- The dimesnsions of the dismiss all recents button. --> <dimen name="recents_dismiss_all_button_size">48dp</dimen> @@ -274,7 +274,10 @@ <dimen name="recents_stack_overscroll">24dp</dimen> <!-- The size of the peek area at the top of the stack. --> - <dimen name="recents_layout_focused_peek_size">@dimen/recents_history_button_height</dimen> + <dimen name="recents_layout_focused_top_peek_size">@dimen/recents_history_button_height</dimen> + + <!-- The size of the peek area at the bottom of the stack. --> + <dimen name="recents_layout_focused_bottom_peek_size">@dimen/recents_history_button_height</dimen> <!-- The height of the history button. --> <dimen name="recents_history_button_height">48dp</dimen> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 6ff9be14b3ce..5e8c12310bb0 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -714,6 +714,8 @@ <string name="recents_search_bar_label">search</string> <!-- Recents: Launch error string. [CHAR LIMIT=NONE] --> <string name="recents_launch_error_message">Could not start <xliff:g id="app" example="Calendar">%s</xliff:g>.</string> + <!-- Recents: Launch disabled string. [CHAR LIMIT=NONE] --> + <string name="recents_launch_disabled_message"><xliff:g id="app" example="Calendar">%s</xliff:g> is disabled in safe-mode.</string> <!-- Recents: Show history string. [CHAR LIMIT=NONE] --> <string name="recents_history_button_label">History</string> <!-- Recents: History clear all string. [CHAR LIMIT=NONE] --> @@ -1174,15 +1176,10 @@ <!-- Option to use new paging layout in quick settings [CHAR LIMIT=60] --> <string name="qs_paging" translatable="false">Use the new Quick Settings</string> - <!-- Toggles fast-toggling recents via the recents button. DO NOT TRANSLATE --> - <string name="overview_fast_toggle_via_button">Enable fast toggle</string> + <!-- Disables fast-toggling recents via the recents button. DO NOT TRANSLATE --> + <string name="overview_disable_fast_toggle_via_button">Disable fast toggle</string> <!-- Description for the toggle for fast-toggling recents via the recents button. DO NOT TRANSLATE --> - <string name="overview_fast_toggle_via_button_desc">Enable launch timeout while paging</string> - - <!-- Toggle to set the initial scroll state to be paging or stack. DO NOT TRANSLATE --> - <string name="overview_initial_state_paging">Initialize to paging</string> - <!-- Description for the toggle to set the initial scroll state to be paging or stack. DO NOT TRANSLATE --> - <string name="overview_initial_state_paging_desc">Determines whether Overview will initially be in a stacked or paged state</string> + <string name="overview_disable_fast_toggle_via_button_desc">Disable launch timeout while paging</string> <!-- Toggle to enable the gesture to enter split-screen by swiping up from the Overview button. [CHAR LIMIT=60]--> <string name="overview_nav_bar_gesture">Enable split-screen swipe-up accelerator</string> @@ -1405,4 +1402,7 @@ <!-- Label for area where tiles can be dragged out of [CHAR LIMIT=60] --> <string name="drag_to_add_tiles">Drag to add tiles</string> + <!-- Button to edit the tile ordering of quick settings [CHAR LIMIT=60] --> + <string name="qs_edit">Edit</string> + </resources> diff --git a/packages/SystemUI/res/xml/tuner_prefs.xml b/packages/SystemUI/res/xml/tuner_prefs.xml index 4de4cede0faa..39281bcb0809 100644 --- a/packages/SystemUI/res/xml/tuner_prefs.xml +++ b/packages/SystemUI/res/xml/tuner_prefs.xml @@ -113,14 +113,9 @@ android:title="@string/overview" > <com.android.systemui.tuner.TunerSwitch - android:key="overview_initial_state_paging" - android:title="@string/overview_initial_state_paging" - android:summary="@string/overview_initial_state_paging_desc" /> - - <com.android.systemui.tuner.TunerSwitch - android:key="overview_fast_toggle_via_button" - android:title="@string/overview_fast_toggle_via_button" - android:summary="@string/overview_fast_toggle_via_button_desc" /> + android:key="overview_disable_fast_toggle_via_button" + android:title="@string/overview_disable_fast_toggle_via_button" + android:summary="@string/overview_disable_fast_toggle_via_button_desc" /> <com.android.systemui.tuner.TunerSwitch android:key="overview_nav_bar_gesture" diff --git a/packages/SystemUI/src/com/android/systemui/BatteryMeterDrawable.java b/packages/SystemUI/src/com/android/systemui/BatteryMeterDrawable.java index 454d1ceb4b04..4845425c233e 100755 --- a/packages/SystemUI/src/com/android/systemui/BatteryMeterDrawable.java +++ b/packages/SystemUI/src/com/android/systemui/BatteryMeterDrawable.java @@ -62,6 +62,7 @@ public class BatteryMeterDrawable extends Drawable implements DemoMode, mPlusPaint; private float mTextHeight, mWarningTextHeight; private int mIconTint = Color.WHITE; + private float mOldDarkIntensity = 0f; private int mHeight; private int mWidth; @@ -295,6 +296,9 @@ public class BatteryMeterDrawable extends Drawable implements DemoMode, } public void setDarkIntensity(float darkIntensity) { + if (darkIntensity == mOldDarkIntensity) { + return; + } int backgroundColor = getBackgroundColor(darkIntensity); int fillColor = getFillColor(darkIntensity); mIconTint = fillColor; @@ -302,6 +306,7 @@ public class BatteryMeterDrawable extends Drawable implements DemoMode, mBoltPaint.setColor(fillColor); mChargeColor = fillColor; invalidateSelf(); + mOldDarkIntensity = darkIntensity; } private int getBackgroundColor(float darkIntensity) { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 958572fd4fd5..90d56f78697e 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -475,6 +475,23 @@ public class KeyguardViewMediator extends SystemUI { break; } } + + @Override + public void onFingerprintAuthFailed() { + final int currentUser = KeyguardUpdateMonitor.getCurrentUser(); + if (mLockPatternUtils.isSecure(currentUser)) { + mLockPatternUtils.getDevicePolicyManager().reportFailedFingerprintAttempt( + currentUser); + } + } + + @Override + public void onFingerprintAuthenticated(int userId) { + if (mLockPatternUtils.isSecure(userId)) { + mLockPatternUtils.getDevicePolicyManager().reportSuccessfulFingerprintAttempt( + userId); + } + } }; ViewMediatorCallback mViewMediatorCallback = new ViewMediatorCallback() { @@ -1370,8 +1387,9 @@ public class KeyguardViewMediator extends SystemUI { * @see #KEYGUARD_DONE */ private void handleKeyguardDone(boolean authenticated) { - if (mLockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser())) { - mLockPatternUtils.getDevicePolicyManager().reportKeyguardDismissed(); + final int currentUser = KeyguardUpdateMonitor.getCurrentUser(); + if (mLockPatternUtils.isSecure(currentUser)) { + mLockPatternUtils.getDevicePolicyManager().reportKeyguardDismissed(currentUser); } if (DEBUG) Log.d(TAG, "handleKeyguardDone"); synchronized (this) { @@ -1484,8 +1502,9 @@ public class KeyguardViewMediator extends SystemUI { * @see #SHOW */ private void handleShow(Bundle options) { - if (mLockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser())) { - mLockPatternUtils.getDevicePolicyManager().reportKeyguardSecured(); + final int currentUser = KeyguardUpdateMonitor.getCurrentUser(); + if (mLockPatternUtils.isSecure(currentUser)) { + mLockPatternUtils.getDevicePolicyManager().reportKeyguardSecured(currentUser); } synchronized (KeyguardViewMediator.this) { if (!mSystemReady) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java index d72336739886..8e9857d27f74 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java +++ b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java @@ -6,7 +6,6 @@ import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; - import com.android.internal.widget.PagerAdapter; import com.android.internal.widget.ViewPager; import com.android.systemui.R; @@ -27,6 +26,7 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { private PageIndicator mPageIndicator; private int mNumPages; + private View mDecorGroup; public PagedTileLayout(Context context, AttributeSet attrs) { super(context, attrs); @@ -55,7 +55,8 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { protected void onFinishInflate() { super.onFinishInflate(); mPageIndicator = (PageIndicator) findViewById(R.id.page_indicator); - ((LayoutParams) mPageIndicator.getLayoutParams()).isDecor = true; + mDecorGroup = findViewById(R.id.page_decor); + ((LayoutParams) mDecorGroup.getLayoutParams()).isDecor = true; mPages.add((TilePage) LayoutInflater.from(mContext) .inflate(R.layout.qs_paged_page, this, false)); @@ -137,7 +138,7 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { maxHeight = height; } } - setMeasuredDimension(getMeasuredWidth(), maxHeight + mPageIndicator.getMeasuredHeight()); + setMeasuredDimension(getMeasuredWidth(), maxHeight + mDecorGroup.getMeasuredHeight()); } private final Runnable mDistribute = new Runnable() { diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainer.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainer.java index cfe8d07dd14b..32eeb0787113 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSContainer.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainer.java @@ -16,19 +16,38 @@ package com.android.systemui.qs; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; import android.content.Context; import android.util.AttributeSet; +import android.util.Log; +import android.view.View; +import android.view.ViewTreeObserver; import android.widget.FrameLayout; - +import com.android.systemui.Interpolators; import com.android.systemui.R; +import com.android.systemui.statusbar.phone.BaseStatusBarHeader; +import com.android.systemui.statusbar.stack.StackStateAnimator; /** - * Wrapper view with background which contains {@link QSPanel} + * Wrapper view with background which contains {@link QSPanel} and {@link BaseStatusBarHeader} + * + * Also manages animations for the QS Header and Panel. */ public class QSContainer extends FrameLayout { + private static final String TAG = "QSContainer"; + private static final boolean DEBUG = false; private int mHeightOverride = -1; private QSPanel mQSPanel; + protected BaseStatusBarHeader mHeader; + private float mQsExpansion; + private boolean mQsExpanded; + private boolean mHeaderAnimating; + private boolean mKeyguardShowing; + private boolean mStackScrollerOverscrolling; + + private long mDelay; public QSContainer(Context context, AttributeSet attrs) { super(context, attrs); @@ -38,6 +57,7 @@ public class QSContainer extends FrameLayout { protected void onFinishInflate() { super.onFinishInflate(); mQSPanel = (QSPanel) findViewById(R.id.quick_settings_panel); + mHeader = (BaseStatusBarHeader) findViewById(R.id.header); } @Override @@ -63,14 +83,132 @@ public class QSContainer extends FrameLayout { */ public int getDesiredHeight() { if (mQSPanel.isClosingDetail()) { - return mQSPanel.getGridHeight() + getPaddingTop() + getPaddingBottom(); + return mQSPanel.getGridHeight() + mHeader.getCollapsedHeight() + getPaddingBottom(); } else { return getMeasuredHeight(); } } private void updateBottom() { - int height = mHeightOverride != -1 ? mHeightOverride : getMeasuredHeight(); + int heightOverride = mHeightOverride != -1 ? mHeightOverride : getMeasuredHeight(); + int height = (int) (mQsExpansion * (heightOverride - mHeader.getCollapsedHeight())) + + mHeader.getCollapsedHeight(); setBottom(getTop() + height); } + + private void updateQsState() { + boolean expandVisually = mQsExpanded || mStackScrollerOverscrolling || mHeaderAnimating; + mQSPanel.setExpanded(mQsExpanded); + mHeader.setVisibility((mQsExpanded || !mKeyguardShowing || mHeaderAnimating) + ? View.VISIBLE + : View.INVISIBLE); + mHeader.setExpanded((mKeyguardShowing && !mHeaderAnimating) + || (mQsExpanded && !mStackScrollerOverscrolling)); + mQSPanel.setVisibility(expandVisually ? View.VISIBLE : View.INVISIBLE); + } + + public BaseStatusBarHeader getHeader() { + return mHeader; + } + + public QSPanel getQsPanel() { + return mQSPanel; + } + + public void setHeaderClickable(boolean clickable) { + if (DEBUG) Log.d(TAG, "setHeaderClickable " + clickable); + mHeader.setClickable(clickable); + } + + public void setExpanded(boolean expanded) { + if (DEBUG) Log.d(TAG, "setExpanded " + expanded); + mQsExpanded = expanded; + updateQsState(); + } + + public void setKeyguardShowing(boolean keyguardShowing) { + if (DEBUG) Log.d(TAG, "setKeyguardShowing " + keyguardShowing); + mKeyguardShowing = keyguardShowing; + updateQsState(); + } + + public void setOverscrolling(boolean stackScrollerOverscrolling) { + if (DEBUG) Log.d(TAG, "setOverscrolling " + stackScrollerOverscrolling); + mStackScrollerOverscrolling = stackScrollerOverscrolling; + updateQsState(); + } + + public void setListening(boolean listening) { + if (DEBUG) Log.d(TAG, "setListening " + listening); + mQSPanel.setListening(listening); + mHeader.setListening(listening); + } + + public void setQsExpansion(float expansion, float headerTranslation) { + if (DEBUG) Log.d(TAG, "setQSExpansion " + expansion + " " + headerTranslation); + mQsExpansion = expansion; + final float translationScaleY = expansion - 1; + if (!mHeaderAnimating) { + setTranslationY(mKeyguardShowing ? (translationScaleY * mHeader.getHeight()) + : headerTranslation); + } + mHeader.setExpansion(mKeyguardShowing ? 1 : expansion); + mQSPanel.setTranslationY(translationScaleY * mQSPanel.getHeight()); + updateBottom(); + } + + public void animateHeaderSlidingIn(long delay) { + if (DEBUG) Log.d(TAG, "animateHeaderSlidingIn"); + // If the QS is already expanded we don't need to slide in the header as it's already + // visible. + if (!mQsExpanded) { + mHeaderAnimating = true; + mDelay = delay; + getViewTreeObserver().addOnPreDrawListener(mStartHeaderSlidingIn); + } + } + + public void animateHeaderSlidingOut() { + if (DEBUG) Log.d(TAG, "animateHeaderSlidingOut"); + mHeaderAnimating = true; + animate().y(-mHeader.getHeight()) + .setStartDelay(0) + .setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD) + .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) + .setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + animate().setListener(null); + mHeaderAnimating = false; + updateQsState(); + } + }) + .start(); + } + + private final ViewTreeObserver.OnPreDrawListener mStartHeaderSlidingIn + = new ViewTreeObserver.OnPreDrawListener() { + @Override + public boolean onPreDraw() { + getViewTreeObserver().removeOnPreDrawListener(this); + animate() + .translationY(0f) + .setStartDelay(mDelay) + .setDuration(StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE) + .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) + .setListener(mAnimateHeaderSlidingInListener) + .start(); + setY(-mHeader.getHeight()); + return true; + } + }; + + private final Animator.AnimatorListener mAnimateHeaderSlidingInListener + = new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mHeaderAnimating = false; + updateQsState(); + } + }; } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java index 1961860d95f6..4ffa527c6a42 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java @@ -117,6 +117,17 @@ public class QSPanel extends FrameLayout implements Tunable { mTileLayout = (QSTileLayout) LayoutInflater.from(mContext).inflate( R.layout.qs_paged_tile_layout, mQsContainer, false); mQsContainer.addView((View) mTileLayout); + findViewById(android.R.id.edit).setOnClickListener(new OnClickListener() { + @Override + public void onClick(final View v) { + mHost.startRunnableDismissingKeyguard(new Runnable() { + @Override + public void run() { + showEdit(v); + } + }); + } + }); mFooter = new QSFooter(this, context); mQsContainer.addView(mFooter.getView()); @@ -369,19 +380,7 @@ public class QSPanel extends FrameLayout implements Tunable { final View.OnLongClickListener longClick = new View.OnLongClickListener() { @Override public boolean onLongClick(View v) { - if (mCustomizePanel != null) { - if (!mCustomizePanel.isCustomizing()) { - int[] loc = new int[2]; - getLocationInWindow(loc); - int x = r.tileView.getLeft() + r.tileView.getWidth() / 2 + loc[0]; - int y = r.tileView.getTop() + mTileLayout.getOffsetTop(r) - + r.tileView.getHeight() / 2 + loc[1]; - mCustomizePanel.show(x, y); - } - } else { - r.tile.longClick(); - } - return true; + return false; } }; r.tileView.init(click, longClick); @@ -395,6 +394,25 @@ public class QSPanel extends FrameLayout implements Tunable { } } + + private void showEdit(final View v) { + v.post(new Runnable() { + @Override + public void run() { + if (mCustomizePanel != null) { + if (!mCustomizePanel.isCustomizing()) { + int[] loc = new int[2]; + v.getLocationInWindow(loc); + int x = loc[0]; + int y = loc[1]; + mCustomizePanel.show(x, y); + } + } + + } + }); + } + protected void onTileClick(QSTile<?> tile) { tile.click(); } diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsDebugFlags.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsDebugFlags.java index fc14758af0f4..711d834cc07b 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/RecentsDebugFlags.java +++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsDebugFlags.java @@ -52,11 +52,9 @@ public class RecentsDebugFlags implements TunerService.Tunable { public static final int MockTaskGroupsTaskCount = 12; } - private static final String KEY_FAST_TOGGLE = "overview_fast_toggle_via_button"; - private static final String KEY_INITIAL_STATE_PAGING = "overview_initial_state_paging"; + private static final String KEY_DISABLE_FAST_TOGGLE = "overview_disable_fast_toggle_via_button"; - private boolean mFastToggleRecents; - private boolean mInitialStatePaging; + private boolean mDisableFastToggleRecents; /** * We read the prefs once when we start the activity, then update them as the tuner changes @@ -65,7 +63,7 @@ public class RecentsDebugFlags implements TunerService.Tunable { public RecentsDebugFlags(Context context) { // Register all our flags, this will also call onTuningChanged() for each key, which will // initialize the current state of each flag - TunerService.get(context).addTunable(this, KEY_FAST_TOGGLE, KEY_INITIAL_STATE_PAGING); + TunerService.get(context).addTunable(this, KEY_DISABLE_FAST_TOGGLE); } /** @@ -74,32 +72,21 @@ public class RecentsDebugFlags implements TunerService.Tunable { public boolean isFastToggleRecentsEnabled() { // These checks EnableFastToggleTimeoutOverride SystemServicesProxy ssp = Recents.getSystemServices(); - if (ssp.hasFreeformWorkspaceSupport() || ssp.hasDockedTask() || - ssp.isTouchExplorationEnabled()) { + if (mDisableFastToggleRecents || ssp.hasFreeformWorkspaceSupport() || ssp.hasDockedTask() + || ssp.isTouchExplorationEnabled()) { return false; } if (Static.EnableFastToggleTimeoutOverride) { return true; } - return mFastToggleRecents; - } - - /** - * @return whether the initial stack state is paging. - */ - public boolean isInitialStatePaging() { - return mInitialStatePaging; + return true; } @Override public void onTuningChanged(String key, String newValue) { switch (key) { - case KEY_FAST_TOGGLE: - mFastToggleRecents = (newValue != null) && - (Integer.parseInt(newValue) != 0); - break; - case KEY_INITIAL_STATE_PAGING: - mInitialStatePaging = (newValue != null) && + case KEY_DISABLE_FAST_TOGGLE: + mDisableFastToggleRecents = (newValue != null) && (Integer.parseInt(newValue) != 0); break; } diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java index 9da5c2bd3f02..e0efaa5ae38d 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java +++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java @@ -837,11 +837,13 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener * Draws the header of a task used for the window animation into a bitmap. */ private Bitmap drawThumbnailTransitionBitmap(Task toTask, TaskViewTransform toTransform) { + SystemServicesProxy ssp = Recents.getSystemServices(); if (toTransform != null && toTask.key != null) { Bitmap thumbnail; synchronized (mHeaderBarLock) { int toHeaderWidth = (int) toTransform.rect.width(); int toHeaderHeight = (int) (mHeaderBar.getMeasuredHeight() * toTransform.scale); + boolean disabledInSafeMode = !toTask.isSystemApp && ssp.isInSafeMode(); mHeaderBar.onTaskViewSizeChanged((int) toTransform.rect.width(), (int) toTransform.rect.height()); thumbnail = Bitmap.createBitmap(toHeaderWidth, toHeaderHeight, @@ -851,7 +853,8 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener } else { Canvas c = new Canvas(thumbnail); c.scale(toTransform.scale, toTransform.scale); - mHeaderBar.rebindToTask(toTask, false /* touchExplorationEnabled */); + mHeaderBar.rebindToTask(toTask, false /* touchExplorationEnabled */, + disabledInSafeMode); mHeaderBar.draw(c); c.setBitmap(null); } diff --git a/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryView.java b/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryView.java index 843adc15c54a..3c4adb23e77a 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryView.java @@ -186,6 +186,7 @@ public class RecentsHistoryView extends LinearLayout mRecyclerView = (RecyclerView) findViewById(R.id.list); mRecyclerView.setAdapter(mAdapter); mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext())); + mRecyclerView.getItemAnimator().setRemoveDuration(100); ItemTouchHelper touchHelper = new ItemTouchHelper(mItemTouchHandler); touchHelper.attachToRecyclerView(mRecyclerView); } diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/DozeTrigger.java b/packages/SystemUI/src/com/android/systemui/recents/misc/DozeTrigger.java index 244c0df30da4..95aa10f4876e 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/misc/DozeTrigger.java +++ b/packages/SystemUI/src/com/android/systemui/recents/misc/DozeTrigger.java @@ -17,6 +17,7 @@ package com.android.systemui.recents.misc; import android.os.Handler; +import android.view.ViewDebug; /** * A dozer is a class that fires a trigger after it falls asleep. @@ -26,8 +27,11 @@ public class DozeTrigger { Handler mHandler; + @ViewDebug.ExportedProperty(category="recents") boolean mIsDozing; + @ViewDebug.ExportedProperty(category="recents") boolean mHasTriggered; + @ViewDebug.ExportedProperty(category="recents") int mDozeDurationMilliseconds; Runnable mOnSleepRunnable; 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 22ab79430cd2..8b4474f1cea5 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java +++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java @@ -112,6 +112,8 @@ public class SystemServicesProxy { Display mDisplay; String mRecentsPackage; ComponentName mAssistComponent; + + boolean mIsSafeMode; boolean mHasFreeformWorkspaceSupport; Bitmap mDummyIcon; @@ -137,6 +139,7 @@ public class SystemServicesProxy { mPm.hasSystemFeature(PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT) || Settings.Global.getInt(context.getContentResolver(), DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT, 0) != 0; + mIsSafeMode = mPm.isSafeMode(); // Get the dummy thumbnail width/heights Resources res = context.getResources(); @@ -187,7 +190,8 @@ public class SystemServicesProxy { rti.firstActiveTime = rti.lastActiveTime = i; if (i % 2 == 0) { rti.taskDescription = new ActivityManager.TaskDescription(description, - Bitmap.createBitmap(mDummyIcon), + Bitmap.createBitmap(mDummyIcon), null, + 0xFF000000 | (0xFFFFFF & new Random().nextInt()), 0xFF000000 | (0xFFFFFF & new Random().nextInt())); } else { rti.taskDescription = new ActivityManager.TaskDescription(); @@ -260,6 +264,13 @@ public class SystemServicesProxy { return mHasFreeformWorkspaceSupport; } + /** + * Returns whether this device is in the safe mode. + */ + public boolean isInSafeMode() { + return mIsSafeMode; + } + /** Returns whether the recents is currently running */ public boolean isRecentsTopMost(ActivityManager.RunningTaskInfo topTask, MutableBoolean isHomeTopMost) { diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java b/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java index 52043f400bd7..e86b92dd39b5 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java +++ b/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java @@ -17,6 +17,8 @@ package com.android.systemui.recents.misc; import android.animation.Animator; +import android.animation.AnimatorSet; +import android.annotation.FloatRange; import android.graphics.Color; import android.graphics.Rect; import android.graphics.RectF; @@ -30,6 +32,7 @@ import android.view.ViewParent; import com.android.systemui.recents.model.Task; import com.android.systemui.recents.views.TaskViewTransform; +import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -102,6 +105,46 @@ public class Utilities { return setOut; } + /** + * @return the clamped {@param value} between the provided {@param min} and {@param max}. + */ + public static float clamp(float value, float min, float max) { + return Math.max(min, Math.min(max, value)); + } + + /** + * @return the clamped {@param value} between the provided {@param min} and {@param max}. + */ + public static int clamp(int value, int min, int max) { + return Math.max(min, Math.min(max, value)); + } + + /** + * @return the clamped {@param value} between 0 and 1. + */ + public static float clamp01(float value) { + return Math.max(0f, Math.min(1f, value)); + } + + /** + * Scales the {@param value} to be proportionally between the {@param min} and + * {@param max} values. + * + * @param value must be between 0 and 1 + */ + public static float mapRange(@FloatRange(from=0.0,to=1.0) float value, float min, float max) { + return min + (value * (max - min)); + } + + /** + * Scales the {@param value} proportionally from {@param min} and {@param max} to 0 and 1. + * + * @param value must be between {@param min} and {@param max} + */ + public static float unmapRange(float value, float min, float max) { + return (value - min) / (max - min); + } + /** Scales a rect about its centroid */ public static void scaleRectAboutCenter(RectF r, float scale) { if (scale != 1.0f) { @@ -154,12 +197,25 @@ public class Utilities { */ public static void cancelAnimationWithoutCallbacks(Animator animator) { if (animator != null) { - animator.removeAllListeners(); + removeAnimationListenersRecursive(animator); animator.cancel(); } } /** + * Recursively removes all the listeners of all children of this animator + */ + public static void removeAnimationListenersRecursive(Animator animator) { + if (animator instanceof AnimatorSet) { + ArrayList<Animator> animators = ((AnimatorSet) animator).getChildAnimations(); + for (int i = animators.size() - 1; i >= 0; i--) { + removeAnimationListenersRecursive(animators.get(i)); + } + } + animator.removeAllListeners(); + } + + /** * Updates {@param transforms} to be the same size as {@param tasks}. */ public static void matchTaskListSize(List<Task> tasks, List<TaskViewTransform> transforms) { 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 c51aa7ce38c9..016e6d30843d 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java +++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java @@ -18,6 +18,7 @@ package com.android.systemui.recents.model; import android.app.ActivityManager; import android.content.Context; +import android.content.pm.ApplicationInfo; import android.content.pm.UserInfo; import android.content.res.Resources; import android.graphics.Bitmap; @@ -188,11 +189,15 @@ public class RecentsTaskLoadPlan { : null; Bitmap thumbnail = loader.getAndUpdateThumbnail(taskKey, false); int activityColor = loader.getActivityPrimaryColor(t.taskDescription); + int backgroundColor = loader.getActivityBackgroundColor(t.taskDescription); + boolean isSystemApp = (loader.getAndUpdateActivityInfo(taskKey).applicationInfo.flags + & ApplicationInfo.FLAG_SYSTEM) != 0; // Add the task to the stack Task task = new Task(taskKey, t.affiliatedTaskId, t.affiliatedTaskColor, icon, thumbnail, title, contentDescription, dismissDescription, activityColor, - !isStackTask, isLaunchTarget, t.bounds, t.taskDescription); + backgroundColor, !isStackTask, isLaunchTarget, isSystemApp, t.bounds, + t.taskDescription); allTasks.add(task); affiliatedTaskCounts.put(taskKey.id, affiliatedTaskCounts.get(taskKey.id, 0) + 1); 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 26130abc138b..5e1af1280400 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java +++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java @@ -264,13 +264,16 @@ public class RecentsTaskLoader { private int mNumVisibleThumbnailsLoaded; int mDefaultTaskBarBackgroundColor; + int mDefaultTaskViewBackgroundColor; BitmapDrawable mDefaultIcon; Bitmap mDefaultThumbnail; public RecentsTaskLoader(Context context) { Resources res = context.getResources(); mDefaultTaskBarBackgroundColor = - res.getColor(R.color.recents_task_bar_default_background_color); + context.getColor(R.color.recents_task_bar_default_background_color); + mDefaultTaskViewBackgroundColor = + context.getColor(R.color.recents_task_view_default_background_color); mMaxThumbnailCacheSize = res.getInteger(R.integer.config_recents_max_thumbnail_count); mMaxIconCacheSize = res.getInteger(R.integer.config_recents_max_icon_count); int iconCacheSize = RecentsDebugFlags.Static.DisableBackgroundCache ? 1 : @@ -556,10 +559,20 @@ public class RecentsTaskLoader { } /** + * Returns the task's background color if possible. + */ + int getActivityBackgroundColor(ActivityManager.TaskDescription td) { + if (td != null && td.getBackgroundColor() != 0) { + return td.getBackgroundColor(); + } + return mDefaultTaskViewBackgroundColor; + } + + /** * Returns the activity info for the given task key, retrieving one from the system if the * task key is expired. */ - private ActivityInfo getAndUpdateActivityInfo(Task.TaskKey taskKey) { + ActivityInfo getAndUpdateActivityInfo(Task.TaskKey taskKey) { SystemServicesProxy ssp = Recents.getSystemServices(); ComponentName cn = taskKey.getComponent(); ActivityInfo activityInfo = mActivityInfoCache.get(cn); diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java index 1c277d5a3d92..8ed6dd78a357 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java +++ b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java @@ -23,6 +23,7 @@ import android.graphics.Bitmap; import android.graphics.Color; import android.graphics.Rect; import android.graphics.drawable.Drawable; +import android.view.ViewDebug; import com.android.systemui.recents.Recents; import com.android.systemui.recents.misc.SystemServicesProxy; @@ -48,11 +49,17 @@ public class Task { /* The Task Key represents the unique primary key for the task */ public static class TaskKey { + @ViewDebug.ExportedProperty(category="recents") public final int id; + @ViewDebug.ExportedProperty(category="recents") public int stackId; + @ViewDebug.ExportedProperty(category="recents") public final Intent baseIntent; + @ViewDebug.ExportedProperty(category="recents") public final int userId; + @ViewDebug.ExportedProperty(category="recents") public long firstActiveTime; + @ViewDebug.ExportedProperty(category="recents") public long lastActiveTime; private int mHashCode; @@ -105,17 +112,21 @@ public class Task { } } + @ViewDebug.ExportedProperty(deepExport=true, prefix="key_") public TaskKey key; /** * The group will be computed separately from the initialization of the task */ + @ViewDebug.ExportedProperty(deepExport=true, prefix="group_") public TaskGrouping group; /** * The affiliationTaskId is the task id of the parent task or itself if it is not affiliated * with any task. */ + @ViewDebug.ExportedProperty(category="recents") public int affiliationTaskId; + @ViewDebug.ExportedProperty(category="recents") public int affiliationColor; /** @@ -124,15 +135,23 @@ public class Task { */ public Drawable icon; public Bitmap thumbnail; + @ViewDebug.ExportedProperty(category="recents") public String title; + @ViewDebug.ExportedProperty(category="recents") public String contentDescription; + @ViewDebug.ExportedProperty(category="recents") public String dismissDescription; + @ViewDebug.ExportedProperty(category="recents") public int colorPrimary; + @ViewDebug.ExportedProperty(category="recents") + public int colorBackground; + @ViewDebug.ExportedProperty(category="recents") public boolean useLightOnPrimaryColor; /** * The bounds of the task, used only if it is a freeform task. */ + @ViewDebug.ExportedProperty(category="recents") public Rect bounds; /** @@ -143,8 +162,12 @@ public class Task { /** * The state isLaunchTarget will be set for the correct task upon launching Recents. */ + @ViewDebug.ExportedProperty(category="recents") public boolean isLaunchTarget; + @ViewDebug.ExportedProperty(category="recents") public boolean isHistorical; + @ViewDebug.ExportedProperty(category="recents") + public boolean isSystemApp; private ArrayList<TaskCallbacks> mCallbacks = new ArrayList<>(); @@ -154,8 +177,8 @@ public class Task { public Task(TaskKey key, int affiliationTaskId, int affiliationColor, Drawable icon, Bitmap thumbnail, String title, String contentDescription, - String dismissDescription, int colorPrimary, boolean isHistorical, - boolean isLaunchTarget, Rect bounds, + String dismissDescription, int colorPrimary, int colorBackground, + boolean isHistorical, boolean isLaunchTarget, boolean isSystemApp, Rect bounds, ActivityManager.TaskDescription taskDescription) { boolean isInAffiliationGroup = (affiliationTaskId != key.id); boolean hasAffiliationGroupColor = isInAffiliationGroup && (affiliationColor != 0); @@ -168,12 +191,14 @@ public class Task { this.contentDescription = contentDescription; this.dismissDescription = dismissDescription; this.colorPrimary = hasAffiliationGroupColor ? affiliationColor : colorPrimary; + this.colorBackground = colorBackground; this.useLightOnPrimaryColor = Utilities.computeContrastBetweenColors(this.colorPrimary, Color.WHITE) > 3f; this.bounds = bounds; this.taskDescription = taskDescription; this.isLaunchTarget = isLaunchTarget; this.isHistorical = isHistorical; + this.isSystemApp = isSystemApp; } /** Copies the other task. */ @@ -188,10 +213,12 @@ public class Task { this.contentDescription = o.contentDescription; this.dismissDescription = o.dismissDescription; this.colorPrimary = o.colorPrimary; + this.colorBackground = o.colorBackground; this.useLightOnPrimaryColor = o.useLightOnPrimaryColor; this.bounds = o.bounds; this.isLaunchTarget = o.isLaunchTarget; this.isHistorical = o.isHistorical; + this.isSystemApp = o.isSystemApp; } /** diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/TaskKeyLruCache.java b/packages/SystemUI/src/com/android/systemui/recents/model/TaskKeyLruCache.java index 67a6a9f1d514..d433b6c00269 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/model/TaskKeyLruCache.java +++ b/packages/SystemUI/src/com/android/systemui/recents/model/TaskKeyLruCache.java @@ -96,6 +96,6 @@ public class TaskKeyLruCache<V> { /** Trims the cache to a specific size */ final void trimToSize(int cacheSize) { - mCache.resize(cacheSize); + mCache.trimToSize(cacheSize); } } diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/AnimateableViewBounds.java b/packages/SystemUI/src/com/android/systemui/recents/views/AnimateableViewBounds.java index 584209510e45..8575c0d8035e 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/AnimateableViewBounds.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/AnimateableViewBounds.java @@ -19,18 +19,28 @@ package com.android.systemui.recents.views; import android.graphics.Outline; import android.graphics.Rect; import android.view.View; +import android.view.ViewDebug; import android.view.ViewOutlineProvider; +import com.android.systemui.recents.misc.Utilities; + /* An outline provider that has a clip and outline that can be animated. */ public class AnimateableViewBounds extends ViewOutlineProvider { + private static final float MIN_ALPHA = 0.1f; + private static final float MAX_ALPHA = 0.8f; + View mSourceView; + @ViewDebug.ExportedProperty(category="recents") Rect mClipRect = new Rect(); + @ViewDebug.ExportedProperty(category="recents") Rect mClipBounds = new Rect(); + @ViewDebug.ExportedProperty(category="recents") Rect mLastClipBounds = new Rect(); + @ViewDebug.ExportedProperty(category="recents") int mCornerRadius; + @ViewDebug.ExportedProperty(category="recents") float mAlpha = 1f; - final float mMinAlpha = 0.25f; public AnimateableViewBounds(View source, int cornerRadius) { mSourceView = source; @@ -47,7 +57,7 @@ public class AnimateableViewBounds extends ViewOutlineProvider { @Override public void getOutline(View view, Outline outline) { - outline.setAlpha(mMinAlpha + mAlpha / (1f - mMinAlpha)); + outline.setAlpha(Utilities.mapRange(mAlpha, MIN_ALPHA, MAX_ALPHA)); if (mCornerRadius > 0) { outline.setRoundRect(mClipRect.left, mClipRect.top, mSourceView.getWidth() - mClipRect.right, @@ -60,7 +70,9 @@ public class AnimateableViewBounds extends ViewOutlineProvider { } } - /** Sets the view outline alpha. */ + /** + * Sets the view outline alpha. + */ void setAlpha(float alpha) { if (Float.compare(alpha, mAlpha) != 0) { mAlpha = alpha; diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/AnimationProps.java b/packages/SystemUI/src/com/android/systemui/recents/views/AnimationProps.java index 93878c52d6de..48e137028a7f 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/AnimationProps.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/AnimationProps.java @@ -49,6 +49,8 @@ public class AnimationProps { public static final int ALPHA = 4; public static final int SCALE = 5; public static final int BOUNDS = 6; + public static final int DIM_ALPHA = 7; + public static final int FOCUS_STATE = 8; private SparseLongArray mPropStartDelay; private SparseLongArray mPropDuration; diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/FreeformWorkspaceLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/FreeformWorkspaceLayoutAlgorithm.java index 511aa3c9c001..d8a3e766e257 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/FreeformWorkspaceLayoutAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/FreeformWorkspaceLayoutAlgorithm.java @@ -152,10 +152,10 @@ public class FreeformWorkspaceLayoutAlgorithm { transformOut.scale = 1f; transformOut.alpha = 1f; transformOut.translationZ = stackLayout.mMaxTranslationZ; + transformOut.dimAlpha = 0f; transformOut.rect.set(ffRect); transformOut.rect.offset(stackLayout.mFreeformRect.left, stackLayout.mFreeformRect.top); transformOut.visible = true; - transformOut.p = 1f; return transformOut; } return null; diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java index 42aaa9782712..c4db48552377 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java @@ -16,6 +16,8 @@ package com.android.systemui.recents.views; +import static android.app.ActivityManager.StackId.INVALID_STACK_ID; + import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; @@ -32,6 +34,7 @@ import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; +import android.view.ViewDebug; import android.view.ViewOutlineProvider; import android.view.ViewPropertyAnimator; import android.view.WindowInsets; @@ -79,8 +82,6 @@ import com.android.systemui.statusbar.FlingAnimationUtils; import java.util.ArrayList; import java.util.List; -import static android.app.ActivityManager.StackId.INVALID_STACK_ID; - /** * This view is the the top level layout that contains TaskStacks (which are laid out according * to their SpaceNode bounds. @@ -103,6 +104,8 @@ public class RecentsView extends FrameLayout { private boolean mAwaitingFirstLayout = true; private boolean mLastTaskLaunchedWasFreeform; + + @ViewDebug.ExportedProperty(category="recents") private Rect mSystemInsets = new Rect(); private int mDividerSize; @@ -110,6 +113,7 @@ public class RecentsView extends FrameLayout { private Animator mBackgroundScrimAnimator; private RecentsTransitionHelper mTransitionHelper; + @ViewDebug.ExportedProperty(deepExport=true, prefix="touch_") private RecentsViewTouchHandler mTouchHandler; private final FlingAnimationUtils mFlingAnimationUtils; diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewTouchHandler.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewTouchHandler.java index 346ce167cd17..016d9370364d 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewTouchHandler.java @@ -20,6 +20,7 @@ import android.content.res.Configuration; import android.graphics.Point; import android.view.MotionEvent; import android.view.ViewConfiguration; +import android.view.ViewDebug; import com.android.systemui.recents.Recents; import com.android.systemui.recents.RecentsConfiguration; @@ -61,12 +62,18 @@ public class RecentsViewTouchHandler { private RecentsView mRv; + @ViewDebug.ExportedProperty(deepExport=true, prefix="drag_task") private Task mDragTask; + @ViewDebug.ExportedProperty(deepExport=true, prefix="drag_task_view_") private TaskView mTaskView; + @ViewDebug.ExportedProperty(category="recents") private Point mTaskViewOffset = new Point(); + @ViewDebug.ExportedProperty(category="recents") private Point mDownPos = new Point(); + @ViewDebug.ExportedProperty(category="recents") private boolean mDragRequested; + @ViewDebug.ExportedProperty(category="recents") private boolean mIsDragging; private float mDragSlop; diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java index 19ac1e7dd44d..360a13985526 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java @@ -16,6 +16,8 @@ package com.android.systemui.recents.views; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; import android.content.Context; import android.content.res.Resources; @@ -25,13 +27,13 @@ import android.util.ArrayMap; import android.util.ArraySet; import android.util.FloatProperty; import android.util.Property; +import android.view.ViewDebug; import com.android.systemui.Interpolators; import com.android.systemui.R; import com.android.systemui.recents.Recents; import com.android.systemui.recents.RecentsActivityLaunchState; import com.android.systemui.recents.RecentsConfiguration; -import com.android.systemui.recents.RecentsDebugFlags; import com.android.systemui.recents.misc.FreePathInterpolator; import com.android.systemui.recents.misc.SystemServicesProxy; import com.android.systemui.recents.misc.Utilities; @@ -106,6 +108,27 @@ public class TaskStackLayoutAlgorithm { // The scale factor to apply to the user movement in the stack to unfocus it private static final float UNFOCUS_MULTIPLIER = 0.8f; + // The distribution of dim to apply to tasks in the stack + public static final float DIM_MAX_VALUE = 0.35f; + private static final Path UNFOCUSED_DIM_PATH = new Path(); + private static final Path FOCUSED_DIM_PATH = new Path(); + static { + // The unfocused dim interpolator peaks to 1 at 0.5 (the focused task), then slowly drops + // back to 0.5 at the front of the stack + UNFOCUSED_DIM_PATH.moveTo(0f, 0f); + UNFOCUSED_DIM_PATH.cubicTo(0f, 0.1f, 0.4f, 0.8f, 0.5f, 1f); + UNFOCUSED_DIM_PATH.cubicTo(0.6f, 1f, 0.9f, 0.6f, 1f, 0.5f); + // The focused dim interpolator peaks to 1 at 0.5 (the focused task), then drops back to 0 + // at the front of the stack + FOCUSED_DIM_PATH.moveTo(0f, 0f); + FOCUSED_DIM_PATH.cubicTo(0.1f, 0f, 0.4f, 1f, 0.5f, 1f); + FOCUSED_DIM_PATH.cubicTo(0.6f, 1f, 0.9f, 0f, 1f, 0f); + } + private static final FreePathInterpolator UNFOCUSED_DIM_INTERPOLATOR = + new FreePathInterpolator(UNFOCUSED_DIM_PATH); + private static final FreePathInterpolator FOCUSED_DIM_INTERPOLATOR = + new FreePathInterpolator(FOCUSED_DIM_PATH); + // The various focus states public static final float STATE_FOCUSED = 1f; public static final float STATE_UNFOCUSED = 0f; @@ -216,15 +239,20 @@ public class TaskStackLayoutAlgorithm { private TaskStackLayoutAlgorithmCallbacks mCb; // The task bounds (untransformed) for layout. This rect is anchored at mTaskRoot. + @ViewDebug.ExportedProperty(category="recents") public Rect mTaskRect = new Rect(); // The freeform workspace bounds, inset from the top by the search bar, and is a fixed height + @ViewDebug.ExportedProperty(category="recents") public Rect mFreeformRect = new Rect(); // The stack bounds, inset from the top by the search bar, and runs to // the bottom of the screen + @ViewDebug.ExportedProperty(category="recents") public Rect mStackRect = new Rect(); // This is the current system insets + @ViewDebug.ExportedProperty(category="recents") public Rect mSystemInsets = new Rect(); // This is the bounds of the history button above the stack rect + @ViewDebug.ExportedProperty(category="recents") public Rect mHistoryButtonRect = new Rect(); // The visible ranges when the stack is focused and unfocused @@ -232,14 +260,19 @@ public class TaskStackLayoutAlgorithm { private Range mFocusedRange; // The offset from the top when scrolled to the top of the stack - private int mFocusedPeekHeight; + @ViewDebug.ExportedProperty(category="recents") + private int mFocusedTopPeekHeight; + @ViewDebug.ExportedProperty(category="recents") + private int mFocusedBottomPeekHeight; // The offset from the top of the stack to the top of the bounds when the stack is scrolled to // the end + @ViewDebug.ExportedProperty(category="recents") private int mStackTopOffset; // The offset from the bottom of the stack to the bottom of the bounds when the stack is // scrolled to the front + @ViewDebug.ExportedProperty(category="recents") private int mStackBottomOffset; // The paths defining the motion of the tasks when the stack is focused and unfocused @@ -250,27 +283,36 @@ public class TaskStackLayoutAlgorithm { // The state of the stack focus (0..1), which controls the transition of the stack from the // focused to non-focused state + @ViewDebug.ExportedProperty(category="recents") private float mFocusState; // The animator used to reset the focused state private ObjectAnimator mFocusStateAnimator; // The smallest scroll progress, at this value, the back most task will be visible + @ViewDebug.ExportedProperty(category="recents") float mMinScrollP; // The largest scroll progress, at this value, the front most task will be visible above the // navigation bar + @ViewDebug.ExportedProperty(category="recents") float mMaxScrollP; // The initial progress that the scroller is set when you first enter recents + @ViewDebug.ExportedProperty(category="recents") float mInitialScrollP; // The task progress for the front-most task in the stack + @ViewDebug.ExportedProperty(category="recents") float mFrontMostTaskP; // The last computed task counts + @ViewDebug.ExportedProperty(category="recents") int mNumStackTasks; + @ViewDebug.ExportedProperty(category="recents") int mNumFreeformTasks; // The min/max z translations + @ViewDebug.ExportedProperty(category="recents") int mMinTranslationZ; + @ViewDebug.ExportedProperty(category="recents") int mMaxTranslationZ; // Optimization, allows for quick lookup of task -> index @@ -293,7 +335,10 @@ public class TaskStackLayoutAlgorithm { mUnfocusedRange = new Range(res.getFloat(R.integer.recents_layout_unfocused_range_min), res.getFloat(R.integer.recents_layout_unfocused_range_max)); mFocusState = getDefaultFocusState(); - mFocusedPeekHeight = res.getDimensionPixelSize(R.dimen.recents_layout_focused_peek_size); + mFocusedTopPeekHeight = + res.getDimensionPixelSize(R.dimen.recents_layout_focused_top_peek_size); + mFocusedBottomPeekHeight = + res.getDimensionPixelSize(R.dimen.recents_layout_focused_bottom_peek_size); mMinTranslationZ = res.getDimensionPixelSize(R.dimen.recents_task_view_z_min); mMaxTranslationZ = res.getDimensionPixelSize(R.dimen.recents_task_view_z_max); @@ -347,19 +392,19 @@ public class TaskStackLayoutAlgorithm { // The freeform height is the visible height (not including system insets) - padding above // freeform and below stack - gap between the freeform and stack mState = state; - mStackTopOffset = mFocusedPeekHeight + heightPadding; + mStackTopOffset = mFocusedTopPeekHeight + heightPadding; mStackBottomOffset = mSystemInsets.bottom + heightPadding; state.computeRects(mFreeformRect, mStackRect, taskStackBounds, widthPadding, heightPadding, mStackBottomOffset); // The history button will take the full un-padded header space above the stack mHistoryButtonRect.set(mStackRect.left, mStackRect.top - heightPadding, - mStackRect.right, mStackRect.top + mFocusedPeekHeight); + mStackRect.right, mStackRect.top + mFocusedTopPeekHeight); // Anchor the task rect to the top-center of the non-freeform stack rect float aspect = (float) (taskStackBounds.width() - mSystemInsets.left - mSystemInsets.right) / (taskStackBounds.height() - mSystemInsets.bottom); int width = mStackRect.width(); - int minHeight = mStackRect.height() - mFocusedPeekHeight - mStackBottomOffset; + int minHeight = mStackRect.height() - mFocusedTopPeekHeight - mStackBottomOffset; int height = (int) Math.min(width / aspect, minHeight); mTaskRect.set(mStackRect.left, mStackRect.top, mStackRect.left + width, mStackRect.top + height); @@ -434,8 +479,8 @@ public class TaskStackLayoutAlgorithm { mStackRect.height(); float normX = mUnfocusedCurveInterpolator.getX(bottomOffsetPct); mMinScrollP = 0; - mMaxScrollP = Math.max(mMinScrollP, - (mNumStackTasks - 1) - Math.max(0, mUnfocusedRange.getAbsoluteX(normX))); + mMaxScrollP = Math.max(mMinScrollP, (mNumStackTasks - 1) - + Math.max(0, mUnfocusedRange.getAbsoluteX(normX))); } } @@ -451,16 +496,16 @@ public class TaskStackLayoutAlgorithm { mInitialScrollP = mMinScrollP; } else if (getDefaultFocusState() > 0f) { if (launchState.launchedFromHome) { - mInitialScrollP = Math.max(mMinScrollP, Math.min(mMaxScrollP, launchTaskIndex)); + mInitialScrollP = Utilities.clamp(launchTaskIndex, mMinScrollP, mMaxScrollP); } else { - mInitialScrollP = Math.max(mMinScrollP, Math.min(mMaxScrollP, - launchTaskIndex - 1)); + mInitialScrollP = Utilities.clamp(launchTaskIndex - 1, mMinScrollP, + mMaxScrollP); } } else { float offsetPct = (float) (mTaskRect.height() / 3) / mStackRect.height(); float normX = mUnfocusedCurveInterpolator.getX(offsetPct); - mInitialScrollP = Math.max(mMinScrollP, Math.min(mMaxScrollP, - launchTaskIndex - mUnfocusedRange.getAbsoluteX(normX))); + mInitialScrollP = Utilities.clamp(launchTaskIndex - + mUnfocusedRange.getAbsoluteX(normX), mMinScrollP, mMaxScrollP); } } } @@ -495,12 +540,7 @@ public class TaskStackLayoutAlgorithm { * Returns the default focus state. */ public float getDefaultFocusState() { - RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState(); - RecentsDebugFlags debugFlags = Recents.getDebugFlags(); - if (launchState.launchedWithAltTab || debugFlags.isInitialStatePaging()) { - return STATE_FOCUSED; - } - return STATE_UNFOCUSED; + return STATE_FOCUSED; } /** @@ -649,20 +689,9 @@ public class TaskStackLayoutAlgorithm { // Compute the focused and unfocused offset mUnfocusedRange.offset(stackScroll); - float p = mUnfocusedRange.getNormalizedX(taskProgress); - float yp = mUnfocusedCurveInterpolator.getInterpolation(p); - float unfocusedP = p; - int unFocusedY = (int) (Math.max(0f, (1f - yp)) * mStackRect.height()); + mFocusedRange.offset(stackScroll); boolean unfocusedVisible = mUnfocusedRange.isInRange(taskProgress); - int focusedY = 0; - boolean focusedVisible = true; - if (mFocusState > 0f) { - mFocusedRange.offset(stackScroll); - p = mFocusedRange.getNormalizedX(taskProgress); - yp = mFocusedCurveInterpolator.getInterpolation(p); - focusedY = (int) (Math.max(0f, (1f - yp)) * mStackRect.height()); - focusedVisible = mFocusedRange.isInRange(taskProgress); - } + boolean focusedVisible = mFocusedRange.isInRange(taskProgress); // Skip if the task is not visible if (!forceUpdate && !unfocusedVisible && !focusedVisible) { @@ -670,43 +699,48 @@ public class TaskStackLayoutAlgorithm { return; } + float unfocusedRangeX = mUnfocusedRange.getNormalizedX(taskProgress); + float focusedRangeX = mFocusedRange.getNormalizedX(taskProgress); + int x = (mStackRect.width() - mTaskRect.width()) / 2; int y; float z; - float relP; + float dimAlpha; if (!ssp.hasFreeformWorkspaceSupport() && mNumStackTasks == 1 && !ignoreSingleTaskCase) { // When there is exactly one task, then decouple the task from the stack and just move // in screen space - p = (mMinScrollP - stackScroll) / mNumStackTasks; + float tmpP = (mMinScrollP - stackScroll) / mNumStackTasks; int centerYOffset = (mStackRect.top - mTaskRect.top) + (mStackRect.height() - mTaskRect.height()) / 2; - y = centerYOffset + getYForDeltaP(p, 0); + y = centerYOffset + getYForDeltaP(tmpP, 0); z = mMaxTranslationZ; - relP = 1f; + dimAlpha = 0f; } else { // Otherwise, update the task to the stack layout - y = unFocusedY + (int) (mFocusState * (focusedY - unFocusedY)); - y += (mStackRect.top - mTaskRect.top); - z = Math.max(mMinTranslationZ, Math.min(mMaxTranslationZ, - mMinTranslationZ + (p * (mMaxTranslationZ - mMinTranslationZ)))); - if (mNumStackTasks == 1) { - relP = 1f; - } else { - relP = Math.min(mMaxScrollP, unfocusedP); - } + int unfocusedY = (int) ((1f - mUnfocusedCurveInterpolator.getInterpolation( + unfocusedRangeX)) * mStackRect.height()); + int focusedY = (int) ((1f - mFocusedCurveInterpolator.getInterpolation( + focusedRangeX)) * mStackRect.height()); + float unfocusedDim = 1f - UNFOCUSED_DIM_INTERPOLATOR.getInterpolation(unfocusedRangeX); + float focusedDim = 1f - FOCUSED_DIM_INTERPOLATOR.getInterpolation(focusedRangeX); + + y = (mStackRect.top - mTaskRect.top) + + (int) Utilities.mapRange(mFocusState, unfocusedY, focusedY); + z = Utilities.clamp01(unfocusedRangeX) * mMaxTranslationZ; + dimAlpha = Utilities.mapRange(mFocusState, unfocusedDim, focusedDim); } // Fill out the transform transformOut.scale = 1f; transformOut.alpha = 1f; transformOut.translationZ = z; + transformOut.dimAlpha = DIM_MAX_VALUE * dimAlpha; transformOut.rect.set(mTaskRect); transformOut.rect.offset(x, y); Utilities.scaleRectAboutCenter(transformOut.rect, transformOut.scale); transformOut.visible = (transformOut.rect.top < mStackRect.bottom) && (frontTransform == null || transformOut.rect.top != frontTransform.rect.top); - transformOut.p = relP; } /** @@ -750,18 +784,16 @@ public class TaskStackLayoutAlgorithm { * Creates a new path for the focused curve. */ private Path constructFocusedCurve() { - int taskBarHeight = mContext.getResources().getDimensionPixelSize( - R.dimen.recents_task_bar_height); - // Initialize the focused curve. This curve is a piecewise curve composed of several - // quadradic beziers that goes from (0,1) through (0.5, peek height offset), - // (0.667, next task offset), (0.833, bottom task offset), and (1,0). - float peekHeightPct = (float) mFocusedPeekHeight / mStackRect.height(); + // linear pieces that goes from (0,1) through (0.5, peek height offset), + // (0.5, bottom task offsets), and (1,0). + float topPeekHeightPct = (float) mFocusedTopPeekHeight / mStackRect.height(); + float bottomPeekHeightPct = (float) Math.max(mFocusedBottomPeekHeight, mStackRect.bottom - + mTaskRect.bottom) / mStackRect.height(); Path p = new Path(); p.moveTo(0f, 1f); - p.lineTo(0.5f, 1f - peekHeightPct); - p.lineTo(0.66666667f, (float) (taskBarHeight * 3) / mStackRect.height()); - p.lineTo(0.83333333f, (float) (taskBarHeight / 2) / mStackRect.height()); + p.lineTo(0.5f, 1f - topPeekHeightPct); + p.lineTo(0.5f + (0.5f / mFocusedRange.relativeMax), bottomPeekHeightPct); p.lineTo(1f, 0f); return p; } @@ -778,7 +810,7 @@ public class TaskStackLayoutAlgorithm { // there is a tangent at (0.5, peek height offset). float cpoint1X = 0.4f; float cpoint1Y = 1f; - float peekHeightPct = (float) mFocusedPeekHeight / mStackRect.height(); + float peekHeightPct = (float) mFocusedTopPeekHeight / mStackRect.height(); float slope = ((1f - peekHeightPct) - cpoint1Y) / (0.5f - cpoint1X); float b = 1f - slope * cpoint1X; float cpoint2X = 0.75f; @@ -799,10 +831,10 @@ public class TaskStackLayoutAlgorithm { return; } - float min = mUnfocusedRange.relativeMin + - mFocusState * (mFocusedRange.relativeMin - mUnfocusedRange.relativeMin); - float max = mUnfocusedRange.relativeMax + - mFocusState * (mFocusedRange.relativeMax - mUnfocusedRange.relativeMax); + float min = Utilities.mapRange(mFocusState, mUnfocusedRange.relativeMin, + mFocusedRange.relativeMin); + float max = Utilities.mapRange(mFocusState, mUnfocusedRange.relativeMax, + mFocusedRange.relativeMax); getStackTransform(min, 0f, mBackOfStackTransform, null, true /* ignoreSingleTaskCase */, true /* forceUpdate */); getStackTransform(max, 0f, mFrontOfStackTransform, null, true /* ignoreSingleTaskCase */, diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java index fb3515a26380..2195b5e7eb3d 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java @@ -20,8 +20,6 @@ import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID; import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID; import static android.app.ActivityManager.StackId.INVALID_STACK_ID; -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; import android.animation.ValueAnimator; import android.content.ComponentName; @@ -40,11 +38,10 @@ import android.util.MutableBoolean; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; +import android.view.ViewDebug; import android.view.ViewGroup; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; -import android.view.animation.Interpolator; -import android.view.animation.PathInterpolator; import android.widget.FrameLayout; import com.android.internal.logging.MetricsLogger; @@ -121,8 +118,11 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal LayoutInflater mInflater; TaskStack mStack; + @ViewDebug.ExportedProperty(deepExport=true, prefix="layout_") TaskStackLayoutAlgorithm mLayoutAlgorithm; + @ViewDebug.ExportedProperty(deepExport=true, prefix="scroller_") TaskStackViewScroller mStackScroller; + @ViewDebug.ExportedProperty(deepExport=true, prefix="touch_") TaskStackViewTouchHandler mTouchHandler; TaskStackAnimationHelper mAnimationHelper; GradientDrawable mFreeformWorkspaceBackground; @@ -134,25 +134,36 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal ArraySet<Task.TaskKey> mIgnoreTasks = new ArraySet<>(); AnimationProps mDeferredTaskViewLayoutAnimation = null; + @ViewDebug.ExportedProperty(deepExport=true, prefix="doze_") DozeTrigger mUIDozeTrigger; + @ViewDebug.ExportedProperty(deepExport=true, prefix="focused_task_") Task mFocusedTask; int mTaskCornerRadiusPx; private int mDividerSize; private int mStartTimerIndicatorDuration; + @ViewDebug.ExportedProperty(category="recents") boolean mTaskViewsClipDirty = true; + @ViewDebug.ExportedProperty(category="recents") boolean mAwaitingFirstLayout = true; + @ViewDebug.ExportedProperty(category="recents") boolean mInMeasureLayout = false; + @ViewDebug.ExportedProperty(category="recents") boolean mEnterAnimationComplete = false; + @ViewDebug.ExportedProperty(category="recents") boolean mTouchExplorationEnabled; + @ViewDebug.ExportedProperty(category="recents") boolean mScreenPinningEnabled; // The stable stack bounds are the full bounds that we were measured with from RecentsView + @ViewDebug.ExportedProperty(category="recents") private Rect mStableStackBounds = new Rect(); // The current stack bounds are dynamic and may change as the user drags and drops + @ViewDebug.ExportedProperty(category="recents") private Rect mStackBounds = new Rect(); + @ViewDebug.ExportedProperty(category="recents") private int[] mTmpVisibleRange = new int[2]; private Rect mTmpRect = new Rect(); private ArrayMap<Task.TaskKey, TaskView> mTmpTaskViewMap = new ArrayMap<>(); @@ -549,7 +560,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal tv.updateViewPropertiesToTaskTransform(transform, AnimationProps.IMMEDIATE, mRequestUpdateClippingListener); } else { - if (Float.compare(transform.p, 0f) <= 0) { + if (transform.rect.top <= mLayoutAlgorithm.mStackRect.top) { tv.updateViewPropertiesToTaskTransform( mLayoutAlgorithm.getBackOfStackTransform(), AnimationProps.IMMEDIATE, mRequestUpdateClippingListener); @@ -827,7 +838,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal boolean requestViewFocus, int timerIndicatorDuration) { // Find the next task to focus int newFocusedTaskIndex = mStack.getTaskCount() > 0 ? - Math.max(0, Math.min(mStack.getTaskCount() - 1, focusTaskIndex)) : -1; + Utilities.clamp(focusTaskIndex, 0, mStack.getTaskCount() - 1) : -1; final Task newFocusedTask = (newFocusedTaskIndex != -1) ? mStack.getStackTasks().get(newFocusedTaskIndex) : null; diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java index d1bce55c324e..b7ff8bc89ea8 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java @@ -23,6 +23,7 @@ import android.content.Context; import android.util.FloatProperty; import android.util.Log; import android.util.Property; +import android.view.ViewDebug; import android.widget.OverScroller; import com.android.systemui.Interpolators; @@ -63,6 +64,7 @@ public class TaskStackViewScroller { TaskStackLayoutAlgorithm mLayoutAlgorithm; TaskStackViewScrollerCallbacks mCb; + @ViewDebug.ExportedProperty(category="recents") float mStackScrollP; float mFlingDownScrollP; int mFlingDownY; @@ -150,8 +152,7 @@ public class TaskStackViewScroller { /** Returns the bounded stack scroll */ float getBoundedStackScroll(float scroll) { - return Math.max(mLayoutAlgorithm.mMinScrollP, - Math.min(mLayoutAlgorithm.mMaxScrollP, scroll)); + return Utilities.clamp(scroll, mLayoutAlgorithm.mMinScrollP, mLayoutAlgorithm.mMaxScrollP); } /** Returns the amount that the absolute value of how much the scroll is out of bounds. */ diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java index 5d1bb66f0dac..b94a9f7d8687 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java @@ -28,8 +28,8 @@ import android.view.MotionEvent; import android.view.VelocityTracker; import android.view.View; import android.view.ViewConfiguration; +import android.view.ViewDebug; import android.view.ViewParent; -import android.view.animation.Animation; import android.view.animation.Interpolator; import android.view.animation.PathInterpolator; @@ -44,7 +44,6 @@ import com.android.systemui.recents.events.EventBus; import com.android.systemui.recents.events.activity.HideRecentsEvent; import com.android.systemui.recents.events.ui.StackViewScrolledEvent; import com.android.systemui.recents.events.ui.TaskViewDismissedEvent; -import com.android.systemui.recents.misc.RectFEvaluator; import com.android.systemui.recents.misc.SystemServicesProxy; import com.android.systemui.recents.misc.Utilities; import com.android.systemui.recents.model.Task; @@ -69,6 +68,7 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback { FlingAnimationUtils mFlingAnimUtils; ValueAnimator mScrollFlingAnimator; + @ViewDebug.ExportedProperty(category="recents") boolean mIsScrolling; float mDownScrollP; int mDownX, mDownY; @@ -545,7 +545,8 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback { // We only really need to interpolate the bounds, progress and translation mTmpTransform.rect.set(Utilities.RECTF_EVALUATOR.evaluate(dismissFraction, fromTransform.rect, toTransform.rect)); - mTmpTransform.p = fromTransform.p + (toTransform.p - fromTransform.p) * dismissFraction; + mTmpTransform.dimAlpha = fromTransform.dimAlpha + (toTransform.dimAlpha - + fromTransform.dimAlpha) * dismissFraction; mTmpTransform.translationZ = fromTransform.translationZ + (toTransform.translationZ - fromTransform.translationZ) * dismissFraction; diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java index 850e36e73e36..972b02aa3741 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java @@ -31,12 +31,12 @@ import android.graphics.PorterDuffColorFilter; import android.graphics.Rect; import android.util.AttributeSet; import android.util.FloatProperty; -import android.util.IntProperty; import android.util.Property; import android.view.MotionEvent; import android.view.View; +import android.view.ViewDebug; import android.view.ViewOutlineProvider; -import android.view.animation.AccelerateInterpolator; +import android.widget.Toast; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.MetricsProto.MetricsEvent; @@ -79,57 +79,54 @@ public class TaskView extends FixedSizeFrameLayout implements Task.TaskCallbacks * The dim overlay is generally calculated from the task progress, but occasionally (like when * launching) needs to be animated independently of the task progress. */ - public static final Property<TaskView, Integer> DIM = - new IntProperty<TaskView>("dim") { + public static final Property<TaskView, Float> DIM_ALPHA = + new FloatProperty<TaskView>("dim") { @Override - public void setValue(TaskView tv, int dim) { - tv.setDim(dim); - } - - @Override - public Integer get(TaskView tv) { - return tv.getDim(); - } - }; - - public static final Property<TaskView, Float> TASK_PROGRESS = - new FloatProperty<TaskView>("taskProgress") { - @Override - public void setValue(TaskView tv, float p) { - tv.setTaskProgress(p); + public void setValue(TaskView tv, float dimAlpha) { + tv.setDimAlpha(dimAlpha); } @Override public Float get(TaskView tv) { - return tv.getTaskProgress(); + return tv.getDimAlpha(); } }; - float mTaskProgress; - float mMaxDimScale; - int mDimAlpha; - AccelerateInterpolator mDimInterpolator = new AccelerateInterpolator(3f); + @ViewDebug.ExportedProperty(category="recents") + float mDimAlpha; PorterDuffColorFilter mDimColorFilter = new PorterDuffColorFilter(0, PorterDuff.Mode.SRC_ATOP); Paint mDimLayerPaint = new Paint(); float mActionButtonTranslationZ; + @ViewDebug.ExportedProperty(deepExport=true, prefix="task_") Task mTask; + @ViewDebug.ExportedProperty(category="recents") boolean mTaskDataLoaded; + @ViewDebug.ExportedProperty(category="recents") boolean mClipViewInStack = true; + @ViewDebug.ExportedProperty(category="recents") boolean mTouchExplorationEnabled; + @ViewDebug.ExportedProperty(category="recents") + boolean mIsDisabledInSafeMode; + @ViewDebug.ExportedProperty(deepExport=true, prefix="view_bounds_") AnimateableViewBounds mViewBounds; private AnimatorSet mTransformAnimation; private ArrayList<Animator> mTmpAnimators = new ArrayList<>(); View mContent; + @ViewDebug.ExportedProperty(deepExport=true, prefix="thumbnail_") TaskViewThumbnail mThumbnailView; + @ViewDebug.ExportedProperty(deepExport=true, prefix="header_") TaskViewHeader mHeaderView; View mActionButtonView; TaskViewCallbacks mCb; + @ViewDebug.ExportedProperty(category="recents") Point mDownTouchPos = new Point(); + private Toast mDisabledAppToast; + public TaskView(Context context) { this(context, null); } @@ -146,7 +143,6 @@ public class TaskView extends FixedSizeFrameLayout implements Task.TaskCallbacks super(context, attrs, defStyleAttr, defStyleRes); RecentsConfiguration config = Recents.getConfiguration(); Resources res = context.getResources(); - mMaxDimScale = res.getInteger(R.integer.recents_max_task_stack_view_dim) / 255f; mViewBounds = new AnimateableViewBounds(this, res.getDimensionPixelSize( R.dimen.recents_task_view_rounded_corners_radius)); if (config.fakeShadows) { @@ -252,8 +248,8 @@ public class TaskView extends FixedSizeFrameLayout implements Task.TaskCallbacks mTmpAnimators.clear(); toTransform.applyToTaskView(this, mTmpAnimators, toAnimation, !config.fakeShadows); if (toAnimation.isImmediate()) { - if (Float.compare(getTaskProgress(), toTransform.p) != 0) { - setTaskProgress(toTransform.p); + if (Float.compare(getDimAlpha(), toTransform.dimAlpha) != 0) { + setDimAlpha(toTransform.dimAlpha); } // Manually call back to the animator listener and update callback if (toAnimation.getListener() != null) { @@ -264,9 +260,9 @@ public class TaskView extends FixedSizeFrameLayout implements Task.TaskCallbacks } } else { // Both the progress and the update are a function of the bounds movement of the task - if (Float.compare(getTaskProgress(), toTransform.p) != 0) { - ObjectAnimator anim = ObjectAnimator.ofFloat(this, TASK_PROGRESS, getTaskProgress(), - toTransform.p); + if (Float.compare(getDimAlpha(), toTransform.dimAlpha) != 0) { + ObjectAnimator anim = ObjectAnimator.ofFloat(this, DIM_ALPHA, getDimAlpha(), + toTransform.dimAlpha); mTmpAnimators.add(toAnimation.apply(AnimationProps.BOUNDS, anim)); } if (updateCallback != null) { @@ -284,7 +280,7 @@ public class TaskView extends FixedSizeFrameLayout implements Task.TaskCallbacks /** Resets this view's properties */ void resetViewProperties() { cancelTransformAnimation(); - setDim(0); + setDimAlpha(0); setVisibility(View.VISIBLE); getViewBounds().reset(); getHeaderView().reset(); @@ -359,76 +355,58 @@ public class TaskView extends FixedSizeFrameLayout implements Task.TaskCallbacks } } - /** Sets the current task progress. */ - public void setTaskProgress(float p) { - mTaskProgress = p; - mViewBounds.setAlpha(p); - updateDimFromTaskProgress(); - } - public TaskViewHeader getHeaderView() { return mHeaderView; } - /** Returns the current task progress. */ - public float getTaskProgress() { - return mTaskProgress; - } - - /** Returns the current dim. */ - public void setDim(int dim) { + /** + * Sets the current dim. + */ + public void setDimAlpha(float dimAlpha) { RecentsConfiguration config = Recents.getConfiguration(); - mDimAlpha = dim; + int dimAlphaInt = (int) (dimAlpha * 255); + mDimAlpha = dimAlpha; + mViewBounds.setAlpha(1f - (dimAlpha / TaskStackLayoutAlgorithm.DIM_MAX_VALUE)); if (config.useHardwareLayers) { // Defer setting hardware layers if we have not yet measured, or there is no dim to draw if (getMeasuredWidth() > 0 && getMeasuredHeight() > 0) { - mDimColorFilter.setColor(Color.argb(mDimAlpha, 0, 0, 0)); + mDimColorFilter.setColor(Color.argb(dimAlphaInt, 0, 0, 0)); mDimLayerPaint.setColorFilter(mDimColorFilter); mContent.setLayerType(LAYER_TYPE_HARDWARE, mDimLayerPaint); } } else { - float dimAlpha = mDimAlpha / 255.0f; mThumbnailView.setDimAlpha(dimAlpha); mHeaderView.setDimAlpha(dimAlpha); } } - /** Returns the current dim. */ - public int getDim() { + /** + * Returns the current dim. + */ + public float getDimAlpha() { return mDimAlpha; } - /** Animates the dim to the task progress. */ - void animateDimToProgress(int duration, Animator.AnimatorListener animListener) { + /** + * Animates the dim to the given value. + */ + void animateDimAlpha(float toDimAlpha, AnimationProps animation) { // Animate the dim into view as well - int toDim = getDimFromTaskProgress(); - if (toDim != getDim()) { - ObjectAnimator anim = ObjectAnimator.ofInt(this, DIM, getDim(), toDim); - anim.setDuration(duration); - if (animListener != null) { - anim.addListener(animListener); + if (Float.compare(toDimAlpha, getDimAlpha()) != 0) { + Animator anim = animation.apply(AnimationProps.DIM_ALPHA, ObjectAnimator.ofFloat(this, + DIM_ALPHA, getDimAlpha(), toDimAlpha)); + if (animation.getListener() != null) { + anim.addListener(animation.getListener()); } anim.start(); } else { - animListener.onAnimationEnd(null); + if (animation.getListener() != null) { + animation.getListener().onAnimationEnd(null); + } } } - /** Compute the dim as a function of the scale of this view. */ - int getDimFromTaskProgress() { - float x = mTaskProgress < 0 - ? 1f - : mDimInterpolator.getInterpolation(1f - mTaskProgress); - float dim = mMaxDimScale * x; - return (int) (dim * 255); - } - - /** Update the dim as a function of the scale of this view. */ - void updateDimFromTaskProgress() { - setDim(getDimFromTaskProgress()); - } - /** * Explicitly sets the focused state of this task. */ @@ -515,15 +493,18 @@ public class TaskView extends FixedSizeFrameLayout implements Task.TaskCallbacks @Override public void onPrepareLaunchTargetForEnterAnimation() { // These values will be animated in when onStartLaunchTargetEnterAnimation() is called - setDim(0); + setDimAlpha(0); mActionButtonView.setAlpha(0f); } @Override public void onStartLaunchTargetEnterAnimation(int duration, boolean screenPinningEnabled, ReferenceCountedTrigger postAnimationTrigger) { + // Un-dim the view before/while launching the target + AnimationProps animation = new AnimationProps(duration, Interpolators.ALPHA_OUT) + .setListener(postAnimationTrigger.decrementOnAnimationEnd()); postAnimationTrigger.increment(); - animateDimToProgress(duration, postAnimationTrigger.decrementOnAnimationEnd()); + animateDimAlpha(0, animation); if (screenPinningEnabled) { showActionButton(true /* fadeIn */, duration /* fadeInDuration */); @@ -533,12 +514,9 @@ public class TaskView extends FixedSizeFrameLayout implements Task.TaskCallbacks @Override public void onStartLaunchTargetLaunchAnimation(int duration, boolean screenPinningRequested, ReferenceCountedTrigger postAnimationTrigger) { - if (mDimAlpha > 0) { - ObjectAnimator anim = ObjectAnimator.ofInt(this, DIM, getDim(), 0); - anim.setDuration(duration); - anim.setInterpolator(Interpolators.ALPHA_OUT); - anim.start(); - } + // Un-dim the view before/while launching the target + AnimationProps animation = new AnimationProps(duration, Interpolators.ALPHA_OUT); + animateDimAlpha(0, animation); postAnimationTrigger.increment(); hideActionButton(true /* fadeOut */, duration, @@ -549,15 +527,17 @@ public class TaskView extends FixedSizeFrameLayout implements Task.TaskCallbacks /**** TaskCallbacks Implementation ****/ public void onTaskBound(Task t) { + SystemServicesProxy ssp = Recents.getSystemServices(); mTask = t; mTask.addCallback(this); + mIsDisabledInSafeMode = !mTask.isSystemApp && ssp.isInSafeMode(); } @Override public void onTaskDataLoaded(Task task) { // Bind each of the views to the new task data - mThumbnailView.rebindToTask(mTask); - mHeaderView.rebindToTask(mTask, mTouchExplorationEnabled); + mThumbnailView.rebindToTask(mTask, mIsDisabledInSafeMode); + mHeaderView.rebindToTask(mTask, mTouchExplorationEnabled, mIsDisabledInSafeMode); mTaskDataLoaded = true; } @@ -572,13 +552,24 @@ public class TaskView extends FixedSizeFrameLayout implements Task.TaskCallbacks @Override public void onTaskStackIdChanged() { - mHeaderView.rebindToTask(mTask, mTouchExplorationEnabled); + mHeaderView.rebindToTask(mTask, mTouchExplorationEnabled, mIsDisabledInSafeMode); } /**** View.OnClickListener Implementation ****/ @Override public void onClick(final View v) { + if (mIsDisabledInSafeMode) { + Context context = getContext(); + String msg = context.getString(R.string.recents_launch_disabled_message, mTask.title); + if (mDisabledAppToast != null) { + mDisabledAppToast.cancel(); + } + mDisabledAppToast = Toast.makeText(context, msg, Toast.LENGTH_SHORT); + mDisabledAppToast.show(); + return; + } + boolean screenPinningRequested = false; if (v == mActionButtonView) { // Reset the translation of the action button before we animate it out diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java index c91a833e943a..bb56a520fbdd 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java @@ -36,6 +36,7 @@ import android.support.v4.graphics.ColorUtils; import android.util.AttributeSet; import android.view.View; import android.view.ViewAnimationUtils; +import android.view.ViewDebug; import android.view.ViewStub; import android.widget.FrameLayout; import android.widget.ImageView; @@ -74,6 +75,8 @@ public class TaskViewHeader extends FrameLayout private Paint mHighlightPaint = new Paint(); private Paint mBackgroundPaint = new Paint(); + private int mColor; + private float mDimAlpha; public HighlightColorDrawable() { mBackgroundPaint.setColor(Color.argb(255, 0, 0, 0)); @@ -83,15 +86,19 @@ public class TaskViewHeader extends FrameLayout } public void setColorAndDim(int color, float dimAlpha) { - mBackgroundPaint.setColor(color); - - ColorUtils.colorToHSL(color, mTmpHSL); - // TODO: Consider using the saturation of the color to adjust the lightness as well - mTmpHSL[2] = Math.min(1f, - mTmpHSL[2] + HIGHLIGHT_LIGHTNESS_INCREMENT * (1.0f - dimAlpha)); - mHighlightPaint.setColor(ColorUtils.HSLToColor(mTmpHSL)); - - invalidateSelf(); + if (mColor != color || Float.compare(mDimAlpha, dimAlpha) != 0) { + mColor = color; + mDimAlpha = dimAlpha; + mBackgroundPaint.setColor(color); + + ColorUtils.colorToHSL(color, mTmpHSL); + // TODO: Consider using the saturation of the color to adjust the lightness as well + mTmpHSL[2] = Math.min(1f, + mTmpHSL[2] + HIGHLIGHT_LIGHTNESS_INCREMENT * (1.0f - dimAlpha)); + mHighlightPaint.setColor(ColorUtils.HSLToColor(mTmpHSL)); + + invalidateSelf(); + } } @Override @@ -121,6 +128,10 @@ public class TaskViewHeader extends FrameLayout public int getOpacity() { return PixelFormat.OPAQUE; } + + public int getColor() { + return mColor; + } } Task mTask; @@ -139,9 +150,11 @@ public class TaskViewHeader extends FrameLayout ProgressBar mFocusTimerIndicator; // Header drawables + @ViewDebug.ExportedProperty(category="recents") Rect mTaskViewRect = new Rect(); int mCornerRadius; int mHighlightHeight; + @ViewDebug.ExportedProperty(category="recents") float mDimAlpha; Drawable mLightDismissDrawable; Drawable mDarkDismissDrawable; @@ -153,6 +166,7 @@ public class TaskViewHeader extends FrameLayout Drawable mDarkInfoIcon; int mTaskBarViewLightTextColor; int mTaskBarViewDarkTextColor; + int mDisabledTaskBarBackgroundColor; int mMoveTaskTargetStackId = INVALID_STACK_ID; // Header background @@ -195,6 +209,8 @@ public class TaskViewHeader extends FrameLayout mDarkFullscreenIcon = context.getDrawable(R.drawable.recents_move_task_fullscreen_dark); mLightInfoIcon = context.getDrawable(R.drawable.recents_info_light); mDarkInfoIcon = context.getDrawable(R.drawable.recents_info_dark); + mDisabledTaskBarBackgroundColor = + context.getColor(R.color.recents_task_bar_disabled_background_color); // Configure the background and dim mBackground = new HighlightColorDrawable(); @@ -331,17 +347,17 @@ public class TaskViewHeader extends FrameLayout */ void setDimAlpha(float dimAlpha) { mDimAlpha = dimAlpha; - updateBackgroundColor(dimAlpha); + updateBackgroundColor(mBackground.getColor(), dimAlpha); } /** * Updates the background and highlight colors for this header. */ - private void updateBackgroundColor(float dimAlpha) { + private void updateBackgroundColor(int color, float dimAlpha) { if (mTask != null) { - mBackground.setColorAndDim(mTask.colorPrimary, dimAlpha); + mBackground.setColorAndDim(color, dimAlpha); // TODO: Consider using the saturation of the color to adjust the lightness as well - ColorUtils.colorToHSL(mTask.colorPrimary, mTmpHSL); + ColorUtils.colorToHSL(color, mTmpHSL); mTmpHSL[2] = Math.min(1f, mTmpHSL[2] + OVERLAY_LIGHTNESS_INCREMENT * (1.0f - dimAlpha)); mOverlayBackground.setColorAndDim(ColorUtils.HSLToColor(mTmpHSL), dimAlpha); mDimLayerPaint.setAlpha((int) (dimAlpha * 255)); @@ -350,12 +366,15 @@ public class TaskViewHeader extends FrameLayout } /** Binds the bar view to the task */ - public void rebindToTask(Task t, boolean touchExplorationEnabled) { + public void rebindToTask(Task t, boolean touchExplorationEnabled, boolean disabledInSafeMode) { mTask = t; // If an activity icon is defined, then we use that as the primary icon to show in the bar, // otherwise, we fall back to the application icon - updateBackgroundColor(mDimAlpha); + int primaryColor = disabledInSafeMode + ? mDisabledTaskBarBackgroundColor + : t.colorPrimary; + updateBackgroundColor(primaryColor, mDimAlpha); if (t.icon != null) { mIconView.setImageDrawable(t.icon); } diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java index f90951e4ccd1..0fec9c38b5a4 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java @@ -21,13 +21,17 @@ import android.graphics.Bitmap; import android.graphics.BitmapShader; import android.graphics.Canvas; import android.graphics.Color; +import android.graphics.ColorMatrix; +import android.graphics.ColorMatrixColorFilter; import android.graphics.LightingColorFilter; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.Rect; +import android.graphics.Region; import android.graphics.Shader; import android.util.AttributeSet; import android.view.View; +import android.view.ViewDebug; import com.android.systemui.R; import com.android.systemui.recents.model.Task; @@ -39,27 +43,40 @@ import com.android.systemui.recents.model.Task; */ public class TaskViewThumbnail extends View { + + private static final ColorMatrix TMP_FILTER_COLOR_MATRIX = new ColorMatrix(); + private static final ColorMatrix TMP_BRIGHTNESS_COLOR_MATRIX = new ColorMatrix(); + private Task mTask; // Drawing + @ViewDebug.ExportedProperty(category="recents") Rect mThumbnailRect = new Rect(); + @ViewDebug.ExportedProperty(category="recents") Rect mTaskViewRect = new Rect(); int mCornerRadius; + @ViewDebug.ExportedProperty(category="recents") float mDimAlpha; Matrix mScaleMatrix = new Matrix(); Paint mDrawPaint = new Paint(); + Paint mBgFillPaint = new Paint(); BitmapShader mBitmapShader; LightingColorFilter mLightingColorFilter = new LightingColorFilter(0xffffffff, 0); // Task bar clipping, the top of this thumbnail can be clipped against the opaque header // bar that overlaps this thumbnail View mTaskBar; + @ViewDebug.ExportedProperty(category="recents") Rect mClipRect = new Rect(); // Visibility optimization, if the thumbnail height is less than the height of the header // bar for the task view, then just mark this thumbnail view as invisible + @ViewDebug.ExportedProperty(category="recents") boolean mInvisible; + @ViewDebug.ExportedProperty(category="recents") + boolean mDisabledInSafeMode; + public TaskViewThumbnail(Context context) { this(context, null); } @@ -79,6 +96,7 @@ public class TaskViewThumbnail extends View { mDrawPaint.setAntiAlias(true); mCornerRadius = getResources().getDimensionPixelSize( R.dimen.recents_task_view_rounded_corners_radius); + mBgFillPaint.setColor(Color.WHITE); } /** @@ -100,10 +118,39 @@ public class TaskViewThumbnail extends View { if (mInvisible) { return; } - // Draw the thumbnail with the rounded corners - canvas.drawRoundRect(0, 0, mTaskViewRect.width(), mTaskViewRect.height(), - mCornerRadius, - mCornerRadius, mDrawPaint); + + int thumbnailHeight = (int) (((float) mTaskViewRect.width() / mThumbnailRect.width()) * + mThumbnailRect.height()); + if (thumbnailHeight >= mTaskViewRect.height()) { + // The thumbnail fills the full task view bounds, so just draw it + canvas.drawRoundRect(0, 0, mTaskViewRect.width(), mTaskViewRect.height(), + mCornerRadius, mCornerRadius, mDrawPaint); + } else { + int count = 0; + if (thumbnailHeight > 0) { + // The thumbnail only covers part of the task view bounds, so fill in the + // non-thumbnail space with the default background color. This is the equivalent of + // the GL border texture mode. + count = canvas.save(); + + // Since we only want the top corners to be rounded, draw slightly beyond the + // thumbnail height, but clip to the thumbnail height + canvas.clipRect(0, 0, mTaskViewRect.width(), thumbnailHeight, Region.Op.REPLACE); + canvas.drawRoundRect(0, 0, mTaskViewRect.width(), thumbnailHeight + mCornerRadius, + mCornerRadius, mCornerRadius, mDrawPaint); + } + + // In the remaining space, draw the background color + canvas.clipRect(0, thumbnailHeight, mTaskViewRect.width(), mTaskViewRect.height(), + Region.Op.REPLACE); + canvas.drawRoundRect(0, Math.max(0, thumbnailHeight - mCornerRadius), + mTaskViewRect.width(), mTaskViewRect.height(), mCornerRadius, mCornerRadius, + mBgFillPaint); + + if (thumbnailHeight > 0) { + canvas.restoreToCount(count); + } + } } /** Sets the thumbnail to a given bitmap. */ @@ -128,9 +175,27 @@ public class TaskViewThumbnail extends View { } int mul = (int) ((1.0f - mDimAlpha) * 255); if (mBitmapShader != null) { - mLightingColorFilter.setColorMultiply(Color.argb(255, mul, mul, mul)); - mDrawPaint.setColorFilter(mLightingColorFilter); - mDrawPaint.setColor(0xffffffff); + if (mDisabledInSafeMode) { + // Brightness: C-new = C-old*(1-amount) + amount + TMP_FILTER_COLOR_MATRIX.setSaturation(0); + float scale = 1f - mDimAlpha; + float[] mat = TMP_BRIGHTNESS_COLOR_MATRIX.getArray(); + mat[0] = scale; + mat[6] = scale; + mat[12] = scale; + mat[4] = mDimAlpha; + mat[9] = mDimAlpha; + mat[14] = mDimAlpha; + TMP_FILTER_COLOR_MATRIX.preConcat(TMP_BRIGHTNESS_COLOR_MATRIX); + ColorMatrixColorFilter filter = new ColorMatrixColorFilter(TMP_FILTER_COLOR_MATRIX); + mDrawPaint.setColorFilter(filter); + mBgFillPaint.setColorFilter(filter); + } else { + mLightingColorFilter.setColorMultiply(Color.argb(255, mul, mul, mul)); + mDrawPaint.setColorFilter(mLightingColorFilter); + mDrawPaint.setColor(0xFFffffff); + mBgFillPaint.setColorFilter(mLightingColorFilter); + } } else { int grey = mul; mDrawPaint.setColorFilter(null); @@ -196,10 +261,14 @@ public class TaskViewThumbnail extends View { } /** Binds the thumbnail view to the task */ - void rebindToTask(Task t) { + void rebindToTask(Task t, boolean disabledInSafeMode) { mTask = t; + mDisabledInSafeMode = disabledInSafeMode; if (t.thumbnail != null) { setThumbnail(t.thumbnail); + if (t.colorBackground != 0) { + mBgFillPaint.setColor(t.colorBackground); + } } else { setThumbnail(null); } diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java index 32878b0afcb7..d3d2dbed99d3 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java @@ -86,13 +86,10 @@ public class TaskViewTransform { public float translationZ = 0; public float scale = 1f; public float alpha = 1f; + public float dimAlpha = 0f; public boolean visible = false; - // This is the relative task progress of this task, relative to the stack scroll at which this - // transform was computed - public float p = 0f; - // This is a window-space rect used for positioning the task in the stack and freeform workspace public RectF rect = new RectF(); @@ -104,7 +101,7 @@ public class TaskViewTransform { scale = tv.getScaleX(); alpha = tv.getAlpha(); visible = true; - p = tv.getTaskProgress(); + dimAlpha = tv.getDimAlpha(); rect.set(tv.getLeft(), tv.getTop(), tv.getRight(), tv.getBottom()); } @@ -116,7 +113,7 @@ public class TaskViewTransform { scale = other.scale; alpha = other.alpha; visible = other.visible; - p = other.p; + dimAlpha = other.dimAlpha; rect.set(other.rect); } @@ -127,9 +124,9 @@ public class TaskViewTransform { translationZ = 0; scale = 1f; alpha = 1f; + dimAlpha = 0f; visible = false; rect.setEmpty(); - p = 0f; } /** Convenience functions to compare against current property values */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java index 7d37ad22223f..2bebac2c5136 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java @@ -38,6 +38,7 @@ import android.content.pm.UserInfo; import android.content.res.Configuration; import android.content.res.Resources; import android.database.ContentObserver; +import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; import android.os.AsyncTask; @@ -116,7 +117,7 @@ public abstract class BaseStatusBar extends SystemUI implements ExpandableNotificationRow.ExpansionLogger, NotificationData.Environment, ExpandableNotificationRow.OnExpandClickListener { public static final String TAG = "StatusBar"; - public static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + public static final boolean DEBUG = false; public static final boolean MULTIUSER_DEBUG = false; public static final boolean ENABLE_REMOTE_INPUT = @@ -194,6 +195,7 @@ public abstract class BaseStatusBar extends SystemUI implements private final SparseBooleanArray mUsersAllowingPrivateNotifications = new SparseBooleanArray(); private UserManager mUserManager; + private int mDensity; // UI-specific methods @@ -227,6 +229,7 @@ public abstract class BaseStatusBar extends SystemUI implements protected int mState; protected boolean mBouncerShowing; protected boolean mShowLockscreenNotifications; + protected boolean mAllowLockscreenRemoteInput; protected NotificationOverflowContainer mKeyguardIconOverflowContainer; protected DismissView mDismissView; @@ -398,11 +401,26 @@ public abstract class BaseStatusBar extends SystemUI implements } p = p.getParent(); } + ExpandableNotificationRow row = null; + while (p != null) { + if (p instanceof ExpandableNotificationRow) { + row = (ExpandableNotificationRow) p; + break; + } + p = p.getParent(); + } - if (riv == null) { + if (riv == null || row == null) { return false; } + row.setUserExpanded(true); + + if (isLockscreenPublicMode() && !mAllowLockscreenRemoteInput) { + onLockedRemoteInput(row, view); + return true; + } + riv.setVisibility(View.VISIBLE); int cx = view.getLeft() + view.getWidth() / 2; int cy = view.getTop() + view.getHeight() / 2; @@ -617,6 +635,10 @@ public abstract class BaseStatusBar extends SystemUI implements Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS), false, mSettingsObserver, UserHandle.USER_ALL); + mContext.getContentResolver().registerContentObserver( + Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_ALLOW_REMOTE_INPUT), false, + mSettingsObserver, + UserHandle.USER_ALL); mContext.getContentResolver().registerContentObserver( Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS), @@ -633,18 +655,22 @@ public abstract class BaseStatusBar extends SystemUI implements mLocale = currentConfig.locale; mLayoutDirection = TextUtils.getLayoutDirectionFromLocale(mLocale); mFontScale = currentConfig.fontScale; + mDensity = currentConfig.densityDpi; mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE); // Connect in to the status bar manager service mCommandQueue = new CommandQueue(this); - int[] switches = new int[8]; + int[] switches = new int[9]; ArrayList<IBinder> binders = new ArrayList<IBinder>(); ArrayList<String> iconSlots = new ArrayList<>(); ArrayList<StatusBarIcon> icons = new ArrayList<>(); + Rect fullscreenStackBounds = new Rect(); + Rect dockedStackBounds = new Rect(); try { - mBarService.registerStatusBar(mCommandQueue, iconSlots, icons, switches, binders); + mBarService.registerStatusBar(mCommandQueue, iconSlots, icons, switches, binders, + fullscreenStackBounds, dockedStackBounds); } catch (RemoteException ex) { // If the system process isn't there we're doomed anyway. } @@ -653,7 +679,8 @@ public abstract class BaseStatusBar extends SystemUI implements mSettingsObserver.onChange(false); // set up disable(switches[0], switches[6], false /* animate */); - setSystemUiVisibility(switches[1], 0xffffffff); + setSystemUiVisibility(switches[1], switches[7], switches[8], 0xffffffff, + fullscreenStackBounds, dockedStackBounds); topAppWindowChanged(switches[2] != 0); // StatusBarManagerService has a back up of IME token and it's restored here. setImeWindowStatus(binders.get(0), switches[3], switches[4], switches[5] != 0); @@ -813,8 +840,13 @@ public abstract class BaseStatusBar extends SystemUI implements final Locale locale = mContext.getResources().getConfiguration().locale; final int ld = TextUtils.getLayoutDirectionFromLocale(locale); final float fontScale = newConfig.fontScale; - - if (! locale.equals(mLocale) || ld != mLayoutDirection || fontScale != mFontScale) { + final int density = newConfig.densityDpi; + if (density != mDensity || mFontScale != fontScale) { + reInflateViews(); + mDensity = density; + mFontScale = fontScale; + } + if (! locale.equals(mLocale) || ld != mLayoutDirection) { if (DEBUG) { Log.v(TAG, String.format( "config changed locale/LD: %s (%d) -> %s (%d)", mLocale, mLayoutDirection, @@ -826,6 +858,21 @@ public abstract class BaseStatusBar extends SystemUI implements } } + protected void reInflateViews() { + ArrayList<Entry> activeNotifications = mNotificationData.getActiveNotifications(); + for (int i = 0; i < activeNotifications.size(); i++) { + Entry entry = activeNotifications.get(i); + boolean exposedGuts = entry.row.getGuts() == mNotificationGutsExposed; + entry.row.reInflateViews(); + if (exposedGuts) { + mNotificationGutsExposed = entry.row.getGuts(); + bindGuts(entry.row); + } + entry.cacheContentViews(mContext, null /* updatedNotification */); + inflateViews(entry, mStackScroller); + } + } + protected View bindVetoButtonClickListener(View row, StatusBarNotification n) { View vetoButton = row.findViewById(R.id.veto); final String _pkg = n.getPackageName(); @@ -1273,6 +1320,8 @@ public abstract class BaseStatusBar extends SystemUI implements } } + protected void onLockedRemoteInput(ExpandableNotificationRow row, View clickedView) {} + @Override public void onExpandClicked(Entry clickedEntry, boolean nowExpanded) { } @@ -1909,6 +1958,10 @@ public abstract class BaseStatusBar extends SystemUI implements mShowLockscreenNotifications = show; } + protected void setLockScreenAllowRemoteInput(boolean allowLockscreenRemoteInput) { + mAllowLockscreenRemoteInput = allowLockscreenRemoteInput; + } + private void updateLockscreenNotificationSetting() { final boolean show = Settings.Secure.getIntForUser(mContext.getContentResolver(), Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, @@ -1918,7 +1971,14 @@ public abstract class BaseStatusBar extends SystemUI implements null /* admin */, mCurrentUserId); final boolean allowedByDpm = (dpmFlags & DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS) == 0; + + final boolean remoteInput = Settings.Secure.getIntForUser(mContext.getContentResolver(), + Settings.Secure.LOCK_SCREEN_ALLOW_REMOTE_INPUT, + 0, + mCurrentUserId) != 0; + setShowLockscreenNotifications(show && allowedByDpm); + setLockScreenAllowRemoteInput(remoteInput); } protected abstract void setAreThereNotifications(); @@ -2080,25 +2140,44 @@ public abstract class BaseStatusBar extends SystemUI implements return false; } - if (isSnoozedPackage(sbn)) { - if (DEBUG) Log.d(TAG, "No peeking: snoozed package: " + sbn.getKey()); + boolean inUse = mPowerManager.isScreenOn() + && (!mStatusBarKeyguardViewManager.isShowing() + || mStatusBarKeyguardViewManager.isOccluded()) + && !mStatusBarKeyguardViewManager.isInputRestricted(); + try { + inUse = inUse && !mDreamManager.isDreaming(); + } catch (RemoteException e) { + Log.d(TAG, "failed to query dream manager", e); + } + + if (!inUse) { + if (DEBUG) { + Log.d(TAG, "No peeking: not in use: " + sbn.getKey()); + } return false; } - if (entry.hasJustLaunchedFullScreenIntent()) { - if (DEBUG) Log.d(TAG, "No peeking: recent fullscreen: " + sbn.getKey()); + if (mNotificationData.shouldSuppressScreenOn(sbn.getKey())) { + if (DEBUG) Log.d(TAG, "No peeking: suppressed by DND: " + sbn.getKey()); return false; } - if (sbn.getNotification().fullScreenIntent != null - && mAccessibilityManager.isTouchExplorationEnabled()) { - if (DEBUG) Log.d(TAG, "No peeking: accessible fullscreen: " + sbn.getKey()); + if (entry.hasJustLaunchedFullScreenIntent()) { + if (DEBUG) Log.d(TAG, "No peeking: recent fullscreen: " + sbn.getKey()); return false; } + if (sbn.getNotification().fullScreenIntent != null) { + if (mAccessibilityManager.isTouchExplorationEnabled()) { + if (DEBUG) Log.d(TAG, "No peeking: accessible fullscreen: " + sbn.getKey()); + return false; + } else { + return true; + } + } - if (mNotificationData.shouldSuppressScreenOn(sbn.getKey())) { - if (DEBUG) Log.d(TAG, "No peeking: suppressed by DND: " + sbn.getKey()); + if (isSnoozedPackage(sbn)) { + if (DEBUG) Log.d(TAG, "No peeking: snoozed package: " + sbn.getKey()); return false; } @@ -2107,17 +2186,7 @@ public abstract class BaseStatusBar extends SystemUI implements return false; } - boolean inUse = mPowerManager.isScreenOn() - && (!mStatusBarKeyguardViewManager.isShowing() - || mStatusBarKeyguardViewManager.isOccluded()) - && !mStatusBarKeyguardViewManager.isInputRestricted(); - try { - inUse = inUse && !mDreamManager.isDreaming(); - } catch (RemoteException e) { - Log.d(TAG, "failed to query dream manager", e); - } - if (DEBUG) Log.d(TAG, "peek if device in use: " + inUse); - return inUse; + return true; } protected abstract boolean isSnoozedPackage(StatusBarNotification sbn); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java index 71347c4ba611..3b960eef84c9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java @@ -17,12 +17,14 @@ package com.android.systemui.statusbar; import android.content.ComponentName; +import android.graphics.Rect; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Message; import android.util.Pair; +import com.android.internal.os.SomeArgs; import com.android.internal.statusbar.IStatusBar; import com.android.internal.statusbar.StatusBarIcon; @@ -94,7 +96,8 @@ public class CommandQueue extends IStatusBar.Stub { public void animateExpandNotificationsPanel(); public void animateCollapsePanels(int flags); public void animateExpandSettingsPanel(String obj); - public void setSystemUiVisibility(int vis, int mask); + public void setSystemUiVisibility(int vis, int fullscreenStackVis, + int dockedStackVis, int mask, Rect fullscreenStackBounds, Rect dockedStackBounds); public void topAppWindowChanged(boolean visible); public void setImeWindowStatus(IBinder token, int vis, int backDisposition, boolean showImeSwitcher); @@ -169,11 +172,19 @@ public class CommandQueue extends IStatusBar.Stub { } } - public void setSystemUiVisibility(int vis, int mask) { + public void setSystemUiVisibility(int vis, int fullscreenStackVis, int dockedStackVis, + int mask, Rect fullscreenStackBounds, Rect dockedStackBounds) { synchronized (mLock) { // Don't coalesce these, since it might have one time flags set such as // STATUS_BAR_UNHIDE which might get lost. - mHandler.obtainMessage(MSG_SET_SYSTEMUI_VISIBILITY, vis, mask, null).sendToTarget(); + SomeArgs args = SomeArgs.obtain(); + args.argi1 = vis; + args.argi2 = fullscreenStackVis; + args.argi3 = dockedStackVis; + args.argi4 = mask; + args.arg1 = fullscreenStackBounds; + args.arg2 = dockedStackBounds; + mHandler.obtainMessage(MSG_SET_SYSTEMUI_VISIBILITY, args).sendToTarget(); } } @@ -377,7 +388,10 @@ public class CommandQueue extends IStatusBar.Stub { mCallbacks.animateExpandSettingsPanel((String) msg.obj); break; case MSG_SET_SYSTEMUI_VISIBILITY: - mCallbacks.setSystemUiVisibility(msg.arg1, msg.arg2); + SomeArgs args = (SomeArgs) msg.obj; + mCallbacks.setSystemUiVisibility(args.argi1, args.argi2, args.argi3, + args.argi4, (Rect) args.arg1, (Rect) args.arg2); + args.recycle(); break; case MSG_TOP_APP_WINDOW_CHANGED: mCallbacks.topAppWindowChanged(msg.arg1 != 0); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java index 84b2031491fd..7422902e593e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java @@ -25,6 +25,7 @@ import android.graphics.drawable.Drawable; import android.os.Build; import android.service.notification.StatusBarNotification; import android.util.AttributeSet; +import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.NotificationHeaderView; import android.view.View; @@ -50,11 +51,11 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { private static final int DEFAULT_DIVIDER_ALPHA = 0x29; private static final int COLORED_DIVIDER_ALPHA = 0x7B; - private final int mNotificationMinHeightLegacy; - private final int mMaxHeadsUpHeightLegacy; - private final int mMaxHeadsUpHeight; - private final int mNotificationMinHeight; - private final int mNotificationMaxHeight; + private int mNotificationMinHeightLegacy; + private int mMaxHeadsUpHeightLegacy; + private int mMaxHeadsUpHeight; + private int mNotificationMinHeight; + private int mNotificationMaxHeight; /** Does this row contain layouts that can adapt to row expansion */ private boolean mExpandable; @@ -507,6 +508,29 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { mHeadsUpManager = headsUpManager; } + public void reInflateViews() { + initDimens(); + if (mIsSummaryWithChildren) { + removeView(mNotificationHeader); + mNotificationHeader = null; + recreateNotificationHeader(); + if (mChildrenContainer != null) { + mChildrenContainer.reInflateViews(); + } + } + if (mGuts != null) { + View oldGuts = mGuts; + int index = indexOfChild(oldGuts); + removeView(oldGuts); + mGuts = (NotificationGuts) LayoutInflater.from(mContext).inflate( + R.layout.notification_guts, this, false); + mGuts.setVisibility(oldGuts.getVisibility()); + addView(mGuts, index); + } + mPrivateLayout.reInflateViews(); + mPublicLayout.reInflateViews(); + } + public interface ExpansionLogger { public void logNotificationExpansion(String key, boolean userAction, boolean expanded); } @@ -514,6 +538,10 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { public ExpandableNotificationRow(Context context, AttributeSet attrs) { super(context, attrs); mFalsingManager = FalsingManager.getInstance(context); + initDimens(); + } + + private void initDimens() { mNotificationMinHeightLegacy = getResources().getDimensionPixelSize( R.dimen.notification_min_height_legacy); mNotificationMinHeight = getResources().getDimensionPixelSize( diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java index 76e522e336ca..d5361dd75e83 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java @@ -248,14 +248,16 @@ public class NotificationContentView extends FrameLayout { public void reset(boolean resetActualHeight) { if (mContractedChild != null) { mContractedChild.animate().cancel(); + removeView(mContractedChild); } if (mExpandedChild != null) { mExpandedChild.animate().cancel(); + removeView(mExpandedChild); } if (mHeadsUpChild != null) { mHeadsUpChild.animate().cancel(); + removeView(mHeadsUpChild); } - removeAllViews(); mContractedChild = null; mExpandedChild = null; mHeadsUpChild = null; @@ -494,7 +496,8 @@ public class NotificationContentView extends FrameLayout { return VISIBLE_TYPE_EXPANDED; } } else { - if (viewHeight <= mContractedChild.getHeight() || noExpandedChild) { + if (noExpandedChild || (viewHeight <= mContractedChild.getHeight() + && (!mIsChildInGroup || !mContainingNotification.isExpanded()))) { return VISIBLE_TYPE_CONTRACTED; } else { return VISIBLE_TYPE_EXPANDED; @@ -569,6 +572,9 @@ public class NotificationContentView extends FrameLayout { if (mIsChildInGroup) { mSingleLineView = mHybridViewManager.bindFromNotification( mSingleLineView, mStatusBarNotification.getNotification()); + } else if (mSingleLineView != null) { + removeView(mSingleLineView); + mSingleLineView = null; } } @@ -685,4 +691,12 @@ public class NotificationContentView extends FrameLayout { public void requestSelectLayout(boolean needsAnimation) { selectLayout(needsAnimation, false); } + + public void reInflateViews() { + if (mIsChildInGroup && mSingleLineView != null) { + removeView(mSingleLineView); + mSingleLineView = null; + updateSingleLineView(); + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java index dd6d6f381c02..7346becdb0eb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java @@ -113,15 +113,15 @@ public class NotificationGuts extends LinearLayout { ? new Notification.Topic(Notification.TOPIC_DEFAULT, mContext.getString( com.android.internal.R.string.default_notification_topic_label)) : sbn.getNotification().getTopic(); - boolean doesAppUseTopics = false; + boolean doesUserUseTopics = false; try { - doesAppUseTopics = - mINotificationManager.doesAppUseTopics(sbn.getPackageName(), sbn.getUid()); + doesUserUseTopics = + mINotificationManager.doesUserUseTopics(sbn.getPackageName(), sbn.getUid()); } catch (RemoteException e) {} - final boolean appUsesTopics = doesAppUseTopics; + final boolean userUsesTopics = doesUserUseTopics; mApplyToTopic = (RadioButton) row.findViewById(R.id.apply_to_topic); - if (appUsesTopics) { + if (userUsesTopics) { mApplyToTopic.setChecked(true); } final View applyToApp = row.findViewById(R.id.apply_to_app); @@ -156,7 +156,7 @@ public class NotificationGuts extends LinearLayout { updateTitleAndSummary(progress); if (fromUser) { MetricsLogger.action(mContext, MetricsEvent.ACTION_MODIFY_IMPORTANCE_SLIDER); - if (appUsesTopics) { + if (userUsesTopics) { mApplyToTopic.setVisibility(View.VISIBLE); mApplyToTopic.setText( mContext.getString(R.string.apply_to_topic, mTopic.getLabel())); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java index 6801e5f1017d..9aa5ea0c3032 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java @@ -21,6 +21,7 @@ import android.content.Context; import android.content.res.ColorStateList; import android.content.res.Resources; import android.graphics.Color; +import android.graphics.Rect; import android.graphics.drawable.Animatable; import android.graphics.drawable.Drawable; import android.telephony.SubscriptionInfo; @@ -80,6 +81,7 @@ public class SignalClusterView private ArrayList<PhoneState> mPhoneStates = new ArrayList<PhoneState>(); private int mIconTint = Color.WHITE; private float mDarkIntensity; + private final Rect mTintArea = new Rect(); ViewGroup mEthernetGroup, mWifiGroup; View mNoSimsCombo; @@ -490,23 +492,31 @@ public class SignalClusterView } } - public void setIconTint(int tint, float darkIntensity) { - boolean changed = tint != mIconTint || darkIntensity != mDarkIntensity; + public void setIconTint(int tint, float darkIntensity, Rect tintArea) { + boolean changed = tint != mIconTint || darkIntensity != mDarkIntensity + || !mTintArea.equals(tintArea); mIconTint = tint; mDarkIntensity = darkIntensity; + mTintArea.set(tintArea); if (changed && isAttachedToWindow()) { applyIconTint(); } } private void applyIconTint() { - setTint(mVpn, mIconTint); - setTint(mAirplane, mIconTint); - applyDarkIntensity(mDarkIntensity, mNoSims, mNoSimsDark); - applyDarkIntensity(mDarkIntensity, mWifi, mWifiDark); - applyDarkIntensity(mDarkIntensity, mEthernet, mEthernetDark); + setTint(mVpn, StatusBarIconController.getTint(mTintArea, mVpn, mIconTint)); + setTint(mAirplane, StatusBarIconController.getTint(mTintArea, mAirplane, mIconTint)); + applyDarkIntensity( + StatusBarIconController.getDarkIntensity(mTintArea, mNoSims, mDarkIntensity), + mNoSims, mNoSimsDark); + applyDarkIntensity( + StatusBarIconController.getDarkIntensity(mTintArea, mWifi, mDarkIntensity), + mWifi, mWifiDark); + applyDarkIntensity( + StatusBarIconController.getDarkIntensity(mTintArea, mEthernet, mDarkIntensity), + mEthernet, mEthernetDark); for (int i = 0; i < mPhoneStates.size(); i++) { - mPhoneStates.get(i).setIconTint(mIconTint, mDarkIntensity); + mPhoneStates.get(i).setIconTint(mIconTint, mDarkIntensity, mTintArea); } } @@ -613,9 +623,11 @@ public class SignalClusterView } } - public void setIconTint(int tint, float darkIntensity) { - applyDarkIntensity(darkIntensity, mMobile, mMobileDark); - setTint(mMobileType, tint); + public void setIconTint(int tint, float darkIntensity, Rect tintArea) { + applyDarkIntensity( + StatusBarIconController.getDarkIntensity(tintArea, mMobile, darkIntensity), + mMobile, mMobileDark); + setTint(mMobileType, StatusBarIconController.getTint(tintArea, mMobileType, tint)); } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java index 60c191126116..aa001ed2057c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java @@ -45,4 +45,10 @@ public class NotificationCustomViewWrapper extends NotificationViewWrapper { mInvertHelper.update(dark); } } + + @Override + public void setVisible(boolean visible) { + super.setVisible(visible); + mView.setAlpha(visible ? 1.0f : 0.0f); + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java index a2b4c5d24442..328f8b55b0ef 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java @@ -105,6 +105,7 @@ public abstract class NotificationViewWrapper implements TransformableView { @Override public void setVisible(boolean visible) { + mView.animate().cancel(); mView.setVisibility(visible ? View.VISIBLE : View.INVISIBLE); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java index 5832d86628af..67d31be7f511 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java @@ -293,14 +293,19 @@ public class TransformState { mTransformedView.setVisibility(visible ? View.VISIBLE : View.INVISIBLE); mTransformedView.setAlpha(visible ? 1.0f : 0.0f); if (visible) { - mTransformedView.setTranslationX(0); - mTransformedView.setTranslationY(0); - mTransformedView.setScaleX(1.0f); - mTransformedView.setScaleY(1.0f); + resetTransformedView(); } } public void prepareFadeIn() { + resetTransformedView(); + } + + private void resetTransformedView() { + mTransformedView.setTranslationX(0); + mTransformedView.setTranslationY(0); + mTransformedView.setScaleX(1.0f); + mTransformedView.setScaleY(1.0f); } public static TransformState obtain() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightStatusBarController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightStatusBarController.java new file mode 100644 index 000000000000..f98b9e586a1c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightStatusBarController.java @@ -0,0 +1,126 @@ +/* + * 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.systemui.statusbar.phone; + +import android.graphics.Rect; +import android.view.View; + +import com.android.systemui.statusbar.policy.BatteryController; + +import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT_TRANSPARENT; +import static com.android.systemui.statusbar.phone.BarTransitions.MODE_TRANSPARENT; + +/** + * Controls how light status bar flag applies to the icons. + */ +public class LightStatusBarController { + + private final StatusBarIconController mIconController; + private final BatteryController mBatteryController; + private FingerprintUnlockController mFingerprintUnlockController; + + private int mFullscreenStackVisibility; + private int mDockedStackVisibility; + private boolean mFullscreenLight; + private boolean mDockedLight; + + private final Rect mLastFullscreenBounds = new Rect(); + private final Rect mLastDockedBounds = new Rect(); + + public LightStatusBarController(StatusBarIconController iconController, + BatteryController batteryController) { + mIconController = iconController; + mBatteryController = batteryController; + } + + public void setFingerprintUnlockController( + FingerprintUnlockController fingerprintUnlockController) { + mFingerprintUnlockController = fingerprintUnlockController; + } + + public void onSystemUiVisibilityChanged(int fullscreenStackVis, int dockedStackVis, int mask, + Rect fullscreenStackBounds, Rect dockedStackBounds, boolean sbModeChanged, + int statusBarMode) { + int oldFullscreen = mFullscreenStackVisibility; + int newFullscreen = (oldFullscreen & ~mask) | (fullscreenStackVis & mask); + int diffFullscreen = newFullscreen ^ oldFullscreen; + int oldDocked = mDockedStackVisibility; + int newDocked = (oldDocked & ~mask) | (dockedStackVis & mask); + int diffDocked = newDocked ^ oldDocked; + if ((diffFullscreen & View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR) != 0 + || (diffDocked & View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR) != 0 + || sbModeChanged + || !mLastFullscreenBounds.equals(fullscreenStackBounds) + || !mLastDockedBounds.equals(dockedStackBounds)) { + + mFullscreenLight = isLight(newFullscreen, statusBarMode); + mDockedLight = isLight(newDocked, statusBarMode); + update(fullscreenStackBounds, dockedStackBounds); + } + mFullscreenStackVisibility = newFullscreen; + mDockedStackVisibility = newDocked; + mLastFullscreenBounds.set(fullscreenStackBounds); + mLastDockedBounds.set(dockedStackBounds); + } + + private boolean isLight(int vis, int statusBarMode) { + boolean isTransparentBar = (statusBarMode == MODE_TRANSPARENT + || statusBarMode == MODE_LIGHTS_OUT_TRANSPARENT); + boolean allowLight = isTransparentBar && !mBatteryController.isPowerSave(); + boolean light = (vis & View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR) != 0; + return allowLight && light; + } + + private boolean animateChange() { + if (mFingerprintUnlockController == null) { + return false; + } + int unlockMode = mFingerprintUnlockController.getMode(); + return unlockMode != FingerprintUnlockController.MODE_WAKE_AND_UNLOCK_PULSING + && unlockMode != FingerprintUnlockController.MODE_WAKE_AND_UNLOCK; + } + + private void update(Rect fullscreenStackBounds, Rect dockedStackBounds) { + boolean hasDockedStack = !dockedStackBounds.isEmpty(); + + // If both are light or fullscreen is light and there is no docked stack, all icons get + // dark. + if ((mFullscreenLight && mDockedLight) || (mFullscreenLight && !hasDockedStack)) { + mIconController.setIconsDarkArea(null); + mIconController.setIconsDark(true, animateChange()); + + } + + // If no one is light or the fullscreen is not light and there is no docked stack, + // all icons become white. + else if ((!mFullscreenLight && !mDockedLight) || (!mFullscreenLight && !hasDockedStack)) { + mIconController.setIconsDark(false, animateChange()); + + } + + // Not the same for every stack, magic! + else { + Rect bounds = mFullscreenLight ? fullscreenStackBounds : dockedStackBounds; + if (bounds.isEmpty()) { + mIconController.setIconsDarkArea(null); + } else { + mIconController.setIconsDarkArea(bounds); + } + mIconController.setIconsDark(true, animateChange()); + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java index 03a597c46bc5..6e345f088b5d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java @@ -4,6 +4,7 @@ import android.content.Context; import android.content.res.ColorStateList; import android.content.res.Resources; import android.graphics.Color; +import android.graphics.Rect; import android.view.LayoutInflater; import android.view.View; import android.widget.ImageView; @@ -32,6 +33,7 @@ public class NotificationIconAreaController { protected View mNotificationIconArea; private IconMerger mNotificationIcons; private ImageView mMoreIcon; + private final Rect mTintArea = new Rect(); public NotificationIconAreaController(Context context, PhoneStatusBar phoneStatusBar) { mPhoneStatusBar = phoneStatusBar; @@ -67,6 +69,20 @@ public class NotificationIconAreaController { } /** + * See {@link StatusBarIconController#setIconsDarkArea}. + * + * @param tintArea the area in which to tint the icons, specified in screen coordinates + */ + public void setTintArea(Rect tintArea) { + if (tintArea == null) { + mTintArea.setEmpty(); + } else { + mTintArea.set(tintArea); + } + applyNotificationIconsTint(); + } + + /** * Sets the color that should be used to tint any icons in the notification area. If this * method is not called, the default tint is {@link Color#WHITE}. */ @@ -145,7 +161,8 @@ public class NotificationIconAreaController { boolean isPreL = Boolean.TRUE.equals(v.getTag(R.id.icon_is_pre_L)); boolean colorize = !isPreL || NotificationUtils.isGrayscale(v, mNotificationColorUtil); if (colorize) { - v.setImageTintList(ColorStateList.valueOf(mIconTint)); + v.setImageTintList(ColorStateList.valueOf( + StatusBarIconController.getTint(mTintArea, v, mIconTint))); } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java index f822bd535851..8f0f51f502b7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java @@ -19,7 +19,6 @@ package com.android.systemui.statusbar.phone; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; -import android.animation.PropertyValuesHolder; import android.animation.ValueAnimator; import android.app.ActivityManager; import android.app.StatusBarManager; @@ -35,13 +34,11 @@ import android.util.MathUtils; import android.view.MotionEvent; import android.view.VelocityTracker; import android.view.View; -import android.view.ViewStub; import android.view.ViewTreeObserver; import android.view.WindowInsets; import android.view.accessibility.AccessibilityEvent; import android.widget.FrameLayout; import android.widget.TextView; - import com.android.internal.logging.MetricsLogger; import com.android.keyguard.KeyguardStatusView; import com.android.systemui.DejankUtils; @@ -51,7 +48,6 @@ import com.android.systemui.Interpolators; import com.android.systemui.R; import com.android.systemui.classifier.FalsingManager; import com.android.systemui.qs.QSContainer; -import com.android.systemui.qs.QSPanel; import com.android.systemui.statusbar.ExpandableNotificationRow; import com.android.systemui.statusbar.ExpandableView; import com.android.systemui.statusbar.FlingAnimationUtils; @@ -91,13 +87,10 @@ public class NotificationPanelView extends PanelView implements public static final long DOZE_ANIMATION_DURATION = 700; private KeyguardAffordanceHelper mAfforanceHelper; - protected BaseStatusBarHeader mHeader; private KeyguardUserSwitcher mKeyguardUserSwitcher; private KeyguardStatusBarView mKeyguardStatusBar; private QSContainer mQsContainer; - private QSPanel mQsPanel; private KeyguardStatusView mKeyguardStatusView; - private ObservableScrollView mScrollView; private TextView mClockView; private View mReserveNotificationSpace; private View mQsNavbarScrim; @@ -168,15 +161,12 @@ public class NotificationPanelView extends PanelView implements * If we are in a panel collapsing motion, we reset scrollY of our scroll view but still * need to take this into account in our panel height calculation. */ - private int mScrollYOverride = -1; private boolean mQsAnimatorExpand; private boolean mIsLaunchTransitionFinished; private boolean mIsLaunchTransitionRunning; private Runnable mLaunchAnimationEndRunnable; private boolean mOnlyAffordanceInThisMotion; private boolean mKeyguardStatusViewAnimating; - private boolean mHeaderAnimating; - private ObjectAnimator mQsContainerAnimator; private ValueAnimator mQsSizeChangeAnimator; private boolean mShadeEmpty; @@ -223,19 +213,11 @@ public class NotificationPanelView extends PanelView implements @Override protected void onFinishInflate() { super.onFinishInflate(); - ViewStub stub = (ViewStub) findViewById(R.id.status_bar_header); - stub.setLayoutResource(R.layout.quick_status_bar_expanded_header); - mHeader = (BaseStatusBarHeader) stub.inflate(); - mHeader.setOnClickListener(this); mKeyguardStatusBar = (KeyguardStatusBarView) findViewById(R.id.keyguard_header); mKeyguardStatusView = (KeyguardStatusView) findViewById(R.id.keyguard_status_view); mQsContainer = (QSContainer) findViewById(R.id.quick_settings_container); - mQsPanel = (QSPanel) findViewById(R.id.quick_settings_panel); + mQsContainer.getHeader().setOnClickListener(this); mClockView = (TextView) findViewById(R.id.clock_view); - mScrollView = (ObservableScrollView) findViewById(R.id.scroll_view); - mScrollView.setListener(this); - mScrollView.setFocusable(false); - mReserveNotificationSpace = findViewById(R.id.reserve_notification_space); mNotificationContainerParent = (NotificationsQuickSettingsContainer) findViewById(R.id.notification_container_parent); mNotificationStackScroller = (NotificationStackScrollLayout) @@ -243,7 +225,7 @@ public class NotificationPanelView extends PanelView implements mNotificationStackScroller.setOnHeightChangedListener(this); mNotificationStackScroller.setOverscrollTopChangedListener(this); mNotificationStackScroller.setOnEmptySpaceClickListener(this); - mNotificationStackScroller.setScrollView(mScrollView); + mNotificationStackScroller.setQsContainer(mQsContainer); mKeyguardBottomArea = (KeyguardBottomAreaView) findViewById(R.id.keyguard_bottom_area); mQsNavbarScrim = findViewById(R.id.qs_navbar_scrim); mAfforanceHelper = new KeyguardAffordanceHelper(this, getContext()); @@ -285,12 +267,12 @@ public class NotificationPanelView extends PanelView implements public void updateResources() { int panelWidth = getResources().getDimensionPixelSize(R.dimen.notification_panel_width); int panelGravity = getResources().getInteger(R.integer.notification_panel_layout_gravity); - FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mHeader.getLayoutParams(); + FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mQsContainer.getLayoutParams(); if (lp.width != panelWidth) { lp.width = panelWidth; lp.gravity = panelGravity; - mHeader.setLayoutParams(lp); - mHeader.post(mUpdateHeader); + mQsContainer.setLayoutParams(lp); + mQsContainer.post(mUpdateHeader); } lp = (FrameLayout.LayoutParams) mNotificationStackScroller.getLayoutParams(); @@ -299,13 +281,6 @@ public class NotificationPanelView extends PanelView implements lp.gravity = panelGravity; mNotificationStackScroller.setLayoutParams(lp); } - - lp = (FrameLayout.LayoutParams) mScrollView.getLayoutParams(); - if (lp.width != panelWidth) { - lp.width = panelWidth; - lp.gravity = panelGravity; - mScrollView.setLayoutParams(lp); - } } @Override @@ -318,8 +293,8 @@ public class NotificationPanelView extends PanelView implements // Calculate quick setting heights. int oldMaxHeight = mQsMaxExpansionHeight; - mQsMinExpansionHeight = mKeyguardShowing ? 0 : mHeader.getCollapsedHeight() + mQsPeekHeight; - mQsMaxExpansionHeight = mHeader.getExpandedHeight() + mQsContainer.getDesiredHeight(); + mQsMinExpansionHeight = mKeyguardShowing ? 0 : mQsContainer.getHeader().getHeight(); + mQsMaxExpansionHeight = mQsContainer.getDesiredHeight(); positionClockAndNotifications(); if (mQsExpanded && mQsFullyExpanded) { mQsExpansionHeight = mQsMaxExpansionHeight; @@ -361,7 +336,7 @@ public class NotificationPanelView extends PanelView implements requestScrollerTopPaddingUpdate(false /* animate */); requestPanelHeightUpdate(); int height = (int) mQsSizeChangeAnimator.getAnimatedValue(); - mQsContainer.setHeightOverride(height - mHeader.getExpandedHeight()); + mQsContainer.setHeightOverride(height); } }); mQsSizeChangeAnimator.addListener(new AnimatorListenerAdapter() { @@ -381,7 +356,7 @@ public class NotificationPanelView extends PanelView implements boolean animate = mNotificationStackScroller.isAddOrRemoveAnimationPending(); int stackScrollerPadding; if (mStatusBarState != StatusBarState.KEYGUARD) { - int bottom = mHeader.getCollapsedHeight(); + int bottom = mQsContainer.getHeader().getHeight(); stackScrollerPadding = mStatusBarState == StatusBarState.SHADE ? bottom + mQsPeekHeight : mKeyguardStatusBar.getHeight(); @@ -485,7 +460,7 @@ public class NotificationPanelView extends PanelView implements public void setQsExpansionEnabled(boolean qsExpansionEnabled) { mQsExpansionEnabled = qsExpansionEnabled; - mHeader.setClickable(qsExpansionEnabled); + mQsContainer.setHeaderClickable(qsExpansionEnabled); } @Override @@ -676,17 +651,6 @@ public class NotificationPanelView extends PanelView implements } } - @Override - public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) { - - // Block request when interacting with the scroll view so we can still intercept the - // scrolling when QS is expanded. - if (mScrollView.isHandlingTouchEvent()) { - return; - } - super.requestDisallowInterceptTouchEvent(disallowIntercept); - } - private void flingQsWithCurrentVelocity(float y, boolean isCancelMotionEvent) { float vel = getCurrentVelocity(); final boolean expandsQs = flingExpandsQs(vel); @@ -808,7 +772,7 @@ public class NotificationPanelView extends PanelView implements } private boolean isInQsArea(float x, float y) { - return (x >= mScrollView.getX() && x <= mScrollView.getX() + mScrollView.getWidth()) && + return (x >= mQsContainer.getX() && x <= mQsContainer.getX() + mQsContainer.getWidth()) && (y <= mNotificationStackScroller.getBottomMostNotificationBottom() || y <= mQsContainer.getY() + mQsContainer.getHeight()); } @@ -948,7 +912,7 @@ public class NotificationPanelView extends PanelView implements amount = 0f; } float rounded = amount >= 1f ? amount : 0f; - mStackScrollerOverscrolling = rounded != 0f && isRubberbanded; + setOverScrolling(rounded != 0f && isRubberbanded); mQsExpansionFromOverscroll = rounded != 0f; mLastOverscroll = rounded; updateQsState(); @@ -964,12 +928,17 @@ public class NotificationPanelView extends PanelView implements @Override public void run() { mStackScrollerOverscrolling = false; - mQsExpansionFromOverscroll = false; + setOverScrolling(false); updateQsState(); } }, false /* isClick */); } + private void setOverScrolling(boolean overscrolling) { + mStackScrollerOverscrolling = overscrolling; + mQsContainer.setOverscrolling(overscrolling); + } + private void onQsExpansionStarted() { onQsExpansionStarted(0); } @@ -979,11 +948,7 @@ public class NotificationPanelView extends PanelView implements cancelHeightAnimator(); // Reset scroll position and apply that position to the expanded height. - float height = mQsExpansionHeight - mScrollView.getScrollY() - overscrollAmount; - if (mScrollView.getScrollY() != 0) { - mScrollYOverride = mScrollView.getScrollY(); - } - mScrollView.scrollTo(0, 0); + float height = mQsExpansionHeight - overscrollAmount; setQsExpansion(height); requestPanelHeightUpdate(); } @@ -995,9 +960,7 @@ public class NotificationPanelView extends PanelView implements updateQsState(); requestPanelHeightUpdate(); mFalsingManager.setQsExpanded(expanded); - mNotificationStackScroller.setInterceptDelegateEnabled(expanded); mStatusBar.setQsExpanded(expanded); - mQsPanel.setExpanded(expanded); mNotificationContainerParent.setQsExpanded(expanded); } } @@ -1011,15 +974,18 @@ public class NotificationPanelView extends PanelView implements mStatusBarState = statusBarState; mKeyguardShowing = keyguardShowing; + mQsContainer.setKeyguardShowing(mKeyguardShowing); if (goingToFullShade || (oldState == StatusBarState.KEYGUARD && statusBarState == StatusBarState.SHADE_LOCKED)) { animateKeyguardStatusBarOut(); - animateHeaderSlidingIn(); + long delay = mStatusBarState == StatusBarState.SHADE_LOCKED + ? 0 : mStatusBar.calculateGoingToFullShadeDelay(); + mQsContainer.animateHeaderSlidingIn(delay); } else if (oldState == StatusBarState.SHADE_LOCKED && statusBarState == StatusBarState.KEYGUARD) { animateKeyguardStatusBarIn(StackStateAnimator.ANIMATION_DURATION_STANDARD); - animateHeaderSlidingOut(); + mQsContainer.animateHeaderSlidingOut(); } else { mKeyguardStatusBar.setAlpha(1f); mKeyguardStatusBar.setVisibility(keyguardShowing ? View.VISIBLE : View.INVISIBLE); @@ -1050,95 +1016,6 @@ public class NotificationPanelView extends PanelView implements } }; - private final Animator.AnimatorListener mAnimateHeaderSlidingInListener - = new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - mHeaderAnimating = false; - mQsContainerAnimator = null; - mQsContainer.removeOnLayoutChangeListener(mQsContainerAnimatorUpdater); - } - }; - - private final OnLayoutChangeListener mQsContainerAnimatorUpdater - = new OnLayoutChangeListener() { - @Override - public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, - int oldTop, int oldRight, int oldBottom) { - int oldHeight = oldBottom - oldTop; - int height = bottom - top; - if (height != oldHeight && mQsContainerAnimator != null) { - PropertyValuesHolder[] values = mQsContainerAnimator.getValues(); - float newEndValue = mHeader.getCollapsedHeight() + mQsPeekHeight - height - top; - float newStartValue = -height - top; - values[0].setFloatValues(newStartValue, newEndValue); - mQsContainerAnimator.setCurrentPlayTime(mQsContainerAnimator.getCurrentPlayTime()); - } - } - }; - - private final ViewTreeObserver.OnPreDrawListener mStartHeaderSlidingIn - = new ViewTreeObserver.OnPreDrawListener() { - @Override - public boolean onPreDraw() { - getViewTreeObserver().removeOnPreDrawListener(this); - long delay = mStatusBarState == StatusBarState.SHADE_LOCKED - ? 0 - : mStatusBar.calculateGoingToFullShadeDelay(); - mHeader.setTranslationY(-mHeader.getCollapsedHeight() - mQsPeekHeight); - mHeader.animate() - .translationY(0f) - .setStartDelay(delay) - .setDuration(StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE) - .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) - .start(); - mQsContainer.setY(-mQsContainer.getHeight()); - mQsContainerAnimator = ObjectAnimator.ofFloat(mQsContainer, View.TRANSLATION_Y, - mQsContainer.getTranslationY(), - mHeader.getCollapsedHeight() + mQsPeekHeight - mQsContainer.getHeight() - - mQsContainer.getTop()); - mQsContainerAnimator.setStartDelay(delay); - mQsContainerAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE); - mQsContainerAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN); - mQsContainerAnimator.addListener(mAnimateHeaderSlidingInListener); - mQsContainerAnimator.start(); - mQsContainer.addOnLayoutChangeListener(mQsContainerAnimatorUpdater); - return true; - } - }; - - private void animateHeaderSlidingIn() { - // If the QS is already expanded we don't need to slide in the header as it's already - // visible. - if (!mQsExpanded) { - mHeaderAnimating = true; - getViewTreeObserver().addOnPreDrawListener(mStartHeaderSlidingIn); - } - } - - private void animateHeaderSlidingOut() { - mHeaderAnimating = true; - mHeader.animate().y(-mHeader.getHeight()) - .setStartDelay(0) - .setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD) - .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) - .setListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - mHeader.animate().setListener(null); - mHeaderAnimating = false; - updateQsState(); - } - }) - .start(); - mQsContainer.animate() - .y(-mQsContainer.getHeight()) - .setStartDelay(0) - .setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD) - .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) - .start(); - } - private final Runnable mAnimateKeyguardStatusBarInvisibleEndRunnable = new Runnable() { @Override public void run() { @@ -1262,19 +1139,10 @@ public class NotificationPanelView extends PanelView implements } private void updateQsState() { - boolean expandVisually = mQsExpanded || mStackScrollerOverscrolling || mHeaderAnimating; - mHeader.setVisibility((mQsExpanded || !mKeyguardShowing || mHeaderAnimating) - ? View.VISIBLE - : View.INVISIBLE); - mHeader.setExpanded((mKeyguardShowing && !mHeaderAnimating) - || (mQsExpanded && !mStackScrollerOverscrolling)); + mQsContainer.setExpanded(mQsExpanded); mNotificationStackScroller.setScrollingEnabled( mStatusBarState != StatusBarState.KEYGUARD && (!mQsExpanded || mQsExpansionFromOverscroll)); - mQsPanel.setVisibility(expandVisually ? View.VISIBLE : View.INVISIBLE); - mQsContainer.setVisibility( - mKeyguardShowing && !expandVisually ? View.INVISIBLE : View.VISIBLE); - mScrollView.setTouchEnabled(mQsExpanded); updateEmptyShadeView(); mQsNavbarScrim.setVisibility(mStatusBarState == StatusBarState.SHADE && mQsExpanded && !mStackScrollerOverscrolling && mQsScrimEnabled @@ -1298,11 +1166,10 @@ public class NotificationPanelView extends PanelView implements } } mQsExpansionHeight = height; - mHeader.setExpansion(getHeaderExpansionFraction()); - setQsTranslation(height); + updateQsExpansion(); requestScrollerTopPaddingUpdate(false /* animate */); if (mKeyguardShowing) { - updateHeaderKeyguard(); + updateHeaderKeyguardAlpha(); } if (mStatusBarState == StatusBarState.SHADE_LOCKED || mStatusBarState == StatusBarState.KEYGUARD) { @@ -1329,6 +1196,10 @@ public class NotificationPanelView extends PanelView implements } } + private void updateQsExpansion() { + mQsContainer.setQsExpansion(getQsExpansionFraction(), getHeaderTranslation()); + } + private String getKeyguardOrLockScreenString() { if (mStatusBarState == StatusBarState.KEYGUARD) { return getContext().getString(R.string.accessibility_desc_lock_screen); @@ -1337,23 +1208,6 @@ public class NotificationPanelView extends PanelView implements } } - private float getHeaderExpansionFraction() { - if (!mKeyguardShowing) { - return getQsExpansionFraction(); - } else { - return 1f; - } - } - - private void setQsTranslation(float height) { - if (!mHeaderAnimating) { - mQsContainer.setY(height - mQsContainer.getDesiredHeight() + getHeaderTranslation()); - } - if (mKeyguardShowing && !mHeaderAnimating) { - mHeader.setY(interpolate(getQsExpansionFraction(), -mHeader.getHeight(), 0)); - } - } - private float calculateQsTopPadding() { if (mKeyguardShowing && (mQsExpandImmediate || mIsExpanding && mQsExpandedWhenExpandingStarted)) { @@ -1372,7 +1226,7 @@ public class NotificationPanelView extends PanelView implements mQsMinExpansionHeight, max); } else if (mQsSizeChangeAnimator != null) { return (int) mQsSizeChangeAnimator.getAnimatedValue(); - } else if (mKeyguardShowing && mScrollYOverride == -1) { + } else if (mKeyguardShowing) { // We can only do the smoother transition on Keyguard when we also are not collapsing // from a scrolled quick settings. @@ -1386,7 +1240,6 @@ public class NotificationPanelView extends PanelView implements private void requestScrollerTopPaddingUpdate(boolean animate) { mNotificationStackScroller.updateTopPadding(calculateQsTopPadding(), - mScrollView.getScrollY(), mAnimateNextTopPaddingChange || animate, mKeyguardShowing && (mQsExpandImmediate || mIsExpanding && mQsExpandedWhenExpandingStarted)); @@ -1428,7 +1281,6 @@ public class NotificationPanelView extends PanelView implements boolean isClick) { float target = expand ? mQsMaxExpansionHeight : mQsMinExpansionHeight; if (target == mQsExpansionHeight) { - mScrollYOverride = -1; if (onFinishRunnable != null) { onFinishRunnable.run(); } @@ -1438,7 +1290,6 @@ public class NotificationPanelView extends PanelView implements if (belowFalsingThreshold) { vel = 0; } - mScrollView.setBlockFlinging(true); ValueAnimator animator = ValueAnimator.ofFloat(mQsExpansionHeight, target); if (isClick) { animator.setInterpolator(Interpolators.TOUCH_RESPONSE); @@ -1458,8 +1309,6 @@ public class NotificationPanelView extends PanelView implements animator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { - mScrollView.setBlockFlinging(false); - mScrollYOverride = -1; mQsExpansionAnimator = null; if (onFinishRunnable != null) { onFinishRunnable.run(); @@ -1478,11 +1327,11 @@ public class NotificationPanelView extends PanelView implements if (!mQsExpansionEnabled || mCollapsedOnDown) { return false; } - View header = mKeyguardShowing ? mKeyguardStatusBar : mHeader; + View header = mKeyguardShowing ? mKeyguardStatusBar : mQsContainer.getHeader(); boolean onHeader = x >= header.getX() && x <= header.getX() + header.getWidth() && y >= header.getTop() && y <= header.getBottom(); if (mQsExpanded) { - return onHeader || (mScrollView.isScrolledToBottom() && yDiff < 0) && isInQsArea(x, y); + return onHeader || (yDiff < 0 && isInQsArea(x, y)); } else { return onHeader; } @@ -1494,7 +1343,7 @@ public class NotificationPanelView extends PanelView implements return mStatusBar.getBarState() == StatusBarState.KEYGUARD || mNotificationStackScroller.isScrolledToBottom(); } else { - return mScrollView.isScrolledToBottom(); + return true; } } @@ -1571,11 +1420,7 @@ public class NotificationPanelView extends PanelView implements * collapsing QS / the panel when QS was scrolled */ private int getTempQsMaxExpansion() { - int qsTempMaxExpansion = mQsMaxExpansionHeight; - if (mScrollYOverride != -1) { - qsTempMaxExpansion -= mScrollYOverride; - } - return qsTempMaxExpansion; + return mQsMaxExpansionHeight; } private int calculatePanelHeightShade() { @@ -1613,20 +1458,12 @@ public class NotificationPanelView extends PanelView implements + notificationHeight; if (totalHeight > mNotificationStackScroller.getHeight()) { float fullyCollapsedHeight = maxQsHeight - + mNotificationStackScroller.getMinStackHeight() - - getScrollViewScrollY(); + + mNotificationStackScroller.getMinStackHeight(); totalHeight = Math.max(fullyCollapsedHeight, mNotificationStackScroller.getHeight()); } return (int) totalHeight; } - private int getScrollViewScrollY() { - if (mScrollYOverride != -1 && !mQsTracking) { - return mScrollYOverride; - } else { - return mScrollView.getScrollY(); - } - } private void updateNotificationTranslucency() { float alpha = 1f; if (mClosingWithAlphaFadeOut && !mExpandingFromHeadsUp && !mHeadsUpManager.hasPinnedHeadsUp()) { @@ -1678,18 +1515,9 @@ public class NotificationPanelView extends PanelView implements */ private void updateHeader() { if (mStatusBar.getBarState() == StatusBarState.KEYGUARD) { - updateHeaderKeyguard(); - } else { - updateHeaderShade(); - } - - } - - private void updateHeaderShade() { - if (!mHeaderAnimating) { - mHeader.setTranslationY(getHeaderTranslation()); + updateHeaderKeyguardAlpha(); } - setQsTranslation(mQsExpansionHeight); + updateQsExpansion(); } private float getHeaderTranslation() { @@ -1744,11 +1572,6 @@ public class NotificationPanelView extends PanelView implements && !mDozing ? VISIBLE : INVISIBLE); } - private void updateHeaderKeyguard() { - updateHeaderKeyguardAlpha(); - setQsTranslation(mQsExpansionHeight); - } - private void updateKeyguardBottomAreaAlpha() { float alpha = Math.min(getKeyguardContentsAlpha(), 1 - getQsExpansionFraction()); mKeyguardBottomArea.setAlpha(alpha); @@ -1781,7 +1604,6 @@ public class NotificationPanelView extends PanelView implements mNotificationStackScroller.onExpansionStopped(); mHeadsUpManager.onExpandingFinished(); mIsExpanding = false; - mScrollYOverride = -1; if (isFullyCollapsed()) { DejankUtils.postAfterTraversal(new Runnable() { @Override @@ -1811,9 +1633,8 @@ public class NotificationPanelView extends PanelView implements } private void setListening(boolean listening) { - mHeader.setListening(listening); + mQsContainer.setListening(listening); mKeyguardStatusBar.setListening(listening); - mQsPanel.setListening(listening); } @Override @@ -1931,7 +1752,7 @@ public class NotificationPanelView extends PanelView implements @Override public void onClick(View v) { - if (v == mHeader) { + if (v == mQsContainer.getHeader()) { onQsExpansionStarted(); if (mQsExpanded) { flingSettings(0 /* vel */, false /* expand */, null, true /* isClick */); @@ -2149,24 +1970,16 @@ public class NotificationPanelView extends PanelView implements return mConflictingQsExpansionGesture && mQsExpanded; } - public void notifyVisibleChildrenChanged() { - if (mNotificationStackScroller.getNotGoneChildCount() != 0) { - mReserveNotificationSpace.setVisibility(View.VISIBLE); - } else { - mReserveNotificationSpace.setVisibility(View.GONE); - } - } - public boolean isQsExpanded() { return mQsExpanded; } public boolean isQsDetailShowing() { - return mQsPanel.isShowingDetail(); + return mQsContainer.getQsPanel().isShowingDetail(); } public void closeQsDetail() { - mQsPanel.closeDetail(); + mQsContainer.getQsPanel().closeDetail(); } @Override @@ -2254,7 +2067,7 @@ public class NotificationPanelView extends PanelView implements private final Runnable mUpdateHeader = new Runnable() { @Override public void run() { - mHeader.updateEverything(); + mQsContainer.getHeader().updateEverything(); } }; @@ -2400,8 +2213,7 @@ public class NotificationPanelView extends PanelView implements protected void setVerticalPanelTranslation(float translation) { mNotificationStackScroller.setTranslationX(translation); - mScrollView.setTranslationX(translation); - mHeader.setTranslationX(translation); + mQsContainer.setTranslationX(translation); } private void updateStackHeight(float stackHeight) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java index fd28b095c09d..7cc720df1c27 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java @@ -33,7 +33,7 @@ import com.android.systemui.R; public class NotificationsQuickSettingsContainer extends FrameLayout implements ViewStub.OnInflateListener { - private View mScrollView; + private View mQsContainer; private View mUserSwitcher; private View mStackScroller; private View mKeyguardStatusBar; @@ -47,7 +47,7 @@ public class NotificationsQuickSettingsContainer extends FrameLayout @Override protected void onFinishInflate() { super.onFinishInflate(); - mScrollView = findViewById(R.id.scroll_view); + mQsContainer = findViewById(R.id.quick_settings_container); mStackScroller = findViewById(R.id.notification_stack_scroller); mKeyguardStatusBar = findViewById(R.id.keyguard_header); ViewStub userSwitcher = (ViewStub) findViewById(R.id.keyguard_user_switcher); @@ -58,7 +58,7 @@ public class NotificationsQuickSettingsContainer extends FrameLayout @Override protected void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); - reloadWidth(mScrollView); + reloadWidth(mQsContainer); reloadWidth(mStackScroller); } @@ -80,11 +80,11 @@ public class NotificationsQuickSettingsContainer extends FrameLayout boolean userSwitcherVisible = mInflated && mUserSwitcher.getVisibility() == View.VISIBLE; boolean statusBarVisible = mKeyguardStatusBar.getVisibility() == View.VISIBLE; - View stackQsTop = mQsExpanded ? mStackScroller : mScrollView; - View stackQsBottom = !mQsExpanded ? mStackScroller : mScrollView; + View stackQsTop = mQsExpanded ? mStackScroller : mQsContainer; + View stackQsBottom = !mQsExpanded ? mStackScroller : mQsContainer; // Invert the order of the scroll view and user switcher such that the notifications receive // touches first but the panel gets drawn above. - if (child == mScrollView) { + if (child == mQsContainer) { return super.drawChild(canvas, userSwitcherVisible && statusBarVisible ? mUserSwitcher : statusBarVisible ? mKeyguardStatusBar : userSwitcherVisible ? mUserSwitcher @@ -104,7 +104,7 @@ public class NotificationsQuickSettingsContainer extends FrameLayout return super.drawChild(canvas, stackQsTop, drawingTime); - }else { + } else { return super.drawChild(canvas, child, drawingTime); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java index 50a49a1dbd2c..278dfe6c9314 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java @@ -69,6 +69,7 @@ import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; import android.os.SystemProperties; +import android.os.Trace; import android.os.UserHandle; import android.os.UserManager; import android.os.Vibrator; @@ -88,6 +89,7 @@ import android.view.ThreadedRenderer; import android.view.View; import android.view.ViewGroup; import android.view.ViewGroup.LayoutParams; +import android.view.ViewParent; import android.view.ViewStub; import android.view.WindowManager; import android.view.WindowManagerGlobal; @@ -296,6 +298,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, BrightnessMirrorController mBrightnessMirrorController; AccessibilityController mAccessibilityController; FingerprintUnlockController mFingerprintUnlockController; + LightStatusBarController mLightStatusBarController; int mNaturalBarHeight = -1; @@ -340,6 +343,9 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, private long mKeyguardFadingAwayDelay; private long mKeyguardFadingAwayDuration; + // RemoteInputView to be activated after unlock + private View mPendingRemoteInputView; + int mMaxAllowedKeyguardNotifications; boolean mExpandedVisible; @@ -361,6 +367,8 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, // tracking calls to View.setSystemUiVisibility() int mSystemUiVisibility = View.SYSTEM_UI_FLAG_VISIBLE; + private final Rect mLastFullscreenStackBounds = new Rect(); + private final Rect mLastDockedStackBounds = new Rect(); // last value sent to window manager private int mLastDispatchedSystemUiVisibility = ~View.SYSTEM_UI_FLAG_VISIBLE; @@ -754,19 +762,8 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, mStackScroller.setOverflowContainer(mKeyguardIconOverflowContainer); - mEmptyShadeView = (EmptyShadeView) LayoutInflater.from(mContext).inflate( - R.layout.status_bar_no_notifications, mStackScroller, false); - mStackScroller.setEmptyShadeView(mEmptyShadeView); - mDismissView = (DismissView) LayoutInflater.from(mContext).inflate( - R.layout.status_bar_notification_dismiss_all, mStackScroller, false); - mDismissView.setOnButtonClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - MetricsLogger.action(mContext, MetricsEvent.ACTION_DISMISS_ALL_NOTES); - clearAllNotifications(); - } - }); - mStackScroller.setDismissView(mDismissView); + inflateEmptyShadeView(); + inflateDismissView(); mExpandedContents = mStackScroller; mBackdrop = (BackDropView) mStatusBarWindow.findViewById(R.id.backdrop); @@ -855,6 +852,8 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, mAccessibilityController = new AccessibilityController(mContext); mKeyguardBottomArea.setAccessibilityController(mAccessibilityController); mNextAlarmController = new NextAlarmController(mContext); + mLightStatusBarController = new LightStatusBarController(mIconController, + mBatteryController); mKeyguardMonitor = new KeyguardMonitor(mContext); if (UserManager.get(mContext).isUserSwitcherEnabled()) { mUserSwitcherController = new UserSwitcherController(mContext, mKeyguardMonitor, @@ -930,6 +929,34 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, return mStatusBarView; } + @Override + protected void reInflateViews() { + super.reInflateViews(); + inflateDismissView(); + updateClearAll(); + inflateEmptyShadeView(); + updateEmptyShadeView(); + } + + private void inflateEmptyShadeView() { + mEmptyShadeView = (EmptyShadeView) LayoutInflater.from(mContext).inflate( + R.layout.status_bar_no_notifications, mStackScroller, false); + mStackScroller.setEmptyShadeView(mEmptyShadeView); + } + + private void inflateDismissView() { + mDismissView = (DismissView) LayoutInflater.from(mContext).inflate( + R.layout.status_bar_notification_dismiss_all, mStackScroller, false); + mDismissView.setOnButtonClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + MetricsLogger.action(mContext, MetricsEvent.ACTION_DISMISS_ALL_NOTES); + clearAllNotifications(); + } + }); + mStackScroller.setDismissView(mDismissView); + } + protected void createUserSwitcher() { mKeyguardUserSwitcher = new KeyguardUserSwitcher(mContext, (ViewStub) mStatusBarWindow.findViewById(R.id.keyguard_user_switcher), @@ -1072,6 +1099,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, mFingerprintUnlockController.setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager); mRemoteInputController.addCallback(mStatusBarKeyguardViewManager); mKeyguardViewMediatorCallback = keyguardViewMediator.getViewMediatorCallback(); + mLightStatusBarController.setFingerprintUnlockController(mFingerprintUnlockController); } @Override @@ -1483,8 +1511,8 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, } private void updateNotificationShadeForChildren() { + // First let's remove all children which don't belong in the parents ArrayList<ExpandableNotificationRow> toRemove = new ArrayList<>(); - boolean orderChanged = false; for (int i = 0; i < mStackScroller.getChildCount(); i++) { View view = mStackScroller.getChildAt(i); if (!(view instanceof ExpandableNotificationRow)) { @@ -1496,7 +1524,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, List<ExpandableNotificationRow> children = parent.getNotificationChildren(); List<ExpandableNotificationRow> orderedChildren = mTmpChildOrderMap.get(parent); - // lets first remove all undesired children if (children != null) { toRemove.clear(); for (ExpandableNotificationRow childRow : children) { @@ -1509,8 +1536,21 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, mStackScroller.notifyGroupChildRemoved(remove); } } + } + + // Let's now add all notification children which are missing + boolean orderChanged = false; + for (int i = 0; i < mStackScroller.getChildCount(); i++) { + View view = mStackScroller.getChildAt(i); + if (!(view instanceof ExpandableNotificationRow)) { + // We don't care about non-notification views. + continue; + } + + ExpandableNotificationRow parent = (ExpandableNotificationRow) view; + List<ExpandableNotificationRow> children = parent.getNotificationChildren(); + List<ExpandableNotificationRow> orderedChildren = mTmpChildOrderMap.get(parent); - // We now add all the children which are not in there already for (int childIndex = 0; orderedChildren != null && childIndex < orderedChildren.size(); childIndex++) { ExpandableNotificationRow childView = orderedChildren.get(childIndex); @@ -1600,12 +1640,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, } @Override - protected void updateRowStates() { - super.updateRowStates(); - mNotificationPanel.notifyVisibleChildrenChanged(); - } - - @Override protected void setAreThereNotifications() { if (SPEW) { @@ -2227,7 +2261,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, @Override public void maybeEscalateHeadsUp() { - TreeSet<HeadsUpManager.HeadsUpEntry> entries = mHeadsUpManager.getSortedEntries(); + Collection<HeadsUpManager.HeadsUpEntry> entries = mHeadsUpManager.getAllEntries(); for (HeadsUpManager.HeadsUpEntry entry : entries) { final StatusBarNotification sbn = entry.entry.notification; final Notification notification = sbn.getNotification(); @@ -2513,7 +2547,8 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, } @Override // CommandQueue - public void setSystemUiVisibility(int vis, int mask) { + public void setSystemUiVisibility(int vis, int fullscreenStackVis, int dockedStackVis, + int mask, Rect fullscreenStackBounds, Rect dockedStackBounds) { final int oldVal = mSystemUiVisibility; final int newVal = (oldVal&~mask) | (vis&mask); final int diff = newVal ^ oldVal; @@ -2522,6 +2557,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, Integer.toHexString(vis), Integer.toHexString(mask), Integer.toHexString(oldVal), Integer.toHexString(newVal), Integer.toHexString(diff))); + boolean sbModeChanged = false; if (diff != 0) { // we never set the recents bit via this method, so save the prior state to prevent // clobbering the bit below @@ -2555,7 +2591,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, oldVal, newVal, mNavigationBarView.getBarTransitions(), View.NAVIGATION_BAR_TRANSIENT, View.NAVIGATION_BAR_TRANSLUCENT, View.NAVIGATION_BAR_TRANSPARENT); - final boolean sbModeChanged = sbMode != -1; + sbModeChanged = sbMode != -1; final boolean nbModeChanged = nbMode != -1; boolean checkBarModes = false; if (sbModeChanged && sbMode != mStatusBarMode) { @@ -2582,18 +2618,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, mSystemUiVisibility &= ~View.NAVIGATION_BAR_UNHIDE; } - if ((diff & View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR) != 0 || sbModeChanged) { - boolean isTransparentBar = (mStatusBarMode == MODE_TRANSPARENT - || mStatusBarMode == MODE_LIGHTS_OUT_TRANSPARENT); - boolean allowLight = isTransparentBar && !mBatteryController.isPowerSave(); - boolean light = (vis & View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR) != 0; - boolean animate = mFingerprintUnlockController == null - || (mFingerprintUnlockController.getMode() - != FingerprintUnlockController.MODE_WAKE_AND_UNLOCK_PULSING - && mFingerprintUnlockController.getMode() - != FingerprintUnlockController.MODE_WAKE_AND_UNLOCK); - mIconController.setIconsDark(allowLight && light, animate); - } // restore the recents bit if (wasRecentsVisible) { mSystemUiVisibility |= View.RECENT_APPS_VISIBLE; @@ -2602,6 +2626,9 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, // send updated sysui visibility to window manager notifyUiVisibilityChanged(mSystemUiVisibility); } + + mLightStatusBarController.onSystemUiVisibilityChanged(fullscreenStackVis, dockedStackVis, + mask, fullscreenStackBounds, dockedStackBounds, sbModeChanged, mStatusBarMode); } private int computeBarMode(int oldVis, int newVis, BarTransitions transitions, @@ -2729,9 +2756,12 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, public void setLightsOn(boolean on) { Log.v(TAG, "setLightsOn(" + on + ")"); if (on) { - setSystemUiVisibility(0, View.SYSTEM_UI_FLAG_LOW_PROFILE); + setSystemUiVisibility(0, 0, 0, View.SYSTEM_UI_FLAG_LOW_PROFILE, + mLastFullscreenStackBounds, mLastDockedStackBounds); } else { - setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE, View.SYSTEM_UI_FLAG_LOW_PROFILE); + setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE, 0, 0, + View.SYSTEM_UI_FLAG_LOW_PROFILE, mLastFullscreenStackBounds, + mLastDockedStackBounds); } } @@ -3553,6 +3583,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, mDraggedDownRow.notifyHeightChanged(false /* needsAnimation */); mDraggedDownRow = null; } + mPendingRemoteInputView = null; mAssistManager.onLockscreenShown(); } @@ -3669,6 +3700,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, public boolean hideKeyguard() { boolean staying = mLeaveOpenOnKeyguardHide; setBarState(StatusBarState.SHADE); + View viewToClick = null; if (mLeaveOpenOnKeyguardHide) { mLeaveOpenOnKeyguardHide = false; long delay = calculateGoingToFullShadeDelay(); @@ -3677,6 +3709,8 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, mDraggedDownRow.setUserLocked(false); mDraggedDownRow = null; } + viewToClick = mPendingRemoteInputView; + mPendingRemoteInputView = null; // Disable layout transitions in navbar for this transition because the load is just // too heavy for the CPU and GPU on any device. @@ -3694,6 +3728,10 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, } updateKeyguardState(staying, false /* fromShadeLocked */); + if (viewToClick != null) { + viewToClick.callOnClick(); + } + // Keyguard state has changed, but QS is not listening anymore. Make sure to update the tile // visibilities so next time we open the panel we know the correct height already. if (mQSPanel != null) { @@ -4057,6 +4095,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, mLeaveOpenOnKeyguardHide = true; showBouncer(); mDraggedDownRow = row; + mPendingRemoteInputView = null; } else { mNotificationPanel.animateToFullShade(0 /* delay */); setBarState(StatusBarState.SHADE_LOCKED); @@ -4065,6 +4104,13 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, } @Override + protected void onLockedRemoteInput(ExpandableNotificationRow row, View clicked) { + mLeaveOpenOnKeyguardHide = true; + showBouncer(); + mPendingRemoteInputView = clicked; + } + + @Override public void onExpandClicked(Entry clickedEntry, boolean nowExpanded) { mHeadsUpManager.setExpanded(clickedEntry, nowExpanded); if (mState == StatusBarState.KEYGUARD && nowExpanded) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java index 45aae2dd61f0..f61f31e814f7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java @@ -22,6 +22,7 @@ import android.content.Context; import android.content.res.ColorStateList; import android.content.res.Resources; import android.graphics.Color; +import android.graphics.Rect; import android.graphics.drawable.Icon; import android.os.Bundle; import android.os.Handler; @@ -58,8 +59,8 @@ import java.util.ArrayList; public class StatusBarIconController extends StatusBarIconList implements Tunable { public static final long DEFAULT_TINT_ANIMATION_DURATION = 120; - public static final String ICON_BLACKLIST = "icon_blacklist"; + public static final int DEFAULT_ICON_TINT = Color.WHITE; private Context mContext; private PhoneStatusBar mPhoneStatusBar; @@ -79,8 +80,11 @@ public class StatusBarIconController extends StatusBarIconList implements Tunabl private int mIconSize; private int mIconHPadding; - private int mIconTint = Color.WHITE; + private int mIconTint = DEFAULT_ICON_TINT; private float mDarkIntensity; + private final Rect mTintArea = new Rect(); + private static final Rect sTmpRect = new Rect(); + private static final int[] sTmpInt2 = new int[2]; private boolean mTransitionPending; private boolean mTintChangePending; @@ -395,6 +399,25 @@ public class StatusBarIconController extends StatusBarIconList implements Tunabl } } + /** + * Sets the dark area so {@link #setIconsDark} only affects the icons in the specified area. + * + * @param darkArea the area in which icons should change it's tint, in logical screen + * coordinates + */ + public void setIconsDarkArea(Rect darkArea) { + if (darkArea == null && mTintArea.isEmpty()) { + return; + } + if (darkArea == null) { + mTintArea.setEmpty(); + } else { + mTintArea.set(darkArea); + } + applyIconTint(); + mNotificationIconAreaController.setTintArea(darkArea); + } + public void setIconsDark(boolean dark, boolean animate) { if (!animate) { setIconTintInternal(dark ? 1.0f : 0.0f); @@ -446,14 +469,60 @@ public class StatusBarIconController extends StatusBarIconList implements Tunabl mPendingDarkIntensity = darkIntensity; } + /** + * @return the tint to apply to {@param view} depending on the desired tint {@param color} and + * the screen {@param tintArea} in which to apply that tint + */ + public static int getTint(Rect tintArea, View view, int color) { + if (isInArea(tintArea, view)) { + return color; + } else { + return DEFAULT_ICON_TINT; + } + } + + /** + * @return the dark intensity to apply to {@param view} depending on the desired dark + * {@param intensity} and the screen {@param tintArea} in which to apply that intensity + */ + public static float getDarkIntensity(Rect tintArea, View view, float intensity) { + if (isInArea(tintArea, view)) { + return intensity; + } else { + return 0f; + } + } + + /** + * @return true if more than half of the {@param view} area are in {@param area}, false + * otherwise + */ + private static boolean isInArea(Rect area, View view) { + if (area.isEmpty()) { + return true; + } + sTmpRect.set(area); + view.getLocationOnScreen(sTmpInt2); + int left = sTmpInt2[0]; + + int intersectStart = Math.max(left, area.left); + int intersectEnd = Math.min(left + view.getWidth(), area.right); + int intersectAmount = Math.max(0, intersectEnd - intersectStart); + + boolean coversFullStatusBar = area.top <= 0; + boolean majorityOfWidth = 2 * intersectAmount > view.getWidth(); + return majorityOfWidth && coversFullStatusBar; + } + private void applyIconTint() { for (int i = 0; i < mStatusIcons.getChildCount(); i++) { StatusBarIconView v = (StatusBarIconView) mStatusIcons.getChildAt(i); - v.setImageTintList(ColorStateList.valueOf(mIconTint)); + v.setImageTintList(ColorStateList.valueOf(getTint(mTintArea, v, mIconTint))); } - mSignalCluster.setIconTint(mIconTint, mDarkIntensity); - mBatteryMeterView.setDarkIntensity(mDarkIntensity); - mClock.setTextColor(mIconTint); + mSignalCluster.setIconTint(mIconTint, mDarkIntensity, mTintArea); + mBatteryMeterView.setDarkIntensity( + isInArea(mTintArea, mBatteryMeterView) ? mDarkIntensity : 0); + mClock.setTextColor(getTint(mTintArea, mClock, mIconTint)); } public void appTransitionPending() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java index f06552283219..ab817127564c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java @@ -39,10 +39,10 @@ import com.android.systemui.statusbar.phone.PhoneStatusBar; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Stack; -import java.util.TreeSet; /** * A manager which handles heads up notifications which is a special mode where @@ -90,7 +90,6 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL private int mSnoozeLengthMs; private ContentObserver mSettingsObserver; private HashMap<String, HeadsUpEntry> mHeadsUpEntries = new HashMap<>(); - private TreeSet<HeadsUpEntry> mSortedEntries = new TreeSet<>(); private HashSet<String> mSwipedOutKeys = new HashSet<>(); private int mUser; private Clock mClock; @@ -230,7 +229,6 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL private void removeHeadsUpEntry(NotificationData.Entry entry) { HeadsUpEntry remove = mHeadsUpEntries.remove(entry.key); - mSortedEntries.remove(remove); entry.row.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); entry.row.setHeadsUp(false); setEntryPinned(remove, false /* isPinned */); @@ -345,12 +343,21 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL return mHeadsUpEntries.get(key).entry; } - public TreeSet<HeadsUpEntry> getSortedEntries() { - return mSortedEntries; + public Collection<HeadsUpEntry> getAllEntries() { + return mHeadsUpEntries.values(); } public HeadsUpEntry getTopEntry() { - return mSortedEntries.isEmpty() ? null : mSortedEntries.first(); + if (mHeadsUpEntries.isEmpty()) { + return null; + } + HeadsUpEntry topEntry = null; + for (HeadsUpEntry entry: mHeadsUpEntries.values()) { + if (topEntry == null || entry.compareTo(topEntry) == -1) { + topEntry = entry; + } + } + return topEntry; } /** @@ -374,26 +381,18 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL return; } if (mHasPinnedNotification) { - int minX = 0; - int maxX = 0; - int maxY = 0; - for (HeadsUpEntry entry : mSortedEntries) { - ExpandableNotificationRow row = entry.entry.row; - if (row.isPinned()) { - if (row.isChildInGroup()) { - final ExpandableNotificationRow groupSummary - = mGroupManager.getGroupSummary(row.getStatusBarNotification()); - if (groupSummary != null) { - row = groupSummary; - } - } - row.getLocationOnScreen(mTmpTwoArray); - minX = mTmpTwoArray[0]; - maxX = mTmpTwoArray[0] + row.getWidth(); - maxY = row.getIntrinsicHeight(); - break; + ExpandableNotificationRow topEntry = getTopEntry().entry.row; + if (topEntry.isChildInGroup()) { + final ExpandableNotificationRow groupSummary + = mGroupManager.getGroupSummary(topEntry.getStatusBarNotification()); + if (groupSummary != null) { + topEntry = groupSummary; } } + topEntry.getLocationOnScreen(mTmpTwoArray); + int minX = mTmpTwoArray[0]; + int maxX = mTmpTwoArray[0] + topEntry.getWidth(); + int maxY = topEntry.getIntrinsicHeight(); info.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION); info.touchableRegion.set(minX, 0, maxX, maxY); @@ -413,7 +412,7 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL pw.print(" mSnoozeLengthMs="); pw.println(mSnoozeLengthMs); pw.print(" now="); pw.println(SystemClock.elapsedRealtime()); pw.print(" mUser="); pw.println(mUser); - for (HeadsUpEntry entry: mSortedEntries) { + for (HeadsUpEntry entry: mHeadsUpEntries.values()) { pw.print(" HeadsUpEntry="); pw.println(entry.entry); } int N = mSnoozedPackages.size(); @@ -633,7 +632,6 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL } public void updateEntry(boolean updatePostTime) { - mSortedEntries.remove(HeadsUpEntry.this); long currentTime = mClock.currentTimeMillis(); earliestRemovaltime = currentTime + mMinimumDisplayTime; if (updatePostTime) { @@ -648,7 +646,6 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL long removeDelay = Math.max(finishTime - currentTime, mMinimumDisplayTime); mHandler.postDelayed(mRemoveHeadsUpRunnable, removeDelay); } - mSortedEntries.add(HeadsUpEntry.this); } private boolean isSticky() { @@ -658,6 +655,13 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL @Override public int compareTo(HeadsUpEntry o) { + boolean isPinned = entry.row.isPinned(); + boolean otherPinned = o.entry.row.isPinned(); + if (isPinned && !otherPinned) { + return -1; + } else if (!isPinned && otherPinned) { + return 1; + } boolean selfFullscreen = hasFullScreenIntent(entry); boolean otherFullscreen = hasFullScreenIntent(o.entry); if (selfFullscreen && !otherFullscreen) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java index 5cfcd8981890..49aec423db1c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java @@ -43,16 +43,16 @@ public class NotificationChildrenContainer extends ViewGroup { private static final int NUMBER_OF_CHILDREN_WHEN_SYSTEM_EXPANDED = 5; private static final int NUMBER_OF_CHILDREN_WHEN_CHILDREN_EXPANDED = 8; - private final int mChildPadding; - private final int mDividerHeight; - private final int mMaxNotificationHeight; private final List<View> mDividers = new ArrayList<>(); private final List<ExpandableNotificationRow> mChildren = new ArrayList<>(); - private final int mNotificationHeaderHeight; - private final int mNotificationAppearDistance; - private final int mNotificatonTopPadding; private final HybridNotificationViewManager mHybridViewManager; - private final float mCollapsedBottompadding; + private int mChildPadding; + private int mDividerHeight; + private int mMaxNotificationHeight; + private int mNotificationHeaderHeight; + private int mNotificationAppearDistance; + private int mNotificatonTopPadding; + private float mCollapsedBottompadding; private ViewInvertHelper mOverflowInvertHelper; private boolean mChildrenExpanded; private ExpandableNotificationRow mNotificationParent; @@ -76,6 +76,11 @@ public class NotificationChildrenContainer extends ViewGroup { public NotificationChildrenContainer(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); + initDimens(); + mHybridViewManager = new HybridNotificationViewManager(getContext(), this); + } + + private void initDimens() { mChildPadding = getResources().getDimensionPixelSize( R.dimen.notification_children_padding); mDividerHeight = Math.max(1, getResources().getDimensionPixelSize( @@ -89,7 +94,6 @@ public class NotificationChildrenContainer extends ViewGroup { mNotificatonTopPadding = getResources().getDimensionPixelSize( R.dimen.notification_children_container_top_padding); mCollapsedBottompadding = 11.5f * getResources().getDisplayMetrics().density; - mHybridViewManager = new HybridNotificationViewManager(getContext(), this); } @Override @@ -461,4 +465,16 @@ public class NotificationChildrenContainer extends ViewGroup { mOverflowInvertHelper.setInverted(dark, fade, delay); } } + + public void reInflateViews() { + initDimens(); + for (int i = 0; i < mDividers.size(); i++) { + View prevDivider = mDividers.get(i); + int index = indexOfChild(prevDivider); + removeView(prevDivider); + View divider = inflateDivider(); + addView(divider, index); + mDividers.set(i, divider); + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java index 49e9c3db9f30..bf4245b5e7ed 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java @@ -213,7 +213,6 @@ public class NotificationStackScrollLayout extends ViewGroup * animating. */ private boolean mOnlyScrollingInThisMotion; - private ViewGroup mScrollView; private boolean mInterceptDelegateEnabled; private boolean mDelegateToScrollView; private boolean mDisallowScrollingInThisMotion; @@ -281,6 +280,7 @@ public class NotificationStackScrollLayout extends ViewGroup setDimAmount((Float) animation.getAnimatedValue()); } }; + private ViewGroup mQsContainer; public NotificationStackScrollLayout(Context context) { this(context, null); @@ -630,12 +630,8 @@ public class NotificationStackScrollLayout extends ViewGroup mLongPressListener = listener; } - public void setScrollView(ViewGroup scrollView) { - mScrollView = scrollView; - } - - public void setInterceptDelegateEnabled(boolean interceptDelegateEnabled) { - mInterceptDelegateEnabled = interceptDelegateEnabled; + public void setQsContainer(ViewGroup qsContainer) { + mQsContainer = qsContainer; } public void onChildDismissed(View v) { @@ -883,13 +879,6 @@ public class NotificationStackScrollLayout extends ViewGroup public boolean onTouchEvent(MotionEvent ev) { boolean isCancelOrUp = ev.getActionMasked() == MotionEvent.ACTION_CANCEL || ev.getActionMasked()== MotionEvent.ACTION_UP; - if (mDelegateToScrollView) { - if (isCancelOrUp) { - mDelegateToScrollView = false; - } - transformTouchEvent(ev, this, mScrollView); - return mScrollView.onTouchEvent(ev); - } handleEmptySpaceClick(ev); boolean expandWantsIt = false; if (mIsExpanded && !mSwipingInProgress && !mOnlyScrollingInThisMotion) { @@ -929,6 +918,9 @@ public class NotificationStackScrollLayout extends ViewGroup if (!isScrollingEnabled()) { return false; } + if (ev.getY() < mQsContainer.getBottom()) { + return false; + } initVelocityTrackerIfNotExists(); mVelocityTracker.addMovement(ev); @@ -1776,15 +1768,13 @@ public class NotificationStackScrollLayout extends ViewGroup * account. * * @param qsHeight the top padding imposed by the quick settings panel - * @param scrollY how much the notifications are scrolled inside the QS/notifications scroll - * container * @param animate whether to animate the change * @param ignoreIntrinsicPadding if true, {@link #getIntrinsicPadding()} is ignored and * {@code qsHeight} is the final top padding */ - public void updateTopPadding(float qsHeight, int scrollY, boolean animate, + public void updateTopPadding(float qsHeight, boolean animate, boolean ignoreIntrinsicPadding) { - float start = qsHeight - scrollY; + float start = qsHeight; float stackHeight = getHeight() - start; int minStackHeight = getMinStackHeight(); if (stackHeight <= minStackHeight) { @@ -1867,15 +1857,6 @@ public class NotificationStackScrollLayout extends ViewGroup @Override public boolean onInterceptTouchEvent(MotionEvent ev) { - if (mInterceptDelegateEnabled) { - transformTouchEvent(ev, this, mScrollView); - if (mScrollView.onInterceptTouchEvent(ev)) { - mDelegateToScrollView = true; - removeLongPressCallback(); - return true; - } - transformTouchEvent(ev, mScrollView, this); - } initDownStates(ev); handleEmptySpaceClick(ev); boolean expandWantsIt = false; @@ -2157,7 +2138,7 @@ public class NotificationStackScrollLayout extends ViewGroup } private void updateAnimationState(View child) { - updateAnimationState((mAnimationsEnabled || isPinnedHeadsUp(child)) && mIsExpanded, child); + updateAnimationState(mAnimationsEnabled && (mIsExpanded || isPinnedHeadsUp(child)), child); } @@ -2893,13 +2874,23 @@ public class NotificationStackScrollLayout extends ViewGroup } public void setDismissView(DismissView dismissView) { + int index = -1; + if (mDismissView != null) { + index = indexOfChild(mDismissView); + removeView(mDismissView); + } mDismissView = dismissView; - addView(mDismissView); + addView(mDismissView, index); } public void setEmptyShadeView(EmptyShadeView emptyShadeView) { + int index = -1; + if (mEmptyShadeView != null) { + index = indexOfChild(mEmptyShadeView); + removeView(mEmptyShadeView); + } mEmptyShadeView = emptyShadeView; - addView(mEmptyShadeView); + addView(mEmptyShadeView, index); } public void updateEmptyShadeView(boolean visible) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java index 784f610e40a1..110258c360dc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java @@ -17,6 +17,7 @@ package com.android.systemui.statusbar.tv; import android.content.ComponentName; +import android.graphics.Rect; import android.os.IBinder; import android.service.notification.NotificationListenerService.RankingMap; import android.service.notification.StatusBarNotification; @@ -68,7 +69,8 @@ public class TvStatusBar extends BaseStatusBar { } @Override - public void setSystemUiVisibility(int vis, int mask) { + public void setSystemUiVisibility(int vis, int fullscreenStackVis, int dockedStackVis, + int mask, Rect fullscreenStackBounds, Rect dockedStackBounds) { } @Override diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityGestureDetector.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityGestureDetector.java index b79a7ba69b4e..ad70853f13ba 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityGestureDetector.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityGestureDetector.java @@ -110,9 +110,25 @@ class AccessibilityGestureDetector extends GestureDetector.SimpleOnGestureListen // The minimal score for accepting a predicted gesture. private static final float MIN_PREDICTION_SCORE = 2.0f; + // Distance a finger must travel before we decide if it is a gesture or not. private static final int GESTURE_CONFIRM_MM = 10; - private static final long CANCEL_ON_PAUSE_THRESHOLD_STARTED_MS = 1000; - private static final long CANCEL_ON_PAUSE_THRESHOLD_NOT_STARTED_MS = 500; + + // Time threshold used to determine if an interaction is a gesture or not. + // If the first movement of 1cm takes longer than this value, we assume it's + // a slow movement, and therefore not a gesture. + // + // This value was determined by measuring the time for the first 1cm + // movement when gesturing, and touch exploring. Based on user testing, + // all gestures started with the initial movement taking less than 100ms. + // When touch exploring, the first movement almost always takes longer than + // 200ms. From this data, 150ms seems the best value to decide what + // kind of interaction it is. + private static final long CANCEL_ON_PAUSE_THRESHOLD_NOT_STARTED_MS = 150; + + // Time threshold used to determine if a gesture should be cancelled. If + // the finger pauses for longer than this delay, the ongoing gesture is + // cancelled. + private static final long CANCEL_ON_PAUSE_THRESHOLD_STARTED_MS = 500; AccessibilityGestureDetector(Context context, Listener listener) { mListener = listener; diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java index 830b0f27f31b..f8bf59d29f48 100644 --- a/services/backup/java/com/android/server/backup/BackupManagerService.java +++ b/services/backup/java/com/android/server/backup/BackupManagerService.java @@ -87,6 +87,7 @@ import android.util.ArrayMap; import android.util.AtomicFile; import android.util.EventLog; import android.util.Log; +import android.util.Pair; import android.util.Slog; import android.util.SparseArray; import android.util.StringBuilderPrinter; @@ -142,6 +143,7 @@ import java.util.TreeMap; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; import java.util.zip.Deflater; import java.util.zip.DeflaterOutputStream; import java.util.zip.InflaterInputStream; @@ -786,8 +788,9 @@ public class BackupManagerService { case MSG_OP_COMPLETE: { try { - BackupRestoreTask task = (BackupRestoreTask) msg.obj; - task.operationComplete(msg.arg1); + Pair<BackupRestoreTask, Long> taskWithResult = + (Pair<BackupRestoreTask, Long>) msg.obj; + taskWithResult.first.operationComplete(taskWithResult.second); } catch (ClassCastException e) { Slog.e(TAG, "Invalid completion in flight, obj=" + msg.obj); } @@ -1044,7 +1047,7 @@ public class BackupManagerService { // If Encrypted file systems is enabled or disabled, this call will return the // correct directory. - mBaseStateDir = new File(Environment.getSecureDataDirectory(), "backup"); + mBaseStateDir = new File(Environment.getDataDirectory(), "backup"); mBaseStateDir.mkdirs(); if (!SELinux.restorecon(mBaseStateDir)) { Slog.e(TAG, "SELinux restorecon failed on " + mBaseStateDir); @@ -2460,7 +2463,7 @@ public class BackupManagerService { void execute(); // An operation that wanted a callback has completed - void operationComplete(int result); + void operationComplete(long result); // An operation that wanted a callback has timed out void handleTimeout(); @@ -3095,7 +3098,7 @@ public class BackupManagerService { } @Override - public void operationComplete(int unusedResult) { + public void operationComplete(long unusedResult) { // The agent reported back to us! if (mBackupData == null) { @@ -3494,7 +3497,7 @@ public class BackupManagerService { */ int preflightFullBackup(PackageInfo pkg, IBackupAgent agent); - long expectedSize(); + long getExpectedSizeOrErrorCode(); }; class FullBackupEngine { @@ -4532,7 +4535,7 @@ public class BackupManagerService { // a standalone thread. The runner owns this half of the pipe, and closes // it to indicate EOD to the other end. class SinglePackageBackupPreflight implements BackupRestoreTask, FullBackupPreflight { - final AtomicInteger mResult = new AtomicInteger(); + final AtomicLong mResult = new AtomicLong(); final CountDownLatch mLatch = new CountDownLatch(1); final IBackupTransport mTransport; @@ -4554,7 +4557,11 @@ public class BackupManagerService { // now wait to get our result back mLatch.await(); - int totalSize = mResult.get(); + long totalSize = mResult.get(); + // If preflight timeouted, mResult will contain error code as int. + if (totalSize < 0) { + return (int) totalSize; + } if (MORE_DEBUG) { Slog.v(TAG, "Got preflight response; size=" + totalSize); } @@ -4581,7 +4588,7 @@ public class BackupManagerService { } @Override - public void operationComplete(int result) { + public void operationComplete(long result) { // got the callback, and our preflightFullBackup() method is waiting for the result if (MORE_DEBUG) { Slog.i(TAG, "Preflight op complete, result=" + result); @@ -4600,7 +4607,7 @@ public class BackupManagerService { } @Override - public long expectedSize() { + public long getExpectedSizeOrErrorCode() { try { mLatch.await(); return mResult.get(); @@ -4649,7 +4656,7 @@ public class BackupManagerService { } long expectedSize() { - return mPreflight.expectedSize(); + return mPreflight.getExpectedSizeOrErrorCode(); } } } @@ -8558,7 +8565,7 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF } @Override - public void operationComplete(int unusedResult) { + public void operationComplete(long unusedResult) { if (MORE_DEBUG) { Slog.i(TAG, "operationComplete() during restore: target=" + mCurrentPackage.packageName @@ -9643,9 +9650,8 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF // The completion callback, if any, is invoked on the handler if (op != null && op.callback != null) { - Message msg = mBackupHandler.obtainMessage(MSG_OP_COMPLETE, op.callback); - // NB: this cannot distinguish between results > 2 gig - msg.arg1 = (result > Integer.MAX_VALUE) ? Integer.MAX_VALUE : (int) result; + Pair<BackupRestoreTask, Long> callbackAndResult = Pair.create(op.callback, result); + Message msg = mBackupHandler.obtainMessage(MSG_OP_COMPLETE, callbackAndResult); mBackupHandler.sendMessage(msg); } } diff --git a/services/backup/java/com/android/server/backup/Trampoline.java b/services/backup/java/com/android/server/backup/Trampoline.java index bbf881be102f..e74526338a8f 100644 --- a/services/backup/java/com/android/server/backup/Trampoline.java +++ b/services/backup/java/com/android/server/backup/Trampoline.java @@ -54,7 +54,7 @@ public class Trampoline extends IBackupManager.Stub { public Trampoline(Context context) { mContext = context; - File dir = new File(Environment.getSecureDataDirectory(), "backup"); + File dir = new File(Environment.getDataDirectory(), "backup"); dir.mkdirs(); mSuppressFile = new File(dir, BACKUP_SUPPRESS_FILENAME); mGlobalDisable = SystemProperties.getBoolean(BACKUP_DISABLE_PROPERTY, false); diff --git a/services/core/java/com/android/server/AppOpsService.java b/services/core/java/com/android/server/AppOpsService.java index a4455e91f294..07d472dc5375 100644 --- a/services/core/java/com/android/server/AppOpsService.java +++ b/services/core/java/com/android/server/AppOpsService.java @@ -16,6 +16,8 @@ package com.android.server; +import static android.content.pm.ApplicationInfo.FLAG_SUSPENDED; + import java.io.File; import java.io.FileDescriptor; import java.io.FileInputStream; @@ -882,6 +884,11 @@ public class AppOpsService extends IAppOpsService.Stub { @Override public int checkAudioOperation(int code, int usage, int uid, String packageName) { + if (isApplicationSuspended(packageName, uid)) { + Log.i(TAG, "Audio disabled for suspended package=" + packageName + " for uid=" + uid); + return AppOpsManager.MODE_IGNORED; + } + synchronized (this) { final int mode = checkRestrictionLocked(code, usage, uid, packageName); if (mode != AppOpsManager.MODE_ALLOWED) { @@ -891,6 +898,23 @@ public class AppOpsService extends IAppOpsService.Stub { return checkOperation(code, uid, packageName); } + private boolean isApplicationSuspended(String pkg, int uid) { + int userId = UserHandle.getUserId(uid); + + ApplicationInfo ai; + try { + ai = AppGlobals.getPackageManager().getApplicationInfo(pkg, 0, userId); + if (ai == null) { + Log.w(TAG, "No application info for package " + pkg + " and user " + userId); + return false; + } + } catch (RemoteException re) { + throw new SecurityException("Could not talk to package manager service"); + } + + return ((ai.flags & FLAG_SUSPENDED) != 0); + } + private int checkRestrictionLocked(int code, int usage, int uid, String packageName) { final SparseArray<Restriction> usageRestrictions = mAudioRestrictions.get(code); if (usageRestrictions != null) { diff --git a/services/core/java/com/android/server/LockSettingsService.java b/services/core/java/com/android/server/LockSettingsService.java index 4dbb49005c28..c318140ae7e8 100644 --- a/services/core/java/com/android/server/LockSettingsService.java +++ b/services/core/java/com/android/server/LockSettingsService.java @@ -17,6 +17,7 @@ package com.android.server; import android.app.ActivityManagerNative; +import android.app.KeyguardManager; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; @@ -34,6 +35,7 @@ import android.content.pm.UserInfo; import android.content.res.Resources; import static android.Manifest.permission.ACCESS_KEYGUARD_SECURE_STORAGE; +import static android.content.Context.KEYGUARD_SERVICE; import static android.content.Context.USER_SERVICE; import static android.Manifest.permission.READ_CONTACTS; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT; @@ -124,7 +126,7 @@ public class LockSettingsService extends ILockSettings.Stub { @Override public void onBootPhase(int phase) { if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) { - mLockSettingsService.maybeShowEncryptionNotification(UserHandle.ALL); + mLockSettingsService.maybeShowEncryptionNotifications(); } else if (phase == SystemService.PHASE_BOOT_COMPLETED) { // TODO } @@ -176,22 +178,48 @@ public class LockSettingsService extends ILockSettings.Stub { * If the account is credential-encrypted, show notification requesting the user to unlock * the device. */ - private void maybeShowEncryptionNotification(UserHandle userHandle) { - if (UserHandle.ALL.equals(userHandle)) { - final List<UserInfo> users = mUserManager.getUsers(); - for (int i = 0; i < users.size(); i++) { - UserHandle user = users.get(i).getUserHandle(); - if (!mUserManager.isUserUnlocked(user)) { - showEncryptionNotification(user); + private void maybeShowEncryptionNotifications() { + final List<UserInfo> users = mUserManager.getUsers(); + for (int i = 0; i < users.size(); i++) { + UserInfo user = users.get(i); + UserHandle userHandle = user.getUserHandle(); + if (!mUserManager.isUserUnlocked(userHandle)) { + if (!user.isManagedProfile()) { + showEncryptionNotification(userHandle); + } else { + UserInfo parent = mUserManager.getProfileParent(user.id); + if (parent != null && mUserManager.isUserUnlocked(parent.getUserHandle())) { + // Only show notifications for managed profiles once their parent + // user is unlocked. + showEncryptionNotificationForProfile(userHandle); + } } } - } else if (!mUserManager.isUserUnlocked(userHandle)){ - showEncryptionNotification(userHandle); } } + private void showEncryptionNotificationForProfile(UserHandle user) { + Resources r = mContext.getResources(); + CharSequence title = r.getText( + com.android.internal.R.string.user_encrypted_title); + CharSequence message = r.getText( + com.android.internal.R.string.profile_encrypted_message); + CharSequence detail = r.getText( + com.android.internal.R.string.profile_encrypted_detail); + + final KeyguardManager km = (KeyguardManager) mContext.getSystemService(KEYGUARD_SERVICE); + final Intent unlockIntent = km.createConfirmDeviceCredentialIntent(null, null, user.getIdentifier()); + if (unlockIntent == null) { + return; + } + unlockIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); + PendingIntent intent = PendingIntent.getActivity(mContext, 0, unlockIntent, + PendingIntent.FLAG_UPDATE_CURRENT); + + showEncryptionNotification(user, title, message, detail, intent); + } + private void showEncryptionNotification(UserHandle user) { - if (DEBUG) Slog.v(TAG, "showing encryption notification, user: " + user.getIdentifier()); Resources r = mContext.getResources(); CharSequence title = r.getText( com.android.internal.R.string.user_encrypted_title); @@ -203,6 +231,12 @@ public class LockSettingsService extends ILockSettings.Stub { PendingIntent intent = PendingIntent.getBroadcast(mContext, 0, ACTION_NULL, PendingIntent.FLAG_UPDATE_CURRENT); + showEncryptionNotification(user, title, message, detail, intent); + } + + private void showEncryptionNotification(UserHandle user, CharSequence title, CharSequence message, + CharSequence detail, PendingIntent intent) { + if (DEBUG) Slog.v(TAG, "showing encryption notification, user: " + user.getIdentifier()); Notification notification = new Notification.Builder(mContext) .setSmallIcon(com.android.internal.R.drawable.ic_user_secure) .setWhen(0) @@ -230,8 +264,21 @@ public class LockSettingsService extends ILockSettings.Stub { hideEncryptionNotification(new UserHandle(userId)); } - public void onUnlockUser(int userHandle) { - hideEncryptionNotification(new UserHandle(userHandle)); + public void onUnlockUser(int userId) { + hideEncryptionNotification(new UserHandle(userId)); + + // Now we have unlocked the parent user we should show notifications + // about any profiles that exist. + List<UserInfo> profiles = mUserManager.getProfiles(userId); + for (int i = 0; i < profiles.size(); i++) { + UserInfo profile = profiles.get(i); + if (profile.isManagedProfile()) { + UserHandle userHandle = profile.getUserHandle(); + if (!mUserManager.isUserUnlocked(userHandle)) { + showEncryptionNotificationForProfile(userHandle); + } + } + } } private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { diff --git a/services/core/java/com/android/server/MountService.java b/services/core/java/com/android/server/MountService.java index cbd477a9500b..d09edc88ae8a 100644 --- a/services/core/java/com/android/server/MountService.java +++ b/services/core/java/com/android/server/MountService.java @@ -1473,7 +1473,7 @@ class MountService extends IMountService.Stub } mSettingsFile = new AtomicFile( - new File(Environment.getSystemSecureDirectory(), "storage.xml")); + new File(Environment.getDataSystemDirectory(), "storage.xml")); synchronized (mLock) { readSettingsLocked(); diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java index 95f57346dcfc..799d0bda895f 100644 --- a/services/core/java/com/android/server/NetworkManagementService.java +++ b/services/core/java/com/android/server/NetworkManagementService.java @@ -2086,7 +2086,7 @@ public class NetworkManagementService extends INetworkManagementService.Stub final int oldUidFirewallRule = uidFirewallRules.get(uid, FIREWALL_RULE_DEFAULT); if (DBG) { Slog.d(TAG, "oldRule = " + oldUidFirewallRule - + ", newRule=" + rule + " for uid=" + uid); + + ", newRule=" + rule + " for uid=" + uid + " on chain " + chain); } if (oldUidFirewallRule == rule) { if (DBG) Slog.d(TAG, "!!!!! Skipping change"); diff --git a/services/core/java/com/android/server/NetworkScoreService.java b/services/core/java/com/android/server/NetworkScoreService.java index b984e1938758..879bb6f72895 100644 --- a/services/core/java/com/android/server/NetworkScoreService.java +++ b/services/core/java/com/android/server/NetworkScoreService.java @@ -234,12 +234,24 @@ public class NetworkScoreService extends INetworkScoreService.Stub { // safety as scores should never be compared across apps; in practice, Settings should // only be allowing valid apps to be set as scorers, so failure here should be rare. clearInternal(); + // Get the scorer that is about to be replaced, if any, so we can notify it directly. + NetworkScorerAppData prevScorer = NetworkScorerAppManager.getActiveScorer(mContext); boolean result = NetworkScorerAppManager.setActiveScorer(mContext, packageName); - if (result) { + if (result) { // new scorer successfully set registerPackageReceiverIfNeeded(); Intent intent = new Intent(NetworkScoreManager.ACTION_SCORER_CHANGED); - intent.putExtra(NetworkScoreManager.EXTRA_NEW_SCORER, packageName); - mContext.sendBroadcastAsUser(intent, UserHandle.ALL); + if (prevScorer != null) { // Directly notify the old scorer. + intent.setPackage(prevScorer.mPackageName); + // TODO: Need to update when we support per-user scorers. http://b/23422763 + mContext.sendBroadcastAsUser(intent, UserHandle.SYSTEM); + } + + if (packageName != null) { // Then notify the new scorer + intent.putExtra(NetworkScoreManager.EXTRA_NEW_SCORER, packageName); + intent.setPackage(packageName); + // TODO: Need to update when we support per-user scorers. http://b/23422763 + mContext.sendBroadcastAsUser(intent, UserHandle.SYSTEM); + } } return result; } finally { diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java index 2683be66b347..ca1e3719c037 100644 --- a/services/core/java/com/android/server/accounts/AccountManagerService.java +++ b/services/core/java/com/android/server/accounts/AccountManagerService.java @@ -3809,7 +3809,7 @@ public class AccountManagerService } private static String getDatabaseName(int userId) { - File systemDir = Environment.getSystemSecureDirectory(); + File systemDir = Environment.getDataSystemDirectory(); File databaseFile = new File(Environment.getUserSystemDirectory(userId), DATABASE_NAME); if (userId == 0) { // Migrate old file, if it exists, to the new location. diff --git a/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java b/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java index bef6f0aabf5d..1b9d968f18db 100644 --- a/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java +++ b/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java @@ -62,7 +62,7 @@ class ActivityManagerDebugConfig { static final boolean DEBUG_LRU = DEBUG_ALL || false; static final boolean DEBUG_MU = DEBUG_ALL || false; static final boolean DEBUG_OOM_ADJ = DEBUG_ALL || false; - static final boolean DEBUG_PAUSE = DEBUG_ALL || true; + static final boolean DEBUG_PAUSE = DEBUG_ALL || false; static final boolean DEBUG_POWER = DEBUG_ALL || false; static final boolean DEBUG_POWER_QUICK = DEBUG_POWER || false; static final boolean DEBUG_PROCESS_OBSERVERS = DEBUG_ALL || false; @@ -85,7 +85,7 @@ class ActivityManagerDebugConfig { static final boolean DEBUG_UID_OBSERVERS = DEBUG_ALL || false; static final boolean DEBUG_URI_PERMISSION = DEBUG_ALL || false; static final boolean DEBUG_USER_LEAVING = DEBUG_ALL || false; - static final boolean DEBUG_VISIBILITY = DEBUG_ALL || true; + static final boolean DEBUG_VISIBILITY = DEBUG_ALL || false; static final boolean DEBUG_VISIBLE_BEHIND = DEBUG_ALL_ACTIVITIES || false; static final boolean DEBUG_USAGE_STATS = DEBUG_ALL || false; static final boolean DEBUG_PERMISSIONS_REVIEW = DEBUG_ALL || false; diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 104217a63104..50619015bff9 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -2851,31 +2851,39 @@ public final class ActivityManagerService extends ActivityManagerNative @Override public void setFocusedStack(int stackId) { + enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "setFocusedStack()"); if (DEBUG_FOCUS) Slog.d(TAG_FOCUS, "setFocusedStack: stackId=" + stackId); - synchronized (ActivityManagerService.this) { - ActivityStack stack = mStackSupervisor.getStack(stackId); - if (stack != null) { - ActivityRecord r = stack.topRunningActivityLocked(); - if (r != null) { - setFocusedActivityLocked(r, "setFocusedStack"); + final long callingId = Binder.clearCallingIdentity(); + try { + synchronized (this) { + final ActivityStack stack = mStackSupervisor.getStack(stackId); + if (stack == null) { + return; + } + final ActivityRecord r = stack.topRunningActivityLocked(); + if (setFocusedActivityLocked(r, "setFocusedStack")) { mStackSupervisor.resumeFocusedStackTopActivityLocked(); } } + } finally { + Binder.restoreCallingIdentity(callingId); } } @Override public void setFocusedTask(int taskId) { + enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "setFocusedTask()"); if (DEBUG_FOCUS) Slog.d(TAG_FOCUS, "setFocusedTask: taskId=" + taskId); - long callingId = Binder.clearCallingIdentity(); + final long callingId = Binder.clearCallingIdentity(); try { - synchronized (ActivityManagerService.this) { - TaskRecord task = mStackSupervisor.anyTaskForIdLocked(taskId); - if (task != null) { - final ActivityRecord r = task.topRunningActivityLocked(); - if (setFocusedActivityLocked(r, "setFocusedTask")) { - mStackSupervisor.resumeFocusedStackTopActivityLocked(); - } + synchronized (this) { + final TaskRecord task = mStackSupervisor.anyTaskForIdLocked(taskId); + if (task == null) { + return; + } + final ActivityRecord r = task.topRunningActivityLocked(); + if (setFocusedActivityLocked(r, "setFocusedTask")) { + mStackSupervisor.resumeFocusedStackTopActivityLocked(); } } } finally { @@ -20794,7 +20802,7 @@ public final class ActivityManagerService extends ActivityManagerNative pkgUid = pm.getPackageUid(packageName, MATCH_DEBUG_TRIAGED_MISSING, userId); } catch (RemoteException e) { } - if (pkgUid == -1) { + if (userId != UserHandle.USER_ALL && pkgUid == -1) { throw new IllegalArgumentException( "Cannot kill dependents of non-existing package " + packageName); } diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java index 8560a9eeb494..1103ea4874d2 100644 --- a/services/core/java/com/android/server/am/ActivityStack.java +++ b/services/core/java/com/android/server/am/ActivityStack.java @@ -768,7 +768,6 @@ final class ActivityStack { r.state = ActivityState.RESUMED; if (DEBUG_STATES) Slog.v(TAG_STATES, "Moving to RESUMED: " + r + " (starting new instance)"); - r.stopped = false; mResumedActivity = r; r.task.touchActiveTime(); mRecentTasks.addLocked(r.task); @@ -1127,7 +1126,8 @@ final class ActivityStack { if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Executing finish of activity: " + prev); prev = finishCurrentActivityLocked(prev, FINISH_AFTER_VISIBLE, false); } else if (prev.app != null) { - if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Enqueueing pending stop: " + prev); + if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Enqueue pending stop if needed: " + prev + + " wasStopping=" + wasStopping + " visible=" + prev.visible); if (mStackSupervisor.mWaitingVisibleActivities.remove(prev)) { if (DEBUG_SWITCH || DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Complete pause, no longer waiting: " + prev); @@ -1142,7 +1142,8 @@ final class ActivityStack { // 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; - } else if (!hasVisibleBehindActivity() || mService.isSleepingOrShuttingDown()) { + } else if ((!prev.visible && !hasVisibleBehindActivity()) + || mService.isSleepingOrShuttingDown()) { // If we were visible then resumeTopActivities will release resources before // stopping. addToStopping(prev); @@ -1226,9 +1227,11 @@ final class ActivityStack { * this function updates the rest of our state to match that fact. */ private void completeResumeLocked(ActivityRecord next) { + next.visible = true; next.idle = false; next.results = null; next.newIntents = null; + next.stopped = false; if (next.isHomeActivity()) { ProcessRecord app = next.task.mActivities.get(0).app; @@ -1727,6 +1730,8 @@ final class ActivityStack { // This activity is not currently visible, but is running. Tell it to become visible. if (r.state == ActivityState.RESUMED || r == starting) { + Slog.d(TAG_VISIBILITY, "Not making visible, r=" + r + " state=" + r.state + + " starting=" + starting); return; } @@ -2289,7 +2294,6 @@ final class ActivityStack { // From this point on, if something goes wrong there is no way // to recover the activity. try { - next.visible = true; completeResumeLocked(next); } catch (Exception e) { // If any exception gets thrown, toss away this @@ -2300,8 +2304,6 @@ final class ActivityStack { if (DEBUG_STACK) mStackSupervisor.validateTopActivitiesLocked(); return true; } - next.stopped = false; - } else { // Whoops, need to restart this activity! if (!next.hasBeenLaunched) { @@ -4281,6 +4283,12 @@ final class ActivityStack { // "restart!". if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION, "Config is relaunching resumed " + r); + + if (DEBUG_STATES && !r.visible) { + Slog.v(TAG_STATES, "Config is relaunching resumed invisible activity " + r + + " called by " + Debug.getCallers(4)); + } + relaunchActivityLocked(r, r.configChangeFlags, true, preserveWindow); } else { if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION, @@ -4431,9 +4439,21 @@ final class ActivityStack { } if (andResume) { - r.results = null; - r.newIntents = null; + if (DEBUG_STATES) { + Slog.d(TAG_STATES, "Resumed after relaunch " + r); + } r.state = ActivityState.RESUMED; + // Relaunch-resume could happen either when the app is already in the front, + // or while it's being brought to front. In the latter case, it's marked RESUMED + // but not yet visible (or stopped). We need to complete the resume here as the + // code in resumeTopActivityInnerLocked to complete the resume might be skipped. + if (!r.visible || r.stopped) { + mWindowManager.setAppVisibility(r.appToken, true); + completeResumeLocked(r); + } else { + r.results = null; + r.newIntents = null; + } } else { mHandler.removeMessages(PAUSE_TIMEOUT_MSG, r); r.state = ActivityState.PAUSED; diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java index 26108a362273..93a36eb1d3d8 100644 --- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java @@ -1046,10 +1046,14 @@ public final class ActivityStackSupervisor implements DisplayListener { } ResolveInfo resolveIntent(Intent intent, String resolvedType, int userId) { + return resolveIntent(intent, resolvedType, userId, 0); + } + + ResolveInfo resolveIntent(Intent intent, String resolvedType, int userId, int flags) { try { return AppGlobals.getPackageManager().resolveIntent(intent, resolvedType, - PackageManager.MATCH_DEFAULT_ONLY - | ActivityManagerService.STOCK_PM_FLAGS, userId); + PackageManager.MATCH_DEFAULT_ONLY | flags + | ActivityManagerService.STOCK_PM_FLAGS, userId); } catch (RemoteException e) { } return null; @@ -1938,9 +1942,6 @@ public final class ActivityStackSupervisor implements DisplayListener { } final boolean updated = stack.ensureActivityConfigurationLocked(r, 0, preserveWindows); - // And we need to make sure at this point that all other activities - // are made visible with the correct configuration. - ensureActivitiesVisibleLocked(r, 0, preserveWindows); if (!updated) { resumeFocusedStackTopActivityLocked(); } diff --git a/services/core/java/com/android/server/am/ActivityStartInterceptor.java b/services/core/java/com/android/server/am/ActivityStartInterceptor.java index 1ed749fdd9da..9b2bca009da6 100644 --- a/services/core/java/com/android/server/am/ActivityStartInterceptor.java +++ b/services/core/java/com/android/server/am/ActivityStartInterceptor.java @@ -1,3 +1,19 @@ +/* + * 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.am; import static android.app.ActivityManager.INTENT_SENDER_ACTIVITY; diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java index 28be456628ae..cca6fc5a85d5 100644 --- a/services/core/java/com/android/server/am/ActivityStarter.java +++ b/services/core/java/com/android/server/am/ActivityStarter.java @@ -1,3 +1,19 @@ +/* + * 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.am; import static android.app.Activity.RESULT_CANCELED; @@ -74,7 +90,9 @@ import android.content.Intent; import android.content.IntentSender; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; +import android.content.pm.UserInfo; import android.content.res.Configuration; import android.graphics.Rect; import android.os.Binder; @@ -84,6 +102,7 @@ import android.os.IBinder; import android.os.RemoteException; import android.os.SystemClock; import android.os.UserHandle; +import android.os.UserManager; import android.service.voice.IVoiceInteractionSession; import android.util.EventLog; import android.util.Slog; @@ -582,6 +601,22 @@ class ActivityStarter { intent = new Intent(intent); ResolveInfo rInfo = mSupervisor.resolveIntent(intent, resolvedType, userId); + if (rInfo == null) { + UserInfo userInfo = mSupervisor.getUserInfo(userId); + if (userInfo != null && userInfo.isManagedProfile()) { + // Special case for managed profiles, if attempting to launch non-cryto aware + // app in a locked managed profile from an unlocked parent allow it to resolve + // as user will be sent via confirm credentials to unlock the profile. + UserManager userManager = UserManager.get(mService.mContext); + UserInfo parent = userManager.getProfileParent(userId); + if (parent != null + && userManager.isUserUnlocked(parent.getUserHandle()) + && !userManager.isUserUnlocked(userInfo.getUserHandle())) { + rInfo = mSupervisor.resolveIntent(intent, resolvedType, userId, + PackageManager.MATCH_ENCRYPTION_AWARE_AND_UNAWARE); + } + } + } // Collect information about the target of the Intent. ActivityInfo aInfo = mSupervisor.resolveActivity(intent, rInfo, startFlags, profilerInfo); diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java index 98a7ead339df..16fd909b329a 100644 --- a/services/core/java/com/android/server/am/TaskRecord.java +++ b/services/core/java/com/android/server/am/TaskRecord.java @@ -70,6 +70,7 @@ import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_DEFAULT; import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_IF_WHITELISTED; import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_NEVER; import static android.content.pm.ActivityInfo.RESIZE_MODE_CROP_WINDOWS; +import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZEABLE; import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE; import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE; import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_PRIVILEGED; @@ -122,9 +123,6 @@ final class TaskRecord { private static final String ATTR_TASK_AFFILIATION_COLOR = "task_affiliation_color"; private static final String ATTR_CALLING_UID = "calling_uid"; private static final String ATTR_CALLING_PACKAGE = "calling_package"; - // TODO(b/26847884): Currently needed while migrating to resize_mode. - // Can be removed at some later point. - private static final String ATTR_RESIZEABLE = "resizeable"; private static final String ATTR_RESIZE_MODE = "resize_mode"; private static final String ATTR_PRIVILEGED = "privileged"; private static final String ATTR_NON_FULLSCREEN_BOUNDS = "non_fullscreen_bounds"; @@ -1011,6 +1009,7 @@ final class TaskRecord { String label = null; String iconFilename = null; int colorPrimary = 0; + int colorBackground = 0; for (--activityNdx; activityNdx >= 0; --activityNdx) { final ActivityRecord r = mActivities.get(activityNdx); if (r.taskDescription != null) { @@ -1023,9 +1022,13 @@ final class TaskRecord { if (colorPrimary == 0) { colorPrimary = r.taskDescription.getPrimaryColor(); } + if (colorBackground == 0) { + colorBackground = r.taskDescription.getBackgroundColor(); + } } } - lastTaskDescription = new TaskDescription(label, colorPrimary, iconFilename); + lastTaskDescription = new TaskDescription(label, null, iconFilename, colorPrimary, + colorBackground); // Update the task affiliation color if we are the parent of the group if (taskId == mAffiliatedTaskId) { mAffiliatedTaskColor = lastTaskDescription.getPrimaryColor(); @@ -1169,7 +1172,7 @@ final class TaskRecord { int nextTaskId = INVALID_TASK_ID; int callingUid = -1; String callingPackage = ""; - int resizeMode = RESIZE_MODE_UNRESIZEABLE; + int resizeMode = RESIZE_MODE_FORCE_RESIZEABLE; boolean privileged = false; Rect bounds = null; @@ -1231,11 +1234,10 @@ final class TaskRecord { callingUid = Integer.valueOf(attrValue); } else if (ATTR_CALLING_PACKAGE.equals(attrName)) { callingPackage = attrValue; - } else if (ATTR_RESIZEABLE.equals(attrName)) { - resizeMode = Boolean.valueOf(attrValue) - ? RESIZE_MODE_RESIZEABLE : RESIZE_MODE_CROP_WINDOWS; } else if (ATTR_RESIZE_MODE.equals(attrName)) { resizeMode = Integer.valueOf(attrValue); + resizeMode = (resizeMode == RESIZE_MODE_CROP_WINDOWS) + ? RESIZE_MODE_FORCE_RESIZEABLE : resizeMode; } else if (ATTR_PRIVILEGED.equals(attrName)) { privileged = Boolean.valueOf(attrValue); } else if (ATTR_NON_FULLSCREEN_BOUNDS.equals(attrName)) { diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java index a355fa4750eb..90ebe1878a6a 100644 --- a/services/core/java/com/android/server/am/UserController.java +++ b/services/core/java/com/android/server/am/UserController.java @@ -165,15 +165,15 @@ final class UserController { void register(ContentResolver resolver) { resolver.registerContentObserver(mUserSetupComplete, false, this, UserHandle.USER_ALL); synchronized (mService) { - updateCurrentUserSetupCompleteLocked(); + updateUserSetupCompleteLocked(UserHandle.USER_ALL); } } @Override - public void onChange(boolean selfChange, Uri uri) { + public void onChange(boolean selfChange, Uri uri, int userId) { if (mUserSetupComplete.equals(uri)) { synchronized (mService) { - updateCurrentUserSetupCompleteLocked(); + updateUserSetupCompleteLocked(userId); } } } @@ -297,6 +297,22 @@ final class UserController { null, null, AppOpsManager.OP_NONE, null, false, false, MY_PID, SYSTEM_UID, userId); + if (getUserInfo(userId).isManagedProfile()) { + UserInfo parent = getUserManager().getProfileParent(userId); + if (parent != null) { + final Intent profileUnlockedIntent = new Intent( + Intent.ACTION_MANAGED_PROFILE_UNLOCKED); + unlockedIntent.putExtra(Intent.EXTRA_USER, UserHandle.of(userId)); + unlockedIntent.addFlags( + Intent.FLAG_RECEIVER_REGISTERED_ONLY + | Intent.FLAG_RECEIVER_FOREGROUND); + mService.broadcastIntentLocked(null, null, profileUnlockedIntent, + null, null, 0, null, null, null, AppOpsManager.OP_NONE, + null, false, false, MY_PID, SYSTEM_UID, + parent.id); + } + } + final Intent bootIntent = new Intent(Intent.ACTION_BOOT_COMPLETED, null); bootIntent.putExtra(Intent.EXTRA_USER_HANDLE, userId); bootIntent.addFlags(Intent.FLAG_RECEIVER_NO_ABORT @@ -655,7 +671,7 @@ final class UserController { final Integer userIdInt = userId; mUserLru.remove(userIdInt); mUserLru.add(userIdInt); - updateCurrentUserSetupCompleteLocked(); + updateUserSetupCompleteLocked(userId); if (foreground) { mCurrentUserId = userId; @@ -870,11 +886,16 @@ final class UserController { mUserSwitchObservers.finishBroadcast(); } - void updateCurrentUserSetupCompleteLocked() { + void updateUserSetupCompleteLocked(int userId) { final ContentResolver cr = mService.mContext.getContentResolver(); - final boolean setupComplete = - Settings.Secure.getIntForUser(cr, USER_SETUP_COMPLETE, 0, mCurrentUserId) != 0; - mSetupCompletedUsers.put(mCurrentUserId, setupComplete); + for (int i = mStartedUsers.size() - 1; i >= 0; i--) { + int startedUser = mStartedUsers.keyAt(i); + if (startedUser == userId || userId == UserHandle.USER_ALL) { + final boolean setupComplete = + Settings.Secure.getIntForUser(cr, USER_SETUP_COMPLETE, 0, startedUser) != 0; + mSetupCompletedUsers.put(startedUser, setupComplete); + } + } } boolean isUserSetupCompleteLocked(int userId) { diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java index 8dec5870427c..0d974344a432 100644 --- a/services/core/java/com/android/server/content/SyncManager.java +++ b/services/core/java/com/android/server/content/SyncManager.java @@ -62,6 +62,7 @@ import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.Message; +import android.os.Messenger; import android.os.PowerManager; import android.os.RemoteException; import android.os.ServiceManager; @@ -70,41 +71,42 @@ import android.os.SystemProperties; import android.os.UserHandle; import android.os.UserManager; import android.os.WorkSource; -import android.os.Messenger; import android.provider.Settings; import android.text.format.DateUtils; import android.text.format.Time; import android.util.EventLog; import android.util.Log; -import android.util.Slog; import android.util.Pair; - +import android.util.Slog; import android.util.SparseArray; + +import com.google.android.collect.Lists; +import com.google.android.collect.Maps; + import com.android.internal.R; import com.android.internal.app.IBatteryStats; import com.android.internal.os.BackgroundThread; import com.android.internal.util.IndentingPrintWriter; import com.android.server.accounts.AccountManagerService; +import com.android.server.backup.AccountSyncSettingsBackupHelper; import com.android.server.content.SyncStorageEngine.AuthorityInfo; import com.android.server.content.SyncStorageEngine.EndPoint; import com.android.server.content.SyncStorageEngine.OnSyncRequestListener; -import com.google.android.collect.Lists; -import com.google.android.collect.Maps; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; -import java.util.Random; -import java.util.List; -import java.util.Set; -import java.util.HashSet; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; -import java.util.Map; -import java.util.Arrays; import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; import java.util.Objects; +import java.util.Random; +import java.util.Set; /** * Implementation details: @@ -451,10 +453,48 @@ public class SyncManager { } } } + ensureDefaultPeriodicSyncsH(); } }); } + private void ensureDefaultPeriodicSyncsH() { + + long defaultPeriod = SyncStorageEngine.DEFAULT_POLL_FREQUENCY_SECONDS; + long defaultFlex = SyncStorageEngine.calculateDefaultFlexTime(defaultPeriod); + + List<AuthorityInfo> authorities = mSyncStorageEngine.getAllAuthorities(); + List<SyncOperation> syncs = getAllPendingSyncsFromCache(); + for (AuthorityInfo authority: authorities) { + boolean foundPeriodicSync = false; + for (SyncOperation op: syncs) { + if (op.isPeriodic && authority.target.matchesSpec(op.target)) { + foundPeriodicSync = true; + break; + } + } + if (!foundPeriodicSync) { + EndPoint target = authority.target; + final RegisteredServicesCache.ServiceInfo<SyncAdapterType> + syncAdapterInfo = mSyncAdapters.getServiceInfo( + SyncAdapterType.newKey( + target.provider, target.account.type), + target.userId); + if (syncAdapterInfo == null) { + continue; + } + scheduleSyncOperationH( + new SyncOperation(target, syncAdapterInfo.uid, + syncAdapterInfo.componentName.getPackageName(), + SyncOperation.REASON_PERIODIC, SyncStorageEngine.SOURCE_PERIODIC, + new Bundle(), syncAdapterInfo.type.allowParallelSyncs(), + true /* periodic */, SyncOperation.NO_JOB_ID, defaultPeriod * 1000L, + defaultFlex * 1000L) + ); + } + } + } + private synchronized void verifyJobScheduler() { if (mJobScheduler != null) { return; @@ -510,12 +550,12 @@ public class SyncManager { mSyncStorageEngine.setPeriodicSyncAddedListener( new SyncStorageEngine.PeriodicSyncAddedListener() { - @Override - public void onPeriodicSyncAdded(EndPoint target, Bundle extras, long pollFrequency, - long flex) { - updateOrAddPeriodicSync(target, pollFrequency, flex, extras); - } - }); + @Override + public void onPeriodicSyncAdded(EndPoint target, Bundle extras, long pollFrequency, + long flex) { + updateOrAddPeriodicSync(target, pollFrequency, flex, extras); + } + }); mSyncStorageEngine.setOnAuthorityRemovedListener(new SyncStorageEngine.OnAuthorityRemovedListener() { @Override @@ -750,8 +790,8 @@ public class SyncManager { * @param onlyThoseWithUnkownSyncableState Only sync authorities that have unknown state. */ public void scheduleSync(Account requestedAccount, int userId, int reason, - String requestedAuthority, Bundle extras, long beforeRuntimeMillis, - long runtimeMillis, boolean onlyThoseWithUnkownSyncableState) { + String requestedAuthority, Bundle extras, long beforeRuntimeMillis, + long runtimeMillis, boolean onlyThoseWithUnkownSyncableState) { final boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE); if (extras == null) { extras = new Bundle(); @@ -941,7 +981,7 @@ public class SyncManager { * flexMillis will be updated. */ public void updateOrAddPeriodicSync(EndPoint target, long pollFrequency, long flex, - Bundle extras) { + Bundle extras) { UpdatePeriodicSyncMessagePayload payload = new UpdatePeriodicSyncMessagePayload(target, pollFrequency, flex, extras); mSyncHandler.obtainMessage(SyncHandler.MESSAGE_UPDATE_PERIODIC_SYNC, payload) @@ -995,7 +1035,7 @@ public class SyncManager { } private void sendSyncFinishedOrCanceledMessage(ActiveSyncContext syncContext, - SyncResult syncResult) { + SyncResult syncResult) { if (Log.isLoggable(TAG, Log.VERBOSE)) Slog.v(TAG, "sending MESSAGE_SYNC_FINISHED"); Message msg = mSyncHandler.obtainMessage(); msg.what = SyncHandler.MESSAGE_SYNC_FINISHED; @@ -1053,7 +1093,7 @@ public class SyncManager { public final SyncResult syncResult; SyncFinishedOrCancelledMessagePayload(ActiveSyncContext syncContext, - SyncResult syncResult) { + SyncResult syncResult) { this.activeSyncContext = syncContext; this.syncResult = syncResult; } @@ -1066,7 +1106,7 @@ public class SyncManager { public final Bundle extras; UpdatePeriodicSyncMessagePayload(EndPoint target, long pollFrequency, long flex, - Bundle extras) { + Bundle extras) { this.target = target; this.pollFrequency = pollFrequency; this.flex = flex; @@ -1297,7 +1337,7 @@ public class SyncManager { JobInfo.NETWORK_TYPE_UNMETERED : JobInfo.NETWORK_TYPE_ANY; JobInfo.Builder b = new JobInfo.Builder(syncOperation.jobId, - new ComponentName(mContext, SyncJobService.class)) + new ComponentName(mContext, SyncJobService.class)) .setExtras(syncOperation.toJobInfoExtras()) .setRequiredNetworkType(networkType) .setPersisted(true) @@ -1502,7 +1542,7 @@ public class SyncManager { * for this sync. This is used to attribute the wakelock hold to that application. */ public ActiveSyncContext(SyncOperation syncOperation, long historyRowId, - int syncAdapterUid) { + int syncAdapterUid) { super(); mSyncAdapterUid = syncAdapterUid; mSyncOperation = syncOperation; @@ -1731,7 +1771,7 @@ public class SyncManager { new Comparator<RegisteredServicesCache.ServiceInfo<SyncAdapterType>>() { @Override public int compare(RegisteredServicesCache.ServiceInfo<SyncAdapterType> lhs, - RegisteredServicesCache.ServiceInfo<SyncAdapterType> rhs) { + RegisteredServicesCache.ServiceInfo<SyncAdapterType> rhs) { return lhs.type.authority.compareTo(rhs.type.authority); } }); @@ -2546,6 +2586,7 @@ public class SyncManager { Slog.v(TAG, "Pushing back running sync due to a higher priority sync"); } deferActiveSyncH(asc); + break; } } } @@ -2571,6 +2612,7 @@ public class SyncManager { } private void updateRunningAccountsH(EndPoint syncTargets) { + AccountAndUser[] oldAccounts = mRunningAccounts; mRunningAccounts = AccountManagerService.getSingleton().getRunningAccounts(); if (Log.isLoggable(TAG, Log.VERBOSE)) { Slog.v(TAG, "Accounts list: "); @@ -2594,6 +2636,17 @@ public class SyncManager { } } + // On account add, check if there are any settings to be restored. + for (AccountAndUser aau : mRunningAccounts) { + if (!containsAccountAndUser(oldAccounts, aau.account, aau.userId)) { + if (Log.isLoggable(TAG, Log.DEBUG)) { + Log.d(TAG, "Account " + aau.account + " added, checking sync restore data"); + } + AccountSyncSettingsBackupHelper.accountAdded(mContext); + break; + } + } + List<SyncOperation> ops = getAllPendingSyncsFromCache(); for (SyncOperation op: ops) { if (!containsAccountAndUser(accounts, op.target.account, op.target.userId)) { @@ -2617,7 +2670,7 @@ public class SyncManager { * @param flexMillis new flex time in milliseconds. */ private void maybeUpdateSyncPeriodH(SyncOperation syncOperation, long pollFrequencyMillis, - long flexMillis) { + long flexMillis) { if (!(pollFrequencyMillis == syncOperation.periodMillis && flexMillis == syncOperation.flexMillis)) { if (Log.isLoggable(TAG, Log.VERBOSE)) { @@ -2632,7 +2685,7 @@ public class SyncManager { } private void updateOrAddPeriodicSyncH(EndPoint target, long pollFrequency, long flex, - Bundle extras) { + Bundle extras) { final boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE); verifyJobScheduler(); // Will fill in mScheduledSyncs cache if it is not already filled. final long pollFrequencyMillis = pollFrequency * 1000L; @@ -2820,7 +2873,7 @@ public class SyncManager { } private void runBoundToAdapterH(final ActiveSyncContext activeSyncContext, - IBinder syncAdapter) { + IBinder syncAdapter) { final SyncOperation syncOperation = activeSyncContext.mSyncOperation; try { activeSyncContext.mIsLinkedToDeath = true; @@ -2888,7 +2941,7 @@ public class SyncManager { } private void runSyncFinishedOrCanceledH(SyncResult syncResult, - ActiveSyncContext activeSyncContext) { + ActiveSyncContext activeSyncContext) { final boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE); final SyncOperation syncOperation = activeSyncContext.mSyncOperation; @@ -3022,7 +3075,7 @@ public class SyncManager { } private void installHandleTooManyDeletesNotification(Account account, String authority, - long numDeletes, int userId) { + long numDeletes, int userId) { if (mNotificationMgr == null) return; final ProviderInfo providerInfo = mContext.getPackageManager().resolveContentProvider( @@ -3098,7 +3151,7 @@ public class SyncManager { } public void stopSyncEvent(long rowId, SyncOperation syncOperation, String resultMessage, - int upstreamActivity, int downstreamActivity, long elapsedTime) { + int upstreamActivity, int downstreamActivity, long elapsedTime) { EventLog.writeEvent(2720, syncOperation.toEventLog(SyncStorageEngine.EVENT_STOP)); mSyncStorageEngine.stopSyncEvent(rowId, elapsedTime, @@ -3262,4 +3315,4 @@ public class SyncManager { return mContext; } } -} +}
\ No newline at end of file diff --git a/services/core/java/com/android/server/content/SyncStorageEngine.java b/services/core/java/com/android/server/content/SyncStorageEngine.java index f8e3e48f8026..cefaa8ddd831 100644 --- a/services/core/java/com/android/server/content/SyncStorageEngine.java +++ b/services/core/java/com/android/server/content/SyncStorageEngine.java @@ -80,7 +80,7 @@ public class SyncStorageEngine extends Handler { private static final String XML_TAG_LISTEN_FOR_TICKLES = "listenForTickles"; /** Default time for a periodic sync. */ - private static final long DEFAULT_POLL_FREQUENCY_SECONDS = 60 * 60 * 24; // One day + static final long DEFAULT_POLL_FREQUENCY_SECONDS = 60 * 60 * 24; // One day /** Percentage of period that is flex by default, if no flexMillis is set. */ private static final double DEFAULT_FLEX_PERCENT_SYNC = 0.04; @@ -438,9 +438,7 @@ public class SyncStorageEngine extends Handler { if (sSyncStorageEngine != null) { return; } - // This call will return the correct directory whether Encrypted File Systems is - // enabled or not. - File dataDir = Environment.getSecureDataDirectory(); + File dataDir = Environment.getDataDirectory(); sSyncStorageEngine = new SyncStorageEngine(context, dataDir); } @@ -859,6 +857,16 @@ public class SyncStorageEngine extends Handler { } } + List<AuthorityInfo> getAllAuthorities() { + List<AuthorityInfo> authorities = new ArrayList<AuthorityInfo>(); + synchronized (mAuthorities) { + for (int i = 0; i < mAuthorities.size(); i++) { + authorities.add(mAuthorities.valueAt(i)); + } + } + return authorities; + } + /** * Returns true if there is currently a sync operation being actively processed for the given * target. diff --git a/services/core/java/com/android/server/firewall/IntentFirewall.java b/services/core/java/com/android/server/firewall/IntentFirewall.java index 62114cd0c5c8..7e19c66b1ad6 100644 --- a/services/core/java/com/android/server/firewall/IntentFirewall.java +++ b/services/core/java/com/android/server/firewall/IntentFirewall.java @@ -52,7 +52,7 @@ public class IntentFirewall { static final String TAG = "IntentFirewall"; // e.g. /data/system/ifw or /data/secure/system/ifw - private static final File RULES_DIR = new File(Environment.getSystemSecureDirectory(), "ifw"); + private static final File RULES_DIR = new File(Environment.getDataSystemDirectory(), "ifw"); private static final int LOG_PACKAGES_MAX_LENGTH = 150; private static final int LOG_PACKAGES_SUFFICIENT_LENGTH = 125; diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java index e5e86ac44a26..402837281609 100644 --- a/services/core/java/com/android/server/job/JobSchedulerService.java +++ b/services/core/java/com/android/server/job/JobSchedulerService.java @@ -330,7 +330,7 @@ public class JobSchedulerService extends com.android.server.SystemService private void cancelJobImpl(JobStatus cancelled) { if (DEBUG) Slog.d(TAG, "CANCEL: " + cancelled.toShortString()); - stopTrackingJob(cancelled); + stopTrackingJob(cancelled, true /* writeBack */); synchronized (mJobs) { // Remove from pending queue. mPendingJobs.remove(cancelled); @@ -509,12 +509,12 @@ public class JobSchedulerService extends com.android.server.SystemService * Called when we want to remove a JobStatus object that we've finished executing. Returns the * object removed. */ - private boolean stopTrackingJob(JobStatus jobStatus) { + private boolean stopTrackingJob(JobStatus jobStatus, boolean writeBack) { boolean removed; boolean rocking; synchronized (mJobs) { // Remove from store as well as controllers. - removed = mJobs.remove(jobStatus); + removed = mJobs.remove(jobStatus, writeBack); rocking = mReadyToRock; } if (removed && rocking) { @@ -645,7 +645,9 @@ public class JobSchedulerService extends com.android.server.SystemService if (DEBUG) { Slog.d(TAG, "Completed " + jobStatus + ", reschedule=" + needsReschedule); } - if (!stopTrackingJob(jobStatus)) { + // Do not write back immediately if this is a periodic job. The job may get lost if system + // shuts down before it is added back. + if (!stopTrackingJob(jobStatus, !jobStatus.getJob().isPeriodic())) { if (DEBUG) { Slog.d(TAG, "Could not find job to remove. Was job removed while executing?"); } diff --git a/services/core/java/com/android/server/job/JobStore.java b/services/core/java/com/android/server/job/JobStore.java index f796164ef8ce..f6a6778aa496 100644 --- a/services/core/java/com/android/server/job/JobStore.java +++ b/services/core/java/com/android/server/job/JobStore.java @@ -159,9 +159,10 @@ public class JobStore { /** * Remove the provided job. Will also delete the job if it was persisted. + * @param writeBack If true, the job will be deleted (if it was persisted) immediately. * @return Whether or not the job existed to be removed. */ - public boolean remove(JobStatus jobStatus) { + public boolean remove(JobStatus jobStatus, boolean writeBack) { boolean removed = mJobSet.remove(jobStatus); if (!removed) { if (DEBUG) { @@ -169,7 +170,7 @@ public class JobStore { } return false; } - if (jobStatus.isPersisted()) { + if (writeBack && jobStatus.isPersisted()) { maybeWriteStatusToDiskAsync(); } return removed; diff --git a/services/core/java/com/android/server/location/GnssLocationProvider.java b/services/core/java/com/android/server/location/GnssLocationProvider.java index ffc52b31a7d5..c1eb844f09fa 100644 --- a/services/core/java/com/android/server/location/GnssLocationProvider.java +++ b/services/core/java/com/android/server/location/GnssLocationProvider.java @@ -1557,15 +1557,13 @@ public class GnssLocationProvider implements LocationProviderInterface { * called from native code to update SV info */ private void reportSvStatus() { - int svCount = native_read_sv_status(mPrnWithFlags, mSnrs, mSvElevations, mSvAzimuths, - mConstellationTypes); + int svCount = native_read_sv_status(mSvidWithFlags, mSnrs, mSvElevations, mSvAzimuths); mListenerHelper.onSvStatusChanged( svCount, - mPrnWithFlags, + mSvidWithFlags, mSnrs, mSvElevations, - mSvAzimuths, - mConstellationTypes); + mSvAzimuths); if (VERBOSE) { Log.v(TAG, "SV count: " + svCount); @@ -1573,19 +1571,19 @@ public class GnssLocationProvider implements LocationProviderInterface { // Calculate number of sets used in fix. int usedInFixCount = 0; for (int i = 0; i < svCount; i++) { - if ((mPrnWithFlags[i] & GnssStatus.GNSS_SV_FLAGS_USED_IN_FIX) != 0) { + if ((mSvidWithFlags[i] & GnssStatus.GNSS_SV_FLAGS_USED_IN_FIX) != 0) { ++usedInFixCount; } if (VERBOSE) { - Log.v(TAG, "prn: " + (mPrnWithFlags[i] >> GnssStatus.PRN_SHIFT_WIDTH) + + Log.v(TAG, "svid: " + (mSvidWithFlags[i] >> GnssStatus.SVID_SHIFT_WIDTH) + " snr: " + mSnrs[i]/10 + " elev: " + mSvElevations[i] + " azimuth: " + mSvAzimuths[i] + - ((mPrnWithFlags[i] & GnssStatus.GNSS_SV_FLAGS_HAS_EPHEMERIS_DATA) == 0 + ((mSvidWithFlags[i] & GnssStatus.GNSS_SV_FLAGS_HAS_EPHEMERIS_DATA) == 0 ? " " : " E") + - ((mPrnWithFlags[i] & GnssStatus.GNSS_SV_FLAGS_HAS_ALMANAC_DATA) == 0 + ((mSvidWithFlags[i] & GnssStatus.GNSS_SV_FLAGS_HAS_ALMANAC_DATA) == 0 ? " " : " A") + - ((mPrnWithFlags[i] & GnssStatus.GNSS_SV_FLAGS_USED_IN_FIX) == 0 + ((mSvidWithFlags[i] & GnssStatus.GNSS_SV_FLAGS_USED_IN_FIX) == 0 ? "" : "U")); } } @@ -2398,14 +2396,13 @@ public class GnssLocationProvider implements LocationProviderInterface { } // for GPS SV statistics - private static final int MAX_SVS = 512; + private static final int MAX_SVS = 64; // preallocated arrays, to avoid memory allocation in reportStatus() - private int mPrnWithFlags[] = new int[MAX_SVS]; + private int mSvidWithFlags[] = new int[MAX_SVS]; private float mSnrs[] = new float[MAX_SVS]; private float mSvElevations[] = new float[MAX_SVS]; private float mSvAzimuths[] = new float[MAX_SVS]; - private int mConstellationTypes[] = new int[MAX_SVS]; private int mSvCount; // preallocated to avoid memory allocation in reportNmea() private byte[] mNmeaBuffer = new byte[120]; @@ -2426,7 +2423,7 @@ public class GnssLocationProvider implements LocationProviderInterface { // returns number of SVs // mask[0] is ephemeris mask and mask[1] is almanac mask private native int native_read_sv_status(int[] prnWithFlags, float[] snrs, float[] elevations, - float[] azimuths, int[] constellationTypes); + float[] azimuths); private native int native_read_nmea(byte[] buffer, int bufferSize); private native void native_inject_location(double latitude, double longitude, float accuracy); diff --git a/services/core/java/com/android/server/location/GnssStatusListenerHelper.java b/services/core/java/com/android/server/location/GnssStatusListenerHelper.java index 9840c61ec516..0b3111cd726c 100644 --- a/services/core/java/com/android/server/location/GnssStatusListenerHelper.java +++ b/services/core/java/com/android/server/location/GnssStatusListenerHelper.java @@ -77,8 +77,7 @@ abstract class GnssStatusListenerHelper extends RemoteListenerHelper<IGnssStatus final int[] prnWithFlags, final float[] snrs, final float[] elevations, - final float[] azimuths, - final int[] constellationTypes) { + final float[] azimuths) { Operation operation = new Operation() { @Override public void execute(IGnssStatusListener listener) throws RemoteException { @@ -87,8 +86,7 @@ abstract class GnssStatusListenerHelper extends RemoteListenerHelper<IGnssStatus prnWithFlags, snrs, elevations, - azimuths, - constellationTypes); + azimuths); } }; foreach(operation); diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java index b2e6adfd028a..426ce41298e7 100644 --- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java +++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java @@ -339,6 +339,8 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { private final AppOpsManager mAppOps; private final MyPackageMonitor mPackageMonitor; + private final IPackageManager mIPm; + // TODO: keep whitelist of system-critical services that should never have // rules enforced, such as system, phone, and radio UIDs. @@ -369,6 +371,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { Context.DEVICE_IDLE_CONTROLLER)); mTime = checkNotNull(time, "missing TrustedTime"); mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE); + mIPm = AppGlobals.getPackageManager(); HandlerThread thread = new HandlerThread(TAG); thread.start(); @@ -1864,31 +1867,58 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { @Override public void addRestrictBackgroundWhitelistedUid(int uid) { mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG); - Slog.i(TAG, "adding uid " + uid + " to restrict background whitelist"); + if (!isUidValidForRules(uid)) return; + final boolean changed; synchronized (mRulesLock) { + final boolean oldStatus = mRestrictBackgroundWhitelistUids.get(uid); + if (oldStatus) { + if (LOGD) Slog.d(TAG, "uid " + uid + " is already whitelisted"); + return; + } + Slog.i(TAG, "adding uid " + uid + " to restrict background whitelist"); mRestrictBackgroundWhitelistUids.append(uid, true); - updateRulesForGlobalChangeLocked(true); + changed = mRestrictBackground && !oldStatus; + if (changed && hasInternetPermissions(uid)) { + setUidNetworkRules(uid, false); + } writePolicyLocked(); } - mHandler.obtainMessage(MSG_RESTRICT_BACKGROUND_WHITELIST_CHANGED, uid, 0).sendToTarget(); + if (changed) { + mHandler.obtainMessage(MSG_RESTRICT_BACKGROUND_WHITELIST_CHANGED, uid, 0) + .sendToTarget(); + } } @Override public void removeRestrictBackgroundWhitelistedUid(int uid) { mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG); - Slog.i(TAG, "removing uid " + uid + " from restrict background whitelist"); + if (!isUidValidForRules(uid)) return; + final boolean changed; synchronized (mRulesLock) { - removeRestrictBackgroundWhitelistedUidLocked(uid, true); + changed = removeRestrictBackgroundWhitelistedUidLocked(uid, true); + } + if (changed) { + mHandler.obtainMessage(MSG_RESTRICT_BACKGROUND_WHITELIST_CHANGED, uid, 0) + .sendToTarget(); } - mHandler.obtainMessage(MSG_RESTRICT_BACKGROUND_WHITELIST_CHANGED, uid, 0).sendToTarget(); } - private void removeRestrictBackgroundWhitelistedUidLocked(int uid, boolean updateNow) { + private boolean removeRestrictBackgroundWhitelistedUidLocked(int uid, boolean updateNow) { + final boolean oldStatus = mRestrictBackgroundWhitelistUids.get(uid); + if (!oldStatus) { + if (LOGD) Slog.d(TAG, "uid " + uid + " was not whitelisted before"); + return false; + } + Slog.i(TAG, "removing uid " + uid + " from restrict background whitelist"); + final boolean changed = mRestrictBackground && oldStatus; mRestrictBackgroundWhitelistUids.delete(uid); if (updateNow) { - updateRulesForGlobalChangeLocked(true); + if (changed && hasInternetPermissions(uid)) { + setUidNetworkRules(uid, true); + } writePolicyLocked(); } + return changed; } @Override @@ -2298,13 +2328,19 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { uidRules.clear(); // Fully update the app idle firewall chain. + final IPackageManager ipm = AppGlobals.getPackageManager(); final List<UserInfo> users = mUserManager.getUsers(); for (int ui = users.size() - 1; ui >= 0; ui--) { UserInfo user = users.get(ui); int[] idleUids = mUsageStats.getIdleUidsForUser(user.id); for (int uid : idleUids) { if (!mPowerSaveTempWhitelistAppIds.get(UserHandle.getAppId(uid), false)) { - uidRules.put(uid, FIREWALL_RULE_DENY); + // quick check: if this uid doesn't have INTERNET permission, it + // doesn't have network access anyway, so it is a waste to mess + // with it here. + if (hasInternetPermissions(uid)) { + uidRules.put(uid, FIREWALL_RULE_DENY); + } } } } @@ -2408,22 +2444,27 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } /** - * Applies network rules to bandwidth and firewall controllers based on uid policy. - * @param uid The uid for which to apply the latest policy + * Checks if an uid has INTERNET permissions. + * <p> + * Useful for the cases where the lack of network access can simplify the rules. */ - void updateRulesForUidLocked(int uid) { - if (!isUidValidForRules(uid)) return; - - // quick check: if this uid doesn't have INTERNET permission, it doesn't have - // network access anyway, so it is a waste to mess with it here. - final IPackageManager ipm = AppGlobals.getPackageManager(); + private boolean hasInternetPermissions(int uid) { try { - if (ipm.checkUidPermission(Manifest.permission.INTERNET, uid) + if (mIPm.checkUidPermission(Manifest.permission.INTERNET, uid) != PackageManager.PERMISSION_GRANTED) { - return; + return false; } } catch (RemoteException e) { } + return true; + } + + /** + * Applies network rules to bandwidth and firewall controllers based on uid policy. + * @param uid The uid for which to apply the latest policy + */ + void updateRulesForUidLocked(int uid) { + if (!isUidValidForRules(uid) || !hasInternetPermissions(uid)) return; final int uidPolicy = mUidPolicy.get(uid, POLICY_NONE); final boolean uidForeground = isUidForegroundLocked(uid); @@ -2598,7 +2639,6 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { intent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); mContext.sendBroadcastAsUser(intent, UserHandle.of(userId)); } - return true; } case MSG_ADVISE_PERSIST_THRESHOLD: { @@ -2831,13 +2871,5 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { removeRestrictBackgroundWhitelistedUidLocked(uid, true); } } - - @Override - public void onPackageRemovedAllUsers(String packageName, int uid) { - if (LOGV) Slog.v(TAG, "onPackageRemovedAllUsers: " + packageName + " ->" + uid); - synchronized (mRulesLock) { - removeRestrictBackgroundWhitelistedUidLocked(uid, true); - } - } } } diff --git a/services/core/java/com/android/server/notification/ConditionProviders.java b/services/core/java/com/android/server/notification/ConditionProviders.java index ce18818c1fba..9820a1261684 100644 --- a/services/core/java/com/android/server/notification/ConditionProviders.java +++ b/services/core/java/com/android/server/notification/ConditionProviders.java @@ -234,9 +234,13 @@ public class ConditionProviders extends ManagedServices { final ConditionRecord r = getRecordLocked(c.id, info.component, true /*create*/); r.info = info; r.condition = c; - if (mCallback != null) { - mCallback.onConditionChanged(c.id, c); - } + } + } + final int N = conditions.length; + for (int i = 0; i < N; i++) { + final Condition c = conditions[i]; + if (mCallback != null) { + mCallback.onConditionChanged(c.id, c); } } } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 36ddbcf50f07..0519cf237053 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -1350,9 +1350,9 @@ public class NotificationManagerService extends SystemService { } @Override - public boolean doesAppUseTopics(String pkg, int uid) { + public boolean doesUserUseTopics(String pkg, int uid) { enforceSystemOrSystemUI("Caller not system or systemui"); - return mRankingHelper.doesAppUseTopics(pkg, uid); + return mRankingHelper.doesUserUseTopics(pkg, uid); } /** diff --git a/services/core/java/com/android/server/notification/RankingConfig.java b/services/core/java/com/android/server/notification/RankingConfig.java index 17bb9075b0eb..9773474c2273 100644 --- a/services/core/java/com/android/server/notification/RankingConfig.java +++ b/services/core/java/com/android/server/notification/RankingConfig.java @@ -36,7 +36,7 @@ public interface RankingConfig { int getImportance(String packageName, int uid, Notification.Topic topic); - boolean doesAppUseTopics(String packageName, int uid); + boolean doesUserUseTopics(String packageName, int uid); boolean hasBannedTopics(String packageName, int uid); diff --git a/services/core/java/com/android/server/notification/RankingHelper.java b/services/core/java/com/android/server/notification/RankingHelper.java index 6554bf91cf18..91eab10e1258 100644 --- a/services/core/java/com/android/server/notification/RankingHelper.java +++ b/services/core/java/com/android/server/notification/RankingHelper.java @@ -521,15 +521,14 @@ public class RankingHelper implements RankingConfig { } @Override - public boolean doesAppUseTopics(String pkgName, int uid) { + public boolean doesUserUseTopics(String pkgName, int uid) { final Record r = getOrCreateRecord(pkgName, uid); - int numTopics = r.topics.size(); - if (numTopics == 0 - || (numTopics == 1 && r.topics.containsKey(Notification.TOPIC_DEFAULT))) { - return false; - } else { - return true; + for (Topic topic : r.topics.values()) { + if (topic.importance != Ranking.IMPORTANCE_UNSPECIFIED + && r.importance != topic.importance) + return true; } + return false; } private Topic getOrCreateTopic(Record r, Notification.Topic topic) { diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java index d82bb3d1ed9f..c6613f54d63c 100644 --- a/services/core/java/com/android/server/pm/LauncherAppsService.java +++ b/services/core/java/com/android/server/pm/LauncherAppsService.java @@ -201,8 +201,7 @@ public class LauncherAppsService extends SystemService { long ident = Binder.clearCallingIdentity(); try { List<ResolveInfo> apps = mPm.queryIntentActivitiesAsUser(mainIntent, - PackageManager.MATCH_DEBUG_TRIAGED_MISSING, - user.getIdentifier()); + PackageManager.MATCH_ENCRYPTION_AWARE_AND_UNAWARE, user.getIdentifier()); return new ParceledListSlice<>(apps); } finally { Binder.restoreCallingIdentity(ident); @@ -220,7 +219,7 @@ public class LauncherAppsService extends SystemService { long ident = Binder.clearCallingIdentity(); try { ResolveInfo app = mPm.resolveActivityAsUser(intent, - PackageManager.MATCH_DEBUG_TRIAGED_MISSING, user.getIdentifier()); + PackageManager.MATCH_ENCRYPTION_AWARE_AND_UNAWARE, user.getIdentifier()); return app; } finally { Binder.restoreCallingIdentity(ident); @@ -239,7 +238,7 @@ public class LauncherAppsService extends SystemService { try { IPackageManager pm = AppGlobals.getPackageManager(); PackageInfo info = pm.getPackageInfo(packageName, - PackageManager.MATCH_DEBUG_TRIAGED_MISSING, user.getIdentifier()); + PackageManager.MATCH_ENCRYPTION_AWARE_AND_UNAWARE, user.getIdentifier()); return info != null && info.applicationInfo.enabled; } finally { Binder.restoreCallingIdentity(ident); @@ -277,7 +276,7 @@ public class LauncherAppsService extends SystemService { try { IPackageManager pm = AppGlobals.getPackageManager(); ActivityInfo info = pm.getActivityInfo(component, - PackageManager.MATCH_DEBUG_TRIAGED_MISSING, user.getIdentifier()); + PackageManager.MATCH_ENCRYPTION_AWARE_AND_UNAWARE, user.getIdentifier()); return info != null; } finally { Binder.restoreCallingIdentity(ident); @@ -303,7 +302,7 @@ public class LauncherAppsService extends SystemService { try { IPackageManager pm = AppGlobals.getPackageManager(); ActivityInfo info = pm.getActivityInfo(component, - PackageManager.MATCH_DEBUG_TRIAGED_MISSING, user.getIdentifier()); + PackageManager.MATCH_ENCRYPTION_AWARE_AND_UNAWARE, user.getIdentifier()); if (!info.exported) { throw new SecurityException("Cannot launch non-exported components " + component); @@ -313,7 +312,7 @@ public class LauncherAppsService extends SystemService { // as calling startActivityAsUser ignores the category and just // resolves based on the component if present. List<ResolveInfo> apps = mPm.queryIntentActivitiesAsUser(launchIntent, - PackageManager.MATCH_DEBUG_TRIAGED_MISSING, user.getIdentifier()); + PackageManager.MATCH_ENCRYPTION_AWARE_AND_UNAWARE, user.getIdentifier()); final int size = apps.size(); for (int i = 0; i < size; ++i) { ActivityInfo activityInfo = apps.get(i).activityInfo; diff --git a/services/core/java/com/android/server/pm/OtaDexoptService.java b/services/core/java/com/android/server/pm/OtaDexoptService.java index da62a2d6fc84..94b3b2d613cf 100644 --- a/services/core/java/com/android/server/pm/OtaDexoptService.java +++ b/services/core/java/com/android/server/pm/OtaDexoptService.java @@ -55,8 +55,6 @@ import static com.android.server.pm.Installer.DEXOPT_OTA; public class OtaDexoptService extends IOtaDexopt.Stub { private final static String TAG = "OTADexopt"; private final static boolean DEBUG_DEXOPT = true; - // Apps used in the last 7 days. - private final static long DEXOPT_LRU_THRESHOLD_IN_MINUTES = 7 * 24 * 60; private final Context mContext; private final PackageDexOptimizer mPackageDexOptimizer; @@ -94,69 +92,9 @@ public class OtaDexoptService extends IOtaDexopt.Stub { if (mDexoptPackages != null) { throw new IllegalStateException("already called prepare()"); } - - mDexoptPackages = new LinkedList<>(); - - ArrayList<PackageParser.Package> pkgs; synchronized (mPackageManagerService.mPackages) { - pkgs = new ArrayList<PackageParser.Package>(mPackageManagerService.mPackages.values()); - } - - // Sort apps by importance for dexopt ordering. Important apps are given more priority - // in case the device runs out of space. - - // Give priority to core apps. - for (PackageParser.Package pkg : pkgs) { - if (pkg.coreApp) { - if (DEBUG_DEXOPT) { - Log.i(TAG, "Adding core app " + mDexoptPackages.size() + ": " + pkg.packageName); - } - mDexoptPackages.add(pkg); - } - } - pkgs.removeAll(mDexoptPackages); - - // Give priority to system apps that listen for pre boot complete. - Intent intent = new Intent(Intent.ACTION_PRE_BOOT_COMPLETED); - ArraySet<String> pkgNames = getPackageNamesForIntent(intent, UserHandle.USER_SYSTEM); - for (PackageParser.Package pkg : pkgs) { - if (pkgNames.contains(pkg.packageName)) { - if (DEBUG_DEXOPT) { - Log.i(TAG, "Adding pre boot system app " + mDexoptPackages.size() + ": " + - pkg.packageName); - } - mDexoptPackages.add(pkg); - } - } - pkgs.removeAll(mDexoptPackages); - - // Filter out packages that aren't recently used, add all remaining apps. - // TODO: add a property to control this? - if (mPackageManagerService.isHistoricalPackageUsageAvailable()) { - filterRecentlyUsedApps(pkgs, DEXOPT_LRU_THRESHOLD_IN_MINUTES * 60 * 1000); - } - mDexoptPackages.addAll(pkgs); - - // Now go ahead and also add the libraries required for these packages. - // TODO: Think about interleaving things. - Set<PackageParser.Package> dependencies = new HashSet<>(); - for (PackageParser.Package p : mDexoptPackages) { - dependencies.addAll(mPackageManagerService.findSharedNonSystemLibraries(p)); - } - if (!dependencies.isEmpty()) { - dependencies.removeAll(mDexoptPackages); - } - mDexoptPackages.addAll(dependencies); - - if (DEBUG_DEXOPT) { - StringBuilder sb = new StringBuilder(); - for (PackageParser.Package pkg : mDexoptPackages) { - if (sb.length() > 0) { - sb.append(", "); - } - sb.append(pkg.packageName); - } - Log.i(TAG, "Packages to be optimized: " + sb.toString()); + mDexoptPackages = PackageManagerServiceUtils.getPackagesForDexopt( + mPackageManagerService.mPackages.values(), mPackageManagerService); } } @@ -228,29 +166,6 @@ public class OtaDexoptService extends IOtaDexopt.Stub { return pkgNames; } - private void filterRecentlyUsedApps(Collection<PackageParser.Package> pkgs, - long dexOptLRUThresholdInMills) { - // Filter out packages that aren't recently used. - int total = pkgs.size(); - int skipped = 0; - long now = System.currentTimeMillis(); - for (Iterator<PackageParser.Package> i = pkgs.iterator(); i.hasNext();) { - PackageParser.Package pkg = i.next(); - long then = pkg.mLastPackageUsageTimeInMills; - if (then + dexOptLRUThresholdInMills < now) { - if (DEBUG_DEXOPT) { - Log.i(TAG, "Skipping dexopt of " + pkg.packageName + " last resumed: " + - ((then == 0) ? "never" : new Date(then))); - } - i.remove(); - skipped++; - } - } - if (DEBUG_DEXOPT) { - Log.i(TAG, "Skipped optimizing " + skipped + " of " + total); - } - } - private static class OTADexoptPackageDexOptimizer extends PackageDexOptimizer.ForcedUpdatePackageDexOptimizer { diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java index 23a58d06eed1..928c19fa84be 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerService.java +++ b/services/core/java/com/android/server/pm/PackageInstallerService.java @@ -212,8 +212,8 @@ public class PackageInstallerService extends IPackageInstaller.Stub { mCallbacks = new Callbacks(mInstallThread.getLooper()); mSessionsFile = new AtomicFile( - new File(Environment.getSystemSecureDirectory(), "install_sessions.xml")); - mSessionsDir = new File(Environment.getSystemSecureDirectory(), "install_sessions"); + new File(Environment.getDataSystemDirectory(), "install_sessions.xml")); + mSessionsDir = new File(Environment.getDataSystemDirectory(), "install_sessions"); mSessionsDir.mkdirs(); synchronized (mSessions) { diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index e47d5144cce0..7b6247cce8e2 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -99,7 +99,6 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; import android.app.ActivityManagerNative; -import android.app.AppGlobals; import android.app.IActivityManager; import android.app.admin.IDevicePolicyManager; import android.app.backup.IBackupManager; @@ -141,7 +140,6 @@ import android.content.pm.PackageManager.LegacyPackageDeleteObserver; import android.content.pm.PackageManagerInternal; import android.content.pm.PackageParser; import android.content.pm.PackageParser.ActivityIntentInfo; -import android.content.pm.PackageParser.Package; import android.content.pm.PackageParser.PackageLite; import android.content.pm.PackageParser.PackageParserException; import android.content.pm.PackageStats; @@ -265,7 +263,6 @@ import java.io.FileReader; import java.io.FilenameFilter; import java.io.IOException; import java.io.InputStream; -import java.io.PrintStream; import java.io.PrintWriter; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; @@ -1421,160 +1418,33 @@ public class PackageManagerService extends IPackageManager.Stub { PostInstallData data = mRunningInstalls.get(msg.arg1); mRunningInstalls.delete(msg.arg1); - boolean deleteOld = false; if (data != null) { InstallArgs args = data.args; - PackageInstalledInfo res = data.res; - - if (res.returnCode == PackageManager.INSTALL_SUCCEEDED) { - //TODO: Broadcast for child packages too - final String packageName = res.pkg.applicationInfo.packageName; - res.removedInfo.sendBroadcast(false, true, false); - Bundle extras = new Bundle(1); - extras.putInt(Intent.EXTRA_UID, res.uid); - - // Now that we successfully installed the package, grant runtime - // permissions if requested before broadcasting the install. - if ((args.installFlags - & PackageManager.INSTALL_GRANT_RUNTIME_PERMISSIONS) != 0 - && res.pkg.applicationInfo.targetSdkVersion - >= Build.VERSION_CODES.M) { - grantRequestedRuntimePermissions(res.pkg, args.user.getIdentifier(), - args.installGrantPermissions); - } - - synchronized (mPackages) { - mEphemeralApplicationRegistry.onPackageInstalledLPw(res.pkg); - } - - // Determine the set of users who are adding this - // package for the first time vs. those who are seeing - // an update. - int[] firstUsers; - int[] updateUsers = new int[0]; - if (res.origUsers == null || res.origUsers.length == 0) { - firstUsers = res.newUsers; - } else { - firstUsers = new int[0]; - for (int i=0; i<res.newUsers.length; i++) { - int user = res.newUsers[i]; - boolean isNew = true; - for (int j=0; j<res.origUsers.length; j++) { - if (res.origUsers[j] == user) { - isNew = false; - break; - } - } - if (isNew) { - int[] newFirst = new int[firstUsers.length+1]; - System.arraycopy(firstUsers, 0, newFirst, 0, - firstUsers.length); - newFirst[firstUsers.length] = user; - firstUsers = newFirst; - } else { - int[] newUpdate = new int[updateUsers.length+1]; - System.arraycopy(updateUsers, 0, newUpdate, 0, - updateUsers.length); - newUpdate[updateUsers.length] = user; - updateUsers = newUpdate; - } - } - } - // don't broadcast for ephemeral installs/updates - final boolean isEphemeral = isEphemeral(res.pkg); - if (!isEphemeral) { - sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, packageName, - extras, 0 /*flags*/, null /*targetPackage*/, - null /*finishedReceiver*/, firstUsers); - } - final boolean update = res.removedInfo.removedPackage != null; - if (update) { - extras.putBoolean(Intent.EXTRA_REPLACING, true); - } - if (!isEphemeral) { - sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, packageName, - extras, 0 /*flags*/, null /*targetPackage*/, - null /*finishedReceiver*/, updateUsers); - } - if (update) { - if (!isEphemeral) { - sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED, - packageName, extras, 0 /*flags*/, - null /*targetPackage*/, null /*finishedReceiver*/, - updateUsers); - sendPackageBroadcast(Intent.ACTION_MY_PACKAGE_REPLACED, - null /*package*/, null /*extras*/, 0 /*flags*/, - packageName /*targetPackage*/, - null /*finishedReceiver*/, updateUsers); - } - - // treat asec-hosted packages like removable media on upgrade - if (res.pkg.isForwardLocked() || isExternal(res.pkg)) { - if (DEBUG_INSTALL) { - Slog.i(TAG, "upgrading pkg " + res.pkg - + " is ASEC-hosted -> AVAILABLE"); - } - int[] uidArray = new int[] { res.pkg.applicationInfo.uid }; - ArrayList<String> pkgList = new ArrayList<String>(1); - pkgList.add(packageName); - sendResourcesChangedBroadcast(true, true, - pkgList,uidArray, null); - } - } - if (res.removedInfo.args != null) { - // Remove the replaced package's older resources safely now - deleteOld = true; - } - - - // Work that needs to happen on first install within each user - if (firstUsers.length > 0) { - for (int userId : firstUsers) { - synchronized (mPackages) { - // If this app is a browser and it's newly-installed for - // some users, clear any default-browser state in those - // users. The app's nature doesn't depend on the user, - // so we can just check its browser nature in any user - // and generalize. - if (packageIsBrowser(packageName, firstUsers[0])) { - mSettings.setDefaultBrowserPackageNameLPw( - null, userId); - } - - // We may also need to apply pending (restored) runtime - // permission grants within these users. - mSettings.applyPendingPermissionGrantsLPw( - packageName, userId); - } - } - } - // Log current value of "unknown sources" setting - EventLog.writeEvent(EventLogTags.UNKNOWN_SOURCES_ENABLED, - getUnknownSourcesSettings()); - } - // Force a gc to clear up things - Runtime.getRuntime().gc(); - // We delete after a gc for applications on sdcard. - if (deleteOld) { - synchronized (mInstallLock) { - res.removedInfo.args.doPostDeleteLI(true); - } - } - if (args.observer != null) { - try { - Bundle extras = extrasForInstallResult(res); - args.observer.onPackageInstalled(res.name, res.returnCode, - res.returnMsg, extras); - } catch (RemoteException e) { - Slog.i(TAG, "Observer no longer exists."); - } + PackageInstalledInfo parentRes = data.res; + + final boolean grantPermissions = (args.installFlags + & PackageManager.INSTALL_GRANT_RUNTIME_PERMISSIONS) != 0; + final String[] grantedPermissions = args.installGrantPermissions; + + // Handle the parent package + handlePackagePostInstall(parentRes, grantPermissions, grantedPermissions, + args.observer); + + // Handle the child packages + final int childCount = (parentRes.addedChildPackages != null) + ? parentRes.addedChildPackages.size() : 0; + for (int i = 0; i < childCount; i++) { + PackageInstalledInfo childRes = parentRes.addedChildPackages.valueAt(i); + handlePackagePostInstall(childRes, grantPermissions, grantedPermissions, + args.observer); } + + // Log tracing if needed if (args.traceMethod != null) { Trace.asyncTraceEnd(TRACE_TAG_PACKAGE_MANAGER, args.traceMethod, args.traceCookie); } - return; } else { Slog.e(TAG, "Bogus post-install token " + msg.arg1); } @@ -1761,6 +1631,185 @@ public class PackageManagerService extends IPackageManager.Stub { } } + private void handlePackagePostInstall(PackageInstalledInfo res, boolean grantPermissions, + String[] grantedPermissions, IPackageInstallObserver2 installObserver) { + if (res.returnCode == PackageManager.INSTALL_SUCCEEDED) { + // Send the removed broadcasts + if (res.removedInfo != null) { + res.removedInfo.sendPackageRemovedBroadcasts(); + } + + // Now that we successfully installed the package, grant runtime + // permissions if requested before broadcasting the install. + if (grantPermissions && res.pkg.applicationInfo.targetSdkVersion + >= Build.VERSION_CODES.M) { + grantRequestedRuntimePermissions(res.pkg, res.newUsers, grantedPermissions); + } + + final boolean update = res.removedInfo != null + && res.removedInfo.removedPackage != null; + + // If this is the first time we have child packages for a disabled privileged + // app that had no children, we grant requested runtime permissions to the new + // children if the parent on the system image had them already granted. + if (res.pkg.parentPackage != null) { + synchronized (mPackages) { + grantRuntimePermissionsGrantedToDisabledPrivSysPackageParentLPw(res.pkg); + } + } + + synchronized (mPackages) { + mEphemeralApplicationRegistry.onPackageInstalledLPw(res.pkg); + } + + final String packageName = res.pkg.applicationInfo.packageName; + Bundle extras = new Bundle(1); + extras.putInt(Intent.EXTRA_UID, res.uid); + + // Determine the set of users who are adding this package for + // the first time vs. those who are seeing an update. + int[] firstUsers = EMPTY_INT_ARRAY; + int[] updateUsers = EMPTY_INT_ARRAY; + if (res.origUsers == null || res.origUsers.length == 0) { + firstUsers = res.newUsers; + } else { + for (int newUser : res.newUsers) { + boolean isNew = true; + for (int origUser : res.origUsers) { + if (origUser == newUser) { + isNew = false; + break; + } + } + if (isNew) { + firstUsers = ArrayUtils.appendInt(firstUsers, newUser); + } else { + updateUsers = ArrayUtils.appendInt(updateUsers, newUser); + } + } + } + + // Send installed broadcasts if the install/update is not ephemeral + if (!isEphemeral(res.pkg)) { + // Send added for users that see the package for the first time + sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, packageName, + extras, 0 /*flags*/, null /*targetPackage*/, + null /*finishedReceiver*/, firstUsers); + + // Send added for users that don't see the package for the first time + if (update) { + extras.putBoolean(Intent.EXTRA_REPLACING, true); + } + sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, packageName, + extras, 0 /*flags*/, null /*targetPackage*/, + null /*finishedReceiver*/, updateUsers); + + // Send replaced for users that don't see the package for the first time + if (update) { + sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED, + packageName, extras, 0 /*flags*/, + null /*targetPackage*/, null /*finishedReceiver*/, + updateUsers); + sendPackageBroadcast(Intent.ACTION_MY_PACKAGE_REPLACED, + null /*package*/, null /*extras*/, 0 /*flags*/, + packageName /*targetPackage*/, + null /*finishedReceiver*/, updateUsers); + } + + // Send broadcast package appeared if forward locked/external for all users + // treat asec-hosted packages like removable media on upgrade + if (res.pkg.isForwardLocked() || isExternal(res.pkg)) { + if (DEBUG_INSTALL) { + Slog.i(TAG, "upgrading pkg " + res.pkg + + " is ASEC-hosted -> AVAILABLE"); + } + final int[] uidArray = new int[]{res.pkg.applicationInfo.uid}; + ArrayList<String> pkgList = new ArrayList<>(1); + pkgList.add(packageName); + sendResourcesChangedBroadcast(true, true, pkgList, uidArray, null); + } + } + + // Work that needs to happen on first install within each user + if (firstUsers != null && firstUsers.length > 0) { + synchronized (mPackages) { + for (int userId : firstUsers) { + // If this app is a browser and it's newly-installed for some + // users, clear any default-browser state in those users. The + // app's nature doesn't depend on the user, so we can just check + // its browser nature in any user and generalize. + if (packageIsBrowser(packageName, userId)) { + mSettings.setDefaultBrowserPackageNameLPw(null, userId); + } + + // We may also need to apply pending (restored) runtime + // permission grants within these users. + mSettings.applyPendingPermissionGrantsLPw(packageName, userId); + } + } + } + + // Log current value of "unknown sources" setting + EventLog.writeEvent(EventLogTags.UNKNOWN_SOURCES_ENABLED, + getUnknownSourcesSettings()); + + // Force a gc to clear up things + Runtime.getRuntime().gc(); + + // Remove the replaced package's older resources safely now + // We delete after a gc for applications on sdcard. + if (res.removedInfo != null && res.removedInfo.args != null) { + synchronized (mInstallLock) { + res.removedInfo.args.doPostDeleteLI(true); + } + } + } + + // If someone is watching installs - notify them + if (installObserver != null) { + try { + Bundle extras = extrasForInstallResult(res); + installObserver.onPackageInstalled(res.name, res.returnCode, + res.returnMsg, extras); + } catch (RemoteException e) { + Slog.i(TAG, "Observer no longer exists."); + } + } + } + + private void grantRuntimePermissionsGrantedToDisabledPrivSysPackageParentLPw( + PackageParser.Package pkg) { + if (pkg.parentPackage == null) { + return; + } + if (pkg.requestedPermissions == null) { + return; + } + final PackageSetting disabledSysParentPs = mSettings + .getDisabledSystemPkgLPr(pkg.parentPackage.packageName); + if (disabledSysParentPs == null || disabledSysParentPs.pkg == null + || !disabledSysParentPs.isPrivileged() + || (disabledSysParentPs.childPackageNames != null + && !disabledSysParentPs.childPackageNames.isEmpty())) { + return; + } + final int[] allUserIds = sUserManager.getUserIds(); + final int permCount = pkg.requestedPermissions.size(); + for (int i = 0; i < permCount; i++) { + String permission = pkg.requestedPermissions.get(i); + BasePermission bp = mSettings.mPermissions.get(permission); + if (bp == null || !(bp.isRuntime() || bp.isDevelopment())) { + continue; + } + for (int userId : allUserIds) { + if (disabledSysParentPs.getPermissionsState().hasRuntimePermission( + permission, userId)) { + grantRuntimePermission(pkg.packageName, permission, userId); + } + } + } + } + private StorageEventListener mStorageListener = new StorageEventListener() { @Override public void onVolumeStateChanged(VolumeInfo vol, int oldState, int newState) { @@ -1815,18 +1864,10 @@ public class PackageManagerService extends IPackageManager.Stub { } }; - private void grantRequestedRuntimePermissions(PackageParser.Package pkg, int userId, + private void grantRequestedRuntimePermissions(PackageParser.Package pkg, int[] userIds, String[] grantedPermissions) { - if (userId >= UserHandle.USER_SYSTEM) { + for (int userId : userIds) { grantRequestedRuntimePermissionsForUser(pkg, userId, grantedPermissions); - } else if (userId == UserHandle.USER_ALL) { - final int[] userIds; - synchronized (mPackages) { - userIds = UserManagerService.getInstance().getUserIds(); - } - for (int someUserId : userIds) { - grantRequestedRuntimePermissionsForUser(pkg, someUserId, grantedPermissions); - } } // We could have touched GID membership, so flush out packages.list @@ -2229,7 +2270,7 @@ public class PackageManagerService extends IPackageManager.Stub { + ps.codePathString + ", installStatus=" + ps.installStatus + ", versionCode=" + ps.versionCode + "; scanned versionCode=" + scannedPkg.mVersionCode); - removePackageSettingLI(scannedPkg, true); + removePackageLI(scannedPkg, true); mExpectingBetter.put(ps.name, ps.codePath); } @@ -6576,7 +6617,7 @@ public class PackageManagerService extends IPackageManager.Stub { != PackageManager.SIGNATURE_MATCH) { logCriticalInfo(Log.WARN, "Package " + ps.name + " appeared on system, but" + " signatures don't match existing userdata copy; removing"); - deletePackageLI(pkg.packageName, null, true, null, null, 0, null, false, null); + deletePackageLI(pkg.packageName, null, true, null, 0, null, false, null); ps = null; } else { /* @@ -6778,30 +6819,17 @@ public class PackageManagerService extends IPackageManager.Stub { // Extract pacakges only if profile-guided compilation is enabled because // otherwise BackgroundDexOptService will not dexopt them later. if (mUseJitProfiles) { - ArraySet<String> pkgs = getOptimizablePackages(); - if (pkgs != null) { - for (String pkg : pkgs) { - performDexOpt(pkg, null /* instructionSet */, false /* useProfiles */, - true /* extractOnly */, false /* force */); - } + List<PackageParser.Package> pkgs; + synchronized (mPackages) { + pkgs = PackageManagerServiceUtils.getPackagesForDexopt(mPackages.values(), this); } - } - } - - private ArraySet<String> getPackageNamesForIntent(Intent intent, int userId) { - List<ResolveInfo> ris = null; - try { - ris = AppGlobals.getPackageManager().queryIntentReceivers( - intent, null, 0, userId); - } catch (RemoteException e) { - } - ArraySet<String> pkgNames = new ArraySet<String>(); - if (ris != null) { - for (ResolveInfo ri : ris) { - pkgNames.add(ri.activityInfo.packageName); + for (PackageParser.Package pkg : pkgs) { + if (PackageDexOptimizer.canOptimizePackage(pkg)) { + performDexOpt(pkg.packageName, null /* instructionSet */, + false /* useProfiles */, true /* extractOnly */, false /* force */); + } } } - return pkgNames; } @Override @@ -8733,11 +8761,11 @@ public class PackageManagerService extends IPackageManager.Stub { } } - private void removePackageSettingLI(PackageParser.Package pkg, boolean chatty) { + private void removePackageLI(PackageParser.Package pkg, boolean chatty) { // Remove the parent package setting PackageSetting ps = (PackageSetting) pkg.mExtras; if (ps != null) { - removePackageSettingLI(ps, chatty); + removePackageLI(ps, chatty); } // Remove the child package setting final int childCount = (pkg.childPackages != null) ? pkg.childPackages.size() : 0; @@ -8745,12 +8773,12 @@ public class PackageManagerService extends IPackageManager.Stub { PackageParser.Package childPkg = pkg.childPackages.get(i); ps = (PackageSetting) childPkg.mExtras; if (ps != null) { - removePackageSettingLI(ps, chatty); + removePackageLI(ps, chatty); } } } - void removePackageSettingLI(PackageSetting ps, boolean chatty) { + void removePackageLI(PackageSetting ps, boolean chatty) { if (DEBUG_INSTALL) { if (chatty) Log.d(TAG, "Removing package " + ps.name); @@ -10451,16 +10479,21 @@ public class PackageManagerService extends IPackageManager.Stub { mHandler.sendMessage(msg); } - private void sendPackageAddedForUser(String packageName, PackageSetting pkgSetting, int userId) { + private void sendPackageAddedForUser(String packageName, PackageSetting pkgSetting, + int userId) { + final boolean isSystem = isSystemApp(pkgSetting) || isUpdatedSystemApp(pkgSetting); + sendPackageAddedForUser(packageName, isSystem, pkgSetting.appId, userId); + } + + private void sendPackageAddedForUser(String packageName, boolean isSystem, + int appId, int userId) { Bundle extras = new Bundle(1); - extras.putInt(Intent.EXTRA_UID, UserHandle.getUid(userId, pkgSetting.appId)); + extras.putInt(Intent.EXTRA_UID, UserHandle.getUid(userId, appId)); sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, packageName, extras, 0, null, null, new int[] {userId}); try { IActivityManager am = ActivityManagerNative.getDefault(); - final boolean isSystem = - isSystemApp(pkgSetting) || isUpdatedSystemApp(pkgSetting); if (isSystem && am.isUserRunning(userId, 0)) { // The just-installed/enabled app is bundled on the system, so presumed // to be able to run automatically without needing an explicit launch. @@ -10533,7 +10566,7 @@ public class PackageManagerService extends IPackageManager.Stub { info.removedPackage = packageName; info.removedUsers = new int[] {userId}; info.uid = UserHandle.getUid(userId, pkgSetting.appId); - info.sendBroadcast(false, false, false); + info.sendPackageRemovedBroadcasts(); } private void sendPackagesSuspendedForUser(String[] pkgList, int userId, boolean suspended) { @@ -11084,10 +11117,10 @@ public class PackageManagerService extends IPackageManager.Stub { mHandler.removeCallbacks(this); // Result object to be returned PackageInstalledInfo res = new PackageInstalledInfo(); - res.returnCode = currentStatus; + res.setReturnCode(currentStatus); res.uid = -1; res.pkg = null; - res.removedInfo = new PackageRemovedInfo(); + res.removedInfo = null; if (res.returnCode == PackageManager.INSTALL_SUCCEEDED) { args.doPreInstall(res.returnCode); synchronized (mInstallLock) { @@ -11099,7 +11132,8 @@ public class PackageManagerService extends IPackageManager.Stub { // A restore should be performed at this point if (a) the install // succeeded, (b) the operation is not an update, and (c) the new // package has not opted out of backup participation. - final boolean update = res.removedInfo.removedPackage != null; + final boolean update = res.removedInfo != null + && res.removedInfo.removedPackage != null; final int flags = (res.pkg == null) ? 0 : res.pkg.applicationInfo.flags; boolean doRestore = !update && ((flags & ApplicationInfo.FLAG_ALLOW_BACKUP) != 0); @@ -12688,25 +12722,42 @@ public class PackageManagerService extends IPackageManager.Stub { int returnCode; String returnMsg; PackageRemovedInfo removedInfo; + ArrayMap<String, PackageInstalledInfo> addedChildPackages; public void setError(int code, String msg) { - returnCode = code; - returnMsg = msg; + setReturnCode(code); + setReturnMessage(msg); Slog.w(TAG, msg); } public void setError(String msg, PackageParserException e) { - returnCode = e.error; - returnMsg = ExceptionUtils.getCompleteMessage(msg, e); + setReturnCode(e.error); + setReturnMessage(ExceptionUtils.getCompleteMessage(msg, e)); Slog.w(TAG, msg, e); } public void setError(String msg, PackageManagerException e) { returnCode = e.error; - returnMsg = ExceptionUtils.getCompleteMessage(msg, e); + setReturnMessage(ExceptionUtils.getCompleteMessage(msg, e)); Slog.w(TAG, msg, e); } + public void setReturnCode(int returnCode) { + this.returnCode = returnCode; + final int childCount = (addedChildPackages != null) ? addedChildPackages.size() : 0; + for (int i = 0; i < childCount; i++) { + addedChildPackages.valueAt(i).returnCode = returnCode; + } + } + + private void setReturnMessage(String returnMsg) { + this.returnMsg = returnMsg; + final int childCount = (addedChildPackages != null) ? addedChildPackages.size() : 0; + for (int i = 0; i < childCount; i++) { + addedChildPackages.valueAt(i).returnMsg = returnMsg; + } + } + // In some error cases we want to convey more info back to the observer String origPackage; String origPermission; @@ -12724,9 +12775,6 @@ public class PackageManagerService extends IPackageManager.Stub { String pkgName = pkg.packageName; if (DEBUG_INSTALL) Slog.d(TAG, "installNewPackageLI: " + pkg); - // TODO: b/23350563 - final boolean dataDirExists = Environment - .getDataUserPackageDirectory(volumeUuid, UserHandle.USER_SYSTEM, pkgName).exists(); synchronized(mPackages) { if (mSettings.mRenamedPackages.containsKey(pkgName)) { @@ -12751,21 +12799,17 @@ public class PackageManagerService extends IPackageManager.Stub { PackageParser.Package newPackage = scanPackageTracedLI(pkg, parseFlags, scanFlags, System.currentTimeMillis(), user); - updateSettingsLI(newPackage, installerPackageName, null, null, res, user); - prepareAppDataAfterInstall(newPackage); + updateSettingsLI(newPackage, installerPackageName, null, res, user); - // delete the partially installed application. the data directory will have to be - // restored if it was already existing - if (res.returnCode != PackageManager.INSTALL_SUCCEEDED) { - // remove package from internal structures. Note that we want deletePackageX to - // delete the package data and cache directories that it created in - // scanPackageLocked, unless those directories existed before we even tried to - // install. - deletePackageLI(pkgName, UserHandle.ALL, false, null, null, - dataDirExists ? PackageManager.DELETE_KEEP_DATA : 0, - res.removedInfo, true, null); - } + if (res.returnCode == PackageManager.INSTALL_SUCCEEDED) { + prepareAppDataAfterInstall(newPackage); + } else { + // Remove package from internal structures, but keep around any + // data that might have already existed + deletePackageLI(pkgName, UserHandle.ALL, false, null, + PackageManager.DELETE_KEEP_DATA, res.removedInfo, true, null); + } } catch (PackageManagerException e) { res.setError("Package couldn't be installed in " + pkg.codePath, e); } @@ -12810,14 +12854,12 @@ public class PackageManagerService extends IPackageManager.Stub { } private void replacePackageLI(PackageParser.Package pkg, int parseFlags, int scanFlags, - UserHandle user, String installerPackageName, String volumeUuid, - PackageInstalledInfo res) { + UserHandle user, String installerPackageName, PackageInstalledInfo res) { final boolean isEphemeral = (parseFlags & PackageParser.PARSE_IS_EPHEMERAL) != 0; final PackageParser.Package oldPackage; final String pkgName = pkg.packageName; final int[] allUsers; - final boolean[] perUserInstalled; // First find the old package info and check signatures synchronized(mPackages) { @@ -12826,7 +12868,7 @@ public class PackageManagerService extends IPackageManager.Stub { if (isEphemeral && !oldIsEphemeral) { // can't downgrade from full to ephemeral Slog.w(TAG, "Can't replace app with ephemeral: " + pkgName); - res.returnCode = PackageManager.INSTALL_FAILED_EPHEMERAL_INVALID; + res.setReturnCode(PackageManager.INSTALL_FAILED_EPHEMERAL_INVALID); return; } if (DEBUG_INSTALL) Slog.d(TAG, "replacePackageLI: new=" + pkg + ", old=" + oldPackage); @@ -12850,26 +12892,58 @@ public class PackageManagerService extends IPackageManager.Stub { // In case of rollback, remember per-user/profile install state allUsers = sUserManager.getUserIds(); - perUserInstalled = new boolean[allUsers.length]; - for (int i = 0; i < allUsers.length; i++) { - perUserInstalled[i] = ps != null ? ps.getInstalled(allUsers[i]) : false; + } + + // Update what is removed + res.removedInfo = new PackageRemovedInfo(); + res.removedInfo.uid = oldPackage.applicationInfo.uid; + res.removedInfo.removedPackage = oldPackage.packageName; + res.removedInfo.isUpdate = true; + final int childCount = (oldPackage.childPackages != null) + ? oldPackage.childPackages.size() : 0; + for (int i = 0; i < childCount; i++) { + boolean childPackageUpdated = false; + PackageParser.Package childPkg = oldPackage.childPackages.get(i); + if (res.addedChildPackages != null) { + PackageInstalledInfo childRes = res.addedChildPackages.get(childPkg.packageName); + if (childRes != null) { + childRes.removedInfo.uid = childPkg.applicationInfo.uid; + childRes.removedInfo.removedPackage = childPkg.packageName; + childRes.removedInfo.isUpdate = true; + childPackageUpdated = true; + } + } + if (!childPackageUpdated) { + PackageRemovedInfo childRemovedRes = new PackageRemovedInfo(); + childRemovedRes.removedPackage = childPkg.packageName; + childRemovedRes.isUpdate = false; + childRemovedRes.dataRemoved = true; + synchronized (mPackages) { + PackageSetting childPs = mSettings.peekPackageLPr(childPkg.packageName); + if (childPs != null) { + childRemovedRes.origUsers = childPs.queryInstalledUsers(allUsers, true); + } + } + if (res.removedInfo.removedChildPackages == null) { + res.removedInfo.removedChildPackages = new ArrayMap<>(); + } + res.removedInfo.removedChildPackages.put(childPkg.packageName, childRemovedRes); } } boolean sysPkg = (isSystemApp(oldPackage)); if (sysPkg) { replaceSystemPackageLI(oldPackage, pkg, parseFlags, scanFlags, - user, allUsers, perUserInstalled, installerPackageName, res); + user, allUsers, installerPackageName, res); } else { replaceNonSystemPackageLI(oldPackage, pkg, parseFlags, scanFlags, - user, allUsers, perUserInstalled, installerPackageName, res); + user, allUsers, installerPackageName, res); } } private void replaceNonSystemPackageLI(PackageParser.Package deletedPackage, PackageParser.Package pkg, int parseFlags, int scanFlags, UserHandle user, - int[] allUsers, boolean[] perUserInstalled, String installerPackageName, - PackageInstalledInfo res) { + int[] allUsers, String installerPackageName, PackageInstalledInfo res) { if (DEBUG_INSTALL) Slog.d(TAG, "replaceNonSystemPackageLI: new=" + pkg + ", old=" + deletedPackage); @@ -12881,7 +12955,7 @@ public class PackageManagerService extends IPackageManager.Stub { ? ((PackageSetting)pkg.mExtras).lastUpdateTime : 0; // First delete the existing package while retaining the data directory - if (!deletePackageLI(pkgName, null, true, null, null, PackageManager.DELETE_KEEP_DATA, + if (!deletePackageLI(pkgName, null, true, allUsers, PackageManager.DELETE_KEEP_DATA, res.removedInfo, true, pkg)) { // If the existing package wasn't successfully deleted res.setError(INSTALL_FAILED_REPLACE_COULDNT_DELETE, "replaceNonSystemPackageLI"); @@ -12906,8 +12980,7 @@ public class PackageManagerService extends IPackageManager.Stub { try { final PackageParser.Package newPackage = scanPackageTracedLI(pkg, parseFlags, scanFlags | SCAN_UPDATE_TIME, System.currentTimeMillis(), user); - updateSettingsLI(newPackage, installerPackageName, allUsers, - perUserInstalled, res, user); + updateSettingsLI(newPackage, installerPackageName, allUsers, res, user); prepareAppDataAfterInstall(newPackage); addedPkg = true; } catch (PackageManagerException e) { @@ -12920,8 +12993,8 @@ public class PackageManagerService extends IPackageManager.Stub { // Revert all internal state mutations and added folders for the failed install if (addedPkg) { - deletePackageLI(pkgName, null, true, allUsers, perUserInstalled, - PackageManager.DELETE_KEEP_DATA, res.removedInfo, true, null); + deletePackageLI(pkgName, null, true, allUsers, PackageManager.DELETE_KEEP_DATA, + res.removedInfo, true, null); } // Restore the old package @@ -12955,13 +13028,34 @@ public class PackageManagerService extends IPackageManager.Stub { Slog.i(TAG, "Successfully restored package : " + pkgName + " after failed upgrade"); } + } else { + synchronized (mPackages) { + PackageSetting ps = mSettings.peekPackageLPr(pkg.packageName); + if (ps != null) { + res.removedInfo.removedForAllUsers = mPackages.get(ps.name) == null; + if (res.removedInfo.removedChildPackages != null) { + final int childCount = res.removedInfo.removedChildPackages.size(); + // Iterate in reverse as we may modify the collection + for (int i = childCount - 1; i >= 0; i--) { + String childPackageName = res.removedInfo.removedChildPackages.keyAt(i); + if (res.addedChildPackages.containsKey(childPackageName)) { + res.removedInfo.removedChildPackages.removeAt(i); + } else { + PackageRemovedInfo childInfo = res.removedInfo + .removedChildPackages.valueAt(i); + childInfo.removedForAllUsers = mPackages.get( + childInfo.removedPackage) == null; + } + } + } + } + } } } private void replaceSystemPackageLI(PackageParser.Package deletedPackage, PackageParser.Package pkg, int parseFlags, int scanFlags, UserHandle user, - int[] allUsers, boolean[] perUserInstalled, String installerPackageName, - PackageInstalledInfo res) { + int[] allUsers, String installerPackageName, PackageInstalledInfo res) { if (DEBUG_INSTALL) Slog.d(TAG, "replaceSystemPackageLI: new=" + pkg + ", old=" + deletedPackage); @@ -12977,12 +13071,8 @@ public class PackageManagerService extends IPackageManager.Stub { // Kill package processes including services, providers, etc. killPackage(deletedPackage, "replace sys pkg"); - // Report the result for the parent package only - res.removedInfo.uid = deletedPackage.applicationInfo.uid; - res.removedInfo.removedPackage = deletedPackage.packageName; - // Remove existing system package - removePackageSettingLI(deletedPackage, true); + removePackageLI(deletedPackage, true); disabledSystem = disableSystemPackageLPw(deletedPackage, pkg); if (!disabledSystem) { @@ -13000,7 +13090,7 @@ public class PackageManagerService extends IPackageManager.Stub { // Successfully disabled the old package. Now proceed with re-installation deleteCodeCacheDirsLI(pkg); - res.returnCode = PackageManager.INSTALL_SUCCEEDED; + res.setReturnCode(PackageManager.INSTALL_SUCCEEDED); pkg.setApplicationInfoFlags(ApplicationInfo.FLAG_UPDATED_SYSTEM_APP, ApplicationInfo.FLAG_UPDATED_SYSTEM_APP); @@ -13015,7 +13105,8 @@ public class PackageManagerService extends IPackageManager.Stub { System.currentTimeMillis()); // Check for shared user id changes - String invalidPackageName = getParentOrChildPackageChangedSharedUser(deletedPackage, newPackage); + String invalidPackageName = getParentOrChildPackageChangedSharedUser( + deletedPackage, newPackage); if (invalidPackageName != null) { res.setError(INSTALL_FAILED_SHARED_USER_INCOMPATIBLE, "Forbidding shared user change from " + deletedPkgSetting.sharedUser @@ -13043,18 +13134,20 @@ public class PackageManagerService extends IPackageManager.Stub { if (childPackageDeleted) { PackageSetting ps = mSettings.getDisabledSystemPkgLPr( deletedChildPkg.packageName); - if (ps != null) { - removePackageDataLI(ps, allUsers, perUserInstalled, null, 0, false); + if (ps != null && res.removedInfo.removedChildPackages != null) { + PackageRemovedInfo removedChildRes = res.removedInfo + .removedChildPackages.get(deletedChildPkg.packageName); + removePackageDataLI(ps, allUsers, removedChildRes, 0, false); + removedChildRes.removedForAllUsers = mPackages.get(ps.name) == null; } } } - updateSettingsLI(newPackage, installerPackageName, allUsers, - perUserInstalled, res, user); + updateSettingsLI(newPackage, installerPackageName, allUsers, res, user); prepareAppDataAfterInstall(newPackage); } } catch (PackageManagerException e) { - res.returnCode = INSTALL_FAILED_INTERNAL_ERROR; + res.setReturnCode(INSTALL_FAILED_INTERNAL_ERROR); res.setError("Package couldn't be installed in " + pkg.codePath, e); } @@ -13235,22 +13328,23 @@ public class PackageManagerService extends IPackageManager.Stub { } private void updateSettingsLI(PackageParser.Package newPackage, String installerPackageName, - int[] allUsers, boolean[] perUserInstalled, PackageInstalledInfo res, UserHandle user) { + int[] allUsers, PackageInstalledInfo res, UserHandle user) { // Update the parent package setting - updateSettingsInternalLI(newPackage, installerPackageName, allUsers, perUserInstalled, + updateSettingsInternalLI(newPackage, installerPackageName, allUsers, res.origUsers, res, user); // Update the child packages setting final int childCount = (newPackage.childPackages != null) ? newPackage.childPackages.size() : 0; for (int i = 0; i < childCount; i++) { PackageParser.Package childPackage = newPackage.childPackages.get(i); - updateSettingsInternalLI(childPackage, installerPackageName, allUsers, perUserInstalled, - res, user); + PackageInstalledInfo childRes = res.addedChildPackages.get(childPackage.packageName); + updateSettingsInternalLI(childPackage, installerPackageName, allUsers, + childRes.origUsers, childRes, user); } } private void updateSettingsInternalLI(PackageParser.Package newPackage, - String installerPackageName, int[] allUsers, boolean[] perUserInstalled, + String installerPackageName, int[] allUsers, int[] installedForUsers, PackageInstalledInfo res, UserHandle user) { Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "updateSettings"); @@ -13274,26 +13368,30 @@ public class PackageManagerService extends IPackageManager.Stub { // of the package implies that the user actually wants to run that new code, // so we enable the package. PackageSetting ps = mSettings.mPackages.get(pkgName); + final int userId = user.getIdentifier(); if (ps != null) { if (isSystemApp(newPackage)) { - // NB: implicit assumption that system package upgrades apply to all users if (DEBUG_INSTALL) { Slog.d(TAG, "Implicitly enabling system package on upgrade: " + pkgName); } + // Enable system package for requested users if (res.origUsers != null) { - for (int userHandle : res.origUsers) { - ps.setEnabled(COMPONENT_ENABLED_STATE_DEFAULT, - userHandle, installerPackageName); + for (int origUserId : res.origUsers) { + if (userId == UserHandle.USER_ALL || userId == origUserId) { + ps.setEnabled(COMPONENT_ENABLED_STATE_DEFAULT, + origUserId, installerPackageName); + } } } // Also convey the prior install/uninstall state - if (allUsers != null && perUserInstalled != null) { - for (int i = 0; i < allUsers.length; i++) { + if (allUsers != null && installedForUsers != null) { + for (int currentUserId : allUsers) { + final boolean installed = ArrayUtils.contains( + installedForUsers, currentUserId); if (DEBUG_INSTALL) { - Slog.d(TAG, " user " + allUsers[i] - + " => " + perUserInstalled[i]); + Slog.d(TAG, " user " + currentUserId + " => " + installed); } - ps.setInstalled(perUserInstalled[i], allUsers[i]); + ps.setInstalled(installed, currentUserId); } // these install state changes will be persisted in the // upcoming call to mSettings.writeLPr(). @@ -13301,7 +13399,6 @@ public class PackageManagerService extends IPackageManager.Stub { } // It's implied that when a user requests installation, they want the app to be // installed and enabled. - int userId = user.getIdentifier(); if (userId != UserHandle.USER_ALL) { ps.setInstalled(true, userId); ps.setEnabled(COMPONENT_ENABLED_STATE_DEFAULT, userId, installerPackageName); @@ -13312,7 +13409,7 @@ public class PackageManagerService extends IPackageManager.Stub { res.pkg = newPackage; mSettings.setInstallStatus(pkgName, PackageSettingBase.PKG_INSTALL_COMPLETE); mSettings.setInstallerPackageName(pkgName, installerPackageName); - res.returnCode = PackageManager.INSTALL_SUCCEEDED; + res.setReturnCode(PackageManager.INSTALL_SUCCEEDED); //to update install status Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "writeSettings"); mSettings.writeLPr(); @@ -13343,11 +13440,12 @@ public class PackageManagerService extends IPackageManager.Stub { boolean replace = false; int scanFlags = SCAN_NEW_INSTALL | SCAN_UPDATE_SIGNATURE; if (args.move != null) { - // moving a complete application; perfom an initial scan on the new install location + // moving a complete application; perform an initial scan on the new install location scanFlags |= SCAN_INITIAL; } + // Result object to be returned - res.returnCode = PackageManager.INSTALL_SUCCEEDED; + res.setReturnCode(PackageManager.INSTALL_SUCCEEDED); if (DEBUG_INSTALL) Slog.d(TAG, "installPackageLI: path=" + tmpPackageFile); @@ -13355,7 +13453,7 @@ public class PackageManagerService extends IPackageManager.Stub { if (ephemeral && (forwardLocked || onExternal)) { Slog.i(TAG, "Incompatible ephemeral install; fwdLocked=" + forwardLocked + " external=" + onExternal); - res.returnCode = PackageManager.INSTALL_FAILED_EPHEMERAL_INVALID; + res.setReturnCode(PackageManager.INSTALL_FAILED_EPHEMERAL_INVALID); return; } @@ -13380,6 +13478,33 @@ public class PackageManagerService extends IPackageManager.Stub { Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); } + // If we are installing a clustered package add results for the children + if (pkg.childPackages != null) { + synchronized (mPackages) { + final int childCount = pkg.childPackages.size(); + for (int i = 0; i < childCount; i++) { + PackageParser.Package childPkg = pkg.childPackages.get(i); + PackageInstalledInfo childRes = new PackageInstalledInfo(); + childRes.setReturnCode(PackageManager.INSTALL_SUCCEEDED); + childRes.pkg = childPkg; + childRes.name = childPkg.packageName; + PackageSetting childPs = mSettings.peekPackageLPr(childPkg.packageName); + if (childPs != null) { + childRes.origUsers = childPs.queryInstalledUsers( + sUserManager.getUserIds(), true); + } + if ((mPackages.containsKey(childPkg.packageName))) { + childRes.removedInfo = new PackageRemovedInfo(); + childRes.removedInfo.removedPackage = childPkg.packageName; + } + if (res.addedChildPackages == null) { + res.addedChildPackages = new ArrayMap<>(); + } + res.addedChildPackages.put(childPkg.packageName, childRes); + } + } + } + // If package doesn't declare API override, mark that we have an install // time CPU ABI override. if (TextUtils.isEmpty(pkg.cpuAbiOverride)) { @@ -13611,7 +13736,7 @@ public class PackageManagerService extends IPackageManager.Stub { if (replace) { replacePackageLI(pkg, parseFlags, scanFlags | SCAN_REPLACING, args.user, - installerPackageName, volumeUuid, res); + installerPackageName, res); } else { installNewPackageLI(pkg, parseFlags, scanFlags | SCAN_DELETE_DATA_ON_FAILURES, args.user, installerPackageName, volumeUuid, res); @@ -13621,6 +13746,17 @@ public class PackageManagerService extends IPackageManager.Stub { if (ps != null) { res.newUsers = ps.queryInstalledUsers(sUserManager.getUserIds(), true); } + + final int childCount = (pkg.childPackages != null) ? pkg.childPackages.size() : 0; + for (int i = 0; i < childCount; i++) { + PackageParser.Package childPkg = pkg.childPackages.get(i); + PackageInstalledInfo childRes = res.addedChildPackages.get(childPkg.packageName); + PackageSetting childPs = mSettings.peekPackageLPr(childPkg.packageName); + if (childPs != null) { + childRes.newUsers = childPs.queryInstalledUsers( + sUserManager.getUserIds(), true); + } + } } } @@ -13637,10 +13773,17 @@ public class PackageManagerService extends IPackageManager.Stub { MATCH_DEBUG_TRIAGED_MISSING, (userId == UserHandle.USER_ALL) ? UserHandle.USER_SYSTEM : userId); - mHandler.removeMessages(START_INTENT_FILTER_VERIFICATIONS); - final Message msg = mHandler.obtainMessage(START_INTENT_FILTER_VERIFICATIONS); + Message msg = mHandler.obtainMessage(START_INTENT_FILTER_VERIFICATIONS); msg.obj = new IFVerificationParams(pkg, replacing, userId, verifierUid); mHandler.sendMessage(msg); + + final int childCount = (pkg.childPackages != null) ? pkg.childPackages.size() : 0; + for (int i = 0; i < childCount; i++) { + PackageParser.Package childPkg = pkg.childPackages.get(i); + msg = mHandler.obtainMessage(START_INTENT_FILTER_VERIFICATIONS); + msg.obj = new IFVerificationParams(childPkg, replacing, userId, verifierUid); + mHandler.sendMessage(msg); + } } private void verifyIntentFiltersIfNeeded(int userId, int verifierUid, boolean replacing, @@ -13959,60 +14102,37 @@ public class PackageManagerService extends IPackageManager.Stub { return PackageManager.DELETE_FAILED_DEVICE_POLICY_MANAGER; } - boolean removedForAllUsers = false; - boolean systemUpdate = false; - PackageParser.Package uninstalledPkg; // for the uninstall-updates case and restricted profiles, remember the per- - // userhandle installed state + // user handle installed state int[] allUsers; - boolean[] perUserInstalled; synchronized (mPackages) { uninstalledPkg = mPackages.get(packageName); PackageSetting ps = mSettings.mPackages.get(packageName); - allUsers = sUserManager.getUserIds(); - perUserInstalled = new boolean[allUsers.length]; - for (int i = 0; i < allUsers.length; i++) { - perUserInstalled[i] = ps != null ? ps.getInstalled(allUsers[i]) : false; + if (ps == null || uninstalledPkg == null) { + Slog.w(TAG, "Not removing non-existent package " + packageName); + return PackageManager.DELETE_FAILED_INTERNAL_ERROR; } + allUsers = sUserManager.getUserIds(); + info.origUsers = ps.queryInstalledUsers(allUsers, true); } synchronized (mInstallLock) { if (DEBUG_REMOVE) Slog.d(TAG, "deletePackageX: pkg=" + packageName + " user=" + userId); - res = deletePackageLI(packageName, removeForUser, true, allUsers, perUserInstalled, + res = deletePackageLI(packageName, removeForUser, true, allUsers, flags | REMOVE_CHATTY, info, true, null); - systemUpdate = info.isRemovedPackageSystemUpdate; synchronized (mPackages) { if (res) { - if (!systemUpdate && mPackages.get(packageName) == null) { - removedForAllUsers = true; - } mEphemeralApplicationRegistry.onPackageUninstalledLPw(uninstalledPkg); } } - if (DEBUG_REMOVE) Slog.d(TAG, "delete res: systemUpdate=" + systemUpdate - + " removedForAllUsers=" + removedForAllUsers); } if (res) { - info.sendBroadcast(true, systemUpdate, removedForAllUsers); - - // If the removed package was a system update, the old system package - // was re-enabled; we need to broadcast this information - if (systemUpdate) { - Bundle extras = new Bundle(1); - extras.putInt(Intent.EXTRA_UID, info.removedAppId >= 0 - ? info.removedAppId : info.uid); - extras.putBoolean(Intent.EXTRA_REPLACING, true); - - sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, packageName, - extras, 0, null, null, null); - sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED, packageName, - extras, 0, null, null, null); - sendPackageBroadcast(Intent.ACTION_MY_PACKAGE_REPLACED, null, - null, 0, packageName, null, null); - } + info.sendPackageRemovedBroadcasts(); + info.sendSystemPackageUpdatedBroadcasts(); + info.sendSystemPackageAppearedBroadcasts(); } // Force a gc here. Runtime.getRuntime().gc(); @@ -14031,25 +14151,78 @@ public class PackageManagerService extends IPackageManager.Stub { String removedPackage; int uid = -1; int removedAppId = -1; + int[] origUsers; int[] removedUsers = null; boolean isRemovedPackageSystemUpdate = false; + boolean isUpdate; + boolean dataRemoved; + boolean removedForAllUsers; // Clean up resources deleted packages. InstallArgs args = null; + ArrayMap<String, PackageRemovedInfo> removedChildPackages; + ArrayMap<String, PackageInstalledInfo> appearedChildPackages; - void sendBroadcast(boolean fullRemove, boolean replacing, boolean removedForAllUsers) { - Bundle extras = new Bundle(1); + void sendPackageRemovedBroadcasts() { + sendPackageRemovedBroadcastInternal(); + final int childCount = removedChildPackages != null ? removedChildPackages.size() : 0; + for (int i = 0; i < childCount; i++) { + PackageRemovedInfo childInfo = removedChildPackages.valueAt(i); + childInfo.sendPackageRemovedBroadcastInternal(); + } + } + + void sendSystemPackageUpdatedBroadcasts() { + if (isRemovedPackageSystemUpdate) { + sendSystemPackageUpdatedBroadcastsInternal(); + final int childCount = (removedChildPackages != null) + ? removedChildPackages.size() : 0; + for (int i = 0; i < childCount; i++) { + PackageRemovedInfo childInfo = removedChildPackages.valueAt(i); + if (childInfo.isRemovedPackageSystemUpdate) { + childInfo.sendSystemPackageUpdatedBroadcastsInternal(); + } + } + } + } + + void sendSystemPackageAppearedBroadcasts() { + final int packageCount = (appearedChildPackages != null) + ? appearedChildPackages.size() : 0; + for (int i = 0; i < packageCount; i++) { + PackageInstalledInfo installedInfo = appearedChildPackages.valueAt(i); + for (int userId : installedInfo.newUsers) { + sendPackageAddedForUser(installedInfo.name, true, + UserHandle.getAppId(installedInfo.uid), userId); + } + } + } + + private void sendSystemPackageUpdatedBroadcastsInternal() { + Bundle extras = new Bundle(2); extras.putInt(Intent.EXTRA_UID, removedAppId >= 0 ? removedAppId : uid); - extras.putBoolean(Intent.EXTRA_DATA_REMOVED, fullRemove); - if (replacing) { + extras.putBoolean(Intent.EXTRA_REPLACING, true); + sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, removedPackage, + extras, 0, null, null, null); + sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED, removedPackage, + extras, 0, null, null, null); + sendPackageBroadcast(Intent.ACTION_MY_PACKAGE_REPLACED, null, + null, 0, removedPackage, null, null); + } + + private void sendPackageRemovedBroadcastInternal() { + Bundle extras = new Bundle(2); + extras.putInt(Intent.EXTRA_UID, removedAppId >= 0 ? removedAppId : uid); + extras.putBoolean(Intent.EXTRA_DATA_REMOVED, dataRemoved); + if (isUpdate || isRemovedPackageSystemUpdate) { extras.putBoolean(Intent.EXTRA_REPLACING, true); } extras.putBoolean(Intent.EXTRA_REMOVED_FOR_ALL_USERS, removedForAllUsers); if (removedPackage != null) { sendPackageBroadcast(Intent.ACTION_PACKAGE_REMOVED, removedPackage, extras, 0, null, null, removedUsers); - if (fullRemove && !replacing) { - sendPackageBroadcast(Intent.ACTION_PACKAGE_FULLY_REMOVED, removedPackage, - extras, 0, null, null, removedUsers); + if (dataRemoved && !isRemovedPackageSystemUpdate) { + sendPackageBroadcast(Intent.ACTION_PACKAGE_FULLY_REMOVED, + removedPackage, extras, 0, null, null, removedUsers); } } if (removedAppId >= 0) { @@ -14065,12 +14238,11 @@ public class PackageManagerService extends IPackageManager.Stub { * make sure this flag is set for partially installed apps. If not its meaningless to * delete a partially installed application. */ - private void removePackageDataLI(PackageSetting ps, - int[] allUserHandles, boolean[] perUserInstalled, + private void removePackageDataLI(PackageSetting ps, int[] allUserHandles, PackageRemovedInfo outInfo, int flags, boolean writeSettings) { String packageName = ps.name; if (DEBUG_REMOVE) Slog.d(TAG, "removePackageDataLI: " + ps); - removePackageSettingLI(ps, (flags&REMOVE_CHATTY) != 0); + removePackageLI(ps, (flags&REMOVE_CHATTY) != 0); // Retrieve object to delete permissions for shared user later on final PackageSetting deletedPs; // reader @@ -14085,6 +14257,9 @@ public class PackageManagerService extends IPackageManager.Stub { } if ((flags&PackageManager.DELETE_KEEP_DATA) == 0) { removeDataDirsLI(ps.volumeUuid, packageName); + if (outInfo != null) { + outInfo.dataRemoved = true; + } schedulePackageCleaning(packageName, UserHandle.USER_ALL, true); } // writer @@ -14126,16 +14301,16 @@ public class PackageManagerService extends IPackageManager.Stub { } // make sure to preserve per-user disabled state if this removal was just // a downgrade of a system app to the factory package - if (allUserHandles != null && perUserInstalled != null) { + if (allUserHandles != null && outInfo != null && outInfo.origUsers != null) { if (DEBUG_REMOVE) { Slog.d(TAG, "Propagating install state across downgrade"); } - for (int i = 0; i < allUserHandles.length; i++) { + for (int userId : allUserHandles) { + final boolean installed = ArrayUtils.contains(outInfo.origUsers, userId); if (DEBUG_REMOVE) { - Slog.d(TAG, " user " + allUserHandles[i] - + " => " + perUserInstalled[i]); + Slog.d(TAG, " user " + userId + " => " + installed); } - ps.setInstalled(perUserInstalled[i], allUserHandles[i]); + ps.setInstalled(installed, userId); } } } @@ -14167,16 +14342,15 @@ public class PackageManagerService extends IPackageManager.Stub { * Tries to delete system package. */ private boolean deleteSystemPackageLI(PackageParser.Package deletedPkg, - PackageSetting deletedPs, int[] allUserHandles, boolean[] perUserInstalled, - int flags, PackageRemovedInfo outInfo, boolean writeSettings, - PackageParser.Package replacingPackage) { + PackageSetting deletedPs, int[] allUserHandles, int flags, PackageRemovedInfo outInfo, + boolean writeSettings) { if (deletedPkg.parentPackage != null) { Slog.w(TAG, "Attempt to delete child system package " + deletedPkg.packageName); return false; } final boolean applyUserRestrictions - = (allUserHandles != null) && (perUserInstalled != null); + = (allUserHandles != null) && (outInfo.origUsers != null); final PackageSetting disabledPs; // Confirm if the system package has been updated // An updated system app can be deleted. This will also have to restore @@ -14199,14 +14373,31 @@ public class PackageManagerService extends IPackageManager.Stub { if (DEBUG_REMOVE) { if (applyUserRestrictions) { Slog.d(TAG, "Remembering install states:"); - for (int i = 0; i < allUserHandles.length; i++) { - Slog.d(TAG, " u=" + allUserHandles[i] + " inst=" + perUserInstalled[i]); + for (int userId : allUserHandles) { + final boolean finstalled = ArrayUtils.contains(outInfo.origUsers, userId); + Slog.d(TAG, " u=" + userId + " inst=" + finstalled); } } } // Delete the updated package outInfo.isRemovedPackageSystemUpdate = true; + if (outInfo.removedChildPackages != null) { + final int childCount = (deletedPkg.childPackages != null) + ? deletedPkg.childPackages.size() : 0; + for (int i = 0; i < childCount; i++) { + String childPackageName = deletedPkg.childPackages.get(i).packageName; + if (disabledPs.childPackageNames != null && disabledPs.childPackageNames + .contains(childPackageName)) { + PackageRemovedInfo childInfo = outInfo.removedChildPackages.get( + childPackageName); + if (childInfo != null) { + childInfo.isRemovedPackageSystemUpdate = true; + } + } + } + } + if (disabledPs.versionCode < deletedPs.versionCode) { // Delete data for downgrades flags &= ~PackageManager.DELETE_KEEP_DATA; @@ -14214,8 +14405,9 @@ public class PackageManagerService extends IPackageManager.Stub { // Preserve data by setting flag flags |= PackageManager.DELETE_KEEP_DATA; } + boolean ret = deleteInstalledPackageLI(deletedPkg, true, flags, allUserHandles, - perUserInstalled, outInfo, writeSettings, replacingPackage); + outInfo, writeSettings, disabledPs.pkg); if (!ret) { return false; } @@ -14261,14 +14453,14 @@ public class PackageManagerService extends IPackageManager.Stub { if (DEBUG_REMOVE) { Slog.d(TAG, "Propagating install state across reinstall"); } - for (int i = 0; i < allUserHandles.length; i++) { + for (int userId : allUserHandles) { + final boolean installed = ArrayUtils.contains(outInfo.origUsers, userId); if (DEBUG_REMOVE) { - Slog.d(TAG, " user " + allUserHandles[i] - + " => " + perUserInstalled[i]); + Slog.d(TAG, " user " + userId + " => " + installed); } - ps.setInstalled(perUserInstalled[i], allUserHandles[i]); + ps.setInstalled(installed, userId); - mSettings.writeRuntimePermissionsForUserLPr(allUserHandles[i], false); + mSettings.writeRuntimePermissionsForUserLPr(userId, false); } // Regardless of writeSettings we need to ensure that this restriction // state propagation is persisted @@ -14284,7 +14476,7 @@ public class PackageManagerService extends IPackageManager.Stub { private boolean deleteInstalledPackageLI(PackageParser.Package pkg, boolean deleteCodeAndResources, int flags, int[] allUserHandles, - boolean[] perUserInstalled, PackageRemovedInfo outInfo, boolean writeSettings, + PackageRemovedInfo outInfo, boolean writeSettings, PackageParser.Package replacingPackage) { PackageSetting ps = null; @@ -14302,11 +14494,26 @@ public class PackageManagerService extends IPackageManager.Stub { if (outInfo != null) { outInfo.uid = ps.appId; } + + if (outInfo != null && outInfo.removedChildPackages != null) { + final int childCount = (pkg.childPackages != null) ? pkg.childPackages.size() : 0; + for (int i = 0; i < childCount; i++) { + String childPackageName = ps.childPackageNames.get(i); + PackageSetting childPs = mSettings.mPackages.get(childPackageName); + if (childPs == null) { + return false; + } + PackageRemovedInfo childInfo = outInfo.removedChildPackages.get( + childPackageName); + if (childInfo != null) { + childInfo.uid = childPs.appId; + } + } + } } // Delete package data from internal structures and also remove data if flag is set - removePackageDataLI(ps, allUserHandles, perUserInstalled, outInfo, flags, - writeSettings); + removePackageDataLI(ps, allUserHandles, outInfo, flags, writeSettings); // Delete the child packages data final int childCount = (pkg.childPackages != null) ? pkg.childPackages.size() : 0; @@ -14316,18 +14523,21 @@ public class PackageManagerService extends IPackageManager.Stub { childPs = mSettings.peekPackageLPr(pkg.childPackages.get(i).packageName); } if (childPs != null) { + PackageRemovedInfo childOutInfo = (outInfo != null + && outInfo.removedChildPackages != null) + ? outInfo.removedChildPackages.get(childPs.name) : null; final int deleteFlags = (flags & DELETE_KEEP_DATA) != 0 && (replacingPackage != null && !replacingPackage.hasChildPackage(childPs.name)) ? flags & ~DELETE_KEEP_DATA : flags; - removePackageDataLI(childPs, allUserHandles, perUserInstalled, outInfo, + removePackageDataLI(childPs, allUserHandles, childOutInfo, deleteFlags, writeSettings); } } // Delete application code and resources only for parent packages if (ps.pkg.parentPackage == null) { - if (deleteCodeAndResources && (outInfo != null)) { + if (deleteCodeAndResources && (outInfo != null)) { outInfo.args = createInstallArgsForExisting(packageFlagsToInstallFlags(ps), ps.codePathString, ps.resourcePathString, getAppDexInstructionSets(ps)); if (DEBUG_SD_INSTALL) Slog.i(TAG, "args=" + outInfo.args); @@ -14398,8 +14608,8 @@ public class PackageManagerService extends IPackageManager.Stub { * This method handles package deletion in general */ private boolean deletePackageLI(String packageName, UserHandle user, - boolean deleteCodeAndResources, int[] allUserHandles, boolean[] perUserInstalled, - int flags, PackageRemovedInfo outInfo, boolean writeSettings, + boolean deleteCodeAndResources, int[] allUserHandles, int flags, + PackageRemovedInfo outInfo, boolean writeSettings, PackageParser.Package replacingPackage) { if (packageName == null) { Slog.w(TAG, "Attempt to delete null packageName."); @@ -14476,19 +14686,83 @@ public class PackageManagerService extends IPackageManager.Stub { } } + // If we are deleting a composite package for all users, keep track + // of result for each child. + if (ps.childPackageNames != null && outInfo != null) { + synchronized (mPackages) { + final int childCount = ps.childPackageNames.size(); + outInfo.removedChildPackages = new ArrayMap<>(childCount); + for (int i = 0; i < childCount; i++) { + String childPackageName = ps.childPackageNames.get(i); + PackageRemovedInfo childInfo = new PackageRemovedInfo(); + childInfo.removedPackage = childPackageName; + outInfo.removedChildPackages.put(childPackageName, childInfo); + PackageSetting childPs = mSettings.peekPackageLPr(childPackageName); + if (childPs != null) { + childInfo.origUsers = childPs.queryInstalledUsers(allUserHandles, true); + } + } + } + } + boolean ret = false; if (isSystemApp(ps)) { if (DEBUG_REMOVE) Slog.d(TAG, "Removing system package: " + ps.name); - // When an updated system application is deleted we delete the existing resources as well and - // fall back to existing code in system partition - ret = deleteSystemPackageLI(ps.pkg, ps, allUserHandles, perUserInstalled, - flags, outInfo, writeSettings, replacingPackage); + // When an updated system application is deleted we delete the existing resources + // as well and fall back to existing code in system partition + ret = deleteSystemPackageLI(ps.pkg, ps, allUserHandles, flags, outInfo, writeSettings); } else { if (DEBUG_REMOVE) Slog.d(TAG, "Removing non-system package: " + ps.name); // Kill application pre-emptively especially for apps on sd. killApplication(packageName, ps.appId, "uninstall pkg"); ret = deleteInstalledPackageLI(ps.pkg, deleteCodeAndResources, flags, allUserHandles, - perUserInstalled, outInfo, writeSettings, replacingPackage); + outInfo, writeSettings, replacingPackage); + } + + // Take a note whether we deleted the package for all users + if (outInfo != null) { + outInfo.removedForAllUsers = mPackages.get(ps.name) == null; + if (outInfo.removedChildPackages != null) { + synchronized (mPackages) { + final int childCount = outInfo.removedChildPackages.size(); + for (int i = 0; i < childCount; i++) { + PackageRemovedInfo childInfo = outInfo.removedChildPackages.valueAt(i); + if (childInfo != null) { + childInfo.removedForAllUsers = mPackages.get( + childInfo.removedPackage) == null; + } + } + } + } + // If we uninstalled an update to a system app there may be some + // child packages that appeared as they are declared in the system + // app but were not declared in the update. + if (isSystemApp(ps)) { + synchronized (mPackages) { + PackageSetting updatedPs = mSettings.peekPackageLPr(ps.name); + final int childCount = (updatedPs.childPackageNames != null) + ? updatedPs.childPackageNames.size() : 0; + for (int i = 0; i < childCount; i++) { + String childPackageName = updatedPs.childPackageNames.get(i); + if (outInfo.removedChildPackages == null + || outInfo.removedChildPackages.indexOfKey(childPackageName) < 0) { + PackageSetting childPs = mSettings.peekPackageLPr(childPackageName); + if (childPs == null) { + continue; + } + PackageInstalledInfo installRes = new PackageInstalledInfo(); + installRes.name = childPackageName; + installRes.newUsers = childPs.queryInstalledUsers(allUserHandles, true); + installRes.pkg = mPackages.get(childPackageName); + installRes.uid = childPs.pkg.applicationInfo.uid; + if (outInfo.appearedChildPackages == null) { + outInfo.appearedChildPackages = new ArrayMap<>(); + } + outInfo.appearedChildPackages.put(childPackageName, installRes); + } + } + } + } } return ret; @@ -17255,7 +17529,7 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName()); // Delete package internally PackageRemovedInfo outInfo = new PackageRemovedInfo(); synchronized (mInstallLock) { - boolean res = deletePackageLI(pkgName, null, false, null, null, + boolean res = deletePackageLI(pkgName, null, false, null, PackageManager.DELETE_KEEP_DATA, outInfo, false, null); if (res) { pkgList.add(pkgName); @@ -17401,7 +17675,7 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName()); final ApplicationInfo info = ps.pkg.applicationInfo; final PackageRemovedInfo outInfo = new PackageRemovedInfo(); - if (deletePackageLI(ps.name, null, false, null, null, + if (deletePackageLI(ps.name, null, false, null, PackageManager.DELETE_KEEP_DATA, outInfo, false, null)) { unloaded.add(info); } else { @@ -17423,8 +17697,9 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName()); * recycled. */ private void reconcileUsers(String volumeUuid) { + // TODO: also reconcile DE directories final File[] files = FileUtils - .listFilesOrEmpty(Environment.getDataUserDirectory(volumeUuid)); + .listFilesOrEmpty(Environment.getDataUserCeDirectory(volumeUuid)); for (File file : files) { if (!file.isDirectory()) continue; @@ -17553,8 +17828,8 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName()); Slog.v(TAG, "reconcileAppsData for " + volumeUuid + " u" + userId + " 0x" + Integer.toHexString(flags)); - final File ceDir = Environment.getDataUserCredentialEncryptedDirectory(volumeUuid, userId); - final File deDir = Environment.getDataUserDeviceEncryptedDirectory(volumeUuid, userId); + final File ceDir = Environment.getDataUserCeDirectory(volumeUuid, userId); + final File deDir = Environment.getDataUserDeDirectory(volumeUuid, userId); boolean restoreconNeeded = false; diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java new file mode 100644 index 000000000000..a3ac514ccd22 --- /dev/null +++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.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.pm; + +import android.app.AppGlobals; +import android.content.Intent; +import android.content.pm.PackageParser; +import android.content.pm.PackageParser.Package; +import android.content.pm.ResolveInfo; +import android.os.UserHandle; +import android.os.RemoteException; +import android.util.ArraySet; +import android.util.Log; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; + +import static com.android.server.pm.PackageManagerService.DEBUG_DEXOPT; +import static com.android.server.pm.PackageManagerService.TAG; + +/** + * Class containing helper methods for the PackageManagerService. + * + * {@hide} + */ +public class PackageManagerServiceUtils { + private final static long SEVEN_DAYS_IN_MILLISECONDS = 7 * 24 * 60 * 60 * 1000; + + private static ArraySet<String> getPackageNamesForIntent(Intent intent, int userId) { + List<ResolveInfo> ris = null; + try { + ris = AppGlobals.getPackageManager().queryIntentReceivers(intent, null, 0, userId); + } catch (RemoteException e) { + } + ArraySet<String> pkgNames = new ArraySet<String>(); + if (ris != null) { + for (ResolveInfo ri : ris) { + pkgNames.add(ri.activityInfo.packageName); + } + } + return pkgNames; + } + + private static void filterRecentlyUsedApps(Collection<PackageParser.Package> pkgs, + long dexOptLRUThresholdInMills) { + // Filter out packages that aren't recently used. + int total = pkgs.size(); + int skipped = 0; + long now = System.currentTimeMillis(); + for (Iterator<PackageParser.Package> i = pkgs.iterator(); i.hasNext();) { + PackageParser.Package pkg = i.next(); + long then = pkg.mLastPackageUsageTimeInMills; + if (then + dexOptLRUThresholdInMills < now) { + if (DEBUG_DEXOPT) { + Log.i(TAG, "Skipping dexopt of " + pkg.packageName + " last resumed: " + + ((then == 0) ? "never" : new Date(then))); + } + i.remove(); + skipped++; + } + } + if (DEBUG_DEXOPT) { + Log.i(TAG, "Skipped dexopt " + skipped + " of " + total); + } + } + + // Sort apps by importance for dexopt ordering. Important apps are given + // more priority in case the device runs out of space. + public static List<PackageParser.Package> getPackagesForDexopt( + Collection<PackageParser.Package> packages, + PackageManagerService packageManagerService) { + ArrayList<PackageParser.Package> remainingPkgs = new ArrayList<>(packages); + LinkedList<PackageParser.Package> result = new LinkedList<>(); + + // Give priority to core apps. + for (PackageParser.Package pkg : remainingPkgs) { + if (pkg.coreApp) { + if (DEBUG_DEXOPT) { + Log.i(TAG, "Adding core app " + result.size() + ": " + pkg.packageName); + } + result.add(pkg); + } + } + remainingPkgs.removeAll(result); + + // Give priority to system apps that listen for pre boot complete. + Intent intent = new Intent(Intent.ACTION_PRE_BOOT_COMPLETED); + ArraySet<String> pkgNames = getPackageNamesForIntent(intent, UserHandle.USER_SYSTEM); + for (PackageParser.Package pkg : remainingPkgs) { + if (pkgNames.contains(pkg.packageName)) { + if (DEBUG_DEXOPT) { + Log.i(TAG, "Adding pre boot system app " + result.size() + ": " + + pkg.packageName); + } + result.add(pkg); + } + } + remainingPkgs.removeAll(result); + + // Filter out packages that aren't recently used, add all remaining apps. + // TODO: add a property to control this? + if (packageManagerService.isHistoricalPackageUsageAvailable()) { + filterRecentlyUsedApps(remainingPkgs, SEVEN_DAYS_IN_MILLISECONDS); + } + result.addAll(remainingPkgs); + + // Now go ahead and also add the libraries required for these packages. + // TODO: Think about interleaving things. + Set<PackageParser.Package> dependencies = new HashSet<>(); + for (PackageParser.Package p : result) { + dependencies.addAll(packageManagerService.findSharedNonSystemLibraries(p)); + } + if (!dependencies.isEmpty()) { + // We might have packages already in `result` that are dependencies + // of other packages. Make sure we don't add those to the list twice. + dependencies.removeAll(result); + } + result.addAll(dependencies); + + if (DEBUG_DEXOPT) { + StringBuilder sb = new StringBuilder(); + for (PackageParser.Package pkg : result) { + if (sb.length() > 0) { + sb.append(", "); + } + sb.append(pkg.packageName); + } + Log.i(TAG, "Packages to be dexopted: " + sb.toString()); + } + + return result; + } +}
\ No newline at end of file diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index c1a5c5a24978..187237168109 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -540,8 +540,6 @@ final class Settings { // is okay to muck with. PackageSetting newp = new PackageSetting(p); replacePackageLPw(name, newp); - } else { - mPackages.remove(name); } return true; } @@ -576,9 +574,10 @@ final class Settings { } PackageSetting addPackageLPw(String name, String realName, File codePath, File resourcePath, - String legacyNativeLibraryPathString, String primaryCpuAbiString, String secondaryCpuAbiString, - String cpuAbiOverrideString, int uid, int vc, int pkgFlags, int pkgPrivateFlags, - String parentPackageName, List<String> childPackageNames) { + String legacyNativeLibraryPathString, String primaryCpuAbiString, + String secondaryCpuAbiString, String cpuAbiOverrideString, int uid, int vc, int + pkgFlags, int pkgPrivateFlags, String parentPackageName, + List<String> childPackageNames) { PackageSetting p = mPackages.get(name); if (p != null) { if (p.appId == uid) { @@ -679,6 +678,9 @@ final class Settings { if (p != null) { p.primaryCpuAbiString = primaryCpuAbiString; p.secondaryCpuAbiString = secondaryCpuAbiString; + if (childPackageNames != null) { + p.childPackageNames = new ArrayList<>(childPackageNames); + } if (!p.codePath.equals(codePath)) { // Check to see if its a disabled system app diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index 3cc7b10c6f24..76d6b2862534 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -1789,6 +1789,10 @@ public class UserManagerService extends IUserManager.Stub { Log.w(LOG_TAG, "Cannot add user. DISALLOW_ADD_USER is enabled."); return null; } + return createUserInternalUnchecked(name, flags, parentId); + } + + private UserInfo createUserInternalUnchecked(String name, int flags, int parentId) { if (ActivityManager.isLowRamDeviceStatic()) { return null; } @@ -1930,13 +1934,18 @@ public class UserManagerService extends IUserManager.Stub { if (user == null) { return null; } - setUserRestriction(UserManager.DISALLOW_MODIFY_ACCOUNTS, true, user.id); - // Change the setting before applying the DISALLOW_SHARE_LOCATION restriction, otherwise - // the putIntForUser() will fail. - android.provider.Settings.Secure.putIntForUser(mContext.getContentResolver(), - android.provider.Settings.Secure.LOCATION_MODE, - android.provider.Settings.Secure.LOCATION_MODE_OFF, user.id); - setUserRestriction(UserManager.DISALLOW_SHARE_LOCATION, true, user.id); + long identity = Binder.clearCallingIdentity(); + try { + setUserRestriction(UserManager.DISALLOW_MODIFY_ACCOUNTS, true, user.id); + // Change the setting before applying the DISALLOW_SHARE_LOCATION restriction, otherwise + // the putIntForUser() will fail. + android.provider.Settings.Secure.putIntForUser(mContext.getContentResolver(), + android.provider.Settings.Secure.LOCATION_MODE, + android.provider.Settings.Secure.LOCATION_MODE_OFF, user.id); + setUserRestriction(UserManager.DISALLOW_SHARE_LOCATION, true, user.id); + } finally { + Binder.restoreCallingIdentity(identity); + } return user; } @@ -2975,6 +2984,17 @@ public class UserManagerService extends IUserManager.Stub { am.switchUser(UserHandle.USER_SYSTEM); } } + + @Override + public UserInfo createUserEvenWhenDisallowed(String name, int flags) { + UserInfo user = createUserInternalUnchecked(name, flags, UserHandle.USER_NULL); + // Keep this in sync with UserManager.createUser + if (user != null && !user.isAdmin()) { + setUserRestriction(UserManager.DISALLOW_SMS, true, user.id); + setUserRestriction(UserManager.DISALLOW_OUTGOING_CALLS, true, user.id); + } + return user; + } } /* Remove all the users except of the system one. */ diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java index f57f75f04ba6..4b355de62e87 100644 --- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java +++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java @@ -155,7 +155,7 @@ public class UserRestrictionsUtils { */ public static boolean isValidRestriction(@NonNull String restriction) { if (!USER_RESTRICTIONS.contains(restriction)) { - Slog.wtf(TAG, "Unknown restriction: " + restriction); + Slog.e(TAG, "Unknown restriction: " + restriction); return false; } return true; diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index c046ba610b3a..3a70e675acdb 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -18,6 +18,8 @@ package com.android.server.policy; import static android.app.ActivityManager.StackId.DOCKED_STACK_ID; import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID; +import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID; +import static android.app.ActivityManager.StackId.HOME_STACK_ID; import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE; import static android.content.pm.PackageManager.FEATURE_TELEVISION; import static android.content.pm.PackageManager.FEATURE_WATCH; @@ -32,6 +34,7 @@ import static android.view.WindowManagerPolicy.WindowManagerFuncs.LID_CLOSED; import static android.view.WindowManagerPolicy.WindowManagerFuncs.LID_OPEN; import android.app.ActivityManager; +import android.app.ActivityManager.StackId; import android.app.ActivityManagerInternal; import android.app.ActivityManagerInternal.SleepToken; import android.app.ActivityManagerNative; @@ -135,6 +138,7 @@ import com.android.server.GestureLauncherService; import com.android.server.LocalServices; import com.android.server.policy.keyguard.KeyguardServiceDelegate; import com.android.server.policy.keyguard.KeyguardServiceDelegate.DrawnListener; +import com.android.server.statusbar.StatusBarManagerInternal; import java.io.File; import java.io.FileReader; @@ -280,6 +284,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { DreamManagerInternal mDreamManagerInternal; PowerManagerInternal mPowerManagerInternal; IStatusBarService mStatusBarService; + StatusBarManagerInternal mStatusBarManagerInternal; boolean mPreloadedRecentApps; final Object mServiceAquireLock = new Object(); Vibrator mVibrator; // Vibrator for giving feedback of orientation changes @@ -488,6 +493,13 @@ public class PhoneWindowManager implements WindowManagerPolicy { int mResettingSystemUiFlags = 0; // Bits that we are currently always keeping cleared. int mForceClearedSystemUiFlags = 0; + int mLastFullscreenStackSysUiFlags; + int mLastDockedStackSysUiFlags; + final Rect mNonDockedStackBounds = new Rect(); + final Rect mDockedStackBounds = new Rect(); + final Rect mLastNonDockedStackBounds = new Rect(); + final Rect mLastDockedStackBounds = new Rect(); + // What we last reported to system UI about whether the compatibility // menu needs to be displayed. boolean mLastFocusNeedsMenu = false; @@ -508,6 +520,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { WindowState mTopFullscreenOpaqueWindowState; WindowState mTopFullscreenOpaqueOrDimmingWindowState; + WindowState mTopDockedOpaqueWindowState; + WindowState mTopDockedOpaqueOrDimmingWindowState; HashSet<IApplicationToken> mAppsToBeHidden = new HashSet<IApplicationToken>(); HashSet<IApplicationToken> mAppsThatDismissKeyguard = new HashSet<IApplicationToken>(); boolean mTopIsFullscreen; @@ -844,6 +858,16 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } + StatusBarManagerInternal getStatusBarManagerInternal() { + synchronized (mServiceAquireLock) { + if (mStatusBarManagerInternal == null) { + mStatusBarManagerInternal = + LocalServices.getService(StatusBarManagerInternal.class); + } + return mStatusBarManagerInternal; + } + } + /* * We always let the sensor be switched on by default except when * the user has explicitly disabled sensor based rotation or when the @@ -4373,6 +4397,23 @@ public class PhoneWindowManager implements WindowManagerPolicy { + mUnrestrictedScreenWidth; pf.bottom = df.bottom = of.bottom = cf.bottom = mUnrestrictedScreenTop + mUnrestrictedScreenHeight; + } else if ((sysUiFl & View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) != 0) { + pf.left = df.left = of.left = mRestrictedScreenLeft; + pf.top = df.top = of.top = mRestrictedScreenTop; + pf.right = df.right = of.right = mRestrictedScreenLeft + mRestrictedScreenWidth; + pf.bottom = df.bottom = of.bottom = mRestrictedScreenTop + + mRestrictedScreenHeight; + if (adjust != SOFT_INPUT_ADJUST_RESIZE) { + cf.left = mDockLeft; + cf.top = mDockTop; + cf.right = mDockRight; + cf.bottom = mDockBottom; + } else { + cf.left = mContentLeft; + cf.top = mContentTop; + cf.right = mContentRight; + cf.bottom = mContentBottom; + } } else { pf.left = df.left = of.left = cf.left = mRestrictedScreenLeft; pf.top = df.top = of.top = cf.top = mRestrictedScreenTop; @@ -4552,6 +4593,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { public void beginPostLayoutPolicyLw(int displayWidth, int displayHeight) { mTopFullscreenOpaqueWindowState = null; mTopFullscreenOpaqueOrDimmingWindowState = null; + mTopDockedOpaqueWindowState = null; + mTopDockedOpaqueOrDimmingWindowState = null; mAppsToBeHidden.clear(); mAppsThatDismissKeyguard.clear(); mForceStatusBar = false; @@ -4597,7 +4640,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { && attrs.type < FIRST_SYSTEM_WINDOW; final boolean showWhenLocked = (fl & FLAG_SHOW_WHEN_LOCKED) != 0; final boolean dismissKeyguard = (fl & FLAG_DISMISS_KEYGUARD) != 0; - + final int stackId = win.getStackId(); if (mTopFullscreenOpaqueWindowState == null && win.isVisibleOrBehindKeyguardLw() && !win.isGoneForLayoutLw()) { if ((fl & FLAG_FORCE_NOT_FULLSCREEN) != 0) { @@ -4646,9 +4689,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { } else { mAppsToBeHidden.add(appToken); } - if (attrs.x == 0 && attrs.y == 0 - && attrs.width == WindowManager.LayoutParams.MATCH_PARENT - && attrs.height == WindowManager.LayoutParams.MATCH_PARENT) { + if (isFullscreen(attrs) && StackId.normallyFullscreenWindows(stackId)) { if (DEBUG_LAYOUT) Slog.v(TAG, "Fullscreen window: " + win); mTopFullscreenOpaqueWindowState = win; if (mTopFullscreenOpaqueOrDimmingWindowState == null) { @@ -4692,11 +4733,37 @@ public class PhoneWindowManager implements WindowManagerPolicy { mWinShowWhenLocked = win; } } - if (mTopFullscreenOpaqueOrDimmingWindowState == null - && win.isVisibleOrBehindKeyguardLw() && !win.isGoneForLayoutLw() - && win.isDimming()) { + + // Keep track of the window if it's dimming but not necessarily fullscreen. + final boolean reallyVisible = win.isVisibleOrBehindKeyguardLw() && !win.isGoneForLayoutLw(); + if (mTopFullscreenOpaqueOrDimmingWindowState == null && reallyVisible + && win.isDimming() && StackId.normallyFullscreenWindows(stackId)) { mTopFullscreenOpaqueOrDimmingWindowState = win; } + + // We need to keep track of the top "fullscreen" opaque window for the docked stack + // separately, because both the "real fullscreen" opaque window and the one for the docked + // stack can control View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR. + if (mTopDockedOpaqueWindowState == null && reallyVisible && appWindow && attached == null + && isFullscreen(attrs) && stackId == DOCKED_STACK_ID) { + mTopDockedOpaqueWindowState = win; + if (mTopDockedOpaqueOrDimmingWindowState == null) { + mTopDockedOpaqueOrDimmingWindowState = win; + } + } + + // Also keep track of any windows that are dimming but not necessarily fullscreen in the + // docked stack. + if (mTopDockedOpaqueOrDimmingWindowState == null && reallyVisible && win.isDimming() + && stackId == DOCKED_STACK_ID) { + mTopDockedOpaqueOrDimmingWindowState = win; + } + } + + private boolean isFullscreen(WindowManager.LayoutParams attrs) { + return attrs.x == 0 && attrs.y == 0 + && attrs.width == WindowManager.LayoutParams.MATCH_PARENT + && attrs.height == WindowManager.LayoutParams.MATCH_PARENT; } /** {@inheritDoc} */ @@ -6838,42 +6905,52 @@ public class PhoneWindowManager implements WindowManagerPolicy { tmpVisibility |= StatusBarManager.DISABLE_RECENT; } - tmpVisibility = updateLightStatusBarLw(tmpVisibility); + final int fullscreenVisibility = updateLightStatusBarLw(0 /* vis */, + mTopFullscreenOpaqueWindowState, mTopFullscreenOpaqueOrDimmingWindowState); + final int dockedVisibility = updateLightStatusBarLw(0 /* vis */, + mTopDockedOpaqueWindowState, mTopDockedOpaqueOrDimmingWindowState); + mWindowManagerFuncs.getStackBounds(HOME_STACK_ID, mNonDockedStackBounds); + mWindowManagerFuncs.getStackBounds(DOCKED_STACK_ID, mDockedStackBounds); final int visibility = updateSystemBarsLw(win, mLastSystemUiFlags, tmpVisibility); final int diff = visibility ^ mLastSystemUiFlags; + final int fullscreenDiff = fullscreenVisibility ^ mLastFullscreenStackSysUiFlags; + final int dockedDiff = dockedVisibility ^ mLastDockedStackSysUiFlags; final boolean needsMenu = win.getNeedsMenuLw(mTopFullscreenOpaqueWindowState); - if (diff == 0 && mLastFocusNeedsMenu == needsMenu - && mFocusedApp == win.getAppToken()) { + if (diff == 0 && fullscreenDiff == 0 && dockedDiff == 0 && mLastFocusNeedsMenu == needsMenu + && mFocusedApp == win.getAppToken() + && mLastNonDockedStackBounds.equals(mNonDockedStackBounds) + && mLastDockedStackBounds.equals(mDockedStackBounds)) { return 0; } mLastSystemUiFlags = visibility; + mLastFullscreenStackSysUiFlags = fullscreenVisibility; + mLastDockedStackSysUiFlags = dockedVisibility; mLastFocusNeedsMenu = needsMenu; mFocusedApp = win.getAppToken(); + final Rect fullscreenStackBounds = new Rect(mNonDockedStackBounds); + final Rect dockedStackBounds = new Rect(mDockedStackBounds); mHandler.post(new Runnable() { @Override public void run() { - try { - IStatusBarService statusbar = getStatusBarService(); - if (statusbar != null) { - statusbar.setSystemUiVisibility(visibility, 0xffffffff, win.toString()); - statusbar.topAppWindowChanged(needsMenu); - } - } catch (RemoteException e) { - // re-acquire status bar service next time it is needed. - mStatusBarService = null; + StatusBarManagerInternal statusbar = getStatusBarManagerInternal(); + if (statusbar != null) { + statusbar.setSystemUiVisibility(visibility, fullscreenVisibility, + dockedVisibility, 0xffffffff, fullscreenStackBounds, + dockedStackBounds, win.toString()); + statusbar.topAppWindowChanged(needsMenu); } } }); return diff; } - private int updateLightStatusBarLw(int vis) { + private int updateLightStatusBarLw(int vis, WindowState opaque, WindowState opaqueOrDimming) { WindowState statusColorWin = isStatusBarKeyguard() && !mHideLockScreen ? mStatusBar - : mTopFullscreenOpaqueOrDimmingWindowState; + : opaqueOrDimming; if (statusColorWin != null) { - if (statusColorWin == mTopFullscreenOpaqueWindowState) { + if (statusColorWin == opaque) { // If the top fullscreen-or-dimming window is also the top fullscreen, respect // its light flag. vis &= ~View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR; diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java index 25d646d2bb3b..cbbcdae91488 100644 --- a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java +++ b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java @@ -16,6 +16,7 @@ package com.android.server.statusbar; +import android.graphics.Rect; import android.os.Bundle; import com.android.server.notification.NotificationDelegate; @@ -29,4 +30,7 @@ public interface StatusBarManagerInternal { void showAssistDisclosure(); void startAssist(Bundle args); void onCameraLaunchGestureDetected(int source); + void topAppWindowChanged(boolean menuVisible); + void setSystemUiVisibility(int vis, int fullscreenStackVis, int dockedStackVis, int mask, + Rect fullscreenBounds, Rect dockedBounds, String cause); } diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java index 90340d5589a5..6eab8d4e1f19 100644 --- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java +++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java @@ -20,6 +20,7 @@ import android.app.StatusBarManager; import android.content.ComponentName; import android.content.Context; import android.content.pm.PackageManager; +import android.graphics.Rect; import android.os.Binder; import android.os.Bundle; import android.os.Handler; @@ -70,6 +71,10 @@ public class StatusBarManagerService extends IStatusBarService.Stub { private Object mLock = new Object(); // encompasses lights-out mode and other flags defined on View private int mSystemUiVisibility = 0; + private int mFullscreenStackSysUiVisibility; + private int mDockedStackSysUiVisibility; + private final Rect mFullscreenStackBounds = new Rect(); + private final Rect mDockedStackBounds = new Rect(); private boolean mMenuVisible = false; private int mImeWindowVis = 0; private int mImeBackDisposition; @@ -186,6 +191,19 @@ public class StatusBarManagerService extends IStatusBarService.Stub { } } } + + @Override + public void topAppWindowChanged(boolean menuVisible) { + StatusBarManagerService.this.topAppWindowChanged(menuVisible); + } + + @Override + public void setSystemUiVisibility(int vis, int fullscreenStackVis, int dockedStackVis, + int mask, + Rect fullscreenBounds, Rect dockedBounds, String cause) { + StatusBarManagerService.this.setSystemUiVisibility(vis, fullscreenStackVis, + dockedStackVis, mask, fullscreenBounds, dockedBounds, cause); + } }; // ================================================================================ @@ -390,8 +408,7 @@ public class StatusBarManagerService extends IStatusBarService.Stub { * response to a window with {@link android.view.WindowManager.LayoutParams#needsMenuKey} set * to {@link android.view.WindowManager.LayoutParams#NEEDS_MENU_SET_TRUE}. */ - @Override - public void topAppWindowChanged(final boolean menuVisible) { + private void topAppWindowChanged(final boolean menuVisible) { enforceStatusBar(); if (SPEW) Slog.d(TAG, (menuVisible?"showing":"hiding") + " MENU key"); @@ -399,15 +416,15 @@ public class StatusBarManagerService extends IStatusBarService.Stub { synchronized(mLock) { mMenuVisible = menuVisible; mHandler.post(new Runnable() { - public void run() { - if (mBar != null) { - try { - mBar.topAppWindowChanged(menuVisible); - } catch (RemoteException ex) { - } + public void run() { + if (mBar != null) { + try { + mBar.topAppWindowChanged(menuVisible); + } catch (RemoteException ex) { } } - }); + } + }); } } @@ -443,13 +460,19 @@ public class StatusBarManagerService extends IStatusBarService.Stub { @Override public void setSystemUiVisibility(int vis, int mask, String cause) { + setSystemUiVisibility(vis, 0, 0, mask, mFullscreenStackBounds, mDockedStackBounds, cause); + } + + private void setSystemUiVisibility(int vis, int fullscreenStackVis, int dockedStackVis, int mask, + Rect fullscreenBounds, Rect dockedBounds, String cause) { // also allows calls from window manager which is in this process. enforceStatusBarService(); if (SPEW) Slog.d(TAG, "setSystemUiVisibility(0x" + Integer.toHexString(vis) + ")"); synchronized (mLock) { - updateUiVisibilityLocked(vis, mask); + updateUiVisibilityLocked(vis, fullscreenStackVis, dockedStackVis, mask, + fullscreenBounds, dockedBounds); disableLocked( mCurrentUserId, vis & StatusBarManager.DISABLE_MASK, @@ -458,14 +481,25 @@ public class StatusBarManagerService extends IStatusBarService.Stub { } } - private void updateUiVisibilityLocked(final int vis, final int mask) { - if (mSystemUiVisibility != vis) { + private void updateUiVisibilityLocked(final int vis, + final int fullscreenStackVis, final int dockedStackVis, final int mask, + final Rect fullscreenBounds, final Rect dockedBounds) { + if (mSystemUiVisibility != vis + || mFullscreenStackSysUiVisibility != fullscreenStackVis + || mDockedStackSysUiVisibility != dockedStackVis + || !mFullscreenStackBounds.equals(fullscreenBounds) + || !mDockedStackBounds.equals(dockedBounds)) { mSystemUiVisibility = vis; + mFullscreenStackSysUiVisibility = fullscreenStackVis; + mDockedStackSysUiVisibility = dockedStackVis; + mFullscreenStackBounds.set(fullscreenBounds); + mDockedStackBounds.set(dockedBounds); mHandler.post(new Runnable() { public void run() { if (mBar != null) { try { - mBar.setSystemUiVisibility(vis, mask); + mBar.setSystemUiVisibility(vis, fullscreenStackVis, dockedStackVis, + mask, fullscreenBounds, dockedBounds); } catch (RemoteException ex) { } } @@ -617,7 +651,8 @@ public class StatusBarManagerService extends IStatusBarService.Stub { // ================================================================================ @Override public void registerStatusBar(IStatusBar bar, List<String> iconSlots, - List<StatusBarIcon> iconList, int switches[], List<IBinder> binders) { + List<StatusBarIcon> iconList, int switches[], List<IBinder> binders, + Rect fullscreenStackBounds, Rect dockedStackBounds) { enforceStatusBarService(); Slog.i(TAG, "registerStatusBar bar=" + bar); @@ -636,7 +671,11 @@ public class StatusBarManagerService extends IStatusBarService.Stub { switches[4] = mImeBackDisposition; switches[5] = mShowImeSwitcher ? 1 : 0; switches[6] = gatherDisableActionsLocked(mCurrentUserId, 2); + switches[7] = mFullscreenStackSysUiVisibility; + switches[8] = mDockedStackSysUiVisibility; binders.add(mImeToken); + fullscreenStackBounds.set(mFullscreenStackBounds); + dockedStackBounds.set(mDockedStackBounds); } } diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateService.java b/services/core/java/com/android/server/webkit/WebViewUpdateService.java index c3a6f5d2f637..f3b120f944fd 100644 --- a/services/core/java/com/android/server/webkit/WebViewUpdateService.java +++ b/services/core/java/com/android/server/webkit/WebViewUpdateService.java @@ -29,8 +29,9 @@ import android.content.pm.Signature; import android.os.Binder; import android.os.Process; import android.os.RemoteException; +import android.os.UserHandle; import android.provider.Settings; -import android.provider.Settings.Secure; +import android.provider.Settings.Global; import android.util.AndroidRuntimeException; import android.util.Slog; import android.webkit.IWebViewUpdateService; @@ -90,7 +91,7 @@ public class WebViewUpdateService extends SystemService { // change provider when the new version of the package is being installed). if (intent.getAction().equals(Intent.ACTION_PACKAGE_REMOVED) && intent.getExtras().getBoolean(Intent.EXTRA_REPLACING)) { - synchronized(this) { + synchronized(WebViewUpdateService.this) { if (mCurrentWebViewPackage == null) return; String webViewPackage = "package:" + mCurrentWebViewPackage.packageName; @@ -141,7 +142,7 @@ public class WebViewUpdateService extends SystemService { // only kills dependents of packages that are being removed. try { ActivityManagerNative.getDefault().killPackageDependents( - oldProviderName, getContext().getUserId()); + oldProviderName, UserHandle.USER_ALL); } catch (RemoteException e) { } } @@ -209,7 +210,7 @@ public class WebViewUpdateService extends SystemService { try { if (oldPackage != null) { ActivityManagerNative.getDefault().killPackageDependents( - oldPackage.packageName, getContext().getUserId()); + oldPackage.packageName, UserHandle.USER_ALL); } } catch (RemoteException e) { } @@ -267,13 +268,13 @@ public class WebViewUpdateService extends SystemService { } private static String getUserChosenWebViewProvider() { - return Settings.Secure.getString(AppGlobals.getInitialApplication().getContentResolver(), - Settings.Secure.WEBVIEW_PROVIDER); + return Settings.Global.getString(AppGlobals.getInitialApplication().getContentResolver(), + Settings.Global.WEBVIEW_PROVIDER); } private void updateUserSetting(String newProviderName) { - Settings.Secure.putString(getContext().getContentResolver(), - Settings.Secure.WEBVIEW_PROVIDER, + Settings.Global.putString(getContext().getContentResolver(), + Settings.Global.WEBVIEW_PROVIDER, newProviderName == null ? "" : newProviderName); } diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java index d1c088175a17..9a3aaa5066ef 100644 --- a/services/core/java/com/android/server/wm/DragState.java +++ b/services/core/java/com/android/server/wm/DragState.java @@ -24,6 +24,7 @@ import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import android.content.ClipData; import android.content.ClipDescription; +import android.content.Context; import android.graphics.Matrix; import android.graphics.Point; import android.graphics.Rect; @@ -33,6 +34,10 @@ import android.os.IBinder; import android.os.Message; import android.os.Process; import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.UserHandle; +import android.os.UserManager; +import android.os.IUserManager; import android.util.Slog; import android.view.Display; import android.view.DragEvent; @@ -73,6 +78,8 @@ class DragState { IBinder mLocalWin; int mPid; int mUid; + int mSourceUserId; + boolean mCrossProfileCopyAllowed; ClipData mData; ClipDescription mDataDescription; int mTouchSource; @@ -221,6 +228,18 @@ class DragState { mNotifiedWindows.clear(); mDragInProgress = true; + mSourceUserId = UserHandle.getUserId(mUid); + + final IUserManager userManager = + (IUserManager) ServiceManager.getService(Context.USER_SERVICE); + try { + mCrossProfileCopyAllowed = !userManager.getUserRestrictions(mSourceUserId).getBoolean( + UserManager.DISALLOW_CROSS_PROFILE_COPY_PASTE); + } catch (RemoteException e) { + Slog.e(TAG_WM, "Remote Exception calling UserManager: " + e); + mCrossProfileCopyAllowed = false; + } + if (DEBUG_DRAG) { Slog.d(TAG_WM, "broadcasting DRAG_STARTED at (" + touchX + ", " + touchY + ")"); } @@ -234,7 +253,7 @@ class DragState { } } - /* helper - send a caller-provided event, presumed to be DRAG_STARTED, if the + /* helper - send a ACTION_DRAG_STARTED event, if the * designated window is potentially a drop recipient. There are race situations * around DRAG_ENDED broadcast, so we make sure that once we've declared that * the drag has ended, we never send out another DRAG_STARTED for this drag action. @@ -244,19 +263,7 @@ class DragState { */ private void sendDragStartedLw(WindowState newWin, float touchX, float touchY, ClipDescription desc) { - // Don't actually send the event if the drag is supposed to be pinned - // to the originating window but 'newWin' is not that window. - if ((mFlags & View.DRAG_FLAG_GLOBAL) == 0) { - final IBinder winBinder = newWin.mClient.asBinder(); - if (winBinder != mLocalWin) { - if (DEBUG_DRAG) { - Slog.d(TAG_WM, "Not dispatching local DRAG_STARTED to " + newWin); - } - return; - } - } - - if (mDragInProgress && newWin.isPotentialDragTarget()) { + if (mDragInProgress && isValidDropTarget(newWin)) { DragEvent event = obtainDragEvent(newWin, DragEvent.ACTION_DRAG_STARTED, touchX, touchY, null, desc, null, null, false); try { @@ -274,17 +281,33 @@ class DragState { } } - /* helper - construct and send a DRAG_STARTED event only if the window has not + private boolean isValidDropTarget(WindowState targetWin) { + if (targetWin == null) { + return false; + } + if (!targetWin.isPotentialDragTarget()) { + return false; + } + if ((mFlags & View.DRAG_FLAG_GLOBAL) == 0) { + // Drag is limited to the current window. + if (mLocalWin != targetWin.mClient.asBinder()) { + return false; + } + } + + return mCrossProfileCopyAllowed || + mSourceUserId == UserHandle.getUserId(targetWin.getOwningUid()); + } + + /* helper - send a ACTION_DRAG_STARTED event only if the window has not * previously been notified, i.e. it became visible after the drag operation * was begun. This is a rare case. */ void sendDragStartedIfNeededLw(WindowState newWin) { if (mDragInProgress) { // If we have sent the drag-started, we needn't do so again - for (WindowState ws : mNotifiedWindows) { - if (ws == newWin) { - return; - } + if (isWindowNotified(newWin)) { + return; } if (DEBUG_DRAG) { Slog.d(TAG_WM, "need to send DRAG_STARTED to new window " + newWin); @@ -293,6 +316,15 @@ class DragState { } } + private boolean isWindowNotified(WindowState newWin) { + for (WindowState ws : mNotifiedWindows) { + if (ws == newWin) { + return true; + } + } + return false; + } + private void broadcastDragEndedLw() { final int myPid = Process.myPid(); @@ -346,7 +378,9 @@ class DragState { private void cleanUpDragLw() { broadcastDragEndedLw(); - restorePointerIconLw(); + if (isFromSource(InputDevice.SOURCE_MOUSE)) { + mService.restorePointerIconLocked(mDisplay, mCurrentX, mCurrentY); + } // stop intercepting input unregister(); @@ -384,19 +418,18 @@ class DragState { void notifyLocationLw(float x, float y) { // Tell the affected window - WindowState touchedWin = getTouchedWinAtPointLw(x, y); + WindowState touchedWin = mService.getTouchableWinAtPointLocked(mDisplay, x, y); if (touchedWin == null) { if (DEBUG_DRAG) Slog.d(TAG_WM, "No touched win at x=" + x + " y=" + y); return; } - if ((mFlags & View.DRAG_FLAG_GLOBAL) == 0) { - final IBinder touchedBinder = touchedWin.mClient.asBinder(); - if (touchedBinder != mLocalWin) { - // This drag is pinned only to the originating window, but the drag - // point is outside that window. Pretend it's over empty space. - touchedWin = null; - } + + if (!isWindowNotified(touchedWin)) { + // The drag point is over a window which was not notified about a drag start. + // Pretend it's over empty space. + touchedWin = null; } + try { final int myPid = Process.myPid(); @@ -430,10 +463,6 @@ class DragState { mTargetWindow = touchedWin; } - WindowState getDropTargetWinLw(float x, float y) { - return getTouchedWinAtPointLw(x, y); - } - // Tell the drop target about the data. Returns 'true' if we can immediately // dispatch the global drag-ended message, 'false' if we need to wait for a // result from the recipient. @@ -445,7 +474,7 @@ class DragState { mCurrentX = x; mCurrentY = y; - if (touchedWin == null) { + if (!isWindowNotified(touchedWin)) { // "drop" outside a valid window -- no recipient to apply a // timeout to, and we can send the drag-ended message immediately. mDragResult = false; @@ -455,6 +484,9 @@ class DragState { if (DEBUG_DRAG) { Slog.d(TAG_WM, "sending DROP to " + touchedWin); } + if (mSourceUserId != UserHandle.getUserId(touchedWin.getOwningUid())){ + mData.fixUris(mSourceUserId); + } final int myPid = Process.myPid(); final IBinder token = touchedWin.mClient.asBinder(); DragEvent evt = obtainDragEvent(touchedWin, DragEvent.ACTION_DROP, x, y, @@ -478,77 +510,17 @@ class DragState { return false; } - // Find the visible, touch-deliverable window under the given point - private WindowState getTouchedWinAtPointLw(float xf, float yf) { - WindowState touchedWin = null; - final int x = (int) xf; - final int y = (int) yf; - - final WindowList windows = mService.getWindowListLocked(mDisplay); - if (windows == null) { - return null; - } - final int N = windows.size(); - for (int i = N - 1; i >= 0; i--) { - WindowState child = windows.get(i); - final int flags = child.mAttrs.flags; - if (!child.isVisibleLw()) { - // not visible == don't tell about drags - continue; - } - if ((flags & WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE) != 0) { - // not touchable == don't tell about drags - continue; - } - - child.getVisibleBounds(mTmpRect); - if (!mTmpRect.contains(x, y)) { - // outside of this window's activity stack == don't tell about drags - continue; - } - - child.getTouchableRegion(mTmpRegion); - - final int touchFlags = flags & - (WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE - | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL); - if (mTmpRegion.contains(x, y) || touchFlags == 0) { - // Found it - touchedWin = child; - break; - } - } - - return touchedWin; - } - private static DragEvent obtainDragEvent(WindowState win, int action, float x, float y, Object localState, ClipDescription description, ClipData data, IDropPermissions dropPermissions, boolean result) { - final float winX = translateToWindowX(win, x); - final float winY = translateToWindowY(win, y); + final float winX = win.translateToWindowX(x); + final float winY = win.translateToWindowY(y); return DragEvent.obtain(action, winX, winY, localState, description, data, dropPermissions, result); } - private static float translateToWindowX(WindowState win, float x) { - float winX = x - win.mFrame.left; - if (win.mEnforceSizeCompat) { - winX *= win.mGlobalScale; - } - return winX; - } - - private static float translateToWindowY(WindowState win, float y) { - float winY = y - win.mFrame.top; - if (win.mEnforceSizeCompat) { - winY *= win.mGlobalScale; - } - return winY; - } - boolean stepAnimationLocked(long currentTimeMs) { if (mAnimation == null) { return false; @@ -604,21 +576,4 @@ class DragState { InputManager.getInstance().setPointerIconShape(PointerIcon.STYLE_GRAB); } } - - private void restorePointerIconLw() { - if (isFromSource(InputDevice.SOURCE_MOUSE)) { - WindowState touchWin = getTouchedWinAtPointLw(mCurrentX, mCurrentY); - if (touchWin != null) { - try { - touchWin.mClient.updatePointerIcon( - translateToWindowX(touchWin, mCurrentX), - translateToWindowY(touchWin, mCurrentY)); - return; - } catch (RemoteException e) { - Slog.w(TAG_WM, "unable to restore pointer icon"); - } - } - InputManager.getInstance().setPointerIconShape(PointerIcon.STYLE_DEFAULT); - } - } } diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java index a8d974f729a6..25de75a55926 100644 --- a/services/core/java/com/android/server/wm/Session.java +++ b/services/core/java/com/android/server/wm/Session.java @@ -528,6 +528,16 @@ final class Session extends IWindowSession.Stub } } + @Override + public void updatePointerIcon(IWindow window) { + final long identity = Binder.clearCallingIdentity(); + try { + mService.updatePointerIcon(window); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + void windowAddedLocked() { if (mSurfaceSession == null) { if (WindowManagerService.localLOGV) Slog.v( diff --git a/services/core/java/com/android/server/wm/WindowManagerDebugConfig.java b/services/core/java/com/android/server/wm/WindowManagerDebugConfig.java index 0979cd32a1e2..e229c5e3fc35 100644 --- a/services/core/java/com/android/server/wm/WindowManagerDebugConfig.java +++ b/services/core/java/com/android/server/wm/WindowManagerDebugConfig.java @@ -37,8 +37,8 @@ public class WindowManagerDebugConfig { static final boolean DEBUG = false; static final boolean DEBUG_ADD_REMOVE = false; static final boolean DEBUG_FOCUS = false; - static final boolean DEBUG_FOCUS_LIGHT = DEBUG_FOCUS || false; - static final boolean DEBUG_ANIM = false; + static final boolean DEBUG_FOCUS_LIGHT = DEBUG_FOCUS || true; + static final boolean DEBUG_ANIM = true; static final boolean DEBUG_KEYGUARD = false; static final boolean DEBUG_LAYOUT = false; static final boolean DEBUG_LAYERS = false; @@ -50,7 +50,7 @@ public class WindowManagerDebugConfig { static final boolean DEBUG_ORIENTATION = false; static final boolean DEBUG_APP_ORIENTATION = false; static final boolean DEBUG_CONFIGURATION = false; - static final boolean DEBUG_APP_TRANSITIONS = false; + static final boolean DEBUG_APP_TRANSITIONS = true; static final boolean DEBUG_STARTING_WINDOW = false; static final boolean DEBUG_WALLPAPER = false; static final boolean DEBUG_WALLPAPER_LIGHT = false || DEBUG_WALLPAPER; diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index d1ffaa07ed13..c8f5dda1fb3a 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -41,6 +41,7 @@ import android.graphics.Rect; import android.graphics.Region; import android.hardware.display.DisplayManager; import android.hardware.display.DisplayManagerInternal; +import android.hardware.input.InputManager; import android.net.Uri; import android.os.Binder; import android.os.Build; @@ -81,6 +82,7 @@ import android.view.Choreographer; import android.view.Display; import android.view.DisplayInfo; import android.view.Gravity; +import android.view.PointerIcon; import android.view.IAppTransitionAnimationSpecsFuture; import android.view.IApplicationToken; import android.view.IDockedStackListener; @@ -466,6 +468,7 @@ public class WindowManagerService extends IWindowManager.Stub final float[] mTmpFloats = new float[9]; final Rect mTmpRect = new Rect(); final Rect mTmpRect2 = new Rect(); + final Region mTmpRegion = new Region(); boolean mDisplayReady; boolean mSafeMode; @@ -758,7 +761,7 @@ public class WindowManagerService extends IWindowManager.Stub } private boolean completeDropLw(float x, float y) { - WindowState dropTargetWin = mDragState.getDropTargetWinLw(x, y); + WindowState dropTargetWin = getTouchableWinAtPointLocked(mDragState.mDisplay, x, y); DropPermissionsHandler dropPermissions = null; if (dropTargetWin != null && @@ -769,7 +772,7 @@ public class WindowManagerService extends IWindowManager.Stub mDragState.mUid, dropTargetWin.getOwningPackage(), mDragState.mFlags & DRAG_FLAGS_URI_PERMISSIONS, - UserHandle.getUserId(mDragState.mUid), + mDragState.mSourceUserId, UserHandle.getUserId(dropTargetWin.getOwningUid())); } @@ -1192,7 +1195,7 @@ public class WindowManagerService extends IWindowManager.Stub break; } } - if (DEBUG_FOCUS_LIGHT || DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v(TAG_WM, + if (DEBUG_FOCUS || DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v(TAG_WM, "Based on layer: Adding window " + win + " at " + (i + 1) + " of " + windows.size()); windows.add(i + 1, win); @@ -1224,7 +1227,7 @@ public class WindowManagerService extends IWindowManager.Stub //apptoken note that the window could be a floating window //that was created later or a window at the top of the list of //windows associated with this token. - if (DEBUG_FOCUS_LIGHT || DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v(TAG_WM, + if (DEBUG_FOCUS || DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v(TAG_WM, "not Base app: Adding window " + win + " at " + (newIdx + 1) + " of " + windows.size()); windows.add(newIdx + 1, win); @@ -1262,7 +1265,7 @@ public class WindowManagerService extends IWindowManager.Stub } } i++; - if (DEBUG_FOCUS_LIGHT || DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v(TAG_WM, + if (DEBUG_FOCUS || DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v(TAG_WM, "Free window: Adding window " + win + " at " + i + " of " + windows.size()); windows.add(i, win); mWindowsChanged = true; @@ -1333,7 +1336,7 @@ public class WindowManagerService extends IWindowManager.Stub } private void addWindowToListInOrderLocked(final WindowState win, boolean addToToken) { - if (DEBUG_FOCUS_LIGHT) Slog.d(TAG_WM, "addWindowToListInOrderLocked: win=" + win + + if (DEBUG_FOCUS) Slog.d(TAG_WM, "addWindowToListInOrderLocked: win=" + win + " Callers=" + Debug.getCallers(4)); if (win.mAttachedWindow == null) { final WindowToken token = win.mToken; @@ -4236,7 +4239,6 @@ public class WindowManagerService extends IWindowManager.Stub mOpeningApps.remove(wtoken); mClosingApps.remove(wtoken); - wtoken.mAppStopped = false; wtoken.waitingToShow = false; wtoken.hiddenRequested = !visible; @@ -4246,6 +4248,8 @@ public class WindowManagerService extends IWindowManager.Stub // if made visible again. wtoken.appDied = false; wtoken.removeAllWindows(); + } else if (visible) { + wtoken.mAppStopped = false; } // If we are preparing an app transition, then delay changing @@ -4863,6 +4867,7 @@ public class WindowManagerService extends IWindowManager.Stub } } + @Override public void getStackBounds(int stackId, Rect bounds) { synchronized (mWindowMap) { final TaskStack stack = mStackIdToStack.get(stackId); @@ -10168,6 +10173,7 @@ public class WindowManagerService extends IWindowManager.Stub if (displayId == Display.DEFAULT_DISPLAY) { displayContent.mTapDetector = new TaskTapPointerEventListener(this, displayContent); registerPointerEventListener(displayContent.mTapDetector); + registerPointerEventListener(mMousePositionTracker); } return displayContent; @@ -10260,6 +10266,7 @@ public class WindowManagerService extends IWindowManager.Stub displayContent.close(); if (displayId == Display.DEFAULT_DISPLAY) { unregisterPointerEventListener(displayContent.mTapDetector); + unregisterPointerEventListener(mMousePositionTracker); } } mAnimator.removeDisplayLocked(displayId); @@ -10459,6 +10466,128 @@ public class WindowManagerService extends IWindowManager.Stub } } + /** + * Find the visible, touch-deliverable window under the given point + */ + WindowState getTouchableWinAtPointLocked(Display display, float xf, float yf) { + WindowState touchedWin = null; + final int x = (int) xf; + final int y = (int) yf; + + final WindowList windows = getWindowListLocked(display); + if (windows == null) { + return null; + } + final int N = windows.size(); + for (int i = N - 1; i >= 0; i--) { + WindowState child = windows.get(i); + final int flags = child.mAttrs.flags; + if (!child.isVisibleLw()) { + continue; + } + if ((flags & WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE) != 0) { + continue; + } + + child.getTouchableRegion(mTmpRegion); + + final int touchFlags = flags & + (WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE + | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL); + if (mTmpRegion.contains(x, y) || touchFlags == 0) { + touchedWin = child; + break; + } + } + + return touchedWin; + } + + private MousePositionTracker mMousePositionTracker = new MousePositionTracker(); + + private static class MousePositionTracker implements PointerEventListener { + private boolean mLatestEventWasMouse; + private float mLatestMouseX; + private float mLatestMouseY; + + void updatePosition(float x, float y) { + synchronized (this) { + mLatestEventWasMouse = true; + mLatestMouseX = x; + mLatestMouseY = y; + } + } + + @Override + public void onPointerEvent(MotionEvent motionEvent) { + if (motionEvent.isFromSource(InputDevice.SOURCE_MOUSE)) { + updatePosition(motionEvent.getRawX(), motionEvent.getRawY()); + } else { + synchronized (this) { + mLatestEventWasMouse = false; + } + } + } + }; + + void updatePointerIcon(IWindow client) { + float mouseX, mouseY; + + synchronized(mMousePositionTracker) { + if (!mMousePositionTracker.mLatestEventWasMouse) { + return; + } + mouseX = mMousePositionTracker.mLatestMouseX; + mouseY = mMousePositionTracker.mLatestMouseY; + } + + synchronized (mWindowMap) { + if (mDragState != null) { + // Drag cursor overrides the app cursor. + return; + } + WindowState callingWin = windowForClientLocked(null, client, false); + if (callingWin == null) { + Slog.w(TAG_WM, "Bad requesting window " + client); + return; + } + final DisplayContent displayContent = callingWin.getDisplayContent(); + if (displayContent == null) { + return; + } + Display display = displayContent.getDisplay(); + WindowState windowUnderPointer = getTouchableWinAtPointLocked(display, mouseX, mouseY); + if (windowUnderPointer != callingWin) { + return; + } + try { + windowUnderPointer.mClient.updatePointerIcon( + windowUnderPointer.translateToWindowX(mouseX), + windowUnderPointer.translateToWindowY(mouseY)); + } catch (RemoteException e) { + Slog.w(TAG_WM, "unable to update pointer icon"); + } + } + } + + void restorePointerIconLocked(Display display, float latestX, float latestY) { + // Mouse position tracker has not been getting updates while dragging, update it now. + mMousePositionTracker.updatePosition(latestX, latestY); + + WindowState windowUnderPointer = getTouchableWinAtPointLocked(display, latestX, latestY); + if (windowUnderPointer != null) { + try { + windowUnderPointer.mClient.updatePointerIcon( + windowUnderPointer.translateToWindowX(latestX), + windowUnderPointer.translateToWindowY(latestY)); + } catch (RemoteException e) { + Slog.w(TAG_WM, "unable to restore pointer icon"); + } + } else { + InputManager.getInstance().setPointerIconShape(PointerIcon.STYLE_DEFAULT); + } + } + private final class LocalService extends WindowManagerInternal { @Override public void requestTraversalFromDisplayManager() { diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 2096ea065d0d..37c8a7e4f032 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -2088,7 +2088,8 @@ final class WindowState implements WindowManagerPolicy.WindowState { return mTmpRect; } - private int getStackId() { + @Override + public int getStackId() { final TaskStack stack = getStack(); if (stack == null) { return INVALID_STACK_ID; @@ -2482,4 +2483,20 @@ final class WindowState implements WindowManagerPolicy.WindowState { mReplacingWindow = null; mAnimateReplacingWindow = false; } + + float translateToWindowX(float x) { + float winX = x - mFrame.left; + if (mEnforceSizeCompat) { + winX *= mGlobalScale; + } + return winX; + } + + float translateToWindowY(float y) { + float winY = y - mFrame.top; + if (mEnforceSizeCompat) { + winY *= mGlobalScale; + } + return winY; + } } diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java index 02012961f5be..f8f8363c6412 100644 --- a/services/core/java/com/android/server/wm/WindowStateAnimator.java +++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java @@ -1347,7 +1347,7 @@ class WindowStateAnimator { } } else { if (DEBUG_ANIM && isAnimating()) { - Slog.v(TAG, "prepareSurface: No changes in animation for " + this); + //Slog.v(TAG, "prepareSurface: No changes in animation for " + this); } displayed = true; } diff --git a/services/core/jni/com_android_server_hdmi_HdmiCecController.cpp b/services/core/jni/com_android_server_hdmi_HdmiCecController.cpp index b72cf4dc94d0..656c2141ff2e 100644 --- a/services/core/jni/com_android_server_hdmi_HdmiCecController.cpp +++ b/services/core/jni/com_android_server_hdmi_HdmiCecController.cpp @@ -330,7 +330,7 @@ static jint nativeSendCecCommand(JNIEnv* env, jclass clazz, jlong controllerPtr, jsize len = env->GetArrayLength(body); message.length = MIN(len, CEC_MESSAGE_BODY_MAX_LENGTH); ScopedByteArrayRO bodyPtr(env, body); - std::memcpy(message.body, bodyPtr.get(), len); + std::memcpy(message.body, bodyPtr.get(), message.length); HdmiCecController* controller = reinterpret_cast<HdmiCecController*>(controllerPtr); diff --git a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp index e75775fa9237..cdd551936511 100644 --- a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp +++ b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp @@ -68,13 +68,14 @@ static const GpsMeasurementInterface* sGpsMeasurementInterface = NULL; static const GpsNavigationMessageInterface* sGpsNavigationMessageInterface = NULL; static const GnssConfigurationInterface* sGnssConfigurationInterface = NULL; -#define MAX_SATELLITE_COUNT 512 -#define MAX_GPS_SATELLITE_COUNT 512 +#define GPS_MAX_SATELLITE_COUNT 32 +#define GNSS_MAX_SATELLITE_COUNT 64 -#define PRN_SHIFT_WIDTH 3 +#define SVID_SHIFT_WIDTH 7 +#define CONSTELLATION_TYPE_SHIFT_WIDTH 3 // temporary storage for GPS callbacks -static GnssSvInfo sGnssSvList[MAX_SATELLITE_COUNT]; +static GnssSvInfo sGnssSvList[GNSS_MAX_SATELLITE_COUNT]; static size_t sGnssSvListSize; static const char* sNmeaString; static int sNmeaStringLength; @@ -113,56 +114,75 @@ static void sv_status_callback(GpsSvStatus* sv_status) { JNIEnv* env = AndroidRuntime::getJNIEnv(); size_t status_size = sv_status->size; - // Some drive doesn't set the size field correctly. Assume GpsSvStatus_v1 if - // it doesn't provide a valid size. + // Some drives doesn't set the size field correctly. Assume GpsSvStatus_v1 + // if it doesn't provide a valid size. if (status_size == 0) { - status_size = sizeof(GpsSvStatus_v1); - } - if (status_size == sizeof(GpsSvStatus)) { - sGnssSvListSize = sv_status->gnss_sv_list_size; - // Cramp the list size - if (sGnssSvListSize > MAX_SATELLITE_COUNT) { - sGnssSvListSize = MAX_SATELLITE_COUNT; - } - // Copy GNSS SV info into sGnssSvList, if any. - if (sGnssSvListSize > 0 && sv_status->gnss_sv_list) { - memcpy(sGnssSvList, sv_status->gnss_sv_list, sizeof(GnssSvInfo) * sGnssSvListSize); - } - } else if (status_size == sizeof(GpsSvStatus_v1)) { - sGnssSvListSize = sv_status->num_svs; - // Cramp the list size - if (sGnssSvListSize > MAX_GPS_SATELLITE_COUNT) { - sGnssSvListSize = MAX_GPS_SATELLITE_COUNT; - } - uint32_t ephemeris_mask = sv_status->ephemeris_mask; - uint32_t almanac_mask = sv_status->almanac_mask; - uint32_t used_in_fix_mask = sv_status->used_in_fix_mask; - for (size_t i = 0; i < sGnssSvListSize; i++) { - GnssSvInfo& info = sGnssSvList[i]; + ALOGW("Invalid size of GpsSvStatus found: %zd.", status_size); + } + sGnssSvListSize = sv_status->num_svs; + // Clamp the list size. Legacy GpsSvStatus has only 32 elements in sv_list. + if (sGnssSvListSize > GPS_MAX_SATELLITE_COUNT) { + ALOGW("Too many satellites %zd. Clamps to %d.", + sGnssSvListSize, + GPS_MAX_SATELLITE_COUNT); + sGnssSvListSize = GPS_MAX_SATELLITE_COUNT; + } + uint32_t ephemeris_mask = sv_status->ephemeris_mask; + uint32_t almanac_mask = sv_status->almanac_mask; + uint32_t used_in_fix_mask = sv_status->used_in_fix_mask; + for (size_t i = 0; i < sGnssSvListSize; i++) { + GnssSvInfo& info = sGnssSvList[i]; + info.svid = sv_status->sv_list[i].prn; + // TODO: implement the correct logic to derive the constellation type + // based on PRN ranges. + if (info.svid >=1 && info.svid <= 32) { info.constellation = GNSS_CONSTELLATION_GPS; - info.prn = sv_status->sv_list[i].prn; - info.snr = sv_status->sv_list[i].snr; - info.elevation = sv_status->sv_list[i].elevation; - info.azimuth = sv_status->sv_list[i].azimuth; - info.flags = GNSS_SV_FLAGS_NONE; - if (info.prn > 0 && info.prn <= 32) { - int32_t this_prn_mask = (1 << (info.prn - 1)); - if ((ephemeris_mask & this_prn_mask) != 0) { + } else { + info.constellation = GNSS_CONSTELLATION_UNKNOWN; + } + info.snr = sv_status->sv_list[i].snr; + info.elevation = sv_status->sv_list[i].elevation; + info.azimuth = sv_status->sv_list[i].azimuth; + info.flags = GNSS_SV_FLAGS_NONE; + if (info.svid > 0 && info.svid <= 32) { + int32_t this_svid_mask = (1 << (info.svid - 1)); + if ((ephemeris_mask & this_svid_mask) != 0) { info.flags |= GNSS_SV_FLAGS_HAS_EPHEMERIS_DATA; - } - if ((almanac_mask & this_prn_mask) != 0) { + } + if ((almanac_mask & this_svid_mask) != 0) { info.flags |= GNSS_SV_FLAGS_HAS_ALMANAC_DATA; - } - if ((used_in_fix_mask & this_prn_mask) != 0) { + } + if ((used_in_fix_mask & this_svid_mask) != 0) { info.flags |= GNSS_SV_FLAGS_USED_IN_FIX; - } } } - } else { - sGnssSvListSize = 0; - ALOGE("Invalid size of GpsSvStatus found: %zd.", status_size); + } + env->CallVoidMethod(mCallbacksObj, method_reportSvStatus); + checkAndClearExceptionFromCallback(env, __FUNCTION__); +} + +static void gnss_sv_status_callback(GnssSvStatus* sv_status) { + JNIEnv* env = AndroidRuntime::getJNIEnv(); + size_t status_size = sv_status->size; + // Check the size, and reject the object that has invalid size. + if (status_size != sizeof(GnssSvStatus)) { + ALOGE("Invalid size of GnssSvStatus found: %zd.", status_size); return; } + sGnssSvListSize = sv_status->num_svs; + // Clamp the list size + if (sGnssSvListSize > GNSS_MAX_SATELLITE_COUNT) { + ALOGD("Too many satellites %zd. Clamps to %d.", + sGnssSvListSize, + GNSS_MAX_SATELLITE_COUNT); + sGnssSvListSize = GNSS_MAX_SATELLITE_COUNT; + } + // Copy GNSS SV info into sGnssSvList, if any. + if (sGnssSvListSize > 0) { + memcpy(sGnssSvList, + sv_status->gnss_sv_list, + sizeof(GnssSvInfo) * sGnssSvListSize); + } env->CallVoidMethod(mCallbacksObj, method_reportSvStatus); checkAndClearExceptionFromCallback(env, __FUNCTION__); } @@ -228,6 +248,7 @@ GpsCallbacks sGpsCallbacks = { create_thread_callback, request_utc_time_callback, set_system_info_callback, + gnss_sv_status_callback, }; static void xtra_download_request_callback() @@ -677,31 +698,30 @@ static void android_location_GnssLocationProvider_delete_aiding_data(JNIEnv* /* } static jint android_location_GnssLocationProvider_read_sv_status(JNIEnv* env, jobject /* obj */, - jintArray prnWithFlagArray, jfloatArray snrArray, jfloatArray elevArray, - jfloatArray azumArray, jintArray constellationTypeArray) + jintArray svidWithFlagArray, jfloatArray snrArray, jfloatArray elevArray, + jfloatArray azumArray) { // this should only be called from within a call to reportSvStatus - jint* prnWithFlags = env->GetIntArrayElements(prnWithFlagArray, 0); + jint* svidWithFlags = env->GetIntArrayElements(svidWithFlagArray, 0); jfloat* snrs = env->GetFloatArrayElements(snrArray, 0); jfloat* elev = env->GetFloatArrayElements(elevArray, 0); jfloat* azim = env->GetFloatArrayElements(azumArray, 0); - jint* constellationTypes = env->GetIntArrayElements(constellationTypeArray, 0); // GNSS SV info. for (size_t i = 0; i < sGnssSvListSize; ++i) { const GnssSvInfo& info = sGnssSvList[i]; - constellationTypes[i] = info.constellation; - prnWithFlags[i] = (info.prn << PRN_SHIFT_WIDTH) | info.flags; + svidWithFlags[i] = (info.svid << SVID_SHIFT_WIDTH) | + (info.constellation << CONSTELLATION_TYPE_SHIFT_WIDTH) | + info.flags; snrs[i] = info.snr; elev[i] = info.elevation; azim[i] = info.azimuth; } - env->ReleaseIntArrayElements(prnWithFlagArray, prnWithFlags, 0); + env->ReleaseIntArrayElements(svidWithFlagArray, svidWithFlags, 0); env->ReleaseFloatArrayElements(snrArray, snrs, 0); env->ReleaseFloatArrayElements(elevArray, elev, 0); env->ReleaseFloatArrayElements(azumArray, azim, 0); - env->ReleaseIntArrayElements(constellationTypeArray, constellationTypes, 0); return (jint) sGnssSvListSize; } @@ -968,370 +988,367 @@ static jboolean android_location_GnssLocationProvider_resume_geofence(JNIEnv* /* return JNI_FALSE; } -static jobject translate_gps_clock(JNIEnv* env, void* data, size_t size) { - const char* doubleSignature = "(D)V"; - const char* longSignature = "(J)V"; - - GpsClock* clock = reinterpret_cast<GpsClock*>(data); - - jclass gpsClockClass = env->FindClass("android/location/GnssClock"); - jmethodID gpsClockCtor = env->GetMethodID(gpsClockClass, "<init>", "()V"); - - jobject gpsClockObject = env->NewObject(gpsClockClass, gpsClockCtor); - GpsClockFlags flags = clock->flags; - - if (flags & GPS_CLOCK_HAS_LEAP_SECOND) { - jmethodID setterMethod = env->GetMethodID(gpsClockClass, "setLeapSecond", "(S)V"); - env->CallVoidMethod(gpsClockObject, setterMethod, clock->leap_second); - } +template<class T> +class JavaMethodHelper { + public: + // Helper function to call setter on a Java object. + static void callJavaMethod( + JNIEnv* env, + jclass clazz, + jobject object, + const char* method_name, + T value); + + private: + static const char *const signature_; +}; - jmethodID typeSetterMethod = env->GetMethodID(gpsClockClass, "setType", "(B)V"); - env->CallVoidMethod(gpsClockObject, typeSetterMethod, clock->type); +template<class T> +void JavaMethodHelper<T>::callJavaMethod( + JNIEnv* env, + jclass clazz, + jobject object, + const char* method_name, + T value) { + jmethodID method = env->GetMethodID(clazz, method_name, signature_); + env->CallVoidMethod(object, method, value); +} - jmethodID setterMethod = env->GetMethodID(gpsClockClass, "setTimeInNs", longSignature); - env->CallVoidMethod(gpsClockObject, setterMethod, clock->time_ns); +class JavaObject { + public: + JavaObject(JNIEnv* env, const char* class_name); + virtual ~JavaObject(); + + template<class T> + void callSetter(const char* method_name, T value); + template<class T> + void callSetter(const char* method_name, T* value, size_t size); + jobject get(); + + private: + JNIEnv* env_; + jclass clazz_; + jobject object_; +}; - if (flags & GPS_CLOCK_HAS_TIME_UNCERTAINTY) { - jmethodID setterMethod = - env->GetMethodID(gpsClockClass, "setTimeUncertaintyInNs", doubleSignature); - env->CallVoidMethod(gpsClockObject, setterMethod, clock->time_uncertainty_ns); - } +JavaObject::JavaObject(JNIEnv* env, const char* class_name) : env_(env) { + clazz_ = env_->FindClass(class_name); + jmethodID ctor = env->GetMethodID(clazz_, "<init>", "()V"); + object_ = env_->NewObject(clazz_, ctor); +} - if (flags & GPS_CLOCK_HAS_FULL_BIAS) { - jmethodID setterMethod = env->GetMethodID(gpsClockClass, "setFullBiasInNs", longSignature); - env->CallVoidMethod(gpsClockObject, setterMethod, clock->full_bias_ns); - } +JavaObject::~JavaObject() { + env_->DeleteLocalRef(clazz_); +} - if (flags & GPS_CLOCK_HAS_BIAS) { - jmethodID setterMethod = env->GetMethodID(gpsClockClass, "setBiasInNs", doubleSignature); - env->CallVoidMethod(gpsClockObject, setterMethod, clock->bias_ns); - } +template<class T> +void JavaObject::callSetter(const char* method_name, T value) { + JavaMethodHelper<T>::callJavaMethod( + env_, clazz_, object_, method_name, value); +} - if (flags & GPS_CLOCK_HAS_BIAS_UNCERTAINTY) { - jmethodID setterMethod = - env->GetMethodID(gpsClockClass, "setBiasUncertaintyInNs", doubleSignature); - env->CallVoidMethod(gpsClockObject, setterMethod, clock->bias_uncertainty_ns); - } +template<> +void JavaObject::callSetter( + const char* method_name, uint8_t* value, size_t size) { + jbyteArray array = env_->NewByteArray(size); + env_->SetByteArrayRegion(array, 0, size, (jbyte*) value); + jmethodID method = env_->GetMethodID( + clazz_, + method_name, + "([B)V"); + env_->CallVoidMethod(object_, method, array); +} - if (flags & GPS_CLOCK_HAS_DRIFT) { - jmethodID setterMethod = - env->GetMethodID(gpsClockClass, "setDriftInNsPerSec", doubleSignature); - env->CallVoidMethod(gpsClockObject, setterMethod, clock->drift_nsps); - } +jobject JavaObject::get() { + return object_; +} - if (flags & GPS_CLOCK_HAS_DRIFT_UNCERTAINTY) { - jmethodID setterMethod = - env->GetMethodID(gpsClockClass, "setDriftUncertaintyInNsPerSec", doubleSignature); - env->CallVoidMethod(gpsClockObject, setterMethod, clock->drift_uncertainty_nsps); - } +// Define Java method signatures for all known types. + +template<> +const char *const JavaMethodHelper<uint8_t>::signature_ = "(B)V"; +template<> +const char *const JavaMethodHelper<int8_t>::signature_ = "(B)V"; +template<> +const char *const JavaMethodHelper<int16_t>::signature_ = "(S)V"; +template<> +const char *const JavaMethodHelper<uint16_t>::signature_ = "(S)V"; +template<> +const char *const JavaMethodHelper<int>::signature_ = "(I)V"; +template<> +const char *const JavaMethodHelper<int64_t>::signature_ = "(J)V"; +template<> +const char *const JavaMethodHelper<float>::signature_ = "(F)V"; +template<> +const char *const JavaMethodHelper<double>::signature_ = "(D)V"; +template<> +const char *const JavaMethodHelper<bool>::signature_ = "(Z)V"; + +#define SET(setter, value) object.callSetter("set" # setter, (value)) +#define SET_IF(flag, setter, value) \ + if (flags & (flag)) object.callSetter("set" # setter, (value)) + +static jobject translate_gps_clock(JNIEnv* env, GpsClock* clock) { + JavaObject object(env, "android/location/GnssClock"); + GpsClockFlags flags = clock->flags; + SET_IF(GPS_CLOCK_HAS_LEAP_SECOND, LeapSecond, clock->leap_second); + SET(Type, clock->type); + SET(TimeInNs, clock->time_ns); + SET_IF(GPS_CLOCK_HAS_TIME_UNCERTAINTY, + TimeUncertaintyInNs, + clock->time_uncertainty_ns); + SET_IF(GPS_CLOCK_HAS_FULL_BIAS, FullBiasInNs, clock->full_bias_ns); + SET_IF(GPS_CLOCK_HAS_BIAS, BiasInNs, clock->bias_ns); + SET_IF(GPS_CLOCK_HAS_BIAS_UNCERTAINTY, + BiasUncertaintyInNs, + clock->bias_uncertainty_ns); + SET_IF(GPS_CLOCK_HAS_DRIFT, DriftInNsPerSec, clock->drift_nsps); + SET_IF(GPS_CLOCK_HAS_DRIFT_UNCERTAINTY, + DriftUncertaintyInNsPerSec, + clock->drift_uncertainty_nsps); + + /* if (flags & GPS_CLOCK_TYPE_LOCAL_HW_TIME) { - if (size == sizeof(GpsClock)) { + if (size == sizeof(GnssClock)) { jmethodID setterMethod = env->GetMethodID(gpsClockClass, "setTimeOfLastHwClockDiscontinuityInNs", longSignature); env->CallVoidMethod(gpsClockObject, setterMethod, - clock->time_of_last_hw_clock_discontinuity_ns); + reinterpret_cast<GnssClock*>(clock)->time_of_last_hw_clock_discontinuity_ns); } } + */ - env->DeleteLocalRef(gpsClockClass); - return gpsClockObject; + return object.get(); } -static jobject translate_gps_measurement(JNIEnv* env, void* data, size_t size) { - const char* byteSignature = "(B)V"; - const char* shortSignature = "(S)V"; - const char* intSignature = "(I)V"; - const char* longSignature = "(J)V"; - const char* floatSignature = "(F)V"; - const char* doubleSignature = "(D)V"; +static jobject translate_gnss_clock(JNIEnv* env, GnssClock* clock) { + JavaObject object(env, "android/location/GnssClock"); + GpsClockFlags flags = clock->flags; - jclass gnssMeasurementClass = env->FindClass("android/location/GnssMeasurement"); - jmethodID gnssMeasurementCtor = env->GetMethodID(gnssMeasurementClass, "<init>", "()V"); - GpsMeasurement* measurement = reinterpret_cast<GpsMeasurement*>(data); + SET_IF(GPS_CLOCK_HAS_LEAP_SECOND, LeapSecond, clock->leap_second); + SET(Type, clock->type); + SET(TimeInNs, clock->time_ns); + SET_IF(GPS_CLOCK_HAS_TIME_UNCERTAINTY, + TimeUncertaintyInNs, + clock->time_uncertainty_ns); + SET_IF(GPS_CLOCK_HAS_FULL_BIAS, FullBiasInNs, clock->full_bias_ns); + SET_IF(GPS_CLOCK_HAS_BIAS, BiasInNs, clock->bias_ns); + SET_IF(GPS_CLOCK_HAS_BIAS_UNCERTAINTY, + BiasUncertaintyInNs, + clock->bias_uncertainty_ns); + SET_IF(GPS_CLOCK_HAS_DRIFT, DriftInNsPerSec, clock->drift_nsps); + SET_IF(GPS_CLOCK_HAS_DRIFT_UNCERTAINTY, + DriftUncertaintyInNsPerSec, + clock->drift_uncertainty_nsps); + + SET_IF(GPS_CLOCK_TYPE_LOCAL_HW_TIME, + TimeOfLastHwClockDiscontinuityInNs, + clock->time_of_last_hw_clock_discontinuity_ns); + + return object.get(); +} - jobject gnssMeasurementObject = env->NewObject(gnssMeasurementClass, gnssMeasurementCtor); +static jobject translate_gps_measurement(JNIEnv* env, + GpsMeasurement* measurement) { + JavaObject object(env, "android/location/GnssMeasurement"); GpsMeasurementFlags flags = measurement->flags; - jmethodID prnSetterMethod = env->GetMethodID(gnssMeasurementClass, "setPrn", byteSignature); - env->CallVoidMethod(gnssMeasurementObject, prnSetterMethod, measurement->prn); + SET(Svid, static_cast<int16_t>(measurement->prn)); + SET(TimeOffsetInNs, measurement->time_offset_ns); + SET(State, measurement->state); + SET(ReceivedGpsTowInNs, measurement->received_gps_tow_ns); + SET(ReceivedGpsTowUncertaintyInNs, + measurement->received_gps_tow_uncertainty_ns); + SET(Cn0InDbHz, measurement->c_n0_dbhz); + SET(PseudorangeRateInMetersPerSec, measurement->pseudorange_rate_mps); + SET(PseudorangeRateUncertaintyInMetersPerSec, + measurement->pseudorange_rate_uncertainty_mps); + SET(AccumulatedDeltaRangeState, measurement->accumulated_delta_range_state); + SET(AccumulatedDeltaRangeInMeters, measurement->accumulated_delta_range_m); + SET(AccumulatedDeltaRangeUncertaintyInMeters, + measurement->accumulated_delta_range_uncertainty_m); + SET_IF(GPS_MEASUREMENT_HAS_PSEUDORANGE, + PseudorangeInMeters, + measurement->pseudorange_m); + SET_IF(GPS_MEASUREMENT_HAS_PSEUDORANGE_UNCERTAINTY, + PseudorangeUncertaintyInMeters, + measurement->pseudorange_uncertainty_m); + SET_IF(GPS_MEASUREMENT_HAS_CODE_PHASE, + CodePhaseInChips, + measurement->code_phase_chips); + SET_IF(GPS_MEASUREMENT_HAS_CODE_PHASE_UNCERTAINTY, + CodePhaseUncertaintyInChips, + measurement->code_phase_uncertainty_chips); + SET_IF(GPS_MEASUREMENT_HAS_CARRIER_FREQUENCY, + CarrierFrequencyInHz, + measurement->carrier_frequency_hz); + SET_IF(GPS_MEASUREMENT_HAS_CARRIER_CYCLES, + CarrierCycles, + measurement->carrier_cycles); + SET_IF(GPS_MEASUREMENT_HAS_CARRIER_PHASE, + CarrierPhase, + measurement->carrier_phase); + SET_IF(GPS_MEASUREMENT_HAS_CARRIER_PHASE_UNCERTAINTY, + CarrierPhaseUncertainty, + measurement->carrier_phase_uncertainty); + SET(LossOfLock, measurement->loss_of_lock); + SET_IF(GPS_MEASUREMENT_HAS_BIT_NUMBER, BitNumber, measurement->bit_number); + SET_IF(GPS_MEASUREMENT_HAS_TIME_FROM_LAST_BIT, + TimeFromLastBitInMs, + measurement->time_from_last_bit_ms); + SET_IF(GPS_MEASUREMENT_HAS_DOPPLER_SHIFT, + DopplerShiftInHz, + measurement->doppler_shift_hz); + SET_IF(GPS_MEASUREMENT_HAS_DOPPLER_SHIFT_UNCERTAINTY, + DopplerShiftUncertaintyInHz, + measurement->doppler_shift_uncertainty_hz); + SET(MultipathIndicator, measurement->multipath_indicator); + SET_IF(GPS_MEASUREMENT_HAS_SNR, SnrInDb, measurement->snr_db); + SET_IF(GPS_MEASUREMENT_HAS_ELEVATION, + ElevationInDeg, + measurement->elevation_deg); + SET_IF(GPS_MEASUREMENT_HAS_ELEVATION_UNCERTAINTY, + ElevationUncertaintyInDeg, + measurement->elevation_uncertainty_deg); + SET_IF(GPS_MEASUREMENT_HAS_AZIMUTH, + AzimuthInDeg, + measurement->azimuth_deg); + SET_IF(GPS_MEASUREMENT_HAS_AZIMUTH_UNCERTAINTY, + AzimuthUncertaintyInDeg, + measurement->azimuth_uncertainty_deg); + SET(UsedInFix, + (flags & GPS_MEASUREMENT_HAS_USED_IN_FIX) && measurement->used_in_fix); + + return object.get(); +} - jmethodID timeOffsetSetterMethod = - env->GetMethodID(gnssMeasurementClass, "setTimeOffsetInNs", doubleSignature); - env->CallVoidMethod( - gnssMeasurementObject, - timeOffsetSetterMethod, - measurement->time_offset_ns); +static jobject translate_gnss_measurement(JNIEnv* env, + GnssMeasurement* measurement) { + JavaObject object(env, "android/location/GnssMeasurement"); + GpsMeasurementFlags flags = measurement->flags; - jmethodID stateSetterMethod = env->GetMethodID(gnssMeasurementClass, "setState", shortSignature); - env->CallVoidMethod(gnssMeasurementObject, stateSetterMethod, measurement->state); + SET(Svid, measurement->svid); + SET(TimeOffsetInNs, measurement->time_offset_ns); + SET(State, measurement->state); + SET(ReceivedGpsTowInNs, measurement->received_gps_tow_ns); + SET(ReceivedGpsTowUncertaintyInNs, + measurement->received_gps_tow_uncertainty_ns); + SET(Cn0InDbHz, measurement->c_n0_dbhz); + SET(PseudorangeRateInMetersPerSec, measurement->pseudorange_rate_mps); + SET(PseudorangeRateUncertaintyInMetersPerSec, + measurement->pseudorange_rate_uncertainty_mps); + SET(AccumulatedDeltaRangeState, measurement->accumulated_delta_range_state); + SET(AccumulatedDeltaRangeInMeters, measurement->accumulated_delta_range_m); + SET(AccumulatedDeltaRangeUncertaintyInMeters, + measurement->accumulated_delta_range_uncertainty_m); + SET_IF(GPS_MEASUREMENT_HAS_PSEUDORANGE, + PseudorangeInMeters, + measurement->pseudorange_m); + SET_IF(GPS_MEASUREMENT_HAS_PSEUDORANGE_UNCERTAINTY, + PseudorangeUncertaintyInMeters, + measurement->pseudorange_uncertainty_m); + SET_IF(GPS_MEASUREMENT_HAS_CODE_PHASE, + CodePhaseInChips, + measurement->code_phase_chips); + SET_IF(GPS_MEASUREMENT_HAS_CODE_PHASE_UNCERTAINTY, + CodePhaseUncertaintyInChips, + measurement->code_phase_uncertainty_chips); + SET_IF(GPS_MEASUREMENT_HAS_CARRIER_FREQUENCY, + CarrierFrequencyInHz, + measurement->carrier_frequency_hz); + SET_IF(GPS_MEASUREMENT_HAS_CARRIER_CYCLES, + CarrierCycles, + measurement->carrier_cycles); + SET_IF(GPS_MEASUREMENT_HAS_CARRIER_PHASE, + CarrierPhase, + measurement->carrier_phase); + SET_IF(GPS_MEASUREMENT_HAS_CARRIER_PHASE_UNCERTAINTY, + CarrierPhaseUncertainty, + measurement->carrier_phase_uncertainty); + SET(LossOfLock, measurement->loss_of_lock); + SET_IF(GPS_MEASUREMENT_HAS_BIT_NUMBER, BitNumber, measurement->bit_number); + SET_IF(GPS_MEASUREMENT_HAS_TIME_FROM_LAST_BIT, + TimeFromLastBitInMs, + measurement->time_from_last_bit_ms); + SET_IF(GPS_MEASUREMENT_HAS_DOPPLER_SHIFT, + DopplerShiftInHz, + measurement->doppler_shift_hz); + SET_IF(GPS_MEASUREMENT_HAS_DOPPLER_SHIFT_UNCERTAINTY, + DopplerShiftUncertaintyInHz, + measurement->doppler_shift_uncertainty_hz); + SET(MultipathIndicator, measurement->multipath_indicator); + SET_IF(GPS_MEASUREMENT_HAS_SNR, SnrInDb, measurement->snr_db); + SET_IF(GPS_MEASUREMENT_HAS_ELEVATION, + ElevationInDeg, + measurement->elevation_deg); + SET_IF(GPS_MEASUREMENT_HAS_ELEVATION_UNCERTAINTY, + ElevationUncertaintyInDeg, + measurement->elevation_uncertainty_deg); + SET_IF(GPS_MEASUREMENT_HAS_AZIMUTH, + AzimuthInDeg, + measurement->azimuth_deg); + SET_IF(GPS_MEASUREMENT_HAS_AZIMUTH_UNCERTAINTY, + AzimuthUncertaintyInDeg, + measurement->azimuth_uncertainty_deg); + SET(UsedInFix, + (flags & GPS_MEASUREMENT_HAS_USED_IN_FIX) && measurement->used_in_fix); + + SET(PseudorangeRateCarrierInMetersPerSec, + measurement->pseudorange_rate_carrier_mps); + SET(PseudorangeRateCarrierUncertaintyInMetersPerSec, + measurement->pseudorange_rate_carrier_uncertainty_mps); + + return object.get(); +} - jmethodID receivedGpsTowSetterMethod = - env->GetMethodID(gnssMeasurementClass, "setReceivedGpsTowInNs", longSignature); - env->CallVoidMethod( - gnssMeasurementObject, - receivedGpsTowSetterMethod, - measurement->received_gps_tow_ns); +static jobjectArray translate_gps_measurements(JNIEnv* env, + GpsMeasurement* measurements, + size_t count) { + if (count == 0) { + return NULL; + } - jmethodID receivedGpsTowUncertaintySetterMethod = env->GetMethodID( - gnssMeasurementClass, - "setReceivedGpsTowUncertaintyInNs", - longSignature); - env->CallVoidMethod( - gnssMeasurementObject, - receivedGpsTowUncertaintySetterMethod, - measurement->received_gps_tow_uncertainty_ns); - - jmethodID cn0SetterMethod = - env->GetMethodID(gnssMeasurementClass, "setCn0InDbHz", doubleSignature); - env->CallVoidMethod(gnssMeasurementObject, cn0SetterMethod, measurement->c_n0_dbhz); - - jmethodID pseudorangeRateSetterMethod = env->GetMethodID( - gnssMeasurementClass, - "setPseudorangeRateInMetersPerSec", - doubleSignature); - env->CallVoidMethod( - gnssMeasurementObject, - pseudorangeRateSetterMethod, - measurement->pseudorange_rate_mps); - - jmethodID pseudorangeRateUncertaintySetterMethod = env->GetMethodID( - gnssMeasurementClass, - "setPseudorangeRateUncertaintyInMetersPerSec", - doubleSignature); - env->CallVoidMethod( - gnssMeasurementObject, - pseudorangeRateUncertaintySetterMethod, - measurement->pseudorange_rate_uncertainty_mps); - - jmethodID accumulatedDeltaRangeStateSetterMethod = - env->GetMethodID(gnssMeasurementClass, "setAccumulatedDeltaRangeState", shortSignature); - env->CallVoidMethod( - gnssMeasurementObject, - accumulatedDeltaRangeStateSetterMethod, - measurement->accumulated_delta_range_state); - - jmethodID accumulatedDeltaRangeSetterMethod = env->GetMethodID( - gnssMeasurementClass, - "setAccumulatedDeltaRangeInMeters", - doubleSignature); - env->CallVoidMethod( - gnssMeasurementObject, - accumulatedDeltaRangeSetterMethod, - measurement->accumulated_delta_range_m); - - jmethodID accumulatedDeltaRangeUncertaintySetterMethod = env->GetMethodID( + jclass gnssMeasurementClass = env->FindClass( + "android/location/GnssMeasurement"); + jobjectArray gnssMeasurementArray = env->NewObjectArray( + count, gnssMeasurementClass, - "setAccumulatedDeltaRangeUncertaintyInMeters", - doubleSignature); - env->CallVoidMethod( - gnssMeasurementObject, - accumulatedDeltaRangeUncertaintySetterMethod, - measurement->accumulated_delta_range_uncertainty_m); - - if (flags & GPS_MEASUREMENT_HAS_PSEUDORANGE) { - jmethodID setterMethod = - env->GetMethodID(gnssMeasurementClass, "setPseudorangeInMeters", doubleSignature); - env->CallVoidMethod(gnssMeasurementObject, setterMethod, measurement->pseudorange_m); - } - - if (flags & GPS_MEASUREMENT_HAS_PSEUDORANGE_UNCERTAINTY) { - jmethodID setterMethod = env->GetMethodID( - gnssMeasurementClass, - "setPseudorangeUncertaintyInMeters", - doubleSignature); - env->CallVoidMethod( - gnssMeasurementObject, - setterMethod, - measurement->pseudorange_uncertainty_m); - } - - if (flags & GPS_MEASUREMENT_HAS_CODE_PHASE) { - jmethodID setterMethod = - env->GetMethodID(gnssMeasurementClass, "setCodePhaseInChips", doubleSignature); - env->CallVoidMethod(gnssMeasurementObject, setterMethod, measurement->code_phase_chips); - } - - if (flags & GPS_MEASUREMENT_HAS_CODE_PHASE_UNCERTAINTY) { - jmethodID setterMethod = env->GetMethodID( - gnssMeasurementClass, - "setCodePhaseUncertaintyInChips", - doubleSignature); - env->CallVoidMethod( - gnssMeasurementObject, - setterMethod, - measurement->code_phase_uncertainty_chips); - } - - if (flags & GPS_MEASUREMENT_HAS_CARRIER_FREQUENCY) { - jmethodID setterMethod = - env->GetMethodID(gnssMeasurementClass, "setCarrierFrequencyInHz", floatSignature); - env->CallVoidMethod( - gnssMeasurementObject, - setterMethod, - measurement->carrier_frequency_hz); - } - - if (flags & GPS_MEASUREMENT_HAS_CARRIER_CYCLES) { - jmethodID setterMethod = - env->GetMethodID(gnssMeasurementClass, "setCarrierCycles", longSignature); - env->CallVoidMethod(gnssMeasurementObject, setterMethod, measurement->carrier_cycles); - } - - if (flags & GPS_MEASUREMENT_HAS_CARRIER_PHASE) { - jmethodID setterMethod = - env->GetMethodID(gnssMeasurementClass, "setCarrierPhase", doubleSignature); - env->CallVoidMethod(gnssMeasurementObject, setterMethod, measurement->carrier_phase); - } - - if (flags & GPS_MEASUREMENT_HAS_CARRIER_PHASE_UNCERTAINTY) { - jmethodID setterMethod = env->GetMethodID( - gnssMeasurementClass, - "setCarrierPhaseUncertainty", - doubleSignature); - env->CallVoidMethod( - gnssMeasurementObject, - setterMethod, - measurement->carrier_phase_uncertainty); - } - - jmethodID lossOfLockSetterMethod = - env->GetMethodID(gnssMeasurementClass, "setLossOfLock", byteSignature); - env->CallVoidMethod(gnssMeasurementObject, lossOfLockSetterMethod, measurement->loss_of_lock); - - if (flags & GPS_MEASUREMENT_HAS_BIT_NUMBER) { - jmethodID setterMethod = - env->GetMethodID(gnssMeasurementClass, "setBitNumber", intSignature); - env->CallVoidMethod(gnssMeasurementObject, setterMethod, measurement->bit_number); - } - - if (flags & GPS_MEASUREMENT_HAS_TIME_FROM_LAST_BIT) { - jmethodID setterMethod = - env->GetMethodID(gnssMeasurementClass, "setTimeFromLastBitInMs", shortSignature); - env->CallVoidMethod( - gnssMeasurementObject, - setterMethod, - measurement->time_from_last_bit_ms); - } - - if (flags & GPS_MEASUREMENT_HAS_DOPPLER_SHIFT) { - jmethodID setterMethod = - env->GetMethodID(gnssMeasurementClass, "setDopplerShiftInHz", doubleSignature); - env->CallVoidMethod(gnssMeasurementObject, setterMethod, measurement->doppler_shift_hz); - } - - if (flags & GPS_MEASUREMENT_HAS_DOPPLER_SHIFT_UNCERTAINTY) { - jmethodID setterMethod = env->GetMethodID( - gnssMeasurementClass, - "setDopplerShiftUncertaintyInHz", - doubleSignature); - env->CallVoidMethod( - gnssMeasurementObject, - setterMethod, - measurement->doppler_shift_uncertainty_hz); - } - - jmethodID multipathIndicatorSetterMethod = - env->GetMethodID(gnssMeasurementClass, "setMultipathIndicator", byteSignature); - env->CallVoidMethod( - gnssMeasurementObject, - multipathIndicatorSetterMethod, - measurement->multipath_indicator); - - if (flags & GPS_MEASUREMENT_HAS_SNR) { - jmethodID setterMethod = - env->GetMethodID(gnssMeasurementClass, "setSnrInDb", doubleSignature); - env->CallVoidMethod(gnssMeasurementObject, setterMethod, measurement->snr_db); - } - - if (flags & GPS_MEASUREMENT_HAS_ELEVATION) { - jmethodID setterMethod = - env->GetMethodID(gnssMeasurementClass, "setElevationInDeg", doubleSignature); - env->CallVoidMethod(gnssMeasurementObject, setterMethod, measurement->elevation_deg); - } - - if (flags & GPS_MEASUREMENT_HAS_ELEVATION_UNCERTAINTY) { - jmethodID setterMethod = - env->GetMethodID(gnssMeasurementClass, "setElevationUncertaintyInDeg", doubleSignature); - env->CallVoidMethod( - gnssMeasurementObject, - setterMethod, - measurement->elevation_uncertainty_deg); - } - - if (flags & GPS_MEASUREMENT_HAS_AZIMUTH) { - jmethodID setterMethod = - env->GetMethodID(gnssMeasurementClass, "setAzimuthInDeg", doubleSignature); - env->CallVoidMethod(gnssMeasurementObject, setterMethod, measurement->azimuth_deg); - } - - if (flags & GPS_MEASUREMENT_HAS_AZIMUTH_UNCERTAINTY) { - jmethodID setterMethod = env->GetMethodID( - gnssMeasurementClass, - "setAzimuthUncertaintyInDeg", - doubleSignature); - env->CallVoidMethod( - gnssMeasurementObject, - setterMethod, - measurement->azimuth_uncertainty_deg); - } - - jmethodID usedInFixSetterMethod = env->GetMethodID(gnssMeasurementClass, "setUsedInFix", "(Z)V"); - env->CallVoidMethod( - gnssMeasurementObject, - usedInFixSetterMethod, - (flags & GPS_MEASUREMENT_HAS_USED_IN_FIX) && measurement->used_in_fix); - - if (size == sizeof(GpsMeasurement)) { - jmethodID setterMethod = - env->GetMethodID(gnssMeasurementClass, - "setPseudorangeRateCarrierInMetersPerSec", - doubleSignature); - env->CallVoidMethod( - gnssMeasurementObject, - setterMethod, - measurement->pseudorange_rate_carrier_mps); - - setterMethod = - env->GetMethodID(gnssMeasurementClass, - "setPseudorangeRateCarrierUncertaintyInMetersPerSec", - doubleSignature); - env->CallVoidMethod( - gnssMeasurementObject, - setterMethod, - measurement->pseudorange_rate_carrier_uncertainty_mps); + NULL /* initialElement */); + + for (uint16_t i = 0; i < count; ++i) { + jobject gnssMeasurement = translate_gps_measurement( + env, + &measurements[i]); + env->SetObjectArrayElement(gnssMeasurementArray, i, gnssMeasurement); + env->DeleteLocalRef(gnssMeasurement); } env->DeleteLocalRef(gnssMeasurementClass); - return gnssMeasurementObject; + return gnssMeasurementArray; } -/** - * <T> can only be GpsData or GpsData_v1. Must rewrite this function if more - * types are introduced in the future releases. - */ -template<class T> -static jobjectArray translate_gps_measurements(JNIEnv* env, void* data) { - T* gps_data = reinterpret_cast<T*>(data); - size_t measurementCount = gps_data->measurement_count; - if (measurementCount == 0) { +static jobjectArray translate_gnss_measurements(JNIEnv* env, + GnssMeasurement* measurements, + size_t count) { + if (count == 0) { return NULL; } - jclass gnssMeasurementClass = env->FindClass("android/location/GnssMeasurement"); + jclass gnssMeasurementClass = env->FindClass( + "android/location/GnssMeasurement"); jobjectArray gnssMeasurementArray = env->NewObjectArray( - measurementCount, + count, gnssMeasurementClass, NULL /* initialElement */); - for (uint16_t i = 0; i < measurementCount; ++i) { - jobject gnssMeasurement = translate_gps_measurement( + for (uint16_t i = 0; i < count; ++i) { + jobject gnssMeasurement = translate_gnss_measurement( env, - &(gps_data->measurements[i]), - sizeof(gps_data->measurements[0])); + &measurements[i]); env->SetObjectArrayElement(gnssMeasurementArray, i, gnssMeasurement); env->DeleteLocalRef(gnssMeasurement); } @@ -1340,50 +1357,81 @@ static jobjectArray translate_gps_measurements(JNIEnv* env, void* data) { return gnssMeasurementArray; } +static void set_measurement_data(JNIEnv *env, + jobject clock, + jobjectArray measurementArray) { + jclass gnssMeasurementsEventClass = env->FindClass( + "android/location/GnssMeasurementsEvent"); + jmethodID gnssMeasurementsEventCtor = env->GetMethodID( + gnssMeasurementsEventClass, + "<init>", + "(Landroid/location/GnssClock;[Landroid/location/GnssMeasurement;)V"); + + jobject gnssMeasurementsEvent = env->NewObject( + gnssMeasurementsEventClass, + gnssMeasurementsEventCtor, + clock, + measurementArray); + env->CallVoidMethod(mCallbacksObj, + method_reportMeasurementData, + gnssMeasurementsEvent); + checkAndClearExceptionFromCallback(env, __FUNCTION__); + env->DeleteLocalRef(gnssMeasurementsEventClass); + env->DeleteLocalRef(gnssMeasurementsEvent); +} + static void measurement_callback(GpsData* data) { JNIEnv* env = AndroidRuntime::getJNIEnv(); if (data == NULL) { ALOGE("Invalid data provided to gps_measurement_callback"); return; } - if (data->size != sizeof(GpsData) && data->size != sizeof(GpsData_v1)) { - ALOGE("Invalid GpsData size found in gps_measurement_callback, size=%zd", data->size); + if (data->size != sizeof(GpsData)) { + ALOGE("Invalid GpsData size found in gps_measurement_callback, " + "size=%zd", + data->size); return; } - jobject gpsClock; + jobject clock; jobjectArray measurementArray; - if (data->size == sizeof(GpsData)) { - gpsClock = translate_gps_clock(env, &data->clock, sizeof(GpsClock)); - measurementArray = translate_gps_measurements<GpsData>(env, data); - } else { - gpsClock = translate_gps_clock(env, &data->clock, sizeof(GpsClock_v1)); - measurementArray = translate_gps_measurements<GpsData_v1>(env, data); - } - jclass gnssMeasurementsEventClass = env->FindClass("android/location/GnssMeasurementsEvent"); - jmethodID gnssMeasurementsEventCtor = env->GetMethodID( - gnssMeasurementsEventClass, - "<init>", - "(Landroid/location/GnssClock;[Landroid/location/GnssMeasurement;)V"); + clock = translate_gps_clock(env, &data->clock); + measurementArray = translate_gps_measurements( + env, data->measurements, data->measurement_count); + set_measurement_data(env, clock, measurementArray); - jobject gnssMeasurementsEvent = env->NewObject( - gnssMeasurementsEventClass, - gnssMeasurementsEventCtor, - gpsClock, - measurementArray); + env->DeleteLocalRef(clock); + env->DeleteLocalRef(measurementArray); +} - env->CallVoidMethod(mCallbacksObj, method_reportMeasurementData, gnssMeasurementsEvent); - checkAndClearExceptionFromCallback(env, __FUNCTION__); +static void gnss_measurement_callback(GnssData* data) { + JNIEnv* env = AndroidRuntime::getJNIEnv(); + if (data == NULL) { + ALOGE("Invalid data provided to gps_measurement_callback"); + return; + } + if (data->size != sizeof(GpsData)) { + ALOGE("Invalid GpsData size found in gps_measurement_callback, " + "size=%zd", + data->size); + return; + } - env->DeleteLocalRef(gpsClock); + jobject clock; + jobjectArray measurementArray; + clock = translate_gnss_clock(env, &data->clock); + measurementArray = translate_gnss_measurements( + env, data->measurements, data->measurement_count); + set_measurement_data(env, clock, measurementArray); + + env->DeleteLocalRef(clock); env->DeleteLocalRef(measurementArray); - env->DeleteLocalRef(gnssMeasurementsEventClass); - env->DeleteLocalRef(gnssMeasurementsEvent); } GpsMeasurementCallbacks sGpsMeasurementCallbacks = { sizeof(GpsMeasurementCallbacks), measurement_callback, + gnss_measurement_callback, }; static jboolean android_location_GnssLocationProvider_is_measurement_supported( @@ -1431,69 +1479,86 @@ static jobject translate_gps_navigation_message(JNIEnv* env, GpsNavigationMessag ALOGE("Invalid Navigation Message found: data=%p, length=%zd", data, dataLength); return NULL; } + JavaObject object(env, "android/location/GnssNavigationMessage"); + SET(Type, message->type); + SET(Svid, static_cast<int16_t>(message->prn)); + SET(MessageId, message->message_id); + SET(SubmessageId, message->submessage_id); + object.callSetter("setData", data, dataLength); + return object.get(); +} - jclass navigationMessageClass = env->FindClass("android/location/GnssNavigationMessage"); - jmethodID navigationMessageCtor = env->GetMethodID(navigationMessageClass, "<init>", "()V"); - jobject navigationMessageObject = env->NewObject(navigationMessageClass, navigationMessageCtor); - - jmethodID setTypeMethod = env->GetMethodID(navigationMessageClass, "setType", "(B)V"); - env->CallVoidMethod(navigationMessageObject, setTypeMethod, message->type); - - jmethodID setPrnMethod = env->GetMethodID(navigationMessageClass, "setPrn", "(B)V"); - env->CallVoidMethod(navigationMessageObject, setPrnMethod, message->prn); - - jmethodID setMessageIdMethod = env->GetMethodID(navigationMessageClass, "setMessageId", "(S)V"); - env->CallVoidMethod(navigationMessageObject, setMessageIdMethod, message->message_id); - - jmethodID setSubmessageIdMethod = - env->GetMethodID(navigationMessageClass, "setSubmessageId", "(S)V"); - env->CallVoidMethod(navigationMessageObject, setSubmessageIdMethod, message->submessage_id); - - jbyteArray dataArray = env->NewByteArray(dataLength); - env->SetByteArrayRegion(dataArray, 0, dataLength, (jbyte*) data); - jmethodID setDataMethod = env->GetMethodID(navigationMessageClass, "setData", "([B)V"); - env->CallVoidMethod(navigationMessageObject, setDataMethod, dataArray); +static jobject translate_gnss_navigation_message( + JNIEnv* env, GnssNavigationMessage* message) { + size_t dataLength = message->data_length; + uint8_t* data = message->data; + if (dataLength == 0 || data == NULL) { + ALOGE("Invalid Navigation Message found: data=%p, length=%zd", data, dataLength); + return NULL; + } + JavaObject object(env, "android/location/GnssNavigationMessage"); + SET(Type, message->type); + SET(Svid, message->svid); + SET(MessageId, message->message_id); + SET(SubmessageId, message->submessage_id); + object.callSetter("setData", data, dataLength); + return object.get(); +} - env->DeleteLocalRef(navigationMessageClass); - env->DeleteLocalRef(dataArray); - return navigationMessageObject; +static void set_navigation_message(jobject navigationMessage) { + JNIEnv* env = AndroidRuntime::getJNIEnv(); + jclass navigationMessageEventClass = + env->FindClass("android/location/GnssNavigationMessageEvent"); + jmethodID navigationMessageEventCtor = env->GetMethodID( + navigationMessageEventClass, + "<init>", + "(Landroid/location/GnssNavigationMessage;)V"); + jobject navigationMessageEvent = env->NewObject( + navigationMessageEventClass, + navigationMessageEventCtor, + navigationMessage); + env->CallVoidMethod(mCallbacksObj, + method_reportNavigationMessages, + navigationMessageEvent); + checkAndClearExceptionFromCallback(env, __FUNCTION__); + env->DeleteLocalRef(navigationMessageEventClass); + env->DeleteLocalRef(navigationMessageEvent); } static void navigation_message_callback(GpsNavigationMessage* message) { - JNIEnv* env = AndroidRuntime::getJNIEnv(); if (message == NULL) { ALOGE("Invalid Navigation Message provided to callback"); return; } - - if (message->size == sizeof(GpsNavigationMessage)) { - jobject navigationMessage = translate_gps_navigation_message(env, message); - - jclass navigationMessageEventClass = - env->FindClass("android/location/GnssNavigationMessageEvent"); - jmethodID navigationMessageEventCtor = env->GetMethodID( - navigationMessageEventClass, - "<init>", - "(Landroid/location/GnssNavigationMessage;)V"); - jobject navigationMessageEvent = env->NewObject( - navigationMessageEventClass, - navigationMessageEventCtor, - navigationMessage); - - env->CallVoidMethod(mCallbacksObj, method_reportNavigationMessages, navigationMessageEvent); - checkAndClearExceptionFromCallback(env, __FUNCTION__); - - env->DeleteLocalRef(navigationMessage); - env->DeleteLocalRef(navigationMessageEventClass); - env->DeleteLocalRef(navigationMessageEvent); - } else { + if (message->size != sizeof(GpsNavigationMessage)) { ALOGE("Invalid GpsNavigationMessage size found: %zd", message->size); + return; + } + JNIEnv* env = AndroidRuntime::getJNIEnv(); + jobject navigationMessage = translate_gps_navigation_message(env, message); + set_navigation_message(navigationMessage); + env->DeleteLocalRef(navigationMessage); +} + +static void gnss_navigation_message_callback(GnssNavigationMessage* message) { + if (message == NULL) { + ALOGE("Invalid Navigation Message provided to callback"); + return; + } + if (message->size != sizeof(GnssNavigationMessage)) { + ALOGE("Invalid GnssNavigationMessage size found: %zd", message->size); + return; } + JNIEnv* env = AndroidRuntime::getJNIEnv(); + jobject navigationMessage = translate_gnss_navigation_message(env, message); + set_navigation_message(navigationMessage); + env->DeleteLocalRef(navigationMessage); } GpsNavigationMessageCallbacks sGpsNavigationMessageCallbacks = { sizeof(GpsNavigationMessageCallbacks), navigation_message_callback, + gnss_navigation_message_callback, }; static jboolean android_location_GnssLocationProvider_is_navigation_message_supported( @@ -1567,7 +1632,7 @@ static const JNINativeMethod sMethods[] = { "(I)V", (void*)android_location_GnssLocationProvider_delete_aiding_data}, {"native_read_sv_status", - "([I[F[F[F[I)I", + "([I[F[F[F)I", (void*)android_location_GnssLocationProvider_read_sv_status}, {"native_read_nmea", "([BI)I", (void*)android_location_GnssLocationProvider_read_nmea}, {"native_inject_time", "(JJI)V", (void*)android_location_GnssLocationProvider_inject_time}, diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index eacf11f47415..916b66d04a54 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -116,6 +116,7 @@ import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; import android.util.Log; +import android.util.Pair; import android.util.Slog; import android.util.SparseArray; import android.util.Xml; @@ -274,6 +275,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { private static final int PROFILE_KEYGUARD_FEATURES = PROFILE_KEYGUARD_FEATURES_AFFECT_OWNER | PROFILE_KEYGUARD_FEATURES_AFFECT_PROFILE; + private static final int DEVICE_ADMIN_DEACTIVATE_TIMEOUT = 10000; + final Context mContext; final Injector mInjector; final IPackageManager mIPackageManager; @@ -281,6 +284,13 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { final UserManagerInternal mUserManagerInternal; private final LockPatternUtils mLockPatternUtils; + /** + * Contains (package-user) pairs to remove. An entry (p, u) implies that removal of package p + * is requested for user u. + */ + private final Set<Pair<String, Integer>> mPackagesToRemove = + new ArraySet<Pair<String, Integer>>(); + final LocalService mLocalService; // Stores and loads state on device and profile owners. @@ -1239,7 +1249,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (packageName == null || packageName.equals(adminPackage)) { if (mIPackageManager.getPackageInfo(adminPackage, 0, userHandle) == null || mIPackageManager.getReceiverInfo( - aa.info.getComponent(), 0, userHandle) == null) { + aa.info.getComponent(), + PackageManager.MATCH_ENCRYPTION_AWARE_AND_UNAWARE, + userHandle) == null) { removed = true; policy.mAdminList.remove(i); policy.mAdminMap.remove(aa.info.getComponent()); @@ -2015,35 +2027,19 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { final ActiveAdmin admin = getActiveAdminUncheckedLocked(adminReceiver, userHandle); if (admin != null) { getUserData(userHandle).mRemovingAdmins.add(adminReceiver); - sendAdminCommandLocked(admin, DeviceAdminReceiver.ACTION_DEVICE_ADMIN_DISABLED, new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { - synchronized (DevicePolicyManagerService.this) { - int userHandle = admin.getUserHandle().getIdentifier(); - DevicePolicyData policy = getUserData(userHandle); - boolean doProxyCleanup = admin.info.usesPolicy( - DeviceAdminInfo.USES_POLICY_SETS_GLOBAL_PROXY); - policy.mAdminList.remove(admin); - policy.mAdminMap.remove(adminReceiver); - validatePasswordOwnerLocked(policy); - if (doProxyCleanup) { - resetGlobalProxyLocked(getUserData(userHandle)); - } - saveSettingsLocked(userHandle); - updateMaximumTimeToLockLocked(userHandle); - policy.mRemovingAdmins.remove(adminReceiver); - } - // The removed admin might have disabled camera, so update user - // restrictions. - pushUserRestrictions(userHandle); + removeAdminArtifacts(adminReceiver, userHandle); + removePackageIfRequired(adminReceiver.getPackageName(), userHandle); } }); } } + public DeviceAdminInfo findAdmin(ComponentName adminName, int userHandle, boolean throwForMissiongPermission) { if (!mHasFeature) { @@ -3528,11 +3524,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public int getCurrentFailedPasswordAttempts(int userHandle, boolean parent) { + enforceFullCrossUsersPermission(userHandle); synchronized (this) { - // This API can only be called by an active device admin, - // so try to retrieve it to check that the caller is one. - getActiveAdminForCallerLocked( - null, DeviceAdminInfo.USES_POLICY_WATCH_LOGIN, parent); + if (!isCallerWithSystemUid()) { + // This API can only be called by an active device admin, + // so try to retrieve it to check that the caller is one. + getActiveAdminForCallerLocked( + null, DeviceAdminInfo.USES_POLICY_WATCH_LOGIN, parent); + } DevicePolicyData policy = getUserDataUnchecked(getCredentialOwner(userHandle, parent)); @@ -4492,7 +4491,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } if (mInjector.securityLogIsLoggingEnabled()) { - SecurityLog.writeEvent(SecurityLog.TAG_KEYGUARD_DISMISS_AUTH_ATTEMPT, /*result*/ 0); + SecurityLog.writeEvent(SecurityLog.TAG_KEYGUARD_DISMISS_AUTH_ATTEMPT, /*result*/ 0, + /*method strength*/ 1); } } @@ -4522,23 +4522,50 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } if (mInjector.securityLogIsLoggingEnabled()) { - SecurityLog.writeEvent(SecurityLog.TAG_KEYGUARD_DISMISS_AUTH_ATTEMPT, /*result*/ 1); + SecurityLog.writeEvent(SecurityLog.TAG_KEYGUARD_DISMISS_AUTH_ATTEMPT, /*result*/ 1, + /*method strength*/ 1); + } + } + + @Override + public void reportFailedFingerprintAttempt(int userHandle) { + enforceFullCrossUsersPermission(userHandle); + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.BIND_DEVICE_ADMIN, null); + if (mInjector.securityLogIsLoggingEnabled()) { + SecurityLog.writeEvent(SecurityLog.TAG_KEYGUARD_DISMISS_AUTH_ATTEMPT, /*result*/ 0, + /*method strength*/ 0); + } + } + + @Override + public void reportSuccessfulFingerprintAttempt(int userHandle) { + enforceFullCrossUsersPermission(userHandle); + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.BIND_DEVICE_ADMIN, null); + if (mInjector.securityLogIsLoggingEnabled()) { + SecurityLog.writeEvent(SecurityLog.TAG_KEYGUARD_DISMISS_AUTH_ATTEMPT, /*result*/ 1, + /*method strength*/ 0); } } @Override - public void reportKeyguardDismissed() { + public void reportKeyguardDismissed(int userHandle) { + enforceFullCrossUsersPermission(userHandle); mContext.enforceCallingOrSelfPermission( android.Manifest.permission.BIND_DEVICE_ADMIN, null); + if (mInjector.securityLogIsLoggingEnabled()) { SecurityLog.writeEvent(SecurityLog.TAG_KEYGUARD_DISMISSED); } } @Override - public void reportKeyguardSecured() { + public void reportKeyguardSecured(int userHandle) { + enforceFullCrossUsersPermission(userHandle); mContext.enforceCallingOrSelfPermission( android.Manifest.permission.BIND_DEVICE_ADMIN, null); + if (mInjector.securityLogIsLoggingEnabled()) { SecurityLog.writeEvent(SecurityLog.TAG_KEYGUARD_SECURED); } @@ -6769,6 +6796,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { throw new IllegalArgumentException("profileOwner " + profileOwner + " and admin " + admin + " are not in the same package"); } + // Only allow the system user to use this method + if (!mInjector.binderGetCallingUserHandle().isSystem()) { + throw new SecurityException("createAndManageUser was called from non-system user"); + } // Create user. UserHandle user = null; synchronized (this) { @@ -6780,7 +6811,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if ((flags & DevicePolicyManager.MAKE_USER_EPHEMERAL) != 0) { userInfoFlags |= UserInfo.FLAG_EPHEMERAL; } - UserInfo userInfo = mUserManager.createUser(name, userInfoFlags); + UserInfo userInfo = mUserManagerInternal.createUserEvenWhenDisallowed(name, + userInfoFlags); if (userInfo != null) { user = userInfo.getUserHandle(); } @@ -7110,7 +7142,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { List<ResolveInfo> activitiesToEnable = mIPackageManager.queryIntentActivities( intent, intent.resolveTypeIfNeeded(mContext.getContentResolver()), - 0, // no flags + PackageManager.MATCH_ENCRYPTION_AWARE_AND_UNAWARE, parentUserId); if (VERBOSE_LOG) { @@ -8009,7 +8041,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { boolean isPackageInstalledForUser(String packageName, int userHandle) { try { - PackageInfo pi = mIPackageManager.getPackageInfo(packageName, 0, userHandle); + PackageInfo pi = mInjector.getIPackageManager().getPackageInfo(packageName, 0, + userHandle); return (pi != null) && (pi.applicationInfo.flags != 0); } catch (RemoteException re) { throw new RuntimeException("Package manager has died", re); @@ -8465,4 +8498,137 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { List<SecurityEvent> logs = mSecurityLogMonitor.retrieveLogs(); return logs != null ? new ParceledListSlice<SecurityEvent>(logs) : null; } + + private void enforceCanManageDeviceAdmin() { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_DEVICE_ADMINS, + null); + } + + @Override + public boolean isUninstallInQueue(final String packageName) { + enforceCanManageDeviceAdmin(); + final int userId = mInjector.userHandleGetCallingUserId(); + Pair<String, Integer> packageUserPair = new Pair<>(packageName, userId); + synchronized (this) { + return mPackagesToRemove.contains(packageUserPair); + } + } + + @Override + public void uninstallPackageWithActiveAdmins(final String packageName) { + enforceCanManageDeviceAdmin(); + Preconditions.checkArgument(!TextUtils.isEmpty(packageName)); + + final int userId = mInjector.userHandleGetCallingUserId(); + + final ComponentName profileOwner = getProfileOwner(userId); + if (profileOwner != null && packageName.equals(profileOwner.getPackageName())) { + throw new IllegalArgumentException("Cannot uninstall a package with a profile owner"); + } + + final ComponentName deviceOwner = getDeviceOwnerComponent(/* callingUserOnly= */ false); + if (getDeviceOwnerUserId() == userId && deviceOwner != null + && packageName.equals(deviceOwner.getPackageName())) { + throw new IllegalArgumentException("Cannot uninstall a package with a device owner"); + } + + final Pair<String, Integer> packageUserPair = new Pair<>(packageName, userId); + synchronized (this) { + mPackagesToRemove.add(packageUserPair); + } + + // All active admins on the user. + final List<ComponentName> allActiveAdmins = getActiveAdmins(userId); + + // Active admins in the target package. + final List<ComponentName> packageActiveAdmins = new ArrayList<>(); + if (allActiveAdmins != null) { + for (ComponentName activeAdmin : allActiveAdmins) { + if (packageName.equals(activeAdmin.getPackageName())) { + packageActiveAdmins.add(activeAdmin); + removeActiveAdmin(activeAdmin, userId); + } + } + } + if (packageActiveAdmins.size() == 0) { + startUninstallIntent(packageName, userId); + } else { + mHandler.postDelayed(new Runnable() { + @Override + public void run() { + for (ComponentName activeAdmin : packageActiveAdmins) { + removeAdminArtifacts(activeAdmin, userId); + } + startUninstallIntent(packageName, userId); + } + }, DEVICE_ADMIN_DEACTIVATE_TIMEOUT); // Start uninstall after timeout anyway. + } + } + + private void removePackageIfRequired(final String packageName, final int userId) { + if (!packageHasActiveAdmins(packageName, userId)) { + // Will not do anything if uninstall was not requested or was already started. + startUninstallIntent(packageName, userId); + } + } + + private void startUninstallIntent(final String packageName, final int userId) { + final Pair<String, Integer> packageUserPair = new Pair<>(packageName, userId); + synchronized (this) { + if (!mPackagesToRemove.contains(packageUserPair)) { + // Do nothing if uninstall was not requested or was already started. + return; + } + mPackagesToRemove.remove(packageUserPair); + } + try { + if (mInjector.getIPackageManager().getPackageInfo(packageName, 0, userId) == null) { + // Package does not exist. Nothing to do. + return; + } + } catch (RemoteException re) { + Log.e(LOG_TAG, "Failure talking to PackageManager while getting package info"); + } + + try { // force stop the package before uninstalling + mInjector.getIActivityManager().forceStopPackage(packageName, userId); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Failure talking to ActivityManager while force stopping package"); + } + final Uri packageURI = Uri.parse("package:" + packageName); + final Intent uninstallIntent = new Intent(Intent.ACTION_UNINSTALL_PACKAGE, packageURI); + uninstallIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + mContext.startActivityAsUser(uninstallIntent, UserHandle.of(userId)); + } + + /** + * Removes the admin from the policy. Ideally called after the admin's + * {@link DeviceAdminReceiver#onDisabled(Context, Intent)} has been successfully completed. + * + * @param adminReceiver The admin to remove + * @param userHandle The user for which this admin has to be removed. + */ + private void removeAdminArtifacts(final ComponentName adminReceiver, final int userHandle) { + synchronized (this) { + final ActiveAdmin admin = getActiveAdminUncheckedLocked(adminReceiver, userHandle); + if (admin == null) { + return; + } + final DevicePolicyData policy = getUserData(userHandle); + final boolean doProxyCleanup = admin.info.usesPolicy( + DeviceAdminInfo.USES_POLICY_SETS_GLOBAL_PROXY); + policy.mAdminList.remove(admin); + policy.mAdminMap.remove(adminReceiver); + validatePasswordOwnerLocked(policy); + if (doProxyCleanup) { + resetGlobalProxyLocked(policy); + } + saveSettingsLocked(userHandle); + updateMaximumTimeToLockLocked(userHandle); + policy.mRemovingAdmins.remove(adminReceiver); + } + // The removed admin might have disabled camera, so update user + // restrictions. + pushUserRestrictions(userHandle); + } } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java index 880f810107a0..68fd0f681aa5 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java @@ -716,11 +716,11 @@ class Owners { } File getLegacyConfigFileWithTestOverride() { - return new File(Environment.getSystemSecureDirectory(), DEVICE_OWNER_XML_LEGACY); + return new File(Environment.getDataSystemDirectory(), DEVICE_OWNER_XML_LEGACY); } File getDeviceOwnerFileWithTestOverride() { - return new File(Environment.getSystemSecureDirectory(), DEVICE_OWNER_XML); + return new File(Environment.getDataSystemDirectory(), DEVICE_OWNER_XML); } File getProfileOwnerFileWithTestOverride(int userId) { diff --git a/services/net/java/android/net/dhcp/DhcpClient.java b/services/net/java/android/net/dhcp/DhcpClient.java index 2329b4251fc7..ceabfceccca6 100644 --- a/services/net/java/android/net/dhcp/DhcpClient.java +++ b/services/net/java/android/net/dhcp/DhcpClient.java @@ -27,7 +27,6 @@ import android.content.Intent; import android.content.IntentFilter; import android.net.DhcpResults; import android.net.BaseDhcpStateMachine; -import android.net.DhcpStateMachine; import android.net.InterfaceConfiguration; import android.net.LinkAddress; import android.net.NetworkUtils; @@ -103,12 +102,35 @@ public class DhcpClient extends BaseDhcpStateMachine { // t=0, t=2, t=6, t=14, t=30, allowing for 10% jitter. private static final int DHCP_TIMEOUT_MS = 36 * SECONDS; + private static final int PUBLIC_BASE = Protocol.BASE_DHCP; + + /* Commands from controller to start/stop DHCP */ + public static final int CMD_START_DHCP = PUBLIC_BASE + 1; + public static final int CMD_STOP_DHCP = PUBLIC_BASE + 2; + public static final int CMD_RENEW_DHCP = PUBLIC_BASE + 3; + + /* Notification from DHCP state machine prior to DHCP discovery/renewal */ + public static final int CMD_PRE_DHCP_ACTION = PUBLIC_BASE + 4; + /* Notification from DHCP state machine post DHCP discovery/renewal. Indicates + * success/failure */ + public static final int CMD_POST_DHCP_ACTION = PUBLIC_BASE + 5; + /* Notification from DHCP state machine before quitting */ + public static final int CMD_ON_QUIT = PUBLIC_BASE + 6; + + /* Command from controller to indicate DHCP discovery/renewal can continue + * after pre DHCP action is complete */ + public static final int CMD_PRE_DHCP_ACTION_COMPLETE = PUBLIC_BASE + 7; + + /* Message.arg1 arguments to CMD_POST_DHCP notification */ + public static final int DHCP_SUCCESS = 1; + public static final int DHCP_FAILURE = 2; + // Messages. - private static final int BASE = Protocol.BASE_DHCP + 100; - private static final int CMD_KICK = BASE + 1; - private static final int CMD_RECEIVED_PACKET = BASE + 2; - private static final int CMD_TIMEOUT = BASE + 3; - private static final int CMD_ONESHOT_TIMEOUT = BASE + 4; + private static final int PRIVATE_BASE = Protocol.BASE_DHCP + 100; + private static final int CMD_KICK = PRIVATE_BASE + 1; + private static final int CMD_RECEIVED_PACKET = PRIVATE_BASE + 2; + private static final int CMD_TIMEOUT = PRIVATE_BASE + 3; + private static final int CMD_ONESHOT_TIMEOUT = PRIVATE_BASE + 4; // DHCP parameters that we request. private static final byte[] REQUESTED_PARAMS = new byte[] { @@ -211,7 +233,7 @@ public class DhcpClient extends BaseDhcpStateMachine { // Used to time out PacketRetransmittingStates. mTimeoutAlarm = makeWakeupMessage("TIMEOUT", CMD_TIMEOUT); // Used to schedule DHCP renews. - mRenewAlarm = makeWakeupMessage("RENEW", DhcpStateMachine.CMD_RENEW_DHCP); + mRenewAlarm = makeWakeupMessage("RENEW", CMD_RENEW_DHCP); // Used to tell the caller when its request (CMD_START_DHCP or CMD_RENEW_DHCP) timed out. // TODO: when the legacy DHCP client is gone, make the client fully asynchronous and // remove this. @@ -400,13 +422,12 @@ public class DhcpClient extends BaseDhcpStateMachine { } private void notifySuccess() { - mController.sendMessage(DhcpStateMachine.CMD_POST_DHCP_ACTION, - DhcpStateMachine.DHCP_SUCCESS, 0, new DhcpResults(mDhcpLease)); + mController.sendMessage( + CMD_POST_DHCP_ACTION, DHCP_SUCCESS, 0, new DhcpResults(mDhcpLease)); } private void notifyFailure() { - mController.sendMessage(DhcpStateMachine.CMD_POST_DHCP_ACTION, - DhcpStateMachine.DHCP_FAILURE, 0, null); + mController.sendMessage(CMD_POST_DHCP_ACTION, DHCP_FAILURE, 0, null); } private void clearDhcpState() { @@ -428,7 +449,7 @@ public class DhcpClient extends BaseDhcpStateMachine { protected void onQuitting() { Log.d(TAG, "onQuitting"); - mController.sendMessage(DhcpStateMachine.CMD_ON_QUIT); + mController.sendMessage(CMD_ON_QUIT); } private void maybeLog(String msg) { @@ -442,17 +463,17 @@ public class DhcpClient extends BaseDhcpStateMachine { private String messageName(int what) { switch (what) { - case DhcpStateMachine.CMD_START_DHCP: + case CMD_START_DHCP: return "CMD_START_DHCP"; - case DhcpStateMachine.CMD_STOP_DHCP: + case CMD_STOP_DHCP: return "CMD_STOP_DHCP"; - case DhcpStateMachine.CMD_RENEW_DHCP: + case CMD_RENEW_DHCP: return "CMD_RENEW_DHCP"; - case DhcpStateMachine.CMD_PRE_DHCP_ACTION: + case CMD_PRE_DHCP_ACTION: return "CMD_PRE_DHCP_ACTION"; - case DhcpStateMachine.CMD_PRE_DHCP_ACTION_COMPLETE: + case CMD_PRE_DHCP_ACTION_COMPLETE: return "CMD_PRE_DHCP_ACTION_COMPLETE"; - case DhcpStateMachine.CMD_POST_DHCP_ACTION: + case CMD_POST_DHCP_ACTION: return "CMD_POST_DHCP_ACTION"; case CMD_KICK: return "CMD_KICK"; @@ -495,14 +516,14 @@ public class DhcpClient extends BaseDhcpStateMachine { @Override public void enter() { super.enter(); - mController.sendMessage(DhcpStateMachine.CMD_PRE_DHCP_ACTION); + mController.sendMessage(CMD_PRE_DHCP_ACTION); } @Override public boolean processMessage(Message message) { super.processMessage(message); switch (message.what) { - case DhcpStateMachine.CMD_PRE_DHCP_ACTION_COMPLETE: + case CMD_PRE_DHCP_ACTION_COMPLETE: transitionTo(mOtherState); return HANDLED; default: @@ -532,7 +553,7 @@ public class DhcpClient extends BaseDhcpStateMachine { public boolean processMessage(Message message) { super.processMessage(message); switch (message.what) { - case DhcpStateMachine.CMD_START_DHCP: + case CMD_START_DHCP: scheduleOneshotTimeout(); if (mRegisteredForPreDhcpNotification) { transitionTo(mWaitBeforeStartState); @@ -588,7 +609,7 @@ public class DhcpClient extends BaseDhcpStateMachine { public boolean processMessage(Message message) { super.processMessage(message); switch (message.what) { - case DhcpStateMachine.CMD_STOP_DHCP: + case CMD_STOP_DHCP: transitionTo(mStoppedState); return HANDLED; case CMD_ONESHOT_TIMEOUT: @@ -810,7 +831,7 @@ public class DhcpClient extends BaseDhcpStateMachine { public boolean processMessage(Message message) { super.processMessage(message); switch (message.what) { - case DhcpStateMachine.CMD_RENEW_DHCP: + case CMD_RENEW_DHCP: if (mRegisteredForPreDhcpNotification) { transitionTo(mWaitBeforeRenewalState); } else { diff --git a/services/net/java/android/net/ip/IpManager.java b/services/net/java/android/net/ip/IpManager.java index 06b6ee75da7a..a388a26d9e91 100644 --- a/services/net/java/android/net/ip/IpManager.java +++ b/services/net/java/android/net/ip/IpManager.java @@ -61,7 +61,6 @@ import java.util.Objects; * @hide */ public class IpManager extends StateMachine { - private static final String TAG = IpManager.class.getSimpleName(); private static final boolean DBG = true; private static final boolean VDBG = false; @@ -92,13 +91,18 @@ public class IpManager extends StateMachine { */ // Implementations must call IpManager#completedPreDhcpAction(). + // TODO: Remove this requirement, perhaps via some + // registerForPreDhcpAction()-style mechanism. public void onPreDhcpAction() {} public void onPostDhcpAction() {} - // TODO: Kill with fire once DHCP and static configuration are moved - // out of WifiStateMachine. - public void onIPv4ProvisioningSuccess(DhcpResults dhcpResults) {} - public void onIPv4ProvisioningFailure() {} + // This is purely advisory and not an indication of provisioning + // success or failure. This is only here for callers that want to + // expose DHCPv4 results to other APIs (e.g., WifiInfo#setInetAddress). + // DHCPv4 or static IPv4 configuration failure or success can be + // determined by whether or not the passed-in DhcpResults object is + // null or not. + public void onNewDhcpResults(DhcpResults dhcpResults) {} public void onProvisioningSuccess(LinkProperties newLp) {} public void onProvisioningFailure(LinkProperties newLp) {} @@ -122,8 +126,10 @@ public class IpManager extends StateMachine { private final Object mLock = new Object(); private final State mStoppedState = new StoppedState(); + private final State mStoppingState = new StoppingState(); private final State mStartedState = new StartedState(); + private final String mTag; private final Context mContext; private final String mInterfaceName; @VisibleForTesting @@ -150,11 +156,11 @@ public class IpManager extends StateMachine { public IpManager(Context context, String ifName, Callback callback) throws IllegalArgumentException { - super(TAG + "." + ifName); + super(IpManager.class.getSimpleName() + "." + ifName); + mTag = getName(); mContext = context; mInterfaceName = ifName; - mCallback = callback; mNwService = INetworkManagementService.Stub.asInterface( @@ -171,7 +177,7 @@ public class IpManager extends StateMachine { try { mNwService.registerObserver(mNetlinkTracker); } catch (RemoteException e) { - Log.e(TAG, "Couldn't register NetlinkTracker: " + e.toString()); + Log.e(mTag, "Couldn't register NetlinkTracker: " + e.toString()); } resetLinkProperties(); @@ -179,6 +185,8 @@ public class IpManager extends StateMachine { // Super simple StateMachine. addState(mStoppedState); addState(mStartedState); + addState(mStoppingState); + setInitialState(mStoppedState); setLogRecSize(MAX_LOG_RECORDS); super.start(); @@ -192,7 +200,9 @@ public class IpManager extends StateMachine { */ @VisibleForTesting protected IpManager(String ifName, Callback callback) { - super(TAG + ".test-" + ifName); + super(IpManager.class.getSimpleName() + ".test-" + ifName); + mTag = getName(); + mInterfaceName = ifName; mCallback = callback; @@ -203,13 +213,11 @@ public class IpManager extends StateMachine { public void startProvisioning(StaticIpConfiguration staticIpConfig) { getInterfaceIndex(); - sendMessage(CMD_START, staticIpConfig); } public void startProvisioning() { getInterfaceIndex(); - sendMessage(CMD_START); } @@ -236,12 +244,48 @@ public class IpManager extends StateMachine { * Internals. */ + @Override + protected String getWhatToString(int what) { + // TODO: Investigate switching to reflection. + switch (what) { + case CMD_STOP: + return "CMD_STOP"; + case CMD_START: + return "CMD_START"; + case CMD_CONFIRM: + return "CMD_CONFIRM"; + case EVENT_PRE_DHCP_ACTION_COMPLETE: + return "EVENT_PRE_DHCP_ACTION_COMPLETE"; + case EVENT_NETLINK_LINKPROPERTIES_CHANGED: + return "EVENT_NETLINK_LINKPROPERTIES_CHANGED"; + case DhcpClient.CMD_PRE_DHCP_ACTION: + return "DhcpClient.CMD_PRE_DHCP_ACTION"; + case DhcpClient.CMD_POST_DHCP_ACTION: + return "DhcpClient.CMD_POST_DHCP_ACTION"; + case DhcpClient.CMD_ON_QUIT: + return "DhcpClient.CMD_ON_QUIT"; + } + return "UNKNOWN:" + Integer.toString(what); + } + + @Override + protected String getLogRecString(Message msg) { + final String logLine = String.format( + "iface{%s/%d} arg1{%d} arg2{%d} obj{%s}", + mInterfaceName, mInterfaceIndex, + msg.arg1, msg.arg2, Objects.toString(msg.obj)); + if (VDBG) { + Log.d(mTag, getWhatToString(msg.what) + " " + logLine); + } + return logLine; + } + private void getInterfaceIndex() { try { mInterfaceIndex = NetworkInterface.getByName(mInterfaceName).getIndex(); } catch (SocketException | NullPointerException e) { // TODO: throw new IllegalStateException. - Log.e(TAG, "ALERT: Failed to get interface index: ", e); + Log.e(mTag, "ALERT: Failed to get interface index: ", e); } } @@ -260,16 +304,93 @@ public class IpManager extends StateMachine { } } + // For now: use WifiStateMachine's historical notion of provisioned. + private static boolean isProvisioned(LinkProperties lp) { + // For historical reasons, we should connect even if all we have is + // an IPv4 address and nothing else. + return lp.isProvisioned() || lp.hasIPv4Address(); + } + + // TODO: Investigate folding all this into the existing static function + // LinkProperties.compareProvisioning() or some other single function that + // takes two LinkProperties objects and returns a ProvisioningChange + // object that is a correct and complete assessment of what changed, taking + // account of the asymmetries described in the comments in this function. + // Then switch to using it everywhere (IpReachabilityMonitor, etc.). + private static ProvisioningChange compareProvisioning( + LinkProperties oldLp, LinkProperties newLp) { + ProvisioningChange delta; + + final boolean wasProvisioned = isProvisioned(oldLp); + final boolean isProvisioned = isProvisioned(newLp); + + if (!wasProvisioned && isProvisioned) { + delta = ProvisioningChange.GAINED_PROVISIONING; + } else if (wasProvisioned && isProvisioned) { + delta = ProvisioningChange.STILL_PROVISIONED; + } else if (!wasProvisioned && !isProvisioned) { + delta = ProvisioningChange.STILL_NOT_PROVISIONED; + } else { + // (wasProvisioned && !isProvisioned) + // + // Note that this is true even if we lose a configuration element + // (e.g., a default gateway) that would not be required to advance + // into provisioned state. This is intended: if we have a default + // router and we lose it, that's a sure sign of a problem, but if + // we connect to a network with no IPv4 DNS servers, we consider + // that to be a network without DNS servers and connect anyway. + // + // See the comment below. + delta = ProvisioningChange.LOST_PROVISIONING; + } + + // Additionally: + // + // Partial configurations (e.g., only an IPv4 address with no DNS + // servers and no default route) are accepted as long as DHCPv4 + // succeeds. On such a network, isProvisioned() will always return + // false, because the configuration is not complete, but we want to + // connect anyway. It might be a disconnected network such as a + // Chromecast or a wireless printer, for example. + // + // Because on such a network isProvisioned() will always return false, + // delta will never be LOST_PROVISIONING. So check for loss of + // provisioning here too. + if ((oldLp.hasIPv4Address() && !newLp.hasIPv4Address()) || + (oldLp.isIPv6Provisioned() && !newLp.isIPv6Provisioned())) { + delta = ProvisioningChange.LOST_PROVISIONING; + } + + return delta; + } + + private void dispatchCallback(ProvisioningChange delta, LinkProperties newLp) { + switch (delta) { + case GAINED_PROVISIONING: + if (VDBG) { Log.d(mTag, "onProvisioningSuccess()"); } + mCallback.onProvisioningSuccess(newLp); + break; + + case LOST_PROVISIONING: + if (VDBG) { Log.d(mTag, "onProvisioningFailure()"); } + mCallback.onProvisioningFailure(newLp); + break; + + default: + if (VDBG) { Log.d(mTag, "onLinkPropertiesChange()"); } + mCallback.onLinkPropertiesChange(newLp); + break; + } + } + private ProvisioningChange setLinkProperties(LinkProperties newLp) { if (mIpReachabilityMonitor != null) { mIpReachabilityMonitor.updateLinkProperties(newLp); } - // TODO: Figure out whether and how to incorporate static configuration - // into the notion of provisioning. ProvisioningChange delta; synchronized (mLock) { - delta = LinkProperties.compareProvisioning(mLinkProperties, newLp); + delta = compareProvisioning(mLinkProperties, newLp); mLinkProperties = new LinkProperties(newLp); } @@ -277,7 +398,7 @@ public class IpManager extends StateMachine { switch (delta) { case GAINED_PROVISIONING: case LOST_PROVISIONING: - Log.d(TAG, "provisioning: " + delta); + Log.d(mTag, "provisioning: " + delta); break; } } @@ -333,7 +454,7 @@ public class IpManager extends StateMachine { } if (VDBG) { - Log.d(TAG, "newLp{" + newLp + "}"); + Log.d(mTag, "newLp{" + newLp + "}"); } return newLp; @@ -345,21 +466,51 @@ public class IpManager extends StateMachine { ifcg.setLinkAddress(new LinkAddress("0.0.0.0/0")); mNwService.setInterfaceConfig(mInterfaceName, ifcg); } catch (RemoteException e) { - Log.e(TAG, "ALERT: Failed to clear IPv4 address on interface " + mInterfaceName, e); + Log.e(mTag, "ALERT: Failed to clear IPv4 address on interface " + mInterfaceName, e); } } private void handleIPv4Success(DhcpResults dhcpResults) { mDhcpResults = new DhcpResults(dhcpResults); - setLinkProperties(assembleLinkProperties()); - mCallback.onIPv4ProvisioningSuccess(dhcpResults); + final LinkProperties newLp = assembleLinkProperties(); + final ProvisioningChange delta = setLinkProperties(newLp); + + if (VDBG) { + Log.d(mTag, "onNewDhcpResults(" + Objects.toString(dhcpResults) + ")"); + } + mCallback.onNewDhcpResults(dhcpResults); + + dispatchCallback(delta, newLp); } private void handleIPv4Failure() { + // TODO: Figure out to de-dup this and the same code in DhcpClient. clearIPv4Address(); mDhcpResults = null; - setLinkProperties(assembleLinkProperties()); - mCallback.onIPv4ProvisioningFailure(); + final LinkProperties newLp = assembleLinkProperties(); + ProvisioningChange delta = setLinkProperties(newLp); + // If we've gotten here and we're still not provisioned treat that as + // a total loss of provisioning. + // + // Either (a) static IP configuration failed or (b) DHCPv4 failed AND + // there was no usable IPv6 obtained before the DHCPv4 timeout. + // + // Regardless: GAME OVER. + // + // TODO: Make the DHCP client not time out and just continue in + // exponential backoff. Callers such as Wi-Fi which need a timeout + // should implement it themselves. + if (delta == ProvisioningChange.STILL_NOT_PROVISIONED) { + delta = ProvisioningChange.LOST_PROVISIONING; + } + + if (VDBG) { Log.d(mTag, "onNewDhcpResults(null)"); } + mCallback.onNewDhcpResults(null); + + dispatchCallback(delta, newLp); + if (delta == ProvisioningChange.LOST_PROVISIONING) { + transitionTo(mStoppingState); + } } class StoppedState extends State { @@ -369,7 +520,7 @@ public class IpManager extends StateMachine { mNwService.disableIpv6(mInterfaceName); mNwService.clearInterfaceAddresses(mInterfaceName); } catch (Exception e) { - Log.e(TAG, "Failed to clear addresses or disable IPv6" + e); + Log.e(mTag, "Failed to clear addresses or disable IPv6" + e); } resetLinkProperties(); @@ -390,14 +541,9 @@ public class IpManager extends StateMachine { setLinkProperties(assembleLinkProperties()); break; - case DhcpStateMachine.CMD_ON_QUIT: - // CMD_ON_QUIT is really more like "EVENT_ON_QUIT". - // Shutting down DHCPv4 progresses simultaneously with - // transitioning to StoppedState, so we can receive this - // message after we've already transitioned here. - // - // TODO: Figure out if this is actually useful and if not - // expunge it. + case DhcpClient.CMD_ON_QUIT: + // Everything is already stopped. + Log.e(mTag, "Unexpected CMD_ON_QUIT (already stopped)."); break; default: @@ -407,6 +553,30 @@ public class IpManager extends StateMachine { } } + class StoppingState extends State { + @Override + public void enter() { + if (mDhcpStateMachine == null) { + // There's no DHCPv4 for which to wait; proceed to stopped. + transitionTo(mStoppedState); + } + } + + @Override + public boolean processMessage(Message msg) { + switch (msg.what) { + case DhcpClient.CMD_ON_QUIT: + mDhcpStateMachine = null; + transitionTo(mStoppedState); + break; + + default: + deferMessage(msg); + } + return HANDLED; + } + } + class StartedState extends State { @Override public void enter() { @@ -416,9 +586,9 @@ public class IpManager extends StateMachine { mNwService.enableIpv6(mInterfaceName); // TODO: Perhaps clearIPv4Address() as well. } catch (RemoteException re) { - Log.e(TAG, "Unable to change interface settings: " + re); + Log.e(mTag, "Unable to change interface settings: " + re); } catch (IllegalStateException ie) { - Log.e(TAG, "Unable to change interface settings: " + ie); + Log.e(mTag, "Unable to change interface settings: " + ie); } mIpReachabilityMonitor = new IpReachabilityMonitor( @@ -439,13 +609,15 @@ public class IpManager extends StateMachine { if (applyStaticIpConfig()) { handleIPv4Success(new DhcpResults(mStaticIpConfig)); } else { - handleIPv4Failure(); + if (VDBG) { Log.d(mTag, "onProvisioningFailure()"); } + mCallback.onProvisioningFailure(getLinkProperties()); + transitionTo(mStoppingState); } } else { // Start DHCPv4. makeDhcpStateMachine(); mDhcpStateMachine.registerForPreDhcpNotification(); - mDhcpStateMachine.sendMessage(DhcpStateMachine.CMD_START_DHCP); + mDhcpStateMachine.sendMessage(DhcpClient.CMD_START_DHCP); } } @@ -455,9 +627,8 @@ public class IpManager extends StateMachine { mIpReachabilityMonitor = null; if (mDhcpStateMachine != null) { - mDhcpStateMachine.sendMessage(DhcpStateMachine.CMD_STOP_DHCP); + mDhcpStateMachine.sendMessage(DhcpClient.CMD_STOP_DHCP); mDhcpStateMachine.doQuit(); - mDhcpStateMachine = null; } resetLinkProperties(); @@ -471,7 +642,7 @@ public class IpManager extends StateMachine { break; case CMD_START: - Log.e(TAG, "ALERT: START received in StartedState. Please fix caller."); + Log.e(mTag, "ALERT: START received in StartedState. Please fix caller."); break; case CMD_CONFIRM: @@ -489,8 +660,7 @@ public class IpManager extends StateMachine { // calls completedPreDhcpAction() after provisioning with // a static IP configuration. if (mDhcpStateMachine != null) { - mDhcpStateMachine.sendMessage( - DhcpStateMachine.CMD_PRE_DHCP_ACTION_COMPLETE); + mDhcpStateMachine.sendMessage(DhcpClient.CMD_PRE_DHCP_ACTION_COMPLETE); } break; @@ -500,57 +670,43 @@ public class IpManager extends StateMachine { break; } final ProvisioningChange delta = setLinkProperties(newLp); - - // NOTE: The only receiver of these callbacks currently - // treats all three of them identically, namely it calls - // IpManager#getLinkProperties() and makes its own determination. - switch (delta) { - case GAINED_PROVISIONING: - mCallback.onProvisioningSuccess(newLp); - break; - - case LOST_PROVISIONING: - mCallback.onProvisioningFailure(newLp); - break; - - default: - // TODO: Only notify on STILL_PROVISIONED? - mCallback.onLinkPropertiesChange(newLp); - break; + dispatchCallback(delta, newLp); + if (delta == ProvisioningChange.LOST_PROVISIONING) { + transitionTo(mStoppedState); } break; } - case DhcpStateMachine.CMD_PRE_DHCP_ACTION: + case DhcpClient.CMD_PRE_DHCP_ACTION: + if (VDBG) { Log.d(mTag, "onPreDhcpAction()"); } mCallback.onPreDhcpAction(); break; - case DhcpStateMachine.CMD_POST_DHCP_ACTION: { + case DhcpClient.CMD_POST_DHCP_ACTION: { // Note that onPostDhcpAction() is likely to be // asynchronous, and thus there is no guarantee that we // will be able to observe any of its effects here. + if (VDBG) { Log.d(mTag, "onPostDhcpAction()"); } mCallback.onPostDhcpAction(); final DhcpResults dhcpResults = (DhcpResults) msg.obj; switch (msg.arg1) { - case DhcpStateMachine.DHCP_SUCCESS: + case DhcpClient.DHCP_SUCCESS: handleIPv4Success(dhcpResults); break; - case DhcpStateMachine.DHCP_FAILURE: + case DhcpClient.DHCP_FAILURE: handleIPv4Failure(); break; default: - Log.e(TAG, "Unknown CMD_POST_DHCP_ACTION status:" + msg.arg1); + Log.e(mTag, "Unknown CMD_POST_DHCP_ACTION status:" + msg.arg1); } break; } - case DhcpStateMachine.CMD_ON_QUIT: - // CMD_ON_QUIT is really more like "EVENT_ON_QUIT". - // Regardless, we ignore it. - // - // TODO: Figure out if this is actually useful and if not - // expunge it. + case DhcpClient.CMD_ON_QUIT: + // DHCPv4 quit early for some reason. + Log.e(mTag, "Unexpected CMD_ON_QUIT."); + mDhcpStateMachine = null; break; default: @@ -565,9 +721,9 @@ public class IpManager extends StateMachine { ifcg.setInterfaceUp(); try { mNwService.setInterfaceConfig(mInterfaceName, ifcg); - if (DBG) Log.d(TAG, "Static IP configuration succeeded"); + if (DBG) Log.d(mTag, "Static IP configuration succeeded"); } catch (IllegalStateException | RemoteException e) { - Log.e(TAG, "Static IP configuration failed: ", e); + Log.e(mTag, "Static IP configuration failed: ", e); return false; } diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java index 64f60d93628b..467ecd77970e 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java @@ -22,9 +22,12 @@ import android.app.admin.DevicePolicyManager; import android.app.admin.DevicePolicyManagerInternal; import android.content.BroadcastReceiver; import android.content.ComponentName; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.net.wifi.WifiInfo; import android.os.Build.VERSION_CODES; +import android.os.Build; import android.os.Bundle; import android.os.Process; import android.os.UserHandle; @@ -995,6 +998,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { public void testApplicationRestrictionsManagingApp() throws Exception { setAsProfileOwner(admin1); + final String nonExistAppRestrictionsManagerPackage = "com.google.app.restrictions.manager2"; final String appRestrictionsManagerPackage = "com.google.app.restrictions.manager"; final int appRestrictionsManagerAppId = 20987; final int appRestrictionsManagerUid = UserHandle.getUid( @@ -1004,6 +1008,14 @@ public class DevicePolicyManagerTest extends DpmTestBase { eq(DpmMockContext.CALLER_USER_HANDLE)); mContext.binder.callingUid = appRestrictionsManagerUid; + final PackageInfo pi = new PackageInfo(); + pi.applicationInfo = new ApplicationInfo(); + pi.applicationInfo.flags = ApplicationInfo.FLAG_HAS_CODE; + doReturn(pi).when(mContext.ipackageManager).getPackageInfo( + eq(appRestrictionsManagerPackage), + anyInt(), + eq(DpmMockContext.CALLER_USER_HANDLE)); + // appRestrictionsManager package shouldn't be able to manage restrictions as the PO hasn't // delegated that permission yet. assertFalse(dpm.isCallerApplicationRestrictionsManagingPackage()); @@ -1028,6 +1040,16 @@ public class DevicePolicyManagerTest extends DpmTestBase { mContext.binder.callingUid = DpmMockContext.CALLER_UID; assertEquals(0, dpm.getApplicationRestrictions(admin1, "pkg1").size()); + // Check the API does not allow setting a non-existent package + try { + dpm.setApplicationRestrictionsManagingPackage(admin1, + nonExistAppRestrictionsManagerPackage); + fail("Non-existent app set as app restriction manager."); + } catch (IllegalArgumentException expected) { + MoreAsserts.assertContainsRegex( + "is not installed on the current user", expected.getMessage()); + } + // Let appRestrictionsManagerPackage manage app restrictions dpm.setApplicationRestrictionsManagingPackage(admin1, appRestrictionsManagerPackage); assertEquals(appRestrictionsManagerPackage, diff --git a/telecomm/java/android/telecom/Log.java b/telecomm/java/android/telecom/Log.java index 73cc4a5f6138..3f32dbe0fdbe 100644 --- a/telecomm/java/android/telecom/Log.java +++ b/telecomm/java/android/telecom/Log.java @@ -16,6 +16,8 @@ package android.telecom; +import android.os.AsyncTask; + import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.IllegalFormatException; @@ -38,8 +40,26 @@ final public class Log { public static final boolean WARN = isLoggable(android.util.Log.WARN); public static final boolean ERROR = isLoggable(android.util.Log.ERROR); + private static MessageDigest sMessageDigest; + private Log() {} + public static void initMd5Sum() { + new AsyncTask<Void, Void, Void>() { + @Override + public Void doInBackground(Void... args) { + MessageDigest md; + try { + md = MessageDigest.getInstance("SHA-1"); + } catch (NoSuchAlgorithmException e) { + md = null; + } + sMessageDigest = md; + return null; + } + }.execute(); + } + public static boolean isLoggable(int level) { return FORCE_LOGGING || android.util.Log.isLoggable(TAG, level); } @@ -137,15 +157,14 @@ final public class Log { } private static String secureHash(byte[] input) { - MessageDigest messageDigest; - try { - messageDigest = MessageDigest.getInstance("SHA-1"); - } catch (NoSuchAlgorithmException e) { - return null; + if (sMessageDigest != null) { + sMessageDigest.reset(); + sMessageDigest.update(input); + byte[] result = sMessageDigest.digest(); + return encodeHex(result); + } else { + return "Uninitialized SHA1"; } - messageDigest.update(input); - byte[] result = messageDigest.digest(); - return encodeHex(result); } private static String encodeHex(byte[] bytes) { diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java index f1cbb9ab73d3..9f478df2063e 100644 --- a/telecomm/java/android/telecom/TelecomManager.java +++ b/telecomm/java/android/telecom/TelecomManager.java @@ -498,6 +498,7 @@ public class TelecomManager { mContext = context; } mTelecomServiceOverride = telecomServiceImpl; + android.telecom.Log.initMd5Sum(); } /** diff --git a/telephony/java/com/android/ims/internal/IImsRegistrationListener.aidl b/telephony/java/com/android/ims/internal/IImsRegistrationListener.aidl index 23a69d10aa7a..69259d078baf 100644 --- a/telephony/java/com/android/ims/internal/IImsRegistrationListener.aidl +++ b/telephony/java/com/android/ims/internal/IImsRegistrationListener.aidl @@ -17,6 +17,7 @@ package com.android.ims.internal; import com.android.ims.ImsReasonInfo; + /** * A listener type for receiving notifications about the changes to * the IMS connection(registration). @@ -26,15 +27,36 @@ import com.android.ims.ImsReasonInfo; interface IImsRegistrationListener { /** * Notifies the application when the device is connected to the IMS network. + * + * @deprecated see {@link registrationConnectedWithRadioTech} */ void registrationConnected(); /** * Notifies the application when the device is trying to connect the IMS network. + * + * @deprecated see {@link registrationProgressingWithRadioTech} */ void registrationProgressing(); /** + * Notifies the application when the device is connected to the IMS network. + * + * @param imsRadioTech the radio access technology. Valid values are {@code + * RIL_RADIO_TECHNOLOGY_*} defined in {@link ServiceState}. + */ + void registrationConnectedWithRadioTech(int imsRadioTech); + + /** + * Notifies the application when the device is trying to connect the IMS network. + * + * @param imsRadioTech the radio access technology. Valid values are {@code + * RIL_RADIO_TECHNOLOGY_*} defined in {@link ServiceState}. + */ + void registrationProgressingWithRadioTech(int imsRadioTech); + + + /** * Notifies the application when the device is disconnected from the IMS network. */ void registrationDisconnected(in ImsReasonInfo imsReasonInfo); diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java index 429839f6f6f9..0d6c70b9a4c6 100644 --- a/telephony/java/com/android/internal/telephony/RILConstants.java +++ b/telephony/java/com/android/internal/telephony/RILConstants.java @@ -92,7 +92,33 @@ public interface RILConstants { int NO_SMS_TO_ACK = 48; /* ACK received when there is no SMS to ack */ int NETWORK_ERR = 49; /* Received error from network */ int REQUEST_RATE_LIMITED = 50; /* Operation denied due to overly-frequent requests */ - + // Below is list of OEM specific error codes which can by used by OEMs in case they don't want to + // reveal particular replacement for Generic failure + int OEM_ERROR_1 = 501; + int OEM_ERROR_2 = 502; + int OEM_ERROR_3 = 503; + int OEM_ERROR_4 = 504; + int OEM_ERROR_5 = 505; + int OEM_ERROR_6 = 506; + int OEM_ERROR_7 = 507; + int OEM_ERROR_8 = 508; + int OEM_ERROR_9 = 509; + int OEM_ERROR_10 = 510; + int OEM_ERROR_11 = 511; + int OEM_ERROR_12 = 512; + int OEM_ERROR_13 = 513; + int OEM_ERROR_14 = 514; + int OEM_ERROR_15 = 515; + int OEM_ERROR_16 = 516; + int OEM_ERROR_17 = 517; + int OEM_ERROR_18 = 518; + int OEM_ERROR_19 = 519; + int OEM_ERROR_20 = 520; + int OEM_ERROR_21 = 521; + int OEM_ERROR_22 = 522; + int OEM_ERROR_23 = 523; + int OEM_ERROR_24 = 524; + int OEM_ERROR_25 = 525; /* NETWORK_MODE_* See ril.h RIL_REQUEST_SET_PREFERRED_NETWORK_TYPE */ int NETWORK_MODE_WCDMA_PREF = 0; /* GSM/WCDMA (WCDMA preferred) */ diff --git a/tests/BatteryWaster/res/layout/battery_waster.xml b/tests/BatteryWaster/res/layout/battery_waster.xml index 57a5b551fe21..ea7648708e75 100644 --- a/tests/BatteryWaster/res/layout/battery_waster.xml +++ b/tests/BatteryWaster/res/layout/battery_waster.xml @@ -27,7 +27,6 @@ android:layout_marginTop="25dp" android:saveEnabled="false" android:textSize="18sp" - android:textColor="#ffffffff" android:text="@string/waste_away" /> @@ -38,7 +37,6 @@ android:layout_marginTop="25dp" android:saveEnabled="false" android:textSize="18sp" - android:textColor="#ffffffff" android:text="@string/wake_away" /> @@ -52,7 +50,6 @@ android:layout_height="wrap_content" android:layout_marginTop="25dp" android:textSize="12sp" - android:textColor="#ffffffff" /> </ScrollView> diff --git a/tools/aapt/Resource.cpp b/tools/aapt/Resource.cpp index 4d9ba6c95d9e..18a194326de7 100644 --- a/tools/aapt/Resource.cpp +++ b/tools/aapt/Resource.cpp @@ -1883,8 +1883,6 @@ status_t buildResources(Bundle* bundle, const sp<AaptAssets>& assets, sp<ApkBuil //printf("Comment of %s: %s\n", String8(e).string(), // String8(cmt).string()); syms->appendComment(String8(e), String16(cmt), srcPos); - } else { - //printf("No comment for %s\n", String8(e).string()); } syms->makeSymbolPublic(String8(e), srcPos); } else if (strcmp16(block.getElementName(&len), uses_permission16.string()) == 0) { @@ -2535,10 +2533,6 @@ static status_t writeSymbolClass( fprintf(fp, "%s/** %s\n", getIndentSpace(indent), cmt.string()); - } else if (sym.isPublic && !includePrivate) { - sym.sourcePos.warning("No comment for public symbol %s:%s/%s", - assets->getPackage().string(), className.string(), - String8(sym.name).string()); } String16 typeComment(sym.typeComment); if (typeComment.size() > 0) { @@ -2581,10 +2575,6 @@ static status_t writeSymbolClass( "%s */\n", getIndentSpace(indent), cmt.string(), getIndentSpace(indent)); - } else if (sym.isPublic && !includePrivate) { - sym.sourcePos.warning("No comment for public symbol %s:%s/%s", - assets->getPackage().string(), className.string(), - String8(sym.name).string()); } ann.printAnnotations(fp, getIndentSpace(indent)); fprintf(fp, "%spublic static final String %s=\"%s\";\n", diff --git a/tools/aapt2/Android.mk b/tools/aapt2/Android.mk index 88b6270fad60..cb82ac37a3b2 100644 --- a/tools/aapt2/Android.mk +++ b/tools/aapt2/Android.mk @@ -38,6 +38,7 @@ sources := \ io/ZipArchive.cpp \ link/AutoVersioner.cpp \ link/ManifestFixer.cpp \ + link/ProductFilter.cpp \ link/PrivateAttributeMover.cpp \ link/ReferenceLinker.cpp \ link/TableMerger.cpp \ @@ -83,6 +84,7 @@ testSources := \ link/AutoVersioner_test.cpp \ link/ManifestFixer_test.cpp \ link/PrivateAttributeMover_test.cpp \ + link/ProductFilter_test.cpp \ link/ReferenceLinker_test.cpp \ link/TableMerger_test.cpp \ link/XmlReferenceLinker_test.cpp \ diff --git a/tools/aapt2/Debug.cpp b/tools/aapt2/Debug.cpp index b4e75f9be3a9..4bea12973692 100644 --- a/tools/aapt2/Debug.cpp +++ b/tools/aapt2/Debug.cpp @@ -144,8 +144,8 @@ void Debug::printTable(ResourceTable* table) { PrintVisitor visitor; for (const auto& value : entry->values) { - std::cout << " (" << value.config << ") "; - value.value->accept(&visitor); + std::cout << " (" << value->config << ") "; + value->value->accept(&visitor); std::cout << std::endl; } } @@ -176,7 +176,7 @@ void Debug::printStyleGraph(ResourceTable* table, const ResourceName& targetStyl if (result) { ResourceEntry* entry = result.value().entry; for (const auto& value : entry->values) { - if (Style* style = valueCast<Style>(value.value.get())) { + if (Style* style = valueCast<Style>(value->value.get())) { if (style->parent && style->parent.value().name) { parents.insert(style->parent.value().name.value()); stylesToVisit.push(style->parent.value().name.value()); diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp index b37d366a7887..b100e84f8a46 100644 --- a/tools/aapt2/ResourceParser.cpp +++ b/tools/aapt2/ResourceParser.cpp @@ -19,7 +19,6 @@ #include "ResourceUtils.h" #include "ResourceValues.h" #include "ValueVisitor.h" -#include "util/Comparators.h" #include "util/ImmutableMap.h" #include "util/Util.h" #include "xml/XmlPullParser.h" @@ -71,6 +70,7 @@ static uint32_t parseFormatAttribute(const StringPiece16& str) { struct ParsedResource { ResourceName name; ConfigDescription config; + std::string product; Source source; ResourceId id; Maybe<SymbolState> symbolState; @@ -79,35 +79,6 @@ struct ParsedResource { std::list<ParsedResource> childResources; }; -bool ResourceParser::shouldStripResource(const ResourceNameRef& name, - const Maybe<std::u16string>& product) const { - if (product) { - for (const std::u16string& productToMatch : mOptions.products) { - if (product.value() == productToMatch) { - // We specified a product, and it is in the list, so don't strip. - return false; - } - } - } - - // Nothing matched, try 'default'. Default only matches if we didn't already use another - // product variant. - if (!product || product.value() == u"default") { - if (Maybe<ResourceTable::SearchResult> result = mTable->findResource(name)) { - const ResourceEntry* entry = result.value().entry; - auto iter = std::lower_bound(entry->values.begin(), entry->values.end(), mConfig, - cmp::lessThanConfig); - if (iter != entry->values.end() && iter->config == mConfig && !iter->value->isWeak()) { - // We have a value for this config already, and it is not weak, - // so filter out this default. - return true; - } - } - return false; - } - return true; -} - // Recursively adds resources to the ResourceTable. static bool addResourcesToTable(ResourceTable* table, IDiagnostics* diag, ParsedResource* res) { if (res->symbolState) { @@ -125,7 +96,8 @@ static bool addResourcesToTable(ResourceTable* table, IDiagnostics* diag, Parsed res->value->setComment(std::move(res->comment)); res->value->setSource(std::move(res->source)); - if (!table->addResource(res->name, res->id, res->config, std::move(res->value), diag)) { + if (!table->addResource(res->name, res->id, res->config, res->product, + std::move(res->value), diag)) { return false; } } @@ -295,9 +267,8 @@ bool ResourceParser::parseResources(xml::XmlPullParser* parser) { parsedResource.comment = std::move(comment); // Extract the product name if it exists. - Maybe<std::u16string> product; if (Maybe<StringPiece16> maybeProduct = xml::findNonEmptyAttribute(parser, u"product")) { - product = maybeProduct.value().toString(); + parsedResource.product = util::utf16ToUtf8(maybeProduct.value()); } // Parse the resource regardless of product. @@ -306,12 +277,7 @@ bool ResourceParser::parseResources(xml::XmlPullParser* parser) { continue; } - // We successfully parsed the resource. Check if we should include it or strip it. - if (shouldStripResource(parsedResource.name, product)) { - // Record that we stripped out this resource name. - // We will check that at least one variant of this resource was included. - strippedResources.insert(parsedResource.name); - } else if (!addResourcesToTable(mTable, mDiag, &parsedResource)) { + if (!addResourcesToTable(mTable, mDiag, &parsedResource)) { error = true; } } @@ -524,7 +490,7 @@ std::unique_ptr<Item> ResourceParser::parseXml(xml::XmlPullParser* parser, const // name.package can be empty here, as it will assume the package name of the table. std::unique_ptr<Id> id = util::make_unique<Id>(); id->setSource(mSource.withLine(beginXmlLine)); - mTable->addResource(name, {}, std::move(id), mDiag); + mTable->addResource(name, {}, {}, std::move(id), mDiag); }; // Process the raw value. diff --git a/tools/aapt2/ResourceParser.h b/tools/aapt2/ResourceParser.h index 51cbbe19384e..ee5b33788312 100644 --- a/tools/aapt2/ResourceParser.h +++ b/tools/aapt2/ResourceParser.h @@ -34,13 +34,6 @@ struct ParsedResource; struct ResourceParserOptions { /** - * Optional product names by which to filter resources. - * This is like a preprocessor definition in that we strip out resources - * that don't match before we compile them. - */ - std::vector<std::u16string> products; - - /** * Whether the default setting for this parser is to allow translation. */ bool translatable = true; @@ -106,9 +99,6 @@ private: bool parseArrayImpl(xml::XmlPullParser* parser, ParsedResource* outResource, uint32_t typeMask); bool parsePlural(xml::XmlPullParser* parser, ParsedResource* outResource); - bool shouldStripResource(const ResourceNameRef& name, - const Maybe<std::u16string>& product) const; - IDiagnostics* mDiag; ResourceTable* mTable; Source mSource; diff --git a/tools/aapt2/ResourceParser_test.cpp b/tools/aapt2/ResourceParser_test.cpp index cf0fcd11b903..3450de9078bb 100644 --- a/tools/aapt2/ResourceParser_test.cpp +++ b/tools/aapt2/ResourceParser_test.cpp @@ -48,24 +48,13 @@ struct ResourceParserTest : public ::testing::Test { } ::testing::AssertionResult testParse(const StringPiece& str) { - return testParse(str, ConfigDescription{}, {}); + return testParse(str, ConfigDescription{}); } ::testing::AssertionResult testParse(const StringPiece& str, const ConfigDescription& config) { - return testParse(str, config, {}); - } - - ::testing::AssertionResult testParse(const StringPiece& str, - std::initializer_list<std::u16string> products) { - return testParse(str, {}, std::move(products)); - } - - ::testing::AssertionResult testParse(const StringPiece& str, const ConfigDescription& config, - std::initializer_list<std::u16string> products) { std::stringstream input(kXmlPreamble); input << "<resources>\n" << str << "\n</resources>" << std::endl; ResourceParserOptions parserOptions; - parserOptions.products = products; ResourceParser parser(mContext->getDiagnostics(), &mTable, Source{ "test" }, config, parserOptions); xml::XmlPullParser xmlParser(input); @@ -546,7 +535,7 @@ TEST_F(ResourceParserTest, ParsePublicIdAsDefinition) { ASSERT_NE(nullptr, id); } -TEST_F(ResourceParserTest, FilterProductsThatDontMatch) { +TEST_F(ResourceParserTest, KeepAllProducts) { std::string input = R"EOF( <string name="foo" product="phone">hi</string> <string name="foo" product="no-sdcard">ho</string> @@ -555,33 +544,26 @@ TEST_F(ResourceParserTest, FilterProductsThatDontMatch) { <string name="bit" product="phablet">hoot</string> <string name="bot" product="default">yes</string> )EOF"; - ASSERT_TRUE(testParse(input, { std::u16string(u"no-sdcard"), std::u16string(u"phablet") })); - - String* fooStr = test::getValue<String>(&mTable, u"@string/foo"); - ASSERT_NE(nullptr, fooStr); - EXPECT_EQ(StringPiece16(u"ho"), *fooStr->value); - - EXPECT_NE(nullptr, test::getValue<String>(&mTable, u"@string/bar")); - EXPECT_NE(nullptr, test::getValue<String>(&mTable, u"@string/baz")); - EXPECT_NE(nullptr, test::getValue<String>(&mTable, u"@string/bit")); - EXPECT_NE(nullptr, test::getValue<String>(&mTable, u"@string/bot")); -} - -TEST_F(ResourceParserTest, FilterProductsThatBothMatchInOrder) { - std::string input = R"EOF( - <string name="foo" product="phone">phone</string> - <string name="foo" product="default">default</string> - )EOF"; - ASSERT_TRUE(testParse(input, { std::u16string(u"phone") })); - - String* foo = test::getValue<String>(&mTable, u"@string/foo"); - ASSERT_NE(nullptr, foo); - EXPECT_EQ(std::u16string(u"phone"), *foo->value); -} + ASSERT_TRUE(testParse(input)); -TEST_F(ResourceParserTest, FailWhenProductFilterStripsOutAllVersionsOfResource) { - std::string input = "<string name=\"foo\" product=\"tablet\">hello</string>\n"; - ASSERT_FALSE(testParse(input, { std::u16string(u"phone") })); + EXPECT_NE(nullptr, test::getValueForConfigAndProduct<String>(&mTable, u"@string/foo", + ConfigDescription::defaultConfig(), + "phone")); + EXPECT_NE(nullptr, test::getValueForConfigAndProduct<String>(&mTable, u"@string/foo", + ConfigDescription::defaultConfig(), + "no-sdcard")); + EXPECT_NE(nullptr, test::getValueForConfigAndProduct<String>(&mTable, u"@string/bar", + ConfigDescription::defaultConfig(), + "")); + EXPECT_NE(nullptr, test::getValueForConfigAndProduct<String>(&mTable, u"@string/baz", + ConfigDescription::defaultConfig(), + "")); + EXPECT_NE(nullptr, test::getValueForConfigAndProduct<String>(&mTable, u"@string/bit", + ConfigDescription::defaultConfig(), + "phablet")); + EXPECT_NE(nullptr, test::getValueForConfigAndProduct<String>(&mTable, u"@string/bot", + ConfigDescription::defaultConfig(), + "default")); } TEST_F(ResourceParserTest, AutoIncrementIdsInPublicGroup) { diff --git a/tools/aapt2/ResourceTable.cpp b/tools/aapt2/ResourceTable.cpp index 8a3d047f6e8d..3e73be4dbc54 100644 --- a/tools/aapt2/ResourceTable.cpp +++ b/tools/aapt2/ResourceTable.cpp @@ -19,8 +19,6 @@ #include "ResourceTable.h" #include "ResourceValues.h" #include "ValueVisitor.h" - -#include "util/Comparators.h" #include "util/Util.h" #include <algorithm> @@ -124,6 +122,73 @@ ResourceEntry* ResourceTableType::findOrCreateEntry(const StringPiece16& name) { return entries.emplace(iter, new ResourceEntry(name))->get(); } +ResourceConfigValue* ResourceEntry::findValue(const ConfigDescription& config) { + return findValue(config, StringPiece()); +} + +struct ConfigKey { + const ConfigDescription* config; + const StringPiece& product; +}; + +bool ltConfigKeyRef(const std::unique_ptr<ResourceConfigValue>& lhs, const ConfigKey& rhs) { + int cmp = lhs->config.compare(*rhs.config); + if (cmp == 0) { + cmp = StringPiece(lhs->product).compare(rhs.product); + } + return cmp < 0; +} + +ResourceConfigValue* ResourceEntry::findValue(const ConfigDescription& config, + const StringPiece& product) { + auto iter = std::lower_bound(values.begin(), values.end(), + ConfigKey{ &config, product }, ltConfigKeyRef); + if (iter != values.end()) { + ResourceConfigValue* value = iter->get(); + if (value->config == config && StringPiece(value->product) == product) { + return value; + } + } + return nullptr; +} + +ResourceConfigValue* ResourceEntry::findOrCreateValue(const ConfigDescription& config, + const StringPiece& product) { + auto iter = std::lower_bound(values.begin(), values.end(), + ConfigKey{ &config, product }, ltConfigKeyRef); + if (iter != values.end()) { + ResourceConfigValue* value = iter->get(); + if (value->config == config && StringPiece(value->product) == product) { + return value; + } + } + ResourceConfigValue* newValue = values.insert( + iter, util::make_unique<ResourceConfigValue>(config, product))->get(); + return newValue; +} + +std::vector<ResourceConfigValue*> ResourceEntry::findAllValues(const ConfigDescription& config) { + std::vector<ResourceConfigValue*> results; + + auto iter = values.begin(); + for (; iter != values.end(); ++iter) { + ResourceConfigValue* value = iter->get(); + if (value->config == config) { + results.push_back(value); + ++iter; + break; + } + } + + for (; iter != values.end(); ++iter) { + ResourceConfigValue* value = iter->get(); + if (value->config == config) { + results.push_back(value); + } + } + return results; +} + /** * The default handler for collisions. A return value of -1 means keep the * existing value, 0 means fail, and +1 means take the incoming value. @@ -188,56 +253,69 @@ int ResourceTable::resolveValueCollision(Value* existing, Value* incoming) { static constexpr const char16_t* kValidNameChars = u"._-"; static constexpr const char16_t* kValidNameMangledChars = u"._-$"; -bool ResourceTable::addResource(const ResourceNameRef& name, const ConfigDescription& config, - std::unique_ptr<Value> value, IDiagnostics* diag) { - return addResourceImpl(name, {}, config, std::move(value), kValidNameChars, +bool ResourceTable::addResource(const ResourceNameRef& name, + const ConfigDescription& config, + const StringPiece& product, + std::unique_ptr<Value> value, + IDiagnostics* diag) { + return addResourceImpl(name, {}, config, product, std::move(value), kValidNameChars, resolveValueCollision, diag); } -bool ResourceTable::addResource(const ResourceNameRef& name, const ResourceId resId, - const ConfigDescription& config, std::unique_ptr<Value> value, +bool ResourceTable::addResource(const ResourceNameRef& name, + const ResourceId resId, + const ConfigDescription& config, + const StringPiece& product, + std::unique_ptr<Value> value, IDiagnostics* diag) { - return addResourceImpl(name, resId, config, std::move(value), kValidNameChars, + return addResourceImpl(name, resId, config, product, std::move(value), kValidNameChars, resolveValueCollision, diag); } -bool ResourceTable::addFileReference(const ResourceNameRef& name, const ConfigDescription& config, - const Source& source, const StringPiece16& path, +bool ResourceTable::addFileReference(const ResourceNameRef& name, + const ConfigDescription& config, + const Source& source, + const StringPiece16& path, IDiagnostics* diag) { return addFileReference(name, config, source, path, resolveValueCollision, diag); } -bool ResourceTable::addFileReference(const ResourceNameRef& name, const ConfigDescription& config, - const Source& source, const StringPiece16& path, +bool ResourceTable::addFileReference(const ResourceNameRef& name, + const ConfigDescription& config, + const Source& source, + const StringPiece16& path, std::function<int(Value*,Value*)> conflictResolver, IDiagnostics* diag) { std::unique_ptr<FileReference> fileRef = util::make_unique<FileReference>( stringPool.makeRef(path)); fileRef->setSource(source); - return addResourceImpl(name, ResourceId{}, config, std::move(fileRef), kValidNameChars, - conflictResolver, diag); + return addResourceImpl(name, ResourceId{}, config, StringPiece{}, std::move(fileRef), + kValidNameChars, conflictResolver, diag); } bool ResourceTable::addResourceAllowMangled(const ResourceNameRef& name, const ConfigDescription& config, + const StringPiece& product, std::unique_ptr<Value> value, IDiagnostics* diag) { - return addResourceImpl(name, ResourceId{}, config, std::move(value), kValidNameMangledChars, - resolveValueCollision, diag); + return addResourceImpl(name, ResourceId{}, config, product, std::move(value), + kValidNameMangledChars, resolveValueCollision, diag); } bool ResourceTable::addResourceAllowMangled(const ResourceNameRef& name, const ResourceId id, const ConfigDescription& config, + const StringPiece& product, std::unique_ptr<Value> value, IDiagnostics* diag) { - return addResourceImpl(name, id, config, std::move(value), kValidNameMangledChars, + return addResourceImpl(name, id, config, product, std::move(value), kValidNameMangledChars, resolveValueCollision, diag); } bool ResourceTable::addResourceImpl(const ResourceNameRef& name, const ResourceId resId, const ConfigDescription& config, + const StringPiece& product, std::unique_ptr<Value> value, const char16_t* validChars, std::function<int(Value*,Value*)> conflictResolver, @@ -298,21 +376,21 @@ bool ResourceTable::addResourceImpl(const ResourceNameRef& name, return false; } - const auto endIter = entry->values.end(); - auto iter = std::lower_bound(entry->values.begin(), endIter, config, cmp::lessThanConfig); - if (iter == endIter || iter->config != config) { - // This resource did not exist before, add it. - entry->values.insert(iter, ResourceConfigValue{ config, std::move(value) }); + ResourceConfigValue* configValue = entry->findOrCreateValue(config, product); + if (!configValue->value) { + // Resource does not exist, add it now. + configValue->value = std::move(value); + } else { - int collisionResult = conflictResolver(iter->value.get(), value.get()); + int collisionResult = conflictResolver(configValue->value.get(), value.get()); if (collisionResult > 0) { // Take the incoming value. - iter->value = std::move(value); + configValue->value = std::move(value); } else if (collisionResult == 0) { diag->error(DiagMessage(value->getSource()) << "duplicate value for resource '" << name << "' " << "with config '" << config << "'"); - diag->error(DiagMessage(iter->value->getSource()) + diag->error(DiagMessage(configValue->value->getSource()) << "resource previously defined here"); return false; } diff --git a/tools/aapt2/ResourceTable.h b/tools/aapt2/ResourceTable.h index 6b7b07ea3a93..8ffff1f70710 100644 --- a/tools/aapt2/ResourceTable.h +++ b/tools/aapt2/ResourceTable.h @@ -24,9 +24,12 @@ #include "Source.h" #include "StringPool.h" +#include <android-base/macros.h> +#include <map> #include <memory> #include <string> #include <tuple> +#include <unordered_map> #include <vector> namespace aapt { @@ -46,19 +49,36 @@ struct Symbol { std::u16string comment; }; -/** - * Represents a value defined for a given configuration. - */ -struct ResourceConfigValue { - ConfigDescription config; +class ResourceConfigValue { +public: + /** + * The configuration for which this value is defined. + */ + const ConfigDescription config; + + /** + * The product for which this value is defined. + */ + const std::string product; + + /** + * The actual Value. + */ std::unique_ptr<Value> value; + + ResourceConfigValue(const ConfigDescription& config, const StringPiece& product) : + config(config), product(product.toString()) { } + +private: + DISALLOW_COPY_AND_ASSIGN(ResourceConfigValue); }; /** * Represents a resource entry, which may have * varying values for each defined configuration. */ -struct ResourceEntry { +class ResourceEntry { +public: /** * The name of the resource. Immutable, as * this determines the order of this resource @@ -72,24 +92,33 @@ struct ResourceEntry { Maybe<uint16_t> id; /** - * Whether this resource is public (and must maintain the same - * entry ID across builds). + * Whether this resource is public (and must maintain the same entry ID across builds). */ Symbol symbolStatus; /** * The resource's values for each configuration. */ - std::vector<ResourceConfigValue> values; + std::vector<std::unique_ptr<ResourceConfigValue>> values; ResourceEntry(const StringPiece16& name) : name(name.toString()) { } + + ResourceConfigValue* findValue(const ConfigDescription& config); + ResourceConfigValue* findValue(const ConfigDescription& config, const StringPiece& product); + ResourceConfigValue* findOrCreateValue(const ConfigDescription& config, + const StringPiece& product); + std::vector<ResourceConfigValue*> findAllValues(const ConfigDescription& config); + +private: + DISALLOW_COPY_AND_ASSIGN(ResourceEntry); }; /** * Represents a resource type, which holds entries defined * for this type. */ -struct ResourceTableType { +class ResourceTableType { +public: /** * The logical type of resource (string, drawable, layout, etc.). */ @@ -114,8 +143,10 @@ struct ResourceTableType { explicit ResourceTableType(const ResourceType type) : type(type) { } ResourceEntry* findEntry(const StringPiece16& name); - ResourceEntry* findOrCreateEntry(const StringPiece16& name); + +private: + DISALLOW_COPY_AND_ASSIGN(ResourceTableType); }; enum class PackageType { @@ -125,16 +156,20 @@ enum class PackageType { Dynamic }; -struct ResourceTablePackage { +class ResourceTablePackage { +public: PackageType type = PackageType::App; Maybe<uint8_t> id; std::u16string name; std::vector<std::unique_ptr<ResourceTableType>> types; + ResourceTablePackage() = default; ResourceTableType* findType(ResourceType type); - ResourceTableType* findOrCreateType(const ResourceType type); + +private: + DISALLOW_COPY_AND_ASSIGN(ResourceTablePackage); }; /** @@ -144,8 +179,6 @@ struct ResourceTablePackage { class ResourceTable { public: ResourceTable() = default; - ResourceTable(const ResourceTable&) = delete; - ResourceTable& operator=(const ResourceTable&) = delete; /** * When a collision of resources occurs, this method decides which value to keep. @@ -155,38 +188,59 @@ public: */ static int resolveValueCollision(Value* existing, Value* incoming); - bool addResource(const ResourceNameRef& name, const ConfigDescription& config, - std::unique_ptr<Value> value, IDiagnostics* diag); + bool addResource(const ResourceNameRef& name, + const ConfigDescription& config, + const StringPiece& product, + std::unique_ptr<Value> value, + IDiagnostics* diag); - bool addResource(const ResourceNameRef& name, const ResourceId resId, - const ConfigDescription& config, std::unique_ptr<Value> value, + bool addResource(const ResourceNameRef& name, + const ResourceId resId, + const ConfigDescription& config, + const StringPiece& product, + std::unique_ptr<Value> value, IDiagnostics* diag); - bool addFileReference(const ResourceNameRef& name, const ConfigDescription& config, - const Source& source, const StringPiece16& path, + bool addFileReference(const ResourceNameRef& name, + const ConfigDescription& config, + const Source& source, + const StringPiece16& path, IDiagnostics* diag); - bool addFileReference(const ResourceNameRef& name, const ConfigDescription& config, - const Source& source, const StringPiece16& path, - std::function<int(Value*,Value*)> conflictResolver, IDiagnostics* diag); + bool addFileReference(const ResourceNameRef& name, + const ConfigDescription& config, + const Source& source, + const StringPiece16& path, + std::function<int(Value*,Value*)> conflictResolver, + IDiagnostics* diag); /** * Same as addResource, but doesn't verify the validity of the name. This is used * when loading resources from an existing binary resource table that may have mangled * names. */ - bool addResourceAllowMangled(const ResourceNameRef& name, const ConfigDescription& config, - std::unique_ptr<Value> value, IDiagnostics* diag); + bool addResourceAllowMangled(const ResourceNameRef& name, + const ConfigDescription& config, + const StringPiece& product, + std::unique_ptr<Value> value, + IDiagnostics* diag); - bool addResourceAllowMangled(const ResourceNameRef& name, const ResourceId id, - const ConfigDescription& config, std::unique_ptr<Value> value, + bool addResourceAllowMangled(const ResourceNameRef& name, + const ResourceId id, + const ConfigDescription& config, + const StringPiece& product, + std::unique_ptr<Value> value, IDiagnostics* diag); - bool setSymbolState(const ResourceNameRef& name, const ResourceId resId, - const Symbol& symbol, IDiagnostics* diag); + bool setSymbolState(const ResourceNameRef& name, + const ResourceId resId, + const Symbol& symbol, + IDiagnostics* diag); - bool setSymbolStateAllowMangled(const ResourceNameRef& name, const ResourceId resId, - const Symbol& symbol, IDiagnostics* diag); + bool setSymbolStateAllowMangled(const ResourceNameRef& name, + const ResourceId resId, + const Symbol& symbol, + IDiagnostics* diag); struct SearchResult { ResourceTablePackage* package; @@ -229,13 +283,19 @@ private: bool addResourceImpl(const ResourceNameRef& name, ResourceId resId, const ConfigDescription& config, + const StringPiece& product, std::unique_ptr<Value> value, const char16_t* validChars, std::function<int(Value*,Value*)> conflictResolver, IDiagnostics* diag); - bool setSymbolStateImpl(const ResourceNameRef& name, ResourceId resId, - const Symbol& symbol, const char16_t* validChars, IDiagnostics* diag); + bool setSymbolStateImpl(const ResourceNameRef& name, + ResourceId resId, + const Symbol& symbol, + const char16_t* validChars, + IDiagnostics* diag); + + DISALLOW_COPY_AND_ASSIGN(ResourceTable); }; } // namespace aapt diff --git a/tools/aapt2/ResourceTable_test.cpp b/tools/aapt2/ResourceTable_test.cpp index 42508fe154b8..180bd11275df 100644 --- a/tools/aapt2/ResourceTable_test.cpp +++ b/tools/aapt2/ResourceTable_test.cpp @@ -43,13 +43,13 @@ TEST_F(ResourceTableTest, FailToAddResourceWithBadName) { EXPECT_FALSE(table.addResource( ResourceNameRef(u"android", ResourceType::kId, u"hey,there"), - ConfigDescription{}, + ConfigDescription{}, "", test::ValueBuilder<Id>().setSource("test.xml", 21u).build(), &mDiagnostics)); EXPECT_FALSE(table.addResource( ResourceNameRef(u"android", ResourceType::kId, u"hey:there"), - ConfigDescription{}, + ConfigDescription{}, "", test::ValueBuilder<Id>().setSource("test.xml", 21u).build(), &mDiagnostics)); } @@ -59,6 +59,7 @@ TEST_F(ResourceTableTest, AddOneResource) { EXPECT_TRUE(table.addResource(test::parseNameOrDie(u"@android:attr/id"), ConfigDescription{}, + "", test::ValueBuilder<Id>() .setSource("test/path/file.xml", 23u).build(), &mDiagnostics)); @@ -76,24 +77,28 @@ TEST_F(ResourceTableTest, AddMultipleResources) { EXPECT_TRUE(table.addResource( test::parseNameOrDie(u"@android:attr/layout_width"), config, + "", test::ValueBuilder<Id>().setSource("test/path/file.xml", 10u).build(), &mDiagnostics)); EXPECT_TRUE(table.addResource( test::parseNameOrDie(u"@android:attr/id"), config, + "", test::ValueBuilder<Id>().setSource("test/path/file.xml", 12u).build(), &mDiagnostics)); EXPECT_TRUE(table.addResource( test::parseNameOrDie(u"@android:string/ok"), config, + "", test::ValueBuilder<Id>().setSource("test/path/file.xml", 14u).build(), &mDiagnostics)); EXPECT_TRUE(table.addResource( test::parseNameOrDie(u"@android:string/ok"), languageConfig, + "", test::ValueBuilder<BinaryPrimitive>(android::Res_value{}) .setSource("test/path/file.xml", 20u) .build(), @@ -110,18 +115,49 @@ TEST_F(ResourceTableTest, OverrideWeakResourceValue) { ResourceTable table; ASSERT_TRUE(table.addResource(test::parseNameOrDie(u"@android:attr/foo"), ConfigDescription{}, - util::make_unique<Attribute>(true), &mDiagnostics)); + "", util::make_unique<Attribute>(true), &mDiagnostics)); Attribute* attr = test::getValue<Attribute>(&table, u"@android:attr/foo"); ASSERT_NE(nullptr, attr); EXPECT_TRUE(attr->isWeak()); ASSERT_TRUE(table.addResource(test::parseNameOrDie(u"@android:attr/foo"), ConfigDescription{}, - util::make_unique<Attribute>(false), &mDiagnostics)); + "", util::make_unique<Attribute>(false), &mDiagnostics)); attr = test::getValue<Attribute>(&table, u"@android:attr/foo"); ASSERT_NE(nullptr, attr); EXPECT_FALSE(attr->isWeak()); } +TEST_F(ResourceTableTest, ProductVaryingValues) { + ResourceTable table; + + EXPECT_TRUE(table.addResource(test::parseNameOrDie(u"@android:string/foo"), + test::parseConfigOrDie("land"), + "tablet", + util::make_unique<Id>(), + &mDiagnostics)); + EXPECT_TRUE(table.addResource(test::parseNameOrDie(u"@android:string/foo"), + test::parseConfigOrDie("land"), + "phone", + util::make_unique<Id>(), + &mDiagnostics)); + + EXPECT_NE(nullptr, test::getValueForConfigAndProduct<Id>(&table, u"@android:string/foo", + test::parseConfigOrDie("land"), + "tablet")); + EXPECT_NE(nullptr, test::getValueForConfigAndProduct<Id>(&table, u"@android:string/foo", + test::parseConfigOrDie("land"), + "phone")); + + Maybe<ResourceTable::SearchResult> sr = table.findResource( + test::parseNameOrDie(u"@android:string/foo")); + AAPT_ASSERT_TRUE(sr); + std::vector<ResourceConfigValue*> values = sr.value().entry->findAllValues( + test::parseConfigOrDie("land")); + ASSERT_EQ(2u, values.size()); + EXPECT_EQ(std::string("phone"), values[0]->product); + EXPECT_EQ(std::string("tablet"), values[1]->product); +} + } // namespace aapt diff --git a/tools/aapt2/ValueVisitor.h b/tools/aapt2/ValueVisitor.h index 549303939351..ea2aa55764c1 100644 --- a/tools/aapt2/ValueVisitor.h +++ b/tools/aapt2/ValueVisitor.h @@ -146,7 +146,7 @@ inline void visitAllValuesInPackage(ResourceTablePackage* pkg, RawValueVisitor* for (auto& type : pkg->types) { for (auto& entry : type->entries) { for (auto& configValue : entry->values) { - configValue.value->accept(visitor); + configValue->value->accept(visitor); } } } diff --git a/tools/aapt2/compile/Compile.cpp b/tools/aapt2/compile/Compile.cpp index 1eefb821768e..0dd8e1859661 100644 --- a/tools/aapt2/compile/Compile.cpp +++ b/tools/aapt2/compile/Compile.cpp @@ -107,7 +107,6 @@ static Maybe<ResourcePathData> extractResourcePathData(const std::string& path, struct CompileOptions { std::string outputPath; Maybe<std::string> resDir; - std::vector<std::u16string> products; bool pseudolocalize = false; bool legacyMode = false; bool verbose = false; @@ -198,7 +197,6 @@ static bool compileTable(IAaptContext* context, const CompileOptions& options, xml::XmlPullParser xmlParser(fin); ResourceParserOptions parserOptions; - parserOptions.products = options.products; parserOptions.errorOnPositionalArguments = !options.legacyMode; // If the filename includes donottranslate, then the default translatable is false. @@ -457,11 +455,8 @@ public: int compile(const std::vector<StringPiece>& args) { CompileOptions options; - Maybe<std::string> productList; Flags flags = Flags() .requiredFlag("-o", "Output path", &options.outputPath) - .optionalFlag("--product", "Comma separated list of product types to compile", - &productList) .optionalFlag("--dir", "Directory to scan for resources", &options.resDir) .optionalSwitch("--pseudo-localize", "Generate resources for pseudo-locales " "(en-XA and ar-XB)", &options.pseudolocalize) @@ -472,12 +467,6 @@ int compile(const std::vector<StringPiece>& args) { return 1; } - if (productList) { - for (StringPiece part : util::tokenize<char>(productList.value(), ',')) { - options.products.push_back(util::utf8ToUtf16(part)); - } - } - CompileContext context; std::unique_ptr<IArchiveWriter> archiveWriter; diff --git a/tools/aapt2/compile/PseudolocaleGenerator.cpp b/tools/aapt2/compile/PseudolocaleGenerator.cpp index 2963d135cbca..be26b528b184 100644 --- a/tools/aapt2/compile/PseudolocaleGenerator.cpp +++ b/tools/aapt2/compile/PseudolocaleGenerator.cpp @@ -19,7 +19,6 @@ #include "ValueVisitor.h" #include "compile/PseudolocaleGenerator.h" #include "compile/Pseudolocalizer.h" -#include "util/Comparators.h" namespace aapt { @@ -208,10 +207,12 @@ ConfigDescription modifyConfigForPseudoLocale(const ConfigDescription& base, return modified; } -void pseudolocalizeIfNeeded(std::vector<ResourceConfigValue>* configValues, - Pseudolocalizer::Method method, StringPool* pool, Value* value) { +void pseudolocalizeIfNeeded(const Pseudolocalizer::Method method, + ResourceConfigValue* originalValue, + StringPool* pool, + ResourceEntry* entry) { Visitor visitor(pool, method); - value->accept(&visitor); + originalValue->value->accept(&visitor); std::unique_ptr<Value> localizedValue; if (visitor.mValue) { @@ -220,16 +221,18 @@ void pseudolocalizeIfNeeded(std::vector<ResourceConfigValue>* configValues, localizedValue = std::move(visitor.mItem); } - if (localizedValue) { - ConfigDescription pseudolocalizedConfig = modifyConfigForPseudoLocale(ConfigDescription{}, - method); - auto iter = std::lower_bound(configValues->begin(), configValues->end(), - pseudolocalizedConfig, cmp::lessThanConfig); - if (iter == configValues->end() || iter->config != pseudolocalizedConfig) { - // The pseudolocalized config doesn't exist, add it. - configValues->insert(iter, ResourceConfigValue{ pseudolocalizedConfig, - std::move(localizedValue) }); - } + if (!localizedValue) { + return; + } + + ConfigDescription configWithAccent = modifyConfigForPseudoLocale( + originalValue->config, method); + + ResourceConfigValue* newConfigValue = entry->findOrCreateValue( + configWithAccent, originalValue->product); + if (!newConfigValue->value) { + // Only use auto-generated pseudo-localization if none is defined. + newConfigValue->value = std::move(localizedValue); } } @@ -239,18 +242,13 @@ bool PseudolocaleGenerator::consume(IAaptContext* context, ResourceTable* table) for (auto& package : table->packages) { for (auto& type : package->types) { for (auto& entry : type->entries) { - auto iter = std::lower_bound(entry->values.begin(), entry->values.end(), - ConfigDescription{}, cmp::lessThanConfig); - if (iter != entry->values.end() && iter->config == ConfigDescription{}) { - // Only pseudolocalize the default configuration. - - // The iterator will be invalidated, so grab a pointer to the value. - Value* originalValue = iter->value.get(); - - pseudolocalizeIfNeeded(&entry->values, Pseudolocalizer::Method::kAccent, - &table->stringPool, originalValue); - pseudolocalizeIfNeeded(&entry->values, Pseudolocalizer::Method::kBidi, - &table->stringPool, originalValue); + std::vector<ResourceConfigValue*> values = entry->findAllValues( + ConfigDescription::defaultConfig()); + for (ResourceConfigValue* value : values) { + pseudolocalizeIfNeeded(Pseudolocalizer::Method::kAccent, value, + &table->stringPool, entry.get()); + pseudolocalizeIfNeeded(Pseudolocalizer::Method::kBidi, value, + &table->stringPool, entry.get()); } } } diff --git a/tools/aapt2/flatten/TableFlattener.cpp b/tools/aapt2/flatten/TableFlattener.cpp index 71ab3dbf52ca..da81046b2e42 100644 --- a/tools/aapt2/flatten/TableFlattener.cpp +++ b/tools/aapt2/flatten/TableFlattener.cpp @@ -402,10 +402,10 @@ private: const size_t configCount = entry->values.size(); for (size_t i = 0; i < configCount; i++) { - const ConfigDescription& config = entry->values[i].config; + const ConfigDescription& config = entry->values[i]->config; for (size_t j = i + 1; j < configCount; j++) { configMasks[entry->id.value()] |= util::hostToDevice32( - config.diff(entry->values[j].config)); + config.diff(entry->values[j]->config)); } } } @@ -445,8 +445,8 @@ private: // Group values by configuration. for (auto& configValue : entry->values) { - configToEntryListMap[configValue.config].push_back(FlatEntry{ - entry, configValue.value.get(), keyIndex }); + configToEntryListMap[configValue->config].push_back(FlatEntry{ + entry, configValue->value.get(), keyIndex }); } } diff --git a/tools/aapt2/java/JavaClassGenerator.cpp b/tools/aapt2/java/JavaClassGenerator.cpp index 7280f3a968a0..6e340a2a2742 100644 --- a/tools/aapt2/java/JavaClassGenerator.cpp +++ b/tools/aapt2/java/JavaClassGenerator.cpp @@ -23,7 +23,6 @@ #include "java/AnnotationProcessor.h" #include "java/ClassDefinitionWriter.h" #include "java/JavaClassGenerator.h" -#include "util/Comparators.h" #include "util/StringPiece.h" #include <algorithm> @@ -258,12 +257,12 @@ bool JavaClassGenerator::writeEntriesForClass(ClassDefinitionWriter* outClassDef } for (const auto& configValue : entry->values) { - processor.appendComment(configValue.value->getComment()); + processor.appendComment(configValue->value->getComment()); } // If this is an Attribute, append the format Javadoc. if (!entry->values.empty()) { - if (Attribute* attr = valueCast<Attribute>(entry->values.front().value.get())) { + if (Attribute* attr = valueCast<Attribute>(entry->values.front()->value.get())) { // We list out the available values for the given attribute. addAttributeFormatDoc(&processor, attr); } @@ -272,7 +271,7 @@ bool JavaClassGenerator::writeEntriesForClass(ClassDefinitionWriter* outClassDef if (type->type == ResourceType::kStyleable) { assert(!entry->values.empty()); const Styleable* styleable = static_cast<const Styleable*>( - entry->values.front().value.get()); + entry->values.front()->value.get()); writeStyleableEntryForClass(outClassDef, &processor, packageNameToGenerate, unmangledName, styleable); } else { @@ -311,11 +310,10 @@ bool JavaClassGenerator::generate(const StringPiece16& packageNameToGenerate, if (type->type == ResourceType::kAttr) { // Also include private attributes in this same class. - auto iter = std::lower_bound(package->types.begin(), package->types.end(), - ResourceType::kAttrPrivate, cmp::lessThanType); - if (iter != package->types.end() && (*iter)->type == ResourceType::kAttrPrivate) { + ResourceTableType* privType = package->findType(ResourceType::kAttrPrivate); + if (privType) { result = writeEntriesForClass(&classDef, packageNameToGenerate, - package.get(), iter->get()); + package.get(), privType); if (!result) { return false; } diff --git a/tools/aapt2/link/AutoVersioner.cpp b/tools/aapt2/link/AutoVersioner.cpp index c7e603ea3774..459c330cfbdc 100644 --- a/tools/aapt2/link/AutoVersioner.cpp +++ b/tools/aapt2/link/AutoVersioner.cpp @@ -18,9 +18,7 @@ #include "ResourceTable.h" #include "SdkConstants.h" #include "ValueVisitor.h" - #include "link/Linkers.h" -#include "util/Comparators.h" #include <algorithm> #include <cassert> @@ -31,7 +29,12 @@ bool shouldGenerateVersionedResource(const ResourceEntry* entry, const ConfigDes const int sdkVersionToGenerate) { assert(sdkVersionToGenerate > config.sdkVersion); const auto endIter = entry->values.end(); - auto iter = std::lower_bound(entry->values.begin(), endIter, config, cmp::lessThanConfig); + auto iter = entry->values.begin(); + for (; iter != endIter; ++iter) { + if ((*iter)->config == config) { + break; + } + } // The source config came from this list, so it should be here. assert(iter != entry->values.end()); @@ -45,10 +48,10 @@ bool shouldGenerateVersionedResource(const ResourceEntry* entry, const ConfigDes // are no higher sdk level versions of this resource. ConfigDescription tempConfig(config); for (; iter != endIter; ++iter) { - tempConfig.sdkVersion = iter->config.sdkVersion; - if (tempConfig == iter->config) { + tempConfig.sdkVersion = (*iter)->config.sdkVersion; + if (tempConfig == (*iter)->config) { // The two configs are the same, check the sdk version. - return sdkVersionToGenerate < iter->config.sdkVersion; + return sdkVersionToGenerate < (*iter)->config.sdkVersion; } } @@ -65,14 +68,14 @@ bool AutoVersioner::consume(IAaptContext* context, ResourceTable* table) { for (auto& entry : type->entries) { for (size_t i = 0; i < entry->values.size(); i++) { - ResourceConfigValue& configValue = entry->values[i]; - if (configValue.config.sdkVersion >= SDK_LOLLIPOP_MR1) { + ResourceConfigValue* configValue = entry->values[i].get(); + if (configValue->config.sdkVersion >= SDK_LOLLIPOP_MR1) { // If this configuration is only used on L-MR1 then we don't need // to do anything since we use private attributes since that version. continue; } - if (Style* style = valueCast<Style>(configValue.value.get())) { + if (Style* style = valueCast<Style>(configValue->value.get())) { Maybe<size_t> minSdkStripped; std::vector<Style::Entry> stripped; @@ -82,7 +85,7 @@ bool AutoVersioner::consume(IAaptContext* context, ResourceTable* table) { // Find the SDK level that is higher than the configuration allows. const size_t sdkLevel = findAttributeSdkLevel(iter->key.id.value()); - if (sdkLevel > std::max<size_t>(configValue.config.sdkVersion, 1)) { + if (sdkLevel > std::max<size_t>(configValue->config.sdkVersion, 1)) { // Record that we are about to strip this. stripped.emplace_back(std::move(*iter)); @@ -105,10 +108,10 @@ bool AutoVersioner::consume(IAaptContext* context, ResourceTable* table) { // there is no other defined resource for the version we want to // generate. if (shouldGenerateVersionedResource(entry.get(), - configValue.config, + configValue->config, minSdkStripped.value())) { // Let's create a new Style for this versioned resource. - ConfigDescription newConfig(configValue.config); + ConfigDescription newConfig(configValue->config); newConfig.sdkVersion = minSdkStripped.value(); std::unique_ptr<Style> newStyle(style->clone(&table->stringPool)); @@ -121,14 +124,8 @@ bool AutoVersioner::consume(IAaptContext* context, ResourceTable* table) { std::make_move_iterator(stripped.end())); // Insert the new Resource into the correct place. - auto iter = std::lower_bound(entry->values.begin(), - entry->values.end(), - newConfig, - cmp::lessThanConfig); - - entry->values.insert( - iter, - ResourceConfigValue{ newConfig, std::move(newStyle) }); + entry->findOrCreateValue(newConfig, {})->value = + std::move(newStyle); } } } diff --git a/tools/aapt2/link/AutoVersioner_test.cpp b/tools/aapt2/link/AutoVersioner_test.cpp index 29bcc93518cf..9b3a87c4eed0 100644 --- a/tools/aapt2/link/AutoVersioner_test.cpp +++ b/tools/aapt2/link/AutoVersioner_test.cpp @@ -15,9 +15,7 @@ */ #include "ConfigDescription.h" - #include "link/Linkers.h" - #include "test/Builders.h" #include "test/Context.h" @@ -31,9 +29,9 @@ TEST(AutoVersionerTest, GenerateVersionedResources) { const ConfigDescription sw600dpLandConfig = test::parseConfigOrDie("sw600dp-land"); ResourceEntry entry(u"foo"); - entry.values.push_back(ResourceConfigValue{ defaultConfig }); - entry.values.push_back(ResourceConfigValue{ landConfig }); - entry.values.push_back(ResourceConfigValue{ sw600dpLandConfig }); + entry.values.push_back(util::make_unique<ResourceConfigValue>(defaultConfig, "")); + entry.values.push_back(util::make_unique<ResourceConfigValue>(landConfig, "")); + entry.values.push_back(util::make_unique<ResourceConfigValue>(sw600dpLandConfig, "")); EXPECT_TRUE(shouldGenerateVersionedResource(&entry, defaultConfig, 17)); EXPECT_TRUE(shouldGenerateVersionedResource(&entry, landConfig, 17)); @@ -45,9 +43,9 @@ TEST(AutoVersionerTest, GenerateVersionedResourceWhenHigherVersionExists) { const ConfigDescription v21Config = test::parseConfigOrDie("v21"); ResourceEntry entry(u"foo"); - entry.values.push_back(ResourceConfigValue{ defaultConfig }); - entry.values.push_back(ResourceConfigValue{ sw600dpV13Config }); - entry.values.push_back(ResourceConfigValue{ v21Config }); + entry.values.push_back(util::make_unique<ResourceConfigValue>(defaultConfig, "")); + entry.values.push_back(util::make_unique<ResourceConfigValue>(sw600dpV13Config, "")); + entry.values.push_back(util::make_unique<ResourceConfigValue>(v21Config, "")); EXPECT_TRUE(shouldGenerateVersionedResource(&entry, defaultConfig, 17)); EXPECT_FALSE(shouldGenerateVersionedResource(&entry, defaultConfig, 22)); diff --git a/tools/aapt2/link/Link.cpp b/tools/aapt2/link/Link.cpp index 8e321798787a..3437ac0d9a7f 100644 --- a/tools/aapt2/link/Link.cpp +++ b/tools/aapt2/link/Link.cpp @@ -31,6 +31,7 @@ #include "java/ManifestClassGenerator.h" #include "java/ProguardRules.h" #include "link/Linkers.h" +#include "link/ProductFilter.h" #include "link/ReferenceLinker.h" #include "link/ManifestFixer.h" #include "link/TableMerger.h" @@ -70,6 +71,7 @@ struct LinkOptions { Maybe<std::u16string> privateSymbols; ManifestFixerOptions manifestFixerOptions; IConfigFilter* configFilter = nullptr; + std::unordered_set<std::string> products; }; struct LinkContext : public IAaptContext { @@ -292,16 +294,16 @@ public: for (const auto& configValue : entry->values) { // Special case the occurrence of an ID that is being generated for the // 'android' package. This is due to legacy reasons. - if (valueCast<Id>(configValue.value.get()) && + if (valueCast<Id>(configValue->value.get()) && package->name == u"android") { mContext->getDiagnostics()->warn( - DiagMessage(configValue.value->getSource()) + DiagMessage(configValue->value->getSource()) << "generated id '" << resName << "' for external package '" << package->name << "'"); } else { mContext->getDiagnostics()->error( - DiagMessage(configValue.value->getSource()) + DiagMessage(configValue->value->getSource()) << "defined resource '" << resName << "' for external package '" << package->name << "'"); @@ -512,7 +514,10 @@ public: std::unique_ptr<Id> id = util::make_unique<Id>(); id->setSource(fileDesc->source.withLine(exportedSymbol.line)); - bool result = mFinalTable.addResourceAllowMangled(resName, {}, std::move(id), + bool result = mFinalTable.addResourceAllowMangled(resName, + ConfigDescription::defaultConfig(), + std::string(), + std::move(id), mContext->getDiagnostics()); if (!result) { return false; @@ -681,6 +686,12 @@ public: mContext->getDiagnostics()->error(DiagMessage() << "failed linking references"); return 1; } + + ProductFilter productFilter(mOptions.products); + if (!productFilter.consume(mContext, &mFinalTable)) { + mContext->getDiagnostics()->error(DiagMessage() << "failed stripping products"); + return 1; + } } proguard::KeepSet proguardKeepSet; @@ -931,6 +942,7 @@ int link(const std::vector<StringPiece>& args) { Maybe<std::string> customJavaPackage; std::vector<std::string> extraJavaPackages; Maybe<std::string> configs; + Maybe<std::string> productList; bool legacyXFlag = false; bool requireLocalization = false; Flags flags = Flags() @@ -954,6 +966,8 @@ int link(const std::vector<StringPiece>& args) { &requireLocalization) .optionalFlag("-c", "Comma separated list of configurations to include. The default\n" "is all configurations", &configs) + .optionalFlag("--product", "Comma separated list of product names to keep", + &productList) .optionalSwitch("--output-to-dir", "Outputs the APK contents to a directory specified " "by -o", &options.outputToDirectory) @@ -1039,6 +1053,14 @@ int link(const std::vector<StringPiece>& args) { } } + if (productList) { + for (StringPiece product : util::tokenize<char>(productList.value(), ',')) { + if (product != "" && product != "default") { + options.products.insert(product.toString()); + } + } + } + AxisConfigFilter filter; if (configs) { for (const StringPiece& configStr : util::tokenize<char>(configs.value(), ',')) { diff --git a/tools/aapt2/link/Linkers.h b/tools/aapt2/link/Linkers.h index 4d3a483c6b82..ec532aba465f 100644 --- a/tools/aapt2/link/Linkers.h +++ b/tools/aapt2/link/Linkers.h @@ -26,7 +26,7 @@ namespace aapt { class ResourceTable; -struct ResourceEntry; +class ResourceEntry; struct ConfigDescription; /** diff --git a/tools/aapt2/link/ProductFilter.cpp b/tools/aapt2/link/ProductFilter.cpp new file mode 100644 index 000000000000..8784e891b293 --- /dev/null +++ b/tools/aapt2/link/ProductFilter.cpp @@ -0,0 +1,118 @@ +/* + * 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. + */ + +#include "link/ProductFilter.h" + +namespace aapt { + +ProductFilter::ResourceConfigValueIter +ProductFilter::selectProductToKeep(const ResourceNameRef& name, + const ResourceConfigValueIter begin, + const ResourceConfigValueIter end, + IDiagnostics* diag) { + ResourceConfigValueIter defaultProductIter = end; + ResourceConfigValueIter selectedProductIter = end; + + for (ResourceConfigValueIter iter = begin; iter != end; ++iter) { + ResourceConfigValue* configValue = iter->get(); + if (mProducts.find(configValue->product) != mProducts.end()) { + if (selectedProductIter != end) { + // We have two possible values for this product! + diag->error(DiagMessage(configValue->value->getSource()) + << "selection of product '" << configValue->product + << "' for resource " << name << " is ambiguous"); + + ResourceConfigValue* previouslySelectedConfigValue = selectedProductIter->get(); + diag->note(DiagMessage(previouslySelectedConfigValue->value->getSource()) + << "product '" << previouslySelectedConfigValue->product + << "' is also a candidate"); + return end; + } + + // Select this product. + selectedProductIter = iter; + } + + if (configValue->product.empty() || configValue->product == "default") { + if (defaultProductIter != end) { + // We have two possible default values. + diag->error(DiagMessage(configValue->value->getSource()) + << "multiple default products defined for resource " << name); + + ResourceConfigValue* previouslyDefaultConfigValue = defaultProductIter->get(); + diag->note(DiagMessage(previouslyDefaultConfigValue->value->getSource()) + << "default product also defined here"); + return end; + } + + // Mark the default. + defaultProductIter = iter; + } + } + + if (defaultProductIter == end) { + diag->error(DiagMessage() << "no default product defined for resource " << name); + return end; + } + + if (selectedProductIter == end) { + selectedProductIter = defaultProductIter; + } + return selectedProductIter; +} + +bool ProductFilter::consume(IAaptContext* context, ResourceTable* table) { + bool error = false; + for (auto& pkg : table->packages) { + for (auto& type : pkg->types) { + for (auto& entry : type->entries) { + std::vector<std::unique_ptr<ResourceConfigValue>> newValues; + + ResourceConfigValueIter iter = entry->values.begin(); + ResourceConfigValueIter startRangeIter = iter; + while (iter != entry->values.end()) { + ++iter; + if (iter == entry->values.end() || + (*iter)->config != (*startRangeIter)->config) { + + // End of the array, or we saw a different config, + // so this must be the end of a range of products. + // Select the product to keep from the set of products defined. + ResourceNameRef name(pkg->name, type->type, entry->name); + auto valueToKeep = selectProductToKeep(name, startRangeIter, iter, + context->getDiagnostics()); + if (valueToKeep == iter) { + // An error occurred, we could not pick a product. + error = true; + } else { + // We selected a product to keep. Move it to the new array. + newValues.push_back(std::move(*valueToKeep)); + } + + // Start the next range of products. + startRangeIter = iter; + } + } + + // Now move the new values in to place. + entry->values = std::move(newValues); + } + } + } + return !error; +} + +} // namespace aapt diff --git a/tools/aapt2/link/ProductFilter.h b/tools/aapt2/link/ProductFilter.h new file mode 100644 index 000000000000..d2edd38289dc --- /dev/null +++ b/tools/aapt2/link/ProductFilter.h @@ -0,0 +1,49 @@ +/* + * 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. + */ + +#ifndef AAPT_LINK_PRODUCTFILTER_H +#define AAPT_LINK_PRODUCTFILTER_H + +#include "ResourceTable.h" +#include "process/IResourceTableConsumer.h" + +#include <android-base/macros.h> +#include <unordered_set> + +namespace aapt { + +class ProductFilter { +public: + using ResourceConfigValueIter = std::vector<std::unique_ptr<ResourceConfigValue>>::iterator; + + ProductFilter(std::unordered_set<std::string> products) : mProducts(products) { } + + ResourceConfigValueIter selectProductToKeep(const ResourceNameRef& name, + const ResourceConfigValueIter begin, + const ResourceConfigValueIter end, + IDiagnostics* diag); + + bool consume(IAaptContext* context, ResourceTable* table); + +private: + std::unordered_set<std::string> mProducts; + + DISALLOW_COPY_AND_ASSIGN(ProductFilter); +}; + +} // namespace aapt + +#endif /* AAPT_LINK_PRODUCTFILTER_H */ diff --git a/tools/aapt2/link/ProductFilter_test.cpp b/tools/aapt2/link/ProductFilter_test.cpp new file mode 100644 index 000000000000..f4f756ae4519 --- /dev/null +++ b/tools/aapt2/link/ProductFilter_test.cpp @@ -0,0 +1,136 @@ +/* + * 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. + */ + +#include "link/ProductFilter.h" +#include "test/Builders.h" +#include "test/Context.h" + +#include <gtest/gtest.h> + +namespace aapt { + +TEST(ProductFilterTest, SelectTwoProducts) { + std::unique_ptr<IAaptContext> context = test::ContextBuilder().build(); + + const ConfigDescription land = test::parseConfigOrDie("land"); + const ConfigDescription port = test::parseConfigOrDie("port"); + + ResourceTable table; + ASSERT_TRUE(table.addResource(test::parseNameOrDie(u"@android:string/one"), + land, "", + test::ValueBuilder<Id>() + .setSource(Source("land/default.xml")).build(), + context->getDiagnostics())); + ASSERT_TRUE(table.addResource(test::parseNameOrDie(u"@android:string/one"), + land, "tablet", + test::ValueBuilder<Id>() + .setSource(Source("land/tablet.xml")).build(), + context->getDiagnostics())); + + ASSERT_TRUE(table.addResource(test::parseNameOrDie(u"@android:string/one"), + port, "", + test::ValueBuilder<Id>() + .setSource(Source("port/default.xml")).build(), + context->getDiagnostics())); + ASSERT_TRUE(table.addResource(test::parseNameOrDie(u"@android:string/one"), + port, "tablet", + test::ValueBuilder<Id>() + .setSource(Source("port/tablet.xml")).build(), + context->getDiagnostics())); + + ProductFilter filter({ "tablet" }); + ASSERT_TRUE(filter.consume(context.get(), &table)); + + EXPECT_EQ(nullptr, test::getValueForConfigAndProduct<Id>(&table, u"@android:string/one", + land, "")); + EXPECT_NE(nullptr, test::getValueForConfigAndProduct<Id>(&table, u"@android:string/one", + land, "tablet")); + EXPECT_EQ(nullptr, test::getValueForConfigAndProduct<Id>(&table, u"@android:string/one", + port, "")); + EXPECT_NE(nullptr, test::getValueForConfigAndProduct<Id>(&table, u"@android:string/one", + port, "tablet")); +} + +TEST(ProductFilterTest, SelectDefaultProduct) { + std::unique_ptr<IAaptContext> context = test::ContextBuilder().build(); + + ResourceTable table; + ASSERT_TRUE(table.addResource(test::parseNameOrDie(u"@android:string/one"), + ConfigDescription::defaultConfig(), "", + test::ValueBuilder<Id>() + .setSource(Source("default.xml")).build(), + context->getDiagnostics())); + ASSERT_TRUE(table.addResource(test::parseNameOrDie(u"@android:string/one"), + ConfigDescription::defaultConfig(), "tablet", + test::ValueBuilder<Id>() + .setSource(Source("tablet.xml")).build(), + context->getDiagnostics())); + + ProductFilter filter({}); + ASSERT_TRUE(filter.consume(context.get(), &table)); + + EXPECT_NE(nullptr, test::getValueForConfigAndProduct<Id>(&table, u"@android:string/one", + ConfigDescription::defaultConfig(), + "")); + EXPECT_EQ(nullptr, test::getValueForConfigAndProduct<Id>(&table, u"@android:string/one", + ConfigDescription::defaultConfig(), + "tablet")); +} + +TEST(ProductFilterTest, FailOnAmbiguousProduct) { + std::unique_ptr<IAaptContext> context = test::ContextBuilder().build(); + + ResourceTable table; + ASSERT_TRUE(table.addResource(test::parseNameOrDie(u"@android:string/one"), + ConfigDescription::defaultConfig(), "", + test::ValueBuilder<Id>() + .setSource(Source("default.xml")).build(), + context->getDiagnostics())); + ASSERT_TRUE(table.addResource(test::parseNameOrDie(u"@android:string/one"), + ConfigDescription::defaultConfig(), "tablet", + test::ValueBuilder<Id>() + .setSource(Source("tablet.xml")).build(), + context->getDiagnostics())); + ASSERT_TRUE(table.addResource(test::parseNameOrDie(u"@android:string/one"), + ConfigDescription::defaultConfig(), "no-sdcard", + test::ValueBuilder<Id>() + .setSource(Source("no-sdcard.xml")).build(), + context->getDiagnostics())); + + ProductFilter filter({ "tablet", "no-sdcard" }); + ASSERT_FALSE(filter.consume(context.get(), &table)); +} + +TEST(ProductFilterTest, FailOnMultipleDefaults) { + std::unique_ptr<IAaptContext> context = test::ContextBuilder().build(); + + ResourceTable table; + ASSERT_TRUE(table.addResource(test::parseNameOrDie(u"@android:string/one"), + ConfigDescription::defaultConfig(), "", + test::ValueBuilder<Id>() + .setSource(Source(".xml")).build(), + context->getDiagnostics())); + ASSERT_TRUE(table.addResource(test::parseNameOrDie(u"@android:string/one"), + ConfigDescription::defaultConfig(), "default", + test::ValueBuilder<Id>() + .setSource(Source("default.xml")).build(), + context->getDiagnostics())); + + ProductFilter filter({}); + ASSERT_FALSE(filter.consume(context.get(), &table)); +} + +} // namespace aapt diff --git a/tools/aapt2/link/ReferenceLinker.cpp b/tools/aapt2/link/ReferenceLinker.cpp index 27435398c408..ef3fe4f58d41 100644 --- a/tools/aapt2/link/ReferenceLinker.cpp +++ b/tools/aapt2/link/ReferenceLinker.cpp @@ -316,7 +316,7 @@ bool ReferenceLinker::consume(IAaptContext* context, ResourceTable* table) { &table->stringPool, &declStack, &callSite); for (auto& configValue : entry->values) { - configValue.value->accept(&visitor); + configValue->value->accept(&visitor); } if (visitor.hasError()) { diff --git a/tools/aapt2/link/TableMerger.cpp b/tools/aapt2/link/TableMerger.cpp index e01a00401133..2ecd5b018691 100644 --- a/tools/aapt2/link/TableMerger.cpp +++ b/tools/aapt2/link/TableMerger.cpp @@ -18,9 +18,7 @@ #include "ResourceUtils.h" #include "ResourceValues.h" #include "ValueVisitor.h" - #include "link/TableMerger.h" -#include "util/Comparators.h" #include "util/Util.h" #include <cassert> @@ -197,28 +195,28 @@ bool TableMerger::doMerge(const Source& src, ResourceNameRef resName(mMasterPackage->name, dstType->type, dstEntry->name); - for (ResourceConfigValue& srcValue : srcEntry->values) { - auto iter = std::lower_bound(dstEntry->values.begin(), dstEntry->values.end(), - srcValue.config, cmp::lessThanConfig); + for (auto& srcValue : srcEntry->values) { + ResourceConfigValue* dstValue = dstEntry->findValue(srcValue->config, + srcValue->product); const bool stripConfig = mOptions.filter ? - !mOptions.filter->match(srcValue.config) : false; + !mOptions.filter->match(srcValue->config) : false; - if (iter != dstEntry->values.end() && iter->config == srcValue.config) { + if (dstValue) { const int collisionResult = ResourceTable::resolveValueCollision( - iter->value.get(), srcValue.value.get()); + dstValue->value.get(), srcValue->value.get()); if (collisionResult == 0 && !overlay) { // Error! ResourceNameRef resourceName(srcPackage->name, srcType->type, srcEntry->name); - mContext->getDiagnostics()->error(DiagMessage(srcValue.value->getSource()) + mContext->getDiagnostics()->error(DiagMessage(srcValue->value->getSource()) << "resource '" << resourceName << "' has a conflicting value for " << "configuration (" - << srcValue.config << ")"); - mContext->getDiagnostics()->note(DiagMessage(iter->value->getSource()) + << srcValue->config << ")"); + mContext->getDiagnostics()->note(DiagMessage(dstValue->value->getSource()) << "originally defined here"); error = true; continue; @@ -227,16 +225,18 @@ bool TableMerger::doMerge(const Source& src, continue; } - } else if (!stripConfig){ - // Insert a place holder value. We will fill it in below. - iter = dstEntry->values.insert(iter, ResourceConfigValue{ srcValue.config }); } if (stripConfig) { continue; } - if (FileReference* f = valueCast<FileReference>(srcValue.value.get())) { + if (!dstValue) { + // Force create the entry if we didn't have it. + dstValue = dstEntry->findOrCreateValue(srcValue->config, srcValue->product); + } + + if (FileReference* f = valueCast<FileReference>(srcValue->value.get())) { std::unique_ptr<FileReference> newFileRef; if (manglePackage) { newFileRef = cloneAndMangleFile(srcPackage->name, *f); @@ -246,15 +246,15 @@ bool TableMerger::doMerge(const Source& src, } if (callback) { - if (!callback(resName, iter->config, newFileRef.get(), f)) { + if (!callback(resName, srcValue->config, newFileRef.get(), f)) { error = true; continue; } } - iter->value = std::move(newFileRef); + dstValue->value = std::move(newFileRef); } else { - iter->value = std::unique_ptr<Value>(srcValue.value->clone( + dstValue->value = std::unique_ptr<Value>(srcValue->value->clone( &mMasterTable->stringPool)); } } @@ -290,7 +290,8 @@ bool TableMerger::mergeFileImpl(const ResourceFile& fileDesc, io::IFile* file, b ResourceTablePackage* pkg = table.createPackage(fileDesc.name.package, 0x0); pkg->findOrCreateType(fileDesc.name.type) ->findOrCreateEntry(fileDesc.name.entry) - ->values.push_back(ResourceConfigValue{ fileDesc.config, std::move(fileRef) }); + ->findOrCreateValue(fileDesc.config, {}) + ->value = std::move(fileRef); auto callback = [&](const ResourceNameRef& name, const ConfigDescription& config, FileReference* newFile, FileReference* oldFile) -> bool { diff --git a/tools/aapt2/process/SymbolTable.cpp b/tools/aapt2/process/SymbolTable.cpp index 6ad2f9c10d22..b6030a2874a3 100644 --- a/tools/aapt2/process/SymbolTable.cpp +++ b/tools/aapt2/process/SymbolTable.cpp @@ -17,9 +17,7 @@ #include "ConfigDescription.h" #include "Resource.h" #include "ValueVisitor.h" - #include "process/SymbolTable.h" -#include "util/Comparators.h" #include "util/Util.h" #include <androidfw/AssetManager.h> @@ -55,12 +53,10 @@ const ISymbolTable::Symbol* SymbolTableWrapper::findByName(const ResourceName& n if (name.type == ResourceType::kAttr || name.type == ResourceType::kAttrPrivate) { const ConfigDescription kDefaultConfig; - auto iter = std::lower_bound(sr.entry->values.begin(), sr.entry->values.end(), - kDefaultConfig, cmp::lessThanConfig); - - if (iter != sr.entry->values.end() && iter->config == kDefaultConfig) { + ResourceConfigValue* configValue = sr.entry->findValue(kDefaultConfig); + if (configValue) { // This resource has an Attribute. - if (Attribute* attr = valueCast<Attribute>(iter->value.get())) { + if (Attribute* attr = valueCast<Attribute>(configValue->value.get())) { symbol->attribute = util::make_unique<Attribute>(*attr); } else { return {}; diff --git a/tools/aapt2/proto/TableProtoDeserializer.cpp b/tools/aapt2/proto/TableProtoDeserializer.cpp index 1310aa6774bb..9856a00d7f67 100644 --- a/tools/aapt2/proto/TableProtoDeserializer.cpp +++ b/tools/aapt2/proto/TableProtoDeserializer.cpp @@ -19,7 +19,6 @@ #include "ValueVisitor.h" #include "proto/ProtoHelpers.h" #include "proto/ProtoSerialize.h" -#include "util/Comparators.h" #include <androidfw/ResourceTypes.h> @@ -134,21 +133,19 @@ public: return {}; } - auto iter = std::lower_bound(entry->values.begin(), entry->values.end(), - config, cmp::lessThanConfig); - if (iter != entry->values.end() && iter->config == config) { + ResourceConfigValue* configValue = entry->findOrCreateValue(config, + pbConfig.product()); + if (configValue->value) { // Duplicate config. mDiag->error(DiagMessage(mSource) << "duplicate configuration"); return {}; } - std::unique_ptr<Value> value = deserializeValueFromPb(pbConfigValue.value(), - config, - &table->stringPool); - if (!value) { + configValue->value = deserializeValueFromPb(pbConfigValue.value(), + config, &table->stringPool); + if (!configValue->value) { return {}; } - entry->values.insert(iter, ResourceConfigValue{ config, std::move(value) }); } } } diff --git a/tools/aapt2/proto/TableProtoSerializer.cpp b/tools/aapt2/proto/TableProtoSerializer.cpp index 4a2176d20db9..b3d87d805c6d 100644 --- a/tools/aapt2/proto/TableProtoSerializer.cpp +++ b/tools/aapt2/proto/TableProtoSerializer.cpp @@ -111,7 +111,7 @@ public: serializeReferenceToPb(entry.key, pbEntry->mutable_key()); pb::Item* pbItem = pbEntry->mutable_item(); - serializeItemCommonToPb(*entry.value, pbEntry); + serializeItemCommonToPb(entry.key, pbEntry); PbSerializerVisitor subVisitor(mSourcePool, mSymbolPool, pbItem); entry.value->accept(&subVisitor); } @@ -242,21 +242,24 @@ std::unique_ptr<pb::ResourceTable> serializeTableToPb(ResourceTable* table) { for (auto& configValue : entry->values) { pb::ConfigValue* pbConfigValue = pbEntry->add_config_values(); - serializeConfig(configValue.config, pbConfigValue->mutable_config()); + serializeConfig(configValue->config, pbConfigValue->mutable_config()); + if (!configValue->product.empty()) { + pbConfigValue->mutable_config()->set_product(configValue->product); + } pb::Value* pbValue = pbConfigValue->mutable_value(); - serializeSourceToPb(configValue.value->getSource(), &sourcePool, + serializeSourceToPb(configValue->value->getSource(), &sourcePool, pbValue->mutable_source()); - if (!configValue.value->getComment().empty()) { - pbValue->set_comment(util::utf16ToUtf8(configValue.value->getComment())); + if (!configValue->value->getComment().empty()) { + pbValue->set_comment(util::utf16ToUtf8(configValue->value->getComment())); } - if (configValue.value->isWeak()) { + if (configValue->value->isWeak()) { pbValue->set_weak(true); } PbSerializerVisitor visitor(&sourcePool, &symbolPool, pbValue); - configValue.value->accept(&visitor); + configValue->value->accept(&visitor); } } } diff --git a/tools/aapt2/proto/TableProtoSerializer_test.cpp b/tools/aapt2/proto/TableProtoSerializer_test.cpp index 1061b8f76f57..70a33f795f87 100644 --- a/tools/aapt2/proto/TableProtoSerializer_test.cpp +++ b/tools/aapt2/proto/TableProtoSerializer_test.cpp @@ -49,9 +49,19 @@ TEST(TableProtoSerializer, SerializeSinglePackage) { std::unique_ptr<Plural> plural = util::make_unique<Plural>(); plural->values[Plural::One] = util::make_unique<String>(table->stringPool.makeRef(u"one")); ASSERT_TRUE(table->addResource(test::parseNameOrDie(u"@com.app.a:plurals/hey"), - ConfigDescription{}, std::move(plural), + ConfigDescription{}, std::string(), std::move(plural), context->getDiagnostics())); + // Make a resource with different products. + ASSERT_TRUE(table->addResource(test::parseNameOrDie(u"@com.app.a:integer/one"), + test::parseConfigOrDie("land"), std::string(), + test::buildPrimitive(android::Res_value::TYPE_INT_DEC, 123u), + context->getDiagnostics())); + ASSERT_TRUE(table->addResource(test::parseNameOrDie(u"@com.app.a:integer/one"), + test::parseConfigOrDie("land"), std::string("tablet"), + test::buildPrimitive(android::Res_value::TYPE_INT_DEC, 321u), + context->getDiagnostics())); + std::unique_ptr<pb::ResourceTable> pbTable = serializeTableToPb(table.get()); ASSERT_NE(nullptr, pbTable); @@ -69,6 +79,17 @@ TEST(TableProtoSerializer, SerializeSinglePackage) { AAPT_ASSERT_TRUE(result); EXPECT_EQ(SymbolState::kPublic, result.value().type->symbolStatus.state); EXPECT_EQ(SymbolState::kPublic, result.value().entry->symbolStatus.state); + + // Find the product-dependent values + BinaryPrimitive* prim = test::getValueForConfigAndProduct<BinaryPrimitive>( + newTable.get(), u"@com.app.a:integer/one", test::parseConfigOrDie("land"), ""); + ASSERT_NE(nullptr, prim); + EXPECT_EQ(123u, prim->value.data); + + prim = test::getValueForConfigAndProduct<BinaryPrimitive>( + newTable.get(), u"@com.app.a:integer/one", test::parseConfigOrDie("land"), "tablet"); + ASSERT_NE(nullptr, prim); + EXPECT_EQ(321u, prim->value.data); } TEST(TableProtoSerializer, SerializeFileHeader) { diff --git a/tools/aapt2/test/Builders.h b/tools/aapt2/test/Builders.h index 579a46ec230f..834caf8b9a49 100644 --- a/tools/aapt2/test/Builders.h +++ b/tools/aapt2/test/Builders.h @@ -104,8 +104,8 @@ public: const ConfigDescription& config, std::unique_ptr<Value> value) { ResourceName resName = parseNameOrDie(name); - bool result = mTable->addResourceAllowMangled(resName, id, config, std::move(value), - &mDiagnostics); + bool result = mTable->addResourceAllowMangled(resName, id, config, std::string(), + std::move(value), &mDiagnostics); assert(result); return *this; } @@ -132,6 +132,14 @@ inline std::unique_ptr<Reference> buildReference(const StringPiece16& ref, return reference; } +inline std::unique_ptr<BinaryPrimitive> buildPrimitive(uint8_t type, uint32_t data) { + android::Res_value value = {}; + value.size = sizeof(value); + value.dataType = type; + value.data = data; + return util::make_unique<BinaryPrimitive>(value); +} + template <typename T> class ValueBuilder { private: diff --git a/tools/aapt2/test/Common.h b/tools/aapt2/test/Common.h index 51e2dd44e521..348c32a04e88 100644 --- a/tools/aapt2/test/Common.h +++ b/tools/aapt2/test/Common.h @@ -52,6 +52,11 @@ struct DummyDiagnosticsImpl : public IDiagnostics { void note(const DiagMessage& message) override {} }; +inline IDiagnostics* getDiagnostics() { + static DummyDiagnosticsImpl diag; + return &diag; +} + inline ResourceName parseNameOrDie(const StringPiece16& str) { ResourceNameRef ref; bool result = ResourceUtils::tryParseReference(str, &ref); @@ -66,23 +71,25 @@ inline ConfigDescription parseConfigOrDie(const StringPiece& str) { return config; } -template <typename T> T* getValueForConfig(ResourceTable* table, const StringPiece16& resName, - const ConfigDescription& config) { +template <typename T> T* getValueForConfigAndProduct(ResourceTable* table, + const StringPiece16& resName, + const ConfigDescription& config, + const StringPiece& product) { Maybe<ResourceTable::SearchResult> result = table->findResource(parseNameOrDie(resName)); if (result) { - ResourceEntry* entry = result.value().entry; - auto iter = std::lower_bound(entry->values.begin(), entry->values.end(), config, - [](const ResourceConfigValue& a, const ConfigDescription& b) - -> bool { - return a.config < b; - }); - if (iter != entry->values.end() && iter->config == config) { - return valueCast<T>(iter->value.get()); + ResourceConfigValue* configValue = result.value().entry->findValue(config, product); + if (configValue) { + return valueCast<T>(configValue->value.get()); } } return nullptr; } +template <typename T> T* getValueForConfig(ResourceTable* table, const StringPiece16& resName, + const ConfigDescription& config) { + return getValueForConfigAndProduct<T>(table, resName, config, {}); +} + template <typename T> T* getValue(ResourceTable* table, const StringPiece16& resName) { return getValueForConfig<T>(table, resName, {}); } diff --git a/tools/aapt2/unflatten/BinaryResourceParser.cpp b/tools/aapt2/unflatten/BinaryResourceParser.cpp index 341770360068..33b505ed2eb4 100644 --- a/tools/aapt2/unflatten/BinaryResourceParser.cpp +++ b/tools/aapt2/unflatten/BinaryResourceParser.cpp @@ -352,7 +352,7 @@ bool BinaryResourceParser::parseType(const ResourceTablePackage* package, return false; } - if (!mTable->addResourceAllowMangled(name, config, std::move(resourceValue), + if (!mTable->addResourceAllowMangled(name, config, {}, std::move(resourceValue), mContext->getDiagnostics())) { return false; } diff --git a/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java index c7b24bcb352d..b6588b65b5ae 100644 --- a/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java @@ -288,8 +288,10 @@ public class FontFamily_Delegate { } @LayoutlibDelegate - /*package*/ static boolean nAddFontWeightStyle(long nativeFamily, final String path, + /*package*/ static boolean nAddFontWeightStyle(long nativeFamily, + final String path, final int index, final List<FontListParser.Axis> axes, final int weight, final boolean isItalic) { + // 'index' and 'axes' are not supported by java.awt.Font final FontFamily_Delegate delegate = getDelegate(nativeFamily); if (delegate != null) { if (sFontLocation == null) { diff --git a/tools/layoutlib/bridge/src/android/graphics/Path_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Path_Delegate.java index d0dd22f8faad..a10ac00fc356 100644 --- a/tools/layoutlib/bridge/src/android/graphics/Path_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/Path_Delegate.java @@ -388,21 +388,18 @@ public final class Path_Delegate { @LayoutlibDelegate /*package*/ static void native_addRoundRect(long nPath, float left, float top, float right, float bottom, float[] radii, int dir) { - // Java2D doesn't support different rounded corners in each corner, so just use the - // first value. - native_addRoundRect(nPath, left, top, right, bottom, radii[0], radii[1], dir); - - // there can be a case where this API is used but with similar values for all corners, so - // in that case we don't warn. - // we only care if 2 corners are different so just compare to the next one. - for (int i = 0 ; i < 3 ; i++) { - if (radii[i * 2] != radii[(i + 1) * 2] || radii[i * 2 + 1] != radii[(i + 1) * 2 + 1]) { - Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, - "Different corner sizes are not supported in Path.addRoundRect.", - null, null /*data*/); - break; - } + + Path_Delegate pathDelegate = sManager.getDelegate(nPath); + if (pathDelegate == null) { + return; + } + + float[] cornerDimensions = new float[radii.length]; + for (int i = 0; i < radii.length; i++) { + cornerDimensions[i] = 2 * radii[i]; } + pathDelegate.mPath.append(new RoundRectangle(left, top, right - left, bottom - top, + cornerDimensions), false); } @LayoutlibDelegate diff --git a/tools/layoutlib/bridge/src/android/graphics/RoundRectangle.java b/tools/layoutlib/bridge/src/android/graphics/RoundRectangle.java new file mode 100644 index 000000000000..edd36e54aa77 --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/RoundRectangle.java @@ -0,0 +1,370 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.graphics; + +import java.awt.geom.AffineTransform; +import java.awt.geom.PathIterator; +import java.awt.geom.Rectangle2D; +import java.awt.geom.RectangularShape; +import java.awt.geom.RoundRectangle2D; +import java.util.EnumSet; +import java.util.NoSuchElementException; + +/** + * Defines a rectangle with rounded corners, where the sizes of the corners + * are potentially different. + */ +public class RoundRectangle extends RectangularShape { + public double x; + public double y; + public double width; + public double height; + public double ulWidth; + public double ulHeight; + public double urWidth; + public double urHeight; + public double lrWidth; + public double lrHeight; + public double llWidth; + public double llHeight; + + private enum Zone { + CLOSE_OUTSIDE, + CLOSE_INSIDE, + MIDDLE, + FAR_INSIDE, + FAR_OUTSIDE + } + + private final EnumSet<Zone> close = EnumSet.of(Zone.CLOSE_OUTSIDE, Zone.CLOSE_INSIDE); + private final EnumSet<Zone> far = EnumSet.of(Zone.FAR_OUTSIDE, Zone.FAR_INSIDE); + + /** + * @param cornerDimensions array of 8 floating-point number corresponding to the width and + * the height of each corner in the following order: upper-left, upper-right, lower-right, + * lower-left. It assumes for the size the same convention as {@link RoundRectangle2D}, that + * is that the width and height of a corner correspond to the total width and height of the + * ellipse that corner is a quarter of. + */ + public RoundRectangle(float x, float y, float width, float height, float[] cornerDimensions) { + if (cornerDimensions.length != 8) { + throw new IllegalArgumentException("The array of corner dimensions must have eight " + + "elements"); + } + + this.x = x; + this.y = y; + this.width = width; + this.height = height; + + float[] dimensions = cornerDimensions.clone(); + // If a value is negative, the corresponding corner is squared + for (int i = 0; i < dimensions.length; i += 2) { + if (dimensions[i] < 0 || dimensions[i + 1] < 0) { + dimensions[i] = 0; + dimensions[i + 1] = 0; + } + } + + double topCornerWidth = (dimensions[0] + dimensions[2]) / 2d; + double bottomCornerWidth = (dimensions[4] + dimensions[6]) / 2d; + double leftCornerHeight = (dimensions[1] + dimensions[7]) / 2d; + double rightCornerHeight = (dimensions[3] + dimensions[5]) / 2d; + + // Rescale the corner dimensions if they are bigger than the rectangle + double scale = Math.min(1.0, width / topCornerWidth); + scale = Math.min(scale, width / bottomCornerWidth); + scale = Math.min(scale, height / leftCornerHeight); + scale = Math.min(scale, height / rightCornerHeight); + + this.ulWidth = dimensions[0] * scale; + this.ulHeight = dimensions[1] * scale; + this.urWidth = dimensions[2] * scale; + this.urHeight = dimensions[3] * scale; + this.lrWidth = dimensions[4] * scale; + this.lrHeight = dimensions[5] * scale; + this.llWidth = dimensions[6] * scale; + this.llHeight = dimensions[7] * scale; + } + + @Override + public double getX() { + return x; + } + + @Override + public double getY() { + return y; + } + + @Override + public double getWidth() { + return width; + } + + @Override + public double getHeight() { + return height; + } + + @Override + public boolean isEmpty() { + return (width <= 0d) || (height <= 0d); + } + + @Override + public void setFrame(double x, double y, double w, double h) { + this.x = x; + this.y = y; + this.width = w; + this.height = h; + } + + @Override + public Rectangle2D getBounds2D() { + return new Rectangle2D.Double(x, y, width, height); + } + + @Override + public boolean contains(double x, double y) { + if (isEmpty()) { + return false; + } + + double x0 = getX(); + double y0 = getY(); + double x1 = x0 + getWidth(); + double y1 = y0 + getHeight(); + // Check for trivial rejection - point is outside bounding rectangle + if (x < x0 || y < y0 || x >= x1 || y >= y1) { + return false; + } + + double insideTopX0 = x0 + ulWidth / 2d; + double insideLeftY0 = y0 + ulHeight / 2d; + if (x < insideTopX0 && y < insideLeftY0) { + // In the upper-left corner + return isInsideCorner(x - insideTopX0, y - insideLeftY0, ulWidth / 2d, ulHeight / 2d); + } + + double insideTopX1 = x1 - urWidth / 2d; + double insideRightY0 = y0 + urHeight / 2d; + if (x > insideTopX1 && y < insideRightY0) { + // In the upper-right corner + return isInsideCorner(x - insideTopX1, y - insideRightY0, urWidth / 2d, urHeight / 2d); + } + + double insideBottomX1 = x1 - lrWidth / 2d; + double insideRightY1 = y1 - lrHeight / 2d; + if (x > insideBottomX1 && y > insideRightY1) { + // In the lower-right corner + return isInsideCorner(x - insideBottomX1, y - insideRightY1, lrWidth / 2d, + lrHeight / 2d); + } + + double insideBottomX0 = x0 + llWidth / 2d; + double insideLeftY1 = y1 - llHeight / 2d; + if (x < insideBottomX0 && y > insideLeftY1) { + // In the lower-left corner + return isInsideCorner(x - insideBottomX0, y - insideLeftY1, llWidth / 2d, + llHeight / 2d); + } + + // In the central part of the rectangle + return true; + } + + private boolean isInsideCorner(double x, double y, double width, double height) { + double squareDist = height * height * x * x + width * width * y * y; + return squareDist <= width * width * height * height; + } + + private Zone classify(double coord, double side1, double arcSize1, double side2, + double arcSize2) { + if (coord < side1) { + return Zone.CLOSE_OUTSIDE; + } else if (coord < side1 + arcSize1) { + return Zone.CLOSE_INSIDE; + } else if (coord < side2 - arcSize2) { + return Zone.MIDDLE; + } else if (coord < side2) { + return Zone.FAR_INSIDE; + } else { + return Zone.FAR_OUTSIDE; + } + } + + public boolean intersects(double x, double y, double w, double h) { + if (isEmpty() || w <= 0 || h <= 0) { + return false; + } + double x0 = getX(); + double y0 = getY(); + double x1 = x0 + getWidth(); + double y1 = y0 + getHeight(); + // Check for trivial rejection - bounding rectangles do not intersect + if (x + w <= x0 || x >= x1 || y + h <= y0 || y >= y1) { + return false; + } + + double maxLeftCornerWidth = Math.max(ulWidth, llWidth) / 2d; + double maxRightCornerWidth = Math.max(urWidth, lrWidth) / 2d; + double maxUpperCornerHeight = Math.max(ulHeight, urHeight) / 2d; + double maxLowerCornerHeight = Math.max(llHeight, lrHeight) / 2d; + Zone x0class = classify(x, x0, maxLeftCornerWidth, x1, maxRightCornerWidth); + Zone x1class = classify(x + w, x0, maxLeftCornerWidth, x1, maxRightCornerWidth); + Zone y0class = classify(y, y0, maxUpperCornerHeight, y1, maxLowerCornerHeight); + Zone y1class = classify(y + h, y0, maxUpperCornerHeight, y1, maxLowerCornerHeight); + + // Trivially accept if any point is inside inner rectangle + if (x0class == Zone.MIDDLE || x1class == Zone.MIDDLE || y0class == Zone.MIDDLE || y1class == Zone.MIDDLE) { + return true; + } + // Trivially accept if either edge spans inner rectangle + if ((close.contains(x0class) && far.contains(x1class)) || (close.contains(y0class) && + far.contains(y1class))) { + return true; + } + + // Since neither edge spans the center, then one of the corners + // must be in one of the rounded edges. We detect this case if + // a [xy]0class is 3 or a [xy]1class is 1. One of those two cases + // must be true for each direction. + // We now find a "nearest point" to test for being inside a rounded + // corner. + if (x1class == Zone.CLOSE_INSIDE && y1class == Zone.CLOSE_INSIDE) { + // Potentially in upper-left corner + x = x + w - x0 - ulWidth / 2d; + y = y + h - y0 - ulHeight / 2d; + return x > 0 || y > 0 || isInsideCorner(x, y, ulWidth / 2d, ulHeight / 2d); + } + if (x1class == Zone.CLOSE_INSIDE) { + // Potentially in lower-left corner + x = x + w - x0 - llWidth / 2d; + y = y - y1 + llHeight / 2d; + return x > 0 || y < 0 || isInsideCorner(x, y, llWidth / 2d, llHeight / 2d); + } + if (y1class == Zone.CLOSE_INSIDE) { + //Potentially in the upper-right corner + x = x - x1 + urWidth / 2d; + y = y + h - y0 - urHeight / 2d; + return x < 0 || y > 0 || isInsideCorner(x, y, urWidth / 2d, urHeight / 2d); + } + // Potentially in the lower-right corner + x = x - x1 + lrWidth / 2d; + y = y - y1 + lrHeight / 2d; + return x < 0 || y < 0 || isInsideCorner(x, y, lrWidth / 2d, lrHeight / 2d); + } + + @Override + public boolean contains(double x, double y, double w, double h) { + if (isEmpty() || w <= 0 || h <= 0) { + return false; + } + return (contains(x, y) && + contains(x + w, y) && + contains(x, y + h) && + contains(x + w, y + h)); + } + + @Override + public PathIterator getPathIterator(final AffineTransform at) { + return new PathIterator() { + int index; + + // ArcIterator.btan(Math.PI/2) + public static final double CtrlVal = 0.5522847498307933; + private final double ncv = 1.0 - CtrlVal; + + // Coordinates of control points for Bezier curves approximating the straight lines + // and corners of the rounded rectangle. + private final double[][] ctrlpts = { + {0.0, 0.0, 0.0, ulHeight}, + {0.0, 0.0, 1.0, -llHeight}, + {0.0, 0.0, 1.0, -llHeight * ncv, 0.0, ncv * llWidth, 1.0, 0.0, 0.0, llWidth, + 1.0, 0.0}, + {1.0, -lrWidth, 1.0, 0.0}, + {1.0, -lrWidth * ncv, 1.0, 0.0, 1.0, 0.0, 1.0, -lrHeight * ncv, 1.0, 0.0, 1.0, + -lrHeight}, + {1.0, 0.0, 0.0, urHeight}, + {1.0, 0.0, 0.0, ncv * urHeight, 1.0, -urWidth * ncv, 0.0, 0.0, 1.0, -urWidth, + 0.0, 0.0}, + {0.0, ulWidth, 0.0, 0.0}, + {0.0, ncv * ulWidth, 0.0, 0.0, 0.0, 0.0, 0.0, ncv * ulHeight, 0.0, 0.0, 0.0, + ulHeight}, + {} + }; + private final int[] types = { + SEG_MOVETO, + SEG_LINETO, SEG_CUBICTO, + SEG_LINETO, SEG_CUBICTO, + SEG_LINETO, SEG_CUBICTO, + SEG_LINETO, SEG_CUBICTO, + SEG_CLOSE, + }; + + @Override + public int getWindingRule() { + return WIND_NON_ZERO; + } + + @Override + public boolean isDone() { + return index >= ctrlpts.length; + } + + @Override + public void next() { + index++; + } + + @Override + public int currentSegment(float[] coords) { + if (isDone()) { + throw new NoSuchElementException("roundrect iterator out of bounds"); + } + int nc = 0; + double ctrls[] = ctrlpts[index]; + for (int i = 0; i < ctrls.length; i += 4) { + coords[nc++] = (float) (x + ctrls[i] * width + ctrls[i + 1] / 2d); + coords[nc++] = (float) (y + ctrls[i + 2] * height + ctrls[i + 3] / 2d); + } + if (at != null) { + at.transform(coords, 0, coords, 0, nc / 2); + } + return types[index]; + } + + @Override + public int currentSegment(double[] coords) { + if (isDone()) { + throw new NoSuchElementException("roundrect iterator out of bounds"); + } + int nc = 0; + double ctrls[] = ctrlpts[index]; + for (int i = 0; i < ctrls.length; i += 4) { + coords[nc++] = x + ctrls[i] * width + ctrls[i + 1] / 2d; + coords[nc++] = y + ctrls[i + 2] * height + ctrls[i + 3] / 2d; + } + if (at != null) { + at.transform(coords, 0, coords, 0, nc / 2); + } + return types[index]; + } + }; + } +} diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java index 7b8e29a03d86..fe05b0e91e83 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java @@ -236,4 +236,9 @@ public final class BridgeWindowSession implements IWindowSession { public void prepareToReplaceChildren(IBinder appToken) { // pass for now. } + + @Override + public void updatePointerIcon(IWindow window) { + // pass for now. + } } diff --git a/wifi/java/android/net/wifi/RttManager.java b/wifi/java/android/net/wifi/RttManager.java index 9137d9d90439..7f1ae2471a8d 100644 --- a/wifi/java/android/net/wifi/RttManager.java +++ b/wifi/java/android/net/wifi/RttManager.java @@ -23,7 +23,7 @@ import java.util.concurrent.CountDownLatch; @SystemApi public class RttManager { - private static final boolean DBG = true; + private static final boolean DBG = false; private static final String TAG = "RttManager"; /** @deprecated It is Not supported anymore. */ diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java index 2ad3c2e16e21..4921073cce6c 100644 --- a/wifi/java/android/net/wifi/WifiManager.java +++ b/wifi/java/android/net/wifi/WifiManager.java @@ -44,6 +44,7 @@ import com.android.internal.util.AsyncChannel; import com.android.internal.util.Protocol; import java.net.InetAddress; +import java.util.ArrayList; import java.util.List; import java.util.concurrent.CountDownLatch; @@ -892,6 +893,24 @@ public class WifiManager { } /** + * Sets whether or not the given network is metered from a network policy + * point of view. A network should be classified as metered when the user is + * sensitive to heavy data usage on that connection due to monetary costs, + * data limitations or battery/performance issues. A typical example would + * be a wifi connection where the user was being charged for usage. + * @param netId the integer that identifies the network configuration + * to the supplicant. + * @param isMetered True to mark the network as metered. + * @return {@code true} if the operation succeeded. + * @hide + */ + @SystemApi + public boolean setMetered(int netId, boolean isMetered) { + // TODO(jjoslin): Implement + return false; + } + + /** * Remove the specified network from the list of configured networks. * This may result in the asynchronous delivery of state change * events. @@ -1301,13 +1320,15 @@ public class WifiManager { * @return the list of access points found in the most recent scan. An app must hold * {@link android.Manifest.permission#ACCESS_COARSE_LOCATION ACCESS_COARSE_LOCATION} or * {@link android.Manifest.permission#ACCESS_FINE_LOCATION ACCESS_FINE_LOCATION} permission - * in order to get valid results. + * in order to get valid results. If there is a remote exception (e.g., either a communication + * problem with the system service or an exception within the framework) an empty list will be + * returned. */ public List<ScanResult> getScanResults() { try { return mService.getScanResults(mContext.getOpPackageName()); } catch (RemoteException e) { - return null; + return new ArrayList<ScanResult>(); } } diff --git a/wifi/java/android/net/wifi/nan/WifiNanEventListener.java b/wifi/java/android/net/wifi/nan/WifiNanEventListener.java index 5c18bd7e0f07..9e6ed4ee9634 100644 --- a/wifi/java/android/net/wifi/nan/WifiNanEventListener.java +++ b/wifi/java/android/net/wifi/nan/WifiNanEventListener.java @@ -36,7 +36,7 @@ import android.util.Log; */ public class WifiNanEventListener { private static final String TAG = "WifiNanEventListener"; - private static final boolean DBG = true; + private static final boolean DBG = false; private static final boolean VDBG = false; // STOPSHIP if true /** diff --git a/wifi/java/android/net/wifi/nan/WifiNanManager.java b/wifi/java/android/net/wifi/nan/WifiNanManager.java index cb82268ec195..667c4b1de6d0 100644 --- a/wifi/java/android/net/wifi/nan/WifiNanManager.java +++ b/wifi/java/android/net/wifi/nan/WifiNanManager.java @@ -38,7 +38,7 @@ import android.util.Log; */ public class WifiNanManager { private static final String TAG = "WifiNanManager"; - private static final boolean DBG = true; + private static final boolean DBG = false; private static final boolean VDBG = false; // STOPSHIP if true private IBinder mBinder; diff --git a/wifi/java/android/net/wifi/nan/WifiNanSession.java b/wifi/java/android/net/wifi/nan/WifiNanSession.java index d0a94109d0d8..bc1787fee478 100644 --- a/wifi/java/android/net/wifi/nan/WifiNanSession.java +++ b/wifi/java/android/net/wifi/nan/WifiNanSession.java @@ -27,7 +27,7 @@ import android.util.Log; */ public class WifiNanSession { private static final String TAG = "WifiNanSession"; - private static final boolean DBG = true; + private static final boolean DBG = false; private static final boolean VDBG = false; // STOPSHIP if true /** diff --git a/wifi/java/android/net/wifi/nan/WifiNanSessionListener.java b/wifi/java/android/net/wifi/nan/WifiNanSessionListener.java index 092508766570..b9af7def6868 100644 --- a/wifi/java/android/net/wifi/nan/WifiNanSessionListener.java +++ b/wifi/java/android/net/wifi/nan/WifiNanSessionListener.java @@ -43,7 +43,7 @@ import android.util.Log; */ public class WifiNanSessionListener { private static final String TAG = "WifiNanSessionListener"; - private static final boolean DBG = true; + private static final boolean DBG = false; private static final boolean VDBG = false; // STOPSHIP if true /** |